a1474f1fcd60a238b457107787b61fcc76bf007f
[smartthings-infrastructure.git] / main.groovy
1 //Infrastructure for SmartThings Application
2 //Importing Libraries
3 import groovy.transform.Field
4 import groovy.json.JsonSlurper
5
6 //Importing Classes
7 import ContactSensor.ContactSensor
8 import ContactSensor.ContactSensors
9 import DoorControl.DoorControl
10 import DoorControl.DoorControls
11 import Lock.Lock
12 import Lock.Locks
13 import Thermostat.Thermostat
14 import Thermostat.Thermostats
15 import Switch.Switch
16 import Switch.Switches
17 import PresenceSensor.PresenceSensor
18 import PresenceSensor.PresenceSensors
19 import Logger.Logger
20 import Location.LocationVar
21 import Location.Phrase
22 import appTouch.Touched
23 import NfcTouch.NfcTouch
24 import AeonKeyFob.AeonKeyFob
25 import AeonKeyFob.AeonKeyFobs
26 import MusicPlayer.MusicPlayer
27 import MusicPlayer.MusicPlayers
28 import MotionSensor.MotionSensor
29 import MotionSensor.MotionSensors
30 import ImageCapture.ImageCapture
31 import ImageCapture.ImageCaptures
32 import SmokeDetector.SmokeDetector
33 import SmokeDetector.SmokeDetectors
34 import Alarm.Alarm
35 import Alarm.Alarms
36 import SpeechSynthesis.SpeechSynthesis
37 import SpeechSynthesis.SpeechSynthesises
38 import AccelerationSensor.AccelerationSensor
39 import AccelerationSensor.AccelerationSensors
40 import Battery.Battery
41 import Battery.Batteries
42 import BeaconSensor.BeaconSensor
43 import BeaconSensor.BeaconSensors
44 import CarbonMonoxideDetector.CarbonMonoxideDetector
45 import CarbonMonoxideDetector.CarbonMonoxideDetectors
46 import ColorControl.ColorControl
47 import ColorControl.ColorControls
48 import EnergyMeter.EnergyMeter
49 import EnergyMeter.EnergyMeters
50 import IlluminanceMeasurement.IlluminanceMeasurement
51 import IlluminanceMeasurement.IlluminanceMeasurements
52 import PowerMeter.PowerMeter
53 import PowerMeter.PowerMeters
54 import RelativeHumidityMeasurement.RelativeHumidityMeasurement
55 import RelativeHumidityMeasurement.RelativeHumidityMeasurements
56 import RelaySwitch.RelaySwitch
57 import RelaySwitch.RelaySwitches
58 import SleepSensor.SleepSensor
59 import SleepSensor.SleepSensors
60 import StepSensor.StepSensor
61 import StepSensor.StepSensors
62 import SwitchLevel.SwitchLevel
63 import SwitchLevel.SwitchLevels
64 import TemperatureMeasurement.TemperatureMeasurement
65 import TemperatureMeasurement.TemperatureMeasurements
66 import WaterSensor.WaterSensor
67 import WaterSensor.WaterSensors
68 import Valve.Valve
69 import Valve.Valves
70 import MobilePresence.MobilePresence
71 import MobilePresence.MobilePresences
72 import Event.Event
73 import AtomicState.AtomicState
74 import Timer.SimulatedTimer
75
76 //JPF's Verify API
77 import gov.nasa.jpf.vm.Verify
78
79 //Global eventHandler
80 /////////////////////////////////////////////////////////////////////
81 def eventHandler(LinkedHashMap eventDataMap) {
82         def value = eventDataMap["value"]
83         def name = eventDataMap["name"]
84         def deviceId = eventDataMap["deviceId"]
85         def descriptionText = eventDataMap["descriptionText"]
86         def displayed = eventDataMap["displayed"]
87         def linkText = eventDataMap["linkText"]
88         def isStateChange = eventDataMap["isStateChange"]
89         def unit = eventDataMap["unit"]
90         def data = eventDataMap["data"]
91
92         for (int i = 0;i < app2.eventList.size();i++) {
93                 if (app2.eventList[i] == name) {
94                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
95                         app2.functionList[i](event)
96                 }
97         }
98
99         for (int i = 0;i < app1.eventList.size();i++) {
100                 if (app1.eventList[i] == name) {
101                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
102                         app1.functionList[i](event)
103                 }
104         }
105 }
106
107 //GlobalVariables for both Apps
108 //Create a global variable for send event
109 @Field def sendEvent = {eventDataMap -> 
110                         eventHandler(eventDataMap)
111                         }
112 //Object for location
113 @Field def locationObject = new LocationVar(sendEvent)
114 //Object for touch to call function
115 @Field def appObject = new Touched(sendEvent, 0)
116 //Create a global list for events
117 //@Field def evt = []
118 //Global Object for class AtomicState!
119 @Field def atomicState = new AtomicState()
120 //Global Object for class Touch Sensor!
121 @Field def touchSensorObject = new NfcTouch(sendEvent, 1)
122 //Global Object for class switch!
123 @Field def switchObject = new Switches(sendEvent, 1)
124 //Global Object for class lock!
125 @Field def lockObject = new Locks(sendEvent, 1)
126 //Global Object for class door control!
127 @Field def doorControlObject = new DoorControls(sendEvent, 1)
128 //Global Object for class contact sensor!
129 @Field def contactObject = new ContactSensors(sendEvent, 1)
130 //Global Object for class presence sensor!
131 @Field def presenceSensorObject = new PresenceSensors(sendEvent, 1)
132 //Global Object for class thermostat!
133 @Field def thermostatObject = new Thermostats(sendEvent, 1)
134 //Global Object for class aeon key fob!
135 @Field def aeonKeyFobObject = new AeonKeyFobs(sendEvent, 1)
136 //Global Object for class music player!
137 @Field def musicPlayerObject = new MusicPlayers(sendEvent, 1)
138 //Global Object for class motion sensor!
139 @Field def motionSensorObject = new MotionSensors(sendEvent, 1)
140 //Global Object for class image capture!
141 @Field def imageCaptureObject = new ImageCaptures(sendEvent, 1)
142 //Global Object for class smoke detector!
143 @Field def smokeDetectorObject = new SmokeDetectors(sendEvent, 1)
144 //Global Object for class alarm!
145 @Field def alarmObject = new Alarms(sendEvent, 1)
146 //Global Object for class speech synthesis!
147 @Field def speechSynthesisObject = new SpeechSynthesises(sendEvent, 1)
148 //Global Object for class acceleration sensor!
149 @Field def accelerationSensorObject = new AccelerationSensors(sendEvent, 1)
150 //Global Object for class Battery!
151 @Field def batteryObject = new Batteries(sendEvent, 1)
152 //Global Object for class beacon sensor!
153 @Field def beaconSensorObject = new BeaconSensors(sendEvent, 1)
154 //Global Object for class carbon monoxide!
155 @Field def carbonMonoxideDetectorObject = new CarbonMonoxideDetectors(sendEvent, 1)
156 //Global Object for class color control!
157 @Field def colorControlObject = new ColorControls(sendEvent, 1)
158 //Global Object for class energy meter!
159 @Field def energyMeterObject = new EnergyMeters(sendEvent, 1)
160 //Global Object for class illuminance measurement!
161 @Field def illuminanceMeasurementObject = new IlluminanceMeasurements(sendEvent, 1)
162 //Global Object for class power meter!
163 @Field def powerMeterObject = new PowerMeters(sendEvent, 1)
164 //Global Object for class humidity measurement!
165 @Field def humidityMeasurementObject = new RelativeHumidityMeasurements(sendEvent, 1)
166 //Global Object for class relay switch!
167 @Field def relaySwitchObject = new RelaySwitches(sendEvent, 1)
168 //Global Object for class sleep sensor!
169 @Field def sleepSensorObject = new SleepSensors(sendEvent, 1)
170 //Global Object for class step sensor!
171 @Field def stepSensorObject = new StepSensors(sendEvent, 1)
172 //Global Object for class switch level!
173 @Field def switchLevelObject = new SwitchLevels(sendEvent, 1)
174 //Global Object for class temperature measurement!
175 @Field def temperatureMeasurementObject = new TemperatureMeasurements(sendEvent, 1)
176 //Global Object for class water sensor!
177 @Field def waterSensorObject = new WaterSensors(sendEvent, 1)
178 //Global Object for class valves!
179 @Field def valveObject = new Valves(sendEvent, 1)
180 //Global Object for class mobile presence!
181 @Field def mobilePresenceObject = new MobilePresences(sendEvent, 1)
182
183 //Application #1
184 class App1 {
185         def reference
186         def location
187         def app
188         def atomicState
189
190         //Extracted objects for App1
191         //Object for class lock!
192         def lock1
193         //Object for class contactSensor!
194         def contact
195         //Global variable for number!
196         def minutesLater = 70
197         //Global variable for number!
198         def secondsLater = 93
199         //Global variable for contact!
200         def recipients = "AJ"
201         //Global variable for phone!
202         def phoneNumber = 9495379373
203
204         //Extracted objects for functions for App1
205         //Global Object for functions in subscribe method!
206         def installed = this.&installed
207         //Global Object for functions in subscribe method!
208         def updated = this.&updated
209         //Global Object for functions in subscribe method!
210         def initialize = this.&initialize
211         //Global Object for functions in subscribe method!
212         def lockDoor = this.&lockDoor
213         //Global Object for functions in subscribe method!
214         def unlockDoor = this.&unlockDoor
215         //Global Object for functions in subscribe method!
216         def doorHandler = this.&doorHandler
217
218         App1(Object obj) {
219                 reference = obj
220                 location = obj.locationObject
221                 app = obj.appObject
222                 atomicState = obj.atomicState
223                 lock1 = obj.lockObject
224                 contact = obj.contactObject
225                 //Global variable for settings!
226                 settings = [app:app, lock1:lock1, contact:contact, minutesLater:minutesLater, secondsLater:secondsLater, recipients:recipients, phoneNumber:phoneNumber]
227         }
228         //Global variables for each app
229         //Global variable for state[mode]
230         def state = [home:[],away:[],night:[]]
231         //Create a global logger object for methods
232         def log = new Logger()
233         //Create a global variable for Functions in Subscribe method
234         def functionList = []
235         //Create a global variable for Objects in Subscribe method
236         def objectList = []
237         //Create a global variable for Events in Subscribe method
238         def eventList = []
239         //Create a global list for function schedulers
240         def timersFuncList = []
241         //Create a global list for timer schedulers
242         def timersList = []
243         //Create a global variable for settings
244         def settings
245         //Zip code
246         def zipCode = 92617
247
248         //Methods
249         /////////////////////////////////////////////////////////////////////
250         def setLocationMode(String mode) {
251                 location.mode = mode
252         }
253         
254         /////////////////////////////////////////////////////////////////////
255         ////subscribe(obj, func)
256         def subscribe(Object obj, Closure FunctionToCall) {
257                 if (obj == app) {
258                         objectList.add(obj)
259                         eventList.add("Touched")
260                         functionList.add(FunctionToCall)
261                 } else if (obj == location) {
262                         objectList.add(obj)
263                         eventList.add("Location")
264                         functionList.add(FunctionToCall)
265                 }
266         }
267         ////subscribe(obj, event, func)
268         def subscribe(Object obj, String event, Closure FunctionToCall) {
269                 objectList.add(obj)
270                 eventList.add(event)
271                 functionList.add(FunctionToCall)
272         }
273         ////subscribe(obj, event, func, data)
274         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
275                 objectList.add(obj)     
276                 eventList.add(event)
277                 functionList.add(FunctionToCall)
278         }
279         /////////////////////////////////////////////////////////////////////
280         ////runIn(time, func)
281         def runIn(int seconds, Closure functionToCall) {
282                 if (timersFuncList.contains(functionToCall)) {
283                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
284                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
285                 } else {
286                         timersFuncList.add(functionToCall)
287                         timersList.add(new SimulatedTimer())
288                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
289                 }
290         }
291         
292         def runIn(int seconds, Closure functionToCall, LinkedHashMap metaData) {
293                 runIn(seconds, functionToCall)
294         }
295         
296         def runIn(int seconds, String nameOfFunction, LinkedHashMap metaData) {
297                 runIn(seconds, nameOfFunction)
298         }
299         
300         def runIn(int seconds, String nameOfFunction) {
301                 timersFuncList.add(nameOfFunction)
302                 timersList.add(new SimulatedTimer())
303                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(seconds*1000*0) {
304                         "$nameOfFunction"()
305                 }
306         }
307         /////////////////////////////////////////////////////////////////////
308         ////unschedule(func)
309         def unschedule(Closure functionToUnschedule) {
310                 for (int i = 0;i < timersFuncList.size();i++) {
311                         if (timersFuncList[i] == functionToUnschedule) {
312                                 if (timersList != null)
313                                         timersList[i].cancel()
314                         }
315                 }
316         }
317         
318         
319         def unschedule() {
320                 for (int i = 0;i < timersFuncList.size();i++) {
321                         if (timersList != null)
322                                 timersList[i].cancel()
323                 }
324         }
325         /////////////////////////////////////////////////////////////////////
326         ////sendNotificationToContacts(text, recipients)
327         def sendNotificationToContacts(String text, String recipients) {
328                 for (int i = 0;i < recipients.size();i++) {
329                         for (int j = 0;j < location.contacts.size();j++) {
330                                 if (recipients[i] == location.contacts[j]) {
331                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
332                                 }
333                         }
334                 }
335         }
336         /////////////////////////////////////////////////////////////////////
337         ////sendSms(phone, text)
338         def sendSms(long phoneNumber, String text) {
339                 println("Sending \""+text+"\" to "+phoneNumber.toString())
340         }
341         
342         def sendSMS(long phoneNumber, String text) {
343                 println("Sending \""+text+"\" to "+phoneNumber.toString())
344         }
345         /////////////////////////////////////////////////////////////////////
346         ////sendPush(text)
347         def sendPush(String text) {
348                 println(text)
349         }
350         /////////////////////////////////////////////////////////////////////
351         ////schedule(time, nameOfFunction as String)
352         def schedule(String time, String nameOfFunction) {
353                 def _inputTime = time.split(':')
354                 Date date = new Date()  
355                 def _currentTime = date.format("HH:mm:ss").split(':')
356         
357                 //Convert input time and current time to minutes
358                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
359                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
360                 def delay
361         
362                 if (inputTime < currentTime) {
363                         delay = 24*60*60-inputTime+currentTime
364                 } else {
365                         delay = inputTime-currentTime
366                 }
367         
368                 timersFuncList.add(nameOfFunction)
369                 timersList.add(new SimulatedTimer())
370                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000*0) {
371                         "$nameOfFunction"()
372                 }
373         }
374         ////schedule(time, nameOfFunction as Closure)
375         def schedule(String time, Closure nameOfFunction) {
376                 def _inputTime = time.split(':')
377                 Date date = new Date()  
378                 def _currentTime = date.format("HH:mm:ss").split(':')
379         
380                 //Convert input time and current time to minutes
381                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
382                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
383                 def delay
384         
385                 if (inputTime < currentTime) {
386                         delay = 24*60*60-inputTime+currentTime
387                 } else {
388                         delay = inputTime-currentTime
389                 }
390         
391                 if (timersFuncList.contains(nameOfFunction)) {
392                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
393                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*0, nameOfFunction)
394                 } else {
395                         timersFuncList.add(nameOfFunction)
396                         timersList.add(new SimulatedTimer())
397                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*0, nameOfFunction)
398                 }
399         }
400         /////////////////////////////////////////////////////////////////////
401         def now() {
402                 return System.currentTimeMillis()
403         }
404         /////////////////////////////////////////////////////////////////////
405         def getTemperatureScale() {
406                 return 'F' //Celsius for now
407         }
408         
409         /////////////////////////////////////////////////////////////////////
410         def getSunriseAndSunset(LinkedHashMap metaData) {
411                 def sunRiseSetInfo = [sunrise:[time:1563800160000],sunset:[time:1563850740000]]
412                 return sunRiseSetInfo
413         }
414         /////////////////////////////////////////////////////////////////////
415         def httpPostJson(LinkedHashMap metaData, Closure inputData) {
416                 inputData(metaData)
417         }
418         /////////////////////////////////////////////////////////////////////
419         def runEvery15Minutes(Closure inputData) {
420                 inputData()
421         }
422         /////////////////////////////////////////////////////////////////////
423         def timeToday(String time, Object timeZone) {
424                 def timeOfDay = new Date()
425                 def _inputTime = time.split(':')
426                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60+1564191100415
427                 timeOfDay.time = inputTime
428                 return timeOfDay
429         }
430         /////////////////////////////////////////////////////////////////////
431         def sendNotification(String text, LinkedHashMap metaData) {
432                 println("Sending \""+text+"\" to "+metaData.phone.toString())
433         }
434         /////////////////////////////////////////////////////////////////////
435         def canSchedule() {
436                 return true
437         }
438
439         def installed(){
440             initialize()
441         }
442         
443         def updated(){
444             unsubscribe()
445             unschedule()
446             initialize()
447         }
448         
449         def initialize(){
450             log.debug "Settings: ${settings}"
451             subscribe(lock1, "lock", doorHandler, [filterEvents: false])
452             subscribe(lock1, "unlock", doorHandler, [filterEvents: false])  
453             subscribe(contact, "contact.open", doorHandler)
454             subscribe(contact, "contact.closed", doorHandler)
455         }
456         
457         def lockDoor(){
458             log.debug "Locking the door."
459             lock1.lock()
460             if(location.contactBookEnabled) {
461                 if ( recipients ) {
462                     log.debug ( "Sending Push Notification..." ) 
463                     sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients)
464                 }
465             }
466             if (phoneNumber) {
467                 log.debug("Sending text message...")
468                 sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!")
469             }
470         }
471         
472         def unlockDoor(){
473             log.debug "Unlocking the door."
474             lock1.unlock()
475             if(location.contactBookEnabled) {
476                 if ( recipients ) {
477                     log.debug ( "Sending Push Notification..." ) 
478                     sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients)
479                 }
480             }
481             if ( phoneNumber ) {
482                 log.debug("Sending text message...")
483                 sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!")
484             }
485         }
486         
487         def doorHandler(evt){
488             if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then...  
489                 //def delay = (secondsLater) // runIn uses seconds
490                 runIn( secondsLater, unlockDoor )   // ...schedule (in minutes) to unlock...  We don't want the door to be closed while the lock is engaged. 
491             }
492             else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then...
493                 unschedule( unlockDoor ) // ...we don't need to unlock it later.
494             }
495             else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then...
496                 unschedule( lockDoor ) // ...we don't need to lock it later.
497             }   
498             else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then...
499                //def delay = (minutesLater * 60) // runIn uses seconds
500                 runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
501             }
502             else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door...
503                 unschedule( lockDoor ) // ...we don't need to lock it later.
504             }
505             else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door...
506                 //def delay = (minutesLater * 60) // runIn uses seconds
507                 runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
508             }
509             else { //Opening or Closing door when locked (in case you have a handle lock)
510                 log.debug "Unlocking the door."
511                 lock1.unlock()
512                 if(location.contactBookEnabled) {
513                     if ( recipients ) {
514                         log.debug ( "Sending Push Notification..." ) 
515                         sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients)
516                     }
517                 }
518                 if ( phoneNumber ) {
519                     log.debug("Sending text message...")
520                     sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!")
521                 }
522             }
523         }
524 }
525
526
527 //Application #2
528 class App2 {
529         def reference
530         def location
531         def app
532         def atomicState
533
534         //Extracted objects for App2
535         //Global variable for time!
536         def starting = "15:00"
537         //Global variable for time!
538         def ending = "15:00"
539         //Object for class beacon sensor!
540         def beacons
541         //Object for class mobile presence!
542         def phones
543         //Global variable for enum!
544         def arrivalPhrase = "Good Night!"
545         //Object for class switch!
546         def arrivalOnSwitches
547         //Object for class switch!
548         def arrivalOffSwitches
549         //Object for class lock!
550         def arrivalLocks
551         //Global variable for enum!
552         def departPhrase = "Good Night!"
553         //Object for class switch!
554         def departOnSwitches
555         //Object for class switch!
556         def departOffSwitches
557         //Object for class lock!
558         def departLocks
559         //Global variable for boolean!
560         def pushNotification = "0"
561         //Global variable for phone!
562         def phone = 9495379373
563         //Global variable for enum!
564         def days = "Monday"
565         //Global variable for mode!
566         def modes = "night"
567
568         //Extracted objects for functions for App2
569         //Global Object for functions in subscribe method!
570         def mainPage = this.&mainPage
571         //Global Object for functions in subscribe method!
572         def installed = this.&installed
573         //Global Object for functions in subscribe method!
574         def updated = this.&updated
575         //Global Object for functions in subscribe method!
576         def initialize = this.&initialize
577         //Global Object for functions in subscribe method!
578         def beaconHandler = this.&beaconHandler
579         //Global Object for functions in subscribe method!
580         def arriveActions = this.&arriveActions
581         //Global Object for functions in subscribe method!
582         def departActions = this.&departActions
583         //Global Object for functions in subscribe method!
584         def prefix = this.&prefix
585         //Global Object for functions in subscribe method!
586         def listPhrases = this.&listPhrases
587         //Global Object for functions in subscribe method!
588         def executePhrase = this.&executePhrase
589         //Global Object for functions in subscribe method!
590         def getBeaconName = this.&getBeaconName
591         //Global Object for functions in subscribe method!
592         def getPhoneName = this.&getPhoneName
593         //Global Object for functions in subscribe method!
594         def hideOptionsSection = this.&hideOptionsSection
595         //Global Object for functions in subscribe method!
596         def getAllOk = this.&getAllOk
597         //Global Object for functions in subscribe method!
598         def getModeOk = this.&getModeOk
599         //Global Object for functions in subscribe method!
600         def getDaysOk = this.&getDaysOk
601         //Global Object for functions in subscribe method!
602         def getTimeOk = this.&getTimeOk
603         //Global Object for functions in subscribe method!
604         def hhmm = this.&hhmm
605         //Global Object for functions in subscribe method!
606         def timeIntervalLabel = this.&timeIntervalLabel
607         //Global Object for functions in subscribe method!
608         def list = this.&list
609
610         App2(Object obj) {
611                 reference = obj
612                 location = obj.locationObject
613                 app = obj.appObject
614                 atomicState = obj.atomicState
615                 beacons = obj.beaconSensorObject
616                 phones = obj.mobilePresenceObject
617                 arrivalOnSwitches = obj.switchObject
618                 arrivalOffSwitches = obj.switchObject
619                 arrivalLocks = obj.lockObject
620                 departOnSwitches = obj.switchObject
621                 departOffSwitches = obj.switchObject
622                 departLocks = obj.lockObject
623                 //Global variable for settings!
624                 settings = [app:app, starting:starting, ending:ending, beacons:beacons, phones:phones, arrivalPhrase:arrivalPhrase, arrivalOnSwitches:arrivalOnSwitches, arrivalOffSwitches:arrivalOffSwitches, arrivalLocks:arrivalLocks, departPhrase:departPhrase, departOnSwitches:departOnSwitches, departOffSwitches:departOffSwitches, departLocks:departLocks, pushNotification:pushNotification, phone:phone, days:days, modes:modes]
625         }
626         //Global variables for each app
627         //Global variable for state[mode]
628         def state = [home:[],away:[],night:[]]
629         //Create a global logger object for methods
630         def log = new Logger()
631         //Create a global variable for Functions in Subscribe method
632         def functionList = []
633         //Create a global variable for Objects in Subscribe method
634         def objectList = []
635         //Create a global variable for Events in Subscribe method
636         def eventList = []
637         //Create a global list for function schedulers
638         def timersFuncList = []
639         //Create a global list for timer schedulers
640         def timersList = []
641         //Create a global variable for settings
642         def settings
643         //Zip code
644         def zipCode = 92617
645
646         //Methods
647         /////////////////////////////////////////////////////////////////////
648         def setLocationMode(String mode) {
649                 location.mode = mode
650         }
651         
652         /////////////////////////////////////////////////////////////////////
653         ////subscribe(obj, func)
654         def subscribe(Object obj, Closure FunctionToCall) {
655                 if (obj == app) {
656                         objectList.add(obj)
657                         eventList.add("Touched")
658                         functionList.add(FunctionToCall)
659                 } else if (obj == location) {
660                         objectList.add(obj)
661                         eventList.add("Location")
662                         functionList.add(FunctionToCall)
663                 }
664         }
665         ////subscribe(obj, event, func)
666         def subscribe(Object obj, String event, Closure FunctionToCall) {
667                 objectList.add(obj)
668                 eventList.add(event)
669                 functionList.add(FunctionToCall)
670         }
671         ////subscribe(obj, event, func, data)
672         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
673                 objectList.add(obj)     
674                 eventList.add(event)
675                 functionList.add(FunctionToCall)
676         }
677         /////////////////////////////////////////////////////////////////////
678         ////runIn(time, func)
679         def runIn(int seconds, Closure functionToCall) {
680                 if (timersFuncList.contains(functionToCall)) {
681                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
682                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
683                 } else {
684                         timersFuncList.add(functionToCall)
685                         timersList.add(new SimulatedTimer())
686                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds*0, functionToCall)
687                 }
688         }
689         
690         def runIn(int seconds, Closure functionToCall, LinkedHashMap metaData) {
691                 runIn(seconds, functionToCall)
692         }
693         
694         def runIn(int seconds, String nameOfFunction, LinkedHashMap metaData) {
695                 runIn(seconds, nameOfFunction)
696         }
697         
698         def runIn(int seconds, String nameOfFunction) {
699                 timersFuncList.add(nameOfFunction)
700                 timersList.add(new SimulatedTimer())
701                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(seconds*1000*0) {
702                         "$nameOfFunction"()
703                 }
704         }
705         /////////////////////////////////////////////////////////////////////
706         ////unschedule(func)
707         def unschedule(Closure functionToUnschedule) {
708                 for (int i = 0;i < timersFuncList.size();i++) {
709                         if (timersFuncList[i] == functionToUnschedule) {
710                                 if (timersList != null)
711                                         timersList[i].cancel()
712                         }
713                 }
714         }
715         
716         
717         def unschedule() {
718                 for (int i = 0;i < timersFuncList.size();i++) {
719                         if (timersList != null)
720                                 timersList[i].cancel()
721                 }
722         }
723         /////////////////////////////////////////////////////////////////////
724         ////sendNotificationToContacts(text, recipients)
725         def sendNotificationToContacts(String text, String recipients) {
726                 for (int i = 0;i < recipients.size();i++) {
727                         for (int j = 0;j < location.contacts.size();j++) {
728                                 if (recipients[i] == location.contacts[j]) {
729                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
730                                 }
731                         }
732                 }
733         }
734         /////////////////////////////////////////////////////////////////////
735         ////sendSms(phone, text)
736         def sendSms(long phoneNumber, String text) {
737                 println("Sending \""+text+"\" to "+phoneNumber.toString())
738         }
739         
740         def sendSMS(long phoneNumber, String text) {
741                 println("Sending \""+text+"\" to "+phoneNumber.toString())
742         }
743         /////////////////////////////////////////////////////////////////////
744         ////sendPush(text)
745         def sendPush(String text) {
746                 println(text)
747         }
748         /////////////////////////////////////////////////////////////////////
749         ////schedule(time, nameOfFunction as String)
750         def schedule(String time, String nameOfFunction) {
751                 def _inputTime = time.split(':')
752                 Date date = new Date()  
753                 def _currentTime = date.format("HH:mm:ss").split(':')
754         
755                 //Convert input time and current time to minutes
756                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
757                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
758                 def delay
759         
760                 if (inputTime < currentTime) {
761                         delay = 24*60*60-inputTime+currentTime
762                 } else {
763                         delay = inputTime-currentTime
764                 }
765         
766                 timersFuncList.add(nameOfFunction)
767                 timersList.add(new SimulatedTimer())
768                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000*0) {
769                         "$nameOfFunction"()
770                 }
771         }
772         ////schedule(time, nameOfFunction as Closure)
773         def schedule(String time, Closure nameOfFunction) {
774                 def _inputTime = time.split(':')
775                 Date date = new Date()  
776                 def _currentTime = date.format("HH:mm:ss").split(':')
777         
778                 //Convert input time and current time to minutes
779                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
780                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
781                 def delay
782         
783                 if (inputTime < currentTime) {
784                         delay = 24*60*60-inputTime+currentTime
785                 } else {
786                         delay = inputTime-currentTime
787                 }
788         
789                 if (timersFuncList.contains(nameOfFunction)) {
790                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
791                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*0, nameOfFunction)
792                 } else {
793                         timersFuncList.add(nameOfFunction)
794                         timersList.add(new SimulatedTimer())
795                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*0, nameOfFunction)
796                 }
797         }
798         /////////////////////////////////////////////////////////////////////
799         def now() {
800                 return System.currentTimeMillis()
801         }
802         /////////////////////////////////////////////////////////////////////
803         def getTemperatureScale() {
804                 return 'F' //Celsius for now
805         }
806         
807         /////////////////////////////////////////////////////////////////////
808         def getSunriseAndSunset(LinkedHashMap metaData) {
809                 def sunRiseSetInfo = [sunrise:[time:1563800160000],sunset:[time:1563850740000]]
810                 return sunRiseSetInfo
811         }
812         /////////////////////////////////////////////////////////////////////
813         def httpPostJson(LinkedHashMap metaData, Closure inputData) {
814                 inputData(metaData)
815         }
816         /////////////////////////////////////////////////////////////////////
817         def runEvery15Minutes(Closure inputData) {
818                 inputData()
819         }
820         /////////////////////////////////////////////////////////////////////
821         def timeToday(String time, Object timeZone) {
822                 def timeOfDay = new Date()
823                 def _inputTime = time.split(':')
824                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60+1564191100415
825                 timeOfDay.time = inputTime
826                 return timeOfDay
827         }
828         /////////////////////////////////////////////////////////////////////
829         def sendNotification(String text, LinkedHashMap metaData) {
830                 println("Sending \""+text+"\" to "+metaData.phone.toString())
831         }
832         /////////////////////////////////////////////////////////////////////
833         def canSchedule() {
834                 return true
835         }
836
837         def mainPage() {
838                 dynamicPage(name: "mainPage", install: true, uninstall: true) {
839         
840                         section("Where do you want to watch?") {
841                                 input name: "beacons", type: "capability.beacon", title: "Select your beacon(s)", 
842                                         multiple: true, required: true
843                         }
844         
845                         section("Who do you want to watch for?") {
846                                 input name: "phones", type: "device.mobilePresence", title: "Select your phone(s)", 
847                                         multiple: true, required: true
848                         }
849         
850                         section("What do you want to do on arrival?") {
851                                 input name: "arrivalPhrase", type: "enum", title: "Execute a phrase", 
852                                         options: listPhrases(), required: false
853                                 input "arrivalOnSwitches", "capability.switch", title: "Turn on some switches", 
854                                         multiple: true, required: false
855                                 input "arrivalOffSwitches", "capability.switch", title: "Turn off some switches", 
856                                         multiple: true, required: false
857                                 input "arrivalLocks", "capability.lock", title: "Unlock the door",
858                                         multiple: true, required: false
859                         }
860         
861                         section("What do you want to do on departure?") {
862                                 input name: "departPhrase", type: "enum", title: "Execute a phrase", 
863                                         options: listPhrases(), required: false
864                                 input "departOnSwitches", "capability.switch", title: "Turn on some switches", 
865                                         multiple: true, required: false
866                                 input "departOffSwitches", "capability.switch", title: "Turn off some switches", 
867                                         multiple: true, required: false
868                                 input "departLocks", "capability.lock", title: "Lock the door",
869                                         multiple: true, required: false
870                         }
871         
872                         section("Do you want to be notified?") {
873                                 input "pushNotification", "bool", title: "Send a push notification"
874                                 input "phone", "phone", title: "Send a text message", description: "Tap to enter phone number", 
875                                         required: false
876                         }
877         
878                         section {
879                                 label title: "Give your automation a name", description: "e.g. Goodnight Home, Wake Up"
880                         }
881         
882                         def timeLabel = timeIntervalLabel()
883                         section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
884                                 href "timeIntervalInput", title: "Only during a certain time", 
885                                         description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
886         
887                                 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
888                                         options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
889         
890                                 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
891                         }
892                 }
893         }
894         
895         // Lifecycle management
896         def installed() {
897                 log.debug "<beacon-control> Installed with settings: ${settings}"
898                 initialize()
899         }
900         
901         def updated() {
902                 log.debug "<beacon-control> Updated with settings: ${settings}"
903                 unsubscribe()
904                 initialize()
905         }
906         
907         def initialize() {
908                 subscribe(beacons, "presence", beaconHandler)
909         }
910         
911         // Event handlers
912         def beaconHandler(evt) {
913                 log.debug "<beacon-control> beaconHandler: $evt"
914         
915                 if (allOk) {
916                         def data = new groovy.json.JsonSlurper().parseText(evt.data)
917                         // removed logging of device names. can be added back for debugging
918                         //log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
919         
920                         def beaconName = getBeaconName(evt)
921                         // removed logging of device names. can be added back for debugging
922                         //log.debug "<beacon-control> beaconName: $beaconName"
923         
924                         def phoneName = getPhoneName(data)
925                         // removed logging of device names. can be added back for debugging
926                         //log.debug "<beacon-control> phoneName: $phoneName"
927                         if (phoneName != null) {
928                     def action = data.presence == "1" ? "arrived" : "left"
929                     def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"
930         
931                     if (action == "arrived") {
932                         msg = arriveActions(msg)
933                     }
934                     else if (action == "left") {
935                         msg = departActions(msg)
936                     }
937                     log.debug "<beacon-control> msg: $msg"
938         
939                     if (pushNotification || phone) {
940                         def options = [
941                             method: (pushNotification && phone) ? "both" : (pushNotification ? "push" : "sms"),
942                             phone: phone
943                         ]
944                         sendNotification(msg, options)
945                     }
946                 }
947                 }
948         }
949         
950         // Helpers
951         private arriveActions(msg) {
952                 if (arrivalPhrase || arrivalOnSwitches || arrivalOffSwitches || arrivalLocks) msg += ", so"
953                 
954                 if (arrivalPhrase) {
955                         log.debug "<beacon-control> executing: $arrivalPhrase"
956                         executePhrase(arrivalPhrase)
957                         msg += " ${prefix('executed')} $arrivalPhrase."
958                 }
959                 if (arrivalOnSwitches) {
960                         log.debug "<beacon-control> turning on: $arrivalOnSwitches"
961                         arrivalOnSwitches.on()
962                         msg += " ${prefix('turned')} ${list(arrivalOnSwitches)} on."
963                 }
964                 if (arrivalOffSwitches) {
965                         log.debug "<beacon-control> turning off: $arrivalOffSwitches"
966                         arrivalOffSwitches.off()
967                         msg += " ${prefix('turned')} ${list(arrivalOffSwitches)} off."
968                 }
969                 if (arrivalLocks) {
970                         log.debug "<beacon-control> unlocking: $arrivalLocks"
971                         arrivalLocks.unlock()
972                         msg += " ${prefix('unlocked')} ${list(arrivalLocks)}."
973                 }
974                 msg
975         }
976         
977         private departActions(msg) {
978                 if (departPhrase || departOnSwitches || departOffSwitches || departLocks) msg += ", so"
979                 
980                 if (departPhrase) {
981                         log.debug "<beacon-control> executing: $departPhrase"
982                         executePhrase(departPhrase)
983                         msg += " ${prefix('executed')} $departPhrase."
984                 }
985                 if (departOnSwitches) {
986                         log.debug "<beacon-control> turning on: $departOnSwitches"
987                         departOnSwitches.on()
988                         msg += " ${prefix('turned')} ${list(departOnSwitches)} on."
989                 }
990                 if (departOffSwitches) {
991                         log.debug "<beacon-control> turning off: $departOffSwitches"
992                         departOffSwitches.off()
993                         msg += " ${prefix('turned')} ${list(departOffSwitches)} off."
994                 }
995                 if (departLocks) {
996                         log.debug "<beacon-control> unlocking: $departLocks"
997                         departLocks.lock()
998                         msg += " ${prefix('locked')} ${list(departLocks)}."
999                 }
1000                 msg
1001         }
1002         
1003         private prefix(word) {
1004                 def result
1005                 def index = settings.prefixIndex == null ? 0 : settings.prefixIndex + 1
1006                 switch (index) {
1007                         case 0:
1008                                 result = "I $word"
1009                                 break
1010                         case 1:
1011                                 result = "I also $word"
1012                                 break
1013                         case 2:
1014                                 result = "And I $word"
1015                                 break
1016                         default:
1017                                 result = "And $word"
1018                                 break
1019                 }
1020         
1021                 settings.prefixIndex = index
1022                 log.trace "prefix($word'): $result"
1023                 result
1024         }
1025         
1026         private listPhrases() {
1027                 location.helloHome.getPhrases().label
1028         }
1029         
1030         private executePhrase(phraseName) {
1031                 if (phraseName) {
1032                         location.helloHome.execute(phraseName)
1033                         log.debug "<beacon-control> executed phrase: $phraseName"
1034                 }
1035         }
1036         
1037         private getBeaconName(evt) {
1038                 def beaconName = beacons.find { b -> b.id == evt.deviceId }
1039                 return beaconName
1040         }
1041         
1042         private getPhoneName(data) {    
1043                 def phoneName = phones.find { phone ->
1044                         // Work around DNI bug in data
1045                         def pParts = phone.deviceNetworkId.split('\\|')
1046                         def dParts = data.dni.split('\\|')
1047                 pParts[0] == dParts[0]
1048                 }
1049                 return phoneName
1050         }
1051         
1052         private hideOptionsSection() {
1053                 (starting || ending || days || modes) ? false : true
1054         }
1055         
1056         private getAllOk() {
1057                 modeOk && daysOk && timeOk
1058         }
1059         
1060         private getModeOk() {
1061                 def result = !modes || modes.contains(location.mode)
1062                 log.trace "<beacon-control> modeOk = $result"
1063                 result
1064         }
1065         
1066         private getDaysOk() {
1067                 def result = true
1068                 if (days) {
1069                         def df = new java.text.SimpleDateFormat("EEEE")
1070                         if (location.timeZone) {
1071                                 df.setTimeZone(location.timeZone)
1072                         }
1073                         else {
1074                                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
1075                         }
1076                         def day = df.format(new Date())
1077                         result = days.contains(day)
1078                 }
1079                 log.trace "<beacon-control> daysOk = $result"
1080                 result
1081         }
1082         
1083         private getTimeOk() {
1084                 def result = true
1085                 if (starting && ending) {
1086                         def currTime = now()
1087                         def start = timeToday(starting, location?.timeZone).time
1088                         def stop = timeToday(ending, location?.timeZone).time
1089                         result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
1090                 }
1091                 log.trace "<beacon-control> timeOk = $result"
1092                 result
1093         }
1094         
1095         private hhmm(time, fmt = "h:mm a") {
1096                 def t = timeToday(time, location.timeZone)
1097                 def f = new java.text.SimpleDateFormat(fmt)
1098                 f.setTimeZone(location.timeZone ?: timeZone(time))
1099                 f.format(t)
1100         }
1101         
1102         private timeIntervalLabel() {
1103                 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
1104         }
1105         
1106         private list(Object names) {
1107                 return names[0]
1108         }
1109 }
1110
1111 @Field def app1
1112 @Field def app2
1113 def initOrder = Verify.getBoolean()
1114 if (initOrder) {
1115         app1 = new App1(this)
1116         app2 = new App2(this)
1117 } else {
1118         app2 = new App2(this)
1119         app1 = new App1(this)
1120 }
1121
1122 def installOrder = Verify.getBoolean()
1123 if (installOrder) {
1124         app1.installed()
1125         app2.installed()
1126 } else {
1127         app2.installed()
1128         app1.installed()
1129 }
1130
1131 while(true) {
1132         def eventNumber = Verify.getInt(0,4)
1133         switch(eventNumber) {
1134                 case 0:
1135                         lockObject.setValue([name: "lock", value: "locked", deviceId: "lockID0", descriptionText: "",
1136                                         displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}'])
1137                         break
1138                 case 1:
1139                         lockObject.setValue([name: "unlock", value: "unlocked ", deviceId: "lockID0", descriptionText: "",
1140                                         displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}'])
1141                         break
1142                 case 2:
1143                         contactObject.setValue([name: "contact.open", value: "open", deviceId: "contactSensorID0", descriptionText: "",
1144                                         displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}'])
1145                         break
1146                 case 3:
1147                         contactObject.setValue([name: "contact.closed", value: "closed", deviceId: "contactSensorID0", descriptionText: "",
1148                                         displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}'])
1149                         break
1150                 case 4:
1151                         def event = Verify.getInt(0,1)
1152                         if (event == 0) {
1153                                         presenceSensorObject.setValue([name: "presence", value: "present", deviceId: "presenceSensorID0", descriptionText: "",
1154                                                         displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"presence":"1","dni":"mobile0"}'])
1155                         } else {
1156                                         presenceSensorObject.setValue([name: "presence", value: "not present", deviceId: "presenceSensorID0", descriptionText: "",
1157                                                         displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"presence":"0","dni":"mobile0"}'])
1158                         }
1159                         break
1160         }
1161 }