Update thermostat-mode-director.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 def mainPage() {
40         dynamicPage(name: "mainPage") {
41                 def anythingSet = anythingSet()
42                 if (anythingSet) {
43                         section("Set the lighting mood when..."){
44                                 ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
45                                 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
46                                 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
47                                 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
48                                 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
49                                 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
50                                 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
51                                 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
52                                 ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
53                                 ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
54                                 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
55                                 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
56                                 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
57                         }
58                 }
59                 section(anythingSet ? "Select additional mood lighting triggers" : "Set the lighting mood when...", hideable: anythingSet, hidden: true){
60                         ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
61                         ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
62                         ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
63                         ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
64                         ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
65                         ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
66                         ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
67                         ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
68                         ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
69                         ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
70                         ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
71                         ifUnset "triggerModes", "mode", title: "System Changes Mode", description: "Select mode(s)", required: false, multiple: true
72                         ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
73                 }
74                 section("Control these bulbs...") {
75                         input "hues", "capability.colorControl", title: "Which Hue Bulbs?", required:true, multiple:true
76                 }
77                 section("Choose light effects...")
78                         {
79                                 input "color", "enum", title: "Hue Color?", required: false, multiple:false, options: [
80                                         ["Soft White":"Soft White - Default"],
81                                         ["White":"White - Concentrate"],
82                                         ["Daylight":"Daylight - Energize"],
83                                         ["Warm White":"Warm White - Relax"],
84                                         "Red","Green","Blue","Yellow","Orange","Purple","Pink"]
85                                 input "lightLevel", "enum", title: "Light Level?", required: false, options: [[10:"10%"],[20:"20%"],[30:"30%"],[40:"40%"],[50:"50%"],[60:"60%"],[70:"70%"],[80:"80%"],[90:"90%"],[100:"100%"]]
86                         }
87
88                 section("More options", hideable: true, hidden: true) {
89                         input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
90                         href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
91                         input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
92                                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
93                         input "modes", "mode", title: "Only when mode is", multiple: true, required: false
94                         input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
95                 }
96                 section([mobileOnly:true]) {
97                         label title: "Assign a name", required: false
98                         mode title: "Set for specific mode(s)", required: false
99                 }
100         }
101 }
102 private anythingSet() {
103         for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
104                 if (settings[name]) {
105                         return true
106                 }
107         }
108         return false
109 }
110
111 private ifUnset(Map options, String name, String capability) {
112         if (!settings[name]) {
113                 input(options, name, capability)
114         }
115 }
116
117 private ifSet(Map options, String name, String capability) {
118         if (settings[name]) {
119                 input(options, name, capability)
120         }
121 }
122
123 def installed() {
124         log.debug "Installed with settings: ${settings}"
125         subscribeToEvents()
126 }
127
128 def updated() {
129         log.debug "Updated with settings: ${settings}"
130         unsubscribe()
131         unschedule()
132         subscribeToEvents()
133 }
134
135 def subscribeToEvents() {
136         subscribe(app, appTouchHandler)
137         subscribe(contact, "contact.open", eventHandler)
138         subscribe(contactClosed, "contact.closed", eventHandler)
139         subscribe(acceleration, "acceleration.active", eventHandler)
140         subscribe(motion, "motion.active", eventHandler)
141         subscribe(mySwitch, "switch.on", eventHandler)
142         subscribe(mySwitchOff, "switch.off", eventHandler)
143         subscribe(arrivalPresence, "presence.present", eventHandler)
144         subscribe(departurePresence, "presence.not present", eventHandler)
145         subscribe(smoke, "smoke.detected", eventHandler)
146         subscribe(smoke, "smoke.tested", eventHandler)
147         subscribe(smoke, "carbonMonoxide.detected", eventHandler)
148         subscribe(water, "water.wet", eventHandler)
149         subscribe(button1, "button.pushed", eventHandler)
150
151         if (triggerModes) {
152                 subscribe(location, modeChangeHandler)
153         }
154
155         if (timeOfDay) {
156                 schedule(timeOfDay, scheduledTimeHandler)
157         }
158 }
159
160 def eventHandler(evt=null) {
161         log.trace "Executing Mood Lighting"
162         if (allOk) {
163                 log.trace "allOk"
164                 def lastTime = state[frequencyKey(evt)]
165                 if (oncePerDayOk(lastTime)) {
166                         if (frequency) {
167                                 if (lastTime == null || now() - lastTime >= frequency * 60000) {
168                                         takeAction(evt)
169                                 }
170                         }
171                         else {
172                                 takeAction(evt)
173                         }
174                 }
175                 else {
176                         log.debug "Not taking action because it was already taken today"
177                 }
178         }
179 }
180
181 def modeChangeHandler(evt) {
182         log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
183         if (evt.value in triggerModes) {
184                 eventHandler(evt)
185         }
186 }
187
188 def scheduledTimeHandler() {
189         log.trace "scheduledTimeHandler()"
190         eventHandler()
191 }
192
193 def appTouchHandler(evt) {
194         takeAction(evt)
195 }
196
197 private takeAction(evt) {
198
199         if (frequency || oncePerDay) {
200                 state[frequencyKey(evt)] = now()
201         }
202
203         def hueColor = 0
204         def saturation = 100
205
206         switch(color) {
207                 case "White":
208                         hueColor = 52
209                         saturation = 19
210                         break;
211                 case "Daylight":
212                         hueColor = 53
213                         saturation = 91
214                         break;
215                 case "Soft White":
216                         hueColor = 23
217                         saturation = 56
218                         break;
219                 case "Warm White":
220                         hueColor = 20
221                         saturation = 80 //83
222                         break;
223                 case "Blue":
224                         hueColor = 70
225                         break;
226                 case "Green":
227                         hueColor = 39
228                         break;
229                 case "Yellow":
230                         hueColor = 25
231                         break;
232                 case "Orange":
233                         hueColor = 10
234                         break;
235                 case "Purple":
236                         hueColor = 75
237                         break;
238                 case "Pink":
239                         hueColor = 83
240                         break;
241                 case "Red":
242                         hueColor = 100
243                         break;
244         }
245
246         state.previous = [:]
247
248         hues.each {
249                 state.previous[it.id] = [
250                         "switch": it.currentValue("switch"),
251                         "level" : it.currentValue("level"),
252                         "hue": it.currentValue("hue"),
253                         "saturation": it.currentValue("saturation")
254                 ]
255         }
256
257         log.debug "current values = $state.previous"
258
259         def newValue = [hue: hueColor, saturation: saturation, level: lightLevel as Integer ?: 100]
260         log.debug "new value = $newValue"
261
262         hues*.setColor(newValue)
263 }
264
265 private frequencyKey(evt) {
266         "lastActionTimeStamp"
267 }
268
269 private dayString(Date date) {
270         def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
271         if (location.timeZone) {
272                 df.setTimeZone(location.timeZone)
273         }
274         else {
275                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
276         }
277         df.format(date)
278 }
279
280
281 private oncePerDayOk(Long lastTime) {
282         def result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
283         log.trace "oncePerDayOk = $result - $lastTime"
284         result
285 }
286
287 // TODO - centralize somehow
288 private getAllOk() {
289         modeOk && daysOk && timeOk
290 }
291
292 private getModeOk() {
293         def result = !modes || modes.contains(location.mode)
294         log.trace "modeOk = $result"
295         result
296 }
297
298 private getDaysOk() {
299         def result = true
300         if (days) {
301                 def df = new java.text.SimpleDateFormat("EEEE")
302                 if (location.timeZone) {
303                         df.setTimeZone(location.timeZone)
304                 }
305                 else {
306                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
307                 }
308                 def day = df.format(new Date())
309                 result = days.contains(day)
310         }
311         log.trace "daysOk = $result"
312         result
313 }
314
315 private getTimeOk() {
316         def result = true
317         if (starting && ending) {
318                 def currTime = now()
319                 def start = timeToday(starting, location?.timeZone).time
320                 def stop = timeToday(ending, location?.timeZone).time
321                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
322         }
323         log.trace "timeOk = $result"
324         result
325 }
326
327 private hhmm(time, fmt = "h:mm a")
328 {
329         def t = timeToday(time, location.timeZone)
330         def f = new java.text.SimpleDateFormat(fmt)
331         f.setTimeZone(location.timeZone ?: timeZone(time))
332         f.format(t)
333 }
334
335 private timeIntervalLabel()
336 {
337         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
338 }
339 // TODO - End Centralize