Update step-notifier.groovy
[smartapps.git] / official / lighting-director.groovy
1 /**
2  *  Lighting Director
3  *
4  * Source: https://github.com/tslagle13/SmartThings/blob/master/Director-Series-Apps/Lighting-Director/Lighting%20Director.groovy
5  *
6  *  Current Version: 2.9.4
7  *
8  *
9  *  Changelog:
10  *  Version - 1.3
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
27  *
28 *  Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
29  *
30  *  Copyright 2015 Tim Slagle and Michael Struck
31  *
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:
34  *
35  *      http://www.apache.org/licenses/LICENSE-2.0
36  *
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.
40  *
41  */
42  
43 definition(
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")
52
53 preferences {
54     page name:"pageSetup"
55     page name:"pageSetupScenarioA"
56     page name:"pageSetupScenarioB"
57     page name:"pageSetupScenarioC"
58     page name:"pageSetupScenarioD"
59 }
60
61 // Show setup page
62 def pageSetup() {
63
64     def pageProperties = [
65         name:       "pageSetup",
66         nextPage:   null,
67         install:    true,
68         uninstall:  true
69     ]
70
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)
77             }
78         section([title:"Options", mobileOnly:true]) {
79             label title:"Assign a name", required:false
80         }
81     }
82 }
83
84 // Show "pageSetupScenarioA" page
85 def pageSetupScenarioA() {
86
87     def inputLightsA = [
88         name:       "A_switches",
89         type:       "capability.switch",
90         title:      "Control the following switches...",
91         multiple:   true,
92         required:   false
93     ]
94     def inputDimmersA = [
95         name:       "A_dimmers",
96         type:       "capability.switchLevel",
97         title:      "Dim the following...",
98         multiple:   true,
99         required:   false
100     ]
101
102     def inputMotionA = [
103         name:       "A_motion",
104         type:       "capability.motionSensor",
105         title:      "Using these motion sensors...",
106         multiple:   true,
107         required:   false
108     ]
109     
110         def inputAccelerationA = [
111                 name:       "A_acceleration",
112                 type:       "capability.accelerationSensor",
113                 title:      "Or using these acceleration sensors...",
114                 multiple:   true,
115                 required:   false
116         ]
117     def inputContactA = [
118         name:       "A_contact",
119         type:       "capability.contactSensor",
120         title:      "Or using these contact sensors...",
121         multiple:   true,
122         required:   false
123     ]
124     
125     def inputTriggerOnceA = [
126         name:       "A_triggerOnce",
127         type:       "bool",
128         title:      "Trigger only once per day...",
129         defaultValue:false
130     ]
131     
132     def inputSwitchDisableA = [
133         name:       "A_switchDisable",
134         type:       "bool",
135         title:      "Stop triggering if physical switches/dimmers are turned off...",
136         defaultValue:false
137     ]
138     
139     def inputLockA = [
140         name:       "A_lock",
141         type:       "capability.lock",
142         title:      "Or using these locks...",
143         multiple:   true,
144         required:   false
145     ]
146     
147     def inputModeA = [
148         name:       "A_mode",
149         type:       "mode",
150         title:      "Only during the following modes...",
151         multiple:   true,
152         required:   false
153     ]
154     
155     def inputDayA = [
156         name:       "A_day",
157         type:       "enum",
158         options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
159         title:      "Only on certain days of the week...",
160         multiple:   true,
161         required:   false
162     ]
163     
164     
165     def inputLevelA = [
166         name:       "A_level",
167         type:       "enum",
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",
170         multiple:   false,
171         required:   false
172     ]
173     
174     def inputTurnOnLuxA = [
175         name:       "A_turnOnLux",
176         type:       "number",
177         title:      "Only run this scenario if lux is below...",
178         multiple:   false,
179         required:   false
180     ]
181     
182     def inputLuxSensorsA = [
183         name:       "A_luxSensors",
184         type:       "capability.illuminanceMeasurement",
185         title:      "On these lux sensors",
186         multiple:   false,
187         required:   false
188     ]
189     
190     def inputTurnOffA = [
191         name:       "A_turnOff",
192         type:       "number",
193         title:      "Turn off this scenario after motion stops or doors close/lock (minutes)...",
194         multiple:   false,
195         required:   false
196     ]
197     
198     def inputScenarioNameA = [
199         name:       "ScenarioNameA",
200         type:       "text",
201         title:      "Scenario Name",
202         multiple:   false,
203         required:   false,
204         defaultValue: empty
205     ]
206     
207     def pageProperties = [
208         name:       "pageSetupScenarioA",
209     ]
210
211     return dynamicPage(pageProperties) {
212 section("Name your scenario") {
213             input inputScenarioNameA
214         }
215
216 section("Devices included in the scenario") {
217             input inputMotionA
218                         input inputAccelerationA
219             input inputContactA
220             input inputLockA
221             input inputLightsA
222             input inputDimmersA
223             }
224
225 section("Scenario settings") {
226             input inputLevelA
227             input inputTurnOnLuxA
228             input inputLuxSensorsA
229             input inputTurnOffA
230             }
231             
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
236             input inputDayA
237             input inputModeA
238             }
239
240 section("Help") {
241             paragraph helpText()
242             }
243     }
244     
245 }
246
247 def pageSetupScenarioB() {
248
249     def inputLightsB = [
250         name:       "B_switches",
251         type:       "capability.switch",
252         title:      "Control the following switches...",
253         multiple:   true,
254         required:   false
255     ]
256     def inputDimmersB = [
257         name:       "B_dimmers",
258         type:       "capability.switchLevel",
259         title:      "Dim the following...",
260         multiple:   true,
261         required:   false
262     ]
263     
264     def inputTurnOnLuxB = [
265         name:       "B_turnOnLux",
266         type:       "number",
267         title:      "Only run this scenario if lux is below...",
268         multiple:   false,
269         required:   false
270     ]
271     
272     def inputLuxSensorsB = [
273         name:       "B_luxSensors",
274         type:       "capability.illuminanceMeasurement",
275         title:      "On these lux sensors",
276         multiple:   false,
277         required:   false
278     ]
279
280     def inputMotionB = [
281         name:       "B_motion",
282         type:       "capability.motionSensor",
283         title:      "Using these motion sensors...",
284         multiple:   true,
285         required:   false
286     ]
287     
288         def inputAccelerationB = [
289                 name:       "B_acceleration",
290                 type:       "capability.accelerationSensor",
291                 title:      "Or using these acceleration sensors...",
292                 multiple:   true,
293                 required:   false
294         ]
295     def inputContactB = [
296         name:       "B_contact",
297         type:       "capability.contactSensor",
298         title:      "Or using these contact sensors...",
299         multiple:   true,
300         required:   false
301     ]
302     
303     def inputTriggerOnceB = [
304         name:       "B_triggerOnce",
305         type:       "bool",
306         title:      "Trigger only once per day...",
307         defaultValue:false
308     ]
309     
310     def inputSwitchDisableB = [
311         name:       "B_switchDisable",
312         type:       "bool",
313         title:      "Stop triggering if physical switches/dimmers are turned off...",
314         defaultValue:false
315     ]
316     
317     def inputLockB = [
318         name:       "B_lock",
319         type:       "capability.lock",
320         title:      "Or using these locks...",
321         multiple:   true,
322         required:   false
323     ]
324     
325     def inputModeB = [
326         name:       "B_mode",
327         type:       "mode",
328         title:      "Only during the following modes...",
329         multiple:   true,
330         required:   false
331     ]
332     
333     def inputDayB = [
334         name:       "B_day",
335         type:       "enum",
336         options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
337         title:      "Only on certain days of the week...",
338         multiple:   true,
339         required:   false
340     ]
341     
342     def inputLevelB = [
343         name:       "B_level",
344         type:       "enum",
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",
347         multiple:   false,
348         required:   false
349     ]
350     
351     def inputTurnOffB = [
352         name:       "B_turnOff",
353         type:       "number",
354         title:      "Turn off this scenario after motion stops or doors close/lock (minutes)...",
355         multiple:   false,
356         required:   false
357     ]
358     
359     def inputScenarioNameB = [
360         name:       "ScenarioNameB",
361         type:       "text",
362         title:      "Scenario Name",
363         multiple:   false,
364         required:   false,
365         defaultValue: empty
366     ]
367     
368     def pageProperties = [
369         name:       "pageSetupScenarioB",
370     ]
371
372     return dynamicPage(pageProperties) {
373 section("Name your scenario") {
374             input inputScenarioNameB
375         }
376
377 section("Devices included in the scenario") {
378             input inputMotionB
379                         input inputAccelerationB
380                         input inputContactB
381             input inputLockB
382             input inputLightsB
383             input inputDimmersB
384             }
385
386 section("Scenario settings") {
387             input inputLevelB
388             input inputTurnOnLuxB
389             input inputLuxSensorsB
390             input inputTurnOffB
391             }
392             
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
397             input inputDayB
398             input inputModeB
399             }
400
401 section("Help") {
402             paragraph helpText()
403             }
404     }
405 }
406
407 def pageSetupScenarioC() {
408
409     def inputLightsC = [
410         name:       "C_switches",
411         type:       "capability.switch",
412         title:      "Control the following switches...",
413         multiple:   true,
414         required:   false
415     ]
416     def inputDimmersC = [
417         name:       "C_dimmers",
418         type:       "capability.switchLevel",
419         title:      "Dim the following...",
420         multiple:   true,
421         required:   false
422     ]
423
424     def inputMotionC = [
425         name:       "C_motion",
426         type:       "capability.motionSensor",
427         title:      "Using these motion sensors...",
428         multiple:   true,
429         required:   false
430     ]
431     
432         def inputAccelerationC = [
433                 name:       "C_acceleration",
434                 type:       "capability.accelerationSensor",
435                 title:      "Or using these acceleration sensors...",
436                 multiple:   true,
437                 required:   false
438         ]
439     def inputContactC = [
440         name:       "C_contact",
441         type:       "capability.contactSensor",
442         title:      "Or using these contact sensors...",
443         multiple:   true,
444         required:   false
445     ]
446     
447     def inputTriggerOnceC = [
448         name:       "C_triggerOnce",
449         type:       "bool",
450         title:      "Trigger only once per day...",
451         defaultValue:false
452     ]
453     
454     def inputSwitchDisableC = [
455         name:       "C_switchDisable",
456         type:       "bool",
457         title:      "Stop triggering if physical switches/dimmers are turned off...",
458         defaultValue:false
459     ]
460     
461     def inputLockC = [
462         name:       "C_lock",
463         type:       "capability.lock",
464         title:      "Or using these locks...",
465         multiple:   true,
466         required:   false
467     ]
468     
469     def inputModeC = [
470         name:       "C_mode",
471         type:       "mode",
472         title:      "Only during the following modes...",
473         multiple:   true,
474         required:   false
475     ]
476     
477     def inputDayC = [
478         name:       "C_day",
479         type:       "enum",
480         options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
481         title:      "Only on certain days of the week...",
482         multiple:   true,
483         required:   false
484     ]
485     
486     def inputLevelC = [
487         name:       "C_level",
488         type:       "enum",
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",
491         multiple:   false,
492         required:   false
493     ]
494     
495     def inputTurnOffC = [
496         name:       "C_turnOff",
497         type:       "number",
498         title:      "Turn off this scenario after motion stops or doors close/lock (minutes)...",
499         multiple:   false,
500         required:   false
501     ]
502     
503     def inputScenarioNameC = [
504         name:       "ScenarioNameC",
505         type:       "text",
506         title:      "Scenario Name",
507         multiple:   false,
508         required:   false,
509         defaultValue: empty
510     ]
511     
512     def inputTurnOnLuxC = [
513         name:       "C_turnOnLux",
514         type:       "number",
515         title:      "Only run this scenario if lux is below...",
516         multiple:   false,
517         required:   false
518     ]
519     
520     def inputLuxSensorsC = [
521         name:       "C_luxSensors",
522         type:       "capability.illuminanceMeasurement",
523         title:      "On these lux sensors",
524         multiple:   false,
525         required:   false
526     ]
527     
528     def pageProperties = [
529         name:       "pageSetupScenarioC",
530     ]
531
532     return dynamicPage(pageProperties) {
533         section("Name your scenario") {
534             input inputScenarioNameC
535         }
536
537 section("Devices included in the scenario") {
538             input inputMotionC
539                         input inputAccelerationC
540             input inputContactC
541             input inputLockC
542             input inputLightsC
543             input inputDimmersC
544             }
545
546 section("Scenario settings") {
547             input inputLevelC
548             input inputTurnOnLuxC
549             input inputLuxSensorsC
550             input inputTurnOffC
551                         }
552             
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
557             input inputDayC
558             input inputModeC
559             }
560
561 section("Help") {
562             paragraph helpText()
563             }
564     }
565 }
566
567 def pageSetupScenarioD() {
568
569     def inputLightsD = [
570         name:       "D_switches",
571         type:       "capability.switch",
572         title:      "Control the following switches...",
573         multiple:   true,
574         required:   false
575     ]
576     def inputDimmersD = [
577         name:       "D_dimmers",
578         type:       "capability.switchLevel",
579         title:      "Dim the following...",
580         multiple:   true,
581         required:   false
582     ]
583
584     def inputMotionD = [
585         name:       "D_motion",
586         type:       "capability.motionSensor",
587         title:      "Using these motion sensors...",
588         multiple:   true,
589         required:   false
590     ]
591     
592         def inputAccelerationD = [
593                 name:       "D_acceleration",
594                 type:       "capability.accelerationSensor",
595                 title:      "Or using these acceleration sensors...",
596                 multiple:   true,
597                 required:   false
598         ]
599     def inputContactD = [
600         name:       "D_contact",
601         type:       "capability.contactSensor",
602         title:      "Or using these contact sensors...",
603         multiple:   true,
604         required:   false
605     ]
606     
607     def inputLockD = [
608         name:       "D_lock",
609         type:       "capability.lock",
610         title:      "Or using these locks...",
611         multiple:   true,
612         required:   false
613     ]
614     
615     def inputModeD = [
616         name:       "D_mode",
617         type:       "mode",
618         title:      "Only during the following modes...",
619         multiple:   true,
620         required:   false
621     ]
622     
623     def inputTriggerOnceD = [
624         name:       "D_triggerOnce",
625         type:       "bool",
626         title:      "Trigger only once per day...",
627         defaultValue:false
628     ]
629     
630     def inputSwitchDisableD = [
631         name:       "D_switchDisable",
632         type:       "bool",
633         title:      "Stop triggering if physical switches/dimmers are turned off...",
634         defaultValue:false
635     ]
636     
637     def inputDayD = [
638         name:       "D_day",
639         type:       "enum",
640         options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
641         title:      "Only on certain days of the week...",
642         multiple:   true,
643         required:   false
644     ]
645     
646     
647     def inputLevelD = [
648         name:       "D_level",
649         type:       "enum",
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",
652         multiple:   false,
653         required:   false
654     ]
655     
656     def inputTurnOffD = [
657         name:       "D_turnOff",
658         type:       "number",
659         title:      "Turn off this scenario after motion stops, doors close or close/lock (minutes)...",
660         multiple:   false,
661         required:   false
662     ]
663     
664     def inputScenarioNameD = [
665         name:       "ScenarioNameD",
666         type:       "text",
667         title:      "Scenario Name",
668         multiple:   false,
669         required:   false,
670         defaultValue: empty
671     ]
672     
673     def inputTurnOnLuxD = [
674         name:       "D_turnOnLux",
675         type:       "number",
676         title:      "Only run this scenario if lux is below...",
677         multiple:   false,
678         required:   false
679     ]
680     
681     def inputLuxSensorsD = [
682         name:       "D_luxSensors",
683         type:       "capability.illuminanceMeasurement",
684         title:      "On these lux sensors",
685         multiple:   false,
686         required:   false
687     ]
688
689     def pageProperties = [
690         name:       "pageSetupScenarioD",
691     ]
692
693     return dynamicPage(pageProperties) {
694         section("Name your scenario") {
695             input inputScenarioNameD
696         }
697
698 section("Devices included in the scenario") {
699             input inputMotionD
700                         input inputAccelerationD
701                 input inputContactD
702             input inputLockD
703             input inputLightsD
704             input inputDimmersD
705             }
706
707 section("Scenario settings") {
708             input inputLevelD
709             input inputTurnOnLuxD
710             input inputLuxSensorsD
711             input inputTurnOffD
712                         }
713             
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
718             input inputDayD
719             input inputModeD
720             }
721
722 section("Help") {
723             paragraph helpText()
724             }
725     }
726 }
727
728 def installed() {
729     initialize()
730 }
731
732 def updated() {
733     unschedule()
734     unsubscribe()
735     initialize()
736 }
737
738 def initialize() {
739
740 midNightReset()
741
742 if(A_motion) {
743         subscribe(settings.A_motion, "motion", onEventA)
744 }
745
746 if(A_acceleration) {
747         subscribe(settings.A_acceleration, "acceleration", onEventA)
748 }
749
750 if(A_contact) {
751         subscribe(settings.A_contact, "contact", onEventA)
752 }
753
754 if(A_lock) {
755         subscribe(settings.A_lock, "lock", onEventA)
756 }
757
758 if(A_switchDisable) {
759         subscribe(A_switches, "switch.off", onPressA)
760     subscribe(A_dimmers, "switch.off", onPressA)
761 }
762
763 if(A_mode) {
764     subscribe(location, onEventA)
765 }
766
767 if(B_motion) {
768         subscribe(settings.B_motion, "motion", onEventB)
769 }
770
771 if(B_acceleration) {
772         subscribe(settings.B_acceleration, "acceleration", onEventB)
773 }
774
775 if(B_contact) {
776         subscribe(settings.B_contact, "contact", onEventB)
777 }
778
779 if(B_lock) {
780         subscribe(settings.B_lock, "lock", onEventB)
781 }
782
783 if(B_switchDisable) {
784         subscribe(B_switches, "switch.off", onPressB)
785     subscribe(B_dimmers, "switch.off", onPressB)
786 }
787
788 if(B_mode) {
789     subscribe(location, onEventB)
790 }
791
792 if(C_motion) {
793         subscribe(settings.C_motion, "motion", onEventC)
794 }
795
796 if(C_acceleration) {
797         subscribe(settings.C_acceleration, "acceleration", onEventC)
798 }
799
800 if(C_contact) {
801         subscribe(settings.C_contact, "contact", onEventC)
802 }
803
804 if(C_lock) {
805         subscribe(settings.C_lock, "lock", onEventC)
806 }
807
808 if(C_switchDisable) {
809         subscribe(C_switches, "switch.off", onPressC)
810     subscribe(C_dimmers, "switch.off", onPressC)
811 }
812
813 if(C_mode) {
814     subscribe(location, onEventC)
815 }
816
817 if(D_motion) {
818         subscribe(settings.D_motion, "motion", onEventD)
819 }
820
821 if(D_acceleration) {
822         subscribe(settings.D_acceleration, "acceleration", onEventD)
823 }
824
825 if(D_contact) {
826         subscribe(settings.D_contact, "contact", onEventD)
827 }
828
829 if(D_lock) {
830         subscribe(settings.D_lock, "lock", onEventD)
831 }
832
833 if(D_switchDisable) {
834         subscribe(D_switches, "switch.off", onPressD)
835     subscribe(D_dimmers, "switch.off", onPressD)
836 }
837
838 if(D_mode) {
839     subscribe(location, onEventD)
840 }
841
842 }
843
844 def onEventA(evt) {
845
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
850
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()
856             if (A_triggerOnce){
857                 state.A_triggered = true
858                 if (!A_turnOff) {
859                                         runOnce (getMidnight(), midNightReset)
860                 }
861             }
862                 if (state.A_timerStart){
863                 unschedule(delayTurnOffA)
864                 state.A_timerStart = false
865                 }
866 }
867
868 //if none of the above paramenters meet the expectation of the app then turn off
869 else {
870                 
871         if (settings.A_turnOff) {
872                         runIn(A_turnOff * 60, "delayTurnOffA")
873                 state.A_timerStart = true
874         }
875         else {
876                 settings.A_switches?.off()
877                         settings.A_dimmers?.setLevel(0)
878                 if (state.A_triggered) {
879                         runOnce (getMidnight(), midNightReset)
880                 }
881         }
882 }
883 }
884 }
885 else{
886 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction.  Not running scenario.")
887 }
888 }
889 }
890
891 def delayTurnOffA(){
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)
897     }
898
899 }
900
901 //when physical switch is actuated disable the scenario
902 def onPressA(evt) {
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))) {    
906     if (evt.physical){
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."
911         }
912 }
913 }}}
914
915 def onEventB(evt) {
916
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
921
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)) {
924                 
925                 log.debug("Motion, Door Open or Unlock Detected Running '${ScenarioNameB}'")
926                         settings.B_dimmers?.setLevel(B_levelOn)
927             settings.B_switches?.on()
928             if (B_triggerOnce){
929                 state.B_triggered = true
930                 if (!B_turnOff) {
931                                         runOnce (getMidnight(), midNightReset)
932                 }
933             }
934                 if (state.B_timerStart) {
935                 unschedule(delayTurnOffB)
936                 state.B_timerStart = false
937                 }
938 }
939
940 //if none of the above paramenters meet the expectation of the app then turn off
941 else {
942         if (settings.B_turnOff) {
943                         runIn(B_turnOff * 60, "delayTurnOffB")
944                 state.B_timerStart = true
945         }
946         
947         else {
948                 settings.B_switches?.off()
949                         settings.B_dimmers?.setLevel(0)
950             if (state.B_triggered) {
951                         runOnce (getMidnight(), midNightReset)
952                 }
953         }
954         
955 }
956 }
957 }
958 else{
959 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction.  Not running scenario.")
960 }
961 }
962 }
963
964 def delayTurnOffB(){
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) 
970         }
971 }
972
973 //when physical switch is actuated disable the scenario
974 def onPressB(evt) {
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))) {
978         if (evt.physical){
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."
983         }
984 }
985 }}}
986
987 def onEventC(evt) {
988
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
993
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()
999             if (C_triggerOnce){
1000                 state.C_triggered = true
1001                 if (!C_turnOff) {
1002                                         runOnce (getMidnight(), midNightReset)
1003                 }
1004             }
1005                 if (state.C_timerStart){
1006                 unschedule(delayTurnOffC)
1007                 state.C_timerStart = false
1008                 }
1009 }
1010
1011 //if none of the above paramenters meet the expectation of the app then turn off
1012 else {
1013         if (settings.C_turnOff) {
1014                 runIn(C_turnOff * 60, "delayTurnOffC")
1015         state.C_timerStart = true
1016         }
1017         else {
1018         settings.C_switches?.off()
1019                 settings.C_dimmers?.setLevel(0)
1020                 if (state.C_triggered) {
1021                         runOnce (getMidnight(), midNightReset)
1022                 }
1023         }
1024         
1025 }
1026 }
1027 }
1028 else{
1029 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction.  Not running scenario.")
1030 }
1031 }
1032 }
1033
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)
1040     }
1041
1042 }
1043
1044 //when physical switch is actuated disable the scenario
1045 def onPressC(evt) {
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))) {
1049         if (evt.physical){
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."
1054         }
1055 }
1056 }}}
1057
1058 def onEventD(evt) {
1059
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
1064
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()
1070             if (D_triggerOnce){
1071                 state.D_triggered = true
1072                 if (!D_turnOff) {
1073                                         runOnce (getMidnight(), midNightReset)
1074                 }
1075             }
1076                 if (state.D_timerStart){
1077                 unschedule(delayTurnOffD)
1078                 state.D_timerStart = false
1079                 }
1080 }
1081
1082 //if none of the above paramenters meet the expectation of the app then turn off
1083 else {
1084         if (settings.D_turnOff) {
1085                 runIn(D_turnOff * 60, "delayTurnOffD")
1086         state.D_timerStart = true
1087         }
1088         else {
1089         settings.D_switches?.off()
1090                 settings.D_dimmers?.setLevel(0)
1091                 if (state.D_triggered) {
1092                         runOnce (getMidnight(), midNightReset)
1093                 }
1094         }
1095 }
1096 }
1097 }
1098 else{
1099 log.debug("Motion, Contact or Unlock detected outside of mode or time/day restriction.  Not running scenario.")
1100 }
1101 }
1102 }
1103
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)
1110     }
1111
1112 }
1113
1114 //when physical switch is actuated disable the scenario
1115 def onPressD(evt) {
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))) {  
1119         if (evt.physical){
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."
1124         }
1125 }
1126 }}}
1127
1128 //Common Methods
1129
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
1136 }
1137
1138 private def helpText() {
1139         def text =
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."
1145         text
1146 }
1147
1148 //should scenario be marked complete or not
1149 def greyOut(scenario){
1150         def result = ""
1151     if (scenario) {
1152         result = "complete"     
1153     }
1154     result
1155 }
1156
1157 //should i mark the time restriction green or grey
1158 def greyedOutTime(start, end){
1159         def result = ""
1160     if (start || end) {
1161         result = "complete"     
1162     }
1163     result
1164 }
1165
1166
1167 def getTitle(scenario) {
1168         def title = "Empty"
1169         if (scenario) {
1170                 title = scenario
1171     }
1172         title
1173 }
1174
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"
1178         if (scenario) {
1179                 desc = "Tap to edit scenario"
1180     }
1181         desc    
1182 }
1183
1184
1185 def getMidnight() {
1186         def midnightToday = timeToday("2000-01-01T23:59:59.999-0000", location.timeZone)
1187         midnightToday
1188 }
1189
1190 //used to recursively check device states when methods are triggered 
1191 private getInputOk(motion, contact, lock, acceleration) {
1192
1193 def motionDetected = false
1194 def accelerationDetected = false
1195 def contactDetected = false
1196 def unlockDetected = false
1197 def result = false
1198
1199 if (motion) {
1200         if (motion.latestValue("motion").contains("active")) {
1201                 motionDetected = true
1202         }
1203 }
1204
1205 if (acceleration) {
1206         if (acceleration.latestValue("acceleration").contains("active")) {
1207                 accelerationDetected = true
1208         }
1209 }
1210
1211 if (contact) {
1212         if (contact.latestValue("contact").contains("open")) {
1213                 contactDetected = true
1214         }
1215 }
1216
1217 if (lock) {
1218         if (lock.latestValue("lock").contains("unlocked")) {
1219                 unlockDetected = true
1220         }
1221 }
1222
1223 result = motionDetected || contactDetected || unlockDetected || accelerationDetected
1224 result
1225
1226 }
1227
1228 private getTimeOk(starting, ending) {
1229         def result = true
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
1235         }
1236     
1237     else if (starting){
1238         result = currTime >= start
1239     }
1240     else if (ending){
1241         result = currTime <= stop
1242     }
1243     
1244         log.trace "timeOk = $result"
1245         result
1246 }
1247
1248 def getTimeLabel(start, end){
1249         def timeLabel = "Tap to set"
1250         
1251     if(start && end){
1252         timeLabel = "Between" + " " + hhmm(start) + " "  + "and" + " " +  hhmm(end)
1253     }
1254     else if (start) {
1255                 timeLabel = "Start at" + " " + hhmm(start)
1256     }
1257     else if(end){
1258     timeLabel = "End at" + hhmm(end)
1259     }
1260         timeLabel       
1261 }
1262
1263 private hhmm(time, fmt = "h:mm a")
1264 {
1265         def t = timeToday(time, location.timeZone)
1266         def f = new java.text.SimpleDateFormat(fmt)
1267         f.setTimeZone(location.timeZone ?: timeZone(time))
1268         f.format(t)
1269 }
1270
1271 private getDayOk(dayList) {
1272         def result = true
1273     if (dayList) {
1274                 def df = new java.text.SimpleDateFormat("EEEE")
1275                 if (location.timeZone) {
1276                         df.setTimeZone(location.timeZone)
1277                 }
1278                 else {
1279                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
1280                 }
1281                 def day = df.format(new Date())
1282                 result = dayList.contains(day)
1283         }
1284     result
1285 }
1286
1287
1288 page(name: "timeIntervalInputA", title: "Only during a certain time", refreshAfterSelection:true) {
1289                 section {
1290                         input "A_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1291                         input "A_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true
1292                 }
1293         }  
1294 page(name: "timeIntervalInputB", title: "Only during a certain time", refreshAfterSelection:true) {
1295                 section {
1296                         input "B_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1297                         input "B_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true
1298                 }
1299         }  
1300 page(name: "timeIntervalInputC", title: "Only during a certain time", refreshAfterSelection:true) {
1301                 section {
1302                         input "C_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1303                         input "C_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true
1304                 }
1305         }         
1306 page(name: "timeIntervalInputD", title: "Only during a certain time", refreshAfterSelection:true) {
1307                 section {
1308                         input "D_timeStart", "time", title: "Starting", required: false, refreshAfterSelection:true
1309                         input "D_timeEnd", "time", title: "Ending", required: false, refreshAfterSelection:true
1310                 }
1311         }