Hi,
I am getting this WARNING in my appdaemon log when I try to cancel a scheduled callback
WARNING AppDaemon: Invalid callback in cancel_state_callback() from app Statetest
Full log for this app:
2021-01-21 12:37:12.583508 INFO AppDaemon: Reloading Module: /conf/apps/media_pc/statetest.py
2021-01-21 12:37:12.591522 INFO AppDaemon: Initializing app Statetest using class StateTest from module statetest
2021-01-21 12:37:12.607983 DEBUG Statetest: Calling listen_state for Statetest
2021-01-21 12:39:14.304393 DEBUG Statetest: entity='input_boolean.debug_switch_1'
2021-01-21 12:39:14.307285 DEBUG Statetest: new='off'
2021-01-21 12:39:14.615904 DEBUG Statetest: entity='input_boolean.debug_switch_1'
2021-01-21 12:39:14.618998 DEBUG Statetest: new='on'
2021-01-21 12:39:14.622331 DEBUG Statetest: NEW: wait_for------------------------
2021-01-21 12:39:14.625554 DEBUG Statetest: Waiting for input_boolean.debug_switch_3
2021-01-21 12:39:14.627681 DEBUG Statetest: Calling listen_state for Statetest
2021-01-21 12:39:14.631267 DEBUG Statetest: Registering run_in in 5 seconds for Statetest
2021-01-21 12:39:20.014568 INFO Statetest: Cancelling wait_for. Timeout reached.
2021-01-21 12:39:20.018098 DEBUG Statetest: Canceling listen_state for Statetest
2021-01-21 12:39:20.018749 WARNING AppDaemon: Invalid callback in cancel_state_callback() from app Statetest
2021-01-21 12:39:20.024326 DEBUG Statetest: 'on_timeout' not set to continue and wait_for timed out: stopping the sequence
I am hesitant to call this a bug, but according to google I am the only one with this problem and I can’t for the life of me figure out why it is happening. So I thought before I open an issue on the github issue tracker I’d first ask here.
yaml:
Statetest:
module: statetest
class: StateTest
DEBUG: 1
input_boolean: input_boolean.debug_switch_1
actions:
- wait_for:
entity_id: input_boolean.debug_switch_3 #entity to match
new: "on" #continue if state is this
immediate: True #if True: check if entity already in state. If False: wait for state change to new
timeout: 5 #wait for max this time
unit: seconds
on_timeout: stop #if we waited for max_time and state still no match: continue or stop?
global_dependencies:
- globals
dependencies:
- global_vars
py:
import hassapi as hass
import globals
import copy
class StateTest(hass.Hass):
def initialize(self):
self.listen_state_handle_list = []
self.listen_event_handle_list = []
self.timer_handle_list = []
self.global_vars = self.get_app('global_vars')
self.debug_mode = globals.get_arg(self.args, "DEBUG")
self.ib = globals.get_arg(self.args, "input_boolean")
self.actions = globals.get_arg(self.args, "actions")
self._wait_for_cb_handle = None
self.wait_for_timeout_cb_handle = None
if self.debug_mode:
self.set_log_level('DEBUG')
#add handler for input boolean change
self.state_changed_cb_handle = self.listen_state(self.state_changed_cb, self.ib)
self.listen_state_handle_list.append(self.state_changed_cb_handle)
def state_changed_cb(self, entity, attribute, old, new, kwargs):
self.debug(f"{entity=}")
self.debug(f"{new=}")
if new == "on":
if self.actions:
actions = self.actions
actions_copy = copy.copy(actions)
self.run_sequence({"actions" : actions_copy})
def run_sequence(self, kwargs):
actions = kwargs.pop("actions", [])
while actions:
action = actions.pop(0)
service, params = next(iter(action.items())) #get first (and in this case only) key in dict
self.debug(f"NEW: {service}------------------------")
if service == "wait_for":
kwargs['actions'] = actions
self.wait_for(**params, **kwargs)
break
else:
self.call_service(service, **params)
def wait_for(self, **kwargs):
entity = kwargs.pop("entity_id")
self.debug(f"Waiting for {entity}")
self._wait_for_cb_handle = self.listen_state(self._wait_for_cb, entity=entity, **kwargs)
if "timeout" in kwargs: #don't wait forever
unit = kwargs.pop("unit", "seconds")
timeout = self.seconds_to_unit(kwargs['timeout'], unit)
kwargs["on_timeout"] = kwargs.pop("on_timeout", "continue")
self.wait_for_timeout_cb_handle = self.run_in(self.wait_for_timeout_cb, timeout, **kwargs)
self.timer_handle_list.append(self.wait_for_timeout_cb_handle)
def wait_for_timeout_cb(self, kwargs):
self.log("Cancelling wait_for. Timeout reached.")
if self._wait_for_cb_handle:
self.cancel_listen_state(self._wait_for_cb_handle)
if kwargs["on_timeout"] == "continue":
self.debug("Continuing.")
self.run_sequence(kwargs)
else:
self.debug("'on_timeout' not set to continue and wait_for timed out: stopping the sequence.")
def _wait_for_cb(self, entity, attribute, old, new, kwargs):
self.log(f"wait_for_callback has been activated for entity {entity}")
if self.wait_for_timeout_cb_handle:
self.cancel_timer(self.wait_for_timeout_cb_handle)
self.run_sequence(kwargs)
def seconds_to_unit(self, seconds, unit):
if unit == "seconds":
res = seconds
elif unit == "minutes":
res = seconds * 60
elif unit == "hours":
res = seconds * 60 * 60
elif unit == "days":
res = seconds * 24 * 60 * 60
return res
def debug(self, message):
if self.debug_mode == 1:
self.log(message, level='DEBUG')
def terminate(self):
for listen_state_handle in self.listen_state_handle_list:
self.cancel_listen_state(listen_state_handle)
for listen_event_handle in self.listen_event_handle_list:
self.cancel_listen_event(listen_event_handle)
for timer_handle in self.timer_handle_list:
self.log(f"{timer_handle=}")
self.cancel_timer(timer_handle)
This is a MWE so that I can clarify the issue, the actual app will have more code. Can anybody see why cancelling the callback for the function _wait_for_cb
fails in this line:
self.cancel_listen_state(self._wait_for_cb_handle)
?