Skip to content

Instantly share code, notes, and snippets.

@imnotbob
Last active April 17, 2020 01:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save imnotbob/5c850b85db06a5db4b0e6b941d9f74e1 to your computer and use it in GitHub Desktop.
Save imnotbob/5c850b85db06a5db4b0e6b941d9f74e1 to your computer and use it in GitHub Desktop.
New zxt120 driver for Hubitat
/**
* ZXT-120 IR Sender Unit from Remotec
*
* Author: ERS from Ronald Gouldner (based on b.dahlem@gmail.com version)
*
* Updates merged from Tim Owen for learning; resume commands
*
* Date: 2020-3-25
* Code: https://github.com/gouldner/ST-Devices/src/ZXT-120
*
* Copyright (C) 2016
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and - or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions: The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
def devVer() { return "1.0.2"}
/*
* The ZXT-120 does not "listen" for commands sent to the HVAC from other devics nor does it listen-receive status from the HVAC unit.
* It also does not report states correctly if asked for several items. On resume it is not likely to go back to the last operating state.
* This means if control of the HVAC unit externally the 'rememembered' state (HVAC mode, set temps) in this dth would be wrong
* until this dth sends new commands to the HVAC unit. Immediately after sending commands the unit should be in the requested state.
*
* KEEP THIS IN MIND WHEN DESIGNING YOUR AUTOMATIONS. IT IS A GOOD IDEA TO SEND THE SETTINGS YOU WANT and not believe previous settings will remain,
*,AND NOT HAVE OTHER THINGS CHANGE settings.
* THE ONLY BENFIT OF POLLING THIS DEVICE IS TO GET UPDATED BATTERY STATUS AND TEMPERATURE READING (SENSOR IS ON THE ZXT (not the HVAC))
*
* command optimizations in applications using this device MUST BE avoided because of the likely hood of inaccurate readings.
*/
preferences {
input("remoteCode", "number", title: "Remote Code", description: "The number of the remote to emulate")
input("tempOffset", "enum", title: "Temp correction offset (degrees C)?", options: ["-5","-4","-3","-2","-1","0","1","2","3","4","5"])
input("onCommand", "enum", title: "Command to send when 'On' Button is Pressed?", options: ["emulate(resume)","on(resume)","cool","heat","dry"])
input("learningPosition", "number", title: "Learning Position?", description: "IR Code to Store")
input("debugEnable", "bool", title: "Enable debug logging?")
}
metadata {
definition (name: "ZXT-120 IR Sender", namespace: "gouldner", author: "Ronald Gouldner", vid: "SmartThings-smartthings-Z-Wave_Thermostat") {
capability "Actuator"
capability "Temperature Measurement"
capability "Thermostat"
capability "Configuration"
capability "Polling"
capability "Sensor"
capability "Battery"
capability "Refresh"
capability "FanControl"
// Commands that this device-type exposes for controlling the ZXT-120 directly
// command "fanLow"
// command "fanMed"
// command "fanHigh"
// command "switchFanMode"
command "switchFanOscillate"
command "swingModeOn"
command "swingModeOff"
command "on"
command "off"
//commands for thermostat interface
command "fanOnly"
command "eco"
command "dry"
command "setDrySetpoint", ["number"]
command "setAutoSetpoint", ["number"]
command "autoChangeover"
command "setLearningPosition", ["number"]
command "issueLearningCommand"
/*
command "levelUpDown"
command "levelUp"
command "levelDown"
*/
attribute "swingMode", "STRING"
attribute "currentConfigCode", "STRING"
attribute "currentTempOffset", "STRING"
attribute "currentemitterPower", "STRING"
attribute "currentsurroundIR", "STRING"
attribute "drySetpoint", "STRING"
attribute "autoSetpoint", "STRING"
attribute "reportedCoolingSetpoint", "STRING"
attribute "reportedHeatingSetpoint", "STRING"
attribute "learningPosition", "NUMBER"
attribute "learningPositionTemp", "STRING"
// Z-Wave description of the ZXT-120 device
fingerprint deviceId: "0x0806"
fingerprint inClusters: "0x20,0x27,0x31,0x40,0x43,0x44,0x70,0x72,0x80,0x86"
}
}
def initialize() {
log.trace "initialize()"
}
def installed() {
log.trace "installed()"
String tempscale = getTemperatureScale()
def tz = location.timeZone
if(!tz || !(tempscale == "F" || tempscale == "C")) {
log.warn "Timezone (${tz}) or Temperature Scale (${tempscale}) not set"
}
// set some dummy values, until someone sends us commands
// the zxt does not 'read' state from the HVAC, so commands must be sent for it to reflect 'somewhat accurate' state.
if(tempscale=='F') {
sendEvent(name:"coolingSetpoint", value:80)
sendEvent(name:"heatingSetpoint", value:67)
}else{
sendEvent(name:"coolingSetpoint", value:28)
sendEvent(name:"heatingSetpoint", value:19)
}
sendEvent(name:"thermostatFanMode", value:'auto')
configure()
}
def ping() {
log.trace "ping()"
poll()
}
def configure() {
log.trace "configure()"
def cmd = [
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() ] +
setRemoteCode() + // update the device's remote code to ensure it provides proper mode info
setTempOffset() + [
zwave.thermostatModeV2.thermostatModeSupportedGet().format(), // Request the device's supported modes
zwave.thermostatFanModeV2.thermostatFanModeSupportedGet().format(), // Request the device's supported fan modes
]
cmd << zwave.configurationV1.configurationGet(parameterNumber: commandParameters["remoteCode"]).format() // remote code
cmd << zwave.configurationV1.configurationGet(parameterNumber: commandParameters["tempOffsetParam"]).format() // temp offset
cmd << zwave.configurationV1.configurationGet(parameterNumber: commandParameters["oscillateSetting"]).format() // oscillate setting
cmd << zwave.configurationV1.configurationGet(parameterNumber: commandParameters["emitterPowerSetting"]).format() // emitter setting
cmd << zwave.configurationV1.configurationGet(parameterNumber: commandParameters["surroundIRSetting"]).format() // surround IR setting
delayBetween(cmd, 1300)
}
def updated() {
log.trace "updated()"
Long t=now()
if(!state.updatedLastRanAt || t >= (Long)state.updatedLastRanAt + 2000L) {
state.updatedLastRanAt = t
state.tempUnit = getTemperatureScale()
unschedule()
/* this block will poll to auto-update the temperature reading at the zxt ever 3 mins
def random = new Random()
def random_int = random.nextInt(60)
def random_dint = random.nextInt(3)
schedule("${random_int} ${random_dint}/3 * * * ?", pollLite)
log.info "POLL-LITE scheduled (${random_int} ${random_dint}/3 * * * ?)" // every 3 mins
*/
state.swVersion = devVer()
if(debugEnable) runIn(1800,logsOff)
configure() // no commands after this
} else {
log.trace "updated(): Ran within last 2 seconds - SKIPPING"
return null
}
}
void logsOff() {
debug "debug logging disabled..."
device.updateSetting("debugEnable",[value:"false",type:"bool"])
}
/* NEEDED IF YOU UNCOMMENT auto-update
def pollLite() {
//log.info "PollLite.."
def tz = location.timeZone
def tempscale = getTemperatureScale()
if(!tz || !(tempscale == "F" || tempscale == "C")) {
log.warn "Timezone (${tz}) or Temperature Scale (${tempscale}) not set"
return
}
sendHubCommand(new hubitat.device.HubAction(zwave.sensorMultilevelV2.sensorMultilevelGet().format())) // current temperature
Date now=new Date()
String nowString = now.format("MMM/dd HH:mm",tz)
device.updateDataValue("lastPoll", nowString)
//sendEvent("name":"lastPoll", "value":nowString, displayed: false)
}
*/
def refresh() {
Date now=new Date()
def tz = location.timeZone
String nowString = now.format("MMM/dd HH:mm",tz)
device.updateDataValue("lastPoll", nowString)
def commands = []
commands << zwave.sensorMultilevelV2.sensorMultilevelGet().format() // current temperature
commands << zwave.batteryV1.batteryGet().format() // current battery level
// commands << zwave.thermostatModeV2.thermostatModeGet().format() // thermostat mode (seems to give back cool always?)
// commands << zwave.thermostatFanModeV2.thermostatFanModeGet().format() // fan speed
// add requests for each thermostat setpoint available on the device
// def supportedModes = getDataByName("supportedModes")
// for (Integer setpoint in [1,2,8,10]) { // these return junk values
// commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: setpoint).format()
// }
delayBetween(commands, standardDelay)
}
def poll() {
log.info "Polling"
refresh() // nothing after this
}
def parse(String description)
{
//log.info "Parsing Description=$description"
// BasicV1, BatteryV1, ConfigurationV1, ThermostatModeV2, ThermostatOperatingStateV1(NOT SUPPORTED), ThermostatSetpointV2, ThermostatFanModeV2, SensorMultilevelV1, SWITCHALLV1
//def map = createEvent(zwaveEvent(zwave.parse(description, [0x80:1, 0x70:1, 0x40:2, 0x42:1, 0x43:2, 0x44:2, 0x31:2, 0x27:1 ])))
// 0X20=Basic - V1 supported
// 0x27=Switch All - V1 supported
// 0X31=Sensor Multilevel - V1 supported
// 0X40=Thermostat Mode - V2 supported
// -- 0x42=Thermostat Operating State (NOT SUPPORTED, was in original device handler)
// 0x43=Thermostat Setpoint - V2 supported
// 0x44=Thermostat Fan Mode - V2 supported
// 0x70=Configuration - V1 supported
// 0x72=Manufacturer Specific - V1 supported
// 0x80=Battery - V1 supported
// 0x86=Version - V1 supported
def myzwave = zwave.parse(description, [0x20:1, 0x80:1, 0x70:1, 0x72:1, 0x40:2, 0x43:2, 0x44:2, 0x31:1, 0x27:1, 0x86:1 ])
def map = []
def result=null
if(myzwave) {
debug "parse myzwave is ${myzwave}"
result=zwaveEvent(myzwave)
map = createEvent(result)
} else {
debug "Non-parsed event. Perhaps wrong version is being handled?: ${description}"
return null
}
if(!map) {
//log.warn "parse called generating null map....why is this possible ? description=$description map=$map myzwave=$myzwave"
return null
}
debug "Parsed ${description} to command ${myzwave} to result ${result.inspect()} map=${map}"
result = [map]
String mapName=(String)map.name
String mapVal=(String)map.value
if(map && map.name in ["heatingSetpoint","coolingSetpoint","drySetpoint", "autoSetpoint", "thermostatMode"]) {
// fill in thermstatSetpoint and OperatingState as 'estimates'
def map2 = [
name: "thermostatSetpoint",
unit: getTemperatureScale(),
displayed: false
]
def map3 = [
name: "thermostatOperatingState",
displayed: false
]
if(mapName == "thermostatMode") {
String lastTriedMode=getDataByName("lastTriedMode")
if(lastTriedMode!=mapVal && mapVal!="off") updateState("lastTriedMode", mapVal)
if(mapVal == "cool") {
def t0=device.latestValue("coolingSetpoint")
// map2.value = t0
log.info "last stored cooling setpoint = ${t0}"
map3.value = "cooling"
}
else if(mapVal == "heat") {
def t0=device.latestValue("heatingSetpoint")
// map2.value = t0
log.info "last stored heating setpoint = ${t0}"
map3.value = "heating"
}
else if(mapVal == "dry") {
def t0=device.latestValue("drySetpoint")
// map2.value = t0
log.info "last stored dry setpoint = ${t0}"
map.value= "cool"
map3.value = "cooling"
}
else if(mapVal == "auto") {
def t0=device.latestValue("autoSetpoint")
// map2.value = t0
log.info "last stored auto setpoint = ${t0}"
map3.value = "heating"
}
else if(mapVal == "off") {
map3.value = "idle"
}
else if(mapVal == "fanOnly") {
map.value= "off"
map3.value = "fan only"
sendEvent(name:"thermostatFanMode", value: "on")
}
} else {
String mode = device.latestValue("thermostatMode")
//log.info "THERMOSTAT, latest mode = ${mode}"
if( (mapName == "heatingSetpoint" && mode == "heat") ||
(mapName == "coolingSetpoint" && mode == "cool") ||
(mapName == "drySetpoint" && mode == "dry") ||
(mapName == "autoSetpoint" && mode == "auto") ) {
map2.value = mapVal
map2.unit = map.unit
} else {
//log.debug "mode $mode did not match $map"
}
}
if(map2.value != null) {
debug "THERMOSTAT setpoint, adding setpoint event: $map2"
result << createEvent(map2)
}
if(map3.value != null) {
debug "THERMOSTAT, adding operating state event: $map3"
result << createEvent(map3)
}
} else if(mapName == "thermostatFanMode") {
updateState("lastTriedFanMode", mapVal)
}
//log.debug "Parse returned $result"
result
}
def zwaveEvent(hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) {
debug "ThermostatSetpointReport...cmd=$cmd"
def cmdScale = cmd.scale == 1 ? "F" : "C"
def map = [:]
def convertedDegrees = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
String tscale=getTemperatureScale()
map.value = "${ tscale=='F' ? Math.round(convertedDegrees.toDouble()) : convertedDegrees.toDouble()}".toString() // Math.trunc()?
map.unit = tscale
//map.displayed = false
switch (cmd.setpointType) {
case hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_HEATING_1:
map.name = "heatingSetpoint"
break;
case hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_COOLING_1:
map.name = "coolingSetpoint"
break;
case hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_DRY_AIR:
map.name = "drySetpoint"
break;
case hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_AUTO_CHANGEOVER:
map.name = "autoSetpoint"
break;
default:
debug "Thermostat Setpoint Report for setpointType ${cmd.setpointType} = ${map.value} ${map.unit}"
return [:]
}
// So we can respond with same format
state.scale = cmd.scale
//state.precision = cmd.precision
state.precision = 1
log.info "Thermostat Setpoint Report for ${map.name} = ${map.value} ${map.unit}"
map
}
def zwaveEvent(hubitat.zwave.commands.sensormultilevelv1.SensorMultilevelReport cmd) {
debug "SensorMultilevelReport...cmd=$cmd"
def map = [:]
switch (cmd.sensorType) {
case 1:
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
//log.info "SensorMultilevelReport temperature map.value=${map.value} ${map.unit}"
break;
default:
log.warn "Unknown sensorType ${cmd.sensorType} from device" // 5 is humidity in V2 and later
break;
}
map
}
def zwaveEvent(hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport cmd) {
def map = [:]
map.name = "thermostatFanMode"
def map1=[:]
map1.name = "speed"
debug "FanModeReport $cmd"
switch (cmd.fanMode) {
case hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
map.value = "auto"
map1.value = "low"
break
case hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_AUTO_HIGH:
map.value = "auto"
map1.value = "high"
break
case hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_AUTO_MEDIUM:
map.value = "auto"
map1.value = "medium"
break
case hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_LOW:
map.value = "on"
map1.value = "low"
break
case hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_HIGH:
map.value = "on"
map1.value = "high"
break
case hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_MEDIUM:
map.value = "on"
map1.value = "medium"
break
/*
case hubitat.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION:
map.value = "circulate"
break
*/
}
device.updateDataValue("actualFanMode", "${cmd.fanMode}")
log.info "FanModeReport ${map.value} ${cmd.fanMode}"
map
}
def zwaveEvent(hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [:]
debug "ThermostatModeReport $cmd"
switch (cmd.mode) {
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off"
break
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "heat"
break
/*
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT:
map.value = "emergencyHeat"
break
*/
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL:
map.value = "cool"
break
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO:
map.value = "auto"
break
//non standard
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_RESUME:
map.value = "resume"
break
// non standard - operating state
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_FAN_ONLY:
map.value = "fanOnly"
break
//non standard
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_DRY_AIR:
map.value = "dry"
break
//non standard
case hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO_CHANGEOVER:
map.value = "autoChangeover"
break
}
device.updateDataValue("actualMode", "${cmd.mode}")
map.name = "thermostatMode"
log.info "Thermostat Mode reported : ${map.value.toString().capitalize()} ${cmd.mode}"
map
}
def zwaveEvent(hubitat.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
def map = [:]
debug "thermostatModeSupported $cmd"
def val = []
String supportedModes = ""
if(cmd.off || cmd.off==null) { supportedModes += "off "; val << "off" }
if(cmd.heat || cmd.heat==null) { supportedModes += "heat "; val << "heat" }
//if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergencyHeat " }
if(cmd.cool || cmd.cool==null) { supportedModes += "cool "; val << "cool" }
if(cmd.auto || cmd.auto==null) { supportedModes += "auto "; val << "auto" }
// nonstandard
if(cmd.fanOnly) { supportedModes += "fanOnly " }
if(cmd.dryAir) { supportedModes += "dry " }
if(cmd.resume || cmd.resume==null ) { supportedModes += "resume " }
//if(cmd.autoChangeover) { supportedModes += "autoChangeover " }
if(val) {
map.name = "supportedThermostatModes"
map.value=val
}
log.info "Supported Modes: ${supportedModes}"
updateState("supportedModes", supportedModes)
map
}
def zwaveEvent(hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeSupportedReport cmd) {
def map = [:]
debug "fanModeSupported $cmd"
def val=[]
String supportedFanModes = ""
if(cmd.auto) { supportedFanModes += "fanAuto "; val << "auto" }
if(cmd.low) { supportedFanModes += "fanLow " }
if(cmd.medium) { supportedFanModes += "fanMedium " }
if(cmd.high) { supportedFanModes += "fanHigh " }
val << "on"
if(val) {
map.name = "supportedThermostatFanModes"
map.value=val
}
log.info "Supported Fan Modes: ${supportedFanModes}"
updateState("supportedFanModes", supportedFanModes)
map
}
def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
debug "Battery report $cmd"
map.name = "battery"
map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1
map.unit = "%"
map.displayed = false
map
}
private Map getCommandParameters() { [
"remoteCode": 27,
"tempOffsetParam": 37,
"oscillateSetting": 33,
"emitterPowerSetting": 28,
"surroundIRSetting": 32,
"learningMode": 25,
]}
def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
def map = [:]
debug "ConfigurationReport $cmd"
map.displayed = false
switch (cmd.parameterNumber) {
// remote code
case (Integer)commandParameters["remoteCode"]:
map.name = "currentConfigCode"
def short remoteCodeLow = cmd.configurationValue[1]
def short remoteCodeHigh = cmd.configurationValue[0]
map.value = (remoteCodeHigh << 8) + remoteCodeLow
//log.info "reported currentConfigCode=$map.value"
break
case (Integer)commandParameters["tempOffsetParam"]:
map.name = "currentTempOffset"
def short offset = cmd.configurationValue[0]
if(offset >= 0xFB) { // Hex FB-FF represent negative offsets FF=-1 - FB=-5
offset = offset - 256
}
map.value = offset
//log.info "reported offset=$map.value C"
break
case (Integer)commandParameters["emitterPowerSetting"]:
String power = (cmd.configurationValue[0] == 0) ? "normal" : "high"
map.name = "currentemitterPower"
map.value = power
//log.info "reported power ${cmd.configurationValue[0]} ${power}"
break
case (Integer)commandParameters["surroundIRSetting"]:
String surround = (cmd.configurationValue[0] == 0) ? "disabled" : "enabled"
map.name = "currentsurroundIR"
map.value = surround
//log.info "reported surround ${cmd.configurationValue[0]} ${surround}"
break
case (Integer)commandParameters["oscillateSetting"]:
String oscillateMode = (cmd.configurationValue[0] == 0) ? "off" : "auto" // THIS IS OFF, AUTO (default)
map.name = "swingMode"
map.value = oscillateMode
//log.info "reported swing mode = ${oscillateMode}"
state.swingMode = oscillateMode
break
default:
log.warn "Unknown configuration report ${cmd.parameterNumber}"
break;
}
map
}
def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv1.ManufacturerSpecificReport cmd) {
debug "MSR report $cmd"
def map = [:]
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
device.updateDataValue("MSR", msr)
device.updateDataValue("manufacturer", cmd.manufacturerName)
//createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
map
}
def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {
def map = [:]
debug "Basic report: $cmd typically on/off reports"
map
}
def zwaveEvent(hubitat.zwave.Command cmd) {
def map = [:]
log.warn "Unexpected zwave command $cmd"
map
}
private Map getModeMap() { [
//ENUM ["auto", "off", "heat", "emergency heat", "cool"]
"off": 0, //hubitat.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_OFF,
"heat": 1, //hubitat.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_HEAT,
"cool": 2, //hubitat.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_COOL,
"auto": 3, //hubitat.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_AUTO,
// these are non-standard
"fanOnly": 6,
"resume": 5, // ON //hubitat.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_RESUME,
"dry": 8, //hubitat.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_DRY_AIR,
"autoChangeover": 10 //hubitat.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_AUTO_CHANGEOVER
]}
def setThermostatMode(String value) {
def commands = []
def degrees = 0
log.debug "setting thermostat mode $value ${modeMap[value]}"
commands << zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format()
commands << zwave.thermostatModeV2.thermostatModeGet().format()
if(value == "cool") {
degrees = device.currentValue("coolingSetpoint")
if(degrees) {
commands << setCoolingSetpoint(degrees, true, true)
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 2).format()
} else log.warn "mode is cool without stored coolingSetpoint"
} else if(value == "heat") {
degrees = device.currentValue("heatingSetpoint")
if(degrees) {
commands << setHeatingSetpoint(degrees, true, true)
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 1).format()
} else log.warn "mode is heat without stored heatingSetpoint"
} else if(value in ["auto", "dry"]) {
degrees = device.currentValue("thermostatSetpoint")
if(degrees) {
if(value == "auto"){
commands << setAutoSetpoint(degrees, true, true)
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 10).format()
}else{
commands << setDrySetpoint(degrees, true, true)
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 8).format()
}
} else log.warn "mode is auto without stored thermostatSetpoint"
} else if(value in ["dry","off", "resume", "fanOnly"]) {
debug "Dry, Resume or Off, fanOnly, no need to send temp"
} else {
log.warn("Unknown thermostat mode set:$value")
return null
}
delayBetween(commands, 2300)
}
def auto() {
log.debug "${device.name} received AUTO request"
setThermostatMode("auto")
}
def cool() {
log.debug "${device.name} received COOL request"
setThermostatMode("cool")
}
def emergencyHeat() {
log.warn "setting emergencyheat() not supported"
return null
setThermostatMode("emergencyHeat")
}
def heat() {
log.debug "${device.name} received HEAT request"
setThermostatMode("heat")
}
def off() {
log.debug "${device.name} received OFF request"
setThermostatMode("off")
}
def on() {
String onCommandVal = onCommand == null ? "emulate(resume)" : onCommand
log.debug "${device.name} received on request onCommandVal=${onCommandVal}"
//input("onCommand", "enum", title: "Command to send when 'On' Button is Pressed?", options: ["on(resume)","emulate(resume)","cool","heat","dry"])
switch (onCommandVal) {
case "emulate(resume)":
String lastTriedMode=getDataByName("lastTriedMode")
debug "emulating resume $lastTriedMode"
if(lastTriedMode) { setThermostatMode(lastTriedMode); break }
else log.warn "resume command without saved mode $lastTriedMode"
case "on(resume)":
debug "issuing setThermostatMode:on"
setThermostatMode("resume")
break;
case ['cool','heat','dry']:
debug "issuing setThermostatMode:${onCommandVal}"
setThermostatMode(onCommandVal)
break;
default:
log.warn "Configuration Error: unknown onCommandVal: ${onCommandVal}"
return null
break;
}
}
def fanOnly() {
log.debug "${device.name} received fanOnly mode request"
setThermostatMode("fanOnly")
}
def eco() {
log.debug "${device.name} received ECO request"
setThermostatMode("off")
}
def dry() {
log.debug "${device.name} received DRY request"
setThermostatMode("dry")
}
def autoChangeover() {
log.debug "${device.name} received AUTOCHANGEOVER request"
setThermostatMode("autoChangeover")
}
/*
private Map getSetpointMap() { [
"heatingSetpoint": hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_HEATING_1,
"coolingSetpoint": hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_COOLING_1,
"drySetpoint": hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_DRY_AIR,
"autoSetpoint": hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointSet.SETPOINT_TYPE_AUTO_CHANGEOVER
]}
*/
def setHeatingSetpoint(degrees) {
setHeatingSetpoint(degrees.toDouble())
}
def setHeatingSetpoint(Double degrees, Boolean nocheck = false, Boolean returnCommand=false, Integer delay = 2300) {
log.trace "setHeatingSetpoint($degrees, $delay)"
def commands = []
String hvacMode = device.latestValue("thermostatMode")
def convertedDegrees = checkValidTemp(degrees)
if(nocheck || hvacMode in ["heat"]) {
convertedDegrees=convertToDevice(convertedDegrees)
def deviceScale = state?.scale != null ? state.scale : 1
String deviceScaleString = deviceScale == 1 ? "F" : "C"
debug "setHeatingSetpoint({$convertedDegrees} ${deviceScaleString})"
Integer p = (state?.precision == null) ? 1 : state.precision
def cmd = zwave.thermostatSetpointV2.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format()
if(returnCommand) return cmd
commands << cmd
} else {
log.warn "setting heatingSetpoint but cannot change heat setpoint due to hvacMode: ${hvacMode}"
sendEvent(name:'heatingSetpoint', value: convertedDegrees)
return null
}
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 1).format()
delayBetween(commands, delay)
}
def setCoolingSetpoint(degrees) {
setCoolingSetpoint(degrees.toDouble())
}
def setCoolingSetpoint(Double degrees, Boolean nocheck = false, Boolean returnCommand=false, Integer delay=2300) {
log.trace "setCoolingSetpoint($degrees, $delay)"
def commands = []
String hvacMode = device.latestValue("thermostatMode")
def convertedDegrees = checkValidTemp(degrees)
if(nocheck || hvacMode in ["cool"]) {
convertedDegrees=convertToDevice(convertedDegrees)
def deviceScale = state?.scale != null ? state.scale : 1
String deviceScaleString = deviceScale == 1 ? "F" : "C"
debug "setCoolingSetpoint({$convertedDegrees} ${deviceScaleString})"
Integer p = (state?.precision == null) ? 1 : state.precision
def cmd= zwave.thermostatSetpointV2.thermostatSetpointSet(setpointType: 2, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format()
if(returnCommand) return cmd
commands << cmd
} else {
log.warn "setting coolingSetpoint but cannot change cool setpoint due to hvacMode: ${hvacMode}"
sendEvent(name:'coolingSetpoint', value: convertedDegrees)
return null
}
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 2).format()
delayBetween(commands, delay)
}
def setDrySetpoint(degrees) {
setDrySetpoint(degrees.toDouble())
}
def setDrySetpoint(Double degrees, nocheck = false, Integer delay = 2300) {
log.trace "setDrySetpoint($degrees, $delay)"
def commands = []
String hvacMode = device.latestValue("thermostatMode")
def convertedDegrees = checkValidTemp(degrees)
if(nocheck || hvacMode in ["dry"]) {
convertedDegrees=convertToDevice(convertedDegrees)
def deviceScale = state?.scale != null ? state.scale : 1
String deviceScaleString = deviceScale == 1 ? "F" : "C"
debug "setDrySetpoint({$convertedDegrees} ${deviceScaleString})"
Integer p = (state?.precision == null) ? 1 : state.precision
commands << zwave.thermostatSetpointV2.thermostatSetpointSet(setpointType: 8, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format()
} else {
log.warn "setting drySetpoint but cannot change dry setpoint due to hvacMode: ${hvacMode}"
sendEvent(name:'drySetpoint', value: convertedDegrees)
return null
}
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 8).format()
delayBetween(commands, delay)
}
def setAutoSetpoint(degrees) {
setAutoSetpoint(degrees.toDouble())
}
def setAutoSetpoint(Double degrees, nocheck = false, Integer delay = 2300) {
log.trace "setAutoSetpoint($degrees, $delay)"
def commands = []
String hvacMode = device.latestValue("thermostatMode")
def convertedDegrees = checkValidTemp(degrees)
if(nocheck || hvacMode in ["auto"]) {
convertedDegrees=convertToDevice(convertedDegrees)
def deviceScale = state?.scale != null ? state.scale : 1
String deviceScaleString = deviceScale == 1 ? "F" : "C"
debug "setAutoSetpoint({$convertedDegrees} ${deviceScaleString})"
Integer p = (state?.precision == null) ? 1 : state.precision
commands << zwave.thermostatSetpointV2.thermostatSetpointSet(setpointType: 10, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format()
} else {
log.warn "setting autoSetpoint but cannot change auto setpoint due to hvacMode: ${hvacMode}"
sendEvent(name:'autoSetpoint', value: convertedDegrees)
return null
}
commands << zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 10).format()
delayBetween(commands, delay)
}
def convertToDevice(degrees) {
def deviceScale = state?.scale != null ? state.scale : 1
String deviceScaleString = deviceScale == 1 ? "F" : "C"
String locationScale = getTemperatureScale()
def convertedDegrees = degrees
if(locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees).round(0).toInteger
} else if(locationScale == "F" && deviceScaleString == "C") {
def tval = fahrenheitToCelsius(degrees).toDouble()
convertedDegrees = Math.round(tval.round(1) * 2) / 2.0f
//convertedDegrees = tval.round(0)
debug "tval ${tval} ${convertedDegrees}"
}
Integer p = (state?.precision == null) ? 1 : state.precision
switch (p) {
case 0:
convertedDegrees = Math.ceil(convertedDegrees)
break
default:
break
}
return convertedDegrees
}
def checkValidTemp(degrees) {
String locationScale = getTemperatureScale()
def override = false
def overrideDegrees = degrees
if(locationScale == "F") {
overrideDegrees = Math.round(overrideDegrees)
// ZXT-120 lowest settings is 67
if(overrideDegrees < 67) {
overrideDegrees = 67
override = true
}
// ZXT-120 highest setting is 84
if(overrideDegrees > 84) {
overrideDegrees = 84
override = true
}
} else if(locationScale == "C") {
overrideDegrees = Math.round(overrideDegrees * 2.0D) / 2.0D
// ZXT-120 lowest settings is 19 C
if(overrideDegrees < 19) {
overrideDegrees = 19
override = true
}
// ZXT-120 highest setting is 28 C
if(overrideDegrees > 28) {
overrideDegrees = 28
override = true
}
} else { log.error "checkValidTemp: unknown device scale" }
if(override || degrees!=overrideDegrees) {
log.warn "overriding temp ${degrees} to ${overrideDegrees}"
}
return overrideDegrees
}
def setLearningPosition(position) {
log.debug "Setting learning postition: $position"
sendEvent("name":"learningPosition", "value":learningPosition)
def ctemp = 0
if (position < 12) {
ctemp=position+17
} else {
ctemp=position+7
}
def ftempLow=(Math.ceil(((ctemp*9)/5)+32)).toInteger()
def ftempHigh=ftempLow+1
String positionTemp = "not set"
switch (position) {
case 0:
positionTemp = 'Off'
break
case 1:
positionTemp = 'On(resume)'
break
case [3,4,5,6,8,9,10,11]:
positionTemp = "cool ${ctemp}C ${ftempLow}-${ftempHigh}F"
break
case [2,7]:
positionTemp = "cool ${ctemp}C ${ftempLow}F"
break
case [13,14,15,16,18,19,20,21]:
positionTemp = "heat ${ctemp}C ${ftempLow}-${ftempHigh}F"
break
case [12,17]:
positionTemp = "heat ${ctemp}C ${ftempLow}F"
break
case 22:
positionTemp = 'Dry mode'
break
default:
positionTemp = 'Invalid'
break
}
sendEvent("name":"learningPositionTemp", "value":positionTemp)
}
def issueLearningCommand() {
Integer position = device.currentValue("learningPosition").toInteger()
log.debug "Issue Learning Command pressed Position Currently: $position"
def positionConfigArray = [position]
log.debug "Position Config Array: ${positionConfigArray}"
delayBetween ([
// Send the new remote code
zwave.configurationV1.configurationSet(configurationValue: positionConfigArray,
parameterNumber: commandParameters["learningMode"], size: 1).format()
])
}
def setRemoteCode() {
Integer remoteCodeVal = remoteCode.toInteger()
// Divide the remote code into a 2 byte value
def short remoteCodeLow = remoteCodeVal & 0xFF
def short remoteCodeHigh = (remoteCodeVal >> 8) & 0xFF
def remoteBytes = [remoteCodeHigh, remoteCodeLow]
log.info "Setting Remote Code: ${remoteBytes}"
[
zwave.configurationV1.configurationSet(configurationValue: remoteBytes, parameterNumber: commandParameters["remoteCode"], size: 2).format(),
zwave.configurationV1.configurationGet(parameterNumber: commandParameters["remoteCode"]).format()
]
/* caller will send these
delayBetween ([
zwave.configurationV1.configurationSet(configurationValue: remoteBytes, parameterNumber: commandParameters["remoteCode"], size: 2).format(),
zwave.configurationV1.configurationGet(parameterNumber: commandParameters["remoteCode"]).format()
], standardDelay)
*/
}
def setTempOffset() {
def tempOffsetVal = tempOffset == null ? 0 : tempOffset.toInteger()
if(tempOffsetVal < 0) { // Convert negative values into hex value for this param -1 = 0xFF -5 = 0xFB
tempOffsetVal = 256 + tempOffsetVal
}
def configArray = [tempOffsetVal]
log.info "Setting TempOffset: ${tempOffsetVal}"
[
zwave.configurationV1.configurationSet(configurationValue: configArray, parameterNumber: commandParameters["tempOffsetParam"], size: 1).format(),
zwave.configurationV1.configurationGet(parameterNumber: commandParameters["tempOffsetParam"]).format()
]
/* caller will send these
delayBetween ([
zwave.configurationV1.configurationSet(configurationValue: configArray, parameterNumber: commandParameters["tempOffsetParam"], size: 1).format(),
zwave.configurationV1.configurationGet(parameterNumber: commandParameters["tempOffsetParam"]).format()
], standardDelay)
*/
}
def switchFanOscillate() {
def swingMode = (getDataByName("swingMode") == "off") ? true : false // Load the current swingmode and invert it (Off becomes true, On becomes false)
setFanOscillate(swingMode)
}
def swingModeOn() {
log.debug "Setting Swing mode AUTO"
setFanOscillate(true)
}
def swingModeOff() {
log.debug "Setting Swing mode Off"
setFanOscillate(false)
}
def setFanOscillate(Boolean swingMode) {
def swingValue = swingMode ? 1 : 0 // Convert the swing mode requested to 1 for on, 0 for off
def hvacMode = device.latestValue("thermostatMode")
if( !(hvacMode in ["heat","cool","auto","dry"]) ) {
log.warn "wrong mode ${hvacMode}"
} else {
delayBetween ([
zwave.configurationV1.configurationSet(configurationValue: [swingValue], parameterNumber: commandParameters["oscillateSetting"], size: 1).format(),
zwave.configurationV1.configurationGet(parameterNumber: commandParameters["oscillateSetting"]).format()
], standardDelay)
}
}
List fanModes() {
["fanAuto", "fanOn", "fanLow", "fanMedium", "fanHigh"]
}
def switchFanMode() {
String currentMode = device.currentState("thermostatFanMode")?.value
if(currentMode == "auto") { currentMode = "fanAuto" }
else if(currentMode == "on") { currentMode = "fanLow" }
else { currentMode == null }
String lastTriedFMode = getDataByName("lastTriedFanMode") ?: currentMode.value ?: "fanAuto"
def supportedModes = getDataByName("supportedFanModes") ?: "fanAuto fanLow"
List modeOrder = fanModes()
//log.info modeOrder
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
String nextMode = next(lastTriedFMode)
while (!supportedModes?.contains(nextMode) && nextMode != "fanAuto") {
nextMode = next(nextMode)
}
switchToFanMode(nextMode)
}
def switchToFanMode(String nextMode) {
def supportedFanModes = getDataByName("supportedFanModes")
if(supportedFanModes && !supportedFanModes.tokenize()?.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
if(nextMode in fanModes()) {
updateState("lastTriedFanMode", nextMode)
return "$nextMode"()
} else {
log.warn("no fan mode method '$nextMode'")
return null
}
}
def setThermostatFanMode(String value) {
log.info "fan mode " + value + " ${fanModeMap[value]}"
delayBetween([
zwave.thermostatFanModeV2.thermostatFanModeSet(fanMode: fanModeMap[value]).format(),
zwave.thermostatFanModeV2.thermostatFanModeGet().format()
], standardDelay)
}
def getFanModeMap() { [
"auto": 0, //hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_AUTO_LOW,
"circulate": 1, // hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_AUTO_LOW,
"on": 1, //hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_LOW,
"fanAuto": 0, //hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_AUTO_LOW,
"fanOn": 1, //hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_LOW,
"fanLow": 1, //hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_LOW,
"fanMedium": 5, //hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_MEDIUM,
"fanHigh": 3 //hubitat.zwave.commands.thermostatfanmodev2.ThermostatFanModeReport.FAN_MODE_HIGH
]}
def fanAuto() {
log.debug "${device.name} received FANAUTO request"
setThermostatFanMode("auto")
/* delayBetween([
zwave.thermostatFanModeV2.thermostatFanModeSet(fanMode: 0).format(),
zwave.thermostatFanModeV2.thermostatFanModeGet().format()
], standardDelay)*/
}
def fanCirculate() {
log.warn "fanCirculate() not supported"
return
/* delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)*/
}
def fanOn() {
log.debug "${device.name} received FANON request"
setThermostatFanMode("on")
/* delayBetween([
zwave.thermostatFanModeV2.thermostatFanModeSet(fanMode: 1).format(),
zwave.thermostatFanModeV2.thermostatFanModeGet().format()
], standardDelay)*/
}
def setSpeed(fanspeed){
log.debug "${device.name} received setSpeed $fanspeed request"
// if off, turn on and put thermostat into fan mode
// ENUM ["low","medium-low","medium","medium-high","high","on","off","auto"]
//["fanAuto", "fanOn", "fanLow", "fanMedium", "fanHigh"]
switch(fanspeed) {
case ['low','low-medium']:
setThermostatFanMode("fanLow")
break
case ['medium','medium-high']:
setThermostatFanMode("fanMedium")
break
case 'high':
setThermostatFanMode("fanHigh")
break
case 'on':
setThermostatFanMode("fanOn")
break
case 'auto':
case 'off':
setThermostatFanMode("fanAuto")
break
default:
log.warn "setSpeed: unknown speed"
}
}
def fanLow() {
log.debug "setting fan mode low"
setThermostatFanMode("fanLow")
}
def fanMed() {
log.debug "setting fan mode med"
setThermostatFanMode("fanMedium")
}
def fanHigh() {
log.debug "setting fan mode high"
setThermostatFanMode("fanHigh")
}
private getStandardDelay() {
1000
}
def updateState(String name, String value) {
state[name] = value
// sendEvent(name: name, value: value, displayed: false)
//device.updateDataValue(name, value)
}
def getDataByName(String name) {
//state[name] ?: device.getDataValue(name)
state[name] ?: null // device.currentState(name)?.value
}
void debug(String msg) {
if(debugEnable) log.debug msg
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment