diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 10980af..44ccdc0 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -60,7 +60,7 @@ def bridgeDiscovery(params=[:]) app.updateSetting("selectedHue", "") } - subscribe(location, null, locationHandler, [filterEvents:false]) + ssdpSubscribe() //bridge discovery request every 15 //25 seconds if((bridgeRefreshCount % 5) == 0) { @@ -152,6 +152,10 @@ private discoverBridges() { sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN)) } +void ssdpSubscribe() { + subscribe(location, "ssdpTerm.urn:schemas-upnp-org:device:basic:1", ssdpBridgeHandler) +} + private sendDeveloperReq() { def token = app.id def host = getBridgeIP() @@ -161,7 +165,7 @@ private sendDeveloperReq() { headers: [ HOST: host ], - body: [devicetype: "$token-0"]], "${selectedHue}")) + body: [devicetype: "$token-0"]], "${selectedHue}", [callback: "usernameHandler"])) } private discoverHueBulbs() { @@ -171,7 +175,7 @@ private discoverHueBulbs() { path: "/api/${state.username}/lights", headers: [ HOST: host - ]], "${selectedHue}")) + ]], "${selectedHue}", [callback: "lightsHandler"])) } private verifyHueBridge(String deviceNetworkId, String host) { @@ -181,7 +185,7 @@ private verifyHueBridge(String deviceNetworkId, String host) { path: "/description.xml", headers: [ HOST: host - ]], deviceNetworkId)) + ]], deviceNetworkId, [callback: "bridgeDescriptionHandler"])) } private verifyHueBridges() { @@ -293,8 +297,9 @@ def bulbListHandler(hub, data = "") { } } def bridge = null - if (selectedHue) - bridge = getChildDevice(selectedHue) + if (selectedHue) { + bridge = getChildDevice(selectedHue) + } bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false) msg = "${bulbs.size()} bulbs found. ${bulbs}" return msg @@ -382,8 +387,9 @@ def addBridge() { 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) + if (oldDNI == selectedHue) { + app.updateSetting("selectedHue", newDNI) + } newbridge = false } } @@ -412,6 +418,111 @@ def addBridge() { } } +def ssdpBridgeHandler(evt) { + def description = evt.description + log.trace "Location: $description" + + def hub = evt?.hubId + def parsedEvent = parseLanMessage(description) + parsedEvent << ["hub":hub] + + def bridges = getHueBridges() + log.trace bridges.toString() + if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) { + //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 + log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host." + 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")}" + 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() + } + } + } + } else { + if (d.getDeviceDataByName("networkAddress")) { + networkAddress = d.getDeviceDataByName("networkAddress") + } 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) + } + } + } +} + +void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) { + log.trace "description.xml response (application/xml)" + def body = hubResponse.xml + 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" + } + } +} + +void lightsHandler(physicalgraph.device.HubResponse hubResponse) { + if (isValidSource(hubResponse.mac)) { + def body = hubResponse.json + if (!body?.state?.on) { //check if first time poll made it here by mistake + def bulbs = getHueBulbs() + log.debug "Adding bulbs to state!" + body.each { k, v -> + bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId] + } + } + } +} + +void usernameHandler(physicalgraph.device.HubResponse hubResponse) { + if (isValidSource(hubResponse.mac)) { + def body = hubResponse.json + if (body.success != null) { + if (body.success[0] != null) { + if (body.success[0].username) + state.username = body.success[0].username + } + } else if (body.error != null) { + //TODO: handle retries... + log.error "ERROR: application/json ${body.error}" + } + } +} + +/** + * @deprecated This has been replaced by the combination of {@link #ssdpBridgeHandler()}, {@link #bridgeDescriptionHandler()}, + * {@link #lightsHandler()}, and {@link #usernameHandler()}. After a pending event subscription migration, it can be removed. + */ +@Deprecated def locationHandler(evt) { def description = evt.description log.trace "Location: $description" @@ -447,17 +558,19 @@ def locationHandler(evt) { 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) + if (oldDNI == selectedHue) { + app.updateSetting("selectedHue", newDNI) + } doDeviceSync() } } } } else { - if (d.getDeviceDataByName("networkAddress")) - networkAddress = d.getDeviceDataByName("networkAddress") - else - networkAddress = d.latestState('networkAddress').stringValue + if (d.getDeviceDataByName("networkAddress")) { + networkAddress = d.getDeviceDataByName("networkAddress") + } 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..." @@ -490,8 +603,9 @@ def locationHandler(evt) { def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body) if (body.success != null) { if (body.success[0] != null) { - if (body.success[0].username) + if (body.success[0].username) { state.username = body.success[0].username + } } } else if (body.error != null) { //TODO: handle retries... @@ -516,11 +630,7 @@ def doDeviceSync(){ log.trace "Doing Hue Device Sync!" convertBulbListToMap() poll() - try { - subscribe(location, null, locationHandler, [filterEvents:false]) - } catch (all) { - log.trace "Subscription already exist" - } + ssdpSubscribe() discoverBridges() } @@ -747,15 +857,11 @@ private getId(childDevice) { private poll() { def host = getBridgeIP() def uri = "/api/${state.username}/lights/" - try { - sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1 + log.debug "GET: $host$uri" + 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) {