Update hvac-auto-off.smartapp.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         // Adjustment for saved state
86         frequency = frequency + 1
87         def MAX_CONTACT=30
88         state?.lastThermostatMode = null
89         // subscribe to all contact sensors to check for open/close events
90         state?.status=[]    
91         state?.count=[]    
92         state.lastThermostatMode = ""
93     
94         int i=0    
95         theSensor.each {
96                 subscribe(theSensor[i], "contact.closed", "sensorTriggered${i}")
97                 subscribe(theSensor[i], "contact.open", "sensorTriggered${i}")
98                 state?.status[i] = " "
99                 state?.count[i] = 0
100                 i++   
101                 if (i>=MAX_CONTACT) {
102                         return       
103                 }        
104         }  
105
106 }
107
108 def sensorTriggered0(evt) {
109         int i=0
110         sensorTriggered(evt,i)    
111 }
112
113 def sensorTriggered1(evt) {
114         int i=1
115         sensorTriggered(evt,i)    
116 }
117
118 def sensorTriggered2(evt) {
119         int i=2
120         sensorTriggered(evt,i)    
121 }
122
123 def sensorTriggered3(evt) {
124         int i=3
125         sensorTriggered(evt,i)    
126 }
127
128 def sensorTriggered4(evt) {
129         int i=4
130         sensorTriggered(evt,i)    
131 }
132
133 def sensorTriggered5(evt) {
134         int i=5
135         sensorTriggered(evt,i)    
136 }
137
138 def sensorTriggered6(evt) {
139         int i=6
140         sensorTriggered(evt,i)    
141 }
142
143 def sensorTriggered7(evt) {
144         int i=7
145         sensorTriggered(evt,i)    
146 }
147
148 def sensorTriggered8(evt) {
149         int i=8
150         sensorTriggered(evt,i)    
151 }
152
153 def sensorTriggered9(evt) {
154         int i=9
155         sensorTriggered(evt,i)    
156 }
157
158 def sensorTriggered10(evt) {
159         int i=10
160         sensorTriggered(evt,i)    
161 }
162
163 def sensorTriggered11(evt) {
164         int i=11
165         sensorTriggered(evt,i)    
166 }
167
168 def motionEvtHandler12(evt) {
169         int i=12
170         sensorTriggered(evt,i)    
171 }
172
173 def sensorTriggered13(evt) {
174         int i=13
175         sensorTriggered(evt,i)    
176 }
177
178 def sensorTriggered14(evt) {
179         int i=14
180         sensorTriggered(evt,i)    
181 }
182
183 def sensorTriggered15(evt) {
184         int i=15
185         sensorTriggered(evt,i)    
186 }
187
188 def sensorTriggered16(evt) {
189         int i=16
190         sensorTriggered(evt,i)    
191 }
192
193 def sensorTriggered17(evt) {
194         int i=17
195         sensorTriggered(evt,i)    
196 }
197
198 def sensorTriggered18(evt) {
199         int i=18
200         sensorTriggered(evt,i)    
201 }
202
203 def sensorTriggered19(evt) {
204         int i=19
205         sensorTriggered(evt,i)    
206 }
207
208 def sensorTriggered20(evt) {
209         int i=20
210         sensorTriggered(evt,i)    
211 }
212
213 def sensorTriggered21(evt) {
214         int i=21
215         sensorTriggered(evt,i)    
216 }
217
218 def sensorTriggered22(evt) {
219         int i=22
220         sensorTriggered(evt,i)    
221 }
222
223 def sensorTriggered23(evt) {
224         int i=23
225         sensorTriggered(evt,i)    
226 }
227
228 def sensorTriggered24(evt) {
229         int i=24
230         sensorTriggered(evt,i)    
231 }
232
233 def sensorTriggered25(evt) {
234         int i=25
235         sensorTriggered(evt,i)    
236 }
237
238 def sensorTriggered26(evt) {
239         int i=26
240         sensorTriggered(evt,i)    
241 }
242
243 def sensorTriggered27(evt) {
244         int i=27
245         sensorTriggered(evt,i)    
246 }
247
248 def sensorTriggered28(evt) {
249         int i=28
250         sensorTriggered(evt,i)    
251 }
252
253
254 def sensorTriggered29(evt) {
255         int i=29
256         sensorTriggered(evt,i)    
257 }
258
259
260 def sensorTriggered(evt, indice=0) {
261         def delay = (frequency) ?: 1    
262         def freq = delay * 60
263         def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
264
265         if (evt.value == "closed") {
266                 restore_tstats_mode()
267                 def msg = "your ${theSensor[indice]} is now closed"
268                 send("WindowOrDoorOpen>${msg}")
269                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
270                         theVoice.setLevel(30)
271                         theVoice.speak(msg)
272                 }
273                 clearStatus(indice)
274         } else if ((evt.value == "open") && (state?.status[indice] != "scheduled")) {
275                 def takeActionMethod= "takeAction${indice}"       
276                 state?.status[indice] = "scheduled"
277                 runIn(freq, "${takeActionMethod}",[overwrite: false])
278                 log.debug "${theSensor[indice]} is now open and will be checked every ${delay} minute(s) by ${takeActionMethod}"
279         }
280 }
281
282
283 def takeAction0() {
284         int i=0
285         log.debug ("about to call takeAction() for ${theSensor[i]}")    
286         takeAction(i)
287 }
288
289
290 def takeAction1() {
291         int i=1
292         log.debug ("about to call takeAction() for ${theSensor[i]}")    
293         takeAction(i)
294 }
295
296 def takeAction2() {
297         int i=2
298         log.debug ("about to call takeAction() for ${theSensor[i]}")    
299         takeAction(i)
300 }
301
302 def takeAction3() {
303         int i=3
304         log.debug ("about to call takeAction() for ${theSensor[i]}")    
305         takeAction(i)
306 }
307
308 def takeAction4() {
309         int i=4
310         log.debug ("about to call takeAction() for ${theSensor[i]}")    
311         takeAction(i)
312 }
313
314 def takeAction5() {
315         int i=5
316         log.debug ("about to call takeAction() for ${theSensor[i]}")    
317         takeAction(i)
318 }
319
320 def takeAction6() {
321         int i=6
322         log.debug ("about to call takeAction() for ${theSensor[i]}")    
323         takeAction(i)
324 }
325
326 def takeAction7() {
327         int i=7
328         log.debug ("about to call takeAction() for ${theSensor[i]}")    
329         takeAction(i)
330 }
331
332 def takeAction8() {
333         int i=8
334         log.debug ("about to call takeAction() for ${theSensor[i]}")    
335         takeAction(i)
336 }
337
338 def takeAction9() {
339         int i=9
340         log.debug ("about to call takeAction() for ${theSensor[i]}")    
341         takeAction(i)
342 }
343
344
345 def takeAction10() {
346         int i=10
347         log.debug ("about to call takeAction() for ${theSensor[i]}")    
348         takeAction(i)
349 }
350
351 def takeAction11() {
352         int i=11
353         log.debug ("about to call takeAction() for ${theSensor[i]}")    
354         takeAction(i)
355 }
356
357 def takeAction12() {
358         int i=12
359         log.debug ("about to call takeAction() for ${theSensor[i]}")    
360         takeAction(i)
361 }
362
363 def takeAction13() {
364         int i=13
365         log.debug ("about to call takeAction() for ${theSensor[i]}")    
366         takeAction(i)
367 }
368
369 def takeAction14() {
370         int i=14
371         log.debug ("about to call takeAction() for ${theSensor[i]}")    
372         takeAction(i)
373 }
374
375
376 def takeAction15() {
377         int i=15
378         log.debug ("about to call takeAction() for ${theSensor[i]}")    
379         takeAction(i)
380 }
381
382 def takeAction16() {
383         int i=16
384         log.debug ("about to call takeAction() for ${theSensor[i]}")    
385         takeAction(i)
386 }
387
388
389 def takeAction17() {
390         int i=17
391         log.debug ("about to call takeAction() for ${theSensor[i]}")    
392         takeAction(i)
393 }
394
395 def takeAction18() {
396         int i=18
397         log.debug ("about to call takeAction() for ${theSensor[i]}")    
398         takeAction(i)
399 }
400
401 def takeAction19() {
402         int i=19
403         log.debug ("about to call takeAction() for ${theSensor[i]}")    
404         takeAction(i)
405 }
406
407 def takeAction20() {
408         int i=20
409         log.debug ("about to call takeAction() for ${theSensor[i]}")    
410         takeAction(i)
411 }
412
413
414 def takeAction21() {
415         int i=21
416         log.debug ("about to call takeAction() for ${theSensor[i]}")    
417         takeAction(i)
418 }
419
420 def takeAction22() {
421         int i=22
422         log.debug ("about to call takeAction() for ${theSensor[i]}")    
423         takeAction(i)
424 }
425
426 def takeAction23() {
427         int i=23
428         log.debug ("about to call takeAction() for ${theSensor[i]}")    
429         takeAction(i)
430 }
431
432
433 def takeAction24() {
434         int i=24
435         log.debug ("about to call takeAction() for ${theSensor[i]}")    
436         takeAction(i)
437 }
438
439 def takeAction25() {
440         int i=25
441         log.debug ("about to call takeAction() for ${theSensor[i]}")    
442         takeAction(i)
443 }
444
445 def takeAction26() {
446         int i=26
447         log.debug ("about to call takeAction() for ${theSensor[i]}")    
448         takeAction(i)
449 }
450
451 def takeAction27() {
452         int i=27
453         log.debug ("about to call takeAction() for ${theSensor[i]}")    
454         takeAction(i)
455 }
456
457
458 def takeAction28() {
459         int i=28
460         log.debug ("about to call takeAction() for ${theSensor[i]}")    
461         takeAction(i)
462 }
463
464 def takeAction29() {
465         int i=29
466         log.debug ("about to call takeAction() for ${theSensor[i]}")    
467         takeAction(i)
468 }
469
470
471 def takeAction(indice=0) {      
472         def takeActionMethod
473         def delay = (frequency) ?: 1                
474         def freq =  delay * 60
475         def maxNotif = (givenMaxNotif) ?: 5
476         def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
477         def msg
478         def open = "open"
479         def closed = "closed"
480     
481         def contactState = theSensor[indice].currentState("contact")
482         log.trace "takeAction>${theSensor[indice]}'s contact status = ${contactState.value}, state.status=${state.status[indice]}, indice=$indice"
483         //if ((state?.status[indice] == "scheduled") && (contactState.value == "open")) {
484         if ((state?.status[indice] == "scheduled") && (contactState == open)) {
485                 state.count[indice] = state.count[indice] + 1
486                 log.debug "${theSensor[indice]} was open too long, sending message (count=${state.count[indice]})"
487                 def openMinutesCount = state.count[indice] * delay
488                 msg = "your ${theSensor[indice]} has been open for more than ${openMinutesCount} minute(s)!"
489                 send("WindowOrDoorOpen>${msg}")
490                 if ((masterSwitch) && (masterSwitch?.currentSwitch=="on")) {
491                         log.debug "master switch ${masterSwitch} is now off"
492                         masterSwitch.off() // set the master switch to off as there is an open contact        
493                 }        
494                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
495                         theVoice.setLevel(30)
496                         theVoice.speak(msg)
497                 }
498
499                 if ((tstats) && (openMinutesCount > max_open_time_in_min)) {
500                                                         
501                         save_tstats_mode()        
502                         tstats.off()
503                                     
504                         msg = "thermostats are now turned off after ${max_open_time_in_min} minutes"
505                         send("WindowDoorOpen>${msg}")
506                 }
507                 if ((!tstats) && (state.count[indice] > maxNotif)) {
508                         // stop the repeated notifications if there is no thermostats provided and we've reached maxNotif
509                         clearStatus(indice)
510                         takeActionMethod= "takeAction${indice}"       
511                         unschedule("${takeActionMethod}")
512                         msg = "maximum notifications ($maxNotif) reached for  ${theSensor[indice]}, unscheduled $takeActionMethod"
513                         log.debug msg            
514                         return
515                 }
516                 takeActionMethod= "takeAction${indice}"       
517                 msg = "contact still open at ${theSensor[indice]}, about to reschedule $takeActionMethod"
518                 log.debug msg            
519                 //runIn(freq, "${takeActionMethod}", [overwrite: false])
520         //} else if (contactState.value == "closed") {
521         } else if (contactState == closed) {
522                 restore_tstats_mode()
523                 clearStatus(indice)
524                 takeActionMethod= "takeAction${indice}"       
525                 unschedule("${takeActionMethod}")
526                 msg = "contact closed at ${theSensor[indice]}, unscheduled $takeActionMethod"
527                 log.debug msg            
528         }
529 }
530
531 def clearStatus(indice=0) {
532         state?.status[indice] = " "
533         state?.count[indice] = 0
534 }
535
536
537 private void save_tstats_mode() {
538
539         if ((!tstats)  || (state.lastThermostatMode)) { // If state already saved, then keep it
540                 return    
541         } 
542         tstats.each {
543                 //it.poll() // to get the latest value at thermostat            
544                 state.lastThermostatMode = state.lastThermostatMode + "${it.currentThermostatMode}" + ","
545         }    
546         log.debug "save_tstats_mode>state.lastThermostatMode= $state.lastThermostatMode"
547
548 }
549
550
551 private void restore_tstats_mode() {
552         def msg
553         def MAX_CONTACT=30
554     
555         log.debug "restore_tstats_mode>checking if all contacts are closed..."
556         for (int j = 0;(j < MAX_CONTACT); j++)  {
557                 if (!theSensor[j]) continue
558                 def contactState = theSensor[j].currentState("contact")
559                 log.trace "restore_tstats_mode>For ${theSensor[j]}, Contact's status = ${contactState.value}, indice=$j"
560                 if (contactState.value == "open") {
561                         return
562                 }
563         }  
564         if ((masterSwitch) && (masterSwitch?.currentSwitch=="off")) {
565                         log.debug "master switch ${masterSwitch} is back on"
566                         masterSwitch.on() // set the master switch to on as there is no more any open contacts        
567         }        
568
569         if (!tstats) {
570                 return    
571         }    
572
573         if (state.lastThermostatMode) {
574                 def lastThermostatMode = state.lastThermostatMode.toString().split(',')
575                 int i = 0
576         
577                 tstats.each {
578                         def lastSavedMode = lastThermostatMode[i].trim()
579                         if (lastSavedMode) {
580                                 log.debug "restore_tstats_mode>about to set ${it}, back to saved thermostatMode=${lastSavedMode}"
581                                 if (lastSavedMode == 'cool') {
582                                         it.cool()
583                                 } else if (lastSavedMode.contains('heat')) {
584                                         it.heat()
585                                 } else if (lastSavedMode == 'auto') {
586                                         it.auto()
587                                 } else {
588                                         it.off()
589                                 }
590                                 msg = "thermostat ${it}'s mode is now set back to ${lastSavedMode}"
591                                 send("WindowOrDoorOpen>${theSensor} closed, ${msg}")
592                                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
593                                         theVoice.speak(msg)
594                                 }
595                 
596                         }
597                         i++
598                 }
599         }        
600         state.lastThermostatMode = ""
601 }
602
603
604
605 private send(msg) {
606         if (sendPushMessage != "No") {
607                 log.debug("sending push message")
608                 sendPush(msg)
609         }
610
611         if (phone) {
612                 log.debug("sending text message")
613                 sendSms(phone, msg)
614         }
615         log.debug msg
616 }