Schlage Lock - Notification when unlocked for a period of time

Looking to see if there is a way to be notified if my Schlage lock has been left unlocked for a period of time. Any help would be appreciated!!

 trigger:
   platform: state
   entity_id: lock.your_lock_name
   state: 'unlocked'
   for:
     hours: 1
     minutes: 10
     seconds: 5
 action:
   service: notify.notify
   data:
     message: "Lock has been unlocked for 1 hour, 10 minutes and 5 seconds"

You don’t have to use all three (hour, minutes, seconds), only the ones you need

2 Likes

@jbardi has a cleaner solution here, but since I typed it out I am going to post.
I am pretty new…

Automation

- alias: door unlocked script call
  trigger:
    platform: state
    to: 'unlocked'
  action:
    service: script.turn_on
    entity_id: script.unlocked

- alias: door locked kill script/notification
  trigger:
    platform: state
    to: 'locked'
  action:
    service: script.turn_off
    entity_id: script.unlocked

Script

   unlocked:
     sequence:
     - delay:
         seconds: 600 
     - service: notify.notify
       data:
         message: 'Door is Unlocked!!'
1 Like

Worked flawlessly!!! You guys rock!

Ok, now to take it a step further…

How would I make it so that it continues to notify every x number of seconds, minutes or hours etc. instead of doing only once.

Not sure if that can be done or not.

Again, thanks for all of the help!!

If you wish to repeat the process every 10 minutes until the door is closed, I would use @PtP method, only create 2 door opened scripts, the equivalent of his “unlocked” script, called script.door_opened1 and script.door_opened2, and make the first one call the second one at the end after the notify call, and the second one call the first one at its end just the same. The reason for this is that you can’t have a script call itself to run again because only one copy of a script can be running at any given time, so you have to call a duplicate script, and vice versa, so they take turns notifying.

Then call script.turn_on for script.door_opened1 from the door opened automation and finally, from the door closed automation, have it run script.turn_off for both the script.door_opened1 and script.door_opened2 since you won’t know which one was running.

The only problem with this is since only one script can run at a time, this will get all messed up if you use this for more than one door and a second door opens while this is going on. You see what I mean?

Personally, I don’t use any Automations inside home assistant. Instead I use AppDaemon to create my automations on the fly. It allows me to do things like this in only a few lines of code, in fact, I am doing this exact thing for my door locks as well as notifying myself every 15 minutes after sunset if I didn’t turn my alarm back on.

AppDaemon lets me accomplish things that are otherwise impossible or nearly impossible with Home Assistant auotomations, but still having full access to all of Home Assistant.

What would take 2 automations and 2 scripts in Home Assistant in order to monitor your doors, and still not work right for multiple doors, could be accomplished in AppDaemon in a couple lines with one AppDaemon automation that works for ANY number of doors, flawlessly each time, even when doors are open simultaneously.

All of this without having to restart Home Assistant, since I can change and add automations in AppDaemon and they work immediately, on-the-fly, unlike Home Assistant’s built-in automation system that requires a restart on every little change.

@jbardi is correct about everything. I am working with Appdaemon now to get better at it.

I think the example below would work.

#Automation
trigger:
platform: state
entity_id: lock.your_lock_name
state: ‘unlocked’
for:
hours: 1
minutes: 10
seconds: 5
action:
- service: notify.notify
data:
message: “Lock has been unlocked for 1 hour, 10 minutes and 5 seconds”

   - service: script.turn_on
     entity_id: script.unlock

#Script
unlocked:
sequence:
- delay:
seconds: 600
- service: notify.notify
data:
message: ‘Door is Unlocked!!’
- service: script.turn_on
data:
entity_id: script.unlock

Then you will have to create another script to manually turn the script off and add it to you lock group.

   kill_unlock_loop:
     sequence:
     - service: script.turn_off
       data:
         entity_id: script.unlock

Nice @PtP, if he uses Home Assistant automations, that is a nicer method than the 2 automation 2 script option :smiley: and as for AppDaemon, I don’t know how I lived without it!!!

Very cool!! I will give Appdaemon a look and see what kind of trouble I can get myself into :wink:

Appreciate all of the help and knowledge that you have provided me and others!!!

Here is a possible example for your use case using AppDaemon. This may seem daunting if you are not familiar with Python, and it may even seem complicated for the end result, but if you read the code and follow it like a sentence, it makes perfect sense. All of this is in one AppDaemon App (equivalent of an automation) and can be changed on the fly without a restart of Home Assitant:

import appdaemon.appapi as appapi

#
# DoorLeftUnlocked
#

class DoorLeftUnlocked(appapi.AppDaemon):

    def initialize(self):
        # This will monitory ALL of the locks in your house
        self.listen_state(self.start_timer, 'lock', new='unlocked')
        self.listen_state(self.stop_timer, 'lock', new='locked')

    def start_timer(self, entity, attribute, old, new, kwargs):
        # This starts a timer for 10 minutes (600 seconds)
        self.lock_timer = self.run_in(self.send_notification, 600)

    def send_notification(self, entity, attribute, old, new, kwargs):
        # This sends a notifcation and then starts the timer for another 10 minutes
        self.call_service("notify/notify", message="{} is currently unlocked".format(friendly_name(entity)))
        self.run_in(self.start_timer, 0)

    def stop_timer(self, *args, **kwargs):
        # This cancels the timer once the door is locked
        self.cancel_timer(self.lock_timer)

I haven’t tested this one, so hopefully I didn’t make any mistakes.

1 Like

Here is the error I got when I tried it.

‘’’
2016-09-09 16:03:13.655361 INFO AppDaemon Version 1.3.1 starting
2016-09-09 16:03:13.891068 INFO Got initial state
2016-09-09 16:03:13.902394 INFO Loading Module: /home/ha/appdaemon/conf/apps/doorleftunlocked.py
2016-09-09 16:03:13.937564 INFO Loading Object door_left_unlocked using class DoorLeftUnlocked from module doorleftunlocked
2016-09-09 16:06:25.248735 WARNING ------------------------------------------------------------
2016-09-09 16:06:25.257175 WARNING Unexpected error:
2016-09-09 16:06:25.257398 WARNING ------------------------------------------------------------
2016-09-09 16:06:25.321463 WARNING Traceback (most recent call last):
File “/usr/local/lib/python3.5/dist-packages/appdaemon/appdaemon.py”, line 414, in worker
function(entity, attr, old_state, new_state, ha.sanitize_state_kwargs(args[“kwargs”]))
File “/home/ha/appdaemon/conf/apps/doorleftunlocked.py”, line 25, in stop_timer
self.cancel_timer(self.lock_timer)
AttributeError: ‘DoorLeftUnlocked’ object has no attribute ‘lock_timer’

2016-09-09 16:06:25.321671 WARNING ------------------------------------------------------------

‘’’

Well, technically there is no lock_timer yet since it has not been scheduled. I forgot about that error. I usually do the following in order to create the lock_timer variable before it is used. Should take care of the error.

   def initialize(self):
       self.lock_timer = None

       # This will monitory ALL of the locks in your house
       self.listen_state(self.start_timer, 'lock', new='unlocked')
       self.listen_state(self.stop_timer, 'lock', new='locked')

Let me know if you get any more errors with that code. I didn’t test it so you may or may not get an error about an incorrect number of arguments for a function call. Let me know.

Ok, I am back on this app, and I am having some issues. I implemented the changes above, but when the timer gets called, it throws an error. Here’s the code I am using:

import appdaemon.appapi as appapi

#
# DoorLeftUnlocked
#

class DoorLeftUnlocked(appapi.AppDaemon):

  def initialize(self):
    self.log("Loading Door Left Unlocked App")
    self.lock_timer = None
    # This will monitory ALL of the locks in your house
    self.listen_state(self.start_timer, "lock.schlage_deadbolt", new="unlocked")
    self.listen_state(self.stop_timer, "lock.schlage_deadbolt", new="locked")

  def start_timer(self, entity, attribute, old, new, kwargs):
    self.log("Starting timer")
    self.lock_timer = self.run_in(self.send_notification, 10)

  def send_notification(self, entity, attribute, old, new, kwargs):
   # This sends a notifcation and then starts the timer for another 10 minutes
    self.call_service("notify/notify", message="{} is currently unlocked")
    self.log("Sending notifications")
    self.run_in(self.start_timer, 0)

  def stop_timer(self, entity, attribute, old, new, kwargs):
    # This cancels the timer once the door is locked
    self.cancel_timer(self.lock_timer)

This is the error thrown:

2016-10-03 22:13:47.008124 WARNING ------------------------------------------------------------
2016-10-03 22:13:47.009092 WARNING Unexpected error:
2016-10-03 22:13:47.009679 WARNING ------------------------------------------------------------
2016-10-03 22:13:47.013183 WARNING Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/appdaemon/appdaemon.py", line 418, in worker
    function(ha.sanitize_timer_kwargs(args["kwargs"]))
TypeError: send_notification() missing 4 required positional arguments: 'attribute', 'old', 'new', and 'kwargs'

2016-10-03 22:13:47.013998 WARNING ------------------------------------------------------------

I wanted something similar and was seeing the same error you have. As a result, I made some tweaks and improvements to the script - which is here:

import appdaemon.appapi as appapi

#
# DoorLeftUnlocked
#

class DoorLeftUnlocked(appapi.AppDaemon):

  def initialize(self):
    self.log("Loading Door Left Unlocked App")
    self.lock_timer = None
    
    if "locks" in self.args:
      for lock in self.split_device_list(self.args["lock"]):
        ## If Group - initialize all entities from the group
        if "group" in lock:
          self.log("Group provided.")
          groupitem = self.get_state(lock,"all")
          entity_list = groupitem['attributes']['entity_id']

          for member in entity_list:
            if "lock" in member:
              self.log("Initializing lock: " + self.friendly_name(member))
              self.listen_state(self.state_changed, member, new='unlocked')
              self.listen_state(self.state_changed, member, new='locked')  
        else:
          self.log("Single lock provided.")
          self.log("Initializing lock: " + self.friendly_name(lock))
          self.listen_state(self.state_changed, member, lock=lock, new='unlocked')
          self.listen_state(self.state_changed, member, lock=lock, new='locked')               
    else:
      # This will monitory ALL of the locks in your house
      self.log("No lock provided in cfg, configure for all.")
      self.listen_state(self.state_changed, 'lock', lock='lock', new='unlocked')
      self.listen_state(self.state_changed, 'lock', lock='lock', new='locked')
   
  def state_changed(self, entity, attribute, old, new, kwargs):
    lockstate = new 
      
    if "unlocked" in lockstate:
      self.run_in(self.start_timer, 0, lock=entity, new=lockstate)
    else:
      self.run_in(self.stop_timer, 0, lock=entity, new=lockstate)     
      
  def send_notification(self, kwargs):
   # This sends a notifcation and then starts the timer for another XX minutes
    msg = kwargs["msg"]
    self.call_service("notify/notifyall", message=msg)
    self.log(msg)
      
  def start_timer(self, kwargs):
    lock = kwargs["lock"]
    lockstate = kwargs["new"]
    
    if "notifydelaymin" in self.args:
      cfgtimer = self.args["notifydelaymin"]
      notifydelay = int(cfgtimer) * 60
    else:
      notifydelay = 600
      
    msg = "{} is currently {}. Will alert again in {} seconds.".format(self.friendly_name(lock), lockstate, notifydelay)    
    ## Send notification immediately    
    self.run_in(self.send_notification, 0, new=lockstate, msg=msg)     
    ### Reset timer after XXX seconds
    self.lock_timer = self.run_in(self.start_timer, notifydelay, lock=lock, new=lockstate)
      
  def stop_timer(self, kwargs):
    lock = kwargs["lock"]
    lockstate = kwargs["new"]
    
    msg = "{} is now {}. Cancelling timer.".format(self.friendly_name(lock), lockstate)
    self.run_in(self.send_notification, 0, new=lockstate, msg=msg)
    # This cancels the timer once the door is locked
    self.cancel_timer(self.lock_timer)
    
    self.run_in(self.all_locks_check, 0)
    
  def all_locks_check(self, kwargs):
    ## Will send alert if all locks are locked
    if "all_lock_alert" in self.args & self.args["all_lock_alert"] == "true":
      
      if "locks" in self.args:
        for lock in self.split_device_list(self.args["lock"]):
          allState = self.get_state(lock)
          if allState = "unlocked":
            break
      else:
        allState = get_state("lock")
      
      if "unlocked" not in allState:
        msg = "All doors are now locked."
        self.run_in(self.send_notification, 0, msg=msg) 

config:

[Lock Notifications]
module = door_locks
class = DoorLeftUnlocked
locks = group.all_locks
notifydelaymin = 10
all_lock_alert = true

I added the ability to pass in specific lock(s) or a group. Notification delay defaults to 10 minutes or you can provide it in the config. The all_lock_alert simply sends an additional alert once all are locked - this is currently limited to the locks provided in the cfg.

I’ve tested most of it… but mileage may vary. Let me know if you find any mistakes.

Ok - found some issues in my previous code and realized that I wanted this for locks, and garage doors and possibly others so I made it more generic.

import appdaemon.appapi as appapi

#
# DoorLeftUnentityed
#

class Notifications(appapi.AppDaemon):


  def initialize(self):
    self.log("Generic Notification App")
    notifyState = self.args["notifyState"]
    stopState = self.args["stopState"]
    entityType = self.args["entityType"]
    
    self.entity_timer = None
   
    if "entities" in self.args:
      for entity in self.split_device_list(self.args["entities"]):
        ## If Group - initialize all entities from the group
        if "group" in entity:
          self.log("Group provided.")
          groupitem = self.get_state(entity,"all")
          entity_list = groupitem['attributes']['entity_id']

          for member in entity_list:
            if entityType in member:
              self.log("Initializing entity: " + self.friendly_name(member))
              self.listen_state(self.state_changed, member, new=notifyState)
              self.listen_state(self.state_changed, member, new=stopState)  
        else:
          self.log("Single entity provided.")
          self.log("Initializing entity: " + self.friendly_name(entity))
          self.listen_state(self.state_changed, member, new=notifyState)
          self.listen_state(self.state_changed, member, new=stopState)               
    else:
      # This will monitory ALL of the entities in your house
      self.log("No entity provided in cfg, configure for all.")
      self.listen_state(self.state_changed, entityType, new=notifyState)
      self.listen_state(self.state_changed, entityType, new=stopState)
   
  def state_changed(self, entity, attribute, old, new, kwargs):
    notifyState = self.args["notifyState"]
 
    entitystate = new 
      
    if notifyState in entitystate:
      self.run_in(self.start_timer, 0, item=entity, new=entitystate)
    else:
      self.run_in(self.stop_timer, 0, item=entity, new=entitystate)     
      
  def send_notification(self, kwargs):
   # This sends a notifcation and then starts the timer for another XX minutes
    msg = kwargs["msg"]
    self.call_service("notify/notifyall", message="Generic: {}".format(msg))
    self.log(msg)
     
  def start_timer(self, kwargs):
    entity = kwargs["item"]
    entitystate = kwargs["new"]
    
    if "notifydelaymin" in self.args:
      cfgtimer = self.args["notifydelaymin"]
      notifydelay = int(cfgtimer) * 60
    else:
      notifydelay = 600
      
    msg = "{} is currently {}. Will alert again in {} seconds.".format(self.friendly_name(entity), entitystate, notifydelay)    
    ## Send notification immediately    
    self.run_in(self.send_notification, 0, new=entitystate, msg=msg)     
    ### Reset timer after XXX seconds
    self.entity_timer = self.run_in(self.start_timer, notifydelay, entity=entity, new=entitystate)
      
  def stop_timer(self, kwargs):
    entity = kwargs["item"]
    entitystate = kwargs["new"]
    
    msg = "{} is now {}. Cancelling timer.".format(self.friendly_name(entity), entitystate)
    self.run_in(self.send_notification, 0, new=entitystate, msg=msg)
    # This cancels the timer once the door is entityed
    self.cancel_timer(self.entity_timer)
    
    self.run_in(self.all_entities_check, 0)
    
  def all_entities_check(self, kwargs):
    notifyState = self.args["notifyState"]
    stopState = self.args["stopState"]
    entityType = self.args["entityType"]
    ## Will send alert if all entitys are entityed
    if "all_entity_alert" in self.args and self.args["all_entity_alert"] == "true":
      
      if "entities" in self.args:
        for entity in self.split_device_list(self.args["entities"]):
          allState = self.get_state(entity)
          if allState == stopState:
            break
      else:
        allState = get_state(entityType)
      
      if notifyState not in allState:
        msg = "All {}s are now {}.".format(entityType, stopState)
        self.run_in(self.send_notification, 0, msg=msg)

config

[Lock Notifications]
module = notifications_state-timer
class = Notifications
entityType = lock
entities = group.all_locks
notifyState = unlocked
stopState = locked
notifydelaymin = 10
all_entity_alert = true

[Cover Notifications]
module = notifications_state-timer
class = Notifications
entityType = cover
entities = group.all_covers
notifyState = open
stopState = closed
notifydelaymin = 10
all_entity_alert = true

I think I have all the kinks worked out, now.

Does this work with the new AppDaemon? I modified the config to reflect the new yaml format, but i get an error in the logs with the following:

2017-07-22 01:51:37.498666 INFO Loading Module: /home/pi/appdaemon/conf/apps/notifications_state-timer.py
2017-07-22 01:51:37.500879 WARNING ------------------------------------------------------------
2017-07-22 01:51:37.501339 WARNING Unexpected error during loading of notifications_state-timer.py:
2017-07-22 01:51:37.501641 WARNING ------------------------------------------------------------
2017-07-22 01:51:37.505036 WARNING Traceback (most recent call last):
File “/usr/local/lib/python3.4/dist-packages/appdaemon/appdaemon.py”, line 911, in read_app
conf.modules[module_name] = importlib.import_module(module_name)
File “/usr/lib/python3.4/importlib/init.py”, line 109, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File “”, line 2254, in _gcd_import
File “”, line 2237, in _find_and_load
File “”, line 2226, in _find_and_load_unlocked
File “”, line 1200, in _load_unlocked
File “”, line 1129, in _exec
File “”, line 1467, in exec_module
File “”, line 1572, in get_code
File “”, line 1532, in source_to_code
File “”, line 321, in _call_with_frames_removed
File “/home/pi/appdaemon/conf/apps/notifications_state-timer.py”, line 20
if entityType in member:
^
IndentationError: expected an indented block

From what I can tell, you have to change the import and class lines as well. I’m working on this but not sure it’s working yet. :slight_smile:

Have you tried just setting an alert?
https://www.home-assistant.io/components/alert/