Update groveStreams.groovy
[smartapps.git] / third-party / SmartPresence.groovy
1 /**   Name: SmartPresence
2  *    Author: George Sudarkoff
3  *    Change mode based on presense and current mode.
4  */
5
6 definition(
7     name: "SmartPresence",
8     namespace: "sudarkoff.com",
9     author: "George Sudarkoff",
10     description: "Change mode (by invoking various \"Hello, Home\" phrases) based on presense and current mode.",
11     category: "Mode Magic",
12     iconUrl: "https://raw.githubusercontent.com/sudarkoff/smarttings/master/SmartPresence.png",
13     iconX2Url: "https://raw.githubusercontent.com/sudarkoff/smarttings/master/SmartPresence@2x.png"
14 )
15
16 preferences {
17     page(name: "selectPhrases")
18
19     page( name:"Settings", title:"Settings", uninstall:true, install:true ) {
20
21         section {
22                 input "starting", "time", title: "Starting", required: false
23                 input "ending", "time", title: "Ending", required: false
24         }
25
26         section("False alarm threshold (defaults to 10 min)") {
27             input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false
28         }
29
30         section("Zip code (for sunrise/sunset)") {
31             input "zip", "decimal", required: true
32         }
33
34         section("Notifications") {
35             input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
36         }
37
38         section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
39             input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
40                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
41             input "modes", "mode", title: "Only when mode is", multiple: true, required: false
42         }
43     }
44 }
45
46 def selectPhrases() {
47     def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.awayDay)
48     dynamicPage(name: "selectPhrases", title: "Configure", nextPage:"Settings", uninstall: true) {
49         section("All of these sensors") {
50             input "people", "capability.presenceSensor", title: "Monitor All of These Presences", required: true, multiple: true, refreshAfterSelection:true
51         }
52
53         def phrases = location.helloHome?.getPhrases()*.label
54         if (phrases) {
55             phrases.sort()
56             section("Run This Phrase When...") {
57                 log.trace phrases
58                 input "sunriseAway", "enum", title: "It's Sunrise and Everybody's Away", required: true, options: phrases,  refreshAfterSelection:true
59                 input "sunsetAway", "enum", title: "It's Sunset and Everybody's Away", required: true, options: phrases,  refreshAfterSelection:true
60                 input "sunriseHome", "enum", title: "It's Sunrise and Somebody's Home", required: true, options: phrases,  refreshAfterSelection:true
61                 input "sunsetHome", "enum", title: "It's Sunset and Somebody's Home", required: true, options: phrases,  refreshAfterSelection:true
62                 input "awayDay", "enum", title: "Last Person Leaves and It's Daytime", required: true, options: phrases,  refreshAfterSelection:true
63                 input "awayNight", "enum", title: "Last Person Leaves and It's Nighttime", required: true, options: phrases,  refreshAfterSelection:true
64                 input "homeDay", "enum", title: "Somebody's Back and It's Daytime", required: true, options: phrases,  refreshAfterSelection:true
65                 input "homeNight", "enum", title: "Somebody's Back and It's Nighttime", required: true, options: phrases,  refreshAfterSelection:true
66             }
67         }
68     }
69 }
70
71 def installed() {
72     init()
73     initialize()
74     //subscribe(app)
75 }
76
77 def updated() {
78     unsubscribe()
79     initialize()
80
81     init()
82 }
83
84 def init() {
85     subscribe(people, "presence", presence)
86
87     checkSun();
88 }
89
90 def uninstalled() {
91     unsubscribe()
92 }
93
94 def initialize() {
95     schedule("0 0/5 * 1/1 * ? *", checkSun)
96 }
97
98 def checkSun() {
99     // TODO: Use location information if zip is not provided
100     def zip     = settings.zip as String
101     def sunInfo = getSunriseAndSunset(zipCode: zip)
102     def current = now()
103
104     if(sunInfo.sunrise.time > current ||
105         sunInfo.sunset.time  < current) {
106         state.sunMode = "sunset"
107     }
108     else {
109         state.sunMode = "sunrise"
110     }
111
112     log.info("Sunset: ${sunInfo.sunset.time}")
113     log.info("Sunrise: ${sunInfo.sunrise.time}")
114     log.info("Current: ${current}")
115     log.info("sunMode: ${state.sunMode}")
116
117     if(current < sunInfo.sunrise.time) {
118         runIn(((sunInfo.sunrise.time - current) / 1000).toInteger(), setSunrise)
119     }
120
121     if(current < sunInfo.sunset.time) {
122         runIn(((sunInfo.sunset.time - current) / 1000).toInteger(), setSunset)
123     }
124 }
125
126 def setSunrise() {
127     state.sunMode = "sunrise";
128     changeSunMode(newMode)
129 }
130
131 def setSunset() {
132     state.sunMode = "sunset";
133     changeSunMode(newMode)
134 }
135
136
137 def changeSunMode(newMode) {
138     if(allOk) {
139         if(everyoneIsAway() && (state.sunMode = "sunrise")) {
140             log.info("Sunrise but nobody's home, switching to Away mode.")
141             def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
142             runIn(delay, "setAway")
143         }
144
145         if(everyoneIsAway() && (state.sunMode = "sunset")) {
146             log.info("Sunset and nobody's home, switching to Away mode")
147             def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
148             runIn(delay, "setAway")
149         }
150
151         else {
152             log.info("Somebody's home, switching to Home mode")
153             setHome()
154         }
155     }
156 }
157
158 def presence(evt) {
159     if(allOk) {
160         if(evt.value == "not present") {
161             log.debug("Checking if everyone is away")
162
163             if(everyoneIsAway()) {
164                 log.info("Everybody's gone, running Away sequence.")
165                 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
166                 runIn(delay, "setAway")
167             }
168         } else {
169             def lastTime = state[evt.deviceId]
170             if (lastTime == null || now() - lastTime >= 1 * 60000) {
171                 log.info("Somebody's back, running Home sequence")
172                 setHome()
173             }
174             state[evt.deviceId] = now()
175         }
176     }
177 }
178
179 def setAway() {
180     if(everyoneIsAway()) {
181         if(state.sunMode == "sunset") {
182             def message = "SmartMode says \"${awayNight}\"."
183             log.info(message)
184             send(message)
185             location.helloHome.execute(settings.awayNight)
186         } else if(state.sunMode == "sunrise") {
187           def message = "SmartMode says \"${awayDay}\"."
188           log.info(message)
189           send(message)
190           location.helloHome.execute(settings.awayDay)
191         } else {
192           log.debug("Mode is the same, not evaluating.")
193         }
194     } else {
195         log.info("Somebody returned home before we switched to '${newAwayMode}'")
196     }
197 }
198
199 def setHome() {
200     log.info("Setting Home Mode!")
201     if(anyoneIsHome()) {
202         if(state.sunMode == "sunset") {
203             def message = "SmartMode says \"${homeNight}\"."
204             log.info(message)
205             location.helloHome.execute(settings.homeNight)
206             send(message)
207             sendSms(phone1, message)
208         }
209
210         if(state.sunMode == "sunrise"){
211             def message = "SmartMode says \"${homeDay}\"."
212             log.info(message)
213             location.helloHome.execute(settings.homeDay)
214             send(message)
215             sendSms(phone1, message)
216         }
217     }
218 }
219
220 private everyoneIsAway() {
221     def result = true
222
223     if(people.findAll { it?.currentPresence == "present" }) {
224         result = false
225     }
226
227     log.debug("everyoneIsAway: ${result}")
228
229     return result
230 }
231
232 private anyoneIsHome() {
233     def result = false
234
235     if(people.findAll { it?.currentPresence == "present" }) {
236         result = true
237     }
238
239     log.debug("anyoneIsHome: ${result}")
240
241     return result
242 }
243
244 private send(msg) {
245     if(sendPushMessage != "No") {
246         log.debug("Sending push message")
247         sendPush(msg)
248     }
249
250     log.debug(msg)
251 }
252
253
254
255 private getAllOk() {
256     modeOk && daysOk && timeOk
257 }
258
259 private getModeOk() {
260     def result = !modes || modes.contains(location.mode)
261     log.trace "modeOk = $result"
262     result
263 }
264
265 private getDaysOk() {
266     def result = true
267     if (days) {
268         def df = new java.text.SimpleDateFormat("EEEE")
269         if (location.timeZone) {
270             df.setTimeZone(location.timeZone)
271         }
272         else {
273             df.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
274         }
275         def day = df.format(new Date())
276         result = days.contains(day)
277     }
278     log.trace "daysOk = $result"
279     result
280 }
281
282 private getTimeOk() {
283     def result = true
284     if (starting && ending) {
285         def currTime = now()
286         def start = timeToday(starting).time
287         def stop = timeToday(ending).time
288         result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
289     }
290     log.trace "timeOk = $result"
291     result
292 }
293
294 private hhmm(time, fmt = "h:mm a")
295 {
296     def t = timeToday(time, location.timeZone)
297     def f = new java.text.SimpleDateFormat(fmt)
298     f.setTimeZone(location.timeZone ?: timeZone(time))
299     f.format(t)
300 }
301
302 private getTimeIntervalLabel()
303 {
304     (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
305 }
306
307 private hideOptionsSection() {
308     (starting || ending || days || modes) ? false : true
309 }