From 74288a3bc8a63061fa7a0c5ccbedd3e489052564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Diego=20Rodr=C3=ADguez=20Royo?= Date: Sun, 13 Jul 2025 22:46:42 +0200 Subject: [PATCH] Re-enable Home Connect updates automatically (#148657) Co-authored-by: Martin Hjelmare --- .../components/home_connect/coordinator.py | 46 ++++++------ .../components/home_connect/strings.json | 11 --- .../home_connect/test_coordinator.py | 74 +++++++++++-------- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/home_connect/coordinator.py b/homeassistant/components/home_connect/coordinator.py index bb419f6bd7c..81f785b55ae 100644 --- a/homeassistant/components/home_connect/coordinator.py +++ b/homeassistant/components/home_connect/coordinator.py @@ -38,7 +38,7 @@ from propcache.api import cached_property from homeassistant.config_entries import ConfigEntry from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr, issue_registry as ir +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( @@ -626,39 +626,37 @@ class HomeConnectCoordinator( """Check if the appliance data hasn't been refreshed too often recently.""" now = self.hass.loop.time() - if len(self._execution_tracker[appliance_ha_id]) >= MAX_EXECUTIONS: - return True + + execution_tracker = self._execution_tracker[appliance_ha_id] + initial_len = len(execution_tracker) execution_tracker = self._execution_tracker[appliance_ha_id] = [ timestamp - for timestamp in self._execution_tracker[appliance_ha_id] + for timestamp in execution_tracker if now - timestamp < MAX_EXECUTIONS_TIME_WINDOW ] execution_tracker.append(now) if len(execution_tracker) >= MAX_EXECUTIONS: - ir.async_create_issue( - self.hass, - DOMAIN, - f"home_connect_too_many_connected_paired_events_{appliance_ha_id}", - is_fixable=True, - is_persistent=True, - severity=ir.IssueSeverity.ERROR, - translation_key="home_connect_too_many_connected_paired_events", - data={ - "entry_id": self.config_entry.entry_id, - "appliance_ha_id": appliance_ha_id, - }, - translation_placeholders={ - "appliance_name": self.data[appliance_ha_id].info.name, - "times": str(MAX_EXECUTIONS), - "time_window": str(MAX_EXECUTIONS_TIME_WINDOW // 60), - "home_connect_resource_url": "https://www.home-connect.com/global/help-support/error-codes#/Togglebox=15362315-13320636-1/", - "home_assistant_core_issue_url": "https://github.com/home-assistant/core/issues/147299", - }, - ) + if initial_len < MAX_EXECUTIONS: + _LOGGER.warning( + 'Too many connected/paired events for appliance "%s" ' + "(%s times in less than %s minutes), updates have been disabled " + "and they will be enabled again whenever the connection stabilizes. " + "Consider trying to unplug the appliance " + "for a while to perform a soft reset", + self.data[appliance_ha_id].info.name, + MAX_EXECUTIONS, + MAX_EXECUTIONS_TIME_WINDOW // 60, + ) return True + if initial_len >= MAX_EXECUTIONS: + _LOGGER.info( + 'Connected/paired events from the appliance "%s" have stabilized,' + " updates have been re-enabled", + self.data[appliance_ha_id].info.name, + ) return False diff --git a/homeassistant/components/home_connect/strings.json b/homeassistant/components/home_connect/strings.json index e1c0b42ca0b..853d2bd2f8e 100644 --- a/homeassistant/components/home_connect/strings.json +++ b/homeassistant/components/home_connect/strings.json @@ -124,17 +124,6 @@ } }, "issues": { - "home_connect_too_many_connected_paired_events": { - "title": "{appliance_name} sent too many connected or paired events", - "fix_flow": { - "step": { - "confirm": { - "title": "[%key:component::home_connect::issues::home_connect_too_many_connected_paired_events::title%]", - "description": "The appliance \"{appliance_name}\" has been reported as connected or paired {times} times in less than {time_window} minutes, so refreshes on connected or paired events has been disabled to avoid exceeding the API rate limit.\n\nPlease refer to the [Home Connect Wi-Fi requirements and recommendations]({home_connect_resource_url}). If everything seems right with your network configuration, restart the appliance.\n\nClick \"submit\" to re-enable the updates.\nIf the issue persists, please see the following issue in the [Home Assistant core repository]({home_assistant_core_issue_url})." - } - } - } - }, "deprecated_time_alarm_clock_in_automations_scripts": { "title": "Deprecated alarm clock entity detected in some automations or scripts", "fix_flow": { diff --git a/tests/components/home_connect/test_coordinator.py b/tests/components/home_connect/test_coordinator.py index f9fed995b89..a368cfbef2d 100644 --- a/tests/components/home_connect/test_coordinator.py +++ b/tests/components/home_connect/test_coordinator.py @@ -2,7 +2,6 @@ from collections.abc import Awaitable, Callable from datetime import timedelta -from http import HTTPStatus from typing import Any, cast from unittest.mock import AsyncMock, MagicMock, patch @@ -53,16 +52,11 @@ from homeassistant.core import ( HomeAssistant, callback, ) -from homeassistant.helpers import ( - device_registry as dr, - entity_registry as er, - issue_registry as ir, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry, async_fire_time_changed -from tests.typing import ClientSessionGenerator INITIAL_FETCH_CLIENT_METHODS = [ "get_settings", @@ -580,8 +574,7 @@ async def test_paired_disconnected_devices_not_fetching( async def test_coordinator_disabling_updates_for_appliance( hass: HomeAssistant, - hass_client: ClientSessionGenerator, - issue_registry: ir.IssueRegistry, + freezer: FrozenDateTimeFactory, client: MagicMock, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], @@ -592,7 +585,6 @@ async def test_coordinator_disabling_updates_for_appliance( When the user confirms the issue the updates should be enabled again. """ appliance_ha_id = "SIEMENS-HCS02DWH1-6BE58C26DCC1" - issue_id = f"home_connect_too_many_connected_paired_events_{appliance_ha_id}" assert await integration_setup(client) assert config_entry.state is ConfigEntryState.LOADED @@ -606,13 +598,26 @@ async def test_coordinator_disabling_updates_for_appliance( EventType.CONNECTED, data=ArrayOfEvents([]), ) - for _ in range(8) + for _ in range(6) ] ) await hass.async_block_till_done() - issue = issue_registry.async_get_issue(DOMAIN, issue_id) - assert issue + freezer.tick(timedelta(minutes=10)) + await client.add_events( + [ + EventMessage( + appliance_ha_id, + EventType.CONNECTED, + data=ArrayOfEvents([]), + ) + for _ in range(2) + ] + ) + await hass.async_block_till_done() + + # At this point, the updates have been blocked because + # 6 + 2 connected events have been received in less than an hour get_settings_original_side_effect = client.get_settings.side_effect @@ -644,18 +649,36 @@ async def test_coordinator_disabling_updates_for_appliance( assert hass.states.is_state("switch.dishwasher_power", STATE_ON) - _client = await hass_client() - resp = await _client.post( - "/api/repairs/issues/fix", - json={"handler": DOMAIN, "issue_id": issue.issue_id}, + # After 55 minutes, the updates should be enabled again + # because one hour has passed since the first connect events, + # so there are 2 connected events in the execution_tracker + freezer.tick(timedelta(minutes=55)) + await client.add_events( + [ + EventMessage( + appliance_ha_id, + EventType.CONNECTED, + data=ArrayOfEvents([]), + ) + ] ) - assert resp.status == HTTPStatus.OK - flow_id = (await resp.json())["flow_id"] - resp = await _client.post(f"/api/repairs/issues/fix/{flow_id}") - assert resp.status == HTTPStatus.OK + await hass.async_block_till_done() - assert not issue_registry.async_get_issue(DOMAIN, issue_id) + assert hass.states.is_state("switch.dishwasher_power", STATE_OFF) + # If more connect events are sent, it should be blocked again + await client.add_events( + [ + EventMessage( + appliance_ha_id, + EventType.CONNECTED, + data=ArrayOfEvents([]), + ) + for _ in range(5) # 2 + 1 + 5 = 8 connect events in less than an hour + ] + ) + await hass.async_block_till_done() + client.get_settings = get_settings_original_side_effect await client.add_events( [ EventMessage( @@ -672,7 +695,6 @@ async def test_coordinator_disabling_updates_for_appliance( async def test_coordinator_disabling_updates_for_appliance_is_gone_after_entry_reload( hass: HomeAssistant, - issue_registry: ir.IssueRegistry, client: MagicMock, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], @@ -682,7 +704,6 @@ async def test_coordinator_disabling_updates_for_appliance_is_gone_after_entry_r The repair issue should also be deleted. """ appliance_ha_id = "SIEMENS-HCS02DWH1-6BE58C26DCC1" - issue_id = f"home_connect_too_many_connected_paired_events_{appliance_ha_id}" assert await integration_setup(client) assert config_entry.state is ConfigEntryState.LOADED @@ -701,14 +722,9 @@ async def test_coordinator_disabling_updates_for_appliance_is_gone_after_entry_r ) await hass.async_block_till_done() - issue = issue_registry.async_get_issue(DOMAIN, issue_id) - assert issue - await hass.config_entries.async_unload(config_entry.entry_id) await hass.async_block_till_done() - assert not issue_registry.async_get_issue(DOMAIN, issue_id) - assert await integration_setup(client) assert config_entry.state is ConfigEntryState.LOADED