Skip to content

Commit 73415f5

Browse files
committed
Merge pull request SmartThingsCommunity#150 from tslagle13/MSA-589-9
MSA-589: Routine Director
2 parents 30fdb92 + 39e7ddb commit 73415f5

File tree

1 file changed

+342
-0
lines changed

1 file changed

+342
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
/**
2+
* Rotuine Director
3+
*
4+
*
5+
* Changelog
6+
*
7+
* 2015-09-01
8+
* --Added Contact Book
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
12+
*
13+
* Copyright 2015 Tim Slagle
14+
*
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:
17+
*
18+
* http://www.apache.org/licenses/LICENSE-2.0
19+
*
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.
23+
*
24+
*/
25+
definition(
26+
name: "Routine Director",
27+
namespace: "tslagle13",
28+
author: "Tim Slagle",
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"
33+
)
34+
35+
preferences {
36+
page(name: "selectRoutines")
37+
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
41+
}
42+
43+
section("Zip code (for sunrise/sunset)") {
44+
input "zip", "text", required: true
45+
}
46+
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?"
50+
}
51+
section("Send Notifications?") {
52+
input("recipients", "contact", title: "Send notifications to") {
53+
input "phone", "phone", title: "Send an SMS to this number?"
54+
}
55+
}
56+
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
62+
}
63+
}
64+
}
65+
66+
def selectRoutines() {
67+
def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
68+
dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) {
69+
section("Who?") {
70+
input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true
71+
}
72+
73+
def routines = location.helloHome?.getPhrases()*.label
74+
if (routines) {
75+
routines.sort()
76+
section("Run This Routine When...") {
77+
log.trace routines
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
82+
}
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
86+
}*/
87+
}
88+
}
89+
}
90+
91+
def installed() {
92+
log.debug "Updated with settings: ${settings}"
93+
initialize()
94+
}
95+
96+
def updated() {
97+
log.debug "Updated with settings: ${settings}"
98+
unsubscribe()
99+
initialize()
100+
}
101+
102+
def initialize() {
103+
subscribe(people, "presence", presence)
104+
checkSun()
105+
subscribe(location, "sunrise", setSunrise)
106+
subscribe(location, "sunset", setSunset)
107+
state.homestate = null
108+
}
109+
110+
//check current sun state when installed.
111+
def checkSun() {
112+
def zip = settings.zip as String
113+
def sunInfo = getSunriseAndSunset(zipCode: zip)
114+
def current = now()
115+
116+
if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) {
117+
state.sunMode = "sunrise"
118+
runIn(60,"setSunrise")
119+
}
120+
else {
121+
state.sunMode = "sunset"
122+
runIn(60,"setSunset")
123+
}
124+
}
125+
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}"
131+
}
132+
133+
//change to sunset mode on sunset event
134+
def setSunset(evt) {
135+
state.sunMode = "sunset";
136+
changeSunMode(newMode)
137+
log.debug "Current sun mode is ${state.sunMode}"
138+
}
139+
140+
//change mode on sun event
141+
def changeSunMode(newMode) {
142+
if (allOk) {
143+
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
147+
setAway()
148+
}
149+
/*
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
153+
setAway()
154+
}*/
155+
else if (anyoneIsHome()) {
156+
log.info("Home is Occupied Setting New Home Mode")
157+
setHome()
158+
159+
160+
}
161+
}
162+
}
163+
164+
//presence change run logic based on presence state of home
165+
def presence(evt) {
166+
if (allOk) {
167+
if (evt.value == "not present") {
168+
log.debug("Checking if everyone is away")
169+
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")
174+
}
175+
}
176+
else {
177+
def lastTime = state[evt.deviceId]
178+
if (lastTime == null || now() - lastTime >= 1 * 60000) {
179+
log.info("Someone is home, running home sequence")
180+
setHome()
181+
}
182+
state[evt.deviceId] = now()
183+
184+
}
185+
}
186+
}
187+
188+
//if empty set home to one of the away modes
189+
def setAway() {
190+
if (everyoneIsAway()) {
191+
if (state.sunMode == "sunset") {
192+
def message = "Performing \"${awayNight}\" for you as requested."
193+
log.info(message)
194+
sendAway(message)
195+
location.helloHome.execute(settings.awayNight)
196+
state.homestate = "away"
197+
198+
}
199+
else if (state.sunMode == "sunrise") {
200+
def message = "Performing \"${awayDay}\" for you as requested."
201+
log.info(message)
202+
sendAway(message)
203+
location.helloHome.execute(settings.awayDay)
204+
state.homestate = "away"
205+
}
206+
else {
207+
log.debug("Mode is the same, not evaluating")
208+
}
209+
}
210+
}
211+
212+
//set home mode when house is occupied
213+
def setHome() {
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."
219+
log.info(message)
220+
sendHome(message)
221+
location.helloHome.execute(settings.homeNight)
222+
state.homestate = "homeNight"
223+
}
224+
}
225+
226+
if (state.sunMode == "sunrise") {
227+
if (state.homestate != "homeDay") {
228+
def message = "Performing \"${homeDay}\" for you as requested."
229+
log.info(message)
230+
sendHome(message)
231+
location.helloHome.execute(settings.homeDay)
232+
state.homestate = "homeDay"
233+
}
234+
}
235+
}
236+
}
237+
238+
private everyoneIsAway() {
239+
def result = true
240+
241+
if(people.findAll { it?.currentPresence == "present" }) {
242+
result = false
243+
}
244+
245+
log.debug("everyoneIsAway: ${result}")
246+
247+
return result
248+
}
249+
250+
private anyoneIsHome() {
251+
def result = false
252+
253+
if(people.findAll { it?.currentPresence == "present" }) {
254+
result = true
255+
}
256+
257+
log.debug("anyoneIsHome: ${result}")
258+
259+
return result
260+
}
261+
262+
def sendAway(msg) {
263+
if (sendPushMessage) {
264+
if (recipients) {
265+
sendNotificationToContacts(msg, recipients)
266+
}
267+
else {
268+
sendPush(msg)
269+
sendSms(phone, msg)
270+
}
271+
}
272+
273+
log.debug(msg)
274+
}
275+
276+
def sendHome(msg) {
277+
if (sendPushMessageHome) {
278+
if (recipients) {
279+
sendNotificationToContacts(msg, recipients)
280+
}
281+
else {
282+
sendPush(msg)
283+
sendSms(phone, msg)
284+
}
285+
}
286+
287+
log.debug(msg)
288+
}
289+
290+
private getAllOk() {
291+
modeOk && daysOk && timeOk
292+
}
293+
294+
private getModeOk() {
295+
def result = !modes || modes.contains(location.mode)
296+
log.trace "modeOk = $result"
297+
result
298+
}
299+
300+
private getDaysOk() {
301+
def result = true
302+
if (days) {
303+
def df = new java.text.SimpleDateFormat("EEEE")
304+
if (location.timeZone) {
305+
df.setTimeZone(location.timeZone)
306+
}
307+
else {
308+
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
309+
}
310+
def day = df.format(new Date())
311+
result = days.contains(day)
312+
}
313+
log.trace "daysOk = $result"
314+
result
315+
}
316+
317+
private getTimeOk() {
318+
def result = true
319+
if (starting && ending) {
320+
def currTime = now()
321+
def start = timeToday(starting, location?.timeZone).time
322+
def stop = timeToday(ending, location?.timeZone).time
323+
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
324+
}
325+
log.trace "timeOk = $result"
326+
result
327+
}
328+
329+
private hhmm(time, fmt = "h:mm a") {
330+
def t = timeToday(time, location.timeZone)
331+
def f = new java.text.SimpleDateFormat(fmt)
332+
f.setTimeZone(location.timeZone?:timeZone(time))
333+
f.format(t)
334+
}
335+
336+
private getTimeIntervalLabel() {
337+
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): ""
338+
}
339+
340+
private hideOptionsSection() {
341+
(starting || ending || days || modes) ? false: true
342+
}

0 commit comments

Comments
 (0)