Adding new events in the script.
[smartthings-infrastructure.git] / Extractor / App2 / App2.groovy
index 497be4134d1d9d6286a836019e8b0e6c897c801f..61b6434be9f296ed50f595d13aaae68ce8dde5cf 100644 (file)
@@ -1,7 +1,7 @@
 /**
- *  NFC Tag Toggle
+ *  Beacon Control
  *
- *  Copyright 2014 SmartThings
+ *  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:
  *  for the specific language governing permissions and limitations under the License.
  *
  */
 definition(
-    name: "NFC Tag Toggle",
-    namespace: "smartthings",
-    author: "SmartThings",
-    description: "Allows toggling of a switch, lock, or garage door based on an NFC Tag touch event",
-    category: "SmartThings Internal",
-    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Developers/nfc-tag-executor.png",
-    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Developers/nfc-tag-executor@2x.png",
-    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Developers/nfc-tag-executor@2x.png")
-
+       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: "pageOne", title: "Device selection", uninstall: true, nextPage: "pageTwo") {
-        section("Select an NFC tag") {
-            input "tag", "capability.touchSensor", title: "NFC Tag"
-        }
-        section("Select devices to control") {
-            input "switch1", "capability.switch", title: "Light or switch", required: false, multiple: true
-            input "lock", "capability.lock", title: "Lock", required: false, multiple: true
-            input "garageDoor", "capability.doorControl", title: "Garage door controller", required: false, multiple: true
-        }
-    }
-    
-    page(name: "pageTwo", title: "Master devices", install: true, uninstall: true)
-}
-
-def pageTwo() {
-       dynamicPage(name: "pageTwo") {
-       section("If set, the state of these devices will be toggled each time the tag is touched, " + 
-                "e.g. a light that's on will be turned off and one that's off will be turned on, " +
-                "other devices of the same type will be set to the same state as their master device. " +
-                "If no master is designated then the majority of devices of the same type will be used " +
-                "to determine whether to turn on or off the devices.") {
-            
-            if (switch1 || masterSwitch) {
-                input "masterSwitch", "enum", title: "Master switch", options: switch1.collect{[(it.id): it.displayName]}, required: false
-            }
-            if (lock || masterLock) {
-                input "masterLock", "enum", title: "Master lock", options: lock.collect{[(it.id): it.displayName]}, required: false
-            }
-            if (garageDoor || masterDoor) {
-                input "masterDoor", "enum", title: "Master door", options: garageDoor.collect{[(it.id): it.displayName]}, required: false
-            }            
+       page(name: "timeIntervalInput", title: "Only during a certain time") {
+               section {
+                       input "starting", "time", title: "Starting", required: false
+                       input "ending", "time", title: "Ending", required: false
                }
-               section([mobileOnly:true]) {
-                       label title: "Assign a name", required: false
-                       mode title: "Set for specific mode(s)", required: false
-               }        
-    }
+       }
+       
+       page(name: "mainPage")
 }
 
-def installed() {
-       log.debug "Installed with settings: ${settings}"
+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 "<beacon-control> Installed with settings: ${settings}"
        initialize()
 }
 
 def updated() {
-       log.debug "Updated with settings: ${settings}"
-
+       log.debug "<beacon-control> Updated with settings: ${settings}"
        unsubscribe()
        initialize()
 }
 
 def initialize() {
-       subscribe tag, "nfcTouch", touchHandler
-    subscribe app, touchHandler
-}
-
-private currentStatus(devices, master, attribute) {
-       log.trace "currentStatus($devices, $master, $attribute)"
-       def result = null
-       if (master) {
-       result = devices.find{it.id == master}?.currentValue(attribute)
-    }
-    else {
-       def map = [:]
-        devices.each {
-               def value = it.currentValue(attribute)
-            map[value] = (map[value] ?: 0) + 1
-            log.trace "$it.displayName: $value"
-        }
-        log.trace map
-        result = map.collect{it}.sort{it.value}[-1].key
-    }
-    log.debug "$attribute = $result"
-    result
-}
-
-def touchHandler(evt) {
-       log.trace "touchHandler($evt.descriptionText)"
-    if (switch1) {
-       def status = currentStatus(switch1, masterSwitch, "switch")
-        switch1.each {
-            if (status == "on") {
-                it.off()
-            }
-            else {
-                it.on()
-            }
-        }
-    }
-    
-    if (lock) {
-       def status = currentStatus(lock, masterLock, "lock")
-        lock.each {
-            if (status == "locked") {
-                lock.unlock()
+       subscribe(beacons, "presence", beaconHandler)
+}
+
+// Event handlers
+def beaconHandler(evt) {
+       log.debug "<beacon-control> 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 "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
+
+               def beaconName = getBeaconName(evt)
+                // removed logging of device names. can be added back for debugging
+               //log.debug "<beacon-control> beaconName: $beaconName"
+
+               def phoneName = getPhoneName(data)
+                // removed logging of device names. can be added back for debugging
+               //log.debug "<beacon-control> 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 {
-                lock.lock()
+            else if (action == "left") {
+                msg = departActions(msg)
             }
-        }
-    }
-    
-    if (garageDoor) {
-        def status = currentStatus(garageDoor, masterDoor, "status")
-       garageDoor.each {
-               if (status == "open") {
-               it.close()
-            }
-            else {
-               it.open()
+            log.debug "<beacon-control> 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 "<beacon-control> executing: $arrivalPhrase"
+               executePhrase(arrivalPhrase)
+               msg += " ${prefix('executed')} $arrivalPhrase."
+       }
+       if (arrivalOnSwitches) {
+               log.debug "<beacon-control> turning on: $arrivalOnSwitches"
+               arrivalOnSwitches.on()
+               msg += " ${prefix('turned')} ${list(arrivalOnSwitches)} on."
+       }
+       if (arrivalOffSwitches) {
+               log.debug "<beacon-control> turning off: $arrivalOffSwitches"
+               arrivalOffSwitches.off()
+               msg += " ${prefix('turned')} ${list(arrivalOffSwitches)} off."
+       }
+       if (arrivalLocks) {
+               log.debug "<beacon-control> unlocking: $arrivalLocks"
+               arrivalLocks.unlock()
+               msg += " ${prefix('unlocked')} ${list(arrivalLocks)}."
+       }
+       msg
+}
+
+private departActions(msg) {
+       if (departPhrase || departOnSwitches || departOffSwitches || departLocks) msg += ", so"
+       
+       if (departPhrase) {
+               log.debug "<beacon-control> executing: $departPhrase"
+               executePhrase(departPhrase)
+               msg += " ${prefix('executed')} $departPhrase."
+       }
+       if (departOnSwitches) {
+               log.debug "<beacon-control> turning on: $departOnSwitches"
+               departOnSwitches.on()
+               msg += " ${prefix('turned')} ${list(departOnSwitches)} on."
+       }
+       if (departOffSwitches) {
+               log.debug "<beacon-control> turning off: $departOffSwitches"
+               departOffSwitches.off()
+               msg += " ${prefix('turned')} ${list(departOffSwitches)} off."
+       }
+       if (departLocks) {
+               log.debug "<beacon-control> 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 "<beacon-control> 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
+}
+
+private getModeOk() {
+       def result = !modes || modes.contains(location.mode)
+       log.trace "<beacon-control> 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 "<beacon-control> 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 "<beacon-control> 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]
 }