Commit #7: Events thread-based + new easier Extractor.py + our own Timer class
[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.Contacting
7 import ContactSensor.Contacts
8 import Lock.Locking
9 import Lock.Locks
10 import Switch.Switching
11 import Switch.Switches
12 import Logger.Logger
13 import Location.LocationVar
14 import Location.Phrase
15 import appTouch.Touched
16 import Event.Event
17 import Timer.SimulatedTimer
18
19 //Global eventHandler
20 /////////////////////////////////////////////////////////////////////
21 def eventHandler(LinkedHashMap eventDataMap) {
22         def value = eventDataMap["value"]
23         def name = eventDataMap["name"]
24         def deviceId = eventDataMap["deviceId"]
25         def descriptionText = eventDataMap["descriptionText"]
26         def displayed = eventDataMap["displayed"]
27         def linkText = eventDataMap["linkText"]
28         def isStateChange = eventDataMap["isStateChange"]
29         def unit = eventDataMap["unit"]
30         def data = eventDataMap["data"]
31
32         for (int i = 0;i < app2.eventList.size();i++) {
33                 if (app2.eventList[i] == name) {
34                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
35                         evt.add(event)
36                         app2.functionList[i](event)
37                 }
38         }
39
40         for (int i = 0;i < app1.eventList.size();i++) {
41                 if (app1.eventList[i] == name) {
42                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
43                         evt.add(event)
44                         app1.functionList[i](event)
45                 }
46         }
47 }
48
49 //GlobalVariables for both Apps
50 //Create a global variable for send event
51 @Field def sendEvent = {eventDataMap -> 
52                         eventHandler(eventDataMap)
53                         }
54 //Object for location
55 @Field def locationObject = new LocationVar()
56 //Object for touch
57 @Field def appObject = new Touched(sendEvent, 0)
58 //Create a global list for events
59 @Field def evt = []
60
61 //Extracted global objects for both Apps
62 //Global Object for class lock!
63 @Field def lockObject = new Locking(sendEvent,1)
64 //Global Object for class contactSensor!
65 @Field def contactObject = new Contacting(sendEvent,1)
66 //Global Object for class switch!
67 @Field def switchObject = new Switching(sendEvent, 1)
68
69 //Application #1
70 class App1 {
71         def reference
72         def location
73         def app
74
75         //Extracted objects for App1
76         //Object for class lock!
77         def lock1
78         //Object for class contactSensor!
79         def contact
80         //Global variable for number!
81         def minutesLater = 1
82         //Global variable for number!
83         def secondsLater = 10
84         //Global variable for contact!
85         def recipients = "AJ"
86         //Global variable for phone!
87         def phoneNumber = 9495379373
88
89         //Extracted objects for functions for App1
90         //Global Object for functions in subscribe method!
91         def installed = this.&installed
92         //Global Object for functions in subscribe method!
93         def updated = this.&updated
94         //Global Object for functions in subscribe method!
95         def initialize = this.&initialize
96         //Global Object for functions in subscribe method!
97         def lockDoor = this.&lockDoor
98         //Global Object for functions in subscribe method!
99         def unlockDoor = this.&unlockDoor
100         //Global Object for functions in subscribe method!
101         def doorHandler = this.&doorHandler
102
103         App1(Object obj) {
104                 reference = obj
105                 location = obj.locationObject
106                 app = obj.appObject
107                 lock1 = obj.lockObject
108                 contact = obj.contactObject
109         }
110         //Global variables for each app
111         //Settings variable defined to settings on purpose
112         def settings = "Settings"
113         //Global variable for state[mode]
114         def state = [home:[],away:[],night:[]]
115         //Create a global logger object for methods
116         def log = new Logger()
117         //Create a global variable for Functions in Subscribe method
118         def functionList = []
119         //Create a global variable for Objects in Subscribe method
120         def objectList = []
121         //Create a global variable for Events in Subscribe method
122         def eventList = []
123         //Create a global list for function schedulers
124         def timersFuncList = []
125         //Create a global list for timer schedulers
126         def timersList = []
127
128         //Methods
129         /////////////////////////////////////////////////////////////////////
130         def setLocationMode(String mode) {
131                 location.mode = mode
132         }
133         
134         /////////////////////////////////////////////////////////////////////
135         ////subscribe(obj, func)
136         def subscribe(Object obj, Closure FunctionToCall) {
137                 objectList.add(obj)
138                 eventList.add("Touched")
139                 functionList.add(FunctionToCall)
140         }
141         ////subscribe(obj, event, func)
142         def subscribe(Object obj, String event, Closure FunctionToCall) {
143                 objectList.add(obj)
144                 eventList.add(event)
145                 functionList.add(FunctionToCall)
146         }
147         ////subscribe(obj, event, func, data)
148         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
149                 objectList.add(obj)     
150                 eventList.add(event)
151                 functionList.add(FunctionToCall)
152         }
153         /////////////////////////////////////////////////////////////////////
154         ////runIn(time, func)
155         def runIn(int seconds, Closure functionToCall) {
156                 if (timersFuncList.contains(functionToCall)) {
157                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
158                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
159                 } else {
160                         timersFuncList.add(functionToCall)
161                         timersList.add(new SimulatedTimer())
162                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
163                 }
164         }
165         /////////////////////////////////////////////////////////////////////
166         ////unschedule(func)
167         def unschedule(Closure functionToUnschedule) {
168                 for (int i = 0;i < timersFuncList.size();i++) {
169                         if (timersFuncList[i] == functionToUnschedule) {
170                                 if (timersList != null)
171                                         timersList[i].cancel()
172                         }
173                 }
174         }
175         
176         
177         def unschedule() {
178                 for (int i = 0;i < timersFuncList.size();i++) {
179                         if (timersList != null)
180                                 timersList[i].cancel()
181                 }
182         }
183         /////////////////////////////////////////////////////////////////////
184         ////sendNotificationToContacts(text, recipients)
185         def sendNotificationToContacts(String text, String recipients) {
186                 for (int i = 0;i < recipients.size();i++) {
187                         for (int j = 0;j < location.contacts.size();j++) {
188                                 if (recipients[i] == location.contacts[j]) {
189                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
190                                 }
191                         }
192                 }
193         }
194         /////////////////////////////////////////////////////////////////////
195         ////sendSms(phone, text)
196         def sendSms(long phoneNumber, String text) {
197                 println("Sending \""+text+"\" to "+phoneNumber.toString())
198         }
199         /////////////////////////////////////////////////////////////////////
200         ////sendPush(text)
201         def sendPush(String text) {
202                 println(text)
203         }
204         /////////////////////////////////////////////////////////////////////
205         ////schedule(time, nameOfFunction as String)
206         def schedule(String time, String nameOfFunction) {
207                 def _inputTime = time.split(':')
208                 Date date = new Date()  
209                 def _currentTime = date.format("HH:mm:ss").split(':')
210         
211                 //Convert input time and current time to minutes
212                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
213                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
214                 def delay
215         
216                 if (inputTime < currentTime) {
217                         delay = 24*60*60-inputTime+currentTime
218                 } else {
219                         delay = inputTime-currentTime
220                 }
221         
222                 timersFuncList.add(nameOfFunction)
223                 timersList.add(new SimulatedTimer())
224                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000) {
225                         "$nameOfFunction"()
226                 }
227         }
228         ////schedule(time, nameOfFunction as Closure)
229         def schedule(String time, Closure nameOfFunction) {
230                 def _inputTime = time.split(':')
231                 Date date = new Date()  
232                 def _currentTime = date.format("HH:mm:ss").split(':')
233         
234                 //Convert input time and current time to minutes
235                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
236                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
237                 def delay
238         
239                 if (inputTime < currentTime) {
240                         delay = 24*60*60-inputTime+currentTime
241                 } else {
242                         delay = inputTime-currentTime
243                 }
244         
245                 if (timersFuncList.contains(nameOfFunction)) {
246                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
247                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
248                 } else {
249                         timersFuncList.add(nameOfFunction)
250                         timersList.add(new SimulatedTimer())
251                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
252                 }
253         }
254
255         def installed(){
256             initialize()
257         }
258         
259         def updated(){
260             unsubscribe()
261             unschedule()
262             initialize()
263         }
264         
265         def initialize(){
266             log.debug "Settings: ${settings}"
267             subscribe(lock1, "lock", doorHandler, [filterEvents: false])
268             subscribe(lock1, "unlock", doorHandler, [filterEvents: false])  
269             subscribe(contact, "contact.open", doorHandler)
270             subscribe(contact, "contact.closed", doorHandler)
271         }
272         
273         def lockDoor(){
274             log.debug "Locking the door."
275             lock1.lock()
276             if(location.contactBookEnabled) {
277                 if ( recipients ) {
278                     log.debug ( "Sending Push Notification..." ) 
279                     sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients)
280                 }
281             }
282             if (phoneNumber) {
283                 log.debug("Sending text message...")
284                 sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!")
285             }
286         }
287         
288         def unlockDoor(){
289             log.debug "Unlocking the door."
290             lock1.unlock()
291             if(location.contactBookEnabled) {
292                 if ( recipients ) {
293                     log.debug ( "Sending Push Notification..." ) 
294                     sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients)
295                 }
296             }
297             if ( phoneNumber ) {
298                 log.debug("Sending text message...")
299                 sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!")
300             }
301         }
302         
303         def doorHandler(evt){
304             if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then...  
305                 //def delay = (secondsLater) // runIn uses seconds
306                 runIn( secondsLater, unlockDoor )   // ...schedule (in minutes) to unlock...  We don't want the door to be closed while the lock is engaged. 
307             }
308             else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then...
309                 unschedule( unlockDoor ) // ...we don't need to unlock it later.
310             }
311             else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then...
312                 unschedule( lockDoor ) // ...we don't need to lock it later.
313             }   
314             else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then...
315                //def delay = (minutesLater * 60) // runIn uses seconds
316                 runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
317             }
318             else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door...
319                 unschedule( lockDoor ) // ...we don't need to lock it later.
320             }
321             else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door...
322                 //def delay = (minutesLater * 60) // runIn uses seconds
323                 runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
324             }
325             else { //Opening or Closing door when locked (in case you have a handle lock)
326                 log.debug "Unlocking the door."
327                 lock1.unlock()
328                 if(location.contactBookEnabled) {
329                     if ( recipients ) {
330                         log.debug ( "Sending Push Notification..." ) 
331                         sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients)
332                     }
333                 }
334                 if ( phoneNumber ) {
335                     log.debug("Sending text message...")
336                     sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!")
337                 }
338             }
339         }
340 }
341
342
343 //Application #2
344 class App2 {
345         def reference
346         def location
347         def app
348
349         //Extracted objects for App2
350         //Object for class switch!
351         def switchesoff
352         //Object for class switch!
353         def switcheson
354         //Object for class lock!
355         def lock1
356         //Global variable for mode!
357         def newMode = "away"
358         //Global variable for number!
359         def waitfor = 4
360
361         //Extracted objects for functions for App2
362         //Global Object for functions in subscribe method!
363         def installed = this.&installed
364         //Global Object for functions in subscribe method!
365         def updated = this.&updated
366         //Global Object for functions in subscribe method!
367         def appTouch = this.&appTouch
368
369         App2(Object obj) {
370                 reference = obj
371                 location = obj.locationObject
372                 app = obj.appObject
373                 switchesoff = obj.switchObject
374                 switcheson = obj.switchObject
375                 lock1 = obj.lockObject
376         }
377         //Global variables for each app
378         //Settings variable defined to settings on purpose
379         def settings = "Settings"
380         //Global variable for state[mode]
381         def state = [home:[],away:[],night:[]]
382         //Create a global logger object for methods
383         def log = new Logger()
384         //Create a global variable for Functions in Subscribe method
385         def functionList = []
386         //Create a global variable for Objects in Subscribe method
387         def objectList = []
388         //Create a global variable for Events in Subscribe method
389         def eventList = []
390         //Create a global list for function schedulers
391         def timersFuncList = []
392         //Create a global list for timer schedulers
393         def timersList = []
394
395         //Methods
396         /////////////////////////////////////////////////////////////////////
397         def setLocationMode(String mode) {
398                 location.mode = mode
399         }
400         
401         /////////////////////////////////////////////////////////////////////
402         ////subscribe(obj, func)
403         def subscribe(Object obj, Closure FunctionToCall) {
404                 objectList.add(obj)
405                 eventList.add("Touched")
406                 functionList.add(FunctionToCall)
407         }
408         ////subscribe(obj, event, func)
409         def subscribe(Object obj, String event, Closure FunctionToCall) {
410                 objectList.add(obj)
411                 eventList.add(event)
412                 functionList.add(FunctionToCall)
413         }
414         ////subscribe(obj, event, func, data)
415         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
416                 objectList.add(obj)     
417                 eventList.add(event)
418                 functionList.add(FunctionToCall)
419         }
420         /////////////////////////////////////////////////////////////////////
421         ////runIn(time, func)
422         def runIn(int seconds, Closure functionToCall) {
423                 if (timersFuncList.contains(functionToCall)) {
424                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
425                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
426                 } else {
427                         timersFuncList.add(functionToCall)
428                         timersList.add(new SimulatedTimer())
429                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
430                 }
431         }
432         /////////////////////////////////////////////////////////////////////
433         ////unschedule(func)
434         def unschedule(Closure functionToUnschedule) {
435                 for (int i = 0;i < timersFuncList.size();i++) {
436                         if (timersFuncList[i] == functionToUnschedule) {
437                                 if (timersList != null)
438                                         timersList[i].cancel()
439                         }
440                 }
441         }
442         
443         
444         def unschedule() {
445                 for (int i = 0;i < timersFuncList.size();i++) {
446                         if (timersList != null)
447                                 timersList[i].cancel()
448                 }
449         }
450         /////////////////////////////////////////////////////////////////////
451         ////sendNotificationToContacts(text, recipients)
452         def sendNotificationToContacts(String text, String recipients) {
453                 for (int i = 0;i < recipients.size();i++) {
454                         for (int j = 0;j < location.contacts.size();j++) {
455                                 if (recipients[i] == location.contacts[j]) {
456                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
457                                 }
458                         }
459                 }
460         }
461         /////////////////////////////////////////////////////////////////////
462         ////sendSms(phone, text)
463         def sendSms(long phoneNumber, String text) {
464                 println("Sending \""+text+"\" to "+phoneNumber.toString())
465         }
466         /////////////////////////////////////////////////////////////////////
467         ////schedule(time, nameOfFunction as String)
468         def schedule(String time, String nameOfFunction) {
469                 def _inputTime = time.split(':')
470                 Date date = new Date()  
471                 def _currentTime = date.format("HH:mm:ss").split(':')
472         
473                 //Convert input time and current time to minutes
474                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
475                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
476                 def delay
477         
478                 if (inputTime < currentTime) {
479                         delay = 24*60*60-inputTime+currentTime
480                 } else {
481                         delay = inputTime-currentTime
482                 }
483         
484                 timersFuncList.add(nameOfFunction)
485                 timersList.add(new SimulatedTimer())
486                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000) {
487                         "$nameOfFunction"()
488                 }
489         }
490         ////schedule(time, nameOfFunction as Closure)
491         def schedule(String time, Closure nameOfFunction) {
492                 def _inputTime = time.split(':')
493                 Date date = new Date()  
494                 def _currentTime = date.format("HH:mm:ss").split(':')
495         
496                 //Convert input time and current time to minutes
497                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
498                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
499                 def delay
500         
501                 if (inputTime < currentTime) {
502                         delay = 24*60*60-inputTime+currentTime
503                 } else {
504                         delay = inputTime-currentTime
505                 }
506         
507                 if (timersFuncList.contains(nameOfFunction)) {
508                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
509                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
510                 } else {
511                         timersFuncList.add(nameOfFunction)
512                         timersList.add(new SimulatedTimer())
513                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
514                 }
515         }
516
517         def installed()
518         {
519                 log.debug "Installed with settings: ${settings}"
520                 log.debug "Current mode = ${location.mode}"
521                 subscribe(app, appTouch)
522         }
523         
524         
525         def updated()
526         {
527                 log.debug "Updated with settings: ${settings}"
528                 log.debug "Current mode = ${location.mode}"
529                 unsubscribe()
530                 subscribe(app, appTouch)
531         }
532         
533         def appTouch(evt) {
534                 log.debug "changeMode, location.mode = $location.mode, newMode = $newMode, location.modes = $location.modes"
535             if (location.mode != newMode) {
536                                 setLocationMode(newMode)
537                                 log.debug "Changed the mode to '${newMode}'"
538             }   else {
539                 log.debug "New mode is the same as the old mode, leaving it be"
540                 }
541             log.debug "appTouch: $evt"
542             lock1.lock()
543             switcheson.on()
544             def delay = (waitfor != null && waitfor != "") ? waitfor * 1000 : 120000
545                 switchesoff.off(delay: delay)
546         }
547 }
548
549 @Field def app1 = new App1(this)
550 @Field def app2 = new App2(this)
551 app1.installed()
552 app2.installed()
553
554         // Generate a random variable
555         Random random = new Random(1131)
556         int nextRandom = 10
557         
558         // Touch events
559         new Thread() {
560                 @Override
561                 public void run() {
562                         while(true) {
563                                 appObject.setValue([name: "Touched", value: "Touched", deviceId: 0, descriptionText: "",
564                                                    displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
565                                 Thread.sleep(random.nextInt(nextRandom));       
566                                 
567                         }
568                 }
569         }.start()
570         
571         // Lock events
572         new Thread() {
573                 
574                 @Override
575                 public void run() {
576                         while(true) {
577                                 lockObject.setValue([name: "lock", value: "locked", deviceId: 0, descriptionText: "",
578                                                    displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
579                                 Thread.sleep(random.nextInt(nextRandom));       
580                         }
581                 }
582         }.start()
583         
584         new Thread() {
585                 
586                 @Override
587                 public void run() {
588                         while(true) {
589                                 lockObject.setValue([name: "unlock", value: "unlocked", deviceId: 0, descriptionText: "",
590                                                    displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
591                                 Thread.sleep(random.nextInt(nextRandom));       
592                         }
593                 }
594         }.start()
595         
596         // Contact sensor events
597         new Thread() {
598                 
599                 @Override
600                 public void run() {
601                         while(true) {
602                                 contactObject.setValue([name: "contact.open", value: "open", deviceId: 0, descriptionText: "",
603                                                    displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
604                                 Thread.sleep(random.nextInt(nextRandom));       
605                                 
606                         }
607                 }
608         }.start()
609         
610         new Thread() {
611                 
612                 @Override
613                 public void run() {
614                         while(true) {
615                                 contactObject.setValue([name: "contact.closed", value: "closed", deviceId: 0, descriptionText: "",
616                                                    displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
617                                 Thread.sleep(random.nextInt(nextRandom));       
618                                 
619                         }
620                 }
621         }.start()