mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Re-enable Home Connect updates automatically (#148657)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
b2fe17c6d4
commit
74288a3bc8
@ -38,7 +38,7 @@ from propcache.api import cached_property
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
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 homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -626,39 +626,37 @@ class HomeConnectCoordinator(
|
|||||||
"""Check if the appliance data hasn't been refreshed too often recently."""
|
"""Check if the appliance data hasn't been refreshed too often recently."""
|
||||||
|
|
||||||
now = self.hass.loop.time()
|
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] = [
|
execution_tracker = self._execution_tracker[appliance_ha_id] = [
|
||||||
timestamp
|
timestamp
|
||||||
for timestamp in self._execution_tracker[appliance_ha_id]
|
for timestamp in execution_tracker
|
||||||
if now - timestamp < MAX_EXECUTIONS_TIME_WINDOW
|
if now - timestamp < MAX_EXECUTIONS_TIME_WINDOW
|
||||||
]
|
]
|
||||||
|
|
||||||
execution_tracker.append(now)
|
execution_tracker.append(now)
|
||||||
|
|
||||||
if len(execution_tracker) >= MAX_EXECUTIONS:
|
if len(execution_tracker) >= MAX_EXECUTIONS:
|
||||||
ir.async_create_issue(
|
if initial_len < MAX_EXECUTIONS:
|
||||||
self.hass,
|
_LOGGER.warning(
|
||||||
DOMAIN,
|
'Too many connected/paired events for appliance "%s" '
|
||||||
f"home_connect_too_many_connected_paired_events_{appliance_ha_id}",
|
"(%s times in less than %s minutes), updates have been disabled "
|
||||||
is_fixable=True,
|
"and they will be enabled again whenever the connection stabilizes. "
|
||||||
is_persistent=True,
|
"Consider trying to unplug the appliance "
|
||||||
severity=ir.IssueSeverity.ERROR,
|
"for a while to perform a soft reset",
|
||||||
translation_key="home_connect_too_many_connected_paired_events",
|
self.data[appliance_ha_id].info.name,
|
||||||
data={
|
MAX_EXECUTIONS,
|
||||||
"entry_id": self.config_entry.entry_id,
|
MAX_EXECUTIONS_TIME_WINDOW // 60,
|
||||||
"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",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return True
|
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
|
return False
|
||||||
|
|
||||||
|
@ -124,17 +124,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"issues": {
|
"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": {
|
"deprecated_time_alarm_clock_in_automations_scripts": {
|
||||||
"title": "Deprecated alarm clock entity detected in some automations or scripts",
|
"title": "Deprecated alarm clock entity detected in some automations or scripts",
|
||||||
"fix_flow": {
|
"fix_flow": {
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
@ -53,16 +52,11 @@ from homeassistant.core import (
|
|||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
callback,
|
callback,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
device_registry as dr,
|
|
||||||
entity_registry as er,
|
|
||||||
issue_registry as ir,
|
|
||||||
)
|
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
from tests.typing import ClientSessionGenerator
|
|
||||||
|
|
||||||
INITIAL_FETCH_CLIENT_METHODS = [
|
INITIAL_FETCH_CLIENT_METHODS = [
|
||||||
"get_settings",
|
"get_settings",
|
||||||
@ -580,8 +574,7 @@ async def test_paired_disconnected_devices_not_fetching(
|
|||||||
|
|
||||||
async def test_coordinator_disabling_updates_for_appliance(
|
async def test_coordinator_disabling_updates_for_appliance(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
freezer: FrozenDateTimeFactory,
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
client: MagicMock,
|
client: MagicMock,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
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.
|
When the user confirms the issue the updates should be enabled again.
|
||||||
"""
|
"""
|
||||||
appliance_ha_id = "SIEMENS-HCS02DWH1-6BE58C26DCC1"
|
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 await integration_setup(client)
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
@ -606,13 +598,26 @@ async def test_coordinator_disabling_updates_for_appliance(
|
|||||||
EventType.CONNECTED,
|
EventType.CONNECTED,
|
||||||
data=ArrayOfEvents([]),
|
data=ArrayOfEvents([]),
|
||||||
)
|
)
|
||||||
for _ in range(8)
|
for _ in range(6)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
issue = issue_registry.async_get_issue(DOMAIN, issue_id)
|
freezer.tick(timedelta(minutes=10))
|
||||||
assert issue
|
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
|
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)
|
assert hass.states.is_state("switch.dishwasher_power", STATE_ON)
|
||||||
|
|
||||||
_client = await hass_client()
|
# After 55 minutes, the updates should be enabled again
|
||||||
resp = await _client.post(
|
# because one hour has passed since the first connect events,
|
||||||
"/api/repairs/issues/fix",
|
# so there are 2 connected events in the execution_tracker
|
||||||
json={"handler": DOMAIN, "issue_id": issue.issue_id},
|
freezer.tick(timedelta(minutes=55))
|
||||||
|
await client.add_events(
|
||||||
|
[
|
||||||
|
EventMessage(
|
||||||
|
appliance_ha_id,
|
||||||
|
EventType.CONNECTED,
|
||||||
|
data=ArrayOfEvents([]),
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
assert resp.status == HTTPStatus.OK
|
await hass.async_block_till_done()
|
||||||
flow_id = (await resp.json())["flow_id"]
|
|
||||||
resp = await _client.post(f"/api/repairs/issues/fix/{flow_id}")
|
|
||||||
assert resp.status == HTTPStatus.OK
|
|
||||||
|
|
||||||
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(
|
await client.add_events(
|
||||||
[
|
[
|
||||||
EventMessage(
|
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(
|
async def test_coordinator_disabling_updates_for_appliance_is_gone_after_entry_reload(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
client: MagicMock,
|
client: MagicMock,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
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.
|
The repair issue should also be deleted.
|
||||||
"""
|
"""
|
||||||
appliance_ha_id = "SIEMENS-HCS02DWH1-6BE58C26DCC1"
|
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 await integration_setup(client)
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
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()
|
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.config_entries.async_unload(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert not issue_registry.async_get_issue(DOMAIN, issue_id)
|
|
||||||
|
|
||||||
assert await integration_setup(client)
|
assert await integration_setup(client)
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user