Hi, just today I started my journey with AppDaemon and Python and the first thing I wanted to try was automating the bathroom lights. Previously I tried with the YAML automation and was unable to achieve what I wanted.
Here is the entire automation code which does the following
Listen to door sensor states
If door opens, then based on the time, switch on the lights
If door is closed then, using motion sensor detect if bathroom is occupied or not
If occupied, then, don’t do anything, else, switch off the lights
The automation seems to be working for me ok, however since am new to AppDaemon and Python, wanted to share this code here and understand if this can be improved w.r.t code quality and maybe some other suggestions that you might have. This will really help me continue my journey with this amazing add-on
Edit: Code updated after adding contrain and other changes suggested
Edit 2: Implemented changes per @Burningstone’s suggestion
Edit 3: Implemented few more logics (which made sense to me currently ), thanks @gpbenton@ReneTode
Edit 4: Re-done the entire logic with keep it simple philosophy, the code is much cleaner now
class BathroomAutomations(hass.Hass):
def initialize(self):
self.motion = self.args["motion_id"]
self.light = self.args["light_id"]
self.door = self.args["door_id"]
# Always listen to motion detector and turn on the light in case it is off
self.listen_state(self.motion_cb, entity=self.motion, new="on")
# Listen to Door sensor
self.listen_state(self.door_cb, entity=self.door)
def motion_cb(self, entity, attribute, old, new, kwargs):
self.log("Enter motion_cb")
if self.get_state(self.light) == "off":
self.turn_on(self.light)
def door_cb(self, entity, attribute, old, new, kwargs):
if new == "on":
self.log("Door opened")
if self.get_state(self.light) == "off":
self.turn_on(self.light)
elif new == "off":
self.log("Door closed")
# 20 seconds timeout started
self.door_close_timeout_handle = self.run_in(
self.door_close_timeout_cb, 20)
self.door_close_motion_handle = self.listen_state(
self.door_close_motion_cb, entity=self.motion, new="on")
def door_close_timeout_cb(self, kwargs):
# Cancel the door close motion detection because timeout expired
self.cancel_timer(self.door_close_motion_handle)
if self.get_state(self.light) == "on":
self.turn_off(self.light)
def door_close_motion_cb(self, entity, attribute, old, new, kwargs):
# Cancel the timer as bathroom is occupied
self.cancel_timer(self.door_close_timeout_handle)
But this is more important to get correct. You should not really sleep in callback functions, as this holds up a thread see the note on threading
I think, in this case, you could, in this case, call self.run_in() and then continue your wait by continuously calling that callback, but I think it would be a better design to set up another trigger to be called when the motion occurs.
This is where I was getting confused and could not understand the design that needs to be implemented. Basically, by logic, I want to monitor the motion sensor for the next 20 seconds and the moment it gets triggered, I want to stop everything and ensure that the light does not switch off.
Can you help me figure out how this needs to be coded?
Thank you so much @Burningstone, this really helps. I have updated the code per your suggestions. Did not know about f string honestly. I went from %s to .format to f strings literally in a day .
Thanks for the suggestion, however, my logic is a little different than this one. I want to monitor for the entire duration of 20 seconds and not after 20 seconds. Reason being, the motion sensor has been set up to turn off after 3 seconds of detecting motion; it is highly unlikely that this will be on after 20 seconds.
So, the idea was to see if the sensor gets triggered within 20 seconds of door getting closed, this gives me a very high probability of bathroom being occupied.
Ah I understand. You could create a handle and run the check for motion function every second with self.run_every.
In the check for motion function add a counter and each time it is called increase the counter by one. Also add a condition in the check for motion function,that once the counter hits 20 or motion has been detected, cancel the handle.