Update groveStreams.groovy
[smartapps.git] / third-party / FireCO2Alarm.groovy
1 /**
2  *  Copyright 2014 Yves Racine
3  *  linkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
4  *
5  *  Developer retains all right, title, copyright, and interest, including all copyright, patent rights, trade secret 
6  *  in the Background technology. May be subject to consulting fees under the Agreement between the Developer and the Customer. 
7  *  Developer grants a non exclusive perpetual license to use the Background technology in the Software developed for and delivered 
8  *  to Customer under this Agreement. However, the Customer shall make no commercial use of the Background technology without
9  *  Developer's written consent.
10  *
11  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13  *  Take a series of actions in case of smoke or CO2 alert, i.e. turn on/flash the lights, turn on the siren, unlock the doors, turn
14  *  off the thermostat(s), turn off the alarm system, etc.
15  *
16  *  Software Distribution is restricted and shall be done only with Developer's written approval.
17  *
18  *  N.B. Compatible with MyEcobee device available at 
19  *          http://www.ecomatiqhomes.com/#!store/tc3yr 
20  */
21  
22 // Automatically generated. Make future change here.
23 definition(
24         name: "FireCO2Alarm",
25         namespace: "yracine",
26         author: "yracine@yahoo.com",
27         description: "In case of a fire/CO2 alarm,turn on all the lights/turn off all thermostats, unlock the doors, disarm the alarm system & open the garage door when CO2 is detected ",
28         category: "My Apps",
29         iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
30         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png"
31 )
32
33 preferences {
34         page(name: "actionsSettings", title: "actionsSettings")
35         page(name: "otherSettings", title: "OtherSettings")
36
37
38 }
39
40 def actionsSettings() {
41         dynamicPage(name: "actionsSettings", install: false, uninstall: true, nextPage: "otherSettings") {
42                 section("About") {
43                         paragraph "FireCO2Alarm, the smartapp that executes a series of actions when a Fire or CO2 alarm is triggerred"
44                         paragraph "Version 1.3" 
45                         paragraph "If you like this smartapp, please support the developer via PayPal and click on the Paypal link below " 
46                                 href url: "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=yracine%40yahoo%2ecom&lc=US&item_name=Maisons%20ecomatiq&no_note=0&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHostedGuest",
47                                         title:"Paypal donation..."
48                         paragraph "Copyright©2014 Yves Racine"
49                                 href url:"http://github.com/yracine/device-type.myecobee", style:"embedded", required:false, title:"More information..."  
50                                         description: "http://github.com/yracine"
51                 }
52                 section("When these smoke/CO2 detector trigger, the following actions will be taken...") {
53                         input "smoke_detectors", "capability.smokeDetector", title: "Which Smoke/CO2 detector(s)?", multiple: true
54                 }
55                 section("Unlock the doors [optional]") {
56                         input "locks", "capability.lock", multiple: true, required: false
57                 }
58                 section("Open this Garage Door in case of CO2...") {
59                         input "garageSwitch", "capability.switch", title: "Which Garage Door Switch", required: false
60                 }
61                 section("Only If this Garage's Contact is closed") {
62                         input "garageMulti", "capability.contactSensor", title: "Which Garage Door Contact", required: false
63                 }
64                 section("Turn off the thermostat(s) [optional]") {
65                         input "tstat", "capability.thermostat", title: "Thermostat(s)", multiple: true, required: false
66                 }
67                 section("Disarm the alarm system if armed [optional]") {
68                         input "alarmSwitch", "capability.contactSensor", title: "Alarm System", required: false
69                 }
70                 section("Flash/turn on the lights...") {
71                         input "switches", "capability.switch", title: "These lights", multiple: true
72                         input "numFlashes", "number", title: "This number of times (default 20)", required: false
73                 }
74                 section("Time settings in milliseconds [optional]") {
75                         input "givenOnFor", "number", title: "On for (default 1000)", required: false
76                         input "givenOffFor", "number", title: "Off for (default 1000)", required: false
77                 }
78                 section("And activate the siren [optional]") {
79                         input "securityAlert", "capability.alarm", title: "Security Alert", required: false, multiple:true
80                 }
81                 section("Clear alarm threshold (default = 1 min) to revert actions[optional]") {
82                         input "clearAlarmThreshold", "decimal", title: "Number of minutes after clear alarm", required: false
83                 }
84         }
85 }
86
87
88 def otherSettings() {
89         dynamicPage(name: "otherSettings", title: "Other Settings", install: true, uninstall: false) {
90                 section("Detectors' low battery warning") {
91                         input "lowBattThreshold", "number", title: "Low Batt Threshold % (default 10%)", required: false
92                 }
93                 section("Use Speech capability to warn the residents (optional) ") {
94                         input "theVoice", "capability.speechSynthesis", required: false, multiple: true
95                 }
96                 section("What do I use for the Master on/off switch to enable/disable voice notifications? (optional)") {
97                         input "powerSwitch", "capability.switch", required: false
98                 }
99                 section("Notifications") {
100                         input "sendPushMessage", "enum", title: "Send a push notification?", metadata: [values: ["Yes", "No"]], required: false
101                         input "phone", "phone", title: "Send a Text Message?", required: false
102                 }
103                 section([mobileOnly: true]) {
104                         label title: "Assign a name for this SmartApp", required: false
105                 }
106         }
107 }
108
109
110
111
112 def installed() {
113         initialize()
114 }
115
116 def updated() {
117         unsubscribe()
118         initialize()
119 }
120
121 private initialize() {
122         subscribe(smoke_detectors, "smoke", smokeHandler)
123         subscribe(smoke_detectors, "carbonMonoxide", carbonMonoxideHandler)
124         subscribe(smoke_detectors, "battery", batteryHandler)
125         subscribe(locks, "lock", doorUnlockedHandler)
126         subscribe(garageMulti, "contact", garageDoorContact)
127         subscribe(alarmSwitch, "contact", alarmSwitchContact)
128         if (tstat) {
129                 subscribe(tstat, "thermostatMode", thermostatModeHandler)
130         }
131
132         reset_state_variables()
133 }
134
135
136 private def reset_state_variables() {
137
138         state.lastActivated = null
139         state.lastThermostatMode = null
140
141         if (tstat) {
142                 state.lastThermostatMode = " "
143                 tstat.each {
144                         log.debug "reset_state_variables>thermostat mode reset for $it"
145                         state.lastThermostatMode = state.lastThermostatMode + "${it.currentThermostatMode}" + ","
146                 }
147         }
148         log.debug "reset_state_variables>state.lastThermostatMode= $state.lastThermostatMode"
149
150 }
151
152
153 def thermostatModeHandler(evt) {
154         log.debug "thermostat mode: $evt.value"
155 }
156
157 def garageDoorContact(evt) {
158         log.info "garageDoorContact, $evt.name: $evt.value"
159 }
160
161 def doorUnlockedHandler(evt) {
162         log.debug "Lock ${locks} was: ${evt.value}"
163
164 }
165
166
167 def smokeHandler(evt) {
168         def SMOKE_ALERT = 'detected_SMOKE'
169         def CLEAR_ALERT = 'clear'
170         def CLEAR_SMOKE_ALERT = 'clear_SMOKE'
171         def DETECTED_ALERT = 'detected'
172         def TESTED_ALERT = 'tested'
173
174         log.trace "$evt.value: $evt, $settings"
175
176         String theMessage
177
178         if (evt.value == TESTED_ALERT) {
179                 theMessage = "${evt.displayName} was tested for smoke."
180                 send("FireCO2Alarm>${theMessage}")
181                 takeActions(evt.value)
182
183         } else if (evt.value == CLEAR_ALERT) {
184                 theMessage = "${evt.displayName} is clear of smoke."
185                 send("FireCO2Alarm>${theMessage}")
186                 takeActions(CLEAR_SMOKE_ALERT)
187
188         } else if (evt.value == DETECTED_ALERT) {
189                 theMessage = "${evt.displayName} detected smoke!"
190                 send("FireCO2Alarm>${theMessage}")
191                 takeActions(SMOKE_ALERT)
192         } else {
193                 theMessage = ("Unknown event received from ${evt.name}")
194                 send("FireCO2Alarm>${theMessage}")
195         }
196
197 }
198
199
200 def carbonMonoxideHandler(evt) {
201         def CO2_ALERT = 'detected_CO2'
202         def CLEAR_CO2_ALERT = 'clear_CO2'
203         def CLEAR_ALERT = 'clear'
204         def DETECTED_ALERT = 'detected'
205         def TESTED_ALERT = 'tested'
206
207         log.trace "$evt.value: $evt, $settings"
208
209         String theMessage
210
211         if (evt.value == TESTED_ALERT) {
212                 theMessage = "${evt.displayName} was tested for carbon monoxide."
213                 send("FireCO2Alarm>${theMessage}")
214         } else if (evt.value == CLEAR_ALERT) {
215                 theMessage = "${evt.displayName} is clear of carbon monoxide."
216                 send("FireCO2Alarm>${theMessage}")
217                 takeActions(CLEAR_CO2_ALERT)
218         } else if (evt.value == DETECTED_ALERT) {
219                 theMessage = "${evt.displayName} detected carbon monoxide!"
220                 send("FireCO2Alarm>${theMessage}")
221                 takeActions(CO2_ALERT)
222         } else {
223                 theMessage = "Unknown event received from ${evt.name}"
224                 send("FireCO2Alarm>${theMessage}")
225         }
226
227 }
228
229 def batteryHandler(evt) {
230         log.trace "$evt.value: $evt, $settings"
231         String theMessage
232         int battLevel = evt.integerValue
233
234         log.debug "${evt.displayName} has battery of ${battLevel}"
235
236         if (battLevel < lowBattThreshold ?: 10) {
237                 theMessage = "${evt.displayName} has battery of ${battLevel}"
238                 send("FireCO2Alarm>${theMessage}")
239         }
240 }
241
242
243 def alarmSwitchContact(evt)
244
245 {
246         log.info "alarmSwitchContact, $evt.name: $evt.value"
247 }
248
249 def clearAlert() {
250         def msg
251
252         if (securityAlert) {
253                 securityAlert.off() // Turned off the security alert
254                 msg = "turned security alert off"
255                 send("FireCO2Alarm>now clear, ${msg}")
256                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
257                         theVoice.setLevel(40)
258                         theVoice.speak(msg)
259                 }
260         }
261         /*    
262             if (locks) {
263                 locks.lock()                                      // Lock the locks 
264                 send("FireCO2Alarm>Cleared, locked all locks...")
265             }
266         */
267         if (tstat) {
268                 if (state.lastThermostatMode) {
269                         def lastThermostatMode = state.lastThermostatMode.toString().split(',')
270                         int i = 0
271                         tstat.each {
272                                 def lastSavedMode = lastThermostatMode[i].trim()
273
274                                 if (lastSavedMode) {
275                                         log.debug "About to set ${it}, back to saved thermostatMode=${lastSavedMode}"
276                                         if (lastSavedMode == 'cool') {
277                                                 it.cool()
278                                         } else if (lastSavedMode.contains('heat')) {
279                                                 it.heat()
280                                         } else if (lastSavedMode == 'auto') {
281                                                 it.auto()
282                                         }
283                                         msg = "thermostat ${it}'s mode is now set back to ${lastThermostatMode[i]}"
284                                         send("FireCO2Alarm>Cleared, ${msg}")
285                                         if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
286                                                 theVoice.speak(msg)
287                                         }
288                                 }
289                                 i++
290                         }
291                 } else {
292                         tstat.auto()
293                         msg = "thermostats set to auto"
294                         send("FireCO2Alarm>Cleared, ${msg}")
295                         if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
296                                 theVoice.speak(msg)
297                         }
298                 }
299         }
300 }
301
302
303 private takeActions(String alert) {
304         def msg
305         def CO2_ALERT = 'detected_CO2'
306         def SMOKE_ALERT = 'detected_SMOKE'
307         def CLEAR_ALERT = 'clear'
308         def CLEAR_SMOKE_ALERT = 'clear_SMOKE'
309         def CLEAR_CO2_ALERT = 'clear_CO2'
310         def DETECTED_ALERT = 'detected'
311         def TESTED_ALERT = 'tested'
312
313         // Proceed with the following actions when clear alert
314
315         if ((alert == CLEAR_SMOKE_ALERT) || (alert == CLEAR_CO2_ALERT)) {
316
317                 def delay = (clearAlarmThreshold ?: 1) * 60 // default is 1 minute
318                         //  Wait a certain delay before clearing the alert
319
320
321                 send("FireCO2Alarm>Cleared, wait for ${delay} seconds...")
322                 runIn(delay, "clearAlert", [overwrite: false])
323
324                 if ((alert == CLEAR_CO2_ALERT) && (garageMulti?.currentContact == "open")) {
325                         log.debug "garage door is open,about to close it following cleared CO2 alert..."
326                         garageSwitch?.on() // Open the garage door if it is closed
327                         msg = "closed the garage door following cleared CO2 alert"
328                         send("FireCO2Alarm>${msg}...")
329                         if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
330                                 theVoice.setLevel(50)
331                                 theVoice.speak(msg)
332                         }
333
334                 }
335
336                 return
337         }
338
339         if ((alert != TESTED_ALERT) && (alert != SMOKE_ALERT) && (alert != CO2_ALERT)) {
340                 log.debug "Not in test mode nor smoke/CO2 detected, exiting..."
341                 return
342         }
343
344         // Proceed with the following actions in case of SMOKE or CO2 alert
345
346
347         //  Reset state variables 
348
349         reset_state_variables()
350
351         if (securityAlert) {
352                 securityAlert.on() // Turned on the security alert
353                 msg = "security Alert on"
354                 send("FireCO2Alarm>${msg}...")
355                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
356                         theVoice.setLevel(75)
357                         theVoice.speak(msg)
358                 }
359         }
360         if (alarmSwitch) {
361                 if (alarmSwitch.currentContact == "closed") {
362                         log.debug "alarm system is on, about to disarm it..."
363                         alarmSwitch.off() // disarm the alarm system
364                         msg = "alarm system disarmed"
365                         send("FireCO2Alarm>${msg}...")
366                         if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
367                                 theVoice.setLevel(75)
368                                 theVoice.speak(msg)
369                         }
370
371                 }
372         }
373         if (tstat) {
374                 tstat.off() // Turn off the thermostats
375                 msg = "turning off all thermostats"
376                 send("FireCO2Alarm>${msg}...")
377                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
378                         theVoice.speak(msg)
379                 }
380         }
381         if (!location.mode.contains('Away')) {
382                 if (locks) {
383                         locks.unlock() // Unlock the locks if mode is not 'Away'
384                         msg = "unlocked the doors"
385                         send("FireCO2Alarm>${msg}...")
386                         if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
387                                 theVoice.speak(msg)
388                         }
389                 }
390                 if ((alert == CO2_ALERT) && (garageSwitch)) {
391                         if (garageMulti?.currentContact == "closed") {
392                                 log.debug "garage door is closed,about to open it following CO2 alert..."
393                                 garageSwitch.on() // Open the garage door if it is closed
394                                 msg = "opened the garage door following CO2 alert"
395                                 send("FireCO2Alarm>${msg}...")
396                                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
397                                         theVoice.speak(msg)
398                                 }
399                         }
400                 }
401
402         }
403
404         flashLights() // Flash the lights
405         msg = "flashed the lights"
406         send("FireCO2Alarm>${msg}...")
407
408         if (theVoice) {
409                 theVoice.speak(msg)
410         }
411
412         def now = new Date().getTime() // Turn the switches on at night
413         astroCheck()
414         if (now > state.setTime) {
415                 switches?.on()
416
417                 msg = "turned on the lights"
418                 send("FireCO2Alarm>${msg}")
419                 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { //  Notify by voice only if the powerSwitch is on
420                         theVoice.speak(msg)
421                 }
422
423         }
424
425
426
427 }
428
429 def astroCheck() {
430         def s = getSunriseAndSunset(zipCode: zipCode)
431
432         state.riseTime = s.sunrise.time
433         state.setTime = s.sunset.time
434         log.debug "rise: ${new Date(state.riseTime)}($state.riseTime), set: ${new Date(state.setTime)}($state.setTime)"
435 }
436
437
438
439 private flashLights() {
440         def doFlash = true
441         def onFor = givenOnFor ?: 1000
442         def offFor = givenOffFor ?: 1000
443         def numFlashes = numFlashes ?: 20
444
445         log.debug "LAST ACTIVATED IS: ${state.lastActivated}"
446         if (state.lastActivated) {
447                 def elapsed = now() - state.lastActivated
448                 def sequenceTime = (numFlashes + 1) * (onFor + offFor)
449                 doFlash = elapsed > sequenceTime
450                 log.debug "DO FLASH: $doFlash, ELAPSED: $elapsed, LAST ACTIVATED: ${state.lastActivated}"
451         }
452
453         if (doFlash) {
454                 log.debug "FLASHING $numFlashes times"
455                 state.lastActivated = now()
456                 log.debug "LAST ACTIVATED SET TO: ${state.lastActivated}"
457                 def initialActionOn = switches.collect {
458                         it.currentSwitch != "on"
459                 }
460                 def delay = 1
461                 numFlashes.times {
462                         log.trace "Switch on after  $delay msec"
463                         switches.eachWithIndex {
464                                 s, i ->
465                                         if (initialActionOn[i]) {
466                                                 s.on(delay: delay)
467
468                                         } else {
469                                                 s.off(delay: delay)
470                                         }
471                         }
472                         delay += onFor
473                         log.trace "Switch off after $delay msec"
474                         switches.eachWithIndex {
475                                 s, i ->
476                                         if (initialActionOn[i]) {
477                                                 s.off(delay: delay)
478
479                                         } else {
480                                                 s.on(delay: delay)
481                                         }
482                         }
483                         delay += offFor
484                 }
485         }
486 }
487
488 private send(msg) {
489         /*if (sendPushMessage != "No") {
490                 log.debug("sending push message")
491                 sendPush(msg)
492         }
493
494         if (phoneNumber) {
495                 log.debug("sending text message")
496                 sendSms(phoneNumber, msg)
497         }*/
498         log.debug msg
499 }