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 8d55574f0a17b9..0f14cd06fe3bee 100644 --- a/homeassistant/components/ista_ecotrend/coordinator.py +++ b/homeassistant/components/ista_ecotrend/coordinator.py @@ -8,6 +8,7 @@ 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 @@ 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 @@ def __init__(self, hass: HomeAssistant, ista: PyEcotrendIsta) -> None: 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 @@ async def _async_update_data(self): 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 @@ def get_consumption_data(self) -> 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 4798a07b9cd619..baecbcfb941c3c 100644 --- a/homeassistant/components/jellyfin/config_flow.py +++ b/homeassistant/components/jellyfin/config_flow.py @@ -97,7 +97,11 @@ async def async_step_user( ) 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 4cdee04b1491d6..37b64c375eb5e3 100644 --- a/homeassistant/components/supla/cover.py +++ b/homeassistant/components/supla/cover.py @@ -71,7 +71,9 @@ def current_cover_position(self) -> int | None: 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 bcf1ad9dae21ab..998d3610735b6e 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 38f8b6a44cb615..0a6550c8c43fe7 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 eb46f1e9c40303..9f354c8b64624e 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 4e4f8bdb687d7a..de8283a5533602 100644 --- a/homeassistant/components/enigma2/__init__.py +++ b/homeassistant/components/enigma2/__init__.py @@ -16,6 +16,8 @@ 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 ef08314e541659..538cfb56388c54 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 0a6550c8c43fe7..3be48c03ae9659 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 9f354c8b64624e..d9c49acb59ba94 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 22e7c523d1a6e0..9b7dd499c97e25 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -392,11 +392,11 @@ async def async_turn_on(self, **kwargs: Any) -> None: 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 bb814d1f5d377a..590274b84054a4 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 724c65f83dfc76..654dd4a4d1fffd 100644 --- a/homeassistant/components/knocki/config_flow.py +++ b/homeassistant/components/knocki/config_flow.py @@ -4,7 +4,7 @@ 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 @@ async def async_step_user( 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 e78e9856d621ef..cad3e156c6cf4f 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 3be48c03ae9659..0919effd0bc0b4 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 d9c49acb59ba94..51bc970af1f4dd 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 baf43c3ad30947..188175035dae6e 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 c55ca4c1630505..7eadefcd2601f8 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 fe1888478f8f6d..788cdd1eb05d69 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 0919effd0bc0b4..ec8dd306c4a3db 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 51bc970af1f4dd..4873cddfc044e8 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 a44c9c8bf916c8..3e18faca12bdcc 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 7eceb861955337..97325135de5ef6 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -189,7 +189,6 @@ def __init__( 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 @@ async def async_added_to_hass(self) -> None: 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 cad3e156c6cf4f..f35827b8213003 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 ec8dd306c4a3db..bfa675fed768d9 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 4873cddfc044e8..09b458b53e31b8 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 e93b59b0673d07..b5ad1ea2ff0bfb 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -164,13 +164,12 @@ async def async_step_site( 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 7b37437cd1d5f3..9ae3af19b46e41 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 a09d155b5b06cc..17a5f519274ae3 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 bfa675fed768d9..e9aec3fd756612 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 09b458b53e31b8..f02183553a6a2d 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 d419fdcb04306b..28c2e8ba2a8824 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 e9aec3fd756612..28b1f2f67b64c5 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 f02183553a6a2d..ef01832108b9fa 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 6b43f2887626b0..fcf79258c253de 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 434b4d0071f648..3c593a2bdf7644 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -157,6 +157,9 @@ # 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 24df4cff95cc57..4f071267a253a9 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 28b1f2f67b64c5..1d0d96bbbd7e4d 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 ef01832108b9fa..5a831e5cd591cd 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 3786a2565c2a99..337e05726ac6d4 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 e898f64d128520..f6df799d01ec96 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -827,7 +827,7 @@ }, { "domain": "tplink", - "hostname": "l5*", + "hostname": "l[59]*", "macaddress": "5CE931*", }, { @@ -837,9 +837,14 @@ }, { "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 f2db6ff1b3c4aa..2e5a69393d44cc 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 40f49e57c606b9..fec93a51202630 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -40,7 +40,7 @@ def _connected_callback(): 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 b208edbc0e5131..6b49c8597711f7 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 1d0d96bbbd7e4d..453accf6064690 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 5a831e5cd591cd..7f0fdcf52c0d29 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 d5d6d70bb68950..54aeb00e89ac71 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 337e05726ac6d4..a345f64e4b2d9f 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 453accf6064690..5da52fbb07534f 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 7f0fdcf52c0d29..23d8fd9adda641 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 c7fc34386fda62..5d95812e8458c8 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 47e09db1166653..1849dcd1862e7c 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 d502dce7d018ed..fb27c91eea7626 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 7a77e482f56eb3..ae865b77913eb4 100644 --- a/homeassistant/components/reolink/media_source.py +++ b/homeassistant/components/reolink/media_source.py @@ -5,6 +5,7 @@ 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 @@ async def _async_generate_root(self) -> BrowseMediaSource: 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 0d86106e8e5f7e..66ed32ca82362c 100644 --- a/tests/components/reolink/test_media_source.py +++ b/tests/components/reolink/test_media_source.py @@ -54,6 +54,7 @@ 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 dc858a688cbcca..0cbbd70a805c48 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 5da52fbb07534f..95dfb023ab0212 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 23d8fd9adda641..32d9722af73773 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 765f1624c33f10..560f2efc0cc4f1 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 6aa7fdc630597c..5e68c1bab35dca 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 95dfb023ab0212..896d541e69d120 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 32d9722af73773..f6a7328e927a19 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 5e68c1bab35dca..3d0bd1d73d1107 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 896d541e69d120..5583044dd7c8ba 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 f6a7328e927a19..59afcd79a95d68 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 cf003f72af4006..41a429b7269928 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 46cc6f3daa01c0..a0d791fddd4a9f 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -470,3 +470,8 @@ async def handle_report(status): 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 ad8d09afa89982..7c3760653e8552 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 @@ async def async_will_remove_from_hass(self) -> None: _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 2d9f16874633c4..5eb1246e55f233 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -138,7 +138,7 @@ async def async_will_remove_from_hass(self) -> None: @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 9171292c21b039..8c17aca45169b6 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 @@ async def async_will_remove_from_hass(self) -> None: _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 37e44b12eba3a1..d08a10c26704dc 100644 --- a/homeassistant/components/rainforest_raven/coordinator.py +++ b/homeassistant/components/rainforest_raven/coordinator.py @@ -167,7 +167,7 @@ async def _get_device(self) -> RAVEnSerialDevice: 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 bc44c3fc30c5f9..49bd11e8880008 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 5583044dd7c8ba..1af4edb3b45704 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 59afcd79a95d68..d5b8d307884504 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 63acdd8be725a3..86ed9652106478 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -199,7 +199,8 @@ async def async_media_previous_track(self) -> None: 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 33ed07c35de206..c1be4577bf6435 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -61,6 +61,7 @@ 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 @@ def async_setup(self, pending_platforms: list[Platform] | None = None) -> None: 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 bcd5a859538a94..bb2263c8da71bf 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 7c4ea8accaed24..5891f250faed25 100644 --- a/tests/components/shelly/test_utils.py +++ b/tests/components/shelly/test_utils.py @@ -23,6 +23,7 @@ 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 4e5ac04c3bfab2..b699ea324f6748 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -1178,7 +1178,15 @@ def _process_state_changed_event_into_session( 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 52947ce0c1981a..e1b22b2c245c13 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -1707,7 +1707,9 @@ def _create_tmpdir_for_test_db() -> Path: 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 cb8e402f65a5b8..d5b26eba680750 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 1ccbaada265005..1167fd4de7337e 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 fb636cfa9dc5b3..8a641a2ce7ff0c 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 075285bbdb9998..ebe472d7f0eff9 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 ad609954a572b1..69df8080fa5b1e 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 1af4edb3b45704..a84f565dd64228 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 d5b8d307884504..c35ec3f64f8c2f 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 6684e33e53202c..716d37349530bc 100644 --- a/homeassistant/components/unifi/button.py +++ b/homeassistant/components/unifi/button.py @@ -8,7 +8,7 @@ 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 @@ 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 @@ class 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 b58d01e77247c7..b7bf19aedc2cc6 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 cb1cc15a5bf060..8f6db11babe1ed 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -142,10 +142,10 @@ async def async_update(self) -> None: 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 @@ async def async_update(self) -> None: 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 5608ccfa72f9d2..a0f0ca6eb76e75 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 @@ 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 @@ async def async_step_integration_discovery( ) @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 @@ def _update_config_if_entry_in_setup_error( != 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 @@ async def async_step_reauth_confirm( 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 @@ async def async_step_reauth_confirm( 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 b554cf07015866..c51a451c8478e5 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -57,25 +57,26 @@ 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 @@ 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 f8d933de71eae6..b1256f437e7cd5 100644 --- a/tests/components/tplink/conftest.py +++ b/tests/components/tplink/conftest.py @@ -11,8 +11,10 @@ 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 e9ae79575206c4..ddd67f249e6729 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 @@ DOMAIN, AuthenticationError, Credentials, + Device, DeviceConfig, KasaException, ) @@ -33,19 +35,21 @@ 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 c5c5e2ce6db58c..5b3cf648b6e6dd 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -33,9 +33,9 @@ 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 c1be4577bf6435..469223a58574ca 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -668,6 +668,9 @@ def _async_handle_update( """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 14c06d3f86a9e7..7caaae8621e596 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 3bfbf350f7e9b0..dc68b65779629b 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 a26c6eac4050b9..a3040fc2eb85ac 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 35123a2db91839..d3494c094f9856 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 998d56fc6cc5e9..46698c23c0a6de 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 c62a1f6f6ca92a..9f510ba8fe97d4 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 8448c11681540c..721e86559a3331 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 a345f64e4b2d9f..f935d019541c21 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 a84f565dd64228..49db85e79c84f5 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 c35ec3f64f8c2f..6af377ff73f31a 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 cfe44f5176b598..cd312413db3ad3 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 49db85e79c84f5..1512acef5e85b0 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 6af377ff73f31a..a7cbf46c54bd8b 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 333837a20f2b40..997342033e4114 100644 --- a/homeassistant/components/bring/config_flow.py +++ b/homeassistant/components/bring/config_flow.py @@ -58,7 +58,7 @@ async def async_step_user( ): 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 8587f9e6137c10..f706b2d1243a8f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -24,7 +24,7 @@ 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 82c29948e3c392..f044551ce1e044 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"