From 5edff0df53cef71bceebe612f5f3e6cb4e806556 Mon Sep 17 00:00:00 2001 From: Tim Slagle Date: Mon, 28 Sep 2015 18:48:18 -0700 Subject: [PATCH 1/7] MSA-588: Update to HHFD --- .../hello-home-phrase-director.groovy | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy b/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy index de520ec..50fe8b6 100644 --- a/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy +++ b/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy @@ -1,4 +1,4 @@ - /** +/** * Magic Home * * Copyright 2014 Tim Slagle @@ -85,6 +85,7 @@ } def initialize() { + state.clear() subscribe(people, "presence", presence) runIn(60, checkSun) subscribe(location, "sunrise", setSunrise) @@ -198,7 +199,7 @@ //set home mode when house is occupied def setHome() { - + sendOutOfDateNotification() log.info("Setting Home Mode!!") if(anyoneIsHome()) { if(state.sunMode == "sunset"){ @@ -319,3 +320,14 @@ private hideOptionsSection() { (starting || ending || days || modes) ? false : true } + + def sendOutOfDateNotification(evt){ + if(!state.lastTime){ + state.lastTime = (new Date() + 31).getTime() + sendNotification("Your version of Hello, Home Phrase Director is currently out of date. Please look for the new version of Hello, Home Phrase Director now called 'Routine Director' in the marketplace.") + } + else if (((new Date()).getTime()) >= state.lastTime){ + sendNotification("Your version of Hello, Home Phrase Director is currently out of date. Please look for the new version of Hello, Home Phrase Director now called 'Routine Director' in the marketplace.") + state.lastTime = (new Date() + 31).getTime() + } + } \ No newline at end of file From 09b91014da4a0efe06b1adceecae3c316f51971c Mon Sep 17 00:00:00 2001 From: Tim Slagle Date: Mon, 28 Sep 2015 18:50:25 -0700 Subject: [PATCH 2/7] Modifying 'Hello, Home Phrase Director' --- .../hello-home-phrase-director.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy b/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy index 50fe8b6..9b704f1 100644 --- a/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy +++ b/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy @@ -85,7 +85,6 @@ } def initialize() { - state.clear() subscribe(people, "presence", presence) runIn(60, checkSun) subscribe(location, "sunrise", setSunrise) From 45f08df026adb5d2bf50f10436938a394c3557ec Mon Sep 17 00:00:00 2001 From: Tim Slagle Date: Mon, 28 Sep 2015 18:51:48 -0700 Subject: [PATCH 3/7] Modifying 'Hello, Home Phrase Director' --- .../hello-home-phrase-director.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy b/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy index 9b704f1..cb78696 100644 --- a/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy +++ b/smartapps/tslagle13/hello-home-phrase-director.src/hello-home-phrase-director.groovy @@ -320,7 +320,7 @@ (starting || ending || days || modes) ? false : true } - def sendOutOfDateNotification(evt){ + def sendOutOfDateNotification(){ if(!state.lastTime){ state.lastTime = (new Date() + 31).getTime() sendNotification("Your version of Hello, Home Phrase Director is currently out of date. Please look for the new version of Hello, Home Phrase Director now called 'Routine Director' in the marketplace.") From 39e7ddb781ae932c41e924a57f3a4f7947241658 Mon Sep 17 00:00:00 2001 From: Tim Slagle Date: Mon, 28 Sep 2015 18:54:23 -0700 Subject: [PATCH 4/7] MSA-589: Initial Commit for Routine Director. --- .../routine-director.groovy | 342 ++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 smartapps/tslagle13/routine-director.src/routine-director.groovy diff --git a/smartapps/tslagle13/routine-director.src/routine-director.groovy b/smartapps/tslagle13/routine-director.src/routine-director.groovy new file mode 100644 index 0000000..86263b9 --- /dev/null +++ b/smartapps/tslagle13/routine-director.src/routine-director.groovy @@ -0,0 +1,342 @@ +/** + * Rotuine Director + * + * + * Changelog + * + * 2015-09-01 + * --Added Contact Book + * --Removed references to phrases and replaced with routines + * --Added bool logic to inputs instead of enum for "yes" "no" options + * --Fixed halting error with code installation + * + * Copyright 2015 Tim Slagle + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + * + */ +definition( + name: "Routine Director", + namespace: "tslagle13", + author: "Tim Slagle", + 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.", + category: "Convenience", + iconUrl: "http://icons.iconarchive.com/icons/icons8/ios7/512/Very-Basic-Home-Filled-icon.png", + iconX2Url: "http://icons.iconarchive.com/icons/icons8/ios7/512/Very-Basic-Home-Filled-icon.png" +) + +preferences { + page(name: "selectRoutines") + + page(name: "Settings", title: "Settings", uninstall: true, install: true) { + section("False alarm threshold (defaults to 10 min)") { + input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false + } + + section("Zip code (for sunrise/sunset)") { + input "zip", "text", required: true + } + + section("Notifications") { + input "sendPushMessage", "bool", title: "Send notifications when house is empty?" + input "sendPushMessageHome", "bool", title: "Send notifications when home is occupied?" + } + section("Send Notifications?") { + input("recipients", "contact", title: "Send notifications to") { + input "phone", "phone", title: "Send an SMS to this number?" + } + } + + section(title: "More options", hidden: hideOptionsSection(), hideable: true) { + label title: "Assign a name", required: false + input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false, + options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + input "modes", "mode", title: "Only when mode is", multiple: true, required: false + } + } +} + +def selectRoutines() { + def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight) + dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) { + section("Who?") { + input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true + } + + def routines = location.helloHome?.getPhrases()*.label + if (routines) { + routines.sort() + section("Run This Routine When...") { + log.trace routines + input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: routines, submitOnChange: true + input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: routines, submitOnChange: true + input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: routines, submitOnChange: true + input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: routines, submitOnChange: true + } + /* 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. + input "homeModeDay", "mode", title: "Select Mode Used for 'Home Day'", required: true + input "homeModeNight", "mode", title: "Select Mode Used for 'Home Night'", required: true + }*/ + } + } +} + +def installed() { + log.debug "Updated with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + unsubscribe() + initialize() +} + +def initialize() { + subscribe(people, "presence", presence) + checkSun() + subscribe(location, "sunrise", setSunrise) + subscribe(location, "sunset", setSunset) + state.homestate = null +} + +//check current sun state when installed. +def checkSun() { + def zip = settings.zip as String + def sunInfo = getSunriseAndSunset(zipCode: zip) + def current = now() + + if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) { + state.sunMode = "sunrise" + runIn(60,"setSunrise") + } + else { + state.sunMode = "sunset" + runIn(60,"setSunset") + } +} + +//change to sunrise mode on sunrise event +def setSunrise(evt) { + state.sunMode = "sunrise"; + changeSunMode(newMode); + log.debug "Current sun mode is ${state.sunMode}" +} + +//change to sunset mode on sunset event +def setSunset(evt) { + state.sunMode = "sunset"; + changeSunMode(newMode) + log.debug "Current sun mode is ${state.sunMode}" +} + +//change mode on sun event +def changeSunMode(newMode) { + if (allOk) { + + if (everyoneIsAway()) /*&& (state.sunMode == "sunrise")*/ { + log.info("Home is Empty Setting New Away Mode") + def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60 + setAway() + } +/* + else if (everyoneIsAway() && (state.sunMode == "sunset")) { + log.info("Home is Empty Setting New Away Mode") + def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60 + setAway() + }*/ + else if (anyoneIsHome()) { + log.info("Home is Occupied Setting New Home Mode") + setHome() + + + } + } +} + +//presence change run logic based on presence state of home +def presence(evt) { + if (allOk) { + if (evt.value == "not present") { + log.debug("Checking if everyone is away") + + if (everyoneIsAway()) { + log.info("Nobody is home, running away sequence") + def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60 + runIn(delay, "setAway") + } + } + else { + def lastTime = state[evt.deviceId] + if (lastTime == null || now() - lastTime >= 1 * 60000) { + log.info("Someone is home, running home sequence") + setHome() + } + state[evt.deviceId] = now() + + } + } +} + +//if empty set home to one of the away modes +def setAway() { + if (everyoneIsAway()) { + if (state.sunMode == "sunset") { + def message = "Performing \"${awayNight}\" for you as requested." + log.info(message) + sendAway(message) + location.helloHome.execute(settings.awayNight) + state.homestate = "away" + + } + else if (state.sunMode == "sunrise") { + def message = "Performing \"${awayDay}\" for you as requested." + log.info(message) + sendAway(message) + location.helloHome.execute(settings.awayDay) + state.homestate = "away" + } + else { + log.debug("Mode is the same, not evaluating") + } + } +} + +//set home mode when house is occupied +def setHome() { + log.info("Setting Home Mode!!") + if (anyoneIsHome()) { + if (state.sunMode == "sunset") { + if (state.homestate != "homeNight") { + def message = "Performing \"${homeNight}\" for you as requested." + log.info(message) + sendHome(message) + location.helloHome.execute(settings.homeNight) + state.homestate = "homeNight" + } + } + + if (state.sunMode == "sunrise") { + if (state.homestate != "homeDay") { + def message = "Performing \"${homeDay}\" for you as requested." + log.info(message) + sendHome(message) + location.helloHome.execute(settings.homeDay) + state.homestate = "homeDay" + } + } + } +} + +private everyoneIsAway() { + def result = true + + if(people.findAll { it?.currentPresence == "present" }) { + result = false + } + + log.debug("everyoneIsAway: ${result}") + + return result +} + +private anyoneIsHome() { + def result = false + + if(people.findAll { it?.currentPresence == "present" }) { + result = true + } + + log.debug("anyoneIsHome: ${result}") + + return result +} + +def sendAway(msg) { + if (sendPushMessage) { + if (recipients) { + sendNotificationToContacts(msg, recipients) + } + else { + sendPush(msg) + sendSms(phone, msg) + } + } + + log.debug(msg) +} + +def sendHome(msg) { + if (sendPushMessageHome) { + if (recipients) { + sendNotificationToContacts(msg, recipients) + } + else { + sendPush(msg) + sendSms(phone, msg) + } + } + + log.debug(msg) +} + +private getAllOk() { + modeOk && daysOk && timeOk +} + +private getModeOk() { + def result = !modes || modes.contains(location.mode) + log.trace "modeOk = $result" + result +} + +private getDaysOk() { + def result = true + if (days) { + def df = new java.text.SimpleDateFormat("EEEE") + if (location.timeZone) { + df.setTimeZone(location.timeZone) + } + else { + df.setTimeZone(TimeZone.getTimeZone("America/New_York")) + } + def day = df.format(new Date()) + result = days.contains(day) + } + log.trace "daysOk = $result" + result +} + +private getTimeOk() { + def result = true + if (starting && ending) { + def currTime = now() + def start = timeToday(starting, location?.timeZone).time + def stop = timeToday(ending, location?.timeZone).time + result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start + } + log.trace "timeOk = $result" + result +} + +private hhmm(time, fmt = "h:mm a") { + def t = timeToday(time, location.timeZone) + def f = new java.text.SimpleDateFormat(fmt) + f.setTimeZone(location.timeZone?:timeZone(time)) + f.format(t) +} + +private getTimeIntervalLabel() { + (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): "" +} + +private hideOptionsSection() { + (starting || ending || days || modes) ? false: true +} \ No newline at end of file From eb3d0c2874f578937e243cc2fba7be1909e945b6 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Fri, 9 Oct 2015 12:34:58 -0400 Subject: [PATCH 5/7] Adds capabilities "Temperature Measurement" and "Relative Humidity Measurement" --- devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy b/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy index 662bbc3..c6e598e 100644 --- a/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy +++ b/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy @@ -8,6 +8,8 @@ metadata { definition (name: "Zen Thermostat", namespace: "zenwithin", author: "ZenWithin") { capability "Actuator" capability "Thermostat" + capability "Temperature Measurement" + capability "Relative Humidity Measurement" capability "Configuration" capability "Refresh" capability "Sensor" From 2534afbf818b8a57c0ec402d8c958c266ae6ef21 Mon Sep 17 00:00:00 2001 From: Juan Pablo Risso Date: Fri, 9 Oct 2015 12:39:26 -0400 Subject: [PATCH 6/7] Removed Humidity --- devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy b/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy index c6e598e..a13646f 100644 --- a/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy +++ b/devicetypes/zenwithin/zen-thermostat.src/zen-thermostat.groovy @@ -9,7 +9,6 @@ metadata { capability "Actuator" capability "Thermostat" capability "Temperature Measurement" - capability "Relative Humidity Measurement" capability "Configuration" capability "Refresh" capability "Sensor" From 9538df65e554ee3128f3c58e4e394b51931ff6bc Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Mon, 12 Oct 2015 10:52:53 -0700 Subject: [PATCH 7/7] [DVCSMP-1164] Sensor name not shown During acceleration events, the sensor name is not shown. --- .../smartsense-multi-sensor.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index 3bfda82..8d614c3 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -346,8 +346,8 @@ def getTemperature(value) { log.debug "Acceleration" def name = "acceleration" def value = numValue.endsWith("1") ? "active" : "inactive" - //def linkText = getLinkText(device) - def descriptionText = "was $value" + def linkText = getLinkText(device) + def descriptionText = "$linkText was $value" def isStateChange = isStateChange(device, name, value) [ name: name,