Skip to content

Commit fd5dd25

Browse files
dmamelinDmitrii
authored andcommitted
fix state trigger hold on unrelated state updates
1 parent b9b7eb9 commit fd5dd25

2 files changed

Lines changed: 43 additions & 15 deletions

File tree

custom_components/pyscript/decorators/state.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,13 @@ def _diff(self, dt: float, now: float) -> str:
149149
return "None"
150150
return f"{(now - dt):g} ago"
151151

152-
async def _check_new_state(self, trig_ok: bool) -> None:
152+
async def _check_new_state(
153+
self, trig_ok: bool, new_vars: dict[str, Any], func_args: dict[str, Any]
154+
) -> None:
153155
now = asyncio.get_running_loop().time()
154156
if _LOGGER.isEnabledFor(logging.DEBUG):
155157
msg = f"check_new_state: {self}"
156-
msg += f"\ntrig_ok: {trig_ok} now {now} func_args: {self.last_func_args} new_vars: {self.last_new_vars}"
158+
msg += f"\ntrig_ok: {trig_ok} now {now} func_args: {func_args} new_vars: {new_vars}"
157159
if self.true_entered_at:
158160
msg += f"\ntrue_entered_at: {self.true_entered_at}({(now - self.true_entered_at):g} ago)\n"
159161
if self.false_entered_at:
@@ -179,6 +181,8 @@ async def _check_new_state(self, trig_ok: bool) -> None:
179181

180182
if state_hold_false_passed:
181183
if self.state_hold is None:
184+
self.last_new_vars = new_vars
185+
self.last_func_args = func_args
182186
state_hold_true_passed = True
183187
else:
184188
if self.true_entered_at:
@@ -192,9 +196,10 @@ async def _check_new_state(self, trig_ok: bool) -> None:
192196
else:
193197
_LOGGER.debug("state_hold started, %s", self)
194198
self.true_entered_at = now
199+
self.last_new_vars = new_vars
200+
self.last_func_args = func_args
195201

196202
if state_hold_true_passed:
197-
self.true_entered_at = None
198203
await self.dispatch(
199204
DispatchData(self.last_func_args, trigger_context={"new_vars": self.last_new_vars})
200205
)
@@ -231,14 +236,14 @@ async def _cycle(self) -> None:
231236
check_state_expr_on_start = self.state_check_now or self.state_hold_false is not None
232237

233238
if check_state_expr_on_start:
234-
self.last_new_vars = State.notify_var_get(self.state_trig_ident, {})
235-
trig_ok = await self._is_trig_ok()
239+
new_vars = State.notify_var_get(self.state_trig_ident, {})
240+
trig_ok = await self._is_trig_ok(new_vars)
236241

237242
if self.in_wait_until_function and trig_ok and self.state_check_now is True:
238243
self.state_hold_false = None
239244

240245
if self.state_check_now and self.has_expression():
241-
await self._check_new_state(trig_ok)
246+
await self._check_new_state(trig_ok, new_vars, self.last_func_args)
242247
else:
243248
if not trig_ok and self.state_hold_false is not None:
244249
self.false_entered_at = loop.time()
@@ -273,22 +278,22 @@ async def _cycle(self) -> None:
273278
notify_type, notify_info = await asyncio.wait_for(self.notify_q.get(), effective_timeout)
274279
if notify_type != "state":
275280
raise RuntimeError(f"Invalid notify_type {notify_type}, {self}")
276-
self.last_new_vars = notify_info[0]
277-
self.last_func_args = notify_info[1]
281+
new_vars = notify_info[0]
282+
func_args = notify_info[1]
278283

279-
if ident_any_values_changed(self.last_func_args, self.state_trig_ident_any):
284+
if ident_any_values_changed(func_args, self.state_trig_ident_any):
280285
trig_ok = True
281-
elif ident_values_changed(self.last_func_args, self.state_trig_ident):
282-
trig_ok = await self._is_trig_ok()
286+
elif ident_values_changed(func_args, self.state_trig_ident):
287+
trig_ok = await self._is_trig_ok(new_vars)
283288
else:
284-
trig_ok = False
285-
await self._check_new_state(trig_ok)
289+
continue
290+
await self._check_new_state(trig_ok, new_vars, func_args)
286291
except TimeoutError:
287292
await self._check_state_hold()
288293

289-
async def _is_trig_ok(self) -> bool:
294+
async def _is_trig_ok(self, new_vars: dict[str, Any]) -> bool:
290295
if self.has_expression():
291-
return await self.check_expression_vars(self.last_new_vars)
296+
return await self.check_expression_vars(new_vars)
292297
return True
293298

294299
def _on_task_done(self, task: asyncio.Task) -> None:

tests/test_function.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,13 @@ def func10d(var_name=None, value=None, trigger_type=None, context=None, old_valu
440440
log.info(f"func10d var = {var_name}, value = {value}, kwargs = {kwargs}")
441441
pyscript.done = [seq_num, var_name, kwargs]
442442
443+
@state_trigger("pyscript.f11var1 == 'playing'", state_hold=1e-6)
444+
def func11(var_name=None, value=None, old_value=None):
445+
global seq_num
446+
447+
seq_num += 1
448+
pyscript.done = [seq_num, var_name, old_value, value, value.position, pyscript.f11var1.position]
449+
443450
""",
444451
)
445452
# initialize the trigger and active variables
@@ -672,6 +679,22 @@ def func10d(var_name=None, value=None, trigger_type=None, context=None, old_valu
672679
hass.states.async_set("pyscript.f8bvar1", 30)
673680
hass.states.async_set("pyscript.f8bvar1", 31)
674681

682+
#
683+
# check that state_hold isn't cancelled by unrelated attribute-only updates
684+
#
685+
seq_num += 1
686+
hass.states.async_set("pyscript.f11var1", "stop")
687+
hass.states.async_set("pyscript.f11var1", "playing", {"position": 1})
688+
hass.states.async_set("pyscript.f11var1", "playing", {"position": 2})
689+
assert literal_eval(await wait_until_done(notify_q)) == [
690+
seq_num,
691+
"pyscript.f11var1",
692+
"stop",
693+
"playing",
694+
1,
695+
2,
696+
]
697+
675698
#
676699
# check that state_var.old is None first time
677700
#

0 commit comments

Comments
 (0)