mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
Merge pull request #214 from keenhome/update-keen-home-smart-vent-handler
update keen home smart vent device handler
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
/**
|
||||
* Keen Home Smart Vent
|
||||
*
|
||||
* Author: Keen Home
|
||||
* Date: 2015-06-23
|
||||
*/
|
||||
// keen home smart vent
|
||||
// http://www.keenhome.io
|
||||
// SmartThings Device Handler v1.0.0
|
||||
|
||||
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"
|
||||
capability "Configuration"
|
||||
@@ -21,6 +18,7 @@ metadata {
|
||||
command "getBattery"
|
||||
command "getTemperature"
|
||||
command "setZigBeeIdTile"
|
||||
command "clearObstruction"
|
||||
|
||||
fingerprint endpoint: "1",
|
||||
profileId: "0104",
|
||||
@@ -42,9 +40,10 @@ metadata {
|
||||
// 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"
|
||||
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: "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) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
@@ -206,12 +205,12 @@ private Map makeOnOffResult(rawValue) {
|
||||
|
||||
private Map makeLevelResult(rawValue) {
|
||||
def linkText = getLinkText(device)
|
||||
// log.debug "rawValue: ${rawValue}"
|
||||
def value = Integer.parseInt(rawValue, 16)
|
||||
def rangeMax = 254
|
||||
|
||||
// catch obstruction level
|
||||
if (value == 255) {
|
||||
log.debug "obstructed"
|
||||
log.debug "${linkText} is 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.
|
||||
@@ -220,24 +219,9 @@ private Map makeLevelResult(rawValue) {
|
||||
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",
|
||||
@@ -327,35 +311,79 @@ private def makeSerialResult(serial) {
|
||||
value: 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() {
|
||||
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))
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
}
|
||||
|
||||
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))
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
}
|
||||
|
||||
// does this work?
|
||||
def toggle() {
|
||||
log.debug "toggle()"
|
||||
def clearObstruction() {
|
||||
def linkText = getLinkText(device)
|
||||
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) {
|
||||
log.debug "setting level: ${value}"
|
||||
|
||||
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)
|
||||
if (value > 0) {
|
||||
sendEvent(name: "switch", value: "on", descriptionText: "${linkText} is on by setting a level")
|
||||
@@ -363,29 +391,26 @@ def setLevel(value) {
|
||||
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}"
|
||||
makeLevelCommand(value)
|
||||
}
|
||||
|
||||
|
||||
def 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"]
|
||||
}
|
||||
|
||||
def getPressure() {
|
||||
log.debug "getPressure()"
|
||||
|
||||
// using a Keen Home specific attribute in the pressure measurement cluster
|
||||
[
|
||||
"zcl mfg-code 0x115B", "delay 200",
|
||||
"zcl global read 0x0403 0x20", "delay 200",
|
||||
@@ -395,12 +420,13 @@ def getPressure() {
|
||||
|
||||
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)
|
||||
|
||||
// disallow level updates while vent is obstructed
|
||||
if (device.currentValue("switch") == "obstructed") {
|
||||
log.error("cannot update level status because ${getLinkText(device)} is obstructed")
|
||||
return []
|
||||
}
|
||||
|
||||
["st rattr 0x${device.deviceNetworkId} 1 0x0008 0x0000"]
|
||||
}
|
||||
|
||||
@@ -425,78 +451,59 @@ def setZigBeeIdTile() {
|
||||
name: "zigbeeId",
|
||||
value: device.zigbeeId,
|
||||
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ])
|
||||
return [
|
||||
return [
|
||||
name: "zigbeeId",
|
||||
value: device.zigbeeId,
|
||||
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ]
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
getOnOff() +
|
||||
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}"
|
||||
|
||||
// get ZigBee ID by hidden tile because that's the only way we can do it
|
||||
setZigBeeIdTile()
|
||||
|
||||
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 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]
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500"
|
||||
|
||||
// mike 2015/06/22: preconfigured; see tech spec
|
||||
// configure report commands
|
||||
// zcl global send-me-a-report [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
|
||||
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// 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
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// 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
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// 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",
|
||||
// "zcl global send-me-a-report 0x0402 0 0x29 60 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"
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// keen home custom pressure (tenths of Pascals) - type: int32u, change: 1 = 0.1Pa
|
||||
// "zcl mfg-code 0x115B", "delay 200",
|
||||
// "zcl global send-me-a-report 0x0403 0x20 0x22 60 60 {010000}", "delay 200",
|
||||
// "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
|
||||
// "zcl global send-me-a-report 1 0x21 0x20 60 3600 {01}", "delay 200",
|
||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
Reference in New Issue
Block a user