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
13 * Copyright 2015 Tim Slagle
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:
18 * http://www.apache.org/licenses/LICENSE-2.0
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.
26 name: "Routine Director",
27 namespace: "tslagle13",
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"
36 page(name: "selectRoutines")
38 page(name: "Settings", title: "Settings", uninstall: true, install: true) {
39 section("False alarm threshold (defaults to 10 min)") {
40 input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false
43 section("Zip code (for sunrise/sunset)") {
44 input "zip", "text", required: true
47 section("Notifications") {
48 input "sendPushMessage", "bool", title: "Send notifications when house is empty?"
49 input "sendPushMessageHome", "bool", title: "Send notifications when home is occupied?"
51 section("Send Notifications?") {
52 input("recipients", "contact", title: "Send notifications to") {
53 input "phone", "phone", title: "Send an SMS to this number?", required:false
57 section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
58 label title: "Assign a name", required: false
59 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
60 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
61 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
65 input "starting", "time", title: "Starting", required: false
66 input "ending", "time", title: "Ending", required: false
71 def selectRoutines() {
72 def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
73 dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) {
75 input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true
78 def routines = location.helloHome?.getPhrases()*.label
81 section("Run This Routine When...") {
83 input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: routines, submitOnChange: true
84 input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: routines, submitOnChange: true
85 input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: routines, submitOnChange: true
86 input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: routines, submitOnChange: true
88 /* 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.
89 input "homeModeDay", "mode", title: "Select Mode Used for 'Home Day'", required: true
90 input "homeModeNight", "mode", title: "Select Mode Used for 'Home Night'", required: true
97 log.debug "Updated with settings: ${settings}"
102 log.debug "Updated with settings: ${settings}"
108 subscribe(people, "presence", presence)
110 subscribe(location, "sunrise", setSunrise)
111 subscribe(location, "sunset", setSunset)
112 state.homestate = null
115 //check current sun state when installed.
117 def zip = settings.zip as String
118 def sunInfo = getSunriseAndSunset(zipCode: zip)
121 if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) {
122 state.sunMode = "sunrise"
123 runIn(60,"setSunrise")
126 state.sunMode = "sunset"
127 runIn(60,"setSunset")
131 //change to sunrise mode on sunrise event
132 def setSunrise(evt) {
133 state.sunMode = "sunrise";
134 changeSunMode(newMode);
135 log.debug "Current sun mode is ${state.sunMode}"
138 //change to sunset mode on sunset event
140 state.sunMode = "sunset";
141 changeSunMode(newMode)
142 log.debug "Current sun mode is ${state.sunMode}"
145 //change mode on sun event
146 def changeSunMode(newMode) {
149 if (everyoneIsAway()) /*&& (state.sunMode == "sunrise")*/ {
150 log.info("Home is Empty Setting New Away Mode")
151 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
155 else if (everyoneIsAway() && (state.sunMode == "sunset")) {
156 log.info("Home is Empty Setting New Away Mode")
157 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
160 else if (anyoneIsHome()) {
161 log.info("Home is Occupied Setting New Home Mode")
169 //presence change run logic based on presence state of home
172 if (evt.value == "not present") {
173 log.debug("Checking if everyone is away")
175 if (everyoneIsAway()) {
176 log.info("Nobody is home, running away sequence")
177 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
178 runIn(delay, "setAway")
182 def lastTime = state[evt.deviceId]
183 if (lastTime == null || now() - lastTime >= 1 * 60000) {
184 log.info("Someone is home, running home sequence")
187 state[evt.deviceId] = now()
193 //if empty set home to one of the away modes
195 if (everyoneIsAway()) {
196 if (state.sunMode == "sunset") {
197 def message = "Performing \"${awayNight}\" for you as requested."
200 location.helloHome.execute(settings.awayNight)
201 state.homestate = "away"
204 else if (state.sunMode == "sunrise") {
205 def message = "Performing \"${awayDay}\" for you as requested."
208 location.helloHome.execute(settings.awayDay)
209 state.homestate = "away"
212 log.debug("Mode is the same, not evaluating")
217 //set home mode when house is occupied
219 log.info("Setting Home Mode!!")
220 if (anyoneIsHome()) {
221 if (state.sunMode == "sunset") {
222 if (state.homestate != "homeNight") {
223 def message = "Performing \"${homeNight}\" for you as requested."
226 location.helloHome.execute(settings.homeNight)
227 state.homestate = "homeNight"
231 if (state.sunMode == "sunrise") {
232 if (state.homestate != "homeDay") {
233 def message = "Performing \"${homeDay}\" for you as requested."
236 location.helloHome.execute(settings.homeDay)
237 state.homestate = "homeDay"
243 private everyoneIsAway() {
246 if(people.findAll { it?.currentPresence == "present" }) {
250 log.debug("everyoneIsAway: ${result}")
255 private anyoneIsHome() {
258 if(people.findAll { it?.currentPresence == "present" }) {
262 log.debug("anyoneIsHome: ${result}")
268 if (sendPushMessage) {
270 sendNotificationToContacts(msg, recipients)
284 if (sendPushMessageHome) {
286 sendNotificationToContacts(msg, recipients)
300 modeOk && daysOk && timeOk
303 private getModeOk() {
304 def result = !modes || modes.contains(location.mode)
305 log.trace "modeOk = $result"
309 private getDaysOk() {
312 def df = new java.text.SimpleDateFormat("EEEE")
313 if (location.timeZone) {
314 df.setTimeZone(location.timeZone)
317 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
319 def day = df.format(new Date())
320 result = days.contains(day)
322 log.trace "daysOk = $result"
326 private getTimeOk() {
328 if (starting && ending) {
330 def start = timeToday(starting, location?.timeZone).time
331 def stop = timeToday(ending, location?.timeZone).time
332 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
334 log.trace "timeOk = $result"
338 private hhmm(time, fmt = "h:mm a") {
339 def t = timeToday(time, location.timeZone)
340 def f = new java.text.SimpleDateFormat(fmt)
341 f.setTimeZone(location.timeZone?:timeZone(time))
345 private getTimeIntervalLabel() {
346 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): ""
349 private hideOptionsSection() {
350 (starting || ending || days || modes) ? false: true