Update groveStreams.groovy
[smartapps.git] / third-party / hue-lights-and-groups-and-scenes-oh-my.groovy
1 /**
2  *  Hue Lights and Groups and Scenes (OH MY) - new Hue Service Manager
3  *
4  *  Version 1.4:        Added ability to create / modify / delete Hue Hub Scenes directly from SmartApp
5  *                                      Added ability to create / modify / delete Hue Hub Groups directly from SmartApp
6  *                                      Overhauled communications to / from Hue Hub
7  *                                      Revised child app functions             
8  *
9  *  Authors: Anthony Pastor (infofiend) and Clayton (claytonjn)
10  *
11  */
12
13 definition(
14     name: "Hue Lights and Groups and Scenes (OH MY)",
15     namespace: "info_fiend",
16     author: "Anthony Pastor",
17         description: "Allows you to connect your Philips Hue lights with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your Hue lights (tap the gear on Hue tiles).\n\nPlease update your Hue Bridge first, outside of the SmartThings app, using the Philips Hue app.",
18         category: "SmartThings Labs",
19         iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
20         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
21         singleInstance: true
22 )
23
24 preferences {
25         page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
26         page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
27         page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
28         page(name:"bulbDiscovery", title:"Bulb Discovery", content:"bulbDiscovery", refreshTimeout:5)
29         page(name:"groupDiscovery", title:"Group Discovery", content:"groupDiscovery", refreshTimeout:5)
30         page(name:"sceneDiscovery", title:"Scene Discovery", content:"sceneDiscovery", refreshTimeout:5)
31     page(name:"defaultTransition", title:"Default Transition", content:"defaultTransition", refreshTimeout:5)
32     page(name:"createScene", title:"Create A New Scene", content:"createScene")
33     page(name:"changeSceneName", title:"Change Name Of An Existing Scene", content:"changeSceneName")
34     page(name:"modifyScene", title:"Modify An Existing Scene", content:"modifyScene")
35         page(name:"removeScene", title:"Delete An Existing Scene", content:"removeScene")
36     page(name:"createGroup", title:"Create A New Group", content:"createGroup")
37     page(name:"modifyGroup", title:"Modify An Existing Group", content:"modifyGroup")
38         page(name:"removeGroup", title:"Delete An Existing Group", content:"removeGroup")
39 }
40
41 def mainPage() {
42         def bridges = bridgesDiscovered()
43         if (state.username && bridges) {
44                 return bulbDiscovery()
45         } else {
46                 return bridgeDiscovery()
47         }
48 }
49
50 def bridgeDiscovery(params=[:])
51 {
52         def bridges = bridgesDiscovered()
53         int bridgeRefreshCount = !state.bridgeRefreshCount ? 0 : state.bridgeRefreshCount as int
54         state.bridgeRefreshCount = bridgeRefreshCount + 1
55         def refreshInterval = 3
56
57         def options = bridges ?: []
58         def numFound = options.size() ?: 0
59
60         if(!state.subscribe) {
61                 subscribe(location, null, locationHandler, [filterEvents:false])
62                 state.subscribe = true
63         }
64
65         //bridge discovery request every 15 //25 seconds
66         if((bridgeRefreshCount % 5) == 0) {
67                 discoverBridges()
68         }
69
70         //setup.xml request every 3 seconds except on discoveries
71         if(((bridgeRefreshCount % 3) == 0) && ((bridgeRefreshCount % 5) != 0)) {
72                 verifyHueBridges()
73         }
74
75         return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
76                 section("Please wait while we discover your Hue Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
77                         input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options
78                 }
79         }
80 }
81
82 def bridgeLinking()
83 {
84         int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
85         state.linkRefreshcount = linkRefreshcount + 1
86         def refreshInterval = 3
87
88         def nextPage = ""
89         def title = "Linking with your Hue"
90         def paragraphText = "Press the button on your Hue Bridge to setup a link."
91         if (state.username) { //if discovery worked
92                 nextPage = "bulbDiscovery"
93                 title = "Success! - click 'Next'"
94                 paragraphText = "Linking to your hub was a success! Please click 'Next'!"
95         }
96
97         if((linkRefreshcount % 2) == 0 && !state.username) {
98                 sendDeveloperReq()
99         }
100
101         return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
102                 section("Button Press") {
103                         paragraph """${paragraphText}"""
104                 }
105         }
106 }
107
108 def bulbDiscovery() {
109
110         if (selectedHue) {
111         def bridge = getChildDevice(selectedHue)
112         subscribe(bridge, "bulbList", bulbListHandler)
113     }
114     
115         int bulbRefreshCount = !state.bulbRefreshCount ? 0 : state.bulbRefreshCount as int
116         state.bulbRefreshCount = bulbRefreshCount + 1
117         def refreshInterval = 3
118
119         def optionsBulbs = bulbsDiscovered() ?: []
120     state.optBulbs = optionsBulbs
121         def numFoundBulbs = optionsBulbs.size() ?: 0
122     
123         if((bulbRefreshCount % 3) == 0) {
124         log.debug "START BULB DISCOVERY"
125         discoverHueBulbs()
126         log.debug "END BULB DISCOVERY"
127         }
128
129         return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"groupDiscovery", refreshInterval:refreshInterval, uninstall: true) {
130                 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.") {
131                         input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFoundBulbs} found)", multiple:true, options:optionsBulbs
132                 }
133                 section {
134                         def title = bridgeDni ? "Hue bridge (${bridgeHostname})" : "Find bridges"
135                         href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
136
137                 }
138         }
139 }
140
141 def groupDiscovery() {
142
143         if (selectedHue) {
144         def bridge = getChildDevice(selectedHue)
145         subscribe(bridge, "groupList", groupListHandler)
146     }
147     
148         int groupRefreshCount = !state.groupRefreshCount ? 0 : state.groupRefreshCount as int
149         state.groupRefreshCount = groupRefreshCount + 1
150         
151     def refreshInterval = 3
152     if (state.gChange) { 
153         refreshInterval = 1
154         state.gChange = false
155     }
156
157         def optionsGroups = []
158         def numFoundGroups = []
159     optionsGroups = groupsDiscovered() ?: []
160     numFoundGroups = optionsGroups.size() ?: 0
161     
162 //    if (numFoundGroups == 0)
163 //        app.updateSetting("selectedGroups", "")
164
165         if((groupRefreshCount % 3) == 0) {
166             log.debug "START GROUP DISCOVERY"
167                 discoverHueGroups()
168         log.debug "END GROUP DISCOVERY"
169         }
170
171         return dynamicPage(name:"groupDiscovery", title:"Group Discovery Started!", nextPage:"sceneDiscovery", refreshInterval:refreshInterval, install: false, uninstall: isInitComplete) {
172                 section("Please wait while we discover your Hue Groups. Discovery can take a few minutes, so sit back and relax! Select your device below once discovered.") {
173                         input "selectedGroups", "enum", required:false, title:"Select Hue Groups (${numFoundGroups} found)", multiple:true, options:optionsGroups
174
175                 }
176                 section {
177                         def title = bridgeDni ? "Hue bridge (${bridgeHostname})" : "Find bridges"
178                         href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
179
180                 }
181         if (state.initialized) {
182         
183                         section {
184                                 href "createGroup", title: "Create a Group", description: "Create A New Group On Hue Hub", state: selectedHue ? "complete" : "incomplete" 
185
186                 }
187                         section {
188                                 href "modifyGroup", title: "Modify a Group", description: "Modify The Lights Contained In Existing Group On Hue Hub", state: selectedHue ? "complete" : "incomplete" 
189
190                 }
191                         section {
192                                 href "removeGroup", title: "Delete a Group", description: "Delete An Existing Group From Hue Hub", state: selectedHue ? "complete" : "incomplete" 
193                 }
194
195                 }             
196         }
197 }
198
199
200 def createGroup(params=[:]) { 
201     
202         def theBulbs = state.bulbs
203     def creBulbsNames = []
204     def bulbID 
205     def theBulbName     
206         theBulbs?.each {
207         bulbID = it.key
208         theBulbName = state.bulbs[bulbID].name as String
209             creBulbsNames << [name: theBulbName, id:bulbID]        
210     }               
211     
212     state.creBulbNames = creBulbsNames
213     log.trace "creBulbsNames = ${creBulbsNames}."
214
215         def creTheLightsID = []
216     if (creTheLights) {
217                                 
218                 creTheLights.each { v ->
219                 creBulbsNames.each { m ->
220                 if (m.name == v) {
221                         creTheLightsID << m.id 
222                         }
223             }
224         }
225         }
226     
227         if (creTheLightsID) {log.debug "The Selected Lights ${creTheLights} have ids of ${creTheLightsID}."}
228         
229     if (creTheLightsID && creGroupName) {
230
231         def body = [name: creGroupName, lights: creTheLightsID]
232
233                 log.debug "***************The body for createNewGroup() will be ${body}."
234
235         if ( creGroupConfirmed == "Yes" ) { 
236                 
237             // create new group on Hue Hub
238                 createNewGroup(body)
239             
240             // reset page
241                         app.updateSetting("creGroupConfirmed", null)
242                         app.updateSetting("creGroupName", null)
243                         app.updateSetting("creTheLights", null)
244             app.updateSetting("creTheLightsID", null)
245                         app.updateSetting("creGroup", null)
246
247             
248             state.gChange = true
249              
250         }
251     }
252                 
253           
254
255                 return dynamicPage(name:"createGroup", title:"Create Group", nextPage:"groupDiscovery", refreshInterval:3, install:false, uninstall: false) {
256                         section("Choose Name for New Group") {
257                                 input "creGroupName", "text", title: "Group Name: (hit enter when done)", required: true, submitOnChange: true
258         
259                         if (creGroupName) {
260                         
261                                         input "creTheLights", "enum", title: "Choose The Lights You Want In This Scene", required: true, multiple: true, submitOnChange: true, options:creBulbsNames.name.sort() 
262                                 
263                         if (creTheLights) {
264                                 
265                             paragraph "ATTENTION: Clicking Yes below will IMMEDIATELY create a new group on the Hue Hub called ${creGroupName} using the selected lights." 
266                 
267                                 input "creGroupConfirmed", "enum", title: "Are you sure?", required: true, options: ["Yes", "No"], defaultValue: "No", submitOnChange: true
268                                     
269                         } 
270                 }
271                         }
272         }
273 }
274
275 def modifyGroup(params=[:]) { 
276      
277     
278         def theGroups = []
279     theGroups = state.groups                                                            // scenesDiscovered() ?: [] 
280    
281         def modGroupOptions = []        
282     def grID
283     def theGroupName
284         theGroups?.each {
285         grID = it.key
286         theGroupName = state.groups[grID].name as String
287             modGroupOptions << [name:theGroupName, id:grID]
288         }
289     
290     state.modGroupOptions = modGroupOptions
291         log.trace "modGroupOptions = ${modGroupOptions}."
292
293         def modGroupID
294         if (modGroup) {
295                 state.modGroupOptions.each { m ->
296             if (modGroup == m.name) {
297                         modGroupID = m.id
298                                 log.debug "The selected group ${modGroup} has an id of ${modGroupID}."        
299                         }
300             }                           
301         }       
302
303         def theBulbs = state.bulbs
304     def modBulbsNames = []
305     def bulbID 
306     def theBulbName     
307         theBulbs?.each {
308         bulbID = it.key
309         theBulbName = state.bulbs[bulbID].name as String
310             modBulbsNames << [name: theBulbName, id:bulbID]        
311     }               
312     
313     state.modBulbNames = modBulbsNames
314     log.trace "modBulbsNames = ${modBulbsNames}."
315
316         def modTheLightsID = []
317     if (modTheLights) {
318                                 
319                 modTheLights.each { v ->
320                 modBulbsNames.each { m ->
321 //              log.debug "m.name is ${m.name} and v is ${v}."
322                 if (m.name == v) {
323 //                      log.debug "m.id is ${m.id}."
324                         modTheLightsID << m.id // myBulbs.find{it.name == v}
325                         }
326             }
327         }
328         }
329     
330         if (modTheLightsID) {log.debug "The Selected Lights ${modTheLights} have ids of ${modTheLightsID}."}
331         
332     if (modTheLightsID && modGroupID) {
333
334         def body = [groupID: modGroupID]
335         if (modGroupName) {body.name = modGroupName}
336         if (modTheLightsID) {body.lights = modTheLightsID}
337
338                 log.debug "***************The body for updateGroup() will be ${body}."
339
340         if ( modGroupConfirmed == "Yes" ) {
341                         
342             // modify Group lights (and optionally name) on Hue Hub
343                 updateGroup(body)
344             
345             // modify Group lights within ST
346             def dni = app.id + "/" + modGroupID + "g"            
347                 if (modTheLightsID) { sendEvent(dni, [name: "lights", value: modTheLights]) }
348             
349             // reset page
350             app.updateSetting("modGroupConfirmed", null)
351                         app.updateSetting("modGroupName", "")
352                         app.updateSetting("modTheLights", null)
353                         app.updateSetting("modTheLightsID", null)            
354                         app.updateSetting("modGroup", null)           
355             app.updateSetting("modGroupID", null) 
356             
357             state.gChange = true 
358         }
359     }
360                 
361           
362
363                 return dynamicPage(name:"modifyGroup", title:"Modify Group", nextPage:"groupDiscovery", refreshInterval:3, install:false, uninstall: false) {
364                         section("Choose Group to Modify") {
365                                 input "modGroup", "enum", title: "Modify Group:", required: true, multiple: false, submitOnChange: true, options:modGroupOptions.name.sort() {it.value}
366         
367                         if (modGroup) {
368                         
369                                         input "modTheLights", "enum", title: "Choose The Lights You Want In This Group (reflected in Hue Hub and within ST device)", required: true, multiple: true, submitOnChange: true, options:modBulbsNames.name.sort() 
370                                 
371                         if (modTheLights) {
372                                 input "modGroupName", "text", title: "Change Group Name (Reflected ONLY within Hue Hub - ST only allows name / label change via mobile app or IDE ). ", required: false, submitOnChange: true
373
374                             paragraph "ATTENTION: Clicking Yes below will IMMEDIATELY set the ${modGroup} group to the selected lights." 
375                 
376                                 input "modGroupConfirmed", "enum", title: "Are you sure?", required: true, options: ["Yes", "No"], defaultValue: "No", submitOnChange: true
377                                     
378                         } 
379                 }
380                         }
381         }
382 }
383
384
385
386
387 def removeGroup(params=[:]) {     
388     
389         def theGroups = []
390     theGroups = state.groups
391    
392         def remGroupOptions = []        
393     def grID
394     def theGroupName
395         theGroups?.each {
396         grID = it.key
397         theGroupName = state.groups[grID].name as String
398             remGroupOptions << [name:theGroupName, id:grID]
399         }
400     
401     state.remGroupOptions = remGroupOptions
402         log.trace "remGroupOptions = ${remGroupOptions}."
403
404         def remGroupID
405         if (remGroup) {
406                 state.remGroupOptions.each { m ->
407             if (remGroup == m.name) {
408                         remGroupID = m.id
409                                 log.debug "The selected group ${remGroup} has an id of ${remGroupID}."        
410                         }
411             }                           
412         }       
413         
414     if (remGroupID) {
415
416         def body = [groupID: remGroupID]
417
418                 log.debug "***************The body for deleteGroup() will be ${body}."
419
420         if ( remGroupConfirmed == "Yes" ) { 
421                                   
422                         // delete group from hub                              
423                 deleteGroup(body)             
424             
425             // try deleting group from ST (if exists)            
426             def dni = app.id + "/" + remGroupID + "g"
427             log.debug "remGroup: dni = ${dni}."
428             try {
429                 deleteChildDevice(dni)
430                 log.trace "${remGroup} found and successfully deleted from ST."
431                         } catch (e) {
432                 log.debug "${remGroup} not found within ST - no action taken."                  
433                         }
434             
435             // reset page
436             app.updateSetting("remGroupConfirmed", null)
437                         app.updateSetting("remGroup", null)
438             app.updateSetting("remGroupID", null)
439             
440             state.gChange = true
441         }
442     }
443                 
444           
445
446                 return dynamicPage(name:"removeGroup", title:"Delete Group", nextPage:"groupDiscovery", refreshInterval:3, install:false, uninstall: false) {
447                         section("Choose Group to DELETE") {
448                                 input "remGroup", "enum", title: "Delete Group:", required: true, multiple: false, submitOnChange: true, options:remGroupOptions.name.sort() {it.value}
449         
450                         if (remGroup) {
451                         
452                         paragraph "ATTENTION: Clicking Yes below will IMMEDIATELY DELETE the ${remGroup} group FOREVER!!!" 
453                 
454                         input "remGroupConfirmed", "enum", title: "Are you sure?", required: true, options: ["Yes", "No"], defaultValue: "No", submitOnChange: true
455                                     
456                                 } 
457                 
458                         }
459         }
460 }
461
462
463
464
465 def sceneDiscovery() {
466
467         def isInitComplete = initComplete() == "complete"
468         
469     state.inItemDiscovery = true
470
471     if (selectedHue) {
472         def bridge = getChildDevice(selectedHue)
473         subscribe(bridge, "sceneList", sceneListHandler)
474     }
475
476     def toDo = ""
477      
478         int sceneRefreshCount = !state.sceneRefreshCount ? 0 : state.sceneRefreshCount as int
479         state.sceneRefreshCount = sceneRefreshCount + 1
480         
481     def refreshInterval = 3
482     if (state.sChange) {
483         refreshInterval = 1
484         state.sChange = false
485     }    
486
487         def optionsScenes = scenesDiscovered() ?: []
488         def numFoundScenes = optionsScenes.size() ?: 0
489
490 //      if (numFoundScenes == 0)
491 //        app.updateSetting("selectedScenes", "")
492         
493         if((sceneRefreshCount % 3) == 0) {
494         log.debug "START HUE SCENE DISCOVERY"
495         discoverHueScenes()
496         log.debug "END HUE SCENE DISCOVERY"
497         }
498
499         return dynamicPage(name:"sceneDiscovery", title:"Scene Discovery Started!", nextPage:toDo, refreshInterval:refreshInterval, install: true, uninstall: isInitComplete) {
500                 section("Please wait while we discover your Hue Scenes. Discovery can take a few minutes, so sit back and relax! Select your device below once discovered.") {
501                         input "selectedScenes", "enum", required:false, title:"Select Hue Scenes (${numFoundScenes} found)", multiple:true, options:optionsScenes.sort {it.value}
502                 }
503                 section {
504                         def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
505                         href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
506
507                 }
508                 if (state.initialized) {
509         
510                         section {
511                                 href "createScene", title: "Create a Scene", description: "Create A New Scene On Hue Hub", state: selectedHue ? "complete" : "incomplete" 
512
513                 }
514                         section {
515                                 href "changeSceneName", title: "Change the Name of a Scene", description: "Change Scene Name On Hue Hub", state: selectedHue ? "complete" : "incomplete" 
516
517                 }
518             section {
519                                 href "modifyScene", title: "Modify a Scene", description: "Modify The Lights And / Or The Light Settings For An Existing Scene On Hue Hub", state: selectedHue ? "complete" : "incomplete" 
520
521                 }
522                         section {
523                                 href "removeScene", title: "Delete a Scene", description: "Delete An Existing Scene From Hue Hub", state: selectedHue ? "complete" : "incomplete" 
524                 }
525
526                 }              
527         }
528 }
529
530 def createScene(params=[:]) { 
531     
532         def theBulbs = state.bulbs
533     def creBulbsNames = []
534     def bulbID 
535     def theBulbName     
536         theBulbs?.each {
537         bulbID = it.key
538         theBulbName = state.bulbs[bulbID].name as String
539             creBulbsNames << [name: theBulbName, id:bulbID]        
540     }               
541     
542     state.creBulbNames = creBulbsNames
543     log.trace "creBulbsNames = ${creBulbsNames}."
544
545         def creTheLightsID = []
546     if (creTheLights) {
547                                 
548                 creTheLights.each { v ->
549                 creBulbsNames.each { m ->
550                 if (m.name == v) {
551                         creTheLightsID << m.id // myBulbs.find{it.name == v}
552                         }
553             }
554         }
555         }
556     
557         if (creTheLightsID) {log.debug "The Selected Lights ${creTheLights} have ids of ${creTheLightsID}."}
558         
559     if (creTheLightsID && creSceneName) {
560
561         def body = [name: creSceneName, lights: creTheLightsID]
562
563                 log.debug "***************The body for createNewScene() will be ${body}."
564
565         if ( creSceneConfirmed == "Yes" ) { 
566                  
567             // create new scene on Hue Hub 
568                 createNewScene(body)
569             
570             // refresh page
571                         app.updateSetting("creSceneConfirmed", null)
572                         app.updateSetting("creSceneName", "")
573                         app.updateSetting("creTheLights", null)
574             app.updateSetting("creTheLightsID", null)
575                         app.updateSetting("creScene", null)
576             
577             state.sChange = true 
578         }
579     }
580                 
581           
582
583                 return dynamicPage(name:"createScene", title:"Create Scene", nextPage:"sceneDiscovery", refreshInterval:3, install:false, uninstall: false) {
584                         section("Choose Name for New Scene") {
585                                 input "creSceneName", "text", title: "New Scene Name:", required: true, submitOnChange: true
586         
587                         if (creSceneName) {
588                         
589                                         input "creTheLights", "enum", title: "Choose The Lights You Want In This Scene", required: true, multiple: true, submitOnChange: true, options:creBulbsNames.name.sort() 
590                                 
591                         if (creTheLights) {
592                                 
593                             paragraph "ATTENTION: Clicking Yes below will IMMEDIATELY create a new scene on the Hue Hub called ${creSceneName} using the selected lights' current configuration." 
594                 
595                                 input "creSceneConfirmed", "enum", title: "Are you sure?", required: true, options: ["Yes", "No"], defaultValue: "No", submitOnChange: true
596                                     
597                         } 
598                 }
599                         }
600         }
601 }
602
603
604
605 def modifyScene(params=[:]) {     
606     
607         def theScenes = []
608     theScenes = state.scenes                                                            // scenesDiscovered() ?: [] 
609    
610         def modSceneOptions = []        
611     def scID
612     def theSceneName
613         theScenes?.each {
614         scID = it.key
615         theSceneName = state.scenes[scID].name as String
616             modSceneOptions << [name:theSceneName, id:scID]
617         }
618     
619     state.modSceneOptions = modSceneOptions
620         log.trace "modSceneOptions = ${modSceneOptions}."
621
622         def modSceneID
623         if (modScene) {
624                 state.modSceneOptions.each { m ->
625             if (modScene == m.name) {
626                         modSceneID = m.id
627                                 log.debug "The selected scene ${modScene} has an id of ${modSceneID}."        
628                         }
629             }                           
630         }       
631
632         def theBulbs = state.bulbs
633     def modBulbsNames = []
634     def bulbID 
635     def theBulbName     
636         theBulbs?.each {
637         bulbID = it.key
638         theBulbName = state.bulbs[bulbID].name as String
639             modBulbsNames << [name: theBulbName, id:bulbID]        
640     }               
641     
642     state.modBulbNames = modBulbsNames
643     log.trace "modBulbsNames = ${modBulbsNames}."
644
645         def modTheLightsID = []
646     if (modTheLights) {
647                                 
648                 modTheLights.each { v ->
649                 modBulbsNames.each { m ->
650                 if (m.name == v) {
651                         modTheLightsID << m.id 
652                         }
653             }
654         }
655         }
656     
657         if (modTheLightsID) {log.debug "The Selected Lights ${modTheLights} have ids of ${modTheLightsID}."}
658         
659     if (modTheLightsID && modSceneID) {
660
661         def body = [sceneID: modSceneID]
662         if (modSceneName) {body.name = modSceneName}
663         if (modTheLightsID) {body.lights = modTheLightsID}
664
665                 log.debug "***************The body for updateScene() will be ${body}."
666
667         if ( modSceneConfirmed == "Yes" ) { 
668                                   
669                 // update Scene lights and settings on Hue Hub
670             updateScene(body)
671             
672             // update Scene lights on ST device
673                         def dni = app.id + "/" + modSceneID + "s"            
674                 if (modTheLightsID) { sendEvent(dni, [name: "lights", value: modTheLightsID]) }
675             
676             // reset page
677             
678             app.updateSetting("modSceneConfirmed", null)
679                         app.updateSetting("modSceneName", "")
680                         app.updateSetting("modTheLights", null)           
681             app.updateSetting("modScene", null)    
682             // pause(300)
683             // app.updateSetting("modSceneID", null)    
684                         // app.updateSetting("modTheLightsID", null)           
685             
686             state.sChange = true
687         }
688     }
689                 
690           
691
692                 return dynamicPage(name:"modifyScene", title:"Modify Scene", nextPage:"sceneDiscovery", refreshInterval:3, install:false, uninstall: false) {
693                         section("Choose Scene to Modify") {
694                                 input "modScene", "enum", title: "Modify Scene:", required: true, multiple: false, submitOnChange: true, options:modSceneOptions.name.sort() {it.value}
695         
696                         if (modScene) {
697                         
698                                         input "modTheLights", "enum", title: "Choose The Lights You Want In This Scene (reflected within Hue Hub and on ST Scene device", required: true, multiple: true, submitOnChange: true, options:modBulbsNames.name.sort() 
699                                 
700                         if (modTheLights) {
701                                 input "modSceneName", "text", title: "Change Scene Name (Reflected ONLY within Hue Hub - ST only allows name / label change via mobile app or IDE ).", required: false, submitOnChange: true
702
703                             paragraph "ATTENTION: Clicking Yes below will IMMEDIATELY set the ${modScene} scene to selected lights' current configuration." 
704                 
705                                 input "modSceneConfirmed", "enum", title: "Are you sure?", required: true, options: ["Yes", "No"], defaultValue: "No", submitOnChange: true
706                                     
707                         } 
708                 }
709                         }
710         }
711 }
712
713 def changeSceneName(params=[:]) { 
714     
715     
716         def theScenes = []
717     theScenes = state.scenes                             
718    
719         def renameSceneOptions = []        
720     def scID
721     def theSceneName
722         theScenes?.each {
723         scID = it.key
724         theSceneName = state.scenes[scID].name as String
725             renameSceneOptions << [name:theSceneName, id:scID]
726         }
727     
728     state.renameSceneOptions = renameSceneOptions
729         log.trace "renameSceneOptions = ${renameSceneOptions}."
730
731         def renameSceneID
732         if (oldSceneName) {
733                 state.renameSceneOptions.each { m ->
734             if (oldSceneName == m.name) {
735                         renameSceneID = m.id
736                                 log.debug "The selected scene ${oldSceneName} has an id of ${renameSceneID}."        
737                         }
738             }                           
739         }       
740         
741     if (renameSceneID && newSceneName) {
742
743         def body = [sceneID: renameSceneID, name: newSceneName]
744
745                 log.debug "***************The body for renameScene() will be ${body}."
746
747         if ( renameSceneConfirmed == "Yes" ) { 
748                 
749             // rename scene on Hue Hub 
750                 renameScene(body)
751
752                         // refresh page           
753             app.updateSetting("renameSceneConfirmed", null)
754                         app.updateSetting("newSceneName", "")  
755             app.updateSetting("oldSceneName", null)  
756             app.updateSetting("renameSceneID", null)  
757             
758             state.sChange = true  
759         }
760     }
761                 
762           
763
764                 return dynamicPage(name:"changeSceneName", title:"Change Scene Name", nextPage:"sceneDiscovery", refreshInterval:3, install:false, uninstall: false) {
765                         section("Change NAME of which Scene ") {
766                                 input "oldSceneName", "enum", title: "Change Scene:", required: true, multiple: false, submitOnChange: true, options:renameSceneOptions.name.sort() {it.value}
767         
768                         if (oldSceneName) {
769                 
770                         input "newSceneName", "text", title: "Change Scene Name to (click enter when done): ", required: false, submitOnChange: true
771                         
772                         paragraph "ATTENTION: Clicking Yes below will IMMEDIATELY CHANGE the name of ${oldSceneName} to ${newSceneName}!!!" 
773                 
774                         input "renameSceneConfirmed", "enum", title: "Are you sure?", required: true, options: ["Yes", "No"], defaultValue: "No", submitOnChange: true
775                                     
776                                 } 
777                 
778                         }
779         }
780 }
781
782
783
784
785
786 def removeScene(params=[:]) { 
787     
788     
789         def theScenes = []
790     theScenes = state.scenes                                                            // scenesDiscovered() ?: [] 
791    
792         def remSceneOptions = []        
793     def scID
794     def theSceneName
795         theScenes?.each {
796         scID = it.key
797         theSceneName = state.scenes[scID].name as String
798             remSceneOptions << [name:theSceneName, id:scID]
799         }
800     
801     state.remSceneOptions = remSceneOptions
802         log.trace "remSceneOptions = ${remSceneOptions}."
803
804         def remSceneID
805         if (remScene) {
806                 state.remSceneOptions.each { m ->
807             if (remScene == m.name) {
808                         remSceneID = m.id
809                                 log.debug "The selected scene ${remScene} has an id of ${remSceneID}."        
810                         }
811             }                           
812         }       
813         
814     if (remSceneID) {
815
816         def body = [sceneID: remSceneID]
817
818                 log.debug "***************The body for deleteScene() will be ${body}."
819
820         if ( remSceneConfirmed == "Yes" ) { 
821                 
822             // delete scene on Hue Buh
823                 deleteScene(body)
824                         log.trace "${remScene} found and deleted from Hue Hub."
825             
826             // try deleting child scene from ST
827             def dni = app.id + "/" + renameSceneID + "s"
828             try {
829                 deleteChildDevice(dni)
830                 log.trace "${remScene} found and successfully deleted from ST."
831                         } catch (e) {
832                 log.debug "${remScene} not found within ST - no action taken."                  
833                         }
834             
835                         // refresh page 
836             app.updateSetting("remSceneConfirmed", null)
837                         app.updateSetting("remScene", null)            
838                         app.updateSetting("remSceneID", null)            
839             
840             state.sChange = true  
841         }
842     }
843                 
844           
845
846                 return dynamicPage(name:"removeScene", title:"Delete Scene", nextPage:"sceneDiscovery", refreshInterval:3, install:false, uninstall: false) {
847                         section("Choose Scene to DELETE") {
848                                 input "remScene", "enum", title: "Delete Scene:", required: true, multiple: false, submitOnChange: true, options:remSceneOptions.name.sort() {it.value}
849         
850                         if (remScene) {
851                         
852                         paragraph "ATTENTION: Clicking Yes below will IMMEDIATELY DELETE the ${remScene} scene FOREVER!!!" 
853                 
854                         input "remSceneConfirmed", "enum", title: "Are you sure?", required: true, options: ["Yes", "No"], defaultValue: "No", submitOnChange: true
855                                     
856                                 } 
857                 
858                         }
859         }
860 }
861
862
863
864 def initComplete(){
865         if (state.initialized){
866         return "complete"
867     } else {
868         return null
869     }
870 }
871
872
873 def defaultTransition()
874 {
875         int sceneRefreshCount = !state.sceneRefreshCount ? 0 : state.sceneRefreshCount as int
876         state.sceneRefreshCount = sceneRefreshCount + 1
877         def refreshInterval = 3
878
879         return dynamicPage(name:"defaultTransition", title:"Default Transition", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
880                 section("Choose how long bulbs should take to transition between on/off and color changes. This can be modified per-device.") {
881                         input "selectedTransition", "number", required:true, title:"Transition Time (seconds)", value: 1
882                 }
883         }
884 }
885
886
887
888
889
890
891 private discoverBridges() {
892         sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN))
893 }
894
895 private sendDeveloperReq() {
896         def token = app.id
897         sendHubCommand(new physicalgraph.device.HubAction([
898                 method: "POST",
899                 path: "/api",
900                 headers: [
901                         HOST: bridgeHostnameAndPort
902                 ],
903                 body: [devicetype: "$token-0"]], bridgeDni))
904 }
905
906 private discoverHueBulbs() {
907         log.trace "discoverHueBulbs REACHED"
908     def host = getBridgeIP()
909         sendHubCommand(new physicalgraph.device.HubAction([
910                 method: "GET",
911                 path: "/api/${state.username}/lights",
912                 headers: [
913                         HOST: bridgeHostnameAndPort
914                 ]], bridgeDni))
915 }
916
917 private discoverHueGroups() {
918         log.trace "discoverHueGroups REACHED"
919     def host = getBridgeIP()
920         sendHubCommand(new physicalgraph.device.HubAction([
921                 method: "GET",
922                 path: "/api/${state.username}/groups",
923                 headers: [
924                         HOST: bridgeHostnameAndPort
925                 ]], "${selectedHue}"))
926 }
927
928 private discoverHueScenes() {
929         log.trace "discoverHueScenes REACHED"
930     def host = getBridgeIP()
931     sendHubCommand(new physicalgraph.device.HubAction([
932                 method: "GET",
933                 path: "/api/${state.username}/scenes",
934                 headers: [
935                         HOST: bridgeHostnameAndPort
936                 ]], "${selectedHue}"))
937 }
938
939 private verifyHueBridge(String deviceNetworkId) {
940         log.trace "verifyHueBridge($deviceNetworkId)"
941         sendHubCommand(new physicalgraph.device.HubAction([
942                 method: "GET",
943                 path: "/description.xml",
944                 headers: [
945                         HOST: ipAddressFromDni(deviceNetworkId)
946                 ]], "${selectedHue}"))
947 }
948
949 private verifyHueBridges() {
950         def devices = getHueBridges().findAll { it?.value?.verified != true }
951         log.debug "UNVERIFIED BRIDGES!: $devices"
952         devices.each {
953                 verifyHueBridge((it?.value?.ip + ":" + it?.value?.port))
954         }
955 }
956
957 Map bridgesDiscovered() {
958         def vbridges = getVerifiedHueBridges()
959         def map = [:]
960         vbridges.each {
961                 def value = "${it.value.name}"
962                 def key = it.value.ip + ":" + it.value.port
963                 map["${key}"] = value
964         }
965         map
966 }
967
968 Map bulbsDiscovered() {
969         def bulbs =  getHueBulbs()
970         def map = [:]
971         if (bulbs instanceof java.util.Map) {
972                 bulbs.each {
973                         def value = "${it?.value?.name}"
974                         def key = app.id +"/"+ it?.value?.id
975                         map["${key}"] = value
976                 }
977         } else { //backwards compatable
978                 bulbs.each {
979                         def value = "${it?.name}"
980                         def key = app.id +"/"+ it?.id
981                         map["${key}"] = value
982                 }
983         }
984         map
985 }
986
987 Map groupsDiscovered() {
988         def groups =  getHueGroups()
989         def map = [:]
990         if (groups instanceof java.util.Map) {
991                 groups.each {
992                         def value = "${it?.value?.name}"
993                         def key = app.id +"/"+ it?.value?.id + "g"
994                         map["${key}"] = value
995                 }
996         } else { //backwards compatable
997                 groups.each {
998                         def value = "${it?.name}"
999                         def key = app.id +"/"+ it?.id + "g"
1000                         map["${key}"] = value
1001                 }
1002         }
1003         map
1004 }
1005
1006 Map scenesDiscovered() {
1007         def scenes =  getHueScenes()
1008         def map = [:]
1009         if (scenes instanceof java.util.Map) {
1010                 scenes.each {
1011                         def value = "${it?.value?.name}"
1012                         def key = app.id +"/"+ it?.value?.id + "s"
1013                         map["${key}"] = value
1014                 }
1015         } else { //backwards compatable
1016                 scenes.each {
1017                         def value = "${it?.name}"
1018                         def key = app.id +"/"+ it?.id + "s"
1019                         map["${key}"] = value
1020                 }
1021         }
1022         map
1023 }
1024
1025 def getHueBulbs() {
1026
1027         log.debug "HUE BULBS:"
1028         state.bulbs = state.bulbs ?: [:]        // discoverHueBulbs() //
1029 }
1030
1031 def getHueGroups() {
1032
1033     log.debug "HUE GROUPS:"
1034         state.groups = state.groups ?: [:]              // discoverHueGroups() // 
1035 }
1036
1037 def getHueScenes() {
1038
1039     log.debug "HUE SCENES:"
1040         state.scenes = state.scenes ?: [:]              // discoverHueScenes()   //
1041 }
1042
1043 def getHueBridges() {
1044         state.bridges = state.bridges ?: [:]
1045 }
1046
1047 def getVerifiedHueBridges() {
1048         getHueBridges().findAll{ it?.value?.verified == true }
1049 }
1050
1051 def installed() {
1052         log.trace "Installed with settings: ${settings}"
1053         initialize()
1054 }
1055
1056 def updated() {
1057         log.trace "Updated with settings: ${settings}"
1058         unsubscribe()
1059    unschedule()
1060         initialize()
1061 }
1062
1063 def initialize() {
1064         // remove location subscription aftwards
1065         log.debug "INITIALIZE"
1066         state.initialized = true
1067     state.subscribe = false
1068         state.bridgeSelectedOverride = false
1069
1070         if (selectedHue) {
1071                 addBridge()
1072         }
1073         if (selectedBulbs) {
1074                 addBulbs()
1075         }
1076         if (selectedGroups)
1077         {
1078                 addGroups()
1079         }
1080     if (selectedScenes)
1081         {
1082                 addScenes()
1083         }
1084 /**     if (selectedHue) {
1085       def bridge = getChildDevice(selectedHue)
1086       subscribe(bridge, "bulbList", bulbListHandler)
1087       subscribe(bridge, "groupList", groupListHandler)
1088       subscribe(bridge, "sceneList", sceneListHandler)
1089    }
1090 **/   
1091    runEvery5Minutes("doDeviceSync")
1092    doDeviceSync()
1093 }
1094
1095 def manualRefresh() {
1096    unschedule()
1097    unsubscribe()
1098    doDeviceSync()
1099    runEvery5Minutes("doDeviceSync")
1100 }
1101
1102 def uninstalled(){
1103         state.bridges = [:]
1104     state.username = null
1105 }
1106
1107 // Handles events to add new bulbs
1108 def bulbListHandler(evt) {
1109         def bulbs = [:]
1110         log.trace "Adding bulbs to state..."
1111         //state.bridgeProcessedLightList = true
1112         evt.jsonData.each { k,v ->
1113                 log.trace "$k: $v"
1114                 if (v instanceof Map) {
1115                                 bulbs[k] = [id: k, name: v.name, type: v.type, hub:evt.value]
1116                 }
1117         }
1118         state.bulbs = bulbs
1119         log.info "${bulbs.size()} bulbs found"
1120 }
1121
1122 def groupListHandler(evt) {
1123         def groups =[:]
1124         log.trace "Adding groups to state..."
1125         state.bridgeProcessedGroupList = true
1126         evt.jsonData.each { k,v ->
1127                 log.trace "$k: $v"
1128                 if (v instanceof Map) {
1129                                 groups[k] = [id: k, name: v.name, type: v.type, lights: v.lights, hub:evt.value]
1130                 }
1131         }
1132         state.groups = groups
1133         log.info "${groups.size()} groups found"
1134 }
1135
1136 def sceneListHandler(evt) {
1137         def scenes =[:]
1138         log.trace "Adding scenes to state..."
1139         state.bridgeProcessedSceneList = true
1140         evt.jsonData.each { k,v ->
1141                 log.trace "$k: $v"
1142                 if (v instanceof Map) {
1143                                 scenes[k] = [id: k, name: v.name, type: "Scene", lights: v.lights, hub:evt.value]
1144                 }
1145         }
1146         state.scenes = scenes
1147         log.info "${scenes.size()} scenes found"
1148 }
1149
1150 def addBulbs() {
1151         def bulbs = getHueBulbs()
1152         selectedBulbs.each { dni ->
1153                 def d = getChildDevice(dni)
1154                 if(!d) {
1155                         def newHueBulb
1156                         if (bulbs instanceof java.util.Map) {
1157                                 newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
1158                                 if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light")) {
1159                                         d = addChildDevice("info_fiend", "AP Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
1160                                         d.initialize(newHueBulb?.value.id)
1161                                 } else {
1162                                         d = addChildDevice("info_fiend", "AP Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
1163                                         d.initialize(newHueBulb?.value.id)
1164                                 }
1165                                 d.refresh()
1166                         } else {
1167                 //backwards compatable
1168                                 newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
1169                                 d = addChildDevice("info_fiend", "AP Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
1170                                 d.initialize(newHueBulb?.id)
1171                         }
1172
1173                         log.debug "created ${d.displayName} with id $dni"
1174                         d.refresh()
1175                 } else {
1176                         log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
1177                         if (bulbs instanceof java.util.Map) {
1178                 def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
1179                                 if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") && d.typeName == "Hue Bulb") {
1180                                         d.setDeviceType("AP Hue Lux Bulb")
1181                     d.initialize(newHueBulb?.value.id)
1182                                 }
1183                         }
1184                 }
1185         }
1186 }
1187
1188 def addGroups() {
1189         def groups = getHueGroups()
1190         selectedGroups.each { dni ->
1191                 def d = getChildDevice(dni)
1192                 if(!d)
1193                 {
1194                         def newHueGroup
1195                         if (groups instanceof java.util.Map)
1196                         {
1197                                 newHueGroup = groups.find { (app.id + "/" + it.value.id + "g") == dni }
1198                                 d = addChildDevice("info_fiend", "AP Hue Group", dni, newHueGroup?.value.hub, ["label":newHueGroup?.value.name, "groupID":newHueGroup?.value.id])
1199                                 d.initialize(newHueGroup?.value.id)
1200                         }
1201
1202                         log.debug "created ${d.displayName} with id $dni"
1203                         d.refresh()
1204                 }
1205                 else
1206                 {
1207                         log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
1208                 }
1209         }
1210 }
1211
1212 def addScenes() {
1213         def scenes = getHueScenes()
1214         selectedScenes.each { dni ->
1215                 def d = getChildDevice(dni)
1216                 if(!d)
1217                 {
1218                         def newHueScene
1219                         if (scenes instanceof java.util.Map)
1220                         {
1221                                 newHueScene = scenes.find { (app.id + "/" + it.value.id + "s") == dni }
1222                                 d = addChildDevice("info_fiend", "AP Hue Scene", dni, newHueScene?.value.hub, ["label":newHueScene?.value.name, "sceneID":newHueScene?.value.id])
1223                         }
1224
1225                         log.debug "created ${d.displayName} with id $dni"
1226                         d.refresh()
1227                 }
1228                 else
1229                 {
1230                         log.debug "found ${d.displayName} with id $dni already exists, type: 'Scene'"
1231                 }
1232         }
1233 }
1234
1235 def addBridge() {
1236         def vbridges = getVerifiedHueBridges()
1237         def vbridge = vbridges.find {(it.value.ip + ":" + it.value.port) == selectedHue}
1238
1239         if(vbridge) {
1240                 def d = getChildDevice(selectedHue)
1241                 if(!d) {
1242                         d = addChildDevice("info_fiend", "AP Hue Bridge", selectedHue, vbridge.value.hub, ["data":["mac": vbridge.value.mac]]) // ["preferences":["ip": vbridge.value.ip, "port":vbridge.value.port, "path":vbridge.value.ssdpPath, "term":vbridge.value.ssdpTerm]]
1243
1244                         log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
1245
1246                         sendEvent(d.deviceNetworkId, [name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" +  convertHexToInt(vbridge.value.port)])
1247                         sendEvent(d.deviceNetworkId, [name: "serialNumber", value: vbridge.value.serialNumber])
1248                 }
1249                 else
1250                 {
1251                         log.debug "found ${d.displayName} with id $dni already exists"
1252                 }
1253         }
1254 }
1255
1256 def locationHandler(evt) {
1257         log.info "LOCATION HANDLER: $evt.description"
1258         def description = evt.description
1259         def hub = evt?.hubId
1260
1261         def parsedEvent = parseEventMessage(description)
1262         parsedEvent << ["hub":hub]
1263
1264         if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1"))
1265         { //SSDP DISCOVERY EVENTS
1266                 log.trace "SSDP DISCOVERY EVENTS"
1267                 def bridges = getHueBridges()
1268
1269                 if (!(bridges."${parsedEvent.ssdpUSN.toString()}"))
1270                 { //bridge does not exist
1271                         log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
1272                         bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
1273                 }
1274                 else
1275                 { // update the values
1276
1277                         log.debug "Device was already found in state..."
1278
1279                         def d = bridges."${parsedEvent.ssdpUSN.toString()}"
1280                         def host = parsedEvent.ip + ":" + parsedEvent.port
1281                         if(d.ip != parsedEvent.ip || d.port != parsedEvent.port || host != state.hostname) {
1282
1283                                 log.debug "Device's port or ip changed..."
1284                                 state.hostname = host
1285                                 d.ip = parsedEvent.ip
1286                                 d.port = parsedEvent.port
1287                                 d.name = "Philips hue ($bridgeHostname)"
1288
1289                                 app.updateSetting("selectedHue", host)
1290
1291                                 childDevices.each {
1292                                         if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
1293                                                 log.debug "updating dni for device ${it} with mac ${parsedEvent.mac}"
1294                                                 it.setDeviceNetworkId((parsedEvent.ip + ":" + parsedEvent.port)) //could error if device with same dni already exists
1295                   doDeviceSync()
1296                                         }
1297                                 }
1298                         }
1299                 }
1300         }
1301         else if (parsedEvent.headers && parsedEvent.body)
1302         { // HUE BRIDGE RESPONSES
1303                 log.trace "HUE BRIDGE RESPONSES"
1304                 def headerString = new String(parsedEvent.headers.decodeBase64())
1305                 def bodyString = new String(parsedEvent.body.decodeBase64())
1306                 def type = (headerString =~ /Content-type:.*/) ? (headerString =~ /Content-type:.*/)[0] : null
1307                 def body
1308
1309                 if (type?.contains("xml"))
1310                 { // description.xml response (application/xml)
1311                         body = new XmlSlurper().parseText(bodyString)
1312
1313                         if (body?.device?.modelName?.text().startsWith("Philips hue bridge"))
1314                         {
1315                                 def bridges = getHueBridges()
1316                                 def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
1317                                 if (bridge)
1318                                 {
1319                                         bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
1320                                 }
1321                                 else
1322                                 {
1323                                         log.error "/description.xml returned a bridge that didn't exist"
1324                                 }
1325                         }
1326                 }
1327                 else if(type?.contains("json") && isValidSource(parsedEvent.mac))
1328                 { //(application/json)
1329                         body = new groovy.json.JsonSlurper().parseText(bodyString)
1330
1331                         if (body?.success != null)
1332                         { //POST /api response (application/json)
1333                                 if (body?.success?.username)
1334                                 {
1335                                         state.username = body.success.username[0]
1336                                         state.hostname = selectedHue
1337                                 }
1338                         }
1339                         else if (body.error != null)
1340                         {
1341                                 //TODO: handle retries...
1342                                 log.error "ERROR: application/json ${body.error}"
1343                         }
1344                         else
1345                         { //GET /api/${state.username}/lights response (application/json)
1346                 log.debug "HERE"
1347                                 if (!body.action)
1348                                 { //check if first time poll made it here by mistake
1349
1350                         if(!body?.type?.equalsIgnoreCase("LightGroup") || !body?.type?.equalsIgnoreCase("Room"))
1351                                         {
1352                                                 log.debug "LIGHT GROUP!!!"
1353                                         }
1354
1355                                         def bulbs = getHueBulbs()
1356                                         def groups = getHueGroups()
1357                     def scenes = getHueScenes()
1358
1359                                         log.debug "Adding bulbs, groups, and scenes to state!"
1360                                         body.each { k,v ->
1361                         log.debug v.type
1362                                                 if(v.type == "LightGroup" || v.type == "Room")
1363                                                 {
1364                                                         groups[k] = [id: k, name: v.name, type: v.type, hub:parsedEvent.hub]
1365                                                 }
1366                                                 else if (v.type == "Extended color light" || v.type == "Color light" || v.type == "Dimmable light" )
1367                                                 {
1368                                                         bulbs[k] = [id: k, name: v.name, type: v.type, hub:parsedEvent.hub]
1369                                                 }
1370                         else
1371                         {
1372                                 scenes[k] = [id: k, name: v.name, type: "Scene", hub:parsedEvent.hub]
1373                         }
1374                                         }
1375                                 }
1376                         }
1377                 }
1378         }
1379         else {
1380                 log.trace "NON-HUE EVENT $evt.description"
1381         }
1382 }
1383
1384 private def parseEventMessage(Map event) {
1385         //handles bridge attribute events
1386         return event
1387 }
1388
1389 private def parseEventMessage(String description) {
1390         def event = [:]
1391         def parts = description.split(',')
1392         parts.each { part ->
1393                 part = part.trim()
1394                 if (part.startsWith('devicetype:')) {
1395                         def valueString = part.split(":")[1].trim()
1396                         event.devicetype = valueString
1397                 }
1398                 else if (part.startsWith('mac:')) {
1399                         def valueString = part.split(":")[1].trim()
1400                         if (valueString) {
1401                                 event.mac = valueString
1402                         }
1403                 }
1404                 else if (part.startsWith('networkAddress:')) {
1405                         def valueString = part.split(":")[1].trim()
1406                         if (valueString) {
1407                                 event.ip = valueString
1408                         }
1409                 }
1410                 else if (part.startsWith('deviceAddress:')) {
1411                         def valueString = part.split(":")[1].trim()
1412                         if (valueString) {
1413                                 event.port = valueString
1414                         }
1415                 }
1416                 else if (part.startsWith('ssdpPath:')) {
1417                         def valueString = part.split(":")[1].trim()
1418                         if (valueString) {
1419                                 event.ssdpPath = valueString
1420                         }
1421                 }
1422                 else if (part.startsWith('ssdpUSN:')) {
1423                         part -= "ssdpUSN:"
1424                         def valueString = part.trim()
1425                         if (valueString) {
1426                                 event.ssdpUSN = valueString
1427                         }
1428                 }
1429                 else if (part.startsWith('ssdpTerm:')) {
1430                         part -= "ssdpTerm:"
1431                         def valueString = part.trim()
1432                         if (valueString) {
1433                                 event.ssdpTerm = valueString
1434                         }
1435                 }
1436                 else if (part.startsWith('headers')) {
1437                         part -= "headers:"
1438                         def valueString = part.trim()
1439                         if (valueString) {
1440                                 event.headers = valueString
1441                         }
1442                 }
1443                 else if (part.startsWith('body')) {
1444                         part -= "body:"
1445                         def valueString = part.trim()
1446                         if (valueString) {
1447                                 event.body = valueString
1448                         }
1449                 }
1450         }
1451
1452         event
1453 }
1454
1455 def doDeviceSync(){
1456         log.trace "Doing Hue Device Sync!"
1457
1458         //shrink the large bulb lists
1459         convertBulbListToMap()
1460         convertGroupListToMap()
1461         convertSceneListToMap()
1462
1463    poll()
1464    try {
1465                 subscribe(location, null, locationHandler, [filterEvents:false])
1466    } catch (all) {
1467       log.trace "Subscription already exist"
1468    }
1469         discoverBridges()
1470 }
1471
1472 def isValidSource(macAddress) {
1473         def vbridges = getVerifiedHueBridges()
1474         return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
1475 }
1476
1477 /////////////////////////////////////
1478 //CHILD DEVICE METHODS
1479 /////////////////////////////////////
1480
1481 def parse(childDevice, description) {
1482         def parsedEvent = parseEventMessage(description)
1483
1484         if (parsedEvent.headers && parsedEvent.body) {
1485                 def headerString = new String(parsedEvent.headers.decodeBase64())
1486                 def bodyString = new String(parsedEvent.body.decodeBase64())
1487                 childDevice?.log "parse() - ${bodyString}"
1488                 def body = new groovy.json.JsonSlurper().parseText(bodyString)
1489 //              childDevice?.log "BODY - $body"
1490         
1491                 if (body instanceof java.util.HashMap) {   // POLL RESPONSE
1492                   
1493                         def devices = getChildDevices() 
1494             
1495             // BULBS
1496             for (bulb in body) {
1497                 def d = devices.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
1498                  if (d) {
1499                         if (bulb.value.type == "Extended color light" || bulb.value.type == "Color light" || bulb.value.type == "Dimmable light") {
1500                                         log.trace "Reading Poll for Lights"
1501                                     if (bulb.value.state.reachable) {
1502                                                                 sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"])
1503                                         sendEvent(d.deviceNetworkId, [name: "level", value: Math.round(bulb.value?.state?.bri * 100 / 255)])
1504                                         if (bulb.value.state.sat) {
1505                                                 def hue = Math.min(Math.round(bulb.value?.state?.hue * 100 / 65535), 65535) as int
1506                                             def sat = Math.round(bulb.value?.state?.sat * 100 / 255) as int
1507                                             def hex = colorUtil.hslToHex(hue, sat)
1508                                             sendEvent(d.deviceNetworkId, [name: "color", value: hex])
1509                                     sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
1510                                     sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
1511                                         }
1512                                     if (bulb.value.state.ct) {
1513                                         def ct = mireksToKelvin(bulb.value?.state?.ct) as int
1514                                         sendEvent(d.deviceNetworkId, [name: "colorTemperature", value: ct])
1515                                     }
1516                                     if (bulb.value.state.effect) { sendEvent(d.deviceNetworkId, [name: "effect", value: bulb.value?.state?.effect]) }
1517                                                                         if (bulb.value.state.colormode) { sendEvent(d.deviceNetworkId, [name: "colormode", value: bulb.value?.state?.colormode]) }
1518                                     
1519                                         } else {                // Bulb not reachable
1520                                             sendEvent(d.deviceNetworkId, [name: "switch", value: "off"])
1521                                             sendEvent(d.deviceNetworkId, [name: "level", value: 100])
1522                                             def hue = 23
1523                                             def sat = 56
1524                                             def hex = colorUtil.hslToHex(23, 56)
1525                                     sendEvent(d.deviceNetworkId, [name: "color", value: hex])
1526                                     sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
1527                                     sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
1528                                             def ct = 2710
1529                                     sendEvent(d.deviceNetworkId, [name: "colorTemperature", value: ct])
1530                                     sendEvent(d.deviceNetworkId, [name: "effect", value: "none"]) 
1531                                     sendEvent(d.deviceNetworkId, [name: "colormode", value: hs] )
1532                                         }
1533                             }
1534                     }               
1535             }    
1536
1537 //              devices = getChildDevices()
1538
1539                         // GROUPS
1540             for (bulb in body) {
1541                 def g = devices.find{it.deviceNetworkId == "${app.id}/${bulb.key}g"}
1542                 if (g) {
1543                         if(bulb.value.type == "LightGroup" || bulb.value.type == "Room" || bulb.value.type == "Luminaire" || bulb.value.type == "Lightsource" ) {
1544                                 log.trace "Reading Poll for Groups"
1545                         
1546                         sendEvent(g.deviceNetworkId, [name: "name", value: bulb.value?.name ])
1547                                                 sendEvent(g.deviceNetworkId, [name: "switch", value: bulb.value?.action?.on ? "on" : "off"])
1548                                 sendEvent(g.deviceNetworkId, [name: "level", value: Math.round(bulb.value?.action?.bri * 100 / 255)])
1549                         sendEvent(g.deviceNetworkId, [name: "lights", value: bulb.value?.lights ])
1550                         
1551                         if (bulb.value.action.sat) {
1552                             def hue = Math.min(Math.round(bulb.value?.action?.hue * 100 / 65535), 65535) as int
1553                             def sat = Math.round(bulb.value?.action?.sat * 100 / 255) as int
1554                             def hex = colorUtil.hslToHex(hue, sat)
1555                             sendEvent(g.deviceNetworkId, [name: "color", value: hex])
1556                             sendEvent(g.deviceNetworkId, [name: "hue", value: hue])
1557                             sendEvent(g.deviceNetworkId, [name: "saturation", value: sat])
1558                         }
1559                         
1560                         if (bulb.value.action.ct) {
1561                              def ct = mireksToKelvin(bulb.value?.action?.ct) as int
1562                              sendEvent(g.deviceNetworkId, [name: "colorTemperature", value: ct])
1563                         }
1564                         
1565                         if (bulb.value.action.effect) { sendEvent(g.deviceNetworkId, [name: "effect", value: bulb.value?.action?.effect]) }
1566                                                 if (bulb.value.action.alert) { sendEvent(g.deviceNetworkId, [name: "alert", value: bulb.value?.action?.alert]) }
1567                         if (bulb.value.action.transitiontime) { sendEvent(g.deviceNetworkId, [name: "transitiontime", value: bulb.value?.action?.transitiontime ?: 0]) }
1568                                                 if (bulb.value.action.colormode) { sendEvent(g.deviceNetworkId, [name: "colormode", value: bulb.value?.action?.colormode]) }
1569                    }
1570                }         
1571            }
1572            
1573                 // SCENES
1574           for (bulb in body) {
1575                 def sc = devices.find{it.deviceNetworkId == "${app.id}/${bulb.key}s"}    
1576             if (sc) {
1577                         if ( !bulb.value.type || bulb.value.type == "Scene" || bulb.value.recycle ) {
1578                         log.trace "Reading Poll for Scene"
1579                         sendEvent(sc.deviceNetworkId, [ name: "name", value: bulb.value?.name ])
1580                         sendEvent(sc.deviceNetworkId, [ name: "lights", value: bulb.value?.lights ])
1581                 }
1582                 }
1583                   }
1584           
1585         } else {                // PUT RESPONSE
1586                         def hsl = [:]
1587                         body.each { payload ->
1588                                 childDevice?.log $payload
1589                                 if (payload?.success) {
1590
1591                                         def childDeviceNetworkId = app.id + "/"
1592                                         def eventType
1593                                         body?.success[0].each { k,v ->
1594                                                 log.trace "********************************************************"
1595                                                 log.trace "********************************************************"
1596                                                 if (k.split("/")[1] == "groups") {
1597                                                         childDeviceNetworkId += k.split("/")[2] + "g"
1598                                                 } else if (k.split("/")[1] == "scenes") {
1599                                                         childDeviceNetworkId += k.split("/")[2] + "s"                        
1600                         } else {
1601                                                         childDeviceNetworkId += k.split("/")[2]
1602                                                 }
1603                         
1604                                                 if (!hsl[childDeviceNetworkId]) hsl[childDeviceNetworkId] = [:]
1605                                                 
1606                         eventType = k.split("/")[4]
1607                                                 childDevice?.log "eventType: $eventType"
1608                                                 switch(eventType) {
1609                                                         case "on":
1610                                                                 sendEvent(childDeviceNetworkId, [name: "switch", value: (v == true) ? "on" : "off"])
1611                                                                 break
1612                                                         case "bri":
1613                                                                 sendEvent(childDeviceNetworkId, [name: "level", value: Math.round(v * 100 / 255)])
1614                                                                 break
1615                                                         case "sat":
1616                                                                 hsl[childDeviceNetworkId].saturation = Math.round(v * 100 / 255) as int
1617                                                                 break
1618                                                         case "hue":
1619                                                                 hsl[childDeviceNetworkId].hue = Math.min(Math.round(v * 100 / 65535), 65535) as int
1620                                                                 break
1621                             case "ct":
1622                                         sendEvent(childDeviceNetworkId, [name: "colorTemperature", value: mireksToKelvin(v)])
1623                                 break
1624                                 case "effect":
1625                                 sendEvent(childDeviceNetworkId, [name: "effect", value: v])
1626                                     break
1627                                                         case "colormode":
1628                                                                 sendEvent(childDeviceNetworkId, [name: "colormode", value: v])
1629                                                                 break
1630                             case "lights":
1631                                                                 sendEvent(childDeviceNetworkId, [name: "lights", value: v])
1632                                                                 break
1633                             case "transitiontime":
1634                                 sendEvent(childDeviceNetworkId, [name: "transitiontime", value: v ]) // ?: getSelectedTransition()
1635                                 break    
1636                                                 }
1637                                         }
1638
1639                                 } else if (payload.error) {
1640                                         childDevice?.error "JSON error - ${body?.error}"
1641                                 }
1642
1643                         }
1644
1645                         hsl.each { childDeviceNetworkId, hueSat ->
1646                                 if (hueSat.hue && hueSat.saturation) {
1647                                         def hex = colorUtil.hslToHex(hueSat.hue, hueSat.saturation)
1648                                         childDevice?.log "sending ${hueSat} for ${childDeviceNetworkId} as ${hex}"
1649                                         sendEvent(hsl.childDeviceNetworkId, [name: "color", value: hex])
1650                                 }
1651                         }
1652                 }
1653         } else {   // SOME OTHER RESPONSE
1654                 childDevice?.log "parse - got something other than headers,body..."
1655                 return []
1656         }       
1657 }
1658
1659
1660 def hubVerification(bodytext) {
1661         childDevice?.trace "Bridge sent back description.xml for verification"
1662     def body = new XmlSlurper().parseText(bodytext)
1663     if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
1664         def bridges = getHueBridges()
1665         def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
1666         if (bridge) {
1667             bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
1668         } else {
1669             childDevice?.error "/description.xml returned a bridge that didn't exist"
1670         }
1671     }
1672 }
1673 def setTransitionTime ( childDevice, transitionTime, deviceType ) {
1674         childDevice?.log "HLGS:  Executing 'setTransitionTime'"
1675     def api = "state" 
1676     def dType = "lights"
1677     def deviceID = getId(childDevice) 
1678     if(deviceType == "groups") { 
1679         api = "action"
1680         dType = "groups" 
1681         deviceID = deviceID - "g"
1682     }
1683         def path = dType + "/" + deviceID + "/" + api
1684     childDevice?.log "HLGS: 'transitionTime' path is $path"
1685     
1686         def value = [transitiontime: transitionTime * 10]
1687
1688         childDevice?.log "HLGS: sending ${value}."      
1689         put( path, value )
1690
1691 }
1692
1693 def on( childDevice, percent, transitionTime, deviceType ) {
1694         childDevice?.log "HLGS:  Executing 'on'"
1695     def api = "state" 
1696     def dType = "lights"
1697     def deviceID = getId(childDevice) 
1698     if(deviceType == "groups") { 
1699         api = "action"
1700         dType = "groups" 
1701         deviceID = deviceID - "g"
1702     }
1703         def level = Math.min(Math.round(percent * 255 / 100), 255)
1704     
1705         def path = dType + "/" + deviceID + "/" + api
1706     childDevice?.log "HLGS: 'on' path is $path"
1707     
1708         def value = [on: true, bri: level, transitiontime: transitionTime * 10 ]
1709
1710         childDevice?.log "HLGS:  sending 'on' using ${value}."  
1711         put( path, value )
1712 }
1713
1714 def off( childDevice, transitionTime, deviceType ) {
1715         childDevice?.log "HLGS:  Executing 'off'"
1716     def api = "state" 
1717     def dType = "lights"
1718     def deviceID = getId(childDevice) 
1719     if(deviceType == "groups") { 
1720         api = "action"
1721         dType = "groups" 
1722         deviceID = deviceID - "g"
1723     }
1724         def path = dType + "/" + deviceID + "/" + api
1725     childDevice?.log "HLGS:  'off' path is ${path}."    
1726         def value = [on: false, transitiontime: transitionTime * 10 ]
1727         childDevice?.log "HLGS:  sending 'off' using ${value}." 
1728
1729         put( path, value )
1730 }
1731
1732 def setLevel( childDevice, percent, transitionTime, deviceType ) {
1733         def api = "state" 
1734     def dType = "lights"
1735     def deviceID = getId(childDevice) 
1736     if(deviceType == "groups") { 
1737         api = "action"
1738         dType = deviceType
1739         deviceID = deviceID - "g"
1740     }
1741         def level = Math.min(Math.round(percent * 255 / 100), 255)
1742         def value = [bri: level, on: percent > 0, transitiontime: transitionTime * 10 ]         
1743         def path = dType + "/" + deviceID + "/" + api
1744         childDevice?.log "HLGS: 'on' path is $path"
1745         childDevice?.log "HLGS:  Executing 'setLevel($percent).'"
1746         put( path, value)
1747 }
1748
1749 def setSaturation(childDevice, percent, transitionTime, deviceType) {
1750         def api = "state" 
1751     def dType = "lights"
1752     def deviceID = getId(childDevice) 
1753     
1754     if(deviceType == "groups") { 
1755         api = "action"
1756         dType = deviceType
1757         deviceID = deviceID - "g"
1758     }
1759    def path = dType + "/" + deviceID + "/" + api
1760     
1761         def level = Math.min(Math.round(percent * 255 / 100), 255)
1762     
1763         childDevice?.log "HLGS:  Executing 'setSaturation($percent).'"
1764         put( path, [sat: level, transitiontime: transitionTime * 10 ])
1765 }
1766
1767 def setHue(childDevice, percent, transitionTime, deviceType ) {
1768         def api = "state" 
1769     def dType = "lights"
1770     def deviceID = getId(childDevice) 
1771     if(deviceType == "groups") { 
1772         api = "action"
1773         dType = "groups" 
1774         deviceID = deviceID - "g"
1775     }
1776
1777         def path = dType + "/" + deviceID + "/" + api
1778
1779         childDevice?.log "HLGS: Executing 'setHue($percent)'"
1780         def level =     Math.min(Math.round(percent * 65535 / 100), 65535)
1781         put( path, [hue: level, transitiontime: transitionTime * 10 ])
1782 }
1783
1784 def setColorTemperature(childDevice, huesettings, transitionTime, deviceType ) {
1785         def api = "state" 
1786     def dType = "lights" 
1787     def deviceID = getId(childDevice) 
1788     if(deviceType == "groups") { 
1789         api = "action"
1790         dType = "groups" 
1791         deviceID = deviceID - "g"
1792     }
1793     
1794         def path = dType + "/" + deviceID + "/" + api
1795     
1796         childDevice?.log "HLGS: Executing 'setColorTemperature($huesettings)'"
1797         def value = [on: true, ct: kelvinToMireks(huesettings), transitiontime: transitionTime * 10 ]
1798     
1799         put( path, value )
1800 }
1801
1802 def setColor(childDevice, huesettings, transitionTime, deviceType ) {
1803         def api = "state" 
1804     def dType = "lights"
1805     def deviceID = getId(childDevice) 
1806     if(deviceType == "groups") { 
1807         api = "action"
1808         dType = deviceType
1809         deviceID = deviceID - "g"
1810     }
1811         def path = dType + "/" + deviceID + "/" + api
1812     
1813         def value = [:]
1814         def hue = null
1815     def sat = null
1816     def xy = null
1817
1818     if (huesettings.hex != null) {
1819         value.xy = getHextoXY(huesettings.hex)
1820     } else {
1821         if (huesettings.hue != null)
1822             value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
1823         if (huesettings.saturation != null)
1824             value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
1825     }
1826    
1827         if (huesettings.level > 0 || huesettings.switch == "on") { 
1828         value.on = true
1829     } else if (huesettings.switch == "off") { 
1830         value.on = false
1831     }
1832     value.transitiontime = transitionTime * 10 
1833     value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)        
1834         value.alert = huesettings.alert ? huesettings.alert : "none"
1835
1836         childDevice?.log "HLGS: Executing 'setColor($value).'"
1837         put( path, value)
1838
1839 //      return "Color set to $value"
1840 }
1841
1842 def setGroupScene(childDevice, Number inGroupID) {
1843         childDevice?.log "HLGS: Executing setGroupScene with inGroupID of ${inGroupID}." 
1844         def sceneID = getId(childDevice) // - "s"
1845     def groupID = inGroupID ?: "0"
1846         childDevice?.log "HLGS: setGroupScene scene is ${sceneID} "
1847     String path = "groups/${groupID}/action/"
1848
1849         childDevice?.log "Path = ${path} "
1850
1851         put( path, [scene: sceneID]) 
1852 }
1853
1854 def setToGroup(childDevice, Number inGroupID ) {
1855         childDevice?.log "HLGS: setToGroup with inGroupID of ${inGroupID}." 
1856         def sceneID = getId(childDevice) - "s"
1857     def groupID = inGroupID ?: "0"
1858
1859         childDevice?.log "HLGS: setToGroup: sceneID = ${sceneID} "
1860     String gPath = "groups/${groupID}/action/"
1861
1862         childDevice?.log "Group path = ${gPath} "
1863
1864         put( gPath, [scene: sceneID])
1865 }
1866
1867 /**
1868 def nextLevel(childDevice) {
1869         def level = device.latestValue("level") as Integer ?: 0
1870         if (level < 100) {
1871                 level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
1872         } else { level = 25     }
1873         setLevel(childDevice, level)
1874 }
1875 **/
1876
1877 def getId(childDevice) {
1878         childDevice?.log "HLGS: Executing getId"
1879         if (childDevice.device?.deviceNetworkId?.startsWith("Hue") || childDevice.device?.deviceNetworkId?.startsWith("AP Hue") ) {
1880                 childDevice?.log "Device ID returned is ${childDevice.device?.deviceNetworkId[3..-1]}."
1881                 return childDevice.device?.deviceNetworkId[3..-1]
1882         } else {
1883                 childDevice?.log "Device ID returned (based on SPLIT '/') is ${childDevice.device?.deviceNetworkId.split("/")[-1]}."    
1884                 return childDevice.device?.deviceNetworkId.split("/")[-1]
1885         }
1886 }
1887
1888
1889 def updateSceneFromDevice(childDevice) {
1890         childDevice?.log "HLGS: updateSceneFromDevice: Scene ${childDevice} requests scene use current light states."
1891         def sceneID = getId(childDevice) - "s"
1892
1893         childDevice?.log "HLGS: updateScene: sceneID = ${sceneID} "
1894     String path = "scenes/${sceneID}/"
1895
1896         def value = [storelightstate: true]
1897         childDevice?.log "Path = ${path} "
1898
1899         put( path, value )
1900     
1901 }
1902
1903 def updateScene(body) {
1904         log.trace "HLGS: updateScene "
1905         def sceneID = body.sceneID
1906
1907     String path = "scenes/${sceneID}/"
1908         def value = [lights: body.lights, storelightstate: true]
1909     
1910     if (body.name) {
1911         value.name = body.name
1912         }
1913     
1914         log.trace "HLGS: updateScene:  Path = ${path} & body = ${value}"
1915
1916         put( path, value )
1917     
1918         app.updateSetting("modSceneConfirmed", null)
1919         app.updateSetting("modSceneName", "")
1920         app.updateSetting("modTheLights", [])
1921         app.updateSetting("modScene", null)
1922     state.updateScene == null
1923 //    state.scenes = []
1924
1925 }
1926
1927 def renameScene(body) {
1928         log.trace "HLGS: renameScene "
1929         def sceneID = body.sceneID
1930
1931     String path = "scenes/${sceneID}/"
1932             
1933     def value = body.name
1934         
1935     
1936                 log.trace "HLGS: renameScene:  Path = ${path} & body = ${value}"
1937
1938                 put( path, value )
1939     
1940                 app.updateSetting("renameSceneConfirmed", null)
1941                 app.updateSetting("renameSceneName", "")
1942                 app.updateSetting("renameScene", null)
1943             state.renameScene == null
1944     
1945 //    state.scenes = []
1946 }
1947
1948
1949
1950 def deleteScene(body) {
1951         log.trace "HLGS: deleteScene "
1952         def host = getBridgeIP()
1953
1954
1955         def sceneID = body.sceneID
1956     String path = "scenes/${sceneID}/"
1957         def uri = "/api/${state.username}/$path"
1958
1959         log.trace "HLGS: deleteScene:  uri =  $uri"
1960
1961
1962         sendHubCommand(new physicalgraph.device.HubAction([
1963                 method: "DELETE",
1964                 path: uri,
1965                 headers: [
1966                         HOST: host
1967                 ]
1968     ],"${selectedHue}"))
1969
1970         app.updateSetting("remSceneConfirmed", null)
1971         app.updateSetting("remScene", null)
1972     state.deleteScene == null 
1973 //    state.scenes = []
1974     
1975 }
1976
1977 def createNewScene(body) {
1978         log.trace "HLGS: createNewScene "
1979         def host = getBridgeIP()
1980
1981     String path = "scenes/"
1982         def uri = "/api/${state.username}/$path"
1983
1984         def newBody = [name: body.name, lights: body.lights]
1985     
1986         def bodyJSON = new groovy.json.JsonBuilder(newBody).toString()
1987
1988         log.trace "HLGS: createNewScene:  POST:  $uri"
1989     log.trace "HLGS: createNewScene:  BODY: $bodyJSON"
1990
1991
1992         sendHubCommand(new physicalgraph.device.HubAction([
1993                 method: "POST",
1994                 path: uri,
1995                 headers: [
1996                         HOST: host
1997                 ], body: bodyJSON],"${selectedHue}"))
1998         
1999         app.updateSetting("creSceneConfirmed", null)
2000         app.updateSetting("creSceneName", "")
2001         app.updateSetting("creTheLights", [])
2002         app.updateSetting("creScene", null)
2003     state.createScene == null 
2004 //  state.scenes = []
2005
2006 }
2007
2008 def updateGroup(body) {
2009         log.trace "HLGS: updateGroup "
2010         def groupID = body.groupID
2011
2012     String path = "groups/${groupID}/"
2013         def value = [lights: body.lights]
2014     
2015     if (body.name) {
2016         value.name = body.name
2017         }
2018
2019         log.trace "HLGS: updateGroup:  Path = ${path} & body = ${value}"
2020
2021         put( path, value )
2022     
2023         app.updateSetting("modGroupConfirmed", null)
2024         app.updateSetting("modGroupName", "")
2025         app.updateSetting("modTheLights", [])
2026         app.updateSetting("modGroup", null)
2027     state.updateGroup == null
2028 //    state.groups = []
2029 }
2030
2031 def renameGroup(body) {
2032         log.trace "HLGS: renameGroup "
2033         def groupID = body.groupID
2034
2035     String path = "groups/${groupID}/"
2036         def value = body.name
2037         
2038     
2039                 log.trace "HLGS: renameGroup:  Path = ${path} & body = ${value}"
2040
2041                 put( path, value )
2042     
2043                 app.updateSetting("renameGroupConfirmed", null)
2044                 app.updateSetting("renameGroupName", "")
2045                 app.updateSetting("renameGroup", null)
2046             state.renameGroup == null
2047     
2048 //    state.groups = []
2049 }
2050
2051
2052 def deleteGroup(body) {
2053         log.trace "HLGS: deleteGroup "
2054         def host = getBridgeIP()
2055
2056
2057         def groupID = body.groupID
2058     String path = "groups/${groupID}/"
2059         def uri = "/api/${state.username}/$path"
2060
2061         log.trace "HLGS: deleteGroup:  uri =  $uri"
2062
2063
2064         sendHubCommand(new physicalgraph.device.HubAction([
2065                 method: "DELETE",
2066                 path: uri,
2067                 headers: [
2068                         HOST: host
2069                 ]
2070     ],"${selectedHue}"))
2071
2072         app.updateSetting("remGroupConfirmed", null)
2073         app.updateSetting("remGroup", null)
2074     state.deleteGroup == null        
2075 //    state.groups = []    
2076 }
2077
2078 def createNewGroup(body) {
2079         log.trace "HLGS: createNewGroup "
2080         def host = getBridgeIP()
2081
2082     String path = "groups/"
2083         def uri = "/api/${state.username}/$path"
2084
2085         body.type = "LightGroup"
2086         def bodyJSON = new groovy.json.JsonBuilder(body).toString()
2087
2088         log.trace "HLGS: createNewGroup:  POST:  $uri"
2089     log.trace "HLGS: createNewGroup:  BODY: $bodyJSON"
2090
2091
2092         sendHubCommand(new physicalgraph.device.HubAction([
2093                 method: "POST",
2094                 path: uri,
2095                 headers: [
2096                         HOST: host
2097                 ], body: bodyJSON],"${selectedHue}"))
2098         
2099         app.updateSetting("creGroupConfirmed", null)
2100         app.updateSetting("creGroupName", "")
2101         app.updateSetting("creTheLights", [])
2102         app.updateSetting("creGroup", null)
2103     state.createGroup == null        
2104 //    state.groups = []
2105     
2106 }
2107
2108
2109 private poll() {
2110    def host = getBridgeIP()
2111
2112    def uris = ["/api/${state.username}/lights/", "/api/${state.username}/groups/", "/api/${state.username}/scenes/"]
2113    for (uri in uris) {
2114       try {
2115          sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
2116          HOST: ${host}
2117          """, physicalgraph.device.Protocol.LAN, selectedHue))
2118       } catch (all) {
2119          log.warn "Parsing Body failed - trying again..."
2120          doDeviceSync()
2121       }
2122    }
2123 }
2124
2125
2126
2127 private put(path, body) {
2128         childDevice?.log "HLGS: put: path = ${path}."
2129         def host = getBridgeIP()
2130         def uri = "/api/${state.username}/$path"  // "lights"
2131         
2132     if ( path.startsWith("groups") || path.startsWith("scenes")) {
2133                 uri = "/api/${state.username}/$path"[0..-1]
2134         }
2135     
2136 //    if (path.startsWith("scenes")) {
2137 //              uri = "/api/${state.username}/$path"[0..-1]
2138 //      }
2139
2140         def bodyJSON = new groovy.json.JsonBuilder(body).toString()
2141
2142         childDevice?.log "PUT:  $uri"
2143         childDevice?.log "BODY: $bodyJSON"  // ${body} ?
2144
2145
2146         sendHubCommand(new physicalgraph.device.HubAction([
2147                 method: "PUT",
2148                 path: uri,
2149                 headers: [
2150                         HOST: host
2151                 ],
2152                 body: bodyJSON], "${selectedHue}"))
2153
2154 }
2155
2156 def getBridgeDni() {
2157         state.hostname
2158 }
2159
2160 def getBridgeHostname() {
2161         def dni = state.hostname
2162         if (dni) {
2163                 def segs = dni.split(":")
2164                 convertHexToIP(segs[0])
2165         } else {
2166                 null
2167         }
2168 }
2169
2170 def getBridgeHostnameAndPort() {
2171         def result = null
2172         def dni = state.hostname
2173         if (dni) {
2174                 def segs = dni.split(":")
2175                 result = convertHexToIP(segs[0]) + ":" +  convertHexToInt(segs[1])
2176         }
2177         log.trace "result = $result"
2178         result
2179 }
2180
2181 private getBridgeIP() {
2182         def host = null
2183         if (selectedHue) {
2184         def d = getChildDevice(selectedHue)
2185         if (d) {
2186                 if (d.getDeviceDataByName("networkAddress"))
2187                 host = d.getDeviceDataByName("networkAddress")
2188             else
2189                         host = d.latestState('networkAddress').stringValue
2190         }
2191         if (host == null || host == "") {
2192             def serialNumber = selectedHue
2193             def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
2194             if (!bridge) {
2195                 bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
2196             }
2197             if (bridge?.ip && bridge?.port) {
2198                 if (bridge?.ip.contains("."))
2199                         host = "${bridge?.ip}:${bridge?.port}"
2200                 else
2201                         host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
2202             } else if (bridge?.networkAddress && bridge?.deviceAddress)
2203                 host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
2204         }
2205         log.trace "Bridge: $selectedHue - Host: $host"
2206     }
2207     return host
2208 }
2209
2210 private getHextoXY(String colorStr) {
2211     // For the hue bulb the corners of the triangle are:
2212     // -Red: 0.675, 0.322
2213     // -Green: 0.4091, 0.518
2214     // -Blue: 0.167, 0.04
2215
2216     def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 )
2217     def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 )
2218     def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 )
2219
2220     double[] normalizedToOne = new double[3];
2221     normalizedToOne[0] = (cred / 255);
2222     normalizedToOne[1] = (cgreen / 255);
2223     normalizedToOne[2] = (cblue / 255);
2224     float red, green, blue;
2225
2226     // Make red more vivid
2227     if (normalizedToOne[0] > 0.04045) {
2228        red = (float) Math.pow(
2229                 (normalizedToOne[0] + 0.055) / (1.0 + 0.055), 2.4);
2230     } else {
2231         red = (float) (normalizedToOne[0] / 12.92);
2232     }
2233
2234     // Make green more vivid
2235     if (normalizedToOne[1] > 0.04045) {
2236         green = (float) Math.pow((normalizedToOne[1] + 0.055) / (1.0 + 0.055), 2.4);
2237     } else {
2238         green = (float) (normalizedToOne[1] / 12.92);
2239     }
2240
2241     // Make blue more vivid
2242     if (normalizedToOne[2] > 0.04045) {
2243         blue = (float) Math.pow((normalizedToOne[2] + 0.055) / (1.0 + 0.055), 2.4);
2244     } else {
2245         blue = (float) (normalizedToOne[2] / 12.92);
2246     }
2247
2248     float X = (float) (red * 0.649926 + green * 0.103455 + blue * 0.197109);
2249     float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
2250     float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
2251
2252     float x = (X != 0 ? X / (X + Y + Z) : 0);
2253     float y = (Y != 0 ? Y / (X + Y + Z) : 0);
2254
2255     double[] xy = new double[2];
2256     xy[0] = x;
2257     xy[1] = y;
2258     return xy;
2259 }
2260
2261 private Integer convertHexToInt(hex) {
2262         Integer.parseInt(hex,16)
2263 }
2264
2265 def convertBulbListToMap() {
2266         log.debug "CONVERT BULB LIST"
2267     try {
2268                 if (state.bulbs instanceof java.util.List) {
2269                         def map = [:]
2270                         state.bulbs.unique {it.id}.each { bulb ->
2271                                 map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "hub":bulb.hub]]
2272                         }
2273                         state.bulbs = map
2274                 }
2275         } catch(Exception e) {
2276                 log.error "Caught error attempting to convert bulb list to map: $e"
2277         }
2278 }
2279
2280 def convertGroupListToMap() {
2281         log.debug "CONVERT GROUP LIST"
2282         try {
2283                 if (state.groups instanceof java.util.List) {
2284                         def map = [:]
2285                         state.groups.unique {it.id}.each { group ->
2286                                 map << ["${group.id}g":["id":group.id+"g", "name":group.name, "type": group.type, "lights": group.lights, "hub":group.hub]]
2287                         }
2288                         state.group = map
2289                 }
2290         }
2291         catch(Exception e) {
2292                 log.error "Caught error attempting to convert group list to map: $e"
2293         }
2294 }
2295
2296 def convertSceneListToMap() {
2297         log.debug "CONVERT SCENE LIST"
2298         try {
2299                 if (state.scenes instanceof java.util.List) {
2300                         def map = [:]
2301                         state.scenes.unique {it.id}.each { scene ->
2302                                 map << ["${scene.id}s":["id":scene.id+"s", "name":scene.name, "type": group.type, "lights": group.lights, "hub":scene.hub]]
2303                         }
2304                         state.scene = map
2305                 }
2306         }
2307         catch(Exception e) {
2308                 log.error "Caught error attempting to convert scene list to map: $e"
2309         }
2310 }
2311
2312 private String convertHexToIP(hex) {
2313         [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
2314 }
2315
2316 private Boolean hasAllHubsOver(String desiredFirmware)
2317 {
2318         return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
2319 }
2320
2321 private List getRealHubFirmwareVersions()
2322 {
2323         return location.hubs*.firmwareVersionString.findAll { it }
2324 }
2325
2326 def ipAddressFromDni(dni) {
2327         if (dni) {
2328                 def segs = dni.split(":")
2329                 convertHexToIP(segs[0]) + ":" +  convertHexToInt(segs[1])
2330         } else {
2331                 null
2332         }
2333 }
2334
2335 def getSelectedTransition() {
2336         return settings.selectedTransition
2337 }
2338
2339 def setAlert(childDevice, effect, transitionTime, deviceType ) {
2340         def api = "state" 
2341     def dType = "lights"
2342     def deviceID = getId(childDevice) 
2343     if(deviceType == "groups") { 
2344         api = "action"
2345         dType = deviceType
2346         deviceID = deviceID - "g"
2347     }
2348         def path = dType + "/" + deviceID + "/" + api
2349
2350
2351         if(effect != "none" && effect != "select" && effect != "lselect") { childDevice?.log "Invalid alert value!" }
2352     else {
2353                 def value = [alert: effect, transitiontime: transitionTime * 10 ]
2354                 childDevice?.log "setAlert: Alert ${effect}."
2355                 put( path, value )
2356         }
2357 }
2358
2359 def setEffect(childDevice, effect, transitionTime, deviceType) {
2360         def api = "state" 
2361     def dType = "lights"
2362     def deviceID = getId(childDevice) 
2363     if(deviceType == "groups") { 
2364         api = "action"
2365         dType = deviceType
2366         deviceID = deviceID - "g"
2367     }
2368         def path = dType + "/" + deviceID + "/" + api
2369
2370
2371         def value = [effect: effect, transitiontime: transitionTime * 10 ]
2372         childDevice?.log "setEffect: Effect ${effect}."
2373         put( path, value )
2374 }
2375
2376
2377 int kelvinToMireks(kelvin) {
2378         return 1000000 / kelvin //https://en.wikipedia.org/wiki/Mired
2379 }
2380
2381 int mireksToKelvin(mireks) {
2382         return 1000000 / mireks //https://en.wikipedia.org/wiki/Mired
2383 }