Update sonos-music-modes.groovy
[smartapps.git] / official / speaker-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  *  Speaker Control
14  *
15  *  Author: SmartThings
16  *
17  *  Date: 2013-12-10
18  */
19 definition(
20     name: "Speaker Control",
21     namespace: "smartthings",
22     author: "SmartThings",
23     description: "Play or pause your Speaker when certain actions take place in your home.",
24     category: "SmartThings Labs",
25     iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
26     iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
27 )
28
29 preferences {
30         page(name: "mainPage", title: "Control your Speaker when something happens", 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("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 triggers" : "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", required: false, multiple: true
72                         ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
73                 }
74                 section("Perform this action"){
75                         input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
76                                 "Play",
77                                 "Stop Playing",
78                                 "Toggle Play/Pause",
79                                 "Skip to Next Track",
80                                 "Play Previous Track"
81                         ]
82                 }
83                 section {
84                         input "sonos", "capability.musicPlayer", title: "Speaker music player", required: true
85                 }
86                 section("More options", hideable: true, hidden: true) {
87                         input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
88                         input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
89                         href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
90                         input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
91                                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
92                         if (settings.modes) {
93                 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
94             }
95                         input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
96                 }
97                 section([mobileOnly:true]) {
98                         label title: "Assign a name", required: false
99                         mode title: "Set for specific mode(s)"
100                 }
101         }
102 }
103
104 private anythingSet() {
105         for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
106                 if (settings[name]) {
107                         return true
108                 }
109         }
110         return false
111 }
112
113 private ifUnset(Map options, String name, String capability) {
114         if (!settings[name]) {
115                 input(options, name, capability)
116         }
117 }
118
119 private ifSet(Map options, String name, String capability) {
120         if (settings[name]) {
121                 input(options, name, capability)
122         }
123 }
124
125 def installed() {
126         log.debug "Installed with settings: ${settings}"
127         subscribeToEvents()
128 }
129
130 def updated() {
131         log.debug "Updated with settings: ${settings}"
132         unsubscribe()
133         unschedule()
134         subscribeToEvents()
135 }
136
137 def subscribeToEvents() {
138         log.trace "subscribeToEvents()"
139         subscribe(app, appTouchHandler)
140         subscribe(contact, "contact.open", eventHandler)
141         subscribe(contactClosed, "contact.closed", eventHandler)
142         subscribe(acceleration, "acceleration.active", eventHandler)
143         subscribe(motion, "motion.active", eventHandler)
144         subscribe(mySwitch, "switch.on", eventHandler)
145         subscribe(mySwitchOff, "switch.off", eventHandler)
146         subscribe(arrivalPresence, "presence.present", eventHandler)
147         subscribe(departurePresence, "presence.not present", eventHandler)
148         subscribe(smoke, "smoke.detected", eventHandler)
149         subscribe(smoke, "smoke.tested", eventHandler)
150         subscribe(smoke, "carbonMonoxide.detected", eventHandler)
151         subscribe(water, "water.wet", eventHandler)
152         subscribe(button1, "button.pushed", eventHandler)
153
154         if (triggerModes) {
155                 subscribe(location, modeChangeHandler)
156         }
157
158         if (timeOfDay) {
159                 schedule(timeOfDay, scheduledTimeHandler)
160         }
161 }
162
163 def eventHandler(evt) {
164         if (allOk) {
165                 def lastTime = state[frequencyKey(evt)]
166                 if (oncePerDayOk(lastTime)) {
167                         if (frequency) {
168                                 if (lastTime == null || now() - lastTime >= frequency * 60000) {
169                                         takeAction(evt)
170                                 }
171                                 else {
172                                         log.debug "Not taking action because $frequency minutes have not elapsed since last action"
173                                 }
174                         }
175                         else {
176                                 takeAction(evt)
177                         }
178                 }
179                 else {
180                         log.debug "Not taking action because it was already taken today"
181                 }
182         }
183 }
184
185 def modeChangeHandler(evt) {
186         log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
187         if (evt.value in triggerModes) {
188                 eventHandler(evt)
189         }
190 }
191
192 def scheduledTimeHandler() {
193         eventHandler(null)
194 }
195
196 def appTouchHandler(evt) {
197         takeAction(evt)
198 }
199
200 private takeAction(evt) {
201         log.debug "takeAction($actionType)"
202         def options = [:]
203         if (volume) {
204                 sonos.setLevel(volume as Integer)
205                 options.delay = 1000
206         }
207
208         switch (actionType) {
209                 case "Play":
210                         options ? sonos.on(options) : sonos.on()
211                         break
212                 case "Stop Playing":
213                         options ? sonos.off(options) : sonos.off()
214                         break
215                 case "Toggle Play/Pause":
216                         def currentStatus = sonos.currentValue("status")
217                         if (currentStatus == "playing") {
218                                 options ? sonos.pause(options) : sonos.pause()
219                         }
220                         else {
221                                 options ? sonos.play(options) : sonos.play()
222                         }
223                         break
224                 case "Skip to Next Track":
225                         options ? sonos.nextTrack(options) : sonos.nextTrack()
226                         break
227                 case "Play Previous Track":
228                         options ? sonos.previousTrack(options) : sonos.previousTrack()
229                         break
230                 default:
231                         log.error "Action type '$actionType' not defined"
232         }
233
234         if (frequency) {
235                 state.lastActionTimeStamp = now()
236         }
237 }
238
239 private frequencyKey(evt) {
240         //evt.deviceId ?: evt.value
241         "lastActionTimeStamp"
242 }
243
244 private dayString(Date date) {
245         def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
246         if (location.timeZone) {
247                 df.setTimeZone(location.timeZone)
248         }
249         else {
250                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
251         }
252         df.format(date)
253 }
254
255 private oncePerDayOk(Long lastTime) {
256         def result = true
257         if (oncePerDay) {
258                 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
259                 log.trace "oncePerDayOk = $result"
260         }
261         result
262 }
263
264 // TODO - centralize somehow
265 private getAllOk() {
266         modeOk && daysOk && timeOk
267 }
268
269 private getModeOk() {
270         def result = !modes || modes.contains(location.mode)
271         log.trace "modeOk = $result"
272         result
273 }
274
275 private getDaysOk() {
276         def result = true
277         if (days) {
278                 def df = new java.text.SimpleDateFormat("EEEE")
279                 if (location.timeZone) {
280                         df.setTimeZone(location.timeZone)
281                 }
282                 else {
283                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
284                 }
285                 def day = df.format(new Date())
286                 result = days.contains(day)
287         }
288         log.trace "daysOk = $result"
289         result
290 }
291
292 private getTimeOk() {
293         def result = true
294         if (starting && ending) {
295                 def currTime = now()
296                 def start = timeToday(starting, location?.timeZone).time
297                 def stop = timeToday(ending, location?.timeZone).time
298                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
299         }
300         log.trace "timeOk = $result"
301         result
302 }
303
304 private hhmm(time, fmt = "h:mm a")
305 {
306         def t = timeToday(time, location.timeZone)
307         def f = new java.text.SimpleDateFormat(fmt)
308         f.setTimeZone(location.timeZone ?: timeZone(time))
309         f.format(t)
310 }
311
312 private timeIntervalLabel()
313 {
314         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
315 }
316 // TODO - End Centralize
317