1
+ /**
2
+ * Motion and time control
3
+ *
4
+ * Copyright 2017 Geoff Farmer
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7
+ * in compliance with the License. You may obtain a copy of the License at:
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13
+ * for the specific language governing permissions and limitations under the License.
14
+ *
15
+ */
16
+ import groovy.time.TimeCategory
17
+
18
+ definition(
19
+ name : " Motion, light-level and time control" ,
20
+ namespace : " geofffarmer" ,
21
+ author : " Geoff Farmer" ,
22
+ description : " Turn on off lights when motion or light-level at night with brightness dependent on time" ,
23
+ category : " Convenience" ,
24
+ iconUrl : " https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png" ,
25
+ iconX2Url :
" https://s3.amazonaws.com/smartapp-icons/Convenience/[email protected] " ,
26
+ iconX3Url :
" https://s3.amazonaws.com/smartapp-icons/Convenience/[email protected] " )
27
+
28
+ preferences {
29
+ page(name : " outputsPage" , title : " Outputs" , nextPage : " inputsPage" , uninstall : true ) {
30
+ section(" Items to switch on and off" ) {
31
+ paragraph " What should be switched on and off?"
32
+ input " theSwitchLevels" , " capability.switchLevel" , title : " Dimmable lights" , multiple : true
33
+ }
34
+ }
35
+ page(name : " inputsPage" , title : " Motion inputs" , nextPage : " overridesPage" , uninstall : true ) {
36
+ section(" Motion sensors" ) {
37
+ paragraph " Which motion sensors should be the trigger?"
38
+ input " theMotionSensors" , " capability.motionSensor" , title : " Motion Sensors" , multiple : true
39
+ }
40
+ section(" Delay" ) {
41
+ paragraph " How long after motion ends before turning off lights?"
42
+ input(name : " theDelay" , type : " number" , range : " 0..60" , title : " Delay (Minutes)" , required : true )
43
+ }
44
+ }
45
+
46
+ page(name : " activeTimesPage" )
47
+
48
+ page(name : " overridesPage" )
49
+
50
+ page(name : " lowLightTimes" , title : " Low light times" , nextPage : " lightLevels" , uninstall : true ) {
51
+ section(" Start time" ) {
52
+ paragraph " At what time should low light start?"
53
+ input(name : " lowLightStartTime" , type : " time" , title : " Start time" , required : true )
54
+ }
55
+ section(" End time" ) {
56
+ paragraph " At what time should low light end?"
57
+ input(name : " lowLightEndTime" , type : " time" , title : " End time" , required : true )
58
+ }
59
+ }
60
+
61
+ page(name : " lightLevels" , title : " Light brightness levels" , nextPage : " nameAndMode" , uninstall : true ) {
62
+ section(" Low light level" ) {
63
+ paragraph " What brightness should be set at low light times?"
64
+ input(name : " lowLightLevel" , type : " number" , range : " 0..100" , title : " Low light level (0 to 100)" , required : true )
65
+ }
66
+ section(" High light level" ) {
67
+ paragraph " What brightness should be set at high light times??"
68
+ input(name : " highLightLevel" , type : " number" , range : " 0..100" , title : " High light level (0 to 100)" , required : true )
69
+ }
70
+ }
71
+
72
+ page(name : " nameAndMode" , title : " Name app and configure modes" , install : true , uninstall : true ) {
73
+ section(" General settings" ) {
74
+ label title : " Assign a name" , required : false
75
+ mode title : " Set for specific mode(s)" , required : false
76
+ icon title : " Icon" , required : false
77
+ }
78
+ }
79
+ }
80
+
81
+ def overridesPage () {
82
+ dynamicPage(name : " overridesPage" , title : " Overrides" , nextPage : " activeTimesPage" , uninstall : true ) {
83
+ section(" Only run if another device is on" ) {
84
+ paragraph " Should this automation only run if another device is already on?"
85
+ input(name : " onlyRunIfAnotherDeviceOn" , type : " enum" , title : " Select" , options : [" yes" :" Yes" , " no" :" No" ], submitOnChange : true )
86
+ if (onlyRunIfAnotherDeviceOn == " yes" ) {
87
+ input " theOnSwitches" , " capability.switch" , title : " Switches" , multiple : true
88
+ }
89
+ }
90
+ section(" Only run if other devices are off" ) {
91
+ paragraph " Should this automation only run if selected devices are all off?"
92
+ input(name : " onlyRunIfAllDeviceOff" , type : " enum" , title : " Select" , options : [" yes" :" Yes" , " no" :" No" ], submitOnChange : true )
93
+ if (onlyRunIfAllDeviceOff == " yes" ) {
94
+ input " theOffSwitches" , " capability.switch" , title : " Switches" , multiple : true
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ def activeTimesPage () {
101
+ dynamicPage(name : " activeTimesPage" , title : " Active Times" , nextPage : " lowLightTimes" , uninstall : true ) {
102
+ section(" Start time" ) {
103
+ paragraph " When should this automation start?"
104
+ input(name : " startTimeType" , type : " enum" , title : " Start time type?" , options : [" sunset" :" Sunset" , " specificTime" :" Speccific time" ," lightLevel" :" Light level" ], submitOnChange : true )
105
+ switch (startTimeType) {
106
+ case " sunset" :
107
+ input(name : " sunsetStartOffset" , type : " number" , title : " Minutes offset" , required : true )
108
+ break
109
+ case " specificTime" :
110
+ input(name : " specificStartTime" , type : " time" , title : " Specific time" , required : true )
111
+ break
112
+ case " lightLevel" :
113
+ input " theLightLevel1" , " capability.illuminanceMeasurement" , title : " Light level sensor" , multiple : false
114
+ input(name : " luxStartLevel" , type : " number" , title : " Light level (lux)" , required : true )
115
+ break
116
+ }
117
+ }
118
+ section(" End time" ) {
119
+ paragraph " When should this automation end?"
120
+ input(name : " endTimeType" , type : " enum" , title : " End time type?" , options : [" sunrise" :" Sunrise" , " specificTime" :" Speccific time" ," lightLevel" :" Light Level" ], submitOnChange : true )
121
+ switch (endTimeType) {
122
+ case " sunrise" :
123
+ input(name : " sunriseEndOffset" , type : " number" , title : " Minutes offset" , required : true )
124
+ break
125
+ case " specificTime" :
126
+ input(name : " specificEndTime" , type : " time" , title : " Specific time" , required : true )
127
+ break
128
+ case " lightLevel" :
129
+ input " theLightLevel2" , " capability.illuminanceMeasurement" , title : " Light level sensor" , multiple : false
130
+ input(name : " luxEndLevel" , type : " number" , title : " Light level (lux)" , required : true )
131
+ break
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ def installed () {
138
+ log. debug " Installed with settings: ${ settings} "
139
+ initialize()
140
+ }
141
+
142
+ def updated () {
143
+ log. debug " Updated with settings: ${ settings} "
144
+ unsubscribe()
145
+ initialize()
146
+ }
147
+
148
+ def initialize () {
149
+ subscribe(theMotionSensors, " motion" , motionHandler)
150
+ // subscribe(theOnSwitches, "switch.on", eventHandler)
151
+ }
152
+
153
+ def motionHandler (evt ) {
154
+ def startTime = getSunriseAndSunset(). sunset + sunsetStartOffset. minutes
155
+ def endTime = getSunriseAndSunset(). sunrise + sunriseEndOffset. minutes
156
+ def luxLevelLow = false
157
+ switch (startTimeType) {
158
+ case " specificTime" :
159
+ startTime = specificStartTime
160
+ break
161
+ case " sunset" :
162
+ use(TimeCategory ) {
163
+ startTime = getSunriseAndSunset(). sunset + sunsetStartOffset. minutes
164
+ }
165
+ break
166
+ case " lightLevel" :
167
+ if (theLightLevel1. currentIlluminance < luxStartLevel) {
168
+ luxLevelLow = true
169
+ }
170
+ break
171
+ }
172
+ switch (endTimeType) {
173
+ case " specificTime" :
174
+ endTime = specificEndTime
175
+ break
176
+ case " sunrise" :
177
+ use(TimeCategory ) {
178
+ endTime = getSunriseAndSunset(). sunrise + sunriseEndOffset. minutes
179
+ }
180
+ break
181
+ case " lightLevel" :
182
+ if (theLightLevel2. currentIlluminance > luxEndLevel) {
183
+ luxLevelLow = false
184
+ }
185
+ break
186
+ }
187
+ def active = luxLevelLow || ! timeOfDayIsBetween(endTime, startTime, new Date (), location. timeZone)
188
+ def lowLight = ! timeOfDayIsBetween(lowLightEndTime, lowLightStartTime, new Date (), location. timeZone)
189
+ if ((active && noOverride()) || state. switchOffPending) {
190
+ if (noMotion()) {
191
+ runIn(60 * theDelay, turnOffLights)
192
+ } else {
193
+ if (active && noOverride()) {
194
+ if (lowLight) {
195
+ if (lowLightLevel != 0 ) {
196
+ theSwitchLevels. setLevel(lowLightLevel)
197
+ state. switchOffPending = true
198
+ unschedule()
199
+ }
200
+ } else {
201
+ theSwitchLevels. setLevel(highLightLevel)
202
+ state. switchOffPending = true
203
+ unschedule()
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ def turnOffLights () {
211
+ theSwitchLevels. setLevel(0 )
212
+ state. switchOffPending = false
213
+ }
214
+
215
+ private noMotion () {
216
+ def result = true
217
+ theMotionSensors. each {n -> if (n. currentValue(" motion" ) == " active" ) result = false }
218
+ return result
219
+ }
220
+
221
+ private noOverride () {
222
+ def result = false
223
+ if (onlyRunIfAnotherDeviceOn == " no" ) {
224
+ result = true
225
+ } else {
226
+ theOnSwitches. each {n -> if (n. currentValue(" switch" ) == " on" ) result = true }
227
+ }
228
+ if (onlyRunIfAllDeviceOff == " yes" ) {
229
+ theOffSwitches. each {n -> if (n. currentValue(" switch" ) == " on" ) result = false }
230
+ }
231
+ return result
232
+ }
0 commit comments