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: "timeIntervalInput", title: "Only during a certain time") {
38 input "starting", "time", title: "Starting", required: false
39 input "ending", "time", title: "Ending", required: false
43 page(name: "selectRoutines")
45 page(name: "Settings", title: "Settings", uninstall: true, install: true) {
46 section("False alarm threshold (defaults to 10 min)") {
47 input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false
50 section("Zip code (for sunrise/sunset)") {
51 input "zip", "text", required: true
54 section("Notifications") {
55 input "sendPushMessage", "bool", title: "Send notifications when house is empty?"
56 input "sendPushMessageHome", "bool", title: "Send notifications when home is occupied?"
58 section("Send Notifications?") {
59 input("recipients", "contact", title: "Send notifications to") {
60 input "phone", "phone", title: "Send an SMS to this number?", required:false
64 section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
65 label title: "Assign a name", required: false
66 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
67 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
68 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
71 section("Change to this mode") {
72 input "newMode", "mode", title: "Mode?"
77 def selectRoutines() {
78 def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
79 dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) {
81 input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true
84 def routines = location.helloHome?.getPhrases()*.label
87 section("Run This Routine When...") {
89 input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: routines, submitOnChange: true
90 input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: routines, submitOnChange: true
91 input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: routines, submitOnChange: true
92 input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: routines, submitOnChange: true
94 /* 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.
95 input "homeModeDay", "mode", title: "Select Mode Used for 'Home Day'", required: true
96 input "homeModeNight", "mode", title: "Select Mode Used for 'Home Night'", required: true
103 log.debug "Updated with settings: ${settings}"
108 log.debug "Updated with settings: ${settings}"
114 subscribe(people, "presence", presence)
116 subscribe(location, "sunrise", setSunrise)
117 subscribe(location, "sunset", setSunset)
118 state.homestate = null
121 //check current sun state when installed.
123 def zip = settings.zip as String
124 def sunInfo = getSunriseAndSunset(zipCode: zip)
127 if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) {
128 state.sunMode = "sunrise"
129 runIn(60,"setSunrise")
132 state.sunMode = "sunset"
133 runIn(60,"setSunset")
137 //change to sunrise mode on sunrise event
138 def setSunrise(evt) {
139 state.sunMode = "sunrise";
140 changeSunMode(newMode);
141 log.debug "Current sun mode is ${state.sunMode}"
144 //change to sunset mode on sunset event
146 state.sunMode = "sunset";
147 changeSunMode(newMode)
148 log.debug "Current sun mode is ${state.sunMode}"
151 //change mode on sun event
152 def changeSunMode(newMode) {
155 if (everyoneIsAway()) /*&& (state.sunMode == "sunrise")*/ {
156 log.info("Home is Empty Setting New Away Mode")
157 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
161 else if (everyoneIsAway() && (state.sunMode == "sunset")) {
162 log.info("Home is Empty Setting New Away Mode")
163 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
166 else if (anyoneIsHome()) {
167 log.info("Home is Occupied Setting New Home Mode")
175 //presence change run logic based on presence state of home
178 if (evt.value == "not present") {
179 log.debug("Checking if everyone is away")
181 if (everyoneIsAway()) {
182 log.info("Nobody is home, running away sequence")
183 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
184 runIn(delay, "setAway")
188 def lastTime = state[evt.deviceId]
189 if (lastTime == null || now() - lastTime >= 1 * 60000) {
190 log.info("Someone is home, running home sequence")
193 state[evt.deviceId] = now()
199 //if empty set home to one of the away modes
201 if (everyoneIsAway()) {
202 if (state.sunMode == "sunset") {
203 def message = "Performing \"${awayNight}\" for you as requested."
206 location.helloHome.execute(settings.awayNight)
207 state.homestate = "away"
210 else if (state.sunMode == "sunrise") {
211 def message = "Performing \"${awayDay}\" for you as requested."
214 location.helloHome.execute(settings.awayDay)
215 state.homestate = "away"
218 log.debug("Mode is the same, not evaluating")
223 //set home mode when house is occupied
225 log.info("Setting Home Mode!!")
226 if (anyoneIsHome()) {
227 if (state.sunMode == "sunset") {
228 if (state.homestate != "homeNight") {
229 def message = "Performing \"${homeNight}\" for you as requested."
232 location.helloHome.execute(settings.homeNight)
233 state.homestate = "homeNight"
237 if (state.sunMode == "sunrise") {
238 if (state.homestate != "homeDay") {
239 def message = "Performing \"${homeDay}\" for you as requested."
242 location.helloHome.execute(settings.homeDay)
243 state.homestate = "homeDay"
249 private everyoneIsAway() {
252 if(people.findAll { it?.currentPresence == "present" }) {
256 log.debug("everyoneIsAway: ${result}")
261 private anyoneIsHome() {
264 if(people.findAll { it?.currentPresence == "present" }) {
268 log.debug("anyoneIsHome: ${result}")
274 if (sendPushMessage) {
276 sendNotificationToContacts(msg, recipients)
290 if (sendPushMessageHome) {
292 sendNotificationToContacts(msg, recipients)
306 modeOk && daysOk && timeOk
309 private getModeOk() {
310 def result = !modes || modes.contains(location.mode)
311 log.trace "modeOk = $result"
315 private getDaysOk() {
318 def df = new java.text.SimpleDateFormat("EEEE")
319 if (location.timeZone) {
320 df.setTimeZone(location.timeZone)
323 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
325 def day = df.format(new Date())
326 result = days.contains(day)
328 log.trace "daysOk = $result"
332 private getTimeOk() {
334 if (starting && ending) {
336 def start = timeToday(starting, location?.timeZone).time
337 def stop = timeToday(ending, location?.timeZone).time
338 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
340 log.trace "timeOk = $result"
344 private hhmm(time, fmt = "h:mm a") {
345 def t = timeToday(time, location.timeZone)
346 def f = new java.text.SimpleDateFormat(fmt)
347 f.setTimeZone(location.timeZone?:timeZone(time))
351 private getTimeIntervalLabel() {
352 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): ""
355 private hideOptionsSection() {
356 (starting || ending || days || modes) ? false: true