Skip to content

Commit 97203c8

Browse files
committed
if a timeout occurs earlier than expected, added a short additional timeout to make sure any
time trigger occurs at or shortly after the target time (and never before).
1 parent 57dd962 commit 97203c8

File tree

3 files changed

+60
-32
lines changed

3 files changed

+60
-32
lines changed

custom_components/pyscript/trigger.py

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,10 @@ async def wait_until(
359359
state_trig_timeout = False
360360
time_next = None
361361
startup_time = None
362+
now = dt_now()
363+
if startup_time is None:
364+
startup_time = now
362365
if time_trigger is not None:
363-
now = dt_now()
364-
if startup_time is None:
365-
startup_time = now
366366
time_next = cls.timer_trigger_next(time_trigger, now, startup_time)
367367
_LOGGER.debug(
368368
"trigger %s wait_until time_next = %s, now = %s", ast_ctx.name, time_next, now,
@@ -377,11 +377,13 @@ async def wait_until(
377377
if this_timeout is None or this_timeout > time_left:
378378
ret = {"trigger_type": "timeout"}
379379
this_timeout = time_left
380+
time_next = now + dt.timedelta(seconds=this_timeout)
380381
if state_trig_waiting:
381382
time_left = last_state_trig_time + state_hold - time.monotonic()
382383
if this_timeout is None or time_left < this_timeout:
383384
this_timeout = time_left
384385
state_trig_timeout = True
386+
time_next = now + dt.timedelta(seconds=this_timeout)
385387
if this_timeout is None:
386388
if state_trigger is None and event_trigger is None and mqtt_trigger is None:
387389
_LOGGER.debug(
@@ -392,18 +394,33 @@ async def wait_until(
392394
_LOGGER.debug("trigger %s wait_until no timeout", ast_ctx.name)
393395
notify_type, notify_info = await notify_q.get()
394396
else:
395-
try:
396-
this_timeout = max(0, this_timeout)
397-
_LOGGER.debug("trigger %s wait_until %.6g secs", ast_ctx.name, this_timeout)
398-
notify_type, notify_info = await asyncio.wait_for(notify_q.get(), timeout=this_timeout)
399-
state_trig_timeout = False
400-
except asyncio.TimeoutError:
401-
if not state_trig_timeout:
402-
if not ret:
403-
ret = {"trigger_type": "time"}
404-
if time_next is not None:
405-
ret["trigger_time"] = time_next
406-
break
397+
timeout_occured = False
398+
while True:
399+
try:
400+
this_timeout = max(0, this_timeout)
401+
_LOGGER.debug("trigger %s wait_until %.6g secs", ast_ctx.name, this_timeout)
402+
notify_type, notify_info = await asyncio.wait_for(
403+
notify_q.get(), timeout=this_timeout
404+
)
405+
state_trig_timeout = False
406+
except asyncio.TimeoutError:
407+
actual_now = dt_now()
408+
if actual_now < time_next:
409+
this_timeout = (time_next - actual_now).total_seconds()
410+
# tests/tests_function's simple now() requires us to ignore
411+
# timeouts that are up to 1us too early; otherwise wait for
412+
# longer until we are sure we are at or past time_next
413+
if this_timeout > 1e-6:
414+
continue
415+
if not state_trig_timeout:
416+
if not ret:
417+
ret = {"trigger_type": "time"}
418+
if time_next is not None:
419+
ret["trigger_time"] = time_next
420+
timeout_occured = True
421+
break
422+
if timeout_occured:
423+
break
407424
if state_trig_timeout:
408425
ret = state_trig_notify_info[1]
409426
state_trig_waiting = False
@@ -964,24 +981,31 @@ async def trigger_watch(self):
964981
time_left = last_state_trig_time + self.state_hold - time.monotonic()
965982
if timeout is None or time_left < timeout:
966983
timeout = time_left
984+
time_next = now + dt.timedelta(seconds=timeout)
967985
state_trig_timeout = True
968986
if timeout is not None:
969-
try:
970-
timeout = max(0, timeout)
971-
_LOGGER.debug("trigger %s waiting for %.6g secs", self.name, timeout)
972-
notify_type, notify_info = await asyncio.wait_for(
973-
self.notify_q.get(), timeout=timeout
974-
)
975-
state_trig_timeout = False
976-
now = dt_now()
977-
except asyncio.TimeoutError:
978-
now += dt.timedelta(seconds=timeout)
979-
if not state_trig_timeout:
980-
notify_type = "time"
981-
notify_info = {
982-
"trigger_type": "time",
983-
"trigger_time": time_next,
984-
}
987+
while True:
988+
try:
989+
timeout = max(0, timeout)
990+
_LOGGER.debug("trigger %s waiting for %.6g secs", self.name, timeout)
991+
notify_type, notify_info = await asyncio.wait_for(
992+
self.notify_q.get(), timeout=timeout
993+
)
994+
state_trig_timeout = False
995+
now = dt_now()
996+
except asyncio.TimeoutError:
997+
actual_now = dt_now()
998+
if actual_now < time_next:
999+
timeout = (time_next - actual_now).total_seconds()
1000+
continue
1001+
now = time_next
1002+
if not state_trig_timeout:
1003+
notify_type = "time"
1004+
notify_info = {
1005+
"trigger_type": "time",
1006+
"trigger_time": time_next,
1007+
}
1008+
break
9851009
elif self.have_trigger:
9861010
_LOGGER.debug("trigger %s waiting for state change or event", self.name)
9871011
notify_type, notify_info = await self.notify_q.get()

docs/new_features.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ Breaking changes since 1.2.1 include:
4848

4949
Bug fixes since 1.2.1 include:
5050

51+
- Timeouts that implement time triggers might occur a tiny time before the target time. A fix was added to do
52+
an additional short timeout when there is an early timeout, to make sure any time trigger occurs at or shortly
53+
after the target time (and never before).
5154
- Fixed subscripts when running python 3.9.x.
5255
- When exception text is created, ensure lineno is inside code_list[]; with lambda function or eval it might not be.
5356
- An exception is raised when a function is called with unexpected keyword parameters that don't have corresponding

tests/test_function.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,11 +680,12 @@ async def test_state_trigger_time(hass, caplog):
680680
"""Test state trigger."""
681681
notify_q = asyncio.Queue(0)
682682
notify_q2 = asyncio.Queue(0)
683+
now = [dt(2020, 7, 1, 10, 59, 59, 999999), dt(2020, 7, 1, 11, 59, 59, 999999)]
683684
await setup_script(
684685
hass,
685686
notify_q,
686687
notify_q2,
687-
[dt(2020, 7, 1, 10, 59, 59, 999998), dt(2020, 7, 1, 11, 59, 59, 999998)],
688+
now,
688689
"""
689690
690691
from math import sqrt

0 commit comments

Comments
 (0)