Cancel_timer() reporting "Invalid callback handle"

For all my attempts, I just can’t seem to get a good handle on run_in (and run_at) and timers.

I have an app that keeps throwing this warning (well, a couple of apps, but this is the simplest):

2022-05-30 21:49:05.670489 WARNING AppDaemon: Invalid callback handle '39544d3f4dd54de99c15c37efebbb094' in cancel_timer() from app kitchen_lights

The only part of the code where I use the timer (or cancel_timer) is this function, triggered by a listener for a motion sensor:

    def motion_trigger(self, entity, attribute, old, new, kwargs):

        if self.get_state("binary_sensor.kitchen_motion") == "on":
            if self.timer != None:
                self.cancel_timer(self.timer)
            self.lights_on()
        elif (
            self.get_state("binary_sensor.kitchen_motion") == "off"
        ):
            if self.awake():
                self.timer = self.run_in(self.lights_off, 180)
            else:
                self.timer = self.run_in(self.lights_off, 90)

where awake() returns True/False, lights_on() basically just turns on, and lights_off() basically just turns off.

The lights work as expected, but the warning kind of bugs me, since I don’t understand why.

EDIT:
Btw, I have tried adding

        if self.timer != None:
            self.cancel_timer(self.timer)

both at the top of this (motion_trigger()) function (before if self.get_state("binary_sensor.kitchen_motion") == "on":) and in the lights_off() function.

When you’re dealing with motion sensors that are controlling lights, it would be a little bit easier to use two different callbacks with the duration=. This tells the callback to run only if it has remained in the desired state for the specified amount of time.

# trigger callback as soon as motion sensor changes state to "on"
self.listen_state(self.motion_on_callback, "binary_sensor.kitchen_motion", new="on")

# trigger callback after motion sensors has been changed to state "off" for 60 seconds
self.listen_state(self.motion_off_callback, "binary_sensor.kitchen_motion", new="off", duration=60)

self.motion_on_callback(self, entity, attribute, old, new, kwargs):
    self.turn_on("light.kitchen_light")

self.motion_off_callback(self, entity, attribute, old, new, kwargs):
    self.turn_off("light.kitchen_light") 

As far as the error and properly cancelling the timer. It is possible that you’re trying to cancel a timer that has already been cancelled and I recommend the following for cancelling timers without getting an error.

if self.timer_handle:
    if self.timer_running(self.timer_handle):
        self.cancel_timer(self.timer_handle)
    self.timer_handle = None

Thanks a lot, I’ll try the extra canceling steps. The approach I used now was something suggested when i first tried to make something with timers work. Which, as mentioned, works. It just gave these warnings every now and then…

I did consider duration, but didn’t think it would work in my case, since the duration depends on the result of awake() that uses a bunch of factors to estimate whether or not the adults in the house are awake, and adjusts the duration accordingly. So longer timeout when we are “awake” than if we just go during the night to get some water.

EDIT: Am I right to assume that once the timer runs out (in the above, after 90 or 180 seconds), the timer handle (self.timer) becomes None? Or does it retain some other value?

            if self.timer != None:
                self.cancel_timer(self.timer)

should be (as @proggie suggests):

            if self.timer != None:
                self.cancel_timer(self.timer)
                self.timer = None

because self.cancel_timer doesnt change the value from your var.
i wouldnt consider rewriting your code with duration, because of the different times you use.
self.timer only becomes None if you do that in your code, in any case. in all other cases it just keeps the last known value.

edit: sorry i didnt add it in the last conversation :wink:

Ok, so it might be sensible to have just a:

self.timer = None

in the first line of the function that is called by the self.run_in, just to make sure it is “reset” back to None once it’s done running?

thats right.
if you check for None to make sure you dont use an old handle, then its best to make sure that the var is set to None at the moments you know the handle isnt valid anymore (after cancelling and running the timer)

edit: now that i think of it, it might have been better to use another value then None, because that would explain it better.

            if self.timer != "not set":
                self.cancel_timer(self.timer)
                self.timer = "not set"

:wink: