I seem to have a lot of issues with fully understanding cancel_timer…
I have the following code (simplified for clarity):
class MotionLights(hass.Hass):
def initialize(self) -> None:
self.off_timer = None
motion_sensor_triggers: list = self.args["motion_sensor_triggers"]
for motion_sensor in motion_sensor_triggers:
self.listen_state(self.main, motion_sensor)
def main(self, entity, attribute, old, new, kwargs) -> None:
light: str = self.args["light"]
self.log("LIGHTS")
self.log(self.off_timer)
self.log(type(self.off_timer))
if self.off_timer is not None:
try:
self.cancel_timer(self.off_timer)
self.off_timer = None
except:
pass
if new == "on":
# a few things to turn on with correct color/brightness depending on numerous factors
if new == "off":
off_duration: int = self.determine_off_duration(light)
self.off_timer = self.run_in(self.turn_off_lights, off_duration, light=light)
If i turn on, then off, then on again the motion sensor, I get this in the logs (first 3 lines from turning on, all as expected, there’s no timer yet, next 3 lines from turning off, still as expected):
2022-11-12 22:36:35.062212 INFO entrance_lights: LIGHTS
2022-11-12 22:36:35.062877 INFO entrance_lights: None
2022-11-12 22:36:35.063638 INFO entrance_lights: <class 'NoneType'>
2022-11-12 22:36:46.201925 INFO entrance_lights: LIGHTS
2022-11-12 22:36:46.212214 INFO entrance_lights: None
2022-11-12 22:36:46.214118 INFO entrance_lights: <class 'NoneType'>
2022-11-12 22:36:55.995437 INFO entrance_lights: LIGHTS
2022-11-12 22:36:55.998728 INFO entrance_lights: 1e0005481cfe4815871d7061fd6c0b46
2022-11-12 22:36:55.999616 INFO entrance_lights: <class 'str'>
2022-11-12 22:36:56.011334 WARNING AppDaemon: Invalid callback handle '1e0005481cfe4815871d7061fd6c0b46' in cancel_timer() from app entrance_lights
Why the heck can I not call cancel_timer on self.off_timer (1e0005481cfe4815871d7061fd6c0b46)?
Or maybe more succinctly, why does it claim to be getting an invalid callback handle. Because the timer is actually canceled. Or at least if I turn on the sensor again before the off_duration, the light does not turn off when it should/would have according to the off_duration.
Sometimes home-assistant and Appdeamon do this weird things were they send the same signal twice or they send the signal again for whatever reason. I find for the listen_state function it’s usually best to add a
if old != new:
At the top of the function.
Also with python you should never use except: pass
try
except Exception as error:
self.error(error, level='WARNING')
I know this doesn’t really solve the problem ,but it might help
My ConBeeII is unable to be seen by deCONZ after a power outage yesterday, so I unfortunately can’t test any of this for the moment (well, I’d prefer to wait until I can test on what it’s supposed too work on). Luckily, I ordered a new (upgraded, based on CC2652P2) coordinator a week ago, so I hope I’m up and running again soon.
OK, back up and running. Again, thanks or the suggestions. A bit more digging; it only happens if the timer runs out (@tjntomas, the off_duration is 5 seconds).
If I manually set the timer to None in the turn_off_lights script, it stops throwing the warning. I though that the timer was set to None when it runs after self.run_in(), but it seems like it still has the long string handle.
I added this code under `main` to get the logs shown below
Code:
self.log("LIGHTS GENERAL LIGHT OFF TIMER")
self.log(self.light_off_timer)
self.log(type(self.light_off_timer))
Logs if I turn off and the on again AFTER the delay for the self.run_in() (the 5 second off_duration)
2022-11-16 16:40:48.220656 INFO entrance_lights: LIGHTS GENERAL LIGHT OFF TIMER
2022-11-16 16:40:48.267152 INFO entrance_lights: 2ae17610adec478998014f005dc91699
2022-11-16 16:40:48.275131 INFO entrance_lights: <class 'str'>
2022-11-16 16:40:48.280359 WARNING AppDaemon: Invalid callback handle '2ae17610adec478998014f005dc91699' in cancel_timer() from app entrance_lights
If I turn off, then on before the 5 second off_duration, no warnings, it just works. Technically, I guess, it just works regardless.
Is it a bug that the timer is not set to None after it runs out? Or expected behavior, and I need to have the self.light_off_timer = None line in the turn_off_lights function?
Btw, @Vesha, I tried adding a log of old and new just to see, and I haven’t noticed them being identical yet (one is always off when the other is on).
It is not a bug that a variable referencing a scheduler callback’s handle ID is not set to None. If you manually cancel a timer, you should also manually set the variable referencing its ID to None.
Also, to really simplify things, when dealing with motion sensors that turn things on and off, I highly recommend using 2 separate callbacks for “on” and “off” with the duration parameter. You can avoid using timers completely with a duration method.
Additionally, it is good practice to have a helper function to cancel a timer, I use something like this.
def cancel_handle(self, handle_name):
handle = self.handle.get(handle_name)
if handle:
if self.adapi.timer_running(handle):
self.adapi.cancel_timer(handle)
self.handle[handle] = None
Not sure I can avoid timers, since the “duration” will vary based on numerous factors (time of day, who’s home, what other lights are on, etc.). But the rest seems really useful for my use case.