4 * Copyright 2014 Yves Racine
5 * LinkedIn profile: ca.linkedin.com/pub/yves-racine-m-sc-a/0/406/4b/
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.
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.
16 * Software Distribution is restricted and shall be done only with Developer's written approval.
18 * Compatible with MyEcobee device available at my store:
19 * http://www.ecomatiqhomes.com/#!store/tc3yr
23 name: "WindowOrDoorOpen!",
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")
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"
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
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
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
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
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
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")
71 log.debug "Installed with settings: ${settings}"
77 log.debug "Updated with settings: ${settings}"
85 // Adjustment for saved state
86 frequency = frequency + 1
88 state?.lastThermostatMode = null
89 // subscribe-to all contact sensors to check for open/close events
92 state.lastThermostatMode = ""
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] = " "
103 if (i>=MAX_CONTACT) {
110 def sensorTriggered0(evt) {
112 sensorTriggered(evt,i)
115 def sensorTriggered1(evt) {
117 sensorTriggered(evt,i)
120 def sensorTriggered2(evt) {
122 sensorTriggered(evt,i)
125 def sensorTriggered3(evt) {
127 sensorTriggered(evt,i)
130 def sensorTriggered4(evt) {
132 sensorTriggered(evt,i)
135 def sensorTriggered5(evt) {
137 sensorTriggered(evt,i)
140 def sensorTriggered6(evt) {
142 sensorTriggered(evt,i)
145 def sensorTriggered7(evt) {
147 sensorTriggered(evt,i)
150 def sensorTriggered8(evt) {
152 sensorTriggered(evt,i)
155 def sensorTriggered9(evt) {
157 sensorTriggered(evt,i)
160 def sensorTriggered10(evt) {
162 sensorTriggered(evt,i)
165 def sensorTriggered11(evt) {
167 sensorTriggered(evt,i)
170 def motionEvtHandler12(evt) {
172 sensorTriggered(evt,i)
175 def sensorTriggered13(evt) {
177 sensorTriggered(evt,i)
180 def sensorTriggered14(evt) {
182 sensorTriggered(evt,i)
185 def sensorTriggered15(evt) {
187 sensorTriggered(evt,i)
190 def sensorTriggered16(evt) {
192 sensorTriggered(evt,i)
195 def sensorTriggered17(evt) {
197 sensorTriggered(evt,i)
200 def sensorTriggered18(evt) {
202 sensorTriggered(evt,i)
205 def sensorTriggered19(evt) {
207 sensorTriggered(evt,i)
210 def sensorTriggered20(evt) {
212 sensorTriggered(evt,i)
215 def sensorTriggered21(evt) {
217 sensorTriggered(evt,i)
220 def sensorTriggered22(evt) {
222 sensorTriggered(evt,i)
225 def sensorTriggered23(evt) {
227 sensorTriggered(evt,i)
230 def sensorTriggered24(evt) {
232 sensorTriggered(evt,i)
235 def sensorTriggered25(evt) {
237 sensorTriggered(evt,i)
240 def sensorTriggered26(evt) {
242 sensorTriggered(evt,i)
245 def sensorTriggered27(evt) {
247 sensorTriggered(evt,i)
250 def sensorTriggered28(evt) {
252 sensorTriggered(evt,i)
256 def sensorTriggered29(evt) {
258 sensorTriggered(evt,i)
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
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)
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}"
287 log.debug ("about to call takeAction() for ${theSensor[i]}")
294 log.debug ("about to call takeAction() for ${theSensor[i]}")
300 log.debug ("about to call takeAction() for ${theSensor[i]}")
306 log.debug ("about to call takeAction() for ${theSensor[i]}")
312 log.debug ("about to call takeAction() for ${theSensor[i]}")
318 log.debug ("about to call takeAction() for ${theSensor[i]}")
324 log.debug ("about to call takeAction() for ${theSensor[i]}")
330 log.debug ("about to call takeAction() for ${theSensor[i]}")
336 log.debug ("about to call takeAction() for ${theSensor[i]}")
342 log.debug ("about to call takeAction() for ${theSensor[i]}")
349 log.debug ("about to call takeAction() for ${theSensor[i]}")
355 log.debug ("about to call takeAction() for ${theSensor[i]}")
361 log.debug ("about to call takeAction() for ${theSensor[i]}")
367 log.debug ("about to call takeAction() for ${theSensor[i]}")
373 log.debug ("about to call takeAction() for ${theSensor[i]}")
380 log.debug ("about to call takeAction() for ${theSensor[i]}")
386 log.debug ("about to call takeAction() for ${theSensor[i]}")
393 log.debug ("about to call takeAction() for ${theSensor[i]}")
399 log.debug ("about to call takeAction() for ${theSensor[i]}")
405 log.debug ("about to call takeAction() for ${theSensor[i]}")
411 log.debug ("about to call takeAction() for ${theSensor[i]}")
418 log.debug ("about to call takeAction() for ${theSensor[i]}")
424 log.debug ("about to call takeAction() for ${theSensor[i]}")
430 log.debug ("about to call takeAction() for ${theSensor[i]}")
437 log.debug ("about to call takeAction() for ${theSensor[i]}")
443 log.debug ("about to call takeAction() for ${theSensor[i]}")
449 log.debug ("about to call takeAction() for ${theSensor[i]}")
455 log.debug ("about to call takeAction() for ${theSensor[i]}")
462 log.debug ("about to call takeAction() for ${theSensor[i]}")
468 log.debug ("about to call takeAction() for ${theSensor[i]}")
473 def takeAction(indice=0) {
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
481 def closed = "closed"
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
496 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { // Notify by voice only if the powerSwitch is on
497 theVoice.setLevel(30)
501 if ((tstats) && (openMinutesCount > max_open_time_in_min)) {
506 msg = "thermostats are now turned off after ${max_open_time_in_min} minutes"
507 send("WindowDoorOpen>${msg}")
509 if ((!tstats) && (state.count[indice] > maxNotif)) {
510 // stop the repeated notifications if there is no thermostats provided and we've reached maxNotif
512 takeActionMethod= "takeAction${indice}"
513 unschedule("${takeActionMethod}")
514 msg = "maximum notifications ($maxNotif) reached for ${theSensor[indice]}, unscheduled $takeActionMethod"
518 takeActionMethod= "takeAction${indice}"
519 msg = "contact still open at ${theSensor[indice]}, about to reschedule $takeActionMethod"
521 //runIn(freq, "${takeActionMethod}", [overwrite: false])
522 //} else if (contactState.value == "closed") {
523 } else if (contactState == closed) {
524 restore_tstats_mode()
526 takeActionMethod= "takeAction${indice}"
527 unschedule("${takeActionMethod}")
528 msg = "contact closed at ${theSensor[indice]}, unscheduled $takeActionMethod"
533 def clearStatus(indice=0) {
534 state?.status[indice] = " "
535 state?.count[indice] = 0
539 private void save_tstats_mode() {
541 if ((!tstats) || (state.lastThermostatMode)) { // If state already saved, then keep it
545 //it.poll() // to get the latest value at thermostat
546 state.lastThermostatMode = state.lastThermostatMode + "${it.currentThermostatMode}" + ","
548 log.debug "save_tstats_mode>state.lastThermostatMode= $state.lastThermostatMode"
553 private void restore_tstats_mode() {
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") {
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
575 if (state.lastThermostatMode) {
576 def lastThermostatMode = state.lastThermostatMode.toString().split(',')
580 def lastSavedMode = lastThermostatMode[i].trim()
582 log.debug "restore_tstats_mode>about to set ${it}, back to saved thermostatMode=${lastSavedMode}"
583 if (lastSavedMode == 'cool') {
585 } else if (lastSavedMode.contains('heat')) {
587 } else if (lastSavedMode == 'auto') {
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
602 state.lastThermostatMode = ""
608 if (sendPushMessage != "No") {
609 log.debug("sending push message")
614 log.debug("sending text message")