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 if (settings.modes) {
107 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
109 input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
111 section([mobileOnly:true]) {
112 label title: "Assign a name", required: false
113 mode title: "Set for specific mode(s)"
118 private anythingSet() {
119 for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
120 if (settings[name]) {
127 private ifUnset(Map options, String name, String capability) {
128 if (!settings[name]) {
129 input(options, name, capability)
133 private ifSet(Map options, String name, String capability) {
134 if (settings[name]) {
135 input(options, name, capability)
140 log.debug "Installed with settings: ${settings}"
145 log.debug "Updated with settings: ${settings}"
151 def subscribeToEvents() {
152 log.trace "subscribeToEvents()"
153 subscribe(app, appTouchHandler)
154 subscribe(contact, "contact.open", eventHandler)
155 subscribe(contactClosed, "contact.closed", eventHandler)
156 subscribe(acceleration, "acceleration.active", eventHandler)
157 subscribe(motion, "motion.active", eventHandler)
158 subscribe(mySwitch, "switch.on", eventHandler)
159 subscribe(mySwitchOff, "switch.off", eventHandler)
160 subscribe(arrivalPresence, "presence.present", eventHandler)
161 subscribe(departurePresence, "presence.not present", eventHandler)
162 subscribe(smoke, "smoke.detected", eventHandler)
163 subscribe(smoke, "smoke.tested", eventHandler)
164 subscribe(smoke, "carbonMonoxide.detected", eventHandler)
165 subscribe(water, "water.wet", eventHandler)
166 subscribe(button1, "button.pushed", eventHandler)
169 subscribe(location, modeChangeHandler)
173 schedule(timeOfDay, scheduledTimeHandler)
177 def eventHandler(evt) {
179 def lastTime = state[frequencyKey(evt)]
180 if (oncePerDayOk(lastTime)) {
182 if (lastTime == null || now() - lastTime >= frequency * 60000) {
186 log.debug "Not taking action because $frequency minutes have not elapsed since last action"
194 log.debug "Not taking action because it was already taken today"
199 def modeChangeHandler(evt) {
200 log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
201 if (evt.value in triggerModes) {
206 def scheduledTimeHandler() {
210 def appTouchHandler(evt) {
214 private takeAction(evt) {
215 log.debug "takeAction($actionType)"
218 sonos.setLevel(volume as Integer)
222 switch (actionType) {
224 options ? sonos.on(options) : sonos.on()
227 options ? sonos.off(options) : sonos.off()
229 case "Toggle Play/Pause":
230 def currentStatus = sonos.currentValue("status")
231 if (currentStatus == "playing") {
232 options ? sonos.pause(options) : sonos.pause()
235 options ? sonos.play(options) : sonos.play()
238 case "Skip to Next Track":
239 options ? sonos.nextTrack(options) : sonos.nextTrack()
241 case "Play Previous Track":
242 options ? sonos.previousTrack(options) : sonos.previousTrack()
245 log.error "Action type '$actionType' not defined"
249 state.lastActionTimeStamp = now()
253 private frequencyKey(evt) {
254 //evt.deviceId ?: evt.value
255 "lastActionTimeStamp"
258 private dayString(Date date) {
259 def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
260 if (location.timeZone) {
261 df.setTimeZone(location.timeZone)
264 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
269 private oncePerDayOk(Long lastTime) {
272 result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
273 log.trace "oncePerDayOk = $result"
278 // TODO - centralize somehow
280 modeOk && daysOk && timeOk
283 private getModeOk() {
284 def result = !modes || modes.contains(location.mode)
285 log.trace "modeOk = $result"
289 private getDaysOk() {
292 def df = new java.text.SimpleDateFormat("EEEE")
293 if (location.timeZone) {
294 df.setTimeZone(location.timeZone)
297 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
299 def day = df.format(new Date())
300 result = days.contains(day)
302 log.trace "daysOk = $result"
306 private getTimeOk() {
308 if (starting && ending) {
310 def start = timeToday(starting, location?.timeZone).time
311 def stop = timeToday(ending, location?.timeZone).time
312 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
314 log.trace "timeOk = $result"
318 private hhmm(time, fmt = "h:mm a")
320 def t = timeToday(time, location.timeZone)
321 def f = new java.text.SimpleDateFormat(fmt)
322 f.setTimeZone(location.timeZone ?: timeZone(time))
326 private timeIntervalLabel()
328 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
330 // TODO - End Centralize