diff --git a/homeassistant/components/teslemetry/__init__.py b/homeassistant/components/teslemetry/__init__.py index 7657e5a9c28b..9dc0d4d0f9d4 100644 --- a/homeassistant/components/teslemetry/__init__.py +++ b/homeassistant/components/teslemetry/__init__.py @@ -871,7 +871,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) - if energysites: entry.async_on_unload( stream.async_add_connection_listener( - create_handle_energy_stream_connection(energysites) + create_handle_energy_stream_connection(hass, energysites) ) ) @@ -907,6 +907,7 @@ def _setup_subentry_change_reload( def create_handle_energy_stream_connection( + hass: HomeAssistant, energysites: list[TeslemetryEnergyData], ) -> Callable[[bool], None]: """Create a stream connection listener for the energy coordinators.""" @@ -918,7 +919,8 @@ def create_handle_energy_stream_connection( Each subsequent streamed document restores its coordinator via async_set_updated_data, so no reload is required on reconnect. """ - if connected: + # Stopping cancels the listen task, which reports a disconnect. + if connected or hass.is_stopping: return error = UpdateFailed( translation_domain=DOMAIN, diff --git a/tests/components/teslemetry/test_init.py b/tests/components/teslemetry/test_init.py index 655c75a0e146..417e5c99ad98 100644 --- a/tests/components/teslemetry/test_init.py +++ b/tests/components/teslemetry/test_init.py @@ -2131,6 +2131,38 @@ async def test_energy_stream_unload_unsubscribes_and_closes_stream( mock_close.assert_called_once() +async def test_energy_stream_stop_does_not_fail_coordinators( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + mock_stream_listen: MagicMock, + mock_add_connection_listener: MagicMock, + mock_energy_totals_stream: MagicMock, +) -> None: + """Stopping Home Assistant does not fail the energy coordinators.""" + + async def listen() -> None: + # Like the library, report a disconnect when the listen task ends. + try: + await asyncio.Event().wait() + finally: + mock_add_connection_listener.send(False) + + mock_stream_listen.side_effect = listen + await setup_platform(hass, [Platform.SENSOR, Platform.CALENDAR]) + mock_energy_totals_stream.send() + await hass.async_block_till_done() + assert hass.states.get("sensor.energy_site_solar_power").state == "1.185" + assert hass.states.get("calendar.energy_site_buy_tariff").state != STATE_UNAVAILABLE + assert hass.states.get("sensor.energy_site_battery_discharged").state == "0.036" + + await hass.async_stop() + + assert hass.states.get("sensor.energy_site_solar_power").state == "1.185" + assert hass.states.get("calendar.energy_site_buy_tariff").state != STATE_UNAVAILABLE + assert hass.states.get("sensor.energy_site_battery_discharged").state == "0.036" + assert "Disconnected from the Teslemetry stream" not in caplog.text + + async def test_energy_stream_disconnect_marks_unavailable_and_recovers( hass: HomeAssistant, mock_add_connection_listener: MagicMock,