From 383f72580a500df4e636f48efad1e3c1529b7089 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Tue, 6 Sep 2016 11:39:18 -0400 Subject: [PATCH 01/10] SSVD-2738 - SSVD-2734 - SSVD-2733 (#1187) * SSVD-2738 - Count Down Bug * SSVD-2734 - Copy text * SSVD-2733 - Add Unit --- .../resteele/monitor-on-sense.src/monitor-on-sense.groovy | 8 +++----- .../smartthings/energy-alerts.src/energy-alerts.groovy | 6 ++++-- .../smartthings/gentle-wake-up.src/gentle-wake-up.groovy | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/smartapps/resteele/monitor-on-sense.src/monitor-on-sense.groovy b/smartapps/resteele/monitor-on-sense.src/monitor-on-sense.groovy index d1f3c1f..b547214 100644 --- a/smartapps/resteele/monitor-on-sense.src/monitor-on-sense.groovy +++ b/smartapps/resteele/monitor-on-sense.src/monitor-on-sense.groovy @@ -17,7 +17,7 @@ definition( name: "Monitor on Sense", namespace: "resteele", author: "Rachel Steele", - description: "Turn on Monitor when vibration is sensed", + description: "Turn on switch when vibration is sensed", category: "My Apps", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", @@ -25,10 +25,10 @@ definition( preferences { - section("When the keyboard is used...") { + section("When vibration is sensed...") { input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?" } -section("Turn on/off a light...") { +section("Turn on switch...") { input "switch1", "capability.switch" } } @@ -47,5 +47,3 @@ def updated() { def accelerationActiveHandler(evt) { switch1.on() } - - diff --git a/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy b/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy index 2aad9bb..4a4c9ff 100644 --- a/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy +++ b/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy @@ -64,10 +64,12 @@ def meterHandler(evt) { def lastValue = atomicState.lastValue as double atomicState.lastValue = meterValue + def dUnit ? evt.unit : "Watts" + def aboveThresholdValue = aboveThreshold as int if (meterValue > aboveThresholdValue) { if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold - def msg = "${meter} reported ${evt.value} ${evt.unit} which is above your threshold of ${aboveThreshold}." + def msg = "${meter} reported ${evt.value} ${dUnit} which is above your threshold of ${aboveThreshold}." sendMessage(msg) } else { // log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed" @@ -78,7 +80,7 @@ def meterHandler(evt) { def belowThresholdValue = belowThreshold as int if (meterValue < belowThresholdValue) { if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold - def msg = "${meter} reported ${evt.value} ${evt.unit} which is below your threshold of ${belowThreshold}." + def msg = "${meter} reported ${evt.value} ${dUnit} which is below your threshold of ${belowThreshold}." sendMessage(msg) } else { // log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed" diff --git a/smartapps/smartthings/gentle-wake-up.src/gentle-wake-up.groovy b/smartapps/smartthings/gentle-wake-up.src/gentle-wake-up.groovy index e5b34ae..e5f60b7 100644 --- a/smartapps/smartthings/gentle-wake-up.src/gentle-wake-up.groovy +++ b/smartapps/smartthings/gentle-wake-up.src/gentle-wake-up.groovy @@ -761,7 +761,7 @@ String displayableTime(timeRemaining) { return "${minutes}:00" } def fraction = "0.${parts[1]}" as double - def seconds = "${60 * fraction as int}".padRight(2, "0") + def seconds = "${60 * fraction as int}".padLeft(2, "0") return "${minutes}:${seconds}" } @@ -1101,4 +1101,4 @@ def hasStartLevel() { def hasEndLevel() { return (endLevel != null && endLevel != "") -} \ No newline at end of file +} From f80e094bd9922acccdf06153fd511bc818d0da6d Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Tue, 6 Sep 2016 17:06:38 -0700 Subject: [PATCH 02/10] Revert "revert changes for DVCSMP-1980 and SSVD-2534 on staging" --- .../ecobee-thermostat.groovy | 50 +- .../ecobee-connect.src/ecobee-connect.groovy | 672 ++++++++++-------- 2 files changed, 401 insertions(+), 321 deletions(-) diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 7b08b14..a16d28c 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -152,11 +152,11 @@ def generateEvent(Map results) { sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue isChange = isTemperatureStateChange(device, name, value.toString()) isDisplayed = isChange - event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed] + event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed] } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") { def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue - event << [value: sendValue, displayed: false] + 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] @@ -234,9 +234,9 @@ void setHeatingSetpoint(setpoint) { def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" - if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) { - sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) - sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) + if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) { + sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) + sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}" generateSetpointEvent() generateStatusEvent() @@ -271,9 +271,9 @@ void setCoolingSetpoint(setpoint) { def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite" - if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) { - sendEvent("name":"heatingSetpoint", "value":heatingSetpoint) - sendEvent("name":"coolingSetpoint", "value":coolingSetpoint) + if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) { + sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) + sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}" generateSetpointEvent() generateStatusEvent() @@ -287,14 +287,14 @@ void resumeProgram() { log.debug "resumeProgram() is called" sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false) def deviceId = device.deviceNetworkId.split(/\./).last() - if (parent.resumeProgram(this, deviceId)) { + if (parent.resumeProgram(deviceId)) { sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false) runIn(5, "poll") log.debug "resumeProgram() is done" sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true) } else { sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false) - log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)" + log.error "Error resumeProgram() check parent.resumeProgram(deviceId)" } } @@ -406,7 +406,7 @@ def generateOperatingStateEvent(operatingState) { def off() { log.debug "off" def deviceId = device.deviceNetworkId.split(/\./).last() - if (parent.setMode (this,"off", deviceId)) + if (parent.setMode ("off", deviceId)) generateModeEvent("off") else { log.debug "Error setting new mode." @@ -420,7 +420,7 @@ def off() { def heat() { log.debug "heat" def deviceId = device.deviceNetworkId.split(/\./).last() - if (parent.setMode (this,"heat", deviceId)) + if (parent.setMode ("heat", deviceId)) generateModeEvent("heat") else { log.debug "Error setting new mode." @@ -438,7 +438,7 @@ def emergencyHeat() { def auxHeatOnly() { log.debug "auxHeatOnly" def deviceId = device.deviceNetworkId.split(/\./).last() - if (parent.setMode (this,"auxHeatOnly", deviceId)) + if (parent.setMode ("auxHeatOnly", deviceId)) generateModeEvent("auxHeatOnly") else { log.debug "Error setting new mode." @@ -452,7 +452,7 @@ def auxHeatOnly() { def cool() { log.debug "cool" def deviceId = device.deviceNetworkId.split(/\./).last() - if (parent.setMode (this,"cool", deviceId)) + if (parent.setMode ("cool", deviceId)) generateModeEvent("cool") else { log.debug "Error setting new mode." @@ -466,7 +466,7 @@ def cool() { def auto() { log.debug "auto" def deviceId = device.deviceNetworkId.split(/\./).last() - if (parent.setMode (this,"auto", deviceId)) + if (parent.setMode ("auto", deviceId)) generateModeEvent("auto") else { log.debug "Error setting new mode." @@ -489,7 +489,7 @@ def fanOn() { def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint - if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { + if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { generateFanModeEvent(fanMode) } else { log.debug "Error setting new mode." @@ -510,7 +510,7 @@ def fanAuto() { def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint - if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { + if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) { generateFanModeEvent(fanMode) } else { log.debug "Error setting new mode." @@ -556,12 +556,12 @@ def generateSetpointEvent() { if (mode == "heat") { - sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint ) + sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) } else if (mode == "cool") { - sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint) + sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) } else if (mode == "auto") { @@ -573,7 +573,7 @@ def generateSetpointEvent() { } else if (mode == "auxHeatOnly") { - sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint) + sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) } @@ -608,7 +608,7 @@ void raiseSetpoint() { targetvalue = maxCoolingSetpoint } - sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) + sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false) log.info "In mode $mode raiseSetpoint() to $targetvalue" runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite @@ -644,7 +644,7 @@ void lowerSetpoint() { targetvalue = minCoolingSetpoint } - sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) + sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false) log.info "In mode $mode lowerSetpoint() to $targetvalue" runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite @@ -690,10 +690,10 @@ void alterSetpoint(temp) { def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint - if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) { + if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) { sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false) - sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint) - sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint) + sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale) + sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale) log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}" } else { log.error "Error alterSetpoint()" diff --git a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy index 848b7b7..d8c2179 100644 --- a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy +++ b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy @@ -66,7 +66,7 @@ def authPage() { // get rid of next button until the user is actually auth'd if (!oauthTokenProvided) { return dynamicPage(name: "auth", title: "Login", nextPage: "", uninstall:uninstallAllowed) { - section(){ + section() { paragraph "Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button." href url:redirectUrl, style:"embedded", required:true, title:"ecobee", description:description } @@ -76,7 +76,7 @@ def authPage() { log.debug "thermostat list: $stats" log.debug "sensor list: ${sensorsDiscovered()}" return dynamicPage(name: "auth", title: "Select Your Thermostats", uninstall: true) { - section(""){ + section("") { paragraph "Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings." input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats]) } @@ -84,7 +84,7 @@ def authPage() { def options = sensorsDiscovered() ?: [] def numFound = options.size() ?: 0 if (numFound > 0) { - section(""){ + section("") { paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings." input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options) } @@ -115,13 +115,12 @@ def callback() { def code = params.code def oauthState = params.state - if (oauthState == atomicState.oauthInitState){ - + if (oauthState == atomicState.oauthInitState) { def tokenParams = [ - grant_type: "authorization_code", - code : code, - client_id : smartThingsClientId, - redirect_uri: callbackUrl + grant_type: "authorization_code", + code : code, + client_id : smartThingsClientId, + redirect_uri: callbackUrl ] def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}" @@ -129,9 +128,6 @@ def callback() { httpPost(uri: tokenUrl) { resp -> atomicState.refreshToken = resp.data.refresh_token atomicState.authToken = resp.data.access_token - log.debug "swapped token: $resp.data" - log.debug "atomicState.refreshToken: ${atomicState.refreshToken}" - log.debug "atomicState.authToken: ${atomicState.authToken}" } if (atomicState.authToken) { @@ -148,8 +144,8 @@ def callback() { def success() { def message = """ -

Your ecobee Account is now connected to SmartThings!

-

Click 'Done' to finish setup.

+

Your ecobee Account is now connected to SmartThings!

+

Click 'Done' to finish setup.

""" connectionStatus(message) } @@ -171,64 +167,63 @@ def connectionStatus(message, redirectUrl = null) { } def html = """ - - - - -Ecobee & SmartThings connection - - - -
+ + + + + Ecobee & SmartThings connection + + + +
ecobee icon connected device icon SmartThings logo ${message} -
- - -""" +
+ + + """ render contentType: 'text/html', data: html } @@ -237,19 +232,26 @@ def getEcobeeThermostats() { log.debug "getting device list" atomicState.remoteSensors = [] - def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}' - + def bodyParams = [ + selection: [ + selectionType: "registered", + selectionMatch: "", + includeRuntime: true, + includeSensors: true + ] + ] def deviceListParams = [ - uri: apiEndpoint, - path: "/1/thermostat", - headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], - query: [format: 'json', body: requestBody] + uri: apiEndpoint, + path: "/1/thermostat", + headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], + // TODO - the query string below is not consistent with the Ecobee docs: + // https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml + query: [format: 'json', body: toJson(bodyParams)] ] def stats = [:] try { httpGet(deviceListParams) { resp -> - if (resp.status == 200) { resp.data.thermostatList.each { stat -> atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors @@ -289,9 +291,10 @@ Map sensorsDiscovered() { } def getThermostatDisplayName(stat) { - if(stat?.name) - return stat.name.toString() - return (getThermostatTypeName(stat) + " (${stat.identifier})").toString() + if(stat?.name) { + return stat.name.toString() + } + return (getThermostatTypeName(stat) + " (${stat.identifier})").toString() } def getThermostatTypeName(stat) { @@ -310,7 +313,6 @@ def updated() { } def initialize() { - log.debug "initialize" def devices = thermostats.collect { dni -> def d = getChildDevice(dni) @@ -350,8 +352,6 @@ def initialize() { log.warn "delete: ${delete}, deleting ${delete.size()} thermostats" delete.each { deleteChildDevice(it.deviceNetworkId) } //inherits from SmartApp (data-management) - atomicState.thermostatData = [:] //reset Map to store thermostat data - //send activity feeds to tell that device is connected def notificationMessage = "is connected to SmartThings" sendActivityFeeds(notificationMessage) @@ -381,75 +381,41 @@ def pollHandler() { } def pollChildren(child = null) { - def thermostatIdsString = getChildDeviceIdsString() - log.debug "polling children: $thermostatIdsString" - def data = "" + def thermostatIdsString = getChildDeviceIdsString() + log.debug "polling children: $thermostatIdsString" + + def requestBody = [ + selection: [ + selectionType: "thermostats", + selectionMatch: thermostatIdsString, + includeExtendedRuntime: true, + includeSettings: true, + includeRuntime: true, + includeSensors: true + ] + ] - def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}' def result = false def pollParams = [ - uri: apiEndpoint, - path: "/1/thermostat", - headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], - query: [format: 'json', body: jsonRequestBody] - ] + uri: apiEndpoint, + path: "/1/thermostat", + headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], + // TODO - the query string below is not consistent with the Ecobee docs: + // https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml + query: [format: 'json', body: toJson(requestBody)] + ] try{ httpGet(pollParams) { resp -> if(resp.status == 200) { - log.debug "poll results returned resp.data ${resp.data}" - atomicState.remoteSensors = resp.data.thermostatList.remoteSensors - atomicState.thermostatData = resp.data - updateSensorData() - atomicState.thermostats = resp.data.thermostatList.inject([:]) { collector, stat -> - def dni = [ app.id, stat.identifier ].join('.') - - log.debug "updating dni $dni" - - data = [ - coolMode: (stat.settings.coolStages > 0), - heatMode: (stat.settings.heatStages > 0), - deviceTemperatureUnit: stat.settings.useCelsius, - minHeatingSetpoint: (stat.settings.heatRangeLow / 10), - maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10), - minCoolingSetpoint: (stat.settings.coolRangeLow / 10), - maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10), - autoMode: stat.settings.autoHeatCoolFeatureEnabled, - auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler), - temperature: (stat.runtime.actualTemperature / 10), - heatingSetpoint: stat.runtime.desiredHeat / 10, - coolingSetpoint: stat.runtime.desiredCool / 10, - thermostatMode: stat.settings.hvacMode, - humidity: stat.runtime.actualHumidity, - thermostatFanMode: stat.runtime.desiredFanMode - ] - - if (location.temperatureScale == "F") - { - data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"] - data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"] - data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"] - data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"] - data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"] - data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"] - data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"] - - } - - if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") { - data["deviceTemperatureUnit"] = "F" - - } else { - data["deviceTemperatureUnit"] = "C" - } - - collector[dni] = [data:data] - return collector - } - result = true - log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}" - } + log.debug "poll results returned resp.data ${resp.data}" + atomicState.remoteSensors = resp.data.thermostatList.remoteSensors + updateSensorData() + storeThermostatData(resp.data.thermostatList) + result = true + log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}" + } } } catch (groovyx.net.http.HttpResponseException e) { log.trace "Exception polling children: " + e.response.data.status @@ -463,13 +429,12 @@ def pollChildren(child = null) { } // Poll Child is invoked from the Child Device itself as part of the Poll Capability -def pollChild(){ - +def pollChild() { def devices = getChildDevices() - if (pollChildren()){ + if (pollChildren()) { devices.each { child -> - if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){ + if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")) { if(atomicState.thermostats[child.device.deviceNetworkId] != null) { def tData = atomicState.thermostats[child.device.deviceNetworkId] log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}" @@ -492,36 +457,7 @@ void poll() { } def availableModes(child) { - debugEvent ("atomicState.thermostats = ${atomicState.thermostats}") - - debugEvent ("Child DNI = ${child.device.deviceNetworkId}") - - def tData = atomicState.thermostats[child.device.deviceNetworkId] - - debugEvent("Data = ${tData}") - - if(!tData) - { - log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling" - - return null - } - - def modes = ["off"] - - if (tData.data.heatMode) modes.add("heat") - if (tData.data.coolMode) modes.add("cool") - if (tData.data.autoMode) modes.add("auto") - if (tData.data.auxHeatMode) modes.add("auxHeatOnly") - - modes - -} - -def currentMode(child) { - debugEvent ("atomicState.Thermos = ${atomicState.thermostats}") - debugEvent ("Child DNI = ${child.device.deviceNetworkId}") def tData = atomicState.thermostats[child.device.deviceNetworkId] @@ -530,14 +466,42 @@ def currentMode(child) { if(!tData) { log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling" + return null + } + def modes = ["off"] + if (tData.data.heatMode) { + modes.add("heat") + } + if (tData.data.coolMode) { + modes.add("cool") + } + if (tData.data.autoMode) { + modes.add("auto") + } + if (tData.data.auxHeatMode) { + modes.add("auxHeatOnly") + } + + return modes +} + +def currentMode(child) { + debugEvent ("atomicState.Thermos = ${atomicState.thermostats}") + debugEvent ("Child DNI = ${child.device.deviceNetworkId}") + + def tData = atomicState.thermostats[child.device.deviceNetworkId] + + debugEvent("Data = ${tData}") + + if(!tData) { + log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling" return null } def mode = tData.data.thermostatMode - - mode + return mode } def updateSensorData() { @@ -558,12 +522,12 @@ def updateSensorData() { } } - } else if (it.type == "occupancy") { - if(it.value == "true") - occupancy = "active" - else + if(it.value == "true") { + occupancy = "active" + } else { occupancy = "inactive" + } } } def dni = "ecobee_sensor-"+ it?.id + "-" + it?.code @@ -582,7 +546,7 @@ def getChildDeviceIdsString() { } def toJson(Map m) { - return new org.json.JSONObject(m).toString() + return groovy.json.JsonOutput.toJson(m) } def toQueryString(Map m) { @@ -595,54 +559,24 @@ private refreshAuthToken() { if(!atomicState.refreshToken) { log.warn "Can not refresh OAuth token since there is no refreshToken stored" } else { - def refreshParams = [ - method: 'POST', - uri : apiEndpoint, - path : "/token", - query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId], + method: 'POST', + uri : apiEndpoint, + path : "/token", + query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId], ] - log.debug refreshParams - def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials." //changed to httpPost try { def jsonMap httpPost(refreshParams) { resp -> - if(resp.status == 200) { log.debug "Token refreshed...calling saved RestAction now!" - debugEvent("Token refreshed ... calling saved RestAction now!") - - log.debug resp - - jsonMap = resp.data - - if(resp.data) { - - log.debug resp.data - debugEvent("Response = ${resp.data}") - - atomicState.refreshToken = resp?.data?.refresh_token - atomicState.authToken = resp?.data?.access_token - - debugEvent("Refresh Token = ${atomicState.refreshToken}") - debugEvent("OAUTH Token = ${atomicState.authToken}") - - if(atomicState.action && atomicState.action != "") { - log.debug "Executing next action: ${atomicState.action}" - - "${atomicState.action}"() - - atomicState.action = "" - } - - } - atomicState.action = "" - } - } + saveTokenAndResumeAction(resp.data) + } + } } catch (groovyx.net.http.HttpResponseException e) { log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}" def reAttemptPeriod = 300 // in sec @@ -662,118 +596,220 @@ private refreshAuthToken() { } } -def resumeProgram(child, deviceId) { - - - def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}' - def result = sendJson(jsonRequestBody) - return result +/** + * Saves the refresh and auth token from the passed-in JSON object, + * and invokes any previously executing action that did not complete due to + * an expired token. + * + * @param json - an object representing the parsed JSON response from Ecobee + */ +private void saveTokenAndResumeAction(json) { + log.debug "token response json: $json" + if (json) { + debugEvent("Response = $json") + atomicState.refreshToken = json?.refresh_token + atomicState.authToken = json?.access_token + if (atomicState.action) { + log.debug "got refresh token, executing next action: ${atomicState.action}" + "${atomicState.action}"() + } + } else { + log.warn "did not get response body from refresh token response" + } + atomicState.action = "" } -def setHold(child, heating, cooling, deviceId, sendHoldType) { - - int h = heating * 10 - int c = cooling * 10 - def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}' - - def result = sendJson(child, jsonRequestBody) - return result +/** + * Executes the resume program command on the Ecobee thermostat + * @param deviceId - the ID of the device + * + * @retrun true if the command was successful, false otherwise. + */ +boolean resumeProgram(deviceId) { + def payload = [ + selection: [ + selectionType: "thermostats", + selectionMatch: deviceId, + includeRuntime: true + ], + functions: [ + [ + type: "resumeProgram" + ] + ] + ] + return sendCommandToEcobee(payload) } -def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) { +/** + * Executes the set hold command on the Ecobee thermostat + * @param heating - The heating temperature to set in fahrenheit + * @param cooling - the cooling temperature to set in fahrenheit + * @param deviceId - the ID of the device + * @param sendHoldType - the hold type to execute + * + * @return true if the command was successful, false otherwise + */ +boolean setHold(heating, cooling, deviceId, sendHoldType) { + // Ecobee requires that temp values be in fahrenheit multiplied by 10. + int h = heating * 10 + int c = cooling * 10 - int h = heating * 10 - int c = cooling * 10 + def payload = [ + selection: [ + selectionType: "thermostats", + selectionMatch: deviceId, + includeRuntime: true + ], + functions: [ + [ + type: "setHold", + params: [ + coolHoldTemp: c, + heatHoldTemp: h, + holdType: sendHoldType + ] + ] + ] + ] - - def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+', "fan": '+fanMode+' } } ]}' - def result = sendJson(child, jsonRequestBody) - return result + return sendCommandToEcobee(payload) } -def setMode(child, mode, deviceId) { - def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}' +/** + * Executes the set fan mode command on the Ecobee thermostat + * @param heating - The heating temperature to set in fahrenheit + * @param cooling - the cooling temperature to set in fahrenheit + * @param deviceId - the ID of the device + * @param sendHoldType - the hold type to execute + * @param fanMode - the fan mode to set to + * + * @return true if the command was successful, false otherwise + */ +boolean setFanMode(heating, cooling, deviceId, sendHoldType, fanMode) { + // Ecobee requires that temp values be in fahrenheit multiplied by 10. + int h = heating * 10 + int c = cooling * 10 - def result = sendJson(jsonRequestBody) - return result + def payload = [ + selection: [ + selectionType: "thermostats", + selectionMatch: deviceId, + includeRuntime: true + ], + functions: [ + [ + type: "setHold", + params: [ + coolHoldTemp: c, + heatHoldTemp: h, + holdType: sendHoldType, + fan: fanMode + ] + ] + ] + ] + + return sendCommandToEcobee(payload) } -def sendJson(child = null, String jsonBody) { +/** + * Sets the mode of the Ecobee thermostat + * @param mode - the mode to set to + * @param deviceId - the ID of the device + * + * @return true if the command was successful, false otherwise + */ +boolean setMode(mode, deviceId) { + def payload = [ + selection: [ + selectionType: "thermostats", + selectionMatch: deviceId, + includeRuntime: true + ], + thermostat: [ + settings: [ + hvacMode: mode + ] + ] + ] + return sendCommandToEcobee(payload) +} - def returnStatus = false +/** + * Makes a request to the Ecobee API to actuate the thermostat. + * Used by command methods to send commands to Ecobee. + * + * @param bodyParams - a map of request parameters to send to Ecobee. + * + * @return true if the command was accepted by Ecobee without error, false otherwise. + */ +private boolean sendCommandToEcobee(Map bodyParams) { + def isSuccess = false def cmdParams = [ - uri: apiEndpoint, - path: "/1/thermostat", - headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], - body: jsonBody + uri: apiEndpoint, + path: "/1/thermostat", + headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], + body: toJson(bodyParams) ] try{ - httpPost(cmdParams) { resp -> - - if(resp.status == 200) { - - log.debug "updated ${resp.data}" - returnStatus = resp.data.status.code - if (resp.data.status.code == 0) - log.debug "Successful call to ecobee API." - else { - log.debug "Error return code = ${resp.data.status.code}" - debugEvent("Error return code = ${resp.data.status.code}") - } - } - } + httpPost(cmdParams) { resp -> + if(resp.status == 200) { + log.debug "updated ${resp.data}" + def returnStatus = resp.data.status.code + if (returnStatus == 0) { + log.debug "Successful call to ecobee API." + isSuccess = true + } else { + log.debug "Error return code = ${returnStatus}" + debugEvent("Error return code = ${returnStatus}") + } + } + } } catch (groovyx.net.http.HttpResponseException e) { log.trace "Exception Sending Json: " + e.response.data.status debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}") if (e.response.data.status.code == 14) { + // TODO - figure out why we're setting the next action to be pollChildren + // after refreshing auth token. Is it to keep UI in sync, or just copy/paste error? atomicState.action = "pollChildren" log.debug "Refreshing your auth_token!" refreshAuthToken() - } - else { + } else { debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.") log.error "Authentication error, invalid authentication method, lack of credentials, etc." } } - if (returnStatus == 0) - return true - else - return false + return isSuccess } -def getChildName() { "Ecobee Thermostat" } -def getSensorChildName() { "Ecobee Sensor" } +def getChildName() { return "Ecobee Thermostat" } +def getSensorChildName() { return "Ecobee Sensor" } def getServerUrl() { return "https://graph.api.smartthings.com" } def getShardUrl() { return getApiServerUrl() } -def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" } -def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" } -def getApiEndpoint() { "https://api.ecobee.com" } -def getSmartThingsClientId() { appSettings.clientId } +def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback" } +def getBuildRedirectUrl() { return "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" } +def getApiEndpoint() { return "https://api.ecobee.com" } +def getSmartThingsClientId() { return appSettings.clientId } def debugEvent(message, displayEvent = false) { - def results = [ - name: "appdebug", - descriptionText: message, - displayed: displayEvent + name: "appdebug", + descriptionText: message, + displayed: displayEvent ] log.debug "Generating AppDebug Event: ${results}" sendEvent (results) - -} - -def debugEventFromParent(child, message) { - if (child != null) { child.sendEvent("name":"debugEventFromParent", "value":message, "description":message, displayed: true, isStateChange: true)} } //send both push notification and mobile activity feeds -def sendPushAndFeeds(notificationMessage){ +def sendPushAndFeeds(notificationMessage) { log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}" log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}" - if (atomicState.timeSendPush){ - if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day + if (atomicState.timeSendPush) { + if (now() - atomicState.timeSendPush > 86400000) { // notification is sent to remind user once a day sendPush("Your Ecobee thermostat " + notificationMessage) sendActivityFeeds(notificationMessage) atomicState.timeSendPush = now() @@ -786,6 +822,58 @@ def sendPushAndFeeds(notificationMessage){ atomicState.authToken = null } +/** + * Stores data about the thermostats in atomicState. + * @param thermostats - a list of thermostats as returned from the Ecobee API + */ +private void storeThermostatData(thermostats) { + log.trace "Storing thermostat data: $thermostats" + def data + atomicState.thermostats = thermostats.inject([:]) { collector, stat -> + def dni = [ app.id, stat.identifier ].join('.') + log.debug "updating dni $dni" + + data = [ + coolMode: (stat.settings.coolStages > 0), + heatMode: (stat.settings.heatStages > 0), + deviceTemperatureUnit: stat.settings.useCelsius, + minHeatingSetpoint: (stat.settings.heatRangeLow / 10), + maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10), + minCoolingSetpoint: (stat.settings.coolRangeLow / 10), + maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10), + autoMode: stat.settings.autoHeatCoolFeatureEnabled, + auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler), + temperature: (stat.runtime.actualTemperature / 10), + heatingSetpoint: stat.runtime.desiredHeat / 10, + coolingSetpoint: stat.runtime.desiredCool / 10, + thermostatMode: stat.settings.hvacMode, + humidity: stat.runtime.actualHumidity, + thermostatFanMode: stat.runtime.desiredFanMode + ] + if (location.temperatureScale == "F") { + data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"] + data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"] + data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"] + data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"] + data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"] + data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"] + data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"] + + } + + if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") { + data["deviceTemperatureUnit"] = "F" + + } else { + data["deviceTemperatureUnit"] = "C" + } + + collector[dni] = [data:data] + return collector + } + log.debug "updated ${atomicState.thermostats?.size()} thermostats: ${atomicState.thermostats}" +} + def sendActivityFeeds(notificationMessage) { def devices = getChildDevices() devices.each { child -> @@ -793,14 +881,6 @@ def sendActivityFeeds(notificationMessage) { } } -def roundC (tempC) { - return String.format("%.1f", (Math.round(tempC * 2))/2) -} - def convertFtoC (tempF) { return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2) } - -def convertCtoF (tempC) { - return (Math.round(tempC * (9/5)) + 32).toInteger() -} From 1c0ddd2571cd81d6f4858a442c768dcaa80fd59c Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Tue, 6 Sep 2016 17:07:14 -0700 Subject: [PATCH 03/10] Revert "Revert "WWST-40 Philips Hue: Implement device watch"" --- .../hue-bloom.src/hue-bloom.groovy | 9 ++ .../smartthings/hue-bulb.src/hue-bulb.groovy | 9 ++ .../hue-lux-bulb.src/hue-lux-bulb.groovy | 9 ++ .../hue-white-ambiance-bulb.groovy | 9 ++ .../hue-connect.src/hue-connect.groovy | 141 ++++++++++++------ 5 files changed, 130 insertions(+), 47 deletions(-) diff --git a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy index f99f436..9f7d804 100644 --- a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy +++ b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy @@ -16,6 +16,7 @@ metadata { capability "Switch" capability "Refresh" capability "Sensor" + capability "Health Check" command "setAdjustedColor" command "reset" @@ -55,6 +56,10 @@ metadata { } } +void installed() { + sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) +} + // parse events into attributes def parse(description) { log.debug "parse() - $description" @@ -166,3 +171,7 @@ def verifyPercent(percent) { return false } } + +def ping() { + log.debug "${parent.ping(this)}" +} diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 0c3d917..1f63b09 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -17,6 +17,7 @@ metadata { capability "Switch" capability "Refresh" capability "Sensor" + capability "Health Check" command "setAdjustedColor" command "reset" @@ -64,6 +65,10 @@ metadata { } } +void installed() { + sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) +} + // parse events into attributes def parse(description) { log.debug "parse() - $description" @@ -182,3 +187,7 @@ def verifyPercent(percent) { return false } } + +def ping() { + log.trace "${parent.ping(this)}" +} diff --git a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy index 728b265..b1f076d 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -14,6 +14,7 @@ metadata { capability "Switch" capability "Refresh" capability "Sensor" + capability "Health Check" command "refresh" } @@ -48,6 +49,10 @@ metadata { } } +void installed() { + sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) +} + // parse events into attributes def parse(description) { log.debug "parse() - $description" @@ -87,3 +92,7 @@ void refresh() { log.debug "Executing 'refresh'" parent.manualRefresh() } + +def ping() { + log.debug "${parent.ping(this)}" +} diff --git a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy index 0a25741..fd8c8bf 100644 --- a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy +++ b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy @@ -15,6 +15,7 @@ metadata { capability "Color Temperature" capability "Switch" capability "Refresh" + capability "Health Check" command "refresh" } @@ -53,6 +54,10 @@ metadata { } } +void installed() { + sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) +} + // parse events into attributes def parse(description) { log.debug "parse() - $description" @@ -101,3 +106,7 @@ void refresh() { log.debug "Executing 'refresh'" parent.manualRefresh() } + +def ping() { + log.debug "${parent.ping(this)}" +} diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 9ca7773..fdfb509 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -95,8 +95,7 @@ def bridgeDiscoveryFailed() { } } -def bridgeLinking() -{ +def bridgeLinking() { int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int state.linkRefreshcount = linkRefreshcount + 1 def refreshInterval = 3 @@ -328,7 +327,7 @@ def bulbListHandler(hub, data = "") { def object = new groovy.json.JsonSlurper().parseText(data) object.each { k,v -> if (v instanceof Map) - bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub] + bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable] } } def bridge = null @@ -448,7 +447,6 @@ def addBridge() { 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) @@ -649,8 +647,7 @@ def locationHandler(evt) { } } } - } - else if (parsedEvent.headers && parsedEvent.body) { + } else if (parsedEvent.headers && parsedEvent.body) { log.trace "HUE BRIDGE RESPONSES" def headerString = parsedEvent.headers.toString() if (headerString?.contains("xml")) { @@ -733,7 +730,7 @@ private void updateBridgeStatus(childDevice) { private void checkBridgeStatus() { def bridges = getHueBridges() // Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer) - def time = now() - (1000 * 60 * 16) + def time = now() - (1000 * 60 * 30) bridges.each { def d = getChildDevice(it.value.mac) if(d) { @@ -746,6 +743,8 @@ private void checkBridgeStatus() { if (it.value.lastActivity < time) { // it.value.lastActivity != null && log.warn "Bridge $it.key is Offline" d.sendEvent(name: "status", value: "Offline") + // set all lights to offline since bridge is not reachable + state.bulbs?.each {it.value.online = false} } else { d.sendEvent(name: "status", value: "Online")//setOnline(false) } @@ -785,8 +784,7 @@ def parse(childDevice, description) { if (body instanceof java.util.Map) { // get (poll) reponse return handlePoll(body) - } - else { + } else { //put response return handleCommandResponse(body) } @@ -883,36 +881,40 @@ private handleCommandResponse(body) { // scan entire response before sending events to make sure they are always in the same order def updates = [:] - body.each { payload -> - log.debug $payload + body.each { payload -> + log.debug $payload if (payload?.success) { - def childDeviceNetworkId = app.id + "/" - def eventType + def childDeviceNetworkId = app.id + "/" + def eventType payload.success.each { k, v -> def data = k.split("/") if (data.length == 5) { childDeviceNetworkId = app.id + "/" + k.split("/")[2] if (!updates[childDeviceNetworkId]) updates[childDeviceNetworkId] = [:] - eventType = k.split("/")[4] + eventType = k.split("/")[4] updates[childDeviceNetworkId]."$eventType" = v } } } else if (payload.error) { log.warn "Error returned from Hue bridge error = ${body?.error}" - } - } + } + } // send events for each update found above (order of events should be same as handlePoll()) updates.each { childDeviceNetworkId, params -> def device = getChildDevice(childDeviceNetworkId) - sendBasicEvents(device, "on", params.on) - sendBasicEvents(device, "bri", params.bri) - sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) - } + def id = getId(device) + // If device is offline, then don't send events which will update device watch + if (isOnline(id)) { + sendBasicEvents(device, "on", params.on) + sendBasicEvents(device, "bri", params.bri) + sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) + } + } return [] - } +} /** * Handles a response to a poll (GET) sent to the Hue Bridge. @@ -932,26 +934,32 @@ private handleCommandResponse(body) { * @return empty array */ private handlePoll(body) { - if (state.updating) { - // If user just executed commands, then ignore poll to not confuse the turning on/off state - return [] - } - def bulbs = getChildDevices() for (bulb in body) { def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} if (device) { if (bulb.value.state?.reachable) { - sendBasicEvents(device, "on", bulb.value?.state?.on) - sendBasicEvents(device, "bri", bulb.value?.state?.bri) - sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) + if (state.bulbs[bulb.key]?.online == false) { + // light just came back online, notify device watch + def lastActivity = now() + device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true) + } + state.bulbs[bulb.key]?.online = true + + // If user just executed commands, then do not send events to avoid confusing the turning on/off state + if (!state.updating) { + sendBasicEvents(device, "on", bulb.value?.state?.on) + sendBasicEvents(device, "bri", bulb.value?.state?.bri) + sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) + } } else { + state.bulbs[bulb.key]?.online = false log.warn "$device is not reachable by Hue bridge" - } - } - } - return [] + } + } } + return [] +} private updateInProgress() { state.updating = true @@ -980,22 +988,34 @@ def hubVerification(bodytext) { def on(childDevice) { log.debug "Executing 'on'" + def id = getId(childDevice) + if (!isOnline(id)) { + return "Bulb is unreachable" + } updateInProgress() createSwitchEvent(childDevice, "on") - put("lights/${getId(childDevice)}/state", [on: true]) + put("lights/$id/state", [on: true]) return "Bulb is turning On" } def off(childDevice) { log.debug "Executing 'off'" + def id = getId(childDevice) + if (!isOnline(id)) { + return "Bulb is unreachable" + } updateInProgress() createSwitchEvent(childDevice, "off") - put("lights/${getId(childDevice)}/state", [on: false]) + put("lights/$id/state", [on: false]) return "Bulb is turning Off" } def setLevel(childDevice, percent) { log.debug "Executing 'setLevel'" + def id = getId(childDevice) + if (!isOnline(id)) { + return "Bulb is unreachable" + } updateInProgress() // 1 - 254 def level @@ -1010,48 +1030,64 @@ def setLevel(childDevice, percent) { // that means that the light will still be on when on is called next time // Lets emulate that here if (percent > 0) { - put("lights/${getId(childDevice)}/state", [bri: level, on: true]) + put("lights/$id/state", [bri: level, on: true]) } else { - put("lights/${getId(childDevice)}/state", [on: false]) + put("lights/$id/state", [on: false]) } return "Setting level to $percent" } def setSaturation(childDevice, percent) { log.debug "Executing 'setSaturation($percent)'" - updateInProgress() + def id = getId(childDevice) + if (!isOnline(id)) { + return "Bulb is unreachable" + } + + updateInProgress() // 0 - 254 def level = Math.min(Math.round(percent * 254 / 100), 254) // TODO should this be done by app only or should we default to on? createSwitchEvent(childDevice, "on") - put("lights/${getId(childDevice)}/state", [sat: level, on: true]) + put("lights/$id/state", [sat: level, on: true]) return "Setting saturation to $percent" } def setHue(childDevice, percent) { log.debug "Executing 'setHue($percent)'" + def id = getId(childDevice) + if (!isOnline(id)) { + return "Bulb is unreachable" + } updateInProgress() // 0 - 65535 def level = Math.min(Math.round(percent * 65535 / 100), 65535) // TODO should this be done by app only or should we default to on? createSwitchEvent(childDevice, "on") - put("lights/${getId(childDevice)}/state", [hue: level, on: true]) + put("lights/$id/state", [hue: level, on: true]) return "Setting hue to $percent" } def setColorTemperature(childDevice, huesettings) { log.debug "Executing 'setColorTemperature($huesettings)'" + def id = getId(childDevice) + if (!isOnline(id)) { + return "Bulb is unreachable" + } updateInProgress() // 153 (6500K) to 500 (2000K) def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings) createSwitchEvent(childDevice, "on") - put("lights/${getId(childDevice)}/state", [ct: ct, on: true]) + put("lights/$id/state", [ct: ct, on: true]) return "Setting color temperature to $percent" } def setColor(childDevice, huesettings) { log.debug "Executing 'setColor($huesettings)'" - + def id = getId(childDevice) + if (!isOnline(id)) { + return "Bulb is unreachable" + } updateInProgress() def value = [:] @@ -1108,15 +1144,23 @@ def setColor(childDevice, huesettings) { value.on = false createSwitchEvent(childDevice, value.on ? "on" : "off") - put("lights/${getId(childDevice)}/state", value) + put("lights/$id/state", value) return "Setting color to $value" } +def ping(childDevice) { + if (isOnline(getId(childDevice))) { + childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true) + return "Device is Online" + } else { + return "Device is Offline" + } +} + private getId(childDevice) { if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) { return childDevice.device?.deviceNetworkId[3..-1] - } - else { + } else { return childDevice.device?.deviceNetworkId.split("/")[-1] } } @@ -1127,10 +1171,13 @@ private poll() { log.debug "GET: $host$uri" sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1 HOST: ${host} - """, physicalgraph.device.Protocol.LAN, selectedHue)) } +private isOnline(id) { + return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null +} + private put(path, body) { def host = getBridgeIP() def uri = "/api/${state.username}/$path" @@ -1198,7 +1245,7 @@ def convertBulbListToMap() { if (state.bulbs instanceof java.util.List) { def map = [:] state.bulbs.unique {it.id}.each { bulb -> - map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]] + map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]] } state.bulbs = map } From 1e4f1223e7d2c38258b50ac6e4db33d9b88b0406 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Fri, 9 Sep 2016 10:50:23 -0600 Subject: [PATCH 04/10] SSVD-2798 Philips Hue: Light discovery UI issue -Keep checked lights in "lights to add list" when page refreshes --- smartapps/smartthings/hue-connect.src/hue-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index fdfb509..f0aad65 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -170,7 +170,7 @@ def bulbDiscovery() { 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 to add (${numFound} found)", multiple:true, options:newLights + input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription } section { From 01b8399893c5c02ada58885bf99ac07108fab771 Mon Sep 17 00:00:00 2001 From: jackchi Date: Fri, 9 Sep 2016 14:48:19 -0700 Subject: [PATCH 05/10] [CHF-201] Removing DTH workaround now that all events go to Kafka --- .../smartthings/cree-bulb.src/cree-bulb.groovy | 16 +--------------- .../smartpower-outlet.groovy | 16 +--------------- .../smartsense-moisture-sensor.groovy | 17 +---------------- .../smartsense-motion-sensor.groovy | 17 +---------------- .../smartsense-multi-sensor.groovy | 17 +---------------- .../smartsense-open-closed-sensor.groovy | 17 +---------------- .../smartsense-temp-humidity-sensor.groovy | 16 +--------------- .../zigbee-dimmer.src/zigbee-dimmer.groovy | 16 +--------------- .../zigbee-rgbw-bulb.groovy | 16 +--------------- .../zigbee-white-color-temperature-bulb.groovy | 16 +--------------- 10 files changed, 10 insertions(+), 154 deletions(-) diff --git a/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy b/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy index ed5e751..5837ba1 100644 --- a/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy +++ b/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy @@ -67,12 +67,6 @@ 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" @@ -96,15 +90,7 @@ def 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.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) - } + return zigbee.levelRefresh() } def refresh() { diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index b978cc0..fe9d0be 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -101,12 +101,6 @@ def parse(String description) { else { def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) - // Temporary fix for the case when Device is OFFLINE and is connected again - if (state.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 { @@ -126,15 +120,7 @@ def on() { * 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) - } + return zigbee.onOffRefresh() } def 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 2e9745d..f8ea7d9 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -101,13 +101,6 @@ 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 @@ -285,15 +278,7 @@ 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) - } + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level } def refresh() { 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 c5128b1..a278644 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -105,13 +105,6 @@ 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 @@ -296,15 +289,7 @@ 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) - } + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level } def refresh() { 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 94575bd..5fa2139 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -127,13 +127,6 @@ 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')) { @@ -378,15 +371,7 @@ 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) - } + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level } def refresh() { 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 7fbd099..8bee02a 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,13 +92,6 @@ 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 @@ -248,15 +241,7 @@ 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) - } + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level } def refresh() { 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 b320853..26955c1 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,13 +83,6 @@ 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 } @@ -253,14 +246,7 @@ 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) - } + return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level } def refresh() diff --git a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy index 1326496..ed71f9a 100644 --- a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy +++ b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy @@ -54,12 +54,6 @@ def parse(String 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 { sendEvent(event) @@ -86,15 +80,7 @@ def 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) - } + return zigbee.onOffRefresh() } def refresh() { 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 e7137b6..f578311 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -85,12 +85,6 @@ def parse(String description) { def event = zigbee.getEvent(description) if (event) { log.debug 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") { @@ -130,15 +124,7 @@ def 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) - } + return zigbee.onOffRefresh() } def refresh() { 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 de8424d..6d06b35 100644 --- a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy +++ b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy @@ -74,12 +74,6 @@ 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") { @@ -110,15 +104,7 @@ def 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) - } + return zigbee.onOffRefresh() } def refresh() { From bc817f8530aca1b703b1511a8d9346c082d3ad2d Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Fri, 9 Sep 2016 16:52:52 -0600 Subject: [PATCH 06/10] DVCSMP-2024 OSRAM: Hue is incorrectly set to be 0-360 -Hue changed to handle 0-100 according to SmartThings API --- .../osram-lightify-gardenspot-mini-rgb.groovy | 4 ++-- .../osram-lightify-led-flexible-strip-rgbw.groovy | 14 +++++++------- .../zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy b/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy index bc2d7ba..2dc1bf6 100644 --- a/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy +++ b/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy @@ -91,7 +91,7 @@ def parse(String description) { if (descMap.cluster == "0300") { if(descMap.attrId == "0000"){ //Hue Attribute - def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360) + def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100) log.debug "Hue value returned is $hueValue" sendEvent(name: "hue", value: hueValue, displayed:false) } @@ -203,7 +203,7 @@ def setLevel(value) { //input Hue Integer values; returns color name for saturation 100% private getColorName(hueValue){ - if(hueValue>360 || hueValue<0) + if(hueValue>100 || hueValue<0) return hueValue = Math.round(hueValue / 100 * 360) diff --git a/devicetypes/smartthings/osram-lightify-led-flexible-strip-rgbw.src/osram-lightify-led-flexible-strip-rgbw.groovy b/devicetypes/smartthings/osram-lightify-led-flexible-strip-rgbw.src/osram-lightify-led-flexible-strip-rgbw.groovy index e3d15d1..ef1ff1c 100644 --- a/devicetypes/smartthings/osram-lightify-led-flexible-strip-rgbw.src/osram-lightify-led-flexible-strip-rgbw.groovy +++ b/devicetypes/smartthings/osram-lightify-led-flexible-strip-rgbw.src/osram-lightify-led-flexible-strip-rgbw.groovy @@ -1,4 +1,4 @@ -/* +/* Osram Flex RGBW Light Strip Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling @@ -8,7 +8,7 @@ metadata { definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") { - + capability "Color Temperature" capability "Actuator" capability "Switch" @@ -18,7 +18,7 @@ metadata { capability "Refresh" capability "Sensor" capability "Color Control" - + attribute "colorName", "string" command "setAdjustedColor" @@ -49,7 +49,7 @@ metadata { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" } - + controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") { state "colorTemperature", action:"color temperature.setColorTemperature" } @@ -118,7 +118,7 @@ def parse(String description) { } } else if(descMap.attrId == "0000"){ //Hue Attribute - def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360) + def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100) log.debug "Hue value returned is $hueValue" sendEvent(name: "hue", value: hueValue, displayed:false) } @@ -274,7 +274,7 @@ private getGenericName(value){ //input Hue Integer values; returns color name for saturation 100% private getColorName(hueValue){ - if(hueValue>360 || hueValue<0) + if(hueValue>100 || hueValue<0) return hueValue = Math.round(hueValue / 100 * 360) @@ -449,7 +449,7 @@ def setColor(value){ def level = hex(value.level * 255 / 100) cmd << zigbeeSetLevel(level) } - + if (value.switch == "off") { cmd << "delay 150" cmd << off() 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 e7137b6..b170fc8 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -105,7 +105,7 @@ def parse(String description) { if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) { if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute - def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360) + def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100) sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed") } else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute From 3d05d42cb8940f1ad697c75c6159fce447566d35 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Sun, 11 Sep 2016 22:56:24 -0600 Subject: [PATCH 07/10] Add offline events to Philips Hue -Handles case when bridge goes offline -Handles case when indiviudual lights go offline --- .../hue-connect.src/hue-connect.groovy | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index f0aad65..577168f 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -740,16 +740,21 @@ private void checkBridgeStatus() { d.sendEvent(name: "idNumber", value: it.value.idNumber) } - if (it.value.lastActivity < time) { // it.value.lastActivity != null && - log.warn "Bridge $it.key is Offline" - d.sendEvent(name: "status", value: "Offline") - // set all lights to offline since bridge is not reachable - state.bulbs?.each {it.value.online = false} - } else { + if (it.value.lastActivity < time) { // it.value.lastActivity != null && + log.warn "Bridge $it.key is Offline" + d.sendEvent(name: "status", value: "Offline") + + state.bulbs?.each { + it.value.online = false + } + getChildDevices().each { + it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline") + } + } else { d.sendEvent(name: "status", value: "Online")//setOnline(false) - } - } - } + } + } + } } def isValidSource(macAddress) { @@ -955,6 +960,7 @@ private handlePoll(body) { } else { state.bulbs[bulb.key]?.online = false log.warn "$device is not reachable by Hue bridge" + device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline") } } } From 7149a81c85cbda67ae6ff740e0020240df588ed0 Mon Sep 17 00:00:00 2001 From: jackchi Date: Mon, 12 Sep 2016 12:00:04 -0700 Subject: [PATCH 08/10] [CHF-348] Set 10 VD required devices to report in at 5 minutes --- .../smartthings/cree-bulb.src/cree-bulb.groovy | 6 ++++-- .../smartpower-outlet.groovy | 6 ++++-- .../smartsense-moisture-sensor.groovy | 18 +++++++----------- .../smartsense-motion-sensor.groovy | 17 ++++++----------- .../smartsense-multi-sensor.groovy | 7 +++++-- .../smartsense-open-closed-sensor.groovy | 18 +++++++----------- .../smartsense-temp-humidity-sensor.groovy | 18 +++++++----------- .../zigbee-dimmer.src/zigbee-dimmer.groovy | 7 ++++--- .../zigbee-rgbw-bulb.groovy | 7 ++++--- .../zigbee-white-color-temperature-bulb.groovy | 7 ++++--- 10 files changed, 52 insertions(+), 59 deletions(-) diff --git a/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy b/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy index 5837ba1..f956800 100644 --- a/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy +++ b/devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy @@ -103,6 +103,8 @@ 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() + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) + // minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity + zigbee.onOffConfig(0, 300) + 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 fe9d0be..f4e6e0d 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -128,8 +128,10 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"]) - zigbee.onOffConfig() + powerConfig() + refresh() + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) + // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity + zigbee.onOffConfig(0, 300) + powerConfig() + refresh() } //power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s) 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 f8ea7d9..415fd73 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -292,23 +292,19 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." - def configCmds = [ + def enrollCmds = [ "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "send 0x${device.deviceNetworkId} 1 1", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500", - "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs - "send 0x${device.deviceNetworkId} 1 1", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500", - "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", - "send 0x${device.deviceNetworkId} 1 1", "delay 500" ] - return configCmds + refresh() // send refresh cmds as part of config + + // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity + // battery minReport 30 seconds, maxReportTime 6 hrs by default + return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config } def enrollResponse() { diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index a278644..1a7ecec 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -303,24 +303,19 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." - def configCmds = [ + def enrollCmds = [ "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200", - "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200", - "zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" ] - return configCmds + refresh() // send refresh cmds as part of config + // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity + // battery minReport 30 seconds, maxReportTime 6 hrs by default + return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config } def enrollResponse() { 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 5fa2139..6ce4044 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -401,13 +401,16 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) log.debug "Configuring Reporting" + // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity + // battery minReport 30 seconds, maxReportTime 6 hrs by default def configCmds = enrollResponse() + zigbee.batteryConfig() + - zigbee.temperatureConfig() + + zigbee.temperatureConfig(30, 300) + zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + 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 8bee02a..7d9c4c9 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 @@ -255,23 +255,19 @@ def refresh() { } def configure() { - sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." - def configCmds = [ + def enrollCmds = [ "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "send 0x${device.deviceNetworkId} 1 1", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500", - "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs - "send 0x${device.deviceNetworkId} 1 1", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500", - "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", - "send 0x${device.deviceNetworkId} 1 1", "delay 500" ] - return configCmds + refresh() // send refresh cmds as part of config + + // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity + // battery minReport 30 seconds, maxReportTime 6 hrs by default + return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config } def enrollResponse() { diff --git a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy index 26955c1..96b56e5 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 @@ -264,23 +264,19 @@ def refresh() } def configure() { - sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) log.debug "Configuring Reporting and Bindings." - def configCmds = [ - "zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500", - "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs - "send 0x${device.deviceNetworkId} 1 1", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500", - "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", - "send 0x${device.deviceNetworkId} 1 1", "delay 500", - + def humidityConfigCmds = [ "zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500", "zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}", "send 0x${device.deviceNetworkId} 1 1", "delay 500" ] - return configCmds + refresh() // send refresh cmds as part of config + + // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity + // battery minReport 30 seconds, maxReportTime 6 hrs by default + return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config } private hex(value) { diff --git a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy index ed71f9a..b977c8b 100644 --- a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy +++ b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy @@ -89,7 +89,8 @@ 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() + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) + // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity + zigbee.onOffConfig(0, 300) + 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 f1e760f..367e480 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -133,9 +133,10 @@ 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.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) + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) + // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity + zigbee.onOffConfig(0, 300) + 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) } def setColorTemperature(value) { diff --git a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy index 6d06b35..5a37fab 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 @@ -113,9 +113,10 @@ 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.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + // Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min + sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) + // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() } def setColorTemperature(value) { From 079919260b4124a3d8728a8a074b25de2e08dbb6 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Mon, 12 Sep 2016 17:08:57 -0600 Subject: [PATCH 09/10] CHF-348 Philips Hue: Change offline time to 16 min --- .../smartthings/hue-bloom.src/hue-bloom.groovy | 2 +- devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy | 2 +- .../smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy | 2 +- .../hue-white-ambiance-bulb.groovy | 2 +- .../smartthings/hue-connect.src/hue-connect.groovy | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy index 9f7d804..ff4405a 100644 --- a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy +++ b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy @@ -57,7 +57,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) + sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) } // parse events into attributes diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 1f63b09..6642672 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -66,7 +66,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) + sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) } // parse events into attributes diff --git a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy index b1f076d..4d8bfbb 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -50,7 +50,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) + sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) } // parse events into attributes diff --git a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy index fd8c8bf..b24b1dd 100644 --- a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy +++ b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy @@ -55,7 +55,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false) + sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) } // parse events into attributes diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 577168f..8df8621 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -724,13 +724,13 @@ private void updateBridgeStatus(childDevice) { } /** - * Check if all Hue bridges have been heard from in the last 16 minutes, if not an Offline event will be sent - * for the bridge. Also, set ID number on bridge if not done previously. + * Check if all Hue bridges have been heard from in the last 11 minutes, if not an Offline event will be sent + * for the bridge and all connected lights. Also, set ID number on bridge if not done previously. */ private void checkBridgeStatus() { def bridges = getHueBridges() - // Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer) - def time = now() - (1000 * 60 * 30) + // Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer) + def time = now() - (1000 * 60 * 11) bridges.each { def d = getChildDevice(it.value.mac) if(d) { @@ -748,7 +748,7 @@ private void checkBridgeStatus() { it.value.online = false } getChildDevices().each { - it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline") + it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false) } } else { d.sendEvent(name: "status", value: "Online")//setOnline(false) @@ -960,7 +960,7 @@ private handlePoll(body) { } else { state.bulbs[bulb.key]?.online = false log.warn "$device is not reachable by Hue bridge" - device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline") + device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true) } } } From d5329dbde3b14e578fc34c6f039fa28dc3b96d9a Mon Sep 17 00:00:00 2001 From: juano2310 Date: Mon, 12 Sep 2016 20:58:36 -0400 Subject: [PATCH 10/10] SSVD-2733 - Typo fix --- smartapps/smartthings/energy-alerts.src/energy-alerts.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy b/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy index 4a4c9ff..bd394a1 100644 --- a/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy +++ b/smartapps/smartthings/energy-alerts.src/energy-alerts.groovy @@ -64,7 +64,7 @@ def meterHandler(evt) { def lastValue = atomicState.lastValue as double atomicState.lastValue = meterValue - def dUnit ? evt.unit : "Watts" + def dUnit = evt.unit ?: "Watts" def aboveThresholdValue = aboveThreshold as int if (meterValue > aboveThresholdValue) {