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.
13 * Bose® SoundTouch® Control
15 * Author: SmartThings & Joe Geiger
20 name: "Bose® SoundTouch® Control",
21 namespace: "smartthings",
22 author: "SmartThings & Joe Geiger",
23 description: "Control your Bose® SoundTouch® when certain actions take place in your home.",
24 category: "SmartThings Labs",
25 iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
26 iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
27 iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png"
31 page(name: "mainPage", title: "Control your Bose® SoundTouch® when something happens", install: true, uninstall: true)
32 page(name: "timeIntervalInput", title: "Only during a certain time") {
34 input "starting", "time", title: "Starting", required: false
35 input "ending", "time", title: "Ending", required: false
41 dynamicPage(name: "mainPage") {
42 def anythingSet = anythingSet()
45 ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
46 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
47 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
48 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
49 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
50 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
51 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
52 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
53 ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
54 ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
55 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
56 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
57 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
60 section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
61 ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
62 ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
63 ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
64 ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
65 ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
66 ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
67 ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
68 ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
69 ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
70 ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
71 ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
72 ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
73 ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
75 section("Perform this action"){
76 input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
81 "Skip to Beginning/Previous Track",
91 input "bose", "capability.musicPlayer", title: "Bose® SoundTouch® music player", required: true
93 section("More options", hideable: true, hidden: true) {
94 input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
95 input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
96 href "timeIntervalInput", title: "Only during a certain time"
97 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
98 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
100 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
102 input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
104 section([mobileOnly:true]) {
105 label title: "Assign a name", required: false
106 mode title: "Set for specific mode(s)"
111 private anythingSet() {
112 for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
113 if (settings[name]) {
120 private ifUnset(Map options, String name, String capability) {
121 if (!settings[name]) {
122 input(options, name, capability)
126 private ifSet(Map options, String name, String capability) {
127 if (settings[name]) {
128 input(options, name, capability)
133 log.debug "Installed with settings: ${settings}"
138 log.debug "Updated with settings: ${settings}"
144 def subscribeToEvents() {
145 log.trace "subscribeToEvents()"
146 subscribe(app, appTouchHandler)
147 subscribe(contact, "contact.open", eventHandler)
148 subscribe(contactClosed, "contact.closed", eventHandler)
149 subscribe(acceleration, "acceleration.active", eventHandler)
150 subscribe(motion, "motion.active", eventHandler)
151 subscribe(mySwitch, "switch.on", eventHandler)
152 subscribe(mySwitchOff, "switch.off", eventHandler)
153 subscribe(arrivalPresence, "presence.present", eventHandler)
154 subscribe(departurePresence, "presence.not present", eventHandler)
155 subscribe(smoke, "smoke.detected", eventHandler)
156 subscribe(smoke, "smoke.tested", eventHandler)
157 subscribe(smoke, "carbonMonoxide.detected", eventHandler)
158 subscribe(water, "water.wet", eventHandler)
159 subscribe(button1, "button.pushed", eventHandler)
162 subscribe(location, modeChangeHandler)
166 schedule(timeOfDay, scheduledTimeHandler)
170 def eventHandler(evt) {
172 def lastTime = state[frequencyKey(evt)]
173 if (oncePerDayOk(lastTime)) {
175 if (lastTime == null || now() - lastTime >= frequency * 60000) {
179 log.debug "Not taking action because $frequency minutes have not elapsed since last action"
187 log.debug "Not taking action because it was already taken today"
192 def modeChangeHandler(evt) {
193 log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
194 if (evt.value in triggerModes) {
199 def scheduledTimeHandler() {
203 def appTouchHandler(evt) {
207 private takeAction(evt) {
208 log.debug "takeAction($actionType)"
211 bose.setLevel(volume as Integer)
215 switch (actionType) {
216 case "Turn On & Play":
217 options ? bose.on(options) : bose.on()
220 options ? bose.off(options) : bose.off()
222 case "Toggle Play/Pause":
223 def currentStatus = bose.currentValue("playpause")
224 if (currentStatus == "play") {
225 options ? bose.pause(options) : bose.pause()
227 else if (currentStatus == "pause") {
228 options ? bose.play(options) : bose.play()
231 case "Skip to Next Track":
232 options ? bose.nextTrack(options) : bose.nextTrack()
234 case "Skip to Beginning/Previous Track":
235 options ? bose.previousTrack(options) : bose.previousTrack()
237 case "Play Preset 1":
238 options ? bose.preset1(options) : bose.preset1()
240 case "Play Preset 2":
241 options ? bose.preset2(options) : bose.preset2()
243 case "Play Preset 3":
244 options ? bose.preset3(options) : bose.preset3()
246 case "Play Preset 4":
247 options ? bose.preset4(options) : bose.preset4()
249 case "Play Preset 5":
250 options ? bose.preset5(options) : bose.preset5()
252 case "Play Preset 6":
253 options ? bose.preset6(options) : bose.preset6()
256 log.error "Action type '$actionType' not defined"
260 state.lastActionTimeStamp = now()
264 private frequencyKey(evt) {
265 //evt.deviceId ?: evt.value
266 "lastActionTimeStamp"
269 private dayString(Date date) {
270 def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
271 if (location.timeZone) {
272 df.setTimeZone(location.timeZone)
275 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
280 private oncePerDayOk(Long lastTime) {
283 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
284 log.trace "oncePerDayOk = $result"
289 // TODO - centralize somehow
291 modeOk && daysOk && timeOk
294 private getModeOk() {
295 def result = !modes || modes.contains(location.mode)
296 log.trace "modeOk = $result"
300 private getDaysOk() {
303 def df = new java.text.SimpleDateFormat("EEEE")
304 if (location.timeZone) {
305 df.setTimeZone(location.timeZone)
308 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
310 def day = df.format(new Date())
311 result = days.contains(day)
313 log.trace "daysOk = $result"
317 private getTimeOk() {
319 if (starting && ending) {
321 def start = timeToday(starting, location?.timeZone).time
322 def stop = timeToday(ending, location?.timeZone).time
323 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
325 log.trace "timeOk = $result"
329 private hhmm(time, fmt = "h:mm a")
331 def t = timeToday(time, location.timeZone)
332 def f = new java.text.SimpleDateFormat(fmt)
333 f.setTimeZone(location.timeZone ?: timeZone(time))
337 private timeIntervalLabel()
339 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
341 // TODO - End Centralize