From 969852602c163f4064736c90ba2635ede50f5994 Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Mon, 31 Oct 2016 11:23:48 -0500 Subject: [PATCH] ETI Clear binding table entries to other devices This adds support for ETI devices which have a firmware bug in which the binding table is not properly cleared on network leave. So the DTH will on configure (and refresh) look at the binding table and remove any entries to not the current hub. This resolves: https://smartthings.atlassian.net/browse/DVCSMP-2175 --- ...zigbee-white-color-temperature-bulb.groovy | 104 ++++++++++++++++-- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy index 15a83f6..d7db5bd 100644 --- a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy +++ b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy @@ -83,24 +83,105 @@ def parse(String description) { } } else { - def cluster = zigbee.parse(description) + Map bindingTable = parseBindingTableResponse(description) + if (bindingTable) { + List cmds = [] + bindingTable.table_entries.inject(cmds) { acc, entry -> + // The binding entry is not for our hub and should be deleted + if (entry["dstAddr"] != zigbeeEui) { + acc.addAll(removeBinding(entry.clusterId, entry.srcAddr, entry.srcEndpoint, entry.dstAddr, entry.dstEndpoint)) + } + acc + } + // There are more entries that we haven't examined yet + if (bindingTable.numTableEntries > bindingTable.startIndex + bindingTable.numEntriesReturned) { + def startPos + if (cmds) { + log.warn "Removing binding entries for other devices: $cmds" + // Since we are removing some entries, we should start in the same spot as we just read since values + // will fill in the newly vacated spots + startPos = bindingTable.startIndex + } else { + // Since we aren't removing anything we move forward to the next set of table entries + startPos = bindingTable.startIndex + bindingTable.numEntriesReturned + } + cmds.addAll(requestBindingTable(startPos)) + } + sendHubCommand(cmds.collect { it -> + new physicalgraph.device.HubAction(it) + }, 2000) + } else { + def cluster = zigbee.parse(description) - if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) { - if (cluster.data[0] == 0x00) { - log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster - sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) + if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) { + if (cluster.data[0] == 0x00) { + log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster + sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) + } + else { + log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}" + } } else { - log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}" + log.warn "DID NOT PARSE MESSAGE for description : $description" + log.debug "${cluster}" } } - else { - log.warn "DID NOT PARSE MESSAGE for description : $description" - log.debug "${cluster}" - } } } +def parseBindingTableResponse(description) { + Map descMap = zigbee.parseDescriptionAsMap(description) + if (descMap["clusterInt"] == 0x8033) { + def header_field_lengths = ["transactionSeqNo": 1, "status": 1, "numTableEntries": 1, "startIndex": 1, "numEntriesReturned": 1] + def field_values = [:] + def data = descMap["data"] + header_field_lengths.each { k, v -> + field_values[k] = Integer.parseInt(data.take(v).join(""), 16); + data = data.drop(v); + } + + List table = [] + if (field_values.numEntriesReturned) { + def table_entry_lengths = ["srcAddr": 8, "srcEndpoint": 1, "clusterId": 2, "dstAddrMode": 1] + for (def i : 0..(field_values.numEntriesReturned - 1)) { + def entryMap = [:] + table_entry_lengths.each { k, v -> + def val = data.take(v).reverse().join("") + entryMap[k] = val.length() < 8 ? Integer.parseInt(val, 16) : val + data = data.drop(v) + } + + switch (entryMap.dstAddrMode) { + case 0x01: + entryMap["dstAddr"] = data.take(2).reverse().join("") + data = data.drop(2) + break + case 0x03: + entryMap["dstAddr"] = data.take(8).reverse().join("") + data = data.drop(8) + entryMap["dstEndpoint"] = Integer.parseInt(data.take(1).join(""), 16) + data = data.drop(1) + break + } + table << entryMap + } + } + field_values["table_entries"] = table + return field_values + } + return [:] +} + +def requestBindingTable(startPos=0) { + return ["zdo mgmt-bind 0x${zigbee.deviceNetworkId} $startPos"] +} + +def removeBinding(cluster, srcAddr, srcEndpoint, destAddr, destEndpoint) { + return ["zdo unbind unicast 0x${zigbee.deviceNetworkId} {${srcAddr}} $srcEndpoint $cluster {${destAddr}} $destEndpoint"] +} + + def off() { zigbee.off() } @@ -130,8 +211,7 @@ def configure() { // enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) - // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity - refresh() + refresh() + requestBindingTable(0) + ["delay 2000"] } def setColorTemperature(value) {