PageRenderTime 26ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/smartapps/smartthings/sonos-mood-music.groovy

https://github.com/rappleg/SmartThings
Groovy | 316 lines | 264 code | 41 blank | 11 comment | 43 complexity | 545305726e63b5906d567fa2aeafdcb3 MD5 | raw file
Possible License(s): Apache-2.0
  1. /**
  2. * Sonos Mood Music
  3. *
  4. * Author: SmartThings
  5. * Date: 2014-02-12
  6. */
  7. private songOptions() {
  8. // Make sure current selection is in the set
  9. def options = new LinkedHashSet()
  10. if (state.selectedSong?.station) {
  11. options << state.selectedSong.station
  12. }
  13. else if (state.selectedSong?.description) {
  14. // TODO - Remove eventually? 'description' for backward compatibility
  15. options << state.selectedSong.description
  16. }
  17. // Query for recent tracks
  18. def states = sonos.statesSince("trackData", new Date(0), [max:30])
  19. def dataMaps = states.collect{it.jsonValue}
  20. options.addAll(dataMaps.collect{it.station})
  21. log.trace "${options.size()} songs in list"
  22. options.take(20) as List
  23. }
  24. private saveSelectedSong() {
  25. try {
  26. def thisSong = song
  27. log.info "Looking for $thisSong"
  28. def songs = sonos.statesSince("trackData", new Date(0), [max:30]).collect{it.jsonValue}
  29. log.info "Searching ${songs.size()} records"
  30. def data = songs.find {s -> s.station == thisSong}
  31. log.info "Found ${data?.station}"
  32. if (data) {
  33. state.selectedSong = data
  34. log.debug "Selected song = $state.selectedSong"
  35. }
  36. else if (song == state.selectedSong?.station) {
  37. log.debug "Selected existing entry '$song', which is no longer in the last 20 list"
  38. }
  39. else {
  40. log.warn "Selected song '$song' not found"
  41. }
  42. }
  43. catch (Throwable t) {
  44. log.error t
  45. }
  46. }
  47. preferences {
  48. page(name: "mainPage", title: "Play a selected song or station on your Sonos when something happens", nextPage: "chooseTrack", uninstall: true)
  49. page(name: "chooseTrack", title: "Select a song", install: true)
  50. page(name: "timeIntervalInput", title: "Only during a certain time") {
  51. section {
  52. input "starting", "time", title: "Starting", required: false
  53. input "ending", "time", title: "Ending", required: false
  54. }
  55. }
  56. }
  57. def mainPage() {
  58. dynamicPage(name: "mainPage") {
  59. def anythingSet = anythingSet()
  60. if (anythingSet) {
  61. section("Play music when..."){
  62. ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
  63. ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
  64. ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
  65. ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
  66. ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
  67. ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
  68. ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
  69. ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
  70. ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
  71. ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
  72. ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
  73. ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
  74. ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
  75. }
  76. }
  77. def hideable = anythingSet || app.installationState == "COMPLETE"
  78. def sectionTitle = anythingSet ? "Select additional triggers" : "Play music when..."
  79. section(sectionTitle, hideable: hideable, hidden: true){
  80. ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
  81. ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
  82. ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
  83. ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
  84. ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
  85. ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
  86. ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
  87. ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
  88. ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
  89. ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
  90. ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
  91. ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
  92. ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
  93. }
  94. section {
  95. input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
  96. }
  97. section("More options", hideable: true, hidden: true) {
  98. input "volume", "number", title: "Set the volume", description: "0-100%", required: false
  99. input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
  100. href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
  101. input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
  102. options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
  103. input "modes", "mode", title: "Only when mode is", multiple: true, required: false
  104. input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
  105. }
  106. }
  107. }
  108. def chooseTrack() {
  109. dynamicPage(name: "chooseTrack") {
  110. section{
  111. input "song","enum",title:"Play this track", required:true, multiple: false, options: songOptions()
  112. }
  113. section([mobileOnly:true]) {
  114. label title: "Assign a name", required: false
  115. mode title: "Set for specific mode(s)", required: false
  116. }
  117. }
  118. }
  119. private anythingSet() {
  120. for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","timeOfDay","triggerModes","timeOfDay"]) {
  121. if (settings[name]) {
  122. return true
  123. }
  124. }
  125. return false
  126. }
  127. private ifUnset(Map options, String name, String capability) {
  128. if (!settings[name]) {
  129. input(options, name, capability)
  130. }
  131. }
  132. private ifSet(Map options, String name, String capability) {
  133. if (settings[name]) {
  134. input(options, name, capability)
  135. }
  136. }
  137. def installed() {
  138. log.debug "Installed with settings: ${settings}"
  139. subscribeToEvents()
  140. }
  141. def updated() {
  142. log.debug "Updated with settings: ${settings}"
  143. unsubscribe()
  144. unschedule()
  145. subscribeToEvents()
  146. }
  147. def subscribeToEvents() {
  148. log.trace "subscribeToEvents()"
  149. saveSelectedSong()
  150. subscribe(app, appTouchHandler)
  151. subscribe(contact, "contact.open", eventHandler)
  152. subscribe(contactClosed, "contact.closed", eventHandler)
  153. subscribe(acceleration, "acceleration.active", eventHandler)
  154. subscribe(motion, "motion.active", eventHandler)
  155. subscribe(mySwitch, "switch.on", eventHandler)
  156. subscribe(mySwitchOff, "switch.off", eventHandler)
  157. subscribe(arrivalPresence, "presence.present", eventHandler)
  158. subscribe(departurePresence, "presence.not present", eventHandler)
  159. subscribe(smoke, "smoke.detected", eventHandler)
  160. subscribe(smoke, "smoke.tested", eventHandler)
  161. subscribe(smoke, "carbonMonoxide.detected", eventHandler)
  162. subscribe(water, "water.wet", eventHandler)
  163. subscribe(button1, "button.pushed", eventHandler)
  164. if (triggerModes) {
  165. subscribe(location, modeChangeHandler)
  166. }
  167. if (timeOfDay) {
  168. runDaily(timeOfDay, scheduledTimeHandler)
  169. }
  170. }
  171. def eventHandler(evt) {
  172. if (allOk) {
  173. if (frequency) {
  174. def lastTime = state[frequencyKey(evt)]
  175. if (lastTime == null || now() - lastTime >= frequency * 60000) {
  176. takeAction(evt)
  177. }
  178. }
  179. else {
  180. takeAction(evt)
  181. }
  182. }
  183. }
  184. def modeChangeHandler(evt) {
  185. log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
  186. if (evt.value in triggerModes) {
  187. eventHandler(evt)
  188. }
  189. }
  190. def scheduledTimeHandler() {
  191. eventHandler(null)
  192. }
  193. def appTouchHandler(evt) {
  194. takeAction(evt)
  195. }
  196. private takeAction(evt) {
  197. log.info "Playing '$state.selectedSong"
  198. if (volume != null) {
  199. sonos.stop()
  200. pause(500)
  201. sonos.setLevel(volume)
  202. pause(500)
  203. }
  204. sonos.playTrack(state.selectedSong)
  205. if (frequency || oncePerDay) {
  206. state[frequencyKey(evt)] = now()
  207. }
  208. log.trace "Exiting takeAction()"
  209. }
  210. private frequencyKey(evt) {
  211. "lastActionTimeStamp"
  212. }
  213. private dayString(Date date) {
  214. def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
  215. if (location.timeZone) {
  216. df.setTimeZone(location.timeZone)
  217. }
  218. else {
  219. df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
  220. }
  221. df.format(date)
  222. }
  223. private oncePerDayOk(Long lastTime) {
  224. def result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
  225. log.trace "oncePerDayOk = $result"
  226. result
  227. }
  228. // TODO - centralize somehow
  229. private getAllOk() {
  230. modeOk && daysOk && timeOk
  231. }
  232. private getModeOk() {
  233. def result = !modes || modes.contains(location.mode)
  234. log.trace "modeOk = $result"
  235. result
  236. }
  237. private getDaysOk() {
  238. def result = true
  239. if (days) {
  240. def df = new java.text.SimpleDateFormat("EEEE")
  241. if (location.timeZone) {
  242. df.setTimeZone(location.timeZone)
  243. }
  244. else {
  245. df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
  246. }
  247. def day = df.format(new Date())
  248. result = days.contains(day)
  249. }
  250. log.trace "daysOk = $result"
  251. result
  252. }
  253. private getTimeOk() {
  254. def result = true
  255. if (starting && ending) {
  256. def currTime = now()
  257. def start = timeToday(starting).time
  258. def stop = timeToday(ending).time
  259. result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
  260. }
  261. log.trace "timeOk = $result"
  262. result
  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. private timeIntervalLabel()
  272. {
  273. (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
  274. }
  275. // TODO - End Centralize