Adding differentiation of two some event subscribing but different capabilities,...
[smartthings-infrastructure.git] / main.groovy
1 //Infrastructure for SmartThings Application
2 //Importing Libraries
3 import groovy.transform.Field
4
5 //Importing Classes
6 import ContactSensor.ContactSensor
7 import ContactSensor.ContactSensors
8 import DoorControl.DoorControl
9 import DoorControl.DoorControls
10 import Lock.Lock
11 import Lock.Locks
12 import Thermostat.Thermostat
13 import Thermostat.Thermostats
14 import Switch.Switch
15 import Switch.Switches
16 import PresenceSensor.PresenceSensor
17 import PresenceSensor.PresenceSensors
18 import Logger.Logger
19 import Location.LocationVar
20 import Location.Phrase
21 import appTouch.Touched
22 import NfcTouch.NfcTouch
23 import AeonKeyFob.AeonKeyFob
24 import AeonKeyFob.AeonKeyFobs
25 import MusicPlayer.MusicPlayer
26 import MusicPlayer.MusicPlayers
27 import MotionSensor.MotionSensor
28 import MotionSensor.MotionSensors
29 import ImageCapture.ImageCapture
30 import ImageCapture.ImageCaptures
31 import SmokeDetector.SmokeDetector
32 import SmokeDetector.SmokeDetectors
33 import Alarm.Alarm
34 import Alarm.Alarms
35 import SpeechSynthesis.SpeechSynthesis
36 import SpeechSynthesis.SpeechSynthesises
37 import Event.Event
38 import Timer.SimulatedTimer
39
40 //Global eventHandler
41 /////////////////////////////////////////////////////////////////////
42 def eventHandler(LinkedHashMap eventDataMap) {
43         def value = eventDataMap["value"]
44         def name = eventDataMap["name"]
45         def deviceId = eventDataMap["deviceId"]
46         def descriptionText = eventDataMap["descriptionText"]
47         def displayed = eventDataMap["displayed"]
48         def linkText = eventDataMap["linkText"]
49         def isStateChange = eventDataMap["isStateChange"]
50         def unit = eventDataMap["unit"]
51         def data = eventDataMap["data"]
52
53         for (int i = 0;i < app2.eventList.size();i++) {
54                 if (app2.eventList[i] == name) {
55                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
56                         evt.add(event)
57                         app2.functionList[i](event)
58                 }
59         }
60
61         for (int i = 0;i < app1.eventList.size();i++) {
62                 if (app1.eventList[i] == name) {
63                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
64                         evt.add(event)
65                         app1.functionList[i](event)
66                 }
67         }
68 }
69
70 //GlobalVariables for both Apps
71 //Create a global variable for send event
72 @Field def sendEvent = {eventDataMap -> 
73                         eventHandler(eventDataMap)
74                         }
75 //Object for location
76 @Field def locationObject = new LocationVar(sendEvent)
77 //Object for touch to call function
78 @Field def appObject = new Touched(sendEvent, 0)
79 //Create a global list for events
80 @Field def evt = []
81 //Global Object for class Touch Sensor!
82 @Field def touchSensorObject = new NfcTouch(sendEvent, 1)
83 //Global Object for class switch!
84 @Field def switchObject = new Switches(sendEvent, 1)
85 //Global Object for class lock!
86 @Field def lockObject = new Locks(sendEvent, 1)
87 //Global Object for class door control!
88 @Field def doorControlObject = new DoorControls(sendEvent, 1)
89 //Global Object for class contact sensor!
90 @Field def contactObject = new ContactSensors(sendEvent, 1)
91 //Global Object for class presence sensor!
92 @Field def presenceSensorObject = new PresenceSensors(sendEvent, 1)
93 //Global Object for class thermostat!
94 @Field def thermostatObject = new Thermostats(sendEvent, 1)
95 //Global Object for class aeon key fob!
96 @Field def aeonKeyFobObject = new AeonKeyFobs(sendEvent, 1)
97 //Global Object for class music player!
98 @Field def musicPlayerObject = new MusicPlayers(sendEvent, 1)
99 //Global Object for class motion sensor!
100 @Field def motionSensorObject = new MotionSensors(sendEvent, 1)
101 //Global Object for class image capture!
102 @Field def imageCaptureObject = new ImageCaptures(sendEvent, 1)
103 //Global Object for class smoke detector!
104 @Field def smokeDetectorObject = new SmokeDetectors(sendEvent, 1)
105 //Global Object for class alarm!
106 @Field def alarmObject = new Alarms(sendEvent, 1)
107 //Global Object for class speech synthesis!
108 @Field def speechSynthesisObject = new SpeechSynthesises(sendEvent, 1)
109
110 //Application #1
111 class App1 {
112         def reference
113         def location
114         def app
115
116         //Extracted objects for App1
117         //Global variable for time!
118         def time = "15:00"
119         //Object for class lock!
120         def lock
121         //Object for class contactSensor!
122         def contact
123         //Global variable for enum!
124         def sendPushMessage = "Yes"
125         //Global variable for phone!
126         def phone = 9495379373
127
128         //Extracted objects for functions for App1
129         //Global Object for functions in subscribe method!
130         def installed = this.&installed
131         //Global Object for functions in subscribe method!
132         def updated = this.&updated
133         //Global Object for functions in subscribe method!
134         def setTimeCallback = this.&setTimeCallback
135         //Global Object for functions in subscribe method!
136         def doorOpenCheck = this.&doorOpenCheck
137         //Global Object for functions in subscribe method!
138         def lockMessage = this.&lockMessage
139
140         App1(Object obj) {
141                 reference = obj
142                 location = obj.locationObject
143                 app = obj.appObject
144                 lock = obj.lockObject
145                 contact = obj.contactObject
146                 //Global variable for settings!
147                 settings = [app:app, time:time, lock:lock, contact:contact, sendPushMessage:sendPushMessage, phone:phone]
148         }
149         //Global variables for each app
150         //Global variable for state[mode]
151         def state = [home:[],away:[],night:[]]
152         //Create a global logger object for methods
153         def log = new Logger()
154         //Create a global variable for Functions in Subscribe method
155         def functionList = []
156         //Create a global variable for Objects in Subscribe method
157         def objectList = []
158         //Create a global variable for Events in Subscribe method
159         def eventList = []
160         //Create a global list for function schedulers
161         def timersFuncList = []
162         //Create a global list for timer schedulers
163         def timersList = []
164         //Create a global variable for settings
165         def settings
166         //Zip code
167         def zipCode = 92617
168
169         //Methods
170         /////////////////////////////////////////////////////////////////////
171         def setLocationMode(String mode) {
172                 location.mode = mode
173         }
174         
175         /////////////////////////////////////////////////////////////////////
176         ////subscribe(obj, func)
177         def subscribe(Object obj, Closure FunctionToCall) {
178                 if (obj == app) {
179                         objectList.add(obj)
180                         eventList.add("Touched")
181                         functionList.add(FunctionToCall)
182                 } else if (obj == location) {
183                         objectList.add(obj)
184                         eventList.add("Location")
185                         functionList.add(FunctionToCall)
186                 }
187         }
188         ////subscribe(obj, event, func)
189         def subscribe(Object obj, String event, Closure FunctionToCall) {
190                 objectList.add(obj)
191                 eventList.add(event)
192                 functionList.add(FunctionToCall)
193         }
194         ////subscribe(obj, event, func, data)
195         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
196                 objectList.add(obj)     
197                 eventList.add(event)
198                 functionList.add(FunctionToCall)
199         }
200         /////////////////////////////////////////////////////////////////////
201         ////runIn(time, func)
202         def runIn(int seconds, Closure functionToCall) {
203                 if (timersFuncList.contains(functionToCall)) {
204                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
205                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
206                 } else {
207                         timersFuncList.add(functionToCall)
208                         timersList.add(new SimulatedTimer())
209                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
210                 }
211         }
212         
213         def runIn(int seconds, Closure functionToCall, LinkedHashMap metaData) {
214                 runIn(seconds, functionToCall)
215         }
216         
217         def runIn(int seconds, String nameOfFunction, LinkedHashMap metaData) {
218                 runIn(seconds, nameOfFunction)
219         }
220         
221         def runIn(int seconds, String nameOfFunction) {
222                 timersFuncList.add(nameOfFunction)
223                 timersList.add(new SimulatedTimer())
224                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(seconds*1000*0) {
225                         "$nameOfFunction"()
226                 }
227         }
228         /////////////////////////////////////////////////////////////////////
229         ////unschedule(func)
230         def unschedule(Closure functionToUnschedule) {
231                 for (int i = 0;i < timersFuncList.size();i++) {
232                         if (timersFuncList[i] == functionToUnschedule) {
233                                 if (timersList != null)
234                                         timersList[i].cancel()
235                         }
236                 }
237         }
238         
239         
240         def unschedule() {
241                 for (int i = 0;i < timersFuncList.size();i++) {
242                         if (timersList != null)
243                                 timersList[i].cancel()
244                 }
245         }
246         /////////////////////////////////////////////////////////////////////
247         ////sendNotificationToContacts(text, recipients)
248         def sendNotificationToContacts(String text, String recipients) {
249                 for (int i = 0;i < recipients.size();i++) {
250                         for (int j = 0;j < location.contacts.size();j++) {
251                                 if (recipients[i] == location.contacts[j]) {
252                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
253                                 }
254                         }
255                 }
256         }
257         /////////////////////////////////////////////////////////////////////
258         ////sendSms(phone, text)
259         def sendSms(long phoneNumber, String text) {
260                 println("Sending \""+text+"\" to "+phoneNumber.toString())
261         }
262         
263         def sendSMS(long phoneNumber, String text) {
264                 println("Sending \""+text+"\" to "+phoneNumber.toString())
265         }
266         /////////////////////////////////////////////////////////////////////
267         ////sendPush(text)
268         def sendPush(String text) {
269                 println(text)
270         }
271         /////////////////////////////////////////////////////////////////////
272         ////schedule(time, nameOfFunction as String)
273         def schedule(String time, String nameOfFunction) {
274                 def _inputTime = time.split(':')
275                 Date date = new Date()  
276                 def _currentTime = date.format("HH:mm:ss").split(':')
277         
278                 //Convert input time and current time to minutes
279                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
280                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
281                 def delay
282         
283                 if (inputTime < currentTime) {
284                         delay = 24*60*60-inputTime+currentTime
285                 } else {
286                         delay = inputTime-currentTime
287                 }
288         
289                 timersFuncList.add(nameOfFunction)
290                 timersList.add(new SimulatedTimer())
291                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000*0) {
292                         "$nameOfFunction"()
293                 }
294         }
295         ////schedule(time, nameOfFunction as Closure)
296         def schedule(String time, Closure nameOfFunction) {
297                 def _inputTime = time.split(':')
298                 Date date = new Date()  
299                 def _currentTime = date.format("HH:mm:ss").split(':')
300         
301                 //Convert input time and current time to minutes
302                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
303                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
304                 def delay
305         
306                 if (inputTime < currentTime) {
307                         delay = 24*60*60-inputTime+currentTime
308                 } else {
309                         delay = inputTime-currentTime
310                 }
311         
312                 if (timersFuncList.contains(nameOfFunction)) {
313                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
314                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds*0, nameOfFunction)
315                 } else {
316                         timersFuncList.add(nameOfFunction)
317                         timersList.add(new SimulatedTimer())
318                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds*0, nameOfFunction)
319                 }
320         }
321         /////////////////////////////////////////////////////////////////////
322         def now() {
323                 return System.currentTimeMillis()
324         }
325         /////////////////////////////////////////////////////////////////////
326         def getTemperatureScale() {
327                 return 'C' //Celsius for now
328         }
329         
330         /////////////////////////////////////////////////////////////////////
331         def getSunriseAndSunset(LinkedHashMap metaData) {
332                 def sunRiseSetInfo = [sunrise:[time:1563800160000],sunset:[time:1563850740000]]
333                 return sunRiseSetInfo
334         }
335
336         def installed() {
337           schedule(time, "setTimeCallback")
338         
339         }
340         
341         def updated(settings) {
342           unschedule()
343           schedule(time, "setTimeCallback")
344         }
345         
346         def setTimeCallback() {
347           if (contact) {
348             doorOpenCheck()
349           } else {
350             lockMessage()
351             lock.lock()
352           }
353         }
354         def doorOpenCheck() {
355           def currentState = contact.contactState
356           if (currentState?.value == "open") {
357             def msg = "${contact.displayName} is open.  Scheduled lock failed."
358             log.info msg
359             if (sendPushMessage) {
360               sendPush msg
361             }
362             if (phone) {
363               sendSms phone, msg
364             }
365           } else {
366             lockMessage()
367             lock.lock()
368           }
369         }
370         
371         def lockMessage() {
372           def msg = "Locking ${lock.displayName} due to scheduled lock."
373           log.info msg
374           if (sendPushMessage) {
375             sendPush msg
376           }
377           if (phone) {
378             sendSms phone, msg
379           }
380         }
381 }
382
383
384 //Application #2
385 class App2 {
386         def reference
387         def location
388         def app
389
390         //Extracted objects for App2
391         //Object for class Touch Sensor!
392         def tag
393         //Object for class switch!
394         def switch1
395         //Object for class lock!
396         def lock
397         //Object for class door control!
398         def garageDoor
399         //Global variable for enum!
400         def masterSwitch = "Yes"
401         //Global variable for enum!
402         def masterLock = "Yes"
403         //Global variable for enum!
404         def masterDoor = "Yes"
405
406         //Extracted objects for functions for App2
407         //Global Object for functions in subscribe method!
408         def pageTwo = this.&pageTwo
409         //Global Object for functions in subscribe method!
410         def installed = this.&installed
411         //Global Object for functions in subscribe method!
412         def updated = this.&updated
413         //Global Object for functions in subscribe method!
414         def initialize = this.&initialize
415         //Global Object for functions in subscribe method!
416         def touchHandler = this.&touchHandler
417
418         App2(Object obj) {
419                 reference = obj
420                 location = obj.locationObject
421                 app = obj.appObject
422                 tag = obj.touchSensorObject
423                 switch1 = obj.switchObject
424                 lock = obj.lockObject
425                 garageDoor = obj.doorControlObject
426                 //Global variable for settings!
427                 settings = [app:app, tag:tag, switch1:switch1, lock:lock, garageDoor:garageDoor, masterSwitch:masterSwitch, masterLock:masterLock, masterDoor:masterDoor]
428         }
429         //Global variables for each app
430         //Global variable for state[mode]
431         def state = [home:[],away:[],night:[]]
432         //Create a global logger object for methods
433         def log = new Logger()
434         //Create a global variable for Functions in Subscribe method
435         def functionList = []
436         //Create a global variable for Objects in Subscribe method
437         def objectList = []
438         //Create a global variable for Events in Subscribe method
439         def eventList = []
440         //Create a global list for function schedulers
441         def timersFuncList = []
442         //Create a global list for timer schedulers
443         def timersList = []
444         //Create a global variable for settings
445         def settings
446         //Zip code
447         def zipCode = 92617
448
449         //Methods
450         /////////////////////////////////////////////////////////////////////
451         def setLocationMode(String mode) {
452                 location.mode = mode
453         }
454         
455         /////////////////////////////////////////////////////////////////////
456         ////subscribe(obj, func)
457         def subscribe(Object obj, Closure FunctionToCall) {
458                 if (obj == app) {
459                         objectList.add(obj)
460                         eventList.add("Touched")
461                         functionList.add(FunctionToCall)
462                 } else if (obj == location) {
463                         objectList.add(obj)
464                         eventList.add("Location")
465                         functionList.add(FunctionToCall)
466                 }
467         }
468         ////subscribe(obj, event, func)
469         def subscribe(Object obj, String event, Closure FunctionToCall) {
470                 objectList.add(obj)
471                 eventList.add(event)
472                 functionList.add(FunctionToCall)
473         }
474         ////subscribe(obj, event, func, data)
475         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
476                 objectList.add(obj)     
477                 eventList.add(event)
478                 functionList.add(FunctionToCall)
479         }
480         /////////////////////////////////////////////////////////////////////
481         ////runIn(time, func)
482         def runIn(int seconds, Closure functionToCall) {
483                 if (timersFuncList.contains(functionToCall)) {
484                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
485                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
486                 } else {
487                         timersFuncList.add(functionToCall)
488                         timersList.add(new SimulatedTimer())
489                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
490                 }
491         }
492         
493         def runIn(int seconds, Closure functionToCall, LinkedHashMap metaData) {
494                 runIn(seconds, functionToCall)
495         }
496         
497         def runIn(int seconds, String nameOfFunction, LinkedHashMap metaData) {
498                 runIn(seconds, nameOfFunction)
499         }
500         
501         def runIn(int seconds, String nameOfFunction) {
502                 timersFuncList.add(nameOfFunction)
503                 timersList.add(new SimulatedTimer())
504                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(seconds*1000*0) {
505                         "$nameOfFunction"()
506                 }
507         }
508         /////////////////////////////////////////////////////////////////////
509         ////unschedule(func)
510         def unschedule(Closure functionToUnschedule) {
511                 for (int i = 0;i < timersFuncList.size();i++) {
512                         if (timersFuncList[i] == functionToUnschedule) {
513                                 if (timersList != null)
514                                         timersList[i].cancel()
515                         }
516                 }
517         }
518         
519         
520         def unschedule() {
521                 for (int i = 0;i < timersFuncList.size();i++) {
522                         if (timersList != null)
523                                 timersList[i].cancel()
524                 }
525         }
526         /////////////////////////////////////////////////////////////////////
527         ////sendNotificationToContacts(text, recipients)
528         def sendNotificationToContacts(String text, String recipients) {
529                 for (int i = 0;i < recipients.size();i++) {
530                         for (int j = 0;j < location.contacts.size();j++) {
531                                 if (recipients[i] == location.contacts[j]) {
532                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
533                                 }
534                         }
535                 }
536         }
537         /////////////////////////////////////////////////////////////////////
538         ////sendSms(phone, text)
539         def sendSms(long phoneNumber, String text) {
540                 println("Sending \""+text+"\" to "+phoneNumber.toString())
541         }
542         
543         def sendSMS(long phoneNumber, String text) {
544                 println("Sending \""+text+"\" to "+phoneNumber.toString())
545         }
546         /////////////////////////////////////////////////////////////////////
547         ////schedule(time, nameOfFunction as String)
548         def schedule(String time, String nameOfFunction) {
549                 def _inputTime = time.split(':')
550                 Date date = new Date()  
551                 def _currentTime = date.format("HH:mm:ss").split(':')
552         
553                 //Convert input time and current time to minutes
554                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
555                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
556                 def delay
557         
558                 if (inputTime < currentTime) {
559                         delay = 24*60*60-inputTime+currentTime
560                 } else {
561                         delay = inputTime-currentTime
562                 }
563         
564                 timersFuncList.add(nameOfFunction)
565                 timersList.add(new SimulatedTimer())
566                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000*0) {
567                         "$nameOfFunction"()
568                 }
569         }
570         ////schedule(time, nameOfFunction as Closure)
571         def schedule(String time, Closure nameOfFunction) {
572                 def _inputTime = time.split(':')
573                 Date date = new Date()  
574                 def _currentTime = date.format("HH:mm:ss").split(':')
575         
576                 //Convert input time and current time to minutes
577                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
578                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
579                 def delay
580         
581                 if (inputTime < currentTime) {
582                         delay = 24*60*60-inputTime+currentTime
583                 } else {
584                         delay = inputTime-currentTime
585                 }
586         
587                 if (timersFuncList.contains(nameOfFunction)) {
588                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
589                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds*0, nameOfFunction)
590                 } else {
591                         timersFuncList.add(nameOfFunction)
592                         timersList.add(new SimulatedTimer())
593                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds*0, nameOfFunction)
594                 }
595         }
596         /////////////////////////////////////////////////////////////////////
597         def now() {
598                 return System.currentTimeMillis()
599         }
600         /////////////////////////////////////////////////////////////////////
601         def getTemperatureScale() {
602                 return 'C' //Celsius for now
603         }
604         
605         /////////////////////////////////////////////////////////////////////
606         def getSunriseAndSunset(LinkedHashMap metaData) {
607                 def sunRiseSetInfo = [sunrise:[time:1563800160000],sunset:[time:1563850740000]]
608                 return sunRiseSetInfo
609         }
610
611         def pageTwo() {
612                 dynamicPage(name: "pageTwo") {
613                 section("If set, the state of these devices will be toggled each time the tag is touched, " + 
614                         "e.g. a light that's on will be turned off and one that's off will be turned on, " +
615                         "other devices of the same type will be set to the same state as their master device. " +
616                         "If no master is designated then the majority of devices of the same type will be used " +
617                         "to determine whether to turn on or off the devices.") {
618                     
619                     if (switch1 || masterSwitch) {
620                         input "masterSwitch", "enum", title: "Master switch", options: switch1.collect{[(it.id): it.displayName]}, required: false
621                     }
622                     if (lock || masterLock) {
623                         input "masterLock", "enum", title: "Master lock", options: lock.collect{[(it.id): it.displayName]}, required: false
624                     }
625                     if (garageDoor || masterDoor) {
626                         input "masterDoor", "enum", title: "Master door", options: garageDoor.collect{[(it.id): it.displayName]}, required: false
627                     }            
628                         }
629                         section([mobileOnly:true]) {
630                                 label title: "Assign a name", required: false
631                                 mode title: "Set for specific mode(s)", required: false
632                         }        
633             }
634         }
635         
636         def installed() {
637                 log.debug "Installed with settings: ${settings}"
638         
639                 initialize()
640         }
641         
642         def updated() {
643                 log.debug "Updated with settings: ${settings}"
644         
645                 unsubscribe()
646                 initialize()
647         }
648         
649         def initialize() {
650                 subscribe tag, "nfcTouch", touchHandler
651             subscribe app, touchHandler
652         }
653         
654         private currentStatus(devices, master, attribute) {
655                 log.trace "currentStatus($devices, $master, $attribute)"
656                 def result = null
657                 if (master) {
658                 result = devices.find{it.id == master}?.currentValue(attribute)
659             }
660             else {
661                 def map = [:]
662                 devices.each {
663                         def value = it.currentValue(attribute)
664                     map[value] = (map[value] ?: 0) + 1
665                     log.trace "$it.displayName: $value"
666                 }
667                 log.trace map
668                 result = map.collect{it}.sort{it.value}[-1].key
669             }
670             log.debug "$attribute = $result"
671             result
672         }
673         
674         def touchHandler(evt) {
675                 log.trace "touchHandler($evt.descriptionText)"
676             if (switch1) {
677                 def status = currentStatus(switch1, masterSwitch, "switch")
678                 switch1.each {
679                     if (status == "on") {
680                         it.off()
681                     }
682                     else {
683                         it.on()
684                     }
685                 }
686             }
687             
688             if (lock) {
689                 def status = currentStatus(lock, masterLock, "lock")
690                 lock.each {
691                     if (status == "locked") {
692                         lock.unlock()
693                     }
694                     else {
695                         lock.lock()
696                     }
697                 }
698             }
699             
700             if (garageDoor) {
701                 def status = currentStatus(garageDoor, masterDoor, "status")
702                 garageDoor.each {
703                         if (status == "open") {
704                         it.close()
705                     }
706                     else {
707                         it.open()
708                     }
709                 }
710             }
711         }
712 }
713
714 @Field def app1 = new App1(this)
715 @Field def app2 = new App2(this)
716 app1.installed()
717 app2.installed()
718