Automating the ERV Part 3
In Part 1 I leveraged the Z-Wave functionality I’d added to my ERV to create some basic automation for its operation. I implemented a SmartThings routine to begin high-speed ventilation on demand and, not liking the open-endedness of that, wrote a Smart App to shut the thing down again after some amount of time.
In Part 2 I built on this foundation and added some functionality that would run my ventilator for n minutes of every hour depending on outdoor conditions as determined by a temperature and humidity sensor.
Now it’s time to go one step further and implement a backup plan.
The Backup Plan
It might be the case that the values stored in state
become stale because the outdoor module stops working, in which case subscription events stop occurring and the handler method that updates the state
no longer executes. In this case I want to fall back to a Web weather API from Forecast.io. Since I’m storing a timestamp along with the other values in state
, I can figure out how fresh that reading is. Let’s say I want to fall back to the web if the state
values are more than 45 minutes old.
Forecast.io has a developer program that allows 1000 API hits a day, which is more than enough for private residential use. I’ll use this more down the line when I start putting together logic for my irrigation use case, but for today I’ll use Forecast.io’s current conditions data.
I will update the getCurrentDewPoint
method in my SmartApp to check the application state for staleness.
def getCurrentDewPoint() {
def lastForecast = state.lastForecast
def staleThreshold = ( now() / 1000 ) - ( 60 * 45 )
def DP
if ( lastForecast < staleThreshold ) {
DP = getWebForecast()
} else {
DP = state.dewpoint
}
DP
}
Now the method returns the dewpoint value from state
only if that value is reasonably fresh. Otherwise it calls the getWebForecast()
method, which I now need to write and make sure it returns a dew point value.
An interesting note about the above code – in determining the staleThreshold
I use the now()
method, which returns the current time as Epoch time (the number of seconds since midnight January 1, 1970) with a millisecond resolution. The subsequent division by 1000 removes the millisecond precision and gives me the time in seconds. Then I subtract 45 minutes (in seconds) from the current time to get my threshold for staleness, i.e., the earliest timestamp which I will consider “fresh”.
To use the Forecast.io API my app is going to have to know my private API key and the latitude and longitude of my location. The way you introduce private settings into a SmartApp is in its definition
section, which is kind of its preamble. Recall I originally created this SmartApp “From Form,” and that form set up the definition for me.
I will add three private settings for this SmartApp:
definition(
//...
) {
appSetting "apikey"
appSetting "latitude"
appSetting "longitude"
}
Note the unusual specification of these settings; they are specified in a closure (in {...}
) associated with the definition()
method.
With those defined, I first have to populate the values, which is done in the SmartThings IDE, in the “Settings” section of the SmartApp; then I can access them through the appSettings
Groovy map, as you’ll see in the URL constructor in this getWebForecast()
method definition:
def getWebForecast() {
def forecastURL = "https://api.forecast.io/forecast/${appSettings.apikey}/${appSettings.latitude},${appSettings.longitude}"
def responseTime
def responseDewPoint
def responseTemp
def responseRH
log.debug "Checking Forecast.io weather"
httpGet(forecastURL) { response ->
if (response.data) {
responseTime = response.data.currently.time.intValue()
responseDewPoint = response.data.currently.dewPoint.floatValue()
responseTemp = response.data.currently.temperature.floatValue()
responseRH = response.data.currently.humidity.floatValue()
state.lastForecast = responseTime
state.forecastSource = "Web"
state.dewpoint = responseDewPoint
state.outtemp = responseTemp
state.outrh = responseRH
} else {
responseDewPoint = 100
state.forecastSource = "Unavailable"
state.dewpoint = responseDewPoint
log.debug "HttpGet Response data unsuccessful"
}
}
responseDewPoint
}
That’s a lot of stuff, but ought to be easy to follow. The SmartApp documentation for using Web Services is comprehensive on this topic. The response from the Forecast.io API call is a JSON document, which is parsed automatically for us, so in the closure associated with the httpGet
call we can read directly from response.data
and pull out any values we want. The JSON returned by Forecast.io is very large and contains multiple forecasts. Here I want only the values that represent conditions “currently” in effect. Then I update the application’s state
with the Forecast.io values and return the dew point measurement.
In case the HTTP call should fail, notice that a value of 100 will be returned for the dew point. This should effectively disable periodic ventilation; when all else fails we’ll err on the side of not running the ventilator. I might want to add a push notification here, too, so I can be made aware that this thing is not working.
One last thing: I want to make this getWebForecast
method part of the initialize
part of the SmartApp, so that the application state will be primed with some data when it is first installed and configured.
def initialize() {
getWebForecast()
runEvery1Hour(periodicVentilation)
subscribe(location, "routineExecuted", routineChanged)
subscribe(outdoor, "temperature", outdoorHandler)
subscribe(outdoor, "humidity", outdoorHandler)
}
Next Time
In Part 4 of this series, I’ll address the coexistence of the periodic and on-demand ventilation rules.
Here is the complete SmartApp so far.