From b4a4d83ce7fc0eab628b783863269faf4557adb9 Mon Sep 17 00:00:00 2001 From: Jim Mangione Date: Mon, 27 Jun 2016 20:31:56 -0500 Subject: [PATCH 01/11] MSA-1375: These apps use different sensors to detect if a draw or container has been opened or moved. If no activity is detected without 60 minutes of a set reminder time, then an in-app reminder is generated. If no activity after 10 additional minutes, an LED light is turned on and set to RED until activity is recorded. Additionally, the Temp-Motion app sends an in-app notification if the temperature of the container exceeds a particular threshold. --- .../medicine-management-contact-sensor.groovy | 177 ++++++++++++++++ .../medicine-management-temp-motion.groovy | 189 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy create mode 100644 smartapps/mangioneimagery/medicine-management-temp-motion.src/medicine-management-temp-motion.groovy diff --git a/smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy b/smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy new file mode 100644 index 0000000..e8bb54c --- /dev/null +++ b/smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy @@ -0,0 +1,177 @@ +/** + * Medicine Management - Contact Sensor + * + * Copyright 2016 Jim Mangione + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + * + * Logic: + * --- Send notification at the medicine reminder time IF draw wasn't alread opened in past 60 minutes + * --- If draw still isn't open 10 minutes AFTER reminder time, LED will turn RED. + * --- ----- Once draw IS open, LED will return back to it's original color + * + */ +import groovy.time.TimeCategory + +definition( + name: "Medicine Management - Contact Sensor", + namespace: "MangioneImagery", + author: "Jim Mangione", + description: "This supports devices with capabilities of ContactSensor and ColorControl (LED). It sends an in-app and ambient light notification if you forget to open the drawer or cabinet where meds are stored. A reminder will be set to a single time per day. If the draw or cabinet isn't opened within 60 minutes of that reminder, an in-app message will be sent. If the draw or cabinet still isn't opened after an additional 10 minutes, then an LED light turns red until the draw or cabinet is opened", + category: "Health & Wellness", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", + iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") + + +preferences { + + section("My Medicine Draw/Cabinet"){ + input "deviceContactSensor", "capability.contactSensor", title: "Opened Sensor" + } + + section("Remind me to take my medicine at"){ + input "reminderTime", "time", title: "Time" + } + + // NOTE: Use REAL device - virtual device causes compilation errors + section("My LED Light"){ + input "deviceLight", "capability.colorControl", title: "Smart light" + } + +} + +def installed() { + log.debug "Installed with settings: ${settings}" + + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + + unsubscribe() + + initialize() +} + +def initialize() { + + // will stop LED notification incase it was set by med reminder + subscribe(deviceContactSensor, "contact", contactHandler) + + // how many minutes to look in the past from the reminder time, for an open draw + state.minutesToCheckOpenDraw = 60 + + // Set a timer to run once a day to notify if draw wasn't opened yet + schedule(reminderTime, checkOpenDrawInPast) + +} + +// Should turn off any LED notification on OPEN state +def contactHandler(evt){ + if (evt.value == "open") { + // always call out to stop any possible LED notification + log.debug "Cabinet opened. Sending reset to LED" + resetLEDNotification() + } +} + +// If the draw was NOT opened within 60 minutes of the timer send notification out. +def checkOpenDrawInPast(){ + log.debug "Checking past 60 minutes of activity from $reminderTime" + + // check activity of sensor for past 60 minutes for any OPENED status + def cabinetOpened = isOpened(state.minutesToCheckOpenDraw) + log.debug "Cabinet found opened: $cabinetOpened" + + // if it's opened, then do nothing and assume they took their meds + if (!cabinetOpened) { + sendNotification("Hi, please remember to take your meds in the cabinet") + + // if no open activity, send out notification and set new reminder + def reminderTimePlus10 = new Date(now() + (10 * 60000)) + + // needs to be scheduled if draw wasn't already opened + runOnce(reminderTimePlus10, checkOpenDrawAfterReminder) + } +} + +// If the draw was NOT opened after 10 minutes past reminder, use LED notification +def checkOpenDrawAfterReminder(){ + log.debug "Checking additional 10 minutes of activity from $reminderTime" + + // check activity of sensor for past 10 minutes for any OPENED status + def cabinetOpened = isOpened(10) + + log.debug "Cabinet found opened: $cabinetOpened" + + // if no open activity, blink lights + if (!cabinetOpened) { + log.debug "Set LED to Notification color" + setLEDNotification() + } + +} + +// Helper function for sending out an app notification +def sendNotification(msg){ + log.debug "Message Sent: $msg" + sendPush(msg) +} + +// Check if the sensor has been opened since the minutes entered +// Return true if opened found, else false. +def isOpened(minutes){ + // query last X minutes of activity log + def previousDateTime = new Date(now() - (minutes * 60000)) + + // capture all events recorded + def evts = deviceContactSensor.eventsSince(previousDateTime) + def cabinetOpened = false + if (evts.size() > 0) { + evts.each{ + if(it.value == "open") { + cabinetOpened = true + } + } + } + + return cabinetOpened +} + +// Saves current color and sets the light to RED +def setLEDNotification(){ + + // turn light back off when reset is called if it was originally off + state.ledState = deviceLight.currentValue("switch") + + // set light to RED and store original color until stopped + state.origColor = deviceLight.currentValue("hue") + deviceLight.on() + deviceLight.setHue(100) + + log.debug "LED set to RED. Original color stored: $state.origColor" + +} + +// Sets the color back to the original saved color +def resetLEDNotification(){ + + // return color to original + log.debug "Reset LED color to: $state.origColor" + deviceLight.setHue(state.origColor) + + // if the light was turned on just for the notification, turn it back off now + if (state.ledState == "off") { + deviceLight.off() + } + +} diff --git a/smartapps/mangioneimagery/medicine-management-temp-motion.src/medicine-management-temp-motion.groovy b/smartapps/mangioneimagery/medicine-management-temp-motion.src/medicine-management-temp-motion.groovy new file mode 100644 index 0000000..a047306 --- /dev/null +++ b/smartapps/mangioneimagery/medicine-management-temp-motion.src/medicine-management-temp-motion.groovy @@ -0,0 +1,189 @@ +/** + * Medicine Management - Temp-Motion + * + * Copyright 2016 Jim Mangione + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + * + * Logic: + * --- If temp > threshold set, send notification + * --- Send in-app notification at the medicine reminder time if no motion is detected in past 60 minutes + * --- If motion still isn't detected 10 minutes AFTER reminder time, LED will turn RED + * --- ----- Once motion is detected, LED will turn back to it's original color + */ +import groovy.time.TimeCategory + +definition( + name: "Medicine Management - Temp-Motion", + namespace: "MangioneImagery", + author: "Jim Mangione", + description: "This only supports devices with capabilities TemperatureMeasurement, AccelerationSensor and ColorControl (LED). Supports two use cases. First, will notifies via in-app if the fridge where meds are stored exceeds a temperature threshold set in degrees. Secondly, sends an in-app and ambient light notification if you forget to take your meds by sensing movement of the medicine box in the fridge. A reminder will be set to a single time per day. If the box isn't moved within 60 minutes of that reminder, an in-app message will be sent. If the box still isn't moved after an additional 10 minutes, then an LED light turns red until the box is moved", + category: "Health & Wellness", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", + iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") + + +preferences { + + section("My Medicine in the Refrigerator"){ + input "deviceAccelerationSensor", "capability.accelerationSensor", required: true, multiple: false, title: "Movement" + input "deviceTemperatureMeasurement", "capability.temperatureMeasurement", required: true, multiple: false, title: "Temperature" + } + + section("Temperature Threshold"){ + input "tempThreshold", "number", title: "Temperature Threshold" + } + + section("Remind me to take my medicine at"){ + input "reminderTime", "time", title: "Time" + } + + // NOTE: Use REAL device - virtual device causes compilation errors + section("My LED Light"){ + input "deviceLight", "capability.colorControl", title: "Smart light" + } + +} + +def installed() { + log.debug "Installed with settings: ${settings}" + + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + + unsubscribe() + + initialize() +} + +def initialize() { + // will notify when temp exceeds max + subscribe(deviceTemperatureMeasurement, "temperature", tempHandler) + + // will stop LED notification incase it was set by med reminder + subscribe(deviceAccelerationSensor, "acceleration.active", motionHandler) + + // how many minutes to look in the past from the reminder time + state.minutesToCheckPriorToReminder = 60 + + // Set a timer to run once a day to notify if draw wasn't opened yet + schedule(reminderTime, checkMotionInPast) +} + + +// If temp > 39 then send an app notification out. +def tempHandler(evt){ + if (evt.doubleValue > tempThreshold) { + log.debug "Fridge temp of $evt.value exceeded threshold" + sendNotification("WARNING: Fridge temp is $evt.value with threshold of $tempThreshold") + } +} + +// Should turn off any LED notification once motion detected +def motionHandler(evt){ + // always call out to stop any possible LED notification + log.debug "Medication moved. Send stop LED notification" + resetLEDNotification() +} + +// If no motion detected within 60 minutes of the timer send notification out. +def checkMotionInPast(){ + log.debug "Checking past 60 minutes of activity from $reminderTime" + + // check activity of sensor for past 60 minutes for any OPENED status + def movement = isMoved(state.minutesToCheckPriorToReminder) + log.debug "Motion found: $movement" + + // if there was movement, then do nothing and assume they took their meds + if (!movement) { + sendNotification("Hi, please remember to take your meds in the fridge") + + // if no movement, send out notification and set new reminder + def reminderTimePlus10 = new Date(now() + (10 * 60000)) + + // needs to be scheduled if draw wasn't already opened + runOnce(reminderTimePlus10, checkMotionAfterReminder) + } +} + +// If still no movement after 10 minutes past reminder, use LED notification +def checkMotionAfterReminder(){ + log.debug "Checking additional 10 minutes of activity from $reminderTime" + + // check activity of sensor for past 10 minutes for any OPENED status + def movement = isMoved(10) + + log.debug "Motion found: $movement" + + // if no open activity, blink lights + if (!movement) { + log.debug "Notify LED API" + setLEDNotification() + } + +} + +// Helper function for sending out an app notification +def sendNotification(msg){ + log.debug "Message Sent: $msg" + sendPush(msg) +} + +// Check if the accelerometer has been activated since the minutes entered +// Return true if active, else false. +def isMoved(minutes){ + // query last X minutes of activity log + def previousDateTime = new Date(now() - (minutes * 60000)) + + // capture all events recorded + def evts = deviceAccelerationSensor.eventsSince(previousDateTime) + def motion = false + if (evts.size() > 0) { + evts.each{ + if(it.value == "active") { + motion = true + } + } + } + + return motion +} + +// Saves current color and sets the light to RED +def setLEDNotification(){ + + // turn light back off when reset is called if it was originally off + state.ledState = deviceLight.currentValue("switch") + + // set light to RED and store original color until stopped + state.origColor = deviceLight.currentValue("hue") + deviceLight.on() + deviceLight.setHue(100) + + log.debug "LED set to RED. Original color stored: $state.origColor" + +} + +// Sets the color back to the original saved color +def resetLEDNotification(){ + + // return color to original + log.debug "Reset LED color to: $state.origColor" + deviceLight.setHue(state.origColor) + + // if the light was turned on just for the notification, turn it back off now + if (state.ledState == "off") { + deviceLight.off() + } +} \ No newline at end of file From 53d0957383ed0a798205442b997110183b787e8a Mon Sep 17 00:00:00 2001 From: Jim Mangione Date: Wed, 29 Jun 2016 20:03:27 -0500 Subject: [PATCH 02/11] Modifying 'Reminders using notifications and light' From 1ddd0632c98f9e1f6f1188aa66087a6f1f68e9f2 Mon Sep 17 00:00:00 2001 From: Jim Mangione Date: Wed, 29 Jun 2016 20:44:59 -0500 Subject: [PATCH 03/11] Modifying 'Reminders using notifications and light' --- .../medicine-management-contact-sensor.groovy | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy b/smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy index e8bb54c..3c4c9d1 100644 --- a/smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy +++ b/smartapps/mangioneimagery/medicine-management-contact-sensor.src/medicine-management-contact-sensor.groovy @@ -70,6 +70,9 @@ def initialize() { // how many minutes to look in the past from the reminder time, for an open draw state.minutesToCheckOpenDraw = 60 + // is true when LED notification is set after exceeding 10 minutes past reminder time + state.ledNotificationTriggered = false + // Set a timer to run once a day to notify if draw wasn't opened yet schedule(reminderTime, checkOpenDrawInPast) @@ -78,9 +81,11 @@ def initialize() { // Should turn off any LED notification on OPEN state def contactHandler(evt){ if (evt.value == "open") { - // always call out to stop any possible LED notification - log.debug "Cabinet opened. Sending reset to LED" - resetLEDNotification() + // if LED notification triggered, reset it. + log.debug "Cabinet opened" + if (state.ledNotificationTriggered) { + resetLEDNotification() + } } } @@ -150,6 +155,8 @@ def isOpened(minutes){ // Saves current color and sets the light to RED def setLEDNotification(){ + state.ledNotificationTriggered = true + // turn light back off when reset is called if it was originally off state.ledState = deviceLight.currentValue("switch") @@ -165,9 +172,13 @@ def setLEDNotification(){ // Sets the color back to the original saved color def resetLEDNotification(){ + state.ledNotificationTriggered = false + // return color to original log.debug "Reset LED color to: $state.origColor" - deviceLight.setHue(state.origColor) + if (state.origColor != null) { + deviceLight.setHue(state.origColor) + } // if the light was turned on just for the notification, turn it back off now if (state.ledState == "off") { From 8dc36eb8f6e7a037b372c9c0e7a7f12b157a0133 Mon Sep 17 00:00:00 2001 From: jackchi Date: Thu, 18 Aug 2016 14:51:43 -0700 Subject: [PATCH 04/11] [CHF-201] [CHF-206] Health Check Core Devices --- .../aeon-multisensor-6.groovy | 4 --- .../aeon-multisensor-gen5.groovy | 3 -- .../aeon-multisensor.groovy | 4 --- .../cree-bulb.src/cree-bulb.groovy | 23 +++++++++++++++ .../smartpower-outlet.groovy | 25 ++++++++--------- .../smartsense-moisture-sensor.groovy | 24 +++++++++++++++- .../smartsense-motion-sensor.groovy | 24 +++++++++++++++- .../smartsense-multi-sensor.groovy | 28 +++++++++++++++++-- .../smartsense-open-closed-sensor.groovy | 24 +++++++++++++++- .../smartsense-temp-humidity-sensor.groovy | 23 ++++++++++++++- 10 files changed, 151 insertions(+), 31 deletions(-) diff --git a/devicetypes/smartthings/aeon-multisensor-6.src/aeon-multisensor-6.groovy b/devicetypes/smartthings/aeon-multisensor-6.src/aeon-multisensor-6.groovy index 7ff06b6..4b194db 100644 --- a/devicetypes/smartthings/aeon-multisensor-6.src/aeon-multisensor-6.groovy +++ b/devicetypes/smartthings/aeon-multisensor-6.src/aeon-multisensor-6.groovy @@ -22,7 +22,6 @@ metadata { capability "Configuration" capability "Sensor" capability "Battery" - capability "Health Check" attribute "tamper", "enum", ["detected", "clear"] attribute "batteryStatus", "string" @@ -328,9 +327,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) { } def configure() { - // allow device user configured or default 16 min to check in; double the periodic reporting interval - sendEvent(name: "checkInterval", value: 2* (timeOptionValueMap[reportInterval] ?: (2*8*60)), displayed: false) - // This sensor joins as a secure device if you double-click the button to include it log.debug "${device.displayName} is configuring its settings" def request = [] diff --git a/devicetypes/smartthings/aeon-multisensor-gen5.src/aeon-multisensor-gen5.groovy b/devicetypes/smartthings/aeon-multisensor-gen5.src/aeon-multisensor-gen5.groovy index 8cc44a9..04888d3 100644 --- a/devicetypes/smartthings/aeon-multisensor-gen5.src/aeon-multisensor-gen5.groovy +++ b/devicetypes/smartthings/aeon-multisensor-gen5.src/aeon-multisensor-gen5.groovy @@ -20,7 +20,6 @@ metadata { capability "Configuration" capability "Sensor" capability "Battery" - capability "Health Check" command "configureAfterSecure" @@ -248,8 +247,6 @@ def configureAfterSecure() { def configure() { // log.debug "configure()" //["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet()) - // allow device 16 min to check in; double the periodic reporting interval - sendEvent(name: "checkInterval", value: 2*8*60, displayed: false) } private setConfigured() { diff --git a/devicetypes/smartthings/aeon-multisensor.src/aeon-multisensor.groovy b/devicetypes/smartthings/aeon-multisensor.src/aeon-multisensor.groovy index 9a6b164..5d6de58 100644 --- a/devicetypes/smartthings/aeon-multisensor.src/aeon-multisensor.groovy +++ b/devicetypes/smartthings/aeon-multisensor.src/aeon-multisensor.groovy @@ -20,7 +20,6 @@ metadata { capability "Illuminance Measurement" capability "Sensor" capability "Battery" - capability "Health Check" fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86" } @@ -181,9 +180,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) { } def configure() { - // allow device 10 min to check in; double the periodic reporting interval - sendEvent(name: "checkInterval", value: 2*5*60, displayed: false) - delayBetween([ // send binary sensor report instead of basic set for motion zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(), diff --git a/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy b/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy index 70548b0..ed5e751 100644 --- a/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy +++ b/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy @@ -23,6 +23,7 @@ metadata { capability "Refresh" capability "Switch" capability "Switch Level" + capability "Health Check" fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019" } @@ -66,6 +67,12 @@ def parse(String description) { def resultMap = zigbee.getEvent(description) if (resultMap) { sendEvent(resultMap) + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() } else { log.debug "DID NOT PARSE MESSAGE for description : $description" @@ -85,6 +92,21 @@ def setLevel(value) { zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.levelRefresh() + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} + def refresh() { zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() } @@ -95,5 +117,6 @@ def poll() { def configure() { log.debug "Configuring Reporting and Bindings." + sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"]) zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() } diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index 4a71834..b978cc0 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -102,11 +102,11 @@ def parse(String description) { 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) + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) } - state.lastOnOff = now() + state.lastActivity = now() } } else { @@ -123,18 +123,17 @@ def on() { zigbee.on() } /** - * PING is used by Device-Watch in attempt to reach the Outlet + * PING is used by Device-Watch in attempt to reach the Device * */ 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 + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = 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) + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) } } @@ -143,7 +142,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 1200, displayed: false) + sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"]) zigbee.onOffConfig() + powerConfig() + refresh() } 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..746787c 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -101,6 +101,13 @@ def parse(String description) { map = parseIasMessage(description) } + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() + log.debug "Parse returned $map" def result = map ? createEvent(map) : null @@ -271,6 +278,21 @@ private Map getMoistureResult(value) { ] } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} + def refresh() { log.debug "Refreshing Temperature and Battery" def refreshCmds = [ @@ -282,7 +304,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false) + sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." 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..1dab182 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -105,6 +105,13 @@ def parse(String description) { map = parseIasMessage(description) } + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() + log.debug "Parse returned $map" def result = map ? createEvent(map) : null @@ -282,6 +289,21 @@ private Map getMotionResult(value) { ] } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} + def refresh() { log.debug "refresh called" def refreshCmds = [ @@ -293,7 +315,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false) + sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." 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..72973e5 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -127,6 +127,13 @@ def parse(String description) { map = parseIasMessage(description) } + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() + def result = map ? createEvent(map) : null if (description?.startsWith('enroll request')) { @@ -228,9 +235,9 @@ private Map parseIasMessage(String description) { ZoneStatus zs = zigbee.parseZoneStatus(description) Map resultMap = [:] - if(garageSensor != "Yes") { + if (garageSensor != "Yes"){ resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') - } + } return resultMap } @@ -364,6 +371,21 @@ private getAccelerationResult(numValue) { ] } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} + def refresh() { log.debug "Refreshing Values " @@ -391,7 +413,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false) + sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) log.debug "Configuring Reporting" 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..90fb06b 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 @@ -92,6 +92,13 @@ def parse(String description) { map = parseIasMessage(description) } + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() + log.debug "Parse returned $map" def result = map ? createEvent(map) : null @@ -234,6 +241,21 @@ private Map getContactResult(value) { ] } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} + def refresh() { log.debug "Refreshing Temperature and Battery" def refreshCmds = [ @@ -245,7 +267,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false) + sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." diff --git a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy index e06dc78..70bf317 100644 --- a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy +++ b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy @@ -83,6 +83,13 @@ def parse(String description) { map = parseCustomMessage(description) } + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() + log.debug "Parse returned $map" return map ? createEvent(map) : null } @@ -239,6 +246,20 @@ private Map getHumidityResult(value) { ] } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} + def refresh() { log.debug "refresh temperature, humidity, and battery" @@ -254,7 +275,7 @@ def refresh() } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false) + sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) log.debug "Configuring Reporting and Bindings." def configCmds = [ From fadc496e1fa563dec23756c678195544d4d1ae30 Mon Sep 17 00:00:00 2001 From: jackchi Date: Mon, 22 Aug 2016 15:06:57 -0700 Subject: [PATCH 05/11] [CHF-239] Health Check Osram RGB / Dimmer Devices --- .../zigbee-dimmer.src/zigbee-dimmer.groovy | 23 +++++++++++++++++ .../zigbee-rgbw-bulb.groovy | 25 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy index 72809f5..f23c78c 100644 --- a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy +++ b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy @@ -19,6 +19,7 @@ metadata { capability "Refresh" capability "Switch" capability "Switch Level" + capability "Health Check" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008" @@ -54,6 +55,12 @@ def parse(String description) { def event = zigbee.getEvent(description) if (event) { sendEvent(event) + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() } else { log.warn "DID NOT PARSE MESSAGE for description : $description" @@ -72,6 +79,20 @@ def on() { def setLevel(value) { zigbee.setLevel(value) } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.onOffRefresh() + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} def refresh() { zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() @@ -79,5 +100,7 @@ def refresh() { def configure() { log.debug "Configuring Reporting and Bindings." + // Enrolls device to Device-Watch with 3 x Reporting interval 30min + sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() } 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..31fc7b0 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -27,6 +27,7 @@ metadata { capability "Refresh" capability "Switch" capability "Switch Level" + capability "Health Check" 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" @@ -82,6 +83,12 @@ def parse(String description) { if (finalResult) { log.debug finalResult sendEvent(finalResult) + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() } else { def zigbeeMap = zigbee.parseDescriptionAsMap(description) @@ -110,13 +117,29 @@ def on() { def off() { zigbee.off() } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.onOffRefresh() + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} def refresh() { - zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) } def configure() { log.debug "Configuring Reporting and Bindings." + // Enrolls device to Device-Watch with 3 x Reporting interval 30min + sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) } From feb6ba0e24fd0b84ddc345b968445d630878948e Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Tue, 23 Aug 2016 14:04:47 -0700 Subject: [PATCH 06/11] 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) { From 33df9b1ff10f6bda692cc31ae5633e06c32f13f4 Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Wed, 24 Aug 2016 14:04:19 -0500 Subject: [PATCH 07/11] Fix event map name in NYCE Motion sensor There was a null pointer as a result of using the wrong map name This resolves: https://smartthings.atlassian.net/browse/DPROT-153 --- .../nyce-motion-sensor.src/nyce-motion-sensor.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 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..f48e788 100644 --- a/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy +++ b/devicetypes/smartthings/nyce-motion-sensor.src/nyce-motion-sensor.groovy @@ -147,8 +147,8 @@ private Map parseIasMessage(String description) { ZoneStatus zs = zigbee.parseZoneStatus(description) Map resultMap = [:] - result.name = 'motion' - result.value = zs.isAlarm2Set() ? 'active' : 'inactive' + resultMap.name = 'motion' + resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive' log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion') return resultMap From 8eb6001f9f66a2d994e4030866f11cb2c88510ac Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Fri, 26 Aug 2016 13:20:51 -0700 Subject: [PATCH 08/11] CHF-239 extension for osram cct bulbs --- ...zigbee-white-color-temperature-bulb.groovy | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 e2cac3a..2154599 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 @@ -22,6 +22,7 @@ metadata { capability "Actuator" capability "Color Temperature" capability "Configuration" + capability "Health Check" capability "Refresh" capability "Switch" capability "Switch Level" @@ -72,6 +73,12 @@ def parse(String description) { log.debug "description is $description" def event = zigbee.getEvent(description) if (event) { + // Temporary fix for the case when Device is OFFLINE and is connected again + if (state.lastActivity == null){ + state.lastActivity = now() + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } + state.lastActivity = now() if (event.name=="level" && event.value==0) {} else { if (event.name=="colorTemperature") { @@ -98,12 +105,29 @@ def setLevel(value) { zigbee.setLevel(value) } +/** + * PING is used by Device-Watch in attempt to reach the Device + * */ +def ping() { + + if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ + log.info "ping, alive=no, lastActivity=${state.lastActivity}" + state.lastActivity = null + return zigbee.onOffRefresh() + } else { + log.info "ping, alive=yes, lastActivity=${state.lastActivity}" + sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) + } +} + def refresh() { zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() } def configure() { log.debug "Configuring Reporting and Bindings." + // Enrolls device to Device-Watch with 3 x Reporting interval 30min + sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"]) zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() } From 6ad4f0990cc4882fe55fe143f1cb88aa2848747c Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Sat, 27 Aug 2016 01:05:10 -0700 Subject: [PATCH 09/11] CHF-201 changing timeout for health check to 4 hours for battery powered devices --- .../smartsense-moisture-sensor.groovy | 4 ++-- .../smartsense-motion-sensor.groovy | 4 ++-- .../smartsense-multi-sensor.groovy | 2 +- .../smartsense-open-closed-sensor.groovy | 2 +- .../smartsense-temp-humidity-sensor.groovy | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) 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 746787c..7de68d1 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -24,7 +24,7 @@ metadata { capability "Temperature Measurement" capability "Water Sensor" capability "Health Check" - capability "Sensor" + capability "Sensor" command "enrollResponse" @@ -304,7 +304,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) + sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." 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 1dab182..bb9acb7 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -24,7 +24,7 @@ metadata { capability "Temperature Measurement" capability "Refresh" capability "Health Check" - capability "Sensor" + capability "Sensor" command "enrollResponse" @@ -315,7 +315,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) + sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." 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 72973e5..ad3c60d 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -413,7 +413,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) + sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) log.debug "Configuring Reporting" 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 90fb06b..f9b24e8 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 @@ -267,7 +267,7 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) + sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." diff --git a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy index 70bf317..cbdb78b 100644 --- a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy +++ b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy @@ -275,7 +275,7 @@ def refresh() } def configure() { - sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"]) + sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) log.debug "Configuring Reporting and Bindings." def configCmds = [ From cdf5d21e8fa1cc5a8c686e028ff8148b3d0bdda5 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Mon, 29 Aug 2016 15:17:40 -0600 Subject: [PATCH 10/11] SSVD-2603 Prevent removing of Hue bridge -Moved warning messages to top of bridge DTH screen -User is now presented with a warning message when removing --- .../hue-bridge.src/hue-bridge.groovy | 6 +-- .../hue-connect.src/hue-connect.groovy | 46 ++++++++++++------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy index dc0a9f0..0986ae8 100644 --- a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy +++ b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy @@ -28,8 +28,8 @@ metadata { } } valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) { - state "default", label:'Do not remove' - } + state "default", label:'If removed, Hue lights will not work properly' + } valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) { state "default", label:'ID: ${currentValue}' } @@ -38,7 +38,7 @@ metadata { } main (["rich-control"]) - details(["rich-control", "idNumber", "networkAddress", "doNotRemove"]) + details(["rich-control", "doNotRemove", "idNumber", "networkAddress"]) } } diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 415dd44..6a6859c 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -290,8 +290,11 @@ def manualRefresh() { } def uninstalled(){ + // Remove bridgedevice connection to allow uninstall of smartapp even though bridge is listed + // as user of smartapp + app.updateSetting("bridgeDevice", null) state.bridges = [:] - state.username = null + state.username = null } // Handles events to add new bulbs @@ -415,23 +418,32 @@ def addBridge() { // 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) - 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) - childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port) - } else { - childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) - childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) - } + if (d) { + // Associate smartapp to bridge so user will be warned if trying to delete bridge + app.updateSetting("bridgeDevice", [type: "device.hueBridge", value: d.id]) + + d.completedSetup = true + log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" + def childDevice = getChildDevice(d.deviceNetworkId) + 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) + childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port) + } else { + childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) + childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) + } + } else { + childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) + childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) + } } else { - childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) - childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) - } + log.error "Failed to create Hue Bridge device" + } } } else { log.debug "found ${d.displayName} with id $selectedHue already exists" From 9c8398b7a062070f616064d0442b525dc21a7441 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Mon, 29 Aug 2016 15:45:25 -0600 Subject: [PATCH 11/11] SSVD-2613 UX for Hue when adding lights is confusing -Split lights list in SmartApp into already added lights and new lights -Also fixed WWST-39 Sort list of bulbs by name during discovery --- .../hue-connect.src/hue-connect.groovy | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 415dd44..c83f9ed 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -133,14 +133,23 @@ def bulbDiscovery() { state.inBulbDiscovery = true def bridge = null if (selectedHue) { - bridge = getChildDevice(selectedHue) - subscribe(bridge, "bulbList", bulbListData) + bridge = getChildDevice(selectedHue) + subscribe(bridge, "bulbList", bulbListData) } - state.bridgeRefreshCount = 0 - def bulboptions = bulbsDiscovered() ?: [:] - def numFound = bulboptions.size() ?: 0 - if (numFound == 0) - app.updateSetting("selectedBulbs", "") + state.bridgeRefreshCount = 0 + def allLightsFound = bulbsDiscovered() ?: [:] + + // List lights currently not added to the user (editable) + def newLights = allLightsFound.findAll {getChildDevice(it.key) == null} ?: [:] + newLights = newLights.sort {it.value.toLowerCase()} + + // List lights already added to the user (not editable) + def existingLights = allLightsFound.findAll {getChildDevice(it.key) != null} ?: [:] + existingLights = existingLights.sort {it.value.toLowerCase()} + + def numFound = newLights.size() ?: 0 + if (numFound == 0) + app.updateSetting("selectedBulbs", "") if((bulbRefreshCount % 5) == 0) { discoverHueBulbs() @@ -148,14 +157,25 @@ def bulbDiscovery() { def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) } def title = selectedBridge?.value?.name ?: "Find bridges" + // List of all lights previously added shown to user + def existingLightsDescription = "" + if (existingLights) { + existingLights.each { + if (existingLightsDescription.isEmpty()) { + existingLightsDescription += it.value + } else { + existingLightsDescription += ", ${it.value}" + } + } + } 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 + input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, options:newLights + paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription } section { href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true] - } } }