From 9263107f0e0fc41b2a7cdee7c951462de137c8e0 Mon Sep 17 00:00:00 2001 From: Luke Bredeson Date: Tue, 16 Feb 2016 17:05:09 -0600 Subject: [PATCH] INSIDE-787: Improve Wemo (Connect) event subscriptions --- .../wemo-connect.src/wemo-connect.groovy | 200 ++++++++++++++---- 1 file changed, 164 insertions(+), 36 deletions(-) diff --git a/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy b/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy index a5819e1..0002c37 100644 --- a/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy +++ b/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy @@ -16,14 +16,14 @@ * Date: 2013-09-06 */ definition( - name: "Wemo (Connect)", - namespace: "smartthings", - author: "SmartThings", - description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.", - category: "SmartThings Labs", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png", - singleInstance: true + name: "Wemo (Connect)", + namespace: "smartthings", + author: "SmartThings", + description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.", + category: "SmartThings Labs", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png", + singleInstance: true ) preferences { @@ -39,7 +39,7 @@ private getFriendlyName(String deviceNetworkId) { sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1 HOST: ${deviceNetworkId} -""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}")) +""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"])) } private verifyDevices() { @@ -52,6 +52,13 @@ private verifyDevices() { } } +void ssdpSubscribe() { + subscribe(location, "ssdpTerm.urn:Belkin:device:insight:1", ssdpSwitchHandler) + subscribe(location, "ssdpTerm.urn:Belkin:device:controllee:1", ssdpSwitchHandler) + subscribe(location, "ssdpTerm.urn:Belkin:device:sensor:1", ssdpMotionHandler) + subscribe(location, "ssdpTerm.urn:Belkin:device:lightswitch:1", ssdpLightSwitchHandler) +} + def firstPage() { if(canInstallLabs()) @@ -62,7 +69,7 @@ def firstPage() log.debug "REFRESH COUNT :: ${refreshCount}" - subscribe(location, null, locationHandler, [filterEvents:false]) + ssdpSubscribe() //ssdp request every 25 seconds if((refreshCount % 5) == 0) { @@ -105,9 +112,7 @@ def devicesDiscovered() { def motions = getWemoMotions() def lightSwitches = getWemoLightSwitches() def devices = switches + motions + lightSwitches - def list = [] - - list = devices?.collect{ [app.id, it.ssdpUSN].join('.') } + devices?.collect{ [app.id, it.ssdpUSN].join('.') } } def switchesDiscovered() { @@ -175,8 +180,9 @@ def updated() { def initialize() { unsubscribe() - unschedule() - subscribe(location, null, locationHandler, [filterEvents:false]) + unschedule() + + ssdpSubscribe() if (selectedSwitches) addSwitches() @@ -189,7 +195,7 @@ def initialize() { runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds - runEvery5Minutes("refresh") + runEvery5Minutes("refresh") } def resubscribe() { @@ -199,7 +205,7 @@ def resubscribe() { def refresh() { log.debug "refresh() called" - doDeviceSync() + doDeviceSync() refreshDevices() } @@ -235,14 +241,14 @@ def addSwitches() { if (!d) { log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}" d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [ - "label": selectedSwitch?.value?.name ?: "Wemo Switch", - "data": [ - "mac": selectedSwitch.value.mac, - "ip": selectedSwitch.value.ip, - "port": selectedSwitch.value.port - ] + "label": selectedSwitch?.value?.name ?: "Wemo Switch", + "data": [ + "mac": selectedSwitch.value.mac, + "ip": selectedSwitch.value.ip, + "port": selectedSwitch.value.port + ] ]) - def ipvalue = convertHexToIP(selectedSwitch.value.ip) + def ipvalue = convertHexToIP(selectedSwitch.value.ip) d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" } else { @@ -273,9 +279,9 @@ def addMotions() { "port": selectedMotion.value.port ] ]) - def ipvalue = convertHexToIP(selectedMotion.value.ip) - d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") - log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" + def ipvalue = convertHexToIP(selectedMotion.value.ip) + d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") + log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" } else { log.debug "found ${d.displayName} with id $dni already exists" } @@ -304,26 +310,147 @@ def addLightSwitches() { "port": selectedLightSwitch.value.port ] ]) - def ipvalue = convertHexToIP(selectedLightSwitch.value.ip) - d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") + def ipvalue = convertHexToIP(selectedLightSwitch.value.ip) + d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}") log.debug "created ${d.displayName} with id $dni" } else { - log.debug "found ${d.displayName} with id $dni already exists" + log.debug "found ${d.displayName} with id $dni already exists" } } } +def ssdpSwitchHandler(evt) { + def description = evt.description + def hub = evt?.hubId + def parsedEvent = parseDiscoveryMessage(description) + parsedEvent << ["hub":hub] + log.debug parsedEvent + + def switches = getWemoSwitches() + if (!(switches."${parsedEvent.ssdpUSN.toString()}")) { + //if it doesn't already exist + switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] + } else { + log.debug "Device was already found in state..." + def d = switches."${parsedEvent.ssdpUSN.toString()}" + boolean deviceChangedValues = false + log.debug "$d.ip <==> $parsedEvent.ip" + if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { + d.ip = parsedEvent.ip + d.port = parsedEvent.port + deviceChangedValues = true + log.debug "Device's port or ip changed..." + def child = getChildDevice(parsedEvent.mac) + child.subscribe(parsedEvent.ip, parsedEvent.port) + child.poll() + } + } +} + +def ssdpMotionHandler(evt) { + log.info("ssdpMotionHandler") + def description = evt.description + def hub = evt?.hubId + def parsedEvent = parseDiscoveryMessage(description) + parsedEvent << ["hub":hub] + log.debug parsedEvent + + def motions = getWemoMotions() + if (!(motions."${parsedEvent.ssdpUSN.toString()}")) { + //if it doesn't already exist + motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] + } else { // just update the values + log.debug "Device was already found in state..." + + def d = motions."${parsedEvent.ssdpUSN.toString()}" + boolean deviceChangedValues = false + + if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { + d.ip = parsedEvent.ip + d.port = parsedEvent.port + deviceChangedValues = true + log.debug "Device's port or ip changed..." + } + + if (deviceChangedValues) { + def children = getChildDevices() + log.debug "Found children ${children}" + children.each { + if (it.getDeviceDataByName("mac") == parsedEvent.mac) { + log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}" + it.subscribe(parsedEvent.ip, parsedEvent.port) + } + } + } + } +} + +def ssdpLightSwitchHandler(evt) { + log.info("ssdpLightSwitchHandler") + def description = evt.description + def hub = evt?.hubId + def parsedEvent = parseDiscoveryMessage(description) + parsedEvent << ["hub":hub] + log.debug parsedEvent + + def lightSwitches = getWemoLightSwitches() + + if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}")) { + //if it doesn't already exist + lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] + } else { + log.debug "Device was already found in state..." + + def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}" + boolean deviceChangedValues = false + + if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { + d.ip = parsedEvent.ip + d.port = parsedEvent.port + deviceChangedValues = true + log.debug "Device's port or ip changed..." + def child = getChildDevice(parsedEvent.mac) + log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}" + child.subscribe(parsedEvent.ip, parsedEvent.port) + } + } +} + +void setupHandler(hubResponse) { + String contentType = hubResponse?.headers['Content-Type'] + if (contentType != null && contentType == 'text/xml') { + def body = hubResponse.xml + def wemoDevices = [] + String deviceType = body?.device?.deviceType?.text() ?: "" + if (deviceType.startsWith("urn:Belkin:device:controllee:1") || deviceType.startsWith("urn:Belkin:device:insight:1")) { + wemoDevices = getWemoSwitches() + } else if (deviceType.startsWith("urn:Belkin:device:sensor")) { + wemoDevices = getWemoMotions() + } else if (deviceType.startsWith("urn:Belkin:device:lightswitch")) { + wemoDevices = getWemoLightSwitches() + } + + def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())} + if (wemoDevice) { + wemoDevice.value << [name:body?.device?.friendlyName?.text(), verified: true] + } else { + log.error "/setup.xml returned a wemo device that didn't exist" + } + } +} + +@Deprecated def locationHandler(evt) { def description = evt.description def hub = evt?.hubId def parsedEvent = parseDiscoveryMessage(description) parsedEvent << ["hub":hub] - log.debug parsedEvent + log.debug parsedEvent if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) { def switches = getWemoSwitches() if (!(switches."${parsedEvent.ssdpUSN.toString()}")) { - //if it doesn't already exist + //if it doesn't already exist switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] } else { log.debug "Device was already found in state..." @@ -335,16 +462,16 @@ def locationHandler(evt) { d.port = parsedEvent.port deviceChangedValues = true log.debug "Device's port or ip changed..." - def child = getChildDevice(parsedEvent.mac) + def child = getChildDevice(parsedEvent.mac) child.subscribe(parsedEvent.ip, parsedEvent.port) - child.poll() + child.poll() } } } else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) { def motions = getWemoMotions() if (!(motions."${parsedEvent.ssdpUSN.toString()}")) { - //if it doesn't already exist + //if it doesn't already exist motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] } else { // just update the values log.debug "Device was already found in state..." @@ -459,6 +586,7 @@ def locationHandler(evt) { } } +@Deprecated private def parseXmlBody(def body) { def decodedBytes = body.decodeBase64() def bodyString @@ -540,4 +668,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) { private List getRealHubFirmwareVersions() { return location.hubs*.firmwareVersionString.findAll { it } -} +} \ No newline at end of file