From 010b04437930d8a53715daf7888fced574c1d5fe Mon Sep 17 00:00:00 2001 From: TimL Date: Sat, 10 May 2025 02:31:00 +1000 Subject: [PATCH 01/55] Allow dns hostnames to be retained for SMLIGHT user flow. (#142514) * Dont overwrite host with local IP * adjust test for user flow change --- homeassistant/components/smlight/config_flow.py | 2 -- tests/components/smlight/conftest.py | 1 + tests/components/smlight/test_config_flow.py | 16 +++++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/smlight/config_flow.py b/homeassistant/components/smlight/config_flow.py index ce4f8f4323326..39750bdc4227d 100644 --- a/homeassistant/components/smlight/config_flow.py +++ b/homeassistant/components/smlight/config_flow.py @@ -53,7 +53,6 @@ async def async_step_user( try: if not await self._async_check_auth_required(user_input): info = await self.client.get_info() - self._host = str(info.device_ip) self._device_name = str(info.hostname) if info.model not in Devices: @@ -79,7 +78,6 @@ async def async_step_auth( try: if not await self._async_check_auth_required(user_input): info = await self.client.get_info() - self._host = str(info.device_ip) self._device_name = str(info.hostname) if info.model not in Devices: diff --git a/tests/components/smlight/conftest.py b/tests/components/smlight/conftest.py index 7a1b16f1d6bc6..6c056c95fd98a 100644 --- a/tests/components/smlight/conftest.py +++ b/tests/components/smlight/conftest.py @@ -21,6 +21,7 @@ MOCK_DEVICE_NAME = "slzb-06" MOCK_HOST = "192.168.1.161" +MOCK_HOSTNAME = "slzb-06p7.lan" MOCK_USERNAME = "test-user" MOCK_PASSWORD = "test-pass" diff --git a/tests/components/smlight/test_config_flow.py b/tests/components/smlight/test_config_flow.py index 4ecfe9366e3c0..497cb8d948400 100644 --- a/tests/components/smlight/test_config_flow.py +++ b/tests/components/smlight/test_config_flow.py @@ -15,7 +15,13 @@ from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo -from .conftest import MOCK_DEVICE_NAME, MOCK_HOST, MOCK_PASSWORD, MOCK_USERNAME +from .conftest import ( + MOCK_DEVICE_NAME, + MOCK_HOST, + MOCK_HOSTNAME, + MOCK_PASSWORD, + MOCK_USERNAME, +) from tests.common import MockConfigEntry @@ -53,14 +59,14 @@ async def test_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> No result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_HOST: "slzb-06p7.local", + CONF_HOST: MOCK_HOSTNAME, }, ) assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == "SLZB-06p7" assert result2["data"] == { - CONF_HOST: MOCK_HOST, + CONF_HOST: MOCK_HOSTNAME, } assert result2["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff" assert len(mock_setup_entry.mock_calls) == 1 @@ -82,7 +88,7 @@ async def test_user_flow_auth( result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_HOST: "slzb-06p7.local", + CONF_HOST: MOCK_HOSTNAME, }, ) assert result2["type"] is FlowResultType.FORM @@ -100,7 +106,7 @@ async def test_user_flow_auth( assert result3["data"] == { CONF_USERNAME: MOCK_USERNAME, CONF_PASSWORD: MOCK_PASSWORD, - CONF_HOST: MOCK_HOST, + CONF_HOST: MOCK_HOSTNAME, } assert result3["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff" assert len(mock_setup_entry.mock_calls) == 1 From 7d163aa659cf0e00738eab5f4d00c2b7f63fd9aa Mon Sep 17 00:00:00 2001 From: Seweryn Zeman Date: Sun, 11 May 2025 20:33:17 +0200 Subject: [PATCH 02/55] Removed unused file_id param from open_ai_conversation request (#143878) --- homeassistant/components/openai_conversation/__init__.py | 1 - tests/components/openai_conversation/test_init.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/homeassistant/components/openai_conversation/__init__.py b/homeassistant/components/openai_conversation/__init__.py index 7da1becd333e0..71effe83884b8 100644 --- a/homeassistant/components/openai_conversation/__init__.py +++ b/homeassistant/components/openai_conversation/__init__.py @@ -140,7 +140,6 @@ def append_files_to_content() -> None: content.append( ResponseInputImageParam( type="input_image", - file_id=filename, image_url=f"data:{mime_type};base64,{base64_file}", detail="auto", ) diff --git a/tests/components/openai_conversation/test_init.py b/tests/components/openai_conversation/test_init.py index dc83aa4880786..b4f816707e9da 100644 --- a/tests/components/openai_conversation/test_init.py +++ b/tests/components/openai_conversation/test_init.py @@ -324,7 +324,6 @@ async def test_init_error( "type": "input_image", "image_url": "data:image/jpeg;base64,BASE64IMAGE1", "detail": "auto", - "file_id": "/a/b/c.jpg", }, ], }, @@ -349,13 +348,11 @@ async def test_init_error( "type": "input_image", "image_url": "data:image/jpeg;base64,BASE64IMAGE1", "detail": "auto", - "file_id": "/a/b/c.jpg", }, { "type": "input_image", "image_url": "data:image/jpeg;base64,BASE64IMAGE2", "detail": "auto", - "file_id": "d/e/f.jpg", }, ], }, From bde04bc47b21b1c02d7012fbae3106e225575cd9 Mon Sep 17 00:00:00 2001 From: hahn-th <15319212+hahn-th@users.noreply.github.com> Date: Wed, 14 May 2025 11:44:59 +0200 Subject: [PATCH 03/55] Doorbell Event is fired just once in homematicip_cloud (#144357) * fire event if event type if correct * Fix requested changes --- .../components/homematicip_cloud/event.py | 37 +++++++++++++++++-- .../homematicip_cloud/test_event.py | 29 +++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/event.py b/homeassistant/components/homematicip_cloud/event.py index 47a5ff4622492..fc7f43bad1aa0 100644 --- a/homeassistant/components/homematicip_cloud/event.py +++ b/homeassistant/components/homematicip_cloud/event.py @@ -1,8 +1,11 @@ """Support for HomematicIP Cloud events.""" +from collections.abc import Callable from dataclasses import dataclass from typing import TYPE_CHECKING +from homematicip.base.channel_event import ChannelEvent +from homematicip.base.functionalChannels import FunctionalChannel from homematicip.device import Device from homeassistant.components.event import ( @@ -23,6 +26,9 @@ class HmipEventEntityDescription(EventEntityDescription): """Description of a HomematicIP Cloud event.""" + channel_event_types: list[str] | None = None + channel_selector_fn: Callable[[FunctionalChannel], bool] | None = None + EVENT_DESCRIPTIONS = { "doorbell": HmipEventEntityDescription( @@ -30,6 +36,8 @@ class HmipEventEntityDescription(EventEntityDescription): translation_key="doorbell", device_class=EventDeviceClass.DOORBELL, event_types=["ring"], + channel_event_types=["DOOR_BELL_SENSOR_EVENT"], + channel_selector_fn=lambda channel: channel.channelRole == "DOOR_BELL_INPUT", ), } @@ -41,24 +49,29 @@ async def async_setup_entry( ) -> None: """Set up the HomematicIP cover from a config entry.""" hap = hass.data[DOMAIN][config_entry.unique_id] + entities: list[HomematicipGenericEntity] = [] - async_add_entities( + entities.extend( HomematicipDoorBellEvent( hap, device, channel.index, - EVENT_DESCRIPTIONS["doorbell"], + description, ) + for description in EVENT_DESCRIPTIONS.values() for device in hap.home.devices for channel in device.functionalChannels - if channel.channelRole == "DOOR_BELL_INPUT" + if description.channel_selector_fn and description.channel_selector_fn(channel) ) + async_add_entities(entities) + class HomematicipDoorBellEvent(HomematicipGenericEntity, EventEntity): """Event class for HomematicIP doorbell events.""" _attr_device_class = EventDeviceClass.DOORBELL + entity_description: HmipEventEntityDescription def __init__( self, @@ -86,9 +99,27 @@ async def async_added_to_hass(self) -> None: @callback def _async_handle_event(self, *args, **kwargs) -> None: """Handle the event fired by the functional channel.""" + raised_channel_event = self._get_channel_event_from_args(*args) + + if not self._should_raise(raised_channel_event): + return + event_types = self.entity_description.event_types if TYPE_CHECKING: assert event_types is not None self._trigger_event(event_type=event_types[0]) self.async_write_ha_state() + + def _should_raise(self, event_type: str) -> bool: + """Check if the event should be raised.""" + if self.entity_description.channel_event_types is None: + return False + return event_type in self.entity_description.channel_event_types + + def _get_channel_event_from_args(self, *args) -> str: + """Get the channel event.""" + if isinstance(args[0], ChannelEvent): + return args[0].channelEventType + + return "" diff --git a/tests/components/homematicip_cloud/test_event.py b/tests/components/homematicip_cloud/test_event.py index de615b35808ab..fcd16ca62d52b 100644 --- a/tests/components/homematicip_cloud/test_event.py +++ b/tests/components/homematicip_cloud/test_event.py @@ -35,3 +35,32 @@ async def test_door_bell_event( ha_state = hass.states.get(entity_id) assert ha_state.state != STATE_UNKNOWN + + +async def test_door_bell_event_wrong_event_type( + hass: HomeAssistant, + default_mock_hap_factory: HomeFactory, +) -> None: + """Test of door bell event of HmIP-DSD-PCB.""" + entity_id = "event.dsdpcb_klingel_doorbell" + entity_name = "dsdpcb_klingel doorbell" + device_model = "HmIP-DSD-PCB" + mock_hap = await default_mock_hap_factory.async_get_mock_hap( + test_devices=["dsdpcb_klingel"] + ) + + ha_state, hmip_device = get_and_check_entity_basics( + hass, mock_hap, entity_id, entity_name, device_model + ) + + ch = hmip_device.functionalChannels[1] + channel_event = ChannelEvent( + channelEventType="KEY_PRESS", channelIndex=1, deviceId=ch.device.id + ) + + assert ha_state.state == STATE_UNKNOWN + + ch.fire_channel_event(channel_event) + + ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_UNKNOWN From 48aa6be8895c8bbbacc0e111d9624f2a9ce37137 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Fri, 9 May 2025 12:50:55 -0400 Subject: [PATCH 04/55] Don't scale Roborock mop Path (#144421) don't scale mop path --- homeassistant/components/roborock/coordinator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index 2439a4f904a88..dc0677b25d2ff 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -28,7 +28,7 @@ from roborock.web_api import RoborockApiClient from vacuum_map_parser_base.config.color import ColorsPalette from vacuum_map_parser_base.config.image_config import ImageConfig -from vacuum_map_parser_base.config.size import Sizes +from vacuum_map_parser_base.config.size import Size, Sizes from vacuum_map_parser_base.map_data import MapData from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser @@ -148,7 +148,13 @@ def __init__( ] self.map_parser = RoborockMapDataParser( ColorsPalette(), - Sizes({k: v * MAP_SCALE for k, v in Sizes.SIZES.items()}), + Sizes( + { + k: v * MAP_SCALE + for k, v in Sizes.SIZES.items() + if k != Size.MOP_PATH_WIDTH + } + ), drawables, ImageConfig(scale=MAP_SCALE), [], From 2fbc75f89bd5dd565409ca67b204ba50d21f5b12 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 9 May 2025 18:46:32 +0200 Subject: [PATCH 05/55] Reolink fix privacy mode availability for NVR IPC cams (#144569) * Correct "available" for IPC cams * Check privacy mode when updating --- homeassistant/components/reolink/entity.py | 9 ++++++++- homeassistant/components/reolink/host.py | 7 ++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/reolink/entity.py b/homeassistant/components/reolink/entity.py index ec598de663d28..3d66939a13cb8 100644 --- a/homeassistant/components/reolink/entity.py +++ b/homeassistant/components/reolink/entity.py @@ -198,7 +198,14 @@ def __init__( @property def available(self) -> bool: """Return True if entity is available.""" - return super().available and self._host.api.camera_online(self._channel) + if self.entity_description.always_available: + return True + + return ( + super().available + and self._host.api.camera_online(self._channel) + and not self._host.api.baichuan.privacy_mode(self._channel) + ) def register_callback(self, callback_id: str, cmd_id: int) -> None: """Register callback for TCP push events.""" diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index a027177f1fca7..378c167d46938 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -465,10 +465,11 @@ async def update_states(self) -> None: wake = True self.last_wake = time() + for channel in self._api.channels: + if self._api.baichuan.privacy_mode(channel): + await self._api.baichuan.get_privacy_mode(channel) if self._api.baichuan.privacy_mode(): - await self._api.baichuan.get_privacy_mode() - if self._api.baichuan.privacy_mode(): - return # API is shutdown, no need to check states + return # API is shutdown, no need to check states await self._api.get_states(cmd_list=self.update_cmd, wake=wake) From 36a35132c0371c75da8af0220ea778610a7849b4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 9 May 2025 11:59:38 -0500 Subject: [PATCH 06/55] Bump aiodiscover to 2.7.0 (#144571) --- homeassistant/components/dhcp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index 64fd2ff38c643..c425aafdb00ff 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -15,7 +15,7 @@ "quality_scale": "internal", "requirements": [ "aiodhcpwatcher==1.1.1", - "aiodiscover==2.6.1", + "aiodiscover==2.7.0", "cached-ipaddress==0.10.0" ] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bd3ec0bb03f44..d457184d4f540 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,7 +1,7 @@ # Automatically generated by gen_requirements_all.py, do not edit aiodhcpwatcher==1.1.1 -aiodiscover==2.6.1 +aiodiscover==2.7.0 aiodns==3.4.0 aiohasupervisor==0.3.1 aiohttp-asyncmdnsresolver==0.1.1 diff --git a/requirements_all.txt b/requirements_all.txt index 7973c10cbe3b9..18466a4bdd17f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -220,7 +220,7 @@ aiocomelit==0.12.0 aiodhcpwatcher==1.1.1 # homeassistant.components.dhcp -aiodiscover==2.6.1 +aiodiscover==2.7.0 # homeassistant.components.dnsip aiodns==3.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0e41fc37f4a88..409d91af9b6bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -208,7 +208,7 @@ aiocomelit==0.12.0 aiodhcpwatcher==1.1.1 # homeassistant.components.dhcp -aiodiscover==2.6.1 +aiodiscover==2.7.0 # homeassistant.components.dnsip aiodns==3.4.0 From 5a95f43992addd17b01d354c9f935112c4eb6a03 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 12 May 2025 00:15:30 +0200 Subject: [PATCH 07/55] Bump reolink_aio to 0.13.3 (#144583) --- homeassistant/components/reolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index 59a2741571f5f..a6f0b59426ac3 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -19,5 +19,5 @@ "iot_class": "local_push", "loggers": ["reolink_aio"], "quality_scale": "platinum", - "requirements": ["reolink-aio==0.13.2"] + "requirements": ["reolink-aio==0.13.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 18466a4bdd17f..f88325f892fbd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2637,7 +2637,7 @@ renault-api==0.3.1 renson-endura-delta==1.7.2 # homeassistant.components.reolink -reolink-aio==0.13.2 +reolink-aio==0.13.3 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 409d91af9b6bf..f3c4c3ec31a62 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2144,7 +2144,7 @@ renault-api==0.3.1 renson-endura-delta==1.7.2 # homeassistant.components.reolink -reolink-aio==0.13.2 +reolink-aio==0.13.3 # homeassistant.components.rflink rflink==0.0.66 From e9cc624d93121155d98fa002605b7822028235f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 9 May 2025 17:19:00 -0500 Subject: [PATCH 08/55] Mark inkbird coordinator as not needing connectable (#144584) --- homeassistant/components/inkbird/coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/inkbird/coordinator.py b/homeassistant/components/inkbird/coordinator.py index d52ebd83595d4..fbacedf7e0f06 100644 --- a/homeassistant/components/inkbird/coordinator.py +++ b/homeassistant/components/inkbird/coordinator.py @@ -58,6 +58,7 @@ def __init__( update_method=self._async_on_update, needs_poll_method=self._async_needs_poll, poll_method=self._async_poll_data, + connectable=False, # Polling only happens if active scanning is disabled ) async def async_init(self) -> None: From 27db4e90b52015c51219059c8e0f2fe9481e1441 Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Sat, 10 May 2025 20:23:52 +0200 Subject: [PATCH 09/55] fix enphase_envoy diagnostics home endpoint name (#144634) --- homeassistant/components/enphase_envoy/diagnostics.py | 2 +- .../enphase_envoy/snapshots/test_diagnostics.ambr | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/enphase_envoy/diagnostics.py b/homeassistant/components/enphase_envoy/diagnostics.py index 6fcf73bebe947..97079255876c0 100644 --- a/homeassistant/components/enphase_envoy/diagnostics.py +++ b/homeassistant/components/enphase_envoy/diagnostics.py @@ -64,7 +64,7 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]: "/ivp/ensemble/generator", "/ivp/meters", "/ivp/meters/readings", - "/home,", + "/home", ] for end_point in end_points: diff --git a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr index acbd7de6c0e23..650fb0bb81010 100644 --- a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr +++ b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr @@ -896,8 +896,8 @@ '/api/v1/production/inverters': 'Testing request replies.', '/api/v1/production/inverters_log': '{"headers":{"Hello":"World"},"code":200}', '/api/v1/production_log': '{"headers":{"Hello":"World"},"code":200}', - '/home,': 'Testing request replies.', - '/home,_log': '{"headers":{"Hello":"World"},"code":200}', + '/home': 'Testing request replies.', + '/home_log': '{"headers":{"Hello":"World"},"code":200}', '/info': 'Testing request replies.', '/info_log': '{"headers":{"Hello":"World"},"code":200}', '/ivp/ensemble/dry_contacts': 'Testing request replies.', @@ -1390,7 +1390,7 @@ '/api/v1/production_log': dict({ 'Error': "EnvoyError('Test')", }), - '/home,_log': dict({ + '/home_log': dict({ 'Error': "EnvoyError('Test')", }), '/info_log': dict({ From a54816a6e51cd323dffde68d86a5e76f02a87691 Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Sun, 11 May 2025 17:07:33 +0200 Subject: [PATCH 10/55] Bump pylamarzocco to 2.0.2 (#144635) Co-authored-by: Shay Levy --- homeassistant/components/lamarzocco/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lamarzocco/manifest.json b/homeassistant/components/lamarzocco/manifest.json index fb6a3660c66b6..1fbef07339413 100644 --- a/homeassistant/components/lamarzocco/manifest.json +++ b/homeassistant/components/lamarzocco/manifest.json @@ -37,5 +37,5 @@ "iot_class": "cloud_push", "loggers": ["pylamarzocco"], "quality_scale": "platinum", - "requirements": ["pylamarzocco==2.0.1"] + "requirements": ["pylamarzocco==2.0.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index f88325f892fbd..4e91ce1aca814 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2093,7 +2093,7 @@ pykwb==0.0.8 pylacrosse==0.4 # homeassistant.components.lamarzocco -pylamarzocco==2.0.1 +pylamarzocco==2.0.2 # homeassistant.components.lastfm pylast==5.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f3c4c3ec31a62..14b2e100b6b08 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1708,7 +1708,7 @@ pykrakenapi==0.1.8 pykulersky==0.5.8 # homeassistant.components.lamarzocco -pylamarzocco==2.0.1 +pylamarzocco==2.0.2 # homeassistant.components.lastfm pylast==5.1.0 From ca143222276e9795bb76312a72ccaa05a3eb54d7 Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Sun, 11 May 2025 13:07:35 +0200 Subject: [PATCH 11/55] bump pyenphase to 1.26.1 (#144641) --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 4516a35f4fef5..e978ded73218b 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -7,7 +7,7 @@ "iot_class": "local_polling", "loggers": ["pyenphase"], "quality_scale": "platinum", - "requirements": ["pyenphase==1.26.0"], + "requirements": ["pyenphase==1.26.1"], "zeroconf": [ { "type": "_enphase-envoy._tcp.local." diff --git a/requirements_all.txt b/requirements_all.txt index 4e91ce1aca814..f1af6720606ad 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1955,7 +1955,7 @@ pyeiscp==0.0.7 pyemoncms==0.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.26.0 +pyenphase==1.26.1 # homeassistant.components.envisalink pyenvisalink==4.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 14b2e100b6b08..b8ceb03eecc2e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1600,7 +1600,7 @@ pyeiscp==0.0.7 pyemoncms==0.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.26.0 +pyenphase==1.26.1 # homeassistant.components.everlights pyeverlights==0.1.0 From 081afe6034646968968c7b09e499135f0934620c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 10 May 2025 20:59:01 -0700 Subject: [PATCH 12/55] Bump ical to 9.2.1 (#144642) --- homeassistant/components/google/manifest.json | 2 +- homeassistant/components/local_calendar/manifest.json | 2 +- homeassistant/components/local_todo/manifest.json | 2 +- homeassistant/components/remote_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 32af3e675b321..668ab6e34be0e 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.2.0"] + "requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.2.1"] } diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index eba26e88d5a7e..c3ffce2890bcf 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/local_calendar", "iot_class": "local_polling", "loggers": ["ical"], - "requirements": ["ical==9.2.0"] + "requirements": ["ical==9.2.1"] } diff --git a/homeassistant/components/local_todo/manifest.json b/homeassistant/components/local_todo/manifest.json index fb48ca7233753..f93129be94ca8 100644 --- a/homeassistant/components/local_todo/manifest.json +++ b/homeassistant/components/local_todo/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_todo", "iot_class": "local_polling", - "requirements": ["ical==9.2.0"] + "requirements": ["ical==9.2.1"] } diff --git a/homeassistant/components/remote_calendar/manifest.json b/homeassistant/components/remote_calendar/manifest.json index b31fa3389dc51..4df3f11cf1034 100644 --- a/homeassistant/components/remote_calendar/manifest.json +++ b/homeassistant/components/remote_calendar/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["ical"], "quality_scale": "silver", - "requirements": ["ical==9.2.0"] + "requirements": ["ical==9.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index f1af6720606ad..1fcb506ed9a87 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1200,7 +1200,7 @@ ibmiotf==0.3.4 # homeassistant.components.local_calendar # homeassistant.components.local_todo # homeassistant.components.remote_calendar -ical==9.2.0 +ical==9.2.1 # homeassistant.components.caldav icalendar==6.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b8ceb03eecc2e..2dd70218d3203 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1021,7 +1021,7 @@ ibeacon-ble==1.2.0 # homeassistant.components.local_calendar # homeassistant.components.local_todo # homeassistant.components.remote_calendar -ical==9.2.0 +ical==9.2.1 # homeassistant.components.caldav icalendar==6.1.0 From 0635856761ecda49e217cdedeb3d094eb7f9a516 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Sun, 11 May 2025 12:56:40 +0200 Subject: [PATCH 13/55] Bump python-linkplay to v0.2.5 (#144666) Bump linkplay to 0.2.5 --- homeassistant/components/linkplay/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/linkplay/manifest.json b/homeassistant/components/linkplay/manifest.json index 69a7b71eeb6f4..ac89d2ff39938 100644 --- a/homeassistant/components/linkplay/manifest.json +++ b/homeassistant/components/linkplay/manifest.json @@ -7,6 +7,6 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["linkplay"], - "requirements": ["python-linkplay==0.2.4"], + "requirements": ["python-linkplay==0.2.5"], "zeroconf": ["_linkplay._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 1fcb506ed9a87..21fb24d7001ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2437,7 +2437,7 @@ python-juicenet==1.1.0 python-kasa[speedups]==0.10.2 # homeassistant.components.linkplay -python-linkplay==0.2.4 +python-linkplay==0.2.5 # homeassistant.components.lirc # python-lirc==1.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2dd70218d3203..2530baafd4327 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1980,7 +1980,7 @@ python-juicenet==1.1.0 python-kasa[speedups]==0.10.2 # homeassistant.components.linkplay -python-linkplay==0.2.4 +python-linkplay==0.2.5 # homeassistant.components.matter python-matter-server==7.0.0 From 543348fe581645eee1ca5c5d3205c97e123fa974 Mon Sep 17 00:00:00 2001 From: Ruben van Dijk <15885455+RubenNL@users.noreply.github.com> Date: Sun, 11 May 2025 21:06:04 +0200 Subject: [PATCH 14/55] Close Octoprint aiohttp session on unload (#144670) --- homeassistant/components/octoprint/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 59fd04357eb52..48d81b81f0cc6 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -181,11 +181,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: session = aiohttp.ClientSession(connector=connector) @callback - def _async_close_websession(event: Event) -> None: + def _async_close_websession(event: Event | None = None) -> None: """Close websession.""" session.detach() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_close_websession) + entry.async_on_unload(_async_close_websession) + entry.async_on_unload( + hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, _async_close_websession) + ) client = OctoprintClient( host=entry.data[CONF_HOST], From 358b0c1c17614922a255fe6c4bc4e67da5d3dea6 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 11 May 2025 17:00:44 +0200 Subject: [PATCH 15/55] Bump holidays to 0.72 (#144671) --- 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 d54d69550879d..9809862cd52fe 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.70", "babel==2.15.0"] + "requirements": ["holidays==0.72", "babel==2.15.0"] } diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 60196fb15b70e..542b68169a3ea 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.70"] + "requirements": ["holidays==0.72"] } diff --git a/requirements_all.txt b/requirements_all.txt index 21fb24d7001ab..85af7abd3568f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1158,7 +1158,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.70 +holidays==0.72 # homeassistant.components.frontend home-assistant-frontend==20250509.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2530baafd4327..d934cc39e8656 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -988,7 +988,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.70 +holidays==0.72 # homeassistant.components.frontend home-assistant-frontend==20250509.0 From da79d5b2e3c3f05264aeb6ff5e85e9e8604ad7ff Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sun, 11 May 2025 18:01:51 +0300 Subject: [PATCH 16/55] Fix strings typo for Comelit (#144672) --- homeassistant/components/comelit/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/comelit/strings.json b/homeassistant/components/comelit/strings.json index 2076ecb5c1e3a..8f2ae1433e5f2 100644 --- a/homeassistant/components/comelit/strings.json +++ b/homeassistant/components/comelit/strings.json @@ -76,7 +76,7 @@ "cannot_authenticate": { "message": "Error authenticating" }, - "updated_failed": { + "update_failed": { "message": "Failed to update data: {error}" } } From cf0911cc5697fbc68b8cbc61cd6f469e9813a003 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sun, 11 May 2025 22:00:21 +0300 Subject: [PATCH 17/55] Avoid closing shared session for Comelit (#144682) --- homeassistant/components/comelit/__init__.py | 1 - homeassistant/components/comelit/config_flow.py | 1 - 2 files changed, 2 deletions(-) diff --git a/homeassistant/components/comelit/__init__.py b/homeassistant/components/comelit/__init__.py index c2a7498afecaf..23be67fc1a128 100644 --- a/homeassistant/components/comelit/__init__.py +++ b/homeassistant/components/comelit/__init__.py @@ -77,6 +77,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: ComelitConfigEntry) -> coordinator = entry.runtime_data if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms): await coordinator.api.logout() - await coordinator.api.close() return unload_ok diff --git a/homeassistant/components/comelit/config_flow.py b/homeassistant/components/comelit/config_flow.py index f6bda97a781c4..10180236f791b 100644 --- a/homeassistant/components/comelit/config_flow.py +++ b/homeassistant/components/comelit/config_flow.py @@ -73,7 +73,6 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, ) from err finally: await api.logout() - await api.close() return {"title": data[CONF_HOST]} From 47b45444eb8f39f5a09dbe15ad8fe96799a2ff4f Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Mon, 12 May 2025 09:39:30 +0200 Subject: [PATCH 18/55] Fix wrong state in Husqvarna Automower (#144684) --- .../components/husqvarna_automower/lawn_mower.py | 8 ++++---- tests/components/husqvarna_automower/test_lawn_mower.py | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/lawn_mower.py b/homeassistant/components/husqvarna_automower/lawn_mower.py index 9ae214524a7a4..5a728265651ee 100644 --- a/homeassistant/components/husqvarna_automower/lawn_mower.py +++ b/homeassistant/components/husqvarna_automower/lawn_mower.py @@ -110,14 +110,14 @@ def activity(self) -> LawnMowerActivity: mower_attributes = self.mower_attributes if mower_attributes.mower.state in PAUSED_STATES: return LawnMowerActivity.PAUSED - if mower_attributes.mower.state in MowerStates.IN_OPERATION: - if mower_attributes.mower.activity == MowerActivities.GOING_HOME: - return LawnMowerActivity.RETURNING - return LawnMowerActivity.MOWING if (mower_attributes.mower.state == "RESTRICTED") or ( mower_attributes.mower.activity in DOCKED_ACTIVITIES ): return LawnMowerActivity.DOCKED + if mower_attributes.mower.state in MowerStates.IN_OPERATION: + if mower_attributes.mower.activity == MowerActivities.GOING_HOME: + return LawnMowerActivity.RETURNING + return LawnMowerActivity.MOWING return LawnMowerActivity.ERROR @property diff --git a/tests/components/husqvarna_automower/test_lawn_mower.py b/tests/components/husqvarna_automower/test_lawn_mower.py index 12c53d709ca63..de7479bf9086d 100644 --- a/tests/components/husqvarna_automower/test_lawn_mower.py +++ b/tests/components/husqvarna_automower/test_lawn_mower.py @@ -37,6 +37,11 @@ MowerStates.IN_OPERATION, LawnMowerActivity.MOWING, ), + ( + MowerActivities.PARKED_IN_CS, + MowerStates.IN_OPERATION, + LawnMowerActivity.DOCKED, + ), ], ) async def test_lawn_mower_states( From e465276464402da34c9b9067a7a27950ebd4a374 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 11 May 2025 18:01:20 -0700 Subject: [PATCH 19/55] Bump voluptuous-openapi to 0.1.0 (#144703) --- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d457184d4f540..a42910fc7e2ae 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -70,7 +70,7 @@ typing-extensions>=4.13.0,<5.0 ulid-transform==1.4.0 urllib3>=1.26.5,<2 uv==0.7.1 -voluptuous-openapi==0.0.7 +voluptuous-openapi==0.1.0 voluptuous-serialize==2.6.0 voluptuous==0.15.2 webrtc-models==0.3.0 diff --git a/pyproject.toml b/pyproject.toml index fa960f6f815b4..ad3f9ad86e2c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ dependencies = [ "uv==0.7.1", "voluptuous==0.15.2", "voluptuous-serialize==2.6.0", - "voluptuous-openapi==0.0.7", + "voluptuous-openapi==0.1.0", "yarl==1.20.0", "webrtc-models==0.3.0", "zeroconf==0.147.0", diff --git a/requirements.txt b/requirements.txt index e87c17503360e..27095417cb01c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -57,7 +57,7 @@ urllib3>=1.26.5,<2 uv==0.7.1 voluptuous==0.15.2 voluptuous-serialize==2.6.0 -voluptuous-openapi==0.0.7 +voluptuous-openapi==0.1.0 yarl==1.20.0 webrtc-models==0.3.0 zeroconf==0.147.0 From f1a3d62db2c772132fc05c72163bbd484a32b804 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 12 May 2025 00:37:57 -0700 Subject: [PATCH 20/55] Bump ical to 9.2.2 (#144713) --- homeassistant/components/google/manifest.json | 2 +- homeassistant/components/local_calendar/manifest.json | 2 +- homeassistant/components/local_todo/manifest.json | 2 +- homeassistant/components/remote_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 668ab6e34be0e..296ac519e1dd3 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.2.1"] + "requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.2.2"] } diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index c3ffce2890bcf..2fa603d51ff16 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/local_calendar", "iot_class": "local_polling", "loggers": ["ical"], - "requirements": ["ical==9.2.1"] + "requirements": ["ical==9.2.2"] } diff --git a/homeassistant/components/local_todo/manifest.json b/homeassistant/components/local_todo/manifest.json index f93129be94ca8..735c11e645aa2 100644 --- a/homeassistant/components/local_todo/manifest.json +++ b/homeassistant/components/local_todo/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_todo", "iot_class": "local_polling", - "requirements": ["ical==9.2.1"] + "requirements": ["ical==9.2.2"] } diff --git a/homeassistant/components/remote_calendar/manifest.json b/homeassistant/components/remote_calendar/manifest.json index 4df3f11cf1034..33a46ea3dc87a 100644 --- a/homeassistant/components/remote_calendar/manifest.json +++ b/homeassistant/components/remote_calendar/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["ical"], "quality_scale": "silver", - "requirements": ["ical==9.2.1"] + "requirements": ["ical==9.2.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 85af7abd3568f..33b97d2ea4d13 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1200,7 +1200,7 @@ ibmiotf==0.3.4 # homeassistant.components.local_calendar # homeassistant.components.local_todo # homeassistant.components.remote_calendar -ical==9.2.1 +ical==9.2.2 # homeassistant.components.caldav icalendar==6.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d934cc39e8656..12be241c790fd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1021,7 +1021,7 @@ ibeacon-ble==1.2.0 # homeassistant.components.local_calendar # homeassistant.components.local_todo # homeassistant.components.remote_calendar -ical==9.2.1 +ical==9.2.2 # homeassistant.components.caldav icalendar==6.1.0 From 41a503f76fdf69f5b465c13af457d0a6b9751111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Matheson=20Wergeland?= Date: Mon, 12 May 2025 19:45:34 +0200 Subject: [PATCH 21/55] Bump gcal-sync to 7.0.1 (#144718) Co-authored-by: Allen Porter --- homeassistant/components/google/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 296ac519e1dd3..b43ded01d6edc 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.2.2"] + "requirements": ["gcal-sync==7.0.1", "oauth2client==4.1.3", "ical==9.2.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 33b97d2ea4d13..a6188612341cd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -986,7 +986,7 @@ gardena-bluetooth==1.6.0 gassist-text==0.0.12 # homeassistant.components.google -gcal-sync==7.0.0 +gcal-sync==7.0.1 # homeassistant.components.geniushub geniushub-client==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 12be241c790fd..4aedcccd17460 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -840,7 +840,7 @@ gardena-bluetooth==1.6.0 gassist-text==0.0.12 # homeassistant.components.google -gcal-sync==7.0.0 +gcal-sync==7.0.1 # homeassistant.components.geniushub geniushub-client==0.7.1 From a4a7601f9ff2e40556be58eb2d99b775e56b49a7 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 12 May 2025 12:23:44 +0300 Subject: [PATCH 22/55] Bump aiocomelit to 0.12.1 (#144720) --- homeassistant/components/comelit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/comelit/manifest.json b/homeassistant/components/comelit/manifest.json index 2097d1c25f6fa..58f347b4ba3d5 100644 --- a/homeassistant/components/comelit/manifest.json +++ b/homeassistant/components/comelit/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_polling", "loggers": ["aiocomelit"], "quality_scale": "bronze", - "requirements": ["aiocomelit==0.12.0"] + "requirements": ["aiocomelit==0.12.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index a6188612341cd..67230b4bfcb5d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -214,7 +214,7 @@ aiobafi6==0.9.0 aiobotocore==2.21.1 # homeassistant.components.comelit -aiocomelit==0.12.0 +aiocomelit==0.12.1 # homeassistant.components.dhcp aiodhcpwatcher==1.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4aedcccd17460..3de21584bfe66 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -202,7 +202,7 @@ aiobafi6==0.9.0 aiobotocore==2.21.1 # homeassistant.components.comelit -aiocomelit==0.12.0 +aiocomelit==0.12.1 # homeassistant.components.dhcp aiodhcpwatcher==1.1.1 From f25e50b017e3f0fecb91c819d34555102729970e Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 15 May 2025 10:56:54 +0200 Subject: [PATCH 23/55] Fix Netgear handeling of missing MAC in device registry (#144722) --- homeassistant/components/netgear/router.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index d81f556193b51..23ee47e7a2de0 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -150,7 +150,11 @@ async def async_setup(self) -> bool: if device_entry.via_device_id is None: continue # do not add the router itself - device_mac = dict(device_entry.connections)[dr.CONNECTION_NETWORK_MAC] + device_mac = dict(device_entry.connections).get( + dr.CONNECTION_NETWORK_MAC + ) + if device_mac is None: + continue self.devices[device_mac] = { "mac": device_mac, "name": device_entry.name, From b69ebdaecb853a52ca696a18d022a3e9bab4d1cd Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 12 May 2025 21:18:55 +0200 Subject: [PATCH 24/55] Repair Z-Wave unknown controller (#144738) Co-authored-by: Franck Nijhof --- homeassistant/components/zwave_js/__init__.py | 33 +++++ homeassistant/components/zwave_js/repairs.py | 44 +++++++ .../components/zwave_js/strings.json | 11 ++ tests/components/zwave_js/conftest.py | 6 +- tests/components/zwave_js/test_repairs.py | 116 ++++++++++++++++++ 5 files changed, 209 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index e73bd01deba29..349baecc21d7c 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -278,6 +278,39 @@ async def handle_ha_shutdown(event: Event) -> None: # and we'll handle the clean up below. await driver_events.setup(driver) + if (old_unique_id := entry.unique_id) is not None and old_unique_id != ( + new_unique_id := str(driver.controller.home_id) + ): + device_registry = dr.async_get(hass) + controller_model = "Unknown model" + if ( + (own_node := driver.controller.own_node) + and ( + controller_device_entry := device_registry.async_get_device( + identifiers={get_device_id(driver, own_node)} + ) + ) + and (model := controller_device_entry.model) + ): + controller_model = model + async_create_issue( + hass, + DOMAIN, + f"migrate_unique_id.{entry.entry_id}", + data={ + "config_entry_id": entry.entry_id, + "config_entry_title": entry.title, + "controller_model": controller_model, + "new_unique_id": new_unique_id, + "old_unique_id": old_unique_id, + }, + is_fixable=True, + severity=IssueSeverity.ERROR, + translation_key="migrate_unique_id", + ) + else: + async_delete_issue(hass, DOMAIN, f"migrate_unique_id.{entry.entry_id}") + # If the listen task is already failed, we need to raise ConfigEntryNotReady if listen_task.done(): listen_error, error_message = _get_listen_task_error(listen_task) diff --git a/homeassistant/components/zwave_js/repairs.py b/homeassistant/components/zwave_js/repairs.py index e515ae105496c..f1deb91d869eb 100644 --- a/homeassistant/components/zwave_js/repairs.py +++ b/homeassistant/components/zwave_js/repairs.py @@ -57,6 +57,47 @@ async def async_step_ignore( ) +class MigrateUniqueIDFlow(RepairsFlow): + """Handler for an issue fixing flow.""" + + def __init__(self, data: dict[str, str]) -> None: + """Initialize.""" + self.description_placeholders: dict[str, str] = { + "config_entry_title": data["config_entry_title"], + "controller_model": data["controller_model"], + "new_unique_id": data["new_unique_id"], + "old_unique_id": data["old_unique_id"], + } + self._config_entry_id: str = data["config_entry_id"] + + async def async_step_init( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Handle the first step of a fix flow.""" + return await self.async_step_confirm() + + async def async_step_confirm( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Handle the confirm step of a fix flow.""" + if user_input is not None: + config_entry = self.hass.config_entries.async_get_entry( + self._config_entry_id + ) + # If config entry was removed, we can ignore the issue. + if config_entry is not None: + self.hass.config_entries.async_update_entry( + config_entry, + unique_id=self.description_placeholders["new_unique_id"], + ) + return self.async_create_entry(data={}) + + return self.async_show_form( + step_id="confirm", + description_placeholders=self.description_placeholders, + ) + + async def async_create_fix_flow( hass: HomeAssistant, issue_id: str, data: dict[str, str] | None ) -> RepairsFlow: @@ -65,4 +106,7 @@ async def async_create_fix_flow( if issue_id.split(".")[0] == "device_config_file_changed": assert data return DeviceConfigFileChangedFlow(data) + if issue_id.split(".")[0] == "migrate_unique_id": + assert data + return MigrateUniqueIDFlow(data) return ConfirmRepairFlow() diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 56ae4e124013f..2a8e2c6ea2dfa 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -273,6 +273,17 @@ "invalid_server_version": { "description": "The version of Z-Wave Server you are currently running is too old for this version of Home Assistant. Please update the Z-Wave Server to the latest version to fix this issue.", "title": "Newer version of Z-Wave Server needed" + }, + "migrate_unique_id": { + "fix_flow": { + "step": { + "confirm": { + "description": "A Z-Wave controller of model {controller_model} with a different ID ({new_unique_id}) than the previously connected controller ({old_unique_id}) was connected to the {config_entry_title} configuration entry.\n\nReasons for a different controller ID could be:\n\n1. The controller was factory reset, with a 3rd party application.\n2. A controller Non Volatile Memory (NVM) backup was restored to the controller, with a 3rd party application.\n3. A different controller was connected to this configuration entry.\n\nIf a different controller was connected, you should instead set up a new configuration entry for the new controller.\n\nIf you are sure that the current controller is the correct controller you can confirm this by pressing Submit, and the configuration entry will remember the new controller ID.", + "title": "An unknown controller was detected" + } + } + }, + "title": "An unknown controller was detected" } }, "services": { diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index e4e757ad36300..609a0229bcfd7 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -843,7 +843,11 @@ async def integration_fixture( platforms: list[Platform], ) -> MockConfigEntry: """Set up the zwave_js integration.""" - entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) + entry = MockConfigEntry( + domain="zwave_js", + data={"url": "ws://test.org"}, + unique_id=str(client.driver.controller.home_id), + ) entry.add_to_hass(hass) with patch("homeassistant.components.zwave_js.PLATFORMS", platforms): await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/zwave_js/test_repairs.py b/tests/components/zwave_js/test_repairs.py index 1d0f74c72695f..d8c3de92b3b2f 100644 --- a/tests/components/zwave_js/test_repairs.py +++ b/tests/components/zwave_js/test_repairs.py @@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, issue_registry as ir +from tests.common import MockConfigEntry from tests.components.repairs import ( async_process_repairs_platforms, process_repair_fix_flow, @@ -268,3 +269,118 @@ async def test_abort_confirm( assert data["type"] == "abort" assert data["reason"] == "cannot_connect" assert data["description_placeholders"] == {"device_name": device.name} + + +@pytest.mark.usefixtures("client") +async def test_migrate_unique_id( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test the migrate unique id flow.""" + old_unique_id = "123456789" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + data={ + "url": "ws://test.org", + }, + unique_id=old_unique_id, + ) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + http_client = await hass_client() + + # Assert the issue is present + await ws_client.send_json_auto_id({"type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 1 + issue = msg["result"]["issues"][0] + issue_id = issue["issue_id"] + assert issue_id == f"migrate_unique_id.{config_entry.entry_id}" + + data = await start_repair_fix_flow(http_client, DOMAIN, issue_id) + + flow_id = data["flow_id"] + assert data["step_id"] == "confirm" + assert data["description_placeholders"] == { + "config_entry_title": "Z-Wave JS", + "controller_model": "ZW090", + "new_unique_id": "3245146787", + "old_unique_id": old_unique_id, + } + + # Apply fix + data = await process_repair_fix_flow(http_client, flow_id) + + assert data["type"] == "create_entry" + assert config_entry.unique_id == "3245146787" + + await ws_client.send_json_auto_id({"type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 0 + + +@pytest.mark.usefixtures("client") +async def test_migrate_unique_id_missing_config_entry( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test the migrate unique id flow with missing config entry.""" + old_unique_id = "123456789" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + data={ + "url": "ws://test.org", + }, + unique_id=old_unique_id, + ) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + http_client = await hass_client() + + # Assert the issue is present + await ws_client.send_json_auto_id({"type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 1 + issue = msg["result"]["issues"][0] + issue_id = issue["issue_id"] + assert issue_id == f"migrate_unique_id.{config_entry.entry_id}" + + await hass.config_entries.async_remove(config_entry.entry_id) + + assert not hass.config_entries.async_get_entry(config_entry.entry_id) + + data = await start_repair_fix_flow(http_client, DOMAIN, issue_id) + + flow_id = data["flow_id"] + assert data["step_id"] == "confirm" + assert data["description_placeholders"] == { + "config_entry_title": "Z-Wave JS", + "controller_model": "ZW090", + "new_unique_id": "3245146787", + "old_unique_id": old_unique_id, + } + + # Apply fix + data = await process_repair_fix_flow(http_client, flow_id) + + assert data["type"] == "create_entry" + + await ws_client.send_json_auto_id({"type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 0 From 9de1d3b1434df9881d21bf6039be530d17977fff Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 12 May 2025 19:37:45 +0200 Subject: [PATCH 25/55] Fill in Plaato URL via placeholders (#144754) --- homeassistant/components/plaato/config_flow.py | 7 ++++++- homeassistant/components/plaato/strings.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plaato/config_flow.py b/homeassistant/components/plaato/config_flow.py index 9adfb4a14fe1c..6a05b209f2cb5 100644 --- a/homeassistant/components/plaato/config_flow.py +++ b/homeassistant/components/plaato/config_flow.py @@ -32,6 +32,8 @@ PLACEHOLDER_WEBHOOK_URL, ) +AUTH_TOKEN_URL = "https://intercom.help/plaato/en/articles/5004720-auth_token" + class PlaatoConfigFlow(ConfigFlow, domain=DOMAIN): """Handles a Plaato config flow.""" @@ -153,7 +155,10 @@ async def _show_api_method_form( step_id="api_method", data_schema=data_schema, errors=errors, - description_placeholders={PLACEHOLDER_DEVICE_TYPE: device_type.name}, + description_placeholders={ + PLACEHOLDER_DEVICE_TYPE: device_type.name, + "auth_token_url": AUTH_TOKEN_URL, + }, ) async def _get_webhook_id(self): diff --git a/homeassistant/components/plaato/strings.json b/homeassistant/components/plaato/strings.json index 2356825811863..3fb593a9c731c 100644 --- a/homeassistant/components/plaato/strings.json +++ b/homeassistant/components/plaato/strings.json @@ -11,7 +11,7 @@ }, "api_method": { "title": "Select API method", - "description": "To be able to query the API an `auth_token` is required which can be obtained by following [these](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instructions\n\n Selected device: **{device_type}** \n\nIf you rather use the built in webhook method (Airlock only) please check the box below and leave Auth Token blank", + "description": "To be able to query the API an 'auth token' is required which can be obtained by following [these instructions]({auth_token_url})\n\nSelected device: **{device_type}** \n\nIf you prefer to use the built-in webhook method (Airlock only) please check the box below and leave 'Auth token' blank", "data": { "use_webhook": "Use webhook", "token": "Paste Auth Token here" From 139b48440f8506aa56ce17328a4bc43d24c9d10c Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 16 May 2025 12:58:28 +0200 Subject: [PATCH 26/55] Cleanup wrongly combined Reolink devices (#144771) --- homeassistant/components/reolink/__init__.py | 119 ++++++++++++------- homeassistant/components/reolink/util.py | 9 +- tests/components/reolink/test_init.py | 62 +++++++++- 3 files changed, 146 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index 433af396d6314..48b5dc1a3d61c 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -364,53 +364,90 @@ def migrate_entity_ids( devices = dr.async_entries_for_config_entry(device_reg, config_entry_id) ch_device_ids = {} for device in devices: - (device_uid, ch, is_chime) = get_device_uid_and_ch(device, host) + for dev_id in device.identifiers: + (device_uid, ch, is_chime) = get_device_uid_and_ch(dev_id, host) + if not device_uid: + continue - if host.api.supported(None, "UID") and device_uid[0] != host.unique_id: - if ch is None: - new_device_id = f"{host.unique_id}" - else: - new_device_id = f"{host.unique_id}_{device_uid[1]}" - _LOGGER.debug( - "Updating Reolink device UID from %s to %s", device_uid, new_device_id - ) - new_identifiers = {(DOMAIN, new_device_id)} - device_reg.async_update_device(device.id, new_identifiers=new_identifiers) - - if ch is None or is_chime: - continue # Do not consider the NVR itself or chimes - - # Check for wrongfully added MAC of the NVR/Hub to the camera - # Can be removed in HA 2025.12 - host_connnection = (CONNECTION_NETWORK_MAC, host.api.mac_address) - if host_connnection in device.connections: - new_connections = device.connections.copy() - new_connections.remove(host_connnection) - device_reg.async_update_device(device.id, new_connections=new_connections) - - ch_device_ids[device.id] = ch - if host.api.supported(ch, "UID") and device_uid[1] != host.api.camera_uid(ch): - if host.api.supported(None, "UID"): - new_device_id = f"{host.unique_id}_{host.api.camera_uid(ch)}" - else: - new_device_id = f"{device_uid[0]}_{host.api.camera_uid(ch)}" - _LOGGER.debug( - "Updating Reolink device UID from %s to %s", device_uid, new_device_id - ) - new_identifiers = {(DOMAIN, new_device_id)} - existing_device = device_reg.async_get_device(identifiers=new_identifiers) - if existing_device is None: + if host.api.supported(None, "UID") and device_uid[0] != host.unique_id: + if ch is None: + new_device_id = f"{host.unique_id}" + else: + new_device_id = f"{host.unique_id}_{device_uid[1]}" + _LOGGER.debug( + "Updating Reolink device UID from %s to %s", + device_uid, + new_device_id, + ) + new_identifiers = {(DOMAIN, new_device_id)} device_reg.async_update_device( device.id, new_identifiers=new_identifiers ) - else: - _LOGGER.warning( - "Reolink device with uid %s already exists, " - "removing device with uid %s", - new_device_id, + + if ch is None or is_chime: + continue # Do not consider the NVR itself or chimes + + # Check for wrongfully combined host with NVR entities in one device + # Can be removed in HA 2025.12 + if (DOMAIN, host.unique_id) in device.identifiers: + new_identifiers = device.identifiers.copy() + for old_id in device.identifiers: + if old_id[0] == DOMAIN and old_id[1] != host.unique_id: + new_identifiers.remove(old_id) + _LOGGER.debug( + "Updating Reolink device identifiers from %s to %s", + device.identifiers, + new_identifiers, + ) + device_reg.async_update_device( + device.id, new_identifiers=new_identifiers + ) + break + + # Check for wrongfully added MAC of the NVR/Hub to the camera + # Can be removed in HA 2025.12 + host_connnection = (CONNECTION_NETWORK_MAC, host.api.mac_address) + if host_connnection in device.connections: + new_connections = device.connections.copy() + new_connections.remove(host_connnection) + _LOGGER.debug( + "Updating Reolink device connections from %s to %s", + device.connections, + new_connections, + ) + device_reg.async_update_device( + device.id, new_connections=new_connections + ) + + ch_device_ids[device.id] = ch + if host.api.supported(ch, "UID") and device_uid[1] != host.api.camera_uid( + ch + ): + if host.api.supported(None, "UID"): + new_device_id = f"{host.unique_id}_{host.api.camera_uid(ch)}" + else: + new_device_id = f"{device_uid[0]}_{host.api.camera_uid(ch)}" + _LOGGER.debug( + "Updating Reolink device UID from %s to %s", device_uid, + new_device_id, + ) + new_identifiers = {(DOMAIN, new_device_id)} + existing_device = device_reg.async_get_device( + identifiers=new_identifiers ) - device_reg.async_remove_device(device.id) + if existing_device is None: + device_reg.async_update_device( + device.id, new_identifiers=new_identifiers + ) + else: + _LOGGER.warning( + "Reolink device with uid %s already exists, " + "removing device with uid %s", + new_device_id, + device_uid, + ) + device_reg.async_remove_device(device.id) entity_reg = er.async_get(hass) entities = er.async_entries_for_config_entry(entity_reg, config_entry_id) diff --git a/homeassistant/components/reolink/util.py b/homeassistant/components/reolink/util.py index 17e666ac52c36..a80e9f8962cb1 100644 --- a/homeassistant/components/reolink/util.py +++ b/homeassistant/components/reolink/util.py @@ -76,13 +76,18 @@ def get_store(hass: HomeAssistant, config_entry_id: str) -> Store[str]: def get_device_uid_and_ch( - device: dr.DeviceEntry, host: ReolinkHost + device: dr.DeviceEntry | tuple[str, str], host: ReolinkHost ) -> tuple[list[str], int | None, bool]: """Get the channel and the split device_uid from a reolink DeviceEntry.""" device_uid = [] is_chime = False - for dev_id in device.identifiers: + if isinstance(device, dr.DeviceEntry): + dev_ids = device.identifiers + else: + dev_ids = {device} + + for dev_id in dev_ids: if dev_id[0] == DOMAIN: device_uid = dev_id[1].split("_") if device_uid[0] == host.unique_id: diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index 6b57c1c253f60..f2ae22913ad43 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -630,7 +630,7 @@ async def test_cleanup_mac_connection( domain = Platform.SWITCH dev_entry = device_registry.async_get_or_create( - identifiers={(DOMAIN, dev_id)}, + identifiers={(DOMAIN, dev_id), ("OTHER_INTEGRATION", "SOME_ID")}, connections={(CONNECTION_NETWORK_MAC, TEST_MAC)}, config_entry_id=config_entry.entry_id, disabled_by=None, @@ -664,6 +664,66 @@ async def test_cleanup_mac_connection( reolink_connect.baichuan.mac_address.return_value = TEST_MAC_CAM +async def test_cleanup_combined_with_NVR( + hass: HomeAssistant, + config_entry: MockConfigEntry, + reolink_connect: MagicMock, + entity_registry: er.EntityRegistry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test cleanup of the device registry if IPC camera device was combined with the NVR device.""" + reolink_connect.channels = [0] + reolink_connect.baichuan.mac_address.return_value = None + entity_id = f"{TEST_UID}_{TEST_UID_CAM}_record_audio" + dev_id = f"{TEST_UID}_{TEST_UID_CAM}" + domain = Platform.SWITCH + start_identifiers = { + (DOMAIN, dev_id), + (DOMAIN, TEST_UID), + ("OTHER_INTEGRATION", "SOME_ID"), + } + + dev_entry = device_registry.async_get_or_create( + identifiers=start_identifiers, + connections={(CONNECTION_NETWORK_MAC, TEST_MAC)}, + config_entry_id=config_entry.entry_id, + disabled_by=None, + ) + + entity_registry.async_get_or_create( + domain=domain, + platform=DOMAIN, + unique_id=entity_id, + config_entry=config_entry, + suggested_object_id=entity_id, + disabled_by=None, + device_id=dev_entry.id, + ) + + assert entity_registry.async_get_entity_id(domain, DOMAIN, entity_id) + device = device_registry.async_get_device(identifiers={(DOMAIN, dev_id)}) + assert device + assert device.identifiers == start_identifiers + + # setup CH 0 and host entities/device + with patch("homeassistant.components.reolink.PLATFORMS", [domain]): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert entity_registry.async_get_entity_id(domain, DOMAIN, entity_id) + device = device_registry.async_get_device(identifiers={(DOMAIN, dev_id)}) + assert device + assert device.identifiers == {(DOMAIN, dev_id)} + host_device = device_registry.async_get_device(identifiers={(DOMAIN, TEST_UID)}) + assert host_device + assert host_device.identifiers == { + (DOMAIN, TEST_UID), + ("OTHER_INTEGRATION", "SOME_ID"), + } + + reolink_connect.baichuan.mac_address.return_value = TEST_MAC_CAM + + async def test_no_repair_issue( hass: HomeAssistant, config_entry: MockConfigEntry, issue_registry: ir.IssueRegistry ) -> None: From c373fa9296922ba568d6242025d9f05a3733dd91 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Fri, 16 May 2025 10:26:00 +0200 Subject: [PATCH 27/55] Do not show an empty component name on MQTT device subentries not as `None` if it is not set (#144792) --- homeassistant/components/mqtt/config_flow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 74f55afabaa22..4445462003f50 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -2063,7 +2063,7 @@ def _show_update_or_delete_form(self, step_id: str) -> SubentryFlowResult: entities = [ SelectOptionDict( value=key, - label=f"{device_name} {component_data.get(CONF_NAME, '-')}" + label=f"{device_name} {component_data.get(CONF_NAME, '-') or '-'}" f" ({component_data[CONF_PLATFORM]})", ) for key, component_data in self._subentry_data["components"].items() @@ -2295,7 +2295,8 @@ async def async_step_summary_menu( self._component_id = None mqtt_device = self._subentry_data[CONF_DEVICE][CONF_NAME] mqtt_items = ", ".join( - f"{mqtt_device} {component_data.get(CONF_NAME, '-')} ({component_data[CONF_PLATFORM]})" + f"{mqtt_device} {component_data.get(CONF_NAME, '-') or '-'} " + f"({component_data[CONF_PLATFORM]})" for component_data in self._subentry_data["components"].values() ) menu_options = [ From d82feb807f320f8b4de89a60ea255e420c66bf2b Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Tue, 13 May 2025 10:46:46 +0200 Subject: [PATCH 28/55] Fix blocking call in azure storage (#144803) --- .../components/azure_storage/__init__.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/azure_storage/__init__.py b/homeassistant/components/azure_storage/__init__.py index f22e7b70c1291..00e419fd3c9b2 100644 --- a/homeassistant/components/azure_storage/__init__.py +++ b/homeassistant/components/azure_storage/__init__.py @@ -39,11 +39,20 @@ async def async_setup_entry( session = async_create_clientsession( hass, timeout=ClientTimeout(connect=10, total=12 * 60 * 60) ) - container_client = ContainerClient( - account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/", - container_name=entry.data[CONF_CONTAINER_NAME], - credential=entry.data[CONF_STORAGE_ACCOUNT_KEY], - transport=AioHttpTransport(session=session), + + def create_container_client() -> ContainerClient: + """Create a ContainerClient.""" + + return ContainerClient( + account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/", + container_name=entry.data[CONF_CONTAINER_NAME], + credential=entry.data[CONF_STORAGE_ACCOUNT_KEY], + transport=AioHttpTransport(session=session), + ) + + # has a blocking call to open in cpython + container_client: ContainerClient = await hass.async_add_executor_job( + create_container_client ) try: From 6c3a4f17f08af42c471e221c5ff9e02804132391 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 13 May 2025 13:12:00 +0200 Subject: [PATCH 29/55] Fix Z-Wave unique id after controller reset (#144813) --- homeassistant/components/zwave_js/api.py | 23 +++++++++++ .../components/zwave_js/config_flow.py | 26 +----------- homeassistant/components/zwave_js/helpers.py | 26 ++++++++++++ tests/components/zwave_js/conftest.py | 40 ++++++++++++++++++ tests/components/zwave_js/test_api.py | 40 ++++++++++++++++++ tests/components/zwave_js/test_config_flow.py | 41 +------------------ 6 files changed, 133 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index f43977372345a..ddfd0cb003db8 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -71,6 +71,7 @@ ActiveConnection, ) from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -88,13 +89,16 @@ DATA_CLIENT, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY, + LOGGER, RESTORE_NVM_DRIVER_READY_TIMEOUT, USER_AGENT, ) from .helpers import ( + CannotConnect, async_enable_statistics, async_get_node_from_device_id, async_get_provisioning_entry_from_device_id, + async_get_version_info, get_device_id, ) @@ -2857,6 +2861,25 @@ def set_driver_ready(event: dict) -> None: async with asyncio.timeout(HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT): await wait_driver_ready.wait() + # When resetting the controller, the controller home id is also changed. + # The controller state in the client is stale after resetting the controller, + # so get the new home id with a new client using the helper function. + # The client state will be refreshed by reloading the config entry, + # after the unique id of the config entry has been updated. + try: + version_info = await async_get_version_info(hass, entry.data[CONF_URL]) + except CannotConnect: + # Just log this error, as there's nothing to do about it here. + # The stale unique id needs to be handled by a repair flow, + # after the config entry has been reloaded. + LOGGER.error( + "Failed to get server version, cannot update config entry" + "unique id with new home id, after controller reset" + ) + else: + hass.config_entries.async_update_entry( + entry, unique_id=str(version_info.home_id) + ) await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 407af9e902b0d..e52a5e784e8fb 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -9,14 +9,13 @@ from pathlib import Path from typing import Any -import aiohttp from awesomeversion import AwesomeVersion from serial.tools import list_ports import voluptuous as vol from zwave_js_server.client import Client from zwave_js_server.exceptions import FailedCommand from zwave_js_server.model.driver import Driver -from zwave_js_server.version import VersionInfo, get_server_version +from zwave_js_server.version import VersionInfo from homeassistant.components import usb from homeassistant.components.hassio import ( @@ -36,7 +35,6 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import AbortFlow from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.hassio import is_hassio from homeassistant.helpers.service_info.hassio import HassioServiceInfo from homeassistant.helpers.service_info.usb import UsbServiceInfo @@ -69,6 +67,7 @@ DOMAIN, RESTORE_NVM_DRIVER_READY_TIMEOUT, ) +from .helpers import CannotConnect, async_get_version_info _LOGGER = logging.getLogger(__name__) @@ -79,7 +78,6 @@ ADDON_SETUP_TIMEOUT_ROUNDS = 40 CONF_EMULATE_HARDWARE = "emulate_hardware" CONF_LOG_LEVEL = "log_level" -SERVER_VERSION_TIMEOUT = 10 ADDON_LOG_LEVELS = { "error": "Error", @@ -130,22 +128,6 @@ async def validate_input(hass: HomeAssistant, user_input: dict) -> VersionInfo: raise InvalidInput("cannot_connect") from err -async def async_get_version_info(hass: HomeAssistant, ws_address: str) -> VersionInfo: - """Return Z-Wave JS version info.""" - try: - async with asyncio.timeout(SERVER_VERSION_TIMEOUT): - version_info: VersionInfo = await get_server_version( - ws_address, async_get_clientsession(hass) - ) - except (TimeoutError, aiohttp.ClientError) as err: - # We don't want to spam the log if the add-on isn't started - # or takes a long time to start. - _LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err) - raise CannotConnect from err - - return version_info - - def get_usb_ports() -> dict[str, str]: """Return a dict of USB ports and their friendly names.""" ports = list_ports.comports() @@ -1357,10 +1339,6 @@ def _get_driver(self) -> Driver: return client.driver -class CannotConnect(HomeAssistantError): - """Indicate connection error.""" - - class InvalidInput(HomeAssistantError): """Error to indicate input data is invalid.""" diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index ded87b590a48c..bfa093f7db950 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -2,11 +2,13 @@ from __future__ import annotations +import asyncio from collections.abc import Callable from dataclasses import astuple, dataclass import logging from typing import Any, cast +import aiohttp import voluptuous as vol from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ( @@ -25,6 +27,7 @@ ValueDataType, get_value_id_str, ) +from zwave_js_server.version import VersionInfo, get_server_version from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigEntryState @@ -38,6 +41,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.group import expand_entity_ids from homeassistant.helpers.typing import ConfigType, VolSchemaType @@ -54,6 +58,8 @@ LOGGER, ) +SERVER_VERSION_TIMEOUT = 10 + @dataclass class ZwaveValueID: @@ -568,3 +574,23 @@ def get_network_identifier_for_notification( return f"`{config_entry.title}`, with the home ID `{home_id}`," return f"with the home ID `{home_id}`" return "" + + +async def async_get_version_info(hass: HomeAssistant, ws_address: str) -> VersionInfo: + """Return Z-Wave JS version info.""" + try: + async with asyncio.timeout(SERVER_VERSION_TIMEOUT): + version_info: VersionInfo = await get_server_version( + ws_address, async_get_clientsession(hass) + ) + except (TimeoutError, aiohttp.ClientError) as err: + # We don't want to spam the log if the add-on isn't started + # or takes a long time to start. + LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err) + raise CannotConnect from err + + return version_info + + +class CannotConnect(HomeAssistantError): + """Indicate connection error.""" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 609a0229bcfd7..e0485ced09164 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -1,6 +1,7 @@ """Provide common Z-Wave JS fixtures.""" import asyncio +from collections.abc import Generator import copy import io from typing import Any, cast @@ -15,6 +16,7 @@ from homeassistant.components.zwave_js import PLATFORMS from homeassistant.components.zwave_js.const import DOMAIN +from homeassistant.components.zwave_js.helpers import SERVER_VERSION_TIMEOUT from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.util.json import JsonArrayType @@ -587,6 +589,44 @@ async def async_send_command_side_effect(message, require_schema=None): yield client +@pytest.fixture(name="server_version_side_effect") +def server_version_side_effect_fixture() -> Any | None: + """Return the server version side effect.""" + return None + + +@pytest.fixture(name="get_server_version", autouse=True) +def mock_get_server_version( + server_version_side_effect: Any | None, server_version_timeout: int +) -> Generator[AsyncMock]: + """Mock server version.""" + version_info = VersionInfo( + driver_version="mock-driver-version", + server_version="mock-server-version", + home_id=1234, + min_schema_version=0, + max_schema_version=1, + ) + with ( + patch( + "homeassistant.components.zwave_js.helpers.get_server_version", + side_effect=server_version_side_effect, + return_value=version_info, + ) as mock_version, + patch( + "homeassistant.components.zwave_js.helpers.SERVER_VERSION_TIMEOUT", + new=server_version_timeout, + ), + ): + yield mock_version + + +@pytest.fixture(name="server_version_timeout") +def mock_server_version_timeout() -> int: + """Patch the timeout for getting server version.""" + return SERVER_VERSION_TIMEOUT + + @pytest.fixture(name="multisensor_6") def multisensor_6_fixture(client, multisensor_6_state) -> Node: """Mock a multisensor 6 node.""" diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index c6ce3d9ac1bec..a3f08513b7013 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -7,6 +7,7 @@ from typing import Any from unittest.mock import AsyncMock, MagicMock, PropertyMock, call, patch +from aiohttp import ClientError import pytest from zwave_js_server.const import ( ExclusionStrategy, @@ -5080,14 +5081,17 @@ async def test_subscribe_node_statistics( async def test_hard_reset_controller( hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, device_registry: dr.DeviceRegistry, client: MagicMock, + get_server_version: AsyncMock, integration: MockConfigEntry, hass_ws_client: WebSocketGenerator, ) -> None: """Test that the hard_reset_controller WS API call works.""" entry = integration ws_client = await hass_ws_client(hass) + assert entry.unique_id == "3245146787" async def async_send_command_driver_ready( message: dict[str, Any], @@ -5122,6 +5126,40 @@ async def async_send_command_driver_ready( assert client.async_send_command.call_args_list[0] == call( {"command": "driver.hard_reset"}, 25 ) + assert entry.unique_id == "1234" + + client.async_send_command.reset_mock() + + # Test client connect error when getting the server version. + + get_server_version.side_effect = ClientError("Boom!") + + await ws_client.send_json_auto_id( + { + TYPE: "zwave_js/hard_reset_controller", + ENTRY_ID: entry.entry_id, + } + ) + + msg = await ws_client.receive_json() + + device = device_registry.async_get_device( + identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])} + ) + assert device is not None + assert msg["result"] == device.id + assert msg["success"] + + assert client.async_send_command.call_count == 3 + # The first call is the relevant hard reset command. + # 25 is the require_schema parameter. + assert client.async_send_command.call_args_list[0] == call( + {"command": "driver.hard_reset"}, 25 + ) + assert ( + "Failed to get server version, cannot update config entry" + "unique id with new home id, after controller reset" + ) in caplog.text client.async_send_command.reset_mock() @@ -5162,6 +5200,8 @@ async def async_send_command_no_driver_ready( {"command": "driver.hard_reset"}, 25 ) + client.async_send_command.reset_mock() + # Test FailedZWaveCommand is caught with patch( "zwave_js_server.model.driver.Driver.async_hard_reset", diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 15fd9fcbd30f5..ac420564f3fa8 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -17,8 +17,9 @@ from zwave_js_server.version import VersionInfo from homeassistant import config_entries, data_entry_flow -from homeassistant.components.zwave_js.config_flow import SERVER_VERSION_TIMEOUT, TITLE +from homeassistant.components.zwave_js.config_flow import TITLE from homeassistant.components.zwave_js.const import ADDON_SLUG, CONF_USB_PATH, DOMAIN +from homeassistant.components.zwave_js.helpers import SERVER_VERSION_TIMEOUT from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers.service_info.hassio import HassioServiceInfo @@ -89,44 +90,6 @@ def mock_supervisor_fixture() -> Generator[None]: yield -@pytest.fixture(name="server_version_side_effect") -def server_version_side_effect_fixture() -> Any | None: - """Return the server version side effect.""" - return None - - -@pytest.fixture(name="get_server_version", autouse=True) -def mock_get_server_version( - server_version_side_effect: Any | None, server_version_timeout: int -) -> Generator[AsyncMock]: - """Mock server version.""" - version_info = VersionInfo( - driver_version="mock-driver-version", - server_version="mock-server-version", - home_id=1234, - min_schema_version=0, - max_schema_version=1, - ) - with ( - patch( - "homeassistant.components.zwave_js.config_flow.get_server_version", - side_effect=server_version_side_effect, - return_value=version_info, - ) as mock_version, - patch( - "homeassistant.components.zwave_js.config_flow.SERVER_VERSION_TIMEOUT", - new=server_version_timeout, - ), - ): - yield mock_version - - -@pytest.fixture(name="server_version_timeout") -def mock_server_version_timeout() -> int: - """Patch the timeout for getting server version.""" - return SERVER_VERSION_TIMEOUT - - @pytest.fixture(name="addon_setup_time", autouse=True) def mock_addon_setup_time() -> Generator[None]: """Mock add-on setup sleep time.""" From b7c07209b8ecda7c19286a9e8ea1fcc75e15e1dd Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Tue, 13 May 2025 14:23:41 +0200 Subject: [PATCH 30/55] Fix blocking call in azure_storage config flow (#144818) * Fix blocking call in azure_storage config flow * Fix blocking call in azure_storage config_flow as well * move session getting to event flow --- .../components/azure_storage/config_flow.py | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/azure_storage/config_flow.py b/homeassistant/components/azure_storage/config_flow.py index 2862d290f9579..25bd39a6608b0 100644 --- a/homeassistant/components/azure_storage/config_flow.py +++ b/homeassistant/components/azure_storage/config_flow.py @@ -27,9 +27,25 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for azure storage.""" - def get_account_url(self, account_name: str) -> str: - """Get the account URL.""" - return f"https://{account_name}.blob.core.windows.net/" + async def get_container_client( + self, account_name: str, container_name: str, storage_account_key: str + ) -> ContainerClient: + """Get the container client. + + ContainerClient has a blocking call to open in cpython + """ + + session = async_get_clientsession(self.hass) + + def create_container_client() -> ContainerClient: + return ContainerClient( + account_url=f"https://{account_name}.blob.core.windows.net/", + container_name=container_name, + credential=storage_account_key, + transport=AioHttpTransport(session=session), + ) + + return await self.hass.async_add_executor_job(create_container_client) async def validate_config( self, container_client: ContainerClient @@ -58,11 +74,10 @@ async def async_step_user( self._async_abort_entries_match( {CONF_ACCOUNT_NAME: user_input[CONF_ACCOUNT_NAME]} ) - container_client = ContainerClient( - account_url=self.get_account_url(user_input[CONF_ACCOUNT_NAME]), + container_client = await self.get_container_client( + account_name=user_input[CONF_ACCOUNT_NAME], container_name=user_input[CONF_CONTAINER_NAME], - credential=user_input[CONF_STORAGE_ACCOUNT_KEY], - transport=AioHttpTransport(session=async_get_clientsession(self.hass)), + storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY], ) errors = await self.validate_config(container_client) @@ -99,12 +114,12 @@ async def async_step_reauth_confirm( reauth_entry = self._get_reauth_entry() if user_input is not None: - container_client = ContainerClient( - account_url=self.get_account_url(reauth_entry.data[CONF_ACCOUNT_NAME]), + container_client = await self.get_container_client( + account_name=reauth_entry.data[CONF_ACCOUNT_NAME], container_name=reauth_entry.data[CONF_CONTAINER_NAME], - credential=user_input[CONF_STORAGE_ACCOUNT_KEY], - transport=AioHttpTransport(session=async_get_clientsession(self.hass)), + storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY], ) + errors = await self.validate_config(container_client) if not errors: return self.async_update_reload_and_abort( @@ -129,13 +144,10 @@ async def async_step_reconfigure( reconfigure_entry = self._get_reconfigure_entry() if user_input is not None: - container_client = ContainerClient( - account_url=self.get_account_url( - reconfigure_entry.data[CONF_ACCOUNT_NAME] - ), + container_client = await self.get_container_client( + account_name=reconfigure_entry.data[CONF_ACCOUNT_NAME], container_name=user_input[CONF_CONTAINER_NAME], - credential=user_input[CONF_STORAGE_ACCOUNT_KEY], - transport=AioHttpTransport(session=async_get_clientsession(self.hass)), + storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY], ) errors = await self.validate_config(container_client) if not errors: From d9cbd1b65f868a6f2184bef7c87ae0b4365224a4 Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Wed, 14 May 2025 06:04:38 +0200 Subject: [PATCH 31/55] Bump pylamarzocco to 2.0.3 (#144825) --- homeassistant/components/lamarzocco/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lamarzocco/manifest.json b/homeassistant/components/lamarzocco/manifest.json index 1fbef07339413..d948d46ef1fc8 100644 --- a/homeassistant/components/lamarzocco/manifest.json +++ b/homeassistant/components/lamarzocco/manifest.json @@ -37,5 +37,5 @@ "iot_class": "cloud_push", "loggers": ["pylamarzocco"], "quality_scale": "platinum", - "requirements": ["pylamarzocco==2.0.2"] + "requirements": ["pylamarzocco==2.0.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 67230b4bfcb5d..4bb970422557a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2093,7 +2093,7 @@ pykwb==0.0.8 pylacrosse==0.4 # homeassistant.components.lamarzocco -pylamarzocco==2.0.2 +pylamarzocco==2.0.3 # homeassistant.components.lastfm pylast==5.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3de21584bfe66..a54cb3f3057b8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1708,7 +1708,7 @@ pykrakenapi==0.1.8 pykulersky==0.5.8 # homeassistant.components.lamarzocco -pylamarzocco==2.0.2 +pylamarzocco==2.0.3 # homeassistant.components.lastfm pylast==5.1.0 From 8161ce6ea85d391f74d3794f15fb3d7d39bda403 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Wed, 14 May 2025 05:42:43 -0400 Subject: [PATCH 32/55] Bump python-snoo to 0.6.6 (#144849) --- homeassistant/components/snoo/manifest.json | 2 +- homeassistant/components/snoo/strings.json | 3 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/snoo/manifest.json b/homeassistant/components/snoo/manifest.json index 839382b2d848f..2afec990e4b20 100644 --- a/homeassistant/components/snoo/manifest.json +++ b/homeassistant/components/snoo/manifest.json @@ -7,5 +7,5 @@ "iot_class": "cloud_push", "loggers": ["snoo"], "quality_scale": "bronze", - "requirements": ["python-snoo==0.6.5"] + "requirements": ["python-snoo==0.6.6"] } diff --git a/homeassistant/components/snoo/strings.json b/homeassistant/components/snoo/strings.json index 1c86c066c7f7f..e4a5c634a68c2 100644 --- a/homeassistant/components/snoo/strings.json +++ b/homeassistant/components/snoo/strings.json @@ -56,7 +56,8 @@ "power": "Power button pressed", "status_requested": "Status requested", "sticky_white_noise_updated": "Sleepytime sounds updated", - "config_change": "Config changed" + "config_change": "Config changed", + "restart": "Restart" } } } diff --git a/requirements_all.txt b/requirements_all.txt index 4bb970422557a..b07208625f41e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2486,7 +2486,7 @@ python-roborock==2.18.2 python-smarttub==0.0.39 # homeassistant.components.snoo -python-snoo==0.6.5 +python-snoo==0.6.6 # homeassistant.components.songpal python-songpal==0.16.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a54cb3f3057b8..5613b58face17 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2023,7 +2023,7 @@ python-roborock==2.18.2 python-smarttub==0.0.39 # homeassistant.components.snoo -python-snoo==0.6.5 +python-snoo==0.6.6 # homeassistant.components.songpal python-songpal==0.16.2 From 3123a7b1685fa6f71f95d15d872b7714f0454c91 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Wed, 14 May 2025 02:42:22 -0700 Subject: [PATCH 33/55] Bump ical to 9.2.4 (#144852) --- homeassistant/components/google/manifest.json | 2 +- homeassistant/components/local_calendar/manifest.json | 2 +- homeassistant/components/local_todo/manifest.json | 2 +- homeassistant/components/remote_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index b43ded01d6edc..d6f2ee76615e0 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==7.0.1", "oauth2client==4.1.3", "ical==9.2.2"] + "requirements": ["gcal-sync==7.0.1", "oauth2client==4.1.3", "ical==9.2.4"] } diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 2fa603d51ff16..07de4a82244ef 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/local_calendar", "iot_class": "local_polling", "loggers": ["ical"], - "requirements": ["ical==9.2.2"] + "requirements": ["ical==9.2.4"] } diff --git a/homeassistant/components/local_todo/manifest.json b/homeassistant/components/local_todo/manifest.json index 735c11e645aa2..367c75d575560 100644 --- a/homeassistant/components/local_todo/manifest.json +++ b/homeassistant/components/local_todo/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_todo", "iot_class": "local_polling", - "requirements": ["ical==9.2.2"] + "requirements": ["ical==9.2.4"] } diff --git a/homeassistant/components/remote_calendar/manifest.json b/homeassistant/components/remote_calendar/manifest.json index 33a46ea3dc87a..9cf39b7ce4551 100644 --- a/homeassistant/components/remote_calendar/manifest.json +++ b/homeassistant/components/remote_calendar/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["ical"], "quality_scale": "silver", - "requirements": ["ical==9.2.2"] + "requirements": ["ical==9.2.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index b07208625f41e..3d348388b93a4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1200,7 +1200,7 @@ ibmiotf==0.3.4 # homeassistant.components.local_calendar # homeassistant.components.local_todo # homeassistant.components.remote_calendar -ical==9.2.2 +ical==9.2.4 # homeassistant.components.caldav icalendar==6.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5613b58face17..4fc92103165df 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1021,7 +1021,7 @@ ibeacon-ble==1.2.0 # homeassistant.components.local_calendar # homeassistant.components.local_todo # homeassistant.components.remote_calendar -ical==9.2.2 +ical==9.2.4 # homeassistant.components.caldav icalendar==6.1.0 From 5a83627dc5002b199dc1e5b5fdfb25a3a496e316 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Wed, 14 May 2025 16:08:24 +1000 Subject: [PATCH 34/55] Fix wall connector states in Teslemetry (#144855) * Fix wall connector * Update snapshot --- homeassistant/components/teslemetry/entity.py | 3 ++- homeassistant/components/teslemetry/sensor.py | 3 +-- tests/components/teslemetry/snapshots/test_sensor.ambr | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index 9ce812980dbc5..4bc63fea5e2d6 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -9,6 +9,7 @@ from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN @@ -229,7 +230,7 @@ def __init__( super().__init__(data.live_coordinator, key) @property - def _value(self) -> int: + def _value(self) -> StateType: """Return a specific wall connector value from coordinator data.""" return ( self.coordinator.data.get("wall_connectors", {}) diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index b87bd334e8c9a..3567069011dc7 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -1763,8 +1763,7 @@ def __init__( def _async_update_attrs(self) -> None: """Update the attributes of the sensor.""" - if self.exists: - self._attr_native_value = self.entity_description.value_fn(self._value) + self._attr_native_value = self.entity_description.value_fn(self._value) class TeslemetryEnergyInfoSensorEntity(TeslemetryEnergyInfoEntity, SensorEntity): diff --git a/tests/components/teslemetry/snapshots/test_sensor.ambr b/tests/components/teslemetry/snapshots/test_sensor.ambr index 8e9ce51e2978b..3b860039b0396 100644 --- a/tests/components/teslemetry/snapshots/test_sensor.ambr +++ b/tests/components/teslemetry/snapshots/test_sensor.ambr @@ -4978,7 +4978,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'disconnected', }) # --- # name: test_sensors[sensor.wall_connector_vehicle-statealt] @@ -4991,7 +4991,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'disconnected', }) # --- # name: test_sensors[sensor.wall_connector_vehicle_2-entry] @@ -5038,7 +5038,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'disconnected', }) # --- # name: test_sensors[sensor.wall_connector_vehicle_2-statealt] @@ -5051,7 +5051,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'disconnected', }) # --- # name: test_sensors_streaming[sensor.test_battery_level-state] From 4c4be883231b357a6373b1fcb85575bdefd28d67 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 14 May 2025 11:58:29 +0200 Subject: [PATCH 35/55] Fix Reolink setup when ONVIF push is unsupported (#144869) * Fix setup when ONVIF push is not supported * fix styling --- homeassistant/components/reolink/host.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 378c167d46938..c3a8d3405018c 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -581,7 +581,12 @@ async def subscribe(self) -> None: ) return - await self._api.subscribe(self._webhook_url) + try: + await self._api.subscribe(self._webhook_url) + except NotSupportedError as err: + self._onvif_push_supported = False + _LOGGER.debug(err) + return _LOGGER.debug( "Host %s: subscribed successfully to webhook %s", @@ -602,7 +607,11 @@ async def renew(self) -> None: return # API is shutdown, no need to subscribe try: - if self._onvif_push_supported and not self._api.baichuan.events_active: + if ( + self._onvif_push_supported + and not self._api.baichuan.events_active + and self._cancel_tcp_push_check is None + ): await self._renew(SubType.push) if self._onvif_long_poll_supported and self._long_poll_task is not None: From e5e1c9fb0581c09fd74c29ba104ff6e573d0b2ca Mon Sep 17 00:00:00 2001 From: rjblake Date: Fri, 16 May 2025 10:37:11 +0200 Subject: [PATCH 36/55] Fix some Home Connect translation strings (#144905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update strings.json Corrected program names: changed "Pre_rinse" to "Pre-Rinse" changed "Kurz 60°C" to "Speed 60°C" Both match the Home Connect app; although the UK documentation refers to "Speed 60°C" as "Quick 60°C" * Adjust casing --------- Co-authored-by: Martin Hjelmare --- homeassistant/components/home_connect/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/home_connect/strings.json b/homeassistant/components/home_connect/strings.json index 19d7cc0604623..7e364a6aa50ca 100644 --- a/homeassistant/components/home_connect/strings.json +++ b/homeassistant/components/home_connect/strings.json @@ -234,7 +234,7 @@ "consumer_products_coffee_maker_program_coffee_world_black_eye": "Black eye", "consumer_products_coffee_maker_program_coffee_world_dead_eye": "Dead eye", "consumer_products_coffee_maker_program_beverage_hot_water": "Hot water", - "dishcare_dishwasher_program_pre_rinse": "Pre_rinse", + "dishcare_dishwasher_program_pre_rinse": "Pre-rinse", "dishcare_dishwasher_program_auto_1": "Auto 1", "dishcare_dishwasher_program_auto_2": "Auto 2", "dishcare_dishwasher_program_auto_3": "Auto 3", @@ -252,7 +252,7 @@ "dishcare_dishwasher_program_intensiv_power": "Intensive power", "dishcare_dishwasher_program_magic_daily": "Magic daily", "dishcare_dishwasher_program_super_60": "Super 60ºC", - "dishcare_dishwasher_program_kurz_60": "Kurz 60ºC", + "dishcare_dishwasher_program_kurz_60": "Speed 60ºC", "dishcare_dishwasher_program_express_sparkle_65": "Express sparkle 65ºC", "dishcare_dishwasher_program_machine_care": "Machine care", "dishcare_dishwasher_program_steam_fresh": "Steam fresh", From bf1d2069e4b6409fe2d503e4ae8e8954b55e6835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Wed, 14 May 2025 19:25:01 +0200 Subject: [PATCH 37/55] Update Tibber lib 0.31.2 (#144908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/tibber/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 3a3a772a93432..43cbd79afefa0 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/tibber", "iot_class": "cloud_polling", "loggers": ["tibber"], - "requirements": ["pyTibber==0.30.8"] + "requirements": ["pyTibber==0.31.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3d348388b93a4..dd2f06568a519 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1804,7 +1804,7 @@ pyRFXtrx==0.31.1 pySDCP==1 # homeassistant.components.tibber -pyTibber==0.30.8 +pyTibber==0.31.2 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4fc92103165df..ec35f07b3c7ea 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1491,7 +1491,7 @@ pyHomee==1.2.8 pyRFXtrx==0.31.1 # homeassistant.components.tibber -pyTibber==0.30.8 +pyTibber==0.31.2 # homeassistant.components.dlink pyW215==0.7.0 From 543104b36c797639e11c80469b521f19e2f59698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Wed, 14 May 2025 20:46:28 +0200 Subject: [PATCH 38/55] Update mill library 0.12.5 (#144911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update mill library 0.12.5 Signed-off-by: Daniel Hjelseth Høyer * Update mill library 0.12.5 Signed-off-by: Daniel Hjelseth Høyer --------- Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/mill/coordinator.py | 4 ++-- homeassistant/components/mill/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mill/coordinator.py b/homeassistant/components/mill/coordinator.py index 288b341b0f90a..a701acb8ddbf1 100644 --- a/homeassistant/components/mill/coordinator.py +++ b/homeassistant/components/mill/coordinator.py @@ -26,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) -TWO_YEARS = 2 * 365 * 24 +TWO_YEARS_DAYS = 2 * 365 class MillDataUpdateCoordinator(DataUpdateCoordinator): @@ -91,7 +91,7 @@ async def _async_update_data(self): if not last_stats or not last_stats.get(statistic_id): hourly_data = ( await self.mill_data_connection.fetch_historic_energy_usage( - dev_id, n_days=TWO_YEARS + dev_id, n_days=TWO_YEARS_DAYS ) ) hourly_data = dict(sorted(hourly_data.items(), key=lambda x: x[0])) diff --git a/homeassistant/components/mill/manifest.json b/homeassistant/components/mill/manifest.json index bfad9b48cb904..c5cc94ead3055 100644 --- a/homeassistant/components/mill/manifest.json +++ b/homeassistant/components/mill/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/mill", "iot_class": "local_polling", "loggers": ["mill", "mill_local"], - "requirements": ["millheater==0.12.3", "mill-local==0.3.0"] + "requirements": ["millheater==0.12.5", "mill-local==0.3.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index dd2f06568a519..2557955164de3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1427,7 +1427,7 @@ microBeesPy==0.3.5 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.12.3 +millheater==0.12.5 # homeassistant.components.minio minio==7.1.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ec35f07b3c7ea..6952c71ca64f4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1200,7 +1200,7 @@ microBeesPy==0.3.5 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.12.3 +millheater==0.12.5 # homeassistant.components.minio minio==7.1.12 From a657964c25c3248f9cf9e91ae1a17d65b46950fb Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 15 May 2025 09:27:48 +0200 Subject: [PATCH 39/55] Fix unknown Pure AQI in Sensibo (#144924) * Fix unknown Pure AQI in Sensibo * Fix mypy --- homeassistant/components/sensibo/climate.py | 2 +- homeassistant/components/sensibo/manifest.json | 2 +- homeassistant/components/sensibo/sensor.py | 16 ++++++++++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/sensibo/test_sensor.py | 13 ++++++++++++- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 906c4259ce525..a40cb110f667a 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -252,7 +252,7 @@ def supported_features(self) -> ClimateEntityFeature: return features @property - def current_humidity(self) -> int | None: + def current_humidity(self) -> float | None: """Return the current humidity.""" return self.device_data.humidity diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index 610695aaf7be1..4cadd3f869200 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -15,5 +15,5 @@ "iot_class": "cloud_polling", "loggers": ["pysensibo"], "quality_scale": "platinum", - "requirements": ["pysensibo==1.1.0"] + "requirements": ["pysensibo==1.2.1"] } diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py index 09f095bfaecc7..bab85eb2294a2 100644 --- a/homeassistant/components/sensibo/sensor.py +++ b/homeassistant/components/sensibo/sensor.py @@ -101,14 +101,25 @@ class SensiboDeviceSensorEntityDescription(SensorEntityDescription): value_fn=lambda data: data.temperature, ), ) + + +def _pure_aqi(pm25_pure: PureAQI | None) -> str | None: + """Return the Pure aqi name or None if unknown.""" + if pm25_pure: + aqi_name = pm25_pure.name.lower() + if aqi_name != "unknown": + return aqi_name + return None + + PURE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( SensiboDeviceSensorEntityDescription( key="pm25", translation_key="pm25_pure", device_class=SensorDeviceClass.ENUM, - value_fn=lambda data: data.pm25_pure.name.lower() if data.pm25_pure else None, + value_fn=lambda data: _pure_aqi(data.pm25_pure), extra_fn=None, - options=[aqi.name.lower() for aqi in PureAQI], + options=[aqi.name.lower() for aqi in PureAQI if aqi.name != "UNKNOWN"], ), SensiboDeviceSensorEntityDescription( key="pure_sensitivity", @@ -119,6 +130,7 @@ class SensiboDeviceSensorEntityDescription(SensorEntityDescription): FILTER_LAST_RESET_DESCRIPTION, ) + DEVICE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( SensiboDeviceSensorEntityDescription( key="timer_time", diff --git a/requirements_all.txt b/requirements_all.txt index 2557955164de3..98d38adcdcac9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2293,7 +2293,7 @@ pysaj==0.0.16 pyschlage==2025.4.0 # homeassistant.components.sensibo -pysensibo==1.1.0 +pysensibo==1.2.1 # homeassistant.components.serial pyserial-asyncio-fast==0.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6952c71ca64f4..4b8ff028a797c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1875,7 +1875,7 @@ pysabnzbd==1.1.1 pyschlage==2025.4.0 # homeassistant.components.sensibo -pysensibo==1.1.0 +pysensibo==1.2.1 # homeassistant.components.acer_projector # homeassistant.components.crownstone diff --git a/tests/components/sensibo/test_sensor.py b/tests/components/sensibo/test_sensor.py index 8ea760361231d..7b7450b97a472 100644 --- a/tests/components/sensibo/test_sensor.py +++ b/tests/components/sensibo/test_sensor.py @@ -11,7 +11,7 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform +from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -45,3 +45,14 @@ async def test_sensor( state = hass.states.get("sensor.kitchen_pure_aqi") assert state.state == "moderate" + + mock_client.async_get_devices_data.return_value.parsed[ + "AAZZAAZZ" + ].pm25_pure = PureAQI(0) + + freezer.tick(timedelta(minutes=5)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get("sensor.kitchen_pure_aqi") + assert state.state == STATE_UNKNOWN From f086f4a9556050002c4257ad97c531f0b95bb94a Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 16 May 2025 12:58:59 +0200 Subject: [PATCH 40/55] Ignore Fronius Gen24 firmware 1.35.4-1 SSL verification issue for new setups (#144940) --- homeassistant/components/fronius/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fronius/config_flow.py b/homeassistant/components/fronius/config_flow.py index b8aa2da81c6e3..97e040abf98f2 100644 --- a/homeassistant/components/fronius/config_flow.py +++ b/homeassistant/components/fronius/config_flow.py @@ -35,7 +35,7 @@ async def validate_host( hass: HomeAssistant, host: str ) -> tuple[str, FroniusConfigEntryData]: """Validate the user input allows us to connect.""" - fronius = Fronius(async_get_clientsession(hass), host) + fronius = Fronius(async_get_clientsession(hass, verify_ssl=False), host) try: datalogger_info: dict[str, Any] From a9520888cf0a2b6e8c090cf76dad818664557e25 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 15 May 2025 16:07:53 +0200 Subject: [PATCH 41/55] Fix Home Assistant Yellow config entry data (#144948) --- .../homeassistant_yellow/__init__.py | 9 +-- .../homeassistant_yellow/config_flow.py | 7 +- .../homeassistant_yellow/test_config_flow.py | 4 +- .../homeassistant_yellow/test_init.py | 71 +++++++++++++++++++ 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/homeassistant_yellow/__init__.py b/homeassistant/components/homeassistant_yellow/__init__.py index 71aa8ef99b702..27c40e35946b8 100644 --- a/homeassistant/components/homeassistant_yellow/__init__.py +++ b/homeassistant/components/homeassistant_yellow/__init__.py @@ -90,16 +90,17 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> minor_version=2, ) - if config_entry.minor_version == 2: - # Add a `firmware_version` key + if config_entry.minor_version <= 3: + # Add a `firmware_version` key if it doesn't exist to handle entries created + # with minor version 1.3 where the firmware version was not set. hass.config_entries.async_update_entry( config_entry, data={ **config_entry.data, - FIRMWARE_VERSION: None, + FIRMWARE_VERSION: config_entry.data.get(FIRMWARE_VERSION), }, version=1, - minor_version=3, + minor_version=4, ) _LOGGER.debug( diff --git a/homeassistant/components/homeassistant_yellow/config_flow.py b/homeassistant/components/homeassistant_yellow/config_flow.py index 5472c346e94f2..1fac6bcac9688 100644 --- a/homeassistant/components/homeassistant_yellow/config_flow.py +++ b/homeassistant/components/homeassistant_yellow/config_flow.py @@ -62,7 +62,7 @@ class HomeAssistantYellowConfigFlow(BaseFirmwareConfigFlow, domain=DOMAIN): """Handle a config flow for Home Assistant Yellow.""" VERSION = 1 - MINOR_VERSION = 3 + MINOR_VERSION = 4 def __init__(self, *args: Any, **kwargs: Any) -> None: """Instantiate config flow.""" @@ -116,6 +116,11 @@ def _async_flow_finished(self) -> ConfigFlowResult: if self._probed_firmware_info is not None else ApplicationType.EZSP ).value, + FIRMWARE_VERSION: ( + self._probed_firmware_info.firmware_version + if self._probed_firmware_info is not None + else None + ), }, ) diff --git a/tests/components/homeassistant_yellow/test_config_flow.py b/tests/components/homeassistant_yellow/test_config_flow.py index 46fec0a1f30c1..1d5a64eafb917 100644 --- a/tests/components/homeassistant_yellow/test_config_flow.py +++ b/tests/components/homeassistant_yellow/test_config_flow.py @@ -101,12 +101,12 @@ async def test_config_flow(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.CREATE_ENTRY assert result["title"] == "Home Assistant Yellow" - assert result["data"] == {"firmware": "ezsp"} + assert result["data"] == {"firmware": "ezsp", "firmware_version": None} assert result["options"] == {} assert len(mock_setup_entry.mock_calls) == 1 config_entry = hass.config_entries.async_entries(DOMAIN)[0] - assert config_entry.data == {"firmware": "ezsp"} + assert config_entry.data == {"firmware": "ezsp", "firmware_version": None} assert config_entry.options == {} assert config_entry.title == "Home Assistant Yellow" diff --git a/tests/components/homeassistant_yellow/test_init.py b/tests/components/homeassistant_yellow/test_init.py index 57d63c7441e2b..00e3383cf77a8 100644 --- a/tests/components/homeassistant_yellow/test_init.py +++ b/tests/components/homeassistant_yellow/test_init.py @@ -10,6 +10,9 @@ ApplicationType, FirmwareInfo, ) +from homeassistant.components.homeassistant_yellow.config_flow import ( + HomeAssistantYellowConfigFlow, +) from homeassistant.components.homeassistant_yellow.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant @@ -248,3 +251,71 @@ async def test_setup_entry_addon_info_fails( await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.SETUP_RETRY + + +@pytest.mark.parametrize( + ("start_version", "data", "migrated_data"), + [ + (1, {}, {"firmware": "ezsp", "firmware_version": None}), + (2, {"firmware": "ezsp"}, {"firmware": "ezsp", "firmware_version": None}), + ( + 2, + {"firmware": "ezsp", "firmware_version": "123"}, + {"firmware": "ezsp", "firmware_version": "123"}, + ), + (3, {"firmware": "ezsp"}, {"firmware": "ezsp", "firmware_version": None}), + ( + 3, + {"firmware": "ezsp", "firmware_version": "123"}, + {"firmware": "ezsp", "firmware_version": "123"}, + ), + ], +) +async def test_migrate_entry( + hass: HomeAssistant, + start_version: int, + data: dict, + migrated_data: dict, +) -> None: + """Test migration of a config entry.""" + mock_integration(hass, MockModule("hassio")) + await async_setup_component(hass, HASSIO_DOMAIN, {}) + + # Setup the config entry + config_entry = MockConfigEntry( + data=data, + domain=DOMAIN, + options={}, + title="Home Assistant Yellow", + version=1, + minor_version=start_version, + ) + config_entry.add_to_hass(hass) + + with ( + patch( + "homeassistant.components.homeassistant_yellow.get_os_info", + return_value={"board": "yellow"}, + ), + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=True, + ), + patch( + "homeassistant.components.homeassistant_yellow.guess_firmware_info", + return_value=FirmwareInfo( # Nothing is setup + device="/dev/ttyAMA1", + firmware_version="1234", + firmware_type=ApplicationType.EZSP, + source="unknown", + owners=[], + ), + ), + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.data == migrated_data + assert config_entry.options == {} + assert config_entry.minor_version == HomeAssistantYellowConfigFlow.MINOR_VERSION + assert config_entry.version == HomeAssistantYellowConfigFlow.VERSION From 19b7cfbd4a19dbbc408b946996a9cc1e57e47700 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Thu, 15 May 2025 13:46:15 +0200 Subject: [PATCH 42/55] Bump deebot-client to 13.2.0 (#144957) --- homeassistant/components/ecovacs/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json index e670a36cf723f..b1674e123fa1a 100644 --- a/homeassistant/components/ecovacs/manifest.json +++ b/homeassistant/components/ecovacs/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/ecovacs", "iot_class": "cloud_push", "loggers": ["sleekxmppfs", "sucks", "deebot_client"], - "requirements": ["py-sucks==0.9.10", "deebot-client==13.1.0"] + "requirements": ["py-sucks==0.9.10", "deebot-client==13.2.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 98d38adcdcac9..7ac4c47d7f386 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -762,7 +762,7 @@ debugpy==1.8.13 # decora==0.6 # homeassistant.components.ecovacs -deebot-client==13.1.0 +deebot-client==13.2.0 # homeassistant.components.ihc # homeassistant.components.namecheapdns diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4b8ff028a797c..e2fc32ea09e2d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -653,7 +653,7 @@ dbus-fast==2.43.0 debugpy==1.8.13 # homeassistant.components.ecovacs -deebot-client==13.1.0 +deebot-client==13.2.0 # homeassistant.components.ihc # homeassistant.components.namecheapdns From 0ba55c31e88dac1540b840815e90918e68268b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Fri, 16 May 2025 01:57:16 +0200 Subject: [PATCH 43/55] Fix ESPHome entities unavailable if deep sleep enabled after entry setup (#144970) --- homeassistant/components/esphome/entity.py | 6 ++- tests/components/esphome/test_entity.py | 62 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index 7b02680afee7d..94c4a8ffe4639 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -223,7 +223,6 @@ def __init__( self._states = cast(dict[int, _StateT], entry_data.state[state_type]) assert entry_data.device_info is not None device_info = entry_data.device_info - self._device_info = device_info self._on_entry_data_changed() self._key = entity_info.key self._state_type = state_type @@ -311,6 +310,11 @@ def _on_state_update(self) -> None: @callback def _on_entry_data_changed(self) -> None: entry_data = self._entry_data + # Update the device info since it can change + # when the device is reconnected + if TYPE_CHECKING: + assert entry_data.device_info is not None + self._device_info = entry_data.device_info self._api_version = entry_data.api_version self._client = entry_data.client if self._device_info.has_deep_sleep: diff --git a/tests/components/esphome/test_entity.py b/tests/components/esphome/test_entity.py index ee6e6b6785fcd..36185efeb72d9 100644 --- a/tests/components/esphome/test_entity.py +++ b/tests/components/esphome/test_entity.py @@ -1,6 +1,7 @@ """Test ESPHome binary sensors.""" import asyncio +from dataclasses import asdict from typing import Any from unittest.mock import AsyncMock @@ -8,6 +9,7 @@ APIClient, BinarySensorInfo, BinarySensorState, + DeviceInfo, SensorInfo, SensorState, build_unique_id, @@ -665,3 +667,63 @@ async def test_entity_id_preserved_on_upgrade_when_in_storage( ) state = hass.states.get("binary_sensor.user_named") assert state is not None + + +async def test_deep_sleep_added_after_setup( + hass: HomeAssistant, + mock_client: APIClient, + mock_esphome_device: MockESPHomeDeviceType, +) -> None: + """Test deep sleep added after setup.""" + mock_device = await mock_esphome_device( + mock_client=mock_client, + entity_info=[ + BinarySensorInfo( + object_id="test", + key=1, + name="test", + unique_id="test", + ), + ], + user_service=[], + states=[ + BinarySensorState(key=1, state=True, missing_state=False), + ], + device_info={"has_deep_sleep": False}, + ) + + entity_id = "binary_sensor.test_test" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_ON + + await mock_device.mock_disconnect(expected_disconnect=True) + + # No deep sleep, should be unavailable + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_UNAVAILABLE + + await mock_device.mock_connect() + + # reconnect, should be available + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_ON + + await mock_device.mock_disconnect(expected_disconnect=True) + new_device_info = DeviceInfo( + **{**asdict(mock_device.device_info), "has_deep_sleep": True} + ) + mock_device.client.device_info = AsyncMock(return_value=new_device_info) + mock_device.device_info = new_device_info + + await mock_device.mock_connect() + + # Now disconnect that deep sleep is set in device info + await mock_device.mock_disconnect(expected_disconnect=True) + + # Deep sleep, should be available + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_ON From 9f0db9874569c051b51b2050f8a900acef4bde9d Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Fri, 16 May 2025 04:25:24 -0400 Subject: [PATCH 44/55] Strip `_CLIENT` suffix from ZHA event `unique_id` (#145006) --- homeassistant/components/zha/helpers.py | 15 ++++- tests/components/zha/test_device_action.py | 64 ++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/helpers.py b/homeassistant/components/zha/helpers.py index c819f94ceba08..084e1c882acf1 100644 --- a/homeassistant/components/zha/helpers.py +++ b/homeassistant/components/zha/helpers.py @@ -419,13 +419,26 @@ def zha_device_info(self) -> dict[str, Any]: @callback def handle_zha_event(self, zha_event: ZHAEvent) -> None: """Handle a ZHA event.""" + if ATTR_UNIQUE_ID in zha_event.data: + unique_id = zha_event.data[ATTR_UNIQUE_ID] + + # Client cluster handler unique IDs in the ZHA lib were disambiguated by + # adding a suffix of `_CLIENT`. Unfortunately, this breaks existing + # automations that match the `unique_id` key. This can be removed in a + # future release with proper notice of a breaking change. + unique_id = unique_id.removesuffix("_CLIENT") + else: + unique_id = zha_event.unique_id + self.gateway_proxy.hass.bus.async_fire( ZHA_EVENT, { ATTR_DEVICE_IEEE: str(zha_event.device_ieee), - ATTR_UNIQUE_ID: zha_event.unique_id, ATTR_DEVICE_ID: self.device_id, **zha_event.data, + # The order of these keys is intentional, `zha_event.data` can contain + # a `unique_id` key, which we explicitly replace + ATTR_UNIQUE_ID: unique_id, }, ) diff --git a/tests/components/zha/test_device_action.py b/tests/components/zha/test_device_action.py index 6708250e4484a..becf9d8155732 100644 --- a/tests/components/zha/test_device_action.py +++ b/tests/components/zha/test_device_action.py @@ -258,3 +258,67 @@ async def test_invalid_zha_event_type( # `zha_send_event` accepts only zigpy responses, lists, and dicts with pytest.raises(TypeError): cluster_handler.zha_send_event(COMMAND_SINGLE, 123) + + +async def test_client_unique_id_suffix_stripped( + hass: HomeAssistant, setup_zha, zigpy_device_mock +) -> None: + """Test that the `_CLIENT_` unique ID suffix is stripped.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "event", + "event_type": "zha_event", + "event_data": { + "unique_id": "38:5b:44:ff:fe:a7:cc:69:1:0x0006", # no `_CLIENT` suffix + "endpoint_id": 1, + "cluster_id": 6, + "command": "on", + "args": [], + "params": {}, + }, + }, + "action": {"service": "zha.test"}, + } + }, + ) + + service_calls = async_mock_service(hass, DOMAIN, "test") + + await setup_zha() + gateway = get_zha_gateway(hass) + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [ + general.Basic.cluster_id, + security.IasZone.cluster_id, + security.IasWd.cluster_id, + ], + SIG_EP_OUTPUT: [general.OnOff.cluster_id], + SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH, + SIG_EP_PROFILE: zha.PROFILE_ID, + } + } + ) + + zha_device = gateway.get_or_create_device(zigpy_device) + await gateway.async_device_initialized(zha_device.device) + + zha_device.emit_zha_event( + { + "unique_id": "38:5b:44:ff:fe:a7:cc:69:1:0x0006_CLIENT", + "endpoint_id": 1, + "cluster_id": 6, + "command": "on", + "args": [], + "params": {}, + } + ) + + await hass.async_block_till_done(wait_background_tasks=True) + assert len(service_calls) == 1 From 715f116954c4863f23ec7e19bc1bbbd0990cad91 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 16 May 2025 13:10:58 +0200 Subject: [PATCH 45/55] Bump pySmartThings to 3.2.2 (#145033) --- homeassistant/components/smartthings/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index 043bdea71e229..f72405dae20a4 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -30,5 +30,5 @@ "iot_class": "cloud_push", "loggers": ["pysmartthings"], "quality_scale": "bronze", - "requirements": ["pysmartthings==3.2.1"] + "requirements": ["pysmartthings==3.2.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7ac4c47d7f386..3697f8450c39b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2326,7 +2326,7 @@ pysma==0.7.5 pysmappee==0.2.29 # homeassistant.components.smartthings -pysmartthings==3.2.1 +pysmartthings==3.2.2 # homeassistant.components.smarty pysmarty2==0.10.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2fc32ea09e2d..8d472a0f0e5bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1899,7 +1899,7 @@ pysma==0.7.5 pysmappee==0.2.29 # homeassistant.components.smartthings -pysmartthings==3.2.1 +pysmartthings==3.2.2 # homeassistant.components.smarty pysmarty2==0.10.2 From 0691ad93627206b7802b73134819784fd0e9291f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 16 May 2025 13:17:56 +0200 Subject: [PATCH 46/55] Set SmartThings oven setpoint to unknown if its 1 Fahrenheit (#145038) --- homeassistant/components/smartthings/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 2d6451fa27994..219e1dfe5c15f 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -584,7 +584,7 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription): device_class=SensorDeviceClass.TEMPERATURE, use_temperature_unit=True, # Set the value to None if it is 0 F (-17 C) - value_fn=lambda value: None if value in {0, -17} else value, + value_fn=lambda value: None if value in {-17, 0, 1} else value, ) ] }, From b76ac68fb11f5c6555aceaf8ed3638cfda36163a Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 16 May 2025 19:51:30 +0300 Subject: [PATCH 47/55] Fix climate idle state for Comelit (#145059) --- homeassistant/components/comelit/climate.py | 4 +--- tests/components/comelit/snapshots/test_climate.ambr | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/comelit/climate.py b/homeassistant/components/comelit/climate.py index be5b892e53c1c..e7890cddff8ac 100644 --- a/homeassistant/components/comelit/climate.py +++ b/homeassistant/components/comelit/climate.py @@ -134,11 +134,9 @@ def _update_attributes(self) -> None: self._attr_current_temperature = values[0] / 10 self._attr_hvac_action = None - if _mode == ClimaComelitMode.OFF: - self._attr_hvac_action = HVACAction.OFF if not _active: self._attr_hvac_action = HVACAction.IDLE - if _mode in API_STATUS: + elif _mode in API_STATUS: self._attr_hvac_action = API_STATUS[_mode]["hvac_action"] self._attr_hvac_mode = None diff --git a/tests/components/comelit/snapshots/test_climate.ambr b/tests/components/comelit/snapshots/test_climate.ambr index e5201067ee1f6..0233359bc4573 100644 --- a/tests/components/comelit/snapshots/test_climate.ambr +++ b/tests/components/comelit/snapshots/test_climate.ambr @@ -48,7 +48,7 @@ 'attributes': ReadOnlyDict({ 'current_temperature': 22.1, 'friendly_name': 'Climate0', - 'hvac_action': , + 'hvac_action': , 'hvac_modes': list([ , , From e2ede3ed19770225643ec10ba8ff74c3172aa4aa Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 16 May 2025 20:14:41 +0200 Subject: [PATCH 48/55] Map SmartThings auto mode correctly (#145061) --- .../components/smartthings/climate.py | 8 ++++---- .../smartthings/snapshots/test_climate.ambr | 20 +++++++++---------- tests/components/smartthings/test_climate.py | 10 +++++----- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index f2f9479584c7e..983609b895f1a 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -31,7 +31,7 @@ ATTR_OPERATION_STATE = "operation_state" MODE_TO_STATE = { - "auto": HVACMode.HEAT_COOL, + "auto": HVACMode.AUTO, "cool": HVACMode.COOL, "eco": HVACMode.AUTO, "rush hour": HVACMode.AUTO, @@ -40,7 +40,7 @@ "off": HVACMode.OFF, } STATE_TO_MODE = { - HVACMode.HEAT_COOL: "auto", + HVACMode.AUTO: "auto", HVACMode.COOL: "cool", HVACMode.HEAT: "heat", HVACMode.OFF: "off", @@ -58,7 +58,7 @@ } AC_MODE_TO_STATE = { - "auto": HVACMode.HEAT_COOL, + "auto": HVACMode.AUTO, "cool": HVACMode.COOL, "dry": HVACMode.DRY, "coolClean": HVACMode.COOL, @@ -69,7 +69,7 @@ "wind": HVACMode.FAN_ONLY, } STATE_TO_AC_MODE = { - HVACMode.HEAT_COOL: "auto", + HVACMode.AUTO: "auto", HVACMode.COOL: "cool", HVACMode.DRY: "dry", HVACMode.HEAT: "heat", diff --git a/tests/components/smartthings/snapshots/test_climate.ambr b/tests/components/smartthings/snapshots/test_climate.ambr index 633b02568fc38..b23e7024e05e4 100644 --- a/tests/components/smartthings/snapshots/test_climate.ambr +++ b/tests/components/smartthings/snapshots/test_climate.ambr @@ -146,7 +146,7 @@ , , , - , + , , ]), 'max_temp': 35, @@ -206,7 +206,7 @@ , , , - , + , , ]), 'max_temp': 35, @@ -246,7 +246,7 @@ , , , - , + , ]), 'max_temp': 35, 'min_temp': 7, @@ -308,7 +308,7 @@ , , , - , + , ]), 'max_temp': 35, 'min_temp': 7, @@ -349,7 +349,7 @@ ]), 'hvac_modes': list([ , - , + , , , , @@ -414,7 +414,7 @@ 'friendly_name': 'Aire Dormitorio Principal', 'hvac_modes': list([ , - , + , , , , @@ -462,7 +462,7 @@ , , , - , + , ]), 'max_temp': 35, 'min_temp': 7, @@ -513,7 +513,7 @@ , , , - , + , ]), 'max_temp': 35, 'min_temp': 7, @@ -541,7 +541,7 @@ 'hvac_modes': list([ , , - , + , ]), 'max_temp': 35.0, 'min_temp': 7.0, @@ -589,7 +589,7 @@ 'hvac_modes': list([ , , - , + , ]), 'max_temp': 35.0, 'min_temp': 7.0, diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 138601ec08b12..48cbf4fb4eded 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -119,7 +119,7 @@ async def test_ac_set_hvac_mode_off( @pytest.mark.parametrize( ("hvac_mode", "argument"), [ - (HVACMode.HEAT_COOL, "auto"), + (HVACMode.AUTO, "auto"), (HVACMode.COOL, "cool"), (HVACMode.DRY, "dry"), (HVACMode.HEAT, "heat"), @@ -174,7 +174,7 @@ async def test_ac_set_hvac_mode_turns_on( SERVICE_SET_HVAC_MODE, { ATTR_ENTITY_ID: "climate.ac_office_granit", - ATTR_HVAC_MODE: HVACMode.HEAT_COOL, + ATTR_HVAC_MODE: HVACMode.AUTO, }, blocking=True, ) @@ -266,7 +266,7 @@ async def test_ac_set_temperature_and_hvac_mode_while_off( { ATTR_ENTITY_ID: "climate.ac_office_granit", ATTR_TEMPERATURE: 23, - ATTR_HVAC_MODE: HVACMode.HEAT_COOL, + ATTR_HVAC_MODE: HVACMode.AUTO, }, blocking=True, ) @@ -316,7 +316,7 @@ async def test_ac_set_temperature_and_hvac_mode( { ATTR_ENTITY_ID: "climate.ac_office_granit", ATTR_TEMPERATURE: 23, - ATTR_HVAC_MODE: HVACMode.HEAT_COOL, + ATTR_HVAC_MODE: HVACMode.AUTO, }, blocking=True, ) @@ -623,7 +623,7 @@ async def test_thermostat_set_hvac_mode( await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.asd", ATTR_HVAC_MODE: HVACMode.HEAT_COOL}, + {ATTR_ENTITY_ID: "climate.asd", ATTR_HVAC_MODE: HVACMode.AUTO}, blocking=True, ) devices.execute_device_command.assert_called_once_with( From 146e440d5979990cd2670766d38c41192eaeb5b3 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 16 May 2025 19:37:22 +0200 Subject: [PATCH 49/55] Update frontend to 20250516.0 (#145062) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 9471f863a7239..5c5feca98b758 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20250509.0"] + "requirements": ["home-assistant-frontend==20250516.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a42910fc7e2ae..abed9e8cab244 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -38,7 +38,7 @@ habluetooth==3.48.2 hass-nabucasa==0.96.0 hassil==2.2.3 home-assistant-bluetooth==1.13.1 -home-assistant-frontend==20250509.0 +home-assistant-frontend==20250516.0 home-assistant-intents==2025.5.7 httpx==0.28.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 3697f8450c39b..79ae3501792ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1161,7 +1161,7 @@ hole==0.8.0 holidays==0.72 # homeassistant.components.frontend -home-assistant-frontend==20250509.0 +home-assistant-frontend==20250516.0 # homeassistant.components.conversation home-assistant-intents==2025.5.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8d472a0f0e5bf..f3686c8e39b88 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -991,7 +991,7 @@ hole==0.8.0 holidays==0.72 # homeassistant.components.frontend -home-assistant-frontend==20250509.0 +home-assistant-frontend==20250516.0 # homeassistant.components.conversation home-assistant-intents==2025.5.7 From 4906e78a5cf33a3f96656e49c8cc54c23e78d289 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 16 May 2025 19:53:46 +0200 Subject: [PATCH 50/55] Only set suggested area for new SmartThings devices (#145063) --- .../components/smartthings/__init__.py | 17 ++++++++-- tests/components/smartthings/test_init.py | 31 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index cec71f9175057..ff03ce5ca67cf 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -32,6 +32,7 @@ ATTR_HW_VERSION, ATTR_MANUFACTURER, ATTR_MODEL, + ATTR_SUGGESTED_AREA, ATTR_SW_VERSION, ATTR_VIA_DEVICE, CONF_ACCESS_TOKEN, @@ -453,14 +454,24 @@ def create_devices( ATTR_SW_VERSION: viper.software_version, } ) + if ( + device_registry.async_get_device({(DOMAIN, device.device.device_id)}) + is None + ): + kwargs.update( + { + ATTR_SUGGESTED_AREA: ( + rooms.get(device.device.room_id) + if device.device.room_id + else None + ) + } + ) device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, device.device.device_id)}, configuration_url="https://account.smartthings.com", name=device.device.label, - suggested_area=( - rooms.get(device.device.room_id) if device.device.room_id else None - ), **kwargs, ) diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 1d4b124c60d84..fcb962449bfc3 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -59,6 +59,37 @@ async def test_devices( assert device == snapshot +@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"]) +async def test_device_not_resetting_area( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + devices: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test device not resetting area.""" + await setup_integration(hass, mock_config_entry) + + device_id = devices.get_devices.return_value[0].device_id + + device = device_registry.async_get_device({(DOMAIN, device_id)}) + + assert device.area_id == "theater" + + device_registry.async_update_device(device_id=device.id, area_id=None) + await hass.async_block_till_done() + + device = device_registry.async_get_device({(DOMAIN, device_id)}) + + assert device.area_id is None + + await hass.config_entries.async_reload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + device = device_registry.async_get_device({(DOMAIN, device_id)}) + assert device.area_id is None + + @pytest.mark.parametrize("device_fixture", ["button"]) async def test_button_event( hass: HomeAssistant, From 621a14d7ccb640d89bd2b0e2aadae6f1c0a6cb3e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 16 May 2025 19:38:18 +0200 Subject: [PATCH 51/55] Fix fan AC mode in SmartThings AC (#145064) --- homeassistant/components/smartthings/climate.py | 17 ++++++++++------- .../fixtures/device_status/da_ac_rac_01001.json | 2 +- tests/components/smartthings/test_climate.py | 8 +++++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 983609b895f1a..7cb3b0210bbee 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -66,6 +66,7 @@ "heat": HVACMode.HEAT, "heatClean": HVACMode.HEAT, "fanOnly": HVACMode.FAN_ONLY, + "fan": HVACMode.FAN_ONLY, "wind": HVACMode.FAN_ONLY, } STATE_TO_AC_MODE = { @@ -88,6 +89,7 @@ } WIND = "wind" +FAN = "fan" WINDFREE = "windFree" UNIT_MAP = {"C": UnitOfTemperature.CELSIUS, "F": UnitOfTemperature.FAHRENHEIT} @@ -388,14 +390,15 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: tasks.append(self.async_turn_on()) mode = STATE_TO_AC_MODE[hvac_mode] - # If new hvac_mode is HVAC_MODE_FAN_ONLY and AirConditioner support "wind" mode the AirConditioner new mode has to be "wind" - # The conversion make the mode change working - # The conversion is made only for device that wrongly has capability "wind" instead "fan_only" + # If new hvac_mode is HVAC_MODE_FAN_ONLY and AirConditioner support "wind" or "fan" mode the AirConditioner + # new mode has to be "wind" or "fan" if hvac_mode == HVACMode.FAN_ONLY: - if WIND in self.get_attribute_value( - Capability.AIR_CONDITIONER_MODE, Attribute.SUPPORTED_AC_MODES - ): - mode = WIND + for fan_mode in (WIND, FAN): + if fan_mode in self.get_attribute_value( + Capability.AIR_CONDITIONER_MODE, Attribute.SUPPORTED_AC_MODES + ): + mode = fan_mode + break tasks.append( self.execute_device_command( diff --git a/tests/components/smartthings/fixtures/device_status/da_ac_rac_01001.json b/tests/components/smartthings/fixtures/device_status/da_ac_rac_01001.json index e8e71c53ace36..3982e1174f4ca 100644 --- a/tests/components/smartthings/fixtures/device_status/da_ac_rac_01001.json +++ b/tests/components/smartthings/fixtures/device_status/da_ac_rac_01001.json @@ -32,7 +32,7 @@ "timestamp": "2025-02-09T14:35:56.800Z" }, "supportedAcModes": { - "value": ["auto", "cool", "dry", "wind", "heat", "dryClean"], + "value": ["auto", "cool", "dry", "fan", "heat", "dryClean"], "timestamp": "2025-02-09T15:42:13.444Z" }, "airConditionerMode": { diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 48cbf4fb4eded..8241e6de3b35c 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -196,17 +196,19 @@ async def test_ac_set_hvac_mode_turns_on( @pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"]) -async def test_ac_set_hvac_mode_wind( +@pytest.mark.parametrize("mode", ["fan", "wind"]) +async def test_ac_set_hvac_mode_fan( hass: HomeAssistant, devices: AsyncMock, mock_config_entry: MockConfigEntry, + mode: str, ) -> None: """Test setting AC HVAC mode to wind if the device supports it.""" set_attribute_value( devices, Capability.AIR_CONDITIONER_MODE, Attribute.SUPPORTED_AC_MODES, - ["auto", "cool", "dry", "heat", "wind"], + ["auto", "cool", "dry", "heat", mode], ) set_attribute_value(devices, Capability.SWITCH, Attribute.SWITCH, "on") @@ -223,7 +225,7 @@ async def test_ac_set_hvac_mode_wind( Capability.AIR_CONDITIONER_MODE, Command.SET_AIR_CONDITIONER_MODE, MAIN, - argument="wind", + argument=mode, ) From 8c4eec231f37624dc43aec65ad399d62df61d350 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 16 May 2025 21:17:07 +0200 Subject: [PATCH 52/55] Don't create entities for Smartthings smarttags (#145066) --- .../components/smartthings/__init__.py | 11 ++ tests/components/smartthings/conftest.py | 1 + .../device_status/im_smarttag2_ble_uwb.json | 129 ++++++++++++ .../devices/im_smarttag2_ble_uwb.json | 184 ++++++++++++++++++ .../smartthings/snapshots/test_init.ambr | 33 ++++ 5 files changed, 358 insertions(+) create mode 100644 tests/components/smartthings/fixtures/device_status/im_smarttag2_ble_uwb.json create mode 100644 tests/components/smartthings/fixtures/devices/im_smarttag2_ble_uwb.json diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index ff03ce5ca67cf..557d14f8a6419 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -13,6 +13,7 @@ from pysmartthings import ( Attribute, Capability, + Category, ComponentStatus, Device, DeviceEvent, @@ -194,6 +195,16 @@ def _handle_new_subscription_identifier(identifier: str | None) -> None: } devices = await client.get_devices() for device in devices: + if ( + (main_component := device.components.get(MAIN)) is not None + and main_component.manufacturer_category is Category.BLUETOOTH_TRACKER + ): + device_status[device.device_id] = FullDevice( + device=device, + status={}, + online=True, + ) + continue status = process_status(await client.get_device_status(device.device_id)) online = await client.get_device_health(device.device_id) device_status[device.device_id] = FullDevice( diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index b3a58b1763716..253a01b6d5f16 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -150,6 +150,7 @@ def mock_smartthings() -> Generator[AsyncMock]: "generic_ef00_v1", "bosch_radiator_thermostat_ii", "im_speaker_ai_0001", + "im_smarttag2_ble_uwb", "abl_light_b_001", "tplink_p110", "ikea_kadrilj", diff --git a/tests/components/smartthings/fixtures/device_status/im_smarttag2_ble_uwb.json b/tests/components/smartthings/fixtures/device_status/im_smarttag2_ble_uwb.json new file mode 100644 index 0000000000000..e59db7476de26 --- /dev/null +++ b/tests/components/smartthings/fixtures/device_status/im_smarttag2_ble_uwb.json @@ -0,0 +1,129 @@ +{ + "components": { + "main": { + "tag.e2eEncryption": { + "encryption": { + "value": null + } + }, + "audioVolume": { + "volume": { + "value": null + } + }, + "geofence": { + "enableState": { + "value": null + }, + "geofence": { + "value": null + }, + "name": { + "value": null + } + }, + "tag.updatedInfo": { + "connection": { + "value": "connected", + "timestamp": "2024-02-27T17:44:57.638Z" + } + }, + "tag.factoryReset": {}, + "battery": { + "quantity": { + "value": null + }, + "battery": { + "value": null + }, + "type": { + "value": null + } + }, + "firmwareUpdate": { + "lastUpdateStatusReason": { + "value": null + }, + "availableVersion": { + "value": null + }, + "lastUpdateStatus": { + "value": null + }, + "supportedCommands": { + "value": null + }, + "state": { + "value": null + }, + "updateAvailable": { + "value": false, + "timestamp": "2024-06-25T05:56:22.227Z" + }, + "currentVersion": { + "value": null + }, + "lastUpdateTime": { + "value": null + } + }, + "tag.searchingStatus": { + "searchingStatus": { + "value": null + } + }, + "tag.tagStatus": { + "connectedUserId": { + "value": null + }, + "tagStatus": { + "value": null + }, + "connectedDeviceId": { + "value": null + } + }, + "alarm": { + "alarm": { + "value": null + } + }, + "tag.tagButton": { + "tagButton": { + "value": null + } + }, + "tag.uwbActivation": { + "uwbActivation": { + "value": null + } + }, + "geolocation": { + "method": { + "value": null + }, + "heading": { + "value": null + }, + "latitude": { + "value": null + }, + "accuracy": { + "value": null + }, + "altitudeAccuracy": { + "value": null + }, + "speed": { + "value": null + }, + "longitude": { + "value": null + }, + "lastUpdateTime": { + "value": null + } + } + } + } +} diff --git a/tests/components/smartthings/fixtures/devices/im_smarttag2_ble_uwb.json b/tests/components/smartthings/fixtures/devices/im_smarttag2_ble_uwb.json new file mode 100644 index 0000000000000..802b4da1514f1 --- /dev/null +++ b/tests/components/smartthings/fixtures/devices/im_smarttag2_ble_uwb.json @@ -0,0 +1,184 @@ +{ + "items": [ + { + "deviceId": "83d660e4-b0c8-4881-a674-d9f1730366c1", + "name": "Tag(UWB)", + "label": "SmartTag+ black", + "manufacturerName": "Samsung Electronics", + "presentationId": "IM-SmartTag-BLE-UWB", + "deviceManufacturerCode": "Samsung Electronics", + "locationId": "redacted_locid", + "ownerId": "redacted", + "roomId": "redacted_roomid", + "components": [ + { + "id": "main", + "label": "main", + "capabilities": [ + { + "id": "alarm", + "version": 1 + }, + { + "id": "tag.tagButton", + "version": 1 + }, + { + "id": "audioVolume", + "version": 1 + }, + { + "id": "battery", + "version": 1 + }, + { + "id": "tag.factoryReset", + "version": 1 + }, + { + "id": "tag.e2eEncryption", + "version": 1 + }, + { + "id": "tag.tagStatus", + "version": 1 + }, + { + "id": "geolocation", + "version": 1 + }, + { + "id": "geofence", + "version": 1 + }, + { + "id": "tag.uwbActivation", + "version": 1 + }, + { + "id": "tag.updatedInfo", + "version": 1 + }, + { + "id": "tag.searchingStatus", + "version": 1 + }, + { + "id": "firmwareUpdate", + "version": 1 + } + ], + "categories": [ + { + "name": "BluetoothTracker", + "categoryType": "manufacturer" + } + ] + } + ], + "createTime": "2023-05-25T09:42:59.720Z", + "profile": { + "id": "e443f3e8-a926-3deb-917c-e5c6de3af70f" + }, + "bleD2D": { + "encryptionKey": "ZTbd_04NISrhQODE7_i8JdcG2ZWwqmUfY60taptK7J0=", + "cipher": "AES_128-CBC-PKCS7Padding", + "identifier": "415D4Y16F97F", + "configurationVersion": "2.0", + "configurationUrl": "https://apis.samsungiotcloud.com/v1/miniature/profile/b8e65e7e-6152-4704-b9f5-f16352034237", + "bleDeviceType": "BLE", + "metadata": { + "regionCode": 11, + "privacyIdPoolSize": 2000, + "privacyIdSeed": "AAAAAAAX8IQ=", + "privacyIdInitialVector": "ZfqZKLRGSeCwgNhdqHFRpw==", + "numAllowableConnections": 2, + "firmware": { + "version": "1.03.07", + "specVersion": "0.5.6", + "updateTime": 1685007914000, + "latestFirmware": { + "id": 581, + "version": "1.03.07", + "data": { + "checksum": "50E7", + "size": "586004", + "supportedVersion": "0.5.6" + } + } + }, + "currentServerTime": 1739095473, + "searchingStatus": "stop", + "lastKnownConnection": { + "updated": 1713422813, + "connectedUser": { + "id": "sk3oyvsbkm", + "name": "" + }, + "connectedDevice": { + "id": "4f3faa4c-976c-3bd8-b209-607f3a5a9814", + "name": "" + }, + "d2dStatus": "bleScanned", + "nearby": true, + "onDemand": false + }, + "e2eEncryption": { + "enabled": false + }, + "timer": 1713422675, + "category": { + "id": 0 + }, + "remoteRing": { + "enabled": false + }, + "petWalking": { + "enabled": false + }, + "onboardedBy": { + "saGuid": "sk3oyvsbkm" + }, + "shareable": { + "enabled": false + }, + "agingCounter": { + "status": "VALID", + "updated": 1713422675 + }, + "vendor": { + "mnId": "0AFD", + "setupId": "432", + "modelName": "EI-T7300" + }, + "priorityConnection": { + "lba": false, + "cameraShutter": false + }, + "createTime": 1685007780, + "updateTime": 1713422675, + "fmmSearch": false, + "ooTime": { + "currentOoTime": 8, + "defaultOoTime": 8 + }, + "pidPoolSize": { + "desiredPidPoolSize": 2000, + "currentPidPoolSize": 2000 + }, + "activeMode": { + "mode": 0 + }, + "itemConfig": { + "searchingStatus": "stop" + } + } + }, + "type": "BLE_D2D", + "restrictionTier": 0, + "allowed": [], + "executionContext": "CLOUD" + } + ], + "_links": {} +} diff --git a/tests/components/smartthings/snapshots/test_init.ambr b/tests/components/smartthings/snapshots/test_init.ambr index d70d9a1dcfcfd..596cc487dd570 100644 --- a/tests/components/smartthings/snapshots/test_init.ambr +++ b/tests/components/smartthings/snapshots/test_init.ambr @@ -1487,6 +1487,39 @@ 'via_device_id': None, }) # --- +# name: test_devices[im_smarttag2_ble_uwb] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': 'https://account.smartthings.com', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'smartthings', + '83d660e4-b0c8-4881-a674-d9f1730366c1', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': None, + 'model': None, + 'model_id': None, + 'name': 'SmartTag+ black', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_devices[im_speaker_ai_0001] DeviceRegistryEntrySnapshot({ 'area_id': None, From 34455f97433408a0d66ab3364478c9d4aa77bb69 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 16 May 2025 21:22:43 +0200 Subject: [PATCH 53/55] Fix Ecovacs mower area sensors (#145071) --- homeassistant/components/ecovacs/sensor.py | 39 +++++++++++++++++-- .../ecovacs/snapshots/test_sensor.ambr | 8 ++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/ecovacs/sensor.py b/homeassistant/components/ecovacs/sensor.py index 6c8ae080fc3af..a8600d786a887 100644 --- a/homeassistant/components/ecovacs/sensor.py +++ b/homeassistant/components/ecovacs/sensor.py @@ -6,7 +6,8 @@ from dataclasses import dataclass from typing import Any, Generic -from deebot_client.capabilities import CapabilityEvent, CapabilityLifeSpan +from deebot_client.capabilities import CapabilityEvent, CapabilityLifeSpan, DeviceType +from deebot_client.device import Device from deebot_client.events import ( BatteryEvent, ErrorEvent, @@ -34,7 +35,7 @@ UnitOfArea, UnitOfTime, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType @@ -59,6 +60,15 @@ class EcovacsSensorEntityDescription( """Ecovacs sensor entity description.""" value_fn: Callable[[EventT], StateType] + native_unit_of_measurement_fn: Callable[[DeviceType], str | None] | None = None + + +@callback +def get_area_native_unit_of_measurement(device_type: DeviceType) -> str | None: + """Get the area native unit of measurement based on device type.""" + if device_type is DeviceType.MOWER: + return UnitOfArea.SQUARE_CENTIMETERS + return UnitOfArea.SQUARE_METERS ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = ( @@ -68,7 +78,7 @@ class EcovacsSensorEntityDescription( capability_fn=lambda caps: caps.stats.clean, value_fn=lambda e: e.area, translation_key="stats_area", - native_unit_of_measurement=UnitOfArea.SQUARE_METERS, + native_unit_of_measurement_fn=get_area_native_unit_of_measurement, ), EcovacsSensorEntityDescription[StatsEvent]( key="stats_time", @@ -85,7 +95,7 @@ class EcovacsSensorEntityDescription( value_fn=lambda e: e.area, key="total_stats_area", translation_key="total_stats_area", - native_unit_of_measurement=UnitOfArea.SQUARE_METERS, + native_unit_of_measurement_fn=get_area_native_unit_of_measurement, state_class=SensorStateClass.TOTAL_INCREASING, ), EcovacsSensorEntityDescription[TotalStatsEvent]( @@ -249,6 +259,27 @@ class EcovacsSensor( entity_description: EcovacsSensorEntityDescription + def __init__( + self, + device: Device, + capability: CapabilityEvent, + entity_description: EcovacsSensorEntityDescription, + **kwargs: Any, + ) -> None: + """Initialize entity.""" + super().__init__(device, capability, entity_description, **kwargs) + if ( + entity_description.native_unit_of_measurement_fn + and ( + native_unit_of_measurement + := entity_description.native_unit_of_measurement_fn( + device.capabilities.device_type + ) + ) + is not None + ): + self._attr_native_unit_of_measurement = native_unit_of_measurement + async def async_added_to_hass(self) -> None: """Set up the event listeners now that hass is ready.""" await super().async_added_to_hass() diff --git a/tests/components/ecovacs/snapshots/test_sensor.ambr b/tests/components/ecovacs/snapshots/test_sensor.ambr index c4e5a5b1966b9..7fa7a41234dd9 100644 --- a/tests/components/ecovacs/snapshots/test_sensor.ambr +++ b/tests/components/ecovacs/snapshots/test_sensor.ambr @@ -181,14 +181,14 @@ 'supported_features': 0, 'translation_key': 'stats_area', 'unique_id': '8516fbb1-17f1-4194-0000000_stats_area', - 'unit_of_measurement': , + 'unit_of_measurement': , }) # --- # name: test_sensors[5xu9h3][sensor.goat_g1_area_cleaned:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Goat G1 Area cleaned', - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.goat_g1_area_cleaned', @@ -523,7 +523,7 @@ 'supported_features': 0, 'translation_key': 'total_stats_area', 'unique_id': '8516fbb1-17f1-4194-0000000_total_stats_area', - 'unit_of_measurement': , + 'unit_of_measurement': , }) # --- # name: test_sensors[5xu9h3][sensor.goat_g1_total_area_cleaned:state] @@ -531,7 +531,7 @@ 'attributes': ReadOnlyDict({ 'friendly_name': 'Goat G1 Total area cleaned', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.goat_g1_total_area_cleaned', From 02b028add36c51d0c6b1f4dc9dc062c796cf048e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 16 May 2025 19:31:36 +0000 Subject: [PATCH 54/55] Bump version to 2025.5.2 --- 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 65f8e2bae649d..9e3149fd89ab1 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -25,7 +25,7 @@ APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2025 MINOR_VERSION: Final = 5 -PATCH_VERSION: Final = "1" +PATCH_VERSION: Final = "2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2) diff --git a/pyproject.toml b/pyproject.toml index ad3f9ad86e2c8..cc11f10d3ccdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2025.5.1" +version = "2025.5.2" license = "Apache-2.0" license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"] description = "Open-source home automation platform running on Python 3." From 0ef098a9f39c359cc279b9f944ff9f068acb0e39 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 16 May 2025 22:39:05 +0200 Subject: [PATCH 55/55] Pin rpds-py to 0.24.0 (#145074) --- homeassistant/package_constraints.txt | 5 +++++ script/gen_requirements_all.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index abed9e8cab244..11b1233bcda7f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -217,3 +217,8 @@ aiofiles>=24.1.0 # https://github.com/aio-libs/multidict/issues/1134 # https://github.com/aio-libs/multidict/issues/1131 multidict>=6.4.2 + +# rpds-py > 0.25.0 requires cargo 1.84.0 +# Stable Alpine current only ships cargo 1.83.0 +# No wheels upstream available for armhf & armv7 +rpds-py==0.24.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index b4e18ea596224..307a9c42d530b 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -246,6 +246,11 @@ # https://github.com/aio-libs/multidict/issues/1134 # https://github.com/aio-libs/multidict/issues/1131 multidict>=6.4.2 + +# rpds-py > 0.25.0 requires cargo 1.84.0 +# Stable Alpine current only ships cargo 1.83.0 +# No wheels upstream available for armhf & armv7 +rpds-py==0.24.0 """ GENERATED_MESSAGE = (