Hahaha 7 months later.
Here is cometfish’s original BOM weather device for anyone interested, updated for the current BOM site. As a result of the changes the ftp child device is also no longer needed and can be deleted.
/*
- BoMWeather driver - updated for current BoM web access
- Gets current Australian weather info (observations and forecasts)
- from the Bureau of Meteorology (BoM)
- Updated changes:
-
-
- Forecasts now use direct HTTPS retrieval instead of FTP/Telnet
-
- Forecast period selection updated so maximum temperature is found more reliably
*/
metadata {
definition(name: "BoM Weather", namespace: "community", author: "cometfish", importUrl: "") {
capability "Sensor"
capability "TemperatureMeasurement"
capability "Relative Humidity Measurement"
// observations
attribute "temperature", "number"
attribute "lastupdate", "date"
attribute "apparent_temperature", "number"
attribute "dew_point", "number"
attribute "humidity", "number"
attribute "windDirection", "number"
attribute "windSpeed", "number"
// forecast
attribute "area", "string"
attribute "iconCode", "number"
attribute "weatherIcon", "string"
attribute "forecastlastupdate", "date"
attribute "forecastnextupdate", "date"
attribute "weather", "string"
attribute "rainProbability", "number"
attribute "rainRange", "string"
attribute "forecastLow", "number"
attribute "forecastHigh", "number"
attribute "tile", "string"
command "poll"
command "refresh"
command "clearForecastNextUpdate"
command "updateTile"
}
}
preferences {
section("URIs") {
input "idv", "text", title: "Observation ID number (eg. IDV60901)", required: true
input "wmo", "text", title: "Observation WMO number for your local weather station (eg. 95936)", required: true
input "forecastidv", "text", title: "Forecast Precis ID number (eg. IDV10753)", required: true
input "aac", "text", title: "Forecast Precis AAC code for your local weather station (eg. VIC_PT042)", required: true
input "iconcustomurl", "text", title: "Icon custom url (Leave blank for default. Include trailing slash)", required: false
input "autoPoll", "bool", required: true, title: "Enable Auto Poll", defaultValue: false
input "pollInterval", "text", title: "Poll interval (which minutes of the hour to run on, eg. 5,35)", required: true, defaultValue: "5,35"
input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
}
}
def installed() {
if (logEnable) log.debug "installed"
}
def logsOff() {
log.warn "debug logging disabled..."
device.updateSetting("logEnable", [value: "false", type: "bool"])
}
def updated() {
log.info "updated..."
log.warn "debug logging is: ${logEnable == true}"
unschedule()
if (autoPoll) {
def pollIntervalCmd = settings?.pollInterval
Random rand = new Random(now())
def randomSeconds = rand.nextInt(60)
def sched = "${randomSeconds} ${pollIntervalCmd} * * * ?"
schedule("${sched}", "refresh")
}
}
def poll() {
refresh()
}
def clearForecastNextUpdate() {
sendEvent(name: "forecastnextupdate", value: new Date(), isStateChange: true)
}
def refresh() {
if (logEnable) log.debug "Refreshing data"
refreshForecast()
refreshObservations()
}
def refreshForecast() {
try {
def fnextupdate = device.currentValue("forecastnextupdate")
Date nextupdate = null
if (fnextupdate != null) {
try {
nextupdate = Date.parseToStringDate(fnextupdate)
} catch (Exception ignored) {
nextupdate = null
}
}
if (nextupdate == null || nextupdate < new Date()) {
def forecastUrl = "https://www.bom.gov.au/fwo/${settings.forecastidv}.xml"
if (logEnable) log.debug "Forecast URL: ${forecastUrl}"
httpGet([
uri: forecastUrl,
contentType: "text/plain",
requestContentType: "application/xml",
textParser: true,
timeout: 30,
headers: [
"User-Agent": "Mozilla/5.0"
]
]) { resp ->
if (resp?.status == 200) {
String xmlText = resp.data?.text ?: resp.data?.toString()
if (xmlText) {
readXMLData(xmlText)
} else {
log.warn "Forecast response was empty"
}
} else {
log.warn "Forecast fetch failed: HTTP ${resp?.status}"
}
}
} else {
if (logEnable) log.info "Forecast data still current, will not refresh until: ${nextupdate}"
def icontype = "day"
def nowdate = new Date()
if (location?.sunrise && location?.sunset) {
if (nowdate < location.sunrise || nowdate >= location.sunset) icontype = "night"
}
if (state.icontype != icontype) {
state.icontype = icontype
def iconCodeStr = device.currentValue("iconCode")?.toString()
if (iconCodeStr) {
def iconurl = settings.iconcustomurl
if (iconurl == null || iconurl == '') {
iconurl = "https://raw.githubusercontent.com/cometfish/hubitat_driver_bomweather/master/images/white/"
}
def wIcon = "${iconurl}${iconCodeStr}${icontype}.png"
sendEvent(name: "weatherIcon", value: wIcon, isStateChange: true)
def vals = [:]
vals.weatherIcon = device.currentValue("weatherIcon")
vals.weather = device.currentValue("weather")
vals.forecastHigh = device.currentValue("forecastHigh")
vals.temperature = device.currentValue("temperature")
vals.apparent_temperature = device.currentValue("apparent_temperature")
vals.windDirection = device.currentValue("windDirection")
vals.windSpeed = device.currentValue("windSpeed")
updateTileWithVals(vals)
}
}
}
} catch (Exception e) {
log.warn "Refresh call forecasts failed: ${e.message}"
}
}
def refreshObservations() {
try {
def url = "https://www.bom.gov.au/fwo/${settings.idv}/${settings.idv}.${settings.wmo}.json"
if (logEnable) log.debug "Observations URL: ${url}"
httpGet([
uri: url,
contentType: "application/json",
timeout: 30,
headers: [
"User-Agent": "Mozilla/5.0"
]
]) { resp ->
if (resp?.status == 200 && resp?.data?.observations?.data) {
def obs = resp.data.observations.data[0]
def date = Date.parse("yyyyMMddHHmmssX", obs.aifstime_utc + "Z")
sendEvent(name: "lastupdate", value: date, isStateChange: true)
sendEvent(name: "temperature", value: obs.air_temp, unit: "°C", isStateChange: true)
sendEvent(name: "apparent_temperature", value: obs.apparent_t, unit: "°C", isStateChange: true)
sendEvent(name: "dew_point", value: obs.dewpt, unit: "°C", isStateChange: true)
sendEvent(name: "humidity", value: obs.rel_hum, unit: "%", isStateChange: true)
def deg = windDirToDegrees(obs.wind_dir)
sendEvent(name: "windDirection", value: deg, unit: "°", isStateChange: true)
sendEvent(name: "windSpeed", value: obs.wind_spd_kmh, unit: "kmh", isStateChange: true)
def vals = [:]
vals.weatherIcon = device.currentValue("weatherIcon")
vals.weather = device.currentValue("weather")
vals.forecastHigh = device.currentValue("forecastHigh")
vals.temperature = obs.air_temp
vals.apparent_temperature = obs.apparent_t
vals.windDirection = deg
vals.windSpeed = obs.wind_spd_kmh
updateTileWithVals(vals)
} else {
log.warn "Observations fetch failed: HTTP ${resp?.status}"
}
}
} catch (Exception e) {
log.warn "Refresh observations failed: ${e.message}"
}
}
private Integer windDirToDegrees(String dir) {
Map dirs = [
"N": 0,
"NNE": 23,
"NE": 45,
"ENE": 68,
"E": 90,
"ESE": 113,
"SE": 135,
"SSE": 158,
"S": 180,
"SSW": 203,
"SW": 225,
"WSW": 248,
"W": 270,
"WNW": 293,
"NW": 315,
"NNW": 338,
"CALM": 0
]
return (dirs[dir ?: "CALM"] ?: 0) as Integer
}
private readXMLData(String xmlText) {
if (logEnable) log.debug "Parsing forecast XML"
def fcdata = parseXML(xmlText)
def date = Date.parse("yyyy-MM-dd'T'HH:mm:ssX", fcdata.amoc.'issue-time-utc'.text())
sendEvent(name: "forecastlastupdate", value: date, isStateChange: true)
date = Date.parse("yyyy-MM-dd'T'HH:mm:ssX", fcdata.amoc.'next-routine-issue-time-utc'.text())
sendEvent(name: "forecastnextupdate", value: date, isStateChange: true)
def local = fcdata.forecast.area.find { it.@aac == settings.aac }
if (!local) {
log.warn "No matching AAC found in forecast XML for ${settings.aac}"
return
}
sendEvent(name: "area", value: local.@description.toString(), isStateChange: true)
def today = local.'forecast-period'.find { period ->
period.'**'.find { it.@type == 'air_temperature_maximum' }
}
if (!today) {
today = local.'forecast-period'.find { it.@index == "1" } ?: local.'forecast-period'.find { it.@index == "0" }
}
if (!today) {
log.warn "No suitable forecast-period found"
return
}
if (logEnable) {
log.debug "Using forecast-period index=${today.@index}"
}
def el = today.'**'.find { it.@type == 'precis' }
def w = el?.text()
sendEvent(name: "weather", value: w, isStateChange: true)
el = today.'**'.find { it.@type == 'probability_of_precipitation' }
if (el?.text()) {
def rp = el.text().replace("%", "").trim()
if (rp?.isInteger()) {
sendEvent(name: "rainProbability", value: rp.toInteger(), unit: "%", isStateChange: true)
}
}
el = today.'**'.find { it.@type == 'precipitation_range' }
if (el?.text()) {
sendEvent(name: "rainRange", value: el.text(), isStateChange: true)
}
el = today.'**'.find { it.@type == 'air_temperature_minimum' }
if (el?.text()) {
sendEvent(name: "forecastLow", value: el.text(), unit: "°C", isStateChange: true)
}
el = today.'**'.find { it.@type == 'air_temperature_maximum' }
def fhigh = el?.text()
if (fhigh != null && fhigh != '') {
sendEvent(name: "forecastHigh", value: fhigh, unit: "°C", isStateChange: true)
} else {
log.warn "No air_temperature_maximum found in selected forecast period"
}
el = today.'**'.find { it.@type == 'forecast_icon_code' }
def iconCodeStr = el?.text()
if (iconCodeStr) {
sendEvent(name: "iconCode", value: iconCodeStr.toInteger(), isStateChange: true)
}
def icontype = "day"
def nowdate = new Date()
if (location?.sunrise && location?.sunset) {
if (nowdate < location.sunrise || nowdate >= location.sunset) icontype = "night"
}
state.icontype = icontype
def iconurl = settings.iconcustomurl
if (iconurl == null || iconurl == '') {
iconurl = "https://raw.githubusercontent.com/cometfish/hubitat_driver_bomweather/master/images/white/"
}
def wIcon = iconCodeStr ? "${iconurl}${iconCodeStr}${icontype}.png" : ""
if (wIcon) {
sendEvent(name: "weatherIcon", value: wIcon, isStateChange: true)
sendEvent(name: "tile", value: "<br /><img src=\"" + wIcon + "\" /><br />" + w, isStateChange: true)
}
def vals = [:]
vals.weatherIcon = wIcon ?: device.currentValue("weatherIcon")
vals.weather = w ?: device.currentValue("weather")
vals.forecastHigh = (fhigh != null && fhigh != '') ? fhigh : device.currentValue("forecastHigh")
vals.temperature = device.currentValue("temperature")
vals.apparent_temperature = device.currentValue("apparent_temperature")
vals.windDirection = device.currentValue("windDirection")
vals.windSpeed = device.currentValue("windSpeed")
updateTileWithVals(vals)
}
def updateTile() {
def vals = [:]
vals.weatherIcon = device.currentValue("weatherIcon")
vals.weather = device.currentValue("weather")
vals.forecastHigh = device.currentValue("forecastHigh")
vals.temperature = device.currentValue("temperature")
vals.apparent_temperature = device.currentValue("apparent_temperature")
vals.windDirection = device.currentValue("windDirection")
vals.windSpeed = device.currentValue("windSpeed")
updateTileWithVals(vals)
}
def updateTileWithVals(vals) {
def windArrow = ""
if ((vals.windSpeed ?: 0) != 0) {
windArrow = """↑ """
}
sendEvent(name: "tile", value: """
.wfc {display:inline-block;padding:0 8px;font-size:12px;}
span.v {font-size:20px;}