Hello,
I'm trying to port over this SmartApp from ST. It controls our homes hot water recirculating pump based on motion detected in the master bathroom.
/**
* Recirculating Pump Thermostat
*
* Copyright 2017 Jeremy Akers
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Recirculating Pump Thermostat",
namespace: "jeremy.akers",
author: "Jeremy Akers",
description: "Recirculating Pump Thermostat",
category: "Green Living",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo-switch.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo-switch@2x.png")
preferences {
section("Choose water line temperature sensor... "){
input "watersensor", "capability.temperatureMeasurement", title: "Water Line Sensor"
}
section("Choose ambient temperature sensor... "){
input "ambientsensor", "capability.temperatureMeasurement", title: "Ambient Sensor"
}
section("Select the recirc pump switch(es)... "){
input "outlets", "capability.switch", title: "Outlets", multiple: true
}
section("Desired water temp at faucet:"){
input "desiredwatertemp", "number", title: "Set Temp"
}
section("Set the desired temperature difference..."){
input "threshold", "decimal", title: "Set Temp"
}
section("Solar Tank Temp Limit (Recirc on above this temp):"){
input "solarlimit", "decimal", title: "Set Temp"
}
section("Minimum water line temp:"){
input "minlinetemp", "decimal", title: "Set Temp"
}
section("When there's been movement from..."){
input "motion", "capability.motionSensor", title: "Motion", multiple: true, required: true
}
section("Within this number of minutes..."){
input "minutes", "number", title: "Minutes", required: false
}
section("Turn off recirc after delay (minutes)..."){
input "delayminutes", "decimal", title: "Minutes", required: false
}
section("Don't re-activate recirc for at least this many minutes after turning on:"){
input "stayoffminutes", "number", title: "Minutes", required: false
}
}
def installed()
{
subscribe(watersensor, "temperature", temperatureHandler)
//subscribe(ambientsensor, "temperature", temperatureHandler) // We don't need to "subscribe" to updates from this sensor.
if (motion) {
subscribe(motion, "motion", motionHandler)
}
atomicState.previousWaterTemp = watersensor.currentTemperature
atomicState.previousWaterTempTime = now()
atomicState.tempWhenOn = null
atomicState.timeWhenOn = null
}
def updated()
{
unsubscribe()
subscribe(watersensor, "temperature", temperatureHandler)
//subscribe(ambientsensor, "temperature", temperatureHandler) // We don't need to "subscribe" to updates from this sensor.
if (motion) {
subscribe(motion, "motion", motionHandler)
}
atomicState.previousWaterTemp = watersensor.currentTemperature
atomicState.previousWaterTempTime = now()
atomicState.tempWhenOn = null
atomicState.timeWhenOn = null
}
def temperatureHandler(evt)
{
def ambientTemp = ambientsensor.currentTemperature
def isActive = hasBeenRecentMotion()
def maxTemp = ((evt.doubleValue >= 105) ? evt.doubleValue + 1 : 105)
def percentChange = (evt.doubleValue - atomicState.previousWaterTemp) / (maxTemp - evt.doubleValue)
def solarTemp = getSolarTemp()
logAndNotify("Previous temp: ${atomicState.previousWaterTemp}, new temp: ${evt.doubleValue}, percentChange: ${percentChange}, Solar temp: ${solarTemp}")
if(solarTemp > solarlimit)
{
logAndNotify("Solar tank hotter than preset: ${solarlimit}. Turning on recirc to heat attic tank.")
outlets.on()
}
else
{
if(atomicState.previousWaterTemp != null && atomicState.previousWaterTemp > 0 && percentChange >= 0.05)
{
logAndNotify("Temperature has risen significantly. Turning off.")
turnOffSwitch()
}
else if (isActive)
{
logAndNotify("TemperatureHandler calling Evaluate")
evaluate(evt.doubleValue, ambientTemp)
}
else
{
log.debug("TemperatureHandler outlets OFF")
turnOffSwitch()
}
}
atomicState.previousWaterTemp = evt.doubleValue
atomicState.previousWaterTempTime = now()
}
def motionHandler(evt)
{
def lastTemp = watersensor.currentTemperature
def watersensorevents = watersensor.eventsSince(new Date(now() - (60000 * 60))).flatten().findAll() // All water sensor events in past 60 minutes.
def ambientTemp = ambientsensor.currentTemperature
def ambientevents = ambientsensor.eventsSince(new Date(now() - (60000 * 60))).flatten().findAll() // All ambient sensor events in past 60 minutes.
def switchEvents = outlets.eventsSince(new Date(now() - (60000 * stayoffminutes))).flatten().findAll{ it.value=="on" } // All switch events in past ${stayoffminutes} minutes.
def isActive = hasBeenRecentMotion()
if (!(watersensorevents.find()))
{
logAndNotify("No water temperature sensor events in past hour. Assuming dead sensor. Setting water line temp to 70 degrees.")
lastTemp = 70
}
if (!(ambientevents.find()))
{
logAndNotify("No ambient temperature sensor events in past hour. Assuming dead sensor. Setting ambient temp to 70 degrees.")
ambientTemp = 70
}
if(solarTemp > solarlimit)
{
logAndNotify("Solar tank hotter than preset: ${solarlimit}. Turning on recirc to heat attic tank.")
outlets.on()
}
else
{
log.debug("Event value: ${evt.value}, isActive: ${isActive}")
//switchEvents.each { log.trace "Recirc switchEvents value iterator: ${it.value}" }
if ((evt.value == "active" || isActive))
{
if (!(switchEvents.find { it.value == "on" }) && lastTemp != null && ambientTemp != null)
{
logAndNotify("MotionHandler calling Evaluate")
evaluate(lastTemp, ambientTemp)
}
else
{
logAndNotify("motionHandler: opted not to switch on since outlet had recently already been on in the past ${stayoffminutes} minutes.")
}
}
else
{
logAndNotify("MotionHandler outlets OFF")
turnOffSwitch()
}
}
}
private evaluate(waterTemp, ambientTemp)
{
def solarTemp = getSolarTemp()
def myDesiredWaterTemp = (desiredwatertemp > solarTemp) ? desiredwatertemp : solarTemp
def desiredTemp = (ambientTemp + myDesiredWaterTemp) / 2
logAndNotify("Ambient Temp: ${ambientTemp}, Desired Water Temp: ${myDesiredWaterTemp}, Desired Sensor Temp: ${desiredTemp}, Solar tank temp: ${solarTemp}")
if (solarTemp != null && solarTemp > 0 && desiredTemp > solarTemp)
{
desiredTemp = solarTemp
}
if(desiredTemp < minlinetemp)
{
desiredTemp = minlinetemp
}
logAndNotify("Recirc: Evaluate: Water Temp: $waterTemp, Desired/Solar Temp: $desiredTemp, Threshold: $threshold")
if (waterTemp < 105 && waterTemp <= desiredTemp + threshold) {
log.info("Recirc EVALUATE outlets ON")
turnOnSwitch()
}
else //if (waterTemp >= desiredTemp + (threshold * 2) || waterTemp >= 105)
{
log.info("Recirc EVALUATE outlets OFF")
turnOffSwitch()
}
}
private hasBeenRecentMotion()
{
def isActive = false
if (motion && minutes) {
def deltaMinutes = minutes as Long
if (deltaMinutes) {
def motionEvents = motion.eventsSince(new Date(now() - (60000 * deltaMinutes))).flatten()
log.trace "Recirc: Found ${motionEvents?.size() ?: 0} events in the last $deltaMinutes minutes"
//motionEvents.each { log.trace "Recirc value iterator: ${it.value}" }
if (motionEvents.find { it.value == "active" }) {
isActive = true
}
}
}
else {
isActive = true
}
isActive
}
def turnOffAfterDelay() {
logAndNotify("Outlets OFF after delay of ${delayminutes}")
turnOffSwitch()
}
def turnOffSwitch() {
def currSwitches = outlets.currentSwitch
def onSwitches = currSwitches.findAll { switchVal ->
switchVal == "on" ? true : false
}
if(onSwitches.size() > 0)
{
logAndNotify("Outlets OFF")
outlets.off()
}
else
{
log.debug("Outlets already OFF, skipping.")
}
}
def turnOnSwitch(waterTemp) {
def currSwitches = outlets.currentSwitch
def offSwitches = currSwitches.findAll { switchVal ->
switchVal == "off" ? true : false
}
if(offSwitches.size() > 0)
{
logAndNotify("Outlets ON")
atomicState.tempWhenOn = waterTemp
atomicState.timeWhenOn = now()
outlets.on()
runIn(delayminutes*60, turnOffAfterDelay) // Turn off after ${delayminutes} minutes.
}
else
{
log.debug("Outlets already ON, skipping.")
}
}
def logAndNotify(message)
{
log.info(message)
sendNotificationEvent(message)
}
def getSolarTemp()
{
def solarTemp = -1
def dateTime = new Date()
def formattedDate = dateTime.format("yyyy-MM-dd HH:mm:ss", location.timeZone)
def currentTimeEpoch = dateTime.getTime() - 60000
def params = [
uri: 'http://www.log-alert.com/logalert/feed/',
path: 'data.json',
contentType: 'application/json',
query: [id:1571, start:currentTimeEpoch, dp:400]
]
log.debug "Current time epoch: ${currentTimeEpoch}, formatted: ${formattedDate}"
try {
httpGet(params) {resp ->
def dataItem = resp.data[0]
solarTemp = dataItem[1]
logAndNotify("Solar tank temp: ${solarTemp}")
}
} catch (e) {
log.error "Error getting solar tank temp: $e"
}
solarTemp
}
And I get this error when the motion event fires:
java.lang.IllegalArgumentException: Command 'eventsSince' is not supported by device. on line 131 (motionHandler)
Line 131 is this line in motion handler:
def switchEvents = outlets.eventsSince(new Date(now() - (60000 * stayoffminutes))).flatten().findAll{ it.value=="on" } // All switch events in past ${stayoffminutes} minutes.
It appears Hubitat doesn't support the "eventsSince" method? or is there some code missing from that device's driver to enable that functionality?
Thanks,
-Jeremy