From ef7d68bfd6dcbdf6d797a73eeb65f0bc03828153 Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Fri, 19 Jul 2024 12:26:40 +0200 Subject: [PATCH 01/39] Fix reauth error and exception in ista EcoTrend integration (#121482) --- .../components/ista_ecotrend/coordinator.py | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/ista_ecotrend/coordinator.py b/homeassistant/components/ista_ecotrend/coordinator.py index 8d55574f0a1..0f14cd06fe3 100644 --- a/homeassistant/components/ista_ecotrend/coordinator.py +++ b/homeassistant/components/ista_ecotrend/coordinator.py @@ -8,6 +8,7 @@ from typing import Any from pyecotrend_ista import KeycloakError, LoginError, PyEcotrendIsta, ServerError +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EMAIL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed @@ -21,6 +22,8 @@ _LOGGER = logging.getLogger(__name__) class IstaCoordinator(DataUpdateCoordinator[dict[str, Any]]): """Ista EcoTrend data update coordinator.""" + config_entry: ConfigEntry + def __init__(self, hass: HomeAssistant, ista: PyEcotrendIsta) -> None: """Initialize ista EcoTrend data update coordinator.""" super().__init__( @@ -35,11 +38,14 @@ class IstaCoordinator(DataUpdateCoordinator[dict[str, Any]]): async def _async_update_data(self): """Fetch ista EcoTrend data.""" - if not self.details: - self.details = await self.async_get_details() - try: + await self.hass.async_add_executor_job(self.ista.login) + + if not self.details: + self.details = await self.async_get_details() + return await self.hass.async_add_executor_job(self.get_consumption_data) + except ServerError as e: raise UpdateFailed( "Unable to connect and retrieve data from ista EcoTrend, try again later" @@ -48,7 +54,9 @@ class IstaCoordinator(DataUpdateCoordinator[dict[str, Any]]): raise ConfigEntryAuthFailed( translation_domain=DOMAIN, translation_key="authentication_exception", - translation_placeholders={CONF_EMAIL: self.ista._email}, # noqa: SLF001 + translation_placeholders={ + CONF_EMAIL: self.config_entry.data[CONF_EMAIL] + }, ) from e def get_consumption_data(self) -> dict[str, Any]: @@ -61,26 +69,16 @@ class IstaCoordinator(DataUpdateCoordinator[dict[str, Any]]): async def async_get_details(self) -> dict[str, Any]: """Retrieve details of consumption units.""" - try: - result = await self.hass.async_add_executor_job( - self.ista.get_consumption_unit_details + + result = await self.hass.async_add_executor_job( + self.ista.get_consumption_unit_details + ) + + return { + consumption_unit: next( + details + for details in result["consumptionUnits"] + if details["id"] == consumption_unit ) - except ServerError as e: - raise UpdateFailed( - "Unable to connect and retrieve data from ista EcoTrend, try again later" - ) from e - except (LoginError, KeycloakError) as e: - raise ConfigEntryAuthFailed( - translation_domain=DOMAIN, - translation_key="authentication_exception", - translation_placeholders={CONF_EMAIL: self.ista._email}, # noqa: SLF001 - ) from e - else: - return { - consumption_unit: next( - details - for details in result["consumptionUnits"] - if details["id"] == consumption_unit - ) - for consumption_unit in self.ista.get_uuids() - } + for consumption_unit in self.ista.get_uuids() + } From 37f37f728783f192ac61f24e2cf1fca5d3e046d6 Mon Sep 17 00:00:00 2001 From: Jan Stienstra <65826735+j-stienstra@users.noreply.github.com> Date: Wed, 10 Jul 2024 23:54:02 +0200 Subject: [PATCH 02/39] Retain Jellyfin config flow input on connection issue (#121618) --- homeassistant/components/jellyfin/config_flow.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/jellyfin/config_flow.py b/homeassistant/components/jellyfin/config_flow.py index 4798a07b9cd..baecbcfb941 100644 --- a/homeassistant/components/jellyfin/config_flow.py +++ b/homeassistant/components/jellyfin/config_flow.py @@ -97,7 +97,11 @@ class JellyfinConfigFlow(ConfigFlow, domain=DOMAIN): ) return self.async_show_form( - step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + step_id="user", + data_schema=self.add_suggested_values_to_schema( + STEP_USER_DATA_SCHEMA, user_input + ), + errors=errors, ) async def async_step_reauth( From ec8e6398048882601f443a303b98b496d2033d8d Mon Sep 17 00:00:00 2001 From: Tomek Porozynski <36776636+ontaptom@users.noreply.github.com> Date: Wed, 10 Jul 2024 23:48:08 +0200 Subject: [PATCH 03/39] Update Supla async_set_cover_position to use "REVEAL_PARTIALLY" (#121663) --- homeassistant/components/supla/cover.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/supla/cover.py b/homeassistant/components/supla/cover.py index 4cdee04b149..37b64c375eb 100644 --- a/homeassistant/components/supla/cover.py +++ b/homeassistant/components/supla/cover.py @@ -71,7 +71,9 @@ class SuplaCoverEntity(SuplaEntity, CoverEntity): async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" - await self.async_action("REVEAL", percentage=kwargs.get(ATTR_POSITION)) + await self.async_action( + "REVEAL_PARTIALLY", percentage=kwargs.get(ATTR_POSITION) + ) @property def is_closed(self) -> bool | None: From 10cdf64f90a199ab1d7a1cf3e30cbfc804977915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Mind=C3=AAllo=20de=20Andrade?= Date: Wed, 10 Jul 2024 15:16:36 -0300 Subject: [PATCH 04/39] Bump sunweg 3.0.2 (#121684) --- homeassistant/components/sunweg/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sunweg/manifest.json b/homeassistant/components/sunweg/manifest.json index bcf1ad9dae2..998d3610735 100644 --- a/homeassistant/components/sunweg/manifest.json +++ b/homeassistant/components/sunweg/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/sunweg/", "iot_class": "cloud_polling", "loggers": ["sunweg"], - "requirements": ["sunweg==3.0.1"] + "requirements": ["sunweg==3.0.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 38f8b6a44cb..0a6550c8c43 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2662,7 +2662,7 @@ stringcase==1.2.0 subarulink==0.7.11 # homeassistant.components.sunweg -sunweg==3.0.1 +sunweg==3.0.2 # homeassistant.components.surepetcare surepy==0.9.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eb46f1e9c40..9f354c8b646 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2081,7 +2081,7 @@ stringcase==1.2.0 subarulink==0.7.11 # homeassistant.components.sunweg -sunweg==3.0.1 +sunweg==3.0.2 # homeassistant.components.surepetcare surepy==0.9.0 From ad5cbf0da67ea4c33ec7aa39aa7feecffdd6205c Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:46:39 +0200 Subject: [PATCH 05/39] Allow enigma2 devices to use different source bouquets (#121686) --- homeassistant/components/enigma2/__init__.py | 6 +++++- homeassistant/components/enigma2/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/enigma2/__init__.py b/homeassistant/components/enigma2/__init__.py index 4e4f8bdb687..de8283a5533 100644 --- a/homeassistant/components/enigma2/__init__.py +++ b/homeassistant/components/enigma2/__init__.py @@ -16,6 +16,8 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_create_clientsession +from .const import CONF_SOURCE_BOUQUET + type Enigma2ConfigEntry = ConfigEntry[OpenWebIfDevice] PLATFORMS = [Platform.MEDIA_PLAYER] @@ -35,7 +37,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: Enigma2ConfigEntry) -> b hass, verify_ssl=entry.data[CONF_VERIFY_SSL], base_url=base_url ) - entry.runtime_data = OpenWebIfDevice(session) + entry.runtime_data = OpenWebIfDevice( + session, source_bouquet=entry.options.get(CONF_SOURCE_BOUQUET) + ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/enigma2/manifest.json b/homeassistant/components/enigma2/manifest.json index ef08314e541..538cfb56388 100644 --- a/homeassistant/components/enigma2/manifest.json +++ b/homeassistant/components/enigma2/manifest.json @@ -7,5 +7,5 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["openwebif"], - "requirements": ["openwebifpy==4.2.4"] + "requirements": ["openwebifpy==4.2.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0a6550c8c43..3be48c03ae9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1492,7 +1492,7 @@ openhomedevice==2.2.0 opensensemap-api==0.2.0 # homeassistant.components.enigma2 -openwebifpy==4.2.4 +openwebifpy==4.2.5 # homeassistant.components.luci openwrt-luci-rpc==1.1.17 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f354c8b646..d9c49acb59b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1207,7 +1207,7 @@ openerz-api==0.3.0 openhomedevice==2.2.0 # homeassistant.components.enigma2 -openwebifpy==4.2.4 +openwebifpy==4.2.5 # homeassistant.components.opower opower==0.4.7 From 269fb235279bc681d12bc06c798834c036cc19cb Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:10:47 +0100 Subject: [PATCH 06/39] Fix tplink bug changing color temp on bulbs with light effects (#121696) --- homeassistant/components/tplink/light.py | 4 ++-- tests/components/tplink/test_light.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 22e7c523d1a..9b7dd499c97 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -392,11 +392,11 @@ class TPLinkLightEffectEntity(TPLinkLightEntity): kwargs[ATTR_EFFECT], brightness=brightness, transition=transition ) elif ATTR_COLOR_TEMP_KELVIN in kwargs: - if self.effect: + if self.effect and self.effect != EFFECT_OFF: # If there is an effect in progress # we have to clear the effect # before we can set a color temp - await self._light_module.set_hsv(0, 0, brightness) + await self._effect_module.set_effect(LightEffect.LIGHT_EFFECTS_OFF) await self._async_set_color_temp( kwargs[ATTR_COLOR_TEMP_KELVIN], brightness, transition ) diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index bb814d1f5d3..590274b8405 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -533,16 +533,16 @@ async def test_smart_strip_effects(hass: HomeAssistant) -> None: assert state.attributes[ATTR_EFFECT_LIST] == ["Off", "Effect1", "Effect2"] # Ensure setting color temp when an effect - # is in progress calls set_hsv to clear the effect + # is in progress calls set_effect to clear the effect await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP_KELVIN: 4000}, blocking=True, ) - light.set_hsv.assert_called_once_with(0, 0, None) + light_effect.set_effect.assert_called_once_with(LightEffect.LIGHT_EFFECTS_OFF) light.set_color_temp.assert_called_once_with(4000, brightness=None, transition=None) - light.set_hsv.reset_mock() + light_effect.set_effect.reset_mock() light.set_color_temp.reset_mock() await hass.services.async_call( From 98df46f3ea9324476ba132fc903ed88247b084b7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 10 Jul 2024 21:53:11 +0200 Subject: [PATCH 07/39] Bump knocki to 0.3.0 (#121704) --- homeassistant/components/knocki/config_flow.py | 4 +++- homeassistant/components/knocki/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/knocki/test_config_flow.py | 8 ++++++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/knocki/config_flow.py b/homeassistant/components/knocki/config_flow.py index 724c65f83df..654dd4a4d1f 100644 --- a/homeassistant/components/knocki/config_flow.py +++ b/homeassistant/components/knocki/config_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import Any -from knocki import KnockiClient, KnockiConnectionError +from knocki import KnockiClient, KnockiConnectionError, KnockiInvalidAuthError import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult @@ -45,6 +45,8 @@ class KnockiConfigFlow(ConfigFlow, domain=DOMAIN): raise except KnockiConnectionError: errors["base"] = "cannot_connect" + except KnockiInvalidAuthError: + errors["base"] = "invalid_auth" except Exception: # noqa: BLE001 LOGGER.exception("Error logging into the Knocki API") errors["base"] = "unknown" diff --git a/homeassistant/components/knocki/manifest.json b/homeassistant/components/knocki/manifest.json index e78e9856d62..cad3e156c6c 100644 --- a/homeassistant/components/knocki/manifest.json +++ b/homeassistant/components/knocki/manifest.json @@ -7,5 +7,5 @@ "integration_type": "device", "iot_class": "cloud_push", "loggers": ["knocki"], - "requirements": ["knocki==0.2.0"] + "requirements": ["knocki==0.3.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3be48c03ae9..0919effd0bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1203,7 +1203,7 @@ kegtron-ble==0.4.0 kiwiki-client==0.1.1 # homeassistant.components.knocki -knocki==0.2.0 +knocki==0.3.0 # homeassistant.components.knx knx-frontend==2024.1.20.105944 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d9c49acb59b..51bc970af1f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -981,7 +981,7 @@ justnimbus==0.7.4 kegtron-ble==0.4.0 # homeassistant.components.knocki -knocki==0.2.0 +knocki==0.3.0 # homeassistant.components.knx knx-frontend==2024.1.20.105944 diff --git a/tests/components/knocki/test_config_flow.py b/tests/components/knocki/test_config_flow.py index baf43c3ad30..188175035da 100644 --- a/tests/components/knocki/test_config_flow.py +++ b/tests/components/knocki/test_config_flow.py @@ -2,7 +2,7 @@ from unittest.mock import AsyncMock -from knocki import KnockiConnectionError +from knocki import KnockiConnectionError, KnockiInvalidAuthError import pytest from homeassistant.components.knocki.const import DOMAIN @@ -72,7 +72,11 @@ async def test_duplcate_entry( @pytest.mark.parametrize(("field"), ["login", "link"]) @pytest.mark.parametrize( ("exception", "error"), - [(KnockiConnectionError, "cannot_connect"), (Exception, "unknown")], + [ + (KnockiConnectionError, "cannot_connect"), + (KnockiInvalidAuthError, "invalid_auth"), + (Exception, "unknown"), + ], ) async def test_exceptions( hass: HomeAssistant, From 372649069e861f2cea781ad3099e974ae20d8e9a Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Wed, 10 Jul 2024 23:08:25 +0200 Subject: [PATCH 08/39] Bump pyloadapi to v1.3.2 (#121709) --- .../components/pyload/coordinator.py | 2 +- homeassistant/components/pyload/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/pyload/test_sensor.py | 22 +++++++++++++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/pyload/coordinator.py b/homeassistant/components/pyload/coordinator.py index c55ca4c1630..7eadefcd260 100644 --- a/homeassistant/components/pyload/coordinator.py +++ b/homeassistant/components/pyload/coordinator.py @@ -30,7 +30,7 @@ class PyLoadData: speed: float download: bool reconnect: bool - captcha: bool + captcha: bool | None = None free_space: int diff --git a/homeassistant/components/pyload/manifest.json b/homeassistant/components/pyload/manifest.json index fe1888478f8..788cdd1eb05 100644 --- a/homeassistant/components/pyload/manifest.json +++ b/homeassistant/components/pyload/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_polling", "loggers": ["pyloadapi"], "quality_scale": "platinum", - "requirements": ["PyLoadAPI==1.2.0"] + "requirements": ["PyLoadAPI==1.3.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0919effd0bc..ec8dd306c4a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -60,7 +60,7 @@ PyFlume==0.6.5 PyFronius==0.7.3 # homeassistant.components.pyload -PyLoadAPI==1.2.0 +PyLoadAPI==1.3.2 # homeassistant.components.mvglive PyMVGLive==1.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 51bc970af1f..4873cddfc04 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -51,7 +51,7 @@ PyFlume==0.6.5 PyFronius==0.7.3 # homeassistant.components.pyload -PyLoadAPI==1.2.0 +PyLoadAPI==1.3.2 # homeassistant.components.met_eireann PyMetEireann==2021.8.0 diff --git a/tests/components/pyload/test_sensor.py b/tests/components/pyload/test_sensor.py index a44c9c8bf91..3e18faca12b 100644 --- a/tests/components/pyload/test_sensor.py +++ b/tests/components/pyload/test_sensor.py @@ -157,3 +157,25 @@ async def test_deprecated_yaml( assert issue_registry.async_get_issue( domain=HOMEASSISTANT_DOMAIN, issue_id=f"deprecated_yaml_{DOMAIN}" ) + + +async def test_pyload_pre_0_5_0( + hass: HomeAssistant, + config_entry: MockConfigEntry, + mock_pyloadapi: AsyncMock, +) -> None: + """Test setup of the pyload sensor platform.""" + mock_pyloadapi.get_status.return_value = { + "pause": False, + "active": 1, + "queue": 6, + "total": 37, + "speed": 5405963.0, + "download": True, + "reconnect": False, + } + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED From 4ab180f01644ab88ac5d1e1cbf9102043e40a338 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Jul 2024 14:06:58 -0700 Subject: [PATCH 09/39] Fix update happening too early in unifiprotect (#121714) --- homeassistant/components/unifiprotect/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 7eceb861955..97325135de5 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -189,7 +189,6 @@ class BaseProtectEntity(Entity): self._async_get_ufp_enabled = description.get_ufp_enabled self._async_set_device_info() - self._async_update_device_from_protect(device) self._state_getters = tuple( partial(attrgetter(attr), self) for attr in self._state_attrs ) @@ -264,6 +263,7 @@ class BaseProtectEntity(Entity): self.async_on_remove( self.data.async_subscribe(self.device.mac, self._async_updated_event) ) + self._async_update_device_from_protect(self.device) class ProtectDeviceEntity(BaseProtectEntity): From ebe7bc06866f6605c8e491ddb8ff130c7e98ddcc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 10 Jul 2024 23:22:03 +0200 Subject: [PATCH 10/39] Bump knocki to 0.3.1 (#121717) --- homeassistant/components/knocki/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knocki/manifest.json b/homeassistant/components/knocki/manifest.json index cad3e156c6c..f35827b8213 100644 --- a/homeassistant/components/knocki/manifest.json +++ b/homeassistant/components/knocki/manifest.json @@ -7,5 +7,5 @@ "integration_type": "device", "iot_class": "cloud_push", "loggers": ["knocki"], - "requirements": ["knocki==0.3.0"] + "requirements": ["knocki==0.3.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index ec8dd306c4a..bfa675fed76 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1203,7 +1203,7 @@ kegtron-ble==0.4.0 kiwiki-client==0.1.1 # homeassistant.components.knocki -knocki==0.3.0 +knocki==0.3.1 # homeassistant.components.knx knx-frontend==2024.1.20.105944 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4873cddfc04..09b458b53e3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -981,7 +981,7 @@ justnimbus==0.7.4 kegtron-ble==0.4.0 # homeassistant.components.knocki -knocki==0.3.0 +knocki==0.3.1 # homeassistant.components.knx knx-frontend==2024.1.20.105944 From 85952421425ff84c8893c9e988fc7ecaa0c54247 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 10 Jul 2024 23:53:11 +0200 Subject: [PATCH 11/39] Fix bad access to UniFi runtime_data when not assigned (#121725) * Fix bad access to runtime_data when not assigned * Fix review comment * Clean up if statements --- homeassistant/components/unifi/config_flow.py | 13 +++---- tests/components/unifi/test_config_flow.py | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index e93b59b0673..b5ad1ea2ff0 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -164,13 +164,12 @@ class UnifiFlowHandler(ConfigFlow, domain=UNIFI_DOMAIN): config_entry = self.reauth_config_entry abort_reason = "reauth_successful" - if ( - config_entry is not None - and config_entry.state is not ConfigEntryState.NOT_LOADED - ): - hub = config_entry.runtime_data - - if hub and hub.available: + if config_entry: + if ( + config_entry.state is ConfigEntryState.LOADED + and (hub := config_entry.runtime_data) + and hub.available + ): return self.async_abort(reason="already_configured") return self.async_update_reload_and_abort( diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 7b37437cd1d..9ae3af19b46 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -1,5 +1,6 @@ """Test UniFi Network config flow.""" +from collections.abc import Callable import socket from unittest.mock import PropertyMock, patch @@ -338,6 +339,44 @@ async def test_reauth_flow_update_configuration( assert config_entry.data[CONF_PASSWORD] == "new_pass" +async def test_reauth_flow_update_configuration_on_not_loaded_entry( + hass: HomeAssistant, config_entry_factory: Callable[[], ConfigEntry] +) -> None: + """Verify reauth flow can update hub configuration on a not loaded entry.""" + with patch("aiounifi.Controller.login", side_effect=aiounifi.errors.RequestError): + config_entry = await config_entry_factory() + + result = await hass.config_entries.flow.async_init( + UNIFI_DOMAIN, + context={ + "source": SOURCE_REAUTH, + "unique_id": config_entry.unique_id, + "entry_id": config_entry.entry_id, + }, + data=config_entry.data, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "new_name", + CONF_PASSWORD: "new_pass", + CONF_PORT: 1234, + CONF_VERIFY_SSL: True, + }, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reauth_successful" + assert config_entry.data[CONF_HOST] == "1.2.3.4" + assert config_entry.data[CONF_USERNAME] == "new_name" + assert config_entry.data[CONF_PASSWORD] == "new_pass" + + @pytest.mark.parametrize("client_payload", [CLIENTS]) @pytest.mark.parametrize("device_payload", [DEVICES]) @pytest.mark.parametrize("wlan_payload", [WLANS]) From 3d8afe7cb87a8e8f1ca91220155f68e4245f2ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Thu, 11 Jul 2024 08:07:18 +0100 Subject: [PATCH 12/39] Update Idasen Desk library to 2.6.2 (#121729) --- homeassistant/components/idasen_desk/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/idasen_desk/manifest.json b/homeassistant/components/idasen_desk/manifest.json index a09d155b5b0..17a5f519274 100644 --- a/homeassistant/components/idasen_desk/manifest.json +++ b/homeassistant/components/idasen_desk/manifest.json @@ -12,5 +12,5 @@ "documentation": "https://www.home-assistant.io/integrations/idasen_desk", "iot_class": "local_push", "quality_scale": "silver", - "requirements": ["idasen-ha==2.6.1"] + "requirements": ["idasen-ha==2.6.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index bfa675fed76..e9aec3fd756 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1137,7 +1137,7 @@ ical==8.1.1 icmplib==3.0 # homeassistant.components.idasen_desk -idasen-ha==2.6.1 +idasen-ha==2.6.2 # homeassistant.components.network ifaddr==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 09b458b53e3..f02183553a6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -933,7 +933,7 @@ ical==8.1.1 icmplib==3.0 # homeassistant.components.idasen_desk -idasen-ha==2.6.1 +idasen-ha==2.6.2 # homeassistant.components.network ifaddr==0.2.0 From 68841b3d8a46afe85396adc5e40d84fdb1d82274 Mon Sep 17 00:00:00 2001 From: tronikos Date: Thu, 11 Jul 2024 01:14:11 -0700 Subject: [PATCH 13/39] Bump opower to 0.5.2 to fix 403 forbidden errors for users with multiple accounts (#121762) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index d419fdcb043..28c2e8ba2a8 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", "loggers": ["opower"], - "requirements": ["opower==0.4.7"] + "requirements": ["opower==0.5.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index e9aec3fd756..28b1f2f67b6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1501,7 +1501,7 @@ openwrt-luci-rpc==1.1.17 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.4.7 +opower==0.5.2 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f02183553a6..ef01832108b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1210,7 +1210,7 @@ openhomedevice==2.2.0 openwebifpy==4.2.5 # homeassistant.components.opower -opower==0.4.7 +opower==0.5.2 # homeassistant.components.oralb oralb-ble==0.17.6 From 3b8e736fe39e0a720fa694c1b9ea00e60ae46d1c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 11 Jul 2024 08:23:10 -0700 Subject: [PATCH 14/39] Pin mashumaro version >= 3.13.1 for python 3.12.4 compatibility. (#121782) Pin mashumaro version for python 3.12.4 compatibility. --- homeassistant/package_constraints.txt | 3 +++ script/gen_requirements_all.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 6b43f288762..fcf79258c25 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -136,6 +136,9 @@ backoff>=2.0 # v2 has breaking changes (#99218). pydantic==1.10.17 +# Required for Python 3.12.4 compatibility (#119223). +mashumaro>=3.13.1 + # Breaks asyncio # https://github.com/pubnub/python/issues/130 pubnub!=6.4.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 434b4d0071f..3c593a2bdf7 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -157,6 +157,9 @@ backoff>=2.0 # v2 has breaking changes (#99218). pydantic==1.10.17 +# Required for Python 3.12.4 compatibility (#119223). +mashumaro>=3.13.1 + # Breaks asyncio # https://github.com/pubnub/python/issues/130 pubnub!=6.4.0 From 6aaaba6419949b4706283c22de17beb6221c2274 Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:22:10 +0200 Subject: [PATCH 15/39] Bump pytedee_async to 0.2.20 (#121783) --- homeassistant/components/tedee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tedee/manifest.json b/homeassistant/components/tedee/manifest.json index 24df4cff95c..4f071267a25 100644 --- a/homeassistant/components/tedee/manifest.json +++ b/homeassistant/components/tedee/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["pytedee_async"], "quality_scale": "platinum", - "requirements": ["pytedee-async==0.2.17"] + "requirements": ["pytedee-async==0.2.20"] } diff --git a/requirements_all.txt b/requirements_all.txt index 28b1f2f67b6..1d0d96bbbd7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2209,7 +2209,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.17 +pytedee-async==0.2.20 # homeassistant.components.tfiac pytfiac==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef01832108b..5a831e5cd59 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1742,7 +1742,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.17 +pytedee-async==0.2.20 # homeassistant.components.motionmount python-MotionMount==2.0.0 From 63b14d14c17e5a1c195f72f30ad0c772ab6500bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Jul 2024 10:18:45 -0500 Subject: [PATCH 16/39] Add some missing tplink ouis (#121785) --- homeassistant/components/tplink/manifest.json | 8 ++++++-- homeassistant/generated/dhcp.py | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index 3786a2565c2..337e05726ac 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -181,7 +181,7 @@ "macaddress": "1C61B4*" }, { - "hostname": "l5*", + "hostname": "l[59]*", "macaddress": "5CE931*" }, { @@ -189,9 +189,13 @@ "macaddress": "3C52A1*" }, { - "hostname": "l5*", + "hostname": "l[59]*", "macaddress": "5C628B*" }, + { + "hostname": "l[59]*", + "macaddress": "14EBB6*" + }, { "hostname": "tp*", "macaddress": "5C628B*" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index e898f64d128..f6df799d01e 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -827,7 +827,7 @@ DHCP: Final[list[dict[str, str | bool]]] = [ }, { "domain": "tplink", - "hostname": "l5*", + "hostname": "l[59]*", "macaddress": "5CE931*", }, { @@ -837,9 +837,14 @@ DHCP: Final[list[dict[str, str | bool]]] = [ }, { "domain": "tplink", - "hostname": "l5*", + "hostname": "l[59]*", "macaddress": "5C628B*", }, + { + "domain": "tplink", + "hostname": "l[59]*", + "macaddress": "14EBB6*", + }, { "domain": "tplink", "hostname": "tp*", From 1e6c96c6eb19a121e5a8012059ea413f9ef50bc8 Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Thu, 11 Jul 2024 17:14:22 -0400 Subject: [PATCH 17/39] Use async_connect in newly bumped 0.5.8 UPB library (#121789) --- homeassistant/components/upb/__init__.py | 2 +- homeassistant/components/upb/config_flow.py | 2 +- homeassistant/components/upb/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/upb/test_config_flow.py | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index f2db6ff1b3c..2e5a69393d4 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -26,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b file = config_entry.data[CONF_FILE_PATH] upb = upb_lib.UpbPim({"url": url, "UPStartExportFile": file}) - upb.connect() + await upb.async_connect() hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = {"upb": upb} diff --git a/homeassistant/components/upb/config_flow.py b/homeassistant/components/upb/config_flow.py index 40f49e57c60..fec93a51202 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -40,7 +40,7 @@ async def _validate_input(data): upb = upb_lib.UpbPim({"url": url, "UPStartExportFile": file_path}) - upb.connect(_connected_callback) + await upb.async_connect(_connected_callback) if not upb.config_ok: _LOGGER.error("Missing or invalid UPB file: %s", file_path) diff --git a/homeassistant/components/upb/manifest.json b/homeassistant/components/upb/manifest.json index b208edbc0e5..6b49c859771 100644 --- a/homeassistant/components/upb/manifest.json +++ b/homeassistant/components/upb/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/upb", "iot_class": "local_push", "loggers": ["upb_lib"], - "requirements": ["upb-lib==0.5.7"] + "requirements": ["upb-lib==0.5.8"] } diff --git a/requirements_all.txt b/requirements_all.txt index 1d0d96bbbd7..453accf6064 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2807,7 +2807,7 @@ unifiled==0.11 universal-silabs-flasher==0.0.20 # homeassistant.components.upb -upb-lib==0.5.7 +upb-lib==0.5.8 # homeassistant.components.upcloud upcloud-api==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a831e5cd59..7f0fdcf52c0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2181,7 +2181,7 @@ unifi-discovery==1.2.0 universal-silabs-flasher==0.0.20 # homeassistant.components.upb -upb-lib==0.5.7 +upb-lib==0.5.8 # homeassistant.components.upcloud upcloud-api==2.5.1 diff --git a/tests/components/upb/test_config_flow.py b/tests/components/upb/test_config_flow.py index d5d6d70bb68..54aeb00e89a 100644 --- a/tests/components/upb/test_config_flow.py +++ b/tests/components/upb/test_config_flow.py @@ -1,7 +1,7 @@ """Test the UPB Control config flow.""" from asyncio import TimeoutError -from unittest.mock import MagicMock, PropertyMock, patch +from unittest.mock import AsyncMock, PropertyMock, patch from homeassistant import config_entries from homeassistant.components.upb.const import DOMAIN @@ -15,11 +15,11 @@ def mocked_upb(sync_complete=True, config_ok=True): def _upb_lib_connect(callback): callback() - upb_mock = MagicMock() + upb_mock = AsyncMock() type(upb_mock).network_id = PropertyMock(return_value="42") type(upb_mock).config_ok = PropertyMock(return_value=config_ok) if sync_complete: - upb_mock.connect.side_effect = _upb_lib_connect + upb_mock.async_connect.side_effect = _upb_lib_connect return patch( "homeassistant.components.upb.config_flow.upb_lib.UpbPim", return_value=upb_mock ) From 0f69c58ba9349f726e8266b47104648435fe5f01 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:19:31 +0100 Subject: [PATCH 18/39] Bump python-kasa to 0.7.0.4 (#121791) --- homeassistant/components/tplink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index 337e05726ac..a345f64e4b2 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -301,5 +301,5 @@ "iot_class": "local_polling", "loggers": ["kasa"], "quality_scale": "platinum", - "requirements": ["python-kasa[speedups]==0.7.0.3"] + "requirements": ["python-kasa[speedups]==0.7.0.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index 453accf6064..5da52fbb075 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2275,7 +2275,7 @@ python-join-api==0.0.9 python-juicenet==1.1.0 # homeassistant.components.tplink -python-kasa[speedups]==0.7.0.3 +python-kasa[speedups]==0.7.0.4 # homeassistant.components.lirc # python-lirc==1.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7f0fdcf52c0..23d8fd9adda 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1775,7 +1775,7 @@ python-izone==1.2.9 python-juicenet==1.1.0 # homeassistant.components.tplink -python-kasa[speedups]==0.7.0.3 +python-kasa[speedups]==0.7.0.4 # homeassistant.components.matter python-matter-server==6.2.2 From 976902f22ce0d52969a53f07d36115252fb71230 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 12 Jul 2024 15:51:18 +0200 Subject: [PATCH 19/39] Add missing translations to Roborock (#121796) --- homeassistant/components/roborock/strings.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index c7fc34386fd..5d95812e845 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -214,7 +214,8 @@ "unknown": "Unknown", "locked": "Locked", "air_drying_stopping": "Air drying stopping", - "egg_attack": "Cupid mode" + "egg_attack": "Cupid mode", + "mapping": "Mapping" } }, "total_cleaning_time": { @@ -282,7 +283,8 @@ "deep": "Deep", "deep_plus": "Deep+", "custom": "Custom", - "fast": "Fast" + "fast": "Fast", + "smart_mode": "SmartPlan" } }, "mop_intensity": { @@ -293,10 +295,12 @@ "mild": "Mild", "medium": "Medium", "moderate": "Moderate", + "max": "Max", "high": "High", "intense": "Intense", "custom": "[%key:component::roborock::entity::select::mop_mode::state::custom%]", - "custom_water_flow": "Custom water flow" + "custom_water_flow": "Custom water flow", + "smart_mode": "[%key:component::roborock::entity::select::mop_mode::state::smart_mode%]" } } }, @@ -338,13 +342,14 @@ "custom": "[%key:component::roborock::entity::select::mop_mode::state::custom%]", "gentle": "Gentle", "off": "[%key:common::state::off%]", - "max": "Max", + "max": "[%key:component::roborock::entity::select::mop_intensity::state::max%]", "max_plus": "Max plus", "medium": "Medium", "quiet": "Quiet", "silent": "Silent", "standard": "[%key:component::roborock::entity::select::mop_mode::state::standard%]", - "turbo": "Turbo" + "turbo": "Turbo", + "smart_mode": "[%key:component::roborock::entity::select::mop_mode::state::smart_mode%]" } } } From e0b90c4b36bc7672126b37d990482da35be438e4 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 13 Jul 2024 16:10:09 +0200 Subject: [PATCH 20/39] Fix alexa does to check `current_position` correctly when handling cover range changes (#121798) --- homeassistant/components/alexa/handlers.py | 2 +- tests/components/alexa/test_smart_home.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index 47e09db1166..1849dcd1862 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -1497,7 +1497,7 @@ async def async_api_adjust_range( if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": range_delta = int(range_delta * 20) if range_delta_default else int(range_delta) service = SERVICE_SET_COVER_POSITION - if not (current := entity.attributes.get(cover.ATTR_POSITION)): + if not (current := entity.attributes.get(cover.ATTR_CURRENT_POSITION)): msg = f"Unable to determine {entity.entity_id} current position" raise AlexaInvalidValueError(msg) position = response_value = min(100, max(0, range_delta + current)) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index d502dce7d01..fb27c91eea7 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1979,7 +1979,7 @@ async def test_cover_position( "friendly_name": "Test cover range", "device_class": "blind", "supported_features": supported_features, - "position": position, + "current_position": position, }, ) appliance = await discovery_test(device, hass) @@ -2296,7 +2296,7 @@ async def test_cover_position_range( "friendly_name": "Test cover range", "device_class": "blind", "supported_features": 7, - "position": 30, + "current_position": 30, }, ) appliance = await discovery_test(device, hass) @@ -4658,7 +4658,7 @@ async def test_cover_semantics_position_and_tilt(hass: HomeAssistant) -> None: "friendly_name": "Test cover semantics", "device_class": "blind", "supported_features": 255, - "position": 30, + "current_position": 30, "tilt_position": 30, }, ) From 56a9167ed28a1dc4c62772ad3d30020efcbc59df Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 12 Jul 2024 09:13:55 +0200 Subject: [PATCH 21/39] Reolink media second lens (#121800) DUO lens camera distinguish between lenses for media playback --- homeassistant/components/reolink/media_source.py | 4 ++++ tests/components/reolink/test_media_source.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/reolink/media_source.py b/homeassistant/components/reolink/media_source.py index 7a77e482f56..ae865b77913 100644 --- a/homeassistant/components/reolink/media_source.py +++ b/homeassistant/components/reolink/media_source.py @@ -5,6 +5,7 @@ from __future__ import annotations import datetime as dt import logging +from reolink_aio.api import DUAL_LENS_MODELS from reolink_aio.enums import VodRequestType from homeassistant.components.camera import DOMAIN as CAM_DOMAIN, DynamicStreamSettings @@ -184,6 +185,9 @@ class ReolinkVODMediaSource(MediaSource): if device.name_by_user is not None: device_name = device.name_by_user + if host.api.model in DUAL_LENS_MODELS: + device_name = f"{device_name} lens {ch}" + children.append( BrowseMediaSource( domain=DOMAIN, diff --git a/tests/components/reolink/test_media_source.py b/tests/components/reolink/test_media_source.py index 0d86106e8e5..66ed32ca823 100644 --- a/tests/components/reolink/test_media_source.py +++ b/tests/components/reolink/test_media_source.py @@ -54,6 +54,7 @@ TEST_FILE_NAME = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00" TEST_FILE_NAME_MP4 = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00.mp4" TEST_STREAM = "main" TEST_CHANNEL = "0" +TEST_CAM_NAME = "Cam new name" TEST_MIME_TYPE = "application/x-mpegURL" TEST_MIME_TYPE_MP4 = "video/mp4" @@ -130,6 +131,7 @@ async def test_browsing( """Test browsing the Reolink three.""" entry_id = config_entry.entry_id reolink_connect.api_version.return_value = 1 + reolink_connect.model = "Reolink TrackMix PoE" with patch("homeassistant.components.reolink.PLATFORMS", [Platform.CAMERA]): assert await hass.config_entries.async_setup(entry_id) is True @@ -137,7 +139,7 @@ async def test_browsing( entries = dr.async_entries_for_config_entry(device_registry, entry_id) assert len(entries) > 0 - device_registry.async_update_device(entries[0].id, name_by_user="Cam new name") + device_registry.async_update_device(entries[0].id, name_by_user=TEST_CAM_NAME) caplog.set_level(logging.DEBUG) @@ -149,6 +151,7 @@ async def test_browsing( assert browse.title == "Reolink" assert browse.identifier is None assert browse.children[0].identifier == browse_root_id + assert browse.children[0].title == f"{TEST_CAM_NAME} lens 0" # browse resolution select browse = await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/{browse_root_id}") From e9344ae101a8abd413e052e157f6b687c4a2333f Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Thu, 11 Jul 2024 23:12:33 +0200 Subject: [PATCH 22/39] Bump PySwitchbot to 0.48.1 (#121804) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index dc858a688cb..0cbbd70a805 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -39,5 +39,5 @@ "documentation": "https://www.home-assistant.io/integrations/switchbot", "iot_class": "local_push", "loggers": ["switchbot"], - "requirements": ["PySwitchbot==0.48.0"] + "requirements": ["PySwitchbot==0.48.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5da52fbb075..95dfb023ab0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -90,7 +90,7 @@ PyQRCode==1.2.1 PyRMVtransport==0.3.3 # homeassistant.components.switchbot -PySwitchbot==0.48.0 +PySwitchbot==0.48.1 # homeassistant.components.switchmate PySwitchmate==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 23d8fd9adda..32d9722af73 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -78,7 +78,7 @@ PyQRCode==1.2.1 PyRMVtransport==0.3.3 # homeassistant.components.switchbot -PySwitchbot==0.48.0 +PySwitchbot==0.48.1 # homeassistant.components.syncthru PySyncThru==0.7.10 From 41104324ecf125cdea7f1488a7c1338039b8076f Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Fri, 5 Jul 2024 13:26:44 +1000 Subject: [PATCH 23/39] Bump aiolifx to 1.0.4 (#121267) --- CODEOWNERS | 2 ++ homeassistant/components/lifx/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 765f1624c33..560f2efc0cc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -777,6 +777,8 @@ build.json @home-assistant/supervisor /tests/components/lg_netcast/ @Drafteed @splinter98 /homeassistant/components/lidarr/ @tkdrob /tests/components/lidarr/ @tkdrob +/homeassistant/components/lifx/ @Djelibeybi +/tests/components/lifx/ @Djelibeybi /homeassistant/components/light/ @home-assistant/core /tests/components/light/ @home-assistant/core /homeassistant/components/linear_garage_door/ @IceBotYT diff --git a/homeassistant/components/lifx/manifest.json b/homeassistant/components/lifx/manifest.json index 6aa7fdc6305..5e68c1bab35 100644 --- a/homeassistant/components/lifx/manifest.json +++ b/homeassistant/components/lifx/manifest.json @@ -1,7 +1,7 @@ { "domain": "lifx", "name": "LIFX", - "codeowners": [], + "codeowners": ["@Djelibeybi"], "config_flow": true, "dependencies": ["network"], "dhcp": [ @@ -48,7 +48,7 @@ "iot_class": "local_polling", "loggers": ["aiolifx", "aiolifx_effects", "bitstring"], "requirements": [ - "aiolifx==1.0.2", + "aiolifx==1.0.4", "aiolifx-effects==0.3.2", "aiolifx-themes==0.4.15" ] diff --git a/requirements_all.txt b/requirements_all.txt index 95dfb023ab0..896d541e69d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -282,7 +282,7 @@ aiolifx-effects==0.3.2 aiolifx-themes==0.4.15 # homeassistant.components.lifx -aiolifx==1.0.2 +aiolifx==1.0.4 # homeassistant.components.livisi aiolivisi==0.0.19 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 32d9722af73..f6a7328e927 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -255,7 +255,7 @@ aiolifx-effects==0.3.2 aiolifx-themes==0.4.15 # homeassistant.components.lifx -aiolifx==1.0.2 +aiolifx==1.0.4 # homeassistant.components.livisi aiolivisi==0.0.19 From ad07bdb62bd0614164187fde099cb7e65ef2b0c5 Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Fri, 12 Jul 2024 13:21:45 +1000 Subject: [PATCH 24/39] Bump aiolifx to 1.0.5 (#121824) --- homeassistant/components/lifx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lifx/manifest.json b/homeassistant/components/lifx/manifest.json index 5e68c1bab35..3d0bd1d73d1 100644 --- a/homeassistant/components/lifx/manifest.json +++ b/homeassistant/components/lifx/manifest.json @@ -48,7 +48,7 @@ "iot_class": "local_polling", "loggers": ["aiolifx", "aiolifx_effects", "bitstring"], "requirements": [ - "aiolifx==1.0.4", + "aiolifx==1.0.5", "aiolifx-effects==0.3.2", "aiolifx-themes==0.4.15" ] diff --git a/requirements_all.txt b/requirements_all.txt index 896d541e69d..5583044dd7c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -282,7 +282,7 @@ aiolifx-effects==0.3.2 aiolifx-themes==0.4.15 # homeassistant.components.lifx -aiolifx==1.0.4 +aiolifx==1.0.5 # homeassistant.components.livisi aiolivisi==0.0.19 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f6a7328e927..59afcd79a95 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -255,7 +255,7 @@ aiolifx-effects==0.3.2 aiolifx-themes==0.4.15 # homeassistant.components.lifx -aiolifx==1.0.4 +aiolifx==1.0.5 # homeassistant.components.livisi aiolivisi==0.0.19 From a835750252ea94caf9de23d1274f84ad4d8245d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 12 Jul 2024 08:54:38 -0500 Subject: [PATCH 25/39] Log add/remove index complete at the same level as when it starts (#121852) --- .../components/recorder/migration.py | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index cf003f72af4..41a429b7269 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -313,11 +313,9 @@ def _create_index( index = index_list[0] _LOGGER.debug("Creating %s index", index_name) _LOGGER.warning( - ( - "Adding index `%s` to table `%s`. Note: this can take several " - "minutes on large databases and slow computers. Please " - "be patient!" - ), + "Adding index `%s` to table `%s`. Note: this can take several " + "minutes on large databases and slow computers. Please " + "be patient!", index_name, table_name, ) @@ -331,7 +329,7 @@ def _create_index( "Index %s already exists on %s, continuing", index_name, table_name ) - _LOGGER.debug("Finished creating %s", index_name) + _LOGGER.warning("Finished adding index `%s` to table `%s`", index_name, table_name) def _execute_or_collect_error( @@ -364,11 +362,9 @@ def _drop_index( DO NOT USE THIS FUNCTION IN ANY OPERATION THAT TAKES USER INPUT. """ _LOGGER.warning( - ( - "Dropping index `%s` from table `%s`. Note: this can take several " - "minutes on large databases and slow computers. Please " - "be patient!" - ), + "Dropping index `%s` from table `%s`. Note: this can take several " + "minutes on large databases and slow computers. Please " + "be patient!", index_name, table_name, ) @@ -377,8 +373,8 @@ def _drop_index( index_to_drop = get_index_by_name(session, table_name, index_name) if index_to_drop is None: - _LOGGER.debug( - "The index %s on table %s no longer exists", index_name, table_name + _LOGGER.warning( + "The index `%s` on table `%s` no longer exists", index_name, table_name ) return @@ -395,18 +391,16 @@ def _drop_index( f"DROP INDEX {index_to_drop}", ): if _execute_or_collect_error(session_maker, query, errors): - _LOGGER.debug( - "Finished dropping index %s from table %s", index_name, table_name + _LOGGER.warning( + "Finished dropping index `%s` from table `%s`", index_name, table_name ) return if not quiet: _LOGGER.warning( - ( - "Failed to drop index `%s` from table `%s`. Schema " - "Migration will continue; this is not a " - "critical operation: %s" - ), + "Failed to drop index `%s` from table `%s`. Schema " + "Migration will continue; this is not a " + "critical operation: %s", index_name, table_name, errors, From 24ed003471d8554691e5a2a5214337c34b5ef3e5 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Mon, 15 Jul 2024 09:20:32 +0200 Subject: [PATCH 26/39] Fix opentherm_gw availability (#121892) --- homeassistant/components/opentherm_gw/__init__.py | 5 +++++ homeassistant/components/opentherm_gw/binary_sensor.py | 7 ++----- homeassistant/components/opentherm_gw/climate.py | 2 +- homeassistant/components/opentherm_gw/sensor.py | 7 ++----- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 46cc6f3daa0..a0d791fddd4 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -470,3 +470,8 @@ class OpenThermGatewayDevice: async_dispatcher_send(self.hass, self.update_signal, status) self.gateway.subscribe(handle_report) + + @property + def connected(self): + """Report whether or not we are connected to the gateway.""" + return self.gateway.connection.connected diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index ad8d09afa89..7c3760653e8 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -48,6 +48,7 @@ class OpenThermBinarySensor(BinarySensorEntity): _attr_should_poll = False _attr_entity_registry_enabled_default = False + _attr_available = False def __init__(self, gw_dev, var, source, device_class, friendly_name_format): """Initialize the binary sensor.""" @@ -85,14 +86,10 @@ class OpenThermBinarySensor(BinarySensorEntity): _LOGGER.debug("Removing OpenTherm Gateway binary sensor %s", self._attr_name) self._unsub_updates() - @property - def available(self): - """Return availability of the sensor.""" - return self._attr_is_on is not None - @callback def receive_report(self, status): """Handle status updates from the component.""" + self._attr_available = self._gateway.connected state = status[self._source].get(self._var) self._attr_is_on = None if state is None else bool(state) self.async_write_ha_state() diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 2d9f1687463..5eb1246e55f 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -138,7 +138,7 @@ class OpenThermClimate(ClimateEntity): @callback def receive_report(self, status): """Receive and handle a new report from the Gateway.""" - self._attr_available = status != gw_vars.DEFAULT_STATUS + self._attr_available = self._gateway.connected ch_active = status[gw_vars.BOILER].get(gw_vars.DATA_SLAVE_CH_ACTIVE) flame_on = status[gw_vars.BOILER].get(gw_vars.DATA_SLAVE_FLAME_ON) cooling_active = status[gw_vars.BOILER].get(gw_vars.DATA_SLAVE_COOLING_ACTIVE) diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 9171292c21b..8c17aca4516 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -45,6 +45,7 @@ class OpenThermSensor(SensorEntity): _attr_should_poll = False _attr_entity_registry_enabled_default = False + _attr_available = False def __init__( self, @@ -94,14 +95,10 @@ class OpenThermSensor(SensorEntity): _LOGGER.debug("Removing OpenTherm Gateway sensor %s", self._attr_name) self._unsub_updates() - @property - def available(self): - """Return availability of the sensor.""" - return self._attr_native_value is not None - @callback def receive_report(self, status): """Handle status updates from the component.""" + self._attr_available = self._gateway.connected value = status[self._source].get(self._var) self._attr_native_value = value self.async_write_ha_state() From f9b359ae3010b9283e52525bb2f7286fc1a486a7 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Mon, 15 Jul 2024 02:10:50 -0500 Subject: [PATCH 27/39] Fix rainforest_raven closing device due to timeout (#121905) --- homeassistant/components/rainforest_raven/coordinator.py | 2 +- homeassistant/components/rainforest_raven/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/rainforest_raven/coordinator.py b/homeassistant/components/rainforest_raven/coordinator.py index 37e44b12eba..d08a10c2670 100644 --- a/homeassistant/components/rainforest_raven/coordinator.py +++ b/homeassistant/components/rainforest_raven/coordinator.py @@ -167,7 +167,7 @@ class RAVEnDataCoordinator(DataUpdateCoordinator): await device.synchronize() self._device_info = await device.get_device_info() except: - await device.close() + await device.abort() raise self._raven_device = device diff --git a/homeassistant/components/rainforest_raven/manifest.json b/homeassistant/components/rainforest_raven/manifest.json index bc44c3fc30c..49bd11e8880 100644 --- a/homeassistant/components/rainforest_raven/manifest.json +++ b/homeassistant/components/rainforest_raven/manifest.json @@ -6,7 +6,7 @@ "dependencies": ["usb"], "documentation": "https://www.home-assistant.io/integrations/rainforest_raven", "iot_class": "local_polling", - "requirements": ["aioraven==0.6.0"], + "requirements": ["aioraven==0.7.0"], "usb": [ { "vid": "0403", diff --git a/requirements_all.txt b/requirements_all.txt index 5583044dd7c..1af4edb3b45 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -344,7 +344,7 @@ aiopyarr==23.4.0 aioqsw==0.3.5 # homeassistant.components.rainforest_raven -aioraven==0.6.0 +aioraven==0.7.0 # homeassistant.components.recollect_waste aiorecollect==2023.09.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 59afcd79a95..d5b8d307884 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -317,7 +317,7 @@ aiopyarr==23.4.0 aioqsw==0.3.5 # homeassistant.components.rainforest_raven -aioraven==0.6.0 +aioraven==0.7.0 # homeassistant.components.recollect_waste aiorecollect==2023.09.0 From bf89eaae2514581a8c0b28593ede59a9b5d64930 Mon Sep 17 00:00:00 2001 From: Tomasz Gorochowik Date: Mon, 15 Jul 2024 09:09:19 +0200 Subject: [PATCH 28/39] Fix enigma2 mute (#121928) --- homeassistant/components/enigma2/media_player.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 63acdd8be72..86ed9652106 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -199,7 +199,8 @@ class Enigma2Device(MediaPlayerEntity): async def async_mute_volume(self, mute: bool) -> None: """Mute or unmute.""" - await self._device.toggle_mute() + if mute != self._device.status.muted: + await self._device.toggle_mute() async def async_select_source(self, source: str) -> None: """Select input source.""" From 9bd822d693ab1d5a2a5c176d94af544a22bc8f8f Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 15 Jul 2024 08:31:44 +0200 Subject: [PATCH 29/39] Fix `configuration_url` for Shelly device using IPv6 (#121939) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/shelly/coordinator.py | 3 ++- homeassistant/components/shelly/utils.py | 16 +++++++++++++++- tests/components/shelly/test_utils.py | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 33ed07c35de..c1be4577bf6 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -61,6 +61,7 @@ from .utils import ( async_create_issue_unsupported_firmware, get_block_device_sleep_period, get_device_entry_gen, + get_host, get_http_port, get_rpc_device_wakeup_period, update_device_fw_info, @@ -147,7 +148,7 @@ class ShellyCoordinatorBase[_DeviceT: BlockDevice | RpcDevice]( model=MODEL_NAMES.get(self.model, self.model), sw_version=self.sw_version, hw_version=f"gen{get_device_entry_gen(self.entry)} ({self.model})", - configuration_url=f"http://{self.entry.data[CONF_HOST]}:{get_http_port(self.entry.data)}", + configuration_url=f"http://{get_host(self.entry.data[CONF_HOST])}:{get_http_port(self.entry.data)}", ) self.device_id = device_entry.id diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index bcd5a859538..bb2263c8da7 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import datetime, timedelta -from ipaddress import IPv4Address +from ipaddress import IPv4Address, IPv6Address, ip_address from types import MappingProxyType from typing import Any, cast @@ -482,6 +482,20 @@ def get_http_port(data: MappingProxyType[str, Any]) -> int: return cast(int, data.get(CONF_PORT, DEFAULT_HTTP_PORT)) +def get_host(host: str) -> str: + """Get the device IP address or hostname.""" + try: + ip_object = ip_address(host) + except ValueError: + # host contains hostname + return host + + if isinstance(ip_object, IPv6Address): + return f"[{host}]" + + return host + + @callback def async_remove_shelly_rpc_entities( hass: HomeAssistant, domain: str, mac: str, keys: list[str] diff --git a/tests/components/shelly/test_utils.py b/tests/components/shelly/test_utils.py index 7c4ea8accae..5891f250fae 100644 --- a/tests/components/shelly/test_utils.py +++ b/tests/components/shelly/test_utils.py @@ -23,6 +23,7 @@ from homeassistant.components.shelly.utils import ( get_block_device_sleep_period, get_block_input_triggers, get_device_uptime, + get_host, get_number_of_channels, get_release_url, get_rpc_channel_name, @@ -274,3 +275,19 @@ def test_get_release_url( result = get_release_url(gen, model, beta) assert result is expected + + +@pytest.mark.parametrize( + ("host", "expected"), + [ + ("shelly_device.local", "shelly_device.local"), + ("192.168.178.12", "192.168.178.12"), + ( + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", + ), + ], +) +def test_get_host(host: str, expected: str) -> None: + """Test get_host function.""" + assert get_host(host) == expected From 214b5efd72477a2a1a2f095e7bc9d9cb2b30603b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Jul 2024 16:23:07 -0500 Subject: [PATCH 30/39] Narrow sqlite database corruption check to ensure disk image is malformed (#121947) * Narrow sqlite database corruption check to ensure disk image is malformed The database corruption check would also replace the database when it locked externally instead of only when its malformed. This was discovered in https://github.com/home-assistant/core/issues/121909#issuecomment-2227409124 when a user did a manual index creation while HA was online * tweak * tweak * fix * fix --- homeassistant/components/recorder/core.py | 10 +++++++++- tests/components/recorder/test_init.py | 4 +++- tests/components/recorder/test_migrate.py | 4 +++- tests/components/recorder/test_purge.py | 2 +- tests/components/recorder/test_purge_v32_schema.py | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 4e5ac04c3bf..b699ea324f6 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -1178,7 +1178,15 @@ class Recorder(threading.Thread): def _handle_database_error(self, err: Exception) -> bool: """Handle a database error that may result in moving away the corrupt db.""" - if isinstance(err.__cause__, sqlite3.DatabaseError): + if ( + (cause := err.__cause__) + and isinstance(cause, sqlite3.DatabaseError) + and (cause_str := str(cause)) + # Make sure we do not move away a database when its only locked + # externally by another process. sqlite does not give us a named + # exception for this so we have to check the error message. + and ("malformed" in cause_str or "not a database" in cause_str) + ): _LOGGER.exception( "Unrecoverable sqlite3 database corruption detected: %s", err ) diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 52947ce0c19..e1b22b2c245 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -1707,7 +1707,9 @@ async def test_database_corruption_while_running( hass.states.async_set("test.lost", "on", {}) sqlite3_exception = DatabaseError("statement", {}, []) - sqlite3_exception.__cause__ = sqlite3.DatabaseError() + sqlite3_exception.__cause__ = sqlite3.DatabaseError( + "database disk image is malformed" + ) await async_wait_recording_done(hass) with patch.object( diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index cb8e402f65a..d5b26eba680 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -165,7 +165,9 @@ async def test_database_migration_encounters_corruption( assert recorder.util.async_migration_in_progress(hass) is False sqlite3_exception = DatabaseError("statement", {}, []) - sqlite3_exception.__cause__ = sqlite3.DatabaseError() + sqlite3_exception.__cause__ = sqlite3.DatabaseError( + "database disk image is malformed" + ) with ( patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 1ccbaada265..1167fd4de73 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -210,7 +210,7 @@ async def test_purge_old_states_encouters_database_corruption( await async_wait_recording_done(hass) sqlite3_exception = DatabaseError("statement", {}, []) - sqlite3_exception.__cause__ = sqlite3.DatabaseError() + sqlite3_exception.__cause__ = sqlite3.DatabaseError("not a database") with ( patch( diff --git a/tests/components/recorder/test_purge_v32_schema.py b/tests/components/recorder/test_purge_v32_schema.py index fb636cfa9dc..8a641a2ce7f 100644 --- a/tests/components/recorder/test_purge_v32_schema.py +++ b/tests/components/recorder/test_purge_v32_schema.py @@ -173,7 +173,7 @@ async def test_purge_old_states_encouters_database_corruption( await async_wait_recording_done(hass) sqlite3_exception = DatabaseError("statement", {}, []) - sqlite3_exception.__cause__ = sqlite3.DatabaseError() + sqlite3_exception.__cause__ = sqlite3.DatabaseError("not a database") with ( patch( From 4b93fc61b5dfd7db6847fda7800a124250b8b5c4 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 16 Jul 2024 13:47:11 +0200 Subject: [PATCH 31/39] Bump python-holidays to 0.53 (#122021) --- homeassistant/components/holiday/manifest.json | 2 +- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/holiday/manifest.json b/homeassistant/components/holiday/manifest.json index 075285bbdb9..ebe472d7f0e 100644 --- a/homeassistant/components/holiday/manifest.json +++ b/homeassistant/components/holiday/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/holiday", "iot_class": "local_polling", - "requirements": ["holidays==0.52", "babel==2.15.0"] + "requirements": ["holidays==0.53", "babel==2.15.0"] } diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index ad609954a57..69df8080fa5 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_polling", "loggers": ["holidays"], "quality_scale": "internal", - "requirements": ["holidays==0.52"] + "requirements": ["holidays==0.53"] } diff --git a/requirements_all.txt b/requirements_all.txt index 1af4edb3b45..a84f565dd64 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1087,7 +1087,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.52 +holidays==0.53 # homeassistant.components.frontend home-assistant-frontend==20240710.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d5b8d307884..c35ec3f64f8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -892,7 +892,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.52 +holidays==0.53 # homeassistant.components.frontend home-assistant-frontend==20240710.0 From d9e44bab69f564341de98fd9716c3f692aeb2449 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 16 Jul 2024 20:16:36 +0200 Subject: [PATCH 32/39] Mark UniFi power cycle button as unavailable if PoE is not enabled on port (#122035) --- homeassistant/components/unifi/button.py | 15 +++++++++-- tests/components/unifi/test_button.py | 32 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifi/button.py b/homeassistant/components/unifi/button.py index 6684e33e532..716d3734953 100644 --- a/homeassistant/components/unifi/button.py +++ b/homeassistant/components/unifi/button.py @@ -8,7 +8,7 @@ from __future__ import annotations from collections.abc import Callable, Coroutine from dataclasses import dataclass import secrets -from typing import Any +from typing import TYPE_CHECKING, Any import aiounifi from aiounifi.interfaces.api_handlers import ItemEvent @@ -44,6 +44,17 @@ from .entity import ( async_wlan_device_info_fn, ) +if TYPE_CHECKING: + from .hub import UnifiHub + + +@callback +def async_port_power_cycle_available_fn(hub: UnifiHub, obj_id: str) -> bool: + """Check if port allows power cycle action.""" + if not async_device_available_fn(hub, obj_id): + return False + return bool(hub.api.ports[obj_id].poe_enable) + async def async_restart_device_control_fn( api: aiounifi.Controller, obj_id: str @@ -96,7 +107,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = ( entity_category=EntityCategory.CONFIG, device_class=ButtonDeviceClass.RESTART, api_handler_fn=lambda api: api.ports, - available_fn=async_device_available_fn, + available_fn=async_port_power_cycle_available_fn, control_fn=async_power_cycle_port_control_fn, device_info_fn=async_device_device_info_fn, name_fn=lambda port: f"{port.name} Power Cycle", diff --git a/tests/components/unifi/test_button.py b/tests/components/unifi/test_button.py index b58d01e7724..b7bf19aedc2 100644 --- a/tests/components/unifi/test_button.py +++ b/tests/components/unifi/test_button.py @@ -1,9 +1,11 @@ """UniFi Network button platform tests.""" +from copy import deepcopy from datetime import timedelta from typing import Any from unittest.mock import patch +from aiounifi.models.message import MessageKey import pytest from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonDeviceClass @@ -319,3 +321,33 @@ async def test_wlan_button_entities( request_data, call, ) + + +@pytest.mark.parametrize("device_payload", [DEVICE_POWER_CYCLE_POE]) +@pytest.mark.usefixtures("config_entry_setup") +async def test_power_cycle_availability( + hass: HomeAssistant, + mock_websocket_message, + device_payload: dict[str, Any], +) -> None: + """Verify that disabling PoE marks entity as unavailable.""" + entity_id = "button.switch_port_1_power_cycle" + + assert hass.states.get(entity_id).state != STATE_UNAVAILABLE + + # PoE disabled + + device_1 = deepcopy(device_payload[0]) + device_1["port_table"][0]["poe_enable"] = False + mock_websocket_message(message=MessageKey.DEVICE, data=device_1) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # PoE enabled + device_1 = deepcopy(device_payload[0]) + device_1["port_table"][0]["poe_enable"] = True + mock_websocket_message(message=MessageKey.DEVICE, data=device_1) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state != STATE_UNAVAILABLE From 002db3c3e93ef7ff72ae88d102e9cf5e74b0ef12 Mon Sep 17 00:00:00 2001 From: Harry Martland Date: Fri, 19 Jul 2024 12:47:28 +0100 Subject: [PATCH 33/39] Fix hive not updating when boosting (#122042) * fixes issue where hive does not update when boosting * formats files --- homeassistant/components/hive/climate.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index cb1cc15a5bf..8f6db11babe 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -142,10 +142,10 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): self.device = await self.hive.heating.getClimate(self.device) self._attr_available = self.device["deviceData"].get("online") if self._attr_available: - self._attr_hvac_mode = HIVE_TO_HASS_STATE[self.device["status"]["mode"]] - self._attr_hvac_action = HIVE_TO_HASS_HVAC_ACTION[ + self._attr_hvac_mode = HIVE_TO_HASS_STATE.get(self.device["status"]["mode"]) + self._attr_hvac_action = HIVE_TO_HASS_HVAC_ACTION.get( self.device["status"]["action"] - ] + ) self._attr_current_temperature = self.device["status"][ "current_temperature" ] @@ -154,5 +154,6 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): self._attr_max_temp = self.device["max_temp"] if self.device["status"]["boost"] == "ON": self._attr_preset_mode = PRESET_BOOST + self._attr_hvac_mode = HVACMode.HEAT else: self._attr_preset_mode = PRESET_NONE From 977a55e3b84d8559e304b5fbb9b7e540a220d1e4 Mon Sep 17 00:00:00 2001 From: "Steven B." <51370195+sdb9696@users.noreply.github.com> Date: Wed, 17 Jul 2024 20:07:53 +0100 Subject: [PATCH 34/39] Update tplink device config during reauth flow (#122089) --- .../components/tplink/config_flow.py | 51 ++++-- tests/components/tplink/__init__.py | 46 +++-- tests/components/tplink/conftest.py | 23 ++- tests/components/tplink/test_config_flow.py | 162 +++++++++++++----- tests/components/tplink/test_init.py | 36 ++-- 5 files changed, 219 insertions(+), 99 deletions(-) diff --git a/homeassistant/components/tplink/config_flow.py b/homeassistant/components/tplink/config_flow.py index 5608ccfa72f..a0f0ca6eb76 100644 --- a/homeassistant/components/tplink/config_flow.py +++ b/homeassistant/components/tplink/config_flow.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Mapping +import logging from typing import Any from kasa import ( @@ -52,6 +53,8 @@ from .const import ( DOMAIN, ) +_LOGGER = logging.getLogger(__name__) + STEP_AUTH_DATA_SCHEMA = vol.Schema( {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} ) @@ -88,15 +91,10 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN): ) @callback - def _update_config_if_entry_in_setup_error( + def _get_config_updates( self, entry: ConfigEntry, host: str, config: dict - ) -> ConfigFlowResult | None: - """If discovery encounters a device that is in SETUP_ERROR or SETUP_RETRY update the device config.""" - if entry.state not in ( - ConfigEntryState.SETUP_ERROR, - ConfigEntryState.SETUP_RETRY, - ): - return None + ) -> dict | None: + """Return updates if the host or device config has changed.""" entry_data = entry.data entry_config_dict = entry_data.get(CONF_DEVICE_CONFIG) if entry_config_dict == config and entry_data[CONF_HOST] == host: @@ -110,11 +108,31 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN): != config.get(CONF_CONNECTION_TYPE) ): updates.pop(CONF_CREDENTIALS_HASH, None) - return self.async_update_reload_and_abort( - entry, - data=updates, - reason="already_configured", - ) + _LOGGER.debug( + "Connection type changed for %s from %s to: %s", + host, + entry_config_dict.get(CONF_CONNECTION_TYPE), + config.get(CONF_CONNECTION_TYPE), + ) + return updates + + @callback + def _update_config_if_entry_in_setup_error( + self, entry: ConfigEntry, host: str, config: dict + ) -> ConfigFlowResult | None: + """If discovery encounters a device that is in SETUP_ERROR or SETUP_RETRY update the device config.""" + if entry.state not in ( + ConfigEntryState.SETUP_ERROR, + ConfigEntryState.SETUP_RETRY, + ): + return None + if updates := self._get_config_updates(entry, host, config): + return self.async_update_reload_and_abort( + entry, + data=updates, + reason="already_configured", + ) + return None async def _async_handle_discovery( self, host: str, formatted_mac: str, config: dict | None = None @@ -454,7 +472,7 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN): password = user_input[CONF_PASSWORD] credentials = Credentials(username, password) try: - await self._async_try_discover_and_update( + device = await self._async_try_discover_and_update( host, credentials=credentials, raise_on_progress=True, @@ -467,6 +485,11 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN): placeholders["error"] = str(ex) else: await set_credentials(self.hass, username, password) + config = device.config.to_dict(exclude_credentials=True) + if updates := self._get_config_updates(reauth_entry, host, config): + self.hass.config_entries.async_update_entry( + reauth_entry, data=updates + ) self.hass.async_create_task( self._async_reload_requires_auth_entries(), eager_start=False ) diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index b554cf07015..c51a451c847 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -57,25 +57,26 @@ CREDENTIALS_HASH_LEGACY = "" DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS) DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(exclude_credentials=True) CREDENTIALS = Credentials("foo", "bar") -CREDENTIALS_HASH_AUTH = "abcdefghijklmnopqrstuv==" -DEVICE_CONFIG_AUTH = DeviceConfig( +CREDENTIALS_HASH_AES = "AES/abcdefghijklmnopqrstuvabcdefghijklmnopqrstuv==" +CREDENTIALS_HASH_KLAP = "KLAP/abcdefghijklmnopqrstuv==" +DEVICE_CONFIG_KLAP = DeviceConfig( IP_ADDRESS, credentials=CREDENTIALS, connection_type=DeviceConnectionParameters( - DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Klap + DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap ), uses_http=True, ) -DEVICE_CONFIG_AUTH2 = DeviceConfig( +DEVICE_CONFIG_AES = DeviceConfig( IP_ADDRESS2, credentials=CREDENTIALS, connection_type=DeviceConnectionParameters( - DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Klap + DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Aes ), uses_http=True, ) -DEVICE_CONFIG_DICT_AUTH = DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True) -DEVICE_CONFIG_DICT_AUTH2 = DEVICE_CONFIG_AUTH2.to_dict(exclude_credentials=True) +DEVICE_CONFIG_DICT_KLAP = DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True) +DEVICE_CONFIG_DICT_AES = DEVICE_CONFIG_AES.to_dict(exclude_credentials=True) CREATE_ENTRY_DATA_LEGACY = { CONF_HOST: IP_ADDRESS, @@ -84,24 +85,28 @@ CREATE_ENTRY_DATA_LEGACY = { CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY, } -CREATE_ENTRY_DATA_AUTH = { +CREATE_ENTRY_DATA_KLAP = { CONF_HOST: IP_ADDRESS, CONF_ALIAS: ALIAS, CONF_MODEL: MODEL, - CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AUTH, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_KLAP, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, } -CREATE_ENTRY_DATA_AUTH2 = { +CREATE_ENTRY_DATA_AES = { CONF_HOST: IP_ADDRESS2, CONF_ALIAS: ALIAS, CONF_MODEL: MODEL, - CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AUTH, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH2, + CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AES, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AES, } -NEW_CONNECTION_TYPE = DeviceConnectionParameters( - DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Aes +CONNECTION_TYPE_KLAP = DeviceConnectionParameters( + DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap ) -NEW_CONNECTION_TYPE_DICT = NEW_CONNECTION_TYPE.to_dict() +CONNECTION_TYPE_KLAP_DICT = CONNECTION_TYPE_KLAP.to_dict() +CONNECTION_TYPE_AES = DeviceConnectionParameters( + DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Aes +) +CONNECTION_TYPE_AES_DICT = CONNECTION_TYPE_AES.to_dict() def _load_feature_fixtures(): @@ -187,7 +192,7 @@ def _mocked_device( device_id=DEVICE_ID, alias=ALIAS, model=MODEL, - ip_address=IP_ADDRESS, + ip_address: str | None = None, modules: list[str] | None = None, children: list[Device] | None = None, features: list[str | Feature] | None = None, @@ -202,12 +207,17 @@ def _mocked_device( device.mac = mac device.alias = alias device.model = model - device.host = ip_address device.device_id = device_id device.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"} device.modules = {} device.features = {} + if not ip_address: + ip_address = IP_ADDRESS + else: + device_config.host = ip_address + device.host = ip_address + if modules: device.modules = { module_name: MODULE_TO_MOCK_GEN[module_name](device) diff --git a/tests/components/tplink/conftest.py b/tests/components/tplink/conftest.py index f8d933de71e..b1256f437e7 100644 --- a/tests/components/tplink/conftest.py +++ b/tests/components/tplink/conftest.py @@ -11,8 +11,10 @@ from homeassistant.core import HomeAssistant from . import ( CREATE_ENTRY_DATA_LEGACY, - CREDENTIALS_HASH_AUTH, - DEVICE_CONFIG_AUTH, + CREDENTIALS_HASH_AES, + CREDENTIALS_HASH_KLAP, + DEVICE_CONFIG_AES, + DEVICE_CONFIG_KLAP, IP_ADDRESS, IP_ADDRESS2, MAC_ADDRESS, @@ -32,14 +34,14 @@ def mock_discovery(): discover_single=DEFAULT, ) as mock_discovery: device = _mocked_device( - device_config=copy.deepcopy(DEVICE_CONFIG_AUTH), - credentials_hash=CREDENTIALS_HASH_AUTH, + device_config=copy.deepcopy(DEVICE_CONFIG_KLAP), + credentials_hash=CREDENTIALS_HASH_KLAP, alias=None, ) devices = { "127.0.0.1": _mocked_device( - device_config=copy.deepcopy(DEVICE_CONFIG_AUTH), - credentials_hash=CREDENTIALS_HASH_AUTH, + device_config=copy.deepcopy(DEVICE_CONFIG_KLAP), + credentials_hash=CREDENTIALS_HASH_KLAP, alias=None, ) } @@ -55,12 +57,15 @@ def mock_connect(): with patch("homeassistant.components.tplink.Device.connect") as mock_connect: devices = { IP_ADDRESS: _mocked_device( - device_config=DEVICE_CONFIG_AUTH, credentials_hash=CREDENTIALS_HASH_AUTH + device_config=DEVICE_CONFIG_KLAP, + credentials_hash=CREDENTIALS_HASH_KLAP, + ip_address=IP_ADDRESS, ), IP_ADDRESS2: _mocked_device( - device_config=DEVICE_CONFIG_AUTH, - credentials_hash=CREDENTIALS_HASH_AUTH, + device_config=DEVICE_CONFIG_AES, + credentials_hash=CREDENTIALS_HASH_AES, mac=MAC_ADDRESS2, + ip_address=IP_ADDRESS2, ), } diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index e9ae7957520..ddd67f249e6 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -1,5 +1,6 @@ """Test the tplink config flow.""" +import logging from unittest.mock import AsyncMock, patch from kasa import TimeoutError @@ -11,6 +12,7 @@ from homeassistant.components.tplink import ( DOMAIN, AuthenticationError, Credentials, + Device, DeviceConfig, KasaException, ) @@ -33,19 +35,21 @@ from homeassistant.data_entry_flow import FlowResultType from . import ( ALIAS, - CREATE_ENTRY_DATA_AUTH, - CREATE_ENTRY_DATA_AUTH2, + CONNECTION_TYPE_KLAP_DICT, + CREATE_ENTRY_DATA_AES, + CREATE_ENTRY_DATA_KLAP, CREATE_ENTRY_DATA_LEGACY, - CREDENTIALS_HASH_AUTH, + CREDENTIALS_HASH_AES, + CREDENTIALS_HASH_KLAP, DEFAULT_ENTRY_TITLE, - DEVICE_CONFIG_DICT_AUTH, + DEVICE_CONFIG_DICT_AES, + DEVICE_CONFIG_DICT_KLAP, DEVICE_CONFIG_DICT_LEGACY, DHCP_FORMATTED_MAC_ADDRESS, IP_ADDRESS, MAC_ADDRESS, MAC_ADDRESS2, MODULE, - NEW_CONNECTION_TYPE_DICT, _mocked_device, _patch_connect, _patch_discovery, @@ -135,7 +139,7 @@ async def test_discovery_auth( CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() @@ -154,7 +158,7 @@ async def test_discovery_auth( assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == DEFAULT_ENTRY_TITLE - assert result2["data"] == CREATE_ENTRY_DATA_AUTH + assert result2["data"] == CREATE_ENTRY_DATA_KLAP assert result2["context"]["unique_id"] == MAC_ADDRESS @@ -187,7 +191,7 @@ async def test_discovery_auth_errors( CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() @@ -218,7 +222,7 @@ async def test_discovery_auth_errors( }, ) assert result3["type"] is FlowResultType.CREATE_ENTRY - assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["data"] == CREATE_ENTRY_DATA_KLAP assert result3["context"]["unique_id"] == MAC_ADDRESS @@ -238,7 +242,7 @@ async def test_discovery_new_credentials( CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() @@ -267,7 +271,7 @@ async def test_discovery_new_credentials( {}, ) assert result3["type"] is FlowResultType.CREATE_ENTRY - assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["data"] == CREATE_ENTRY_DATA_KLAP assert result3["context"]["unique_id"] == MAC_ADDRESS @@ -290,7 +294,7 @@ async def test_discovery_new_credentials_invalid( CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() @@ -323,7 +327,7 @@ async def test_discovery_new_credentials_invalid( }, ) assert result3["type"] is FlowResultType.CREATE_ENTRY - assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["data"] == CREATE_ENTRY_DATA_KLAP assert result3["context"]["unique_id"] == MAC_ADDRESS @@ -543,7 +547,7 @@ async def test_manual_auth( await hass.async_block_till_done() assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["title"] == DEFAULT_ENTRY_TITLE - assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["data"] == CREATE_ENTRY_DATA_KLAP assert result3["context"]["unique_id"] == MAC_ADDRESS @@ -607,7 +611,7 @@ async def test_manual_auth_errors( }, ) assert result4["type"] is FlowResultType.CREATE_ENTRY - assert result4["data"] == CREATE_ENTRY_DATA_AUTH + assert result4["data"] == CREATE_ENTRY_DATA_KLAP assert result4["context"]["unique_id"] == MAC_ADDRESS await hass.async_block_till_done() @@ -791,16 +795,16 @@ async def test_integration_discovery_with_ip_change( CONF_HOST: "127.0.0.2", CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["reason"] == "already_configured" - assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP assert mock_config_entry.data[CONF_HOST] == "127.0.0.2" - config = DeviceConfig.from_dict(DEVICE_CONFIG_DICT_AUTH) + config = DeviceConfig.from_dict(DEVICE_CONFIG_DICT_KLAP) mock_connect["connect"].reset_mock(side_effect=True) bulb = _mocked_device( @@ -832,8 +836,8 @@ async def test_integration_discovery_with_connection_change( mock_config_entry = MockConfigEntry( title="TPLink", domain=DOMAIN, - data=CREATE_ENTRY_DATA_AUTH, - unique_id=MAC_ADDRESS, + data=CREATE_ENTRY_DATA_AES, + unique_id=MAC_ADDRESS2, ) mock_config_entry.add_to_hass(hass) with patch("homeassistant.components.tplink.Discover.discover", return_value={}): @@ -849,13 +853,15 @@ async def test_integration_discovery_with_connection_change( ) == 0 ) - assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH - assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1" - assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AUTH + assert mock_config_entry.data[CONF_HOST] == "127.0.0.2" + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES + assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.2" + assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES NEW_DEVICE_CONFIG = { - **DEVICE_CONFIG_DICT_AUTH, - CONF_CONNECTION_TYPE: NEW_CONNECTION_TYPE_DICT, + **DEVICE_CONFIG_DICT_KLAP, + CONF_CONNECTION_TYPE: CONNECTION_TYPE_KLAP_DICT, + CONF_HOST: "127.0.0.2", } config = DeviceConfig.from_dict(NEW_DEVICE_CONFIG) # Reset the connect mock so when the config flow reloads the entry it succeeds @@ -870,8 +876,8 @@ async def test_integration_discovery_with_connection_change( DOMAIN, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, data={ - CONF_HOST: "127.0.0.1", - CONF_MAC: MAC_ADDRESS, + CONF_HOST: "127.0.0.2", + CONF_MAC: MAC_ADDRESS2, CONF_ALIAS: ALIAS, CONF_DEVICE_CONFIG: NEW_DEVICE_CONFIG, }, @@ -880,8 +886,8 @@ async def test_integration_discovery_with_connection_change( assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["reason"] == "already_configured" assert mock_config_entry.data[CONF_DEVICE_CONFIG] == NEW_DEVICE_CONFIG - assert mock_config_entry.data[CONF_HOST] == "127.0.0.1" - assert CREDENTIALS_HASH_AUTH not in mock_config_entry.data + assert mock_config_entry.data[CONF_HOST] == "127.0.0.2" + assert CREDENTIALS_HASH_AES not in mock_config_entry.data assert mock_config_entry.state is ConfigEntryState.LOADED @@ -953,6 +959,77 @@ async def test_reauth( await hass.async_block_till_done() +async def test_reauth_update_with_encryption_change( + hass: HomeAssistant, + mock_discovery: AsyncMock, + mock_connect: AsyncMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test reauth flow.""" + orig_side_effect = mock_connect["connect"].side_effect + mock_connect["connect"].side_effect = AuthenticationError() + mock_config_entry = MockConfigEntry( + title="TPLink", + domain=DOMAIN, + data={**CREATE_ENTRY_DATA_AES}, + unique_id=MAC_ADDRESS2, + ) + mock_config_entry.add_to_hass(hass) + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES + assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES + + with patch("homeassistant.components.tplink.Discover.discover", return_value={}): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR + + caplog.set_level(logging.DEBUG) + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + [result] = flows + assert result["step_id"] == "reauth_confirm" + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES + assert CONF_CREDENTIALS_HASH not in mock_config_entry.data + + new_config = DeviceConfig( + "127.0.0.2", + credentials=None, + connection_type=Device.ConnectionParameters( + Device.Family.SmartTapoPlug, Device.EncryptionType.Klap + ), + uses_http=True, + ) + mock_discovery["mock_device"].host = "127.0.0.2" + mock_discovery["mock_device"].config = new_config + mock_discovery["mock_device"].credentials_hash = None + mock_connect["mock_devices"]["127.0.0.2"].config = new_config + mock_connect["mock_devices"]["127.0.0.2"].credentials_hash = CREDENTIALS_HASH_KLAP + + mock_connect["connect"].side_effect = orig_side_effect + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_USERNAME: "fake_username", + CONF_PASSWORD: "fake_password", + }, + ) + await hass.async_block_till_done(wait_background_tasks=True) + assert "Connection type changed for 127.0.0.2" in caplog.text + credentials = Credentials("fake_username", "fake_password") + mock_discovery["discover_single"].assert_called_once_with( + "127.0.0.2", credentials=credentials + ) + mock_discovery["mock_device"].update.assert_called_once_with() + assert result2["type"] is FlowResultType.ABORT + assert result2["reason"] == "reauth_successful" + assert mock_config_entry.state is ConfigEntryState.LOADED + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == { + **DEVICE_CONFIG_DICT_KLAP, + CONF_HOST: "127.0.0.2", + } + assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_KLAP + + async def test_reauth_update_from_discovery( hass: HomeAssistant, mock_config_entry: MockConfigEntry, @@ -981,13 +1058,13 @@ async def test_reauth_update_from_discovery( CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["reason"] == "already_configured" - assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP async def test_reauth_update_from_discovery_with_ip_change( @@ -1017,13 +1094,13 @@ async def test_reauth_update_from_discovery_with_ip_change( CONF_HOST: "127.0.0.2", CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["reason"] == "already_configured" - assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP assert mock_config_entry.data[CONF_HOST] == "127.0.0.2" @@ -1040,7 +1117,7 @@ async def test_reauth_no_update_if_config_and_ip_the_same( mock_config_entry, data={ **mock_config_entry.data, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.config_entries.async_setup(mock_config_entry.entry_id) @@ -1051,7 +1128,7 @@ async def test_reauth_no_update_if_config_and_ip_the_same( assert len(flows) == 1 [result] = flows assert result["step_id"] == "reauth_confirm" - assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP discovery_result = await hass.config_entries.flow.async_init( DOMAIN, @@ -1060,13 +1137,13 @@ async def test_reauth_no_update_if_config_and_ip_the_same( CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_ALIAS: ALIAS, - CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, + CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP, }, ) await hass.async_block_till_done() assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["reason"] == "already_configured" - assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS @@ -1214,15 +1291,20 @@ async def test_discovery_timeout_connect( async def test_reauth_update_other_flows( hass: HomeAssistant, - mock_config_entry: MockConfigEntry, mock_discovery: AsyncMock, mock_connect: AsyncMock, ) -> None: """Test reauth updates other reauth flows.""" + mock_config_entry = MockConfigEntry( + title="TPLink", + domain=DOMAIN, + data={**CREATE_ENTRY_DATA_KLAP}, + unique_id=MAC_ADDRESS, + ) mock_config_entry2 = MockConfigEntry( title="TPLink", domain=DOMAIN, - data={**CREATE_ENTRY_DATA_AUTH2}, + data={**CREATE_ENTRY_DATA_AES}, unique_id=MAC_ADDRESS2, ) default_side_effect = mock_connect["connect"].side_effect @@ -1244,7 +1326,7 @@ async def test_reauth_update_other_flows( flows_by_entry_id = {flow["context"]["entry_id"]: flow for flow in flows} result = flows_by_entry_id[mock_config_entry.entry_id] assert result["step_id"] == "reauth_confirm" - assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index c5c5e2ce6db..5b3cf648b6e 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -33,9 +33,9 @@ from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from . import ( - CREATE_ENTRY_DATA_AUTH, + CREATE_ENTRY_DATA_KLAP, CREATE_ENTRY_DATA_LEGACY, - DEVICE_CONFIG_AUTH, + DEVICE_CONFIG_KLAP, DEVICE_ID, DEVICE_ID_MAC, IP_ADDRESS, @@ -178,7 +178,7 @@ async def test_config_entry_device_config( mock_config_entry = MockConfigEntry( title="TPLink", domain=DOMAIN, - data={**CREATE_ENTRY_DATA_AUTH}, + data={**CREATE_ENTRY_DATA_KLAP}, unique_id=MAC_ADDRESS, ) mock_config_entry.add_to_hass(hass) @@ -197,7 +197,7 @@ async def test_config_entry_with_stored_credentials( mock_config_entry = MockConfigEntry( title="TPLink", domain=DOMAIN, - data={**CREATE_ENTRY_DATA_AUTH}, + data={**CREATE_ENTRY_DATA_KLAP}, unique_id=MAC_ADDRESS, ) auth = { @@ -210,7 +210,7 @@ async def test_config_entry_with_stored_credentials( await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.LOADED - config = DEVICE_CONFIG_AUTH + config = DEVICE_CONFIG_KLAP assert config.credentials != stored_credentials config.credentials = stored_credentials mock_connect["connect"].assert_called_once_with(config=config) @@ -223,7 +223,7 @@ async def test_config_entry_device_config_invalid( caplog: pytest.LogCaptureFixture, ) -> None: """Test that an invalid device config logs an error and loads the config entry.""" - entry_data = copy.deepcopy(CREATE_ENTRY_DATA_AUTH) + entry_data = copy.deepcopy(CREATE_ENTRY_DATA_KLAP) entry_data[CONF_DEVICE_CONFIG] = {"foo": "bar"} mock_config_entry = MockConfigEntry( title="TPLink", @@ -263,7 +263,7 @@ async def test_config_entry_errors( mock_config_entry = MockConfigEntry( title="TPLink", domain=DOMAIN, - data={**CREATE_ENTRY_DATA_AUTH}, + data={**CREATE_ENTRY_DATA_KLAP}, unique_id=MAC_ADDRESS, ) mock_config_entry.add_to_hass(hass) @@ -520,11 +520,11 @@ async def test_move_credentials_hash( from the device. """ device_config = { - **DEVICE_CONFIG_AUTH.to_dict( + **DEVICE_CONFIG_KLAP.to_dict( exclude_credentials=True, credentials_hash="theHash" ) } - entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config} + entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config} entry = MockConfigEntry( title="TPLink", @@ -567,11 +567,11 @@ async def test_move_credentials_hash_auth_error( in async_setup_entry. """ device_config = { - **DEVICE_CONFIG_AUTH.to_dict( + **DEVICE_CONFIG_KLAP.to_dict( exclude_credentials=True, credentials_hash="theHash" ) } - entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config} + entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config} entry = MockConfigEntry( title="TPLink", @@ -610,11 +610,11 @@ async def test_move_credentials_hash_other_error( at the end of the test. """ device_config = { - **DEVICE_CONFIG_AUTH.to_dict( + **DEVICE_CONFIG_KLAP.to_dict( exclude_credentials=True, credentials_hash="theHash" ) } - entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config} + entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config} entry = MockConfigEntry( title="TPLink", @@ -647,9 +647,9 @@ async def test_credentials_hash( hass: HomeAssistant, ) -> None: """Test credentials_hash used to call connect.""" - device_config = {**DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True)} + device_config = {**DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)} entry_data = { - **CREATE_ENTRY_DATA_AUTH, + **CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config, CONF_CREDENTIALS_HASH: "theHash", } @@ -684,9 +684,9 @@ async def test_credentials_hash_auth_error( hass: HomeAssistant, ) -> None: """Test credentials_hash is deleted after an auth failure.""" - device_config = {**DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True)} + device_config = {**DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)} entry_data = { - **CREATE_ENTRY_DATA_AUTH, + **CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config, CONF_CREDENTIALS_HASH: "theHash", } @@ -710,7 +710,7 @@ async def test_credentials_hash_auth_error( await hass.async_block_till_done() expected_config = DeviceConfig.from_dict( - DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True, credentials_hash="theHash") + DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True, credentials_hash="theHash") ) connect_mock.assert_called_with(config=expected_config) assert entry.state is ConfigEntryState.SETUP_ERROR From a3a99cc631532d1af884dea2ba4b1517d4e78bd9 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 18 Jul 2024 23:27:03 +0300 Subject: [PATCH 35/39] Prevent connecting to a Shelly device that is already connected (#122105) --- .../components/shelly/coordinator.py | 3 +++ tests/components/shelly/conftest.py | 1 + tests/components/shelly/test_binary_sensor.py | 1 + tests/components/shelly/test_config_flow.py | 1 + tests/components/shelly/test_coordinator.py | 20 +++++++++++++++++++ tests/components/shelly/test_init.py | 2 ++ tests/components/shelly/test_sensor.py | 2 ++ tests/components/shelly/test_update.py | 1 + 8 files changed, 31 insertions(+) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index c1be4577bf6..469223a5857 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -668,6 +668,9 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): """Handle device update.""" LOGGER.debug("Shelly %s handle update, type: %s", self.name, update_type) if update_type is RpcUpdateType.ONLINE: + if self.device.connected: + LOGGER.debug("Device %s already connected", self.name) + return self.entry.async_create_background_task( self.hass, self._async_device_connect_task(), diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 14c06d3f86a..7caaae8621e 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -359,6 +359,7 @@ def _mock_rpc_device(version: str | None = None): status=MOCK_STATUS_RPC, firmware_version="some fw string", initialized=True, + connected=True, ) type(device).name = PropertyMock(return_value="Test name") return device diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py index 3bfbf350f7e..dc68b657796 100644 --- a/tests/components/shelly/test_binary_sensor.py +++ b/tests/components/shelly/test_binary_sensor.py @@ -263,6 +263,7 @@ async def test_rpc_sleeping_binary_sensor( ) -> None: """Test RPC online sleeping binary sensor.""" entity_id = f"{BINARY_SENSOR_DOMAIN}.test_name_cloud" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000) config_entry = await init_integration(hass, 2, sleep_period=1000) diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index a26c6eac405..a3040fc2eb8 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -1114,6 +1114,7 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh( caplog: pytest.LogCaptureFixture, ) -> None: """Test zeroconf discovery does not triggers refresh for sleeping device.""" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000) entry = MockConfigEntry( domain="shelly", diff --git a/tests/components/shelly/test_coordinator.py b/tests/components/shelly/test_coordinator.py index 35123a2db91..d3494c094f9 100644 --- a/tests/components/shelly/test_coordinator.py +++ b/tests/components/shelly/test_coordinator.py @@ -545,6 +545,7 @@ async def test_rpc_update_entry_sleep_period( monkeypatch: pytest.MonkeyPatch, ) -> None: """Test RPC update entry sleep period.""" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 600) entry = await init_integration(hass, 2, sleep_period=600) register_entity( @@ -578,6 +579,7 @@ async def test_rpc_sleeping_device_no_periodic_updates( ) -> None: """Test RPC sleeping device no periodic updates.""" entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000) entry = await init_integration(hass, 2, sleep_period=1000) register_entity( @@ -609,6 +611,7 @@ async def test_rpc_sleeping_device_firmware_unsupported( issue_registry: ir.IssueRegistry, ) -> None: """Test RPC sleeping device firmware not supported.""" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setattr(mock_rpc_device, "firmware_supported", False) entry = await init_integration(hass, 2, sleep_period=3600) @@ -912,6 +915,7 @@ async def test_rpc_sleeping_device_connection_error( hass, BINARY_SENSOR_DOMAIN, "test_name_cloud", "cloud-cloud", entry ) mock_restore_cache(hass, [State(entity_id, STATE_ON)]) + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setattr(mock_rpc_device, "initialized", False) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -939,3 +943,19 @@ async def test_rpc_sleeping_device_connection_error( assert "Sleeping device did not update" in caplog.text assert get_entity_state(hass, entity_id) == STATE_UNAVAILABLE + + +async def test_rpc_already_connected( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_rpc_device: Mock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test RPC ignore connect event if already connected.""" + await init_integration(hass, 2) + + mock_rpc_device.mock_online() + await hass.async_block_till_done(wait_background_tasks=True) + + assert "already connected" in caplog.text + mock_rpc_device.initialize.assert_called_once() diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index 998d56fc6cc..46698c23c0a 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -279,6 +279,7 @@ async def test_sleeping_rpc_device_online( caplog: pytest.LogCaptureFixture, ) -> None: """Test sleeping RPC device online.""" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", device_sleep) entry = await init_integration(hass, 2, sleep_period=entry_sleep) assert "will resume when device is online" in caplog.text @@ -297,6 +298,7 @@ async def test_sleeping_rpc_device_online_new_firmware( caplog: pytest.LogCaptureFixture, ) -> None: """Test sleeping device Gen2 with firmware 1.0.0 or later.""" + monkeypatch.setattr(mock_rpc_device, "connected", False) entry = await init_integration(hass, 2, sleep_period=None) assert "will resume when device is online" in caplog.text diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index c62a1f6f6ca..9f510ba8fe9 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -449,6 +449,7 @@ async def test_rpc_sleeping_sensor( ) -> None: """Test RPC online sleeping sensor.""" entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000) entry = await init_integration(hass, 2, sleep_period=1000) @@ -600,6 +601,7 @@ async def test_rpc_sleeping_update_entity_service( await async_setup_component(hass, "homeassistant", {}) entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000) await init_integration(hass, 2, sleep_period=1000) diff --git a/tests/components/shelly/test_update.py b/tests/components/shelly/test_update.py index 8448c116815..721e86559a3 100644 --- a/tests/components/shelly/test_update.py +++ b/tests/components/shelly/test_update.py @@ -334,6 +334,7 @@ async def test_rpc_sleeping_update( monkeypatch: pytest.MonkeyPatch, ) -> None: """Test RPC sleeping device update entity.""" + monkeypatch.setattr(mock_rpc_device, "connected", False) monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000) monkeypatch.setitem(mock_rpc_device.shelly, "ver", "1") monkeypatch.setitem( From c518c4756b6a383bf521773a2b1f09a78279dddc Mon Sep 17 00:00:00 2001 From: "Steven B." <51370195+sdb9696@users.noreply.github.com> Date: Thu, 18 Jul 2024 21:20:10 +0100 Subject: [PATCH 36/39] Bump tplink dependency python-kasa to 0.7.0.5 (#122119) --- homeassistant/components/tplink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index a345f64e4b2..f935d019541 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -301,5 +301,5 @@ "iot_class": "local_polling", "loggers": ["kasa"], "quality_scale": "platinum", - "requirements": ["python-kasa[speedups]==0.7.0.4"] + "requirements": ["python-kasa[speedups]==0.7.0.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index a84f565dd64..49db85e79c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2275,7 +2275,7 @@ python-join-api==0.0.9 python-juicenet==1.1.0 # homeassistant.components.tplink -python-kasa[speedups]==0.7.0.4 +python-kasa[speedups]==0.7.0.5 # homeassistant.components.lirc # python-lirc==1.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c35ec3f64f8..6af377ff73f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1775,7 +1775,7 @@ python-izone==1.2.9 python-juicenet==1.1.0 # homeassistant.components.tplink -python-kasa[speedups]==0.7.0.4 +python-kasa[speedups]==0.7.0.5 # homeassistant.components.matter python-matter-server==6.2.2 From d0d2fd7918917b47bf348734351ecb5377e3a26d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:07:31 +0200 Subject: [PATCH 37/39] Update yt-dlp to 2024.07.16 (#122124) --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index cfe44f5176b..cd312413db3 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -8,6 +8,6 @@ "iot_class": "calculated", "loggers": ["yt_dlp"], "quality_scale": "internal", - "requirements": ["yt-dlp==2024.07.01"], + "requirements": ["yt-dlp==2024.07.16"], "single_config_entry": true } diff --git a/requirements_all.txt b/requirements_all.txt index 49db85e79c8..1512acef5e8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2951,7 +2951,7 @@ youless-api==2.1.2 youtubeaio==1.1.5 # homeassistant.components.media_extractor -yt-dlp==2024.07.01 +yt-dlp==2024.07.16 # homeassistant.components.zamg zamg==0.3.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6af377ff73f..a7cbf46c54b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2307,7 +2307,7 @@ youless-api==2.1.2 youtubeaio==1.1.5 # homeassistant.components.media_extractor -yt-dlp==2024.07.01 +yt-dlp==2024.07.16 # homeassistant.components.zamg zamg==0.3.6 From 1ef4332af6d2cc59cb233100d0e5e6425f5d0dad Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Fri, 19 Jul 2024 09:11:29 +0200 Subject: [PATCH 38/39] Fix KeyError in config flow of Bring integration (#122136) --- homeassistant/components/bring/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bring/config_flow.py b/homeassistant/components/bring/config_flow.py index 333837a20f2..997342033e4 100644 --- a/homeassistant/components/bring/config_flow.py +++ b/homeassistant/components/bring/config_flow.py @@ -58,7 +58,7 @@ class BringConfigFlow(ConfigFlow, domain=DOMAIN): ): self._abort_if_unique_id_configured() return self.async_create_entry( - title=self.info["name"] or user_input[CONF_EMAIL], data=user_input + title=self.info.get("name") or user_input[CONF_EMAIL], data=user_input ) return self.async_show_form( From a08ffdc8d3103daafd90a8551daa03d94ebf17a1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Jul 2024 18:50:21 +0200 Subject: [PATCH 39/39] Bump version to 2024.7.3 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 8587f9e6137..f706b2d1243 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -24,7 +24,7 @@ if TYPE_CHECKING: APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 82c29948e3c..f044551ce1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.7.2" +version = "2024.7.3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"