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[i], "contact.closed", "sensorTriggered${i}")
97 subscribe(theSensor[i], "contact.open", "sensorTriggered${i}")
98 state?.status[i] = " "
101 if (i>=MAX_CONTACT) {
108 def sensorTriggered0(evt) {
110 sensorTriggered(evt,i)
113 def sensorTriggered1(evt) {
115 sensorTriggered(evt,i)
118 def sensorTriggered2(evt) {
120 sensorTriggered(evt,i)
123 def sensorTriggered3(evt) {
125 sensorTriggered(evt,i)
128 def sensorTriggered4(evt) {
130 sensorTriggered(evt,i)
133 def sensorTriggered5(evt) {
135 sensorTriggered(evt,i)
138 def sensorTriggered6(evt) {
140 sensorTriggered(evt,i)
143 def sensorTriggered7(evt) {
145 sensorTriggered(evt,i)
148 def sensorTriggered8(evt) {
150 sensorTriggered(evt,i)
153 def sensorTriggered9(evt) {
155 sensorTriggered(evt,i)
158 def sensorTriggered10(evt) {
160 sensorTriggered(evt,i)
163 def sensorTriggered11(evt) {
165 sensorTriggered(evt,i)
168 def motionEvtHandler12(evt) {
170 sensorTriggered(evt,i)
173 def sensorTriggered13(evt) {
175 sensorTriggered(evt,i)
178 def sensorTriggered14(evt) {
180 sensorTriggered(evt,i)
183 def sensorTriggered15(evt) {
185 sensorTriggered(evt,i)
188 def sensorTriggered16(evt) {
190 sensorTriggered(evt,i)
193 def sensorTriggered17(evt) {
195 sensorTriggered(evt,i)
198 def sensorTriggered18(evt) {
200 sensorTriggered(evt,i)
203 def sensorTriggered19(evt) {
205 sensorTriggered(evt,i)
208 def sensorTriggered20(evt) {
210 sensorTriggered(evt,i)
213 def sensorTriggered21(evt) {
215 sensorTriggered(evt,i)
218 def sensorTriggered22(evt) {
220 sensorTriggered(evt,i)
223 def sensorTriggered23(evt) {
225 sensorTriggered(evt,i)
228 def sensorTriggered24(evt) {
230 sensorTriggered(evt,i)
233 def sensorTriggered25(evt) {
235 sensorTriggered(evt,i)
238 def sensorTriggered26(evt) {
240 sensorTriggered(evt,i)
243 def sensorTriggered27(evt) {
245 sensorTriggered(evt,i)
248 def sensorTriggered28(evt) {
250 sensorTriggered(evt,i)
254 def sensorTriggered29(evt) {
256 sensorTriggered(evt,i)
260 def sensorTriggered(evt, indice=0) {
261 def delay = (frequency) ?: 1
262 def freq = delay * 60
263 def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
265 if (evt.value == "closed") {
266 restore_tstats_mode()
267 def msg = "your ${theSensor[indice]} is now closed"
268 send("WindowOrDoorOpen>${msg}")
269 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { // Notify by voice only if the powerSwitch is on
270 theVoice.setLevel(30)
274 } else if ((evt.value == "open") && (state?.status[indice] != "scheduled")) {
275 def takeActionMethod= "takeAction${indice}"
276 state?.status[indice] = "scheduled"
277 runIn(freq, "${takeActionMethod}",[overwrite: false])
278 log.debug "${theSensor[indice]} is now open and will be checked every ${delay} minute(s) by ${takeActionMethod}"
285 log.debug ("about to call takeAction() for ${theSensor[i]}")
292 log.debug ("about to call takeAction() for ${theSensor[i]}")
298 log.debug ("about to call takeAction() for ${theSensor[i]}")
304 log.debug ("about to call takeAction() for ${theSensor[i]}")
310 log.debug ("about to call takeAction() for ${theSensor[i]}")
316 log.debug ("about to call takeAction() for ${theSensor[i]}")
322 log.debug ("about to call takeAction() for ${theSensor[i]}")
328 log.debug ("about to call takeAction() for ${theSensor[i]}")
334 log.debug ("about to call takeAction() for ${theSensor[i]}")
340 log.debug ("about to call takeAction() for ${theSensor[i]}")
347 log.debug ("about to call takeAction() for ${theSensor[i]}")
353 log.debug ("about to call takeAction() for ${theSensor[i]}")
359 log.debug ("about to call takeAction() for ${theSensor[i]}")
365 log.debug ("about to call takeAction() for ${theSensor[i]}")
371 log.debug ("about to call takeAction() for ${theSensor[i]}")
378 log.debug ("about to call takeAction() for ${theSensor[i]}")
384 log.debug ("about to call takeAction() for ${theSensor[i]}")
391 log.debug ("about to call takeAction() for ${theSensor[i]}")
397 log.debug ("about to call takeAction() for ${theSensor[i]}")
403 log.debug ("about to call takeAction() for ${theSensor[i]}")
409 log.debug ("about to call takeAction() for ${theSensor[i]}")
416 log.debug ("about to call takeAction() for ${theSensor[i]}")
422 log.debug ("about to call takeAction() for ${theSensor[i]}")
428 log.debug ("about to call takeAction() for ${theSensor[i]}")
435 log.debug ("about to call takeAction() for ${theSensor[i]}")
441 log.debug ("about to call takeAction() for ${theSensor[i]}")
447 log.debug ("about to call takeAction() for ${theSensor[i]}")
453 log.debug ("about to call takeAction() for ${theSensor[i]}")
460 log.debug ("about to call takeAction() for ${theSensor[i]}")
466 log.debug ("about to call takeAction() for ${theSensor[i]}")
471 def takeAction(indice=0) {
473 def delay = (frequency) ?: 1
474 def freq = delay * 60
475 def maxNotif = (givenMaxNotif) ?: 5
476 def max_open_time_in_min = maxOpenTime ?: 5 // By default, 5 min. is the max open time
479 def closed = "closed"
481 def contactState = theSensor[indice].currentState("contact")
482 log.trace "takeAction>${theSensor[indice]}'s contact status = ${contactState.value}, state.status=${state.status[indice]}, indice=$indice"
483 //if ((state?.status[indice] == "scheduled") && (contactState.value == "open")) {
484 if ((state?.status[indice] == "scheduled") && (contactState == open)) {
485 state.count[indice] = state.count[indice] + 1
486 log.debug "${theSensor[indice]} was open too long, sending message (count=${state.count[indice]})"
487 def openMinutesCount = state.count[indice] * delay
488 msg = "your ${theSensor[indice]} has been open for more than ${openMinutesCount} minute(s)!"
489 send("WindowOrDoorOpen>${msg}")
490 if ((masterSwitch) && (masterSwitch?.currentSwitch=="on")) {
491 log.debug "master switch ${masterSwitch} is now off"
492 masterSwitch.off() // set the master switch to off as there is an open contact
494 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { // Notify by voice only if the powerSwitch is on
495 theVoice.setLevel(30)
499 if ((tstats) && (openMinutesCount > max_open_time_in_min)) {
504 msg = "thermostats are now turned off after ${max_open_time_in_min} minutes"
505 send("WindowDoorOpen>${msg}")
507 if ((!tstats) && (state.count[indice] > maxNotif)) {
508 // stop the repeated notifications if there is no thermostats provided and we've reached maxNotif
510 takeActionMethod= "takeAction${indice}"
511 unschedule("${takeActionMethod}")
512 msg = "maximum notifications ($maxNotif) reached for ${theSensor[indice]}, unscheduled $takeActionMethod"
516 takeActionMethod= "takeAction${indice}"
517 msg = "contact still open at ${theSensor[indice]}, about to reschedule $takeActionMethod"
519 //runIn(freq, "${takeActionMethod}", [overwrite: false])
520 //} else if (contactState.value == "closed") {
521 } else if (contactState == closed) {
522 restore_tstats_mode()
524 takeActionMethod= "takeAction${indice}"
525 unschedule("${takeActionMethod}")
526 msg = "contact closed at ${theSensor[indice]}, unscheduled $takeActionMethod"
531 def clearStatus(indice=0) {
532 state?.status[indice] = " "
533 state?.count[indice] = 0
537 private void save_tstats_mode() {
539 if ((!tstats) || (state.lastThermostatMode)) { // If state already saved, then keep it
543 //it.poll() // to get the latest value at thermostat
544 state.lastThermostatMode = state.lastThermostatMode + "${it.currentThermostatMode}" + ","
546 log.debug "save_tstats_mode>state.lastThermostatMode= $state.lastThermostatMode"
551 private void restore_tstats_mode() {
555 log.debug "restore_tstats_mode>checking if all contacts are closed..."
556 for (int j = 0;(j < MAX_CONTACT); j++) {
557 if (!theSensor[j]) continue
558 def contactState = theSensor[j].currentState("contact")
559 log.trace "restore_tstats_mode>For ${theSensor[j]}, Contact's status = ${contactState.value}, indice=$j"
560 if (contactState.value == "open") {
564 if ((masterSwitch) && (masterSwitch?.currentSwitch=="off")) {
565 log.debug "master switch ${masterSwitch} is back on"
566 masterSwitch.on() // set the master switch to on as there is no more any open contacts
573 if (state.lastThermostatMode) {
574 def lastThermostatMode = state.lastThermostatMode.toString().split(',')
578 def lastSavedMode = lastThermostatMode[i].trim()
580 log.debug "restore_tstats_mode>about to set ${it}, back to saved thermostatMode=${lastSavedMode}"
581 if (lastSavedMode == 'cool') {
583 } else if (lastSavedMode.contains('heat')) {
585 } else if (lastSavedMode == 'auto') {
590 msg = "thermostat ${it}'s mode is now set back to ${lastSavedMode}"
591 send("WindowOrDoorOpen>${theSensor} closed, ${msg}")
592 if ((theVoice) && (powerSwitch?.currentSwitch == "on")) { // Notify by voice only if the powerSwitch is on
600 state.lastThermostatMode = ""
606 if (sendPushMessage != "No") {
607 log.debug("sending push message")
612 log.debug("sending text message")