/** * Beacon Control * * Copyright 2014 Physical Graph Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * for the specific language governing permissions and limitations under the License. * */ definition( name: "Beacon Control", category: "SmartThings Internal", namespace: "smartthings", author: "SmartThings", 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", iconUrl: "https://s3.amazonaws.com/smartapp-icons/MiscHacking/mindcontrol.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MiscHacking/mindcontrol@2x.png" ) preferences { page(name: "timeIntervalInput", title: "Only during a certain time") { section { input "starting", "time", title: "Starting", required: false input "ending", "time", title: "Ending", required: false } } page(name: "mainPage") } def mainPage() { dynamicPage(name: "mainPage", install: true, uninstall: true) { section("Where do you want to watch?") { input name: "beacons", type: "capability.beacon", title: "Select your beacon(s)", multiple: true, required: true } section("Who do you want to watch for?") { input name: "phones", type: "device.mobilePresence", title: "Select your phone(s)", multiple: true, required: true } section("What do you want to do on arrival?") { input name: "arrivalPhrase", type: "enum", title: "Execute a phrase", options: listPhrases(), required: false input "arrivalOnSwitches", "capability.switch", title: "Turn on some switches", multiple: true, required: false input "arrivalOffSwitches", "capability.switch", title: "Turn off some switches", multiple: true, required: false input "arrivalLocks", "capability.lock", title: "Unlock the door", multiple: true, required: false } section("What do you want to do on departure?") { input name: "departPhrase", type: "enum", title: "Execute a phrase", options: listPhrases(), required: false input "departOnSwitches", "capability.switch", title: "Turn on some switches", multiple: true, required: false input "departOffSwitches", "capability.switch", title: "Turn off some switches", multiple: true, required: false input "departLocks", "capability.lock", title: "Lock the door", multiple: true, required: false } section("Do you want to be notified?") { input "pushNotification", "bool", title: "Send a push notification" input "phone", "phone", title: "Send a text message", description: "Tap to enter phone number", required: false } section { label title: "Give your automation a name", description: "e.g. Goodnight Home, Wake Up" } def timeLabel = timeIntervalLabel() section(title: "More options", hidden: hideOptionsSection(), hideable: true) { href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete" input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] input "modes", "mode", title: "Only when mode is", multiple: true, required: false } } } // Lifecycle management def installed() { log.debug " Installed with settings: ${settings}" initialize() } def updated() { log.debug " Updated with settings: ${settings}" unsubscribe() initialize() } def initialize() { subscribe(beacons, "presence", beaconHandler) } // Event handlers def beaconHandler(evt) { log.debug " beaconHandler: $evt" if (allOk) { def data = new groovy.json.JsonSlurper().parseText(evt.data) // removed logging of device names. can be added back for debugging //log.debug " data: $data - phones: " + phones*.deviceNetworkId def beaconName = getBeaconName(evt) // removed logging of device names. can be added back for debugging //log.debug " beaconName: $beaconName" def phoneName = getPhoneName(data) // removed logging of device names. can be added back for debugging //log.debug " phoneName: $phoneName" if (phoneName != null) { def action = data.presence == "1" ? "arrived" : "left" def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName" if (action == "arrived") { msg = arriveActions(msg) } else if (action == "left") { msg = departActions(msg) } log.debug " msg: $msg" if (pushNotification || phone) { def options = [ method: (pushNotification && phone) ? "both" : (pushNotification ? "push" : "sms"), phone: phone ] sendNotification(msg, options) } } } } // Helpers private arriveActions(msg) { if (arrivalPhrase || arrivalOnSwitches || arrivalOffSwitches || arrivalLocks) msg += ", so" if (arrivalPhrase) { log.debug " executing: $arrivalPhrase" executePhrase(arrivalPhrase) msg += " ${prefix('executed')} $arrivalPhrase." } if (arrivalOnSwitches) { log.debug " turning on: $arrivalOnSwitches" arrivalOnSwitches.on() msg += " ${prefix('turned')} ${list(arrivalOnSwitches)} on." } if (arrivalOffSwitches) { log.debug " turning off: $arrivalOffSwitches" arrivalOffSwitches.off() msg += " ${prefix('turned')} ${list(arrivalOffSwitches)} off." } if (arrivalLocks) { log.debug " unlocking: $arrivalLocks" arrivalLocks.unlock() msg += " ${prefix('unlocked')} ${list(arrivalLocks)}." } msg } private departActions(msg) { if (departPhrase || departOnSwitches || departOffSwitches || departLocks) msg += ", so" if (departPhrase) { log.debug " executing: $departPhrase" executePhrase(departPhrase) msg += " ${prefix('executed')} $departPhrase." } if (departOnSwitches) { log.debug " turning on: $departOnSwitches" departOnSwitches.on() msg += " ${prefix('turned')} ${list(departOnSwitches)} on." } if (departOffSwitches) { log.debug " turning off: $departOffSwitches" departOffSwitches.off() msg += " ${prefix('turned')} ${list(departOffSwitches)} off." } if (departLocks) { log.debug " unlocking: $departLocks" departLocks.lock() msg += " ${prefix('locked')} ${list(departLocks)}." } msg } private prefix(word) { def result def index = settings.prefixIndex == null ? 0 : settings.prefixIndex + 1 switch (index) { case 0: result = "I $word" break case 1: result = "I also $word" break case 2: result = "And I $word" break default: result = "And $word" break } settings.prefixIndex = index log.trace "prefix($word'): $result" result } private listPhrases() { location.helloHome.getPhrases().label } private executePhrase(phraseName) { if (phraseName) { location.helloHome.execute(phraseName) log.debug " executed phrase: $phraseName" } } private getBeaconName(evt) { def beaconName = beacons.find { b -> b.id == evt.deviceId } return beaconName } private getPhoneName(data) { def phoneName = phones.find { phone -> // Work around DNI bug in data def pParts = phone.deviceNetworkId.split('\\|') def dParts = data.dni.split('\\|') pParts[0] == dParts[0] } return phoneName } private hideOptionsSection() { (starting || ending || days || modes) ? false : true } private getAllOk() { //modeOk && daysOk && timeOk return true } private getModeOk() { def result = !modes || modes.contains(location.mode) log.trace " modeOk = $result" result } private getDaysOk() { def result = true if (days) { def df = new java.text.SimpleDateFormat("EEEE") if (location.timeZone) { df.setTimeZone(location.timeZone) } else { df.setTimeZone(TimeZone.getTimeZone("America/New_York")) } def day = df.format(new Date()) result = days.contains(day) } log.trace " daysOk = $result" result } private getTimeOk() { def result = true if (starting && ending) { def currTime = now() def start = timeToday(starting, location?.timeZone).time def stop = timeToday(ending, location?.timeZone).time result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start } log.trace " timeOk = $result" result } private hhmm(time, fmt = "h:mm a") { def t = timeToday(time, location.timeZone) def f = new java.text.SimpleDateFormat(fmt) f.setTimeZone(location.timeZone ?: timeZone(time)) f.format(t) } private timeIntervalLabel() { (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : "" } private list(Object names) { return names[0] }