mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-23 13:14:11 +00:00
Merge pull request #667 from tslagle13/fixes-to-vacation-lighting-director
Update vacation-lighting-director.groovy
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* Vacation Lighting Director
|
* Vacation Lighting Director
|
||||||
*
|
*
|
||||||
* Version 2.4 - Added information paragraphs
|
* Version 2.5 - Moved scheduling over to Cron and added time as a trigger.
|
||||||
|
* Cleaned up formatting and some typos.
|
||||||
|
* Updated license.
|
||||||
|
* Made people option optional
|
||||||
|
* Added sttement to unschedule on mode change if people option is not selected
|
||||||
|
*
|
||||||
|
* 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
|
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
|
||||||
*
|
*
|
||||||
* Copyright 2015 Tim Slagle
|
* Copyright 2016 Tim Slagle
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* 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:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -51,8 +57,7 @@ def pageSetup() {
|
|||||||
return dynamicPage(pageProperties) {
|
return dynamicPage(pageProperties) {
|
||||||
section(""){
|
section(""){
|
||||||
paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " +
|
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. " +
|
"Please use each of the 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") {
|
section("Setup Menu") {
|
||||||
href "Setup", title: "Setup", description: "", state:greyedOut()
|
href "Setup", title: "Setup", description: "", state:greyedOut()
|
||||||
@@ -70,7 +75,7 @@ def Setup() {
|
|||||||
def newMode = [
|
def newMode = [
|
||||||
name: "newMode",
|
name: "newMode",
|
||||||
type: "mode",
|
type: "mode",
|
||||||
title: "Which?",
|
title: "Modes",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
required: true
|
required: true
|
||||||
]
|
]
|
||||||
@@ -96,14 +101,6 @@ def Setup() {
|
|||||||
required: true,
|
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 pageName = "Setup"
|
||||||
|
|
||||||
def pageProperties = [
|
def pageProperties = [
|
||||||
@@ -116,10 +113,11 @@ def Setup() {
|
|||||||
|
|
||||||
section(""){
|
section(""){
|
||||||
paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " +
|
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."
|
"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))") {
|
section("Simulator Triggers") {
|
||||||
input newMode
|
input newMode
|
||||||
|
href "timeIntervalInput", title: "Times", description: timeIntervalLabel(), refreshAfterSelection:true
|
||||||
}
|
}
|
||||||
section("Light switches to turn on/off") {
|
section("Light switches to turn on/off") {
|
||||||
input switches
|
input switches
|
||||||
@@ -130,9 +128,6 @@ def Setup() {
|
|||||||
section("Number of active lights at any given time") {
|
section("Number of active lights at any given time") {
|
||||||
input number_of_active_lights
|
input number_of_active_lights
|
||||||
}
|
}
|
||||||
section("People") {
|
|
||||||
input people
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -162,18 +157,29 @@ def Settings() {
|
|||||||
title: "Settings",
|
title: "Settings",
|
||||||
nextPage: "pageSetup"
|
nextPage: "pageSetup"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def people = [
|
||||||
|
name: "people",
|
||||||
|
type: "capability.presenceSensor",
|
||||||
|
title: "If these people are home do not change light status",
|
||||||
|
required: false,
|
||||||
|
multiple: true
|
||||||
|
]
|
||||||
|
|
||||||
return dynamicPage(pageProperties) {
|
return dynamicPage(pageProperties) {
|
||||||
|
|
||||||
section(""){
|
section(""){
|
||||||
paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " +
|
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."
|
"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") {
|
section("Delay to start simulator") {
|
||||||
input falseAlarmThreshold
|
input falseAlarmThreshold
|
||||||
}
|
}
|
||||||
|
section("People") {
|
||||||
|
paragraph "Not using this setting may cause some lights to remain on when you arrive home"
|
||||||
|
input people
|
||||||
|
}
|
||||||
section("More options") {
|
section("More options") {
|
||||||
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
|
|
||||||
input days
|
input days
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,9 +187,24 @@ def Settings() {
|
|||||||
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
|
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
|
||||||
section {
|
section {
|
||||||
input "starting", "time", title: "Starting", required: false
|
input "startTimeType", "enum", title: "Starting at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
||||||
input "ending", "time", title: "Ending", required: false
|
if (startTimeType in ["sunrise","sunset"]) {
|
||||||
|
input "startTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input "starting", "time", title: "Start time", required: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
section {
|
||||||
|
input "endTimeType", "enum", title: "Ending at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
||||||
|
if (endTimeType in ["sunrise","sunset"]) {
|
||||||
|
input "endTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input "ending", "time", title: "End time", required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
@@ -201,10 +222,13 @@ def initialize(){
|
|||||||
if (newMode != null) {
|
if (newMode != null) {
|
||||||
subscribe(location, modeChangeHandler)
|
subscribe(location, modeChangeHandler)
|
||||||
}
|
}
|
||||||
|
if (starting != null) {
|
||||||
|
schedule(starting, modeChangeHandler)
|
||||||
|
}
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def modeChangeHandler(evt) {
|
def modeChangeHandler(evt) {
|
||||||
log.debug "Mode change to: ${evt.value}"
|
|
||||||
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
|
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
|
||||||
runIn(delay, scheduleCheck)
|
runIn(delay, scheduleCheck)
|
||||||
}
|
}
|
||||||
@@ -212,48 +236,54 @@ def modeChangeHandler(evt) {
|
|||||||
|
|
||||||
//Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off
|
//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) {
|
def scheduleCheck(evt) {
|
||||||
if(allOk){
|
if(allOk){
|
||||||
log.debug("Running")
|
log.debug("Running")
|
||||||
// turn off all the switches
|
// turn off all the switches
|
||||||
switches.off()
|
switches.off()
|
||||||
|
|
||||||
// grab a random switch
|
// grab a random switch
|
||||||
def random = new Random()
|
def random = new Random()
|
||||||
def inactive_switches = switches
|
def inactive_switches = switches
|
||||||
for (int i = 0 ; i < number_of_active_lights ; i++) {
|
for (int i = 0 ; i < number_of_active_lights ; i++) {
|
||||||
// if there are no inactive switches to turn on then let's break
|
// if there are no inactive switches to turn on then let's break
|
||||||
if (inactive_switches.size() == 0){
|
if (inactive_switches.size() == 0){
|
||||||
break
|
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
|
||||||
|
schedule("0 0/${frequency_minutes} * 1/1 * ? *", scheduleCheck)
|
||||||
}
|
}
|
||||||
|
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
|
||||||
// grab a random switch and turn it on
|
else if (modeOk) {
|
||||||
def random_int = random.nextInt(inactive_switches.size())
|
log.debug("mode OK. Running again")
|
||||||
inactive_switches[random_int].on()
|
switches.off()
|
||||||
|
}
|
||||||
// then remove that switch from the pool off switches that can be turned on
|
//if none is ok turn off frequency check and turn off lights.
|
||||||
inactive_switches.remove(random_int)
|
else {
|
||||||
}
|
if(people){
|
||||||
|
//don't turn off lights if anyone is home
|
||||||
// re-run again when the frequency demands it
|
if(someoneIsHome()){
|
||||||
runIn(frequency_minutes * 60, scheduleCheck)
|
log.debug("Stopping Check for Light")
|
||||||
}
|
unschedule()
|
||||||
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
|
}
|
||||||
else if (modeOk) {
|
else{
|
||||||
log.debug("mode OK. Running again")
|
log.debug("Stopping Check for Light and turning off all lights")
|
||||||
runIn(frequency_minutes * 60, scheduleCheck)
|
switches.off()
|
||||||
switches.off()
|
unschedule()
|
||||||
}
|
}
|
||||||
//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{
|
else if (!modeOk) {
|
||||||
log.debug("Stopping Check for Light and turning off all lights")
|
unschedule()
|
||||||
switches.off()
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -286,26 +316,6 @@ private getDaysOk() {
|
|||||||
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() {
|
private getHomeIsEmpty() {
|
||||||
def result = true
|
def result = true
|
||||||
|
|
||||||
@@ -330,25 +340,59 @@ private getSomeoneIsHome() {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTimeOk() {
|
||||||
//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 result = true
|
||||||
def getTimeLabel(starting, ending){
|
def start = timeWindowStart()
|
||||||
|
def stop = timeWindowStop()
|
||||||
def timeLabel = "Tap to set"
|
if (start && stop && location.timeZone) {
|
||||||
|
result = timeOfDayIsBetween(start, stop, new Date(), location.timeZone)
|
||||||
if(starting && ending){
|
}
|
||||||
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
|
log.trace "timeOk = $result"
|
||||||
}
|
result
|
||||||
else if (starting) {
|
}
|
||||||
timeLabel = "Start at" + " " + hhmm(starting)
|
|
||||||
}
|
private timeWindowStart() {
|
||||||
else if(ending){
|
def result = null
|
||||||
timeLabel = "End at" + hhmm(ending)
|
if (startTimeType == "sunrise") {
|
||||||
}
|
result = location.currentState("sunriseTime")?.dateValue
|
||||||
timeLabel
|
if (result && startTimeOffset) {
|
||||||
|
result = new Date(result.time + Math.round(startTimeOffset * 60000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (startTimeType == "sunset") {
|
||||||
|
result = location.currentState("sunsetTime")?.dateValue
|
||||||
|
if (result && startTimeOffset) {
|
||||||
|
result = new Date(result.time + Math.round(startTimeOffset * 60000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (starting && location.timeZone) {
|
||||||
|
result = timeToday(starting, location.timeZone)
|
||||||
|
}
|
||||||
|
log.trace "timeWindowStart = ${result}"
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
private timeWindowStop() {
|
||||||
|
def result = null
|
||||||
|
if (endTimeType == "sunrise") {
|
||||||
|
result = location.currentState("sunriseTime")?.dateValue
|
||||||
|
if (result && endTimeOffset) {
|
||||||
|
result = new Date(result.time + Math.round(endTimeOffset * 60000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (endTimeType == "sunset") {
|
||||||
|
result = location.currentState("sunsetTime")?.dateValue
|
||||||
|
if (result && endTimeOffset) {
|
||||||
|
result = new Date(result.time + Math.round(endTimeOffset * 60000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ending && location.timeZone) {
|
||||||
|
result = timeToday(ending, location.timeZone)
|
||||||
|
}
|
||||||
|
log.trace "timeWindowStop = ${result}"
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
//fomrats time to readable format for time label
|
|
||||||
private hhmm(time, fmt = "h:mm a")
|
private hhmm(time, fmt = "h:mm a")
|
||||||
{
|
{
|
||||||
def t = timeToday(time, location.timeZone)
|
def t = timeToday(time, location.timeZone)
|
||||||
@@ -357,6 +401,41 @@ private hhmm(time, fmt = "h:mm a")
|
|||||||
f.format(t)
|
f.format(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private timeIntervalLabel() {
|
||||||
|
def start = ""
|
||||||
|
switch (startTimeType) {
|
||||||
|
case "time":
|
||||||
|
if (ending) {
|
||||||
|
start += hhmm(starting)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "sunrise":
|
||||||
|
case "sunset":
|
||||||
|
start += startTimeType[0].toUpperCase() + startTimeType[1..-1]
|
||||||
|
if (startTimeOffset) {
|
||||||
|
start += startTimeOffset > 0 ? "+${startTimeOffset} min" : "${startTimeOffset} min"
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
def finish = ""
|
||||||
|
switch (endTimeType) {
|
||||||
|
case "time":
|
||||||
|
if (ending) {
|
||||||
|
finish += hhmm(ending)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "sunrise":
|
||||||
|
case "sunset":
|
||||||
|
finish += endTimeType[0].toUpperCase() + endTimeType[1..-1]
|
||||||
|
if (endTimeOffset) {
|
||||||
|
finish += endTimeOffset > 0 ? "+${endTimeOffset} min" : "${endTimeOffset} min"
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start && finish ? "${start} to ${finish}" : ""
|
||||||
|
}
|
||||||
|
|
||||||
//sets complete/not complete for the setup section on the main dynamic page
|
//sets complete/not complete for the setup section on the main dynamic page
|
||||||
def greyedOut(){
|
def greyedOut(){
|
||||||
def result = ""
|
def result = ""
|
||||||
@@ -369,16 +448,7 @@ def greyedOut(){
|
|||||||
//sets complete/not complete for the settings section on the main dynamic page
|
//sets complete/not complete for the settings section on the main dynamic page
|
||||||
def greyedOutSettings(){
|
def greyedOutSettings(){
|
||||||
def result = ""
|
def result = ""
|
||||||
if (starting || ending || days || falseAlarmThreshold) {
|
if (people || 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 = "complete"
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
|||||||
Reference in New Issue
Block a user