That did the trick - thanks Duncan !
Only two peculiar phenomenon remain unexplained:
- When the device is edited (in the app) and the preferences inputs are entered, the routine updated() gets executed two times. It only gets executed one time on handler installation.
2.The displayed values of the inputs when the device is edited (in the app) are those last entered, not the changed settings values after I change settings in updated() (to limit input values to those allowed).
It would be nice to know why and how to fix those peculiarities. Anyone have a clue as to why, please respond and I will attempt to fix the code.
CHANGES MADE:
I added battery status (in case anyone actually uses a battery instead of 24 volts)
I also added three configuration settings: Calibration; Auto report after elapsed time; Auto report on temperature change.
I removed the 'Configure" tile as it is not needed. configure is done on installation or device edit (in the app).
There are many other configuration parameters for this thermostat but I only added the ones I wanted to change. However, the code gives others enough of an example as to how to add additional configurations inputs, setting and displaying the values. I invite others to continue enhancing the code.
For those that want to use this device handler, I include the code below:
/**
* ZTS-110 Thermostat by Remotec
*
* Copyright 2016 Dennis Spanogle / Template used Copyright Smart Things
*
* Modified Smart Things generic Z-Wave Thermostat
* added 3 configuration parameters (for now)
* added battery status report (in case user selects battery)
*
* 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.
*
*
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "ZTS-110 Thermostat", namespace: "dspanogle", author: "SmartThings + Dennis Spanogle (DES)") {
capability "Actuator"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Thermostat"
capability "Configuration"
capability "Polling"
capability "Sensor"
capability "Battery" // DES
attribute "thermostatFanState", "string" //DES added , "string" (This does not seem to be used)
attribute "Calibration", "number" // DES added
attribute "Autorpt_DegF", "number" // DES added
attribute "Autorpt_Hr", "number" // DES added
command "switchMode"
command "switchFanMode"
command "quickSetCool"
command "quickSetHeat"
// DES replaced fingerprint to match the raw descrption from theRomotec ZTS-110
// fingerprint deviceId: "0x08"
// fingerprint inClusters: "0x43,0x40,0x44,0x31"
// raw description for ZTS-110: 0 0 0x1001 0 0 0 4 0x25 0x27 0x86 0x72
fingerprint deviceId: "0x1001"
fingerprint inClusters: "0x25,0x27,0x86,0x72", manufacturer: "Remotec", model: "ZTS-110"
}
// simulator metadata
simulator {
status "off" : "command: 4003, payload: 00"
status "heat" : "command: 4003, payload: 01"
status "cool" : "command: 4003, payload: 02"
status "auto" : "command: 4003, payload: 03"
status "emergencyHeat" : "command: 4003, payload: 04"
status "fanAuto" : "command: 4403, payload: 00"
status "fanOn" : "command: 4403, payload: 01"
status "fanCirculate" : "command: 4403, payload: 06"
status "heat 60" : "command: 4303, payload: 01 09 3C"
status "heat 68" : "command: 4303, payload: 01 09 44"
status "heat 72" : "command: 4303, payload: 01 09 48"
status "cool 72" : "command: 4303, payload: 02 09 48"
status "cool 76" : "command: 4303, payload: 02 09 4C"
status "cool 80" : "command: 4303, payload: 02 09 50"
status "temp 58" : "command: 3105, payload: 01 2A 02 44"
status "temp 62" : "command: 3105, payload: 01 2A 02 6C"
status "temp 70" : "command: 3105, payload: 01 2A 02 BC"
status "temp 74" : "command: 3105, payload: 01 2A 02 E4"
status "temp 78" : "command: 3105, payload: 01 2A 03 0C"
status "temp 82" : "command: 3105, payload: 01 2A 03 34"
status "idle" : "command: 4203, payload: 00"
status "heating" : "command: 4203, payload: 01"
status "cooling" : "command: 4203, payload: 02"
status "fan only" : "command: 4203, payload: 03"
status "pending heat" : "command: 4203, payload: 04"
status "pending cool" : "command: 4203, payload: 05"
status "vent economizer": "command: 4203, payload: 06"
// reply messages
reply "2502": "command: 2503, payload: FF"
}
// DES changed the layout scale and height, width for all tiles
tiles (scale: 2) {
valueTile("temperature", "device.temperature", width: 4, height: 4) {
state("temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
// DES removed depreciated inactiveLabel: false from all tiles
standardTile("mode", "device.thermostatMode", decoration: "flat", width: 2, height: 2) {
state "off", label:'${name}', action:"switchMode", nextState:"to_heat"
state "heat", label:'${name}', action:"switchMode", nextState:"to_cool"
state "cool", label:'${name}', action:"switchMode", nextState:"..."
state "auto", label:'${name}', action:"switchMode", nextState:"..."
state "emergencyHeat", label:'${name}', action:"switchMode", nextState:"..."
state "to_heat", label: "heat", action:"switchMode", nextState:"to_cool"
state "to_cool", label: "cool", action:"switchMode", nextState:"..."
state "...", label: "...", action:"off", nextState:"off"
}
standardTile("fanMode", "device.thermostatFanMode", decoration: "flat", width: 2, height: 2) {
state "fanAuto", label:'${name}', action:"switchFanMode"
state "fanOn", label:'${name}', action:"switchFanMode"
state "fanCirculate", label:'${name}', action:"switchFanMode"
}
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 4) {
state "setHeatingSetpoint", action:"quickSetHeat", backgroundColor:"#d04e00"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", decoration: "flat", width: 2, height: 1) {
state "heat", label:'${currentValue}° heat', backgroundColor:"#ffffff"
}
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 4) {
state "setCoolingSetpoint", action:"quickSetCool", backgroundColor: "#1e9cbb"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", decoration: "flat", width: 2, height: 1) {
state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff"
}
standardTile("refresh", "device.thermostatMode", decoration: "flat", width: 2, height: 2) {
state "default", action:"polling.poll", icon:"st.secondary.refresh"
}
// DES since edit device in app returns configure, this tile is really not needed
//standardTile("configure", "device.configure", decoration: "flat", width: 2, height: 2) {
//state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
//}
// DES added This tile to match attribute attribute "Calibration", "number"
valueTile("Calibration", "device.Calibration", decoration: "flat", width: 2, height: 1) {
state "Calibration", label:'Cal: ${currentValue}°' //, action:"configuration.configure"
}
valueTile("Autorpt_Hr", "device.Autorpt_Hr", decoration: "flat", width: 2, height: 1) {
state "Autorpt_Hr", label:'Rpt: ${currentValue}hr' // , action:"configuration.configure"
}
valueTile("Autorpt_DegF", "device.Autorpt_DegF", decoration: "flat", width: 2, height: 1) {
state "Autorpt_DegF", label:'Rpt: ${currentValue}°F' //, action:"configuration.configure"
}
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 1) {
state "battery", label:'Bat: ${currentValue}%', unit:""
}
main "temperature"
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl",
"coolingSetpoint", "refresh", "configure", "Calibration", "Autorpt_DegF", "Autorpt_Hr", "battery" ])
}
}
// DES add preferences
// ** Get inputs for the configuration (only 3 of the possible configurations are included at this time)
preferences {
// parm 13 = calibration -3,-2,-1,0,1,2, 3 Etc
input "cal", "number", title: "Calibration: -10°F to +10°F (0=default)",
description: "Offset -2,-1,0 (default),1,2 Etc", defaultValue: 0,
required: false //, displayDuringSetup: true // also displayed during edit in mobile app
// parm 12 = autoreport time trigger: 0 = never, 1 = 30 min, 2 = 1 hr (default)
input "RptTime", "number", title: "Auto Report Time: 0=Never, 1 to 16 half hrs (2=default=1hr)",
description: "0=Never, 1=.5hr, 2=1hr (default), 3=1.5hr, .. max 16=8hr", defaultValue: 2,
required: false //, displayDuringSetup: true
// parm 11 Trigger autoreport if room temp different from last report
// 1 = 1 deg F, 2 = 2 degree F, 3 = 3, 4 = 4 deg F = default Set to 1
input "RptDelta", "number", title: "Auto Report °F Change: 0=Never, 1°F to 8°F (4=default)",
description: "0=Never, 1=1°F, 2=2°F, 3=3°F, 4=4°F (default), .. max8=8°F", defaultValue: 4,
required: false //, displayDuringSetup: true
}
// DES add battery Event Generation
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} Low Battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
state.lastbatt = now()
// log.debug "Got Battery status: ${map}"
map
}
// DES add Configuration Event Generation
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
//log.debug " in zwave Event ConfigurationReport"
def map = [:]
def value = cmd.configurationValue[0]
switch (cmd.parameterNumber) {
case 11:
map.name = "Autorpt_DegF"
if (value < 0){value = 0}
if (value > 8){value = 8}
map.value = value
break
case 12:
map.name = "Autorpt_Hr"
if (value < 0){value = 0} // DES These probably redundant
if (value > 16){value = 16}
map.value = value/2 // report in hours instead of half hours as entered.
break
case 13:
map.name = "Calibration"
if (value > 128){
value = value - 256
}
map.value = value
//log.debug " did Cal map cal = ${map.value}"
break
}
map
}
// DES added updated()
def updated() {
log.trace "Updated Input Settings: ${settings}"
if(settings.cal > 10){settings.cal = 10} // calibration must be between -10 ann +10
if(settings.cal < -10){settings.cal = -10}
if(settings.RptTime > 16){settings.RptTime = 16} // Every 8 hours is max
if(settings.RptTime < 0){settings.RptTime = 0} // Note 0 disables auto report by time
if(settings.RptDelta > 8){settings.RptDelta = 8} // delta of 8 degrees is max
if(settings.RptDelta < 0){settings.RptDelta = 0} // Note 0 disables auto report if different than last
log.trace "Updated Corrected Settings: ${settings}"
response(configure())
}
// DES modified configure()
def configure() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),
zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format(),
zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()
], 2300)
// parm 13 = calibration -2,-1,0,1,2 Etc
// parm 12 = Time elapse auto report 0 = never, 1 = .5 hr, 2 = 1 hr (default), etc
// parm 11 Trigger autoreport if room temp different from last report
// 0 = never, 1 = 1 deg F, 2 = 2 degree F, 3 = 3, 4 = 4 deg F (default)
// if(settings.RptTime > 16){settings.RptTime = 16} // Every 8 hours is max
log.debug "Setting Configuration cal=${settings.cal}, RptTime= ${settings.RptTime}, RptDelta = ${settings.RptDelta}" //des
try
{
delayBetween([
zwave.configurationV1.configurationSet(parameterNumber: 13, size: 1, scaledConfigurationValue: settings.cal ).format(),
zwave.configurationV1.configurationSet(parameterNumber: 12, size: 1, scaledConfigurationValue: settings.RptTime ).format(),
zwave.configurationV1.configurationSet(parameterNumber: 11, size: 1, scaledConfigurationValue: settings.RptDelta ).format(),
// Get reports and update value tiles
zwave.configurationV1.configurationGet(parameterNumber: 13).format(),
zwave.configurationV1.configurationGet(parameterNumber: 12).format(),
zwave.configurationV1.configurationGet(parameterNumber: 11).format()
], 2300)
}
catch (e)
{
log.error "something went wrong in configure: $e"
}
}
// DES modified Command Implementation polling
def poll() {
// DES add poll of calibration parameter 13
delayBetween([
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format(),
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format(),
// DES update the configurations tiles
zwave.configurationV1.configurationGet(parameterNumber: 13).format(),
zwave.configurationV1.configurationGet(parameterNumber: 12).format(),
zwave.configurationV1.configurationGet(parameterNumber: 11).format(),
// DES get the battery status
zwave.batteryV1.batteryGet().format()
], 2300)
}
def parse(String description)
{
// DES added 0x70: COMMAND_CLASS_CONFIGURATION so this will process configuration reports
// DES added 0x80: COMMAND_CLASS_BATTERY so this will process battery reports
def map = createEvent(zwaveEvent(zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3, 0x70: 1, 0x80: 1]))) // DES added 0x70, 1
if (!map) {
return null
}
def result = [map]
if (map.isStateChange && map.name in ["heatingSetpoint","coolingSetpoint","thermostatMode"]) {
def map2 = [
name: "thermostatSetpoint",
unit: getTemperatureScale()
]
if (map.name == "thermostatMode") {
state.lastTriedMode = map.value
if (map.value == "cool") {
map2.value = device.latestValue("coolingSetpoint")
log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}"
}
else {
map2.value = device.latestValue("heatingSetpoint")
log.info "THERMOSTAT, latest heating setpoint = ${map2.value}"
}
}
else {
def mode = device.latestValue("thermostatMode")
log.info "THERMOSTAT, latest mode = ${mode}"
if ((map.name == "heatingSetpoint" && mode == "heat") || (map.name == "coolingSetpoint" && mode == "cool")) {
map2.value = map.value
map2.unit = map.unit
}
}
if (map2.value != null) {
log.debug "THERMOSTAT, adding setpoint event: $map"
result << createEvent(map2)
}
} else if (map.name == "thermostatFanMode" && map.isStateChange) {
state.lastTriedFanMode = map.value
}
log.debug "Parse returned $result"
result
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{
def cmdScale = cmd.scale == 1 ? "F" : "C"
def map = [:]
map.value = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
map.displayed = false
switch (cmd.setpointType) {
case 1:
map.name = "heatingSetpoint"
break;
case 2:
map.name = "coolingSetpoint"
break;
default:
return [:]
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
map
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd)
{
def map = [:]
if (cmd.sensorType == 1) {
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
} else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue
map.unit = "%"
map.name = "humidity"
}
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd)
{
def map = [:]
switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_HEATING:
map.value = "heating"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_COOLING:
map.value = "cooling"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_FAN_ONLY:
map.value = "fan only"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_HEAT:
map.value = "pending heat"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_COOL:
map.value = "pending cool"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_VENT_ECONOMIZER:
map.value = "vent economizer"
break
}
map.name = "thermostatOperatingState"
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) {
def map = [name: "thermostatFanState"]
switch (cmd.fanOperatingState) {
case 0:
map.value = "idle"
break
case 1:
map.value = "running"
break
case 2:
map.value = "running high"
break
}
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "heat"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT:
map.value = "emergencyHeat"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL:
map.value = "cool"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO:
map.value = "auto"
break
}
map.name = "thermostatMode"
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
def map = [:]
switch (cmd.fanMode) {
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
map.value = "fanAuto"
break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW:
map.value = "fanOn"
break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION:
map.value = "fanCirculate"
break
}
map.name = "thermostatFanMode"
map.displayed = false
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
def supportedModes = ""
if(cmd.off) { supportedModes += "off " }
if(cmd.heat) { supportedModes += "heat " }
if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergencyHeat " }
if(cmd.cool) { supportedModes += "cool " }
if(cmd.auto) { supportedModes += "auto " }
state.supportedModes = supportedModes
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
def supportedFanModes = ""
if(cmd.auto) { supportedFanModes += "fanAuto " }
if(cmd.low) { supportedFanModes += "fanOn " }
if(cmd.circulation) { supportedFanModes += "fanCirculate " }
state.supportedFanModes = supportedFanModes
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "Zwave event received: $cmd"
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "Unexpected zwave command $cmd"
}
// Command Implementations
def quickSetHeat(degrees) {
setHeatingSetpoint(degrees, 1000)
}
def setHeatingSetpoint(degrees, delay = 30000) {
setHeatingSetpoint(degrees.toDouble(), delay)
}
def setHeatingSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setHeatingSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()
], delay)
}
def quickSetCool(degrees) {
setCoolingSetpoint(degrees, 1000)
}
def setCoolingSetpoint(degrees, delay = 30000) {
setCoolingSetpoint(degrees.toDouble(), delay)
}
def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setCoolingSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()
], delay)
}
def modes() {
["off", "heat", "cool", "auto", "emergencyHeat"]
}
def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedModes")
def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode)
if (supportedModes?.contains(currentMode)) {
while (!supportedModes.contains(nextMode) && nextMode != "off") {
nextMode = next(nextMode)
}
}
state.lastTriedMode = nextMode
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], 1000)
}
def switchToMode(nextMode) {
def supportedModes = getDataByName("supportedModes")
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
if (nextMode in modes()) {
state.lastTriedMode = nextMode
"$nextMode"()
} else {
log.debug("no mode method '$nextMode'")
}
}
def switchFanMode() {
def currentMode = device.currentState("thermostatFanMode")?.value
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedFanModes") ?: "fanAuto fanOn"
def modeOrder = ["fanAuto", "fanCirculate", "fanOn"]
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode)
while (!supportedModes?.contains(nextMode) && nextMode != "fanAuto") {
nextMode = next(nextMode)
}
switchToFanMode(nextMode)
}
def switchToFanMode(nextMode) {
def supportedFanModes = getDataByName("supportedFanModes")
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
def returnCommand
if (nextMode == "fanAuto") {
returnCommand = fanAuto()
} else if (nextMode == "fanOn") {
returnCommand = fanOn()
} else if (nextMode == "fanCirculate") {
returnCommand = fanCirculate()
} else {
log.debug("no fan mode '$nextMode'")
}
if(returnCommand) state.lastTriedFanMode = nextMode
returnCommand
}
def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def getModeMap() { [
"off": 0,
"heat": 1,
"cool": 2,
"auto": 3,
"emergencyHeat": 4
]}
def setThermostatMode(String value) {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def getFanModeMap() { [
"auto": 0,
"on": 1,
"circulate": 6
]}
def setThermostatFanMode(String value) {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def off() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def heat() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def emergencyHeat() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def cool() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def auto() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def fanOn() {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def fanAuto() {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def fanCirculate() {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
private getStandardDelay() {
1000
}