From b12df3f360d103483ba227dc62d9310f8b15054d Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 11 Oct 2016 15:20:53 -0700 Subject: [PATCH 1/7] Convert setpoints in case of a temperature scale change. Removed useage of convertTemperatureIfNeeded which is an undocumented API fixed spacing On temperature change it will update all setpoints in alterSetpoint function fixed spacing issues spacing changes removed comments --- .../ecobee-thermostat.groovy | 132 ++++++++++-------- 1 file changed, 70 insertions(+), 62 deletions(-) diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 37cbb85..adf6433 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -148,14 +148,12 @@ def generateEvent(Map results) { handlerName: name] if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) { - def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F - sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue + def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() isChange = isTemperatureStateChange(device, name, value.toString()) isDisplayed = isChange event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed] } 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 + def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() event << [value: sendValue, unit: temperatureScale, displayed: false] } else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ isChange = isStateChange(device, name, value.toString()) @@ -253,7 +251,6 @@ void setCoolingSetpoint(setpoint) { def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") - if (coolingSetpoint > maxCoolingSetpoint) { coolingSetpoint = maxCoolingSetpoint } else if (coolingSetpoint < minCoolingSetpoint) { @@ -283,7 +280,6 @@ void setCoolingSetpoint(setpoint) { } void resumeProgram() { - log.debug "resumeProgram() is called" sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false) def deviceId = device.deviceNetworkId.split(/\./).last() @@ -354,7 +350,6 @@ def switchFanMode() { } def switchToFanMode(nextMode) { - log.debug "switching to fan mode: $nextMode" def returnCommand @@ -520,63 +515,56 @@ def fanAuto() { } def generateSetpointEvent() { - log.debug "Generate SetPoint Event" def mode = device.currentValue("thermostatMode") - log.debug "Current Mode = ${mode}" def heatingSetpoint = device.currentValue("heatingSetpoint") - log.debug "Heating Setpoint = ${heatingSetpoint}" - def coolingSetpoint = device.currentValue("coolingSetpoint") - log.debug "Cooling Setpoint = ${coolingSetpoint}" - def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint") def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") def minHeatingSetpoint = device.currentValue("minHeatingSetpoint") def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") - if(location.temperatureScale == "C") - { - maxHeatingSetpoint = roundC(maxHeatingSetpoint) - maxCoolingSetpoint = roundC(maxCoolingSetpoint) - minHeatingSetpoint = roundC(minHeatingSetpoint) - minCoolingSetpoint = roundC(minCoolingSetpoint) - heatingSetpoint = roundC(heatingSetpoint) - coolingSetpoint = roundC(coolingSetpoint) + if(location.temperatureScale == "C") { + maxHeatingSetpoint = maxHeatingSetpoint > 40 ? roundC(convertFtoC(maxHeatingSetpoint)) : roundC(maxHeatingSetpoint) + maxCoolingSetpoint = maxCoolingSetpoint > 40 ? roundC(convertFtoC(maxCoolingSetpoint)) : roundC(maxCoolingSetpoint) + minHeatingSetpoint = minHeatingSetpoint > 40 ? roundC(convertFtoC(minHeatingSetpoint)) : roundC(minHeatingSetpoint) + minCoolingSetpoint = minCoolingSetpoint > 40 ? roundC(convertFtoC(minCoolingSetpoint)) : roundC(minCoolingSetpoint) + heatingSetpoint = heatingSetpoint > 40 ? roundC(convertFtoC(heatingSetpoint)) : roundC(heatingSetpoint) + coolingSetpoint = coolingSetpoint > 40 ? roundC(convertFtoC(coolingSetpoint)) : roundC(coolingSetpoint) + } else { + maxHeatingSetpoint = maxHeatingSetpoint < 40 ? roundC(convertCtoF(maxHeatingSetpoint)) : maxHeatingSetpoint + maxCoolingSetpoint = maxCoolingSetpoint < 40 ? roundC(convertCtoF(maxCoolingSetpoint)) : maxCoolingSetpoint + minHeatingSetpoint = minHeatingSetpoint < 40 ? roundC(convertCtoF(minHeatingSetpoint)) : minHeatingSetpoint + minCoolingSetpoint = minCoolingSetpoint < 40 ? roundC(convertCtoF(minCoolingSetpoint)) : minCoolingSetpoint + heatingSetpoint = heatingSetpoint < 40 ? roundC(convertCtoF(heatingSetpoint)) : heatingSetpoint + coolingSetpoint = coolingSetpoint < 40 ? roundC(convertCtoF(coolingSetpoint)) : coolingSetpoint } + log.debug "Current Mode = ${mode}" + log.debug "Heating Setpoint = ${heatingSetpoint}" + log.debug "Cooling Setpoint = ${coolingSetpoint}" sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale) sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale) - + sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) + sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) if (mode == "heat") { - sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) - } else if (mode == "cool") { - sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) - } else if (mode == "auto") { - sendEvent("name":"thermostatSetpoint", "value":"Auto") - } else if (mode == "off") { - sendEvent("name":"thermostatSetpoint", "value":"Off") - } else if (mode == "auxHeatOnly") { - sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) - } - } void raiseSetpoint() { @@ -585,21 +573,31 @@ void raiseSetpoint() { def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint") def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") - if (mode == "off" || mode == "auto") { log.warn "this mode: $mode does not allow raiseSetpoint" } else { + def heatingSetpoint = device.currentValue("heatingSetpoint") def coolingSetpoint = device.currentValue("coolingSetpoint") def thermostatSetpoint = device.currentValue("thermostatSetpoint") + + if (location.temperatureScale == "C") { + maxHeatingSetpoint = maxHeatingSetpoint > 40 ? convertFtoC(maxHeatingSetpoint) : maxHeatingSetpoint + maxCoolingSetpoint = maxCoolingSetpoint > 40 ? convertFtoC(maxCoolingSetpoint) : maxCoolingSetpoint + heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint + } else { + maxHeatingSetpoint = maxHeatingSetpoint < 40 ? convertCtoF(maxHeatingSetpoint) : maxHeatingSetpoint + maxCoolingSetpoint = maxCoolingSetpoint < 40 ? convertCtoF(maxCoolingSetpoint) : maxCoolingSetpoint + heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint + } + log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" - if (device.latestState('thermostatSetpoint')) { - targetvalue = device.latestState('thermostatSetpoint').value - targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble() - } else { - targetvalue = 0 - } + targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5 if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) { @@ -622,20 +620,29 @@ void lowerSetpoint() { def minHeatingSetpoint = device.currentValue("minHeatingSetpoint") def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") - if (mode == "off" || mode == "auto") { log.warn "this mode: $mode does not allow lowerSetpoint" } else { def heatingSetpoint = device.currentValue("heatingSetpoint") def coolingSetpoint = device.currentValue("coolingSetpoint") def thermostatSetpoint = device.currentValue("thermostatSetpoint") - log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" - if (device.latestState('thermostatSetpoint')) { - targetvalue = device.latestState('thermostatSetpoint').value - targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble() + + if (location.temperatureScale == "C") { + minHeatingSetpoint = minHeatingSetpoint > 40 ? convertFtoC(minHeatingSetpoint) : minHeatingSetpoint + minCoolingSetpoint = minCoolingSetpoint > 40 ? convertFtoC(minCoolingSetpoint) : minCoolingSetpoint + heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint } else { - targetvalue = 0 + minHeatingSetpoint = minHeatingSetpoint < 40 ? convertCtoF(minHeatingSetpoint) : minHeatingSetpoint + minCoolingSetpoint = minCoolingSetpoint < 40 ? convertCtoF(minCoolingSetpoint) : minCoolingSetpoint + heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint } + log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" + + targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5 if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) { @@ -653,7 +660,6 @@ void lowerSetpoint() { //called by raiseSetpoint() and lowerSetpoint() void alterSetpoint(temp) { - def mode = device.currentValue("thermostatMode") if (mode == "off" || mode == "auto") { @@ -666,6 +672,18 @@ void alterSetpoint(temp) { def targetHeatingSetpoint def targetCoolingSetpoint + def temperatureScaleHasChanged = false + + if (location.temperatureScale == "C") { + if ( heatingSetpoint > 40.0 || coolingSetpoint > 40.0 ) { + temperatureScaleHasChanged = true + } + } else { + if ( heatingSetpoint < 40.0 || coolingSetpoint < 40.0 ) { + temperatureScaleHasChanged = true + } + } + //step1: check thermostatMode, enforce limits before sending request to cloud if (mode == "heat" || mode == "auxHeatOnly"){ if (temp.value > coolingSetpoint){ @@ -707,17 +725,18 @@ void alterSetpoint(temp) { sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false) } } + + if ( temperatureScaleHasChanged ) + generateSetpointEvent() generateStatusEvent() } } def generateStatusEvent() { - def mode = device.currentValue("thermostatMode") def heatingSetpoint = device.currentValue("heatingSetpoint") def coolingSetpoint = device.currentValue("coolingSetpoint") def temperature = device.currentValue("temperature") - def statusText log.debug "Generate Status Event for Mode = ${mode}" @@ -727,36 +746,25 @@ def generateStatusEvent() { log.debug "HVAC Mode = ${mode}" if (mode == "heat") { - if (temperature >= heatingSetpoint) statusText = "Right Now: Idle" else statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}" - } else if (mode == "cool") { - if (temperature <= coolingSetpoint) statusText = "Right Now: Idle" else statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}" - } else if (mode == "auto") { - statusText = "Right Now: Auto" - } else if (mode == "off") { - statusText = "Right Now: Off" - } else if (mode == "auxHeatOnly") { - statusText = "Emergency Heat" - } else { - statusText = "?" - } + log.debug "Generate Status Event = ${statusText}" sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true) } @@ -770,7 +778,7 @@ def roundC (tempC) { } def convertFtoC (tempF) { - return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2) + return ((Math.round(((tempF - 32)*(5/9)) * 2))/2).toDouble() } def convertCtoF (tempC) { From 94f57dd249268bcb6e3ba014a482ac28f2db5f11 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Fri, 14 Oct 2016 16:48:14 -0600 Subject: [PATCH 2/7] DVCSMP-2088 Philips Hue: Add explicit online events --- .../hue-bloom.src/hue-bloom.groovy | 5 +- .../hue-bridge.src/hue-bridge.groovy | 5 +- .../smartthings/hue-bulb.src/hue-bulb.groovy | 5 +- .../hue-lux-bulb.src/hue-lux-bulb.groovy | 5 +- .../hue-white-ambiance-bulb.groovy | 5 +- .../hue-connect.src/hue-connect.groovy | 408 +++++++++--------- 6 files changed, 206 insertions(+), 227 deletions(-) diff --git a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy index e6a02ab..ae48f0d 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 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -172,6 +172,3 @@ def verifyPercent(percent) { } } -def ping() { - log.debug "${parent.ping(this)}" -} diff --git a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy index fee818f..e7fbce1 100644 --- a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy +++ b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy @@ -45,7 +45,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -87,6 +87,3 @@ def parse(description) { results } -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 4dbae74..143b0b3 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 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -188,6 +188,3 @@ def verifyPercent(percent) { } } -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 158e11a..d855d38 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 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -93,6 +93,3 @@ void 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 10315a5..718ed57 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 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -107,6 +107,3 @@ void 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 d39d451..7ee6e60 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -8,7 +8,7 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License @@ -24,7 +24,7 @@ definition( category: "SmartThings Labs", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png", - singleInstance: true + singleInstance: true ) preferences { @@ -110,7 +110,7 @@ def bridgeLinking() { if (state.refreshUsernameNeeded) { paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. " } else { - paragraphText = "Press the button on your Hue Bridge to setup a link. " + paragraphText = "Press the button on your Hue Bridge to setup a link. " } } else { paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next." @@ -198,7 +198,7 @@ void ssdpSubscribe() { private sendDeveloperReq() { def token = app.id - def host = getBridgeIP() + def host = getBridgeIP() sendHubCommand(new physicalgraph.device.HubAction([ method: "POST", path: "/api", @@ -209,7 +209,7 @@ private sendDeveloperReq() { } private discoverHueBulbs() { - def host = getBridgeIP() + def host = getBridgeIP() sendHubCommand(new physicalgraph.device.HubAction([ method: "GET", path: "/api/${state.username}/lights", @@ -231,8 +231,8 @@ private verifyHueBridge(String deviceNetworkId, String host) { private verifyHueBridges() { def devices = getHueBridges().findAll { it?.value?.verified != true } devices.each { - def ip = convertHexToIP(it.value.networkAddress) - def port = convertHexToInt(it.value.deviceAddress) + def ip = convertHexToIP(it.value.networkAddress) + def port = convertHexToInt(it.value.deviceAddress) verifyHueBridge("${it.value.mac}", (ip + ":" + port)) } } @@ -261,7 +261,7 @@ Map bulbsDiscovered() { bulbs.each { def value = "${it.name}" def key = app.id +"/"+ it.id - logg += "$value - $key, " + logg += "$value - $key, " bulbmap["${key}"] = value } } @@ -288,22 +288,22 @@ def installed() { def updated() { log.trace "Updated with settings: ${settings}" unsubscribe() - unschedule() + unschedule() initialize() } def initialize() { log.debug "Initializing" - unsubscribe(bridge) - state.inBulbDiscovery = false - state.bridgeRefreshCount = 0 - state.bulbRefreshCount = 0 + unsubscribe(bridge) + state.inBulbDiscovery = false + state.bridgeRefreshCount = 0 + state.bulbRefreshCount = 0 state.updating = false if (selectedHue) { - addBridge() - addBulbs() - doDeviceSync() - runEvery5Minutes("doDeviceSync") + addBridge() + addBulbs() + doDeviceSync() + runEvery5Minutes("doDeviceSync") } } @@ -369,7 +369,6 @@ def addBulbs() { if (d) { log.debug "created ${d.displayName} with id $dni" d.completedSetup = true - d.refresh() } } else { log.debug "$dni in not longer paired to the Hue Bridge or ID changed" @@ -399,23 +398,23 @@ def addBridge() { if(vbridge) { def d = getChildDevice(selectedHue) if(!d) { - // compatibility with old devices - def newbridge = true - childDevices.each { - if (it.getDeviceDataByName("mac")) { - def newDNI = "${it.getDeviceDataByName("mac")}" - if (newDNI != it.deviceNetworkId) { - def oldDNI = it.deviceNetworkId - log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" - it.setDeviceNetworkId("${newDNI}") + // compatibility with old devices + def newbridge = true + childDevices.each { + if (it.getDeviceDataByName("mac")) { + def newDNI = "${it.getDeviceDataByName("mac")}" + if (newDNI != it.deviceNetworkId) { + def oldDNI = it.deviceNetworkId + log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" + it.setDeviceNetworkId("${newDNI}") if (oldDNI == selectedHue) { app.updateSetting("selectedHue", newDNI) } - newbridge = false - } - } - } - if (newbridge) { + newbridge = false + } + } + } + if (newbridge) { // Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge def idNumber = getBridgeIdNumber(selectedHue) d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"]) @@ -426,9 +425,11 @@ def addBridge() { d.completedSetup = true log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" def childDevice = getChildDevice(d.deviceNetworkId) + childDevice?.sendEvent(name: "status", value: "Online") + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) updateBridgeStatus(childDevice) - childDevice.sendEvent(name: "idNumber", value: idNumber) + 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) @@ -580,47 +581,47 @@ void usernameHandler(physicalgraph.device.HubResponse hubResponse) { @Deprecated def locationHandler(evt) { def description = evt.description - log.trace "Location: $description" + log.trace "Location: $description" def hub = evt?.hubId def parsedEvent = parseLanMessage(description) parsedEvent << ["hub":hub] if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) { - //SSDP DISCOVERY EVENTS + //SSDP DISCOVERY EVENTS log.trace "SSDP DISCOVERY EVENTS" def bridges = getHueBridges() log.trace bridges.toString() if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) { - //bridge does not exist + //bridge does not exist log.trace "Adding bridge ${parsedEvent.ssdpUSN}" bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] } else { // update the values - def ip = convertHexToIP(parsedEvent.networkAddress) - def port = convertHexToInt(parsedEvent.deviceAddress) - def host = ip + ":" + port + def ip = convertHexToIP(parsedEvent.networkAddress) + def port = convertHexToInt(parsedEvent.deviceAddress) + def host = ip + ":" + port log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host." - def dstate = bridges."${parsedEvent.ssdpUSN.toString()}" + def dstate = bridges."${parsedEvent.ssdpUSN.toString()}" def dni = "${parsedEvent.mac}" - def d = getChildDevice(dni) - def networkAddress = null - if (!d) { - childDevices.each { - if (it.getDeviceDataByName("mac")) { - def newDNI = "${it.getDeviceDataByName("mac")}" + def d = getChildDevice(dni) + def networkAddress = null + if (!d) { + childDevices.each { + if (it.getDeviceDataByName("mac")) { + def newDNI = "${it.getDeviceDataByName("mac")}" d = it - if (newDNI != it.deviceNetworkId) { - def oldDNI = it.deviceNetworkId - log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" - it.setDeviceNetworkId("${newDNI}") + if (newDNI != it.deviceNetworkId) { + def oldDNI = it.deviceNetworkId + log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" + it.setDeviceNetworkId("${newDNI}") if (oldDNI == selectedHue) { app.updateSetting("selectedHue", newDNI) } - doDeviceSync() - } - } - } + doDeviceSync() + } + } + } } else { updateBridgeStatus(d) if (d.getDeviceDataByName("networkAddress")) { @@ -628,22 +629,22 @@ def locationHandler(evt) { } else { networkAddress = d.latestState('networkAddress').stringValue } - log.trace "Host: $host - $networkAddress" - if(host != networkAddress) { - log.debug "Device's port or ip changed for device $d..." - dstate.ip = ip - dstate.port = port - dstate.name = "Philips hue ($ip)" - d.sendEvent(name:"networkAddress", value: host) - d.updateDataValue("networkAddress", host) - } - } + log.trace "Host: $host - $networkAddress" + if(host != networkAddress) { + log.debug "Device's port or ip changed for device $d..." + dstate.ip = ip + dstate.port = port + dstate.name = "Philips hue ($ip)" + d.sendEvent(name:"networkAddress", value: host) + d.updateDataValue("networkAddress", host) + } + } } } else if (parsedEvent.headers && parsedEvent.body) { log.trace "HUE BRIDGE RESPONSES" def headerString = parsedEvent.headers.toString() if (headerString?.contains("xml")) { - log.trace "description.xml response (application/xml)" + log.trace "description.xml response (application/xml)" def body = new XmlSlurper().parseText(parsedEvent.body) if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) { def bridges = getHueBridges() @@ -655,7 +656,7 @@ def locationHandler(evt) { } } } else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) { - log.trace "description.xml response (application/json)" + log.trace "description.xml response (application/json)" def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body) if (body.success != null) { if (body.success[0] != null) { @@ -692,7 +693,7 @@ def doDeviceSync(){ poll() ssdpSubscribe() discoverBridges() - checkBridgeStatus() + checkBridgeStatus() } /** @@ -705,10 +706,14 @@ def doDeviceSync(){ private void updateBridgeStatus(childDevice) { // Update activity timestamp if child device is a valid bridge def vbridges = getVerifiedHueBridges() - def vbridge = vbridges.find {"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()} + def vbridge = vbridges.find { + "${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase() + } vbridge?.value?.lastActivity = now() - if(vbridge) { + if (vbridge && childDevice?.device?.currentValue("status") == "Offline") { + log.debug "$childDevice is back Online" childDevice?.sendEvent(name: "status", value: "Online") + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) } } @@ -717,29 +722,37 @@ private void updateBridgeStatus(childDevice) { * 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 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) { - // Set id number on bridge if not done - if (it.value.idNumber == null) { - it.value.idNumber = getBridgeIdNumber(it.value.serialNumber) + def bridges = getHueBridges() + // 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) { + // Set id number on bridge if not done + if (it.value.idNumber == null) { + it.value.idNumber = getBridgeIdNumber(it.value.serialNumber) d.sendEvent(name: "idNumber", value: it.value.idNumber) - } + } if (it.value.lastActivity < time) { // it.value.lastActivity != null && - log.warn "Bridge $it.value.idNumber is Offline" - d.sendEvent(name: "status", value: "Offline") + if (d.currentStatus == "Online") { + log.warn "$d is Offline" + d.sendEvent(name: "status", value: "Offline") + d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true) - state.bulbs?.each { - it.value.online = false + Calendar currentTime = Calendar.getInstance() + getChildDevices().each { + def id = getId(it) + if (state.bulbs[id]?.online == true) { + state.bulbs[id]?.online = false + state.bulbs[id]?.unreachableSince = currentTime.getTimeInMillis() + it.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true) + } + } } - getChildDevices().each { - it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false) - } - } else { + } else if (d.currentStatus == "Offline") { + log.debug "$d is back Online" + d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) d.sendEvent(name: "status", value: "Online")//setOnline(false) } } @@ -791,24 +804,24 @@ def parse(childDevice, description) { def parsedEvent = parseLanMessage(description) if (parsedEvent.headers && parsedEvent.body) { def headerString = parsedEvent.headers.toString() - def bodyString = parsedEvent.body.toString() + def bodyString = parsedEvent.body.toString() if (headerString?.contains("json")) { - def body - try { - body = new groovy.json.JsonSlurper().parseText(bodyString) - } catch (all) { - log.warn "Parsing Body failed - trying again..." - poll() - } - if (body instanceof java.util.Map) { - // get (poll) reponse - return handlePoll(body) + def body + try { + body = new groovy.json.JsonSlurper().parseText(bodyString) + } catch (all) { + log.warn "Parsing Body failed - trying again..." + poll() + } + if (body instanceof java.util.Map) { + // get (poll) reponse + return handlePoll(body) } else { //put response return handleCommandResponse(body) - } - } - } else { + } + } + } else { log.debug "parse - got something other than headers,body..." return [] } @@ -829,19 +842,19 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) { device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"]) // Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary return - } + } if (hue != null) { // 0-65535 def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false] - } + } if (sat != null) { // 0-254 def value = Math.round(sat * 100 / 254) as int - events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false] - } + events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false] + } // Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex if (xy != null && colormode != "hs") { @@ -943,12 +956,9 @@ private handleCommandResponse(body) { updates.each { childDeviceNetworkId, params -> def device = getChildDevice(childDeviceNetworkId) 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) - } + sendBasicEvents(device, "on", params.on) + sendBasicEvents(device, "bri", params.bri) + sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) } return [] } @@ -981,39 +991,38 @@ private handlePoll(body) { def bulbs = getChildDevices() for (bulb in body) { - def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} + def device = bulbs.find { it.deviceNetworkId == "${app.id}/${bulb.key}" } if (device) { if (bulb.value.state?.reachable) { - if (state.bulbs[bulb.key]?.online == false) { + if (state.bulbs[bulb.key]?.online == false || state.bulbs[bulb.key]?.online == null) { // 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) + device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) log.debug "$device is Online" } // Mark light as "online" state.bulbs[bulb.key]?.unreachableSince = null 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 { if (state.bulbs[bulb.key]?.unreachableSince == null) { // Store the first time where device was reported as "unreachable" state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis() - } else if (state.bulbs[bulb.key]?.online) { + } + if (state.bulbs[bulb.key]?.online || state.bulbs[bulb.key]?.online == null) { // Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary - if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) { + if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis() || state.bulbs[bulb.key]?.online == null) { log.warn "$device went Offline" state.bulbs[bulb.key]?.online = false - device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true) + device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true) } } log.warn "$device may not reachable by Hue bridge" } + // 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) + } } } return [] @@ -1032,16 +1041,16 @@ def updateHandler() { def hubVerification(bodytext) { log.trace "Bridge sent back description.xml for verification" - def body = new XmlSlurper().parseText(bodytext) - if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) { - def bridges = getHueBridges() - def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())} - if (bridge) { - bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true] - } else { - log.error "/description.xml returned a bridge that didn't exist" - } - } + def body = new XmlSlurper().parseText(bodytext) + if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) { + def bridges = getHueBridges() + def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())} + if (bridge) { + bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true] + } else { + log.error "/description.xml returned a bridge that didn't exist" + } + } } def on(childDevice) { @@ -1050,7 +1059,7 @@ def on(childDevice) { updateInProgress() createSwitchEvent(childDevice, "on") put("lights/$id/state", [on: true]) - return "Bulb is turning On" + return "Bulb is turning On" } def off(childDevice) { @@ -1059,15 +1068,15 @@ def off(childDevice) { updateInProgress() createSwitchEvent(childDevice, "off") put("lights/$id/state", [on: false]) - return "Bulb is turning Off" + return "Bulb is turning Off" } def setLevel(childDevice, percent) { log.debug "Executing 'setLevel'" def id = getId(childDevice) - updateInProgress() + updateInProgress() // 1 - 254 - def level + def level if (percent == 1) level = 1 else @@ -1101,7 +1110,7 @@ def setSaturation(childDevice, percent) { def setHue(childDevice, percent) { log.debug "Executing 'setHue($percent)'" def id = getId(childDevice) - updateInProgress() + 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? @@ -1113,7 +1122,7 @@ def setHue(childDevice, percent) { def setColorTemperature(childDevice, huesettings) { log.debug "Executing 'setColorTemperature($huesettings)'" def id = getId(childDevice) - updateInProgress() + updateInProgress() // 153 (6500K) to 500 (2000K) def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings) createSwitchEvent(childDevice, "on") @@ -1122,14 +1131,14 @@ def setColorTemperature(childDevice, huesettings) { } def setColor(childDevice, huesettings) { - log.debug "Executing 'setColor($huesettings)'" + log.debug "Executing 'setColor($huesettings)'" def id = getId(childDevice) - updateInProgress() + updateInProgress() - def value = [:] - def hue = null - def sat = null - def xy = null + def value = [:] + def hue = null + def sat = null + def xy = null // Prefer hue/sat over hex to make sure it works with the majority of the smartapps if (huesettings.hue != null || huesettings.sat != null) { @@ -1154,48 +1163,32 @@ def setColor(childDevice, huesettings) { // value.xy = calculateXY(hex, model) // Once groups, or scenes are introduced it might be a good idea to use unique models again value.xy = calculateXY(hex) - } + } */ - // Default behavior is to turn light on - value.on = true + // Default behavior is to turn light on + value.on = true - if (huesettings.level != null) { - if (huesettings.level <= 0) - value.on = false - else if (huesettings.level == 1) - value.bri = 1 - else + if (huesettings.level != null) { + if (huesettings.level <= 0) + value.on = false + else if (huesettings.level == 1) + value.bri = 1 + else value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254) - } - value.alert = huesettings.alert ? huesettings.alert : "none" - value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4 + } + value.alert = huesettings.alert ? huesettings.alert : "none" + value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4 - // Make sure to turn off light if requested - if (huesettings.switch == "off") - value.on = false + // Make sure to turn off light if requested + if (huesettings.switch == "off") + value.on = false createSwitchEvent(childDevice, value.on ? "on" : "off") - put("lights/$id/state", value) + put("lights/$id/state", value) return "Setting color to $value" } -def ping(childDevice) { - if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) { - if (childDevice.device?.currentValue("status")?.equalsIgnoreCase("Online")) { - childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Bridge is reachable", displayed: false, isStateChange: true) - return "Bridge is Online" - } else { - return "Bridge is Offline" - } - } else 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] @@ -1246,30 +1239,30 @@ private getBridgeIdNumber(serialNumber) { private getBridgeIP() { def host = null if (selectedHue) { - def d = getChildDevice(selectedHue) - if (d) { - if (d.getDeviceDataByName("networkAddress")) - host = d.getDeviceDataByName("networkAddress") - else - host = d.latestState('networkAddress').stringValue - } - if (host == null || host == "") { - def serialNumber = selectedHue - def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value - if (!bridge) { - bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value - } - if (bridge?.ip && bridge?.port) { - if (bridge?.ip.contains(".")) - host = "${bridge?.ip}:${bridge?.port}" - else - host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}" - } else if (bridge?.networkAddress && bridge?.deviceAddress) - host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}" - } - log.trace "Bridge: $selectedHue - Host: $host" - } - return host + def d = getChildDevice(selectedHue) + if (d) { + if (d.getDeviceDataByName("networkAddress")) + host = d.getDeviceDataByName("networkAddress") + else + host = d.latestState('networkAddress').stringValue + } + if (host == null || host == "") { + def serialNumber = selectedHue + def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value + if (!bridge) { + bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value + } + if (bridge?.ip && bridge?.port) { + if (bridge?.ip.contains(".")) + host = "${bridge?.ip}:${bridge?.port}" + else + host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}" + } else if (bridge?.networkAddress && bridge?.deviceAddress) + host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}" + } + log.trace "Bridge: $selectedHue - Host: $host" + } + return host } private Integer convertHexToInt(hex) { @@ -1309,7 +1302,7 @@ private List getRealHubFirmwareVersions() { * @param childDevice device to send event for * @param setSwitch The new switch state, "on" or "off" * @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since - * that should generate "off" instead of level change + * that should generate "off" instead of level change */ private void createSwitchEvent(childDevice, setSwitch, setLevel = null) { @@ -1452,8 +1445,8 @@ private float[] calculateXY(colorStr, model = null) { xy[0] = closestPoint.x; xy[1] = closestPoint.y; } - // xy[0] = PHHueHelper.precision(4, xy[0]); - // xy[1] = PHHueHelper.precision(4, xy[1]); + // xy[0] = PHHueHelper.precision(4, xy[0]); + // xy[1] = PHHueHelper.precision(4, xy[1]); // TODO needed, assume it just sets number of decimals? @@ -1471,7 +1464,7 @@ private float[] calculateXY(colorStr, model = null) { * * @param points the float array contain x and the y value. [x,y] * @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut. - * If this value is empty the default gamut values are used. + * If this value is empty the default gamut values are used. * @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned */ private String colorFromXY(points, model ) { @@ -1785,3 +1778,4 @@ def hsvToHex(hue, sat, value = 100){ return "#$r1$g1$b1" } + From b582c3d832335cd1f17fa361e80651175a426148 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Tue, 18 Oct 2016 15:47:49 -0600 Subject: [PATCH 3/7] DVCSMP-2108 LIFX: Add devicewatch support --- .../lifx-color-bulb.groovy | 38 ++++++++-------- .../lifx-white-bulb.groovy | 41 ++++++++--------- .../lifx-connect.src/lifx-connect.groovy | 44 ++++++++++++++----- 3 files changed, 71 insertions(+), 52 deletions(-) diff --git a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy index b93235e..5ecfae0 100644 --- a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy +++ b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy @@ -11,9 +11,9 @@ metadata { capability "Color Temperature" capability "Switch" capability "Switch Level" // brightness - capability "Polling" capability "Refresh" capability "Sensor" + capability "Health Check" } simulator { @@ -23,7 +23,6 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { - attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" @@ -64,12 +63,8 @@ metadata { } } -// parse events into attributes -def parse(String description) { - if (description == 'updated') { - return // don't poll when config settings is being updated as it may time out - } - poll() +void installed() { + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}") } // handle commands @@ -141,7 +136,6 @@ def setLevel(percentage) { percentage = 1 // clamp to 1% } if (percentage == 0) { - sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update return off() // if the brightness is set to 0, just turn it off } parent.logErrors(logObject:log) { @@ -193,14 +187,17 @@ def off() { return [] } -def poll() { - log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" +def refresh() { + log.debug "Executing 'refresh'" + def resp = parent.apiGET("/lights/${selector()}") if (resp.status == 404) { - sendEvent(name: "switch", value: "unreachable") + state.online = false + sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false) + log.warn "$device is Offline" return [] } else if (resp.status != 200) { - log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}") return [] } def data = resp.data[0] @@ -209,19 +206,20 @@ def poll() { sendEvent(name: "label", value: data.label) sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) - sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") + sendEvent(name: "switch", value: data.power) sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int)) sendEvent(name: "hue", value: data.color.hue / 3.6) sendEvent(name: "saturation", value: data.color.saturation * 100) sendEvent(name: "colorTemperature", value: data.color.kelvin) - sendEvent(name: "model", value: "${data.product.company} ${data.product.name}") + sendEvent(name: "model", value: data.product.name) - return [] + if (data.connected) { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) + log.debug "$device is Online" + } else { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) + log.warn "$device is Offline" } - -def refresh() { - log.debug "Executing 'refresh'" - poll() } def selector() { diff --git a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy index 54a78df..16e7c57 100644 --- a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy +++ b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy @@ -10,9 +10,9 @@ metadata { capability "Color Temperature" capability "Switch" capability "Switch Level" // brightness - capability "Polling" capability "Refresh" capability "Sensor" + capability "Health Check" } simulator { @@ -22,13 +22,12 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { - attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" - } + tileAttribute ("device.level", key: "SLIDER_CONTROL") { attributeState "level", action:"switch level.setLevel" } @@ -53,15 +52,10 @@ metadata { main "switch" details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) } - } -// parse events into attributes -def parse(String description) { - if (description == 'updated') { - return // don't poll when config settings is being updated as it may time out - } - poll() +void installed() { + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}") } // handle commands @@ -71,7 +65,6 @@ def setLevel(percentage) { percentage = 1 // clamp to 1% } if (percentage == 0) { - sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update return off() // if the brightness is set to 0, just turn it off } parent.logErrors(logObject:log) { @@ -123,14 +116,17 @@ def off() { return [] } -def poll() { - log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" +def refresh() { + log.debug "Executing 'refresh'" + def resp = parent.apiGET("/lights/${selector()}") if (resp.status == 404) { - sendEvent(name: "switch", value: "unreachable") + state.online = false + sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false) + log.warn "$device is Offline" return [] } else if (resp.status != 200) { - log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}") return [] } def data = resp.data[0] @@ -138,16 +134,17 @@ def poll() { sendEvent(name: "label", value: data.label) sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) - sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") + sendEvent(name: "switch", value: data.power) sendEvent(name: "colorTemperature", value: data.color.kelvin) sendEvent(name: "model", value: data.product.name) - return [] -} - -def refresh() { - log.debug "Executing 'refresh'" - poll() + if (data.connected) { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) + log.debug "$device is Online" + } else { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) + log.warn "$device is Offline" + } } def selector() { diff --git a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy index 7832b68..7fc13c9 100644 --- a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy +++ b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy @@ -242,8 +242,6 @@ def installed() { } else { initialize() } - // Check for new devices and remove old ones every 3 hours - runEvery3Hours('updateDevices') } // called after settings are changed @@ -271,6 +269,8 @@ private removeChildDevices(devices) { def initialize() { log.debug "initialize" updateDevices() + // Check for new devices and remove old ones every 3 hours + runEvery5Minutes('updateDevices') } // Misc @@ -376,7 +376,7 @@ def updateDevices() { def data = [ label: device.label, level: Math.round((device.brightness ?: 1) * 100), - switch: device.connected ? device.power : "unreachable", + switch: device.power, colorTemperature: device.color.kelvin ] if (device.product.capabilities.has_color) { @@ -387,18 +387,42 @@ def updateDevices() { } else { childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data) } + childDevice?.completedSetup = true + } else { + if (device.product.capabilities.has_color) { + sendEvent(name: "color", value: colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)) + sendEvent(name: "hue", value: device.color.hue / 3.6) + sendEvent(name: "saturation", value: device.color.saturation * 100) + } + childDevice.sendEvent(name: "label", value: device.label) + childDevice.sendEvent(name: "level", value: Math.round((device.brightness ?: 1) * 100)) + childDevice.sendEvent(name: "switch.setLevel", value: Math.round((device.brightness ?: 1) * 100)) + childDevice.sendEvent(name: "switch", value: device.power) + childDevice.sendEvent(name: "colorTemperature", value: device.color.kelvin) + childDevice.sendEvent(name: "model", value: device.product.name) } + + if (state.devices[device.id] == null) { + // State missing, add it and set it to opposite status as current status to provoke event below + state.devices[device.id] = [online : !device.connected] + } + + if (!state.devices[device.id]?.online && device.connected) { + // Device came online after being offline + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) + log.debug "$device is back Online" + } else if (state.devices[device.id]?.online && !device.connected) { + // Device went offline after being online + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) + log.debug "$device went Offline" + } + state.devices[device.id] = [online: device.connected] } getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") + state.devices[it.deviceNetworkId] = null deleteChildDevice(it.deviceNetworkId) } - runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block } -def refreshDevices() { - log.info("Refreshing all devices...") - getChildDevices().each { device -> - device.refresh() - } -} + From 5e48e710d4da3c15e0fc03d11b17c3484a09647d Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Mon, 24 Oct 2016 14:44:08 -0600 Subject: [PATCH 4/7] DVCSMP-2168 Philips Hue/LIFX: Send device watch registration -Send device watch register events in update() --- smartapps/smartthings/hue-connect.src/hue-connect.groovy | 9 +++++++++ .../smartthings/lifx-connect.src/lifx-connect.groovy | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 7ee6e60..621d0d0 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -299,6 +299,7 @@ def initialize() { state.bridgeRefreshCount = 0 state.bulbRefreshCount = 0 state.updating = false + setupDeviceWatch() if (selectedHue) { addBridge() addBulbs() @@ -321,6 +322,14 @@ def uninstalled(){ state.username = null } +private setupDeviceWatch() { + def hub = location.hubs[0] + // Make sure that all child devices are enrolled in device watch + getChildDevices().each { + it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}") + } +} + private upgradeDeviceType(device, newHueType) { def deviceType = getDeviceType(newHueType) diff --git a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy index 7fc13c9..41f20ef 100644 --- a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy +++ b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy @@ -271,9 +271,17 @@ def initialize() { updateDevices() // Check for new devices and remove old ones every 3 hours runEvery5Minutes('updateDevices') + setupDeviceWatch() } // Misc +private setupDeviceWatch() { + def hub = location.hubs[0] + // Make sure that all child devices are enrolled in device watch + getChildDevices().each { + it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}") + } +} Map apiRequestHeaders() { return ["Authorization": "Bearer ${state.lifxAccessToken}", From 583d42df1359c6492bbb77c6a2964973681ffbd2 Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Tue, 25 Oct 2016 09:10:39 -0500 Subject: [PATCH 5/7] Remove read attribute for new mfg code Because of the interaction between the DTH running in the cloud and the one running in appengine, we can't make this change yet as for unupdated devices the humidity gets replaced with null. So we back out the call with the new mfgID as there aren't devices out there yet using it. This relates to: https://smartthings.atlassian.net/browse/DPROT-183 --- .../smartsense-temp-humidity-sensor.groovy | 1 - 1 file changed, 1 deletion(-) 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 638b9d4..98af1a4 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,7 +264,6 @@ def refresh() { log.debug "refresh temperature, humidity, and battery" return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware - zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware zigbee.readAttribute(0x0402, 0x0000) + zigbee.readAttribute(0x0001, 0x0020) } From b8111e8760647ebb5bad22073c967ab61962db6c Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Tue, 25 Oct 2016 15:17:34 -0600 Subject: [PATCH 6/7] Revert LIFX device watch DVCSMP-2108 LIFX: Add devicewatch support DVCSMP-2168 LIFX: Send device watch registration events in update() Left Hue change for second ticket above --- .../lifx-color-bulb.groovy | 37 ++++++------- .../lifx-white-bulb.groovy | 38 +++++++------- .../lifx-connect.src/lifx-connect.groovy | 52 ++++--------------- 3 files changed, 49 insertions(+), 78 deletions(-) diff --git a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy index 5ecfae0..d98e8d7 100644 --- a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy +++ b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy @@ -11,9 +11,9 @@ metadata { capability "Color Temperature" capability "Switch" capability "Switch Level" // brightness + capability "Polling" capability "Refresh" capability "Sensor" - capability "Health Check" } simulator { @@ -23,6 +23,7 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" @@ -63,8 +64,12 @@ metadata { } } -void installed() { - sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}") +// parse events into attributes +def parse(String description) { + if (description == 'updated') { + return // don't poll when config settings is being updated as it may time out + } + poll() } // handle commands @@ -187,17 +192,14 @@ def off() { return [] } -def refresh() { - log.debug "Executing 'refresh'" - +def poll() { + log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" def resp = parent.apiGET("/lights/${selector()}") if (resp.status == 404) { - state.online = false - sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false) - log.warn "$device is Offline" + sendEvent(name: "switch", value: "unreachable") return [] } else if (resp.status != 200) { - log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}") + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") return [] } def data = resp.data[0] @@ -206,20 +208,19 @@ def refresh() { sendEvent(name: "label", value: data.label) sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) - sendEvent(name: "switch", value: data.power) + sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int)) sendEvent(name: "hue", value: data.color.hue / 3.6) sendEvent(name: "saturation", value: data.color.saturation * 100) sendEvent(name: "colorTemperature", value: data.color.kelvin) - sendEvent(name: "model", value: data.product.name) + sendEvent(name: "model", value: "${data.product.company} ${data.product.name}") - if (data.connected) { - sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) - log.debug "$device is Online" - } else { - sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) - log.warn "$device is Offline" + return [] } + +def refresh() { + log.debug "Executing 'refresh'" + poll() } def selector() { diff --git a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy index 16e7c57..d089f77 100644 --- a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy +++ b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy @@ -10,9 +10,9 @@ metadata { capability "Color Temperature" capability "Switch" capability "Switch Level" // brightness + capability "Polling" capability "Refresh" capability "Sensor" - capability "Health Check" } simulator { @@ -22,12 +22,13 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" - } + } tileAttribute ("device.level", key: "SLIDER_CONTROL") { attributeState "level", action:"switch level.setLevel" } @@ -52,10 +53,15 @@ metadata { main "switch" details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) } + } -void installed() { - sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}") +// parse events into attributes +def parse(String description) { + if (description == 'updated') { + return // don't poll when config settings is being updated as it may time out + } + poll() } // handle commands @@ -116,17 +122,14 @@ def off() { return [] } -def refresh() { - log.debug "Executing 'refresh'" - +def poll() { + log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" def resp = parent.apiGET("/lights/${selector()}") if (resp.status == 404) { - state.online = false - sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false) - log.warn "$device is Offline" + sendEvent(name: "switch", value: "unreachable") return [] } else if (resp.status != 200) { - log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}") + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") return [] } def data = resp.data[0] @@ -134,17 +137,16 @@ def refresh() { sendEvent(name: "label", value: data.label) sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) - sendEvent(name: "switch", value: data.power) + sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") sendEvent(name: "colorTemperature", value: data.color.kelvin) sendEvent(name: "model", value: data.product.name) - if (data.connected) { - sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) - log.debug "$device is Online" - } else { - sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) - log.warn "$device is Offline" + return [] } + +def refresh() { + log.debug "Executing 'refresh'" + poll() } def selector() { diff --git a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy index 41f20ef..8988011 100644 --- a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy +++ b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy @@ -242,6 +242,8 @@ def installed() { } else { initialize() } + // Check for new devices and remove old ones every 3 hours + runEvery3Hours('updateDevices') } // called after settings are changed @@ -269,19 +271,9 @@ private removeChildDevices(devices) { def initialize() { log.debug "initialize" updateDevices() - // Check for new devices and remove old ones every 3 hours - runEvery5Minutes('updateDevices') - setupDeviceWatch() } // Misc -private setupDeviceWatch() { - def hub = location.hubs[0] - // Make sure that all child devices are enrolled in device watch - getChildDevices().each { - it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}") - } -} Map apiRequestHeaders() { return ["Authorization": "Bearer ${state.lifxAccessToken}", @@ -384,7 +376,7 @@ def updateDevices() { def data = [ label: device.label, level: Math.round((device.brightness ?: 1) * 100), - switch: device.power, + switch: device.connected ? device.power : "unreachable", colorTemperature: device.color.kelvin ] if (device.product.capabilities.has_color) { @@ -395,42 +387,18 @@ def updateDevices() { } else { childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data) } - childDevice?.completedSetup = true - } else { - if (device.product.capabilities.has_color) { - sendEvent(name: "color", value: colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)) - sendEvent(name: "hue", value: device.color.hue / 3.6) - sendEvent(name: "saturation", value: device.color.saturation * 100) } - childDevice.sendEvent(name: "label", value: device.label) - childDevice.sendEvent(name: "level", value: Math.round((device.brightness ?: 1) * 100)) - childDevice.sendEvent(name: "switch.setLevel", value: Math.round((device.brightness ?: 1) * 100)) - childDevice.sendEvent(name: "switch", value: device.power) - childDevice.sendEvent(name: "colorTemperature", value: device.color.kelvin) - childDevice.sendEvent(name: "model", value: device.product.name) - } - - if (state.devices[device.id] == null) { - // State missing, add it and set it to opposite status as current status to provoke event below - state.devices[device.id] = [online : !device.connected] - } - - if (!state.devices[device.id]?.online && device.connected) { - // Device came online after being offline - childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) - log.debug "$device is back Online" - } else if (state.devices[device.id]?.online && !device.connected) { - // Device went offline after being online - childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) - log.debug "$device went Offline" - } - state.devices[device.id] = [online: device.connected] } getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") - state.devices[it.deviceNetworkId] = null deleteChildDevice(it.deviceNetworkId) } + runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block } - +def refreshDevices() { + log.info("Refreshing all devices...") + getChildDevices().each { device -> + device.refresh() + } +} From ef2323f1b16216e5e8b453642d8b36e1cd97e2cb Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Tue, 25 Oct 2016 16:10:48 -0600 Subject: [PATCH 7/7] Revert "Revert LIFX device watch" --- .../lifx-color-bulb.groovy | 37 +++++++------ .../lifx-white-bulb.groovy | 38 +++++++------- .../lifx-connect.src/lifx-connect.groovy | 52 +++++++++++++++---- 3 files changed, 78 insertions(+), 49 deletions(-) diff --git a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy index d98e8d7..5ecfae0 100644 --- a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy +++ b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy @@ -11,9 +11,9 @@ metadata { capability "Color Temperature" capability "Switch" capability "Switch Level" // brightness - capability "Polling" capability "Refresh" capability "Sensor" + capability "Health Check" } simulator { @@ -23,7 +23,6 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { - attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" @@ -64,12 +63,8 @@ metadata { } } -// parse events into attributes -def parse(String description) { - if (description == 'updated') { - return // don't poll when config settings is being updated as it may time out - } - poll() +void installed() { + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}") } // handle commands @@ -192,14 +187,17 @@ def off() { return [] } -def poll() { - log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" +def refresh() { + log.debug "Executing 'refresh'" + def resp = parent.apiGET("/lights/${selector()}") if (resp.status == 404) { - sendEvent(name: "switch", value: "unreachable") + state.online = false + sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false) + log.warn "$device is Offline" return [] } else if (resp.status != 200) { - log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}") return [] } def data = resp.data[0] @@ -208,19 +206,20 @@ def poll() { sendEvent(name: "label", value: data.label) sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) - sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") + sendEvent(name: "switch", value: data.power) sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int)) sendEvent(name: "hue", value: data.color.hue / 3.6) sendEvent(name: "saturation", value: data.color.saturation * 100) sendEvent(name: "colorTemperature", value: data.color.kelvin) - sendEvent(name: "model", value: "${data.product.company} ${data.product.name}") + sendEvent(name: "model", value: data.product.name) - return [] + if (data.connected) { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) + log.debug "$device is Online" + } else { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) + log.warn "$device is Offline" } - -def refresh() { - log.debug "Executing 'refresh'" - poll() } def selector() { diff --git a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy index d089f77..16e7c57 100644 --- a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy +++ b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy @@ -10,9 +10,9 @@ metadata { capability "Color Temperature" capability "Switch" capability "Switch Level" // brightness - capability "Polling" capability "Refresh" capability "Sensor" + capability "Health Check" } simulator { @@ -22,13 +22,12 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { - attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" - } + tileAttribute ("device.level", key: "SLIDER_CONTROL") { attributeState "level", action:"switch level.setLevel" } @@ -53,15 +52,10 @@ metadata { main "switch" details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) } - } -// parse events into attributes -def parse(String description) { - if (description == 'updated') { - return // don't poll when config settings is being updated as it may time out - } - poll() +void installed() { + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}") } // handle commands @@ -122,14 +116,17 @@ def off() { return [] } -def poll() { - log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" +def refresh() { + log.debug "Executing 'refresh'" + def resp = parent.apiGET("/lights/${selector()}") if (resp.status == 404) { - sendEvent(name: "switch", value: "unreachable") + state.online = false + sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false) + log.warn "$device is Offline" return [] } else if (resp.status != 200) { - log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}") return [] } def data = resp.data[0] @@ -137,16 +134,17 @@ def poll() { sendEvent(name: "label", value: data.label) sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) - sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") + sendEvent(name: "switch", value: data.power) sendEvent(name: "colorTemperature", value: data.color.kelvin) sendEvent(name: "model", value: data.product.name) - return [] + if (data.connected) { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) + log.debug "$device is Online" + } else { + sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) + log.warn "$device is Offline" } - -def refresh() { - log.debug "Executing 'refresh'" - poll() } def selector() { diff --git a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy index 8988011..41f20ef 100644 --- a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy +++ b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy @@ -242,8 +242,6 @@ def installed() { } else { initialize() } - // Check for new devices and remove old ones every 3 hours - runEvery3Hours('updateDevices') } // called after settings are changed @@ -271,9 +269,19 @@ private removeChildDevices(devices) { def initialize() { log.debug "initialize" updateDevices() + // Check for new devices and remove old ones every 3 hours + runEvery5Minutes('updateDevices') + setupDeviceWatch() } // Misc +private setupDeviceWatch() { + def hub = location.hubs[0] + // Make sure that all child devices are enrolled in device watch + getChildDevices().each { + it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}") + } +} Map apiRequestHeaders() { return ["Authorization": "Bearer ${state.lifxAccessToken}", @@ -376,7 +384,7 @@ def updateDevices() { def data = [ label: device.label, level: Math.round((device.brightness ?: 1) * 100), - switch: device.connected ? device.power : "unreachable", + switch: device.power, colorTemperature: device.color.kelvin ] if (device.product.capabilities.has_color) { @@ -387,18 +395,42 @@ def updateDevices() { } else { childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data) } + childDevice?.completedSetup = true + } else { + if (device.product.capabilities.has_color) { + sendEvent(name: "color", value: colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)) + sendEvent(name: "hue", value: device.color.hue / 3.6) + sendEvent(name: "saturation", value: device.color.saturation * 100) } + childDevice.sendEvent(name: "label", value: device.label) + childDevice.sendEvent(name: "level", value: Math.round((device.brightness ?: 1) * 100)) + childDevice.sendEvent(name: "switch.setLevel", value: Math.round((device.brightness ?: 1) * 100)) + childDevice.sendEvent(name: "switch", value: device.power) + childDevice.sendEvent(name: "colorTemperature", value: device.color.kelvin) + childDevice.sendEvent(name: "model", value: device.product.name) + } + + if (state.devices[device.id] == null) { + // State missing, add it and set it to opposite status as current status to provoke event below + state.devices[device.id] = [online : !device.connected] + } + + if (!state.devices[device.id]?.online && device.connected) { + // Device came online after being offline + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false) + log.debug "$device is back Online" + } else if (state.devices[device.id]?.online && !device.connected) { + // Device went offline after being online + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false) + log.debug "$device went Offline" + } + state.devices[device.id] = [online: device.connected] } getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") + state.devices[it.deviceNetworkId] = null deleteChildDevice(it.deviceNetworkId) } - runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block } -def refreshDevices() { - log.info("Refreshing all devices...") - getChildDevices().each { device -> - device.refresh() - } -} +