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.
20 name: "Speaker Control",
21 namespace: "smartthings",
22 author: "SmartThings",
23 description: "Play or pause your Speaker when certain actions take place in your home.",
24 category: "SmartThings Labs",
25 iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
26 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
30 page(name: "mainPage", title: "Control your Speaker when something happens", install: true, uninstall: true)
31 page(name: "timeIntervalInput", title: "Only during a certain time") {
33 input "starting", "time", title: "Starting", required: false
34 input "ending", "time", title: "Ending", required: false
39 // input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
40 // input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
41 // input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
42 // input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
43 // input "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
44 // input "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
45 // input "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
46 // input "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
47 // input "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
48 // input "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
49 // input "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
50 // input "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
51 // input "timeOfDay", "time", title: "At a Scheduled Time", required: false
54 dynamicPage(name: "mainPage") {
55 def anythingSet = anythingSet()
58 ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
59 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
60 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
61 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
62 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
63 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
64 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
65 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
66 ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
67 ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
68 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
69 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
70 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
73 section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
74 ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
75 ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
76 ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
77 ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
78 ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
79 ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
80 ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
81 ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
82 ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
83 ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
84 ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
85 ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
86 ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
88 section("Perform this action"){
89 input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
98 input "sonos", "capability.musicPlayer", title: "Speaker music player", required: true
100 section("More options", hideable: true, hidden: true) {
101 input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
102 input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
103 //href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
104 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
105 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
106 // Just allow this to be set
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", eventHandler)
156 subscribe(contactClosed, "contact.closed", eventHandler)
157 subscribe(acceleration, "acceleration.active", eventHandler)
158 subscribe(motion, "motion.active", eventHandler)
159 subscribe(mySwitch, "switch.on", eventHandler)
160 subscribe(mySwitchOff, "switch.off", eventHandler)
161 subscribe(arrivalPresence, "presence.present", eventHandler)
162 subscribe(departurePresence, "presence.not present", eventHandler)
163 subscribe(smoke, "smoke.detected", eventHandler)
164 subscribe(smoke, "smoke.tested", eventHandler)
165 subscribe(smoke, "carbonMonoxide.detected", eventHandler)
166 subscribe(water, "water.wet", eventHandler)
167 subscribe(button1, "button.pushed", eventHandler)
170 subscribe(location, modeChangeHandler)
174 schedule(timeOfDay, scheduledTimeHandler)
178 def eventHandler(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 sonos.setLevel(volume as Integer)
223 switch (actionType) {
225 options ? sonos.on(options) : sonos.on()
228 options ? sonos.off(options) : sonos.off()
230 case "Toggle Play/Pause":
231 def currentStatus = sonos.currentValue("status")
232 if (currentStatus == "playing") {
233 options ? sonos.pause(options) : sonos.pause()
236 options ? sonos.play(options) : sonos.play()
239 case "Skip to Next Track":
240 options ? sonos.nextTrack(options) : sonos.nextTrack()
242 case "Play Previous Track":
243 options ? sonos.previousTrack(options) : sonos.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