Skip to content

fix(state-machine): allow blocked from idle at start_time; never raise from the callback - #337

Open
pluskal wants to merge 1 commit into
danobot:masterfrom
pluskal:fix/blocked-from-idle-start-time
Open

fix(state-machine): allow blocked from idle at start_time; never raise from the callback#337
pluskal wants to merge 1 commit into
danobot:masterfrom
pluskal:fix/blocked-from-idle-start-time

Conversation

@pluskal

@pluskal pluskal commented Jul 24, 2026

Copy link
Copy Markdown

Problem

start_time_callback() unconditionally calls blocked() (or enable()) whenever start_time is reached. But the machine is not always in constrained at that moment:

  • HA restarts inside the active window → machine is idle at start_time.
  • an override toggles off before start_time → also idle.

danobot defines blocked only from constrained (and the self/sensor_on rules), so blocked() from idle raises transitions.core.MachineError. Because the exception escapes a async_track_point_in_time callback, HA re-fires the failed callback endlessly, producing a log/error storm.

Fix

  1. Add a blocked: idle -> blocked transition (guarded by is_block_enabled), mirroring the existing sensor_on: idle -> blocked rule.
  2. Extract the start-time logic into _apply_start_time_transition() and wrap it in try/except MachineError, so any state with no defined transition from start_time is logged and left unchanged instead of raising out of the point-in-time callback. This makes the callback robust to future state/transition gaps as well.

from transitions.core import MachineError added to imports.

Testing

Reproduced the error storm by restarting HA inside the active window; after the fix the controller transitions to blocked cleanly, and states without a transition log a single warning instead of looping. py_compile clean.

…e from the callback

start_time_callback calls blocked()/enable() regardless of current
state. When the machine is idle at start_time (HA restarted inside the
active window, or an override toggled off while constrained) blocked()
raised MachineError, and HA re-fires a failed point-in-time callback
endlessly, flooding the log.

- add `blocked: idle -> blocked` transition (mirrors the existing
  sensor_on: idle -> blocked rule)
- extract _apply_start_time_transition() and guard it with
  try/except MachineError, so states with no defined transition log a
  warning and keep their state instead of raising out of the callback
Copilot AI review requested due to automatic review settings July 24, 2026 07:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the entity controller state machine against MachineError exceptions triggered from Home Assistant point-in-time callbacks, preventing repeated callback retries and log storms when HA restarts or overrides change state mid-window.

Changes:

  • Adds a blocked: idle -> blocked transition (guarded by is_block_enabled) so start_time_callback() can safely move into blocked when already idle.
  • Extracts start-time transition selection into _apply_start_time_transition() and wraps it with try/except MachineError to avoid exceptions escaping the scheduled callback.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 45 to 47
from transitions import Machine
from transitions.core import MachineError
from transitions.extensions import HierarchicalMachine as Machine
Comment on lines +1272 to 1274
self._apply_start_time_transition()
self.do_transition_behaviour(CONF_ON_EXIT_CONSTRAINED)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants