1 /** Name: SmartPresence
2 * Author: George Sudarkoff
3 * Change mode based on presense and current mode.
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"
17 page(name: "selectPhrases")
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
24 section("Zip code (for sunrise/sunset)") {
25 input "zip", "decimal", required: true
28 section("Notifications") {
29 input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
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
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
47 def phrases = location.helloHome?.getPhrases()*.label
50 section("Run This Phrase When...") {
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
79 subscribe(people, "presence", presence)
89 schedule("0 0/5 * 1/1 * ? *", checkSun)
93 // TODO: Use location information if zip is not provided
94 def zip = settings.zip as String
95 def sunInfo = getSunriseAndSunset(zipCode: zip)
98 if(sunInfo.sunrise.time > current ||
99 sunInfo.sunset.time < current) {
100 state.sunMode = "sunset"
103 state.sunMode = "sunrise"
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}")
111 if(current < sunInfo.sunrise.time) {
112 runIn(((sunInfo.sunrise.time - current) / 1000).toInteger(), setSunrise)
115 if(current < sunInfo.sunset.time) {
116 runIn(((sunInfo.sunset.time - current) / 1000).toInteger(), setSunset)
121 state.sunMode = "sunrise";
122 changeSunMode(newMode)
126 state.sunMode = "sunset";
127 changeSunMode(newMode)
131 def changeSunMode(newMode) {
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")
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")
146 log.info("Somebody's home, switching to Home mode")
154 if(evt.value == "not present") {
155 log.debug("Checking if everyone is away")
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")
163 def lastTime = state[evt.deviceId]
164 if (lastTime == null || now() - lastTime >= 1 * 60000) {
165 log.info("Somebody's back, running Home sequence")
168 state[evt.deviceId] = now()
174 if(everyoneIsAway()) {
175 if(state.sunMode == "sunset") {
176 def message = "SmartMode says \"${awayNight}\"."
179 location.helloHome.execute(settings.awayNight)
180 } else if(state.sunMode == "sunrise") {
181 def message = "SmartMode says \"${awayDay}\"."
184 location.helloHome.execute(settings.awayDay)
186 log.debug("Mode is the same, not evaluating.")
189 log.info("Somebody returned home before we switched to '${newAwayMode}'")
194 log.info("Setting Home Mode!")
196 if(state.sunMode == "sunset") {
197 def message = "SmartMode says \"${homeNight}\"."
199 location.helloHome.execute(settings.homeNight)
201 sendSms(phone1, message)
204 if(state.sunMode == "sunrise"){
205 def message = "SmartMode says \"${homeDay}\"."
207 location.helloHome.execute(settings.homeDay)
209 sendSms(phone1, message)
214 private everyoneIsAway() {
217 if(people.findAll { it?.currentPresence == "present" }) {
221 log.debug("everyoneIsAway: ${result}")
226 private anyoneIsHome() {
229 if(people.findAll { it?.currentPresence == "present" }) {
233 log.debug("anyoneIsHome: ${result}")
239 if(sendPushMessage != "No") {
240 log.debug("Sending push message")
250 modeOk && daysOk && timeOk
253 private getModeOk() {
254 def result = !modes || modes.contains(location.mode)
255 log.trace "modeOk = $result"
259 private getDaysOk() {
262 def df = new java.text.SimpleDateFormat("EEEE")
263 if (location.timeZone) {
264 df.setTimeZone(location.timeZone)
267 df.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
269 def day = df.format(new Date())
270 result = days.contains(day)
272 log.trace "daysOk = $result"
276 private getTimeOk() {
278 if (starting && ending) {
280 def start = timeToday(starting).time
281 def stop = timeToday(ending).time
282 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
284 log.trace "timeOk = $result"
288 private hhmm(time, fmt = "h:mm a")
290 def t = timeToday(time, location.timeZone)
291 def f = new java.text.SimpleDateFormat(fmt)
292 f.setTimeZone(location.timeZone ?: timeZone(time))
296 private getTimeIntervalLabel()
298 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
301 private hideOptionsSection() {
302 (starting || ending || days || modes) ? false : true