Update double-tap.groovy
[smartapps.git] / official / hue-mood-lighting.groovy
1 /**
2  *  Copyright 2015 SmartThings
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  *  in compliance with the License. You may obtain a copy of the License at:
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
10  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
11  *  for the specific language governing permissions and limitations under the License.
12  *
13  *  Hue Mood Lighting
14  *
15  *  Author: SmartThings
16  *  *
17  *  Date: 2014-02-21
18  */
19 definition(
20     name: "Hue Mood Lighting",
21     namespace: "smartthings",
22     author: "SmartThings",
23     description: "Sets the colors and brightness level of your Philips Hue lights to match your mood.",
24     category: "SmartThings Labs",
25     iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
26     iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png"
27 )
28
29 preferences {
30         page(name: "mainPage", title: "Adjust the color of your Hue lights to match your mood.", install: true, uninstall: true)
31         page(name: "timeIntervalInput", title: "Only during a certain time") {
32                 section {
33                         input "starting", "time", title: "Starting", required: false
34                         input "ending", "time", title: "Ending", required: false
35                 }
36         }
37 }
38
39 // input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
40 // input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
41 // input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
42 // input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
43 // input "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
44 // input "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
45 // input "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
46 // input "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
47 // input "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
48 // input "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
49 // input "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
50 // input "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
51 // input "timeOfDay", "time", title: "At a Scheduled Time", required: false
52
53 def mainPage() {
54         dynamicPage(name: "mainPage") {
55                 def anythingSet = anythingSet()
56                 if (anythingSet) {
57                         section("Set the lighting mood when..."){
58                                 ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
59                                 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
60                                 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
61                                 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
62                                 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
63                                 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
64                                 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
65                                 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
66                                 ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
67                                 ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
68                                 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
69                                 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
70                                 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
71                         }
72                 }
73                 section(anythingSet ? "Select additional mood lighting triggers" : "Set the lighting mood when...", hideable: anythingSet, hidden: true){
74                         ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
75                         ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
76                         ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
77                         ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
78                         ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
79                         ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
80                         ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
81                         ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
82                         ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
83                         ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
84                         ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
85                         ifUnset "triggerModes", "mode", title: "System Changes Mode", description: "Select mode(s)", required: false, multiple: true
86                         ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
87                 }
88                 section("Control these bulbs...") {
89                         input "hues", "capability.colorControl", title: "Which Hue Bulbs?", required:true, multiple:true
90                 }
91                 section("Choose light effects...")
92                         {
93                                 input "color", "enum", title: "Hue Color?", required: false, multiple:false, options: [
94                                         ["Soft White":"Soft White - Default"],
95                                         ["White":"White - Concentrate"],
96                                         ["Daylight":"Daylight - Energize"],
97                                         ["Warm White":"Warm White - Relax"],
98                                         "Red","Green","Blue","Yellow","Orange","Purple","Pink"]
99                                 input "lightLevel", "enum", title: "Light Level?", required: false, options: [10,20,30,40,50,60,70,80,90,100]
100                         }
101
102                 section("More options", hideable: true, hidden: true) {
103                         input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
104                         href "timeIntervalInput", title: "Only during a certain time"
105                         input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
106                                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
107                         input "modes", "mode", title: "Only when mode is", multiple: true, required: false
108                         input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
109                 }
110                 section([mobileOnly:true]) {
111                         label title: "Assign a name", required: false
112                         mode title: "Set for specific mode(s)", required: false
113                 }
114         }
115 }
116 private anythingSet() {
117         for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
118                 if (settings[name]) {
119                         return true
120                 }
121         }
122         return false
123 }
124
125 private ifUnset(Map options, String name, String capability) {
126         if (!settings[name]) {
127                 input(options, name, capability)
128         }
129 }
130
131 private ifSet(Map options, String name, String capability) {
132         if (settings[name]) {
133                 input(options, name, capability)
134         }
135 }
136
137 def installed() {
138         log.debug "Installed with settings: ${settings}"
139         subscribeToEvents()
140 }
141
142 def updated() {
143         log.debug "Updated with settings: ${settings}"
144         unsubscribe()
145         unschedule()
146         subscribeToEvents()
147 }
148
149 def subscribeToEvents() {
150         subscribe(app, appTouchHandler)
151         subscribe(contact, "contact.open", eventHandler1)
152         subscribe(contactClosed, "contact.closed", eventHandler1)
153         subscribe(acceleration, "acceleration.active", eventHandler1)
154         subscribe(motion, "motion.active", eventHandler1)
155         subscribe(mySwitch, "switch.on", eventHandler1)
156         subscribe(mySwitchOff, "switch.off", eventHandler1)
157         subscribe(arrivalPresence, "presence.present", eventHandler1)
158         subscribe(departurePresence, "presence.not present", eventHandler1)
159         subscribe(smoke, "smoke.detected", eventHandler1)
160         subscribe(smoke, "smoke.tested", eventHandler1)
161         subscribe(smoke, "carbonMonoxide.detected", eventHandler1)
162         subscribe(water, "water.wet", eventHandler1)
163         subscribe(button1, "button.pushed", eventHandler1)
164
165         if (triggerModes) {
166                 subscribe(location, modeChangeHandler)
167         }
168
169         if (timeOfDay) {
170                 schedule(timeOfDay, scheduledTimeHandler)
171         }
172 }
173
174 def eventHandler1(evt=null) {
175         log.trace "Executing Mood Lighting"
176         if (allOk) {
177                 log.trace "allOk"
178                 def lastTime = state[frequencyKey(evt)]
179                 if (oncePerDayOk(lastTime)) {
180                         if (frequency) {
181                                 if (lastTime == null || now() - lastTime >= frequency * 60000) {
182                                         takeAction(evt)
183                                 }
184                         }
185                         else {
186                                 takeAction(evt)
187                         }
188                 }
189                 else {
190                         log.debug "Not taking action because it was already taken today"
191                 }
192         }
193 }
194
195 def modeChangeHandler(evt) {
196         log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
197         if (evt.value in triggerModes) {
198                 eventHandler1(evt)
199         }
200 }
201
202 def scheduledTimeHandler() {
203         log.trace "scheduledTimeHandler()"
204         eventHandler1()
205 }
206
207 def appTouchHandler(evt) {
208         takeAction(evt)
209 }
210
211 private takeAction(evt) {
212
213         if (frequency || oncePerDay) {
214                 state[frequencyKey(evt)] = now()
215         }
216
217         def hueColor = 0
218         def saturation = 100
219
220         switch(color) {
221                 case "White":
222                         hueColor = 52
223                         saturation = 19
224                         break;
225                 case "Daylight":
226                         hueColor = 53
227                         saturation = 91
228                         break;
229                 case "Soft White":
230                         hueColor = 23
231                         saturation = 56
232                         break;
233                 case "Warm White":
234                         hueColor = 20
235                         saturation = 80 //83
236                         break;
237                 case "Blue":
238                         hueColor = 70
239                         break;
240                 case "Green":
241                         hueColor = 39
242                         break;
243                 case "Yellow":
244                         hueColor = 25
245                         break;
246                 case "Orange":
247                         hueColor = 10
248                         break;
249                 case "Purple":
250                         hueColor = 75
251                         break;
252                 case "Pink":
253                         hueColor = 83
254                         break;
255                 case "Red":
256                         hueColor = 100
257                         break;
258         }
259
260         state.previous = [:]
261
262         hues.each {
263                 state.previous[it.id] = [
264                         "switch": it.currentValue("switch"),
265                         "level" : it.currentValue("level"),
266                         "hue": it.currentValue("hue"),
267                         "saturation": it.currentValue("saturation")
268                 ]
269         }
270
271         log.debug "current values = $state.previous"
272
273         def newValue = [hue: hueColor, saturation: saturation, level: lightLevel as Integer ?: 100]
274         log.debug "new value = $newValue"
275
276         hues*.setColor(newValue)
277 }
278
279 private frequencyKey(evt) {
280         "lastActionTimeStamp"
281 }
282
283 private dayString(Date date) {
284         def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
285         if (location.timeZone) {
286                 df.setTimeZone(location.timeZone)
287         }
288         else {
289                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
290         }
291         df.format(date)
292 }
293
294
295 private oncePerDayOk(Long lastTime) {
296         def result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
297         log.trace "oncePerDayOk = $result - $lastTime"
298         result
299 }
300
301 // TODO - centralize somehow
302 private getAllOk() {
303         //modeOk && daysOk && timeOk
304         return true
305 }
306
307 private getModeOk() {
308         def result = !modes || modes.contains(location.mode)
309         log.trace "modeOk = $result"
310         result
311 }
312
313 private getDaysOk() {
314         def result = true
315         if (days) {
316                 def df = new java.text.SimpleDateFormat("EEEE")
317                 if (location.timeZone) {
318                         df.setTimeZone(location.timeZone)
319                 }
320                 else {
321                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
322                 }
323                 def day = df.format(new Date())
324                 result = days.contains(day)
325         }
326         log.trace "daysOk = $result"
327         result
328 }
329
330 private getTimeOk() {
331         def result = true
332         if (starting && ending) {
333                 def currTime = now()
334                 def start = timeToday(starting, location?.timeZone).time
335                 def stop = timeToday(ending, location?.timeZone).time
336                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
337         }
338         log.trace "timeOk = $result"
339         result
340 }
341
342 private hhmm(time, fmt = "h:mm a")
343 {
344         def t = timeToday(time, location.timeZone)
345         def f = new java.text.SimpleDateFormat(fmt)
346         f.setTimeZone(location.timeZone ?: timeZone(time))
347         f.format(t)
348 }
349
350 private timeIntervalLabel()
351 {
352         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
353 }
354 // TODO - End Centralize