PROB-1764, PROB-1767 CT100 Thermostat

PROB-1764 Switching CT100 Thermostat from 'Cool' to 'Off' in SmartThings app causes Hub to become unresponsive
PROB-1767 CT100 Not Responding to Routine Cooling Setpoint Command if Heating Setpoint included as well
Removed action from 'Updating' state to prevent DTH sending more commands until response is received
Changed how setpoints are updated, also added deadband
Added thermostat operating state
Changed humidity valueTile to standardTile
This commit is contained in:
marstorp
2017-08-03 11:50:57 -07:00
parent 8bd02707f4
commit e0fe559014

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)
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() 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 = "%"
@@ -302,9 +295,24 @@ 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) {
@@ -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
// Always request both setpoints in case thermostat changed both 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()) 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()) 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 { } else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}") log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
} }
} else {
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 { } else {
log.debug("FanMode $nextMode is not supported by ${device.displayName}") log.debug("FanMode $nextMode is not supported by ${device.displayName}")
} }
} else {
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,9 +774,29 @@ 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) {
if (temp && scale) {
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble() def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp)) 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) {
return (Math.round(tempC.toDouble() * 2))/2 return (Math.round(tempC.toDouble() * 2))/2