Infrastructure that works for all the locks' group!
[smartthings-infrastructure.git] / Extractor / App2 / App2.groovy
index 497be4134d1d9d6286a836019e8b0e6c897c801f..86096f4d96e07b491e68e5d660c2505e2a699116 100644 (file)
@@ -1,7 +1,7 @@
 /**
- *  NFC Tag Toggle
+ *  Notify If Left Unlocked
  *
- *  Copyright 2014 SmartThings
+ *  Copyright 2014 George Sudarkoff
  *
  *  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: "Notify If Left Unlocked",
+    namespace: "com.sudarkoff",
+    author: "George Sudarkoff",
+    description: "Send a push or SMS notification (and lock, if it's closed) if a door is left unlocked for a period of time.",
+    category: "Safety & Security",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@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
-        }
+    section("If this lock...") {
+        input "aLock", "capability.lock", multiple: false, required: true
+        input "openSensor", "capability.contactSensor", title: "Open/close sensor (optional)", multiple: false, required: false
     }
-    
-    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
-            }            
-               }
-               section([mobileOnly:true]) {
-                       label title: "Assign a name", required: false
-                       mode title: "Set for specific mode(s)", required: false
-               }        
+    section("Left unlocked for...") {
+        input "duration", "number", title: "How many minutes?", required: true
+    }
+    section("Notify me...") {
+        input "pushNotification", "bool", title: "Push notification"
+        input "phoneNumber", "phone", title: "Phone number (optional)", required: false
+        input "lockIfClosed", "bool", title: "Lock the door if it's closed?"
     }
 }
 
-def installed() {
-       log.debug "Installed with settings: ${settings}"
-
-       initialize()
+def installed()
+{
+    initialize()
 }
 
-def updated() {
-       log.debug "Updated with settings: ${settings}"
-
-       unsubscribe()
-       initialize()
+def updated()
+{
+    unsubscribe()
+    initialize()
 }
 
-def initialize() {
-       subscribe tag, "nfcTouch", touchHandler
-    subscribe app, touchHandler
+def initialize()
+{
+    log.trace "Initializing with: ${settings}"
+    subscribe(aLock, "lock", lockHandler)
 }
 
-private currentStatus(devices, master, attribute) {
-       log.trace "currentStatus($devices, $master, $attribute)"
-       def result = null
-       if (master) {
-       result = devices.find{it.id == master}?.currentValue(attribute)
+def lockHandler(evt)
+{
+    log.trace "${evt.name} is ${evt.value}."
+    if (evt.value == "locked") {
+        log.debug "Canceling lock check because the door is locked..."
+        unschedule(notifyUnlocked)
     }
     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 "Starting the countdown for ${duration} minutes..."
+        state.retries = 0
+        runIn(duration * 60, notifyUnlocked)
     }
-    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()
-            }
+def notifyUnlocked()
+{
+    // if no open/close sensor specified, assume the door is closed
+    def open = openSensor?.latestValue("contact") ?: "closed"
+
+    def message = "${aLock.displayName} is left unlocked and ${open} for more than ${duration} minutes."
+    log.trace "Sending the notification: ${message}."
+    sendMessage(message)
+
+    if (lockIfClosed) {
+        if (open == "closed") {
+            log.trace "And locking the door."
+            sendMessage("Locking the ${aLock.displayName} as prescribed.")
+            aLock.lock()
         }
-    }
-    
-    if (lock) {
-       def status = currentStatus(lock, masterLock, "lock")
-        lock.each {
-            if (status == "locked") {
-                lock.unlock()
+        else {
+            if (state.retries++ < 3) {
+                log.trace "Door is open, can't lock. Rescheduling the check."
+                sendMessage("Can't lock the ${aLock.displayName} because the door is open. Will try again in ${duration} minutes.")
+                runIn(duration * 60, notifyUnlocked)
             }
             else {
-                lock.lock()
+                log.trace "The door is still open after ${state.retries} retries, giving up."
+                sendMessage("Unable to lock the ${aLock.displayName} after ${state.retries} retries, giving up.")
             }
         }
     }
-    
-    if (garageDoor) {
-        def status = currentStatus(garageDoor, masterDoor, "status")
-       garageDoor.each {
-               if (status == "open") {
-               it.close()
-            }
-            else {
-               it.open()
-            }
-        }
+}
+
+def sendMessage(msg) {
+    if (pushNotification) {
+        sendPush(msg)
+    }
+    if (phoneNumber) {
+        sendSMS(phoneNumber, msg)
     }
 }
+