Allowing State Trigger Decorator Function to be Called While Executing Another Function #716
-
Is there a way to allow a function with a state decorator to be called while another function that is running is manipulating the state of the entity that is in the state decorator. I tried using task.wait_until(state_trigger='entity id'), but it didn't work, for obvious reasons I think. What I'm asking is there a function available similar to 'DoEvents' found in the non-async language VBA? Consider the following code: TEST_ENTITY_ID = 'input_number.test_numeric_input'
_state_trigger_dict = {} #key: trigger string expr, value: trigger function name
_ignore_state_event = False
@time_trigger("once(now)")
def set_value():
global _ignore_state_event
log.info(f"Setting global variable _ignore_state_event: True")
_ignore_state_event = True
for i in range(0,3,1):
log.info(f"Setting entity value: {i}")
state.set(TEST_ENTITY_ID,i)
log.info(f"Setting global variable _ignore_state_event: False")
_ignore_state_event = False
@state_trigger(TEST_ENTITY_ID)
def state_monitor_factory(**kwargs):
global _ignore_state_event
entity_id = kwargs['var_name']
old_value = kwargs['old_value']
new_value = kwargs['value']
if _ignore_state_event == True:
log.info(f"Ignoring state monitor trigger for entity: {entity_id}")
return
log.info(f"State monitor triggered ({entity_id}) value: {new_value}")
_state_trigger_dict[TEST_ENTITY_ID] = state_monitor_factory Here is the log: 2025-04-09 11:36:52.431 INFO (MainThread) [custom_components.pyscript.global_ctx] Reloaded /config/pyscript/test.py
2025-04-09 11:36:52.433 INFO (MainThread) [custom_components.pyscript.file.test.set_value] Setting global variable _ignore_state_event: True
2025-04-09 11:36:52.433 INFO (MainThread) [custom_components.pyscript.file.test.set_value] Setting entity value: 0
2025-04-09 11:36:52.434 INFO (MainThread) [custom_components.pyscript.file.test.set_value] Setting entity value: 1
2025-04-09 11:36:52.434 INFO (MainThread) [custom_components.pyscript.file.test.set_value] Setting entity value: 2
2025-04-09 11:36:52.434 INFO (MainThread) [custom_components.pyscript.file.test.set_value] Setting global variable _ignore_state_event: False
2025-04-09 11:36:52.435 INFO (MainThread) [custom_components.pyscript.file.test.state_monitor_factory] State monitor triggered (input_number.test_numeric_input) value: 0
2025-04-09 11:36:52.435 INFO (MainThread) [custom_components.pyscript.file.test.state_monitor_factory] State monitor triggered (input_number.test_numeric_input) value: 1
2025-04-09 11:36:52.435 INFO (MainThread) [custom_components.pyscript.file.test.state_monitor_factory] State monitor triggered (input_number.test_numeric_input) value: 2 |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 2 replies
-
Hello @jlkermit 👋 Maybe you can try the following to rely more on a pyscriptive approach using decorators to check whether to run or not: state.set("pyscript.ignore_me", false)
@time_trigger("once(now)")
def set_value():
state.set("pyscript.ignore_me", true)
...
state.set("pyscript.ignore_me", false)
@state_trigger(TEST_ENTITY_ID)
@state_active("pyscript.ignore_me == false")
def state_monitor_factory(**kwargs):
...
|
Beta Was this translation helpful? Give feedback.
-
Trigger functions run asynchronously after they are triggered. There's no guarantee that the statements inside one trigger function will be executed before or after another function's statements when triggered at almost the same time. Statements could even be interleaved between functions (actually, the granularity is finer than that: one function could give up control in the middle of evaluating an expression, and execution of the other function could continue next). This means you can suffer from various race conditions if you don't make your code robust to indeterminate relative order of execution. As you saw, when you change the state variable in one function, it takes a bit of time for HASS to generate the state changed event, which means the 2nd function actually gets triggered after the first one finishes, so your flag is already reset. But that's not guaranteed. @IgnusG's solution is more robust, but is still not guaranteed - the state trigger might not be evaluated until after the first function finishes. There are several solutions I can think of. One is to use an async queue to communicate state updates that should be ignored. The function that is updating the state which wants to skip the state monitor function writes the new state value to the queue. The state monitor function checks the queue, and if the values match then it ignores the update. Here's some untested pseudo code:
However, there's an unlikely but still potential race condition that the state triggers happen out of order, so the ignore values in the queue could be extracted in the wrong order. So this isn't a 100% robust solution. You could use a map to count the number of times each state set should be ignored. You'll need a lock to prevent race conditions while it is updated in each function.
The simplest solution would be to set an attribute of the state variable, assuming that doesn't break anything that uses the state variable:
This is robust since the information about what to ignore is part of the state update, so is immune to any order-of-execution issues. These are all untested. |
Beta Was this translation helpful? Give feedback.
-
Appreciate the detailed response. Your last suggestion is very simple and does work. I didn't think to manipulate an attribute. Further I didn't know a 'state_trigger' would fire on entity value changed when the 'state_trigger' expression was an entity attribute. Thanks! |
Beta Was this translation helpful? Give feedback.
-
Yes - the last point is subtle but important. Even if the expression involves an attribute, any change to the entity is monitored (HASS doesn't provide for finer-grained events) and the expression is evaluated each time there is any change. |
Beta Was this translation helpful? Give feedback.
Trigger functions run asynchronously after they are triggered. There's no guarantee that the statements inside one trigger function will be executed before or after another function's statements when triggered at almost the same time. Statements could even be interleaved between functions (actually, the granularity is finer than that: one function could give up control in the middle of evaluating an expression, and execution of the other function could continue next). This means you can suffer from various race conditions if you don't make your code robust to indeterminate relative order of execution.
As you saw, when you change the state variable in one function, it takes a bit of time f…