From 6eca345bd5e42dc6d78c33591ca21a15eec98efa Mon Sep 17 00:00:00 2001 From: Vincent Filby Date: Mon, 24 Aug 2026 19:44:02 -0700 Subject: [PATCH] Reload: don't resurrect the replaced engine if reload runs during boot async_setup defers engine start to EVENT_HOMEASSISTANT_STARTED. A reload issued before that fired stopped the (not yet started) engine, then the listener started it anyway, leaving two engines subscribed. async_stop now marks the engine stopped (start becomes a no-op) and cancels the deferred-start listener. 0.7.1. Co-Authored-By: Claude Fable 5 --- custom_components/shade_engine/__init__.py | 15 ++++++++++++++- custom_components/shade_engine/manifest.json | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/custom_components/shade_engine/__init__.py b/custom_components/shade_engine/__init__.py index 556a34f..1d45622 100644 --- a/custom_components/shade_engine/__init__.py +++ b/custom_components/shade_engine/__init__.py @@ -293,12 +293,17 @@ def __init__(self, hass: HomeAssistant, zones: dict[str, Zone]) -> None: self._cover_to_zone[cover] = zone self._hold_timers: dict[str, object] = {} self._unsubs: list = [] + self._stopped = False # -- lifecycle ---------------------------------------------------------- @callback def async_start(self, delay: float = STARTUP_DELAY) -> None: """Subscribe to everything and schedule the first reconcile.""" + if self._stopped: + # Replaced by a reload before HA finished booting; the deferred + # start must not resurrect this engine alongside its successor. + return self._unsubs.append( async_track_state_change_event(self.hass, [SUN_ENTITY], self._sun_changed) ) @@ -318,9 +323,15 @@ async def _initial(_now) -> None: self._unsubs.append(async_call_later(self.hass, delay, _initial)) + @callback + def track_unsub(self, unsub) -> None: + """Register an external subscription to cancel on stop.""" + self._unsubs.append(unsub) + @callback def async_stop(self) -> None: """Unsubscribe from everything; the engine issues no further commands.""" + self._stopped = True for unsub in self._unsubs: unsub() self._unsubs.clear() @@ -661,7 +672,9 @@ def _start(_event: Event) -> None: if hass.state is CoreState.running: engine.async_start() else: - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _start) + engine.track_unsub( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _start) + ) return True diff --git a/custom_components/shade_engine/manifest.json b/custom_components/shade_engine/manifest.json index a7cbec2..450bdb2 100644 --- a/custom_components/shade_engine/manifest.json +++ b/custom_components/shade_engine/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/vfilby/shade-engine/issues", "requirements": [], "single_config_entry": true, - "version": "0.7.0" + "version": "0.7.1" }