Update Hue Party Mode.groovy
[smartapps.git] / third-party / WindowOrDoorOpen.groovy
1 /**
2  *  WindowOrDoorOpen!
3  *
4  *  Copyright 2014 Yves Racine 
5  *  LinkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
6  *
7  *  Developer retains all right, title, copyright, and interest, including all copyright, patent rights, trade secret 
8  *  in the Background technology. May be subject to consulting fees under the Agreement between the Developer and the Customer. 
9  *  Developer grants a non exclusive perpetual license to use the Background technology in the Software developed for and delivered 
10  *  to Customer under this Agreement. However, the Customer shall make no commercial use of the Background technology without
11  *  Developer's written consent.
12  *
13  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
14  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
15  *
16  * Software Distribution is restricted and shall be done only with Developer's written approval.
17  * 
18  * Compatible with MyEcobee device available at my store:
19  *          http://www.ecomatiqhomes.com/#!store/tc3yr 
20  *
21  */
22 definition(
23         name: "WindowOrDoorOpen!",
24         namespace: "yracine",
25         author: "Yves Racine",
26         description: "Choose some contact sensors and get a notification (with voice as an option) when they are left open for too long.  Optionally, turn off the HVAC and set it back to cool/heat when window/door is closed",
27         category: "Safety & Security",
28         iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
29         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
30
31
32 preferences {
33         section("About") {
34                 paragraph "WindowOrDoorOpen!, the smartapp that warns you if you leave a door or window open (with voice as an option);" +
35                         "it will turn off your thermostats (optional) after a delay and restore their mode when the contact is closed." +
36                 "The smartapp can track up to 30 contacts and can keep track of 6 open contacts at the same time due to ST scheduling limitations"
37                 paragraph "Version 2.4.1" 
38                 paragraph "If you like this smartapp, please support the developer via PayPal and click on the Paypal link below " 
39                         href url: "https://www.paypal.me/ecomatiqhomes",
40                                         title:"Paypal donation..."            
41                 paragraph "Copyright©2014 Yves Racine"
42                         href url:"http://github.com/yracine/device-type.myecobee", style:"embedded", required:false, title:"More information..."  
43                                 description: "http://github.com/yracine/device-type.myecobee/blob/master/README.md"
44         }
45         section("Notify me when the following door(s) or window contact(s) are left open (maximum 30 contacts)...") {
46                 input "theSensor", "capability.contactSensor", multiple:true, required: true
47         }
48         section("Notifications") {
49                 input "sendPushMessage", "enum", title: "Send a push notification?", metadata: [values: ["Yes", "No"]], required: false
50                 input "phone", "phone", title: "Send a Text Message?", required: false
51                 input "frequency", "number", title: "Delay between notifications in minutes", description: "", required: false
52                 input "givenMaxNotif", "number", title: "Max Number of Notifications", description: "", required: false
53         }
54         section("Use Speech capability to warn the residents [optional]") {
55                 input "theVoice", "capability.speechSynthesis", required: false, multiple: true
56                 input "powerSwitch", "capability.switch", title: "On/off switch for Voice notifications? [optional]", required: false
57         }
58         section("And, when contact is left open for more than this delay in minutes [default=5 min.]") {
59                 input "maxOpenTime", "number", title: "Minutes?", required:false
60         }
61         section("Turn off the thermostat(s) after the delay;revert this action when closed [optional]") {
62                 input "tstats", "capability.thermostat", multiple: true, required: false
63         }
64         section("What do I use as the Master on/off switch to enable/disable other smartapps' processing? [optional,ex.for zoned heating/cooling solutions]") {
65                 input (name:"masterSwitch", type:"capability.switch", required: false, description: "Optional")
66         }
67
68 }
69
70 def installed() {
71         log.debug "Installed with settings: ${settings}"
72
73         initialize()
74 }
75
76 def updated() {
77         log.debug "Updated with settings: ${settings}"
78
79         unsubscribe()
80         unschedule()    
81         initialize()
82 }
83
84 def initialize() {      
85         def MAX_CONTACT=30
86         state?.lastThermostatMode = null
87         // subscribe to all contact sensors to check for open/close events
88         state?.status=[]    
89         state?.count=[]    
90         state.lastThermostatMode = ""
91     
92         int i=0    
93         theSensor.each {
94                 subscribe(theSensor[i], "contact.closed", "sensorTriggered${i}")
95                 subscribe(theSensor[i], "contact.open", "sensorTriggered${i}")
96                 state?.status[i] = " "
97                 state?.count[i] = 0
98                 i++   
99                 if (i>=MAX_CONTACT) {
100                         return       
101                 }        
102         }  
103
104 }
105
106 def sensorTriggered0(evt) {
107         int i=0
108         sensorTriggered(evt,i)    
109 }
110
111 def sensorTriggered1(evt) {
112         int i=1
113         sensorTriggered(evt,i)    
114 }
115
116 def sensorTriggered2(evt) {
117         int i=2
118         sensorTriggered(evt,i)    
119 }
120
121 def sensorTriggered3(evt) {
122         int i=3
123         sensorTriggered(evt,i)    
124 }
125
126 def sensorTriggered4(evt) {
127         int i=4
128         sensorTriggered(evt,i)    
129 }
130
131 def sensorTriggered5(evt) {
132         int i=5
133         sensorTriggered(evt,i)    
134 }
135
136 def sensorTriggered6(evt) {
137         int i=6
138         sensorTriggered(evt,i)    
139 }
140
141 def sensorTriggered7(evt) {
142         int i=7
143         sensorTriggered(evt,i)    
144 }
145
146 def sensorTriggered8(evt) {
147         int i=8
148         sensorTriggered(evt,i)    
149 }
150
151 def sensorTriggered9(evt) {
152         int i=9
153         sensorTriggered(evt,i)    
154 }
155
156 def sensorTriggered10(evt) {
157         int i=10
158         sensorTriggered(evt,i)    
159 }
160
161 def sensorTriggered11(evt) {
162         int i=11
163         sensorTriggered(evt,i)    
164 }
165
166 def motionEvtHandler12(evt) {
167         int i=12
168         sensorTriggered(evt,i)    
169 }
170
171 def sensorTriggered13(evt) {
172         int i=13
173         sensorTriggered(evt,i)    
174 }
175
176 def sensorTriggered14(evt) {
177         int i=14
178         sensorTriggered(evt,i)    
179 }
180
181 def sensorTriggered15(evt) {
182         int i=15
183         sensorTriggered(evt,i)    
184 }
185
186 def sensorTriggered16(evt) {
187         int i=16
188         sensorTriggered(evt,i)    
189 }
190
191 def sensorTriggered17(evt) {
192         int i=17
193         sensorTriggered(evt,i)    
194 }
195
196 def sensorTriggered18(evt) {
197         int i=18
198         sensorTriggered(evt,i)    
199 }
200
201 def sensorTriggered19(evt) {
202         int i=19
203         sensorTriggered(evt,i)    
204 }
205
206 def sensorTriggered20(evt) {
207         int i=20
208         sensorTriggered(evt,i)    
209 }
210
211 def sensorTriggered21(evt) {
212         int i=21
213         sensorTriggered(evt,i)    
214 }
215
216 def sensorTriggered22(evt) {
217         int i=22
218         sensorTriggered(evt,i)    
219 }
220
221 def sensorTriggered23(evt) {
222         int i=23
223         sensorTriggered(evt,i)    
224 }
225
226 def sensorTriggered24(evt) {
227         int i=24
228         sensorTriggered(evt,i)    
229 }
230
231 def sensorTriggered25(evt) {
232         int i=25
233         sensorTriggered(evt,i)    
234 }
235
236 def sensorTriggered26(evt) {
237         int i=26
238         sensorTriggered(evt,i)    
239 }
240
241 def sensorTriggered27(evt) {
242         int i=27
243         sensorTriggered(evt,i)    
244 }
245
246 def sensorTriggered28(evt) {
247         int i=28
248         sensorTriggered(evt,i)    
249 }
250
251
252 def sensorTriggered29(evt) {
253         int i=29
254         sensorTriggered(evt,i)    
255 }
256
257
258 def sensorTriggered(evt, indice=0) {
259         def delay = (frequency) ?: 1    
260         def freq = delay * 60
261         def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
262
263         if (evt.value == "closed") {
264                 restore_tstats_mode()
265                 def msg = "your ${theSensor[indice]} is now closed"
266                 send("WindowOrDoorOpen>${msg}")
267                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
268                         theVoice.setLevel(30)
269                         theVoice.speak(msg)
270                 }
271                 clearStatus(indice)
272         } else if ((evt.value == "open") && (state?.status[indice] != "scheduled")) {
273                 def takeActionMethod= "takeAction${indice}"       
274                 runIn(freq, "${takeActionMethod}",[overwrite: false])
275                 state?.status[indice] = "scheduled"
276                 log.debug "${theSensor[indice]} is now open and will be checked every ${delay} minute(s) by ${takeActionMethod}"
277         }
278 }
279
280
281 def takeAction0() {
282         int i=0
283         log.debug ("about to call takeAction() for ${theSensor[i]}")    
284         takeAction(i)
285 }
286
287
288 def takeAction1() {
289         int i=1
290         log.debug ("about to call takeAction() for ${theSensor[i]}")    
291         takeAction(i)
292 }
293
294 def takeAction2() {
295         int i=2
296         log.debug ("about to call takeAction() for ${theSensor[i]}")    
297         takeAction(i)
298 }
299
300 def takeAction3() {
301         int i=3
302         log.debug ("about to call takeAction() for ${theSensor[i]}")    
303         takeAction(i)
304 }
305
306 def takeAction4() {
307         int i=4
308         log.debug ("about to call takeAction() for ${theSensor[i]}")    
309         takeAction(i)
310 }
311
312 def takeAction5() {
313         int i=5
314         log.debug ("about to call takeAction() for ${theSensor[i]}")    
315         takeAction(i)
316 }
317
318 def takeAction6() {
319         int i=6
320         log.debug ("about to call takeAction() for ${theSensor[i]}")    
321         takeAction(i)
322 }
323
324 def takeAction7() {
325         int i=7
326         log.debug ("about to call takeAction() for ${theSensor[i]}")    
327         takeAction(i)
328 }
329
330 def takeAction8() {
331         int i=8
332         log.debug ("about to call takeAction() for ${theSensor[i]}")    
333         takeAction(i)
334 }
335
336 def takeAction9() {
337         int i=9
338         log.debug ("about to call takeAction() for ${theSensor[i]}")    
339         takeAction(i)
340 }
341
342
343 def takeAction10() {
344         int i=10
345         log.debug ("about to call takeAction() for ${theSensor[i]}")    
346         takeAction(i)
347 }
348
349 def takeAction11() {
350         int i=11
351         log.debug ("about to call takeAction() for ${theSensor[i]}")    
352         takeAction(i)
353 }
354
355 def takeAction12() {
356         int i=12
357         log.debug ("about to call takeAction() for ${theSensor[i]}")    
358         takeAction(i)
359 }
360
361 def takeAction13() {
362         int i=13
363         log.debug ("about to call takeAction() for ${theSensor[i]}")    
364         takeAction(i)
365 }
366
367 def takeAction14() {
368         int i=14
369         log.debug ("about to call takeAction() for ${theSensor[i]}")    
370         takeAction(i)
371 }
372
373
374 def takeAction15() {
375         int i=15
376         log.debug ("about to call takeAction() for ${theSensor[i]}")    
377         takeAction(i)
378 }
379
380 def takeAction16() {
381         int i=16
382         log.debug ("about to call takeAction() for ${theSensor[i]}")    
383         takeAction(i)
384 }
385
386
387 def takeAction17() {
388         int i=17
389         log.debug ("about to call takeAction() for ${theSensor[i]}")    
390         takeAction(i)
391 }
392
393 def takeAction18() {
394         int i=18
395         log.debug ("about to call takeAction() for ${theSensor[i]}")    
396         takeAction(i)
397 }
398
399 def takeAction19() {
400         int i=19
401         log.debug ("about to call takeAction() for ${theSensor[i]}")    
402         takeAction(i)
403 }
404
405 def takeAction20() {
406         int i=20
407         log.debug ("about to call takeAction() for ${theSensor[i]}")    
408         takeAction(i)
409 }
410
411
412 def takeAction21() {
413         int i=21
414         log.debug ("about to call takeAction() for ${theSensor[i]}")    
415         takeAction(i)
416 }
417
418 def takeAction22() {
419         int i=22
420         log.debug ("about to call takeAction() for ${theSensor[i]}")    
421         takeAction(i)
422 }
423
424 def takeAction23() {
425         int i=23
426         log.debug ("about to call takeAction() for ${theSensor[i]}")    
427         takeAction(i)
428 }
429
430
431 def takeAction24() {
432         int i=24
433         log.debug ("about to call takeAction() for ${theSensor[i]}")    
434         takeAction(i)
435 }
436
437 def takeAction25() {
438         int i=25
439         log.debug ("about to call takeAction() for ${theSensor[i]}")    
440         takeAction(i)
441 }
442
443 def takeAction26() {
444         int i=26
445         log.debug ("about to call takeAction() for ${theSensor[i]}")    
446         takeAction(i)
447 }
448
449 def takeAction27() {
450         int i=27
451         log.debug ("about to call takeAction() for ${theSensor[i]}")    
452         takeAction(i)
453 }
454
455
456 def takeAction28() {
457         int i=28
458         log.debug ("about to call takeAction() for ${theSensor[i]}")    
459         takeAction(i)
460 }
461
462 def takeAction29() {
463         int i=29
464         log.debug ("about to call takeAction() for ${theSensor[i]}")    
465         takeAction(i)
466 }
467
468
469 def takeAction(indice=0) {      
470         def takeActionMethod
471         def delay = (frequency) ?: 1                
472         def freq =  delay * 60
473         def maxNotif = (givenMaxNotif) ?: 5
474         def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
475         def msg
476     
477         def contactState = theSensor[indice].currentState("contact")
478         log.trace "takeAction>${theSensor[indice]}'s contact status = ${contactState.value}, state.status=${state.status[indice]}, indice=$indice"
479         if ((state?.status[indice] == "scheduled") && (contactState.value == "open")) {
480                 state.count[indice] = state.count[indice] + 1
481                 log.debug "${theSensor[indice]} was open too long, sending message (count=${state.count[indice]})"
482                 def openMinutesCount = state.count[indice] * delay
483                 msg = "your ${theSensor[indice]} has been open for more than ${openMinutesCount} minute(s)!"
484                 send("WindowOrDoorOpen>${msg}")
485                 if ((masterSwitch) && (masterSwitch?.currentSwitch=="on")) {
486                         log.debug "master switch ${masterSwitch} is now off"
487                         masterSwitch.off() // set the master switch to off as there is an open contact        
488                 }        
489                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
490                         theVoice.setLevel(30)
491                         theVoice.speak(msg)
492                 }
493
494                 if ((tstats) && (openMinutesCount > max_open_time_in_min)) {
495                                                         
496                         save_tstats_mode()        
497                         tstats.off()
498                                     
499                         msg = "thermostats are now turned off after ${max_open_time_in_min} minutes"
500                         send("WindowDoorOpen>${msg}")
501                 }
502                 if ((!tstats) && (state.count[indice] > maxNotif)) {
503                         // stop the repeated notifications if there is no thermostats provided and we've reached maxNotif
504                         clearStatus(indice)
505                         takeActionMethod= "takeAction${indice}"       
506                         unschedule("${takeActionMethod}")
507                         msg = "maximum notifications ($maxNotif) reached for  ${theSensor[indice]}, unscheduled $takeActionMethod"
508                         log.debug msg            
509                         return
510                 }
511                 takeActionMethod= "takeAction${indice}"       
512                 msg = "contact still open at ${theSensor[indice]}, about to reschedule $takeActionMethod"
513                 log.debug msg            
514                 runIn(freq, "${takeActionMethod}", [overwrite: false])
515         } else if (contactState.value == "closed") {
516                 restore_tstats_mode()
517                 clearStatus(indice)
518                 takeActionMethod= "takeAction${indice}"       
519                 unschedule("${takeActionMethod}")
520                 msg = "contact closed at ${theSensor[indice]}, unscheduled $takeActionMethod"
521                 log.debug msg            
522         }
523 }
524
525 def clearStatus(indice=0) {
526         state?.status[indice] = " "
527         state?.count[indice] = 0
528 }
529
530
531 private void save_tstats_mode() {
532
533         if ((!tstats)  || (state.lastThermostatMode)) { // If state already saved, then keep it
534                 return    
535         } 
536         tstats.each {
537                 it.poll() // to get the latest value at thermostat            
538                 state.lastThermostatMode = state.lastThermostatMode + "${it.currentThermostatMode}" + ","
539         }    
540         log.debug "save_tstats_mode>state.lastThermostatMode= $state.lastThermostatMode"
541
542 }
543
544
545 private void restore_tstats_mode() {
546         def msg
547         def MAX_CONTACT=30
548     
549         log.debug "restore_tstats_mode>checking if all contacts are closed..."
550         for (int j = 0;(j < MAX_CONTACT); j++)  {
551                 if (!theSensor[j]) continue
552                 def contactState = theSensor[j].currentState("contact")
553                 log.trace "restore_tstats_mode>For ${theSensor[j]}, Contact's status = ${contactState.value}, indice=$j"
554                 if (contactState.value == "open") {
555                         return
556                 }
557         }  
558         if ((masterSwitch) && (masterSwitch?.currentSwitch=="off")) {
559                         log.debug "master switch ${masterSwitch} is back on"
560                         masterSwitch.on() // set the master switch to on as there is no more any open contacts        
561         }        
562
563         if (!tstats) {
564                 return    
565         }    
566
567         if (state.lastThermostatMode) {
568                 def lastThermostatMode = state.lastThermostatMode.toString().split(',')
569                 int i = 0
570         
571                 tstats.each {
572                         def lastSavedMode = lastThermostatMode[i].trim()
573                         if (lastSavedMode) {
574                                 log.debug "restore_tstats_mode>about to set ${it}, back to saved thermostatMode=${lastSavedMode}"
575                                 if (lastSavedMode == 'cool') {
576                                         it.cool()
577                                 } else if (lastSavedMode.contains('heat')) {
578                                         it.heat()
579                                 } else if (lastSavedMode == 'auto') {
580                                         it.auto()
581                                 } else {
582                                         it.off()
583                                 }
584                                 msg = "thermostat ${it}'s mode is now set back to ${lastSavedMode}"
585                                 send("WindowOrDoorOpen>${theSensor} closed, ${msg}")
586                                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
587                                         theVoice.speak(msg)
588                                 }
589                 
590                         }
591                         i++
592                 }
593         }        
594         state.lastThermostatMode = ""
595 }
596
597
598
599 private send(msg) {
600         if (sendPushMessage != "No") {
601                 log.debug("sending push message")
602                 sendPush(msg)
603         }
604
605         if (phone) {
606                 log.debug("sending text message")
607                 sendSms(phone, msg)
608         }
609         log.debug msg
610 }