Initial commit

This commit is contained in:
bflorian
2015-08-04 15:49:03 -07:00
commit 6ad3c4fd7a
322 changed files with 67201 additions and 0 deletions

View File

@@ -0,0 +1,321 @@
/**
* Magic Home
*
* Copyright 2014 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: "Hello, Home Phrase Director",
namespace: "tslagle13",
author: "Tim Slagle",
description: "Monitor a set of presence sensors and activate Hello, Home phrases based on whether your home is empty or occupied. Each presence status change will check against the current 'sun state' to run phrases 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: "selectPhrases")
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", "decimal", required: true
}
section("Notifications") {
input "sendPushMessage", "enum", title: "Send a push notification when house is empty?", metadata:[values:["Yes","No"]], required:false
input "sendPushMessageHome", "enum", title: "Send a push notification when home is occupied?", metadata:[values:["Yes","No"]], required:false
}
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 selectPhrases() {
def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
dynamicPage(name: "selectPhrases", title: "Configure", nextPage:"Settings", uninstall: true) {
section("Who?") {
input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange:true
}
def phrases = location.helloHome?.getPhrases()*.label
if (phrases) {
phrases.sort()
section("Run This Phrase When...") {
log.trace phrases
input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: phrases, submitOnChange:true
input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: phrases, submitOnChange:true
input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: phrases, submitOnChange:true
input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: phrases, submitOnChange:true
}
section("Select modes used for each condition. (Needed for better app logic)") {
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() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
subscribe(people, "presence", presence)
runIn(60, checkSun)
subscribe(location, "sunrise", setSunrise)
subscribe(location, "sunset", setSunset)
}
//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"
setSunrise()
}
else {
state.sunMode = "sunset"
setSunset()
}
}
//change to sunrise mode on sunrise event
def setSunrise(evt) {
state.sunMode = "sunrise";
changeSunMode(newMode);
}
//change to sunset mode on sunset event
def setSunset(evt) {
state.sunMode = "sunset";
changeSunMode(newMode)
}
//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
runIn(delay, "setAway")
}
if(everyoneIsAway() && (state.sunMode == "sunset")) {
log.info("Home is Empty Setting New Away Mode")
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
runIn(delay, "setAway")
}
else {
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)
}
else if(state.sunMode == "sunrise") {
def message = "Performing \"${awayDay}\" for you as requested."
log.info(message)
sendAway(message)
location.helloHome.execute(settings.awayDay)
}
else {
log.debug("Mode is the same, not evaluating")
}
}
else {
log.info("Somebody returned home before we set to '${newAwayMode}'")
}
}
//set home mode when house is occupied
def setHome() {
log.info("Setting Home Mode!!")
if(anyoneIsHome()) {
if(state.sunMode == "sunset"){
if (location.mode != "${homeModeNight}"){
def message = "Performing \"${homeNight}\" for you as requested."
log.info(message)
sendHome(message)
location.helloHome.execute(settings.homeNight)
}
}
if(state.sunMode == "sunrise"){
if (location.mode != "${homeModeDay}"){
def message = "Performing \"${homeDay}\" for you as requested."
log.info(message)
sendHome(message)
location.helloHome.execute(settings.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 != "No") {
log.debug("Sending push message")
sendPush(msg)
}
log.debug(msg)
}
def sendHome(msg) {
if(sendPushMessageHome != "No") {
log.debug("Sending push message")
sendPush(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
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,643 @@
/**
* Thermostat Mode Director
* Source: https://github.com/tslagle13/SmartThings/blob/master/Director-Series-Apps/Thermostat-Mode-Director/Thermostat%20Mode%20Director.groovy
*
* Version 3.0
*
* Changelog:
* 2015-05-25
* --Updated UI to make it look pretty.
* 2015-06-01
* --Added option for modes to trigger thermostat boost.
*
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
*
* 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.
*
*/
// Automatically generated. Make future change here.
definition(
name: "Thermostat Mode Director",
namespace: "tslagle13",
author: "Tim Slagle",
description: "Changes mode of your thermostat based on the temperature range of a specified temperature sensor and shuts off the thermostat if any windows/doors are open.",
category: "Green Living",
iconUrl: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Science-Temperature-icon.png",
iconX2Url: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Science-Temperature-icon.png"
)
preferences {
page name:"pageSetup"
page name:"directorSettings"
page name:"ThermostatandDoors"
page name:"ThermostatBoost"
page name:"Settings"
}
// Show setup page
def pageSetup() {
def pageProperties = [
name: "pageSetup",
title: "Status",
nextPage: null,
install: true,
uninstall: true
]
return dynamicPage(pageProperties) {
section("About 'Thermostat Mode Director'"){
paragraph "Changes mode of your thermostat based on the temperature range of a specified temperature sensor and shuts off the thermostat if any windows/doors are open."
}
section("Setup Menu") {
href "directorSettings", title: "Director Settings", description: "", state:greyedOut()
href "ThermostatandDoors", title: "Thermostat and Doors", description: "", state: greyedOutTherm()
href "ThermostatBoost", title: "Thermostat Boost", description: "", state: greyedOutTherm1()
href "Settings", title: "Settings", description: "", state: greyedOutSettings()
}
section([title:"Options", mobileOnly:true]) {
label title:"Assign a name", required:false
}
}
}
// Show "Setup" page
def directorSettings() {
def sensor = [
name: "sensor",
type: "capability.temperatureMeasurement",
title: "Which?",
multiple: false,
required: true
]
def setLow = [
name: "setLow",
type: "decimal",
title: "Low temp?",
required: true
]
def cold = [
name: "cold",
type: "enum",
title: "Mode?",
metadata: [values:["auto", "heat", "cool", "off"]]
]
def setHigh = [
name: "setHigh",
type: "decimal",
title: "High temp?",
required: true
]
def hot = [
name: "hot",
type: "enum",
title: "Mode?",
metadata: [values:["auto", "heat", "cool", "off"]]
]
def neutral = [
name: "neutral",
type: "enum",
title: "Mode?",
metadata: [values:["auto", "heat", "cool", "off"]]
]
def pageName = "Setup"
def pageProperties = [
name: "directorSettings",
title: "Setup",
nextPage: "pageSetup"
]
return dynamicPage(pageProperties) {
section("Which temperature sensor will control your thermostat?"){
input sensor
}
section(""){
paragraph "Here you will setup the upper and lower thresholds for the temperature sensor that will send commands to your thermostat."
}
section("When the temperature falls below this tempurature set mode to..."){
input setLow
input cold
}
section("When the temperature goes above this tempurature set mode to..."){
input setHigh
input hot
}
section("When temperature is between the previous temperatures, change mode to..."){
input neutral
}
}
}
def ThermostatandDoors() {
def thermostat = [
name: "thermostat",
type: "capability.thermostat",
title: "Which?",
multiple: true,
required: true
]
def doors = [
name: "doors",
type: "capability.contactSensor",
title: "Low temp?",
multiple: true,
required: true
]
def turnOffDelay = [
name: "turnOffDelay",
type: "decimal",
title: "Number of minutes",
required: false
]
def pageName = "Thermostat and Doors"
def pageProperties = [
name: "ThermostatandDoors",
title: "Thermostat and Doors",
nextPage: "pageSetup"
]
return dynamicPage(pageProperties) {
section(""){
paragraph "If any of the doors selected here are open the thermostat will automatically be turned off and this app will be 'disabled' until all the doors are closed. (This is optional)"
}
section("Choose thermostat...") {
input thermostat
}
section("If these doors/windows are open turn off thermostat regardless of outdoor temperature") {
input doors
}
section("Wait this long before turning the thermostat off (defaults to 1 minute)") {
input turnOffDelay
}
}
}
def ThermostatBoost() {
def thermostat1 = [
name: "thermostat1",
type: "capability.thermostat",
title: "Which?",
multiple: true,
required: true
]
def turnOnTherm = [
name: "turnOnTherm",
type: "enum",
metadata: [values: ["cool", "heat"]],
required: false
]
def modes1 = [
name: "modes1",
type: "mode",
title: "Put thermostat into boost mode when mode is...",
multiple: true,
required: false
]
def coolingTemp = [
name: "coolingTemp",
type: "decimal",
title: "Cooling Temp?",
required: false
]
def heatingTemp = [
name: "heatingTemp",
type: "decimal",
title: "Heating Temp?",
required: false
]
def turnOffDelay2 = [
name: "turnOffDelay2",
type: "decimal",
title: "Number of minutes",
required: false,
defaultValue:30
]
def pageName = "Thermostat Boost"
def pageProperties = [
name: "ThermostatBoost",
title: "Thermostat Boost",
nextPage: "pageSetup"
]
return dynamicPage(pageProperties) {
section(""){
paragraph "Here you can setup the ability to 'boost' your thermostat. In the event that your thermostat is 'off'" +
" and you need to heat or cool your your home for a little bit you can 'touch' the app in the 'My Apps' section to boost your thermostat."
}
section("Choose a thermostats to boost") {
input thermostat1
}
section("If thermostat is off switch to which mode?") {
input turnOnTherm
}
section("Set the thermostat to the following temps") {
input coolingTemp
input heatingTemp
}
section("For how long?") {
input turnOffDelay2
}
section("In addtion to 'app touch' the following modes will also boost the thermostat") {
input modes1
}
}
}
// Show "Setup" page
def Settings() {
def sendPushMessage = [
name: "sendPushMessage",
type: "enum",
title: "Send a push notification?",
metadata: [values:["Yes","No"]],
required: true,
defaultValue: "Yes"
]
def phoneNumber = [
name: "phoneNumber",
type: "phone",
title: "Send SMS notifications to?",
required: false
]
def days = [
name: "days",
type: "enum",
title: "Only on certain days of the week",
multiple: true,
required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
]
def modes = [
name: "modes",
type: "mode",
title: "Only when mode is",
multiple: true,
required: false
]
def pageName = "Settings"
def pageProperties = [
name: "Settings",
title: "Settings",
nextPage: "pageSetup"
]
return dynamicPage(pageProperties) {
section( "Notifications" ) {
input sendPushMessage
input phoneNumber
}
section(title: "More options", hideable: true) {
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
input days
input modes
}
}
}
def installed(){
init()
}
def updated(){
unsubscribe()
init()
}
def init(){
state.lastStatus = null
subscribe(app, appTouch)
runIn(60, "temperatureHandler")
subscribe(sensor, "temperature", temperatureHandler)
if(modes1){
subscribe(location, modeBoostChange)
}
if(doors){
subscribe(doors, "contact.open", temperatureHandler)
subscribe(doors, "contact.closed", doorCheck)
}
}
def temperatureHandler(evt) {
if(modeOk && daysOk && timeOk) {
if(setLow > setHigh){
def temp = setLow
setLow = setHigh
setHigh = temp
}
if (doorsOk) {
def currentTemp = sensor.latestValue("temperature")
if (currentTemp < setLow) {
if (state.lastStatus == "two" || state.lastStatus == "three" || state.lastStatus == null){
//log.info "Setting thermostat mode to ${cold}"
def msg = "I changed your thermostat mode to ${cold} because temperature is below ${setLow}"
thermostat?."${cold}"()
sendMessage(msg)
}
state.lastStatus = "one"
}
if (currentTemp > setHigh) {
if (state.lastStatus == "one" || state.lastStatus == "three" || state.lastStatus == null){
//log.info "Setting thermostat mode to ${hot}"
def msg = "I changed your thermostat mode to ${hot} because temperature is above ${setHigh}"
thermostat?."${hot}"()
sendMessage(msg)
}
state.lastStatus = "two"
}
if (currentTemp > setLow && currentTemp < setHigh) {
if (state.lastStatus == "two" || state.lastStatus == "one" || state.lastStatus == null){
//log.info "Setting thermostat mode to ${neutral}"
def msg = "I changed your thermostat mode to ${neutral} because temperature is neutral"
thermostat?."${neutral}"()
sendMessage(msg)
}
state.lastStatus = "three"
}
}
else{
def delay = (turnOffDelay != null && turnOffDelay != "") ? turnOffDelay * 60 : 60
log.debug("Detected open doors. Checking door states again")
runIn(delay, "doorCheck")
}
}
}
def appTouch(evt) {
if(thermostat1){
state.lastStatus = "disabled"
def currentCoolSetpoint = thermostat1.latestValue("coolingSetpoint") as String
def currentHeatSetpoint = thermostat1.latestValue("heatingSetpoint") as String
def currentMode = thermostat1.latestValue("thermostatMode") as String
def mode = turnOnTherm
state.currentCoolSetpoint1 = currentCoolSetpoint
state.currentHeatSetpoint1 = currentHeatSetpoint
state.currentMode1 = currentMode
thermostat1."${mode}"()
thermostat1.setCoolingSetpoint(coolingTemp)
thermostat1.setHeatingSetpoint(heatingTemp)
thermoShutOffTrigger()
//log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
//log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
//log.debug("current mode is ${state.currentMode1}")
}
}
def modeBoostChange(evt) {
if(thermostat1 && modes1.contains(location.mode)){
state.lastStatus = "disabled"
def currentCoolSetpoint = thermostat1.latestValue("coolingSetpoint") as String
def currentHeatSetpoint = thermostat1.latestValue("heatingSetpoint") as String
def currentMode = thermostat1.latestValue("thermostatMode") as String
def mode = turnOnTherm
state.currentCoolSetpoint1 = currentCoolSetpoint
state.currentHeatSetpoint1 = currentHeatSetpoint
state.currentMode1 = currentMode
thermostat1."${mode}"()
thermostat1.setCoolingSetpoint(coolingTemp)
thermostat1.setHeatingSetpoint(heatingTemp)
log.debug("current coolingsetpoint is ${state.currentCoolSetpoint1}")
log.debug("current heatingsetpoint is ${state.currentHeatSetpoint1}")
log.debug("current mode is ${state.currentMode1}")
}
else{
thermoShutOff()
}
}
def thermoShutOffTrigger() {
//log.info("Starting timer to turn off thermostat")
def delay = (turnOffDelay2 != null && turnOffDelay2 != "") ? turnOffDelay2 * 60 : 60
state.turnOffTime = now()
log.debug ("Turn off delay is ${delay}")
runIn(delay, "thermoShutOff")
}
def thermoShutOff(){
if(state.lastStatus == "disabled"){
def coolSetpoint = state.currentCoolSetpoint1
def heatSetpoint = state.currentHeatSetpoint1
def mode = state.currentMode1
def coolSetpoint1 = coolSetpoint.replaceAll("\\]", "").replaceAll("\\[", "")
def heatSetpoint1 = heatSetpoint.replaceAll("\\]", "").replaceAll("\\[", "")
def mode1 = mode.replaceAll("\\]", "").replaceAll("\\[", "")
state.lastStatus = null
//log.info("Returning thermostat back to normal")
thermostat1.setCoolingSetpoint("${coolSetpoint1}")
thermostat1.setHeatingSetpoint("${heatSetpoint1}")
thermostat1."${mode1}"()
temperatureHandler()
}
}
def doorCheck(evt){
if (!doorsOk){
log.debug("doors still open turning off ${thermostat}")
def msg = "I changed your thermostat mode to off because some doors are open"
if (state.lastStatus != "off"){
thermostat?.off()
sendMessage(msg)
}
state.lastStatus = "off"
}
else{
if (state.lastStatus == "off"){
state.lastStatus = null
}
temperatureHandler()
}
}
private sendMessage(msg){
if (sendPushMessage == "Yes") {
sendPush(msg)
}
if (phoneNumber != null) {
sendSms(phoneNumber, msg)
}
}
private getAllOk() {
modeOk && daysOk && timeOk && doorsOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result"
result
}
private getDoorsOk() {
def result = !doors || !doors.latestValue("contact").contains("open")
log.trace "doorsOk = $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).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
else if (starting){
result = currTime >= start
}
else if (ending){
result = currTime <= stop
}
log.trace "timeOk = $result"
result
}
def getTimeLabel(starting, ending){
def timeLabel = "Tap to set"
if(starting && ending){
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
}
else if (starting) {
timeLabel = "Start at" + " " + hhmm(starting)
}
else if(ending){
timeLabel = "End at" + hhmm(ending)
}
timeLabel
}
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)
}
def greyedOut(){
def result = ""
if (sensor) {
result = "complete"
}
result
}
def greyedOutTherm(){
def result = ""
if (thermostat) {
result = "complete"
}
result
}
def greyedOutTherm1(){
def result = ""
if (thermostat1) {
result = "complete"
}
result
}
def greyedOutSettings(){
def result = ""
if (starting || ending || days || modes || sendPushMessage) {
result = "complete"
}
result
}
def greyedOutTime(starting, ending){
def result = ""
if (starting || ending) {
result = "complete"
}
result
}
private anyoneIsHome() {
def result = false
if(people.findAll { it?.currentPresence == "present" }) {
result = true
}
log.debug("anyoneIsHome: ${result}")
return result
}
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
section {
input "starting", "time", title: "Starting (both are required)", required: false
input "ending", "time", title: "Ending (both are required)", required: false
}
}

View File

@@ -0,0 +1,385 @@
/**
* Vacation Lighting Director
*
* Version 2.4 - Added information paragraphs
*
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
*
* 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.
*
*/
// Automatically generated. Make future change here.
definition(
name: "Vacation Lighting Director",
namespace: "tslagle13",
author: "Tim Slagle",
category: "Safety & Security",
description: "Randomly turn on/off lights to simulate the appearance of a occupied home while you are away.",
iconUrl: "http://icons.iconarchive.com/icons/custom-icon-design/mono-general-2/512/settings-icon.png",
iconX2Url: "http://icons.iconarchive.com/icons/custom-icon-design/mono-general-2/512/settings-icon.png"
)
preferences {
page name:"pageSetup"
page name:"Setup"
page name:"Settings"
}
// Show setup page
def pageSetup() {
def pageProperties = [
name: "pageSetup",
title: "Status",
nextPage: null,
install: true,
uninstall: true
]
return dynamicPage(pageProperties) {
section(""){
paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " +
"Please use each othe the sections below to setup the different preferences to your liking. " +
"I recommend this app be used with at least two away modes. An example would be 'Away Day' 'and Away Night'. "
}
section("Setup Menu") {
href "Setup", title: "Setup", description: "", state:greyedOut()
href "Settings", title: "Settings", description: "", state: greyedOutSettings()
}
section([title:"Options", mobileOnly:true]) {
label title:"Assign a name", required:false
}
}
}
// Show "Setup" page
def Setup() {
def newMode = [
name: "newMode",
type: "mode",
title: "Which?",
multiple: true,
required: true
]
def switches = [
name: "switches",
type: "capability.switch",
title: "Switches",
multiple: true,
required: true
]
def frequency_minutes = [
name: "frequency_minutes",
type: "number",
title: "Minutes?",
required: true
]
def number_of_active_lights = [
name: "number_of_active_lights",
type: "number",
title: "Number of active lights",
required: true,
]
def people = [
name: "people",
type: "capability.presenceSensor",
title: "If these people are home do not change light status",
required: true,
multiple: true
]
def pageName = "Setup"
def pageProperties = [
name: "Setup",
title: "Setup",
nextPage: "pageSetup"
]
return dynamicPage(pageProperties) {
section(""){
paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " +
paragraph "you are away. All of these settings are required in order for the simulator to run correctly."
}
section("Which mode change triggers the simulator? (This app will only run in selected mode(s))") {
input newMode
}
section("Light switches to turn on/off") {
input switches
}
section("How often to cycle the lights") {
input frequency_minutes
}
section("Number of active lights at any given time") {
input number_of_active_lights
}
section("People") {
input people
}
}
}
// Show "Setup" page
def Settings() {
def falseAlarmThreshold = [
name: "falseAlarmThreshold",
type: "decimal",
title: "Default is 2 minutes",
required: false
]
def days = [
name: "days",
type: "enum",
title: "Only on certain days of the week",
multiple: true,
required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
]
def pageName = "Settings"
def pageProperties = [
name: "Settings",
title: "Settings",
nextPage: "pageSetup"
]
return dynamicPage(pageProperties) {
section(""){
paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " +
paragraph "as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
}
section("Delay to start simulator") {
input falseAlarmThreshold
}
section("More options") {
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
input days
}
}
}
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
def installed() {
initialize()
}
def updated() {
unsubscribe();
unschedule();
initialize()
}
def initialize(){
if (newMode != null) {
subscribe(location, modeChangeHandler)
}
}
def modeChangeHandler(evt) {
log.debug "Mode change to: ${evt.value}"
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
runIn(delay, scheduleCheck)
}
//Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off
def scheduleCheck(evt) {
if(allOk){
log.debug("Running")
// turn off all the switches
switches.off()
// grab a random switch
def random = new Random()
def inactive_switches = switches
for (int i = 0 ; i < number_of_active_lights ; i++) {
// if there are no inactive switches to turn on then let's break
if (inactive_switches.size() == 0){
break
}
// grab a random switch and turn it on
def random_int = random.nextInt(inactive_switches.size())
inactive_switches[random_int].on()
// then remove that switch from the pool off switches that can be turned on
inactive_switches.remove(random_int)
}
// re-run again when the frequency demands it
runIn(frequency_minutes * 60, scheduleCheck)
}
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
else if (modeOk) {
log.debug("mode OK. Running again")
runIn(frequency_minutes * 60, scheduleCheck)
switches.off()
}
//if none is ok turn off frequency check and turn off lights.
else if(people){
//don't turn off lights if anyone is home
if(someoneIsHome()){
log.debug("Stopping Check for Light")
}
else{
log.debug("Stopping Check for Light and turning off all lights")
switches.off()
}
}
}
//below is used to check restrictions
private getAllOk() {
modeOk && daysOk && timeOk && homeIsEmpty
}
private getModeOk() {
def result = !newMode || newMode.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).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
else if (starting){
result = currTime >= start
}
else if (ending){
result = currTime <= stop
}
log.trace "timeOk = $result"
result
}
private getHomeIsEmpty() {
def result = true
if(people?.findAll { it?.currentPresence == "present" }) {
result = false
}
log.debug("homeIsEmpty: ${result}")
return result
}
private getSomeoneIsHome() {
def result = false
if(people?.findAll { it?.currentPresence == "present" }) {
result = true
}
log.debug("anyoneIsHome: ${result}")
return result
}
//gets the label for time restriction. Label phrasing changes depending on if there is both start and stop times or just one start/stop time.
def getTimeLabel(starting, ending){
def timeLabel = "Tap to set"
if(starting && ending){
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
}
else if (starting) {
timeLabel = "Start at" + " " + hhmm(starting)
}
else if(ending){
timeLabel = "End at" + hhmm(ending)
}
timeLabel
}
//fomrats time to readable format for time label
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)
}
//sets complete/not complete for the setup section on the main dynamic page
def greyedOut(){
def result = ""
if (switches) {
result = "complete"
}
result
}
//sets complete/not complete for the settings section on the main dynamic page
def greyedOutSettings(){
def result = ""
if (starting || ending || days || falseAlarmThreshold) {
result = "complete"
}
result
}
//sets complete/not complete for time restriction section in settings
def greyedOutTime(starting, ending){
def result = ""
if (starting || ending) {
result = "complete"
}
result
}