2 * Copyright 2014 Yves Racine
3 * linkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
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.
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.
16 * Software Distribution is restricted and shall be done only with Developer's written approval.
18 * N.B. Compatible with MyEcobee device available at
19 * http://www.ecomatiqhomes.com/#!store/tc3yr
22 // Automatically generated. Make future change here.
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 ",
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"
34 page(name: "actionsSettings", title: "actionsSettings")
35 page(name: "otherSettings", title: "OtherSettings")
40 def actionsSettings() {
41 dynamicPage(name: "actionsSettings", install: false, uninstall: true, nextPage: "otherSettings") {
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¤cy_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"
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
55 section("Unlock the doors [optional]") {
56 input "locks", "capability.lock", multiple: true, required: false
58 section("Open this Garage Door in case of CO2...") {
59 input "garageSwitch", "capability.switch", title: "Which Garage Door Switch", required: false
61 section("Only If this Garage's Contact is closed") {
62 input "garageMulti", "capability.contactSensor", title: "Which Garage Door Contact", required: false
64 section("Turn off the thermostat(s) [optional]") {
65 input "tstat", "capability.thermostat", title: "Thermostat(s)", multiple: true, required: false
67 section("Disarm the alarm system if armed [optional]") {
68 input "alarmSwitch", "capability.contactSensor", title: "Alarm System", required: false
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
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
78 section("And activate the siren [optional]") {
79 input "securityAlert", "capability.alarm", title: "Security Alert", required: false, multiple:true
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
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
93 section("Use Speech capability to warn the residents (optional) ") {
94 input "theVoice", "capability.speechSynthesis", required: false, multiple: true
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
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
103 section([mobileOnly: true]) {
104 label title: "Assign a name for this SmartApp", required: false
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)
129 subscribe(tstat, "thermostatMode", thermostatModeHandler)
132 reset_state_variables()
136 private def reset_state_variables() {
138 state.lastActivated = null
139 state.lastThermostatMode = null
142 state.lastThermostatMode = " "
144 log.debug "reset_state_variables>thermostat mode reset for $it"
145 state.lastThermostatMode = state.lastThermostatMode + "${it.currentThermostatMode}" + ","
148 log.debug "reset_state_variables>state.lastThermostatMode= $state.lastThermostatMode"
153 def thermostatModeHandler(evt) {
154 log.debug "thermostat mode: $evt.value"
157 def garageDoorContact(evt) {
158 log.info "garageDoorContact, $evt.name: $evt.value"
161 def doorUnlockedHandler(evt) {
162 log.debug "Lock ${locks} was: ${evt.value}"
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'
174 log.trace "$evt.value: $evt, $settings"
178 if (evt.value == TESTED_ALERT) {
179 theMessage = "${evt.displayName} was tested for smoke."
180 send("FireCO2Alarm>${theMessage}")
181 takeActions(evt.value)
183 } else if (evt.value == CLEAR_ALERT) {
184 theMessage = "${evt.displayName} is clear of smoke."
185 send("FireCO2Alarm>${theMessage}")
186 takeActions(CLEAR_SMOKE_ALERT)
188 } else if (evt.value == DETECTED_ALERT) {
189 theMessage = "${evt.displayName} detected smoke!"
190 send("FireCO2Alarm>${theMessage}")
191 takeActions(SMOKE_ALERT)
193 theMessage = ("Unknown event received from ${evt.name}")
194 send("FireCO2Alarm>${theMessage}")
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'
207 log.trace "$evt.value: $evt, $settings"
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)
223 theMessage = "Unknown event received from ${evt.name}"
224 send("FireCO2Alarm>${theMessage}")
229 def batteryHandler(evt) {
230 log.trace "$evt.value: $evt, $settings"
232 int battLevel = evt.integerValue
234 log.debug "${evt.displayName} has battery of ${battLevel}"
236 if (battLevel < lowBattThreshold ?: 10) {
237 theMessage = "${evt.displayName} has battery of ${battLevel}"
238 send("FireCO2Alarm>${theMessage}")
243 def alarmSwitchContact(evt)
246 log.info "alarmSwitchContact, $evt.name: $evt.value"
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)
263 locks.lock() // Lock the locks
264 send("FireCO2Alarm>Cleared, locked all locks...")
268 if (state.lastThermostatMode) {
269 def lastThermostatMode = state.lastThermostatMode.toString().split(',')
272 def lastSavedMode = lastThermostatMode[i].trim()
275 log.debug "About to set ${it}, back to saved thermostatMode=${lastSavedMode}"
276 if (lastSavedMode == 'cool') {
278 } else if (lastSavedMode.contains('heat')) {
280 } else if (lastSavedMode == 'auto') {
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
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
303 private takeActions(String alert) {
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'
313 // Proceed with the following actions when clear alert
315 if ((alert == CLEAR_SMOKE_ALERT) || (alert == CLEAR_CO2_ALERT)) {
317 def delay = (clearAlarmThreshold ?: 1) * 60 // default is 1 minute
318 // Wait a certain delay before clearing the alert
321 send("FireCO2Alarm>Cleared, wait for ${delay} seconds...")
322 runIn(delay, "clearAlert", [overwrite: false])
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)
339 if ((alert != TESTED_ALERT) && (alert != SMOKE_ALERT) && (alert != CO2_ALERT)) {
340 log.debug "Not in test mode nor smoke/CO2 detected, exiting..."
344 // Proceed with the following actions in case of SMOKE or CO2 alert
347 // Reset state variables
349 reset_state_variables()
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)
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)
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
381 if (!location.mode.contains('Away')) {
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
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
404 flashLights() // Flash the lights
405 msg = "flashed the lights"
406 send("FireCO2Alarm>${msg}...")
412 def now = new Date().getTime() // Turn the switches on at night
414 if (now > state.setTime) {
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
430 def s = getSunriseAndSunset(zipCode: zipCode)
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)"
439 private flashLights() {
441 def onFor = givenOnFor ?: 1000
442 def offFor = givenOffFor ?: 1000
443 def numFlashes = numFlashes ?: 20
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}"
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"
462 log.trace "Switch on after $delay msec"
463 switches.eachWithIndex {
465 if (initialActionOn[i]) {
473 log.trace "Switch off after $delay msec"
474 switches.eachWithIndex {
476 if (initialActionOn[i]) {
489 /*if (sendPushMessage != "No") {
490 log.debug("sending push message")
495 log.debug("sending text message")
496 sendSms(phoneNumber, msg)