diff --git a/homeassistant/components/alexa_devices/event.py b/homeassistant/components/alexa_devices/event.py index 16a0b9f47e65..47439e5e8ce0 100644 --- a/homeassistant/components/alexa_devices/event.py +++ b/homeassistant/components/alexa_devices/event.py @@ -2,8 +2,6 @@ from typing import Final, override -from aioamazondevices.const.devices import SPEAKER_GROUP_FAMILY - from homeassistant.components.event import ( DOMAIN as EVENT_DOMAIN, EventEntity, @@ -15,11 +13,12 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import LOGGER from .coordinator import AmazonConfigEntry, AmazonDevicesCoordinator from .entity import AmazonEntity -from .utils import async_remove_entity_from_virtual_group +from .utils import async_remove_entities # Coordinator is used to centralize the data updates PARALLEL_UPDATES = 0 + EVENTS: Final = { EventEntityDescription( key="voice_event", @@ -38,9 +37,13 @@ async def async_setup_entry( """Set up Alexa Devices events based on a config entry.""" coordinator = entry.runtime_data - # Remove voice event from virtual groups - await async_remove_entity_from_virtual_group( - hass, coordinator, EVENT_DOMAIN, "voice_event" + # Remove voice event from virtual groups and AQM devices + await async_remove_entities( + hass, + coordinator, + EVENT_DOMAIN, + "voice_event", + remove_fn=lambda device: not device.voice_control_supported, ) known_devices: set[str] = set() @@ -55,7 +58,7 @@ async def async_setup_entry( AlexaVoiceEvent(coordinator, serial_num, event_desc) for event_desc in EVENTS for serial_num in new_devices - if coordinator.data[serial_num].device_family != SPEAKER_GROUP_FAMILY + if coordinator.data[serial_num].voice_control_supported ) _check_device() diff --git a/homeassistant/components/alexa_devices/switch.py b/homeassistant/components/alexa_devices/switch.py index 05af1ab42912..60193a8d41cf 100644 --- a/homeassistant/components/alexa_devices/switch.py +++ b/homeassistant/components/alexa_devices/switch.py @@ -4,6 +4,7 @@ from collections.abc import Callable from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Final, override +from aioamazondevices.const.devices import SPEAKER_GROUP_FAMILY from aioamazondevices.structures import AmazonDevice from homeassistant.components.switch import ( @@ -17,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .coordinator import AmazonConfigEntry, alexa_api_call from .entity import AmazonEntity -from .utils import async_remove_entity_from_virtual_group, async_update_unique_id +from .utils import async_remove_entities, async_update_unique_id PARALLEL_UPDATES = 1 @@ -90,8 +91,12 @@ async def async_setup_entry( new_key = "dnd" # Remove old DND switch from virtual groups - await async_remove_entity_from_virtual_group( - hass, coordinator, SWITCH_DOMAIN, old_key + await async_remove_entities( + hass, + coordinator, + SWITCH_DOMAIN, + old_key, + remove_fn=lambda device: device.device_family == SPEAKER_GROUP_FAMILY, ) # Replace unique id for DND switch diff --git a/homeassistant/components/alexa_devices/utils.py b/homeassistant/components/alexa_devices/utils.py index 38532109d133..6a629a4efc10 100644 --- a/homeassistant/components/alexa_devices/utils.py +++ b/homeassistant/components/alexa_devices/utils.py @@ -1,11 +1,13 @@ """Utils for Alexa Devices.""" -from aioamazondevices.const.devices import SPEAKER_GROUP_FAMILY +from collections.abc import Callable + from aioamazondevices.const.schedules import ( NOTIFICATION_ALARM, NOTIFICATION_REMINDER, NOTIFICATION_TIMER, ) +from aioamazondevices.structures import AmazonDevice from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -37,22 +39,24 @@ async def async_update_unique_id( entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id) -async def async_remove_entity_from_virtual_group( +async def async_remove_entities( hass: HomeAssistant, coordinator: AmazonDevicesCoordinator, platform: str, key: str, + remove_fn: Callable[[AmazonDevice], bool], ) -> None: - """Remove entity from virtual group.""" + """Remove entities matching remove_fn from the registry.""" entity_registry = er.async_get(hass) - for serial_num in coordinator.data: + for serial_num, device in coordinator.data.items(): + if not remove_fn(device): + continue unique_id = f"{serial_num}-{key}" entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, unique_id) - is_group = coordinator.data[serial_num].device_family == SPEAKER_GROUP_FAMILY - if entity_id and is_group: + if entity_id: entity_registry.async_remove(entity_id) - LOGGER.debug("Removed entity '%s' from virtual group", entity_id) + LOGGER.debug("Removed entity '%s'", entity_id) async def async_remove_unsupported_notification_sensors( diff --git a/tests/components/alexa_devices/test_event.py b/tests/components/alexa_devices/test_event.py index 3cc5b70de786..6eba494df7e6 100644 --- a/tests/components/alexa_devices/test_event.py +++ b/tests/components/alexa_devices/test_event.py @@ -6,9 +6,10 @@ from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.alexa_devices.const import DOMAIN from homeassistant.const import STATE_UNKNOWN, 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 assert_device_removed_and_readded, setup_integration from .const import ( @@ -94,3 +95,57 @@ async def test_device_removed_and_readded( devices_with={TEST_DEVICE_1_SN: TEST_DEVICE_1, TEST_DEVICE_2_SN: TEST_DEVICE_2}, devices_without={TEST_DEVICE_1_SN: TEST_DEVICE_1}, ) + + +async def test_voice_event_not_created_for_unsupported_device( + hass: HomeAssistant, + mock_amazon_devices_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test voice event entity is not created for devices without voice control support.""" + mock_amazon_devices_client.get_devices_data.return_value[ + TEST_DEVICE_1_SN + ].voice_control_supported = False + + with patch("homeassistant.components.alexa_devices.PLATFORMS", [Platform.EVENT]): + await setup_integration(hass, mock_config_entry) + + assert not hass.states.get(ENTITY_ID) + + +async def test_voice_event_removed_for_unsupported_device( + hass: HomeAssistant, + mock_amazon_devices_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test voice event entity is removed for devices without voice control support.""" + mock_config_entry.add_to_hass(hass) + + device = device_registry.async_get_or_create( + config_entry_id=mock_config_entry.entry_id, + identifiers={(DOMAIN, TEST_DEVICE_1_SN)}, + name="Echo Test", + manufacturer="Amazon", + model="Echo Dot", + ) + + entity = entity_registry.async_get_or_create( + Platform.EVENT, + DOMAIN, + unique_id=f"{TEST_DEVICE_1_SN}-voice_event", + 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 + ].voice_control_supported = False + + 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) + assert entity_registry.async_get(entity.entity_id) is None