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