/smartapps/smartthings/sonos-mood-music.groovy
Groovy | 316 lines | 264 code | 41 blank | 11 comment | 43 complexity | 545305726e63b5906d567fa2aeafdcb3 MD5 | raw file
Possible License(s): Apache-2.0
- /**
- * Sonos Mood Music
- *
- * Author: SmartThings
- * Date: 2014-02-12
- */
- private songOptions() {
- // Make sure current selection is in the set
- def options = new LinkedHashSet()
- if (state.selectedSong?.station) {
- options << state.selectedSong.station
- }
- else if (state.selectedSong?.description) {
- // TODO - Remove eventually? 'description' for backward compatibility
- options << state.selectedSong.description
- }
- // Query for recent tracks
- def states = sonos.statesSince("trackData", new Date(0), [max:30])
- def dataMaps = states.collect{it.jsonValue}
- options.addAll(dataMaps.collect{it.station})
- log.trace "${options.size()} songs in list"
- options.take(20) as List
- }
- private saveSelectedSong() {
- try {
- def thisSong = song
- log.info "Looking for $thisSong"
- def songs = sonos.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
- log.info "Searching ${songs.size()} records"
- def data = songs.find {s -> s.station == thisSong}
- log.info "Found ${data?.station}"
- if (data) {
- state.selectedSong = data
- log.debug "Selected song = $state.selectedSong"
- }
- else if (song == state.selectedSong?.station) {
- log.debug "Selected existing entry '$song', which is no longer in the last 20 list"
- }
- else {
- log.warn "Selected song '$song' not found"
- }
- }
- catch (Throwable t) {
- log.error t
- }
- }
- preferences {
- page(name: "mainPage", title: "Play a selected song or station on your Sonos when something happens", nextPage: "chooseTrack", uninstall: true)
- page(name: "chooseTrack", title: "Select a song", install: true)
- page(name: "timeIntervalInput", title: "Only during a certain time") {
- section {
- input "starting", "time", title: "Starting", required: false
- input "ending", "time", title: "Ending", required: false
- }
- }
- }
- def mainPage() {
- dynamicPage(name: "mainPage") {
- def anythingSet = anythingSet()
- if (anythingSet) {
- section("Play music when..."){
- ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
- ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
- ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
- ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
- ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
- ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
- ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
- ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
- ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
- ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
- ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
- ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
- ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
- }
- }
- def hideable = anythingSet || app.installationState == "COMPLETE"
- def sectionTitle = anythingSet ? "Select additional triggers" : "Play music when..."
- section(sectionTitle, hideable: hideable, hidden: true){
- ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
- ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
- ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
- ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
- ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
- ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
- ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
- ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
- ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
- ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
- ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
- ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
- ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
- }
- section {
- input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
- }
- section("More options", hideable: true, hidden: true) {
- input "volume", "number", title: "Set the volume", description: "0-100%", required: false
- input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
- href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
- input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
- options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
- input "modes", "mode", title: "Only when mode is", multiple: true, required: false
- input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
- }
- }
- }
- def chooseTrack() {
- dynamicPage(name: "chooseTrack") {
- section{
- input "song","enum",title:"Play this track", required:true, multiple: false, options: songOptions()
- }
- section([mobileOnly:true]) {
- label title: "Assign a name", required: false
- mode title: "Set for specific mode(s)", required: false
- }
- }
- }
- private anythingSet() {
- for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","timeOfDay","triggerModes","timeOfDay"]) {
- if (settings[name]) {
- return true
- }
- }
- return false
- }
- private ifUnset(Map options, String name, String capability) {
- if (!settings[name]) {
- input(options, name, capability)
- }
- }
- private ifSet(Map options, String name, String capability) {
- if (settings[name]) {
- input(options, name, capability)
- }
- }
- def installed() {
- log.debug "Installed with settings: ${settings}"
- subscribeToEvents()
- }
- def updated() {
- log.debug "Updated with settings: ${settings}"
- unsubscribe()
- unschedule()
- subscribeToEvents()
- }
- def subscribeToEvents() {
- log.trace "subscribeToEvents()"
- saveSelectedSong()
- subscribe(app, appTouchHandler)
- subscribe(contact, "contact.open", eventHandler)
- subscribe(contactClosed, "contact.closed", eventHandler)
- subscribe(acceleration, "acceleration.active", eventHandler)
- subscribe(motion, "motion.active", eventHandler)
- subscribe(mySwitch, "switch.on", eventHandler)
- subscribe(mySwitchOff, "switch.off", eventHandler)
- subscribe(arrivalPresence, "presence.present", eventHandler)
- subscribe(departurePresence, "presence.not present", eventHandler)
- subscribe(smoke, "smoke.detected", eventHandler)
- subscribe(smoke, "smoke.tested", eventHandler)
- subscribe(smoke, "carbonMonoxide.detected", eventHandler)
- subscribe(water, "water.wet", eventHandler)
- subscribe(button1, "button.pushed", eventHandler)
- if (triggerModes) {
- subscribe(location, modeChangeHandler)
- }
- if (timeOfDay) {
- runDaily(timeOfDay, scheduledTimeHandler)
- }
- }
- def eventHandler(evt) {
- if (allOk) {
- if (frequency) {
- def lastTime = state[frequencyKey(evt)]
- if (lastTime == null || now() - lastTime >= frequency * 60000) {
- takeAction(evt)
- }
- }
- else {
- takeAction(evt)
- }
- }
- }
- def modeChangeHandler(evt) {
- log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
- if (evt.value in triggerModes) {
- eventHandler(evt)
- }
- }
- def scheduledTimeHandler() {
- eventHandler(null)
- }
- def appTouchHandler(evt) {
- takeAction(evt)
- }
- private takeAction(evt) {
- log.info "Playing '$state.selectedSong"
- if (volume != null) {
- sonos.stop()
- pause(500)
- sonos.setLevel(volume)
- pause(500)
- }
- sonos.playTrack(state.selectedSong)
- if (frequency || oncePerDay) {
- state[frequencyKey(evt)] = now()
- }
- log.trace "Exiting takeAction()"
- }
- private frequencyKey(evt) {
- "lastActionTimeStamp"
- }
- private dayString(Date date) {
- def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
- if (location.timeZone) {
- df.setTimeZone(location.timeZone)
- }
- else {
- df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
- }
- df.format(date)
- }
- private oncePerDayOk(Long lastTime) {
- def result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
- log.trace "oncePerDayOk = $result"
- result
- }
- // TODO - centralize somehow
- private getAllOk() {
- modeOk && daysOk && timeOk
- }
- private getModeOk() {
- def result = !modes || modes.contains(location.mode)
- log.trace "modeOk = $result"
- result
- }
- private getDaysOk() {
- def result = true
- if (days) {
- def df = new java.text.SimpleDateFormat("EEEE")
- if (location.timeZone) {
- df.setTimeZone(location.timeZone)
- }
- else {
- df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
- }
- def day = df.format(new Date())
- result = days.contains(day)
- }
- log.trace "daysOk = $result"
- result
- }
- private getTimeOk() {
- def result = true
- if (starting && ending) {
- def currTime = now()
- def start = timeToday(starting).time
- def stop = timeToday(ending).time
- result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
- }
- log.trace "timeOk = $result"
- result
- }
- private hhmm(time, fmt = "h:mm a")
- {
- def t = timeToday(time, location.timeZone)
- def f = new java.text.SimpleDateFormat(fmt)
- f.setTimeZone(location.timeZone ?: timeZone(time))
- f.format(t)
- }
- private timeIntervalLabel()
- {
- (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
- }
- // TODO - End Centralize