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
66 def selectRoutines() {
67 def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
68 dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) {
70 input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true
73 def routines = location.helloHome?.getPhrases()*.label
76 section("Run This Routine When...") {
78 input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: routines, submitOnChange: true
79 input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: routines, submitOnChange: true
80 input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: routines, submitOnChange: true
81 input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: routines, submitOnChange: true
83 /* 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.
84 input "homeModeDay", "mode", title: "Select Mode Used for 'Home Day'", required: true
85 input "homeModeNight", "mode", title: "Select Mode Used for 'Home Night'", required: true
92 log.debug "Updated with settings: ${settings}"
97 log.debug "Updated with settings: ${settings}"
103 subscribe(people, "presence", presence)
105 subscribe(location, "sunrise", setSunrise)
106 subscribe(location, "sunset", setSunset)
107 state.homestate = null
110 //check current sun state when installed.
112 def zip = settings.zip as String
113 def sunInfo = getSunriseAndSunset(zipCode: zip)
116 if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) {
117 state.sunMode = "sunrise"
118 runIn(60,"setSunrise")
121 state.sunMode = "sunset"
122 runIn(60,"setSunset")
126 //change to sunrise mode on sunrise event
127 def setSunrise(evt) {
128 state.sunMode = "sunrise";
129 changeSunMode(newMode);
130 log.debug "Current sun mode is ${state.sunMode}"
133 //change to sunset mode on sunset event
135 state.sunMode = "sunset";
136 changeSunMode(newMode)
137 log.debug "Current sun mode is ${state.sunMode}"
140 //change mode on sun event
141 def changeSunMode(newMode) {
144 if (everyoneIsAway()) /*&& (state.sunMode == "sunrise")*/ {
145 log.info("Home is Empty Setting New Away Mode")
146 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
150 else if (everyoneIsAway() && (state.sunMode == "sunset")) {
151 log.info("Home is Empty Setting New Away Mode")
152 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
155 else if (anyoneIsHome()) {
156 log.info("Home is Occupied Setting New Home Mode")
164 //presence change run logic based on presence state of home
167 if (evt.value == "not present") {
168 log.debug("Checking if everyone is away")
170 if (everyoneIsAway()) {
171 log.info("Nobody is home, running away sequence")
172 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
173 runIn(delay, "setAway")
177 def lastTime = state[evt.deviceId]
178 if (lastTime == null || now() - lastTime >= 1 * 60000) {
179 log.info("Someone is home, running home sequence")
182 state[evt.deviceId] = now()
188 //if empty set home to one of the away modes
190 if (everyoneIsAway()) {
191 if (state.sunMode == "sunset") {
192 def message = "Performing \"${awayNight}\" for you as requested."
195 location.helloHome.execute(settings.awayNight)
196 state.homestate = "away"
199 else if (state.sunMode == "sunrise") {
200 def message = "Performing \"${awayDay}\" for you as requested."
203 location.helloHome.execute(settings.awayDay)
204 state.homestate = "away"
207 log.debug("Mode is the same, not evaluating")
212 //set home mode when house is occupied
214 log.info("Setting Home Mode!!")
215 if (anyoneIsHome()) {
216 if (state.sunMode == "sunset") {
217 if (state.homestate != "homeNight") {
218 def message = "Performing \"${homeNight}\" for you as requested."
221 location.helloHome.execute(settings.homeNight)
222 state.homestate = "homeNight"
226 if (state.sunMode == "sunrise") {
227 if (state.homestate != "homeDay") {
228 def message = "Performing \"${homeDay}\" for you as requested."
231 location.helloHome.execute(settings.homeDay)
232 state.homestate = "homeDay"
238 private everyoneIsAway() {
241 if(people.findAll { it?.currentPresence == "present" }) {
245 log.debug("everyoneIsAway: ${result}")
250 private anyoneIsHome() {
253 if(people.findAll { it?.currentPresence == "present" }) {
257 log.debug("anyoneIsHome: ${result}")
263 if (sendPushMessage) {
265 sendNotificationToContacts(msg, recipients)
279 if (sendPushMessageHome) {
281 sendNotificationToContacts(msg, recipients)
295 modeOk && daysOk && timeOk
298 private getModeOk() {
299 def result = !modes || modes.contains(location.mode)
300 log.trace "modeOk = $result"
304 private getDaysOk() {
307 def df = new java.text.SimpleDateFormat("EEEE")
308 if (location.timeZone) {
309 df.setTimeZone(location.timeZone)
312 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
314 def day = df.format(new Date())
315 result = days.contains(day)
317 log.trace "daysOk = $result"
321 private getTimeOk() {
323 if (starting && ending) {
325 def start = timeToday(starting, location?.timeZone).time
326 def stop = timeToday(ending, location?.timeZone).time
327 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
329 log.trace "timeOk = $result"
333 private hhmm(time, fmt = "h:mm a") {
334 def t = timeToday(time, location.timeZone)
335 def f = new java.text.SimpleDateFormat(fmt)
336 f.setTimeZone(location.timeZone?:timeZone(time))
340 private getTimeIntervalLabel() {
341 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): ""
344 private hideOptionsSection() {
345 (starting || ending || days || modes) ? false: true