mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-11 14:24:03 +02:00
fix: re-adapt lights right after the auto-reset timer fires (#1506)
When the auto-reset timer fired, its callback called manager.reset(), which pops the timer and calls timer.cancel() - cancelling the very task that was running the callback. The manual_control flag was cleared, but the re-adaptation that should follow was silently cancelled, so the light only changed on the next interval pass (with the normal transition). _AsyncSingleShotTimer.cancel() now never cancels the task that is currently running its own callback. The trailing assert in the callback is removed because it is reachable now and could trip when a new manual change comes in while the re-adaptation is still running. The existing auto-reset test also checks that a light.turn_on with the 'autoreset' context is sent. Fixes #1233 Co-authored-by: Dennis-Dekker <> Co-authored-by: Bas Nijholt <bas@nijho.lt>
This commit is contained in:
parent
68c66a0ff3
commit
c620d1480a
2 changed files with 26 additions and 3 deletions
|
|
@ -2266,7 +2266,6 @@ class AdaptiveLightingManager:
|
|||
transition=switch.initial_transition,
|
||||
force=True,
|
||||
)
|
||||
assert self.manual_control[light] == LightControlAttributes.NONE
|
||||
|
||||
self._handle_timer(light, self.auto_reset_manual_control_timers, delay, reset)
|
||||
|
||||
|
|
@ -3007,9 +3006,17 @@ class _AsyncSingleShotTimer:
|
|||
|
||||
def cancel(self) -> None:
|
||||
"""Cancel the timer."""
|
||||
if self.task:
|
||||
# Never cancel the task that is currently running our own callback, e.g.
|
||||
# when the auto-reset callback calls manager.reset(), which cancels the
|
||||
# timer it is running in. That used to silently cancel the rest of the
|
||||
# callback (the re-adaptation), see issue #1233.
|
||||
try:
|
||||
current_task = asyncio.current_task()
|
||||
except RuntimeError: # no running event loop
|
||||
current_task = None
|
||||
if self.task and self.task is not current_task:
|
||||
self.task.cancel()
|
||||
self.callback = None
|
||||
self.callback = None
|
||||
|
||||
def remaining_time(self) -> float:
|
||||
"""Return the remaining time before the timer expires."""
|
||||
|
|
|
|||
|
|
@ -914,11 +914,27 @@ async def test_auto_reset_manual_control(hass):
|
|||
switch.extra_state_attributes["autoreset_time_remaining"][light.entity_id] > 0
|
||||
)
|
||||
await update()
|
||||
# The auto reset must also re-adapt the light right away, not only clear the
|
||||
# flag. Collect the 'light.turn_on' calls made with the 'autoreset' context.
|
||||
autoreset_calls: list[Event] = []
|
||||
|
||||
async def _on_call_service(event: Event) -> None:
|
||||
if (
|
||||
event.data.get("domain") == LIGHT_DOMAIN
|
||||
and event.data.get("service") == SERVICE_TURN_ON
|
||||
and is_our_context(event.context, "autoreset")
|
||||
):
|
||||
autoreset_calls.append(event)
|
||||
|
||||
remove_listener = hass.bus.async_listen(EVENT_CALL_SERVICE, _on_call_service)
|
||||
await asyncio.sleep(0.3) # Should be enough time for auto reset
|
||||
await hass.async_block_till_done()
|
||||
remove_listener()
|
||||
assert not manual_control[light.entity_id], (light, manual_control)
|
||||
assert (
|
||||
light.entity_id not in switch.extra_state_attributes["autoreset_time_remaining"]
|
||||
)
|
||||
assert autoreset_calls, "auto reset did not re-adapt the light"
|
||||
|
||||
# Do a couple of quick changes and check that light is not reset
|
||||
for i in range(3):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue