diff --git a/devicetypes/waymirenet/trane-xl624-thermostat.src/trane-xl624-thermostat.groovy b/devicetypes/waymirenet/trane-xl624-thermostat.src/trane-xl624-thermostat.groovy new file mode 100644 index 0000000..22b3d2d --- /dev/null +++ b/devicetypes/waymirenet/trane-xl624-thermostat.src/trane-xl624-thermostat.groovy @@ -0,0 +1,568 @@ +/* TraneXL624.device.groovy + * + * Variation of the stock SmartThings "Zwave-Thermostat" & Better-Thermostat by todd@wackford.net + * + * + * + * Original Modified Code: + * twack@wackware.net + * 20140209 + * + * Modified to add Humidity & Clock Set + * Justin Waymire + * justin@waymirenet.com + * 04-01-15 + * +*/ +metadata { + // Automatically generated. Make future change here. + definition (name: "Trane XL624 Thermostat", author: "justin@waymirenet.com") { + capability "Relative Humidity Measurement" + capability "Temperature Measurement" + capability "Refresh" + capability "Thermostat" + capability "Configuration" + capability "Polling" + + command "heatLevelUp" + command "heatLevelDown" + command "coolLevelUp" + command "coolLevelDown" + command "switchMode" + command "switchFanMode" + } + + // simulator metadata + simulator { + status "off" : "command: 4003, payload: 00" + status "heat" : "command: 4003, payload: 01" + status "cool" : "command: 4003, payload: 02" + status "auto" : "command: 4003, payload: 03" + status "emergencyHeat" : "command: 4003, payload: 04" + + status "fanAuto" : "command: 4403, payload: 00" + status "fanOn" : "command: 4403, payload: 01" + status "fanCirculate" : "command: 4403, payload: 06" + + status "heat 60" : "command: 4303, payload: 01 01 3C" + status "heat 68" : "command: 4303, payload: 01 01 44" + status "heat 72" : "command: 4303, payload: 01 01 48" + + status "cool 72" : "command: 4303, payload: 02 01 48" + status "cool 76" : "command: 4303, payload: 02 01 4C" + status "cool 80" : "command: 4303, payload: 02 01 50" + + status "temp 58" : "command: 3105, payload: 01 22 02 44" + status "temp 62" : "command: 3105, payload: 01 22 02 6C" + status "temp 70" : "command: 3105, payload: 01 22 02 BC" + status "temp 74" : "command: 3105, payload: 01 22 02 E4" + status "temp 78" : "command: 3105, payload: 01 22 03 0C" + status "temp 82" : "command: 3105, payload: 01 22 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 { + valueTile("temperature", "device.temperature", width: 2, height: 2) { + state("temperature", label:'${currentValue}°', unit:'F', + backgroundColors:[ + [value: 31, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 95, color: "#d04e00"], + [value: 96, color: "#bc2323"] + ] + ) + } + valueTile("humidity", "device.humidity", inactiveLabel: false, decoration: "flat") { + state "humidity", label:'Humidity ${currentValue}%', backgroundColor:"#ffffff" + } + standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { + state "off", label:'', action:"switchMode", icon:"st.thermostat.heating-cooling-off" + state "heat", label:'', action:"switchMode", icon:"st.thermostat.heat" + state "emergencyHeat", label:'', action:"switchMode", icon:"st.thermostat.emergency-heat" + state "cool", label:'', action:"switchMode", icon:"st.thermostat.cool" + state "auto", label:'', action:"switchMode", icon:"st.thermostat.auto" + } + standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { + state "fanAuto", label:'', action:"switchFanMode", icon:"st.thermostat.fan-auto" + state "fanOn", label:'', action:"switchFanMode", icon:"st.thermostat.fan-on" + state "fanCirculate", label:' ', action:"switchFanMode", icon:"st.thermostat.fan-circulate" + } + valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") { + state "heat", label:'${currentValue}° heat', unit:"F", backgroundColor:"#ffffff" + } + valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") { + state "cool", label:'${currentValue}° cool', unit:"F", backgroundColor:"#ffffff" + } + standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { + state "default", action:"polling.poll", icon:"st.secondary.refresh" + } + standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { + state "configure", label:' ', action:"configuration.configure", icon:"st.secondary.configure" + } + standardTile("heatLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatLevelUp", label:' ', action:"heatLevelUp", icon:"st.thermostat.thermostat-up" + } + standardTile("heatLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatLevelDown", label:' ', action:"heatLevelDown", icon:"st.thermostat.thermostat-down" + } + standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "coolLevelUp", label:' ', action:"coolLevelUp", icon:"st.thermostat.thermostat-up" + } + standardTile("coolLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "coolLevelDown", label:' ', action:"coolLevelDown", icon:"st.thermostat.thermostat-down" + } + + main (["temperature", "humidity"]) + details(["temperature", "humidity", "mode", "heatLevelDown", "heatingSetpoint", "heatLevelUp", "coolLevelDown", "coolingSetpoint", "coolLevelUp", "refresh", "configure"]) + } +} + +def coolLevelUp(){ + int nextLevel = device.currentValue("coolingSetpoint") + 1 + + if( nextLevel > 99){ + nextLevel = 99 + } + log.debug "Setting cool set point up to: ${nextLevel}" + setCoolingSetpoint(nextLevel) +} + +def coolLevelDown(){ + int nextLevel = device.currentValue("coolingSetpoint") - 1 + + if( nextLevel < 50){ + nextLevel = 50 + } + log.debug "Setting cool set point down to: ${nextLevel}" + setCoolingSetpoint(nextLevel) +} + +def heatLevelUp(){ + int nextLevel = device.currentValue("heatingSetpoint") + 1 + + if( nextLevel > 90){ + nextLevel = 90 + } + log.debug "Setting heat set point up to: ${nextLevel}" + setHeatingSetpoint(nextLevel) +} + +def heatLevelDown(){ + int nextLevel = device.currentValue("heatingSetpoint") - 1 + + if( nextLevel < 40){ + nextLevel = 40 + } + log.debug "Setting heat set point down to: ${nextLevel}" + setHeatingSetpoint(nextLevel) +} + +def parse(String description) +{ + def map = createEvent(zwaveEvent(zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3]))) + if (!map) { + return null + } + + def result = [map] + if (map.isStateChange && map.name in ["heatingSetpoint","coolingSetpoint","thermostatMode"]) { + def map2 = [ + name: "thermostatSetpoint", + unit: "F" + ] + if (map.name == "thermostatMode") { + updateState("lastTriedMode", map.value) + if (map.value == "cool") { + map2.value = device.latestValue("coolingSetpoint") + log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}" + } + else { + map2.value = device.latestValue("heatingSetpoint") + log.info "THERMOSTAT, latest heating setpoint = ${map2.value}" + } + } + else { + def mode = device.latestValue("thermostatMode") + log.info "THERMOSTAT, latest mode = ${mode}" + if ((map.name == "heatingSetpoint" && mode == "heat") || (map.name == "coolingSetpoint" && mode == "cool")) { + map2.value = map.value + map2.unit = map.unit + } + } + if (map2.value != null) { + log.debug "THERMOSTAT, adding setpoint event: $map" + result << createEvent(map2) + } + } else if (map.name == "thermostatFanMode" && map.isStateChange) { + updateState("lastTriedFanMode", map.value) + } + log.debug "Parse returned $result" + result +} + +// Event Generation +def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) +{ + def map = [:] + map.value = cmd.scaledValue.toString() + map.unit = cmd.scale == 1 ? "F" : "C" + map.displayed = false + switch (cmd.setpointType) { + case 1: + map.name = "heatingSetpoint" + break; + case 2: + map.name = "coolingSetpoint" + break; + default: + return [:] + } + // So we can respond with same format + state.size = cmd.size + state.scale = cmd.scale + state.precision = cmd.precision + map +} + +def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd) +{ + def map = [:] + switch (cmd.sensorType) { + case 1: + // temperature + map.value = cmd.scaledSensorValue.toString() + map.unit = cmd.scale == 1 ? "F" : "C" + map.name = "temperature" + break; + case 5: + // humidity + map.value = cmd.scaledSensorValue.toInteger().toString() + map.unit = "%" + map.name = "humidity" + break; + } + map +} + +def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd) +{ + def map = [:] + switch (cmd.operatingState) { + case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE: + map.value = "idle" + break + case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_HEATING: + map.value = "heating" + break + case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_COOLING: + map.value = "cooling" + break + case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_FAN_ONLY: + map.value = "fan only" + break + case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_HEAT: + map.value = "pending heat" + break + case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_COOL: + map.value = "pending cool" + break + case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_VENT_ECONOMIZER: + map.value = "vent economizer" + break + } + map.name = "thermostatOperatingState" + map +} + +def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) { + def map = [:] + switch (cmd.mode) { + case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF: + map.value = "off" + break + case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT: + map.value = "heat" + break + case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT: + map.value = "emergencyHeat" + break + case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL: + map.value = "cool" + break + case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO: + map.value = "auto" + break + } + map.name = "thermostatMode" + map +} + +def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) { + def map = [:] + switch (cmd.fanMode) { + case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW: + map.value = "fanAuto" + break + case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW: + map.value = "fanOn" + break + case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION: + map.value = "fanCirculate" + break + } + map.name = "thermostatFanMode" + map.displayed = false + map +} + +def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) { + def supportedModes = "" + if(cmd.off) { supportedModes += "off " } + if(cmd.heat) { supportedModes += "heat " } + if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergencyHeat " } + if(cmd.cool) { supportedModes += "cool " } + if(cmd.auto) { supportedModes += "auto " } + + updateState("supportedModes", supportedModes) +} + +def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) { + def supportedFanModes = "" + if(cmd.auto) { supportedFanModes += "fanAuto " } + if(cmd.low) { supportedFanModes += "fanOn " } + if(cmd.circulation) { supportedFanModes += "fanCirculate " } + + updateState("supportedFanModes", supportedFanModes) +} + +def updateState(String name, String value) { + state[name] = value + device.updateDataValue(name, value) +} + +def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { + log.debug "Zwave event received: $cmd" +} + +def zwaveEvent(physicalgraph.zwave.Command cmd) { + log.warn "Unexpected zwave command $cmd" +} + +// Command Implementations +def poll() { + delayBetween([ + zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature + zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(), + zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format(), + zwave.thermostatModeV2.thermostatModeGet().format(), + zwave.thermostatFanModeV3.thermostatFanModeGet().format(), + zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format(), + setClock() + ], 2300) +} + +private setClock() { //once a day + def nowTime = new Date().time + def ageInMinutes = state.lastClockSet ? (nowTime - state.lastClockSet)/60000 : 1440 + log.debug "Clock set age: ${ageInMinutes} minutes" + if (ageInMinutes >= 1440) { + log.debug "Setting clock" + state.lastClockSet = nowTime + def nowCal = Calendar.getInstance(TimeZone.getTimeZone("America/Chicago")); + zwave.clockV1.clockSet(hour: nowCal.get(Calendar.HOUR_OF_DAY), minute: nowCal.get(Calendar.MINUTE), weekday: nowCal.get(Calendar.DAY_OF_WEEK)).format() + } else "delay 87" +} + +def setHeatingSetpoint(degreesF) { + setHeatingSetpoint(degreesF.toDouble()) +} + +def setHeatingSetpoint(Double degreesF) { + def p = (state.precision == null) ? 1 : state.precision + delayBetween([ + zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: 1, precision: p, scaledValue: degreesF).format(), + zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format() + ]) +} + +def setCoolingSetpoint(degreesF) { + setCoolingSetpoint(degreesF.toDouble()) +} + +def setCoolingSetpoint(Double degreesF) { + def p = (state.precision == null) ? 1 : state.precision + delayBetween([ + zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: 1, precision: p, scaledValue: degreesF).format(), + zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format() + ]) +} + +def configure() { + delayBetween([ + zwave.thermostatModeV2.thermostatModeSupportedGet().format(), + zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format(), + zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format() + ], 2300) +} + +def modes() { + ["off", "auto", "emergencyHeat", "heat", "cool"] +} + +def switchMode() { + def currentMode = device.currentState("thermostatMode")?.value + def lastTriedMode = getDataByName("lastTriedMode") ?: currentMode ?: "off" + def supportedModes = getDataByName("supportedModes") + def modeOrder = modes() + def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } + def nextMode = next(lastTriedMode) + if (supportedModes?.contains(currentMode)) { + while (!supportedModes.contains(nextMode) && nextMode != "off") { + nextMode = next(nextMode) + } + } + log.debug "Switching to mode: ${nextMode}" + switchToMode(nextMode) +} + +def switchToMode(nextMode) { + def supportedModes = getDataByName("supportedModes") + if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" + if (nextMode in modes()) { + updateState("lastTriedMode", nextMode) + return "$nextMode"() + } else { + log.debug("no mode method '$nextMode'") + } +} + +def switchFanMode() { + def currentMode = device.currentState("thermostatFanMode")?.value + def lastTriedMode = getDataByName("lastTriedFanMode") ?: currentMode ?: "off" + def supportedModes = getDataByName("supportedFanModes") ?: "fanAuto fanOn" + def modeOrder = ["fanAuto", "fanCirculate", "fanOn"] + def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } + def nextMode = next(lastTriedMode) + while (!supportedModes?.contains(nextMode) && nextMode != "fanAuto") { + nextMode = next(nextMode) + } + switchToFanMode(nextMode) +} + +def switchToFanMode(nextMode) { + def supportedFanModes = getDataByName("supportedFanModes") + if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" + + def returnCommand + if (nextMode == "fanAuto") { + returnCommand = fanAuto() + } else if (nextMode == "fanOn") { + returnCommand = fanOn() + } else if (nextMode == "fanCirculate") { + returnCommand = fanCirculate() + } else { + log.debug("no fan mode '$nextMode'") + } + if(returnCommand) updateState("lastTriedFanMode", nextMode) + returnCommand +} + +def getDataByName(String name) { + state[name] ?: device.getDataValue(name) +} + +def getModeMap() { [ + "off": 0, + "heat": 1, + "cool": 2, + "emergency heat": 4 +]} + +def setThermostatMode(String value) { + delayBetween([ + zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(), + zwave.thermostatModeV2.thermostatModeGet().format() + ]) +} + +def getFanModeMap() { [ + "auto": 0, + "on": 1, + "circulate": 6 +]} + +def setThermostatFanMode(String value) { + delayBetween([ + zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(), + zwave.thermostatFanModeV3.thermostatFanModeGet().format() + ]) +} + +def off() { + delayBetween([ + zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(), + zwave.thermostatModeV2.thermostatModeGet().format() + ]) +} + +def heat() { + delayBetween([ + zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(), + zwave.thermostatModeV2.thermostatModeGet().format() + ]) +} + +def emergencyHeat() { + delayBetween([ + zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(), + zwave.thermostatModeV2.thermostatModeGet().format() + ]) +} + +def cool() { + delayBetween([ + zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(), + zwave.thermostatModeV2.thermostatModeGet().format() + ]) +} + +def auto() { + delayBetween([ + zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(), + zwave.thermostatModeV2.thermostatModeGet().format() + ]) +} + +def fanOn() { + delayBetween([ + zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(), + zwave.thermostatFanModeV3.thermostatFanModeGet().format() + ]) +} + +def fanAuto() { + delayBetween([ + zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(), + zwave.thermostatFanModeV3.thermostatFanModeGet().format() + ]) +} + +def fanCirculate() { + delayBetween([ + zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(), + zwave.thermostatFanModeV3.thermostatFanModeGet().format() + ]) +} \ No newline at end of file