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"
85 input "bose", "capability.musicPlayer", title: "Bose® SoundTouch® music player", required: true
87 section("More options", hideable: true, hidden: true) {
88 input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
89 input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
90 href "timeIntervalInput", title: "Only during a certain time"
91 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
92 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
93 //if (settings.modes) {
94 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
96 input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
98 section([mobileOnly:true]) {
99 label title: "Assign a name", required: false
100 mode title: "Set for specific mode(s)"
105 private anythingSet() {
106 for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
107 if (settings[name]) {
114 private ifUnset(Map options, String name, String capability) {
115 if (!settings[name]) {
116 input(options, name, capability)
120 private ifSet(Map options, String name, String capability) {
121 if (settings[name]) {
122 input(options, name, capability)
127 log.debug "Installed with settings: ${settings}"
132 log.debug "Updated with settings: ${settings}"
138 def subscribeToEvents() {
139 log.trace "subscribeToEvents()"
140 subscribe(app, appTouchHandler)
141 subscribe(contact, "contact.open", eventHandler1)
142 subscribe(contactClosed, "contact.closed", eventHandler1)
143 subscribe(acceleration, "acceleration.active", eventHandler1)
144 subscribe(motion, "motion.active", eventHandler1)
145 subscribe(mySwitch, "switch.on", eventHandler1)
146 subscribe(mySwitchOff, "switch.off", eventHandler1)
147 subscribe(arrivalPresence, "presence.present", eventHandler1)
148 subscribe(departurePresence, "presence.not present", eventHandler1)
149 subscribe(smoke, "smoke.detected", eventHandler1)
150 subscribe(smoke, "smoke.tested", eventHandler1)
151 subscribe(smoke, "carbonMonoxide.detected", eventHandler1)
152 subscribe(water, "water.wet", eventHandler1)
153 subscribe(button1, "button.pushed", eventHandler1)
156 subscribe(location, modeChangeHandler)
160 schedule(timeOfDay, scheduledTimeHandler)
164 def eventHandler1(evt) {
166 def lastTime = state[frequencyKey(evt)]
167 if (oncePerDayOk(lastTime)) {
169 if (lastTime == null || now() - lastTime >= frequency * 60000) {
173 log.debug "Not taking action because $frequency minutes have not elapsed since last action"
181 log.debug "Not taking action because it was already taken today"
186 def modeChangeHandler(evt) {
187 log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
188 if (evt.value in triggerModes) {
193 def scheduledTimeHandler() {
197 def appTouchHandler(evt) {
201 private takeAction(evt) {
202 log.debug "takeAction($actionType)"
205 bose.setLevel(volume as Integer)
209 switch (actionType) {
210 case "Turn On & Play":
211 options ? bose.on(options) : bose.on()
214 options ? bose.off(options) : bose.off()
216 case "Toggle Play/Pause":
217 def currentStatus = bose.currentValue("playpause")
218 if (currentStatus == "play") {
219 options ? bose.pause(options) : bose.pause()
221 else if (currentStatus == "pause") {
222 options ? bose.play(options) : bose.play()
225 case "Skip to Next Track":
226 options ? bose.nextTrack(options) : bose.nextTrack()
228 case "Skip to Beginning/Previous Track":
229 options ? bose.previousTrack(options) : bose.previousTrack()
232 log.error "Action type '$actionType' not defined"
236 state.lastActionTimeStamp = now()
240 private frequencyKey(evt) {
241 //evt.deviceId ?: evt.value
242 "lastActionTimeStamp"
245 private dayString(Date date) {
246 def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
247 if (location.timeZone) {
248 df.setTimeZone(location.timeZone)
251 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
256 private oncePerDayOk(Long lastTime) {
259 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
260 log.trace "oncePerDayOk = $result"
265 // TODO - centralize somehow
267 modeOk && daysOk && timeOk
270 private getModeOk() {
271 def result = !modes || modes.contains(location.mode)
272 log.trace "modeOk = $result"
276 private getDaysOk() {
279 def df = new java.text.SimpleDateFormat("EEEE")
280 if (location.timeZone) {
281 df.setTimeZone(location.timeZone)
284 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
286 def day = df.format(new Date())
287 result = days.contains(day)
289 log.trace "daysOk = $result"
293 private getTimeOk() {
295 if (starting && ending) {
297 def start = timeToday(starting, location?.timeZone).time
298 def stop = timeToday(ending, location?.timeZone).time
299 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
301 log.trace "timeOk = $result"
305 private hhmm(time, fmt = "h:mm a")
307 def t = timeToday(time, location.timeZone)
308 def f = new java.text.SimpleDateFormat(fmt)
309 f.setTimeZone(location.timeZone ?: timeZone(time))
313 private timeIntervalLabel()
315 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
317 // TODO - End Centralize