Created
November 4, 2017 01:35
-
-
Save dmsimard/6eb0fb296d030c40144a7123a0254a78 to your computer and use it in GitHub Desktop.
TH1123ZB-TH1124ZB Zigbee Sinope Thermostat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Copyright Sinopé Technologies | |
SVN-311 | |
**/ | |
preferences { | |
// input("zipcode", "text", title: "ZipCode for setting outdoor Temp", description: "by default,use current hub location") | |
input("trace", "bool", title: "trace", description: | |
"Set it to true to enable tracing or leave it empty (no tracing)") | |
input("logFilter", "number",title: "(1=ERROR only,2=<1+WARNING>,3=<2+INFO>,4=<3+DEBUG>,5=<4+TRACE>)", range: "1..5", | |
description: "optional" ) | |
} | |
metadata { | |
// Automatically generated. Make future change here. | |
definition(name: "TH1123ZB-TH1124ZB Sinope Thermostat", namespace: "Sinope Technologies", author: "Rejean Bouchard/Yves Racine") { | |
capability "thermostatHeatingSetpoint" | |
capability "thermostatMode" | |
capability "thermostatOperatingState" | |
capability "thermostatSetpoint" | |
capability "Actuator" | |
capability "Temperature Measurement" | |
capability "Thermostat" | |
capability "Configuration" | |
capability "Refresh" | |
capability "Sensor" | |
attribute "temperatureDisplayMode", "enum", ["Deg_C", "Deg_F"] | |
attribute "occupancyStatus", "enum", ["unoccupy", "occupy"] | |
attribute "outdoorTemp", "number" | |
attribute "heatingSetpointRangeHigh", "number" | |
attribute "heatingSetpointRangeLow", "number" | |
attribute "heatingSetpointRange", "VECTOR3" | |
attribute "verboseTrace", "string" | |
attribute "presence", "enum", ["present", "non present"] | |
command "heatLevelUp" | |
command "heatLevelDown" | |
command "setThermostatMode" | |
command "switchMode" | |
command "setTemperatureDisplayMode" | |
command "setOccupancyStatus" | |
command "setThermostatSetpoint", ["number"] | |
command "setHeatingSetpointRangeHigh" | |
command "setHeatingSetpointRangeLow" | |
command "setHeatingSetpointRange" | |
command "unoccupy" | |
command "occupy" | |
command "present" | |
command "away" | |
fingerprint endpoint: "1", | |
profileId: "0104", | |
inClusters: "0000,0003,0004,0005,0201,0204,0402,0B04,0B05", | |
outClusters: "0019" | |
fingerprint endpoint: "1", | |
profileId: "0104", | |
inClusters: "0000,0003,0004,0005,0201,0204,0402,0B04,0B05", | |
outClusters: "0019" | |
// fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0201,0204,0402,0B04,0B05", manufacturer: "Sinope Technologies", model: "TH1123ZB" | |
// fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0201,0204,0402,0B04,0B05", manufacturer: "Sinope Technologies", model: "TH1124ZB" | |
} | |
//-------------------------------------------------------------------------------------------------------- | |
// simulator metadata | |
simulator {} | |
//-------------------------------------------------------------------------------------------------------- | |
tiles(scale: 2) { | |
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4, canChangeIcon: true) { | |
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { | |
attributeState("default", label:'${currentValue}', unit:"dF", backgroundColor:"#269bd2") | |
} | |
tileAttribute("device.heatingSetpoint", key: "VALUE_CONTROL") { | |
attributeState("VALUE_UP", action: "heatLevelUp") | |
attributeState("VALUE_DOWN", action: "heatLevelDown") | |
} | |
tileAttribute("device.heatingDemand", key: "SECONDARY_CONTROL") { | |
attributeState("default", label:'${currentValue}%', unit:"%") | |
} | |
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { | |
attributeState("idle", backgroundColor:"#44b621") | |
attributeState("heating", backgroundColor:"#ffa81e") | |
attributeState("cooling", backgroundColor:"#269bd2") | |
} | |
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { | |
attributeState("off", label:'${name}') | |
attributeState("heat", label:'${name}') | |
} | |
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { | |
attributeState("default", label:'${currentValue}', unit:"dF") | |
} | |
} | |
//-- Value Tiles ------------------------------------------------------------------------------------------- | |
valueTile("temperature", "device.temperature", width: 4, height: 2) { | |
state("temperature", label: '${currentValue}°', | |
backgroundColors: getBackgroundColors() | |
) | |
} | |
valueTile("heatingDemand", "device.heatingDemand", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { | |
state "heatingDemand", label: '${currentValue}%', unit: "%", backgroundColor: "#ffffff" | |
} | |
//-- Standard Tiles ---------------------------------------------------------------------------------------- | |
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { | |
state "heat", label: '${currentValue}°', backgroundColor: "#ffffff" | |
} | |
standardTile("heatLevelUp", "device.heatingSetpoint", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { | |
state "default", label: '', action: "heatLevelUp", icon: "st.thermostat.thermostat-up" | |
} | |
standardTile("heatLevelDown", "device.heatingSetpoint", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { | |
state "default", label: '', action: "heatLevelDown", icon: "st.thermostat.thermostat-down" | |
} | |
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) { | |
state "idle", label:'${name}', backgroundColor:"#ffffff" | |
state "heating", label:'${name}', backgroundColor:"#e86d13" | |
state "cooling", label:'${name}', backgroundColor:"#00A0DC" | |
} | |
standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, height: 2,width: 2,decoration: "flat") { | |
state "off", label:'', action:"switchMode", icon:"st.thermostat.heating-cooling-off" | |
state "heat",label:'', action:"switchMode", icon:"st.thermostat.heat", defaultState: true | |
} | |
standardTile("temperatureDisplayMode", "device.temperatureDisplayMode", inactiveLabel: false, height: 2, width: 2, decoration: "flat") { | |
state "Deg_C", label: '${name}', action: "setTemperatureDisplayMode", icon: "st.alarm.temperature.normal", defaultState: true | |
state "Deg_F", label: '${name}', action: "setTemperatureDisplayMode", icon: "st.alarm.temperature.normal" | |
} | |
standardTile("occupancyStatus", "device.occupancyStatus", inactiveLabel: false, height: 2,width: 2,decoration: "flat") { | |
state "occupy", label:'${name}', action:"setOccupancyStatus", icon:"st.presence.house.unlocked", defaultState: true | |
state "unoccupy", label:'${name}', action:"setOccupancyStatus", icon:"st.presence.house.secured" | |
} | |
standardTile("refresh", "device.temperature", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { | |
state "default", action: "refresh.refresh", icon: "st.secondary.refresh" | |
} | |
standardTile("weatherTemperature", "device.outdoorTemp", inactiveLabel:false, width: 2, height: 2, | |
decoration: "flat", canChangeIcon: false) { | |
state "default", label: 'OutdoorTemp ${currentValue}°', unit:"dF", | |
icon: "st.Weather.weather2", | |
backgroundColor: "#ffffff" | |
} | |
standardTile("configure", "device.configure", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { | |
state "configure", label: '', action: "configuration.configure", icon: "st.secondary.configure" | |
} | |
//-- Main & Details ---------------------------------------------------------------------------------------- | |
main("thermostatMulti") | |
// details(["thermostatMulti","heatLevelUp","heatingSetpoint","heatLevelDown","thermostatMode", "occupancyStatus", "temperatureDisplayMode", "refresh", "configure"]) | |
details(["thermostatMulti","thermostatMode", "refresh"]) | |
// details(["thermostatMulti","heatLevelUp","heatingSetpoint","heatLevelDown","thermostatMode", "refresh"]) / To uncomment if you want the heatLevelDown and heatLevelUp in the UI | |
} | |
} | |
def getBackgroundColors() { | |
def results | |
if (state?.scale =='C') { | |
// Celsius Color Range | |
results= | |
[ | |
[value: 0, color: "#153591"], | |
[value: 7, color: "#1e9cbb"], | |
[value: 15, color: "#90d2a7"], | |
[value: 23, color: "#44b621"], | |
[value: 29, color: "#f1d801"], | |
[value: 35, color: "#d04e00"], | |
[value: 37, color: "#bc2323"] | |
] | |
} else { | |
results = | |
// Fahrenheit Color Range | |
[ | |
[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"] | |
] | |
} | |
return results | |
} | |
//-- Installation ---------------------------------------------------------------------------------------- | |
def installed() { | |
traceEvent(settings.logFilter,"installed>Device is now Installed", settings.trace) | |
initialize() | |
} | |
def updated() { | |
traceEvent(settings.logFilter,"updated>Device is now updated", settings.trace) | |
try { | |
unschedule() | |
} catch (e) { | |
traceEvent(settings.logFilter,"updated>exception $e, continue processing", settings.trace, get_LOG_ERROR()) | |
} | |
initialize() | |
} | |
void initialize() { | |
state?.scale=getTemperatureScale() | |
runEvery5Minutes(refresh) | |
def supportedThermostatModes=['off', 'heat'] | |
state?.supportedThermostatModes= supportedThermostatModes | |
sendEvent(name: "supportedThermostatModes", value: supportedThermostatModes, displayed: (settings.trace?:false)) | |
def supportedThermostatFanModes=[] | |
sendEvent(name: "supportedThermostatFanModes", value: supportedThermostatFanModes, displayed: (settings.trace?:false)) | |
configure() | |
} | |
def ping() { | |
refresh() | |
} | |
def uninstalled() { | |
unschedule() | |
} | |
//-- Parsing --------------------------------------------------------------------------------------------- | |
// parse events into attributes | |
def parse(String description) { | |
def scale = getTemperatureScale() | |
state?.scale = scale | |
traceEvent(settings.logFilter,"parse>Description :( $description )", settings.trace) | |
def cluster = zigbee.parse(description) | |
traceEvent(settings.logFilter,"parse>Cluster : $cluster", settings.trace) | |
def map = [:] | |
if (description?.startsWith("read attr -")) { | |
def descMap = parseDescriptionAsMap(description) | |
traceEvent(settings.logFilter,"parse>Desc Map: $descMap", settings.trace) | |
if (descMap.cluster == "0201" && descMap.attrId == "0000") { | |
map.name = "temperature" | |
map.value = getTemperatureValue(descMap.value) | |
traceEvent(settings.logFilter,"parse>ACTUAL TEMP: ${map.value}", settings.trace) | |
} else if (descMap.cluster == "0201" && descMap.attrId == "0008") { | |
map.name = "heatingDemand" | |
map.value = getHeatingDemand(descMap.value) | |
def operatingState= (map.value.toInteger() < 10) ? "idle" :"heating" | |
def isChange = isStateChange(device, "thermostatOperatingState", operatingState) | |
def isDisplayed = isChange | |
sendEvent(name:"thermostatOperatingState", value: operatingState, displayed: isDisplayed) | |
traceEvent(settings.logFilter,"parse>HEATING DEMAND: ${map.value}, operatingState=$operatingState", settings.trace) | |
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") { | |
def currentStatus = device.currentState("occupancyStatus")?.value | |
if (currentStatus == "occupy") { | |
map.name = "heatingSetpoint" | |
map.value = getTemperatureValue(descMap.value) | |
def isChange = isStateChange(device, "thermostatSetpoint", map.value.toString()) | |
def isDisplayed = isChange | |
sendEvent(name:"thermostatSetpoint", value: map.value, unit: scale, displayed: isDisplayed) | |
traceEvent(settings.logFilter,"parse>OCCUPY HEATING SETPOINT:${map.value} ", settings.trace) | |
} | |
} else if (descMap.cluster == "0201" && descMap.attrId == "0014") { | |
def currentStatus = device.currentState("occupancyStatus")?.value | |
if (currentStatus == "unoccupy") { | |
map.name = "heatingSetpoint" | |
map.value = getTemperatureValue(descMap.value) | |
def isChange = isStateChange(device, "thermostatSetpoint", map.value.toString()) | |
def isDisplayed = isChange | |
sendEvent(name:"thermostatSetpoint", value:map.value, unit: scale, displayed: isDisplayed) | |
traceEvent(settings.logFilter,"parse>UNOCCUPY HEATING SETPOINT: ${map.value}", settings.trace) | |
} | |
} else if (descMap.cluster == "0201" && descMap.attrId == "0015") { | |
map.name = "heatingSetpointRangeLow" | |
map.value = getTemperatureValue(descMap.value) | |
traceEvent(settings.logFilter,"parse>LOW HeatingSetpoint: ${map.value}", settings.trace) | |
} else if (descMap.cluster == "0201" && descMap.attrId == "0016") { | |
map.name = "heatingSetpointRangeHigh" | |
map.value = getTemperatureValue(descMap.value) | |
traceEvent(settings.logFilter,"parse>HIGH HeatingSetpoint: ${map.value}", settings.trace) | |
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") { | |
map.name = "thermostatMode" | |
map.value = getModeMap()[descMap.value] | |
traceEvent(settings.logFilter,"MODE: ${map.value}", settings.trace) | |
} else if (descMap.cluster == "0201" && descMap.attrId == "0002") { | |
map.name = "occupancyStatus" | |
map.value = getOccupancyMap()[descMap.value] | |
traceEvent(settings.logFilter,"parse>OCCUPANCY: ${map.value}", settings.trace) | |
if (map.value == "occupy") { | |
def isChange = isStateChange(device, "presence", "present") | |
def isDisplayed = isChange | |
sendEvent(name:"presence", value: "present" , displayed: isDisplayed) | |
} else { | |
def isChange = isStateChange(device, "presence", "non present") | |
def isDisplayed = isChange | |
sendEvent(name:"presence", value: "non present" , displayed: isDisplayed) | |
} | |
} else if (descMap.cluster == "0201" && descMap.attrId == "0400") { | |
map.name = "occupancyStatus" | |
map.value = getOccupancyMap()[descMap.value] | |
traceEvent(settings.logFilter,"parse>MF-OCCUPANCY", settings.trace) | |
if (map.value == "occupy") { | |
def isChange = isStateChange(device, "presence", "present") | |
def isDisplayed = isChange | |
sendEvent(name:"presence", value: "present" , displayed: isDisplayed) | |
} else { | |
def isChange = isStateChange(device, "presence", "non present") | |
def isDisplayed = isChange | |
sendEvent(name:"presence", value: "non present" , displayed: isDisplayed) | |
} | |
} else if (descMap.cluster == "0204" && descMap.attrId == "0000") { | |
map.name = "temperatureDisplayMode" | |
map.value = getTemperatureDisplayModeMap()[descMap.value] | |
traceEvent(settings.logFilter,"parse>DISPLAY MODE: ${map.value}", settings.trace) | |
} | |
} | |
def result = null | |
if (map) { | |
def isChange = isStateChange(device, map.name, map.value.toString()) | |
map.displayed = isChange | |
if ((map.name.toLowerCase().contains("temp")) || (map.name.toLowerCase().contains("setpoint"))) { | |
map.scale=scale | |
} | |
result = createEvent(map) | |
// sendEvent(map) | |
} | |
traceEvent(settings.logFilter,"Parse returned $map", settings.trace) | |
return result | |
} | |
//-------------------------------------------------------------------------------------------------------- | |
def parseDescriptionAsMap(description) { | |
traceEvent(settings.logFilter,"parseDescriptionAsMap>parsing MAP ...", settings.trace) | |
(description - "read attr - ").split(",").inject([:]) { | |
map, param -> | |
def nameAndValue = param.split(":") | |
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] | |
} | |
} | |
//-- Temperature ----------------------------------------------------------------------------------------- | |
def getTemperatureValue(value) { | |
def scale= state?.scale | |
if (value != null) { | |
def celsius = Integer.parseInt(value, 16) / 100 | |
if (scale == "C") { | |
return celsius.toDouble().round(1) | |
} else { | |
return Math.round(celsiusToFahrenheit(celsius)) | |
} | |
} | |
} | |
//-- Heating Demand -------------------------------------------------------------------------------------- | |
def getHeatingDemand(value) { | |
if (value != null) { | |
def demand = Integer.parseInt(value, 16) | |
return demand.toString() | |
} | |
} | |
//-- Heating Setpoint ------------------------------------------------------------------------------------ | |
def heatLevelUp() { | |
def scale = state?.scale | |
def heatingSetpointRangeHigh | |
double nextLevel | |
try { | |
heatingSetpointRangeHigh = device.latestValue("heatingSetpointRangeHigh") | |
} catch (any) { | |
traceEvent(settings.logFilter, "heatLevelUp>not able to get heatingSetpointRangeLow ($heatingSetpointRangeLow),using default value", | |
settings.trace, get_LOG_WARN()) | |
} | |
heatingSetpointRangeHigh = (heatingSetpointRangeHigh) ?: (scale == 'C') ? 10.0 : 50 | |
if (scale == 'C') { | |
nextLevel = device.currentValue("heatingSetpoint").toDouble() | |
nextLevel = (nextLevel + 0.5).round(1) | |
if (nextLevel > heatingSetpointRangeHigh.toDouble().round(1)) { | |
nextLevel = heatingSetpointRangeHigh.toDouble().round(1) | |
} | |
setHeatingSetpoint(nextLevel) | |
} else { | |
nextLevel = device.currentValue("heatingSetpoint") | |
nextLevel = (nextLevel + 1) | |
if (nextLevel < heatingSetpointRangeHigh.toDouble()) { | |
nextLevel = heatingSetpointRangeHigh.toDouble() | |
} | |
setHeatingSetpoint(nextLevel.intValue()) | |
} | |
} | |
def heatLevelDown() { | |
def scale = state?.scale | |
def heatingSetpointRangeLow | |
double nextLevel | |
try { | |
heatingSetpointRangeLow = device.latestValue("heatingSetpointRangeLow") | |
} catch (any) { | |
traceEvent(settings.logFilter,"heatLevelDown>not able to get heatingSetpointRangeLow ($heatingSetpointRangeLow),using default value", | |
settings.trace, get_LOG_WARN()) | |
} | |
heatingSetpointRangeLow = (heatingSetpointRangeLow) ?: (scale == 'C') ? 10.0 : 50 | |
if (scale == 'C') { | |
nextLevel = device.currentValue("heatingSetpoint").toDouble() | |
nextLevel = (nextLevel - 0.5).round(1) | |
if (nextLevel <= heatingSetpointRangeLow.toDouble().round(1)) { | |
nextLevel = heatingSetpointRangeLow.toDouble().round(1) | |
} | |
setHeatingSetpoint(nextLevel) | |
} else { | |
nextLevel = device.currentValue("heatingSetpoint") | |
nextLevel = (nextLevel - 1) | |
if (nextLevel < heatingSetpointRangeLow.toDouble()) { | |
nextLevel = heatingSetpointRangeLow.toDouble() | |
} | |
setHeatingSetpoint(nextLevel.intValue()) | |
} | |
} | |
void setThermostatSetpoint(temp) { | |
setHeatingSetpoint(temp) | |
} | |
def setHeatingSetpoint(degrees) { | |
def scale = state?.scale | |
def degreesDouble = degrees as Double | |
sendEvent("name":"heatingSetpoint", "value":degreesDouble, displayed:true) | |
sendEvent("name":"thermostatSetpoint", "value":degreesDouble,displayed:true) | |
traceEvent(settings.logFilter,"setHeatingSetpoint> new setPoint: $degreesDouble", settings.trace) | |
def celsius = (scale== "C") ? degreesDouble : (fahrenheitToCelsius(degreesDouble) as Double).round(1) | |
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}" | |
} | |
def setCoolingSetpoint(temp) { | |
traceEvent(settings.logFilter,"coolingSetpoint is not supported by this thermostat", settings.trace, get_LOG_WARN()) | |
} | |
void setHeatingSetpointRange(rangeArray=[]) { | |
if ((!rangeArray) || (rangeArray.size()!=2)) { | |
traceEvent(settings.logFilter,"setHeatingSetpointRange>cannot change the thermostat Range, value ($rangeArray) is null or invalid",settings.trace, get_LOG_WARN(),true) | |
return | |
} | |
def temp_min=rangeArray[0] | |
def temp_max=rangeArray[1] | |
setHeatingSetpointRangeLow(temp_min) | |
setHeatingSetpointRangeHigh(temp_max) | |
} | |
def setHeatingSetpointRangeLow(degrees) { | |
def scale = state?.scale | |
def degreesDouble = degrees as Double | |
traceEvent(settings.logFilter,"setHeatingSetpointRangeLow> new Low: $degreesDouble", settings.trace) | |
def celsius = (scale== "C") ? degreesDouble : (fahrenheitToCelsius(degreesDouble) as Double).round(1) | |
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x15 0x29 {" + hex(celsius*100) + "}" | |
delayBetween([ | |
zigbee.readAttribute(0x0201, 0x0012), | |
zigbee.readAttribute(0x0201, 0x0015) | |
], 100) | |
float temp=degrees.toFloat().round(1) | |
sendEvent(name: 'heatingSetpointRangeLow', value: temp, isStateChange:true,unit:scale) | |
def setpointRangeHigh=device.currentValue('heatingSetpointRangeHigh') | |
def heatingSetpointRange= [temp,setpointRangeHigh] | |
sendEvent(name: "heatingSetpointRange", value: heatingSetpointRange, isStateChange: true, displayed: (settings.trace?:false)) | |
} | |
def setHeatingSetpointRangeHigh(degrees) { | |
def scale = state?.scale | |
def degreesDouble = degrees as Double | |
traceEvent(settings.logFilter,"setHeatingSetpointRangeHigh> new High: $degreesDouble", settings.trace) | |
def celsius = (scale== "C") ? degreesDouble : (fahrenheitToCelsius(degreesDouble) as Double).round(1) | |
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x16 0x29 {" + hex(celsius*100) + "}" | |
delayBetween([ | |
zigbee.readAttribute(0x0201, 0x0012), | |
zigbee.readAttribute(0x0201, 0x0016) | |
], 100) | |
float temp=degrees.toFloat().round(1) | |
sendEvent(name: 'heatingSetpointRangeHigh', value: temp, isStateChange:true,unit:scale) | |
def setpointRangeLow=device.currentValue('heatingSetpointRangeLow') | |
def heatingSetpointRange= [setpointRangeLow,temp] | |
sendEvent(name: "heatingSetpointRange", value: heatingSetpointRange, isStateChange: true, displayed: (settings.trace?:false)) | |
} | |
//-- Thermostat and Fan Modes ------------------------------------------------------------------------------------- | |
void off() { | |
setThermostatMode('off') | |
} | |
void auto() { | |
setThermostatMode('auto') | |
} | |
void heat() { | |
setThermostatMode('heat') | |
} | |
void emergencyHeat() { | |
setThermostatMode('heat') | |
} | |
void cool() { | |
setThermostatMode('cool') | |
} | |
void fanOn() { | |
setThermostatFanMode('on') | |
} | |
void fanAuto() { | |
setThermostatFanMode('auto') | |
} | |
void fanOff() { | |
setThermostatFanMode('off') | |
} | |
def fanCirculate() { | |
setThermostatFanMode('circulate') | |
} | |
def modes() { | |
["mode_off", "mode_heat"] | |
} | |
def getModeMap() { | |
[ | |
"00": "off", | |
"04": "heat" | |
] | |
} | |
def getSupportedThermostatModes() { | |
if (!state?.supportedThermostatModes) { | |
state?.supportedThermostatModes = (device.currentValue("supportedThermostatModes")) ? | |
device.currentValue("supportedThermostatModes").toString().minus('[').minus(']').tokenize(',') : ['off','heat'] | |
} | |
return state?.supportedThermostatModes | |
} | |
def setThermostatFanMode(mode) { | |
traceEvent(settings.logFilter,"setThermostatFanMode to $mode is not supported by this thermostat", settings.trace, get_LOG_WARN()) | |
} | |
def switchMode() { | |
log.debug "switching thermostatMode" | |
def currentMode = "mode_" + device.currentState("thermostatMode")?.value | |
def modeOrder = modes() | |
def index = modeOrder.indexOf(currentMode) | |
def next = index >= 0 && index < modeOrder.size() - 1 ? modeOrder[index + 1] : modeOrder[0] | |
//"st wattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x1C 0x30 {" + next + "}" | |
log.debug "switching mode from $currentMode to $next" | |
"$next" () | |
} | |
def setThermostatMode(mode) { | |
traceEvent(settings.logFilter,"setThermostatMode>switching thermostatMode", settings.trace) | |
mode=mode?.toLowerCase() | |
def supportedThermostatModes = getSupportedThermostatModes() | |
if (mode in supportedThermostatModes) { | |
"mode_$mode" () | |
} else { | |
traceEvent(settings.logFilter,"setThermostatMode to $mode is not supported by this thermostat", settings.trace, get_LOG_WARN()) | |
} | |
} | |
def mode_off() { | |
log.debug "---off---" | |
sendEvent("name": "thermostatMode", "value": "off") | |
//"st wattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x1C 0x30 {00}" | |
zigbee.writeAttribute(0x0201, 0x001C, 0x30, 0) + zigbee.readAttribute(0x0201, 0x0008) | |
} | |
def mode_heat() { | |
log.debug "---heat---" | |
sendEvent("name": "thermostatMode", "value": "heat") | |
//"st wattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x1C 0x30 {04}" | |
zigbee.writeAttribute(0x0201, 0x001C, 0x30, 4) + zigbee.readAttribute(0x0201, 0x0008) | |
} | |
//-- Temperature Display Mode ---------------------------------------------------------------------------- | |
def temperatureDisplayModes() { | |
["Deg_C", "Deg_F"] | |
} | |
def getTemperatureDisplayModeMap() { | |
[ | |
"00": "Deg_C", | |
"01": "Deg_F" | |
] | |
} | |
def setTemperatureDisplayMode() { | |
log.debug "Switching TempDisplayMode" | |
def currentMode = device.currentState("temperatureDisplayMode")?.value | |
def modeOrder = temperatureDisplayModes() | |
def index = modeOrder.indexOf(currentMode) | |
def next = index == 0 ? modeOrder[1] : modeOrder[0] | |
log.debug "switching Temp Display Mode from $currentMode to $next" | |
"$next" () | |
} | |
def Deg_C() { | |
log.debug "Deg_C" | |
def currentStatus = device.currentState("occupancyStatus")?.value | |
sendEvent("name": "temperatureDisplayMode", "value": "Deg_C") | |
if (currentStatus == "occupy") { | |
zigbee.writeAttribute(0x0204, 0x0000, 0x30, 0) + zigbee.readAttribute(0x0201, 0x0000) + zigbee.readAttribute(0x0201, 0x0012) | |
} else { | |
zigbee.writeAttribute(0x0204, 0x0000, 0x30, 0) + zigbee.readAttribute(0x0201, 0x0000) + zigbee.readAttribute(0x0201, 0x0014) | |
} | |
} | |
def Deg_F() { | |
log.debug "Deg_F" | |
def currentStatus = device.currentState("occupancyStatus")?.value | |
sendEvent("name": "temperatureDisplayMode", "value": "Deg_F") | |
if (currentStatus == "occupy") { | |
zigbee.writeAttribute(0x0204, 0x0000, 0x30, 1) + zigbee.readAttribute(0x0201, 0x0000) + zigbee.readAttribute(0x0201, 0x0012) | |
} else { | |
zigbee.writeAttribute(0x0204, 0x0000, 0x30, 1) + zigbee.readAttribute(0x0201, 0x0000) + zigbee.readAttribute(0x0201, 0x0014) | |
} | |
} | |
//-- Occupancy ------------------------------------------------------------------------------------------- | |
def occupancySts() { | |
["unoccupy", "occupy"] | |
} | |
def getOccupancyMap() { | |
[ | |
"00": "unoccupy", | |
"01": "occupy" | |
] | |
} | |
def setOccupancyStatus() { | |
traceEvent(settings.logFilter,"setOccupancyStatus>switching occupancy status", settings.trace) | |
def currentStatus = device.currentState("occupancyStatus")?.value | |
traceEvent(settings.logFilter,"setOccupancyStatus>Occupancy :$currentStatus", settings.trace) | |
def statusOrder = occupancySts() | |
def index = statusOrder.indexOf(currentStatus) | |
traceEvent(settings.logFilter,"setOccupancyStatus>Index = $index", settings.trace) | |
def next = (index >= 0 && index < (statusOrder.size() - 1)) ? statusOrder[index + 1] : statusOrder[0] | |
traceEvent(settings.logFilter,"setOccupancyStatus>switching occupancy from $currentStatus to $next", settings.trace) | |
"$next" () | |
} | |
void away() { | |
unoccupy() | |
} | |
void unoccupy() { | |
traceEvent(settings.logFilter,"unoccupy>Set unoccupy", settings.trace) | |
sendEvent(name: "occupancyStatus", value: "unoccupy",displayed: true) | |
sendEvent(name:"presence", value:"non present", displayed: true) | |
state?.previousOccupyTemp=device.currentValue("heatingSetpoint") | |
zigbee.writeAttribute(0x0201,0x0400,0x30,0x00,[mfgCode: 0x119C]) | |
def scale = state?.scale | |
def heatingSetpointRangeLow | |
try { | |
heatingSetpointRangeLow = device.latestValue("heatingSetpointRangeLow") | |
} catch (any) { | |
traceEvent(settings.logFilter,"unoccupy>not able to get heatingSetpointRangeLow ($heatingSetpointRangeLow),using default value", | |
settings.trace, get_LOG_WARN()) | |
} | |
heatingSetpointRangeLow = (heatingSetpointRangeLow) ?: (scale == 'C') ? 10.0 : 50 | |
sendEvent(name:"thermostatSetpoint", value:heatingSetpointRangeLow, unit: scale, displayed: true) | |
sendEvent(name:"heatingSetpoint", value:heatingSetpointRangeLow, unit: scale, displayed: true) | |
delayBetween([ | |
zigbee.readAttribute(0x0201, 0x0002), | |
zigbee.readAttribute(0x0201, 0x0014) | |
], 100) | |
} | |
void present() { | |
occupy() | |
} | |
void occupy() { | |
def scale = state?.scale | |
traceEvent(settings.logFilter,"occupy>Set occupy", settings.trace) | |
sendEvent(name: "occupancyStatus", value: "occupy", displayed: true) | |
sendEvent(name:"presence", value:"present", displayed: true) | |
zigbee.writeAttribute(0x0201, 0x0400, 0x30, 0x01, [mfgCode: 0x119C]) | |
def temp=(state?.previousOccupyTemp) ? state?.previousOccupyTemp : (scale == 'C') ? 20.0 : 70 | |
sendEvent(name:"thermostatSetpoint", value:temp, unit: scale, displayed: true) | |
sendEvent(name:"heatingSetpoint", value:temp, unit: scale, displayed: true) | |
delayBetween([ | |
zigbee.readAttribute(0x0201, 0x0002), | |
zigbee.readAttribute(0x0201, 0x0012) | |
], 100) | |
} | |
//-- Keypad Lock ----------------------------------------------------------------------------------------- | |
/* | |
def keypadLockLevel() { | |
["Unlock","Lock1"] //only those level are used for the moment | |
//["Unlock","Lock1","Lock2","Lock3","Lock4","Lock5"] | |
} | |
def getOccupancyMap() { | |
[ | |
"00":"Unlock", | |
"01":"Lock1", | |
"02":"Lock2", | |
"03":"Lock3", | |
"04":"Lock4", | |
"05":"Lock5" | |
] | |
} | |
def setOccupancyStatus() { | |
log.debug "set keypad lock level" | |
def currentLockLevel = device.currentState("keypadLock")?.value | |
log.debug "KeypadLock : $currentLockLevel" | |
def lockOrder = keypadLockLevel() | |
def index = lockOrder.indexOf(currentLockLevel) | |
log.debug "Index = $index" | |
def next = (index >= 0 && index < (lockOrder.size() - 1)) ? lockOrder[index + 1] : lockOrder[0] | |
log.debug "change keypad lock level from $currentLockLevel to $next" | |
"$next"() | |
} | |
def Unlock() { | |
log.debug "Unlock keypad" | |
sendEvent("name":"keypadLock","value":"Unlock") | |
zigbee.writeAttribute(0x0204,0x0001,0x30,0x00) | |
} | |
def Lock1() { | |
log.debug "Lock keypad" | |
sendEvent("name":"keypadLock","value":"Lock1") | |
zigbee.writeAttribute(0x0204,0x0001,0x30,0x01) | |
} | |
*/ | |
def configure() { | |
state?.scale=getTemperatureScale() | |
traceEvent(settings.logFilter,"binding to Thermostat cluster",settings.trace) | |
[ | |
"zdo bind 0x${device.deviceNetworkId} 1 1 0x201 {${device.zigbeeId}} {}", "delay 200", //thermostat cluster | |
//"zdo bind 0x${device.deviceNetworkId} 1 1 0x204 {${device.zigbeeId}} {}" //thermostat user interface cluster | |
//"zdo bind 0x${device.deviceNetworkId} 1 1 0x202 {${device.zigbeeId}} {}" | |
] | |
zigbee.configureReporting(0x0201, 0x0000, 0x29, 19, 301, 50) + //local temperature | |
zigbee.configureReporting(0x0201, 0x0008, 0x20, 22, 61, 25) + //heating demand | |
zigbee.configureReporting(0x0201, 0x0012, 0x29, 15, 0x0000, 50) + //occupied heating setpoint reported only on change | |
zigbee.readAttribute(0x0201,0x0002) + | |
zigbee.readAttribute(0x0204,0x0000) + | |
zigbee.readAttribute(0x0201,0x0012) + | |
zigbee.readAttribute(0x0201,0x0014) + | |
zigbee.readAttribute(0x0201,0x0015) + | |
zigbee.readAttribute(0x0201,0x0016) + | |
zigbee.readAttribute(0x0201,0x0008) + | |
zigbee.readAttribute(0x0201,0x001C) + | |
refreshTime() | |
//Report for Sinope Locate | |
//zigbee.configureReporting(0x0003, 0x0400, 0x10, 1, 65000, 1) | |
} | |
def refresh() | |
{ | |
traceEvent(settings.logFilter,"refresh>begin", settings.trace) | |
[ | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x0000", "delay 200", //local Temp | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x0002", "delay 200", //occupancy | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x0008", "delay 200", //Heating Demand | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x0012", "delay 200", //Occupied Heating Setpoint | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x0014", "delay 200", //Unoccupied Heating Setpoint | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x0015", "delay 200", //Min Heating Setpoint | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x0016", "delay 200", //Max Heating Setpoint | |
"st rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x201 0x001C", "delay 200" //System Mode | |
//zigbee.readAttribute(0x0201,0x0012) | |
] + refreshTime() + refresh_misc() + | |
zigbee.configureReporting(0x0201, 0x0000, 0x29, 19, 301, 50) + //local temperature | |
zigbee.configureReporting(0x0201, 0x0008, 0x20, 22, 61, 25) + //heating demand | |
zigbee.configureReporting(0x0201, 0x0012, 0x29, 15, 0x0000, 50) //occupied heating setpoint reported only on change | |
} | |
void refresh_misc() { | |
traceEvent(settings.logFilter,"refresh_misc> about to refresh other misc variables", settings.trace) | |
state?.scale=getTemperatureScale() | |
if (state?.scale=='C') { | |
Deg_C() | |
} else { | |
Deg_F() | |
} | |
def heatingSetpointRangeHigh= (device.currentValue("heatingSetpointRangeHigh")) ?: (scale=='C')?30:99 | |
def heatingSetpointRangeLow= (device.currentValue("heatingSetpointRangeLow")) ?: (scale=='C')?10:50 | |
def low = heatingSetpointRangeLow.toFloat().round(1) | |
def high = heatingSetpointRangeHigh.toFloat().round(1) | |
def heatingSetpointRange= [low,high] | |
def isChanged= isStateChange(device, "heatingSetpointRange", heatingSetpointRange?.toString()) | |
sendEvent(name: "heatingSetpointRange", value: heatingSetpointRange, isStateChange: isChanged, displayed: (settings.trace?:false)) | |
traceEvent(settings.logFilter,"refresh_misc>end", settings.trace) | |
} | |
//-- Private functions ----------------------------------------------------------------------------------- | |
private def get_weather(zipcode) { | |
def weather | |
if (zipcode) { | |
traceEvent(settings.logFilter,"refresh>ZipCode: ${zipcode}",settings.trace) | |
weather = getWeatherFeature( "conditions", zipcode.trim() ) | |
} else { | |
traceEvent(settings.logFilter,"refresh>ZipCode: current location",settings.trace) | |
weather = getWeatherFeature( "conditions" ) | |
} | |
return weather | |
} | |
private hex(value) { | |
String hex=new BigInteger(Math.round(value).toString()).toString(16) | |
// log.debug "value=$value, hex=$hex" | |
return hex | |
} | |
def refreshTime() { | |
def mytimezone = location.getTimeZone() | |
long secFrom2000 = (((now().toBigInteger() + mytimezone.rawOffset + mytimezone.dstSavings) / 1000) - (10956 * 24 * 3600)).toLong() //number of second from 2000-01-01 00:00:00h | |
long secIndian = zigbee.convertHexToInt(swapEndianHex(hex(secFrom2000).toString())) //switcw endianess | |
traceEvent(settings.logFilter, "refreshTime>myTime = ${secFrom2000} reversed = ${secIndian}", settings.trace) | |
zigbee.writeAttribute(0xFF01, 0x0020, 0x23, secIndian, [mfgCode: 0x119C]) | |
} | |
private String swapEndianHex(String hex) { | |
reverseArray(hex.decodeHex()).encodeHex() | |
} | |
private byte[] reverseArray(byte[] array) { | |
int i = 0; | |
int j = array.length - 1; | |
byte tmp; | |
while (j > i) { | |
tmp = array[j]; | |
array[j] = array[i]; | |
array[i] = tmp; | |
j--; | |
i++; | |
} | |
return array | |
} | |
private int get_LOG_ERROR() {return 1} | |
private int get_LOG_WARN() {return 2} | |
private int get_LOG_INFO() {return 3} | |
private int get_LOG_DEBUG() {return 4} | |
private int get_LOG_TRACE() {return 5} | |
def traceEvent(logFilter,message, displayEvent=false, traceLevel=4, sendMessage=true) { | |
int LOG_ERROR= get_LOG_ERROR() | |
int LOG_WARN= get_LOG_WARN() | |
int LOG_INFO= get_LOG_INFO() | |
int LOG_DEBUG= get_LOG_DEBUG() | |
int LOG_TRACE= get_LOG_TRACE() | |
int filterLevel=(logFilter)?logFilter.toInteger():get_LOG_WARN() | |
if ((displayEvent) || (sendMessage)) { | |
def results = [ | |
name: "verboseTrace", | |
value: message, | |
displayed: ((displayEvent)?: false) | |
] | |
if ((displayEvent) && (filterLevel >= traceLevel)) { | |
switch (traceLevel) { | |
case LOG_ERROR: | |
log.error "${message}" | |
break | |
case LOG_WARN: | |
log.warn "${message}" | |
break | |
case LOG_INFO: | |
log.info "${message}" | |
break | |
case LOG_TRACE: | |
log.trace "${message}" | |
break | |
case LOG_DEBUG: | |
default: | |
log.debug "${message}" | |
break | |
} /* end switch*/ | |
if (sendMessage) sendEvent (results) | |
} /* end if displayEvent*/ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment