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
40 dynamicPage(name: "mainPage") {
41 def anythingSet = anythingSet()
44 ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
45 ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
46 ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
47 ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
48 ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
49 ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
50 ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
51 ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
52 ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
53 ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
54 ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
55 ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
56 ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
59 section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
60 ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
61 ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
62 ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
63 ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
64 ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
65 ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
66 ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
67 ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
68 ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
69 ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
70 ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
71 ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
72 ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
74 section("Perform this action"){
75 input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
84 input "sonos", "capability.musicPlayer", title: "Speaker music player", required: true
86 section("More options", hideable: true, hidden: true) {
87 input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
88 input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
89 href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
90 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
91 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
93 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
95 input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
97 section([mobileOnly:true]) {
98 label title: "Assign a name", required: false
99 mode title: "Set for specific mode(s)"
104 private anythingSet() {
105 for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
106 if (settings[name]) {
113 private ifUnset(Map options, String name, String capability) {
114 if (!settings[name]) {
115 input(options, name, capability)
119 private ifSet(Map options, String name, String capability) {
120 if (settings[name]) {
121 input(options, name, capability)
126 log.debug "Installed with settings: ${settings}"
131 log.debug "Updated with settings: ${settings}"
137 def subscribeToEvents() {
138 log.trace "subscribeToEvents()"
139 subscribe(app, appTouchHandler)
140 subscribe(contact, "contact.open", eventHandler)
141 subscribe(contactClosed, "contact.closed", eventHandler)
142 subscribe(acceleration, "acceleration.active", eventHandler)
143 subscribe(motion, "motion.active", eventHandler)
144 subscribe(mySwitch, "switch.on", eventHandler)
145 subscribe(mySwitchOff, "switch.off", eventHandler)
146 subscribe(arrivalPresence, "presence.present", eventHandler)
147 subscribe(departurePresence, "presence.not present", eventHandler)
148 subscribe(smoke, "smoke.detected", eventHandler)
149 subscribe(smoke, "smoke.tested", eventHandler)
150 subscribe(smoke, "carbonMonoxide.detected", eventHandler)
151 subscribe(water, "water.wet", eventHandler)
152 subscribe(button1, "button.pushed", eventHandler)
155 subscribe(location, modeChangeHandler)
159 schedule(timeOfDay, scheduledTimeHandler)
163 def eventHandler(evt) {
165 def lastTime = state[frequencyKey(evt)]
166 if (oncePerDayOk(lastTime)) {
168 if (lastTime == null || now() - lastTime >= frequency * 60000) {
172 log.debug "Not taking action because $frequency minutes have not elapsed since last action"
180 log.debug "Not taking action because it was already taken today"
185 def modeChangeHandler(evt) {
186 log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
187 if (evt.value in triggerModes) {
192 def scheduledTimeHandler() {
196 def appTouchHandler(evt) {
200 private takeAction(evt) {
201 log.debug "takeAction($actionType)"
204 sonos.setLevel(volume as Integer)
208 switch (actionType) {
210 options ? sonos.on(options) : sonos.on()
213 options ? sonos.off(options) : sonos.off()
215 case "Toggle Play/Pause":
216 def currentStatus = sonos.currentValue("status")
217 if (currentStatus == "playing") {
218 options ? sonos.pause(options) : sonos.pause()
221 options ? sonos.play(options) : sonos.play()
224 case "Skip to Next Track":
225 options ? sonos.nextTrack(options) : sonos.nextTrack()
227 case "Play Previous Track":
228 options ? sonos.previousTrack(options) : sonos.previousTrack()
231 log.error "Action type '$actionType' not defined"
235 state.lastActionTimeStamp = now()
239 private frequencyKey(evt) {
240 //evt.deviceId ?: evt.value
241 "lastActionTimeStamp"
244 private dayString(Date date) {
245 def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
246 if (location.timeZone) {
247 df.setTimeZone(location.timeZone)
250 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
255 private oncePerDayOk(Long lastTime) {
258 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
259 log.trace "oncePerDayOk = $result"
264 // TODO - centralize somehow
266 modeOk && daysOk && timeOk
269 private getModeOk() {
270 def result = !modes || modes.contains(location.mode)
271 log.trace "modeOk = $result"
275 private getDaysOk() {
278 def df = new java.text.SimpleDateFormat("EEEE")
279 if (location.timeZone) {
280 df.setTimeZone(location.timeZone)
283 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
285 def day = df.format(new Date())
286 result = days.contains(day)
288 log.trace "daysOk = $result"
292 private getTimeOk() {
294 if (starting && ending) {
296 def start = timeToday(starting, location?.timeZone).time
297 def stop = timeToday(ending, location?.timeZone).time
298 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
300 log.trace "timeOk = $result"
304 private hhmm(time, fmt = "h:mm a")
306 def t = timeToday(time, location.timeZone)
307 def f = new java.text.SimpleDateFormat(fmt)
308 f.setTimeZone(location.timeZone ?: timeZone(time))
312 private timeIntervalLabel()
314 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
316 // TODO - End Centralize