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}"
86 state?.lastThermostatMode = null
87 // subscribe to all contact sensors to check for open/close events
90 state.lastThermostatMode = ""
94 subscribe(theSensor[i], "contact.closed", "sensorTriggered${i}")
95 subscribe(theSensor[i], "contact.open", "sensorTriggered${i}")
96 state?.status[i] = " "
106 def sensorTriggered0(evt) {
108 sensorTriggered(evt,i)
111 def sensorTriggered1(evt) {
113 sensorTriggered(evt,i)
116 def sensorTriggered2(evt) {
118 sensorTriggered(evt,i)
121 def sensorTriggered3(evt) {
123 sensorTriggered(evt,i)
126 def sensorTriggered4(evt) {
128 sensorTriggered(evt,i)
131 def sensorTriggered5(evt) {
133 sensorTriggered(evt,i)
136 def sensorTriggered6(evt) {
138 sensorTriggered(evt,i)
141 def sensorTriggered7(evt) {
143 sensorTriggered(evt,i)
146 def sensorTriggered8(evt) {
148 sensorTriggered(evt,i)
151 def sensorTriggered9(evt) {
153 sensorTriggered(evt,i)
156 def sensorTriggered10(evt) {
158 sensorTriggered(evt,i)
161 def sensorTriggered11(evt) {
163 sensorTriggered(evt,i)
166 def motionEvtHandler12(evt) {
168 sensorTriggered(evt,i)
171 def sensorTriggered13(evt) {
173 sensorTriggered(evt,i)
176 def sensorTriggered14(evt) {
178 sensorTriggered(evt,i)
181 def sensorTriggered15(evt) {
183 sensorTriggered(evt,i)
186 def sensorTriggered16(evt) {
188 sensorTriggered(evt,i)
191 def sensorTriggered17(evt) {
193 sensorTriggered(evt,i)
196 def sensorTriggered18(evt) {
198 sensorTriggered(evt,i)
201 def sensorTriggered19(evt) {
203 sensorTriggered(evt,i)
206 def sensorTriggered20(evt) {
208 sensorTriggered(evt,i)
211 def sensorTriggered21(evt) {
213 sensorTriggered(evt,i)
216 def sensorTriggered22(evt) {
218 sensorTriggered(evt,i)
221 def sensorTriggered23(evt) {
223 sensorTriggered(evt,i)
226 def sensorTriggered24(evt) {
228 sensorTriggered(evt,i)
231 def sensorTriggered25(evt) {
233 sensorTriggered(evt,i)
236 def sensorTriggered26(evt) {
238 sensorTriggered(evt,i)
241 def sensorTriggered27(evt) {
243 sensorTriggered(evt,i)
246 def sensorTriggered28(evt) {
248 sensorTriggered(evt,i)
252 def sensorTriggered29(evt) {
254 sensorTriggered(evt,i)
258 def sensorTriggered(evt, indice=0) {
259 def delay = (frequency) ?: 1
260 def freq = delay * 60
261 def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
263 if (evt.value == "closed") {
264 restore_tstats_mode()
265 def msg = "your ${theSensor[indice]} is now closed"
266 send("WindowOrDoorOpen>${msg}")
267 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { // Notify by voice only if the powerSwitch is on
268 theVoice.setLevel(30)
272 } else if ((evt.value == "open") && (state?.status[indice] != "scheduled")) {
273 def takeActionMethod= "takeAction${indice}"
274 runIn(freq, "${takeActionMethod}",[overwrite: false])
275 state?.status[indice] = "scheduled"
276 log.debug "${theSensor[indice]} is now open and will be checked every ${delay} minute(s) by ${takeActionMethod}"
283 log.debug ("about to call takeAction() for ${theSensor[i]}")
290 log.debug ("about to call takeAction() for ${theSensor[i]}")
296 log.debug ("about to call takeAction() for ${theSensor[i]}")
302 log.debug ("about to call takeAction() for ${theSensor[i]}")
308 log.debug ("about to call takeAction() for ${theSensor[i]}")
314 log.debug ("about to call takeAction() for ${theSensor[i]}")
320 log.debug ("about to call takeAction() for ${theSensor[i]}")
326 log.debug ("about to call takeAction() for ${theSensor[i]}")
332 log.debug ("about to call takeAction() for ${theSensor[i]}")
338 log.debug ("about to call takeAction() for ${theSensor[i]}")
345 log.debug ("about to call takeAction() for ${theSensor[i]}")
351 log.debug ("about to call takeAction() for ${theSensor[i]}")
357 log.debug ("about to call takeAction() for ${theSensor[i]}")
363 log.debug ("about to call takeAction() for ${theSensor[i]}")
369 log.debug ("about to call takeAction() for ${theSensor[i]}")
376 log.debug ("about to call takeAction() for ${theSensor[i]}")
382 log.debug ("about to call takeAction() for ${theSensor[i]}")
389 log.debug ("about to call takeAction() for ${theSensor[i]}")
395 log.debug ("about to call takeAction() for ${theSensor[i]}")
401 log.debug ("about to call takeAction() for ${theSensor[i]}")
407 log.debug ("about to call takeAction() for ${theSensor[i]}")
414 log.debug ("about to call takeAction() for ${theSensor[i]}")
420 log.debug ("about to call takeAction() for ${theSensor[i]}")
426 log.debug ("about to call takeAction() for ${theSensor[i]}")
433 log.debug ("about to call takeAction() for ${theSensor[i]}")
439 log.debug ("about to call takeAction() for ${theSensor[i]}")
445 log.debug ("about to call takeAction() for ${theSensor[i]}")
451 log.debug ("about to call takeAction() for ${theSensor[i]}")
458 log.debug ("about to call takeAction() for ${theSensor[i]}")
464 log.debug ("about to call takeAction() for ${theSensor[i]}")
469 def takeAction(indice=0) {
471 def delay = (frequency) ?: 1
472 def freq = delay * 60
473 def maxNotif = (givenMaxNotif) ?: 5
474 def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
477 def contactState = theSensor[indice].currentState("contact")
478 log.trace "takeAction>${theSensor[indice]}'s contact status = ${contactState.value}, state.status=${state.status[indice]}, indice=$indice"
479 if ((state?.status[indice] == "scheduled") && (contactState.value == "open")) {
480 state.count[indice] = state.count[indice] + 1
481 log.debug "${theSensor[indice]} was open too long, sending message (count=${state.count[indice]})"
482 def openMinutesCount = state.count[indice] * delay
483 msg = "your ${theSensor[indice]} has been open for more than ${openMinutesCount} minute(s)!"
484 send("WindowOrDoorOpen>${msg}")
485 if ((masterSwitch) && (masterSwitch?.currentSwitch=="on")) {
486 log.debug "master switch ${masterSwitch} is now off"
487 masterSwitch.off() // set the master switch to off as there is an open contact
489 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { // Notify by voice only if the powerSwitch is on
490 theVoice.setLevel(30)
494 if ((tstats) && (openMinutesCount > max_open_time_in_min)) {
499 msg = "thermostats are now turned off after ${max_open_time_in_min} minutes"
500 send("WindowDoorOpen>${msg}")
502 if ((!tstats) && (state.count[indice] > maxNotif)) {
503 // stop the repeated notifications if there is no thermostats provided and we've reached maxNotif
505 takeActionMethod= "takeAction${indice}"
506 unschedule("${takeActionMethod}")
507 msg = "maximum notifications ($maxNotif) reached for ${theSensor[indice]}, unscheduled $takeActionMethod"
511 takeActionMethod= "takeAction${indice}"
512 msg = "contact still open at ${theSensor[indice]}, about to reschedule $takeActionMethod"
514 runIn(freq, "${takeActionMethod}", [overwrite: false])
515 } else if (contactState.value == "closed") {
516 restore_tstats_mode()
518 takeActionMethod= "takeAction${indice}"
519 unschedule("${takeActionMethod}")
520 msg = "contact closed at ${theSensor[indice]}, unscheduled $takeActionMethod"
525 def clearStatus(indice=0) {
526 state?.status[indice] = " "
527 state?.count[indice] = 0
531 private void save_tstats_mode() {
533 if ((!tstats) || (state.lastThermostatMode)) { // If state already saved, then keep it
537 it.poll() // to get the latest value at thermostat
538 state.lastThermostatMode = state.lastThermostatMode + "${it.currentThermostatMode}" + ","
540 log.debug "save_tstats_mode>state.lastThermostatMode= $state.lastThermostatMode"
545 private void restore_tstats_mode() {
549 log.debug "restore_tstats_mode>checking if all contacts are closed..."
550 for (int j = 0;(j < MAX_CONTACT); j++) {
551 if (!theSensor[j]) continue
552 def contactState = theSensor[j].currentState("contact")
553 log.trace "restore_tstats_mode>For ${theSensor[j]}, Contact's status = ${contactState.value}, indice=$j"
554 if (contactState.value == "open") {
558 if ((masterSwitch) && (masterSwitch?.currentSwitch=="off")) {
559 log.debug "master switch ${masterSwitch} is back on"
560 masterSwitch.on() // set the master switch to on as there is no more any open contacts
567 if (state.lastThermostatMode) {
568 def lastThermostatMode = state.lastThermostatMode.toString().split(',')
572 def lastSavedMode = lastThermostatMode[i].trim()
574 log.debug "restore_tstats_mode>about to set ${it}, back to saved thermostatMode=${lastSavedMode}"
575 if (lastSavedMode == 'cool') {
577 } else if (lastSavedMode.contains('heat')) {
579 } else if (lastSavedMode == 'auto') {
584 msg = "thermostat ${it}'s mode is now set back to ${lastSavedMode}"
585 send("WindowOrDoorOpen>${theSensor} closed, ${msg}")
586 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { // Notify by voice only if the powerSwitch is on
594 state.lastThermostatMode = ""
600 if (sendPushMessage != "No") {
601 log.debug("sending push message")
606 log.debug("sending text message")
607 sendSms(phoneNumber, msg)