I saw that, but it was overkill for what I needed. I really just needed rain percent and rain inches for today and the next three days, to use with my irrigation app. So I had AI make a driver for that, but it does also pull in most current conditions as well.
I can paste it back into AI and have it add any additional attributes if needed.
/**
* Weather Underground PWS & Forecast Driver for Hubitat Elevation
* Licensed under the Apache License, Version 2.0
*/
metadata {
definition (
name: "Weather Underground PWS with Forecast",
namespace: "custom",
author: "AI Collaborator"
) {
capability "Sensor"
capability "Refresh"
capability "TemperatureMeasurement"
capability "RelativeHumidityMeasurement"
capability "PressureMeasurement"
attribute "weather", "string"
attribute "windSpeed", "number"
attribute "windDirection", "number"
attribute "windDirectionStr", "string"
attribute "windGust", "number"
attribute "solarRadiation", "number"
attribute "uvIndex", "number"
attribute "precipRate", "number"
attribute "precipTotal", "number"
attribute "dewPoint", "number"
attribute "heatIndex", "number"
attribute "windChill", "number"
attribute "forecastTodayMax", "number"
attribute "forecastTodayMin", "number"
attribute "forecastTodayNarrative", "string"
attribute "forecastTomorrowMax", "number"
attribute "forecastTomorrowMin", "number"
attribute "forecastTomorrowNarrative", "string"
attribute "precipChanceToday", "number"
attribute "precipChanceTomorrow", "number"
attribute "precipChanceDay2", "number"
attribute "precipChanceDay3", "number"
attribute "rainExpectedToday", "number"
attribute "rainExpectedTomorrow", "number"
attribute "rainExpectedDay2", "number"
attribute "rainExpectedDay3", "number"
attribute "lastUpdateTime", "string"
}
}
preferences {
input name: "apiKey", type: "text", title: "Weather Underground API Key", required: true
input name: "stationId", type: "text", title: "PWS Station ID", required: true
input name: "unitSystem", type: "enum", title: "Unit System",
options: ["e", "m"], defaultValue: "e", required: true
input name: "refreshInterval", type: "enum", title: "Poll Interval (Minutes)",
options: ["5", "10", "15", "30", "60"], defaultValue: "15", required: true
// CHANGED TO BOOL: Renders as a true/false toggle slider component natively
input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
}
def installed() {
log.info "WU PWS Driver Installed"
initialize()
}
def updated() {
log.info "WU PWS Driver Updated"
unschedule()
initialize()
}
def initialize() {
def cronMin = settings.refreshInterval ?: "15"
schedule("0 */" + cronMin + " * ? * *", refresh)
refresh()
}
def refresh() {
if (logEnable) log.debug "Weather Underground refresh initiated"
if (!settings.apiKey || !settings.stationId) {
log.warn "Missing configuration attributes"
return
}
def units = settings.unitSystem ?: "e"
def qMap = [stationId: settings.stationId, format: "json", units: units, apiKey: settings.apiKey]
// RESTORED: Fixed back to api.weather.com exactly as your working copy required
asynchttpGet("parseCurrentResponse", [uri: "https://api.weather.com", path: "/v2/pws/observations/current", query: qMap, timeout: 10])
}
def parseCurrentResponse(response, data) {
if (response.hasError() || response.status != 200) {
log.error "Current observations download failed with status code: ${response.status}"
return
}
try {
def json = parseJson(response.data)
if (logEnable) log.debug "Current Obs Response JSON correctly parsed"
def obs = json.observations.get(0)
def units = settings.unitSystem ?: "e"
def u = (units == "e") ? obs.imperial : obs.metric
device.sendEvent(name: "temperature", value: u.temp, unit: (units == "e" ? "°F" : "°C"))
device.sendEvent(name: "dewPoint", value: u.dewpt, unit: (units == "e" ? "°F" : "°C"))
device.sendEvent(name: "heatIndex", value: u.heatIndex, unit: (units == "e" ? "°F" : "°C"))
device.sendEvent(name: "windChill", value: u.windChill, unit: (units == "e" ? "°F" : "°C"))
device.sendEvent(name: "windSpeed", value: u.windSpeed, unit: (units == "e" ? "mph" : "km/h"))
device.sendEvent(name: "windGust", value: u.windGust, unit: (units == "e" ? "mph" : "km/h"))
device.sendEvent(name: "pressure", value: u.pressure, unit: (units == "e" ? "inHg" : "hPa"))
device.sendEvent(name: "precipRate", value: u.precipRate, unit: (units == "e" ? "in/hr" : "mm/hr"))
device.sendEvent(name: "precipTotal", value: u.precipTotal, unit: (units == "e" ? "in" : "mm"))
device.sendEvent(name: "humidity", value: obs.humidity, unit: "%")
device.sendEvent(name: "windDirection", value: obs.winddir, unit: "°")
device.sendEvent(name: "solarRadiation", value: obs.solarRadiation, unit: "w/m²")
device.sendEvent(name: "uvIndex", value: obs.uv)
def dirs = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S",
"SSW","SW","WSW","W","WNW","NW","NNW","N"]
def idx = (obs.winddir != null) ? Math.round(((obs.winddir.toDouble() % 360) / 22.5)).toInteger() : 0
device.sendEvent(name: "windDirectionStr", value: dirs.get(idx))
// ADDED ATTRIBUTE: Safely populates the lastUpdateTime field
def lTime = new Date().format("MM/dd/yyyy hh:mm:ss a", location.timeZone)
device.sendEvent(name: "lastUpdateTime", value: lTime)
// RESTORED: Fixed back to api.weather.com exactly as your working copy required
asynchttpGet("parseForecastResponse", [uri: "https://api.weather.com", path: "/v3/wx/forecast/daily/5day", query: [geocode: obs.lat.toString() + "," + obs.lon.toString(), format: "json", units: units, language: "en-US", apiKey: settings.apiKey], timeout: 10], [currentTemp: u.temp])
} catch (Exception e) {
log.error "Current Parse Error: " + e.message
}
}
def parseForecastResponse(response, data) {
if (response.hasError() || response.status != 200) {
log.error "Extended forecast download failed with status code: ${response.status}"
return
}
try {
def json = parseJson(response.data)
if (logEnable) log.debug "Forecast Response JSON correctly parsed"
def units = settings.unitSystem ?: "e"
def rainUnit = (units == "e" ? "in" : "mm")
def todayMax = json.temperatureMax.get(0)
if (todayMax == null && data != null) {
todayMax = data.currentTemp
}
device.sendEvent(name: "forecastTodayMax", value: todayMax)
device.sendEvent(name: "forecastTodayMin", value: json.temperatureMin.get(0))
device.sendEvent(name: "forecastTodayNarrative", value: json.narrative.get(0))
device.sendEvent(name: "forecastTomorrowMax", value: json.temperatureMax.get(1))
device.sendEvent(name: "forecastTomorrowMin", value: json.temperatureMin.get(1))
device.sendEvent(name: "forecastTomorrowNarrative", value: json.narrative.get(1))
def rToday = (json.qpf != null) ? json.qpf.get(0) : 0.0
def rTomorrow = (json.qpf != null) ? json.qpf.get(1) : 0.0
def rDay2 = (json.qpf != null) ? json.qpf.get(2) : 0.0
def rDay3 = (json.qpf != null) ? json.qpf.get(3) : 0.0
if (json.daypart != null && json.daypart.get(0) != null) {
def dp = json.daypart.get(0)
def names = dp.daypartName
def pToday = 0, pTomorrow = 0, pDay2 = 0, pDay3 = 0
def qpToday = 0.0, qpTomorrow = 0.0, qpDay2 = 0.0, qpDay3 = 0.0
for (int i = 0; i < names.size(); i++) {
def n = names.get(i)
if (n == null) continue
def pct = dp.precipChance.get(i) != null ? dp.precipChance.get(i).toInteger() : 0
def amt = dp.qpf.get(i) != null ? dp.qpf.get(i).toDouble() : 0.0
if (n.contains("Today") || n.contains("Tonight")) {
if (pct > pToday) pToday = pct
qpToday += amt
} else if (n.contains("Tomorrow")) {
if (pct > pTomorrow) pTomorrow = pct
qpTomorrow += amt
} else if (i == 4 || i == 5) {
if (pct > pDay2) pDay2 = pct
qpDay2 += amt
} else if (i == 6 || i == 7) {
if (pct > pDay3) pDay3 = pct
qpDay3 += amt
}
}
device.sendEvent(name: "precipChanceToday", value: pToday, unit: "%")
device.sendEvent(name: "precipChanceTomorrow", value: pTomorrow, unit: "%")
device.sendEvent(name: "precipChanceDay2", value: pDay2, unit: "%")
device.sendEvent(name: "precipChanceDay3", value: pDay3, unit: "%")
if (rToday == 0.0) rToday = qpToday
if (rTomorrow == 0.0) rTomorrow = qpTomorrow
if (rDay2 == 0.0) rDay2 = qpDay2
if (rDay3 == 0.0) rDay3 = qpDay3
def txt = dp.wxPhraseLong.get(0) ?: dp.wxPhraseLong.get(1)
if (txt != null) {
device.sendEvent(name: "weather", value: txt)
}
}
device.sendEvent(name: "rainExpectedToday", value: rToday, unit: rainUnit)
device.sendEvent(name: "rainExpectedTomorrow", value: rTomorrow, unit: rainUnit)
device.sendEvent(name: "rainExpectedDay2", value: rDay2, unit: rainUnit)
device.sendEvent(name: "rainExpectedDay3", value: rDay3, unit: rainUnit)
} catch (Exception e) {
log.error "Forecast Parse Error: " + e.message
}
}