Update step-notifier.groovy
[smartapps.git] / official / wemo-connect.groovy
1 /**
2  *  Copyright 2015 SmartThings
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  *  in compliance with the License. You may obtain a copy of the License at:
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
10  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
11  *  for the specific language governing permissions and limitations under the License.
12  *
13  *  Wemo Service Manager
14  *
15  *  Author: superuser
16  *  Date: 2013-09-06
17  */
18 definition(
19         name: "Wemo (Connect)",
20         namespace: "smartthings",
21         author: "SmartThings",
22         description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
23         category: "SmartThings Labs",
24         iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
25         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
26         singleInstance: true
27 )
28
29 preferences {
30         page(name:"firstPage", title:"Wemo Device Setup", content:"firstPage")
31 }
32
33 private discoverAllWemoTypes()
34 {
35         sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:Belkin:device:insight:1/urn:Belkin:device:controllee:1/urn:Belkin:device:sensor:1/urn:Belkin:device:lightswitch:1", physicalgraph.device.Protocol.LAN))
36 }
37
38 private getFriendlyName(String deviceNetworkId) {
39         sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1
40 HOST: ${deviceNetworkId}
41
42 """, physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
43 }
44
45 private verifyDevices() {
46         def switches = getWemoSwitches().findAll { it?.value?.verified != true }
47         def motions = getWemoMotions().findAll { it?.value?.verified != true }
48         def lightSwitches = getWemoLightSwitches().findAll { it?.value?.verified != true }
49         def devices = switches + motions + lightSwitches
50         devices.each {
51                 getFriendlyName((it.value.ip + ":" + it.value.port))
52         }
53 }
54
55 void ssdpSubscribe() {
56         subscribe(location, "ssdpTerm.urn:Belkin:device:insight:1", ssdpSwitchHandler)
57         subscribe(location, "ssdpTerm.urn:Belkin:device:controllee:1", ssdpSwitchHandler)
58         subscribe(location, "ssdpTerm.urn:Belkin:device:sensor:1", ssdpMotionHandler)
59         subscribe(location, "ssdpTerm.urn:Belkin:device:lightswitch:1", ssdpLightSwitchHandler)
60 }
61
62 def firstPage()
63 {
64         if(canInstallLabs())
65         {
66                 int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
67                 state.refreshCount = refreshCount + 1
68                 def refreshInterval = 5
69
70                 log.debug "REFRESH COUNT :: ${refreshCount}"
71
72                 ssdpSubscribe()
73
74                 //ssdp request every 25 seconds
75                 if((refreshCount % 5) == 0) {
76                         discoverAllWemoTypes()
77                 }
78
79                 //setup.xml request every 5 seconds except on discoveries
80                 if(((refreshCount % 1) == 0) && ((refreshCount % 5) != 0)) {
81                         verifyDevices()
82                 }
83
84                 def switchesDiscovered = switchesDiscovered()
85                 def motionsDiscovered = motionsDiscovered()
86                 def lightSwitchesDiscovered = lightSwitchesDiscovered()
87
88                 return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
89                         section { paragraph title: "Note:", "This device has not been officially tested and certified to “Work with SmartThings”. You can connect it to your SmartThings home but performance may vary and we will not be able to provide support or assistance." }
90                         section("Select a device...") {
91                                 input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered
92                                 input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered
93                                 input "selectedLightSwitches", "enum", required:false, title:"Select Wemo Light Switches \n(${lightSwitchesDiscovered.size() ?: 0} found)", multiple:true, options:lightSwitchesDiscovered
94                         }
95                 }
96         }
97         else
98         {
99                 def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
100
101 To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
102
103                 return dynamicPage(name:"firstPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
104                         section("Upgrade") {
105                                 paragraph "$upgradeNeeded"
106                         }
107                 }
108         }
109 }
110
111 def devicesDiscovered() {
112         def switches = getWemoSwitches()
113         def motions = getWemoMotions()
114         def lightSwitches = getWemoLightSwitches()
115         def devices = switches + motions + lightSwitches
116         devices?.collect{ [app.id, it.ssdpUSN].join('.') }
117 }
118
119 def switchesDiscovered() {
120         def switches = getWemoSwitches().findAll { it?.value?.verified == true }
121         def map = [:]
122         switches.each {
123                 def value = it.value.name ?: "WeMo Switch ${it.value.ssdpUSN.split(':')[1][-3..-1]}"
124                 def key = it.value.mac
125                 map["${key}"] = value
126         }
127         map
128 }
129
130 def motionsDiscovered() {
131         def motions = getWemoMotions().findAll { it?.value?.verified == true }
132         def map = [:]
133         motions.each {
134                 def value = it.value.name ?: "WeMo Motion ${it.value.ssdpUSN.split(':')[1][-3..-1]}"
135                 def key = it.value.mac
136                 map["${key}"] = value
137         }
138         map
139 }
140
141 def lightSwitchesDiscovered() {
142         //def vmotions = switches.findAll { it?.verified == true }
143         //log.trace "MOTIONS HERE: ${vmotions}"
144         def lightSwitches = getWemoLightSwitches().findAll { it?.value?.verified == true }
145         def map = [:]
146         lightSwitches.each {
147                 def value = it.value.name ?: "WeMo Light Switch ${it.value.ssdpUSN.split(':')[1][-3..-1]}"
148                 def key = it.value.mac
149                 map["${key}"] = value
150         }
151         map
152 }
153
154 def getWemoSwitches()
155 {
156         if (!state.switches) { state.switches = [:] }
157         state.switches
158 }
159
160 def getWemoMotions()
161 {
162         if (!state.motions) { state.motions = [:] }
163         state.motions
164 }
165
166 def getWemoLightSwitches()
167 {
168         if (!state.lightSwitches) { state.lightSwitches = [:] }
169         state.lightSwitches
170 }
171
172 def installed() {
173         log.debug "Installed with settings: ${settings}"
174         initialize()
175 }
176
177 def updated() {
178         log.debug "Updated with settings: ${settings}"
179         initialize()
180 }
181
182 def initialize() {
183         unsubscribe()
184         unschedule()
185
186         ssdpSubscribe()
187
188         if (selectedSwitches)
189                 addSwitches()
190
191         if (selectedMotions)
192                 addMotions()
193
194         if (selectedLightSwitches)
195                 addLightSwitches()
196
197         runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
198         runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
199         runEvery5Minutes("refresh")
200 }
201
202 def resubscribe() {
203         log.debug "Resubscribe called, delegating to refresh()"
204         refresh()
205 }
206
207 def refresh() {
208         log.debug "refresh() called"
209         doDeviceSync()
210         refreshDevices()
211 }
212
213 def refreshDevices() {
214         log.debug "refreshDevices() called"
215         def devices = getAllChildDevices()
216         devices.each { d ->
217                 log.debug "Calling refresh() on device: ${d.id}"
218                 d.refresh()
219         }
220 }
221
222 def subscribeToDevices() {
223         log.debug "subscribeToDevices() called"
224         def devices = getAllChildDevices()
225         devices.each { d ->
226                 d.subscribe()
227         }
228 }
229
230 def addSwitches() {
231         def switches = getWemoSwitches()
232
233         selectedSwitches.each { dni ->
234                 def selectedSwitch = switches.find { it.value.mac == dni } ?: switches.find { "${it.value.ip}:${it.value.port}" == dni }
235                 def d
236                 if (selectedSwitch) {
237                         d = getChildDevices()?.find {
238                                 it.deviceNetworkId == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
239                         }
240                         if (!d) {
241                                 log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}"
242                                 d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [
243                                         "label": selectedSwitch?.value?.name ?: "Wemo Switch",
244                                         "data": [
245                                                 "mac": selectedSwitch.value.mac,
246                                                 "ip": selectedSwitch.value.ip,
247                                                 "port": selectedSwitch.value.port
248                                         ]
249                                 ])
250                                 def ipvalue = convertHexToIP(selectedSwitch.value.ip)
251                                 d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
252                                 log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
253                         } else {
254                                 log.debug "found ${d.displayName} with id $dni already exists"
255                         }
256                 }
257         }
258 }
259
260 def addMotions() {
261         def motions = getWemoMotions()
262
263         selectedMotions.each { dni ->
264                 def selectedMotion = motions.find { it.value.mac == dni } ?: motions.find { "${it.value.ip}:${it.value.port}" == dni }
265                 def d
266                 if (selectedMotion) {
267                         d = getChildDevices()?.find {
268                                 it.deviceNetworkId == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
269                         }
270                         if (!d) {
271                                 log.debug "Creating WeMo Motion with dni: ${selectedMotion.value.mac}"
272                                 d = addChildDevice("smartthings", "Wemo Motion", selectedMotion.value.mac, selectedMotion?.value.hub, [
273                                         "label": selectedMotion?.value?.name ?: "Wemo Motion",
274                                         "data": [
275                                                 "mac": selectedMotion.value.mac,
276                                                 "ip": selectedMotion.value.ip,
277                                                 "port": selectedMotion.value.port
278                                         ]
279                                 ])
280                                 def ipvalue = convertHexToIP(selectedMotion.value.ip)
281                                 d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
282                                 log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
283                         } else {
284                                 log.debug "found ${d.displayName} with id $dni already exists"
285                         }
286                 }
287         }
288 }
289
290 def addLightSwitches() {
291         def lightSwitches = getWemoLightSwitches()
292
293         selectedLightSwitches.each { dni ->
294                 def selectedLightSwitch = lightSwitches.find { it.value.mac == dni } ?: lightSwitches.find { "${it.value.ip}:${it.value.port}" == dni }
295                 def d
296                 if (selectedLightSwitch) {
297                         d = getChildDevices()?.find {
298                                 it.deviceNetworkId == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
299                         }
300                         if (!d) {
301                                 log.debug "Creating WeMo Light Switch with dni: ${selectedLightSwitch.value.mac}"
302                                 d = addChildDevice("smartthings", "Wemo Light Switch", selectedLightSwitch.value.mac, selectedLightSwitch?.value.hub, [
303                                         "label": selectedLightSwitch?.value?.name ?: "Wemo Light Switch",
304                                         "data": [
305                                                 "mac": selectedLightSwitch.value.mac,
306                                                 "ip": selectedLightSwitch.value.ip,
307                                                 "port": selectedLightSwitch.value.port
308                                         ]
309                                 ])
310                                 def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
311                                 d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
312                                 log.debug "created ${d.displayName} with id $dni"
313                         } else {
314                                 log.debug "found ${d.displayName} with id $dni already exists"
315                         }
316                 }
317         }
318 }
319
320 def ssdpSwitchHandler(evt) {
321         def description = evt.description
322         def hub = evt?.hubId
323         def parsedEvent = parseDiscoveryMessage(description)
324         parsedEvent << ["hub":hub]
325         log.debug parsedEvent
326
327         def switches = getWemoSwitches()
328         if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
329                 //if it doesn't already exist
330                 switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
331         } else {
332                 log.debug "Device was already found in state..."
333                 def d = switches."${parsedEvent.ssdpUSN.toString()}"
334                 boolean deviceChangedValues = false
335                 log.debug "$d.ip <==> $parsedEvent.ip"
336                 if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
337                         d.ip = parsedEvent.ip
338                         d.port = parsedEvent.port
339                         deviceChangedValues = true
340                         log.debug "Device's port or ip changed..."
341                         def child = getChildDevice(parsedEvent.mac)
342                         if (child) {
343                                 child.subscribe(parsedEvent.ip, parsedEvent.port)
344                                 child.poll()
345                         } else {
346                                 log.debug "Device with mac $parsedEvent.mac not found"
347                         }
348                 }
349         }
350 }
351
352 def ssdpMotionHandler(evt) {
353         log.info("ssdpMotionHandler")
354         def description = evt.description
355         def hub = evt?.hubId
356         def parsedEvent = parseDiscoveryMessage(description)
357         parsedEvent << ["hub":hub]
358         log.debug parsedEvent
359
360         def motions = getWemoMotions()
361         if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
362                 //if it doesn't already exist
363                 motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
364         } else { // just update the values
365                 log.debug "Device was already found in state..."
366
367                 def d = motions."${parsedEvent.ssdpUSN.toString()}"
368                 boolean deviceChangedValues = false
369
370                 if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
371                         d.ip = parsedEvent.ip
372                         d.port = parsedEvent.port
373                         deviceChangedValues = true
374                         log.debug "Device's port or ip changed..."
375                 }
376
377                 if (deviceChangedValues) {
378                         def children = getChildDevices()
379                         log.debug "Found children ${children}"
380                         children.each {
381                                 if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
382                                         log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
383                                         it.subscribe(parsedEvent.ip, parsedEvent.port)
384                                 }
385                         }
386                 }
387         }
388 }
389
390 def ssdpLightSwitchHandler(evt) {
391         log.info("ssdpLightSwitchHandler")
392         def description = evt.description
393         def hub = evt?.hubId
394         def parsedEvent = parseDiscoveryMessage(description)
395         parsedEvent << ["hub":hub]
396         log.debug parsedEvent
397
398         def lightSwitches = getWemoLightSwitches()
399
400         if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}")) {
401                 //if it doesn't already exist
402                 lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
403         } else {
404                 log.debug "Device was already found in state..."
405
406                 def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}"
407                 boolean deviceChangedValues = false
408
409                 if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
410                         d.ip = parsedEvent.ip
411                         d.port = parsedEvent.port
412                         deviceChangedValues = true
413                         log.debug "Device's port or ip changed..."
414                         def child = getChildDevice(parsedEvent.mac)
415                         if (child) {
416                                 log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
417                                 child.subscribe(parsedEvent.ip, parsedEvent.port)
418                         } else {
419                                 log.debug "Device with mac $parsedEvent.mac not found"
420                         }
421                 }
422         }
423 }
424
425 void setupHandler(hubResponse) {
426         String contentType = hubResponse?.headers['Content-Type']
427         if (contentType != null && contentType == 'text/xml') {
428                 def body = hubResponse.xml
429                 def wemoDevices = []
430                 String deviceType = body?.device?.deviceType?.text() ?: ""
431                 if (deviceType.startsWith("urn:Belkin:device:controllee:1") || deviceType.startsWith("urn:Belkin:device:insight:1")) {
432                         wemoDevices = getWemoSwitches()
433                 } else if (deviceType.startsWith("urn:Belkin:device:sensor")) {
434                         wemoDevices = getWemoMotions()
435                 } else if (deviceType.startsWith("urn:Belkin:device:lightswitch")) {
436                         wemoDevices = getWemoLightSwitches()
437                 }
438
439                 def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())}
440                 if (wemoDevice) {
441                         wemoDevice.value << [name:body?.device?.friendlyName?.text(), verified: true]
442                 } else {
443                         log.error "/setup.xml returned a wemo device that didn't exist"
444                 }
445         }
446 }
447
448 @Deprecated
449 def locationHandler(evt) {
450         def description = evt.description
451         def hub = evt?.hubId
452         def parsedEvent = parseDiscoveryMessage(description)
453         parsedEvent << ["hub":hub]
454         log.debug parsedEvent
455
456         if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
457                 def switches = getWemoSwitches()
458                 if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
459                         //if it doesn't already exist
460                         switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
461                 } else {
462                         log.debug "Device was already found in state..."
463                         def d = switches."${parsedEvent.ssdpUSN.toString()}"
464                         boolean deviceChangedValues = false
465                         log.debug "$d.ip <==> $parsedEvent.ip"
466                         if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
467                                 d.ip = parsedEvent.ip
468                                 d.port = parsedEvent.port
469                                 deviceChangedValues = true
470                                 log.debug "Device's port or ip changed..."
471                                 def child = getChildDevice(parsedEvent.mac)
472                                 if (child) {
473                                         child.subscribe(parsedEvent.ip, parsedEvent.port)
474                                         child.poll()
475                                 } else {
476                                         log.debug "Device with mac $parsedEvent.mac not found"
477                                 }
478                         }
479                 }
480         }
481         else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
482                 def motions = getWemoMotions()
483                 if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
484                         //if it doesn't already exist
485                         motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
486                 } else { // just update the values
487                         log.debug "Device was already found in state..."
488
489                         def d = motions."${parsedEvent.ssdpUSN.toString()}"
490                         boolean deviceChangedValues = false
491
492                         if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
493                                 d.ip = parsedEvent.ip
494                                 d.port = parsedEvent.port
495                                 deviceChangedValues = true
496                                 log.debug "Device's port or ip changed..."
497                         }
498
499                         if (deviceChangedValues) {
500                                 def children = getChildDevices()
501                                 log.debug "Found children ${children}"
502                                 children.each {
503                                         if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
504                                                 log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
505                                                 it.subscribe(parsedEvent.ip, parsedEvent.port)
506                                         }
507                                 }
508                         }
509                 }
510
511         }
512         else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:lightswitch")) {
513
514                 def lightSwitches = getWemoLightSwitches()
515
516                 if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}"))
517                 { //if it doesn't already exist
518                         lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
519                 } else {
520                         log.debug "Device was already found in state..."
521
522                         def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}"
523                         boolean deviceChangedValues = false
524
525                         if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
526                                 d.ip = parsedEvent.ip
527                                 d.port = parsedEvent.port
528                                 deviceChangedValues = true
529                                 log.debug "Device's port or ip changed..."
530                                 def child = getChildDevice(parsedEvent.mac)
531                                 if (child) {
532                                         log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
533                                         child.subscribe(parsedEvent.ip, parsedEvent.port)
534                                 } else {
535                                         log.debug "Device with mac $parsedEvent.mac not found"
536                                 }
537                         }
538                 }
539         }
540         else if (parsedEvent.headers && parsedEvent.body) {
541                 String headerString = new String(parsedEvent.headers.decodeBase64())?.toLowerCase()
542                 if (headerString != null && (headerString.contains('text/xml') || headerString.contains('application/xml'))) {
543                         def body = parseXmlBody(parsedEvent.body)
544                         if (body?.device?.deviceType?.text().startsWith("urn:Belkin:device:controllee:1"))
545                         {
546                                 def switches = getWemoSwitches()
547                                 def wemoSwitch = switches.find {it?.key?.contains(body?.device?.UDN?.text())}
548                                 if (wemoSwitch)
549                                 {
550                                         wemoSwitch.value << [name:body?.device?.friendlyName?.text(), verified: true]
551                                 }
552                                 else
553                                 {
554                                         log.error "/setup.xml returned a wemo device that didn't exist"
555                                 }
556                         }
557
558                         if (body?.device?.deviceType?.text().startsWith("urn:Belkin:device:insight:1"))
559                         {
560                                 def switches = getWemoSwitches()
561                                 def wemoSwitch = switches.find {it?.key?.contains(body?.device?.UDN?.text())}
562                                 if (wemoSwitch)
563                                 {
564                                         wemoSwitch.value << [name:body?.device?.friendlyName?.text(), verified: true]
565                                 }
566                                 else
567                                 {
568                                         log.error "/setup.xml returned a wemo device that didn't exist"
569                                 }
570                         }
571
572                         if (body?.device?.deviceType?.text().startsWith("urn:Belkin:device:sensor")) //?:1
573                         {
574                                 def motions = getWemoMotions()
575                                 def wemoMotion = motions.find {it?.key?.contains(body?.device?.UDN?.text())}
576                                 if (wemoMotion)
577                                 {
578                                         wemoMotion.value << [name:body?.device?.friendlyName?.text(), verified: true]
579                                 }
580                                 else
581                                 {
582                                         log.error "/setup.xml returned a wemo device that didn't exist"
583                                 }
584                         }
585
586                         if (body?.device?.deviceType?.text().startsWith("urn:Belkin:device:lightswitch")) //?:1
587                         {
588                                 def lightSwitches = getWemoLightSwitches()
589                                 def wemoLightSwitch = lightSwitches.find {it?.key?.contains(body?.device?.UDN?.text())}
590                                 if (wemoLightSwitch)
591                                 {
592                                         wemoLightSwitch.value << [name:body?.device?.friendlyName?.text(), verified: true]
593                                 }
594                                 else
595                                 {
596                                         log.error "/setup.xml returned a wemo device that didn't exist"
597                                 }
598                         }
599                 }
600         }
601 }
602
603 @Deprecated
604 private def parseXmlBody(def body) {
605         def decodedBytes = body.decodeBase64()
606         def bodyString
607         try {
608                 bodyString = new String(decodedBytes)
609         } catch (Exception e) {
610                 // Keep this log for debugging StringIndexOutOfBoundsException issue
611                 log.error("Exception decoding bytes in sonos connect: ${decodedBytes}")
612                 throw e
613         }
614         return new XmlSlurper().parseText(bodyString)
615 }
616
617 private def parseDiscoveryMessage(String description) {
618         def event = [:]
619         def parts = description.split(',')
620         parts.each { part ->
621                 part = part.trim()
622                 if (part.startsWith('devicetype:')) {
623                         part -= "devicetype:"
624                         event.devicetype = part.trim()
625                 }
626                 else if (part.startsWith('mac:')) {
627                         part -= "mac:"
628                         event.mac = part.trim()
629                 }
630                 else if (part.startsWith('networkAddress:')) {
631                         part -= "networkAddress:"
632                         event.ip = part.trim()
633                 }
634                 else if (part.startsWith('deviceAddress:')) {
635                         part -= "deviceAddress:"
636                         event.port = part.trim()
637                 }
638                 else if (part.startsWith('ssdpPath:')) {
639                         part -= "ssdpPath:"
640                         event.ssdpPath = part.trim()
641                 }
642                 else if (part.startsWith('ssdpUSN:')) {
643                         part -= "ssdpUSN:"
644                         event.ssdpUSN = part.trim()
645                 }
646                 else if (part.startsWith('ssdpTerm:')) {
647                         part -= "ssdpTerm:"
648                         event.ssdpTerm = part.trim()
649                 }
650                 else if (part.startsWith('headers')) {
651                         part -= "headers:"
652                         event.headers = part.trim()
653                 }
654                 else if (part.startsWith('body')) {
655                         part -= "body:"
656                         event.body = part.trim()
657                 }
658         }
659         event
660 }
661
662 def doDeviceSync(){
663         log.debug "Doing Device Sync!"
664         discoverAllWemoTypes()
665 }
666
667 private String convertHexToIP(hex) {
668         [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
669 }
670
671 private Integer convertHexToInt(hex) {
672         Integer.parseInt(hex,16)
673 }
674
675 private Boolean canInstallLabs() {
676         return hasAllHubsOver("000.011.00603")
677 }
678
679 private Boolean hasAllHubsOver(String desiredFirmware) {
680         return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
681 }
682
683 private List getRealHubFirmwareVersions() {
684         return location.hubs*.firmwareVersionString.findAll { it }
685 }