From 4ba765f26596bd6316aa1a1bb2091d9c1da7e8d8 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 6 Oct 2025 06:52:15 +0200 Subject: [PATCH 01/12] Add Shelly Wall Display XL to the list of devices without firmware changelog (#153781) --- homeassistant/components/shelly/const.py | 2 ++ tests/components/shelly/snapshots/test_devices.ambr | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 5606d3a8ce944..d99be1b0eb3e5 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -26,6 +26,7 @@ MODEL_VINTAGE_V2, MODEL_WALL_DISPLAY, MODEL_WALL_DISPLAY_X2, + MODEL_WALL_DISPLAY_XL, ) from homeassistant.components.number import NumberMode @@ -261,6 +262,7 @@ class BLEScannerMode(StrEnum): DEVICES_WITHOUT_FIRMWARE_CHANGELOG = ( MODEL_WALL_DISPLAY, MODEL_WALL_DISPLAY_X2, + MODEL_WALL_DISPLAY_XL, MODEL_MOTION, MODEL_MOTION_2, MODEL_VALVE, diff --git a/tests/components/shelly/snapshots/test_devices.ambr b/tests/components/shelly/snapshots/test_devices.ambr index 90ac21d1b8408..06b9acedf03e6 100644 --- a/tests/components/shelly/snapshots/test_devices.ambr +++ b/tests/components/shelly/snapshots/test_devices.ambr @@ -5887,7 +5887,7 @@ 'installed_version': '2.4.4', 'latest_version': '2.4.4', 'release_summary': None, - 'release_url': 'https://shelly-api-docs.shelly.cloud/gen2/changelog/#unreleased', + 'release_url': None, 'skipped_version': None, 'supported_features': , 'title': None, @@ -5948,7 +5948,7 @@ 'installed_version': '2.4.4', 'latest_version': '2.4.4', 'release_summary': None, - 'release_url': 'https://shelly-api-docs.shelly.cloud/gen2/changelog/', + 'release_url': None, 'skipped_version': None, 'supported_features': , 'title': None, From 1cc3431529b22dcc37187d02765e86f20c0acb19 Mon Sep 17 00:00:00 2001 From: tronikos Date: Sun, 5 Oct 2025 23:46:03 -0700 Subject: [PATCH 02/12] Fix missing google_assistant_sdk.send_text_command (#153735) --- .../google_assistant_sdk/__init__.py | 4 --- .../google_assistant_sdk/helpers.py | 10 +++++-- .../google_assistant_sdk/services.py | 2 +- .../google_assistant_sdk/strings.json | 3 ++ .../google_assistant_sdk/test_init.py | 28 +++++++++++++++---- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/google_assistant_sdk/__init__.py b/homeassistant/components/google_assistant_sdk/__init__.py index 6f747bfb31822..7fe2321d942d7 100644 --- a/homeassistant/components/google_assistant_sdk/__init__.py +++ b/homeassistant/components/google_assistant_sdk/__init__.py @@ -76,10 +76,6 @@ async def async_unload_entry( hass: HomeAssistant, entry: GoogleAssistantSDKConfigEntry ) -> bool: """Unload a config entry.""" - if not hass.config_entries.async_loaded_entries(DOMAIN): - for service_name in hass.services.async_services_for_domain(DOMAIN): - hass.services.async_remove(DOMAIN, service_name) - conversation.async_unset_agent(hass, entry) return True diff --git a/homeassistant/components/google_assistant_sdk/helpers.py b/homeassistant/components/google_assistant_sdk/helpers.py index c40c848ff3f15..93d29e5edb506 100644 --- a/homeassistant/components/google_assistant_sdk/helpers.py +++ b/homeassistant/components/google_assistant_sdk/helpers.py @@ -26,7 +26,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID, CONF_ACCESS_TOKEN from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session from homeassistant.helpers.event import async_call_later @@ -68,7 +68,13 @@ async def async_send_text_commands( ) -> list[CommandResponse]: """Send text commands to Google Assistant Service.""" # There can only be 1 entry (config_flow has single_instance_allowed) - entry: GoogleAssistantSDKConfigEntry = hass.config_entries.async_entries(DOMAIN)[0] + entries = hass.config_entries.async_loaded_entries(DOMAIN) + if not entries: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="entry_not_loaded", + ) + entry: GoogleAssistantSDKConfigEntry = entries[0] session = entry.runtime_data.session try: diff --git a/homeassistant/components/google_assistant_sdk/services.py b/homeassistant/components/google_assistant_sdk/services.py index 981f4d8ba5c54..6e3e92124430d 100644 --- a/homeassistant/components/google_assistant_sdk/services.py +++ b/homeassistant/components/google_assistant_sdk/services.py @@ -1,4 +1,4 @@ -"""Support for Google Assistant SDK.""" +"""Services for the Google Assistant SDK integration.""" from __future__ import annotations diff --git a/homeassistant/components/google_assistant_sdk/strings.json b/homeassistant/components/google_assistant_sdk/strings.json index 5831db9a0e315..bac4489b9bc35 100644 --- a/homeassistant/components/google_assistant_sdk/strings.json +++ b/homeassistant/components/google_assistant_sdk/strings.json @@ -65,6 +65,9 @@ } }, "exceptions": { + "entry_not_loaded": { + "message": "Entry not loaded" + }, "grpc_error": { "message": "Failed to communicate with Google Assistant" } diff --git a/tests/components/google_assistant_sdk/test_init.py b/tests/components/google_assistant_sdk/test_init.py index caddf9ba79729..ede182d889071 100644 --- a/tests/components/google_assistant_sdk/test_init.py +++ b/tests/components/google_assistant_sdk/test_init.py @@ -14,7 +14,7 @@ from homeassistant.components.google_assistant_sdk.const import SUPPORTED_LANGUAGE_CODES from homeassistant.config_entries import ConfigEntryState from homeassistant.core import Context, HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -37,19 +37,29 @@ async def test_setup_success( hass: HomeAssistant, setup_integration: ComponentSetup, ) -> None: - """Test successful setup and unload.""" + """Test successful setup, unload, and re-setup.""" + # Initial setup await setup_integration() - entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 assert entries[0].state is ConfigEntryState.LOADED + assert hass.services.has_service(DOMAIN, "send_text_command") - await hass.config_entries.async_unload(entries[0].entry_id) + # Unload the entry + entry_id = entries[0].entry_id + await hass.config_entries.async_unload(entry_id) await hass.async_block_till_done() - assert not hass.data.get(DOMAIN) assert entries[0].state is ConfigEntryState.NOT_LOADED - assert not hass.services.async_services().get(DOMAIN, {}) + assert hass.services.has_service(DOMAIN, "send_text_command") + + # Re-setup the entry + assert await hass.config_entries.async_setup(entry_id) + await hass.async_block_till_done() + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + assert entries[0].state is ConfigEntryState.LOADED + assert hass.services.has_service(DOMAIN, "send_text_command") @pytest.mark.parametrize("expires_at", [time.time() - 3600], ids=["expired"]) @@ -134,6 +144,12 @@ async def test_setup_client_error( assert len(entries) == 1 assert entries[0].state is ConfigEntryState.SETUP_RETRY + with pytest.raises(ServiceValidationError) as exc: + await hass.services.async_call( + DOMAIN, "send_text_command", {"command": "some command"}, blocking=True + ) + assert exc.value.translation_key == "entry_not_loaded" + @pytest.mark.parametrize( ("configured_language_code", "expected_language_code"), From e05169c7a4147517cc08c8e1b464c012a8a7fa79 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:50:38 +0200 Subject: [PATCH 03/12] Fix Tuya cover position when only control is available (#153803) --- homeassistant/components/tuya/cover.py | 3 +- tests/components/tuya/test_cover.py | 43 +++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index 16fa9f294ea6b..ac14f7686d07c 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -300,9 +300,10 @@ def is_closed(self) -> bool | None: self._current_state is not None and (current_state := self.device.status.get(self._current_state)) is not None + and current_state != "stop" ): return self.entity_description.current_state_inverse is not ( - current_state in (True, "fully_close") + current_state in (True, "close", "fully_close") ) return None diff --git a/tests/components/tuya/test_cover.py b/tests/components/tuya/test_cover.py index e4d6d98250a17..86616ca319be1 100644 --- a/tests/components/tuya/test_cover.py +++ b/tests/components/tuya/test_cover.py @@ -18,7 +18,13 @@ SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, ) -from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.const import ( + ATTR_ENTITY_ID, + STATE_CLOSED, + STATE_OPEN, + STATE_UNKNOWN, + Platform, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceNotSupported from homeassistant.helpers import entity_registry as er @@ -311,3 +317,38 @@ async def test_clkg_wltqkykhni0papzj_action( mock_device.id, expected_commands, ) + + +@pytest.mark.parametrize( + "mock_device_code", + ["cl_rD7uqAAgQOpSA2Rx"], +) +@pytest.mark.parametrize( + ("initial_control", "expected_state"), + [ + ("open", STATE_OPEN), + ("stop", STATE_UNKNOWN), + ("close", STATE_CLOSED), + ], +) +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.COVER]) +async def test_cl_rD7uqAAgQOpSA2Rx_state( + hass: HomeAssistant, + mock_manager: Manager, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, + initial_control: str, + expected_state: str, +) -> None: + """Test cover position for n3xgr5pdmpinictg device. + + See https://github.com/home-assistant/core/issues/153537 + """ + entity_id = "cover.kit_blinds_curtain" + mock_device.status["control"] = initial_control + + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + assert state.state == expected_state From a821d02dfb942fcfe4804d034d97b545df8e4798 Mon Sep 17 00:00:00 2001 From: tronikos Date: Mon, 6 Oct 2025 01:10:40 -0700 Subject: [PATCH 04/12] Translate reauthentication error message in Google Assistant SDK (#153797) Co-authored-by: Josef Zweck --- homeassistant/components/google_assistant_sdk/__init__.py | 2 +- homeassistant/components/google_assistant_sdk/strings.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/google_assistant_sdk/__init__.py b/homeassistant/components/google_assistant_sdk/__init__.py index 7fe2321d942d7..8d98da2fe4e92 100644 --- a/homeassistant/components/google_assistant_sdk/__init__.py +++ b/homeassistant/components/google_assistant_sdk/__init__.py @@ -54,7 +54,7 @@ async def async_setup_entry( except aiohttp.ClientResponseError as err: if 400 <= err.status < 500: raise ConfigEntryAuthFailed( - "OAuth session is not valid, reauth required" + translation_domain=DOMAIN, translation_key="reauth_required" ) from err raise ConfigEntryNotReady from err except aiohttp.ClientError as err: diff --git a/homeassistant/components/google_assistant_sdk/strings.json b/homeassistant/components/google_assistant_sdk/strings.json index bac4489b9bc35..f26fdd4a29c89 100644 --- a/homeassistant/components/google_assistant_sdk/strings.json +++ b/homeassistant/components/google_assistant_sdk/strings.json @@ -70,6 +70,9 @@ }, "grpc_error": { "message": "Failed to communicate with Google Assistant" + }, + "reauth_required": { + "message": "Credentials are invalid, re-authentication required" } } } From f9e75c616ae94621d2b6d262c2116e43f64c2bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Mon, 6 Oct 2025 10:15:37 +0200 Subject: [PATCH 05/12] Use TEMPERATURE_SCALING_FACTOR for Matter sensors (#153807) --- homeassistant/components/matter/sensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index 0c95cda947426..b3b81583b19f9 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -152,6 +152,7 @@ clusters.PumpConfigurationAndControl.Enums.ControlModeEnum.kUnknownEnumValue: None, } +HUMIDITY_SCALING_FACTOR = 100 TEMPERATURE_SCALING_FACTOR = 100 @@ -308,7 +309,7 @@ def _update_from_device(self) -> None: key="TemperatureSensor", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, - device_to_ha=lambda x: x / 100, + device_to_ha=lambda x: x / TEMPERATURE_SCALING_FACTOR, state_class=SensorStateClass.MEASUREMENT, ), entity_class=MatterSensor, @@ -344,7 +345,7 @@ def _update_from_device(self) -> None: key="HumiditySensor", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.HUMIDITY, - device_to_ha=lambda x: x / 100, + device_to_ha=lambda x: x / HUMIDITY_SCALING_FACTOR, state_class=SensorStateClass.MEASUREMENT, ), entity_class=MatterSensor, @@ -1136,7 +1137,7 @@ def _update_from_device(self) -> None: key="ThermostatLocalTemperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, - device_to_ha=lambda x: x / 100, + device_to_ha=lambda x: x / TEMPERATURE_SCALING_FACTOR, state_class=SensorStateClass.MEASUREMENT, ), entity_class=MatterSensor, From 6d97355b42dfc33f78143066dd5ff25b0cd7ab27 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:20:15 +0200 Subject: [PATCH 06/12] Update raspyrfm-client to 1.2.9 (#153789) --- homeassistant/components/raspyrfm/manifest.json | 2 +- requirements_all.txt | 2 +- script/hassfest/requirements.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/raspyrfm/manifest.json b/homeassistant/components/raspyrfm/manifest.json index d001e2b11187e..568a01cf862f4 100644 --- a/homeassistant/components/raspyrfm/manifest.json +++ b/homeassistant/components/raspyrfm/manifest.json @@ -6,5 +6,5 @@ "iot_class": "assumed_state", "loggers": ["raspyrfm_client"], "quality_scale": "legacy", - "requirements": ["raspyrfm-client==1.2.8"] + "requirements": ["raspyrfm-client==1.2.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5fc81177605a3..901d16ff3a15b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2689,7 +2689,7 @@ raincloudy==0.0.7 rapt-ble==0.1.2 # homeassistant.components.raspyrfm -raspyrfm-client==1.2.8 +raspyrfm-client==1.2.9 # homeassistant.components.refoss refoss-ha==1.2.5 diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index ddc3cb649e87a..93859fa301fa4 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -336,8 +336,6 @@ "obihai": {"homeassistant": {"pyobihai"}}, # https://github.com/iamkubi/pydactyl "pterodactyl": {"homeassistant": {"py-dactyl"}}, - # https://github.com/markusressel/raspyrfm-client - "raspyrfm": {"homeassistant": {"raspyrfm-client"}}, # https://github.com/sstallion/sensorpush-api "sensorpush_cloud": { "homeassistant": {"sensorpush-api"}, From 85506ac78a704a89e116b70cffc5347cd3a9ffcc Mon Sep 17 00:00:00 2001 From: tronikos Date: Mon, 6 Oct 2025 01:23:44 -0700 Subject: [PATCH 07/12] Google Assistant SDK: use setup_credentials in setup_integration (#153793) --- tests/components/google_assistant_sdk/conftest.py | 12 +++--------- .../google_assistant_sdk/test_config_flow.py | 6 +++--- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/components/google_assistant_sdk/conftest.py b/tests/components/google_assistant_sdk/conftest.py index 742e89cab0873..50aa1e9249baf 100644 --- a/tests/components/google_assistant_sdk/conftest.py +++ b/tests/components/google_assistant_sdk/conftest.py @@ -66,19 +66,13 @@ def mock_config_entry(expires_at: int, scopes: list[str]) -> MockConfigEntry: @pytest.fixture(name="setup_integration") async def mock_setup_integration( - hass: HomeAssistant, config_entry: MockConfigEntry + hass: HomeAssistant, + config_entry: MockConfigEntry, + setup_credentials: None, ) -> Callable[[], Coroutine[Any, Any, None]]: """Fixture for setting up the component.""" config_entry.add_to_hass(hass) - assert await async_setup_component(hass, "application_credentials", {}) - await async_import_client_credential( - hass, - DOMAIN, - ClientCredential("client-id", "client-secret"), - DOMAIN, - ) - async def func() -> None: assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/google_assistant_sdk/test_config_flow.py b/tests/components/google_assistant_sdk/test_config_flow.py index 332610e74e86c..c911a2a084da9 100644 --- a/tests/components/google_assistant_sdk/test_config_flow.py +++ b/tests/components/google_assistant_sdk/test_config_flow.py @@ -26,7 +26,7 @@ async def test_full_flow( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, aioclient_mock: AiohttpClientMocker, - setup_credentials, + setup_credentials: None, ) -> None: """Check full flow.""" result = await hass.config_entries.flow.async_init( @@ -87,7 +87,7 @@ async def test_reauth( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, aioclient_mock: AiohttpClientMocker, - setup_credentials, + setup_credentials: None, ) -> None: """Test the reauthentication case updates the existing config entry.""" @@ -162,7 +162,7 @@ async def test_single_instance_allowed( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, aioclient_mock: AiohttpClientMocker, - setup_credentials, + setup_credentials: None, ) -> None: """Test case where config flow allows a single test.""" config_entry = MockConfigEntry( From 464dec1dcbad012e21def952ddedb8e13a84ca63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joris=20Pelgr=C3=B6m?= Date: Mon, 6 Oct 2025 10:28:43 +0200 Subject: [PATCH 08/12] Update LetPot integration quality scale to silver (#153783) --- homeassistant/components/letpot/manifest.json | 2 +- homeassistant/components/letpot/quality_scale.yaml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/letpot/manifest.json b/homeassistant/components/letpot/manifest.json index 26f7588033c32..60695a91e2bbb 100644 --- a/homeassistant/components/letpot/manifest.json +++ b/homeassistant/components/letpot/manifest.json @@ -7,6 +7,6 @@ "integration_type": "hub", "iot_class": "cloud_push", "loggers": ["letpot"], - "quality_scale": "bronze", + "quality_scale": "silver", "requirements": ["letpot==0.6.2"] } diff --git a/homeassistant/components/letpot/quality_scale.yaml b/homeassistant/components/letpot/quality_scale.yaml index f5e88bfc36978..3931e7eabed5f 100644 --- a/homeassistant/components/letpot/quality_scale.yaml +++ b/homeassistant/components/letpot/quality_scale.yaml @@ -41,7 +41,10 @@ rules: docs-installation-parameters: done entity-unavailable: done integration-owner: done - log-when-unavailable: todo + log-when-unavailable: + status: done + comment: | + Logging handled by library when (un)available once (push) or coordinator (pull). parallel-updates: done reauthentication-flow: done test-coverage: done From b4997a52dfa4e411bacf1f6e551b1054d287f571 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 6 Oct 2025 10:57:21 +0200 Subject: [PATCH 09/12] Remove stale entities from Alexa Devices (#153759) --- .../components/alexa_devices/binary_sensor.py | 48 +++++++++++++++++ .../components/alexa_devices/switch.py | 9 +++- .../components/alexa_devices/utils.py | 20 +++++++ .../alexa_devices/test_binary_sensor.py | 52 ++++++++++++++++++- tests/components/alexa_devices/test_utils.py | 40 ++++++++++++++ 5 files changed, 167 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/alexa_devices/binary_sensor.py b/homeassistant/components/alexa_devices/binary_sensor.py index 8347fa34423a6..dd8be49f52163 100644 --- a/homeassistant/components/alexa_devices/binary_sensor.py +++ b/homeassistant/components/alexa_devices/binary_sensor.py @@ -18,7 +18,9 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +import homeassistant.helpers.entity_registry as er +from .const import _LOGGER, DOMAIN from .coordinator import AmazonConfigEntry from .entity import AmazonEntity from .utils import async_update_unique_id @@ -58,6 +60,40 @@ class AmazonBinarySensorEntityDescription(BinarySensorEntityDescription): ), ) +DEPRECATED_BINARY_SENSORS: Final = ( + AmazonBinarySensorEntityDescription( + key="bluetooth", + entity_category=EntityCategory.DIAGNOSTIC, + translation_key="bluetooth", + is_on_fn=lambda device, key: False, + ), + AmazonBinarySensorEntityDescription( + key="babyCryDetectionState", + translation_key="baby_cry_detection", + is_on_fn=lambda device, key: False, + ), + AmazonBinarySensorEntityDescription( + key="beepingApplianceDetectionState", + translation_key="beeping_appliance_detection", + is_on_fn=lambda device, key: False, + ), + AmazonBinarySensorEntityDescription( + key="coughDetectionState", + translation_key="cough_detection", + is_on_fn=lambda device, key: False, + ), + AmazonBinarySensorEntityDescription( + key="dogBarkDetectionState", + translation_key="dog_bark_detection", + is_on_fn=lambda device, key: False, + ), + AmazonBinarySensorEntityDescription( + key="waterSoundsDetectionState", + translation_key="water_sounds_detection", + is_on_fn=lambda device, key: False, + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -68,6 +104,8 @@ async def async_setup_entry( coordinator = entry.runtime_data + entity_registry = er.async_get(hass) + # Replace unique id for "detectionState" binary sensor await async_update_unique_id( hass, @@ -77,6 +115,16 @@ async def async_setup_entry( "detectionState", ) + # Clean up deprecated sensors + for sensor_desc in DEPRECATED_BINARY_SENSORS: + for serial_num in coordinator.data: + unique_id = f"{serial_num}-{sensor_desc.key}" + if entity_id := entity_registry.async_get_entity_id( + BINARY_SENSOR_DOMAIN, DOMAIN, unique_id + ): + _LOGGER.debug("Removing deprecated entity %s", entity_id) + entity_registry.async_remove(entity_id) + known_devices: set[str] = set() def _check_device() -> None: diff --git a/homeassistant/components/alexa_devices/switch.py b/homeassistant/components/alexa_devices/switch.py index 003f57620798c..5acaa8c2dd0ee 100644 --- a/homeassistant/components/alexa_devices/switch.py +++ b/homeassistant/components/alexa_devices/switch.py @@ -18,7 +18,11 @@ from .coordinator import AmazonConfigEntry from .entity import AmazonEntity -from .utils import alexa_api_call, async_update_unique_id +from .utils import ( + alexa_api_call, + async_remove_dnd_from_virtual_group, + async_update_unique_id, +) PARALLEL_UPDATES = 1 @@ -60,6 +64,9 @@ async def async_setup_entry( hass, coordinator, SWITCH_DOMAIN, "do_not_disturb", "dnd" ) + # Remove DND switch from virtual groups + await async_remove_dnd_from_virtual_group(hass, coordinator) + known_devices: set[str] = set() def _check_device() -> None: diff --git a/homeassistant/components/alexa_devices/utils.py b/homeassistant/components/alexa_devices/utils.py index f8898aa5fe46d..3fbba539a6a98 100644 --- a/homeassistant/components/alexa_devices/utils.py +++ b/homeassistant/components/alexa_devices/utils.py @@ -4,8 +4,10 @@ from functools import wraps from typing import Any, Concatenate +from aioamazondevices.const import SPEAKER_GROUP_FAMILY from aioamazondevices.exceptions import CannotConnect, CannotRetrieveData +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.entity_registry as er @@ -61,3 +63,21 @@ async def async_update_unique_id( # Update the registry with the new unique_id entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id) + + +async def async_remove_dnd_from_virtual_group( + hass: HomeAssistant, + coordinator: AmazonDevicesCoordinator, +) -> None: + """Remove entity DND from virtual group.""" + entity_registry = er.async_get(hass) + + for serial_num in coordinator.data: + unique_id = f"{serial_num}-do_not_disturb" + entity_id = entity_registry.async_get_entity_id( + DOMAIN, SWITCH_DOMAIN, unique_id + ) + is_group = coordinator.data[serial_num].device_family == SPEAKER_GROUP_FAMILY + if entity_id and is_group: + entity_registry.async_remove(entity_id) + _LOGGER.debug("Removed DND switch from virtual group %s", entity_id) diff --git a/tests/components/alexa_devices/test_binary_sensor.py b/tests/components/alexa_devices/test_binary_sensor.py index 6b55a701b459a..a6775ec8fc9b6 100644 --- a/tests/components/alexa_devices/test_binary_sensor.py +++ b/tests/components/alexa_devices/test_binary_sensor.py @@ -11,10 +11,12 @@ import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.alexa_devices.const import DOMAIN from homeassistant.components.alexa_devices.coordinator import SCAN_INTERVAL +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er from . import setup_integration from .const import TEST_DEVICE_1, TEST_DEVICE_1_SN, TEST_DEVICE_2, TEST_DEVICE_2_SN @@ -139,3 +141,51 @@ async def test_dynamic_device( assert (state := hass.states.get(entity_id_2)) assert state.state == STATE_ON + + +@pytest.mark.parametrize( + "key", + [ + "bluetooth", + "babyCryDetectionState", + "beepingApplianceDetectionState", + "coughDetectionState", + "dogBarkDetectionState", + "waterSoundsDetectionState", + ], +) +async def test_deprecated_sensor_removal( + hass: HomeAssistant, + mock_amazon_devices_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + key: str, +) -> None: + """Test deprecated sensors are removed.""" + + mock_config_entry.add_to_hass(hass) + + device = device_registry.async_get_or_create( + config_entry_id=mock_config_entry.entry_id, + identifiers={(DOMAIN, mock_config_entry.entry_id)}, + name=mock_config_entry.title, + manufacturer="Amazon", + model="Echo Dot", + entry_type=dr.DeviceEntryType.SERVICE, + ) + + entity = entity_registry.async_get_or_create( + BINARY_SENSOR_DOMAIN, + DOMAIN, + unique_id=f"{TEST_DEVICE_1_SN}-{key}", + device_id=device.id, + config_entry=mock_config_entry, + has_entity_name=True, + ) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + entity2 = entity_registry.async_get(entity.entity_id) + assert entity2 is None diff --git a/tests/components/alexa_devices/test_utils.py b/tests/components/alexa_devices/test_utils.py index 020971d8f76f8..3424227673c9b 100644 --- a/tests/components/alexa_devices/test_utils.py +++ b/tests/components/alexa_devices/test_utils.py @@ -2,6 +2,7 @@ from unittest.mock import AsyncMock +from aioamazondevices.const import SPEAKER_GROUP_FAMILY, SPEAKER_GROUP_MODEL from aioamazondevices.exceptions import CannotConnect, CannotRetrieveData import pytest @@ -94,3 +95,42 @@ async def test_alexa_unique_id_migration( assert migrated_entity is not None assert migrated_entity.config_entry_id == mock_config_entry.entry_id assert migrated_entity.unique_id == f"{TEST_DEVICE_1_SN}-dnd" + + +async def test_alexa_dnd_group_removal( + hass: HomeAssistant, + mock_amazon_devices_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test dnd switch is removed for Speaker Groups.""" + + mock_config_entry.add_to_hass(hass) + + device = device_registry.async_get_or_create( + config_entry_id=mock_config_entry.entry_id, + identifiers={(DOMAIN, mock_config_entry.entry_id)}, + name=mock_config_entry.title, + manufacturer="Amazon", + model=SPEAKER_GROUP_MODEL, + entry_type=dr.DeviceEntryType.SERVICE, + ) + + entity = entity_registry.async_get_or_create( + DOMAIN, + SWITCH_DOMAIN, + unique_id=f"{TEST_DEVICE_1_SN}-do_not_disturb", + device_id=device.id, + config_entry=mock_config_entry, + has_entity_name=True, + ) + + mock_amazon_devices_client.get_devices_data.return_value[ + TEST_DEVICE_1_SN + ].device_family = SPEAKER_GROUP_FAMILY + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert not hass.states.get(entity.entity_id) From 4a6d2017fdbef27039d1b23f0985282ef65ccce3 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 6 Oct 2025 10:58:24 +0200 Subject: [PATCH 10/12] Fix stale docstring in recorder (#153811) --- homeassistant/components/recorder/migration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 1c53b52814155..14c4544db4356 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -2490,7 +2490,7 @@ def __init__( start_schema_version: int, migration_changes: dict[str, int], ) -> None: - """Initialize a new BaseRunTimeMigration. + """Initialize a new BaseMigration. :param initial_schema_version: The schema version the database was created with. :param start_schema_version: The schema version when starting the migration. From 1c1fbe0ec1b70f682c29002ac093093b0b03f403 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 6 Oct 2025 10:58:47 +0200 Subject: [PATCH 11/12] Log when failing to remove foreign key in recorder EventIDPostMigration (#153812) --- homeassistant/components/recorder/migration.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 14c4544db4356..708be5eab2095 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -2964,7 +2964,12 @@ def migrate_data_impl(self, instance: Recorder) -> DataMigrationStatus: _drop_foreign_key_constraints( session_maker, instance.engine, TABLE_STATES, "event_id" ) - except (InternalError, OperationalError): + except (InternalError, OperationalError) as err: + _LOGGER.debug( + "Could not drop foreign key constraint on states.event_id, " + "will try again later", + exc_info=err, + ) fk_remove_ok = False else: fk_remove_ok = True From 2447df9341120be6b1eade609d74960a09e7a13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Mon, 6 Oct 2025 11:09:59 +0200 Subject: [PATCH 12/12] Add Matter speaker mute toggle (#150104) Add Matter speaker mute toggle functionality: - OnOff attribute == True state means volume is on, so HA should show mute switch as off - OnOff attribute == False means volume is off (muted), so HA should show mute switch as on --- homeassistant/components/matter/icons.json | 7 + homeassistant/components/matter/number.py | 1 + homeassistant/components/matter/strings.json | 3 + homeassistant/components/matter/switch.py | 19 +- tests/components/matter/conftest.py | 1 + .../matter/fixtures/nodes/speaker.json | 237 ++++++++++++++++++ .../matter/snapshots/test_switch.ambr | 48 ++++ 7 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 tests/components/matter/fixtures/nodes/speaker.json diff --git a/homeassistant/components/matter/icons.json b/homeassistant/components/matter/icons.json index f21a7b7a931ef..92c6e38a6102c 100644 --- a/homeassistant/components/matter/icons.json +++ b/homeassistant/components/matter/icons.json @@ -146,6 +146,13 @@ "off": "mdi:lock-off" } }, + "speaker_mute": { + "default": "mdi:volume-high", + "state": { + "on": "mdi:volume-mute", + "off": "mdi:volume-high" + } + }, "evse_charging_switch": { "default": "mdi:ev-station" }, diff --git a/homeassistant/components/matter/number.py b/homeassistant/components/matter/number.py index f9783127673a0..4162d406e7cb8 100644 --- a/homeassistant/components/matter/number.py +++ b/homeassistant/components/matter/number.py @@ -176,6 +176,7 @@ def _update_from_device(self) -> None: ), entity_class=MatterNumber, required_attributes=(clusters.LevelControl.Attributes.OnLevel,), + not_device_type=(device_types.Speaker,), # allow None value to account for 'default' value allow_none_value=True, ), diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index a46fbddd61244..6766cd57e5e65 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -514,6 +514,9 @@ "power": { "name": "Power" }, + "speaker_mute": { + "name": "Mute" + }, "child_lock": { "name": "Child lock" }, diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py index 2c02522f0a177..682285e9c97ce 100644 --- a/homeassistant/components/matter/switch.py +++ b/homeassistant/components/matter/switch.py @@ -203,7 +203,6 @@ def _update_from_device(self) -> None: device_types.Refrigerator, device_types.RoboticVacuumCleaner, device_types.RoomAirConditioner, - device_types.Speaker, ), ), MatterDiscoverySchema( @@ -242,6 +241,24 @@ def _update_from_device(self) -> None: device_types.Speaker, ), ), + MatterDiscoverySchema( + platform=Platform.SWITCH, + entity_description=MatterNumericSwitchEntityDescription( + key="MatterMuteToggle", + translation_key="speaker_mute", + device_to_ha={ + True: False, # True means volume is on, so HA should show mute as off + False: True, # False means volume is off (muted), so HA should show mute as on + }.get, + ha_to_device={ + False: True, # HA showing mute as off means volume is on, so send True + True: False, # HA showing mute as on means volume is off (muted), so send False + }.get, + ), + entity_class=MatterNumericSwitch, + required_attributes=(clusters.OnOff.Attributes.OnOff,), + device_type=(device_types.Speaker,), + ), MatterDiscoverySchema( platform=Platform.SWITCH, entity_description=MatterNumericSwitchEntityDescription( diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py index 9b82f2ac30559..b7f55ec2abde5 100644 --- a/tests/components/matter/conftest.py +++ b/tests/components/matter/conftest.py @@ -120,6 +120,7 @@ async def integration_fixture( "silabs_water_heater", "smoke_detector", "solar_power", + "speaker", "switch_unit", "tado_smart_radiator_thermostat_x", "temperature_sensor", diff --git a/tests/components/matter/fixtures/nodes/speaker.json b/tests/components/matter/fixtures/nodes/speaker.json new file mode 100644 index 0000000000000..f28923b3b3c96 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/speaker.json @@ -0,0 +1,237 @@ +{ + "node_id": 107, + "date_commissioned": "2025-07-21T13:03:35.743927", + "last_interview": "2025-07-23T12:06:45.342425", + "interview_version": 6, + "available": true, + "is_bridge": false, + "attributes": { + "0/29/0": [ + { + "0": 22, + "1": 3 + } + ], + "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63], + "0/29/2": [], + "0/29/3": [1], + "0/29/65532": 0, + "0/29/65533": 3, + "0/29/65528": [], + "0/29/65529": [], + "0/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531], + "0/31/0": [ + { + "254": 1 + }, + { + "254": 2 + }, + { + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 3 + } + ], + "0/31/2": 4, + "0/31/4": 4, + "0/31/3": 3, + "0/31/65532": 0, + "0/31/65533": 2, + "0/31/65528": [], + "0/31/65529": [], + "0/31/65531": [0, 2, 4, 3, 65532, 65533, 65528, 65529, 65531], + "0/40/65532": 0, + "0/40/0": 19, + "0/40/6": "**REDACTED**", + "0/40/1": "Beep Home", + "0/40/2": 65521, + "0/40/3": "Mock speaker", + "0/40/4": 32768, + "0/40/7": 0, + "0/40/8": "1.0", + "0/40/9": 1, + "0/40/10": "1.0", + "0/40/18": "A576929DE6D138DC", + "0/40/19": { + "0": 3, + "1": 3 + }, + "0/40/21": 17104896, + "0/40/22": 1, + "0/40/65533": 5, + "0/40/5": "", + "0/40/65528": [], + "0/40/65529": [], + "0/40/65531": [ + 65532, 0, 6, 1, 2, 3, 4, 7, 8, 9, 10, 18, 19, 21, 22, 65533, 5, 65528, + 65529, 65531 + ], + "0/48/65532": 0, + "0/48/2": 0, + "0/48/3": 0, + "0/48/1": { + "0": 60, + "1": 900 + }, + "0/48/4": true, + "0/48/65533": 2, + "0/48/0": 0, + "0/48/65528": [1, 3, 5], + "0/48/65529": [0, 2, 4], + "0/48/65531": [65532, 2, 3, 1, 4, 65533, 0, 65528, 65529, 65531], + "0/49/0": 1, + "0/49/1": [ + { + "0": "", + "1": true + } + ], + "0/49/4": true, + "0/49/5": null, + "0/49/6": null, + "0/49/7": null, + "0/49/65532": 4, + "0/49/65533": 2, + "0/49/65528": [], + "0/49/65529": [], + "0/49/65531": [0, 1, 4, 5, 6, 7, 65532, 65533, 65528, 65529, 65531], + "0/51/0": [ + { + "0": "ETH_DEF", + "1": true, + "2": null, + "3": null, + "4": "dk29//5j", + "5": ["wKhP9A=="], + "6": ["/oAAAAAAAAB0Tb3//v/+Yw==", "/Qn01rCr1Hl0Tb3//v/+Yw=="], + "7": 2 + } + ], + "0/51/1": 12, + "0/51/2": 105, + "0/51/8": false, + "0/51/65532": 0, + "0/51/65533": 2, + "0/51/65528": [2], + "0/51/65529": [0, 1], + "0/51/65531": [0, 1, 2, 8, 65532, 65533, 65528, 65529, 65531], + "0/60/65532": 0, + "0/60/0": 1, + "0/60/1": 1, + "0/60/2": 4937, + "0/60/65533": 1, + "0/60/65528": [], + "0/60/65529": [0, 2], + "0/60/65531": [65532, 0, 1, 2, 65533, 65528, 65529, 65531], + "0/62/65532": 0, + "0/62/0": [ + { + "1": "FTABAQEkAgE3AyYUr7XOACYVpq4e6hgmBID2EDAkBQA3BiYVpq4e6iYRHXXVNxgkBwEkCAEwCUEEKNQ0xxl6/rzKRobxdnx+QXGmjCSeFUw6bBTrKgfYVkDVFUuqg9hozYtzj142gJXKYE5a6VZQUrdX12BkVPmm9zcKNQEoARgkAgE2AwQCBAEYMAQU4DNJO9VPf30U7ckieLsZ9Ab89WwwBRQuto07k43Cuvos2+idCKKlR2DjxBgwC0DCJxyTPnVbXleKY4QGudFSpLRwjTOl35z2AqVxPH/EcEM4jRc7dh/8K+x0yzJ6I8+2pm40nwICfzITnm62Am5QGA==", + "2": null, + "254": 1 + }, + { + "1": "FTABAQEkAgE3AycU13xCfZkhuUsmFSYjBIkYJgSH9hAwJAUANwYmFSYjBIkmEVAYUpUYJAcBJAgBMAlBBN7EuwOoVRUJW2qlVIHm4C2zH8hUxwSehRhqrihihxJSS8CVjwB8FsiQ4ChGFVJ/ZM8CzDIxROm0SNN7L199Czw3CjUBKAEYJAIBNgMEAgQBGDAEFNKmpZs7t/7neBldyIiSUc4qtAwrMAUUqSSMvmWet6YT/IEvHDGHhlJUr8QYMAtA/qCoDPaDbjHhVAkr9VBugUQX8QGqUADnjhbWljjl8t7eA9bttntqwZzsB2AGPeYYg7B3E5SIGPpefLFFh9mnSBg=", + "2": null, + "254": 2 + }, + { + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRaxgkBwEkCAEwCUEEWKrZlA9bVwcwMsGjb+7gW8OpSFz3uosgT4gDydSflnuU+gDEumOBodeEQRR4rA0J1n1xIXLi5SAXEgHNDe/IzTcKNQEoARgkAgE2AwQCBAEYMAQUKcKB0dKsw1C31hygt4xKX2DdjiIwBRRi03D+l+yahKETUhU/VMUJYJK7CBgwC0DUBJ6vh/KWrjHdWWBmTSFs4dRye6+TD4nSALMhT0jgDPzGHI6yhMsrbs0/GlXfMNruVmSVOdYDjzANhzwKTjr/GA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE9ZELz0go1r5np3XYnsCRPOqEWH9H/5Sg3RLTVeN8iq4MthnWplYvLfXFvMVaw8IEXHD/97aRGKUCkvTHW32PZTcKNQEpARgkAmAwBBRi03D+l+yahKETUhU/VMUJYJK7CDAFFJ1roLAG+djI0sZE5SMRIPfcwCOvGDALQDi7JqN8dF7piijkZ/YQddu4yTRfN9gonsLIOQ+AHaxA4WNr0lxlK5Lx/PPDA6T1CcaIbUOZ3p7sMOaSWZQSvnIY", + "254": 3 + } + ], + "0/62/2": 5, + "0/62/3": 3, + "0/62/1": [ + { + "1": "BCLmZe5bFElV+dZkFNaQLhuxkAejdcY41G7ZVTob2ezSI3MUZWSM7nFSJP/5hNA1FokKHg5WnX6nAfnX4eMoc98=", + "2": 4937, + "3": 3927879334, + "4": 936736029, + "5": "Home", + "254": 1 + }, + { + "1": "BM19vyMJrIK/NEJUy/J3yfZxzPLt0NSSq/31Uoo1g8Bgby+YKP3Gj3AA5AKTaPppx9aSfO1wYbPVyFaLF6ISaE8=", + "2": 4996, + "3": 2298749734, + "4": 2505185360, + "5": "", + "254": 2 + }, + { + "1": "BE63W9VgU/wxhR2+c0RPC4BFOE9X6bu0nfUuH7SErlaZKcNqSxKy3Qy3k1gjA3nkFT/1VzHzqXGuMJ3kwTRySDs=", + "2": 4939, + "3": 2, + "4": 107, + "5": "", + "254": 3 + } + ], + "0/62/4": [ + "FTABAQAkAgE3AyYUr7XOACYVpq4e6hgmBHdODDAkBQA3BiYUr7XOACYVpq4e6hgkBwEkCAEwCUEEIuZl7lsUSVX51mQU1pAuG7GQB6N1xjjUbtlVOhvZ7NIjcxRlZIzucVIk//mE0DUWiQoeDladfqcB+dfh4yhz3zcKNQEpARgkAmAwBBQuto07k43Cuvos2+idCKKlR2DjxDAFFC62jTuTjcK6+izb6J0IoqVHYOPEGDALQKjJ/1H3GFUojevBS05lU+idNFpSbXbuzkMtixrBKa++dMvTPBX8fqp0ElvMHiSOGoHhBS07bc26vRe7nWcz+FAY", + "FTABAQAkAgE3AycU13xCfZkhuUsmFSYjBIkYJgRw+UstJAUANwYnFNd8Qn2ZIblLJhUmIwSJGCQHASQIATAJQQTNfb8jCayCvzRCVMvyd8n2cczy7dDUkqv99VKKNYPAYG8vmCj9xo9wAOQCk2j6acfWknztcGGz1chWixeiEmhPNwo1ASkBGCQCYDAEFKkkjL5lnremE/yBLxwxh4ZSVK/EMAUUqSSMvmWet6YT/IEvHDGHhlJUr8QYMAtAG4951kJhhdOpU2mr57a1uSmhdp7o1kcFYS88DvQEZXoZVfKXQNSwTAxapobGado5U7FdTlOihLlZRTNqtZZTjhg=", + "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEETrdb1WBT/DGFHb5zRE8LgEU4T1fpu7Sd9S4ftISuVpkpw2pLErLdDLeTWCMDeeQVP/VXMfOpca4wneTBNHJIOzcKNQEpARgkAmAwBBSda6CwBvnYyNLGROUjESD33MAjrzAFFJ1roLAG+djI0sZE5SMRIPfcwCOvGDALQNEMd8zHt6yaUCoi+atIEWEVTc7VKCxYNRkxFg64YWOaHEjo4rJ022E3DtQRdXC7K0aEkctOhqVYLn7yq8Vk6tkY" + ], + "0/62/5": 3, + "0/62/65533": 2, + "0/62/65528": [1, 3, 5, 8, 14], + "0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11, 12, 13], + "0/62/65531": [65532, 0, 2, 3, 1, 4, 5, 65533, 65528, 65529, 65531], + "0/63/65532": 0, + "0/63/65533": 2, + "0/63/0": [], + "0/63/1": [], + "0/63/2": 4, + "0/63/3": 3, + "0/63/65528": [2, 5], + "0/63/65529": [0, 1, 3, 4], + "0/63/65531": [65532, 65533, 0, 1, 2, 3, 65528, 65529, 65531], + "1/29/0": [ + { + "0": 34, + "1": 1 + } + ], + "1/29/1": [29, 3, 6, 8, 30], + "1/29/2": [], + "1/29/3": [2, 3], + "1/29/65532": 0, + "1/29/65533": 3, + "1/29/65528": [], + "1/29/65529": [], + "1/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531], + "1/3/65532": 0, + "1/3/65533": 5, + "1/3/0": 15, + "1/3/1": 0, + "1/3/65528": [], + "1/3/65529": [0], + "1/3/65531": [65532, 65533, 0, 1, 65528, 65529, 65531], + "1/6/65532": 0, + "1/6/65533": 6, + "1/6/0": true, + "1/6/65528": [], + "1/6/65529": [0], + "1/6/65531": [65532, 65533, 0, 65528, 65529, 65531], + "1/8/65532": 0, + "1/8/65533": 6, + "1/8/0": 47, + "1/8/17": null, + "1/8/15": 0, + "1/8/65528": [], + "1/8/65529": [0, 1, 2, 3, 4, 5, 6, 7], + "1/8/65531": [65532, 65533, 0, 17, 15, 65528, 65529, 65531], + "1/30/65532": 0, + "1/30/0": [], + "1/30/65533": 1, + "1/30/65528": [], + "1/30/65529": [], + "1/30/65531": [65532, 0, 65533, 65528, 65529, 65531] + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/snapshots/test_switch.ambr b/tests/components/matter/snapshots/test_switch.ambr index d7c2aba92a398..50542fcdddc0e 100644 --- a/tests/components/matter/snapshots/test_switch.ambr +++ b/tests/components/matter/snapshots/test_switch.ambr @@ -828,6 +828,54 @@ 'state': 'off', }) # --- +# name: test_switches[speaker][switch.mock_speaker_mute-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.mock_speaker_mute', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Mute', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'speaker_mute', + 'unique_id': '00000000000004D2-000000000000006B-MatterNodeDevice-1-MatterMuteToggle-6-0', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[speaker][switch.mock_speaker_mute-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock speaker Mute', + }), + 'context': , + 'entity_id': 'switch.mock_speaker_mute', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[switch_unit][switch.mock_switchunit-entry] EntityRegistryEntrySnapshot({ 'aliases': set({