dfc967563b81701d47d6dcb345d3b1afa399ace6
[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 Event.Event
24 import Timer.SimulatedTimer
25
26 import gov.nasa.jpf.vm.Verify
27
28 //Global eventHandler
29 /////////////////////////////////////////////////////////////////////
30 def eventHandler(LinkedHashMap eventDataMap) {
31         def value = eventDataMap["value"]
32         def name = eventDataMap["name"]
33         def deviceId = eventDataMap["deviceId"]
34         def descriptionText = eventDataMap["descriptionText"]
35         def displayed = eventDataMap["displayed"]
36         def linkText = eventDataMap["linkText"]
37         def isStateChange = eventDataMap["isStateChange"]
38         def unit = eventDataMap["unit"]
39         def data = eventDataMap["data"]
40
41         for (int i = 0;i < app2.eventList.size();i++) {
42                 if (app2.eventList[i] == name) {
43                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
44                         evt.add(event)
45                         app2.functionList[i](event)
46                 }
47         }
48
49         for (int i = 0;i < app1.eventList.size();i++) {
50                 if (app1.eventList[i] == name) {
51                         def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data)
52                         evt.add(event)
53                         app1.functionList[i](event)
54                 }
55         }
56 }
57
58 //GlobalVariables for both Apps
59 //Create a global variable for send event
60 @Field def sendEvent = {eventDataMap -> 
61                         eventHandler(eventDataMap)
62                         }
63 //Object for location
64 @Field def locationObject = new LocationVar()
65 //Object for touch to call function
66 @Field def appObject = new Touched(sendEvent, 0)
67 //Create a global list for events
68 @Field def evt = []
69 //Global Object for class Touch Sensor!
70 @Field def touchSensorObject = new NfcTouch(sendEvent, 1)
71 //Global Object for class switch!
72 @Field def switchObject = new Switches(sendEvent, 1)
73 //Global Object for class lock!
74 @Field def lockObject = new Locks(sendEvent, 1)
75 //Global Object for class door control!
76 @Field def doorControlObject = new DoorControls(sendEvent, 1)
77 //Global Object for class contact sensor!
78 @Field def contactObject = new ContactSensors(sendEvent, 1)
79 //Global Object for class presence sensor!
80 @Field def presenceSensorObject = new PresenceSensors(sendEvent, 1)
81 //Global Object for class thermostat!
82 @Field def thermostatObject = new Thermostats(sendEvent, 1)
83
84 //Application #1
85 class App1 {
86         def reference
87         def location
88         def app
89
90         //Extracted objects for App1
91         //Object for class Touch Sensor!
92         def tag
93         //Object for class switch!
94         def switch1
95         //Object for class lock!
96         def lock
97         //Object for class door control!
98         def garageDoor
99         //Global variable for enum!
100         def masterSwitch = "40"
101         //Global variable for enum!
102         def masterLock = "20"
103         //Global variable for enum!
104         def masterDoor = "40"
105
106         //Extracted objects for functions for App1
107         //Global Object for functions in subscribe method!
108         def pageTwo = this.&pageTwo
109         //Global Object for functions in subscribe method!
110         def installed = this.&installed
111         //Global Object for functions in subscribe method!
112         def updated = this.&updated
113         //Global Object for functions in subscribe method!
114         def initialize = this.&initialize
115         //Global Object for functions in subscribe method!
116         def currentStatus = this.&currentStatus
117         //Global Object for functions in subscribe method!
118         def touchHandler = this.&touchHandler
119
120         App1(Object obj) {
121                 reference = obj
122                 location = obj.locationObject
123                 app = obj.appObject
124                 tag = obj.touchSensorObject
125                 switch1 = obj.switchObject
126                 lock = obj.lockObject
127                 garageDoor = obj.doorControlObject
128         }
129         //Global variables for each app
130         //Settings variable defined to settings on purpose
131         def settings = "Settings"
132         //Global variable for state[mode]
133         def state = [home:[],away:[],night:[]]
134         //Create a global logger object for methods
135         def log = new Logger()
136         //Create a global variable for Functions in Subscribe method
137         def functionList = []
138         //Create a global variable for Objects in Subscribe method
139         def objectList = []
140         //Create a global variable for Events in Subscribe method
141         def eventList = []
142         //Create a global list for function schedulers
143         def timersFuncList = []
144         //Create a global list for timer schedulers
145         def timersList = []
146
147         //Methods
148         /////////////////////////////////////////////////////////////////////
149         def setLocationMode(String mode) {
150                 location.mode = mode
151         }
152         
153         /////////////////////////////////////////////////////////////////////
154         ////subscribe(obj, func)
155         def subscribe(Object obj, Closure FunctionToCall) {
156                 if (obj == app) {
157                         objectList.add(obj)
158                         eventList.add("Touched")
159                         functionList.add(FunctionToCall)
160                 } else if (obj == location) {
161                         objectList.add(obj)
162                         eventList.add("Location")
163                         functionList.add(FunctionToCall)
164                 }
165         }
166         ////subscribe(obj, event, func)
167         def subscribe(Object obj, String event, Closure FunctionToCall) {
168                 objectList.add(obj)
169                 eventList.add(event)
170                 functionList.add(FunctionToCall)
171         }
172         ////subscribe(obj, event, func, data)
173         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
174                 objectList.add(obj)     
175                 eventList.add(event)
176                 functionList.add(FunctionToCall)
177         }
178         /////////////////////////////////////////////////////////////////////
179         ////runIn(time, func)
180         def runIn(int seconds, Closure functionToCall) {
181                 if (timersFuncList.contains(functionToCall)) {
182                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
183                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
184                 } else {
185                         timersFuncList.add(functionToCall)
186                         timersList.add(new SimulatedTimer())
187                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
188                 }
189         }
190         /////////////////////////////////////////////////////////////////////
191         ////unschedule(func)
192         def unschedule(Closure functionToUnschedule) {
193                 for (int i = 0;i < timersFuncList.size();i++) {
194                         if (timersFuncList[i] == functionToUnschedule) {
195                                 if (timersList != null)
196                                         timersList[i].cancel()
197                         }
198                 }
199         }
200         
201         
202         def unschedule() {
203                 for (int i = 0;i < timersFuncList.size();i++) {
204                         if (timersList != null)
205                                 timersList[i].cancel()
206                 }
207         }
208         /////////////////////////////////////////////////////////////////////
209         ////sendNotificationToContacts(text, recipients)
210         def sendNotificationToContacts(String text, String recipients) {
211                 for (int i = 0;i < recipients.size();i++) {
212                         for (int j = 0;j < location.contacts.size();j++) {
213                                 if (recipients[i] == location.contacts[j]) {
214                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
215                                 }
216                         }
217                 }
218         }
219         /////////////////////////////////////////////////////////////////////
220         ////sendSms(phone, text)
221         def sendSms(long phoneNumber, String text) {
222                 println("Sending \""+text+"\" to "+phoneNumber.toString())
223         }
224         /////////////////////////////////////////////////////////////////////
225         ////sendPush(text)
226         def sendPush(String text) {
227                 println(text)
228         }
229         /////////////////////////////////////////////////////////////////////
230         ////schedule(time, nameOfFunction as String)
231         def schedule(String time, String nameOfFunction) {
232                 def _inputTime = time.split(':')
233                 Date date = new Date()  
234                 def _currentTime = date.format("HH:mm:ss").split(':')
235         
236                 //Convert input time and current time to minutes
237                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
238                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
239                 def delay
240         
241                 if (inputTime < currentTime) {
242                         delay = 24*60*60-inputTime+currentTime
243                 } else {
244                         delay = inputTime-currentTime
245                 }
246         
247                 timersFuncList.add(nameOfFunction)
248                 timersList.add(new SimulatedTimer())
249                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000) {
250                         "$nameOfFunction"()
251                 }
252         }
253         ////schedule(time, nameOfFunction as Closure)
254         def schedule(String time, Closure nameOfFunction) {
255                 def _inputTime = time.split(':')
256                 Date date = new Date()  
257                 def _currentTime = date.format("HH:mm:ss").split(':')
258         
259                 //Convert input time and current time to minutes
260                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
261                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
262                 def delay
263         
264                 if (inputTime < currentTime) {
265                         delay = 24*60*60-inputTime+currentTime
266                 } else {
267                         delay = inputTime-currentTime
268                 }
269         
270                 if (timersFuncList.contains(nameOfFunction)) {
271                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
272                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
273                 } else {
274                         timersFuncList.add(nameOfFunction)
275                         timersList.add(new SimulatedTimer())
276                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
277                 }
278         }
279
280         def pageTwo() {
281                 dynamicPage(name: "pageTwo") {
282                 section("If set, the state of these devices will be toggled each time the tag is touched, " + 
283                         "e.g. a light that's on will be turned off and one that's off will be turned on, " +
284                         "other devices of the same type will be set to the same state as their master device. " +
285                         "If no master is designated then the majority of devices of the same type will be used " +
286                         "to determine whether to turn on or off the devices.") {
287                     
288                     if (switch1 || masterSwitch) {
289                         input "masterSwitch", "enum", title: "Master switch", options: switch1.collect{[(it.id): it.displayName]}, required: false
290                     }
291                     if (lock || masterLock) {
292                         input "masterLock", "enum", title: "Master lock", options: lock.collect{[(it.id): it.displayName]}, required: false
293                     }
294                     if (garageDoor || masterDoor) {
295                         input "masterDoor", "enum", title: "Master door", options: garageDoor.collect{[(it.id): it.displayName]}, required: false
296                     }            
297                         }
298                         section([mobileOnly:true]) {
299                                 label title: "Assign a name", required: false
300                                 mode title: "Set for specific mode(s)", required: false
301                         }        
302             }
303         }
304         
305         def installed() {
306                 log.debug "Installed with settings: ${settings}"
307         
308                 initialize()
309         }
310         
311         def updated() {
312                 log.debug "Updated with settings: ${settings}"
313         
314                 unsubscribe()
315                 initialize()
316         }
317         
318         def initialize() {
319                 subscribe tag, "nfcTouch", touchHandler
320             subscribe app, touchHandler
321         }
322         
323         private currentStatus(devices, master, attribute) {
324                 log.trace "currentStatus($devices, $master, $attribute)"
325                 def result = null
326                 if (master) {
327                 result = devices.find{it.id == master}?.currentValue(attribute)
328             }
329             else {
330                 def map = [:]
331                 devices.each {
332                         def value = it.currentValue(attribute)
333                     map[value] = (map[value] ?: 0) + 1
334                     log.trace "$it.displayName: $value"
335                 }
336                 log.trace map
337                 result = map.collect{it}.sort{it.value}[-1].key
338             }
339             log.debug "$attribute = $result"
340             result
341         }
342         
343         def touchHandler(evt) {
344                 log.trace "touchHandler($evt.descriptionText)"
345             if (switch1) {
346                 def status = currentStatus(switch1, masterSwitch, "switch")
347                 switch1.each {
348                     if (status == "on") {
349                         it.off()
350                     }
351                     else {
352                         it.on()
353                     }
354                 }
355             }
356             
357             if (lock) {
358                 def status = currentStatus(lock, masterLock, "lock")
359                 lock.each {
360                     if (status == "locked") {
361                         lock.unlock()
362                     }
363                     else {
364                         lock.lock()
365                     }
366                 }
367             }
368             
369             if (garageDoor) {
370                 def status = currentStatus(garageDoor, masterDoor, "status")
371                 garageDoor.each {
372                         if (status == "open") {
373                         it.close()
374                     }
375                     else {
376                         it.open()
377                     }
378                 }
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 switch!
392         def switchesoff
393         //Object for class switch!
394         def switcheson
395         //Object for class lock!
396         def lock1
397         //Global variable for mode!
398         def newMode = "home"
399         //Global variable for number!
400         def waitfor = 10
401
402         //Extracted objects for functions for App2
403         //Global Object for functions in subscribe method!
404         def installed = this.&installed
405         //Global Object for functions in subscribe method!
406         def updated = this.&updated
407         //Global Object for functions in subscribe method!
408         def appTouch = this.&appTouch
409
410         App2(Object obj) {
411                 reference = obj
412                 location = obj.locationObject
413                 app = obj.appObject
414                 switchesoff = obj.switchObject
415                 switcheson = obj.switchObject
416                 lock1 = obj.lockObject
417         }
418         //Global variables for each app
419         //Settings variable defined to settings on purpose
420         def settings = "Settings"
421         //Global variable for state[mode]
422         def state = [home:[],away:[],night:[]]
423         //Create a global logger object for methods
424         def log = new Logger()
425         //Create a global variable for Functions in Subscribe method
426         def functionList = []
427         //Create a global variable for Objects in Subscribe method
428         def objectList = []
429         //Create a global variable for Events in Subscribe method
430         def eventList = []
431         //Create a global list for function schedulers
432         def timersFuncList = []
433         //Create a global list for timer schedulers
434         def timersList = []
435
436         //Methods
437         /////////////////////////////////////////////////////////////////////
438         def setLocationMode(String mode) {
439                 location.mode = mode
440         }
441         
442         /////////////////////////////////////////////////////////////////////
443         ////subscribe(obj, func)
444         def subscribe(Object obj, Closure FunctionToCall) {
445                 if (obj == app) {
446                         objectList.add(obj)
447                         eventList.add("Touched")
448                         functionList.add(FunctionToCall)
449                 } else if (obj == location) {
450                         objectList.add(obj)
451                         eventList.add("Location")
452                         functionList.add(FunctionToCall)
453                 }
454         }
455         ////subscribe(obj, event, func)
456         def subscribe(Object obj, String event, Closure FunctionToCall) {
457                 objectList.add(obj)
458                 eventList.add(event)
459                 functionList.add(FunctionToCall)
460         }
461         ////subscribe(obj, event, func, data)
462         def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) {
463                 objectList.add(obj)     
464                 eventList.add(event)
465                 functionList.add(FunctionToCall)
466         }
467         /////////////////////////////////////////////////////////////////////
468         ////runIn(time, func)
469         def runIn(int seconds, Closure functionToCall) {
470                 if (timersFuncList.contains(functionToCall)) {
471                         timersList[timersFuncList.indexOf(functionToCall)].cancel()
472                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
473                 } else {
474                         timersFuncList.add(functionToCall)
475                         timersList.add(new SimulatedTimer())
476                         def task = timersList[timersFuncList.indexOf(functionToCall)].runAfter(1000*seconds, functionToCall)
477                 }
478         }
479         /////////////////////////////////////////////////////////////////////
480         ////unschedule(func)
481         def unschedule(Closure functionToUnschedule) {
482                 for (int i = 0;i < timersFuncList.size();i++) {
483                         if (timersFuncList[i] == functionToUnschedule) {
484                                 if (timersList != null)
485                                         timersList[i].cancel()
486                         }
487                 }
488         }
489         
490         
491         def unschedule() {
492                 for (int i = 0;i < timersFuncList.size();i++) {
493                         if (timersList != null)
494                                 timersList[i].cancel()
495                 }
496         }
497         /////////////////////////////////////////////////////////////////////
498         ////sendNotificationToContacts(text, recipients)
499         def sendNotificationToContacts(String text, String recipients) {
500                 for (int i = 0;i < recipients.size();i++) {
501                         for (int j = 0;j < location.contacts.size();j++) {
502                                 if (recipients[i] == location.contacts[j]) {
503                                         println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString())
504                                 }
505                         }
506                 }
507         }
508         /////////////////////////////////////////////////////////////////////
509         ////sendSms(phone, text)
510         def sendSms(long phoneNumber, String text) {
511                 println("Sending \""+text+"\" to "+phoneNumber.toString())
512         }
513         /////////////////////////////////////////////////////////////////////
514         ////schedule(time, nameOfFunction as String)
515         def schedule(String time, String nameOfFunction) {
516                 def _inputTime = time.split(':')
517                 Date date = new Date()  
518                 def _currentTime = date.format("HH:mm:ss").split(':')
519         
520                 //Convert input time and current time to minutes
521                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
522                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
523                 def delay
524         
525                 if (inputTime < currentTime) {
526                         delay = 24*60*60-inputTime+currentTime
527                 } else {
528                         delay = inputTime-currentTime
529                 }
530         
531                 timersFuncList.add(nameOfFunction)
532                 timersList.add(new SimulatedTimer())
533                 def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*1000) {
534                         "$nameOfFunction"()
535                 }
536         }
537         ////schedule(time, nameOfFunction as Closure)
538         def schedule(String time, Closure nameOfFunction) {
539                 def _inputTime = time.split(':')
540                 Date date = new Date()  
541                 def _currentTime = date.format("HH:mm:ss").split(':')
542         
543                 //Convert input time and current time to minutes
544                 def inputTime = Integer.parseInt(_inputTime[0])*3600+Integer.parseInt(_inputTime[1])*60
545                 def currentTime = Integer.parseInt(_currentTime[0])*3600+Integer.parseInt(_currentTime[1])*60+Integer.parseInt(_currentTime[2])
546                 def delay
547         
548                 if (inputTime < currentTime) {
549                         delay = 24*60*60-inputTime+currentTime
550                 } else {
551                         delay = inputTime-currentTime
552                 }
553         
554                 if (timersFuncList.contains(nameOfFunction)) {
555                         timersList[timersFuncList.indexOf(nameOfFunction)].cancel()
556                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
557                 } else {
558                         timersFuncList.add(nameOfFunction)
559                         timersList.add(new SimulatedTimer())
560                         def task = timersList[timersFuncList.indexOf(nameOfFunction)].runAfter(delay*seconds, nameOfFunction)
561                 }
562         }
563
564         def installed()
565         {
566                 log.debug "Installed with settings: ${settings}"
567                 log.debug "Current mode = ${location.mode}"
568                 subscribe(app, appTouch)
569         }
570         
571         
572         def updated()
573         {
574                 log.debug "Updated with settings: ${settings}"
575                 log.debug "Current mode = ${location.mode}"
576                 unsubscribe()
577                 subscribe(app, appTouch)
578         }
579         
580         def appTouch(evt) {
581                 log.debug "changeMode, location.mode = $location.mode, newMode = $newMode, location.modes = $location.modes"
582             if (location.mode != newMode) {
583                                 setLocationMode(newMode)
584                                 log.debug "Changed the mode to '${newMode}'"
585             }   else {
586                 log.debug "New mode is the same as the old mode, leaving it be"
587                 }
588             log.debug "appTouch: $evt"
589             lock1.lock()
590             switcheson.on()
591             def delay = (waitfor != null && waitfor != "") ? waitfor * 1000 : 120000
592                 switchesoff.off(delay: delay)
593         }
594 }
595
596 @Field def app1 = new App1(this)
597 @Field def app2 = new App2(this)
598 app1.installed()
599 app2.installed()
600
601 def events = [1,2,3,4,5,6,7]
602 def list = events.permutations()
603 int count = Verify.getInt(0,list.size()-1)
604 println "COUNT: " + count
605
606 list[count].each {
607   switch(it) {
608     case 1:
609       appObject.setValue([name: "Touched", value: "Touched", deviceId: 0, descriptionText: "",
610                                            displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
611       println "1"
612       break
613     case 2:
614       lockObject.setValue([name: "lock0", value: "locked", deviceId: 0, descriptionText: "",
615                                            displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
616       println "   2"
617                         break
618     case 3:
619       lockObject.setValue([name: "lock0", value: "unlocked", deviceId: 0, descriptionText: "",
620                                            displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
621       println "      3"
622       break
623     case 4:
624       contactObject.setValue([name: "contact0", value: "open", deviceId: 0, descriptionText: "",
625                                            displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
626       println "         4"
627       break
628     case 5:
629       contactObject.setValue([name: "contact0", value: "closed", deviceId: 0, descriptionText: "",
630                                            displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
631       println "            5"
632       break
633     case 6:
634       switchObject.setValue([name: "switch0", value: "on", deviceId: 0, descriptionText: "",
635                                            displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
636       println "               6"
637       break
638     case 7:
639       switchObject.setValue([name: "switch0", value: "off", deviceId: 0, descriptionText: "",
640                                            displayed: true, linkText: "", isStateChange: false, unit: "", data: []])
641       println "                   7"
642     default:
643       break
644   }
645 }