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