Update gentle-wake-up.groovy
[smartapps.git] / third-party / button-controller-for-hlgs.groovy
1 /**
2  *  Copyright 2015 SmartThings
3  *
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:
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
12  *
13  *      Button Controller for HLGS
14  *
15  *      Author: Anthony Pastor (based closely on SmartThings's original Button Controller App)
16  *      Date: 2016-5-4
17  */
18 definition(
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"
26 )
27
28 preferences {
29         page(name: "selectButton")
30         page(name: "configureButton1")
31         page(name: "configureButton2")
32         page(name: "configureButton3")
33         page(name: "configureButton4")
34
35         page(name: "timeIntervalInput", title: "Only during a certain time") {
36                 section {
37                         input "starting", "time", title: "Starting", required: false
38                         input "ending", "time", title: "Ending", required: false
39                 }
40         }
41 }
42
43 def selectButton() {
44         dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) {
45                 section {
46                         input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true
47                 }
48
49                 section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
50
51                         def timeLabel = timeIntervalLabel()
52
53                         href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
54
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"]
57
58                         input "modes", "mode", title: "Only when mode is", multiple: true, required: false
59                 }
60         }
61 }
62
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))
66 }
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))
70 }
71
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))
75 }
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))
79 }
80
81 def getButtonSections(buttonNumber) {
82         return {
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
86                 }
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
90                 }       
91                 section("Locks") {
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
94                 }
95                 section("Sonos") {
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
98                 }
99                 section("Modes") {
100                         input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
101                         input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
102                 }
103                 def phrases = location.helloHome?.getPhrases()*.label
104                 if (phrases) {
105                         section("Hello Home Actions") {
106                                 log.trace phrases
107                                 input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases
108                                 input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases
109                         }
110                 }
111         section("Sirens") {
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
114         }
115
116                 section("Custom Message") {
117                         input "textMessage_${buttonNumber}", "text", title: "Message", required: false
118                 }
119
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
123         }
124
125         section("Sms Notifications") {
126             input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
127             input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
128         }
129         }
130 }
131
132 def installed() {
133         initialize()
134 }
135
136 def updated() {
137         unsubscribe()
138         initialize()
139 }
140
141 def initialize() {
142         subscribe(buttonDevice, "button", buttonEvent)
143 }
144
145 def configured() {
146         return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
147 }
148
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"]
160 }
161
162 def buttonEvent(evt){
163         if(allOk) {
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"
168
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"
171
172                 if(recentEvents.size <= 1){
173                         switch(buttonNumber) {
174                                 case ~/.*1.*/:
175                                         executeHandlers(1, value)
176                                         break
177                                 case ~/.*2.*/:
178                                         executeHandlers(2, value)
179                                         break
180                                 case ~/.*3.*/:
181                                         executeHandlers(3, value)
182                                         break
183                                 case ~/.*4.*/:
184                                         executeHandlers(4, value)
185                                         break
186                         }
187                 } else {
188                         log.debug "Found recent button press events for $buttonNumber with value $value"
189                 }
190         }
191 }
192
193 def executeHandlers(buttonNumber, value) {
194         log.debug "executeHandlers: $buttonNumber - $value"
195
196         def lights = find('lights', buttonNumber, value)
197         if (lights != null) toggle(lights)
198
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) {
203             hlgs1.push()
204                         state.hlgs = true
205             } else {
206                 hlgs2.off()
207             state.hlgs = false
208         }
209     }    
210     
211         def locks = find('locks', buttonNumber, value)
212         if (locks != null) toggle(locks)
213
214         def sonos = find('sonos', buttonNumber, value)
215         if (sonos != null) toggle(sonos)
216
217         def mode = find('mode', buttonNumber, value)
218         if (mode != null) changeMode(mode)
219
220         def phrase = find('phrase', buttonNumber, value)
221         if (phrase != null) location.helloHome.execute(phrase)
222
223         def textMessage = findMsg('textMessage', buttonNumber)
224
225         def notifications = find('notifications', buttonNumber, value)
226         if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )
227
228         def phone = find('phone', buttonNumber, value)
229         if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")
230
231     def sirens = find('sirens', buttonNumber, value)
232     if (sirens != null) toggle(sirens)
233 }
234
235 def find(type, buttonNumber, value) {
236         def preferenceName = type + "_" + buttonNumber + "_" + value
237         def pref = settings[preferenceName]
238         if(pref != null) {
239                 log.debug "Found: $pref for $preferenceName"
240         }
241
242         return pref
243 }
244
245 def findMsg(type, buttonNumber) {
246         def preferenceName = type + "_" + buttonNumber
247         def pref = settings[preferenceName]
248         if(pref != null) {
249                 log.debug "Found: $pref for $preferenceName"
250         }
251
252         return pref
253 }
254
255 def toggle(devices) {
256         log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
257
258         if (devices*.currentValue('switch').contains('on')) {
259                 devices.off()
260         }
261         else if (devices*.currentValue('switch').contains('off')) {
262                 devices.on()
263         }
264         else if (devices*.currentValue('lock').contains('locked')) {
265                 devices.unlock()
266         }
267         else if (devices*.currentValue('lock').contains('unlocked')) {
268                 devices.lock()
269         }
270         else if (devices*.currentValue('alarm').contains('off')) {
271         devices.siren()
272     }
273         else {
274                 devices.on()
275         }
276 }
277
278 def changeMode(mode) {
279         log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes"
280
281         if (location.mode != mode && location.modes?.find { it.name == mode }) {
282                 setLocationMode(mode)
283         }
284 }
285
286 // execution filter methods
287 private getAllOk() {
288         modeOk && daysOk && timeOk
289 }
290
291 private getModeOk() {
292         def result = !modes || modes.contains(location.mode)
293         log.trace "modeOk = $result"
294         result
295 }
296
297 private getDaysOk() {
298         def result = true
299         if (days) {
300                 def df = new java.text.SimpleDateFormat("EEEE")
301                 if (location.timeZone) {
302                         df.setTimeZone(location.timeZone)
303                 }
304                 else {
305                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
306                 }
307                 def day = df.format(new Date())
308                 result = days.contains(day)
309         }
310         log.trace "daysOk = $result"
311         result
312 }
313
314 private getTimeOk() {
315         def result = true
316         if (starting && ending) {
317                 def currTime = now()
318                 def start = timeToday(starting).time
319                 def stop = timeToday(ending).time
320                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
321         }
322         log.trace "timeOk = $result"
323         result
324 }
325
326 private hhmm(time, fmt = "h:mm a")
327 {
328         def t = timeToday(time, location.timeZone)
329         def f = new java.text.SimpleDateFormat(fmt)
330         f.setTimeZone(location.timeZone ?: timeZone(time))
331         f.format(t)
332 }
333
334 private hideOptionsSection() {
335         (starting || ending || days || modes) ? false : true
336 }
337
338 private timeIntervalLabel() {
339         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
340 }