Created
March 31, 2018 08:43
-
-
Save netsheriff/169e5df2f1b7c8b1bf82f5f164fdd56c to your computer and use it in GitHub Desktop.
HUI ZigBee Wall Switch 3 Gang V.1.5
This file contains 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 2017 SmartThings original - shamlessly borrowed by the netsheriff and modded. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | |
* in compliance with the License. You may obtain a copy of the License at: | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed | |
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License | |
* for the specific language governing permissions and limitations under the License. | |
* | |
* Feibit 3 Gang ZigBee Switch | |
* | |
* Author: SmartThings and others | |
* Date: 2017-09-05 | |
*/ | |
metadata { | |
// Automatically generated. Make future change here. | |
// flipped the on/off detection | |
definition (name: "HUI ZigBee Wall Switch 3 Gang V.1.5", namespace: "", author: "netsheriff") { | |
capability "Switch" | |
capability "Configuration" | |
capability "Refresh" | |
capability "Actuator" | |
capability "Sensor" | |
command "OneOn" | |
command "OneOff" | |
command "TwoOn" | |
command "TwoOff" | |
command "ThreeOn" | |
command "ThreeOff" | |
attribute "switch1","ENUM",["on","off"] | |
attribute "switch2","ENUM",["on","off"] | |
attribute "switch3","ENUM",["on","off"] | |
attribute "switchstate","ENUM",["on","off"] | |
// fingerprint profileId: "0104", inClusters: "0000, 0005, 0004, 0006, 0702", outClusters: "0000, 000A, 0019" | |
// fingerprint inClusters: "0000, 0005, 0004, 0006", endpointId: "12", deviceId: "0002", profileId: "0104" | |
fingerprint profileId: "0104", inClusters: "0000 0005 0004 0006, 0702", endpointId: "12", deviceId: "0002", outClusters: "0000", manufacturer: "Feibit Inc co." | |
// fingerprint profileId: "0104", endpointId: "12", deviceId: "0002", manufacturer: "Feibit Inc co.", model: "FB56+ZSW1IKJ1.7" | |
} | |
// simulator metadata | |
simulator { | |
// status messages | |
status "on": "on/off: 1" | |
status "off": "on/off: 0" | |
status "switch1 on": "on/off: 1" | |
status "switch1 off": "on/off: 0" | |
status "switch2 on": "on/off: 1" | |
status "switch2 off": "on/off: 0" | |
status "switch3 on": "on/off: 1" | |
status "switch3 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("switch1", "device.switch1", width: 1, height: 1, canChangeIcon: True) { | |
state "on", label:'SW1 On', action:"OneOff", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff" | |
state "off", label:'SW1 Off', action:"OneOn", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" | |
} | |
standardTile("switch2", "device.switch2", width: 1, height: 1, canChangeIcon: true) { | |
state "on", label:'SW2 On', action:"TwoOff", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff" | |
state "off", label:'SW2 Off', action:"TwoOn", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" | |
} | |
standardTile("switch3", "device.switch3", width: 1, height: 1, canChangeIcon: true) { | |
state "on", label:'SW3 On', action:"ThreeOff", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff" | |
state "off", label:'SW3 Off', action:"ThreeOn", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" | |
} | |
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 1, height: 1) { | |
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" | |
} | |
main (["switch1", "switch2", "switch3"]) | |
details(["switch1", "switch2", "switch3", "refresh"]) | |
} | |
} | |
// Parse incoming device messages to generate events | |
def parse(String description) { | |
log.debug "description is $description" | |
def finalResult = isKnownDescription(description) | |
if (finalResult != "false") { | |
log.info finalResult | |
if (finalResult.type == "update") { | |
log.info "$device updates: ${finalResult.value}" | |
} | |
else { | |
sendEvent(name: finalResult.type, value: finalResult.value) | |
} | |
} | |
else { | |
log.warn "DID NOT PARSE MESSAGE for description : $description" | |
log.debug parseDescriptionAsMap(description) | |
} | |
} | |
// Commands to device | |
def zigbeeCommand(endp, cluster, attribute){ | |
"st cmd 0x${device.deviceNetworkId} ${endp} ${cluster} ${attribute} {}" | |
} | |
def OneOn() { | |
zigbeeCommand("0x12","6", "1") | |
} | |
def OneOff() { | |
zigbeeCommand("0x12","6", "0") | |
} | |
def TwoOn() { | |
zigbeeCommand("0x11","6", "1") | |
} | |
def TwoOff() { | |
zigbeeCommand("0x11","6", "0") | |
} | |
def ThreeOn() { | |
zigbeeCommand("0x10","6", "1") | |
} | |
def ThreeOff() { | |
zigbeeCommand("0x10","6", "0") | |
} | |
def refresh() { | |
[ | |
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 1" | |
] | |
} | |
def configure() { | |
onOffConfig() + refresh() | |
} | |
private getEndpointId() { | |
new BigInteger(device.endpointId, 16).toString() | |
} | |
private hex(value, width=2) { | |
def s = new BigInteger(Math.round(value).toString()).toString(16) | |
while (s.size() < width) { | |
s = "0" + s | |
} | |
s | |
} | |
private String swapEndianHex(String hex) { | |
reverseArray(hex.decodeHex()).encodeHex() | |
} | |
private Integer convertHexToInt(hex) { | |
Integer.parseInt(hex,16) | |
} | |
//Need to reverse array of size 2 | |
private byte[] reverseArray(byte[] array) { | |
byte tmp; | |
tmp = array[1]; | |
array[1] = array[0]; | |
array[0] = tmp; | |
return array | |
} | |
def parseDescriptionAsMap(description) { | |
if (description?.startsWith("read attr -")) { | |
(description - "read attr - ").split(",").inject([:]) { map, param -> | |
def nameAndValue = param.split(":") | |
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()] | |
} | |
} | |
else if (description?.startsWith("catchall: ")) { | |
def seg = (description - "catchall: ").split(" ") | |
def zigbeeMap = [:] | |
zigbeeMap += [raw: (description - "catchall: ")] | |
zigbeeMap += [profileId: seg[0]] | |
zigbeeMap += [clusterId: seg[1]] | |
zigbeeMap += [sourceEndpoint: seg[2]] | |
zigbeeMap += [destinationEndpoint: seg[3]] | |
zigbeeMap += [options: seg[4]] | |
zigbeeMap += [messageType: seg[5]] | |
zigbeeMap += [dni: seg[6]] | |
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0] | |
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0] | |
zigbeeMap += [manufacturerId: seg[9]] | |
zigbeeMap += [command: seg[10]] | |
zigbeeMap += [direction: seg[11]] | |
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect { | |
it.join('') | |
} : []] | |
zigbeeMap | |
} | |
} | |
def isKnownDescription(description) { | |
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) { | |
def descMap = parseDescriptionAsMap(description) | |
if (descMap.cluster == "0006" || descMap.clusterId == "0006") { | |
isDescriptionOnOff(descMap) | |
} | |
else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){ | |
isDescriptionPower(descMap) | |
} | |
else { | |
return "false" | |
} | |
} | |
else if(description?.startsWith("on/off:")) { | |
def switchValue = description?.endsWith("1") ? "on" : "off" | |
if (descMap.sourceEndpoint == "12") { | |
return [type: "switch1", value : switchValue] | |
} | |
else if (descMap.sourceEndpoint == "11") { | |
return [type: "switch2", value : switchValue] | |
} | |
else if (descMap.sourceEndpoint == "10") { | |
return [type: "switch3", value : switchValue] | |
} | |
} | |
else { | |
return "false" | |
} | |
} | |
def isDescriptionOnOff(descMap) { | |
def switchValue = "undefined" | |
if (descMap.cluster == "0006") { //cluster info from read attr | |
value = descMap.value | |
if (value == "01"){ | |
switchValue = "on" | |
} | |
else if (value == "00"){ | |
switchValue = "off" | |
} | |
} | |
else if (descMap.clusterId == "0006") { | |
//cluster info from catch all | |
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00 | |
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00 | |
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){ | |
if (descMap.sourceEndpoint == "12") { | |
return [type: "switch1", value : "on"] | |
} | |
else if (descMap.sourceEndpoint == "11") { | |
return [type: "switch2", value : "on"] | |
} | |
else if (descMap.sourceEndpoint == "10") { | |
return [type: "switch3", value : "on"] | |
} | |
} | |
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){ | |
if (descMap.sourceEndpoint == "12") { | |
return [type: "switch1", value : "off"] | |
} | |
else if (descMap.sourceEndpoint == "11") { | |
return [type: "switch2", value : "off"] | |
} | |
else if (descMap.sourceEndpoint == "10") { | |
return [type: "switch3", value : "off"] | |
} | |
} | |
else if (descMap.command=="01" && descMap.raw.endsWith("2000")){ | |
if (descMap.sourceEndpoint == "12") { | |
return [type: "switch1", value : "off"] | |
} | |
else if (descMap.sourceEndpoint == "11") { | |
return [type: "switch2", value : "off"] | |
} | |
else if (descMap.sourceEndpoint == "10") { | |
return [type: "switch3", value : "off"] | |
} | |
} | |
else if (descMap.command=="01" && descMap.raw.endsWith("2001")){ | |
if (descMap.sourceEndpoint == "12") { | |
return [type: "switch1", value : "on"] | |
} | |
else if (descMap.sourceEndpoint == "11") { | |
return [type: "switch2", value : "on"] | |
} | |
else if (descMap.sourceEndpoint == "10") { | |
return [type: "switch3", value : "on"] | |
} | |
} | |
else if(descMap.command=="07"){ | |
return [type: "update", value : "switch (0006) capability configured successfully"] | |
} | |
} | |
if (switchValue != "undefined"){ | |
return [type: "switch", value : switchValue] | |
} | |
else { | |
return "false" | |
} | |
} | |
def isDescriptionPower(descMap) { | |
def powerValue = "undefined" | |
if (descMap.cluster == "0702") { | |
if (descMap.attrId == "0400") { | |
powerValue = convertHexToInt(descMap.value) | |
} | |
} | |
else if (descMap.clusterId == "0702") { | |
if(descMap.command=="07"){ | |
return [type: "update", value : "power (0702) capability configured successfully"] | |
} | |
} | |
if (powerValue != "undefined"){ | |
return [type: "power", value : powerValue] | |
} | |
else { | |
return "false" | |
} | |
} | |
def onOffConfig() { | |
[ | |
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200", | |
"zcl global send-me-a-report 6 0 0x10 0 600 {01}", | |
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" | |
] | |
} | |
String convertToHexString(value, width=2) { | |
def s = new BigInteger(Math.round(value).toString()).toString(16) | |
while (s.size() < width) { | |
s = "0" + s | |
} | |
s | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment