From bacd335991fcef93b0df24acae3a36510bdd427b Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Tue, 30 Aug 2016 17:58:27 -0600 Subject: [PATCH] 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 e62d363..7a6e8a3 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) } @@ -781,8 +780,7 @@ def parse(childDevice, description) { if (body instanceof java.util.Map) { // get (poll) reponse return handlePoll(body) - } - else { + } else { //put response return handleCommandResponse(body) } @@ -879,36 +877,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. @@ -928,26 +930,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 @@ -976,22 +984,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 @@ -1006,48 +1026,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 = [:] @@ -1104,15 +1140,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] } } @@ -1123,10 +1167,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" @@ -1194,7 +1241,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 }