From 3b3f4066c3c2cfb3450d3d4856ef9eb70e24e9ec Mon Sep 17 00:00:00 2001 From: Christian Lackas Date: Sat, 14 Feb 2026 12:23:16 +0100 Subject: [PATCH] Fix HomematicIP entity recovery after access point cloud reconnect (#162575) --- .../components/homematicip_cloud/hap.py | 5 +++ .../components/homematicip_cloud/test_hap.py | 40 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index d66594da390..304d5354b1b 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -161,6 +161,11 @@ class HomematicipHAP: _LOGGER.error("HMIP access point has lost connection with the cloud") self._ws_connection_closed.set() self.set_all_to_unavailable() + elif self._ws_connection_closed.is_set(): + _LOGGER.info("HMIP access point has reconnected to the cloud") + self._get_state_task = self.hass.async_create_task(self._try_get_state()) + self._get_state_task.add_done_callback(self.get_state_finished) + self._ws_connection_closed.clear() @callback def async_create_entity(self, *args, **kwargs) -> None: diff --git a/tests/components/homematicip_cloud/test_hap.py b/tests/components/homematicip_cloud/test_hap.py index 69078beafaf..f51351a549e 100644 --- a/tests/components/homematicip_cloud/test_hap.py +++ b/tests/components/homematicip_cloud/test_hap.py @@ -269,6 +269,46 @@ async def test_get_state_after_disconnect( mock_sleep.assert_awaited_with(2) +async def test_get_state_after_ap_reconnect( + hass: HomeAssistant, hmip_config_entry: MockConfigEntry, simple_mock_home +) -> None: + """Test state recovery after access point reconnects to cloud. + + When the access point loses its cloud connection, async_update sets all + devices to unavailable. When the access point reconnects (home.connected + becomes True), async_update should trigger a state refresh to restore + entity availability. + """ + hass.config.components.add(DOMAIN) + hap = HomematicipHAP(hass, hmip_config_entry) + assert hap + + simple_mock_home = MagicMock(spec=AsyncHome) + simple_mock_home.devices = [] + simple_mock_home.websocket_is_connected = Mock(return_value=True) + hap.home = simple_mock_home + + with patch.object(hap, "get_state") as mock_get_state: + # Initially not disconnected + assert not hap._ws_connection_closed.is_set() + + # Access point loses cloud connection + hap.home.connected = False + hap.async_update() + assert hap._ws_connection_closed.is_set() + mock_get_state.assert_not_called() + + # Access point reconnects to cloud + hap.home.connected = True + hap.async_update() + + # Let _try_get_state run + await hass.async_block_till_done() + mock_get_state.assert_called_once() + + assert not hap._ws_connection_closed.is_set() + + async def test_try_get_state_exponential_backoff() -> None: """Test _try_get_state waits for websocket connection."""