@Steffen_Nissen
I have added a fan speed settings to the cooling. I’d love for you to add this to your base. Unfortunately, while I could figure out the programming, I don’t know how to submit this change to you. My code is below which consists of a new function (at end of the file) and a few lines in cool().
definition(
name: “Virtual Thermostat With Device”,
namespace: “piratemedia/smartthings”,
author: “Eliot S.”,
description: “Control a heater in conjunction with any temperature sensor, like a SmartSense Multi.”,
category: “Green Living”,
iconUrl: “https://raw.githubusercontent.com/eliotstocker/SmartThings-VirtualThermostat-WithDTH/master/logo-small.png”,
iconX2Url: “https://raw.githubusercontent.com/eliotstocker/SmartThings-VirtualThermostat-WithDTH/master/logo.png”,
parent: “piratemedia/smartthings:Virtual Thermostat Manager”,
)
preferences {
section(“Choose a temperature sensor(s)… (If multiple sensors are selected, the average value will be used)”){
input “sensors”, “capability.temperatureMeasurement”, title: “Sensor”, multiple: true
}
section("Select the heater outlet(s)… "){
input “heating_outlets”, “capability.switch”, title: “Heating Outlets”, multiple: true
}
section("Select the cooling outlet(s)… "){
input “cooling_outlets”, “capability.switch”, title: “Cooling Outlefts”, multiple: true
}
section(“Only heat/cool when contact(s) aren’t open (optional, leave blank to not require contact sensor)…”){
input “motion”, “capability.contactSensor”, title: “Contact”, required: false, multiple: true
}
section(“Never go below this temperature: (optional)”){
input “emergencyHeatingSetpoint”, “decimal”, title: “Emergency Min Temp”, required: false
}
section(“Never go above this temperature: (optional)”){
input “emergencyCoolingSetpoint”, “decimal”, title: “Emergency Max Temp”, required: false
}
section(“The minimum difference between the heating and cooling setpoint, it’s recommended to not put this too low to conserve energy”) {
input “heatCoolDelta”, “decimal”, title: “Heat / Cool Delta”, defaultValue: 3.0
}
section(“The amount that the temperature is allowed to dip below the heating setpoint before engaging heating, it’s recommended to not put this too low to avoid heaters turning on and off too frequently”) {
input “heatDiff”, “decimal”, title: “Heat Differential”, defaultValue: 0.3
}
section(“The amount that the temperature is allowed to go above the cooling setpoint before engaging cooling, it’s recommended to not put this too low to avoid coolers turning on and off too frequently”) {
input “coolDiff”, “decimal”, title: “Cool Differential”, defaultValue: 0.3
}
section("Fix for unreliable switches to automatically turn them on/off again, if it seems like turning them on/off did not work based on the temperature (Experimental)") {
input "unreliableSwitchFix", "bool", title: "Unreliable switch fix", defaultValue: false
}
}
def installed()
{
log.debug “running installed”
state.deviceID = Math.abs(new Random().nextInt() % 9999) + 1
updated()
}
def createDevice() {
def thermostat
def label = app.getLabel()
log.debug "create device with id: pmvt$state.deviceID, named: $label" //, hub: $sensor.hub.id"
try {
thermostat = addChildDevice("piratemedia/smartthings", "Virtual Thermostat Device", "pmvt" + state.deviceID, null, [label: label, name: label, completedSetup: true])
} catch(e) {
log.error("caught exception", e)
}
return thermostat
}
def motionDetected(){
if(motion) {
for(m in motion) {
if(m.currentValue(‘contact’) == “open”) {
return true;
}
}
}
return false;
}
def shouldHeatingBeOn(thermostat) {
def temp = getAverageTemperature()
//if temperature is below emergency setpoint
if(emergencyHeatingSetpoint && emergencyHeatingSetpoint > temp) {
return true;
}
//if thermostat isn't set to heat
if(thermostat.currentValue('thermostatMode') != "heat" && thermostat.currentValue('thermostatMode') != "auto") {
return false;
}
//if any of the contact sensors are open
if(motionDetected()){
return false;
}
//average temperature across all temperature sensors is above set point
if(temp > thermostat.currentValue("adjustedHeatingPoint")) {
return false;
}
return true;
}
def shouldCoolingBeOn(thermostat) {
def temp = getAverageTemperature()
//if temperature is above emergency setpoint
if(emergencyCoolingSetpoint && emergencyCoolingSetpoint < temp) {
return true;
}
//if thermostat isn't set to cool
if(thermostat.currentValue('thermostatMode') != "cool" && thermostat.currentValue('thermostatMode') != "auto") {
return false;
}
//if any of the contact sensors are open
if(motionDetected()){
return false;
}
//average temperature across all temperature sensors is below set point
if(temp < thermostat.currentValue("adjustedCoolingPoint")) {
return false;
}
return true;
}
def getAverageTemperature() {
def total = 0;
def count = 0;
//total all sensors temperature
for(sensor in sensors) {
total += sensor.currentValue("temperature")
thermostat.setIndividualTemperature(sensor.currentValue("temperature"), count, sensor.label)
count++
}
//divide by number of sensors
return total / count
}
def switchOff(switches) {
log.debug "switching off: {switches}, current values: " + switches.currentValue("switch")
for(s in switches) {
s.off()
}
log.debug "done switching off: {switches}, current values: " + switches.currentValue(“switch”)
}
def switchOn(switches) {
log.debug "switching on: {switches}, current values: " + switches.currentValue("switch")
for(s in switches) {
s.on()
}
log.debug "done switching on: {switches}, current values: " + switches.currentValue(“switch”)
}
//set the expected direction (heat/cool/none) to be able to monitor if it’s working
def setExpectedDirection(direction) {
log.debug “direction change to ${direction}”
state.expectedDirection = direction
state.directionChangeWorked = false
state.directionChangeTime = new Date().getTime()
}
def temperatureHandler(evt) {
state.curTemp = getAverageTemperature()
def now = new Date().getTime()
def minSinceDirectionChange = (now - state.directionChangeTime)/(1000*60)
log.debug “temperatureHandler: {evt.stringValue}, curTemp: {state.curTemp}, lastTemp: {state.lastTemp}" +
", expectedDirection: {state.expectedDirection}, directionChangeWorked: {state.directionChangeWorked}" +
", minSinceDirectionChange: {minSinceDirectionChange}, now: {now}, directionChangeTime: {state.directionChangeTime}”
if(!state.directionChangeWorked && state.expectedDirection != 'none') {
//if we haven't proven that the direction change has worked yet, let's confirm that it worked
if(state.expectedDirection == 'cool' && state.curTemp < state.lastTemp) {
log.debug "expecting cool and temp went down from ${state.lastTemp} to ${state.curTemp} all good"
state.directionChangeWorked = true
}
if(state.expectedDirection == 'heat' && state.curTemp > state.lastTemp) {
log.debug "expecting heat and temp went up from ${state.lastTemp} to ${state.curTemp} all good"
state.directionChangeWorked = true
}
if(!state.directionChangeWorked && minSinceDirectionChange > 4){
if(!unreliableSwitchFix) {
log.debug "direction change did not work within 4 min, but since 'Unreliable Switch Fix' is off, nothing will be done. Minutes since direction change: ${minSinceDirectionChange}"
return
}
log.debug "direction change did not work within 4 min, try flipping the switch again and reset the timer. Minutes since direction change: ${minSinceDirectionChange}"
state.directionChangeTime = new Date().getTime()
def oState = thermostat.getOperatingState()
if(state.expectedDirection == 'cool') {
if(oState == 'cooling') {
switchOn(cooling_outlets)
} else {
switchOff(heating_outlets)
}
}
if(state.expectedDirection == 'heat') {
if(oState == 'heating') {
switchOn(heating_outlets)
} else {
switchOff(cooling_outlets)
}
}
}
}
state.lastTemp = state.curTemp
handleChange()
}
def cool() {
//log.debug "cooling outlets on, current value: " + cooling_outlets.currentValue(“switch”)
def oState = thermostat.getOperatingState()
log.debug “In cool(). oState=” + oState;
if(oState != ‘cooling’) {
setExpectedDirection(‘cool’)
thermostat.setThermostatOperatingState(‘cooling’)
switchOn(cooling_outlets)
if(oState == ‘heating’) {
switchOff(heating_outlets)
}
}
else {
log.debug “About to call setcoolfanspeed()”;
setcoolfanspeed(cooling_outlets)
}
}
def heat() {
//log.debug "heating outlets on, current value: " + heating_outlets.currentValue(“switch”)
def oState = thermostat.getOperatingState()
if(oState != ‘heating’) {
setExpectedDirection(‘heat’)
thermostat.setThermostatOperatingState(‘heating’)
switchOn(heating_outlets)
if(oState == ‘cooling’) {
switchOff(cooling_outlets)
}
}
}
def off() {
//log.debug "off, all outlets off, current value heating: " + heating_outlets.currentValue(“switch”) + ", cooling: " + cooling_outlets.currentValue(“switch”)
def oState = thermostat.getOperatingState()
if(oState != ‘off’) {
thermostat.setThermostatOperatingState(‘off’)
setExpectedDirection(‘none’)
if(oState == ‘heating’) {
switchOff(heating_outlets)
} else if(oState == ‘cooling’) {
switchOff(cooling_outlets)
}
}
}
def idle() {
//log.debug "idle, all outlets off, current value heating: " + heating_outlets.currentValue(“switch”) + ", cooling: " + cooling_outlets.currentValue(“switch”)
def oState = thermostat.getOperatingState()
if(oState != ‘idle’) {
thermostat.setThermostatOperatingState(‘idle’)
if(oState == ‘heating’) {
setExpectedDirection(‘cool’)
switchOff(heating_outlets)
} else if(oState == ‘cooling’) {
setExpectedDirection(‘heat’)
switchOff(cooling_outlets)
}
}
}
def handleChange() {
def thermostat = getThermostat()
if(thermostat) {
log.debug "handle change, mode: " + thermostat.currentValue(‘thermostatMode’) +
", operatingState: " + thermostat.currentValue(“thermostatOperatingState”) +
", temp: " + getAverageTemperature() +
", coolingSetPoint: " + thermostat.currentValue(“coolingSetpoint”) +
", heatingSetPoint: " + thermostat.currentValue(“heatingSetpoint”)
/*def attrs = thermostat.supportedAttributes
attrs.each {
log.debug "${thermostat.displayName}, attribute: ${it.name}, dataType: ${it.dataType}, value: " + thermostat.currentValue(it.name)
}*/
switch (thermostat.currentValue('thermostatMode')){
case "heat":
if(shouldHeatingBeOn(thermostat)) {
heat()
} else {
idle()
}
break
case "cool":
if(shouldCoolingBeOn(thermostat)) {
cool()
} else {
idle()
}
break
case "auto":
if(shouldCoolingBeOn(thermostat)) {
cool()
} else if(shouldHeatingBeOn(thermostat)) {
heat()
} else {
idle()
}
break
case "off":
default:
off()
break
}
getThermostat().setVirtualTemperature(getAverageTemperature())
}
}
def getThermostat() {
return getChildDevice(“pmvt” + state.deviceID)
}
def uninstalled() {
deleteChildDevice(“pmvt” + state.deviceID)
}
def updated()
{
log.debug “running updated: $app.label”
unsubscribe()
unschedule()
//get or add thermostat
def thermostat = getThermostat()
if(thermostat == null) {
thermostat = createDevice()
}
//subscribe to temperature changes
subscribe(sensors, "temperature", temperatureHandler)
//subscribe to contact sensor changes
if (motion) {
subscribe(motion, "contact", motionHandler)
}
//subscribe to virtual device changes
//subscribe(thermostat, "thermostatSetpoint", thermostatSetPointHandler)
subscribe(thermostat, "heatingSetpoint", heatingSetPointHandler)
subscribe(thermostat, "coolingSetpoint", coolingSetPointHandler)
subscribe(thermostat, "thermostatMode", thermostatModeHandler)
//reset some values
setExpectedDirection('none')
thermostat.clearSensorData()
thermostat.setVirtualTemperature(getAverageTemperature())
thermostat.setHeatCoolDelta(heatCoolDelta)
thermostat.setHeatDiff(heatDiff)
thermostat.setCoolDiff(coolDiff)
}
def coolingSetPointHandler(evt) {
log.debug “coolingSetPointHandler: ${evt.stringValue}”
handleChange()
}
def heatingSetPointHandler(evt) {
log.debug “heatingSetPointHandler: ${evt.stringValue}”
handleChange()
}
def motionHandler(evt) {
log.debug “motionHandler: ${evt.stringValue}”
handleChange()
}
def thermostatModeHandler(evt) {
log.debug “thermostatModeHandler: ${evt.stringValue}”
handleChange()
}
def setcoolfanspeed(switches) {
def temp = getAverageTemperature()
def thermostat = getThermostat()
def speed = 0
log.debug "setting fan speed on: ${switches}, current values: " + switches.currentValue("level") + " coolDiff= " + coolDiff;
//Determine Desired Fan Speed
if(temp >= thermostat.currentValue("coolingSetpoint") + (coolDiff)*3) {
speed=75;
}
else {
if(temp >= thermostat.currentValue("coolingSetpoint") + (coolDiff)*2) {
speed=50;
}
else {
speed=25;
}
}
//Set the fan speed on each switch
for(s in switches) {
log.debug "temp = " + temp + "; thermostat.currentValue#coolingSetpoint#)= " + thermostat.currentValue("coolingSetpoint") + "; thermostat.coolDiff= " +
coolDiff
log.debug "Setting Level on: " + s + ", to: " + speed;
s.setLevel(speed);
}
log.debug "done setting fan speed: ${switches}, speed: " + speed
}