Update README.md
[smartapps.git] / official / sonos-music-modes.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  *  Sonos Mood Music
14  *
15  *  Author: SmartThings
16  *  Date: 2014-02-12
17  */
18
19
20 definition(
21     name: "Sonos Music Modes",
22     namespace: "smartthings",
23     author: "SmartThings",
24     description: "Plays a different selected song or station for each mode.",
25     category: "SmartThings Internal",
26     iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
27     iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
28 )
29
30 preferences {
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         page(name: "mainPage", title: "Play a message on your Sonos when something happens", nextPage: "chooseTrack", uninstall: true)
38         page(name: "chooseTrack", title: "Select a song", install: true)
39         
40 }
41
42
43 def installed() {
44         log.debug "Installed with settings: ${settings}"
45         subscribeToEvents()
46 }
47
48 def updated() {
49         log.debug "Updated with settings: ${settings}"
50         unsubscribe()
51         subscribeToEvents()
52 }
53
54 def subscribeToEvents() {
55         log.trace "subscribeToEvents()"
56         saveSelectedSongs()
57
58         subscribe(location, modeChangeHandler)
59 }
60
61
62 private songOptions() {
63         /*
64         // Make sure current selection is in the set
65
66         def options = new LinkedHashSet()
67         options << "STOP PLAYING"
68         if (state.selectedSong?.station) {
69                 options << state.selectedSong.station
70         }
71         else if (state.selectedSong?.description) {
72                 // TODO - Remove eventually? 'description' for backward compatibility
73                 options << state.selectedSong.description
74         }
75
76         // Query for recent tracks
77         def states = sonos.statesSince("trackData", new Date(0), [max:30])
78         def dataMaps = states.collect{it.jsonValue}
79         options.addAll(dataMaps.collect{it.station})
80
81         log.trace "${options.size()} songs in list"
82         options.take(20) as List*/
83         state.selectedSong = "SomeTrack"
84 }
85
86 private saveSelectedSongs() {
87         /*
88         try {
89                 def songs = sonos.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
90                 log.info "Searching ${songs.size()} records"
91
92                 if (!state.selectedSongs) {
93                         state.selectedSongs = [:]
94                 }
95
96                 settings.each {name, thisSong ->
97                         if (thisSong == "STOP PLAYING") {
98                                 state.selectedSongs."$name" = "PAUSE"
99                         }
100                         if (name.startsWith("mode_")) {
101                                 log.info "Looking for $thisSong"
102
103                                 def data = songs.find {s -> s.station == thisSong}
104                                 log.info "Found ${data?.station}"
105                                 if (data) {
106                                         state.selectedSongs."$name" = data
107                                         log.debug "Selected song = $data.station"
108                                 }
109                                 else if (song == state.selectedSongs."$name"?.station) {
110                                         log.debug "Selected existing entry '$thisSong', which is no longer in the last 20 list"
111                                 }
112                                 else {
113                                         log.warn "Selected song '$thisSong' not found"
114                                 }
115                         }
116                 }
117         }
118         catch (Throwable t) {
119                 log.error t
120         }*/
121         state.selectedSong = "SomeTrack"
122 }
123
124
125 def mainPage() {
126         dynamicPage(name: "mainPage") {
127
128                 section {
129                         input "sonos", "capability.musicPlayer", title: "Sonos player", required: true
130                 }
131                 section("More options", hideable: true, hidden: true) {
132                         input "volume", "number", title: "Set the volume", description: "0-100%", required: false
133                         input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
134                         href "timeIntervalInput", title: "Only during a certain time"
135                         input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
136                                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
137                         //if (settings.modes) {
138                 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
139           //  }
140                         input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
141                 }
142         }
143 }
144
145 def chooseTrack() {
146         dynamicPage(name: "chooseTrack") {
147                 section("Play a different song for each mode in which you want music") {
148                         def options = songOptions()
149                         location.modes.each {mode ->
150                                 input "mode_$mode.name", "enum", title: mode.name, options: options, required: false
151                         }
152                 }
153                 section([mobileOnly:true]) {
154                         label title: "Assign a name", required: false
155                         mode title: "Set for specific mode(s)", required: false
156                 }
157         }
158 }
159
160
161 def modeChangeHandler(evt) {
162         log.trace "modeChangeHandler($evt.name: $evt.value)"
163         if (allOk) {
164                 if (frequency) {
165                         def lastTime = state[frequencyKey(evt)]
166                         if (lastTime == null || now() - lastTime >= frequency * 60000) {
167                                 takeAction(evt)
168                         }
169                 }
170                 else {
171                         takeAction(evt)
172                 }
173         }
174 }
175
176 private takeAction(evt) {
177
178         def name = "mode_$evt.value".toString()
179         def selectedSong = state.selectedSongs
180
181         if (selectedSong == "PAUSE") {
182                 sonos.stop()
183         }
184         else {
185                 log.info "Playing '$selectedSong"
186
187                 if (volume != null) {
188                         sonos.stop()
189                         //pause(500)
190                         sonos.setLevel(volume)
191                         //pause(500)
192                 }
193
194                 sonos.playTrack(selectedSong)
195         }
196
197         if (frequency || oncePerDay) {
198                 state[frequencyKey(evt)] = now()
199         }
200         log.trace "Exiting takeAction()"
201 }
202
203 private frequencyKey(evt) {
204         "lastActionTimeStamp"
205 }
206
207 private dayString(Date date) {
208         def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
209         if (location.timeZone) {
210                 df.setTimeZone(location.timeZone)
211         }
212         else {
213                 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
214         }
215         df.format(date)
216 }
217
218 private oncePerDayOk(Long lastTime) {
219         def result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
220         log.trace "oncePerDayOk = $result"
221         result
222 }
223
224 // TODO - centralize somehow
225 private getAllOk() {
226         modeOk && daysOk && timeOk
227 }
228
229 private getModeOk() {
230         def result = !modes || modes.contains(location.mode)
231         log.trace "modeOk = $result"
232         result
233 }
234
235 private getDaysOk() {
236         def result = true
237         if (days) {
238                 def df = new java.text.SimpleDateFormat("EEEE")
239                 if (location.timeZone) {
240                         df.setTimeZone(location.timeZone)
241                 }
242                 else {
243                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
244                 }
245                 def day = df.format(new Date())
246                 result = days.contains(day)
247         }
248         log.trace "daysOk = $result"
249         result
250 }
251
252 private getTimeOk() {
253         def result = true
254         if (starting && ending) {
255                 def currTime = now()
256                 def start = timeToday(starting, location?.timeZone).time
257                 def stop = timeToday(ending, location?.timeZone).time
258                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
259         }
260         log.trace "timeOk = $result"
261         result
262 }
263
264 private hhmm(time, fmt = "h:mm a")
265 {
266         def t = timeToday(time, location.timeZone)
267         def f = new java.text.SimpleDateFormat(fmt)
268         f.setTimeZone(location.timeZone ?: timeZone(time))
269         f.format(t)
270 }
271
272 private timeIntervalLabel()
273 {
274         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
275 }
276 // TODO - End Centralize
277