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