diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index adf6433..198fc0a 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -23,6 +23,7 @@ metadata { capability "Sensor" capability "Refresh" capability "Relative Humidity Measurement" + capability "Health Check" command "generateEvent" command "raiseSetpoint" @@ -38,6 +39,7 @@ metadata { attribute "maxCoolingSetpoint", "number" attribute "minCoolingSetpoint", "number" attribute "deviceTemperatureUnit", "string" + attribute "deviceAlive", "enum", ["true", "false"] } tiles { @@ -120,6 +122,21 @@ metadata { } +void installed() { + // The device refreshes every 5 minutes by default so if we miss 2 refreshes we can consider it offline + // Using 12 minutes because in testing, device health team found that there could be "jitter" + sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud", hubHardwareId: device.hub.hardwareID], displayed: false) +} + +// Device Watch will ping the device to proactively determine if the device has gone offline +// If the device was online the last time we refreshed, trigger another refresh as part of the ping. +def ping() { + def isAlive = device.currentValue("deviceAlive") == "true" ? true : false + if (isAlive) { + refresh() + } +} + // parse events into attributes def parse(String description) { log.debug "Parsing '${description}'" @@ -164,7 +181,11 @@ def generateEvent(Map results) { } else if (name=="humidity") { isChange = isStateChange(device, name, value.toString()) event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"] - } else { + } else if (name == "deviceAlive") { + isChange = isStateChange(device, name, value.toString()) + event['isStateChange'] = isChange + event['displayed'] = false + } else { isChange = isStateChange(device, name, value.toString()) isDisplayed = isChange event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed] diff --git a/devicetypes/smartthings/ge-link-bulb.src/ge-link-bulb.groovy b/devicetypes/smartthings/ge-link-bulb.src/ge-link-bulb.groovy index 3e62197..d806b69 100644 --- a/devicetypes/smartthings/ge-link-bulb.src/ge-link-bulb.groovy +++ b/devicetypes/smartthings/ge-link-bulb.src/ge-link-bulb.groovy @@ -87,7 +87,7 @@ metadata { def parse(String description) { def resultMap = zigbee.getEvent(description) if (resultMap) { - if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs + if (resultMap.name != "level" || resultMap.value != 0) { // Ignore level reports of 0 sent when bulb turns off sendEvent(resultMap) } } @@ -188,12 +188,10 @@ def updated() { } def on() { - state.trigger = "on/off" zigbee.on() } def off() { - state.trigger = "on/off" zigbee.off() } @@ -206,7 +204,6 @@ def refresh() { } def setLevel(value) { - state.trigger = "setLevel" def cmd def delayForRefresh = 500 if (dimRate && (state?.rate != null)) { diff --git a/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy b/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy index 27e9734..a4c240a 100644 --- a/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy +++ b/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy @@ -69,15 +69,17 @@ metadata { def parse(String description) { log.debug "description is $description" + def event = [:] def finalResult = isKnownDescription(description) if (finalResult != "false") { log.info finalResult if (finalResult.type == "update") { log.info "$device updates: ${finalResult.value}" + event = null } else if (finalResult.type == "power") { def powerValue = (finalResult.value as Integer)/10 - sendEvent(name: "power", value: powerValue) + event = createEvent(name: "power", value: powerValue) /* Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10 @@ -87,13 +89,14 @@ def parse(String description) { */ } else { - sendEvent(name: finalResult.type, value: finalResult.value) + event = createEvent(name: finalResult.type, value: finalResult.value) } } else { log.warn "DID NOT PARSE MESSAGE for description : $description" log.debug parseDescriptionAsMap(description) } + return event } // Commands to device diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index d96dd77..192f75a 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -79,6 +79,7 @@ def parse(String description) { log.debug "description is $description" def finalResult = zigbee.getKnownDescription(description) + def event = [:] //TODO: Remove this after getKnownDescription can parse it automatically if (!finalResult && description!="updated") @@ -88,10 +89,11 @@ def parse(String description) { log.info "final result = $finalResult" if (finalResult.type == "update") { log.info "$device updates: ${finalResult.value}" + event = null } else if (finalResult.type == "power") { def powerValue = (finalResult.value as Integer)/10 - sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true ) + event = createEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true) /* Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10 power level is an integer. The exact power level with correct units needs to be handled in the device type @@ -100,7 +102,7 @@ def parse(String description) { } else { def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' - sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) + event = createEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) } } else { @@ -109,10 +111,11 @@ def parse(String 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]) + event = createEvent(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]}" + event = null } } else { @@ -120,6 +123,7 @@ def parse(String description) { log.debug "${cluster}" } } + return event } def off() { diff --git a/devicetypes/smartthings/smartsense-garage-door-multi.src/smartsense-garage-door-multi.groovy b/devicetypes/smartthings/smartsense-garage-door-multi.src/smartsense-garage-door-multi.groovy index 73b0fcf..8454b9c 100644 --- a/devicetypes/smartthings/smartsense-garage-door-multi.src/smartsense-garage-door-multi.groovy +++ b/devicetypes/smartthings/smartsense-garage-door-multi.src/smartsense-garage-door-multi.groovy @@ -86,7 +86,7 @@ metadata { def parse(String description) { log.debug "parse($description)" - def results = null + def results = [:] if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) { // Ignore this in favor of orientation-based state diff --git a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy index b426cd8..61fefc3 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -102,7 +102,7 @@ def parse(String description) { } log.debug "Parse returned $map" - def result = map ? createEvent(map) : null + def result = map ? createEvent(map) : [:] if (description?.startsWith('enroll request')) { List cmds = enrollResponse() diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index de99678..3244898 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -106,7 +106,7 @@ def parse(String description) { } log.debug "Parse returned $map" - def result = map ? createEvent(map) : null + def result = map ? createEvent(map) : [:] if (description?.startsWith('enroll request')) { List cmds = enrollResponse() diff --git a/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy b/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy index 3b3da9a..976c1d5 100644 --- a/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy @@ -94,7 +94,7 @@ def parse(String description) { } log.debug "Parse returned $map" - def result = map ? createEvent(map) : null + def result = map ? createEvent(map) : [:] if (description?.startsWith('enroll request')) { List cmds = enrollResponse() diff --git a/devicetypes/smartthings/smartsense-motion.src/smartsense-motion.groovy b/devicetypes/smartthings/smartsense-motion.src/smartsense-motion.groovy index bbb1b7d..5cf3743 100644 --- a/devicetypes/smartthings/smartsense-motion.src/smartsense-motion.groovy +++ b/devicetypes/smartthings/smartsense-motion.src/smartsense-motion.groovy @@ -44,7 +44,7 @@ metadata { } def parse(String description) { - def results + def results = [:] if (isZoneType19(description) || !isSupportedDescription(description)) { results = parseBasicMessage(description) } @@ -57,21 +57,25 @@ def parse(String description) { private Map parseBasicMessage(description) { def name = parseName(description) - def value = parseValue(description) - def linkText = getLinkText(device) - def descriptionText = parseDescriptionText(linkText, value, description) - def handlerName = value - def isStateChange = isStateChange(device, name, value) + if (name != null) { + def value = parseValue(description) + def linkText = getLinkText(device) + def descriptionText = parseDescriptionText(linkText, value, description) + def handlerName = value + def isStateChange = isStateChange(device, name, value) - def results = [ - name: name, - value: value, - linkText: linkText, - descriptionText: descriptionText, - handlerName: handlerName, - isStateChange: isStateChange, - displayed: displayed(description, isStateChange) - ] + def results = [ + name : name, + value : value, + linkText : linkText, + descriptionText: descriptionText, + handlerName : handlerName, + isStateChange : isStateChange, + displayed : displayed(description, isStateChange) + ] + } else { + results = [:] + } log.debug "Parse returned $results.descriptionText" return results } diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index bc8092d..2c1c2e3 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -127,7 +127,7 @@ def parse(String description) { map = parseIasMessage(description) } - def result = map ? createEvent(map) : null + def result = map ? createEvent(map) : [:] if (description?.startsWith('enroll request')) { List cmds = enrollResponse() diff --git a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy index 8e697f1..8c779d8 100644 --- a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy @@ -93,7 +93,7 @@ def parse(String description) { } log.debug "Parse returned $map" - def result = map ? createEvent(map) : null + def result = map ? createEvent(map) : [:] if (description?.startsWith('enroll request')) { List cmds = enrollResponse() diff --git a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy index 0caa602..e0dc7e2 100644 --- a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy +++ b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy @@ -84,7 +84,7 @@ def parse(String description) { } log.debug "Parse returned $map" - return map ? createEvent(map) : null + return map ? createEvent(map) : [:] } private Map parseCatchAllMessage(String description) { diff --git a/devicetypes/smartthings/zigbee-dimmer.src/README.md b/devicetypes/smartthings/zigbee-dimmer.src/README.md index 35997a5..61f3ed0 100644 --- a/devicetypes/smartthings/zigbee-dimmer.src/README.md +++ b/devicetypes/smartthings/zigbee-dimmer.src/README.md @@ -1,10 +1,11 @@ -# OSRAM Lightify LED On/Off/Dim +# Zigbee Dimmer Works with: * [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim) +* [WeMo LED Bulb](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-) ## Table of contents @@ -23,14 +24,16 @@ Works with: ## Device Health -A Category C1 Zigbee dimmer with maxReportTime of 5 mins. -Check-in interval is double the value of maxReportTime. +A Zigbee dimmer with maxReportTime of 5 mins. +Check-in interval is double the value of maxReportTime. This gives the device twice the amount of time to respond before it is marked as offline. -Check-in interval = 12 mins +Enrolls with default periodic reporting until newer 5 min interval is confirmed +It then enrolls the device with updated checkInterval i.e. 12 mins ## Troubleshooting If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range. Pairing needs to be tried again by placing the device closer to the hub. Other troubleshooting tips are listed as follows: -* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim) +* [OSRAM Lightify LED On/Off/Dim Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim) +* [WeMo LED Bulb Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-) diff --git a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/README.md b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/README.md index 3bd9229..779d119 100644 --- a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/README.md +++ b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/README.md @@ -1,10 +1,11 @@ -# OSRAM Lightify Tunable 60 White +# ZigBee White Color Temperature Bulb Works with: * [OSRAM Lightify Tunable 60 White](http://www.osram.com/osram_com/tools-and-services/tools/lightify---smart-connected-light/lightify-for-home---what-is-light-to-you/lightify-products/lightify-classic-a60-tunable-white/index.jsp) +* [OSRAM LIGHTIFY RT5/6 Tunable White](https://www.smartthings.com/works-with-smartthings/light-bulbs/osram-lightify-rt56-tunable-white) ## Table of contents @@ -24,14 +25,16 @@ Works with: ## Device Health -A Category C1 OSRAM Lightify Tunable 60 White with maxReportTime of 5 mins. -Check-in interval is double the value of maxReportTime. +Zigbee Bulb with maxReportTime of 5 mins. +Check-in interval is double the value of maxReportTime. This gives the device twice the amount of time to respond before it is marked as offline. -Check-in interval = 12 mins +Enrolls with default periodic reporting until newer 5 min interval is confirmed +It then enrolls the device with updated checkInterval i.e. 12 mins ## Troubleshooting If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range. Pairing needs to be tried again by placing the device closer to the hub. Other troubleshooting tips are listed as follows: -* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb) +* [OSRAM Lightify Tunable 60 White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb) +* [OSRAM LIGHTIFY RT5/6 Tunable White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/214191863-How-to-connect-OSRAM-LIGHTIFY-Bulbs) \ No newline at end of file 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..ad28c8f 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 @@ -33,7 +33,7 @@ metadata { fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White" - fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White" + fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY RT5/6 Tunable White" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White" @@ -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) { diff --git a/devicetypes/smartthings/zwave-dimmer-switch-generic.src/README.md b/devicetypes/smartthings/zwave-dimmer-switch-generic.src/README.md index 5bbc091..f5f4fef 100644 --- a/devicetypes/smartthings/zwave-dimmer-switch-generic.src/README.md +++ b/devicetypes/smartthings/zwave-dimmer-switch-generic.src/README.md @@ -4,7 +4,8 @@ Works with: -* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](http://www.leviton.com/OA_HTML/ProductDetail.jsp?partnumber=DZPD3-1LW) +* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-plug-in-lamp-dimmer-module) +* [Leviton Universal Dimmer (DZMX1-LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-universal-dimmer) ## Table of contents @@ -24,7 +25,7 @@ Works with: ## Device Health -A Category C5 Leviton Plug-in Lamp Dimmer Module (DZPA1-1LW) (Z-Wave) polled by the hub. +Leviton Plug-in Lamp Dimmer Module (DZPA1-1LW) (Z-wave) and Leviton Universal Dimmer (DZMX1-LZ) (Z-Wave) are polled by the hub. As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed. Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins. Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for @@ -36,4 +37,5 @@ it is not polled for 5 minutes by the hub. This can delay up the process of bein If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range. Pairing needs to be tried again by placing the device closer to the hub. Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link: -* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices) \ No newline at end of file +* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices) +* [Leviton Universal Dimmer (DZMX1-LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices) \ No newline at end of file diff --git a/devicetypes/smartthings/zwave-dimmer-switch-generic.src/zwave-dimmer-switch-generic.groovy b/devicetypes/smartthings/zwave-dimmer-switch-generic.src/zwave-dimmer-switch-generic.groovy index bd1668b..43a65b8 100644 --- a/devicetypes/smartthings/zwave-dimmer-switch-generic.src/zwave-dimmer-switch-generic.groovy +++ b/devicetypes/smartthings/zwave-dimmer-switch-generic.src/zwave-dimmer-switch-generic.groovy @@ -23,6 +23,7 @@ metadata { fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer" fingerprint mfr:"001D", prod:"1902", deviceJoinName: "Z-Wave Dimmer" + fingerprint mfr:"001D", prod:"1B03", model:"0334", deviceJoinName: "Leviton Universal Dimmer" } simulator { diff --git a/devicetypes/smartthings/zwave-switch-generic.src/.st-ignore b/devicetypes/smartthings/zwave-switch-generic.src/.st-ignore new file mode 100644 index 0000000..f78b46e --- /dev/null +++ b/devicetypes/smartthings/zwave-switch-generic.src/.st-ignore @@ -0,0 +1,2 @@ +.st-ignore +README.md diff --git a/devicetypes/smartthings/zwave-switch-generic.src/README.md b/devicetypes/smartthings/zwave-switch-generic.src/README.md new file mode 100644 index 0000000..765bcd1 --- /dev/null +++ b/devicetypes/smartthings/zwave-switch-generic.src/README.md @@ -0,0 +1,43 @@ +# Z-wave Switch + + + +Works with: + +* [Leviton Appliance Module (DZPA1-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-appliance-module) +* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)](https://www.smartthings.com/works-with-smartthings/outlets/ge-plug-in-outdoor-smart-switch) +* [Leviton Outlet (DZR15-1LZ)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-outlet) +* [Leviton Switch (DZS15-1LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-switch) + +## Table of contents + +* [Capabilities](#capabilities) +* [Health](#device-health) + +## Capabilities + +* **Actuator** - represents that a Device has commands +* **Health Check** - indicates ability to get device health notifications +* **Switch** - can detect state (possible values: on/off) +* **Polling** - represents that poll() can be implemented for the device +* **Refresh** - _refresh()_ command for status updates +* **Sensor** - detects sensor events + +## Device Health + +Leviton Appliance Module (DZPA1-1LW), GE Plug-In Outdoor Smart Switch (GE 12720), Leviton Outlet (DZR15-1LZ) and Leviton Switch (DZS15-1LZ) (Z-Wave) are polled by the hub. +As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed. +Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins. +Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for +the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row, +it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time. + +## Troubleshooting + +If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range. +Pairing needs to be tried again by placing the device closer to the hub. +Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link: +* [Leviton Appliance Module (DZPA1-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices) +* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-) +* [Leviton Outlet (DZR15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices) +* [Leviton Switch (DZS15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices) \ No newline at end of file diff --git a/devicetypes/smartthings/zwave-switch-generic.src/zwave-switch-generic.groovy b/devicetypes/smartthings/zwave-switch-generic.src/zwave-switch-generic.groovy index 798b89d..01ce695 100644 --- a/devicetypes/smartthings/zwave-switch-generic.src/zwave-switch-generic.groovy +++ b/devicetypes/smartthings/zwave-switch-generic.src/zwave-switch-generic.groovy @@ -14,12 +14,17 @@ metadata { definition (name: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") { capability "Actuator" + capability "Health Check" capability "Switch" capability "Polling" capability "Refresh" capability "Sensor" fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch" + fingerprint mfr:"001D", prod:"1A02", model:"0334", deviceJoinName: "Leviton Appliance Module" + fingerprint mfr:"0063", prod:"4F50", model:"3031", deviceJoinName: "GE Plug-in Outdoor Switch" + fingerprint mfr:"001D", prod:"1D04", model:"0334", deviceJoinName: "Leviton Outlet" + fingerprint mfr:"001D", prod:"1C02", model:"0334", deviceJoinName: "Leviton Switch" } // simulator metadata @@ -50,6 +55,11 @@ metadata { } } +def updated(){ +// Device-Watch simply pings if no device events received for checkInterval duration of 32min = 2 * 15min + 2min lag time + sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) +} + def parse(String description) { def result = null def cmd = zwave.parse(description, [0x20: 1, 0x70: 1]) @@ -126,6 +136,13 @@ def poll() { ]) } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + refresh() +} + def refresh() { delayBetween([ zwave.switchBinaryV1.switchBinaryGet().format(), diff --git a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy index d8c2179..9b4c178 100644 --- a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy +++ b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy @@ -842,6 +842,7 @@ private void storeThermostatData(thermostats) { minCoolingSetpoint: (stat.settings.coolRangeLow / 10), maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10), autoMode: stat.settings.autoHeatCoolFeatureEnabled, + deviceAlive: stat.runtime.connected == true ? "true" : "false", auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler), temperature: (stat.runtime.actualTemperature / 10), heatingSetpoint: stat.runtime.desiredHeat / 10, diff --git a/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy b/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy index 3cf64e1..9df77ca 100644 --- a/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy +++ b/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy @@ -24,6 +24,12 @@ * switches | switch | on, off | on, off * motionSensors | motion | | active, inactive * contactSensors | contact | | open, closed + * thermostat | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint + * | | setCoolingSetpoint(number) | coolingSetpoint, thermostatSetpoint + * | | off, heat, emergencyHeat | thermostatMode — ["emergency heat", "auto", "cool", "off", "heat"] + * | | cool, setThermostatMode | thermostatFanMode — ["auto", "on", "circulate"] + * | | fanOn, fanAuto, fanCirculate| thermostatOperatingState — ["cooling", "heating", "pending heat", + * | | setThermostatFanMode, auto | "fan only", "vent economizer", "pending cool", "idle"] * presenceSensors | presence | | present, 'not present' * temperatureSensors | temperature | | * accelerationSensors | acceleration | | active, inactive @@ -58,6 +64,7 @@ preferences(oauthPage: "deviceAuthorization") { input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors?", multiple: true, required: false + input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors?", multiple: true, required: false input "temperatureSensors", "capability.temperatureMeasurement", title: "Which Temperature Sensors?", multiple: true, required: false input "accelerationSensors", "capability.accelerationSensor", title: "Which Vibration Sensors?", multiple: true, required: false @@ -936,7 +943,7 @@ def deleteHarmony() { } private getAllDevices() { - ([] + switches + motionSensors + contactSensors + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id } + ([] + switches + motionSensors + contactSensors + thermostats + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id } } private deviceItem(device) {