2 * Copyright 2015 SmartThings
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:
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
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"
31 page(name: "timeIntervalInput", title: "Only during a certain time") {
33 input "starting", "time", title: "Starting", required: false
34 input "ending", "time", title: "Ending", required: false
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)
44 log.debug "Installed with settings: ${settings}"
49 log.debug "Updated with settings: ${settings}"
54 def subscribeToEvents() {
55 log.trace "subscribeToEvents()"
58 subscribe(location, modeChangeHandler)
62 private songOptions() {
64 // Make sure current selection is in the set
66 def options = new LinkedHashSet()
67 options << "STOP PLAYING"
68 if (state.selectedSong?.station) {
69 options << state.selectedSong.station
71 else if (state.selectedSong?.description) {
72 // TODO - Remove eventually? 'description' for backward compatibility
73 options << state.selectedSong.description
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})
81 log.trace "${options.size()} songs in list"
82 options.take(20) as List*/
83 state.selectedSong = "SomeTrack"
86 private saveSelectedSongs() {
89 def songs = sonos.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
90 log.info "Searching ${songs.size()} records"
92 if (!state.selectedSongs) {
93 state.selectedSongs = [:]
96 settings.each {name, thisSong ->
97 if (thisSong == "STOP PLAYING") {
98 state.selectedSongs."$name" = "PAUSE"
100 if (name.startsWith("mode_")) {
101 log.info "Looking for $thisSong"
103 def data = songs.find {s -> s.station == thisSong}
104 log.info "Found ${data?.station}"
106 state.selectedSongs."$name" = data
107 log.debug "Selected song = $data.station"
109 else if (song == state.selectedSongs."$name"?.station) {
110 log.debug "Selected existing entry '$thisSong', which is no longer in the last 20 list"
113 log.warn "Selected song '$thisSong' not found"
118 catch (Throwable t) {
121 state.selectedSong = "SomeTrack"
126 dynamicPage(name: "mainPage") {
129 input "sonos", "capability.musicPlayer", title: "Sonos player", required: true
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
140 input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
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
153 section([mobileOnly:true]) {
154 label title: "Assign a name", required: false
155 mode title: "Set for specific mode(s)", required: false
161 def modeChangeHandler(evt) {
162 log.trace "modeChangeHandler($evt.name: $evt.value)"
165 def lastTime = state[frequencyKey(evt)]
166 if (lastTime == null || now() - lastTime >= frequency * 60000) {
176 private takeAction(evt) {
178 def name = "mode_$evt.value".toString()
179 def selectedSong = state.selectedSongs."$name"
181 if (selectedSong == "PAUSE") {
185 log.info "Playing '$selectedSong"
187 if (volume != null) {
190 sonos.setLevel(volume)
194 sonos.playTrack(selectedSong)
197 if (frequency || oncePerDay) {
198 state[frequencyKey(evt)] = now()
200 log.trace "Exiting takeAction()"
203 private frequencyKey(evt) {
204 "lastActionTimeStamp"
207 private dayString(Date date) {
208 def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
209 if (location.timeZone) {
210 df.setTimeZone(location.timeZone)
213 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
218 private oncePerDayOk(Long lastTime) {
219 def result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
220 log.trace "oncePerDayOk = $result"
224 // TODO - centralize somehow
226 modeOk && daysOk && timeOk
229 private getModeOk() {
230 def result = !modes || modes.contains(location.mode)
231 log.trace "modeOk = $result"
235 private getDaysOk() {
238 def df = new java.text.SimpleDateFormat("EEEE")
239 if (location.timeZone) {
240 df.setTimeZone(location.timeZone)
243 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
245 def day = df.format(new Date())
246 result = days.contains(day)
248 log.trace "daysOk = $result"
252 private getTimeOk() {
254 if (starting && ending) {
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
260 log.trace "timeOk = $result"
264 private hhmm(time, fmt = "h:mm a")
266 def t = timeToday(time, location.timeZone)
267 def f = new java.text.SimpleDateFormat(fmt)
268 f.setTimeZone(location.timeZone ?: timeZone(time))
272 private timeIntervalLabel()
274 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
276 // TODO - End Centralize