mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-23 13:14:11 +00:00
update keen home smart vent device handler
This commit is contained in:
@@ -1,12 +1,9 @@
|
|||||||
/**
|
// keen home smart vent
|
||||||
* Keen Home Smart Vent
|
// http://www.keenhome.io
|
||||||
*
|
// SmartThings Device Handler v1.0.0
|
||||||
* Author: Keen Home
|
|
||||||
* Date: 2015-06-23
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Keen Home Smart Vent", namespace: "Keen Home", author: "Gregg Altschul") {
|
definition (name: "Keen Home Smart Vent", namespace: "Keen Home", author: "Keen Home") {
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
@@ -21,6 +18,7 @@ metadata {
|
|||||||
command "getBattery"
|
command "getBattery"
|
||||||
command "getTemperature"
|
command "getTemperature"
|
||||||
command "setZigBeeIdTile"
|
command "setZigBeeIdTile"
|
||||||
|
command "clearObstruction"
|
||||||
|
|
||||||
fingerprint endpoint: "1",
|
fingerprint endpoint: "1",
|
||||||
profileId: "0104",
|
profileId: "0104",
|
||||||
@@ -42,9 +40,10 @@ metadata {
|
|||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
tiles {
|
tiles {
|
||||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||||
state "on", action:"switch.off", icon:"st.vents.vent-open-text", backgroundColor:"#53a7c0"
|
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 "off", action: "switch.on", icon: "st.vents.vent-closed", backgroundColor: "#ffffff"
|
||||||
state "obstructed", action: "switch.off", icon:"st.vents.vent-closed", backgroundColor:"#ff0000"
|
state "obstructed", action: "clearObstruction", icon: "st.vents.vent-closed", backgroundColor: "#ff0000"
|
||||||
|
state "clearing", action: "", icon: "st.vents.vent-closed", backgroundColor: "#ffff33"
|
||||||
}
|
}
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||||
state "level", action:"switch level.setLevel"
|
state "level", action:"switch level.setLevel"
|
||||||
@@ -206,12 +205,12 @@ private Map makeOnOffResult(rawValue) {
|
|||||||
|
|
||||||
private Map makeLevelResult(rawValue) {
|
private Map makeLevelResult(rawValue) {
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
// log.debug "rawValue: ${rawValue}"
|
|
||||||
def value = Integer.parseInt(rawValue, 16)
|
def value = Integer.parseInt(rawValue, 16)
|
||||||
def rangeMax = 254
|
def rangeMax = 254
|
||||||
|
|
||||||
|
// catch obstruction level
|
||||||
if (value == 255) {
|
if (value == 255) {
|
||||||
log.debug "obstructed"
|
log.debug "${linkText} is obstructed"
|
||||||
// Just return here. Once the vent is power cycled
|
// Just return here. Once the vent is power cycled
|
||||||
// it will go back to the previous level before obstruction.
|
// it will go back to the previous level before obstruction.
|
||||||
// Therefore, no need to update level on the display.
|
// Therefore, no need to update level on the display.
|
||||||
@@ -220,24 +219,9 @@ private Map makeLevelResult(rawValue) {
|
|||||||
value: "obstructed",
|
value: "obstructed",
|
||||||
descriptionText: "${linkText} is obstructed. Please power cycle."
|
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)
|
value = Math.floor(value / rangeMax * 100)
|
||||||
// log.debug "post-value: ${value}"
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
name: "level",
|
name: "level",
|
||||||
@@ -327,35 +311,79 @@ private def makeSerialResult(serial) {
|
|||||||
value: serial,
|
value: serial,
|
||||||
descriptionText: "${linkText} has serial ${serial}" ]
|
descriptionText: "${linkText} has serial ${serial}" ]
|
||||||
}
|
}
|
||||||
/**** COMMAND METHODS ****/
|
|
||||||
// def mfgCode() {
|
|
||||||
// ["zcl mfg-code 0x115B", "delay 200"]
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
// takes a level from 0 to 100 and translates it to a ZigBee move to level with on/off command
|
||||||
|
private def makeLevelCommand(level) {
|
||||||
|
def rangeMax = 254
|
||||||
|
def scaledLevel = Math.round(level * rangeMax / 100)
|
||||||
|
log.debug "scaled level for ${level}%: ${scaledLevel}"
|
||||||
|
|
||||||
|
// convert to hex string and pad to two digits
|
||||||
|
def hexLevel = new BigInteger(scaledLevel.toString()).toString(16).padLeft(2, '0')
|
||||||
|
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 1 8 4 {${hexLevel} 0000}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** COMMAND METHODS ****/
|
||||||
def on() {
|
def on() {
|
||||||
log.debug "on()"
|
def linkText = getLinkText(device)
|
||||||
|
log.debug "open ${linkText}"
|
||||||
|
|
||||||
|
// only change the state if the vent is not obstructed
|
||||||
|
if (device.currentValue("switch") == "obstructed") {
|
||||||
|
log.error("cannot open because ${linkText} is obstructed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sendEvent(makeOnOffResult(1))
|
sendEvent(makeOnOffResult(1))
|
||||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
log.debug "off()"
|
def linkText = getLinkText(device)
|
||||||
|
log.debug "close ${linkText}"
|
||||||
|
|
||||||
|
// only change the state if the vent is not obstructed
|
||||||
|
if (device.currentValue("switch") == "obstructed") {
|
||||||
|
log.error("cannot close because ${linkText} is obstructed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sendEvent(makeOnOffResult(0))
|
sendEvent(makeOnOffResult(0))
|
||||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// does this work?
|
def clearObstruction() {
|
||||||
def toggle() {
|
def linkText = getLinkText(device)
|
||||||
log.debug "toggle()"
|
log.debug "attempting to clear ${linkText} obstruction"
|
||||||
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 1 6 2 {}"
|
sendEvent([
|
||||||
|
name: "switch",
|
||||||
|
value: "clearing",
|
||||||
|
descriptionText: "${linkText} is clearing obstruction"
|
||||||
|
])
|
||||||
|
|
||||||
|
// send a move command to ensure level attribute gets reset for old, buggy firmware
|
||||||
|
// then send a reset to factory defaults
|
||||||
|
// finally re-configure to ensure reports and binding is still properly set after the rtfd
|
||||||
|
[
|
||||||
|
makeLevelCommand(device.currentValue("level")), "delay 500",
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 1 0 0 {}", "delay 5000"
|
||||||
|
] + configure()
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
log.debug "setting level: ${value}"
|
log.debug "setting level: ${value}"
|
||||||
|
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
|
// only change the level if the vent is not obstructed
|
||||||
|
def currentState = device.currentValue("switch")
|
||||||
|
|
||||||
|
if (currentState == "obstructed") {
|
||||||
|
log.error("cannot set level because ${linkText} is obstructed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sendEvent(name: "level", value: value)
|
sendEvent(name: "level", value: value)
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
sendEvent(name: "switch", value: "on", descriptionText: "${linkText} is on by setting a level")
|
sendEvent(name: "switch", value: "on", descriptionText: "${linkText} is on by setting a level")
|
||||||
@@ -363,29 +391,26 @@ def setLevel(value) {
|
|||||||
else {
|
else {
|
||||||
sendEvent(name: "switch", value: "off", descriptionText: "${linkText} is off by setting level to 0")
|
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)
|
makeLevelCommand(value)
|
||||||
log.debug "level: ${level}"
|
|
||||||
|
|
||||||
if (level.size() < 2){
|
|
||||||
level = '0' + level
|
|
||||||
}
|
|
||||||
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 0000}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def getOnOff() {
|
def getOnOff() {
|
||||||
log.debug "getOnOff()"
|
log.debug "getOnOff()"
|
||||||
|
|
||||||
|
// disallow on/off updates while vent is obstructed
|
||||||
|
if (device.currentValue("switch") == "obstructed") {
|
||||||
|
log.error("cannot update open/close status because ${getLinkText(device)} is obstructed")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
["st rattr 0x${device.deviceNetworkId} 1 0x0006 0"]
|
["st rattr 0x${device.deviceNetworkId} 1 0x0006 0"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def getPressure() {
|
def getPressure() {
|
||||||
log.debug "getPressure()"
|
log.debug "getPressure()"
|
||||||
|
|
||||||
|
// using a Keen Home specific attribute in the pressure measurement cluster
|
||||||
[
|
[
|
||||||
"zcl mfg-code 0x115B", "delay 200",
|
"zcl mfg-code 0x115B", "delay 200",
|
||||||
"zcl global read 0x0403 0x20", "delay 200",
|
"zcl global read 0x0403 0x20", "delay 200",
|
||||||
@@ -395,12 +420,13 @@ def getPressure() {
|
|||||||
|
|
||||||
def getLevel() {
|
def getLevel() {
|
||||||
log.debug "getLevel()"
|
log.debug "getLevel()"
|
||||||
// rattr = read attribute
|
|
||||||
// 0x${} = device net id
|
// disallow level updates while vent is obstructed
|
||||||
// 1 = endpoint
|
if (device.currentValue("switch") == "obstructed") {
|
||||||
// 8 = cluster id (level control, in this case)
|
log.error("cannot update level status because ${getLinkText(device)} is obstructed")
|
||||||
// 0 = attribute within cluster
|
return []
|
||||||
// sendEvent(name: "level", value: value)
|
}
|
||||||
|
|
||||||
["st rattr 0x${device.deviceNetworkId} 1 0x0008 0x0000"]
|
["st rattr 0x${device.deviceNetworkId} 1 0x0008 0x0000"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,78 +451,59 @@ def setZigBeeIdTile() {
|
|||||||
name: "zigbeeId",
|
name: "zigbeeId",
|
||||||
value: device.zigbeeId,
|
value: device.zigbeeId,
|
||||||
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ])
|
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ])
|
||||||
return [
|
return [
|
||||||
name: "zigbeeId",
|
name: "zigbeeId",
|
||||||
value: device.zigbeeId,
|
value: device.zigbeeId,
|
||||||
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ]
|
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
getOnOff() +
|
getOnOff() +
|
||||||
getLevel() +
|
getLevel() +
|
||||||
getTemperature() +
|
getTemperature() +
|
||||||
getPressure() +
|
getPressure() +
|
||||||
getBattery()
|
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() {
|
def configure() {
|
||||||
log.debug "CONFIGURE"
|
log.debug "CONFIGURE"
|
||||||
log.debug "zigbeeId: ${device.hub.zigbeeId}"
|
|
||||||
|
|
||||||
|
// get ZigBee ID by hidden tile because that's the only way we can do it
|
||||||
setZigBeeIdTile()
|
setZigBeeIdTile()
|
||||||
|
|
||||||
def configCmds = [
|
def configCmds = [
|
||||||
// binding commands
|
// bind reporting clusters to hub
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 500",
|
"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 0x0008 {${device.zigbeeId}} {}", "delay 500",
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0402 {${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 0x0403 {${device.zigbeeId}} {}", "delay 500",
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500",
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500"
|
||||||
|
|
||||||
// configure report commands
|
// configure report commands
|
||||||
// [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
|
// zcl global send-me-a-report [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
|
||||||
|
|
||||||
// mike 2015/06/22: preconfigured; see tech spec
|
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||||
// vent on/off state - type: boolean, change: 1
|
// vent on/off state - type: boolean, change: 1
|
||||||
// "zcl global send-me-a-report 6 0 0x10 5 60 {01}", "delay 200",
|
// "zcl global send-me-a-report 6 0 0x10 5 60 {01}", "delay 200",
|
||||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
// mike 2015/06/22: preconfigured; see tech spec
|
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||||
// vent level - type: int8u, change: 1
|
// vent level - type: int8u, change: 1
|
||||||
// "zcl global send-me-a-report 8 0 0x20 5 60 {01}", "delay 200",
|
// "zcl global send-me-a-report 8 0 0x20 5 60 {01}", "delay 200",
|
||||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
// mike 2015/06/22: temp and pressure reports are preconfigured, but
|
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||||
// we'd like to override their settings for our own purposes
|
|
||||||
// temperature - type: int16s, change: 0xA = 10 = 0.1C
|
// temperature - type: int16s, change: 0xA = 10 = 0.1C
|
||||||
"zcl global send-me-a-report 0x0402 0 0x29 10 60 {0A00}", "delay 200",
|
// "zcl global send-me-a-report 0x0402 0 0x29 60 60 {0A00}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
// mike 2015/06/22: use new custom pressure attribute
|
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||||
// pressure - type: int32u, change: 1 = 0.1Pa
|
// keen home custom pressure (tenths of Pascals) - type: int32u, change: 1 = 0.1Pa
|
||||||
"zcl mfg-code 0x115B", "delay 200",
|
// "zcl mfg-code 0x115B", "delay 200",
|
||||||
"zcl global send-me-a-report 0x0403 0x20 0x22 10 60 {010000}", "delay 200",
|
// "zcl global send-me-a-report 0x0403 0x20 0x22 60 60 {010000}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500"
|
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
// mike 2015/06/22: preconfigured; see tech spec
|
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||||
// battery - type: int8u, change: 1
|
// battery - type: int8u, change: 1
|
||||||
// "zcl global send-me-a-report 1 0x21 0x20 60 3600 {01}", "delay 200",
|
// "zcl global send-me-a-report 1 0x21 0x20 60 3600 {01}", "delay 200",
|
||||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|||||||
Reference in New Issue
Block a user