Update gidjit-hub.groovy
[smartapps.git] / official / beacon-control.groovy
1 /**
2  *  Beacon Control
3  *
4  *  Copyright 2014 Physical Graph Corporation
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7  *  in compliance with the License. You may obtain a copy of the License at:
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13  *  for the specific language governing permissions and limitations under the License.
14  *
15  */
16 definition(
17         name: "Beacon Control",
18         category: "SmartThings Internal",
19         namespace: "smartthings",
20         author: "SmartThings",
21         description: "Execute a Hello, Home phrase, turn on or off some lights, and/or lock or unlock your door when you enter or leave a monitored region",
22         iconUrl: "https://s3.amazonaws.com/smartapp-icons/MiscHacking/mindcontrol.png",
23         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MiscHacking/mindcontrol@2x.png"
24 )
25
26 preferences {
27         page(name: "timeIntervalInput", title: "Only during a certain time") {
28                 section {
29                         input "starting", "time", title: "Starting", required: false
30                         input "ending", "time", title: "Ending", required: false
31                 }
32         }
33         
34         page(name: "mainPage")
35 }
36
37 def mainPage() {
38         dynamicPage(name: "mainPage", install: true, uninstall: true) {
39
40                 section("Where do you want to watch?") {
41                         input name: "beacons", type: "capability.beacon", title: "Select your beacon(s)", 
42                                 multiple: true, required: true
43                 }
44
45                 section("Who do you want to watch for?") {
46                         input name: "phones", type: "device.mobilePresence", title: "Select your phone(s)", 
47                                 multiple: true, required: true
48                 }
49
50                 section("What do you want to do on arrival?") {
51                         input name: "arrivalPhrase", type: "enum", title: "Execute a phrase", 
52                                 options: listPhrases(), required: false
53                         input "arrivalOnSwitches", "capability.switch", title: "Turn on some switches", 
54                                 multiple: true, required: false
55                         input "arrivalOffSwitches", "capability.switch", title: "Turn off some switches", 
56                                 multiple: true, required: false
57                         input "arrivalLocks", "capability.lock", title: "Unlock the door",
58                                 multiple: true, required: false
59                 }
60
61                 section("What do you want to do on departure?") {
62                         input name: "departPhrase", type: "enum", title: "Execute a phrase", 
63                                 options: listPhrases(), required: false
64                         input "departOnSwitches", "capability.switch", title: "Turn on some switches", 
65                                 multiple: true, required: false
66                         input "departOffSwitches", "capability.switch", title: "Turn off some switches", 
67                                 multiple: true, required: false
68                         input "departLocks", "capability.lock", title: "Lock the door",
69                                 multiple: true, required: false
70                 }
71
72                 section("Do you want to be notified?") {
73                         input "pushNotification", "bool", title: "Send a push notification"
74                         input "phone", "phone", title: "Send a text message", description: "Tap to enter phone number", 
75                                 required: false
76                 }
77
78                 section {
79                         label title: "Give your automation a name", description: "e.g. Goodnight Home, Wake Up"
80                 }
81
82                 def timeLabel = timeIntervalLabel()
83                 section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
84                         href "timeIntervalInput", title: "Only during a certain time", 
85                                 description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
86
87                         input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
88                                 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
89
90                         input "modes", "mode", title: "Only when mode is", multiple: true, required: false
91                 }
92         }
93 }
94
95 // Lifecycle management
96 def installed() {
97         log.debug "<beacon-control> Installed with settings: ${settings}"
98         initialize()
99 }
100
101 def updated() {
102         log.debug "<beacon-control> Updated with settings: ${settings}"
103         unsubscribe()
104         initialize()
105 }
106
107 def initialize() {
108         subscribe(beacons, "presence", beaconHandler)
109 }
110
111 // Event handlers
112 def beaconHandler(evt) {
113         log.debug "<beacon-control> beaconHandler: $evt"
114
115         if (allOk) {
116                 def data = new groovy.json.JsonSlurper().parseText(evt.data)
117                 // removed logging of device names. can be added back for debugging
118                 //log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
119
120                 def beaconName = getBeaconName(evt)
121                 // removed logging of device names. can be added back for debugging
122                 //log.debug "<beacon-control> beaconName: $beaconName"
123
124                 def phoneName = getPhoneName(data)
125                 // removed logging of device names. can be added back for debugging
126                 //log.debug "<beacon-control> phoneName: $phoneName"
127                 if (phoneName != null) {
128             def action = data.presence == "1" ? "arrived" : "left"
129             def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"
130
131             if (action == "arrived") {
132                 msg = arriveActions(msg)
133             }
134             else if (action == "left") {
135                 msg = departActions(msg)
136             }
137             log.debug "<beacon-control> msg: $msg"
138
139             if (pushNotification || phone) {
140                 def options = [
141                     method: (pushNotification && phone) ? "both" : (pushNotification ? "push" : "sms"),
142                     phone: phone
143                 ]
144                 sendNotification(msg, options)
145             }
146         }
147         }
148 }
149
150 // Helpers
151 private arriveActions(msg) {
152         if (arrivalPhrase || arrivalOnSwitches || arrivalOffSwitches || arrivalLocks) msg += ", so"
153         
154         if (arrivalPhrase) {
155                 log.debug "<beacon-control> executing: $arrivalPhrase"
156                 executePhrase(arrivalPhrase)
157                 msg += " ${prefix('executed')} $arrivalPhrase."
158         }
159         if (arrivalOnSwitches) {
160                 log.debug "<beacon-control> turning on: $arrivalOnSwitches"
161                 arrivalOnSwitches.on()
162                 msg += " ${prefix('turned')} ${list(arrivalOnSwitches)} on."
163         }
164         if (arrivalOffSwitches) {
165                 log.debug "<beacon-control> turning off: $arrivalOffSwitches"
166                 arrivalOffSwitches.off()
167                 msg += " ${prefix('turned')} ${list(arrivalOffSwitches)} off."
168         }
169         if (arrivalLocks) {
170                 log.debug "<beacon-control> unlocking: $arrivalLocks"
171                 arrivalLocks.unlock()
172                 msg += " ${prefix('unlocked')} ${list(arrivalLocks)}."
173         }
174         msg
175 }
176
177 private departActions(msg) {
178         if (departPhrase || departOnSwitches || departOffSwitches || departLocks) msg += ", so"
179         
180         if (departPhrase) {
181                 log.debug "<beacon-control> executing: $departPhrase"
182                 executePhrase(departPhrase)
183                 msg += " ${prefix('executed')} $departPhrase."
184         }
185         if (departOnSwitches) {
186                 log.debug "<beacon-control> turning on: $departOnSwitches"
187                 departOnSwitches.on()
188                 msg += " ${prefix('turned')} ${list(departOnSwitches)} on."
189         }
190         if (departOffSwitches) {
191                 log.debug "<beacon-control> turning off: $departOffSwitches"
192                 departOffSwitches.off()
193                 msg += " ${prefix('turned')} ${list(departOffSwitches)} off."
194         }
195         if (departLocks) {
196                 log.debug "<beacon-control> unlocking: $departLocks"
197                 departLocks.lock()
198                 msg += " ${prefix('locked')} ${list(departLocks)}."
199         }
200         msg
201 }
202
203 private prefix(word) {
204         def result
205         def index = settings.prefixIndex == null ? 0 : settings.prefixIndex + 1
206         switch (index) {
207                 case 0:
208                         result = "I $word"
209                         break
210                 case 1:
211                         result = "I also $word"
212                         break
213                 case 2:
214                         result = "And I $word"
215                         break
216                 default:
217                         result = "And $word"
218                         break
219         }
220
221         settings.prefixIndex = index
222         log.trace "prefix($word'): $result"
223         result
224 }
225
226 private listPhrases() {
227         location.helloHome.getPhrases().label
228 }
229
230 private executePhrase(phraseName) {
231         if (phraseName) {
232                 location.helloHome.execute(phraseName)
233                 log.debug "<beacon-control> executed phrase: $phraseName"
234         }
235 }
236
237 private getBeaconName(evt) {
238         def beaconName = beacons.find { b -> b.id == evt.deviceId }
239         return beaconName
240 }
241
242 private getPhoneName(data) {    
243         def phoneName = phones.find { phone ->
244                 // Work around DNI bug in data
245                 def pParts = phone.deviceNetworkId.split('\\|')
246                 def dParts = data.dni.split('\\|')
247         pParts[0] == dParts[0]
248         }
249         return phoneName
250 }
251
252 private hideOptionsSection() {
253         (starting || ending || days || modes) ? false : true
254 }
255
256 private getAllOk() {
257         //modeOk && daysOk && timeOk
258         return true
259 }
260
261 private getModeOk() {
262         def result = !modes || modes.contains(location.mode)
263         log.trace "<beacon-control> modeOk = $result"
264         result
265 }
266
267 private getDaysOk() {
268         def result = true
269         if (days) {
270                 def df = new java.text.SimpleDateFormat("EEEE")
271                 if (location.timeZone) {
272                         df.setTimeZone(location.timeZone)
273                 }
274                 else {
275                         df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
276                 }
277                 def day = df.format(new Date())
278                 result = days.contains(day)
279         }
280         log.trace "<beacon-control> daysOk = $result"
281         result
282 }
283
284 private getTimeOk() {
285         def result = true
286         if (starting && ending) {
287                 def currTime = now()
288                 def start = timeToday(starting, location?.timeZone).time
289                 def stop = timeToday(ending, location?.timeZone).time
290                 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
291         }
292         log.trace "<beacon-control> timeOk = $result"
293         result
294 }
295
296 private hhmm(time, fmt = "h:mm a") {
297         def t = timeToday(time, location.timeZone)
298         def f = new java.text.SimpleDateFormat(fmt)
299         f.setTimeZone(location.timeZone ?: timeZone(time))
300         f.format(t)
301 }
302
303 private timeIntervalLabel() {
304         (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
305 }
306
307 private list(Object names) {
308         return names[0]
309 }