From c278e035f6eed29a22bec4892330199e400d1fc6 Mon Sep 17 00:00:00 2001 From: Dave Hastings Date: Thu, 15 Jun 2017 14:44:14 -0500 Subject: [PATCH 1/6] IOTEVENT-786 check for null/empty values before sending events --- .../fibaro-rgbw-controller.groovy | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/devicetypes/smartthings/fibaro-rgbw-controller.src/fibaro-rgbw-controller.groovy b/devicetypes/smartthings/fibaro-rgbw-controller.src/fibaro-rgbw-controller.groovy index 4385edc..4cec80f 100644 --- a/devicetypes/smartthings/fibaro-rgbw-controller.src/fibaro-rgbw-controller.groovy +++ b/devicetypes/smartthings/fibaro-rgbw-controller.src/fibaro-rgbw-controller.groovy @@ -300,15 +300,21 @@ def setColor(value) { value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}" } - sendEvent(name: "hue", value: value.hue, displayed: false) - sendEvent(name: "saturation", value: value.saturation, displayed: false) - sendEvent(name: "color", value: value.hex, displayed: false) - if (value.level) { - sendEvent(name: "level", value: value.level) - } - if (value.switch) { - sendEvent(name: "switch", value: value.switch) - } + if(value.hue) { + sendEvent(name: "hue", value: value.hue, displayed: false) + } + if(value.saturation) { + sendEvent(name: "saturation", value: value.saturation, displayed: false) + } + if(value.hex?.trim()) { + sendEvent(name: "color", value: value.hex, displayed: false) + } + if (value.level) { + sendEvent(name: "level", value: value.level) + } + if (value.switch?.trim()) { + sendEvent(name: "switch", value: value.switch) + } sendRGB(value.rh, value.gh, value.bh) } From 370b435874a1fa3b76541aa080f3fbb69813fefc Mon Sep 17 00:00:00 2001 From: "piyush.c" Date: Mon, 19 Jun 2017 18:02:29 +0530 Subject: [PATCH 2/6] [DHF-15] Health Check Aeon Minimote --- .../smartthings/aeon-minimote.src/.st-ignore | 2 ++ .../smartthings/aeon-minimote.src/README.md | 33 +++++++++++++++++++ .../aeon-minimote.src/aeon-minimote.groovy | 4 +++ 3 files changed, 39 insertions(+) create mode 100644 devicetypes/smartthings/aeon-minimote.src/.st-ignore create mode 100644 devicetypes/smartthings/aeon-minimote.src/README.md diff --git a/devicetypes/smartthings/aeon-minimote.src/.st-ignore b/devicetypes/smartthings/aeon-minimote.src/.st-ignore new file mode 100644 index 0000000..f78b46e --- /dev/null +++ b/devicetypes/smartthings/aeon-minimote.src/.st-ignore @@ -0,0 +1,2 @@ +.st-ignore +README.md diff --git a/devicetypes/smartthings/aeon-minimote.src/README.md b/devicetypes/smartthings/aeon-minimote.src/README.md new file mode 100644 index 0000000..b64e932 --- /dev/null +++ b/devicetypes/smartthings/aeon-minimote.src/README.md @@ -0,0 +1,33 @@ +# Aeon Minimote + +Cloud Execution + +Works with: + +* [Aeotec Minimote](http://aeotec.com/small-z-wave-remote-control) + +## Table of contents + +* [Capabilities](#capabilities) +* [Health](#device-health) +* [Troubleshooting](#troubleshooting) + +## Capabilities + +* **Actuator** - represents device has commands +* **Button** - represents a device with one or more buttons +* **Holdable Button** - represents a device with one or more holdable buttons +* **Configuration** - allows for configuration of devices +* **Sensor** - detects sensor events +* **Health Check** - indicates ability to get device health notifications + +## Device Health + +Aeon Minimote is a ZWave totally sleepy device and is marked offline only in the case when Hub is offline. + +## Troubleshooting + +If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range. +Pairing needs to be tried again by placing the sensor closer to the hub. +Instructions related to pairing, resetting and removing the Aeotec Minimote from SmartThings can be found in the following link: +* [Aeotec Minimote Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202087904-Aeotec-Minimote) diff --git a/devicetypes/smartthings/aeon-minimote.src/aeon-minimote.groovy b/devicetypes/smartthings/aeon-minimote.src/aeon-minimote.groovy index f1a0c0b..c2fbfc3 100644 --- a/devicetypes/smartthings/aeon-minimote.src/aeon-minimote.groovy +++ b/devicetypes/smartthings/aeon-minimote.src/aeon-minimote.groovy @@ -1,3 +1,4 @@ +import groovy.json.JsonOutput /** * Copyright 2015 SmartThings * @@ -18,6 +19,7 @@ metadata { capability "Holdable Button" capability "Configuration" capability "Sensor" + capability "Health Check" fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B", outClusters: "0x26,0x2B" fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B,0x85,0x84", outClusters: "0x26" // old style with numbered buttons @@ -119,5 +121,7 @@ def updated() { } def initialize() { + // Arrival sensors only goes OFFLINE when Hub is off + sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false) sendEvent(name: "numberOfButtons", value: 4) } From 17465c87c0bd524685fdfba0afc8d6b5c9e23846 Mon Sep 17 00:00:00 2001 From: "piyush.c" Date: Mon, 19 Jun 2017 18:12:28 +0530 Subject: [PATCH 3/6] [DHF-14] Health Check Aeon Key Fob --- .../smartthings/aeon-key-fob.src/.st-ignore | 2 ++ .../smartthings/aeon-key-fob.src/README.md | 34 +++++++++++++++++++ .../aeon-key-fob.src/aeon-key-fob.groovy | 5 +++ 3 files changed, 41 insertions(+) create mode 100644 devicetypes/smartthings/aeon-key-fob.src/.st-ignore create mode 100644 devicetypes/smartthings/aeon-key-fob.src/README.md diff --git a/devicetypes/smartthings/aeon-key-fob.src/.st-ignore b/devicetypes/smartthings/aeon-key-fob.src/.st-ignore new file mode 100644 index 0000000..f78b46e --- /dev/null +++ b/devicetypes/smartthings/aeon-key-fob.src/.st-ignore @@ -0,0 +1,2 @@ +.st-ignore +README.md diff --git a/devicetypes/smartthings/aeon-key-fob.src/README.md b/devicetypes/smartthings/aeon-key-fob.src/README.md new file mode 100644 index 0000000..f254223 --- /dev/null +++ b/devicetypes/smartthings/aeon-key-fob.src/README.md @@ -0,0 +1,34 @@ +# Aeon Labs Key Fob + +Cloud Execution + +Works with: + +* [Aeon Labs Key Fob](http://aeotec.com/z-wave-key-fob-remote-control) + +## Table of contents + +* [Capabilities](#capabilities) +* [Health](#device-health) +* [Troubleshooting](#troubleshooting) + +## Capabilities + +* **Actuator** - represents device has commands +* **Button** - represents a device with one or more buttons +* **Holdable Button** - represents a device with one or more holdable buttons +* **Configuration** - allows for configuration of devices +* **Sensor** - detects sensor events +* **Battery** - defines device uses a battery +* **Health Check** - indicates ability to get device health notifications + +## Device Health + +Aeon Key Fob is a ZWave totally sleepy device and is marked offline only in the case when Hub is offline. + +## Troubleshooting + +If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range. +Pairing needs to be tried again by placing the sensor closer to the hub. +Instructions related to pairing, resetting and removing the Aeon Labs Key Fob from SmartThings can be found in the following link: +* [Aeotec Key Fob Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202294120-Aeon-Labs-Key-Fob) diff --git a/devicetypes/smartthings/aeon-key-fob.src/aeon-key-fob.groovy b/devicetypes/smartthings/aeon-key-fob.src/aeon-key-fob.groovy index deb7047..097479e 100644 --- a/devicetypes/smartthings/aeon-key-fob.src/aeon-key-fob.groovy +++ b/devicetypes/smartthings/aeon-key-fob.src/aeon-key-fob.groovy @@ -1,3 +1,4 @@ +import groovy.json.JsonOutput /** * Copyright 2015 SmartThings * @@ -19,6 +20,7 @@ metadata { capability "Configuration" capability "Sensor" capability "Battery" + capability "Health Check" fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85" fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button" @@ -131,6 +133,9 @@ def updated() { } def initialize() { + // Arrival sensors only goes OFFLINE when Hub is off + sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false) + def zwMap = getZwaveInfo() def buttons = 4 // Default for Key Fob From 9f5378c2b6624dac2892284323bf3c9367cbee2f Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Tue, 20 Jun 2017 10:06:23 -0600 Subject: [PATCH 4/6] DVCSMP-2703 OpenT2T: Update to 6/20 submission --- .../opent2t-smartapp-test.src/opent2t-smartapp-test.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smartapps/opent2t/opent2t-smartapp-test.src/opent2t-smartapp-test.groovy b/smartapps/opent2t/opent2t-smartapp-test.src/opent2t-smartapp-test.groovy index c1e6bff..f7f9ad6 100644 --- a/smartapps/opent2t/opent2t-smartapp-test.src/opent2t-smartapp-test.groovy +++ b/smartapps/opent2t/opent2t-smartapp-test.src/opent2t-smartapp-test.groovy @@ -195,7 +195,10 @@ def registerDeviceChange() { state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt]) log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}" } else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) { - state.deviceSubscriptionMap[deviceId] << subscriptionEndpt + // state.deviceSubscriptionMap[deviceId] << subscriptionEndpt + // For now, we will only have one subscription endpoint per device + state.deviceSubscriptionMap.remove(deviceId) + state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt]) log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}" } From 34174b730c5953bd386bf0cab4e6f2a168f5d444 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Thu, 22 Jun 2017 13:13:08 -0600 Subject: [PATCH 5/6] DVCSMP-2702 smartpower-outlet DTH incorrectly has capability.light --- .../smartthings/smartpower-outlet.src/smartpower-outlet.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index 0630aab..cb635ad 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -23,7 +23,6 @@ metadata { capability "Refresh" capability "Sensor" capability "Health Check" - capability "Light" capability "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet" From 17ae692aa0be70cc14f72e1fec58f8303712f725 Mon Sep 17 00:00:00 2001 From: marstorp Date: Fri, 23 Jun 2017 13:24:03 -0700 Subject: [PATCH 6/6] ICP-1148 Support Thermostat Dynamic data Adding support for dynamic thermostat and fan modes to ecobee --- .../ecobee-thermostat.groovy | 84 +++++++++---------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index b200e94..c444bb6 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -70,7 +70,7 @@ metadata { state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat" state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool" state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto" - state "emergency heat", action:"switchMode", icon: "st.thermostat.emergency-heat" // emergency heat = auxHeatOnly + state "auxheatonly", action:"switchMode", icon: "st.thermostat.emergency-heat" state "updating", label:"Working", icon: "st.secondary.secondary" } standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { @@ -156,47 +156,49 @@ void poll() { def generateEvent(Map results) { log.debug "parsing data $results" if(results) { - results.each { name, value -> def linkText = getLinkText(device) - def isChange = false - def isDisplayed = true + def supportedThermostatModes = [] + def thermostatMode = null + + results.each { name, value -> def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText), handlerName: name] if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) { def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() - isChange = isTemperatureStateChange(device, name, value.toString()) - isDisplayed = isChange - event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed] + event << [value: sendValue, unit: temperatureScale] } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") { def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() event << [value: sendValue, unit: temperatureScale, displayed: false] } else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ - isChange = isStateChange(device, name, value.toString()) - event << [value: value.toString(), isStateChange: isChange, displayed: false] + if (value == true) { + supportedThermostatModes << ((name == "auxHeatMode") ? "auxheatonly" : name - "Mode") + } + return // as we don't want to send this event here, proceed to next name/value pair } else if (name=="thermostatFanMode"){ - isChange = isStateChange(device, name, value.toString()) - event << [value: value.toString(), isStateChange: isChange, displayed: false] + sendEvent(name: "supportedThermostatFanModes", value: fanModes(), displayed: false) + event << [value: value.toString(), data:[supportedThermostatFanModes: fanModes()]] } else if (name=="humidity") { - isChange = isStateChange(device, name, value.toString()) - event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"] + event << [value: value.toString(), displayed: false, unit: "%"] } else if (name == "deviceAlive") { - isChange = isStateChange(device, name, value.toString()) - event['isStateChange'] = isChange event['displayed'] = false } else if (name == "thermostatMode") { - def mode = value.toString() - mode = (mode == "auxHeatOnly") ? "emergency heat" : mode - isChange = isStateChange(device, name, mode) - event << [value: mode, isStateChange: isChange, displayed: isDisplayed] + thermostatMode = value.toLowerCase() + return // as we don't want to send this event here, proceed to next name/value pair } else { - isChange = isStateChange(device, name, value.toString()) - isDisplayed = isChange - event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed] + event << [value: value.toString()] } sendEvent(event) } + if (state.supportedThermostatModes != supportedThermostatModes) { + state.supportedThermostatModes = supportedThermostatModes + sendEvent(name: "supportedThermostatModes", value: supportedThermostatModes, displayed: false) + } + if (thermostatMode) { + sendEvent(name: "thermostatMode", value: thermostatMode, data:[supportedThermostatModes:state.supportedThermostatModes], linkText: linkText, + descriptionText: getThermostatDescriptionText("thermostatMode", thermostatMode, linkText), handlerName: "thermostatMode") + } generateSetpointEvent () generateStatusEvent () } @@ -322,15 +324,7 @@ void resumeProgram() { } def modes() { - if (state.modes) { - log.debug "Modes = ${state.modes}" - return state.modes - } - else { - state.modes = parent.availableModes(this) - log.debug "Modes = ${state.modes}" - return state.modes - } + return state.supportedThermostatModes } def fanModes() { @@ -413,11 +407,13 @@ def setThermostatFanMode(String mode) { } def generateModeEvent(mode) { - sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true) + sendEvent(name: "thermostatMode", value: mode, data:[supportedThermostatModes: state.supportedThermostatModes], + descriptionText: "$device.displayName is in ${mode} mode") } def generateFanModeEvent(fanMode) { - sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true) + sendEvent(name: "thermostatFanMode", value: fanMode, data:[supportedThermostatFanModes: fanModes()], + descriptionText: "$device.displayName fan is in ${fanMode} mode") } def generateOperatingStateEvent(operatingState) { @@ -453,14 +449,14 @@ def heat() { } def emergencyHeat() { - auxHeatOnly() + auxheatonly() } -def auxHeatOnly() { - log.debug "auxHeatOnly = emergency heat" +def auxheatonly() { + log.debug "auxheatonly()" def deviceId = device.deviceNetworkId.split(/\./).last() if (parent.setMode ("auxHeatOnly", deviceId)) - generateModeEvent("emergency heat") // emergency heat = auxHeatOnly + generateModeEvent("auxheatonly") else { log.debug "Error setting new mode." def currentMode = device.currentState("thermostatMode")?.value @@ -593,7 +589,7 @@ def generateSetpointEvent() { } else if (mode == "off") { sendEvent("name":"thermostatSetpoint", "value":averageSetpoint, "unit":location.temperatureScale) sendEvent("name":"displayThermostatSetpoint", "value":"Off", displayed: false) - } else if (mode == "emergency heat") { // emergency heat = auxHeatOnly + } else if (mode == "auxheatonly") { sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"displayThermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale, displayed: false) } @@ -632,7 +628,7 @@ void raiseSetpoint() { targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5 - if ((mode == "heat" || mode == "emergency heat") && targetvalue > maxHeatingSetpoint) { // emergency heat = auxHeatOnly + if ((mode == "heat" || mode == "auxheatonly") && targetvalue > maxHeatingSetpoint) { targetvalue = maxHeatingSetpoint } else if (mode == "cool" && targetvalue > maxCoolingSetpoint) { targetvalue = maxCoolingSetpoint @@ -678,7 +674,7 @@ void lowerSetpoint() { targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5 - if ((mode == "heat" || mode == "emergency heat") && targetvalue < minHeatingSetpoint) { // emergency heat = auxHeatOnly + if ((mode == "heat" || mode == "auxheatonly") && targetvalue < minHeatingSetpoint) { targetvalue = minHeatingSetpoint } else if (mode == "cool" && targetvalue < minCoolingSetpoint) { targetvalue = minCoolingSetpoint @@ -719,7 +715,7 @@ void alterSetpoint(temp) { } //step1: check thermostatMode, enforce limits before sending request to cloud - if (mode == "heat" || mode == "emergency heat"){ // emergency heat = auxHeatOnly + if (mode == "heat" || mode == "auxheatonly"){ if (temp.value > coolingSetpoint){ targetHeatingSetpoint = temp.value targetCoolingSetpoint = temp.value @@ -754,7 +750,7 @@ void alterSetpoint(temp) { log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}" } else { log.error "Error alterSetpoint()" - if (mode == "heat" || mode == "emergency heat"){ // emergency heat = auxHeatOnly + if (mode == "heat" || mode == "auxheatonly"){ sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false) sendEvent("name": "displayThermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false) } else if (mode == "cool") { @@ -783,7 +779,7 @@ def generateStatusEvent() { log.debug "Cooling set point = ${coolingSetpoint}" log.debug "HVAC Mode = ${mode}" - if (mode == "heat") { + if (mode == "heat" || mode == "auxheatonly") { if (temperature >= heatingSetpoint) { statusText = "Right Now: Idle" } else { @@ -806,8 +802,6 @@ def generateStatusEvent() { } } else if (mode == "off") { statusText = "Right Now: Off" - } else if (mode == "emergency heat") { // emergency heat = auxHeatOnly - statusText = "Emergency Heat" } else { statusText = "?" }