|
21 | 21 | CONF_NAME, |
22 | 22 | CONF_PORT, |
23 | 23 | EVENT_HOMEASSISTANT_STARTED, |
| 24 | + EVENT_HOMEASSISTANT_STOP, |
24 | 25 | PERCENTAGE, |
25 | 26 | Platform, |
26 | 27 | UnitOfElectricCurrent, |
@@ -257,6 +258,22 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: |
257 | 258 | else: |
258 | 259 | hass.data[DOMAIN]["_debug_settings"] = {} |
259 | 260 |
|
| 261 | + async def _stop_hubs_on_homeassistant_stop(event: Any) -> None: |
| 262 | + """Stop active hubs before HA reaches final task cancellation.""" |
| 263 | + domain_data = hass.data.get(DOMAIN, {}) |
| 264 | + for name, rec in list(domain_data.items()): |
| 265 | + if not isinstance(rec, dict): |
| 266 | + continue |
| 267 | + hub = rec.get("hub") |
| 268 | + if hub: |
| 269 | + _LOGGER.debug(f"{name}: Home Assistant stop event - stopping hub") |
| 270 | + try: |
| 271 | + await hub.async_stop() |
| 272 | + except Exception as ex: |
| 273 | + _LOGGER.warning(f"{name}: error during Home Assistant stop: {ex}") |
| 274 | + |
| 275 | + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_hubs_on_homeassistant_stop) |
| 276 | + |
260 | 277 | # Register helper services to force-stop hubs |
261 | 278 | async def _svc_stop_all(call: Any) -> None: |
262 | 279 | """Force-stop all SolaX hubs (kills timers/tasks/sockets).""" |
@@ -1206,9 +1223,28 @@ def _track_task(self, coro: Any) -> asyncio.Task[Any]: |
1206 | 1223 | """Wrap coroutines in a Task we can cancel during stop.""" |
1207 | 1224 | task = asyncio.create_task(coro) |
1208 | 1225 | self._inflight_tasks.add(task) |
1209 | | - task.add_done_callback(lambda t: self._inflight_tasks.discard(t)) |
| 1226 | + task.add_done_callback(self._handle_tracked_task_done) |
1210 | 1227 | return task |
1211 | 1228 |
|
| 1229 | + def _handle_tracked_task_done(self, task: asyncio.Task[Any]) -> None: |
| 1230 | + """Collect finished in-flight task results during shutdown.""" |
| 1231 | + self._inflight_tasks.discard(task) |
| 1232 | + if not getattr(self, "_stopping", False): |
| 1233 | + return |
| 1234 | + try: |
| 1235 | + exc = task.exception() |
| 1236 | + except asyncio.CancelledError: |
| 1237 | + return |
| 1238 | + except Exception as ex: |
| 1239 | + _LOGGER.debug(f"{self._name}: failed to collect in-flight Modbus task result during shutdown: {ex}") |
| 1240 | + return |
| 1241 | + if exc is None: |
| 1242 | + return |
| 1243 | + if self._is_expected_shutdown_modbus_error(exc): |
| 1244 | + _LOGGER.debug(f"{self._name}: collected expected Modbus task cancellation during shutdown: {exc}") |
| 1245 | + return |
| 1246 | + _LOGGER.debug(f"{self._name}: in-flight Modbus task ended during shutdown: {exc}") |
| 1247 | + |
1212 | 1248 | def _is_expected_shutdown_modbus_error(self, ex: BaseException) -> bool: |
1213 | 1249 | """Return True for pymodbus cancellation errors caused by HA shutdown.""" |
1214 | 1250 | if not getattr(self, "_stopping", False): |
|
0 commit comments