Update hue-mood-lighting.groovy
[smartapps.git] / official / bose-soundtouch-control.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  *  Bose® SoundTouch® Control
14  *
15  *  Author: SmartThings & Joe Geiger
16  *
17  *  Date: 2015-30-09
18  */
19 definition(
20     name: "Bose® SoundTouch® Control",
21     namespace: "smartthings",
22     author: "SmartThings & Joe Geiger",
23     description: "Control your Bose® SoundTouch® when certain actions take place in your home.",
24     category: "SmartThings Labs",
25     iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
26     iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
27     iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png"
28 )
29
30 preferences {
31         page(name: "mainPage", title: "Control your Bose® SoundTouch® when something happens", install: true, uninstall: true)
32         page(name: "timeIntervalInput", title: "Only during a certain time") {
33                 section {
34                         input "starting", "time", title: "Starting", required: false
35                         input "ending", "time", title: "Ending", required: false
36                 }
37         }
38 }
39
40 def mainPage() {
41         dynamicPage(name: "mainPage") {
42                 def anythingSet = anythingSet()
43                 if (anythingSet) {
44                         section("When..."){
45                                 ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
46                                 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
47                                 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
48                                 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
49                                 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
50                                 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
51                                 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
52                                 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
53                                 ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
54                                 ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
55                                 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
56                                 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
57                                 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
58                         }
59                 }
60                 section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
61                         ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
62                         ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
63                         ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
64                         ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
65                         ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
66                         ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
67                         ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
68                         ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
69                         ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
70                         ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
71                         ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
72                         ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
73                         ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
74                 }
75                 section("Perform this action"){
76                         input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
77                                 "Turn On & Play",
78                                 "Turn Off",
79                                 "Toggle Play/Pause",
80                                 "Skip to Next Track",
81                                 "Skip to Beginning/Previous Track"
82                         ]
83                 }
84                 section {
85                         input "bose", "capability.musicPlayer", title: "Bose® SoundTouch® music player", required: true
86                 }
87                 section("More options", hideable: true, hidden: true) {
88                         input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
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"
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                         //if (settings.modes) {
94                 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
95          //   }
96                         input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
97                 }
98                 section([mobileOnly:true]) {
99                         label title: "Assign a name", required: false
100                         mode title: "Set for specific mode(s)"
101                 }
102         }
103 }
104
105 private anythingSet() {
106         for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
107                 if (settings[name]) {
108                         return true
109                 }
110         }
111         return false
112 }
113
114 private ifUnset(Map options, String name, String capability) {
115         if (!settings[name]) {
116                 input(options, name, capability)
117         }
118 }
119
120 private ifSet(Map options, String name, String capability) {
121         if (settings[name]) {
122                 input(options, name, capability)
123         }
124 }
125
126 def installed() {
127         log.debug "Installed with settings: ${settings}"
128         subscribeToEvents()
129 }
130
131 def updated() {
132         log.debug "Updated with settings: ${settings}"
133         unsubscribe()
134         unschedule()
135         subscribeToEvents()
136 }
137
138 def subscribeToEvents() {
139         log.trace "subscribeToEvents()"
140         subscribe(app, appTouchHandler)
141         subscribe(contact, "contact.open", eventHandler1)
142         subscribe(contactClosed, "contact.closed", eventHandler1)
143         subscribe(acceleration, "acceleration.active", eventHandler1)
144         subscribe(motion, "motion.active", eventHandler1)
145         subscribe(mySwitch, "switch.on", eventHandler1)
146         subscribe(mySwitchOff, "switch.off", eventHandler1)
147         subscribe(arrivalPresence, "presence.present", eventHandler1)
148         subscribe(departurePresence, "presence.not present", eventHandler1)
149         subscribe(smoke, "smoke.detected", eventHandler1)
150         subscribe(smoke, "smoke.tested", eventHandler1)
151         subscribe(smoke, "carbonMonoxide.detected", eventHandler1)
152         subscribe(water, "water.wet", eventHandler1)
153         subscribe(button1, "button.pushed", eventHandler1)
154
155         if (triggerModes) {
156                 subscribe(location, modeChangeHandler)
157         }
158
159         if (timeOfDay) {
160                 schedule(timeOfDay, scheduledTimeHandler)
161         }
162 }
163
164 def eventHandler1(evt) {
165         if (allOk) {
166                 def lastTime = state[frequencyKey(evt)]
167                 if (oncePerDayOk(lastTime)) {
168                         if (frequency) {
169                                 if (lastTime == null || now() - lastTime >= frequency * 60000) {
170                                         takeAction(evt)
171                                 }
172                                 else {
173                                         log.debug "Not taking action because $frequency minutes have not elapsed since last action"
174                                 }
175                         }
176                         else {
177                                 takeAction(evt)
178                         }
179                 }
180                 else {
181                         log.debug "Not taking action because it was already taken today"
182                 }
183         }
184 }
185
186 def modeChangeHandler(evt) {
187         log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
188         if (evt.value in triggerModes) {
189                 eventHandler1(evt)
190         }
191 }
192
193 def scheduledTimeHandler() {
194         //eventHandler(null)
195 }
196
197 def appTouchHandler(evt) {
198         takeAction(evt)
199 }
200
201 private takeAction(evt) {
202         log.debug "takeAction($actionType)"
203         def options = [:]
204         if (volume) {
205                 bose.setLevel(volume as Integer)
206                 options.delay = 1000
207         }
208
209         switch (actionType) {
210                 case "Turn On & Play":
211                         options ? bose.on(options) : bose.on()
212                         break
213                 case "Turn Off":
214                         options ? bose.off(options) : bose.off()
215                         break
216                 case "Toggle Play/Pause":
217                         def currentStatus = bose.currentValue("playpause")
218                         if (currentStatus == "play") {
219                                 options ? bose.pause(options) : bose.pause()
220                         }
221                         else if (currentStatus == "pause") {
222                                 options ? bose.play(options) : bose.play()
223                         }
224                         break
225                 case "Skip to Next Track":
226                         options ? bose.nextTrack(options) : bose.nextTrack()
227                         break
228                 case "Skip to Beginning/Previous Track":
229                         options ? bose.previousTrack(options) : bose.previousTrack()
230                         break
231                 default:
232                         log.error "Action type '$actionType' not defined"
233         }
234
235         if (frequency) {
236                 state.lastActionTimeStamp = now()
237         }
238 }
239
240 private frequencyKey(evt) {
241         //evt.deviceId ?: evt.value
242         "lastActionTimeStamp"
243 }
244
245 private dayString(Date date) {
246         def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
247         if (location.timeZone) {
248                 df.setTimeZone(location.timeZone)
249         }
250         else {
251                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
252         }
253         df.format(date)
254 }
255
256 private oncePerDayOk(Long lastTime) {
257         def result = true
258         if (oncePerDay) {
259                 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
260                 log.trace "oncePerDayOk = $result"
261         }
262         result
263 }
264
265 // TODO - centralize somehow
266 private getAllOk() {
267         modeOk && daysOk && timeOk
268 }
269
270 private getModeOk() {
271         def result = !modes || modes.contains(location.mode)
272         log.trace "modeOk = $result"
273         result
274 }
275
276 private getDaysOk() {
277         def result = true
278         if (days) {
279                 def df = new java.text.SimpleDateFormat("EEEE")
280                 if (location.timeZone) {
281                         df.setTimeZone(location.timeZone)
282                 }
283                 else {
284                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
285                 }
286                 def day = df.format(new Date())
287                 result = days.contains(day)
288         }
289         log.trace "daysOk = $result"
290         result
291 }
292
293 private getTimeOk() {
294         def result = true
295         if (starting && ending) {
296                 def currTime = now()
297                 def start = timeToday(starting, location?.timeZone).time
298                 def stop = timeToday(ending, location?.timeZone).time
299                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
300         }
301         log.trace "timeOk = $result"
302         result
303 }
304
305 private hhmm(time, fmt = "h:mm a")
306 {
307         def t = timeToday(time, location.timeZone)
308         def f = new java.text.SimpleDateFormat(fmt)
309         f.setTimeZone(location.timeZone ?: timeZone(time))
310         f.format(t)
311 }
312
313 private timeIntervalLabel()
314 {
315         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
316 }
317 // TODO - End Centralize