Skip to content

Instantly share code, notes, and snippets.

@rappleg
Last active December 30, 2015 02:09
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 rappleg/7760460 to your computer and use it in GitHub Desktop.
Save rappleg/7760460 to your computer and use it in GitHub Desktop.
CentraLite Thermostat
/**
* CentraLite Thermostat
*
* Author: SmartThings
* Date: 2013-12-02
*/
metadata {
// 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 "0201": "read attr - raw: 637A0102010C00000029AF08, dni: 637A, endpoint: 01, cluster: 0201, size: 0C, attrId: 0000, encoding: 0029, value: 08af"
}
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"]
]
)
}
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "off", label:'${name}', action:"switchMode"
state "heat", label:'${name}', action:"switchMode"
state "cool", label:'${name}', action:"switchMode"
}
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "fanAuto", label:'${name}', action:"switchFanMode"
state "fanOn", label:'${name}', action:"switchFanMode"
}
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue}° heat', unit:"F", backgroundColor:"#ffffff"
}
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setCoolingSetpoint", action:"thermostat.setCoolingSetpoint", backgroundColor: "#1e9cbb"
}
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"
}
main "temperature"
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh", "configure"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parse description $description"
def map = [:]
if (description?.startsWith("read attr -")) {
// TODO: support more than just temperature
log.debug "Read attr"
def reportValue = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
map.name = "temperature"
map.value = toFarenheit(Integer.parseInt(reportValue, 16) / 100)
}
def result = null
if (map) {
result = createEvent(map)
}
log.debug "Parse returned ${result?.descriptionText}"
return result
}
def refresh()
{
log.debug "refresh called"
"st rattr 0x${device.deviceNetworkId} 1 0x201 0"
}
def go()
{
log.debug "before:go tile tapped"
poll()
log.debug "after"
}
def poll() {
log.debug "Executing 'poll'"
refresh()
}
def parseEventData(Map results)
{
log.debug "parsing data $results"
if(results)
{
results.each { name, value ->
def linkText = getLinkText(device)
def isStateChange = isTemperatureStateChange(device, name, value.toString())
sendEvent(
name: name,
value: value,
unit: "F",
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isStateChange,
displayed: isStateChange
)
}
}
}
private getThermostatDescriptionText(name, value, linkText)
{
if(name == "temperature")
{
return "$linkText was $value°F"
}
else if(name == "heatingSetpoint")
{
return "latest heating setpoint was $value°F"
}
else if(name == "coolingSetpoint")
{
return "latest cooling setpoint was $value°F"
}
}
def toCelsius(degreesF) {
log.debug "Farenheit: $degreesF"
def celsius = ((degreesF - 32) * 5/9 as Double).round(2)
log.debug "Celsius: $celsius"
celsius
}
def toFarenheit(degreesC) {
log.debug "Celsius: $degreesC"
def farenheit = ((degreesC * 9/5 + 32) as Double).round(0)
log.debug "Farenheit: $farenheit"
farenheit
}
def setHeatingSetpoint(degreesF) {
def celsius = toCelsius(degreesF)
log.debug "setHeatingSetpoint({$celsius})"
sendEvent("name":"heatingSetpoint", "value":celsius)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius) + "}"
}
def setCoolingSetpoint(degreesF) {
def celsius = toCelsius(degreesF)
log.debug "setCoolingSetpoint({$celsius})"
sendEvent("name":"coolingSetpoint", "value":celsius)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius) + "}"
}
def modes() {
["off", "heat", "cool"]
}
def switchMode() {
log.debug "switchMode"
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = getDataByName("lastTriedMode") ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedModes")
def modeOrder = modes()
def index = modeOrder.indexOf(lastTriedMode)
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)
}
}
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 currentFanMode = device.currentState("thermostatFanMode")?.value
log.debug "switching fan from current mode: $currentFanMode"
def returnCommand
switch (currentFanMode) {
case "fanAuto":
returnCommand = "st wattr 0x${device.deviceNetworkId} 1 0x202 0x30 {05}"
break
case "fanOn":
returnCommand = "st wattr 0x${device.deviceNetworkId} 1 0x202 0x30 {04}"
break
}
if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") }
returnCommand
}
def switchToFanMode(nextMode) {
def supportedFanModes = getDataByName("supportedFanModes")
log.debug "supportedFanModes: $supportedFanModes"
log.debug "switching to fan mode: $nextMode"
def returnCommand
if(nextMode == "fanAuto") {
if(!supportedFanModes || supportedFanModes.contains("fanAuto")) {
returnCommand = fanAuto()
} else {
returnCommand = switchToFanMode("fanOn")
}
} else if(nextMode == "fanOn") {
if(!supportedFanModes || supportedFanModes.contains("fanOn")) {
returnCommand = fanOn()
}
}
returnCommand
}
def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
"$value"()
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
"$value"()
}
def off() {
log.debug "off"
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {00}"
}
def heat() {
log.debug "heat"
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {04}"
}
def cool() {
log.debug "cool"
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {03}"
}
def fanOn() {
log.debug "fanOn"
"st wattr 0x${device.deviceNetworkId} 1 0x202 0x30 {04}"
}
def fanAuto() {
log.debug "fanAuto"
"st wattr 0x${device.deviceNetworkId} 1 0x202 0x30 {05}"
}
def configure() {
log.debug "binding to Thermostat and Fan Control cluster"
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x201 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x202 {${device.zigbeeId}} {}"
]
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment