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