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 }