From 8dd3b2396f2c56aea893b0d41ebb5d754f9e9658 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Tue, 8 Sep 2015 13:53:45 -0400 Subject: [PATCH 1/5] Hue update to match the new UI (Bridge) --- .../hue-bridge.src/hue-bridge.groovy | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy index 37370bb..7d63107 100644 --- a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy +++ b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy @@ -15,19 +15,27 @@ metadata { // TODO: define status and reply messages here } - tiles { + tiles(scale: 2) { + multiAttributeTile(name:"rich-control"){ + tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200" + } + tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") { + attributeState "default", label:'SN: ${currentValue}' + } + } standardTile("icon", "icon", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { state "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#FFFFFF" } valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) { state "default", label:'SN: ${currentValue}' } - valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 1, width: 2, inactiveLabel: false) { + valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) { state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false } main (["icon"]) - details(["networkAddress","serialNumber"]) + details(["rich-control", "networkAddress"]) } } @@ -36,7 +44,6 @@ def parse(description) { log.debug "Parsing '${description}'" def results = [] def result = parent.parse(this, description) - if (result instanceof physicalgraph.device.HubAction){ log.trace "HUE BRIDGE HubAction received -- DOES THIS EVER HAPPEN?" results << result @@ -44,32 +51,30 @@ def parse(description) { //do nothing log.trace "HUE BRIDGE was updated" } else { - log.trace "HUE BRIDGE, OTHER" def map = description if (description instanceof String) { map = stringToMap(description) } if (map?.name && map?.value) { log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value" - results << createEvent(name: "${map?.name}", value: "${map?.value}") - } - else { - log.trace "HUE BRIDGE, OTHER" + results << createEvent(name: "${map.name}", value: "${map.value}") + } else { + log.trace "Parsing description" def msg = parseLanMessage(description) if (msg.body) { def contentType = msg.headers["Content-Type"] if (contentType?.contains("json")) { def bulbs = new groovy.json.JsonSlurper().parseText(msg.body) if (bulbs.state) { - log.warn "NOT PROCESSED: $msg.body" - } - else { - log.debug "HUE BRIDGE, GENERATING BULB LIST EVENT: $bulbs" - sendEvent(name: "bulbList", value: device.hub.id, isStateChange: true, data: bulbs, displayed: false) + log.info "Bridge response: $msg.body" + } else { + // Sending Bulbs List to parent" + if (parent.state.inBulbDiscovery) + log.info parent.bulbListHandler(device.hub.id, msg.body) } } else if (contentType?.contains("xml")) { - log.debug "HUE BRIDGE, SWALLOWING BRIDGE DESCRIPTION RESPONSE -- BRIDGE ALREADY PRESENT" + log.debug "HUE BRIDGE ALREADY PRESENT" } } } From 35a7a7907373d787f440120e48c1e40c79e037c6 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Tue, 8 Sep 2015 13:54:10 -0400 Subject: [PATCH 2/5] Hue update to match the new UI (Bulb) --- .../smartthings/hue-bulb.src/hue-bulb.groovy | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 7bf63f3..2472889 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -23,8 +23,8 @@ metadata { // TODO: define status and reply messages here } - tiles (scale: 2){ - multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ + tiles(scale: 2) { + multiAttributeTile(name:"rich-control", type:"lighting", canChangeIcon: true) { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" @@ -32,24 +32,51 @@ metadata { attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" } tileAttribute ("device.level", key: "SLIDER_CONTROL") { - attributeState "level", action:"switch level.setLevel" + attributeState "level", action:"switch level.setLevel", range:"(0..100)" + } + tileAttribute ("device.level", key: "SECONDARY_CONTROL") { + attributeState "level", label: 'Level ${currentValue}%' } tileAttribute ("device.color", key: "COLOR_CONTROL") { attributeState "color", action:"setAdjustedColor" } } - standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { + state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + } + + standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" } - standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" } + controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { + state "level", action:"switch level.setLevel" + } + valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { + state "level", label: 'Level ${currentValue}%' + } + controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) { + state "saturation", action:"color control.setSaturation" + } + valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") { + state "saturation", label: 'Sat ${currentValue} ' + } + controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) { + state "hue", action:"color control.setHue" + } + valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") { + state "hue", label: 'Hue ${currentValue} ' + } + + main(["switch"]) + details(["rich-control", "reset", "refresh"]) } - - main(["switch"]) - details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"]) - } // parse events into attributes @@ -68,13 +95,13 @@ def parse(description) { } // handle commands -def on(transition = "4") { - log.trace parent.on(this,transition) +def on() { + log.trace parent.on(this) sendEvent(name: "switch", value: "on") } -def off(transition = "4") { - log.trace parent.off(this,transition) +def off() { + log.trace parent.off(this) sendEvent(name: "switch", value: "off") } @@ -107,9 +134,9 @@ def setHue(percent) { sendEvent(name: "hue", value: percent) } -def setColor(value,alert = "none",transition = 4) { +def setColor(value) { log.debug "setColor: ${value}, $this" - parent.setColor(this, value, alert, transition) + parent.setColor(this, value) if (value.hue) { sendEvent(name: "hue", value: value.hue)} if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)} if (value.hex) { sendEvent(name: "color", value: value.hex)} From 1b424a8ea8aad7b98cb32b59ff56faf0c47d4003 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Tue, 8 Sep 2015 13:54:31 -0400 Subject: [PATCH 3/5] Hue update to match the new UI (Lux Bulb) --- .../hue-lux-bulb.src/hue-lux-bulb.groovy | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) 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 5fe1e5f..07b9326 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -19,24 +19,41 @@ metadata { simulator { // TODO: define status and reply messages here } + + tiles(scale: 2) { + multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){ + tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + } + tileAttribute ("device.level", key: "SLIDER_CONTROL") { + attributeState "level", action:"switch level.setLevel", range:"(0..100)" + } + tileAttribute ("device.level", key: "SECONDARY_CONTROL") { + attributeState "level", label: 'Level ${currentValue}%' + } + } + + standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { + state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + } + + controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { + state "level", action:"switch level.setLevel" + } + + standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") { + state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + } - standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { - state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821" - state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff" - } - standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { - state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" - } - controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { - state "level", action:"switch level.setLevel" - } - valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { - state "level", label: 'Level ${currentValue}%' - } - - main(["switch"]) - details(["switch", "levelSliderControl", "refresh"]) - + main(["switch"]) + details(["rich-control", "refresh"]) + } } // parse events into attributes From 6005f7266e263e1901de2b50fb38778a37d5d18d Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Tue, 8 Sep 2015 14:33:42 -0400 Subject: [PATCH 4/5] Removed deprecated tiles --- .../smartthings/hue-bulb.src/hue-bulb.groovy | 50 +++++-------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 2472889..b81395a 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -1,3 +1,4 @@ + /** * Hue Bulb * @@ -15,16 +16,16 @@ metadata { capability "Sensor" command "setAdjustedColor" - command "reset" - command "refresh" + command "reset" + command "refresh" } simulator { // TODO: define status and reply messages here } - tiles(scale: 2) { - multiAttributeTile(name:"rich-control", type:"lighting", canChangeIcon: true) { + tiles (scale: 2){ + multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" @@ -32,51 +33,24 @@ metadata { attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" } tileAttribute ("device.level", key: "SLIDER_CONTROL") { - attributeState "level", action:"switch level.setLevel", range:"(0..100)" - } - tileAttribute ("device.level", key: "SECONDARY_CONTROL") { - attributeState "level", label: 'Level ${currentValue}%' + attributeState "level", action:"switch level.setLevel" } tileAttribute ("device.color", key: "COLOR_CONTROL") { attributeState "color", action:"setAdjustedColor" } } - standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { - state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" - state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" - state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" - state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" - } - - standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { + standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" } - standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { + standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" } - controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { - state "level", action:"switch level.setLevel" - } - valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { - state "level", label: 'Level ${currentValue}%' - } - controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) { - state "saturation", action:"color control.setSaturation" - } - valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") { - state "saturation", label: 'Sat ${currentValue} ' - } - controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) { - state "hue", action:"color control.setHue" - } - valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") { - state "hue", label: 'Hue ${currentValue} ' - } - - main(["switch"]) - details(["rich-control", "reset", "refresh"]) } + + main(["switch"]) + details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"]) + } // parse events into attributes From d7490a086ad80873d2cca9e19414b396e98d3c64 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Thu, 10 Sep 2015 21:45:08 -0400 Subject: [PATCH 5/5] Hue fix bulb discovery and link button --- .../hue-connect.src/hue-connect.groovy | 263 ++++++++++-------- 1 file changed, 145 insertions(+), 118 deletions(-) diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 5726108..9833438 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -15,7 +15,7 @@ * for the specific language governing permissions and limitations under the License. * */ - + definition( name: "Hue (Connect)", namespace: "smartthings", @@ -64,10 +64,13 @@ def bridgeDiscovery(params=[:]) def options = bridges ?: [] def numFound = options.size() ?: 0 - if(!state.subscribe) { - subscribe(location, null, locationHandler, [filterEvents:false]) - state.subscribe = true - } + if (numFound == 0 && state.bridgeRefreshCount > 25) { + log.trace "Cleaning old bridges memory" + state.bridges = [:] + state.bridgeRefreshCount = 0 + } + + subscribe(location, null, locationHandler, [filterEvents:false]) //bridge discovery request every 15 //25 seconds if((bridgeRefreshCount % 5) == 0) { @@ -94,11 +97,20 @@ def bridgeLinking() def nextPage = "" def title = "Linking with your Hue" - def paragraphText = "Press the button on your Hue Bridge to setup a link." + def paragraphText + def hueimage = null + if (selectedHue) { + paragraphText = "Press the button on your Hue Bridge to setup a link. " + hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png" + } else { + paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next." + hueimage = null + } if (state.username) { //if discovery worked nextPage = "bulbDiscovery" - title = "Success! - click 'Next'" + title = "Success!" paragraphText = "Linking to your hub was a success! Please click 'Next'!" + hueimage = null } if((linkRefreshcount % 2) == 0 && !state.username) { @@ -106,18 +118,20 @@ def bridgeLinking() } return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) { - section("Button Press") { + section("") { paragraph """${paragraphText}""" + if (hueimage != null) + image "${hueimage}" } } } -def bulbDiscovery() -{ +def bulbDiscovery() { int bulbRefreshCount = !state.bulbRefreshCount ? 0 : state.bulbRefreshCount as int state.bulbRefreshCount = bulbRefreshCount + 1 def refreshInterval = 3 - + state.inBulbDiscovery = true + state.bridgeRefreshCount = 0 def options = bulbsDiscovered() ?: [] def numFound = options.size() ?: 0 @@ -129,7 +143,7 @@ def bulbDiscovery() section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:options } - section { + section { def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges" href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true] @@ -194,21 +208,22 @@ Map bridgesDiscovered() { Map bulbsDiscovered() { def bulbs = getHueBulbs() - def map = [:] + def bulbmap = [:] if (bulbs instanceof java.util.Map) { bulbs.each { - def value = "${it?.value?.name}" - def key = app.id +"/"+ it?.value?.id - map["${key}"] = value + def value = "${it.value.name}" + def key = app.id +"/"+ it.value.id + bulbmap["${key}"] = value } } else { //backwards compatable bulbs.each { - def value = "${it?.name}" - def key = app.id +"/"+ it?.id - map["${key}"] = value + def value = "${it.name}" + def key = app.id +"/"+ it.id + logg += "$value - $key, " + bulbmap["${key}"] = value } } - map + bulbmap } def getHueBulbs() { @@ -231,24 +246,16 @@ def installed() { def updated() { log.trace "Updated with settings: ${settings}" unschedule() - unsubscribe() + unsubscribe() initialize() } def initialize() { - log.debug "Initializing" - state.subscribe = false - state.bridgeSelectedOverride = false - def bridge = null - + log.debug "Initializing" + state.inBulbDiscovery = false if (selectedHue) { - addBridge() - bridge = getChildDevice(selectedHue) - subscribe(bridge, "bulbList", bulbListHandler) - } - - if (selectedBulbs) { - addBulbs() + addBridge() + addBulbs() doDeviceSync() runEvery5Minutes("doDeviceSync") } @@ -263,22 +270,27 @@ def manualRefresh() { def uninstalled(){ state.bridges = [:] - state.subscribe = false + state.username = null } // Handles events to add new bulbs -def bulbListHandler(evt) { - def bulbs = [:] - log.trace "Adding bulbs to state..." - state.bridgeProcessedLightList = true - evt.jsonData.each { k,v -> - log.trace "$k: $v" - if (v instanceof Map) { - bulbs[k] = [id: k, name: v.name, type: v.type, hub:evt.value] - } - } - state.bulbs = bulbs - log.info "${bulbs.size()} bulbs found" +def bulbListHandler(hub, data = "") { + def msg = "Bulbs list not processed. Only while in settings menu." + log.trace "Here: $hub, $data" + if (state.inBulbDiscovery) { + def bulbs = [:] + def logg = "" + log.trace "Adding bulbs to state..." + state.bridgeProcessedLightList = true + 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, hub:hub] + } + state.bulbs = bulbs + msg = "${bulbs.size()} bulbs found. $state.bulbs" + } + return msg } def addBulbs() { @@ -294,7 +306,7 @@ def addBulbs() { } else { d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name]) } - } else { + } else { //backwards compatable newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name]) @@ -322,7 +334,7 @@ def addBridge() { def d = getChildDevice(selectedHue) if(!d) { // compatibility with old devices - def newbridge = true + def newbridge = true childDevices.each { if (it.getDeviceDataByName("mac")) { def newDNI = "${it.getDeviceDataByName("mac")}" @@ -332,22 +344,27 @@ def addBridge() { it.setDeviceNetworkId("${newDNI}") if (oldDNI == selectedHue) app.updateSetting("selectedHue", newDNI) - newbridge = false + newbridge = false } - } - } + } + } if (newbridge) { d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub) log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" def childDevice = getChildDevice(d.deviceNetworkId) childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber) if (vbridge.value.ip && vbridge.value.port) { - if (vbridge.value.ip.contains(".")) + if (vbridge.value.ip.contains(".")) { childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port) - else - childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) - } else + childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port) + } else { + childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) + childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) + } + } else { childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) + childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) + } } } else { log.debug "found ${d.displayName} with id $selectedHue already exists" @@ -355,7 +372,6 @@ def addBridge() { } } - def locationHandler(evt) { def description = evt.description log.trace "Location: $description" @@ -454,17 +470,13 @@ def locationHandler(evt) { def doDeviceSync(){ log.trace "Doing Hue Device Sync!" - - //shrink the large bulb lists convertBulbListToMap() - poll() - - if(!state.subscribe) { + try { subscribe(location, null, locationHandler, [filterEvents:false]) - state.subscribe = true - } - + } catch (all) { + log.trace "Subscription already exist" + } discoverBridges() } @@ -473,44 +485,49 @@ def doDeviceSync(){ ///////////////////////////////////// def parse(childDevice, description) { - def parsedEvent = parseLanMessage(description) + def parsedEvent = parseLanMessage(description) if (parsedEvent.headers && parsedEvent.body) { def headerString = parsedEvent.headers.toString() - if (headerString?.contains("json")) { - def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body) - if (body instanceof java.util.HashMap) - { //poll response + 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.HashMap) { + //poll response def bulbs = getChildDevices() - //for each bulb for (bulb in body) { - def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} + def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} if (d) { if (bulb.value.state?.reachable) { - sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"]) - sendEvent(d.deviceNetworkId, [name: "level", value: Math.round(bulb.value.state.bri * 100 / 255)]) - if (bulb.value.state.sat) { - def hue = Math.min(Math.round(bulb.value.state.hue * 100 / 65535), 65535) as int - def sat = Math.round(bulb.value.state.sat * 100 / 255) as int - def hex = colorUtil.hslToHex(hue, sat) - sendEvent(d.deviceNetworkId, [name: "color", value: hex]) - sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) - sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) - } - } else { - sendEvent(d.deviceNetworkId, [name: "switch", value: "off"]) - sendEvent(d.deviceNetworkId, [name: "level", value: 100]) - if (bulb.value.state.sat) { - def hue = 23 - def sat = 56 - def hex = colorUtil.hslToHex(23, 56) - sendEvent(d.deviceNetworkId, [name: "color", value: hex]) - sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) - sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) - } - } + sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"]) + sendEvent(d.deviceNetworkId, [name: "level", value: Math.round(bulb.value.state.bri * 100 / 255)]) + if (bulb.value.state.sat) { + def hue = Math.min(Math.round(bulb.value.state.hue * 100 / 65535), 65535) as int + def sat = Math.round(bulb.value.state.sat * 100 / 255) as int + def hex = colorUtil.hslToHex(hue, sat) + sendEvent(d.deviceNetworkId, [name: "color", value: hex]) + sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) + sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) + } + } else { + sendEvent(d.deviceNetworkId, [name: "switch", value: "off"]) + sendEvent(d.deviceNetworkId, [name: "level", value: 100]) + if (bulb.value.state.sat) { + def hue = 23 + def sat = 56 + def hex = colorUtil.hslToHex(23, 56) + sendEvent(d.deviceNetworkId, [name: "color", value: hex]) + sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) + sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) + } + } } - - } + } } else { //put response @@ -559,25 +576,25 @@ def parse(childDevice, description) { } } - } + } } else { log.debug "parse - got something other than headers,body..." return [] } } -def on(childDevice, transition = 4) { +def on(childDevice, transition_deprecated = 0) { log.debug "Executing 'on'" - // Assume bulb is off if no current state is found for level to avoid bulbs getting stuck in off after initial discovery - def percent = childDevice.device?.currentValue("level") as Integer ?: 0 + def percent = childDevice.device?.currentValue("level") as Integer def level = Math.min(Math.round(percent * 255 / 100), 255) - put("lights/${getId(childDevice)}/state", [bri: level, on: true, transitiontime: transition]) + put("lights/${getId(childDevice)}/state", [bri: level, on: true]) return "level: $percent" } -def off(childDevice, transition = 4) { +def off(childDevice, transition_deprecated = 0) { log.debug "Executing 'off'" - put("lights/${getId(childDevice)}/state", [on: false, transitiontime: transition]) + put("lights/${getId(childDevice)}/state", [on: false]) + return "level: 0" } def setLevel(childDevice, percent) { @@ -598,19 +615,21 @@ def setHue(childDevice, percent) { put("lights/${getId(childDevice)}/state", [hue: level]) } -def setColor(childDevice, color, alert = "none", transition = 4) { - log.debug "Executing 'setColor($color)'" - def hue = Math.min(Math.round(color.hue * 65535 / 100), 65535) - def sat = Math.min(Math.round(color.saturation * 255 / 100), 255) +def setColor(childDevice, huesettings, alert_deprecated = "", transition_deprecated = 0) { + log.debug "Executing 'setColor($huesettings)'" + def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535) + def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255) + def alert = huesettings.alert ? huesettings.alert : "none" + def transition = huesettings.transition ? huesettings.transition : 4 def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition] - if (color.level != null) { - value.bri = Math.min(Math.round(color.level * 255 / 100), 255) + if (huesettings.level != null) { + value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255) value.on = value.bri > 0 } - if (color.switch) { - value.on = color.switch == "on" + if (huesettings.switch) { + value.on = huesettings.switch == "on" } log.debug "sending command $value" @@ -640,15 +659,19 @@ private getId(childDevice) { private poll() { def host = getBridgeIP() def uri = "/api/${state.username}/lights/" - log.debug "GET: $host$uri" - sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1 + try { + sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1 HOST: ${host} """, physicalgraph.device.Protocol.LAN, selectedHue)) + } catch (all) { + log.warn "Parsing Body failed - trying again..." + doDeviceSync() + } } private put(path, body) { - def host = getBridgeIP() + def host = getBridgeIP() def uri = "/api/${state.username}/$path" def bodyJSON = new groovy.json.JsonBuilder(body).toString() def length = bodyJSON.getBytes().size().toString() @@ -668,9 +691,13 @@ ${bodyJSON} private getBridgeIP() { def host = null if (selectedHue) { - def d = getChildDevice(dni) - if (d) - host = d.latestState('networkAddress').stringValue + 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 @@ -681,9 +708,9 @@ private getBridgeIP() { 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 }