Update SmartPresence.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: "timeIntervalInput", title: "Only during a certain time") {
37         section {
38             input "starting", "time", title: "Starting", required: false
39             input "ending", "time", title: "Ending", required: false
40         }
41     }
42
43     page(name: "selectRoutines")
44
45     page(name: "Settings", title: "Settings", uninstall: true, install: true) {
46         section("False alarm threshold (defaults to 10 min)") {
47             input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false
48         }
49
50         section("Zip code (for sunrise/sunset)") {
51             input "zip", "text", required: true
52         }
53
54         section("Notifications") {
55             input "sendPushMessage", "bool", title: "Send notifications when house is empty?"
56             input "sendPushMessageHome", "bool", title: "Send notifications when home is occupied?"
57         }
58         section("Send Notifications?") {
59             input("recipients", "contact", title: "Send notifications to") {
60                 input "phone", "phone", title: "Send an SMS to this number?", required:false
61             }
62         }
63
64         section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
65             label title: "Assign a name", required: false
66             input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
67                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
68             input "modes", "mode", title: "Only when mode is", multiple: true, required: false
69         }
70
71         section("Change to this mode") {
72                 input "newMode", "mode", title: "Mode?"
73         }  
74     }
75 }
76
77 def selectRoutines() {
78     def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
79     dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) {
80         section("Who?") {
81             input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true
82         }
83
84         def routines = location.helloHome?.getPhrases()*.label
85         if (routines) {
86             routines.sort()
87             section("Run This Routine When...") {
88                     log.trace routines
89                     input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: routines, submitOnChange: true
90                     input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: routines, submitOnChange: true
91                     input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: routines, submitOnChange: true
92                     input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: routines, submitOnChange: true
93                 }
94                 /*    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. 
95         input "homeModeDay", "mode", title: "Select Mode Used for 'Home Day'", required: true
96         input "homeModeNight", "mode", title: "Select Mode Used for 'Home Night'", required: true
97     }*/
98         }
99     }
100 }
101
102 def installed() {
103         log.debug "Updated with settings: ${settings}"
104     initialize()
105 }
106
107 def updated() {
108         log.debug "Updated with settings: ${settings}"
109     unsubscribe()
110     initialize()
111 }
112
113 def initialize() {
114     subscribe(people, "presence", presence)
115     checkSun()
116     subscribe(location, "sunrise", setSunrise)
117     subscribe(location, "sunset", setSunset)
118     state.homestate = null
119 }
120
121 //check current sun state when installed.
122 def checkSun() {
123     def zip = settings.zip as String
124     def sunInfo = getSunriseAndSunset(zipCode: zip)
125     def current = now()
126
127     if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) {
128         state.sunMode = "sunrise"
129         runIn(60,"setSunrise")
130     }
131     else {
132         state.sunMode = "sunset"
133         runIn(60,"setSunset")
134     }
135 }
136
137 //change to sunrise mode on sunrise event
138 def setSunrise(evt) {
139     state.sunMode = "sunrise";
140     changeSunMode(newMode);
141     log.debug "Current sun mode is ${state.sunMode}"
142 }
143
144 //change to sunset mode on sunset event
145 def setSunset(evt) {
146     state.sunMode = "sunset";
147     changeSunMode(newMode)
148     log.debug "Current sun mode is ${state.sunMode}"
149 }
150
151 //change mode on sun event
152 def changeSunMode(newMode) {
153     if (allOk) {
154
155         if (everyoneIsAway()) /*&& (state.sunMode == "sunrise")*/ {
156             log.info("Home is Empty  Setting New Away Mode")
157             def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
158             setAway()
159         }
160 /*
161         else if (everyoneIsAway() && (state.sunMode == "sunset")) {
162             log.info("Home is Empty  Setting New Away Mode")
163             def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
164             setAway()
165         }*/
166         else if (anyoneIsHome()) {
167             log.info("Home is Occupied Setting New Home Mode")
168             setHome()
169
170
171         }
172     }
173 }
174
175 //presence change run logic based on presence state of home
176 def presence(evt) {
177     if (allOk) {
178         if (evt.value == "not present") {
179             log.debug("Checking if everyone is away")
180
181             if (everyoneIsAway()) {
182                 log.info("Nobody is home, running away sequence")
183                 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
184                 runIn(delay, "setAway")
185             }
186         }
187         else {
188             def lastTime = state[evt.deviceId]
189             if (lastTime == null || now() - lastTime >= 1 * 60000) {
190                 log.info("Someone is home, running home sequence")
191                 setHome()
192             }
193             state[evt.deviceId] = now()
194
195         }
196     }
197 }
198
199 //if empty set home to one of the away modes
200 def setAway() {
201     if (everyoneIsAway()) {
202         if (state.sunMode == "sunset") {
203             def message = "Performing \"${awayNight}\" for you as requested."
204             log.info(message)
205             sendAway(message)
206             location.helloHome.execute(settings.awayNight)
207             state.homestate = "away"
208
209         }
210         else if (state.sunMode == "sunrise") {
211             def message = "Performing \"${awayDay}\" for you as requested."
212             log.info(message)
213             sendAway(message)
214             location.helloHome.execute(settings.awayDay)
215             state.homestate = "away"
216         }
217         else {
218             log.debug("Mode is the same, not evaluating")
219         }
220     }
221 }
222
223 //set home mode when house is occupied
224 def setHome() {
225     log.info("Setting Home Mode!!")
226     if (anyoneIsHome()) {
227         if (state.sunMode == "sunset") {
228             if (state.homestate != "homeNight") {
229                 def message = "Performing \"${homeNight}\" for you as requested."
230                 log.info(message)
231                 sendHome(message)
232                 location.helloHome.execute(settings.homeNight)
233                 state.homestate = "homeNight"
234             }
235         }
236
237         if (state.sunMode == "sunrise") {
238             if (state.homestate != "homeDay") {
239                 def message = "Performing \"${homeDay}\" for you as requested."
240                 log.info(message)
241                 sendHome(message)
242                 location.helloHome.execute(settings.homeDay)
243                 state.homestate = "homeDay"
244             }
245         }
246     }
247 }
248
249 private everyoneIsAway() {
250   def result = true
251
252   if(people.findAll { it?.currentPresence == "present" }) {
253     result = false
254   }
255
256   log.debug("everyoneIsAway: ${result}")
257
258   return result
259 }
260
261 private anyoneIsHome() {
262   def result = false
263
264   if(people.findAll { it?.currentPresence == "present" }) {
265     result = true
266   }
267
268   log.debug("anyoneIsHome: ${result}")
269
270   return result
271 }
272
273 def sendAway(msg) {
274     if (sendPushMessage) {
275         if (recipients) {
276                 sendNotificationToContacts(msg, recipients)
277         }
278         else {
279                 sendPush(msg)
280                 if(phone){
281                         sendSms(phone, msg)
282                 }       
283         }    
284     }
285
286     log.debug(msg)
287 }
288
289 def sendHome(msg) {
290     if (sendPushMessageHome) {
291         if (recipients) {
292                 sendNotificationToContacts(msg, recipients)
293         }
294         else {
295                 sendPush(msg)
296                 if(phone){
297                         sendSms(phone, msg)
298                 }
299         }    
300     }
301
302     log.debug(msg)
303 }
304
305 private getAllOk() {
306     modeOk && daysOk && timeOk
307 }
308
309 private getModeOk() {
310     def result = !modes || modes.contains(location.mode)
311     log.trace "modeOk = $result"
312     result
313 }
314
315 private getDaysOk() {
316     def result = true
317     if (days) {
318         def df = new java.text.SimpleDateFormat("EEEE")
319         if (location.timeZone) {
320             df.setTimeZone(location.timeZone)
321         }
322         else {
323             df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
324         }
325         def day = df.format(new Date())
326         result = days.contains(day)
327     }
328     log.trace "daysOk = $result"
329     result
330 }
331
332 private getTimeOk() {
333     def result = true
334     if (starting && ending) {
335         def currTime = now()
336         def start = timeToday(starting, location?.timeZone).time
337         def stop = timeToday(ending, location?.timeZone).time
338         result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
339     }
340     log.trace "timeOk = $result"
341     result
342 }
343
344 private hhmm(time, fmt = "h:mm a") {
345     def t = timeToday(time, location.timeZone)
346     def f = new java.text.SimpleDateFormat(fmt)
347     f.setTimeZone(location.timeZone?:timeZone(time))
348     f.format(t)
349 }
350
351 private getTimeIntervalLabel() {
352     (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): ""
353 }
354
355 private hideOptionsSection() {
356     (starting || ending || days || modes) ? false: true
357 }