mirror of
				https://github.com/home-assistant/core.git
				synced 2025-11-04 00:19:31 +00:00 
			
		
		
		
	Remove stale entities from Alexa Devices (#153759)
This commit is contained in:
		
				
					committed by
					
						
						Franck Nijhof
					
				
			
			
				
	
			
			
			
						parent
						
							6f3f5a5ec1
						
					
				
				
					commit
					69d9fa89b7
				
			@@ -18,7 +18,9 @@ from homeassistant.components.binary_sensor import (
 | 
			
		||||
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 @@ BINARY_SENSORS: Final = (
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,11 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,10 @@ from collections.abc import Awaitable, Callable, Coroutine
 | 
			
		||||
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)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,12 @@ from freezegun.api import FrozenDateTimeFactory
 | 
			
		||||
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
 | 
			
		||||
@@ -137,3 +139,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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user