4 * Source: https://github.com/tslagle13/SmartThings/blob/master/Director-Series-Apps/Lighting-Director/Lighting%20Director.groovy
6 * Current Version: 2.9.4
11 * Version - 1.30.1 Modification by Michael Struck - Fixed syntax of help text and titles of scenarios, along with a new icon
12 * Version - 1.40.0 Modification by Michael Struck - Code optimization and added door contact sensor capability
13 * Version - 1.41.0 Modification by Michael Struck - Code optimization and added time restrictions to each scenario
14 * Version - 2.0 Tim Slagle - Moved to only have 4 slots. Code was to heavy and needed to be trimmed.
15 * Version - 2.1 Tim Slagle - Moved time interval inputs inline with STs design.
16 * Version - 2.2 Michael Struck - Added the ability to activate switches via the status locks and fixed some syntax issues
17 * Version - 2.5 Michael Struck - Changed the way the app unschedules re-triggered events
18 * Version - 2.5.1 Tim Slagle - Fixed Time Logic
19 * Version - 2.6 Michael Struck - Added the additional restriction of running triggers once per day and misc cleanup of code
20 * Version - 2.7 Michael Struck - Added feature that turns off triggering if the physical switch is pressed.
21 * Version - 2.81 Michael Struck - Fixed an issue with dimmers not stopping light action
22 * Version - 2.9 Michael Struck - Fixed issue where button presses outside of the time restrictions prevent the triggers from firing and code optimization
23 * Version - 2.9.1 Tim Slagle - Further enhanced time interval logic.
24 * Version - 2.9.2 Brandon Gordon - Added support for acceleration sensors.
25 * Version - 2.9.3 Brandon Gordon - Added mode change subscriptions.
26 * Version - 2.9.4 Michael Struck - Code Optimization when triggers are tripped
28 * Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
30 * Copyright 2015 Tim Slagle and Michael Struck
32 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
33 * in compliance with the License. You may obtain a copy of the License at:
35 * http://www.apache.org/licenses/LICENSE-2.0
37 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
38 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
39 * for the specific language governing permissions and limitations under the License.
44 name: "Lighting Director",
45 namespace: "tslagle13",
46 author: "Tim Slagle & Michael Struck",
47 description: "Control up to 4 sets (scenarios) of lights based on motion, door contacts and illuminance levels.",
48 category: "Convenience",
49 iconUrl: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Lighting-Director/LightingDirector.png",
50 iconX2Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Lighting-Director/LightingDirector@2x.png",
51 iconX3Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Lighting-Director/LightingDirector@2x.png")
55 page name:"pageSetupScenarioA"
56 page name:"pageSetupScenarioB"
57 page name:"pageSetupScenarioC"
58 page name:"pageSetupScenarioD"
64 def pageProperties = [
71 return dynamicPage(pageProperties) {
72 section("Setup Menu") {
73 href "pageSetupScenarioA", title: getTitle(settings.ScenarioNameA), description: getDesc(settings.ScenarioNameA), state: greyOut(settings.ScenarioNameA)
74 href "pageSetupScenarioB", title: getTitle(settings.ScenarioNameB), description: getDesc(settings.ScenarioNameB), state: greyOut(settings.ScenarioNameB)
75 href "pageSetupScenarioC", title: getTitle(settings.ScenarioNameC), description: getDesc(settings.ScenarioNameC), state: greyOut(settings.ScenarioNameC)
76 href "pageSetupScenarioD", title: getTitle(settings.ScenarioNameD), description: getDesc(settings.ScenarioNameD), state: greyOut(settings.ScenarioNameD)
78 section([title:"Options", mobileOnly:true]) {
79 label title:"Assign a name", required:false
84 // Show "pageSetupScenarioA" page
85 def pageSetupScenarioA() {
89 type: "capability.switch",
90 title: "Control the following switches...",
96 type: "capability.switchLevel",
97 title: "Dim the following...",
104 type: "capability.motionSensor",
105 title: "Using these motion sensors...",
110 def inputAccelerationA = [
111 name: "A_acceleration",
112 type: "capability.accelerationSensor",
113 title: "Or using these acceleration sensors...",
117 def inputContactA = [
119 type: "capability.contactSensor",
120 title: "Or using these contact sensors...",
125 def inputTriggerOnceA = [
126 name: "A_triggerOnce",
128 title: "Trigger only once per day...",
132 def inputSwitchDisableA = [
133 name: "A_switchDisable",
135 title: "Stop triggering if physical switches/dimmers are turned off...",
141 type: "capability.lock",
142 title: "Or using these locks...",
150 title: "Only during the following modes...",
158 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
159 title: "Only on certain days of the week...",
168 options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],
169 title: "Set dimmers to this level",
174 def inputTurnOnLuxA = [
177 title: "Only run this scenario if lux is below...",
182 def inputLuxSensorsA = [
183 name: "A_luxSensors",
184 type: "capability.illuminanceMeasurement",
185 title: "On these lux sensors",
190 def inputTurnOffA = [
193 title: "Turn off this scenario after motion stops or doors close/lock (minutes)...",
198 def inputScenarioNameA = [
199 name: "ScenarioNameA",
201 title: "Scenario Name",
207 def pageProperties = [
208 name: "pageSetupScenarioA",
211 return dynamicPage(pageProperties) {
212 section("Name your scenario") {
213 input inputScenarioNameA
216 section("Devices included in the scenario") {
218 input inputAccelerationA
225 section("Scenario settings") {
227 input inputTurnOnLuxA
228 input inputLuxSensorsA
232 section("Scenario restrictions") {
233 input inputTriggerOnceA
234 input inputSwitchDisableA
235 href "timeIntervalInputA", title: "Only during a certain time...", description: getTimeLabel(A_timeStart, A_timeEnd), state: greyedOutTime(A_timeStart, A_timeEnd), refreshAfterSelection:true
247 def pageSetupScenarioB() {
251 type: "capability.switch",
252 title: "Control the following switches...",
256 def inputDimmersB = [
258 type: "capability.switchLevel",
259 title: "Dim the following...",
264 def inputTurnOnLuxB = [
267 title: "Only run this scenario if lux is below...",
272 def inputLuxSensorsB = [
273 name: "B_luxSensors",
274 type: "capability.illuminanceMeasurement",
275 title: "On these lux sensors",
282 type: "capability.motionSensor",
283 title: "Using these motion sensors...",
288 def inputAccelerationB = [
289 name: "B_acceleration",
290 type: "capability.accelerationSensor",
291 title: "Or using these acceleration sensors...",
295 def inputContactB = [
297 type: "capability.contactSensor",
298 title: "Or using these contact sensors...",
303 def inputTriggerOnceB = [
304 name: "B_triggerOnce",
306 title: "Trigger only once per day...",
310 def inputSwitchDisableB = [
311 name: "B_switchDisable",
313 title: "Stop triggering if physical switches/dimmers are turned off...",
319 type: "capability.lock",
320 title: "Or using these locks...",
328 title: "Only during the following modes...",
336 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
337 title: "Only on certain days of the week...",
345 options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],
346 title: "Set dimmers to this level",
351 def inputTurnOffB = [
354 title: "Turn off this scenario after motion stops or doors close/lock (minutes)...",
359 def inputScenarioNameB = [
360 name: "ScenarioNameB",
362 title: "Scenario Name",
368 def pageProperties = [
369 name: "pageSetupScenarioB",
372 return dynamicPage(pageProperties) {
373 section("Name your scenario") {
374 input inputScenarioNameB
377 section("Devices included in the scenario") {
379 input inputAccelerationB
386 section("Scenario settings") {
388 input inputTurnOnLuxB
389 input inputLuxSensorsB
393 section("Scenario restrictions") {
394 input inputTriggerOnceB
395 input inputSwitchDisableB
396 href "timeIntervalInputB", title: "Only during a certain time...", description: getTimeLabel(B_timeStart, B_timeEnd), state: greyedOutTime(B_timeStart, B_timeEnd), refreshAfterSelection:true
407 def pageSetupScenarioC() {
411 type: "capability.switch",
412 title: "Control the following switches...",
416 def inputDimmersC = [
418 type: "capability.switchLevel",
419 title: "Dim the following...",
426 type: "capability.motionSensor",
427 title: "Using these motion sensors...",
432 def inputAccelerationC = [
433 name: "C_acceleration",
434 type: "capability.accelerationSensor",
435 title: "Or using these acceleration sensors...",
439 def inputContactC = [
441 type: "capability.contactSensor",
442 title: "Or using these contact sensors...",
447 def inputTriggerOnceC = [
448 name: "C_triggerOnce",
450 title: "Trigger only once per day...",
454 def inputSwitchDisableC = [
455 name: "C_switchDisable",
457 title: "Stop triggering if physical switches/dimmers are turned off...",
463 type: "capability.lock",
464 title: "Or using these locks...",
472 title: "Only during the following modes...",
480 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
481 title: "Only on certain days of the week...",
489 options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],
490 title: "Set dimmers to this level",
495 def inputTurnOffC = [
498 title: "Turn off this scenario after motion stops or doors close/lock (minutes)...",
503 def inputScenarioNameC = [
504 name: "ScenarioNameC",
506 title: "Scenario Name",
512 def inputTurnOnLuxC = [
515 title: "Only run this scenario if lux is below...",
520 def inputLuxSensorsC = [
521 name: "C_luxSensors",
522 type: "capability.illuminanceMeasurement",
523 title: "On these lux sensors",
528 def pageProperties = [
529 name: "pageSetupScenarioC",
532 return dynamicPage(pageProperties) {
533 section("Name your scenario") {
534 input inputScenarioNameC
537 section("Devices included in the scenario") {
539 input inputAccelerationC
546 section("Scenario settings") {
548 input inputTurnOnLuxC
549 input inputLuxSensorsC
553 section("Scenario restrictions") {
554 input inputTriggerOnceC
555 input inputSwitchDisableC
556 href "timeIntervalInputC", title: "Only during a certain time...", description: getTimeLabel(C_timeStart, C_timeEnd), state: greyedOutTime(C_timeStart, C_timeEnd), refreshAfterSelection:true
567 def pageSetupScenarioD() {
571 type: "capability.switch",
572 title: "Control the following switches...",
576 def inputDimmersD = [
578 type: "capability.switchLevel",
579 title: "Dim the following...",
586 type: "capability.motionSensor",
587 title: "Using these motion sensors...",
592 def inputAccelerationD = [
593 name: "D_acceleration",
594 type: "capability.accelerationSensor",
595 title: "Or using these acceleration sensors...",
599 def inputContactD = [
601 type: "capability.contactSensor",
602 title: "Or using these contact sensors...",
609 type: "capability.lock",
610 title: "Or using these locks...",
618 title: "Only during the following modes...",
623 def inputTriggerOnceD = [
624 name: "D_triggerOnce",
626 title: "Trigger only once per day...",
630 def inputSwitchDisableD = [
631 name: "D_switchDisable",
633 title: "Stop triggering if physical switches/dimmers are turned off...",
640 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
641 title: "Only on certain days of the week...",
650 options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]],
651 title: "Set dimmers to this level",
656 def inputTurnOffD = [
659 title: "Turn off this scenario after motion stops, doors close or close/lock (minutes)...",
664 def inputScenarioNameD = [
665 name: "ScenarioNameD",
667 title: "Scenario Name",
673 def inputTurnOnLuxD = [
676 title: "Only run this scenario if lux is below...",
681 def inputLuxSensorsD = [
682 name: "D_luxSensors",
683 type: "capability.illuminanceMeasurement",
684 title: "On these lux sensors",
689 def pageProperties = [
690 name: "pageSetupScenarioD",
693 return dynamicPage(pageProperties) {
694 section("Name your scenario") {
695 input inputScenarioNameD
698 section("Devices included in the scenario") {
700 input inputAccelerationD
707 section("Scenario settings") {
709 input inputTurnOnLuxD
710 input inputLuxSensorsD
714 section("Scenario restrictions") {
715 input inputTriggerOnceD
716 input inputSwitchDisableD
717 href "timeIntervalInputD", title: "Only during a certain time", description: getTimeLabel(D_timeStart, D_timeEnd), state: greyedOutTime(D_timeStart, D_timeEnd), refreshAfterSelection:true
743 subscribe(settings.A_motion, "motion", onEventA)
747 subscribe(settings.A_acceleration, "acceleration", onEventA)
751 subscribe(settings.A_contact, "contact", onEventA)
755 subscribe(settings.A_lock, "lock", onEventA)
758 if(A_switchDisable) {
759 subscribe(A_switches, "switch.off", onPressA)
760 subscribe(A_dimmers, "switch.off", onPressA)
764 subscribe(location, onEventA)
768 subscribe(settings.B_motion, "motion", onEventB)
772 subscribe(settings.B_acceleration, "acceleration", onEventB)
776 subscribe(settings.B_contact, "contact", onEventB)
780 subscribe(settings.B_lock, "lock", onEventB)
783 if(B_switchDisable) {
784 subscribe(B_switches, "switch.off", onPressB)
785 subscribe(B_dimmers, "switch.off", onPressB)
789 subscribe(location, onEventB)
793 subscribe(settings.C_motion, "motion", onEventC)
797 subscribe(settings.C_acceleration, "acceleration", onEventC)
801 subscribe(settings.C_contact, "contact", onEventC)
805 subscribe(settings.C_lock, "lock", onEventC)
808 if(C_switchDisable) {
809 subscribe(C_switches, "switch.off", onPressC)
810 subscribe(C_dimmers, "switch.off", onPressC)
814 subscribe(location, onEventC)
818 subscribe(settings.D_motion, "motion", onEventD)
822 subscribe(settings.D_acceleration, "acceleration", onEventD)
826 subscribe(settings.D_contact, "contact", onEventD)
830 subscribe(settings.D_lock, "lock", onEventD)
833 if(D_switchDisable) {
834 subscribe(D_switches, "switch.off", onPressD)
835 subscribe(D_dimmers, "switch.off", onPressD)
839 subscribe(location, onEventD)
846 if ((!A_triggerOnce || (A_triggerOnce && !state.A_triggered)) && (!A_switchDisable || (A_switchDisable && !state.A_triggered))) { //Checks to make sure this scenario should be triggered more then once in a day
847 if ((!A_mode || A_mode.contains(location.mode)) && getTimeOk (A_timeStart, A_timeEnd) && getDayOk(A_day)) { //checks to make sure we are not opperating outside of set restrictions.
848 if ((!A_luxSensors) || (A_luxSensors.latestValue("illuminance") <= A_turnOnLux)){ //checks to make sure illimunance is either not cared about or if the value is within the restrictions
849 def A_levelOn = A_level as Integer
851 //Check states of each device to see if they are to be ignored or if they meet the requirments of the app to produce an action.
852 if (getInputOk(A_motion, A_contact, A_lock, A_acceleration)) {
853 log.debug("Motion, Door Open or Unlock Detected Running '${ScenarioNameA}'")
854 settings.A_dimmers?.setLevel(A_levelOn)
855 settings.A_switches?.on()
857 state.A_triggered = true
859 runOnce (getMidnight(), midNightReset)
862 if (state.A_timerStart){
863 unschedule(delayTurnOffA)
864 state.A_timerStart = false
868 //if none of the above paramenters meet the expectation of the app then turn off
871 if (settings.A_turnOff) {
872 runIn(A_turnOff * 60, "delayTurnOffA")
873 state.A_timerStart = true
876 settings.A_switches?.off()
877 settings.A_dimmers?.setLevel(0)
878 if (state.A_triggered) {
879 runOnce (getMidnight(), midNightReset)
886 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction. Not running scenario.")
892 settings.A_switches?.off()
893 settings.A_dimmers?.setLevel(0)
894 state.A_timerStart = false
895 if (state.A_triggered) {
896 runOnce (getMidnight(), midNightReset)
901 //when physical switch is actuated disable the scenario
903 if ((!A_mode || A_mode.contains(location.mode)) && getTimeOk (A_timeStart, A_timeEnd) && getDayOk(A_day)) { //checks to make sure we are not opperating outside of set restrictions.
904 if ((!A_luxSensors) || (A_luxSensors.latestValue("illuminance") <= A_turnOnLux)){
905 if ((!A_triggerOnce || (A_triggerOnce && !state.A_triggered)) && (!A_switchDisable || (A_switchDisable && !state.A_triggered))) {
907 state.A_triggered = true
908 unschedule(delayTurnOffA)
909 runOnce (getMidnight(), midNightReset)
910 log.debug "Physical switch in '${ScenarioNameA}' pressed. Triggers for this scenario disabled."
917 if ((!B_triggerOnce || (B_triggerOnce && !state.B_triggered)) && (!B_switchDisable || (B_switchDisable && !state.B_triggered))) { //Checks to make sure this scenario should be triggered more then once in a day
918 if ((!B_mode ||B_mode.contains(location.mode)) && getTimeOk (B_timeStart, B_timeEnd) && getDayOk(B_day)) { //checks to make sure we are not opperating outside of set restrictions.
919 if ((!B_luxSensors) || (B_luxSensors.latestValue("illuminance") <= B_turnOnLux)) { //checks to make sure illimunance is either not cared about or if the value is within the restrictions
920 def B_levelOn = B_level as Integer
922 //Check states of each device to see if they are to be ignored or if they meet the requirments of the app to produce an action.
923 if (getInputOk(B_motion, B_contact, B_lock, B_acceleration)) {
925 log.debug("Motion, Door Open or Unlock Detected Running '${ScenarioNameB}'")
926 settings.B_dimmers?.setLevel(B_levelOn)
927 settings.B_switches?.on()
929 state.B_triggered = true
931 runOnce (getMidnight(), midNightReset)
934 if (state.B_timerStart) {
935 unschedule(delayTurnOffB)
936 state.B_timerStart = false
940 //if none of the above paramenters meet the expectation of the app then turn off
942 if (settings.B_turnOff) {
943 runIn(B_turnOff * 60, "delayTurnOffB")
944 state.B_timerStart = true
948 settings.B_switches?.off()
949 settings.B_dimmers?.setLevel(0)
950 if (state.B_triggered) {
951 runOnce (getMidnight(), midNightReset)
959 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction. Not running scenario.")
965 settings.B_switches?.off()
966 settings.B_dimmers?.setLevel(0)
967 state.B_timerStart = false
968 if (state.B_triggered) {
969 runOnce (getMidnight(), midNightReset)
973 //when physical switch is actuated disable the scenario
975 if ((!B_mode ||B_mode.contains(location.mode)) && getTimeOk (B_timeStart, B_timeEnd) && getDayOk(B_day)) { //checks to make sure we are not opperating outside of set restrictions.
976 if ((!B_luxSensors) || (B_luxSensors.latestValue("illuminance") <= B_turnOnLux)) {
977 if ((!B_triggerOnce || (B_triggerOnce && !state.B_triggered)) && (!B_switchDisable || (B_switchDisable && !state.B_triggered))) {
979 state.B_triggered = true
980 unschedule(delayTurnOffB)
981 runOnce (getMidnight(), midNightReset)
982 log.debug "Physical switch in '${ScenarioNameB}' pressed. Triggers for this scenario disabled."
989 if ((!C_triggerOnce || (C_triggerOnce && !state.C_triggered)) && (!C_switchDisable || (C_switchDisable && !state.C_triggered))) { //Checks to make sure this scenario should be triggered more then once in a day
990 if ((!C_mode || C_mode.contains(location.mode)) && getTimeOk (C_timeStart, C_timeEnd) && getDayOk(C_day) && !state.C_triggered){ //checks to make sure we are not opperating outside of set restrictions.
991 if ((!C_luxSensors) || (C_luxSensors.latestValue("illuminance") <= C_turnOnLux)){ //checks to make sure illimunance is either not cared about or if the value is within the restrictions
992 def C_levelOn = settings.C_level as Integer
994 //Check states of each device to see if they are to be ignored or if they meet the requirments of the app to produce an action.
995 if (getInputOk(C_motion, C_contact, C_lock, C_acceleration)) {
996 log.debug("Motion, Door Open or Unlock Detected Running '${ScenarioNameC}'")
997 settings.C_dimmers?.setLevel(C_levelOn)
998 settings.C_switches?.on()
1000 state.C_triggered = true
1002 runOnce (getMidnight(), midNightReset)
1005 if (state.C_timerStart){
1006 unschedule(delayTurnOffC)
1007 state.C_timerStart = false
1011 //if none of the above paramenters meet the expectation of the app then turn off
1013 if (settings.C_turnOff) {
1014 runIn(C_turnOff * 60, "delayTurnOffC")
1015 state.C_timerStart = true
1018 settings.C_switches?.off()
1019 settings.C_dimmers?.setLevel(0)
1020 if (state.C_triggered) {
1021 runOnce (getMidnight(), midNightReset)
1029 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction. Not running scenario.")
1034 def delayTurnOffC(){
1035 settings.C_switches?.off()
1036 settings.C_dimmers?.setLevel(0)
1037 state.C_timerStart = false
1038 if (state.C_triggered) {
1039 runOnce (getMidnight(), midNightReset)
1044 //when physical switch is actuated disable the scenario
1046 if ((!C_mode || C_mode.contains(location.mode)) && getTimeOk (C_timeStart, C_timeEnd) && getDayOk(C_day) && !state.C_triggered){
1047 if ((!C_luxSensors) || (C_luxSensors.latestValue("illuminance") <= C_turnOnLux)){
1048 if ((!C_triggerOnce || (C_triggerOnce && !state.C_triggered)) && (!C_switchDisable || (C_switchDisable && !state.C_triggered))) {
1050 state.C_triggered = true
1051 unschedule(delayTurnOffC)
1052 runOnce (getMidnight(), midNightReset)
1053 log.debug "Physical switch in '${ScenarioNameC}' pressed. Triggers for this scenario disabled."
1060 if ((!D_triggerOnce || (D_triggerOnce && !state.D_triggered)) && (!D_switchDisable || (D_switchDisable && !state.D_triggered))) { //Checks to make sure this scenario should be triggered more then once in a day
1061 if ((!D_mode || D_mode.contains(location.mode)) && getTimeOk (D_timeStart, D_timeEnd) && getDayOk(D_day) && !state.D_triggered){ //checks to make sure we are not opperating outside of set restrictions.
1062 if ((!D_luxSensors) || (D_luxSensors.latestValue("illuminance") <= D_turnOnLux)){ //checks to make sure illimunance is either not cared about or if the value is within the restrictions
1063 def D_levelOn = D_level as Integer
1065 //Check states of each device to see if they are to be ignored or if they meet the requirments of the app to produce an action.
1066 if (getInputOk(D_motion, D_contact, D_lock, D_acceleration)) {
1067 log.debug("Motion, Door Open or Unlock Detected Running '${ScenarioNameD}'")
1068 settings.D_dimmers?.setLevel(D_levelOn)
1069 settings.D_switches?.on()
1071 state.D_triggered = true
1073 runOnce (getMidnight(), midNightReset)
1076 if (state.D_timerStart){
1077 unschedule(delayTurnOffD)
1078 state.D_timerStart = false
1082 //if none of the above paramenters meet the expectation of the app then turn off
1084 if (settings.D_turnOff) {
1085 runIn(D_turnOff * 60, "delayTurnOffD")
1086 state.D_timerStart = true
1089 settings.D_switches?.off()
1090 settings.D_dimmers?.setLevel(0)
1091 if (state.D_triggered) {
1092 runOnce (getMidnight(), midNightReset)
1099 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction. Not running scenario.")
1104 def delayTurnOffD(){
1105 settings.D_switches?.off()
1106 settings.D_dimmers?.setLevel(0)
1107 state.D_timerStart = false
1108 if (state.D_triggered) {
1109 runOnce (getMidnight(), midNightReset)
1114 //when physical switch is actuated disable the scenario
1116 if ((!D_mode || D_mode.contains(location.mode)) && getTimeOk (D_timeStart, D_timeEnd) && getDayOk(D_day) && !state.D_triggered){ //checks to make sure we are not opperating outside of set restrictions.
1117 if ((!D_luxSensors) || (D_luxSensors.latestValue("illuminance") <= D_turnOnLux)){
1118 if ((!D_triggerOnce || (D_triggerOnce && !state.D_triggered)) && (!D_switchDisable || (D_switchDisable && !state.D_triggered))) {
1120 state.D_triggered = true
1121 unschedule(delayTurnOffD)
1122 runOnce (getMidnight(), midNightReset)
1123 log.debug "Physical switch in '${ScenarioNameD}' pressed. Triggers for this scenario disabled."
1130 //resets once a day trigger at midnight so trigger can be ran again the next day.
1131 def midNightReset() {
1132 state.A_triggered = false
1133 state.B_triggered = false
1134 state.C_triggered = false
1135 state.D_triggered = false
1138 private def helpText() {
1140 "Select motion sensors, acceleration sensors, contact sensors or locks to control a set of lights. " +
1141 "Each scenario can control dimmers and switches but can also be " +
1142 "restricted to modes or between certain times and turned off after " +
1143 "motion stops, doors close or lock. Scenarios can also be limited to " +
1144 "running once or to stop running if the physical switches are turned off."
1148 //should scenario be marked complete or not
1149 def greyOut(scenario){
1157 //should i mark the time restriction green or grey
1158 def greyedOutTime(start, end){
1167 def getTitle(scenario) {
1175 //recursively applies label to each scenario depending on if the scenario has deatils inside it or not
1176 def getDesc(scenario) {
1177 def desc = "Tap to create a scenario"
1179 desc = "Tap to edit scenario"
1186 def midnightToday = timeToday("2000-01-01T23:59:59.999-0000", location.timeZone)
1190 //used to recursively check device states when methods are triggered
1191 private getInputOk(motion, contact, lock, acceleration) {
1193 def motionDetected = false
1194 def accelerationDetected = false
1195 def contactDetected = false
1196 def unlockDetected = false
1200 if (motion.latestValue("motion").contains("active")) {
1201 motionDetected = true
1206 if (acceleration.latestValue("acceleration").contains("active")) {
1207 accelerationDetected = true
1212 if (contact.latestValue("contact").contains("open")) {
1213 contactDetected = true
1218 if (lock.latestValue("lock").contains("unlocked")) {
1219 unlockDetected = true
1223 result = motionDetected || contactDetected || unlockDetected || accelerationDetected
1228 private getTimeOk(starting, ending) {
1230 if (starting && ending) {
1231 def currTime = now()
1232 def start = timeToday(starting).time
1233 def stop = timeToday(ending).time
1234 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
1238 result = currTime >= start
1241 result = currTime <= stop
1244 log.trace "timeOk = $result"
1248 def getTimeLabel(start, end){
1249 def timeLabel = "Tap to set"
1252 timeLabel = "Between" + " " + hhmm(start) + " " + "and" + " " + hhmm(end)
1255 timeLabel = "Start at" + " " + hhmm(start)
1258 timeLabel = "End at" + hhmm(end)
1263 private hhmm(time, fmt = "h:mm a")
1265 def t = timeToday(time, location.timeZone)
1266 def f = new java.text.SimpleDateFormat(fmt)
1267 f.setTimeZone(location.timeZone ?: timeZone(time))
1271 private getDayOk(dayList) {
1274 def df = new java.text.SimpleDateFormat("EEEE")
1275 if (location.timeZone) {
1276 df.setTimeZone(location.timeZone)
1279 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
1281 def day = df.format(new Date())
1282 result = dayList.contains(day)
1288 page(name: "timeIntervalInputA", title: "Only during a certain time", refreshAfterSelection:true) {
1290 input "A_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1291 input "A_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true
1294 page(name: "timeIntervalInputB", title: "Only during a certain time", refreshAfterSelection:true) {
1296 input "B_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1297 input "B_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true
1300 page(name: "timeIntervalInputC", title: "Only during a certain time", refreshAfterSelection:true) {
1302 input "C_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1303 input "C_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true
1306 page(name: "timeIntervalInputD", title: "Only during a certain time", refreshAfterSelection:true) {
1308 input "D_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1309 input "D_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true