Compare commits

...

9 Commits

Author SHA1 Message Date
Laurence Kahn
4bcf16aeb4 Modifying 'improvements to the ct100 device type.' 2015-10-13 21:47:17 -05:00
Laurence Kahn
77016e0043 MSA-614: Add color, and better control of the ct100 thermostat using up and down arrows instead of the sliders that did not work well. 2015-10-10 11:05:12 -05:00
Juan Pablo Risso
6854665f68 Merge pull request #177 from juano2310/Zen_PR
Adds capabilities "Temperature Measurement" and "Relative Humidity Me…
2015-10-09 13:29:31 -04:00
Juan Pablo Risso
2534afbf81 Removed Humidity 2015-10-09 12:39:26 -04:00
Juan Pablo Risso
eb3d0c2874 Adds capabilities "Temperature Measurement" and "Relative Humidity Measurement" 2015-10-09 12:34:58 -04:00
SmartThings, Inc.
5f85cd2873 Merge pull request #172 from SmartThingsCommunity/MSA-603-7
Merged publication request 'Keen Vent'
2015-10-05 16:28:52 -05:00
Donald C. Kirker
7bb6f67dbc MSA-603: Submission of Keen code to new SmartThings repo. 2015-10-05 16:26:47 -05:00
Kristofer Schaller
05cf0a0cb1 Merge pull request #170 from davidsulpy/master
Fixed a potential bug if a InitialState SmartApp hasn't been updated
2015-10-05 11:39:31 -07:00
David Sulpy
f012419710 added an initialization of the atomicState.eventBuffer if the eventBuffer is null when handling an event 2015-10-04 20:19:11 -05:00
4 changed files with 1266 additions and 0 deletions

View File

@@ -0,0 +1,506 @@
/**
* Keen Home Smart Vent
*
* Author: Keen Home
* Date: 2015-06-23
*/
metadata {
definition (name: "Keen Home Smart Vent", namespace: "Keen Home", author: "Gregg Altschul") {
capability "Switch Level"
capability "Switch"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Temperature Measurement"
capability "Battery"
command "getLevel"
command "getOnOff"
command "getPressure"
command "getBattery"
command "getTemperature"
command "setZigBeeIdTile"
fingerprint endpoint: "1",
profileId: "0104",
inClusters: "0000,0001,0003,0004,0005,0006,0008,0020,0402,0403,0B05,FC01,FC02",
outClusters: "0019"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", action:"switch.off", icon:"st.vents.vent-open-text", backgroundColor:"#53a7c0"
state "off", action:"switch.on", icon:"st.vents.vent-closed", backgroundColor:"#ffffff"
state "obstructed", action: "switch.off", icon:"st.vents.vent-closed", backgroundColor:"#ff0000"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("temperature", "device.temperature", inactiveLabel: false) {
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"]
]
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
state "battery", label: 'Battery \n${currentValue}%', backgroundColor:"#ffffff"
}
valueTile("zigbeeId", "device.zigbeeId", inactiveLabel: true, decoration: "flat") {
state "serial", label:'${currentValue}', backgroundColor:"#ffffff"
}
main "switch"
details(["switch","refresh","temperature","levelSliderControl","battery"])
}
}
/**** PARSE METHODS ****/
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('on/off: ')) {
map = parseOnOffMessage(description)
}
log.debug "Parse returned $map"
return map ? createEvent(map) : null
}
private Map parseCatchAllMessage(String description) {
log.debug "parseCatchAllMessage"
def cluster = zigbee.parse(description)
log.debug "cluster: ${cluster}"
if (shouldProcessMessage(cluster)) {
log.debug "processing message"
switch(cluster.clusterId) {
case 0x0001:
return makeBatteryResult(cluster.data.last())
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = convertTemperatureHex(temp)
return makeTemperatureResult(value)
break
case 0x0006:
return makeOnOffResult(cluster.data[-1])
break
}
}
return [:]
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
if (cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)) {
return false
}
return true
}
private Map parseReportAttributeMessage(String description) {
log.debug "parseReportAttributeMessage"
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
if (descMap.cluster == "0006" && descMap.attrId == "0000") {
return makeOnOffResult(Int.parseInt(descMap.value));
}
else if (descMap.cluster == "0008" && descMap.attrId == "0000") {
return makeLevelResult(descMap.value)
}
else if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = convertTemperatureHex(descMap.value)
return makeTemperatureResult(value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0021") {
return makeBatteryResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == "0403" && descMap.attrId == "0020") {
return makePressureResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == "0000" && descMap.attrId == "0006") {
return makeSerialResult(new String(descMap.value.decodeHex()))
}
// shouldn't get here
return [:]
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
// log.debug "${description}"
// def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
// log.debug "split: " + description.split(": ")
def value = Double.parseDouble(description.split(": ")[1])
// log.debug "${value}"
resultMap = makeTemperatureResult(convertTemperature(value))
}
return resultMap
}
private Map parseOnOffMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('on/off: ')) {
def value = Integer.parseInt(description - "on/off: ")
resultMap = makeOnOffResult(value)
}
return resultMap
}
private Map makeOnOffResult(rawValue) {
log.debug "makeOnOffResult: ${rawValue}"
def linkText = getLinkText(device)
def value = rawValue == 1 ? "on" : "off"
return [
name: "switch",
value: value,
descriptionText: "${linkText} is ${value}"
]
}
private Map makeLevelResult(rawValue) {
def linkText = getLinkText(device)
// log.debug "rawValue: ${rawValue}"
def value = Integer.parseInt(rawValue, 16)
def rangeMax = 254
if (value == 255) {
log.debug "obstructed"
// Just return here. Once the vent is power cycled
// it will go back to the previous level before obstruction.
// Therefore, no need to update level on the display.
return [
name: "switch",
value: "obstructed",
descriptionText: "${linkText} is obstructed. Please power cycle."
]
} else if ( device.currentValue("switch") == "obstructed" &&
value == 254) {
// When the device is reset after an obstruction, the switch
// state will be obstructed and the value coming from the device
// will be 254. Since we're not using heating/cooling mode from
// the device type handler, we need to bump it down to the lower
// (cooling) range
sendEvent(makeOnOffResult(1)) // clear the obstructed switch state
value = rangeMax
}
// else if (device.currentValue("switch") == "off") {
// sendEvent(makeOnOffResult(1)) // turn back on if in off state
// }
// log.debug "pre-value: ${value}"
value = Math.floor(value / rangeMax * 100)
// log.debug "post-value: ${value}"
return [
name: "level",
value: value,
descriptionText: "${linkText} level is ${value}%"
]
}
private Map makePressureResult(rawValue) {
log.debug 'makePressureResut'
def linkText = getLinkText(device)
def pascals = rawValue / 10
def result = [
name: 'pressure',
descriptionText: "${linkText} pressure is ${pascals}Pa",
value: pascals
]
return result
}
private Map makeBatteryResult(rawValue) {
// log.debug 'makeBatteryResult'
def linkText = getLinkText(device)
// log.debug
[
name: 'battery',
value: rawValue,
descriptionText: "${linkText} battery is at ${rawValue}%"
]
}
private Map makeTemperatureResult(value) {
// log.debug 'makeTemperatureResult'
def linkText = getLinkText(device)
// log.debug "tempOffset: ${tempOffset}"
if (tempOffset) {
def offset = tempOffset as int
// log.debug "offset: ${offset}"
def v = value as int
// log.debug "v: ${v}"
value = v + offset
// log.debug "value: ${value}"
}
return [
name: 'temperature',
value: "" + value,
descriptionText: "${linkText} is ${value}°${temperatureScale}",
]
}
/**** HELPER METHODS ****/
private def convertTemperatureHex(value) {
// log.debug "convertTemperatureHex(${value})"
def celsius = Integer.parseInt(value, 16).shortValue() / 100
// log.debug "celsius: ${celsius}"
return convertTemperature(celsius)
}
private def convertTemperature(celsius) {
// log.debug "convertTemperature()"
if(getTemperatureScale() == "C"){
return celsius
} else {
def fahrenheit = Math.round(celsiusToFahrenheit(celsius) * 100) /100
// log.debug "converted to F: ${fahrenheit}"
return fahrenheit
}
}
private def makeSerialResult(serial) {
log.debug "makeSerialResult: " + serial
def linkText = getLinkText(device)
sendEvent([
name: "serial",
value: serial,
descriptionText: "${linkText} has serial ${serial}" ])
return [
name: "serial",
value: serial,
descriptionText: "${linkText} has serial ${serial}" ]
}
/**** COMMAND METHODS ****/
// def mfgCode() {
// ["zcl mfg-code 0x115B", "delay 200"]
// }
def on() {
log.debug "on()"
sendEvent(makeOnOffResult(1))
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
}
def off() {
log.debug "off()"
sendEvent(makeOnOffResult(0))
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
}
// does this work?
def toggle() {
log.debug "toggle()"
"st cmd 0x${device.deviceNetworkId} 1 6 2 {}"
}
def setLevel(value) {
log.debug "setting level: ${value}"
def linkText = getLinkText(device)
sendEvent(name: "level", value: value)
if (value > 0) {
sendEvent(name: "switch", value: "on", descriptionText: "${linkText} is on by setting a level")
}
else {
sendEvent(name: "switch", value: "off", descriptionText: "${linkText} is off by setting level to 0")
}
def rangeMax = 254
def computedLevel = Math.round(value * rangeMax / 100)
log.debug "computedLevel: ${computedLevel}"
def level = new BigInteger(computedLevel.toString()).toString(16)
log.debug "level: ${level}"
if (level.size() < 2){
level = '0' + level
}
"st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 0000}"
}
def getOnOff() {
log.debug "getOnOff()"
["st rattr 0x${device.deviceNetworkId} 1 0x0006 0"]
}
def getPressure() {
log.debug "getPressure()"
[
"zcl mfg-code 0x115B", "delay 200",
"zcl global read 0x0403 0x20", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
def getLevel() {
log.debug "getLevel()"
// rattr = read attribute
// 0x${} = device net id
// 1 = endpoint
// 8 = cluster id (level control, in this case)
// 0 = attribute within cluster
// sendEvent(name: "level", value: value)
["st rattr 0x${device.deviceNetworkId} 1 0x0008 0x0000"]
}
def getTemperature() {
log.debug "getTemperature()"
["st rattr 0x${device.deviceNetworkId} 1 0x0402 0"]
}
def getBattery() {
log.debug "getBattery()"
["st rattr 0x${device.deviceNetworkId} 1 0x0001 0x0021"]
}
def setZigBeeIdTile() {
log.debug "setZigBeeIdTile() - ${device.zigbeeId}"
def linkText = getLinkText(device)
sendEvent([
name: "zigbeeId",
value: device.zigbeeId,
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ])
return [
name: "zigbeeId",
value: device.zigbeeId,
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ]
}
def refresh() {
getOnOff() +
getLevel() +
getTemperature() +
getPressure() +
getBattery()
}
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 String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
def configure() {
log.debug "CONFIGURE"
log.debug "zigbeeId: ${device.hub.zigbeeId}"
setZigBeeIdTile()
def configCmds = [
// binding commands
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0008 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0403 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500",
// configure report commands
// [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
// mike 2015/06/22: preconfigured; see tech spec
// vent on/off state - type: boolean, change: 1
// "zcl global send-me-a-report 6 0 0x10 5 60 {01}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// mike 2015/06/22: preconfigured; see tech spec
// vent level - type: int8u, change: 1
// "zcl global send-me-a-report 8 0 0x20 5 60 {01}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// mike 2015/06/22: temp and pressure reports are preconfigured, but
// we'd like to override their settings for our own purposes
// temperature - type: int16s, change: 0xA = 10 = 0.1C
"zcl global send-me-a-report 0x0402 0 0x29 10 60 {0A00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// mike 2015/06/22: use new custom pressure attribute
// pressure - type: int32u, change: 1 = 0.1Pa
"zcl mfg-code 0x115B", "delay 200",
"zcl global send-me-a-report 0x0403 0x20 0x22 10 60 {010000}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500"
// mike 2015/06/22: preconfigured; see tech spec
// battery - type: int8u, change: 1
// "zcl global send-me-a-report 1 0x21 0x20 60 3600 {01}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
]
return configCmds + refresh()
}

View File

@@ -0,0 +1,752 @@
/* lgk larry kahn kahn-st@lgk.com custom ct100 .. changes to stock device type
1. add some icons and colors heat color, cool color,
2. add operating mode tile ie idle heating etc
3. add color for humididity as well
4. get rid of sliders that worked like crap and added up down arrows for both heating and cooling,
and color based on temp for both.
5. add color for battery status.
v 2. some returns between indicators to move things down a line as recommended
v 3 reintegrate changes smarthigns made in setheatingsetpoint and setcoolingsetpoint functions apparently to get around bugs.
these changes were made in the stock ct100 device type.
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "My CT100 Thermostat", namespace: "smartthings", author: "LGK Customized") {
capability "Actuator"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Thermostat"
capability "Battery"
capability "Configuration"
capability "Refresh"
capability "Sensor"
command "heatLevelUp"
command "heatLevelDown"
command "coolLevelUp"
command "coolLevelDown"
attribute "thermostatFanState", "string"
command "switchMode"
command "switchFanMode"
command "quickSetCool"
command "quickSetHeat"
fingerprint deviceId: "0x08", inClusters: "0x43,0x40,0x44,0x31,0x80,0x85,0x60"
}
// 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 72" : "command: 4303, payload: 01 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 78" : "command: 3105, payload: 01 2A 03 0C"
status "temp 86" : "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"
// reply messages
reply "2502": "command: 2503, payload: FF"
}
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°',
icon: "http://cdn.device-icons.smartthings.com/Weather/weather2-icn@2x.png",
backgroundColors:[
[value: 32, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 92, color: "#d04e00"],
[value: 98, color: "#bc2323"]
]
)
}
standardTile("mode", "device.thermostatMode", inactiveLabel: false) {
state "off", label:'${name}', action:"switchMode", icon: "st.Outdoor.outdoor19" ,nextState:"to_heat"
state "heat", label:'${name}', action:"switchMode", icon: "st.Weather.weather14", backgroundColor: '#E14902', nextState:"to_cool"
state "cool", label:'${name}', action:"switchMode", icon: "st.Weather.weather7", backgroundColor: '#1e9cbb', nextState:"..."
state "auto", label:'${name}', action:"switchMode", icon: "st.Weather.weather3", backgroundColor: '#44b621', 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) {
state "fanAuto", label:'${name}', action:"switchFanMode", icon: "st.Appliances.appliances11", backgroundColor: '#44b621'
state "fanOn", label:'${name}', action:"switchFanMode", icon: "st.Appliances.appliances11", backgroundColor: '#44b621'
state "fanCirculate", label:'${name}', action:"switchFanMode", icon: "st.Appliances.appliances11", backgroundColor: '#44b621'
}
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false)
{
state "default", label:'Cool\n${currentValue}°F', 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("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false)
{
state "default", label:'Heat\n${currentValue}°F', 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("heatLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "heatLevelUp", label:' ', action:"heatLevelUp", icon:"st.thermostat.thermostat-up"
}
standardTile("heatLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "heatLevelDown", label:' ', action:"heatLevelDown", icon:"st.thermostat.thermostat-down"
}
standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "coolLevelUp", label:' ', action:"coolLevelUp", icon:"st.thermostat.thermostat-up"
}
standardTile("coolLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "coolLevelDown", label:' ', action:"coolLevelDown", icon:"st.thermostat.thermostat-down"
}
/*
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"
}
*/
valueTile("humidity", "device.humidity", inactiveLabel: false) {
state "humidity", label:'Humidity\n${currentValue}%', unit:"",
icon: "http://cdn.device-icons.smartthings.com/Weather/weather12-icn@2x.png",
backgroundColors : [
[value: 01, color: "#724529"],
[value: 11, color: "#724529"],
[value: 21, color: "#724529"],
[value: 35, color: "#44b621"],
[value: 49, color: "#44b621"],
[value: 50, color: "#1e9cbb"]
]
}
valueTile("battery", "device.battery", inactiveLabel: false) {
state "battery", label:'Battery\n${currentValue}%', unit:"",
backgroundColors : [
[value: 20, color: "#720000"],
[value: 40, color: "#724529"],
[value: 60, color: "#00cccc"],
[value: 80, color: "#00b621"],
[value: 90, color: "#009c00"],
[value: 100, color: "#00ff00"]
]h
}
valueTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat") {
state "thermostatOperatingState", label:'${currentValue}', unit:""
}
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "temperature"
details(["temperature", "mode", "fanMode",
"heatLevelUp", "heatingSetpoint" , "heatLevelDown", "coolLevelUp",
"coolingSetpoint", "coolLevelDown",
"humidity", "battery","thermostatOperatingState","refresh"])
/*main "temperature"
details(["temperature", "mode", "fanMode",
"heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint",
"humidity", "battery","thermostatOperatingState","refresh"])*/
}
}
def parse(String description)
{
def result = []
if (description == "updated") {
} else {
def zwcmd = zwave.parse(description, [0x42:2, 0x43:2, 0x31: 2, 0x60: 3])
if (zwcmd) {
result += zwaveEvent(zwcmd)
} else {
log.debug "$device.displayName couldn't parse $description"
}
}
if (!result) {
return null
}
if (result.size() == 1 && (!state.lastbatt || now() - state.lastbatt > 48*60*60*1000)) {
result << response(zwave.batteryV1.batteryGet().format())
}
//log.debug "$device.displayName parsed '$description' to $result"
result
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
def result = null
def encapsulatedCommand = cmd.encapsulatedCommand([0x42:2, 0x43:2, 0x31: 2])
log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
if (encapsulatedCommand) {
result = zwaveEvent(encapsulatedCommand)
if (cmd.sourceEndPoint == 1) { // indicates a response to refresh() vs an unrequested update
def event = ([] + result)[0] // in case zwaveEvent returns a list
def resp = nextRefreshQuery(event?.name)
if (resp) {
log.debug("sending next refresh query: $resp")
result = [] + result + response(["delay 200", resp])
}
}
}
result
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{
def cmdScale = cmd.scale == 1 ? "F" : "C"
def temp = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
def unit = getTemperatureScale()
def map1 = [ value: temp, unit: unit, displayed: false ]
switch (cmd.setpointType) {
case 1:
map1.name = "heatingSetpoint"
break;
case 2:
map1.name = "coolingSetpoint"
break;
default:
log.debug "unknown setpointType $cmd.setpointType"
return
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
def mode = device.latestValue("thermostatMode")
if (mode && map1.name.startsWith(mode) || (mode == "emergency heat" && map1.name == "heatingSetpoint")) {
def map2 = [ name: "thermostatSetpoint", value: temp, unit: unit ]
[ createEvent(map1), createEvent(map2) ]
} else {
createEvent(map1)
}
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd)
{
def map = [:]
if (cmd.sensorType == 1) {
map.name = "temperature"
map.unit = getTemperatureScale()
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
} else if (cmd.sensorType == 5) {
map.name = "humidity"
map.unit = "%"
map.value = cmd.scaledSensorValue
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd)
{
def map = [name: "thermostatOperatingState" ]
switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_HEATING:
map.value = "heating"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_COOLING:
map.value = "cooling"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_FAN_ONLY:
map.value = "fan only"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_HEAT:
map.value = "pending heat"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_PENDING_COOL:
map.value = "pending cool"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_VENT_ECONOMIZER:
map.value = "vent economizer"
break
}
def result = createEvent(map)
if (result.isStateChange && device.latestValue("thermostatMode") == "auto" && (result.value == "heating" || result.value == "cooling")) {
def thermostatSetpoint = device.latestValue("${result.value}Setpoint")
result = [result, createEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale())]
}
result
}
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
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [name: "thermostatMode"]
def thermostatSetpoint = null
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"
thermostatSetpoint = device.latestValue("heatingSetpoint")
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT:
map.value = "emergency heat"
thermostatSetpoint = device.latestValue("heatingSetpoint")
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL:
map.value = "cool"
thermostatSetpoint = device.latestValue("coolingSetpoint")
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO:
map.value = "auto"
def temp = device.latestValue("temperature")
def heatingSetpoint = device.latestValue("heatingSetpoint")
def coolingSetpoint = device.latestValue("coolingSetpoint")
if (temp && heatingSetpoint && coolingSetpoint) {
if (temp < (heatingSetpoint + coolingSetpoint) / 2.0) {
thermostatSetpoint = heatingSetpoint
} else {
thermostatSetpoint = coolingSetpoint
}
}
break
}
state.lastTriedMode = map.value
if (thermostatSetpoint) {
[ createEvent(map), createEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale()) ]
} else {
createEvent(map)
}
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
def map = [name: "thermostatFanMode", displayed: false]
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
}
state.lastTriedFanMode = map.value
createEvent(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
[ createEvent(name:"supportedModes", value: supportedModes, displayed: false),
response(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet()) ]
}
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
[ createEvent(name:"supportedFanModes", value: supportedModes, displayed: false),
response(refresh()) ]
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "Zwave event received: $cmd"
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} battery is low"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
state.lastbatt = now()
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "Unexpected zwave command $cmd"
}
def refresh() {
// Use encapsulation to differentiate refresh cmds from what the thermostat sends proactively on change
def cmd = zwave.sensorMultilevelV2.sensorMultilevelGet()
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:1).encapsulate(cmd).format()
}
def nextRefreshQuery(name) {
def cmd = null
switch (name) {
case "temperature":
cmd = zwave.thermostatModeV2.thermostatModeGet()
break
case "thermostatMode":
cmd = zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1)
break
case "heatingSetpoint":
cmd = zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2)
break
case "coolingSetpoint":
cmd = zwave.thermostatFanModeV3.thermostatFanModeGet()
break
case "thermostatFanMode":
cmd = zwave.thermostatOperatingStateV2.thermostatOperatingStateGet()
break
case "thermostatOperatingState":
// get humidity, multilevel sensor get to endpoint 2
cmd = zwave.sensorMultilevelV2.sensorMultilevelGet()
return zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:2).encapsulate(cmd).format()
default: return null
}
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:1).encapsulate(cmd).format()
}
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(),
], 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
}
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)
}

View File

@@ -8,6 +8,7 @@ metadata {
definition (name: "Zen Thermostat", namespace: "zenwithin", author: "ZenWithin") {
capability "Actuator"
capability "Thermostat"
capability "Temperature Measurement"
capability "Configuration"
capability "Refresh"
capability "Sensor"

View File

@@ -341,6 +341,13 @@ def eventHandler(name, value) {
def eventBuffer = atomicState.eventBuffer
def epoch = now() / 1000
// if for some reason this code block is being run
// but the SmartApp wasn't propery setup during install
// we need to set initialize the eventBuffer.
if (!atomicState.eventBuffer) {
atomicState.eventBuffer = []
}
eventBuffer << [key: "$name", value: "$value", epoch: "$epoch"]
log.debug eventBuffer