Update auto-humidity-vent.groovy
[smartapps.git] / official / routine-director.groovy
1 /**
2  *  Rotuine Director
3  *
4  *
5  *  Changelog
6  *
7  *  2015-09-01
8  *     --Added Contact Book
9  *     --Removed references to phrases and replaced with routines
10  *     --Added bool logic to inputs instead of enum for "yes" "no" options
11  *     --Fixed halting error with code installation
12  *
13  *  Copyright 2015 Tim Slagle
14  *
15  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
16  *  in compliance with the License. You may obtain a copy of the License at:
17  *
18  *      http://www.apache.org/licenses/LICENSE-2.0
19  *
20  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
21  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
22  *  for the specific language governing permissions and limitations under the License.
23  *
24  */
25 definition(
26     name: "Routine Director",
27     namespace: "tslagle13",
28     author: "Tim Slagle",
29     description: "Monitor a set of presence sensors and activate routines based on whether your home is empty or occupied.  Each presence status change will check against the current 'sun state' to run routines based on occupancy and whether the sun is up or down.",
30     category: "Convenience",
31     iconUrl: "http://icons.iconarchive.com/icons/icons8/ios7/512/Very-Basic-Home-Filled-icon.png",
32     iconX2Url: "http://icons.iconarchive.com/icons/icons8/ios7/512/Very-Basic-Home-Filled-icon.png"
33 )
34
35 preferences {
36     page(name: "selectRoutines")
37
38     page(name: "Settings", title: "Settings", uninstall: true, install: true) {
39         section("False alarm threshold (defaults to 10 min)") {
40             input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false
41         }
42
43         section("Zip code (for sunrise/sunset)") {
44             input "zip", "text", required: true
45         }
46
47         section("Notifications") {
48             input "sendPushMessage", "bool", title: "Send notifications when house is empty?"
49             input "sendPushMessageHome", "bool", title: "Send notifications when home is occupied?"
50         }
51         section("Send Notifications?") {
52             input("recipients", "contact", title: "Send notifications to") {
53                 input "phone", "phone", title: "Send an SMS to this number?", required:false
54             }
55         }
56
57         section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
58             label title: "Assign a name", required: false
59             input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
60                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
61             input "modes", "mode", title: "Only when mode is", multiple: true, required: false
62         }
63     }
64 }
65
66 def selectRoutines() {
67     def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
68     dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) {
69         section("Who?") {
70             input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true
71         }
72
73         def routines = location.helloHome?.getPhrases()*.label
74         if (routines) {
75             routines.sort()
76             section("Run This Routine When...") {
77                     log.trace routines
78                     input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: routines, submitOnChange: true
79                     input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: routines, submitOnChange: true
80                     input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: routines, submitOnChange: true
81                     input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: routines, submitOnChange: true
82                 }
83                 /*    section("Select modes used for each condition.") { This allows the director to know which rotuine has already been ran so it does not run again if someone else comes home. 
84         input "homeModeDay", "mode", title: "Select Mode Used for 'Home Day'", required: true
85         input "homeModeNight", "mode", title: "Select Mode Used for 'Home Night'", required: true
86     }*/
87         }
88     }
89 }
90
91 def installed() {
92         log.debug "Updated with settings: ${settings}"
93     initialize()
94 }
95
96 def updated() {
97         log.debug "Updated with settings: ${settings}"
98     unsubscribe()
99     initialize()
100 }
101
102 def initialize() {
103     subscribe(people, "presence", presence)
104     checkSun()
105     subscribe(location, "sunrise", setSunrise)
106     subscribe(location, "sunset", setSunset)
107     state.homestate = null
108 }
109
110 //check current sun state when installed.
111 def checkSun() {
112     def zip = settings.zip as String
113     def sunInfo = getSunriseAndSunset(zipCode: zip)
114     def current = now()
115
116     if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) {
117         state.sunMode = "sunrise"
118         runIn(60,"setSunrise")
119     }
120     else {
121         state.sunMode = "sunset"
122         runIn(60,"setSunset")
123     }
124 }
125
126 //change to sunrise mode on sunrise event
127 def setSunrise(evt) {
128     state.sunMode = "sunrise";
129     changeSunMode(newMode);
130     log.debug "Current sun mode is ${state.sunMode}"
131 }
132
133 //change to sunset mode on sunset event
134 def setSunset(evt) {
135     state.sunMode = "sunset";
136     changeSunMode(newMode)
137     log.debug "Current sun mode is ${state.sunMode}"
138 }
139
140 //change mode on sun event
141 def changeSunMode(newMode) {
142     if (allOk) {
143
144         if (everyoneIsAway()) /*&& (state.sunMode == "sunrise")*/ {
145             log.info("Home is Empty  Setting New Away Mode")
146             def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
147             setAway()
148         }
149 /*
150         else if (everyoneIsAway() && (state.sunMode == "sunset")) {
151             log.info("Home is Empty  Setting New Away Mode")
152             def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
153             setAway()
154         }*/
155         else if (anyoneIsHome()) {
156             log.info("Home is Occupied Setting New Home Mode")
157             setHome()
158
159
160         }
161     }
162 }
163
164 //presence change run logic based on presence state of home
165 def presence(evt) {
166     if (allOk) {
167         if (evt.value == "not present") {
168             log.debug("Checking if everyone is away")
169
170             if (everyoneIsAway()) {
171                 log.info("Nobody is home, running away sequence")
172                 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
173                 runIn(delay, "setAway")
174             }
175         }
176         else {
177             def lastTime = state[evt.deviceId]
178             if (lastTime == null || now() - lastTime >= 1 * 60000) {
179                 log.info("Someone is home, running home sequence")
180                 setHome()
181             }
182             state[evt.deviceId] = now()
183
184         }
185     }
186 }
187
188 //if empty set home to one of the away modes
189 def setAway() {
190     if (everyoneIsAway()) {
191         if (state.sunMode == "sunset") {
192             def message = "Performing \"${awayNight}\" for you as requested."
193             log.info(message)
194             sendAway(message)
195             location.helloHome.execute(settings.awayNight)
196             state.homestate = "away"
197
198         }
199         else if (state.sunMode == "sunrise") {
200             def message = "Performing \"${awayDay}\" for you as requested."
201             log.info(message)
202             sendAway(message)
203             location.helloHome.execute(settings.awayDay)
204             state.homestate = "away"
205         }
206         else {
207             log.debug("Mode is the same, not evaluating")
208         }
209     }
210 }
211
212 //set home mode when house is occupied
213 def setHome() {
214     log.info("Setting Home Mode!!")
215     if (anyoneIsHome()) {
216         if (state.sunMode == "sunset") {
217             if (state.homestate != "homeNight") {
218                 def message = "Performing \"${homeNight}\" for you as requested."
219                 log.info(message)
220                 sendHome(message)
221                 location.helloHome.execute(settings.homeNight)
222                 state.homestate = "homeNight"
223             }
224         }
225
226         if (state.sunMode == "sunrise") {
227             if (state.homestate != "homeDay") {
228                 def message = "Performing \"${homeDay}\" for you as requested."
229                 log.info(message)
230                 sendHome(message)
231                 location.helloHome.execute(settings.homeDay)
232                 state.homestate = "homeDay"
233             }
234         }
235     }
236 }
237
238 private everyoneIsAway() {
239   def result = true
240
241   if(people.findAll { it?.currentPresence == "present" }) {
242     result = false
243   }
244
245   log.debug("everyoneIsAway: ${result}")
246
247   return result
248 }
249
250 private anyoneIsHome() {
251   def result = false
252
253   if(people.findAll { it?.currentPresence == "present" }) {
254     result = true
255   }
256
257   log.debug("anyoneIsHome: ${result}")
258
259   return result
260 }
261
262 def sendAway(msg) {
263     if (sendPushMessage) {
264         if (recipients) {
265                 sendNotificationToContacts(msg, recipients)
266         }
267         else {
268                 sendPush(msg)
269                 if(phone){
270                         sendSms(phone, msg)
271                 }       
272         }    
273     }
274
275     log.debug(msg)
276 }
277
278 def sendHome(msg) {
279     if (sendPushMessageHome) {
280         if (recipients) {
281                 sendNotificationToContacts(msg, recipients)
282         }
283         else {
284                 sendPush(msg)
285                 if(phone){
286                         sendSms(phone, msg)
287                 }
288         }    
289     }
290
291     log.debug(msg)
292 }
293
294 private getAllOk() {
295     modeOk && daysOk && timeOk
296 }
297
298 private getModeOk() {
299     def result = !modes || modes.contains(location.mode)
300     log.trace "modeOk = $result"
301     result
302 }
303
304 private getDaysOk() {
305     def result = true
306     if (days) {
307         def df = new java.text.SimpleDateFormat("EEEE")
308         if (location.timeZone) {
309             df.setTimeZone(location.timeZone)
310         }
311         else {
312             df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
313         }
314         def day = df.format(new Date())
315         result = days.contains(day)
316     }
317     log.trace "daysOk = $result"
318     result
319 }
320
321 private getTimeOk() {
322     def result = true
323     if (starting && ending) {
324         def currTime = now()
325         def start = timeToday(starting, location?.timeZone).time
326         def stop = timeToday(ending, location?.timeZone).time
327         result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
328     }
329     log.trace "timeOk = $result"
330     result
331 }
332
333 private hhmm(time, fmt = "h:mm a") {
334     def t = timeToday(time, location.timeZone)
335     def f = new java.text.SimpleDateFormat(fmt)
336     f.setTimeZone(location.timeZone?:timeZone(time))
337     f.format(t)
338 }
339
340 private getTimeIntervalLabel() {
341     (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): ""
342 }
343
344 private hideOptionsSection() {
345     (starting || ending || days || modes) ? false: true
346 }