Merge pull request #2225 from SmartThingsCommunity/staging

Rolling down staging to master
This commit is contained in:
Vinay Rao
2017-08-08 13:47:36 -07:00
committed by GitHub
2 changed files with 658 additions and 451 deletions

View File

@@ -53,21 +53,20 @@ metadata {
} }
} }
standardTile("mode", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") { standardTile("mode", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "off", action:"switchMode", nextState:"to_heat", icon: "st.thermostat.heating-cooling-off" state "off", action:"switchMode", nextState:"...", icon: "st.thermostat.heating-cooling-off"
state "heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat" state "heat", action:"switchMode", nextState:"...", icon: "st.thermostat.heat"
state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool" state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto" state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto"
state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat" state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat"
state "to_heat", action:"switchMode", nextState:"to_cool", icon: "st.secondary.secondary" state "...", label: "Updating...",nextState:"...", backgroundColor:"#ffffff"
state "to_cool", action:"switchMode", nextState:"...", icon: "st.secondary.secondary"
state "...", label: "...", action:"off", nextState:"off", icon: "st.secondary.secondary"
} }
standardTile("fanMode", "device.thermostatFanMode", width:2, height:2, inactiveLabel: false, decoration: "flat") { standardTile("fanMode", "device.thermostatFanMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "auto", action:"switchFanMode", icon: "st.thermostat.fan-auto" state "auto", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-auto"
state "on", action:"switchFanMode", icon: "st.thermostat.fan-on" state "on", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-on"
state "circulate", action:"switchFanMode", icon: "st.thermostat.fan-circulate" state "circulate", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-circulate"
state "...", label: "Updating...", nextState:"...", backgroundColor:"#ffffff"
} }
valueTile("humidity", "device.humidity", width:2, height:2, inactiveLabel: false, decoration: "flat") { standardTile("humidity", "device.humidity", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "humidity", label:'${currentValue}%', icon:"st.Weather.weather12" state "humidity", label:'${currentValue}%', icon:"st.Weather.weather12"
} }
standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") { standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
@@ -88,37 +87,43 @@ metadata {
standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") { standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right" state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right"
} }
standardTile("thermostatOperatingState", "device.thermostatOperatingState", width: 2, height:2, decoration: "flat") {
state "thermostatOperatingState", label:'${currentValue}', backgroundColor:"#ffffff"
}
standardTile("refresh", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "temperature" main "temperature"
details(["temperature", "mode", "fanMode", "humidity", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint", "coolingSetpoint", "raiseCoolSetpoint", "refresh"]) details(["temperature", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint",
"coolingSetpoint", "raiseCoolSetpoint", "mode", "fanMode", "humidity", "thermostatOperatingState", "refresh"])
} }
} }
def updated() {
// If not set update ManufacturerSpecific data
if (!getDataValue("manufacturer")) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
}
initialize()
}
def installed() { def installed() {
// Configure device // Configure device
def cmds = [] def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()) cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()) cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
sendHubCommand(cmds) sendHubCommand(cmds)
initialize() runIn(3, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
}
def updated() {
// If not set update ManufacturerSpecific data
if (!getDataValue("manufacturer")) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
runIn(2, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
} else {
initialize()
}
} }
def initialize() { def initialize() {
unschedule()
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
// Poll device for additional data that will be updated by refresh tile // Poll device for additional data that will be updated by refresh tile
refresh() poll()
} }
def parse(String description) def parse(String description)
@@ -151,35 +156,43 @@ def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiInstanceCmdEncap
} }
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) // Event Generation
{ def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) {
def sendCmd = [] def sendCmd = []
def unit = getTemperatureScale() def unit = getTemperatureScale()
def cmdScale = cmd.scale == 1 ? "F" : "C" def cmdScale = cmd.scale == 1 ? "F" : "C"
def setpoint = getTempInLocalScale(cmd.scaledValue, cmdScale) def setpoint = getTempInLocalScale(cmd.scaledValue, cmdScale)
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
// Save device scale, precision, scale as they are used when enforcing setpoint limits
if (cmd.setpointType == 1 || cmd.setpointType == 2) {
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
}
switch (cmd.setpointType) { switch (cmd.setpointType) {
case 1: case 1: // "heatingSetpoint"
//map1.name = "heatingSetpoint" state.deviceHeatingSetpoint = cmd.scaledValue
sendEvent(name: "heatingSetpoint", value: setpoint, unit: unit, displayed: false) if (state.targetHeatingSetpoint) {
updateThermostatSetpoint("heatingSetpoint", setpoint) state.targetHeatingSetpoint = null
// Enforce coolingSetpoint limits, as device doesn't sendEvent(name: "heatingSetpoint", value: setpoint, unit: getTemperatureScale())
if (setpoint > getTempInLocalScale("coolingSetpoint")) { } else if (mode != "cool") {
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet( // if mode is cool heatingSetpoint can't be changed on device, disregard update
setpointType: 2, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format()) // else update heatingSetpoint and enforce limits on coolingSetpoint
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) updateEnforceSetpointLimits("heatingSetpoint", setpoint)
sendHubCommand(sendCmd)
} }
break; break;
case 2: case 2: // "coolingSetpoint"
//map1.name = "coolingSetpoint" state.deviceCoolingSetpoint = cmd.scaledValue
sendEvent(name: "coolingSetpoint", value: setpoint, unit: unit, displayed: false) if (state.targetCoolingSetpoint) {
updateThermostatSetpoint("coolingSetpoint", setpoint) state.targetCoolingSetpoint = null
// Enforce heatingSetpoint limits, as device doesn't sendEvent(name: "coolingSetpoint", value: setpoint, unit: getTemperatureScale())
if (setpoint < getTempInLocalScale("heatingSetpoint")) { } else if (mode != "heat" || mode != "emergency heat") {
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet( // if mode is heat or emergency heat coolingSetpoint can't be changed on device, disregard update
setpointType: 1, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format()) // else update coolingSetpoint and enforce limits on heatingSetpoint
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) updateEnforceSetpointLimits("coolingSetpoint", setpoint)
sendHubCommand(sendCmd)
} }
break; break;
default: default:
@@ -187,28 +200,6 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpo
return return
} }
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
}
// thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
// the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
def updateThermostatSetpoint(setpoint, value) {
def scale = getTemperatureScale()
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
if (mode == "cool") {
thermostatSetpoint = coolingSetpoint
}
// Just set to average of heating + cooling for mode off and auto
if (mode == "off" || mode == "auto") {
thermostatSetpoint = getTempInLocalScale((heatingSetpoint + coolingSetpoint)/2, scale)
}
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: scale)
} }
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) {
@@ -217,6 +208,7 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelR
map.name = "temperature" map.name = "temperature"
map.unit = getTemperatureScale() map.unit = getTemperatureScale()
map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C")) map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
updateThermostatSetpoint(null, null)
} else if (cmd.sensorType == 5) { } else if (cmd.sensorType == 5) {
map.name = "humidity" map.name = "humidity"
map.unit = "%" map.unit = "%"
@@ -231,6 +223,7 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelR
map.name = "temperature" map.name = "temperature"
map.unit = getTemperatureScale() map.unit = getTemperatureScale()
map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C")) map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
updateThermostatSetpoint(null, null)
} else if (cmd.sensorType == 5) { } else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue map.value = cmd.scaledSensorValue
map.unit = "%" map.unit = "%"
@@ -240,7 +233,7 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelR
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd) {
def map = [name: "thermostatOperatingState" ] def map = [name: "thermostatOperatingState"]
switch (cmd.operatingState) { switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_IDLE: case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle" map.value = "idle"
@@ -302,15 +295,30 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeRepor
map.value = "auto" map.value = "auto"
break break
} }
state.lastTriedMode = map.value
sendEvent(map) sendEvent(map)
updateThermostatSetpoint(null, null) // Now that mode and temperature is known we can request setpoints in correct order
// Also makes sure operating state is in sync as it isn't being reported when changed
def cmds = []
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
def currentTemperature = getTempInLocalScale("temperature")
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
if (map.value == "cool" || ((map.value == "auto" || map.value == "off") && (currentTemperature > (heatingSetpoint + coolingSetpoint)/2))) {
// request cooling setpoint first
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
} else {
// request heating setpoint first
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
}
sendHubCommand(cmds)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
def map = [name: "thermostatFanMode", data:[supportedThermostatFanModes: state.supportedFanModes]] def map = [name: "thermostatFanMode", data:[supportedThermostatFanModes: state.supportedFanModes]]
switch (cmd.fanMode) { switch (cmd.fanMode) {
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
map.value = "auto" map.value = "auto"
break break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW:
@@ -320,7 +328,6 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanMod
map.value = "circulate" map.value = "circulate"
break break
} }
state.lastTriedFanMode = map.value
sendEvent(map) sendEvent(map)
} }
@@ -398,64 +405,65 @@ def poll() {
def cmds = [] def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format()) cmds << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 2).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // humidity cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 2).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // humidity
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
def time = getTimeAndDay() def time = getTimeAndDay()
log.debug "time: $time"
if (time) { if (time) {
cmds << new physicalgraph.device.HubAction(zwave.clockV1.clockSet(time).format()) cmds << new physicalgraph.device.HubAction(zwave.clockV1.clockSet(time).format())
} }
// Add 3 seconds delay between each command to avoid flooding the Z-Wave network choking the hub // ThermostatModeReport will spawn request for operating state and setpoints so request this last
sendHubCommand(cmds, 3000) // this as temperature and mode is needed to determine which setpoints should be requested first
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
// Add 2 seconds delay between each command to avoid flooding the Z-Wave network choking the hub
sendHubCommand(cmds, 2000)
} }
def raiseHeatingSetpoint() { def raiseHeatingSetpoint() {
alterSetpoint(null, true, "heatingSetpoint") alterSetpoint(true, "heatingSetpoint")
} }
def lowerHeatingSetpoint() { def lowerHeatingSetpoint() {
alterSetpoint(null, false, "heatingSetpoint") alterSetpoint(false, "heatingSetpoint")
} }
def raiseCoolSetpoint() { def raiseCoolSetpoint() {
alterSetpoint(null, true, "coolingSetpoint") alterSetpoint(true, "coolingSetpoint")
} }
def lowerCoolSetpoint() { def lowerCoolSetpoint() {
alterSetpoint(null, false, "coolingSetpoint") alterSetpoint(false, "coolingSetpoint")
} }
// Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false // Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false
def alterSetpoint(degrees, raise, setpoint) { def alterSetpoint(raise, setpoint) {
def locationScale = getTemperatureScale() def locationScale = getTemperatureScale()
def deviceScale = (state.scale == 1) ? "F" : "C"
def heatingSetpoint = getTempInLocalScale("heatingSetpoint") def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint") def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
def targetvalue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint def targetValue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
def delta = (locationScale == "F") ? 1 : 0.5 def delta = (locationScale == "F") ? 1 : 0.5
if (raise != null) { targetValue += raise ? delta : - delta
targetvalue += raise ? delta : - delta
} else if (degrees) { def data = enforceSetpointLimits(setpoint, [targetValue: targetValue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint], raise)
targetvalue = degrees
} else {
log.warn "alterSetpoint called with neither up/down/degree information"
return
}
def data = enforceSetpointLimits(setpoint, [targetvalue: targetvalue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
// update UI without waiting for the device to respond, this to give user a smoother UI experience // update UI without waiting for the device to respond, this to give user a smoother UI experience
// also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used // also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used
if (data.targetHeatingSetpoint) { if (data.targetHeatingSetpoint) {
sendEvent("name": "heatingSetpoint", "value": data.targetHeatingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false) sendEvent("name": "heatingSetpoint", "value": getTempInLocalScale(data.targetHeatingSetpoint, deviceScale),
runIn(4, "updateHeatingSetpoint", [data: data, overwrite: true]) unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
} }
if (data.targetCoolingSetpoint) { if (data.targetCoolingSetpoint) {
sendEvent("name": "coolingSetpoint", "value": data.targetCoolingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false) sendEvent("name": "coolingSetpoint", "value": getTempInLocalScale(data.targetCoolingSetpoint, deviceScale),
runIn(4, "updateCoolingSetpoint", [data: data, overwrite: true]) unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
}
if (data.targetHeatingSetpoint && data.targetCoolingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "heatingSetpoint" && data.targetHeatingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "coolingSetpoint" && data.targetCoolingSetpoint) {
runIn(5, "updateCoolingSetpoint", [data: data, overwrite: true])
} }
} }
@@ -467,69 +475,138 @@ def updateCoolingSetpoint(data) {
updateSetpoints(data) updateSetpoints(data)
} }
def enforceSetpointLimits(setpoint, data) { def updateEnforceSetpointLimits(setpoint, setpointValue) {
// Enforce max/min for setpoints def heatingSetpoint = (setpoint == "heatingSetpoint") ? setpointValue : getTempInLocalScale("heatingSetpoint")
def maxSetpoint = getTempInLocalScale(95, "F") def coolingSetpoint = (setpoint == "coolingSetpoint") ? setpointValue : getTempInLocalScale("coolingSetpoint")
def minSetpoint = getTempInLocalScale(35, "F")
def targetvalue = data.targetvalue sendEvent(name: setpoint, value: setpointValue, unit: getTemperatureScale(), displayed: false)
updateThermostatSetpoint(setpoint, setpointValue)
// Enforce coolingSetpoint limits, as device doesn't
def data = enforceSetpointLimits(setpoint, [targetValue: setpointValue,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
if (setpoint == "heatingSetpoint" && data.targetHeatingSetpoint) {
data.targetHeatingSetpoint = null
} else if (setpoint == "coolingSetpoint" && data.targetCoolingSetpoint) {
data.targetCoolingSetpoint = null
}
if (data.targetHeatingSetpoint != null || data.targetCoolingSetpoint != null) {
updateSetpoints(data)
}
}
def enforceSetpointLimits(setpoint, data, raise = null) {
def locationScale = getTemperatureScale()
def deviceScale = (state.scale == 1) ? "F" : "C"
// min/max with 3°F/2°C deadband consideration
def minSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(35, "F") : getTempInDeviceScale(38, "F")
def maxSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(92, "F") : getTempInDeviceScale(95, "F")
def deadband = (deviceScale == "F") ? 3 : 2
def delta = (locationScale == "F") ? 1 : 0.5
def targetValue = getTempInDeviceScale(data.targetValue, locationScale)
def heatingSetpoint = null def heatingSetpoint = null
def coolingSetpoint = null def coolingSetpoint = null
if (targetvalue > maxSetpoint) { // Enforce min/mix for setpoints
targetvalue = maxSetpoint if (targetValue > maxSetpoint) {
} else if (targetvalue < minSetpoint) { heatingSetpoint = (setpoint == "heatingSetpoint") ? maxSetpoint : getTempInDeviceScale(data.heatingSetpoint, locationScale)
targetvalue = minSetpoint coolingSetpoint = (setpoint == "heatingSetpoint") ? maxSetpoint + deadband : maxSetpoint
} else if (targetValue < minSetpoint) {
heatingSetpoint = (setpoint == "coolingSetpoint") ? minSetpoint - deadband : minSetpoint
coolingSetpoint = (setpoint == "coolingSetpoint") ? minSetpoint : getTempInDeviceScale(data.coolingSetpoint, locationScale)
} }
// Enforce limits, for now make sure heating <= cooling, and cooling >= heating // Enforce deadband between setpoints
if (setpoint == "heatingSetpoint") { if (setpoint == "heatingSetpoint" && !coolingSetpoint) {
heatingSetpoint = targetvalue // Note if new value is same as old value we need to move it in the direction the user wants to change it, 1°F or 0.5°C,
coolingSetpoint = (heatingSetpoint > data.coolingSetpoint) ? heatingSetpoint : null heatingSetpoint = (targetValue != getTempInDeviceScale(data.heatingSetpoint, locationScale) || !raise) ?
targetValue : (raise ? targetValue + delta : targetValue - delta)
coolingSetpoint = (heatingSetpoint + deadband > getTempInDeviceScale(data.coolingSetpoint, locationScale)) ? heatingSetpoint + deadband : null
} }
if (setpoint == "coolingSetpoint") { if (setpoint == "coolingSetpoint" && !heatingSetpoint) {
coolingSetpoint = targetvalue coolingSetpoint = (targetValue != getTempInDeviceScale(data.coolingSetpoint, locationScale) || !raise) ?
heatingSetpoint = (coolingSetpoint < data.heatingSetpoint) ? coolingSetpoint : null targetValue : (raise ? targetValue + delta : targetValue - delta)
heatingSetpoint = (coolingSetpoint - deadband < getTempInDeviceScale(data.heatingSetpoint, locationScale)) ? coolingSetpoint - deadband : null
} }
return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint] return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint]
} }
def setHeatingSetpoint(degrees) { def setHeatingSetpoint(degrees) {
if (degrees) { if (degrees) {
def data = enforceSetpointLimits("heatingSetpoint", state.heatingSetpoint = degrees.toDouble()
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")]) runIn(2, "updateSetpoints", [overwrite: true])
updateSetpoints(data)
} }
} }
def setCoolingSetpoint(degrees) { def setCoolingSetpoint(degrees) {
if (degrees) { if (degrees) {
def data = enforceSetpointLimits("coolingSetpoint", state.coolingSetpoint = degrees.toDouble()
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")]) runIn(2, "updateSetpoints", [overwrite: true])
updateSetpoints(data)
} }
} }
def updateSetpoints() {
def deviceScale = (state.scale == 1) ? "F" : "C"
def data = [targetHeatingSetpoint: null, targetCoolingSetpoint: null]
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
if (state.heatingSetpoint) {
data = enforceSetpointLimits("heatingSetpoint", [targetValue: state.heatingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
}
if (state.coolingSetpoint) {
heatingSetpoint = data.targetHeatingSetpoint ? getTempInLocalScale(data.targetHeatingSetpoint, deviceScale) : heatingSetpoint
coolingSetpoint = data.targetCoolingSetpoint ? getTempInLocalScale(data.targetCoolingSetpoint, deviceScale) : coolingSetpoint
data = enforceSetpointLimits("coolingSetpoint", [targetValue: state.coolingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
data.targetHeatingSetpoint = data.targetHeatingSetpoint ?: heatingSetpoint
}
state.heatingSetpoint = null
state.coolingSetpoint = null
updateSetpoints(data)
}
def updateSetpoints(data) { def updateSetpoints(data) {
unschedule("updateSetpoints")
def cmds = [] def cmds = []
if (data.targetHeatingSetpoint) { if (data.targetHeatingSetpoint) {
state.targetHeatingSetpoint = data.targetHeatingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet( cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetHeatingSetpoint)).format()) setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: data.targetHeatingSetpoint).format())
} }
if (data.targetCoolingSetpoint) { if (data.targetCoolingSetpoint) {
state.targetCoolingSetpoint = data.targetCoolingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet( cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetCoolingSetpoint)).format()) setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: data.targetCoolingSetpoint).format())
}
// Also make sure temperature and operating state is in sync
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
if (data.targetHeatingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
}
if (data.targetCoolingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
} }
// Always request both setpoints in case thermostat changed both
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
sendHubCommand(cmds) sendHubCommand(cmds)
} }
def convertToDeviceScale(setpoint) { // thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
def locationScale = getTemperatureScale() // the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
def deviceScale = (state.scale == 1) ? "F" : "C" def updateThermostatSetpoint(setpoint, value) {
return (deviceScale == locationScale) ? setpoint : def scale = getTemperatureScale()
(deviceScale == "F" ? celsiusToFahrenheit(setpoint.toBigDecimal()) : roundC(fahrenheitToCelsius(setpoint.toBigDecimal()))) def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
if (mode == "cool") {
thermostatSetpoint = coolingSetpoint
} else if (mode == "auto" || mode == "off") {
// Set thermostatSetpoint to the setpoint closest to the current temperature
def currentTemperature = getTempInLocalScale("temperature")
if (currentTemperature > (heatingSetpoint + coolingSetpoint)/2) {
thermostatSetpoint = coolingSetpoint
}
}
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale())
} }
/** /**
@@ -543,54 +620,68 @@ def ping() {
def switchMode() { def switchMode() {
def currentMode = device.currentValue("thermostatMode") def currentMode = device.currentValue("thermostatMode")
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off"
def supportedModes = state.supportedModes def supportedModes = state.supportedModes
if (supportedModes) { if (supportedModes) {
def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] } def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
def nextMode = next(lastTriedMode) def nextMode = next(currentMode)
setThermostatMode(nextMode) runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
state.lastTriedMode = nextMode
} else { } else {
log.warn "supportedModes not defined" log.warn "supportedModes not defined"
getSupportedModes()
} }
} }
def switchToMode(nextMode) { def switchToMode(nextMode) {
def supportedModes = state.supportedModes def supportedModes = state.supportedModes
if (supportedModes && supportedModes.contains(nextMode)) { if (supportedModes) {
setThermostatMode(nextMode) if (supportedModes.contains(nextMode)) {
state.lastTriedMode = nextMode runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
}
} else { } else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}") log.warn "supportedModes not defined"
getSupportedModes()
} }
} }
def getSupportedModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
sendHubCommand(cmds)
}
def switchFanMode() { def switchFanMode() {
def currentMode = device.currentState("thermostatFanMode")?.value def currentMode = device.currentValue("thermostatFanMode")
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off"
def supportedFanModes = state.supportedFanModes def supportedFanModes = state.supportedFanModes
if (supportedFanModes) { if (supportedFanModes) {
def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] } def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
def nextMode = next(lastTriedMode) def nextMode = next(currentMode)
setThermostatFanMode(nextMode) runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
state.lastTriedFanMode = nextMode
} else { } else {
log.warn "supportedFanModes not defined" log.warn "supportedFanModes not defined"
getSupportedFanModes()
} }
} }
def switchToFanMode(nextMode) { def switchToFanMode(nextMode) {
def supportedFanModes = state.supportedFanModes def supportedFanModes = state.supportedFanModes
if (supportedFanModes && supportedFanModes.contains(nextMode)) { if (supportedFanModes) {
setThermostatFanMode(nextMode) if (supportedFanModes.contains(nextMode)) {
state.lastTriedFanMode = nextMode runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("FanMode $nextMode is not supported by ${device.displayName}")
}
} else { } else {
log.debug("FanMode $nextMode is not supported by ${device.displayName}") log.warn "supportedFanModes not defined"
getSupportedFanModes()
} }
} }
def getDataByName(String name) { def getSupportedFanModes() {
state[name] ?: device.getDataValue(name) def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
sendHubCommand(cmds)
} }
def getModeMap() { [ def getModeMap() { [
@@ -602,8 +693,12 @@ def getModeMap() { [
]} ]}
def setThermostatMode(String value) { def setThermostatMode(String value) {
switchToMode(value)
}
def setThermostatMode(data) {
def cmds = [] def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
sendHubCommand(cmds) sendHubCommand(cmds)
} }
@@ -615,8 +710,12 @@ def getFanModeMap() { [
]} ]}
def setThermostatFanMode(String value) { def setThermostatFanMode(String value) {
switchToFanMode(value)
}
def setThermostatFanMode(data) {
def cmds = [] def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
sendHubCommand(cmds) sendHubCommand(cmds)
} }
@@ -675,8 +774,28 @@ def getTempInLocalScale(state) {
// get/convert temperature to current local scale // get/convert temperature to current local scale
def getTempInLocalScale(temp, scale) { def getTempInLocalScale(temp, scale) {
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble() if (temp && scale) {
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp)) def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
}
return 0
}
def getTempInDeviceScale(state) {
def temp = device.currentState(state)
if (temp && temp.value && temp.unit) {
return getTempInDeviceScale(temp.value.toBigDecimal(), temp.unit)
}
return 0
}
def getTempInDeviceScale(temp, scale) {
if (temp && scale) {
def deviceScale = (state.scale == 1) ? "F" : "C"
return (deviceScale == scale) ? temp :
(deviceScale == "F" ? celsiusToFahrenheit(temp).toDouble().round(0).toInteger() : roundC(fahrenheitToCelsius(temp)))
}
return 0
} }
def roundC (tempC) { def roundC (tempC) {

View File

@@ -16,7 +16,6 @@ metadata {
capability "Actuator" capability "Actuator"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Thermostat" capability "Thermostat"
capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check" capability "Health Check"
@@ -25,209 +24,175 @@ metadata {
command "switchMode" command "switchMode"
command "switchFanMode" command "switchFanMode"
command "quickSetCool" command "lowerHeatingSetpoint"
command "quickSetHeat" command "raiseHeatingSetpoint"
command "lowerCoolSetpoint"
command "raiseCoolSetpoint"
fingerprint deviceId: "0x08" fingerprint deviceId: "0x08"
fingerprint inClusters: "0x43,0x40,0x44,0x31" fingerprint inClusters: "0x43,0x40,0x44,0x31"
fingerprint mfr:"0039", prod:"0011", model:"0001", deviceJoinName: "Honeywell Z-Wave Thermostat" fingerprint mfr:"0039", prod:"0011", model:"0001", deviceJoinName: "Honeywell Z-Wave Thermostat"
} }
// 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 "auto" : "command: 4403, payload: 00" // "fanAuto"
status "on" : "command: 4403, payload: 01" // "fanOn"
status "circulate" : "command: 4403, payload: 06" // "fanCirculate
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"
}
tiles { tiles {
// Using standardTile instead of valueTile as it renders the icon better multiAttributeTile(name:"temperature", type:"generic", width:3, height:2, canChangeIcon: true) {
standardTile("temperature", "device.temperature", width: 2, height: 2) { tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
state("temperature", label:'${currentValue}°', icon: "st.thermostat.ac.air-conditioning", attributeState("temperature", label:'${currentValue}°', icon: "st.alarm.temperature.normal",
backgroundColors:[ backgroundColors:[
[value: 31, color: "#153591"], // Celsius
[value: 44, color: "#1e9cbb"], [value: 0, color: "#153591"],
[value: 59, color: "#90d2a7"], [value: 7, color: "#1e9cbb"],
[value: 74, color: "#44b621"], [value: 15, color: "#90d2a7"],
[value: 84, color: "#f1d801"], [value: 23, color: "#44b621"],
[value: 95, color: "#d04e00"], [value: 28, color: "#f1d801"],
[value: 96, color: "#bc2323"] [value: 35, color: "#d04e00"],
] [value: 37, color: "#bc2323"],
) // Fahrenheit
[value: 40, 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"]
]
)
}
} }
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("mode", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "off", action:"switchMode", nextState:"to_heat", icon: "st.thermostat.heating-cooling-off" state "off", action:"switchMode", nextState:"...", icon: "st.thermostat.heating-cooling-off"
state "heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat" state "heat", action:"switchMode", nextState:"...", icon: "st.thermostat.heat"
state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool" state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto" state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto"
state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat" state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat"
state "to_heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat" state "...", label: "Updating...",nextState:"...", backgroundColor:"#ffffff"
state "to_cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
state "...", label: "...", action:"off", nextState:"off"
} }
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { standardTile("fanMode", "device.thermostatFanMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "auto", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-auto" // "fanAuto" state "auto", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-auto"
state "on", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-on" // "fanOn" state "on", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-on"
state "circulate", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-circulate" // "fanCirculate" state "circulate", action:"switchFanMode", nextState:"...", icon: "st.thermostat.fan-circulate"
state "...", label: "...", nextState:"..." state "...", label: "Updating...", nextState:"...", backgroundColor:"#ffffff"
} }
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) { standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "setHeatingSetpoint", action:"quickSetHeat", backgroundColor:"#d04e00" state "heatingSetpoint", action:"lowerHeatingSetpoint", icon:"st.thermostat.thermostat-left"
} }
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") { valueTile("heatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue}° heat', backgroundColor:"#ffffff" state "heatingSetpoint", label:'${currentValue}° heat', backgroundColor:"#ffffff"
} }
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) { standardTile("raiseHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "setCoolingSetpoint", action:"quickSetCool", backgroundColor: "#1e9cbb" state "heatingSetpoint", action:"raiseHeatingSetpoint", icon:"st.thermostat.thermostat-right"
} }
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") { standardTile("lowerCoolSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff" state "coolingSetpoint", action:"lowerCoolSetpoint", icon:"st.thermostat.thermostat-left"
} }
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { valueTile("coolingSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "coolingSetpoint", label:'${currentValue}° cool', backgroundColor:"#ffffff"
}
standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right"
}
valueTile("thermostatOperatingState", "device.thermostatOperatingState", width: 2, height:1, decoration: "flat") {
state "thermostatOperatingState", label:'${currentValue}', backgroundColor:"#ffffff"
}
standardTile("refresh", "device.thermostatMode", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "temperature" main "temperature"
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh"]) details(["temperature", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint",
"coolingSetpoint", "raiseCoolSetpoint", "mode", "fanMode", "thermostatOperatingState", "refresh"])
} }
} }
def installed(){ def installed() {
sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())) // Configure device
initialize() def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
sendHubCommand(cmds)
runIn(3, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
} }
def updated(){ def updated() {
initialize() // If not set update ManufacturerSpecific data
if (!getDataValue("manufacturer")) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
runIn(2, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
} else {
initialize()
}
} }
def initialize() { def initialize() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
unschedule() unschedule()
runEvery5Minutes("refresh") if (getDataValue("manufacturer") != "Honeywell") {
refresh() runEvery5Minutes("poll") // This is not necessary for Honeywell Z-wave, but could be for other Z-wave thermostats
}
poll()
} }
def parse(String description) def parse(String description)
{ {
def map = createEvent(zwaveEvent(zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3]))) def result = null
if (!map) { if (description == "updated") {
return null } else {
def zwcmd = zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3])
if (zwcmd) {
result = zwaveEvent(zwcmd)
} else {
log.debug "$device.displayName couldn't parse $description"
}
} }
if (!result) {
def result = [map] return []
if (map.isStateChange && map.name in ["heatingSetpoint","coolingSetpoint","thermostatMode"]) {
def map2 = [
name: "thermostatSetpoint",
unit: getTemperatureScale()
]
if (map.name == "thermostatMode") {
state.lastTriedMode = map.value
map.data = [supportedThermostatModes:state.supportedThermostatModes]
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
map.data = [supportedThermostatFanModes: state.supportedThermostatFanModes]
} }
log.debug "Parse returned $result" return [result]
result
} }
// Event Generation // Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) {
{
def cmdScale = cmd.scale == 1 ? "F" : "C" def cmdScale = cmd.scale == 1 ? "F" : "C"
def map = [:] def setpoint = getTempInLocalScale(cmd.scaledValue, cmdScale)
map.value = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision) def unit = getTemperatureScale()
map.unit = getTemperatureScale()
map.displayed = false
switch (cmd.setpointType) { switch (cmd.setpointType) {
case 1: case 1:
map.name = "heatingSetpoint" sendEvent(name: "heatingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("heatingSetpoint", setpoint)
break; break;
case 2: case 2:
map.name = "coolingSetpoint" sendEvent(name: "coolingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("coolingSetpoint", setpoint)
break; break;
default: default:
return [:] log.debug "unknown setpointType $cmd.setpointType"
return
} }
// So we can respond with same format // So we can respond with same format
state.size = cmd.size state.size = cmd.size
state.scale = cmd.scale state.scale = cmd.scale
state.precision = cmd.precision state.precision = cmd.precision
map // Make sure return value is not result from above expresion
return 0
} }
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd) def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd) {
{
def map = [:] def map = [:]
if (cmd.sensorType == 1) { if (cmd.sensorType == 1) {
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision) map.value = getTempInLocalScale(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C")
map.unit = getTemperatureScale() map.unit = getTemperatureScale()
map.name = "temperature" map.name = "temperature"
updateThermostatSetpoint(null, null)
} else if (cmd.sensorType == 5) { } else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue map.value = cmd.scaledSensorValue
map.unit = "%" map.unit = "%"
map.name = "humidity" map.name = "humidity"
} }
map sendEvent(map)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd) def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd) {
{ def map = [name: "thermostatOperatingState"]
def map = [:]
switch (cmd.operatingState) { switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE: case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle" map.value = "idle"
@@ -251,8 +216,9 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.Thermosta
map.value = "vent economizer" map.value = "vent economizer"
break break
} }
map.name = "thermostatOperatingState" // Makes sure we have the correct thermostat mode
map sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format()))
sendEvent(map)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) {
@@ -268,11 +234,11 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanSt
map.value = "running high" map.value = "running high"
break break
} }
map sendEvent(map)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [:] def map = [name: "thermostatMode", data:[supportedThermostatModes: state.supportedModes]]
switch (cmd.mode) { switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF: case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off" map.value = "off"
@@ -290,26 +256,24 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeRepor
map.value = "auto" map.value = "auto"
break break
} }
map.name = "thermostatMode" sendEvent(map)
map updateThermostatSetpoint(null, null)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
def map = [:] def map = [name: "thermostatFanMode", data:[supportedThermostatFanModes: state.supportedFanModes]]
switch (cmd.fanMode) { switch (cmd.fanMode) {
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
map.value = "auto" // "fanAuto" map.value = "auto"
break break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW:
map.value = "on" // "fanOn" map.value = "on"
break break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION:
map.value = "circulate" // "fanCirculate" map.value = "circulate"
break break
} }
map.name = "thermostatFanMode" sendEvent(map)
map.displayed = false
map
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
@@ -320,24 +284,34 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSuppo
if(cmd.auto) { supportedModes << "auto" } if(cmd.auto) { supportedModes << "auto" }
if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" } if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" }
state.supportedThermostatModes = supportedModes state.supportedModes = supportedModes
sendEvent(name: "supportedThermostatModes", value: supportedModes, displayed: false) sendEvent(name: "supportedThermostatModes", value: supportedModes, displayed: false)
return [:]
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
def supportedFanModes = [] def supportedFanModes = []
if(cmd.auto) { supportedFanModes << "auto" } // "fanAuto " if(cmd.auto) { supportedFanModes << "auto" }
if(cmd.circulation) { supportedFanModes << "circulate" } // "fanCirculate" if(cmd.circulation) { supportedFanModes << "circulate" }
if(cmd.low) { supportedFanModes << "on" } // "fanOn" if(cmd.low) { supportedFanModes << "on" }
state.supportedThermostatFanModes = supportedFanModes state.supportedFanModes = supportedFanModes
sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, displayed: false) sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, displayed: false)
return [:] }
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
if (cmd.manufacturerName) {
updateDataValue("manufacturer", cmd.manufacturerName)
}
if (cmd.productTypeId) {
updateDataValue("productTypeId", cmd.productTypeId.toString())
}
if (cmd.productId) {
updateDataValue("productId", cmd.productId.toString())
}
} }
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "Zwave event received: $cmd" log.debug "Zwave BasicReport: $cmd"
} }
def zwaveEvent(physicalgraph.zwave.Command cmd) { def zwaveEvent(physicalgraph.zwave.Command cmd) {
@@ -346,6 +320,16 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Command Implementations // Command Implementations
def refresh() { def refresh() {
// Only allow refresh every 2 minutes to prevent flooding the Zwave network
def timeNow = now()
if (!state.refreshTriggeredAt || (2 * 60 * 1000 < (timeNow - state.refreshTriggeredAt))) {
state.refreshTriggeredAt = timeNow
// use runIn with overwrite to prevent multiple DTH instances run before state.refreshTriggeredAt has been saved
runIn(2, "poll", [overwrite: true])
}
}
def poll() {
def cmds = [] def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format()) cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
@@ -358,64 +342,152 @@ def refresh() {
sendHubCommand(cmds) sendHubCommand(cmds)
} }
def quickSetHeat(degrees) { def raiseHeatingSetpoint() {
setHeatingSetpoint(degrees, 1000) alterSetpoint(true, "heatingSetpoint")
} }
def setHeatingSetpoint(degrees, delay = 30000) { def lowerHeatingSetpoint() {
setHeatingSetpoint(degrees.toDouble(), delay) alterSetpoint(false, "heatingSetpoint")
} }
def setHeatingSetpoint(Double degrees, Integer delay = 30000) { def raiseCoolSetpoint() {
log.trace "setHeatingSetpoint($degrees, $delay)" alterSetpoint(true, "coolingSetpoint")
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) { def lowerCoolSetpoint() {
setCoolingSetpoint(degrees, 1000) alterSetpoint(false, "coolingSetpoint")
} }
def setCoolingSetpoint(degrees, delay = 30000) { // Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false
setCoolingSetpoint(degrees.toDouble(), delay) def alterSetpoint(raise, setpoint) {
def locationScale = getTemperatureScale()
def deviceScale = (state.scale == 1) ? "F" : "C"
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
def targetValue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
def delta = (locationScale == "F") ? 1 : 0.5
targetValue += raise ? delta : - delta
def data = enforceSetpointLimits(setpoint, [targetValue: targetValue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
// update UI without waiting for the device to respond, this to give user a smoother UI experience
// also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used
if (data.targetHeatingSetpoint) {
sendEvent("name": "heatingSetpoint", "value": getTempInLocalScale(data.targetHeatingSetpoint, deviceScale),
unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
}
if (data.targetCoolingSetpoint) {
sendEvent("name": "coolingSetpoint", "value": getTempInLocalScale(data.targetCoolingSetpoint, deviceScale),
unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
}
if (data.targetHeatingSetpoint && data.targetCoolingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "heatingSetpoint" && data.targetHeatingSetpoint) {
runIn(5, "updateHeatingSetpoint", [data: data, overwrite: true])
} else if (setpoint == "coolingSetpoint" && data.targetCoolingSetpoint) {
runIn(5, "updateCoolingSetpoint", [data: data, overwrite: true])
}
} }
def setCoolingSetpoint(Double degrees, Integer delay = 30000) { def updateHeatingSetpoint(data) {
log.trace "setCoolingSetpoint($degrees, $delay)" updateSetpoints(data)
def deviceScale = state.scale ?: 1 }
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees def updateCoolingSetpoint(data) {
if (locationScale == "C" && deviceScaleString == "F") { updateSetpoints(data)
convertedDegrees = celsiusToFahrenheit(degrees) }
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([ def enforceSetpointLimits(setpoint, data) {
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(), def locationScale = getTemperatureScale()
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format() def minSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(40, "F") : getTempInDeviceScale(50, "F")
], delay) def maxSetpoint = (setpoint == "heatingSetpoint") ? getTempInDeviceScale(90, "F") : getTempInDeviceScale(99, "F")
def deadband = (state.scale == 1) ? 3 : 2 // 3°F, 2°C
def targetValue = getTempInDeviceScale(data.targetValue, locationScale)
def heatingSetpoint = null
def coolingSetpoint = null
// Enforce min/mix for setpoints
if (targetValue > maxSetpoint) {
targetValue = maxSetpoint
} else if (targetValue < minSetpoint) {
targetValue = minSetpoint
}
// Enforce 3 degrees F deadband between setpoints
if (setpoint == "heatingSetpoint") {
heatingSetpoint = targetValue
coolingSetpoint = (heatingSetpoint + deadband > getTempInDeviceScale(data.coolingSetpoint, locationScale)) ? heatingSetpoint + deadband : null
}
if (setpoint == "coolingSetpoint") {
coolingSetpoint = targetValue
heatingSetpoint = (coolingSetpoint - deadband < getTempInDeviceScale(data.heatingSetpoint, locationScale)) ? coolingSetpoint - deadband : null
}
return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint]
}
def setHeatingSetpoint(degrees) {
if (degrees) {
state.heatingSetpoint = degrees.toDouble()
runIn(2, "updateSetpoints", [overwrite: true])
}
}
def setCoolingSetpoint(degrees) {
if (degrees) {
state.coolingSetpoint = degrees.toDouble()
runIn(2, "updateSetpoints", [overwrite: true])
}
}
def updateSetpoints() {
def deviceScale = (state.scale == 1) ? "F" : "C"
def data = [targetHeatingSetpoint: null, targetCoolingSetpoint: null]
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
if (state.heatingSetpoint) {
data = enforceSetpointLimits("heatingSetpoint", [targetValue: state.heatingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
}
if (state.coolingSetpoint) {
heatingSetpoint = data.targetHeatingSetpoint ? getTempInLocalScale(data.targetHeatingSetpoint, deviceScale) : heatingSetpoint
coolingSetpoint = data.targetCoolingSetpoint ? getTempInLocalScale(data.targetCoolingSetpoint, deviceScale) : coolingSetpoint
data = enforceSetpointLimits("coolingSetpoint", [targetValue: state.coolingSetpoint,
heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
data.targetHeatingSetpoint = data.targetHeatingSetpoint ?: heatingSetpoint
}
state.heatingSetpoint = null
state.coolingSetpoint = null
updateSetpoints(data)
}
def updateSetpoints(data) {
def cmds = []
if (data.targetHeatingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: data.targetHeatingSetpoint).format())
}
if (data.targetCoolingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: data.targetCoolingSetpoint).format())
}
sendHubCommand(cmds)
}
// thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
// the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
def updateThermostatSetpoint(setpoint, value) {
def scale = getTemperatureScale()
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
if (mode == "cool") {
thermostatSetpoint = coolingSetpoint
} else if (mode == "auto" || mode == "off") {
// Set thermostatSetpoint to the setpoint closest to the current temperature
def currentTemperature = getTempInLocalScale("temperature")
if (currentTemperature > (heatingSetpoint + coolingSetpoint)/2) {
thermostatSetpoint = coolingSetpoint
}
}
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale())
} }
/** /**
@@ -423,76 +495,74 @@ def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
* */ * */
def ping() { def ping() {
log.debug "ping() called" log.debug "ping() called"
poll() // Just get Operating State there's no need to flood more commands
} sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()))
def modes() {
return state.supportedThermostatModes
} }
def switchMode() { def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentValue("thermostatMode")
def lastTriedMode = state.lastTriedMode ?: currentMode ?: ["off"] def supportedModes = state.supportedModes
def supportedModes = getDataByName("supportedThermostatModes") if (supportedModes) {
def modeOrder = modes() def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def nextMode = next(currentMode)
def nextMode = next(lastTriedMode) runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
if (supportedModes?.contains(currentMode)) { } else {
while (!supportedModes.contains(nextMode) && nextMode != "off") { log.warn "supportedModes not defined"
nextMode = next(nextMode) getSupportedModes()
}
} }
state.lastTriedMode = nextMode
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], 1000)
} }
def switchToMode(nextMode) { def switchToMode(nextMode) {
def supportedModes = getDataByName("supportedThermostatModes") def supportedModes = state.supportedModes
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if (supportedModes) {
if (nextMode in modes()) { if (supportedModes.contains(nextMode)) {
state.lastTriedMode = nextMode runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
"$nextMode"() } else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
}
} else { } else {
log.debug("no mode method '$nextMode'") log.warn "supportedModes not defined"
getSupportedModes()
} }
} }
def getSupportedModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
sendHubCommand(cmds)
}
def switchFanMode() { def switchFanMode() {
def currentMode = device.currentState("thermostatFanMode")?.value def currentMode = device.currentValue("thermostatFanMode")
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: ["off"] def supportedFanModes = state.supportedFanModes
def supportedModes = getDataByName("supportedThermostatFanModes") ?: ["auto", "on"] if (supportedFanModes) {
def modeOrder = state.supportedThermostatFanModes def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def nextMode = next(currentMode)
def nextMode = next(lastTriedMode) runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
while (!supportedModes?.contains(nextMode) && nextMode != "auto") { // "fanAuto" } else {
nextMode = next(nextMode) log.warn "supportedFanModes not defined"
getSupportedFanModes()
} }
switchToFanMode(nextMode)
} }
def switchToFanMode(nextMode) { def switchToFanMode(nextMode) {
def supportedFanModes = getDataByName("supportedThermostatFanModes") def supportedFanModes = state.supportedFanModes
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if (supportedFanModes) {
if (supportedFanModes.contains(nextMode)) {
def returnCommand runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
if (nextMode == "auto") { // "fanAuto" } else {
returnCommand = fanAuto() log.debug("FanMode $nextMode is not supported by ${device.displayName}")
} else if (nextMode == "on") { // "fanOn" }
returnCommand = fanOn()
} else if (nextMode == "circulate") { // "fanCirculate"
returnCommand = fanCirculate()
} else { } else {
log.debug("no fan mode '$nextMode'") log.warn "supportedFanModes not defined"
getSupportedFanModes()
} }
if(returnCommand) state.lastTriedFanMode = nextMode
returnCommand
} }
def getDataByName(String name) { def getSupportedFanModes() {
state[name] ?: device.getDataValue(name) def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
sendHubCommand(cmds)
} }
def getModeMap() { [ def getModeMap() { [
@@ -504,10 +574,14 @@ def getModeMap() { [
]} ]}
def setThermostatMode(String value) { def setThermostatMode(String value) {
delayBetween([ switchToMode(value)
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(), }
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay) def setThermostatMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
sendHubCommand(cmds)
} }
def getFanModeMap() { [ def getFanModeMap() { [
@@ -517,69 +591,83 @@ def getFanModeMap() { [
]} ]}
def setThermostatFanMode(String value) { def setThermostatFanMode(String value) {
delayBetween([ switchToFanMode(value)
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(), }
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay) def setThermostatFanMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
sendHubCommand(cmds)
} }
def off() { def off() {
delayBetween([ switchToMode("off")
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def heat() { def heat() {
delayBetween([ switchToMode("heat")
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def emergencyHeat() { def emergencyHeat() {
delayBetween([ switchToMode("emergency heat")
zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def cool() { def cool() {
delayBetween([ switchToMode("cool")
zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def auto() { def auto() {
delayBetween([ switchToMode("auto")
zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def fanOn() { def fanOn() {
delayBetween([ switchToFanMode("on")
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
} }
def fanAuto() { def fanAuto() {
delayBetween([ switchToFanMode("auto")
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
} }
def fanCirculate() { def fanCirculate() {
delayBetween([ switchToFanMode("circulate")
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
} }
private getStandardDelay() { // Get stored temperature from currentState in current local scale
1000 def getTempInLocalScale(state) {
def temp = device.currentState(state)
if (temp && temp.value && temp.unit) {
return getTempInLocalScale(temp.value.toBigDecimal(), temp.unit)
}
return 0
} }
// get/convert temperature to current local scale
def getTempInLocalScale(temp, scale) {
if (temp && scale) {
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
}
return 0
}
def getTempInDeviceScale(state) {
def temp = device.currentState(state)
if (temp && temp.value && temp.unit) {
return getTempInDeviceScale(temp.value.toBigDecimal(), temp.unit)
}
return 0
}
def getTempInDeviceScale(temp, scale) {
if (temp && scale) {
def deviceScale = (state.scale == 1) ? "F" : "C"
return (deviceScale == scale) ? temp :
(deviceScale == "F" ? celsiusToFahrenheit(temp) : roundC(fahrenheitToCelsius(temp)))
}
return 0
}
def roundC (tempC) {
return (Math.round(tempC.toDouble() * 2))/2
}