4 * Copyright 2014 Physical Graph Corporation
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:
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
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"
27 page(name: "timeIntervalInput", title: "Only during a certain time") {
29 input "starting", "time", title: "Starting", required: false
30 input "ending", "time", title: "Ending", required: false
34 page(name: "mainPage")
38 dynamicPage(name: "mainPage", install: true, uninstall: true) {
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
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
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
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
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",
79 label title: "Give your automation a name", description: "e.g. Goodnight Home, Wake Up"
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"
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"]
90 input "modes", "mode", title: "Only when mode is", multiple: true, required: false
95 // Lifecycle management
97 log.debug "<beacon-control> Installed with settings: ${settings}"
102 log.debug "<beacon-control> Updated with settings: ${settings}"
108 subscribe(beacons, "presence", beaconHandler)
112 def beaconHandler(evt) {
113 log.debug "<beacon-control> beaconHandler: $evt"
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
120 def beaconName = getBeaconName(evt)
121 // removed logging of device names. can be added back for debugging
122 //log.debug "<beacon-control> beaconName: $beaconName"
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"
131 if (action == "arrived") {
132 msg = arriveActions(msg)
134 else if (action == "left") {
135 msg = departActions(msg)
137 log.debug "<beacon-control> msg: $msg"
139 if (pushNotification || phone) {
141 method: (pushNotification && phone) ? "both" : (pushNotification ? "push" : "sms"),
144 sendNotification(msg, options)
151 private arriveActions(msg) {
152 if (arrivalPhrase || arrivalOnSwitches || arrivalOffSwitches || arrivalLocks) msg += ", so"
155 log.debug "<beacon-control> executing: $arrivalPhrase"
156 executePhrase(arrivalPhrase)
157 msg += " ${prefix('executed')} $arrivalPhrase."
159 if (arrivalOnSwitches) {
160 log.debug "<beacon-control> turning on: $arrivalOnSwitches"
161 arrivalOnSwitches.on()
162 msg += " ${prefix('turned')} ${list(arrivalOnSwitches)} on."
164 if (arrivalOffSwitches) {
165 log.debug "<beacon-control> turning off: $arrivalOffSwitches"
166 arrivalOffSwitches.off()
167 msg += " ${prefix('turned')} ${list(arrivalOffSwitches)} off."
170 log.debug "<beacon-control> unlocking: $arrivalLocks"
171 arrivalLocks.unlock()
172 msg += " ${prefix('unlocked')} ${list(arrivalLocks)}."
177 private departActions(msg) {
178 if (departPhrase || departOnSwitches || departOffSwitches || departLocks) msg += ", so"
181 log.debug "<beacon-control> executing: $departPhrase"
182 executePhrase(departPhrase)
183 msg += " ${prefix('executed')} $departPhrase."
185 if (departOnSwitches) {
186 log.debug "<beacon-control> turning on: $departOnSwitches"
187 departOnSwitches.on()
188 msg += " ${prefix('turned')} ${list(departOnSwitches)} on."
190 if (departOffSwitches) {
191 log.debug "<beacon-control> turning off: $departOffSwitches"
192 departOffSwitches.off()
193 msg += " ${prefix('turned')} ${list(departOffSwitches)} off."
196 log.debug "<beacon-control> unlocking: $departLocks"
198 msg += " ${prefix('locked')} ${list(departLocks)}."
203 private prefix(word) {
205 def index = settings.prefixIndex == null ? 0 : settings.prefixIndex + 1
211 result = "I also $word"
214 result = "And I $word"
221 settings.prefixIndex = index
222 log.trace "prefix($word'): $result"
226 private listPhrases() {
227 location.helloHome.getPhrases().label
230 private executePhrase(phraseName) {
232 location.helloHome.execute(phraseName)
233 log.debug "<beacon-control> executed phrase: $phraseName"
237 private getBeaconName(evt) {
238 def beaconName = beacons.find { b -> b.id == evt.deviceId }
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]
252 private hideOptionsSection() {
253 (starting || ending || days || modes) ? false : true
257 //modeOk && daysOk && timeOk
261 private getModeOk() {
262 def result = !modes || modes.contains(location.mode)
263 log.trace "<beacon-control> modeOk = $result"
267 private getDaysOk() {
270 def df = new java.text.SimpleDateFormat("EEEE")
271 if (location.timeZone) {
272 df.setTimeZone(location.timeZone)
275 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
277 def day = df.format(new Date())
278 result = days.contains(day)
280 log.trace "<beacon-control> daysOk = $result"
284 private getTimeOk() {
286 if (starting && ending) {
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
292 log.trace "<beacon-control> timeOk = $result"
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))
303 private timeIntervalLabel() {
304 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
307 private list(Object names) {