Virtual Thermostat Update?

Hey folks!

I am using the Virtual Thermostat to turn on a fan that is way up in a vaulted ceiling (via a z-wave switch…we never mess with the fan speed). This works great…even when I’m not home, the fan comes on when it gets warm in that south-facing room, and spreads the heat around the house.

My problem comes when I decide to turn the fan on manually, for whatever reason. The most obvious reason I do this is simply that I got warm before the set temp was reached, so I manually turn the fan on. A couple of minutes later, though, the app realizes that the temperature is below the set point and turns it back off.

I believe that this question is related:

The app seems to send an “off” or “on” command with every temp sensor poll. I think that the small tweak that I need would simply be for it to detect an ‘edge’…store the previous temperature, and only send an “on” command if the last temp was below the set point and the current temp is above, and the opposite for turning off (for cooling, of course…this would be reversed for a heater).

This seems like a simple update, but my hacking skills certainly aren’t up to the task…can someone help me out?

Thanks in advance!
Drew

Ok, I think I managed to hack out a solution based on the existing Virtual Thermostat. I didn’t previously know Java/Groovy, so it took some fiddling, but so far it seems to be working. I used the built-in “state” construct to save a “last temperature.” Then I changed the logic such that the fan would come on if the current temperature is greater than the set temperature and the last temperature was below the setpoint. And vice versa for turning off, and vice vice versa for heating.

The idea is that if the fan turns itself on, and then I turn it off manually when the temperature is still high, there won’t be any further ‘on’ commands until the temp sensor has gone below the set point a little bit. There will be a redundant ‘off’ command sent when that happens, but I think that’s ok.

I’m still “testing” my patch…as in, watching my fan to see how it behaves. I’m not a github user, but if anyone thinks that they want something like this, let me know, and I’ll find a way to share it.

Hi Drew, could you share the lines of code that you added to the virtual thermostat smartapp?

I’m trying to make some changes to the default smartapp to work with my situation. I have an old window a/c I want to control with smartthings. I can’t just use a smart switch because when the power is cut and then comes back on, the a/c doesn’t return automatically to its previous state. I’m using a ZXT-120 z-wave thermostat+IR extender, which has learned the IR command for the a/c’s power button (mapped to the thermostat’s “cool” mode) and I can turn the a/c on/off manually with a device type for the ZXT-120 created by @bdahlem and @Ron. So my goal is to use virtual thermostat to send a “power on” command via the ZXT-120 when the room temp is above the desired temp I set.

But I’m running into a similar issue as you; every time the temp sensor updates and the room temp is still above the desired temp, the power command is re-sent and the a/c keeps turning on/off (since I’m only able to send a “power” command and not “cool” or “heat” like a real thermostat). Adding in a line to remember the last state might help.

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
}

Hi, guys. I also implemented a fix for Drew according to his description. I believe what you want is some so-called buffer degrees to your specified temperature. I just finalized the changes today, and should have more time to confirm real-world behavior, but the simulator yields expected results with several scenarios. The buffer I speak of is another specified value of the amount of degrees to temporarily override your specified temperature, so when you manually toggle your switch (or fan), it will keep running until the new specified temperature is reached. The app is also now smart to know when you have manually toggled the switch or not. If not manually toggled, the original specified temperature is used for turning on or off your switch. Does that make sense?

I too have experienced where my fan turns off at when my temp sensor reaches my specified value, but I still feel warm and I want it to run some more! :slight_smile: So, while I still have to toggle the switch on, I know it my fan will continue running within buffer degrees relative to my specified temperature.

I have yet an additional idea, but I’ll play with that later. If you would like to see my code, ping me in about a week so I have time to confirm it works in real-world (aka: not the matrix. just kidding). :slight_smile:

Hey guys, first post so forgive me, I’m a total nube. Have a similar situation. I have a bathroom heater that’s set to turn on when my multi sensor detects the temp has dropped. My second dominant variable is the state of the door. If it’s open the heater never should operate. Would really appreciate some hand holding. Karma points will come back to you 3fold!

Hi, Jonathan. :slight_smile: I can take a look at this soon for ya, and reply back this week.

1 Like

Hi Drew, I have a complete solution specifically for ceiling fans called Ceiling Fan Guru. It solves the problem you’re referring to. You can pick it up here.

Do give it a try and let me know if you like it.

-Tom

All,

I know this is an old discussion, but in the interests of completeness the example code by Nova has it’s faults. Namely if the set point is at Heat 50 and the temperatures monitored are 55, 53, 50, 49 the result from these inputs will result in the switch never turning on. Also the absence of a threshold can result in the heater/AC short cycling.

I want to thank Nova (Andrew Vaughan) for his contribution for it helped me with my issue, and I have adapted the code changes he made into a Merge Request to get the upstream code adapted.

You can reference my branch here: https://github.com/james-powis/SmartThingsPublic/blob/improve-virtual-thermostat/smartapps/smartthings/virtual-thermostat.src/virtual-thermostat.groovy

And the associated pull request here: https://github.com/SmartThingsCommunity/SmartThingsPublic/pull/1964