From a79c9c1aded7871a310f180b791b20afed168178 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Wed, 17 Aug 2016 14:53:01 -0600 Subject: [PATCH 1/8] SVD-2465 Philips Hue No description for slider -Changed Kelvin tile to Whites (same as Philips Hue app) --- devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy | 12 ++++++------ .../hue-white-ambiance-bulb.groovy | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index d533006..1cec1f6 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -43,13 +43,13 @@ metadata { } } - controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { - state "colorTemperature", action:"color temperature.setColorTemperature" - } + controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { + state "colorTemperature", action:"color temperature.setColorTemperature" + } - valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "colorTemperature", label: '${currentValue} K' - } + valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "colorTemperature", label: 'WHITES' + } standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" diff --git a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy index c9bedb7..03aa095 100644 --- a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy +++ b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy @@ -41,7 +41,7 @@ metadata { } valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "colorTemperature", label: '${currentValue} K' + state "colorTemperature", label: 'WHITES' } standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { @@ -107,4 +107,3 @@ void refresh() { log.debug "Executing 'refresh'" parent.manualRefresh() } - From ef47ec93938aa13ce98cc46e20bf6b6c562f0e69 Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Wed, 17 Aug 2016 18:21:54 -0700 Subject: [PATCH 2/8] hotfix for level jumping issues --- .../smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy | 5 ++++- .../smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy | 5 ++++- .../zigbee-white-color-temperature-bulb.groovy | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy index 72809f5..666a1ec 100644 --- a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy +++ b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy @@ -53,7 +53,10 @@ def parse(String description) { def event = zigbee.getEvent(description) if (event) { - sendEvent(event) + if (event.name=="level" && event.value==0) {} + else { + sendEvent(event) + } } else { log.warn "DID NOT PARSE MESSAGE for description : $description" diff --git a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy index ade4987..a1d3b49 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -81,7 +81,10 @@ def parse(String description) { def finalResult = zigbee.getEvent(description) if (finalResult) { log.debug finalResult - sendEvent(finalResult) + if (finalResult.name=="level" && finalResult.value==0) {} + else { + sendEvent(finalResult) + } } else { def zigbeeMap = zigbee.parseDescriptionAsMap(description) 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 50f270d..eda7009 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 @@ -75,7 +75,10 @@ def parse(String description) { log.debug "description is $description" def event = zigbee.getEvent(description) if (event) { - sendEvent(event) + if (event.name=="level" && event.value==0) {} + else { + sendEvent(event) + } } else { log.warn "DID NOT PARSE MESSAGE for description : $description" From 53fc948b00d2cdb18cc28fbe01159b1c62d56d43 Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Thu, 18 Aug 2016 15:04:48 -0700 Subject: [PATCH 3/8] SSVD-2465 changing the kelvin tile to name tiles --- .../zigbee-rgbw-bulb.groovy | 27 ++++++++++++++++--- ...zigbee-white-color-temperature-bulb.groovy | 9 +++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy index a1d3b49..de4f4d8 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -28,6 +28,9 @@ metadata { capability "Switch" capability "Switch Level" + attribute "colorName", "string" + command "setGenericName" + fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED A19 RGBW" @@ -54,15 +57,15 @@ metadata { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") { state "colorTemperature", action:"color temperature.setColorTemperature" } - valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "colorTemperature", label: '${currentValue} K' + valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "colorName", label: '${currentValue}' } standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" } main(["switch"]) - details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) + details(["switch", "colorTempSliderControl", "colorName", "refresh"]) } } @@ -124,9 +127,27 @@ def configure() { } def setColorTemperature(value) { + setGenericName(value) zigbee.setColorTemperature(value) } +//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature +def setGenericName(value){ + if (value != null) { + def genericName = "White" + if (value < 3300) { + genericName = "Soft White" + } else if (value < 4150) { + genericName = "Moonlight" + } else if (value <= 5000) { + genericName = "Cool White" + } else if (value >= 5000) { + genericName = "Daylight" + } + sendEvent(name: "colorName", value: genericName) + } +} + def setLevel(value) { zigbee.setLevel(value) } 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 eda7009..539d8fd 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 @@ -49,9 +49,6 @@ metadata { tileAttribute ("device.level", key: "SLIDER_CONTROL") { attributeState "level", action:"switch level.setLevel" } - tileAttribute ("colorName", key: "SECONDARY_CONTROL") { - attributeState "colorName", label:'${currentValue}' - } } standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { @@ -61,12 +58,12 @@ metadata { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") { state "colorTemperature", action:"color temperature.setColorTemperature" } - valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "colorTemperature", label: '${currentValue} K' + valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "colorName", label: '${currentValue}' } main(["switch"]) - details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) + details(["switch", "colorTempSliderControl", "colorName", "refresh"]) } } From df6646103a2244c329ab817439255345dd696c0f Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Wed, 15 Jun 2016 10:32:53 -0700 Subject: [PATCH 4/8] DVCSMP-1727 Philips Hue: Update status on response -Only send events when feedback is recevied from bridge -Cleaned up color conversions -DVCSMP-1728 Philips Hue: Handle new LAN polling -DVCSMP-1763 Philips Hue: Bad rounding can cause level to be 0, light on -DVCSMP-1678 Philips Hue: Bridge detail page should display the status. -DVCSMP-1669 Phillips Hue: The on/off button flickering between states -DEVC-450 Hue Connect app is throwing errors in the IDE -Changed manual refresh to check bridge and run poll() -Updated some strings --- .../hue-bloom.src/hue-bloom.groovy | 78 +- .../hue-bridge.src/hue-bridge.groovy | 26 +- .../smartthings/hue-bulb.src/hue-bulb.groovy | 71 +- .../hue-lux-bulb.src/hue-lux-bulb.groovy | 4 - .../hue-white-ambiance-bulb.groovy | 13 +- .../hue-connect.src/hue-connect.groovy | 897 ++++++++++++++---- 6 files changed, 760 insertions(+), 329 deletions(-) diff --git a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy index 0c10c51..f99f436 100644 --- a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy +++ b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy @@ -43,7 +43,7 @@ metadata { } standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { - state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" + state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single" } standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { @@ -51,7 +51,7 @@ metadata { } main(["rich-control"]) - details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"]) + details(["rich-control", "reset", "refresh"]) } } @@ -75,118 +75,78 @@ def parse(description) { // handle commands void on() { log.trace parent.on(this) - sendEvent(name: "switch", value: "on") } void off() { log.trace parent.off(this) - sendEvent(name: "switch", value: "off") -} - -void nextLevel() { - def level = device.latestValue("level") as Integer ?: 0 - if (level <= 100) { - level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer - } - else { - level = 25 - } - setLevel(level) } void setLevel(percent) { log.debug "Executing 'setLevel'" if (verifyPercent(percent)) { - parent.setLevel(this, percent) - sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%") - sendEvent(name: "switch", value: "on") + log.trace parent.setLevel(this, percent) } } void setSaturation(percent) { log.debug "Executing 'setSaturation'" if (verifyPercent(percent)) { - parent.setSaturation(this, percent) - sendEvent(name: "saturation", value: percent, displayed: false) + log.trace parent.setSaturation(this, percent) } } void setHue(percent) { log.debug "Executing 'setHue'" if (verifyPercent(percent)) { - parent.setHue(this, percent) - sendEvent(name: "hue", value: percent, displayed: false) + log.trace parent.setHue(this, percent) } } void setColor(value) { - log.debug "setColor: ${value}, $this" def events = [] def validValues = [:] if (verifyPercent(value.hue)) { - events << createEvent(name: "hue", value: value.hue, displayed: false) validValues.hue = value.hue } if (verifyPercent(value.saturation)) { - events << createEvent(name: "saturation", value: value.saturation, displayed: false) validValues.saturation = value.saturation } if (value.hex != null) { if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) { - events << createEvent(name: "color", value: value.hex) validValues.hex = value.hex } else { log.warn "$value.hex is not a valid color" } } if (verifyPercent(value.level)) { - events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%") validValues.level = value.level } if (value.switch == "off" || (value.level != null && value.level <= 0)) { - events << createEvent(name: "switch", value: "off") validValues.switch = "off" } else { - events << createEvent(name: "switch", value: "on") validValues.switch = "on" } - if (!events.isEmpty()) { - parent.setColor(this, validValues) - } - events.each { - sendEvent(it) + if (!validValues.isEmpty()) { + log.trace parent.setColor(this, validValues) } } void reset() { log.debug "Executing 'reset'" - def value = [level:100, saturation:56, hue:23] - setAdjustedColor(value) - parent.poll() + def value = [hue:20, saturation:2] + setAdjustedColor(value) } void setAdjustedColor(value) { if (value) { log.trace "setAdjustedColor: ${value}" def adjusted = value + [:] - adjusted.hue = adjustOutgoingHue(value.hue) // Needed because color picker always sends 100 adjusted.level = null - setColor(adjusted) + setColor(adjusted) } else { - log.warn "Invalid color input" - } -} - -void setColorTemperature(value) { - if (value) { - log.trace "setColorTemperature: ${value}k" - parent.setColorTemperature(this, value) - sendEvent(name: "colorTemperature", value: value) - sendEvent(name: "switch", value: "on") - } else { - log.warn "Invalid color temperature" + log.warn "Invalid color input $value" } } @@ -195,22 +155,6 @@ void refresh() { parent.manualRefresh() } -def adjustOutgoingHue(percent) { - def adjusted = percent - if (percent > 31) { - if (percent < 63.0) { - adjusted = percent + (7 * (percent -30 ) / 32) - } - else if (percent < 73.0) { - adjusted = 69 + (5 * (percent - 62) / 10) - } - else { - adjusted = percent + (2 * (100 - percent) / 28) - } - } - log.info "percent: $percent, adjusted: $adjusted" - adjusted -} def verifyPercent(percent) { if (percent == null) diff --git a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy index 9537fef..dc0a9f0 100644 --- a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy +++ b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy @@ -7,8 +7,13 @@ metadata { // Automatically generated. Make future change here. definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") { - attribute "serialNumber", "string" attribute "networkAddress", "string" + // Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network + // Possible values "Online" or "Offline" + attribute "status", "string" + // Id is the number on the back of the hub, Hue uses last six digits of Mac address + // This is also used in the Hue application as ID + attribute "idNumber", "string" } simulator { @@ -17,22 +22,23 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"rich-control"){ - tileAttribute ("", key: "PRIMARY_CONTROL") { - attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200" + tileAttribute ("device.status", key: "PRIMARY_CONTROL") { + attributeState "Offline", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#ffffff" + attributeState "Online", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#79b821" } - tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") { - attributeState "default", label:'SN: ${currentValue}' } + valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) { + state "default", label:'Do not remove' } - valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) { - state "default", label:'SN: ${currentValue}' + valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) { + state "default", label:'ID: ${currentValue}' } - valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) { - state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false + valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 6, inactiveLabel: false) { + state "default", label:'IP: ${currentValue}' } main (["rich-control"]) - details(["rich-control", "networkAddress"]) + details(["rich-control", "idNumber", "networkAddress", "doNotRemove"]) } } diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index d533006..ff867d9 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -52,7 +52,7 @@ metadata { } standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { - state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" + state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single" } standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { @@ -84,118 +84,86 @@ def parse(description) { // handle commands void on() { log.trace parent.on(this) - sendEvent(name: "switch", value: "on") } void off() { log.trace parent.off(this) - sendEvent(name: "switch", value: "off") -} - -void nextLevel() { - def level = device.latestValue("level") as Integer ?: 0 - if (level <= 100) { - level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer - } - else { - level = 25 - } - setLevel(level) } void setLevel(percent) { log.debug "Executing 'setLevel'" if (verifyPercent(percent)) { - parent.setLevel(this, percent) - sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%") - sendEvent(name: "switch", value: "on") + log.trace parent.setLevel(this, percent) } } void setSaturation(percent) { log.debug "Executing 'setSaturation'" if (verifyPercent(percent)) { - parent.setSaturation(this, percent) - sendEvent(name: "saturation", value: percent, displayed: false) + log.trace parent.setSaturation(this, percent) } } void setHue(percent) { log.debug "Executing 'setHue'" if (verifyPercent(percent)) { - parent.setHue(this, percent) - sendEvent(name: "hue", value: percent, displayed: false) + log.trace parent.setHue(this, percent) } } void setColor(value) { - log.debug "setColor: ${value}, $this" def events = [] def validValues = [:] if (verifyPercent(value.hue)) { - events << createEvent(name: "hue", value: value.hue, displayed: false) validValues.hue = value.hue } if (verifyPercent(value.saturation)) { - events << createEvent(name: "saturation", value: value.saturation, displayed: false) validValues.saturation = value.saturation } if (value.hex != null) { if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) { - events << createEvent(name: "color", value: value.hex) validValues.hex = value.hex } else { log.warn "$value.hex is not a valid color" } } if (verifyPercent(value.level)) { - events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%") validValues.level = value.level } if (value.switch == "off" || (value.level != null && value.level <= 0)) { - events << createEvent(name: "switch", value: "off") validValues.switch = "off" } else { - events << createEvent(name: "switch", value: "on") validValues.switch = "on" } - if (!events.isEmpty()) { - parent.setColor(this, validValues) - } - events.each { - sendEvent(it) + if (!validValues.isEmpty()) { + log.trace parent.setColor(this, validValues) } } void reset() { log.debug "Executing 'reset'" - def value = [level:100, saturation:56, hue:23] - setAdjustedColor(value) - parent.poll() + setColorTemperature(4000) } void setAdjustedColor(value) { if (value) { log.trace "setAdjustedColor: ${value}" def adjusted = value + [:] - adjusted.hue = adjustOutgoingHue(value.hue) // Needed because color picker always sends 100 adjusted.level = null - setColor(adjusted) + setColor(adjusted) } else { - log.warn "Invalid color input" + log.warn "Invalid color input $value" } } void setColorTemperature(value) { if (value) { log.trace "setColorTemperature: ${value}k" - parent.setColorTemperature(this, value) - sendEvent(name: "colorTemperature", value: value) - sendEvent(name: "switch", value: "on") + log.trace parent.setColorTemperature(this, value) } else { - log.warn "Invalid color temperature" + log.warn "Invalid color temperature $value" } } @@ -204,23 +172,6 @@ void refresh() { parent.manualRefresh() } -def adjustOutgoingHue(percent) { - def adjusted = percent - if (percent > 31) { - if (percent < 63.0) { - adjusted = percent + (7 * (percent -30 ) / 32) - } - else if (percent < 73.0) { - adjusted = 69 + (5 * (percent - 62) / 10) - } - else { - adjusted = percent + (2 * (100 - percent) / 28) - } - } - log.info "percent: $percent, adjusted: $adjusted" - adjusted -} - def verifyPercent(percent) { if (percent == null) return false diff --git a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy index 2944ce3..728b265 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -68,20 +68,16 @@ def parse(description) { // handle commands void on() { log.trace parent.on(this) - sendEvent(name: "switch", value: "on") } void off() { log.trace parent.off(this) - sendEvent(name: "switch", value: "off") } void setLevel(percent) { log.debug "Executing 'setLevel'" if (percent != null && percent >= 0 && percent <= 100) { parent.setLevel(this, percent) - sendEvent(name: "level", value: percent) - sendEvent(name: "switch", value: "on") } else { log.warn "$percent is not 0-100" } diff --git a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy index c9bedb7..8805a28 100644 --- a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy +++ b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy @@ -36,7 +36,7 @@ metadata { } } - controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { + controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2200..6500)") { state "colorTemperature", action:"color temperature.setColorTemperature" } @@ -73,20 +73,16 @@ def parse(description) { // handle commands void on() { log.trace parent.on(this) - sendEvent(name: "switch", value: "on") } void off() { log.trace parent.off(this) - sendEvent(name: "switch", value: "off") } void setLevel(percent) { log.debug "Executing 'setLevel'" if (percent != null && percent >= 0 && percent <= 100) { - parent.setLevel(this, percent) - sendEvent(name: "level", value: percent) - sendEvent(name: "switch", value: "on") + log.trace parent.setLevel(this, percent) } else { log.warn "$percent is not 0-100" } @@ -95,9 +91,7 @@ void setLevel(percent) { void setColorTemperature(value) { if (value) { log.trace "setColorTemperature: ${value}k" - parent.setColorTemperature(this, value) - sendEvent(name: "colorTemperature", value: value) - sendEvent(name: "switch", value: "on") + log.trace parent.setColorTemperature(this, value) } else { log.warn "Invalid color temperature" } @@ -107,4 +101,3 @@ void refresh() { log.debug "Executing 'refresh'" parent.manualRefresh() } - diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 3a3b42e..415dd44 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -53,7 +53,6 @@ def bridgeDiscovery(params=[:]) def options = bridges ?: [] def numFound = options.size() ?: 0 - if (numFound == 0) { if (state.bridgeRefreshCount == 25) { log.trace "Cleaning old bridges memory" @@ -149,9 +148,10 @@ def bulbDiscovery() { def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) } def title = selectedBridge?.value?.name ?: "Find bridges" - return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { - section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { - input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions + + return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { + section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { + input "selectedBulbs", "enum", required:false, title:"Select Hue Lights (${numFound} found)", multiple:true, options:bulboptions } section { href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true] @@ -274,6 +274,7 @@ def initialize() { state.inBulbDiscovery = false state.bridgeRefreshCount = 0 state.bulbRefreshCount = 0 + state.updating = false if (selectedHue) { addBridge() addBulbs() @@ -283,10 +284,9 @@ def initialize() { } def manualRefresh() { - unschedule() - unsubscribe() - doDeviceSync() - runEvery5Minutes("doDeviceSync") + checkBridgeStatus() + state.updating = false + poll() } def uninstalled(){ @@ -360,7 +360,6 @@ def addBulbs() { def newHueBulb if (bulbs instanceof java.util.Map) { newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } - if (newHueBulb != null) { d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub) if (d) { @@ -413,11 +412,14 @@ def addBridge() { } } if (newbridge) { - d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub) + // Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge + def idNumber = getBridgeIdNumber(selectedHue) + d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"]) d?.completedSetup = true log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" def childDevice = getChildDevice(d.deviceNetworkId) - childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber) + updateBridgeStatus(childDevice) + childDevice.sendEvent(name: "idNumber", value: idNumber) if (vbridge.value.ip && vbridge.value.port) { if (vbridge.value.ip.contains(".")) { childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port) @@ -465,6 +467,7 @@ def ssdpBridgeHandler(evt) { childDevices.each { if (it.getDeviceDataByName("mac")) { def newDNI = "${it.getDeviceDataByName("mac")}" + d = it if (newDNI != it.deviceNetworkId) { def oldDNI = it.deviceNetworkId log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" @@ -477,6 +480,7 @@ def ssdpBridgeHandler(evt) { } } } else { + updateBridgeStatus(d) if (d.getDeviceDataByName("networkAddress")) { networkAddress = d.getDeviceDataByName("networkAddress") } else { @@ -498,25 +502,21 @@ def ssdpBridgeHandler(evt) { void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) { log.trace "description.xml response (application/xml)" def body = hubResponse.xml - if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) { + if (body?.device?.modelName?.text()?.startsWith("Philips hue bridge")) { def bridges = getHueBridges() def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())} if (bridge) { - // serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six - // characters are printed on the back so using that to identify bridge - def idNumber = body?.device?.serialNumber?.text() - if (idNumber?.size() >= 6) - idNumber = idNumber[-6..-1].toUpperCase() + def idNumber = getBridgeIdNumber(body?.device?.serialNumber?.text()) // usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2) - // replace IP with serial number to make it easier for user to identify + // replace IP with id number to make it easier for user to identify def name = body?.device?.friendlyName?.text() def index = name?.indexOf('(') if (index != -1) { name = name.substring(0,index) name += " ($idNumber)" } - bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true] + bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), idNumber: idNumber, verified: true] } else { log.error "/description.xml returned a bridge that didn't exist" } @@ -587,6 +587,7 @@ def locationHandler(evt) { childDevices.each { if (it.getDeviceDataByName("mac")) { def newDNI = "${it.getDeviceDataByName("mac")}" + d = it if (newDNI != it.deviceNetworkId) { def oldDNI = it.deviceNetworkId log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" @@ -599,6 +600,7 @@ def locationHandler(evt) { } } } else { + updateBridgeStatus(d) if (d.getDeviceDataByName("networkAddress")) { networkAddress = d.getDeviceDataByName("networkAddress") } else { @@ -661,10 +663,62 @@ def locationHandler(evt) { def doDeviceSync(){ log.trace "Doing Hue Device Sync!" + + // Check if state.updating failed to clear + if (state.lastUpdateStarted < (now() - 20 * 1000) && state.updating) { + state.updating = false + log.warn "state.updating failed to clear" + } + convertBulbListToMap() poll() ssdpSubscribe() discoverBridges() + checkBridgeStatus() +} + +/** + * Called when data is received from the Hue bridge, this will update the lastActivity() that + * is used to keep track of online/offline status of the bridge. Bridge is considered offline + * if not heard from in 16 minutes + * + * @param childDevice Hue Bridge child device + */ +private void updateBridgeStatus(childDevice) { + // Update activity timestamp if child device is a valid bridge + def vbridges = getVerifiedHueBridges() + def vbridge = vbridges.find {"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()} + vbridge?.value?.lastActivity = now() + if(vbridge) { + childDevice?.sendEvent(name: "status", value: "Online") + } +} + +/** + * Check if all Hue bridges have been heard from in the last 16 minutes, if not an Offline event will be sent + * for the bridge. Also, set ID number on bridge if not done previously. + */ +private void checkBridgeStatus() { + def bridges = getHueBridges() + // Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer) + def time = now() - (1000 * 60 * 16) + bridges.each { + def d = getChildDevice(it.value.mac) + if(d) { + // Set id number on bridge if not done + if (it.value.idNumber == null) { + it.value.idNumber = getBridgeIdNumber(it.value.serialNumber) + d.sendEvent(name: "idNumber", value: it.value.idNumber) + } + + if (it.value.lastActivity < time) { // it.value.lastActivity != null && + log.warn "Bridge $it.key is Offline" + d.sendEvent(name: "status", value: "Offline") + } else { + d.sendEvent(name: "status", value: "Online")//setOnline(false) + } + } + } } def isValidSource(macAddress) { @@ -677,6 +731,9 @@ def isValidSource(macAddress) { ///////////////////////////////////// def parse(childDevice, description) { + // Update activity timestamp if child device is a valid bridge + updateBridgeStatus(childDevice) + def parsedEvent = parseLanMessage(description) if (parsedEvent.headers && parsedEvent.body) { def headerString = parsedEvent.headers.toString() @@ -690,91 +747,187 @@ def parse(childDevice, description) { poll() } if (body instanceof java.util.Map) { - //poll response - def bulbs = getChildDevices() - for (bulb in body) { - def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} - if (d) { - if (bulb.value.state?.reachable) { - sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"]) - sendEvent(d.deviceNetworkId, [name: "level", value: Math.round(bulb.value.state.bri * 100 / 255)]) - if (bulb.value.state.sat) { - def hue = Math.min(Math.round(bulb.value.state.hue * 100 / 65535), 65535) as int - def sat = Math.round(bulb.value.state.sat * 100 / 255) as int - def hex = colorUtil.hslToHex(hue, sat) - sendEvent(d.deviceNetworkId, [name: "color", value: hex]) - sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) - sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) - } - } else { - sendEvent(d.deviceNetworkId, [name: "switch", value: "off"]) - sendEvent(d.deviceNetworkId, [name: "level", value: 100]) - if (bulb.value.state.sat) { - def hue = 23 - def sat = 56 - def hex = colorUtil.hslToHex(23, 56) - sendEvent(d.deviceNetworkId, [name: "color", value: hex]) - sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) - sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) - } - } - } - } + // get (poll) reponse + return handlePoll(body) } - else - { //put response - def hsl = [:] - body.each { payload -> - log.debug $payload - if (payload?.success) - { - def childDeviceNetworkId = app.id + "/" - def eventType - body?.success[0].each { k,v -> - childDeviceNetworkId += k.split("/")[2] - if (!hsl[childDeviceNetworkId]) hsl[childDeviceNetworkId] = [:] - eventType = k.split("/")[4] - log.debug "eventType: $eventType" - switch(eventType) { - case "on": - sendEvent(childDeviceNetworkId, [name: "switch", value: (v == true) ? "on" : "off"]) - break - case "bri": - sendEvent(childDeviceNetworkId, [name: "level", value: Math.round(v * 100 / 255)]) - break - case "sat": - hsl[childDeviceNetworkId].saturation = Math.round(v * 100 / 255) as int - break - case "hue": - hsl[childDeviceNetworkId].hue = Math.min(Math.round(v * 100 / 65535), 65535) as int - break - } - } - - } - else if (payload.error) - { - log.debug "JSON error - ${body?.error}" - } - - } - - hsl.each { childDeviceNetworkId, hueSat -> - if (hueSat.hue && hueSat.saturation) { - def hex = colorUtil.hslToHex(hueSat.hue, hueSat.saturation) - log.debug "sending ${hueSat} for ${childDeviceNetworkId} as ${hex}" - sendEvent(hsl.childDeviceNetworkId, [name: "color", value: hex]) - } - } - + else { + //put response + return handleCommandResponse(body) } - } - } else { + } + } else { log.debug "parse - got something other than headers,body..." return [] } } +// Philips Hue priority for color is xy > ct > hs +private sendColorEvents(device, xy, hue, sat, ct, colormode = null) { + if (device == null || (xy == null && hue == null && sat == null && ct == null)) + return + + // For now, only care about changing color temperature if requested by user + if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) { + // for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below + // 153 (6500K) to 500 (2000K) + def temp = (ct == 154) ? 6500 : Math.round(1000000 / ct) + device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"]) + // Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary + return + } + + if (hue != null) { + // 0-65535 + def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int + device.sendEvent([name: "hue", value: value, descriptionText: "Color has changed"]) + } + + if (sat != null) { + // 0-254 + def value = Math.round(sat * 100 / 254) as int + device.sendEvent([name: "saturation", value: value, descriptionText: "Color has changed"]) + } + + // Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex + if (xy != null && colormode != "hs") { + // If xy is present and color mode is not specified to hs, pick xy because of priority + + // [0.0-1.0, 0.0-1.0] + def id = device.deviceNetworkId?.split("/")[1] + def model = state.bulbs[id]?.modelid + def hex = colorFromXY(xy, model) + + // TODO Disabled until a solution for the jumping color picker can be figured out + //device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: false]) + } else if (colormode == "hs" || colormode == null) { + // colormode is "hs" or "xy" is missing, default to follow hue/sat which is already handled above + + // TODO Disabled until the standard behavior of lights is defined (hue and sat events are sent above) + //def hex = colorUtil.hslToHex((int) device.currentHue, (int) device.currentSaturation) + // device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed"]) + } + + return debug +} + +private sendBasicEvents(device, param, value) { + if (device == null || value == null || param == null) + return + + switch (param) { + case "on": + device.sendEvent(name: "switch", value: (value == true) ? "on" : "off") + break + case "bri": + // 1-254 + def level = Math.max(1, Math.round(value * 100 / 254)) as int + device.sendEvent(name: "level", value: level, descriptionText: "Level has changed to ${level}%") + break + } +} + +/** + * Handles a response to a command (PUT) sent to the Hue Bridge. + * + * Will send appropriate events depending on values changed. + * + * Example payload + * [, + * {"success":{"/lights/5/state/bri":87}}, + * {"success":{"/lights/5/state/transitiontime":4}}, + * {"success":{"/lights/5/state/on":true}}, + * {"success":{"/lights/5/state/xy":[0.4152,0.5336]}}, + * {"success":{"/lights/5/state/alert":"none"}} + * ] + * + * @param body a data structure of lists and maps based on a JSON data + * @return empty array + */ +private handleCommandResponse(body) { + // scan entire response before sending events to make sure they are always in the same order + def updates = [:] + + body.each { payload -> + log.debug $payload + + if (payload?.success) { + def childDeviceNetworkId = app.id + "/" + def eventType + payload.success.each { k, v -> + def data = k.split("/") + if (data.length == 5) { + childDeviceNetworkId = app.id + "/" + k.split("/")[2] + if (!updates[childDeviceNetworkId]) + updates[childDeviceNetworkId] = [:] + eventType = k.split("/")[4] + updates[childDeviceNetworkId]."$eventType" = v + } + } + } else if (payload.error) { + log.warn "Error returned from Hue bridge error = ${body?.error}" + } + } + + // send events for each update found above (order of events should be same as handlePoll()) + updates.each { childDeviceNetworkId, params -> + def device = getChildDevice(childDeviceNetworkId) + sendBasicEvents(device, "on", params.on) + sendBasicEvents(device, "bri", params.bri) + sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) + } + return [] + } + +/** + * Handles a response to a poll (GET) sent to the Hue Bridge. + * + * Will send appropriate events depending on values changed. + * + * Example payload + * + * {"5":{"state": {"on":true,"bri":102,"hue":25600,"sat":254,"effect":"none","xy":[0.1700,0.7000],"ct":153,"alert":"none", + * "colormode":"xy","reachable":true}, "type": "Extended color light", "name": "Go", "modelid": "LLC020", "manufacturername": "Philips", + * "uniqueid":"00:17:88:01:01:13:d5:11-0b", "swversion": "5.38.1.14378"}, + * "6":{"state": {"on":true,"bri":103,"hue":14910,"sat":144,"effect":"none","xy":[0.4596,0.4105],"ct":370,"alert":"none", + * "colormode":"ct","reachable":true}, "type": "Extended color light", "name": "Upstairs Light", "modelid": "LCT007", "manufacturername": "Philips", + * "uniqueid":"00:17:88:01:10:56:ba:2c-0b", "swversion": "5.38.1.14919"}, + * + * @param body a data structure of lists and maps based on a JSON data + * @return empty array + */ +private handlePoll(body) { + if (state.updating) { + // If user just executed commands, then ignore poll to not confuse the turning on/off state + return [] + } + + def bulbs = getChildDevices() + for (bulb in body) { + def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} + if (device) { + if (bulb.value.state?.reachable) { + sendBasicEvents(device, "on", bulb.value?.state?.on) + sendBasicEvents(device, "bri", bulb.value?.state?.bri) + sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) + } else { + log.warn "$device is not reachable by Hue bridge" + } + } + } + return [] + } + +private updateInProgress() { + state.updating = true + state.lastUpdateStarted = now() + runIn(20, updateHandler) +} + +def updateHandler() { + state.updating = false + poll() +} + def hubVerification(bodytext) { log.trace "Bridge sent back description.xml for verification" def body = new XmlSlurper().parseText(bodytext) @@ -791,58 +944,111 @@ def hubVerification(bodytext) { def on(childDevice) { log.debug "Executing 'on'" + updateInProgress() + createSwitchEvent(childDevice, "on") put("lights/${getId(childDevice)}/state", [on: true]) - return "Bulb is On" + return "Bulb is turning On" } def off(childDevice) { log.debug "Executing 'off'" + updateInProgress() + createSwitchEvent(childDevice, "off") put("lights/${getId(childDevice)}/state", [on: false]) - return "Bulb is Off" + return "Bulb is turning Off" } def setLevel(childDevice, percent) { log.debug "Executing 'setLevel'" + updateInProgress() + // 1 - 254 def level - if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255) - put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0]) + if (percent == 1) + level = 1 + else + level = Math.min(Math.round(percent * 254 / 100), 254) + + createSwitchEvent(childDevice, level > 0 ,percent) + + // For Zigbee lights, if level is set to 0 ST just turns them off without changing level + // that means that the light will still be on when on is called next time + // Lets emulate that here + if (percent > 0) { + put("lights/${getId(childDevice)}/state", [bri: level, on: true]) + } else { + put("lights/${getId(childDevice)}/state", [on: false]) + } + return "Setting level to $percent" } def setSaturation(childDevice, percent) { log.debug "Executing 'setSaturation($percent)'" - def level = Math.min(Math.round(percent * 255 / 100), 255) - put("lights/${getId(childDevice)}/state", [sat: level]) + updateInProgress() + // 0 - 254 + def level = Math.min(Math.round(percent * 254 / 100), 254) + // TODO should this be done by app only or should we default to on? + createSwitchEvent(childDevice, "on") + put("lights/${getId(childDevice)}/state", [sat: level, on: true]) + return "Setting saturation to $percent" } def setHue(childDevice, percent) { log.debug "Executing 'setHue($percent)'" + updateInProgress() + // 0 - 65535 def level = Math.min(Math.round(percent * 65535 / 100), 65535) - put("lights/${getId(childDevice)}/state", [hue: level]) + // TODO should this be done by app only or should we default to on? + createSwitchEvent(childDevice, "on") + put("lights/${getId(childDevice)}/state", [hue: level, on: true]) + return "Setting hue to $percent" } def setColorTemperature(childDevice, huesettings) { log.debug "Executing 'setColorTemperature($huesettings)'" - def ct = Math.round(Math.abs((huesettings / 12.96829971181556) - 654)) - def value = [ct: ct, on: true] - log.trace "sending command $value" - put("lights/${getId(childDevice)}/state", value) + updateInProgress() + // 153 (6500K) to 500 (2000K) + def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings) + createSwitchEvent(childDevice, "on") + put("lights/${getId(childDevice)}/state", [ct: ct, on: true]) + return "Setting color temperature to $percent" } def setColor(childDevice, huesettings) { log.debug "Executing 'setColor($huesettings)'" + updateInProgress() + def value = [:] def hue = null def sat = null def xy = null - + + // For now ignore model to get a consistent color if same color is set across multiple devices + // def model = state.bulbs[getId(childDevice)]?.modelid if (huesettings.hex != null) { - value.xy = getHextoXY(huesettings.hex) - } else { + // value.xy = calculateXY(huesettings.hex, model) + // Once groups, or scenes are introduced it might be a good idea to use unique models again + value.xy = calculateXY(huesettings.hex) + } + + // If both hex and hue/sat are set, send all values to bridge to get hue/sat in response from bridge to + // generate hue/sat events even though bridge will prioritize XY when setting color if (huesettings.hue != null) value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535) - if (huesettings.saturation != null) - value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255) + else + value.hue = Math.min(Math.round(childDevice.device?.currentValue("hue") * 65535 / 100), 65535) + + if (huesettings.saturation != null) + value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254) + else + value.sat = Math.min(Math.round(childDevice.device?.currentValue("saturation") * 254 / 100), 254) + + if (!value.xy) { + // Below will translate values to hex->XY to take into account the color support of the different hue types + def hex = colorUtil.hslToHex((int) huesettings.hue, (int) huesettings.saturation) + // value.xy = calculateXY(hex, model) + // Once groups, or scenes are introduced it might be a good idea to use unique models again + value.xy = calculateXY(hex) } // Default behavior is to turn light on @@ -853,8 +1059,8 @@ def setColor(childDevice, huesettings) { value.on = false else if (huesettings.level == 1) value.bri = 1 - else - value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255) + else + value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254) } value.alert = huesettings.alert ? huesettings.alert : "none" value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4 @@ -863,19 +1069,9 @@ def setColor(childDevice, huesettings) { if (huesettings.switch == "off") value.on = false - log.debug "sending command $value" + createSwitchEvent(childDevice, value.on ? "on" : "off") put("lights/${getId(childDevice)}/state", value) - return "Color set to $value" -} - -def nextLevel(childDevice) { - def level = device.latestValue("level") as Integer ?: 0 - if (level < 100) { - level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer - } else { - level = 25 - } - setLevel(childDevice,level) + return "Setting color to $value" } private getId(childDevice) { @@ -915,6 +1111,17 @@ ${bodyJSON} } +/* + * Bridge serial number from Hue API is in format of 0017882413ad (mac address), however on the actual bridge hardware + * the id is printed as only last six characters so using that to identify bridge to users + */ +private getBridgeIdNumber(serialNumber) { + def idNumber = serialNumber ?: "" + if (idNumber?.size() >= 6) + idNumber = idNumber[-6..-1].toUpperCase() + return idNumber +} + private getBridgeIP() { def host = null if (selectedHue) { @@ -944,57 +1151,6 @@ private getBridgeIP() { return host } -private getHextoXY(String colorStr) { - // For the hue bulb the corners of the triangle are: - // -Red: 0.675, 0.322 - // -Green: 0.4091, 0.518 - // -Blue: 0.167, 0.04 - - def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) - def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) - def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) - - double[] normalizedToOne = new double[3]; - normalizedToOne[0] = (cred / 255); - normalizedToOne[1] = (cgreen / 255); - normalizedToOne[2] = (cblue / 255); - float red, green, blue; - - // Make red more vivid - if (normalizedToOne[0] > 0.04045) { - red = (float) Math.pow( - (normalizedToOne[0] + 0.055) / (1.0 + 0.055), 2.4); - } else { - red = (float) (normalizedToOne[0] / 12.92); - } - - // Make green more vivid - if (normalizedToOne[1] > 0.04045) { - green = (float) Math.pow((normalizedToOne[1] + 0.055) / (1.0 + 0.055), 2.4); - } else { - green = (float) (normalizedToOne[1] / 12.92); - } - - // Make blue more vivid - if (normalizedToOne[2] > 0.04045) { - blue = (float) Math.pow((normalizedToOne[2] + 0.055) / (1.0 + 0.055), 2.4); - } else { - blue = (float) (normalizedToOne[2] / 12.92); - } - - float X = (float) (red * 0.649926 + green * 0.103455 + blue * 0.197109); - float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598); - float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763); - - float x = (X != 0 ? X / (X + Y + Z) : 0); - float y = (Y != 0 ? Y / (X + Y + Z) : 0); - - double[] xy = new double[2]; - xy[0] = x; - xy[1] = y; - return xy; -} - private Integer convertHexToInt(hex) { Integer.parseInt(hex,16) } @@ -1025,3 +1181,388 @@ private Boolean hasAllHubsOver(String desiredFirmware) { private List getRealHubFirmwareVersions() { return location.hubs*.firmwareVersionString.findAll { it } } + +/** + * Sends appropriate turningOn/turningOff state events depending on switch or level changes. + * + * @param childDevice device to send event for + * @param setSwitch The new switch state, "on" or "off" + * @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since + * that should generate "off" instead of level change + */ +private void createSwitchEvent(childDevice, setSwitch, setLevel = null) { + + if (setLevel == null) { + setLevel = childDevice.device?.currentValue("level") + } + // Create on, off, turningOn or turningOff event as necessary + def currentState = childDevice.device?.currentValue("switch") + if ((currentState == "off" || currentState == "turningOff")) { + if (setSwitch == "on" || setLevel > 0) { + childDevice.sendEvent(name: "switch", value: "turningOn", displayed: false) + } + } else if ((currentState == "on" || currentState == "turningOn")) { + if (setSwitch == "off" || setLevel == 0) { + childDevice.sendEvent(name: "switch", value: "turningOff", displayed: false) + } + } +} + +/** + * Return the supported color range for different Hue lights. If model is not specified + * it defaults to the smallest Gamut (B) to ensure that colors set on a mix of devices + * will be consistent. + * + * @param model Philips model number, e.g. LCT001 + * @return a map with x,y coordinates for red, green and blue according to CIE color space + */ +private colorPointsForModel(model = null) { + def result = null + switch (model) { + // Gamut A + case "LLC001": /* Monet, Renoir, Mondriaan (gen II) */ + case "LLC005": /* Bloom (gen II) */ + case "LLC006": /* Iris (gen III) */ + case "LLC007": /* Bloom, Aura (gen III) */ + case "LLC011": /* Hue Bloom */ + case "LLC012": /* Hue Bloom */ + case "LLC013": /* Storylight */ + case "LST001": /* Light Strips */ + case "LLC010": /* Hue Living Colors Iris + */ + result = [r:[x: 0.704f, y: 0.296f], g:[x: 0.2151f, y: 0.7106f], b:[x: 0.138f, y: 0.08f]]; + break + // Gamut C + case "LLC020": /* Hue Go */ + case "LST002": /* Hue LightStrips Plus */ + result = [r:[x: 0.692f, y: 0.308f], g:[x: 0.17f, y: 0.7f], b:[x: 0.153f, y: 0.048f]]; + break + // Gamut B + case "LCT001": /* Hue A19 */ + case "LCT002": /* Hue BR30 */ + case "LCT003": /* Hue GU10 */ + case "LCT007": /* Hue A19 + */ + case "LLM001": /* Color Light Module + */ + default: + result = [r:[x: 0.675f, y: 0.322f], g:[x: 0.4091f, y: 0.518f], b:[x: 0.167f, y: 0.04f]]; + + } + return result; +} + +/** + * Return x, y value from android color and light model Id. + * Please note: This method does not incorporate brightness values. Get/set it from/to your light seperately. + * + * From Philips Hue SDK modified for Groovy + * + * @param color the color value in hex like // #ffa013 + * @param model the model Id of Light (or null to get default Gamut B) + * @return the float array of length 2, where index 0 and 1 gives x and y values respectively. + */ +private float[] calculateXY(colorStr, model = null) { + + // #ffa013 + def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) + def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) + def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) + + float red = cred / 255.0f; + float green = cgreen / 255.0f; + float blue = cblue / 255.0f; + + // Wide gamut conversion D65 + float r = ((red > 0.04045f) ? (float) Math.pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f)); + float g = (green > 0.04045f) ? (float) Math.pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); + float b = (blue > 0.04045f) ? (float) Math.pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); + + // Why values are different in ios and android , IOS is considered + // Modified conversion from RGB -> XYZ with better results on colors for + // the lights + float x = r * 0.664511f + g * 0.154324f + b * 0.162028f; + float y = r * 0.283881f + g * 0.668433f + b * 0.047685f; + float z = r * 0.000088f + g * 0.072310f + b * 0.986039f; + + float[] xy = new float[2]; + + xy[0] = (x / (x + y + z)); + xy[1] = (y / (x + y + z)); + if (Float.isNaN(xy[0])) { + xy[0] = 0.0f; + } + if (Float.isNaN(xy[1])) { + xy[1] = 0.0f; + } + /*if(true) + return [0.0f,0.0f]*/ + + // Check if the given XY value is within the colourreach of our lamps. + def xyPoint = [x: xy[0], y: xy[1]]; + def colorPoints = colorPointsForModel(model); + boolean inReachOfLamps = checkPointInLampsReach(xyPoint, colorPoints); + if (!inReachOfLamps) { + // It seems the colour is out of reach + // let's find the closes colour we can produce with our lamp and + // send this XY value out. + + // Find the closest point on each line in the triangle. + def pAB = getClosestPointToPoints(colorPoints.r, colorPoints.g, xyPoint); + def pAC = getClosestPointToPoints(colorPoints.b, colorPoints.r, xyPoint); + def pBC = getClosestPointToPoints(colorPoints.g, colorPoints.b, xyPoint); + + // Get the distances per point and see which point is closer to our + // Point. + float dAB = getDistanceBetweenTwoPoints(xyPoint, pAB); + float dAC = getDistanceBetweenTwoPoints(xyPoint, pAC); + float dBC = getDistanceBetweenTwoPoints(xyPoint, pBC); + + float lowest = dAB; + def closestPoint = pAB; + if (dAC < lowest) { + lowest = dAC; + closestPoint = pAC; + } + if (dBC < lowest) { + lowest = dBC; + closestPoint = pBC; + } + + // Change the xy value to a value which is within the reach of the + // lamp. + xy[0] = closestPoint.x; + xy[1] = closestPoint.y; + } + // xy[0] = PHHueHelper.precision(4, xy[0]); + // xy[1] = PHHueHelper.precision(4, xy[1]); + + + // TODO needed, assume it just sets number of decimals? + //xy[0] = PHHueHelper.precision(xy[0]); + //xy[1] = PHHueHelper.precision(xy[1]); + return xy; +} + +/** + * Generates the color for the given XY values and light model. Model can be null if it is not known. + * Note: When the exact values cannot be represented, it will return the closest match. + * Note 2: This method does not incorporate brightness values. Get/Set it from/to your light seperately. + * + * From Philips Hue SDK modified for Groovy + * + * @param points the float array contain x and the y value. [x,y] + * @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut. + * If this value is empty the default gamut values are used. + * @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned + */ +private String colorFromXY(points, model ) { + + if (points == null || model == null) { + log.warn "Input color missing" + return "#000000" + } + + def xy = [x: points[0], y: points[1]]; + + def colorPoints = colorPointsForModel(model); + boolean inReachOfLamps = checkPointInLampsReach(xy, colorPoints); + + if (!inReachOfLamps) { + // It seems the colour is out of reach + // let's find the closest colour we can produce with our lamp and + // send this XY value out. + // Find the closest point on each line in the triangle. + def pAB = getClosestPointToPoints(colorPoints.r, colorPoints.g, xy); + def pAC = getClosestPointToPoints(colorPoints.b, colorPoints.r, xy); + def pBC = getClosestPointToPoints(colorPoints.g, colorPoints.b, xy); + + // Get the distances per point and see which point is closer to our + // Point. + float dAB = getDistanceBetweenTwoPoints(xy, pAB); + float dAC = getDistanceBetweenTwoPoints(xy, pAC); + float dBC = getDistanceBetweenTwoPoints(xy, pBC); + float lowest = dAB; + def closestPoint = pAB; + if (dAC < lowest) { + lowest = dAC; + closestPoint = pAC; + } + if (dBC < lowest) { + lowest = dBC; + closestPoint = pBC; + } + // Change the xy value to a value which is within the reach of the + // lamp. + xy.x = closestPoint.x; + xy.y = closestPoint.y; + } + float x = xy.x; + float y = xy.y; + float z = 1.0f - x - y; + float y2 = 1.0f; + float x2 = (y2 / y) * x; + float z2 = (y2 / y) * z; + /* + * // Wide gamut conversion float r = X * 1.612f - Y * 0.203f - Z * + * 0.302f; float g = -X * 0.509f + Y * 1.412f + Z * 0.066f; float b = X + * * 0.026f - Y * 0.072f + Z * 0.962f; + */ + // sRGB conversion + // float r = X * 3.2410f - Y * 1.5374f - Z * 0.4986f; + // float g = -X * 0.9692f + Y * 1.8760f + Z * 0.0416f; + // float b = X * 0.0556f - Y * 0.2040f + Z * 1.0570f; + + // sRGB D65 conversion + float r = x2 * 1.656492f - y2 * 0.354851f - z2 * 0.255038f; + float g = -x2 * 0.707196f + y2 * 1.655397f + z2 * 0.036152f; + float b = x2 * 0.051713f - y2 * 0.121364f + z2 * 1.011530f; + + if (r > b && r > g && r > 1.0f) { + // red is too big + g = g / r; + b = b / r; + r = 1.0f; + } else if (g > b && g > r && g > 1.0f) { + // green is too big + r = r / g; + b = b / g; + g = 1.0f; + } else if (b > r && b > g && b > 1.0f) { + // blue is too big + r = r / b; + g = g / b; + b = 1.0f; + } + // Apply gamma correction + r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * (float) Math.pow(r, (1.0f / 2.4f)) - 0.055f; + g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * (float) Math.pow(g, (1.0f / 2.4f)) - 0.055f; + b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * (float) Math.pow(b, (1.0f / 2.4f)) - 0.055f; + + if (r > b && r > g) { + // red is biggest + if (r > 1.0f) { + g = g / r; + b = b / r; + r = 1.0f; + } + } else if (g > b && g > r) { + // green is biggest + if (g > 1.0f) { + r = r / g; + b = b / g; + g = 1.0f; + } + } else if (b > r && b > g && b > 1.0f) { + r = r / b; + g = g / b; + b = 1.0f; + } + + // neglecting if the value is negative. + if (r < 0.0f) { + r = 0.0f; + } + if (g < 0.0f) { + g = 0.0f; + } + if (b < 0.0f) { + b = 0.0f; + } + + // Converting float components to int components. + def r1 = String.format("%02X", (int) (r * 255.0f)); + def g1 = String.format("%02X", (int) (g * 255.0f)); + def b1 = String.format("%02X", (int) (b * 255.0f)); + + return "#$r1$g1$b1" +} + + +/** + * Calculates crossProduct of two 2D vectors / points. + * + * From Philips Hue SDK modified for Groovy + * + * @param p1 first point used as vector [x: 0.0f, y: 0.0f] + * @param p2 second point used as vector [x: 0.0f, y: 0.0f] + * @return crossProduct of vectors + */ +private float crossProduct(p1, p2) { + return (p1.x * p2.y - p1.y * p2.x); +} + +/** + * Find the closest point on a line. + * This point will be within reach of the lamp. + * + * From Philips Hue SDK modified for Groovy + * + * @param A the point where the line starts [x:..,y:..] + * @param B the point where the line ends [x:..,y:..] + * @param P the point which is close to a line. [x:..,y:..] + * @return the point which is on the line. [x:..,y:..] + */ +private getClosestPointToPoints(A, B, P) { + def AP = [x: (P.x - A.x), y: (P.y - A.y)]; + def AB = [x: (B.x - A.x), y: (B.y - A.y)]; + + float ab2 = AB.x*AB.x + AB.y*AB.y; + float ap_ab = AP.x*AB.x + AP.y*AB.y; + + float t = ap_ab / ab2; + + if (t < 0.0f) + t = 0.0f; + else if (t > 1.0f) + t = 1.0f; + + def newPoint = [x: (A.x + AB.x * t), y: (A.y + AB.y * t)]; + return newPoint; +} + +/** + * Find the distance between two points. + * + * From Philips Hue SDK modified for Groovy + * + * @param one [x:..,y:..] + * @param two [x:..,y:..] + * @return the distance between point one and two + */ +private float getDistanceBetweenTwoPoints(one, two) { + float dx = one.x - two.x; // horizontal difference + float dy = one.y - two.y; // vertical difference + float dist = Math.sqrt(dx * dx + dy * dy); + + return dist; +} + +/** + * Method to see if the given XY value is within the reach of the lamps. + * + * From Philips Hue SDK modified for Groovy + * + * @param p the point containing the X,Y value [x:..,y:..] + * @return true if within reach, false otherwise. + */ +private boolean checkPointInLampsReach(p, colorPoints) { + + def red = colorPoints.r; + def green = colorPoints.g; + def blue = colorPoints.b; + + def v1 = [x: (green.x - red.x), y: (green.y - red.y)]; + def v2 = [x: (blue.x - red.x), y: (blue.y - red.y)]; + + def q = [x: (p.x - red.x), y: (p.y - red.y)]; + + float s = crossProduct(q, v2) / crossProduct(v1, v2); + float t = crossProduct(v1, q) / crossProduct(v1, v2); + + if ( (s >= 0.0f) && (t >= 0.0f) && (s + t <= 1.0f)) + { + return true; + } + else + { + return false; + } +} From fa9ebed9987893ad9c6f134609d610008cd81451 Mon Sep 17 00:00:00 2001 From: jackchi Date: Mon, 22 Aug 2016 13:58:11 -0700 Subject: [PATCH 5/8] Revert PR-1424 --- .../smartpower-outlet.groovy | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index 4a71834..11febf1 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -101,12 +101,6 @@ 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) - // Temporary fix for the case when Device is OFFLINE and is connected again - if (state.lastOnOff == null){ - state.lastOnOff = now() - sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true) - } - state.lastOnOff = now() } } else { @@ -122,24 +116,9 @@ def off() { def on() { zigbee.on() } -/** - * PING is used by Device-Watch in attempt to reach the Outlet - * */ -def ping() { - - // send read attribute onOFF if the last time we heard from the outlet is outside of the checkInterval - if (state.lastOnOff < (now() - (1000 * device.currentValue("checkInterval"))) ){ - log.info "ping, alive=no, lastOnOff=${new Date(state.lastOnOff)}" - state.lastOnOff = null - return zigbee.onOffRefresh() - } else { // if the last onOff activity is within the checkInterval we artificially create a Device-Watch event - log.info "ping, alive=yes, lastOnOff=${new Date(state.lastOnOff)}" - sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true) - } -} def refresh() { - zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B") } def configure() { From 6009bc52ab168195c45ed789600339cf50088cb5 Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Tue, 23 Aug 2016 02:59:06 -0700 Subject: [PATCH 6/8] SSVD-2532 setting up the generic name at install --- .../zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy | 13 ++++++++----- .../zigbee-white-color-temperature-bulb.groovy | 3 +++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy index de4f4d8..0f4c419 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -81,12 +81,15 @@ private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 } def parse(String description) { log.debug "description is $description" - def finalResult = zigbee.getEvent(description) - if (finalResult) { - log.debug finalResult - if (finalResult.name=="level" && finalResult.value==0) {} + def event = zigbee.getEvent(description) + if (event) { + log.debug event + if (event.name=="level" && event.value==0) {} else { - sendEvent(finalResult) + if (event.name=="colorTemperature") { + setGenericName(event.value) + } + sendEvent(event) } } else { 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 539d8fd..e2cac3a 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 @@ -74,6 +74,9 @@ def parse(String description) { if (event) { if (event.name=="level" && event.value==0) {} else { + if (event.name=="colorTemperature") { + setGenericName(event.value) + } sendEvent(event) } } From 79b90d741f2fe2bf8967b142f008d4dcd7e2ea32 Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Tue, 23 Aug 2016 08:45:47 -0500 Subject: [PATCH 7/8] Revert "Update DTHs to use ZigBee library ZoneStatus" This reverts commit 7bfa0304afccb6b39e967eb7be80219b19f448f2. --- .../nyce-motion-sensor.groovy | 50 ++++++++++++++++--- .../nyce-open-closed-sensor.groovy | 33 +++++++----- .../smartsense-moisture-sensor.groovy | 39 +++++++++++++-- .../smartsense-motion-sensor.groovy | 42 ++++++++++++++-- .../smartsense-motion-temp-sensor.groovy | 41 +++++++++++++-- .../smartsense-multi-sensor.groovy | 43 ++++++++++++++-- ...se-open-closed-accelerometer-sensor.groovy | 36 +++++++++++-- .../smartsense-open-closed-sensor.groovy | 37 ++++++++++++-- .../tyco-door-window-sensor.groovy | 36 +++++++++++-- 9 files changed, 311 insertions(+), 46 deletions(-) diff --git a/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy b/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy index d372c38..c89bc03 100644 --- a/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy +++ b/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy @@ -13,7 +13,6 @@ * for the specific language governing permissions and limitations under the License. * */ -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") { @@ -144,14 +143,51 @@ private Map parseReportAttributeMessage(String description) { private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) - Map resultMap = [:] + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] + + Map resultMap = [:] + switch(msgCode) { + case '0x0030': // Closed/No Motion/Dry + log.debug 'no motion' + resultMap.name = 'motion' + resultMap.value = 'inactive' + break - result.name = 'motion' - result.value = zs.isAlarm2Set() ? 'active' : 'inactive' - log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion') + case '0x0032': // Open/Motion/Wet + log.debug 'motion' + resultMap.name = 'motion' + resultMap.value = 'active' + break - return resultMap + case '0x0032': // Tamper Alarm + log.debug 'motion with tamper alarm' + resultMap.name = 'motion' + resultMap.value = 'active' + break + + case '0x0033': // Battery Alarm + break + + case '0x0034': // Supervision Report + log.debug 'no motion with tamper alarm' + resultMap.name = 'motion' + resultMap.value = 'inactive' + break + + case '0x0035': // Restore Report + break + + case '0x0036': // Trouble/Failure + log.debug 'motion with failure alarm' + resultMap.name = 'motion' + resultMap.value = 'active' + break + + case '0x0038': // Test Mode + break + } + return resultMap } def refresh() diff --git a/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy b/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy index 506ca89..eef9273 100644 --- a/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy +++ b/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy @@ -13,10 +13,7 @@ * for the specific language governing permissions and limitations under the License. * */ - -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus - - + metadata { definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") { capability "Battery" @@ -222,33 +219,40 @@ private Map parseReportAttributeMessage(String description) { } private List parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) - log.debug "parseIasMessage: $description" + List parsedMsg = description.split(" ") + String msgCode = parsedMsg[2] List resultListMap = [] Map resultMap_battery = [:] Map resultMap_battery_state = [:] Map resultMap_sensor = [:] - resultMap_sensor.name = "contact" - resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed" + // Relevant bit field definitions from ZigBee spec + def BATTERY_BIT = ( 1 << 3 ) + def TROUBLE_BIT = ( 1 << 6 ) + def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec + + // Convert hex string to integer + def zoneStatus = Integer.parseInt(msgCode[-4..-1],16) + + log.debug "parseIasMessage: zoneStatus: ${zoneStatus}" // Check each relevant bit, create map for it, and add to list - log.debug "parseIasMessage: Battery Status ${zs.battery}" - log.debug "parseIasMessage: Trouble Status ${zs.trouble}" - log.debug "parseIasMessage: Sensor Status ${zs.alarm1}" + log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}" + log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}" + log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}" /* Comment out this path to check the battery state to avoid overwriting the battery value (Change log #2), but keep these conditions for later use resultMap_battery_state.name = "battery_state" - if (zs.isTroubleSet()) { + if (zoneStatus & TROUBLE_BIT) { resultMap_battery_state.value = "failed" resultMap_battery.name = "battery" resultMap_battery.value = 0 } else { - if (zs.isBatterySet()) { + if (zoneStatus & BATTERY_BIT) { resultMap_battery_state.value = "low" // to generate low battery notification by the platform @@ -266,6 +270,9 @@ private List parseIasMessage(String description) { } */ + resultMap_sensor.name = "contact" + resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed" + resultListMap << resultMap_battery_state resultListMap << resultMap_battery resultListMap << resultMap_sensor 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 26ff46f..c1be409 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -13,8 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus - metadata { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -172,9 +170,42 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] - return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry') + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getMoistureResult('dry') + break + + case '0x0021': // Open/Motion/Wet + resultMap = getMoistureResult('wet') + break + + case '0x0022': // Tamper Alarm + break + + case '0x0023': // Battery Alarm + break + + case '0x0024': // Supervision Report + log.debug 'dry with tamper alarm' + resultMap = getMoistureResult('dry') + break + + case '0x0025': // Restore Report + log.debug 'water with tamper alarm' + resultMap = getMoistureResult('wet') + break + + case '0x0026': // Trouble/Failure + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { 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 1ee8143..342bd2e 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -13,8 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus - metadata { definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -185,10 +183,44 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] - // Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion - return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive') + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getMotionResult('inactive') + break + + case '0x0021': // Open/Motion/Wet + resultMap = getMotionResult('active') + break + + case '0x0022': // Tamper Alarm + log.debug 'motion with tamper alarm' + resultMap = getMotionResult('active') + break + + case '0x0023': // Battery Alarm + break + + case '0x0024': // Supervision Report + log.debug 'no motion with tamper alarm' + resultMap = getMotionResult('inactive') + break + + case '0x0025': // Restore Report + break + + case '0x0026': // Trouble/Failure + log.debug 'motion with failure alarm' + resultMap = getMotionResult('active') + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { 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 f5ebc5f..446ec70 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 @@ -15,7 +15,6 @@ */ //DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") { @@ -169,8 +168,44 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) - return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive') + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] + + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getMotionResult('inactive') + break + + case '0x0021': // Open/Motion/Wet + resultMap = getMotionResult('active') + break + + case '0x0022': // Tamper Alarm + log.debug 'motion with tamper alarm' + resultMap = getMotionResult('active') + break + + case '0x0023': // Battery Alarm + break + + case '0x0024': // Supervision Report + log.debug 'no motion with tamper alarm' + resultMap = getMotionResult('inactive') + break + + case '0x0025': // Restore Report + break + + case '0x0026': // Trouble/Failure + log.debug 'motion with failure alarm' + resultMap = getMotionResult('active') + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { 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 9bb6d2b..02861cc 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -13,7 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -225,13 +224,47 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + if (garageSensor != "Yes"){ + resultMap = getContactResult('closed') + } + break - if(garageSensor != "Yes") { - resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') + case '0x0021': // Open/Motion/Wet + if (garageSensor != "Yes"){ + resultMap = getContactResult('open') + } + break + + case '0x0022': // Tamper Alarm + break + + case '0x0023': // Battery Alarm + break + + case '0x0024': // Supervision Report + if (garageSensor != "Yes"){ + resultMap = getContactResult('closed') + } + break + + case '0x0025': // Restore Report + if (garageSensor != "Yes"){ + resultMap = getContactResult('open') + } + break + + case '0x0026': // Trouble/Failure + break + + case '0x0028': // Test Mode + break } - return resultMap } diff --git a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy index ac82584..7668ff2 100644 --- a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy @@ -14,7 +14,6 @@ * */ //DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -173,9 +172,40 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] - return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getContactResult('closed') + break + + case '0x0021': // Open/Motion/Wet + resultMap = getContactResult('open') + break + + case '0x0022': // Tamper Alarm + break + + case '0x0023': // Battery Alarm + break + + case '0x0024': // Supervision Report + resultMap = getContactResult('closed') + break + + case '0x0025': // Restore Report + resultMap = getContactResult('open') + break + + case '0x0026': // Trouble/Failure + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { 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 8133463..7ff7cb8 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 @@ -13,7 +13,6 @@ * for the specific language governing permissions and limitations under the License. * */ -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -168,8 +167,40 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) - return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] + + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getContactResult('closed') + break + + case '0x0021': // Open/Motion/Wet + resultMap = getContactResult('open') + break + + case '0x0022': // Tamper Alarm + break + + case '0x0023': // Battery Alarm + break + + case '0x0024': // Supervision Report + resultMap = getContactResult('closed') + break + + case '0x0025': // Restore Report + resultMap = getContactResult('open') + break + + case '0x0026': // Trouble/Failure + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { diff --git a/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy b/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy index 4121630..627ab1d 100644 --- a/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy +++ b/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy @@ -13,7 +13,6 @@ * for the specific language governing permissions and limitations under the License. * */ -import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") { @@ -162,9 +161,40 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - ZoneStatus zs = zigbee.parseZoneStatus(description) + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] - return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getContactResult('closed') + break + + case '0x0021': // Open/Motion/Wet + resultMap = getContactResult('open') + break + + case '0x0022': // Tamper Alarm + break + + case '0x0023': // Battery Alarm + break + + case '0x0024': // Supervision Report + resultMap = getContactResult('closed') + break + + case '0x0025': // Restore Report + resultMap = getContactResult('open') + break + + case '0x0026': // Trouble/Failure + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { From feb6ba0e24fd0b84ddc345b968445d630878948e Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Tue, 23 Aug 2016 14:04:47 -0700 Subject: [PATCH 8/8] Revert " Revert DPROT-2: "Update DTHs to use ZigBee library ZoneStatus"" --- .../nyce-motion-sensor.groovy | 50 +++---------------- .../nyce-open-closed-sensor.groovy | 33 +++++------- .../smartsense-moisture-sensor.groovy | 39 ++------------- .../smartsense-motion-sensor.groovy | 42 ++-------------- .../smartsense-motion-temp-sensor.groovy | 41 ++------------- .../smartsense-multi-sensor.groovy | 43 ++-------------- ...se-open-closed-accelerometer-sensor.groovy | 36 ++----------- .../smartsense-open-closed-sensor.groovy | 37 ++------------ .../tyco-door-window-sensor.groovy | 36 ++----------- 9 files changed, 46 insertions(+), 311 deletions(-) diff --git a/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy b/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy index c89bc03..d372c38 100644 --- a/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy +++ b/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * */ +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") { @@ -143,51 +144,14 @@ private Map parseReportAttributeMessage(String description) { private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] - - Map resultMap = [:] - switch(msgCode) { - case '0x0030': // Closed/No Motion/Dry - log.debug 'no motion' - resultMap.name = 'motion' - resultMap.value = 'inactive' - break + ZoneStatus zs = zigbee.parseZoneStatus(description) + Map resultMap = [:] - case '0x0032': // Open/Motion/Wet - log.debug 'motion' - resultMap.name = 'motion' - resultMap.value = 'active' - break + result.name = 'motion' + result.value = zs.isAlarm2Set() ? 'active' : 'inactive' + log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion') - case '0x0032': // Tamper Alarm - log.debug 'motion with tamper alarm' - resultMap.name = 'motion' - resultMap.value = 'active' - break - - case '0x0033': // Battery Alarm - break - - case '0x0034': // Supervision Report - log.debug 'no motion with tamper alarm' - resultMap.name = 'motion' - resultMap.value = 'inactive' - break - - case '0x0035': // Restore Report - break - - case '0x0036': // Trouble/Failure - log.debug 'motion with failure alarm' - resultMap.name = 'motion' - resultMap.value = 'active' - break - - case '0x0038': // Test Mode - break - } - return resultMap + return resultMap } def refresh() diff --git a/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy b/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy index eef9273..506ca89 100644 --- a/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy +++ b/devicetypes/smartthings/nyce-open-closed-sensor.src/nyce-open-closed-sensor.groovy @@ -13,7 +13,10 @@ * for the specific language governing permissions and limitations under the License. * */ - + +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus + + metadata { definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") { capability "Battery" @@ -219,40 +222,33 @@ private Map parseReportAttributeMessage(String description) { } private List parseIasMessage(String description) { - List parsedMsg = description.split(" ") - String msgCode = parsedMsg[2] + ZoneStatus zs = zigbee.parseZoneStatus(description) + log.debug "parseIasMessage: $description" List resultListMap = [] Map resultMap_battery = [:] Map resultMap_battery_state = [:] Map resultMap_sensor = [:] - // Relevant bit field definitions from ZigBee spec - def BATTERY_BIT = ( 1 << 3 ) - def TROUBLE_BIT = ( 1 << 6 ) - def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec - - // Convert hex string to integer - def zoneStatus = Integer.parseInt(msgCode[-4..-1],16) - - log.debug "parseIasMessage: zoneStatus: ${zoneStatus}" + resultMap_sensor.name = "contact" + resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed" // Check each relevant bit, create map for it, and add to list - log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}" - log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}" - log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}" + log.debug "parseIasMessage: Battery Status ${zs.battery}" + log.debug "parseIasMessage: Trouble Status ${zs.trouble}" + log.debug "parseIasMessage: Sensor Status ${zs.alarm1}" /* Comment out this path to check the battery state to avoid overwriting the battery value (Change log #2), but keep these conditions for later use resultMap_battery_state.name = "battery_state" - if (zoneStatus & TROUBLE_BIT) { + if (zs.isTroubleSet()) { resultMap_battery_state.value = "failed" resultMap_battery.name = "battery" resultMap_battery.value = 0 } else { - if (zoneStatus & BATTERY_BIT) { + if (zs.isBatterySet()) { resultMap_battery_state.value = "low" // to generate low battery notification by the platform @@ -270,9 +266,6 @@ private List parseIasMessage(String description) { } */ - resultMap_sensor.name = "contact" - resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed" - resultListMap << resultMap_battery_state resultListMap << resultMap_battery resultListMap << resultMap_sensor 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 c1be409..26ff46f 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -13,6 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus + metadata { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -170,42 +172,9 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] + ZoneStatus zs = zigbee.parseZoneStatus(description) - Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - resultMap = getMoistureResult('dry') - break - - case '0x0021': // Open/Motion/Wet - resultMap = getMoistureResult('wet') - break - - case '0x0022': // Tamper Alarm - break - - case '0x0023': // Battery Alarm - break - - case '0x0024': // Supervision Report - log.debug 'dry with tamper alarm' - resultMap = getMoistureResult('dry') - break - - case '0x0025': // Restore Report - log.debug 'water with tamper alarm' - resultMap = getMoistureResult('wet') - break - - case '0x0026': // Trouble/Failure - break - - case '0x0028': // Test Mode - break - } - return resultMap + return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry') } def getTemperature(value) { 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 342bd2e..1ee8143 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -13,6 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus + metadata { definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -183,44 +185,10 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] + ZoneStatus zs = zigbee.parseZoneStatus(description) - Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - resultMap = getMotionResult('inactive') - break - - case '0x0021': // Open/Motion/Wet - resultMap = getMotionResult('active') - break - - case '0x0022': // Tamper Alarm - log.debug 'motion with tamper alarm' - resultMap = getMotionResult('active') - break - - case '0x0023': // Battery Alarm - break - - case '0x0024': // Supervision Report - log.debug 'no motion with tamper alarm' - resultMap = getMotionResult('inactive') - break - - case '0x0025': // Restore Report - break - - case '0x0026': // Trouble/Failure - log.debug 'motion with failure alarm' - resultMap = getMotionResult('active') - break - - case '0x0028': // Test Mode - break - } - return resultMap + // Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion + return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive') } def getTemperature(value) { 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 446ec70..f5ebc5f 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 @@ -15,6 +15,7 @@ */ //DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") { @@ -168,44 +169,8 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] - - Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - resultMap = getMotionResult('inactive') - break - - case '0x0021': // Open/Motion/Wet - resultMap = getMotionResult('active') - break - - case '0x0022': // Tamper Alarm - log.debug 'motion with tamper alarm' - resultMap = getMotionResult('active') - break - - case '0x0023': // Battery Alarm - break - - case '0x0024': // Supervision Report - log.debug 'no motion with tamper alarm' - resultMap = getMotionResult('inactive') - break - - case '0x0025': // Restore Report - break - - case '0x0026': // Trouble/Failure - log.debug 'motion with failure alarm' - resultMap = getMotionResult('active') - break - - case '0x0028': // Test Mode - break - } - return resultMap + ZoneStatus zs = zigbee.parseZoneStatus(description) + return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive') } def getTemperature(value) { 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 02861cc..9bb6d2b 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -224,47 +225,13 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] - + ZoneStatus zs = zigbee.parseZoneStatus(description) Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - if (garageSensor != "Yes"){ - resultMap = getContactResult('closed') - } - break - case '0x0021': // Open/Motion/Wet - if (garageSensor != "Yes"){ - resultMap = getContactResult('open') - } - break - - case '0x0022': // Tamper Alarm - break - - case '0x0023': // Battery Alarm - break - - case '0x0024': // Supervision Report - if (garageSensor != "Yes"){ - resultMap = getContactResult('closed') - } - break - - case '0x0025': // Restore Report - if (garageSensor != "Yes"){ - resultMap = getContactResult('open') - } - break - - case '0x0026': // Trouble/Failure - break - - case '0x0028': // Test Mode - break + if(garageSensor != "Yes") { + resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') } + return resultMap } diff --git a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy index 7668ff2..ac82584 100644 --- a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy @@ -14,6 +14,7 @@ * */ //DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -172,40 +173,9 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] + ZoneStatus zs = zigbee.parseZoneStatus(description) - Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - resultMap = getContactResult('closed') - break - - case '0x0021': // Open/Motion/Wet - resultMap = getContactResult('open') - break - - case '0x0022': // Tamper Alarm - break - - case '0x0023': // Battery Alarm - break - - case '0x0024': // Supervision Report - resultMap = getContactResult('closed') - break - - case '0x0025': // Restore Report - resultMap = getContactResult('open') - break - - case '0x0026': // Trouble/Failure - break - - case '0x0028': // Test Mode - break - } - return resultMap + return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') } def getTemperature(value) { 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 7ff7cb8..8133463 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 @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * */ +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { @@ -167,40 +168,8 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] - - Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - resultMap = getContactResult('closed') - break - - case '0x0021': // Open/Motion/Wet - resultMap = getContactResult('open') - break - - case '0x0022': // Tamper Alarm - break - - case '0x0023': // Battery Alarm - break - - case '0x0024': // Supervision Report - resultMap = getContactResult('closed') - break - - case '0x0025': // Restore Report - resultMap = getContactResult('open') - break - - case '0x0026': // Trouble/Failure - break - - case '0x0028': // Test Mode - break - } - return resultMap + ZoneStatus zs = zigbee.parseZoneStatus(description) + return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') } def getTemperature(value) { diff --git a/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy b/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy index 627ab1d..4121630 100644 --- a/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy +++ b/devicetypes/smartthings/tyco-door-window-sensor.src/tyco-door-window-sensor.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * */ +import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") { @@ -161,40 +162,9 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] + ZoneStatus zs = zigbee.parseZoneStatus(description) - Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - resultMap = getContactResult('closed') - break - - case '0x0021': // Open/Motion/Wet - resultMap = getContactResult('open') - break - - case '0x0022': // Tamper Alarm - break - - case '0x0023': // Battery Alarm - break - - case '0x0024': // Supervision Report - resultMap = getContactResult('closed') - break - - case '0x0025': // Restore Report - resultMap = getContactResult('open') - break - - case '0x0026': // Trouble/Failure - break - - case '0x0028': // Test Mode - break - } - return resultMap + return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') } def getTemperature(value) {