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