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