Skip to content

Instantly share code, notes, and snippets.

@joshualyon
Created June 10, 2015 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshualyon/cb0f1577bd9a3d50bfbb to your computer and use it in GitHub Desktop.
Save joshualyon/cb0f1577bd9a3d50bfbb to your computer and use it in GitHub Desktop.
metadata {
// Automatically generated. Make future change here.
// Mostly core Z-Wave Thermostat Code from SmartThings
// Just a few modifications made by Josh to add the current operating state tiles
definition (name: "TStat with State", namespace: "boshdirect", author: "SmartThings + Josh Lyon") {
capability "Actuator"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Thermostat"
capability "Configuration"
capability "Polling"
capability "Sensor"
attribute "thermostatFanState", "string"
command "switchMode"
command "switchFanMode"
command "quickSetCool"
command "quickSetHeat"
fingerprint deviceId: "0x08"
fingerprint inClusters: "0x43,0x40,0x44,0x31"
}
// simulator metadata
simulator {
status "off" : "command: 4003, payload: 00"
status "heat" : "command: 4003, payload: 01"
status "cool" : "command: 4003, payload: 02"
status "auto" : "command: 4003, payload: 03"
status "emergencyHeat" : "command: 4003, payload: 04"
status "fanAuto" : "command: 4403, payload: 00"
status "fanOn" : "command: 4403, payload: 01"
status "fanCirculate" : "command: 4403, payload: 06"
status "heat 60" : "command: 4303, payload: 01 09 3C"
status "heat 68" : "command: 4303, payload: 01 09 44"
status "heat 72" : "command: 4303, payload: 01 09 48"
status "cool 72" : "command: 4303, payload: 02 09 48"
status "cool 76" : "command: 4303, payload: 02 09 4C"
status "cool 80" : "command: 4303, payload: 02 09 50"
status "temp 58" : "command: 3105, payload: 01 2A 02 44"
status "temp 62" : "command: 3105, payload: 01 2A 02 6C"
status "temp 70" : "command: 3105, payload: 01 2A 02 BC"
status "temp 74" : "command: 3105, payload: 01 2A 02 E4"
status "temp 78" : "command: 3105, payload: 01 2A 03 0C"
status "temp 82" : "command: 3105, payload: 01 2A 03 34"
status "idle" : "command: 4203, payload: 00"
status "heating" : "command: 4203, payload: 01"
status "cooling" : "command: 4203, payload: 02"
status "fan only" : "command: 4203, payload: 03"
status "pending heat" : "command: 4203, payload: 04"
status "pending cool" : "command: 4203, payload: 05"
status "vent economizer": "command: 4203, payload: 06"
// reply messages
reply "2502": "command: 2503, payload: FF"
}
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "off", label:'${name}', action:"switchMode", nextState:"to_heat"
state "heat", label:'${name}', action:"switchMode", nextState:"to_cool"
state "cool", label:'${name}', action:"switchMode", nextState:"..."
state "auto", label:'${name}', action:"switchMode", nextState:"..."
state "emergency heat", label:'${name}', action:"switchMode", nextState:"..."
state "to_heat", label: "heat", action:"switchMode", nextState:"to_cool"
state "to_cool", label: "cool", action:"switchMode", nextState:"..."
state "...", label: "...", action:"off", nextState:"off"
}
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "fanAuto", label:'${name}', action:"switchFanMode"
state "fanOn", label:'${name}', action:"switchFanMode"
state "fanCirculate", label:'${name}', action:"switchFanMode"
}
valueTile("opStateWide", "device.thermostatOperatingState", height: 1, width: 2, decoration: "flat") {
state "thermostatOperatingState", label:'${currentValue}', icon: ""
}
standardTile("opStateIcon", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat"){
state "default", label: "unknown", defaultState: true, icon: "st.unknown.unknown.unkown"
state "idle", label: "idle", icon: ""
state "fan only", label: "", icon: "st.thermostat.fan-on"
state "vent economizer", label: "Vent Eco", icon: "st.nest.nest-leaf"
state "cooling", label: "", icon: "st.thermostat.cooling"
state "heating", label: "", icon: "st.thermostat.heating"
state "pending heat", label: "", icon: "st.thermostat.heat"
state "pending cool", label: "", icon: "st.thermostat.cool"
}
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setHeatingSetpoint", action:"quickSetHeat", backgroundColor:"#d04e00"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue}° heat', backgroundColor:"#ffffff"
}
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setCoolingSetpoint", action:"quickSetCool", backgroundColor: "#1e9cbb"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue}° cool', 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"
}
main "temperature"
details(["temperature", "mode", "fanMode", "opStateWide", "opStateIcon", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh", "configure"])
}
}
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: getTemperatureScale()
]
if (map.name == "thermostatMode") {
state.lastTriedMode = map.value
if (map.value == "cool") {
map2.value = device.latestValue("coolingSetpoint")
log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}"
}
else {
map2.value = device.latestValue("heatingSetpoint")
log.info "THERMOSTAT, latest heating setpoint = ${map2.value}"
}
}
else {
def mode = device.latestValue("thermostatMode")
log.info "THERMOSTAT, latest mode = ${mode}"
if ((map.name == "heatingSetpoint" && mode == "heat") || (map.name == "coolingSetpoint" && mode == "cool")) {
map2.value = map.value
map2.unit = map.unit
}
}
if (map2.value != null) {
log.debug "THERMOSTAT, adding setpoint event: $map"
result << createEvent(map2)
}
} else if (map.name == "thermostatFanMode" && map.isStateChange) {
state.lastTriedFanMode = map.value
}
log.debug "Parse returned $result"
result
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{
def cmdScale = cmd.scale == 1 ? "F" : "C"
def map = [:]
map.value = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
map.displayed = false
switch (cmd.setpointType) {
case 1:
map.name = "heatingSetpoint"
break;
case 2:
map.name = "coolingSetpoint"
break;
default:
return [:]
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
map
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd)
{
def map = [:]
if (cmd.sensorType == 1) {
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
} else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue
map.unit = "%"
map.name = "humidity"
}
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd)
{
def map = [:]
switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_HEATING:
map.value = "heating"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_COOLING:
map.value = "cooling"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_FAN_ONLY:
map.value = "fan only"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_HEAT:
map.value = "pending heat"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_COOL:
map.value = "pending cool"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_VENT_ECONOMIZER:
map.value = "vent economizer"
break
}
map.name = "thermostatOperatingState"
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) {
def map = [name: "thermostatFanState", unit: ""]
switch (cmd.fanOperatingState) {
case 0:
map.value = "idle"
break
case 1:
map.value = "running"
break
case 2:
map.value = "running high"
break
}
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "heat"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT:
map.value = "emergency heat"
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 += "emergency heat " }
if(cmd.cool) { supportedModes += "cool " }
if(cmd.auto) { supportedModes += "auto " }
state.supportedModes = supportedModes
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
def supportedFanModes = ""
if(cmd.auto) { supportedFanModes += "fanAuto " }
if(cmd.low) { supportedFanModes += "fanOn " }
if(cmd.circulation) { supportedFanModes += "fanCirculate " }
state.supportedFanModes = supportedFanModes
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "Zwave event received: $cmd"
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "Unexpected zwave command $cmd"
}
// Command Implementations
def 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()
], 2300)
}
def quickSetHeat(degrees) {
setHeatingSetpoint(degrees, 1000)
}
def setHeatingSetpoint(degrees, delay = 30000) {
setHeatingSetpoint(degrees.toDouble(), delay)
}
def setHeatingSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setHeatingSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()
], delay)
}
def quickSetCool(degrees) {
setCoolingSetpoint(degrees, 1000)
}
def setCoolingSetpoint(degrees, delay = 30000) {
setCoolingSetpoint(degrees.toDouble(), delay)
}
def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setCoolingSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()
], delay)
}
def configure() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),
zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format(),
zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()
], 2300)
}
def modes() {
["off", "heat", "cool", "auto", "emergency heat"]
}
def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedModes")
def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode)
if (supportedModes?.contains(currentMode)) {
while (!supportedModes.contains(nextMode) && nextMode != "off") {
nextMode = next(nextMode)
}
}
state.lastTriedMode = nextMode
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], 1000)
}
def switchToMode(nextMode) {
def supportedModes = getDataByName("supportedModes")
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
if (nextMode in modes()) {
state.lastTriedMode = nextMode
"$nextMode"()
} else {
log.debug("no mode method '$nextMode'")
}
}
def switchFanMode() {
def currentMode = device.currentState("thermostatFanMode")?.value
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedFanModes") ?: "fanAuto fanOn"
def modeOrder = ["fanAuto", "fanCirculate", "fanOn"]
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode)
while (!supportedModes?.contains(nextMode) && nextMode != "fanAuto") {
nextMode = next(nextMode)
}
switchToFanMode(nextMode)
}
def switchToFanMode(nextMode) {
def supportedFanModes = getDataByName("supportedFanModes")
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
def returnCommand
if (nextMode == "fanAuto") {
returnCommand = fanAuto()
} else if (nextMode == "fanOn") {
returnCommand = fanOn()
} else if (nextMode == "fanCirculate") {
returnCommand = fanCirculate()
} else {
log.debug("no fan mode '$nextMode'")
}
if(returnCommand) state.lastTriedFanMode = nextMode
returnCommand
}
def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def getModeMap() { [
"off": 0,
"heat": 1,
"cool": 2,
"auto": 3,
"emergency heat": 4
]}
def setThermostatMode(String value) {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def getFanModeMap() { [
"auto": 0,
"on": 1,
"circulate": 6
]}
def setThermostatFanMode(String value) {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def off() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def heat() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def emergencyHeat() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def cool() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def auto() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
}
def fanOn() {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def fanAuto() {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
def fanCirculate() {
delayBetween([
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
}
private getStandardDelay() {
1000
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment