Hey Mark! It’s been a while, but I’m happy to share my changes. Are you familiar with the IDE? I haven’t created a new app in a long time…if you have trouble, let me know, and I can try to muddle through it with you. I’m going to paste the entire app, even though most of it didn’t change…I don’t want to miss anything.
Clearly the most important bits are the comparison to ‘state.lastTemp’ in ‘evaluate’, but there are other places dotted around where the saved temp get updated, etc. I have had this running for months now, and it seems to work.
Good luck, let me know if I can help more…
Drew
definition(
name: "Improved Virtual Thermostat",
namespace: "none",
author: "Andrew Vaughan after SmartThings",
description: "Control a space heater or window air conditioner in conjunction with any temperature sensor, like a SmartSense Multi.",
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 a temperature sensor... "){
input "sensor", "capability.temperatureMeasurement", title: "Sensor"
}
section("Select the heater or air conditioner outlet(s)... "){
input "outlets", "capability.switch", title: "Outlets", multiple: true
}
section("Set the desired temperature..."){
input "setpoint", "decimal", title: "Set Temp"
}
section("When there's been movement from (optional, leave blank to not require motion)..."){
input "motion", "capability.motionSensor", title: "Motion", required: false
}
section("Within this number of minutes..."){
input "minutes", "number", title: "Minutes", required: false
}
section("But never go below (or above if A/C) this value with or without motion..."){
input "emergencySetpoint", "decimal", title: "Emer Temp", required: false
}
section("Select 'heat' for a heater and 'cool' for an air conditioner..."){
input "mode", "enum", title: "Heating or cooling?", options: ["heat","cool"]
}
}
def installed()
{
state.lastTemp = null
subscribe(sensor, "temperature", temperatureHandler)
if (motion) {
subscribe(motion, "motion", motionHandler)
}
}
def updated()
{
unsubscribe()
subscribe(sensor, "temperature", temperatureHandler)
if (motion) {
subscribe(motion, "motion", motionHandler)
}
}
def temperatureHandler(evt)
{
def isActive = hasBeenRecentMotion()
if (isActive || emergencySetpoint) {
evaluate(evt.doubleValue, state.lastTemp, isActive ? setpoint : emergencySetpoint)
state.lastTemp = evt.doubleValue
}
else {
outlets.off()
}
}
def motionHandler(evt)
{
if (evt.value == "active") {
def thisTemp = sensor.currentTemperature
if (thisTemp != null) {
evaluate(thisTemp, state.lastTemp, setpoint)
state.lastTemp = thisTemp
}
} else if (evt.value == "inactive") {
def isActive = hasBeenRecentMotion()
log.debug "INACTIVE($isActive)"
if (isActive || emergencySetpoint) {
def thisTemp = sensor.currentTemperature
if (lastTemp != null) {
evaluate(thisTemp, state.lastTemp, isActive ? setpoint : emergencySetpoint)
state.lastTemp = thisTemp
}
}
else {
outlets.off()
}
}
}
private evaluate(currentTemp, lastTemp, desiredTemp)
{
log.debug "EVALUATE($currentTemp, $desiredTemp)"
if (mode == "cool") {
// air conditioner
if ( (currentTemp >= desiredTemp) && ( (lastTemp < desiredTemp) || (lastTemp == null) ) ) {
outlets.on()
}
if ( (currentTemp < desiredTemp) && ( (lastTemp >= desiredTemp) || (lastTemp == null) ) ) {
outlets.off()
}
}
else {
// heater
if ( (currentTemp <= desiredTemp) && ( (lastTemp > desiredTemp) || (lastTemp == null) ) ) {
outlets.on()
}
if ( (currentTemp > desiredTemp) && ( (lastTemp <= desiredTemp) || (lastTemp == null) ) ) {
outlets.off()
}
}
}
private hasBeenRecentMotion()
{
def isActive = false
if (motion && minutes) {
def deltaMinutes = minutes as Long
if (deltaMinutes) {
def motionEvents = motion.eventsSince(new Date(now() - (60000 * deltaMinutes)))
log.trace "Found ${motionEvents?.size() ?: 0} events in the last $deltaMinutes minutes"
if (motionEvents.find { it.value == "active" }) {
isActive = true
}
}
}
else {
isActive = true
}
isActive
}