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 * Button Controller for HLGS
15 * Author: Anthony Pastor (based closely on SmartThings's original Button Controller App)
19 name: "Button Controller for HLGS",
20 namespace: "infofiend",
21 author: "Anthony Pastor",
22 description: "Control devices (including HLGS Scenes) with buttons like the Aeon Labs Minimote",
23 category: "Convenience",
24 iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png",
25 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png"
29 page(name: "selectButton")
30 page(name: "configureButton1")
31 page(name: "configureButton2")
32 page(name: "configureButton3")
33 page(name: "configureButton4")
35 page(name: "timeIntervalInput", title: "Only during a certain time") {
37 input "starting", "time", title: "Starting", required: false
38 input "ending", "time", title: "Ending", required: false
44 dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) {
46 input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true
49 section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
51 def timeLabel = timeIntervalLabel()
53 href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
55 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
56 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
58 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
63 def configureButton1() {
64 dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button",
65 nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
67 def configureButton2() {
68 dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here",
69 nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
72 def configureButton3() {
73 dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here",
74 nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
76 def configureButton4() {
77 dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here",
78 install: true, uninstall: true, getButtonSections(4))
81 def getButtonSections(buttonNumber) {
83 section("Lights (any type) - simple toggle ON / OFF") {
84 input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false
85 input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false
87 section("HLGS - Scene ON / Lights or Groups OFF (Requires BOTH of the following preferences to be set)") {
88 input "hlgs1_${buttonNumber}_pushed", "capability.momentary", title: "First Push of this Button calls this HLGS Scene (momentary device)", multiple: false, required: false
89 input "hlgs2_${buttonNumber}_pushed", "capability.switch", title: "Second Push of this Button turns off these HLGS Lights and Groups", multiple: true, required: false
92 input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false
93 input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false
96 input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false
97 input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false
100 input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
101 input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
103 def phrases = location.helloHome?.getPhrases()*.label
105 section("Hello Home Actions") {
107 input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases
108 input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases
112 input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false
113 input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false
116 section("Custom Message") {
117 input "textMessage_${buttonNumber}", "text", title: "Message", required: false
120 section("Push Notifications") {
121 input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false
122 input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false
125 section("Sms Notifications") {
126 input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
127 input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
142 subscribe(buttonDevice, "button", buttonEvent)
146 return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
149 def buttonConfigured(idx) {
150 return settings["lights_$idx_pushed"] ||
151 settings["locks_$idx_pushed"] ||
152 settings["sonos_$idx_pushed"] ||
153 settings["mode_$idx_pushed"] ||
154 settings["notifications_$idx_pushed"] ||
155 settings["sirens_$idx_pushed"] ||
156 settings["notifications_$idx_pushed"] ||
157 settings["phone_$idx_pushed"] ||
158 settings["hlgs1_$idx_pushed"] ||
159 settings["hlgs2_$idx_pushed"]
162 def buttonEvent(evt){
164 def buttonNumber = evt.data // why doesn't jsonData work? always returning [:]
165 def value = evt.value
166 log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)"
167 log.debug "button: $buttonNumber, value: $value"
169 def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
170 log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds"
172 if(recentEvents.size <= 1){
173 switch(buttonNumber) {
175 executeHandlers(1, value)
178 executeHandlers(2, value)
181 executeHandlers(3, value)
184 executeHandlers(4, value)
188 log.debug "Found recent button press events for $buttonNumber with value $value"
193 def executeHandlers(buttonNumber, value) {
194 log.debug "executeHandlers: $buttonNumber - $value"
196 def lights = find('lights', buttonNumber, value)
197 if (lights != null) toggle(lights)
199 def hlgs1 = find('hlgs1', buttonNumber, value)
200 def hlgs2 = find('hlgs2', buttonNumber, value)
201 if (hlgs1 != null && hlgs2 != null) {
202 if (state.hlgs != true) {
211 def locks = find('locks', buttonNumber, value)
212 if (locks != null) toggle(locks)
214 def sonos = find('sonos', buttonNumber, value)
215 if (sonos != null) toggle(sonos)
217 def mode = find('mode', buttonNumber, value)
218 if (mode != null) changeMode(mode)
220 def phrase = find('phrase', buttonNumber, value)
221 if (phrase != null) location.helloHome.execute(phrase)
223 def textMessage = findMsg('textMessage', buttonNumber)
225 def notifications = find('notifications', buttonNumber, value)
226 if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )
228 def phone = find('phone', buttonNumber, value)
229 if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")
231 def sirens = find('sirens', buttonNumber, value)
232 if (sirens != null) toggle(sirens)
235 def find(type, buttonNumber, value) {
236 def preferenceName = type + "_" + buttonNumber + "_" + value
237 def pref = settings[preferenceName]
239 log.debug "Found: $pref for $preferenceName"
245 def findMsg(type, buttonNumber) {
246 def preferenceName = type + "_" + buttonNumber
247 def pref = settings[preferenceName]
249 log.debug "Found: $pref for $preferenceName"
255 def toggle(devices) {
256 log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
258 if (devices*.currentValue('switch').contains('on')) {
261 else if (devices*.currentValue('switch').contains('off')) {
264 else if (devices*.currentValue('lock').contains('locked')) {
267 else if (devices*.currentValue('lock').contains('unlocked')) {
270 else if (devices*.currentValue('alarm').contains('off')) {
278 def changeMode(mode) {
279 log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes"
281 if (location.mode != mode && location.modes?.find { it.name == mode }) {
282 setLocationMode(mode)
286 // execution filter methods
288 modeOk && daysOk && timeOk
291 private getModeOk() {
292 def result = !modes || modes.contains(location.mode)
293 log.trace "modeOk = $result"
297 private getDaysOk() {
300 def df = new java.text.SimpleDateFormat("EEEE")
301 if (location.timeZone) {
302 df.setTimeZone(location.timeZone)
305 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
307 def day = df.format(new Date())
308 result = days.contains(day)
310 log.trace "daysOk = $result"
314 private getTimeOk() {
316 if (starting && ending) {
318 def start = timeToday(starting).time
319 def stop = timeToday(ending).time
320 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
322 log.trace "timeOk = $result"
326 private hhmm(time, fmt = "h:mm a")
328 def t = timeToday(time, location.timeZone)
329 def f = new java.text.SimpleDateFormat(fmt)
330 f.setTimeZone(location.timeZone ?: timeZone(time))
334 private hideOptionsSection() {
335 (starting || ending || days || modes) ? false : true
338 private timeIntervalLabel() {
339 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""