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
40 // input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
41 // input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
42 // input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
43 // input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
44 // input "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
45 // input "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
46 // input "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
47 // input "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
48 // input "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
49 // input "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
50 // input "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
51 // input "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
52 // input "timeOfDay", "time", title: "At a Scheduled Time", required: false
55 dynamicPage(name: "mainPage") {
56 def anythingSet = anythingSet()
59 ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
60 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
61 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
62 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
63 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
64 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
65 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
66 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
67 ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
68 ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
69 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
70 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
71 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
74 section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
75 ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
76 ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
77 ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
78 ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
79 ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
80 ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
81 ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
82 ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
83 ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
84 ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
85 ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
86 ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
87 ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
89 section("Perform this action"){
90 input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
95 "Skip to Beginning/Previous Track"
99 input "bose", "capability.musicPlayer", title: "Bose® SoundTouch® music player", required: true
101 section("More options", hideable: true, hidden: true) {
102 input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
103 input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
104 href "timeIntervalInput", title: "Only during a certain time"
105 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
106 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
107 //if (settings.modes) {
108 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
110 input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
112 section([mobileOnly:true]) {
113 label title: "Assign a name", required: false
114 mode title: "Set for specific mode(s)"
119 private anythingSet() {
120 for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
121 if (settings[name]) {
128 private ifUnset(Map options, String name, String capability) {
129 if (!settings[name]) {
130 input(options, name, capability)
134 private ifSet(Map options, String name, String capability) {
135 if (settings[name]) {
136 input(options, name, capability)
141 log.debug "Installed with settings: ${settings}"
146 log.debug "Updated with settings: ${settings}"
152 def subscribeToEvents() {
153 log.trace "subscribeToEvents()"
154 subscribe(app, appTouchHandler)
155 subscribe(contact, "contact.open", eventHandler1)
156 subscribe(contactClosed, "contact.closed", eventHandler1)
157 subscribe(acceleration, "acceleration.active", eventHandler1)
158 subscribe(motion, "motion.active", eventHandler1)
159 subscribe(mySwitch, "switch.on", eventHandler1)
160 subscribe(mySwitchOff, "switch.off", eventHandler1)
161 subscribe(arrivalPresence, "presence.present", eventHandler1)
162 subscribe(departurePresence, "presence.not present", eventHandler1)
163 subscribe(smoke, "smoke.detected", eventHandler1)
164 subscribe(smoke, "smoke.tested", eventHandler1)
165 subscribe(smoke, "carbonMonoxide.detected", eventHandler1)
166 subscribe(water, "water.wet", eventHandler1)
167 subscribe(button1, "button.pushed", eventHandler1)
170 subscribe(location, modeChangeHandler)
174 schedule(timeOfDay, scheduledTimeHandler)
178 def eventHandler1(evt) {
180 def lastTime = state[frequencyKey(evt)]
181 if (oncePerDayOk(lastTime)) {
183 if (lastTime == null || now() - lastTime >= frequency * 60000) {
187 log.debug "Not taking action because $frequency minutes have not elapsed since last action"
195 log.debug "Not taking action because it was already taken today"
200 def modeChangeHandler(evt) {
201 log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
202 if (evt.value in triggerModes) {
207 def scheduledTimeHandler() {
211 def appTouchHandler(evt) {
215 private takeAction(evt) {
216 log.debug "takeAction($actionType)"
219 bose.setLevel(volume as Integer)
223 switch (actionType) {
224 case "Turn On & Play":
225 options ? bose.on(options) : bose.on()
228 options ? bose.off(options) : bose.off()
230 case "Toggle Play/Pause":
231 def currentStatus = bose.currentValue("playpause")
232 if (currentStatus == "play") {
233 options ? bose.pause(options) : bose.pause()
235 else if (currentStatus == "pause") {
236 options ? bose.play(options) : bose.play()
239 case "Skip to Next Track":
240 options ? bose.nextTrack(options) : bose.nextTrack()
242 case "Skip to Beginning/Previous Track":
243 options ? bose.previousTrack(options) : bose.previousTrack()
246 log.error "Action type '$actionType' not defined"
250 state.lastActionTimeStamp = now()
254 private frequencyKey(evt) {
255 //evt.deviceId ?: evt.value
256 "lastActionTimeStamp"
259 private dayString(Date date) {
260 def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
261 if (location.timeZone) {
262 df.setTimeZone(location.timeZone)
265 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
270 private oncePerDayOk(Long lastTime) {
273 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
274 log.trace "oncePerDayOk = $result"
279 // TODO - centralize somehow
281 modeOk && daysOk && timeOk
284 private getModeOk() {
285 def result = !modes || modes.contains(location.mode)
286 log.trace "modeOk = $result"
290 private getDaysOk() {
293 def df = new java.text.SimpleDateFormat("EEEE")
294 if (location.timeZone) {
295 df.setTimeZone(location.timeZone)
298 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
300 def day = df.format(new Date())
301 result = days.contains(day)
303 log.trace "daysOk = $result"
307 private getTimeOk() {
309 if (starting && ending) {
311 def start = timeToday(starting, location?.timeZone).time
312 def stop = timeToday(ending, location?.timeZone).time
313 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
315 log.trace "timeOk = $result"
319 private hhmm(time, fmt = "h:mm a")
321 def t = timeToday(time, location.timeZone)
322 def f = new java.text.SimpleDateFormat(fmt)
323 f.setTimeZone(location.timeZone ?: timeZone(time))
327 private timeIntervalLabel()
329 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
331 // TODO - End Centralize