Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
"type": "object",
"properties": {
"value": {
"type": "string"
"title": "PositiveNumber",
"type": "number",
"minimum": 0
},
"unit": {
"type": "string",
"enum": ["KPH", "MPH"],
"default": "KPH"
"enum": ["KPH", "MPH"]
}
},
"additionalProperties": false,
"required": [
"value",
"unit"
"value", "unit"
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ metadata {
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Ultraviolet Index"
//capability "Wind Speed" // Not in production yet
capability "stsmartweather.windSpeed" // "Wind Speed" only supports m/s unit, however we want to create both events
capability "Wind Speed"
capability "stsmartweather.windSpeed"
capability "stsmartweather.windDirection"
capability "stsmartweather.apparentTemperature"
capability "stsmartweather.astronomicalData"
Expand All @@ -34,29 +34,6 @@ metadata {
capability "stsmartweather.weatherSummary"
capability "Sensor"
capability "Refresh"

// While we have created a custom capability for these attributes,
// they will remain to support any custom DataManagement based SmartApps using them.
attribute "localSunrise", "string"
attribute "localSunset", "string"
attribute "city", "string"
attribute "timeZoneOffset", "string"
attribute "weather", "string"
attribute "wind", "string"
attribute "windVector", "string"
attribute "weatherIcon", "string"
attribute "forecastIcon", "string"
attribute "feelsLike", "string"
attribute "percentPrecip", "string"
attribute "alert", "string"
attribute "alertKeys", "string"
attribute "sunriseDate", "string"
attribute "sunsetDate", "string"
attribute "lastUpdate", "string"
attribute "uvDescription", "string"
attribute "forecastToday", "string"
attribute "forecastTonight", "string"
attribute "forecastTomorrow", "string"
}

preferences {
Expand Down Expand Up @@ -209,12 +186,18 @@ def parse(String description) {
}

def installed() {
schedulePoll()
poll()
runEvery30Minutes(poll)
}

def schedulePoll() {
unschedule()
runEvery3Hours("poll")
}

def updated() {
poll
schedulePoll()
poll()
}

def uninstalled() {
Expand Down Expand Up @@ -253,13 +236,16 @@ def pollUsingZipCode(String zipCode) {
send(name: "feelsLike", value: obs.temperatureFeelsLike, unit: tempUnits)

send(name: "humidity", value: obs.relativeHumidity, unit: "%")
send(name: "weather", value: obs.wxPhraseShort)
send(name: "weather", value: obs.wxPhraseLong)
send(name: "weatherIcon", value: obs.iconCode, displayed: false)

send(name: "wind", value: obs.windSpeed, unit: windUnits)
send(name: "windspeed", value: new BigDecimal(convertWindSpeed(obs.windSpeed, tempUnits == "F" ? "imperial" : "metric", "metric") / 3.6).setScale(2, BigDecimal.ROUND_HALF_UP), unit: "m/s")
send(name: "windVector", value: "${obs.windDirectionCardinal} ${obs.windSpeed} ${windUnits}")

log.trace "Getting location info"
def loc = getTwcLocation(zipCode).location
def cityValue = "${loc.city}, ${loc.adminDistrictCode} ${loc.countryCode}"
def loc = getTwcLocation(zipCode)?.location
def cityValue = createCityName(loc) ?: zipCode // I don't think we'll ever hit a point where we can't build a city name... But just in case...
if (cityValue != device.currentValue("city")) {
send(name: "city", value: cityValue, isStateChange: true)
}
Expand All @@ -275,7 +261,7 @@ def pollUsingZipCode(String zipCode) {
def sunsetDate = dtf.parse(obs.sunsetTimeLocal)

def tf = new java.text.SimpleDateFormat("h:mm a")
tf.setTimeZone(TimeZone.getTimeZone(loc.ianaTimeZone))
tf.setTimeZone(TimeZone.getTimeZone(loc?.ianaTimeZone))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If loc is null what will this code do? You added the null check but I'm wondering if we need to skip the whole line if loc is null.

def localSunrise = "${tf.format(sunriseDate)}"
def localSunset = "${tf.format(sunsetDate)}"
Expand All @@ -287,22 +273,26 @@ def pollUsingZipCode(String zipCode) {
// Forecast
def f = getTwcForecast(zipCode)
if (f) {
def icon = f.daypart[0].iconCode[0] ?: f.daypart[0].iconCode[1]
def precip = f.daypart[0].precipChance[0] ?: f.daypart[0].precipChance[1]
def icon = f.daypart[0].iconCode[0] != null ? f.daypart[0].iconCode[0] : f.daypart[0].iconCode[1]
def precip = f.daypart[0].precipChance[0] != null ? f.daypart[0].precipChance[0] : f.daypart[0].precipChance[1]
def narrative = f.daypart[0].narrative

send(name: "percentPrecip", value: precip, unit: "%")
send(name: "forecastIcon", value: icon, displayed: false)
send(name: "forecastToday", value: narrative[0] ?: "-")
send(name: "forecastTonight", value: narrative[1] ?: "-")
send(name: "forecastTomorrow", value: narrative[2] ?: "-")
}
else {
send(name: "forecastToday", value: narrative[0] ?: "n/a")
send(name: "forecastTonight", value: narrative[1] ?: "n/a")
send(name: "forecastTomorrow", value: narrative[2] ?: "n/a")
} else {
log.warn "Forecast not found"
send(name: "percentPrecip", value: 0, unit: "%", descriptionText: "Chance of precipitation could not be found")
send(name: "forecastIcon", value: "", displayed: false)
send(name: "forecastToday", value: "n/a", descriptionText: "Today's forecast could not be found")
send(name: "forecastTonight", value: "n/a", descriptionText: "Tonight's forecast could not be found")
send(name: "forecastTomorrow", value: "n/a", descriptionText: "Tomorrow's forecast could not be found")
}

// Alerts
def alerts = getTwcAlerts("${loc.latitude},${loc.longitude}")
def alerts = getTwcAlerts("${loc?.latitude},${loc?.longitude}")
if (alerts) {
alerts.each {alert ->
def msg = alert.headlineText
Expand All @@ -314,12 +304,10 @@ def pollUsingZipCode(String zipCode) {
}
send(name: "alert", value: msg, descriptionText: msg)
}
}
else {
} else {
send(name: "alert", value: "No current alerts", descriptionText: msg)
}
}
else {
} else {
log.warn "No response from TWC API"
}

Expand All @@ -339,33 +327,76 @@ def pollUsingPwsId(String stationId) {
if (obsWrapper && obsWrapper.observations && obsWrapper.observations.size()) {
def obs = obsWrapper.observations[0]
def dataScale = obs.imperial ? 'imperial' : 'metric'

send(name: "temperature", value: convertTemperature(obs[dataScale].temp, dataScale, tempUnits), unit: tempUnits)
send(name: "feelsLike", value: convertTemperature(obs[dataScale].windChill, dataScale, tempUnits), unit: tempUnits)

send(name: "humidity", value: obs.humidity, unit: "%")
send(name: "weather", value: "n/a")
send(name: "weatherIcon", value: null, displayed: false)
send(name: "wind", value: convertWindSpeed(obs[dataScale].windSpeed, dataScale, tempUnits), unit: windUnits)
send(name: "windVector", value: "${obs.winddir}° ${convertWindSpeed(obs[dataScale].windSpeed, dataScale, tempUnits)} ${windUnits}")
def cityValue = obs.neighborhood

def windSpeed = convertWindSpeed(obs[dataScale].windSpeed, dataScale, tempUnits)
send(name: "wind", value: windSpeed, unit: windUnits)
send(name: "windspeed", value: new BigDecimal(convertWindSpeed(obs[dataScale].windSpeed, dataScale, "metric") / 3.6).setScale(2, BigDecimal.ROUND_HALF_UP), unit: "m/s")
send(name: "windVector", value: "${obs.winddir}° ${windSpeed} ${windUnits}")

def loc = getTwcLocation("${obs.lat},${obs.lon}")?.location
def cityValue = createCityName(loc) ?: "${obs.neighborhood}, ${obs.country}"
if (cityValue != device.currentValue("city")) {
send(name: "city", value: cityValue, isStateChange: true)
}

send(name: "ultravioletIndex", value: obs.uv)
send(name: "uvDescription", value: "n/a")

send(name: "localSunrise", value: "n/a", descriptionText: "Sunrise is not supported when using PWS")
send(name: "localSunset", value: "n/a", descriptionText: "Sunset is not supported when using PWS")
send(name: "illuminance", value: null)
def cond = getTwcConditions("${obs.lat},${obs.lon}")
if (cond) {
send(name: "weather", value: cond.wxPhraseLong)
send(name: "weatherIcon", value: cond.iconCode, displayed: false)
send(name: "uvDescription", value: cond.uvDescription)

def dtf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
def sunriseDate = dtf.parse(cond.sunriseTimeLocal)
log.debug "'${cond.sunriseTimeLocal}'"

def sunsetDate = dtf.parse(cond.sunsetTimeLocal)
def tf = new java.text.SimpleDateFormat("h:mm a")
tf.setTimeZone(TimeZone.getTimeZone(loc?.ianaTimeZone))

def localSunrise = "${tf.format(sunriseDate)}"
def localSunset = "${tf.format(sunsetDate)}"
send(name: "localSunrise", value: localSunrise, descriptionText: "Sunrise today is at $localSunrise")
send(name: "localSunset", value: localSunset, descriptionText: "Sunset today at is $localSunset")

// Forecast not supported
send(name: "percentPrecip", value: "n/a", unit: "%")
send(name: "forecastIcon", value: null, displayed: false)
send(name: "forecastToday", value: "n/a")
send(name: "forecastTonight", value: "n/a")
send(name: "forecastTomorrow", value: "n/a")
log.warn "Forecast not supported when using PWS"
send(name: "illuminance", value: estimateLux(cond, sunriseDate, sunsetDate))
} else {
log.warn "Conditions not found"
send(name: "weather", value: "n/a", descriptionText: "Weather summary could not be found")
send(name: "weatherIcon", value: "", displayed: false)
send(name: "uvDescription", value: "n/a")

send(name: "localSunrise", value: "n/a", descriptionText: "Sunrise time could not be found")
send(name: "localSunset", value: "n/a", descriptionText: "Sunset time could not be found")
send(name: "illuminance", value: 0, descriptionText: "Illuminance could not be found")
}

// Forecast
def f = getTwcForecast("${obs.lat},${obs.lon}")
if (f) {
def icon = f.daypart[0].iconCode[0] != null ? f.daypart[0].iconCode[0] : f.daypart[0].iconCode[1]
def precip = f.daypart[0].precipChance[0] != null ? f.daypart[0].precipChance[0] : f.daypart[0].precipChance[1]
def narrative = f.daypart[0].narrative

send(name: "percentPrecip", value: precip, unit: "%")
send(name: "forecastIcon", value: icon, displayed: false)
send(name: "forecastToday", value: narrative[0] ?: "n/a")
send(name: "forecastTonight", value: narrative[1] ?: "n/a")
send(name: "forecastTomorrow", value: narrative[2] ?: "n/a")
} else {
log.warn "Forecast not found"
send(name: "percentPrecip", value: 0, unit: "%", descriptionText: "Chance of precipitation could not be found")
send(name: "forecastIcon", value: "", displayed: false)
send(name: "forecastToday", value: "n/a", descriptionText: "Today's forecast could not be found")
send(name: "forecastTonight", value: "n/a", descriptionText: "Tonight's forecast could not be found")
send(name: "forecastTomorrow", value: "n/a", descriptionText: "Tomorrow's forecast could not be found")
}

// Alerts
def alerts = getTwcAlerts("${obs.lat},${obs.lon}")
Expand All @@ -380,12 +411,10 @@ def pollUsingPwsId(String stationId) {
}
send(name: "alert", value: msg, descriptionText: msg)
}
}
else {
} else {
send(name: "alert", value: "No current alerts", descriptionText: msg)
}
}
else {
} else {
log.warn "No response from TWC API"
}

Expand Down Expand Up @@ -431,49 +460,16 @@ private localDate(timeZone) {
df.format(new Date())
}

// Create the new custom capability event if needed,
// but also send a legacy custom event for any DM-backed SmartApps using them.
private send(Map map) {
def eventConversion = [
"localSunrise": "stsmartweather.astronomicalData.localSunrise",
"localSunset": "stsmartweather.astronomicalData.localSunset",
"city": "stsmartweather.astronomicalData.city",
"timeZoneOffset": "stsmartweather.astronomicalData.timeZoneOffset",
"weather": "stsmartweather.weatherSummary.weather",
"wind": "stsmartweather.windSpeed.wind",
"windVector": "stsmartweather.windDirection.windVector",
"weatherIcon": "stsmartweather.weatherSummary.weatherIcon",
"forecastIcon": "stsmartweather.weatherForecast.forecastIcon",
"feelsLike": "stsmartweather.apparentTemperature.feelsLike",
"percentPrecip": "stsmartweather.precipitation.percentPrecip",
"alert": "stsmartweather.weatherAlert.alert",
"alertKeys": "stsmartweather.weatherAlert.alertKeys",
"sunriseDate": "stsmartweather.astronomicalData.sunriseDate",
"sunsetDate": "stsmartweather.astronomicalData.sunsetDate",
"lastUpdate": "stsmartweather.smartWeather.lastUpdate",
"uvDescription": "stsmartweather.ultravioletDescription.uvDescription",
"forecastToday": "stsmartweather.weatherForecast.forecastToday",
"forecastTonight": "stsmartweather.weatherForecast.forecastTonight",
"forecastTomorrow": "stsmartweather.weatherForecast.forecastTomorrow"
]

//log.trace "WUSTATION: event: $map"
sendEvent(map)
if (map.name && eventConversion.containsKey(map.name)) {
def newMap = map.clone()
newMap.name = eventConversion[map.name]

//log.trace "WUSTATION: NEW event: $newMap"
sendEvent(newMap)
}
}

private estimateLux(obs, sunriseDate, sunsetDate) {
def lux = 0
if (obs.dayOrNight == 'N') {
lux = 10
}
else {
} else {
//day
switch(obs.iconCode) {
case 4:
Expand All @@ -499,7 +495,7 @@ private estimateLux(obs, sunriseDate, sunsetDate) {
def beforeSunset = sunsetDate.time - now
def oneHour = 1000 * 60 * 60

if(afterSunrise < oneHour) {
if (afterSunrise < oneHour) {
//dawn
lux = (long)(lux * (afterSunrise/oneHour))
} else if (beforeSunset < oneHour) {
Expand Down Expand Up @@ -539,7 +535,25 @@ private convertWindSpeed(value, fromScale, toScale) {
return value
}
if (ts == 'imperial') {
return value * 1.608
return value / 1.609
}
return value * 1.609
}

private createCityName(location) {
def cityName = null

if (location) {
cityName = location.city + ", "

if (location.adminDistrictCode) {
cityName += location.adminDistrictCode
cityName += " "
cityName += location.countryCode ?: location.country
} else {
cityName += location.country
}
}
return value / 1.608

cityName
}