Update double-tap.groovy
[smartapps.git] / official / laundry-monitor.groovy
1 /**
2  *  Copyright 2015 SmartThings
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  *  in compliance with the License. You may obtain a copy of the License at:
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
10  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
11  *  for the specific language governing permissions and limitations under the License.
12  *
13  *  Laundry Monitor
14  *
15  *  Author: SmartThings
16  *
17  *  Sends a message and (optionally) turns on or blinks a light to indicate that laundry is done.
18  *
19  *  Date: 2013-02-21
20  */
21
22 definition(
23         name: "Laundry Monitor",
24         namespace: "smartthings",
25         author: "SmartThings",
26         description: "Sends a message and (optionally) turns on or blinks a light to indicate that laundry is done.",
27         category: "Convenience",
28         iconUrl: "https://s3.amazonaws.com/smartapp-icons/FunAndSocial/App-HotTubTuner.png",
29         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/FunAndSocial/App-HotTubTuner%402x.png"
30 )
31
32 preferences {
33         section("Tell me when this washer/dryer has stopped..."){
34                 input "sensor1", "capability.accelerationSensor"
35         }
36         section("Via this number (optional, sends push notification if not specified)"){
37         input("recipients", "contact", title: "Send notifications to") {
38             input "phone", "phone", title: "Phone Number", required: false
39         }
40         }
41         section("And by turning on these lights (optional)") {
42                 input "switches", "capability.switch", required: false, multiple: true, title: "Which lights?"
43                 input "lightMode", "enum", options: ["Flash Lights", "Turn On Lights"], required: false, defaultValue: "Turn On Lights", title: "Action?"
44         }
45         section("Time thresholds (in minutes, optional)"){
46                 input "cycleTime", "decimal", title: "Minimum cycle time", required: false, defaultValue: 10
47                 input "fillTime", "decimal", title: "Time to fill tub", required: false, defaultValue: 5
48         }
49 }
50
51 def installed()
52 {
53         initialize()
54 }
55
56 def updated()
57 {
58         unsubscribe()
59         initialize()
60 }
61
62 def initialize() {
63         subscribe(sensor1, "acceleration.active", accelerationActiveHandler)
64         subscribe(sensor1, "acceleration.inactive", accelerationInactiveHandler)
65 }
66
67 def accelerationActiveHandler(evt) {
68         log.trace "vibration"
69         if (!state.isRunning) {
70                 log.info "Arming detector"
71                 state.isRunning = true
72                 state.startedAt = now()
73         }
74         state.stoppedAt = null
75 }
76
77 def accelerationInactiveHandler(evt) {
78         log.trace "no vibration, isRunning: $state.isRunning"
79         if (state.isRunning) {
80                 log.debug "startedAt: ${state.startedAt}, stoppedAt: ${state.stoppedAt}"
81                 if (!state.stoppedAt) {
82                         state.stoppedAt = now()
83             def delay = Math.floor(fillTime * 60).toInteger()
84                         runIn(delay, checkRunning, [overwrite: false])
85                 }
86         }
87 }
88
89 def checkRunning() {
90         log.trace "checkRunning()"
91         if (state.isRunning) {
92                 def fillTimeMsec = fillTime ? fillTime * 60000 : 300000
93                 def sensorStates = sensor1.statesSince("acceleration", new Date((now() - fillTimeMsec) as Long))
94
95                 if (!sensorStates.find{it.value == "active"}) {
96
97                         def cycleTimeMsec = cycleTime ? cycleTime * 60000 : 600000
98                         def duration = now() - state.startedAt
99                         if (duration - fillTimeMsec > cycleTimeMsec) {
100                                 log.debug "Sending notification"
101
102                                 def msg = "${sensor1.displayName} is finished"
103                                 log.info msg
104
105                 if (location.contactBookEnabled) {
106                     sendNotificationToContacts(msg, recipients)
107                 }
108                 else {
109
110                     if (phone) {
111                         sendSms phone, msg
112                     } else {
113                         sendPush msg
114                     }
115
116                 }
117
118                                 if (switches) {
119                                         if (lightMode?.equals("Turn On Lights")) {
120                                                 switches.on()
121                                         } else {
122                                                 flashLights()
123                                         }
124                                 }
125                         } else {
126                                 log.debug "Not sending notification because machine wasn't running long enough $duration versus $cycleTimeMsec msec"
127                         }
128                         state.isRunning = false
129                         log.info "Disarming detector"
130                 } else {
131                         log.debug "skipping notification because vibration detected again"
132                 }
133         }
134         else {
135                 log.debug "machine no longer running"
136         }
137 }
138
139 private flashLights() {
140         def doFlash = true
141         def onFor = onFor ?: 1000
142         def offFor = offFor ?: 1000
143         def numFlashes = numFlashes ?: 3
144
145         log.debug "LAST ACTIVATED IS: ${state.lastActivated}"
146         if (state.lastActivated) {
147                 def elapsed = now() - state.lastActivated
148                 def sequenceTime = (numFlashes + 1) * (onFor + offFor)
149                 doFlash = elapsed > sequenceTime
150                 log.debug "DO FLASH: $doFlash, ELAPSED: $elapsed, LAST ACTIVATED: ${state.lastActivated}"
151         }
152
153         if (doFlash) {
154                 log.debug "FLASHING $numFlashes times"
155                 state.lastActivated = now()
156                 log.debug "LAST ACTIVATED SET TO: ${state.lastActivated}"
157                 def initialActionOn = switches.collect{it.currentSwitch != "on"}
158                 def delay = 1L
159                 numFlashes.times {
160                         log.trace "Switch on after  $delay msec"
161                         switches.eachWithIndex {s, i ->
162                                 if (initialActionOn[i]) {
163                                         s.on(delay: delay)
164                                 }
165                                 else {
166                                         s.off(delay:delay)
167                                 }
168                         }
169                         delay += onFor
170                         log.trace "Switch off after $delay msec"
171                         switches.eachWithIndex {s, i ->
172                                 if (initialActionOn[i]) {
173                                         s.off(delay: delay)
174                                 }
175                                 else {
176                                         s.on(delay:delay)
177                                 }
178                         }
179                         delay += offFor
180                 }
181         }
182 }