I was trying to get my Zen Zigbee thermostat to work with both Alexa and Hubitat.
I found some code online to do this but it didn't really work for me. Code seemed over complicated for my single stage system so I trien to strip it down to something I could figure out and thought I'd share the result. As I am not a programmer by any means and don't understand the rules about posting here, please be kind. Any way this seems to work for me but probably has bugs.
/*
*
- Simple ZEN Single Stage Thermostat Manager
- Author: Ron Doan - based on original code by ELFEGE
- Version: 1.0.0
- Date: 04/21/2020
- 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.
- Notes:
- Version 1.0.0
- Simple ZEN single stage thermostat manager app using virtual dimmer (might work on other devices).
- Thanks to ELFEGE for original source - this code is a stripped down verson of his code (with a few tweaks).
- Supports single stage heating and cooling only - Thats all I need where I live and cannot test anything else.
- Receives temperature input data from virtual dimmer switch which Alexa can also control.
- Switches from heat to cold and from cold to heat based on inside temperature reading from thermostat when user changes temperature.
- Should not interfere with manual programming of thermostat - but time will tell.
- Does not use auto heating/cooling mode.
- Does not support heat pump.
- BUG - cannot switch fan mode - suggest manually setting fan mode to auto when using this app.
- I am not a professional programmer and only spent a few evenings on this but so far so good.
*/
import java.text.SimpleDateFormat
import groovy.transform.Field
import groovy.json.JsonOutput
/* Globals */
@Field static int LastDimmerSetting = 0
@Field static boolean setpointSentByApp = false
definition(
name: "Simple ZEN Single Stage Thermostat Manager",
namespace: "Doan",
author: "DOAN based on original code by ELFEGE",
description: "Manage your thermostats with dimmer",
category: "Green Living",
iconUrl: " ",
iconX2Url: " ",
iconX3Url: " ",
image: " "
)
preferences
{
page name: "settings"
}
def settings() // 1
{
if(state.paused)
{
log.debug "new app label: ${app.label} 1"
while(app.label.contains(" (Paused) "))
{
app.updateLabel(app.label.minus("(Paused)" ))
}
app.updateLabel(app.label + ("<font color = 'red'> (Paused) </font>" ))
}
else if(app.label.contains("(Paused)"))
{
app.updateLabel(app.label.minus(" (Paused) " ))
while(app.label.contains(" (Paused) ")){app.updateLabel(app.label.minus("(Paused)" ))}
log.debug "new app label: ${app.label} 1"
}
if(state.paused == true)
{
state.button_name = "Resume"
log.debug "button name is: $state.button_name 1"
}
else
{
state.button_name = "Pause"
log.debug "button name is: $state.button_name 1"
}
def pageProperties = [
name: "settings",
title: "Thermostats and other devices",
nextPage: null,
install: true,
uninstall: true
]
dynamicPage(pageProperties)
{
section()
{
input "pause", "button", title: "$state.button_name"
}
section()
{
label title: "Assign a name", required: false
input "restricted", "mode", title: "Do not run this app while in these modes", multiple: true
}
section("Select the thermostat you want to control")
{
input "thermostat", "capability.thermostat", title: "Select a thermostat", required: true, multiple: false, description: null, submitOnChange:true
input "dimmer", "capability.switchLevel", title: "Use this dimmer as set point input source", required: true, submitOnChange:true
}
section()
{
input "run", "button", title: "RUN"
input "update", "button", title: "UPDATE"
input "poll", "button", title: "REFRESH"
input "polldevices", "bool", title: "Poll devices"
input "enabledebug", "bool", title: "Debug", submitOnChange:true
input "description", "bool", title: "Description Text", submitOnChange:true
}
}
}
def installed() // 2
{
logging("Installed with settings: ${settings} 2")
initialize()
}
def updated() // 3
{
logging("updated with settings: ${settings} 3")
unsubscribe()
unschedule()
initialize()
}
def initialize() // 4
{
if(enabledebug)
{
log.warn "debug enabled 4"
state.EnableDebugTime = now()
runIn(1800,disablelogging)
descriptiontext "debug will be disabled in 30 minutes"
}
else
{
log.warn "debug disabled 4"
}
state.paused = false
state.restricted = false
logging("subscribing to events... 4")
subscribe(location, "mode", ChangedModeHandler)
subscribe(thermostat, "temperature", temperatureHandler)
subscribe(weatherSensor, "temperature", temperatureHandler)
subscribe(dimmer, "level", dimmerHandler)
descriptiontext "subscribed $dimmer to dimmerHandler"
subscribe(thermostat, "heatingSetpoint", setPointHandler)
descriptiontext "subscribed ${thermostat}'s heatingSetpoint to setPointHandler"
subscribe(thermostat, "coolingSetpoint", setPointHandler)
descriptiontext "subscribed ${thermostat}'s coolingSetpoint to setPointHandler"
if(polldevices)
{
schedule("0 0/5 * * * ?", Poll)
}
schedule("0 0/1 * * * ?", mainloop)
descriptiontext "INITIALIZATION DONE"
}
/EVT HANDLERS*********/
def appButtonHandler(btn) // 5
{
switch(btn)
{
case "pause":state.paused = !state.paused
logging("state.paused = ${state.paused} 5")
if(state.paused)
{
log.debug "unsuscribing from events... 5"
unsubscribe()
log.debug "unschedule()... 5"
unschedule()
break
}
else
{
updated()
break
}
case "update":
state.paused = false
updated()
break
case "run":
if(!state.paused) mainloop()
break
case "poll":
Poll()
break
}
}
def ChangedModeHandler(evt) // 6
{
logging("mode is ${evt.value} 6")
if(evt.value in restricted)
{
state.paused = true
state.restricted = true
}
else if(state.paused == true && state.restricted == true)
{
updated()
}
}
def temperatureHandler(evt) //7
{
logging("$evt.device returns ${evt.value}F 7")
mainloop()
}
def setPointHandler(evt) // 8
{
descriptiontext "new $evt.name is $evt.value---------------------------------------"
log.debug "entered setpoint handler. 8"
def currDim = dimmer.currentValue("level") // get value from dimmer
logging"""
Current dimmer value is $currDim
evt.value = $evt.value """
mainloop()
}
def dimmerHandler(evt) // 9
{
descriptiontext "New dimmer level is $evt.value"
log.debug "entered dimmer handler. 9"
setpointSentByApp = true
mainloop()
}
/MAIN loop*********/
def mainloop() // 10
{
int InsideTemperature
int DimmerSetpoint
boolean changed
if(!state.paused)
{
InsideTemperature = GetInsideTemp()
DimmerSetpoint = GetDimmerSetpoint() // Get dimmer's current value
changed = DidSetpointChange(DimmerSetpoint) // Check to see if dimmer requested setpoint change.
logging("changed = ${changed} 10")
if(changed)
{
if(DimmerSetpoint > InsideTemperature)
{
thermostat.setCoolingSetpoint(DimmerSetpoint -2)
thermostat.setHeatingSetpoint(DimmerSetpoint)
thermostat.setThermostatMode("heat")
log.debug "Thermostat's heating set point set to ${DimmerSetpoint} 10"
}
else if(DimmerSetpoint < InsideTemperature)
{
thermostat.setHeatingSetpoint(DimmerSetpoint + 2)
thermostat.setCoolingSetpoint(DimmerSetpoint)
thermostat.setThermostatMode("cool")
log.debug "Thermostat's cooling set point set to ${DimmerSetpoint} 10"
}
logging("waiting 2 seconds 10")
// pauseExecution(2000) // give plenty of time for respective commands to be received and processed by the device driver
// thermostat.refresh()
}
}
}
/INPUTS*********/
def GetDimmerSetpoint() // 11
{
int val = 75
val = dimmer.currentValue("level")
log.debug "Dimmer's temperature setting is ${val}F 11"
return val
}
def GetInsideTemp() // 12
{
float val
val = thermostat.currentValue("temperature") // get inside temperature
log.debug "Thermostat's temperature reading is ${val}F 12"
return val
}
def Poll() // 13
{
if(polldevices)
{
boolean thermPoll = thermostat.hasCommand("poll")
boolean thermRefresh = thermostat.hasCommand("refresh")
if(thermRefresh)
{
thermostat.refresh()
logging("refreshing $thermostat 13")
}
else if(thermPoll)
{
thermostat.poll()
logging("polling ${thermostat} 13")
}
boolean heaterPoll = heater?.hasCommand("poll")
boolean heaterRefresh = heater?.hasCommand("refresh")
if(heaterRefresh)
{
heater.refresh()
logging("refreshing ${heater} 13")
}
else if(heaterPoll)
{
heater.poll()
logging("polling ${heater} 13")
}
}
}
/BOOLEANS*********/
boolean DidSetpointChange(CurrentDimmerSetting) // 14
{
boolean changed = false
logging("LastDimmerSetting = ${LastDimmerSetting} 14")
logging("CurrentDimmerSetting = ${CurrentDimmerSetting} 14")
if(setpointSentByApp == true) // setpoint issued by dommer
{
logging("Setpoint was sent by app 14")
if(LastDimmerSetting != CurrentDimmerSetting)
{
changed = true
LastDimmerSetting = CurrentDimmerSetting
}
}
else // setpoint issued by thermostat panel or Hubitat ZEN Thermostat application UIR
{
def thermMode = thermostat.currentValue("thermostatMode")
logging("Thermostat Mode = ${thermMode} 14") // get thermostat mode this might have changed too
if(thermMode == "heat")
{
def manualSetpoint = thermostat.currentValue("heatingSetpoint").toInteger() // get setpoint from thermostat
logging("Manual heating setpoint = ${manualSetpoint} 14")
dimmer.setLevel(manualSetpoint) // update dimmer to match new setpoint issued by non dimmer command
LastDimmerSetting = manualSetpoint
}
else if(thermMode == "cool")
{
def manualSetpoint = thermostat.currentValue("coolingSetpoint").toInteger() // get setpoint from thermostat
logging("Manual cooling setpoint = ${manualSetpoint} 14")
dimmer.setLevel(manualSetpoint) // update dimmer to match new setpoint issued by non dimmer command
LastDimmerSetting = manualSetpoint
}
}
logging("Setpoint changed = ${changed} 14")
setpointSentByApp = false
return changed
}
/OTHER*********/
def logging(message)
{
if(enabledebug)
{
log.debug message
}
}
def descriptiontext(message)
{
if(description)
{
log.info message
}
}
def disablelogging() // 15
{
log.warn "debug logging disabled... 15"
app.updateSetting("enabledebug",[value:"false",type:"bool"])
}