For those that are interested. I started working on this a couple of years ago and then stopped because I got frustrated with it. Well tonight I finally went back to it with some help from GPT.
You have to have appdaemon for this to work. I’m sure it could be done in yaml but well yaml and I don’t get along so well.
Ever modify a code and it just gets stuck in the Adding/Deleting forever?
Well not anymore
import appdaemon.plugins.hass.hassapi as hass
class lock_code_status(hass.Hass):
def initialize(self):
# Get lock name and code range from configuration
self.lock_name = self.args["lock_name"]
self.lock_code_range = self._parse_lock_code_range(self.args.get("lock_code", ""))
# Initialize timers dictionary for both Adding and Deleting logic
self.adding_timers = {}
self.deleting_timers = {}
# Listen for state changes of dynamically constructed sensors
for code_slot in self.lock_code_range:
sensor_entity = f"sensor.connected_{self.lock_name}_{code_slot}"
self.listen_state(self.monitor_lock_code, sensor_entity, code_slot=code_slot)
self.log(f"Monitoring {sensor_entity} for Adding/Deleting states.")
def _parse_lock_code_range(self, lock_code_range):
"""Parses the lock code range string and returns a list of codes."""
lock_codes = []
if "-" in lock_code_range:
# Handle the case of a range, e.g., "1-20"
start, end = lock_code_range.split("-")
lock_codes = list(range(int(start), int(end) + 1))
else:
# Handle the case of a list of codes (e.g., [1,2,3,...])
lock_codes = [int(code) for code in lock_code_range.split(",")]
return lock_codes
def monitor_lock_code(self, entity, attribute, old, new, kwargs):
code_slot = kwargs["code_slot"]
input_boolean_slot = f"input_boolean.enabled_{self.lock_name}_{code_slot}"
self.log(f"State change for {entity}: {old} -> {new}")
if new == "Adding":
self.log(f"Lock code {code_slot} is in Adding state. Starting timer.")
self._start_timer(self.adding_timers, code_slot, 60, input_boolean_slot, self.handle_lock_code_timeout)
elif new == "Connected":
self.log(f"Lock code {code_slot} is Connected. Canceling Adding timer.")
self._cancel_timer(self.adding_timers, code_slot)
elif new == "Deleting":
self.log(f"Lock code {code_slot} is in Deleting state. Starting timer.")
# Start a timer with a 5-second delay for re-enabling and then disabling again.
self._start_timer(self.deleting_timers, code_slot, 60, input_boolean_slot, self.handle_lock_code_timeout)
elif new == "Disconnected":
self.log(f"Lock code {code_slot} is Disconnected. Canceling Deleting timer.")
self._cancel_timer(self.deleting_timers, code_slot)
def handle_lock_code_timeout(self, kwargs):
code_slot = kwargs["code_slot"]
input_boolean_slot = kwargs["input_boolean_slot"]
self.log(f"Lock code {code_slot} timed out. Toggling {input_boolean_slot}.")
self.call_service("homeassistant/turn_off", entity_id=input_boolean_slot)
if kwargs.get("deleting", False):
# Add specific logic for deleting state to enable for 5 seconds
self.run_in(self.reenable_lock_code, 5, input_boolean_slot=input_boolean_slot, code_slot=code_slot, deleting=True)
else:
# For adding logic, handle the timer as usual
self.run_in(self.reenable_lock_code, 5, input_boolean_slot=input_boolean_slot, code_slot=code_slot)
def reenable_lock_code(self, kwargs):
input_boolean_slot = kwargs["input_boolean_slot"]
code_slot = kwargs["code_slot"]
self.log(f"Re-enabling {input_boolean_slot}.")
self.call_service("homeassistant/turn_on", entity_id=input_boolean_slot)
# Turn off the input_boolean slot again after 5 seconds for deletion
if kwargs.get("deleting", False):
self.run_in(self.disable_lock_code, 5, input_boolean_slot=input_boolean_slot, code_slot=code_slot)
def disable_lock_code(self, kwargs):
input_boolean_slot = kwargs["input_boolean_slot"]
self.log(f"Disabling {input_boolean_slot}.")
self.call_service("homeassistant/turn_off", entity_id=input_boolean_slot)
def _start_timer(self, timer_dict, code_slot, duration, input_boolean_slot, callback):
# Cancel any existing timer for the code slot
self._cancel_timer(timer_dict, code_slot)
# Start a new timer and store it
timer_dict[code_slot] = self.run_in(callback, duration,
input_boolean_slot=input_boolean_slot,
code_slot=code_slot)
def _cancel_timer(self, timer_dict, code_slot):
# Cancel and remove the timer if it exists
if code_slot in timer_dict:
self.cancel_timer(timer_dict[code_slot])
del timer_dict[code_slot]
This will monitor your lock code status and if its stuck for 60s in a Adding/Deleting state then it will do what is necessary to attempt to resolve this.
All you need to do is go to your apps.yml file inside of your appdaemon directory and add
lock_code_status_check_front:
module: lock_code_enable
class: lock_code_status
lock_name: front_door_lock
lock_code: "1-10"
Of course repeat this for however many locks you may have.
At my house I have 2 doors that are just finicky and no matter what I do to make them play nice they just are slow to do as they’re told. So it helps now to have this so that it will attempt to fix the problem for me.
I hope that this helps out some others as well. Biggest problem I had was my AirBnB unit when Rental Control would enable a code for me and it would just get stuck in Adding and then my guest couldn’t get in. Not anymore I hope.
The code will build the 2 entities that it needs automatically for you so long as you give it the range of code slots that you want it to monitor.