Skip to content

Instantly share code, notes, and snippets.

@netsheriff
Created March 31, 2018 08:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save netsheriff/169e5df2f1b7c8b1bf82f5f164fdd56c to your computer and use it in GitHub Desktop.
Save netsheriff/169e5df2f1b7c8b1bf82f5f164fdd56c to your computer and use it in GitHub Desktop.
HUI ZigBee Wall Switch 3 Gang V.1.5
/**
* 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