mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Fix tplink deprecated entity cleanup (#136160)
This commit is contained in:
parent
b2624e6274
commit
2466df2b78
@ -17,7 +17,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from .deprecate import async_cleanup_deprecated
|
||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||
|
||||
|
||||
@ -88,12 +87,10 @@ async def async_setup_entry(
|
||||
feature_type=Feature.Type.BinarySensor,
|
||||
entity_class=TPLinkBinarySensorEntity,
|
||||
descriptions=BINARYSENSOR_DESCRIPTIONS_MAP,
|
||||
platform_domain=BINARY_SENSOR_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
async_cleanup_deprecated(
|
||||
hass, BINARY_SENSOR_DOMAIN, config_entry.entry_id, entities
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
_check_device()
|
||||
|
@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from .deprecate import DeprecatedInfo, async_cleanup_deprecated
|
||||
from .deprecate import DeprecatedInfo
|
||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||
|
||||
|
||||
@ -95,10 +95,10 @@ async def async_setup_entry(
|
||||
feature_type=Feature.Type.Action,
|
||||
entity_class=TPLinkButtonEntity,
|
||||
descriptions=BUTTON_DESCRIPTIONS_MAP,
|
||||
platform_domain=BUTTON_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
async_cleanup_deprecated(hass, BUTTON_DOMAIN, config_entry.entry_id, entities)
|
||||
async_add_entities(entities)
|
||||
|
||||
_check_device()
|
||||
|
@ -11,6 +11,7 @@ from kasa import Device, Module, StreamResolution
|
||||
|
||||
from homeassistant.components import ffmpeg, stream
|
||||
from homeassistant.components.camera import (
|
||||
DOMAIN as CAMERA_DOMAIN,
|
||||
Camera,
|
||||
CameraEntityDescription,
|
||||
CameraEntityFeature,
|
||||
@ -20,7 +21,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry, legacy_device_id
|
||||
from . import TPLinkConfigEntry
|
||||
from .const import CONF_CAMERA_CREDENTIALS
|
||||
from .coordinator import TPLinkDataUpdateCoordinator
|
||||
from .entity import CoordinatedTPLinkModuleEntity, TPLinkModuleEntityDescription
|
||||
@ -75,6 +76,7 @@ async def async_setup_entry(
|
||||
coordinator=parent_coordinator,
|
||||
entity_class=TPLinkCameraEntity,
|
||||
descriptions=CAMERA_DESCRIPTIONS,
|
||||
platform_domain=CAMERA_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
@ -121,10 +123,6 @@ class TPLinkCameraEntity(CoordinatedTPLinkModuleEntity, Camera):
|
||||
self._can_stream = True
|
||||
self._http_mpeg_stream_running = False
|
||||
|
||||
def _get_unique_id(self) -> str:
|
||||
"""Return unique ID for the entity."""
|
||||
return f"{legacy_device_id(self._device)}-{self.entity_description.key}"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call update attributes after the device is added to the platform."""
|
||||
await super().async_added_to_hass()
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
@ -11,6 +12,7 @@ from kasa.smart.modules.temperaturecontrol import ThermostatState
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_TEMPERATURE,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
ClimateEntity,
|
||||
ClimateEntityDescription,
|
||||
ClimateEntityFeature,
|
||||
@ -22,7 +24,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from . import TPLinkConfigEntry, legacy_device_id
|
||||
from .const import DOMAIN, UNIT_MAPPING
|
||||
from .coordinator import TPLinkDataUpdateCoordinator
|
||||
from .entity import (
|
||||
@ -52,6 +54,10 @@ class TPLinkClimateEntityDescription(
|
||||
):
|
||||
"""Base class for climate entity description."""
|
||||
|
||||
unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = (
|
||||
lambda device, desc: f"{legacy_device_id(device)}_{desc.key}"
|
||||
)
|
||||
|
||||
|
||||
CLIMATE_DESCRIPTIONS: tuple[TPLinkClimateEntityDescription, ...] = (
|
||||
TPLinkClimateEntityDescription(
|
||||
@ -81,6 +87,7 @@ async def async_setup_entry(
|
||||
coordinator=parent_coordinator,
|
||||
entity_class=TPLinkClimateEntity,
|
||||
descriptions=CLIMATE_DESCRIPTIONS,
|
||||
platform_domain=CLIMATE_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
@ -182,7 +189,3 @@ class TPLinkClimateEntity(CoordinatedTPLinkModuleEntity, ClimateEntity):
|
||||
cast(ThermostatState, self._mode_feature.value)
|
||||
]
|
||||
return True
|
||||
|
||||
def _get_unique_id(self) -> str:
|
||||
"""Return unique id."""
|
||||
return f"{self._device.device_id}_climate"
|
||||
|
@ -6,16 +6,20 @@ from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kasa import Device
|
||||
|
||||
from homeassistant.components.automation import automations_with_entity
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.script import scripts_with_entity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
|
||||
from . import legacy_device_id
|
||||
from .const import DOMAIN
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||
from .entity import CoordinatedTPLinkEntity, TPLinkEntityDescription
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@ -30,7 +34,7 @@ class DeprecatedInfo:
|
||||
def async_check_create_deprecated(
|
||||
hass: HomeAssistant,
|
||||
unique_id: str,
|
||||
entity_description: TPLinkFeatureEntityDescription,
|
||||
entity_description: TPLinkEntityDescription,
|
||||
) -> bool:
|
||||
"""Return true if the entity should be created based on the deprecated_info.
|
||||
|
||||
@ -58,13 +62,21 @@ def async_check_create_deprecated(
|
||||
return not entity_entry.disabled
|
||||
|
||||
|
||||
def async_cleanup_deprecated(
|
||||
def async_process_deprecated(
|
||||
hass: HomeAssistant,
|
||||
platform: str,
|
||||
platform_domain: str,
|
||||
entry_id: str,
|
||||
entities: Sequence[CoordinatedTPLinkFeatureEntity],
|
||||
entities: Sequence[CoordinatedTPLinkEntity],
|
||||
device: Device,
|
||||
) -> None:
|
||||
"""Remove disabled deprecated entities or create issues if necessary."""
|
||||
"""Process deprecated entities for a device.
|
||||
|
||||
Create issues for deprececated entities that appear in automations.
|
||||
Delete entities that are no longer provided by the integration either
|
||||
because they have been removed at the end of the deprecation period, or
|
||||
they are disabled by the user so the async_check_create_deprecated
|
||||
returned false.
|
||||
"""
|
||||
ent_reg = er.async_get(hass)
|
||||
for entity in entities:
|
||||
if not (deprecated_info := entity.entity_description.deprecated_info):
|
||||
@ -72,7 +84,7 @@ def async_cleanup_deprecated(
|
||||
|
||||
assert entity.unique_id
|
||||
entity_id = ent_reg.async_get_entity_id(
|
||||
platform,
|
||||
platform_domain,
|
||||
DOMAIN,
|
||||
entity.unique_id,
|
||||
)
|
||||
@ -94,17 +106,27 @@ def async_cleanup_deprecated(
|
||||
translation_placeholders={
|
||||
"entity": entity_id,
|
||||
"info": item,
|
||||
"platform": platform,
|
||||
"platform": platform_domain,
|
||||
"new_platform": deprecated_info.new_platform,
|
||||
},
|
||||
)
|
||||
|
||||
# The light platform does not currently support cleaning up disabled
|
||||
# deprecated entities because it uses two entity classes so a completeness
|
||||
# check is not possible. It also uses the mac address as device id in some
|
||||
# instances instead of device_id.
|
||||
if platform_domain == LIGHT_DOMAIN:
|
||||
return
|
||||
|
||||
# Remove entities that are no longer provided and have been disabled.
|
||||
device_id = legacy_device_id(device)
|
||||
|
||||
unique_ids = {entity.unique_id for entity in entities}
|
||||
for entity_entry in er.async_entries_for_config_entry(ent_reg, entry_id):
|
||||
if (
|
||||
entity_entry.domain == platform
|
||||
entity_entry.domain == platform_domain
|
||||
and entity_entry.disabled
|
||||
and entity_entry.unique_id.startswith(device_id)
|
||||
and entity_entry.unique_id not in unique_ids
|
||||
):
|
||||
ent_reg.async_remove(entity_entry.entity_id)
|
||||
|
@ -36,7 +36,11 @@ from .const import (
|
||||
PRIMARY_STATE_ID,
|
||||
)
|
||||
from .coordinator import TPLinkConfigEntry, TPLinkDataUpdateCoordinator
|
||||
from .deprecate import DeprecatedInfo, async_check_create_deprecated
|
||||
from .deprecate import (
|
||||
DeprecatedInfo,
|
||||
async_check_create_deprecated,
|
||||
async_process_deprecated,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -102,6 +106,9 @@ class TPLinkModuleEntityDescription(TPLinkEntityDescription):
|
||||
"""Base class for a TPLink module based entity description."""
|
||||
|
||||
exists_fn: Callable[[Device, TPLinkConfigEntry], bool]
|
||||
unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = (
|
||||
lambda device, desc: f"{legacy_device_id(device)}-{desc.key}"
|
||||
)
|
||||
|
||||
|
||||
def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P](
|
||||
@ -151,6 +158,8 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
|
||||
_attr_has_entity_name = True
|
||||
_device: Device
|
||||
|
||||
entity_description: TPLinkEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: Device,
|
||||
@ -235,7 +244,7 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
|
||||
|
||||
def _get_unique_id(self) -> str:
|
||||
"""Return unique ID for the entity."""
|
||||
return legacy_device_id(self._device)
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call update attributes after the device is added to the platform."""
|
||||
@ -405,6 +414,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
||||
feature_type: Feature.Type,
|
||||
entity_class: type[_E],
|
||||
descriptions: Mapping[str, _D],
|
||||
platform_domain: str,
|
||||
parent: Device | None = None,
|
||||
) -> list[_E]:
|
||||
"""Return a list of entities to add.
|
||||
@ -439,6 +449,9 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
||||
desc,
|
||||
)
|
||||
]
|
||||
async_process_deprecated(
|
||||
hass, platform_domain, coordinator.config_entry.entry_id, entities, device
|
||||
)
|
||||
return entities
|
||||
|
||||
@classmethod
|
||||
@ -454,6 +467,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
||||
feature_type: Feature.Type,
|
||||
entity_class: type[_E],
|
||||
descriptions: Mapping[str, _D],
|
||||
platform_domain: str,
|
||||
known_child_device_ids: set[str],
|
||||
first_check: bool,
|
||||
) -> list[_E]:
|
||||
@ -473,6 +487,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
||||
feature_type=feature_type,
|
||||
entity_class=entity_class,
|
||||
descriptions=descriptions,
|
||||
platform_domain=platform_domain,
|
||||
)
|
||||
)
|
||||
|
||||
@ -498,6 +513,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
||||
feature_type=feature_type,
|
||||
entity_class=entity_class,
|
||||
descriptions=descriptions,
|
||||
platform_domain=platform_domain,
|
||||
parent=device,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
@ -539,6 +555,11 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
||||
else:
|
||||
self._attr_name = get_device_name(device)
|
||||
|
||||
def _get_unique_id(self) -> str:
|
||||
"""Return unique ID for the entity."""
|
||||
desc = self.entity_description
|
||||
return desc.unique_id_fn(self._device, desc)
|
||||
|
||||
@classmethod
|
||||
def _entities_for_device[
|
||||
_E: CoordinatedTPLinkModuleEntity,
|
||||
@ -551,6 +572,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
||||
*,
|
||||
entity_class: type[_E],
|
||||
descriptions: Iterable[_D],
|
||||
platform_domain: str,
|
||||
parent: Device | None = None,
|
||||
) -> list[_E]:
|
||||
"""Return a list of entities to add."""
|
||||
@ -563,7 +585,15 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
||||
)
|
||||
for description in descriptions
|
||||
if description.exists_fn(device, coordinator.config_entry)
|
||||
and async_check_create_deprecated(
|
||||
hass,
|
||||
description.unique_id_fn(device, description),
|
||||
description,
|
||||
)
|
||||
]
|
||||
async_process_deprecated(
|
||||
hass, platform_domain, coordinator.config_entry.entry_id, entities, device
|
||||
)
|
||||
return entities
|
||||
|
||||
@classmethod
|
||||
@ -578,6 +608,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
||||
*,
|
||||
entity_class: type[_E],
|
||||
descriptions: Iterable[_D],
|
||||
platform_domain: str,
|
||||
known_child_device_ids: set[str],
|
||||
first_check: bool,
|
||||
) -> list[_E]:
|
||||
@ -597,6 +628,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
||||
coordinator=coordinator,
|
||||
entity_class=entity_class,
|
||||
descriptions=descriptions,
|
||||
platform_domain=platform_domain,
|
||||
)
|
||||
)
|
||||
has_parent_entities = bool(entities)
|
||||
@ -621,6 +653,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
||||
coordinator=child_coordinator,
|
||||
entity_class=entity_class,
|
||||
descriptions=descriptions,
|
||||
platform_domain=platform_domain,
|
||||
parent=device,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Support for TPLink Fan devices."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import math
|
||||
@ -8,6 +9,7 @@ from typing import Any
|
||||
from kasa import Device, Module
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
DOMAIN as FAN_DOMAIN,
|
||||
FanEntity,
|
||||
FanEntityDescription,
|
||||
FanEntityFeature,
|
||||
@ -20,7 +22,7 @@ from homeassistant.util.percentage import (
|
||||
)
|
||||
from homeassistant.util.scaling import int_states_in_range
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from . import TPLinkConfigEntry, legacy_device_id
|
||||
from .coordinator import TPLinkDataUpdateCoordinator
|
||||
from .entity import (
|
||||
CoordinatedTPLinkModuleEntity,
|
||||
@ -39,6 +41,12 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class TPLinkFanEntityDescription(FanEntityDescription, TPLinkModuleEntityDescription):
|
||||
"""Base class for fan entity description."""
|
||||
|
||||
unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = (
|
||||
lambda device, desc: legacy_device_id(device)
|
||||
if desc.key == "fan"
|
||||
else f"{legacy_device_id(device)}-{desc.key}"
|
||||
)
|
||||
|
||||
|
||||
FAN_DESCRIPTIONS: tuple[TPLinkFanEntityDescription, ...] = (
|
||||
TPLinkFanEntityDescription(
|
||||
@ -68,6 +76,7 @@ async def async_setup_entry(
|
||||
coordinator=parent_coordinator,
|
||||
entity_class=TPLinkFanEntity,
|
||||
descriptions=FAN_DESCRIPTIONS,
|
||||
platform_domain=FAN_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
|
@ -18,6 +18,7 @@ from homeassistant.components.light import (
|
||||
ATTR_EFFECT,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_TRANSITION,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
EFFECT_OFF,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
@ -141,12 +142,39 @@ def _async_build_base_effect(
|
||||
}
|
||||
|
||||
|
||||
def _get_backwards_compatible_light_unique_id(
|
||||
device: Device, entity_description: TPLinkModuleEntityDescription
|
||||
) -> str:
|
||||
"""Return unique ID for the entity."""
|
||||
# For historical reasons the light platform uses the mac address as
|
||||
# the unique id whereas all other platforms use device_id.
|
||||
|
||||
# For backwards compat with pyHS100
|
||||
if device.device_type is DeviceType.Dimmer and isinstance(device, IotDevice):
|
||||
# Dimmers used to use the switch format since
|
||||
# pyHS100 treated them as SmartPlug but the old code
|
||||
# created them as lights
|
||||
# https://github.com/home-assistant/core/blob/2021.9.7/ \
|
||||
# homeassistant/components/tplink/common.py#L86
|
||||
return legacy_device_id(device)
|
||||
|
||||
# Newer devices can have child lights. While there isn't currently
|
||||
# an example of a device with more than one light we use the device_id
|
||||
# for consistency and future proofing
|
||||
if device.parent or device.children:
|
||||
return legacy_device_id(device)
|
||||
|
||||
return device.mac.replace(":", "").upper()
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class TPLinkLightEntityDescription(
|
||||
LightEntityDescription, TPLinkModuleEntityDescription
|
||||
):
|
||||
"""Base class for tplink light entity description."""
|
||||
|
||||
unique_id_fn = _get_backwards_compatible_light_unique_id
|
||||
|
||||
|
||||
LIGHT_DESCRIPTIONS: tuple[TPLinkLightEntityDescription, ...] = (
|
||||
TPLinkLightEntityDescription(
|
||||
@ -186,6 +214,7 @@ async def async_setup_entry(
|
||||
coordinator=parent_coordinator,
|
||||
entity_class=TPLinkLightEntity,
|
||||
descriptions=LIGHT_DESCRIPTIONS,
|
||||
platform_domain=LIGHT_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids_light,
|
||||
first_check=first_check,
|
||||
)
|
||||
@ -196,6 +225,7 @@ async def async_setup_entry(
|
||||
coordinator=parent_coordinator,
|
||||
entity_class=TPLinkLightEffectEntity,
|
||||
descriptions=LIGHT_EFFECT_DESCRIPTIONS,
|
||||
platform_domain=LIGHT_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids_light_effect,
|
||||
first_check=first_check,
|
||||
)
|
||||
@ -242,29 +272,6 @@ class TPLinkLightEntity(CoordinatedTPLinkModuleEntity, LightEntity):
|
||||
# If the light supports only a single color mode, set it now
|
||||
self._fixed_color_mode = next(iter(self._attr_supported_color_modes))
|
||||
|
||||
def _get_unique_id(self) -> str:
|
||||
"""Return unique ID for the entity."""
|
||||
# For historical reasons the light platform uses the mac address as
|
||||
# the unique id whereas all other platforms use device_id.
|
||||
device = self._device
|
||||
|
||||
# For backwards compat with pyHS100
|
||||
if device.device_type is DeviceType.Dimmer and isinstance(device, IotDevice):
|
||||
# Dimmers used to use the switch format since
|
||||
# pyHS100 treated them as SmartPlug but the old code
|
||||
# created them as lights
|
||||
# https://github.com/home-assistant/core/blob/2021.9.7/ \
|
||||
# homeassistant/components/tplink/common.py#L86
|
||||
return legacy_device_id(device)
|
||||
|
||||
# Newer devices can have child lights. While there isn't currently
|
||||
# an example of a device with more than one light we use the device_id
|
||||
# for consistency and future proofing
|
||||
if self._parent or device.children:
|
||||
return legacy_device_id(device)
|
||||
|
||||
return device.mac.replace(":", "").upper()
|
||||
|
||||
@callback
|
||||
def _async_extract_brightness_transition(
|
||||
self, **kwargs: Any
|
||||
|
@ -18,7 +18,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from .deprecate import async_cleanup_deprecated
|
||||
from .entity import (
|
||||
CoordinatedTPLinkFeatureEntity,
|
||||
TPLinkDataUpdateCoordinator,
|
||||
@ -91,10 +90,10 @@ async def async_setup_entry(
|
||||
feature_type=Feature.Type.Number,
|
||||
entity_class=TPLinkNumberEntity,
|
||||
descriptions=NUMBER_DESCRIPTIONS_MAP,
|
||||
platform_domain=NUMBER_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
async_cleanup_deprecated(hass, NUMBER_DOMAIN, config_entry.entry_id, entities)
|
||||
async_add_entities(entities)
|
||||
|
||||
_check_device()
|
||||
|
@ -16,7 +16,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from .deprecate import async_cleanup_deprecated
|
||||
from .entity import (
|
||||
CoordinatedTPLinkFeatureEntity,
|
||||
TPLinkDataUpdateCoordinator,
|
||||
@ -71,10 +70,10 @@ async def async_setup_entry(
|
||||
feature_type=Feature.Type.Choice,
|
||||
entity_class=TPLinkSelectEntity,
|
||||
descriptions=SELECT_DESCRIPTIONS_MAP,
|
||||
platform_domain=SELECT_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
async_cleanup_deprecated(hass, SELECT_DOMAIN, config_entry.entry_id, entities)
|
||||
async_add_entities(entities)
|
||||
|
||||
_check_device()
|
||||
|
@ -19,7 +19,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from .const import UNIT_MAPPING
|
||||
from .deprecate import async_cleanup_deprecated
|
||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||
|
||||
|
||||
@ -141,10 +140,10 @@ async def async_setup_entry(
|
||||
feature_type=Feature.Type.Sensor,
|
||||
entity_class=TPLinkSensorEntity,
|
||||
descriptions=SENSOR_DESCRIPTIONS_MAP,
|
||||
platform_domain=SENSOR_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
async_cleanup_deprecated(hass, SENSOR_DOMAIN, config_entry.entry_id, entities)
|
||||
async_add_entities(entities)
|
||||
|
||||
_check_device()
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
@ -9,6 +10,7 @@ from kasa import Device, Module
|
||||
from kasa.smart.modules.alarm import Alarm
|
||||
|
||||
from homeassistant.components.siren import (
|
||||
DOMAIN as SIREN_DOMAIN,
|
||||
SirenEntity,
|
||||
SirenEntityDescription,
|
||||
SirenEntityFeature,
|
||||
@ -16,7 +18,7 @@ from homeassistant.components.siren import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from . import TPLinkConfigEntry, legacy_device_id
|
||||
from .coordinator import TPLinkDataUpdateCoordinator
|
||||
from .entity import (
|
||||
CoordinatedTPLinkModuleEntity,
|
||||
@ -35,6 +37,12 @@ class TPLinkSirenEntityDescription(
|
||||
):
|
||||
"""Base class for siren entity description."""
|
||||
|
||||
unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = (
|
||||
lambda device, desc: legacy_device_id(device)
|
||||
if desc.key == "siren"
|
||||
else f"{legacy_device_id(device)}-{desc.key}"
|
||||
)
|
||||
|
||||
|
||||
SIREN_DESCRIPTIONS: tuple[TPLinkSirenEntityDescription, ...] = (
|
||||
TPLinkSirenEntityDescription(
|
||||
@ -64,6 +72,7 @@ async def async_setup_entry(
|
||||
coordinator=parent_coordinator,
|
||||
entity_class=TPLinkSirenEntity,
|
||||
descriptions=SIREN_DESCRIPTIONS,
|
||||
platform_domain=SIREN_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
|
@ -17,7 +17,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TPLinkConfigEntry
|
||||
from .deprecate import async_cleanup_deprecated
|
||||
from .entity import (
|
||||
CoordinatedTPLinkFeatureEntity,
|
||||
TPLinkFeatureEntityDescription,
|
||||
@ -100,10 +99,10 @@ async def async_setup_entry(
|
||||
feature_type=Feature.Switch,
|
||||
entity_class=TPLinkSwitch,
|
||||
descriptions=SWITCH_DESCRIPTIONS_MAP,
|
||||
platform_domain=SWITCH_DOMAIN,
|
||||
known_child_device_ids=known_child_device_ids,
|
||||
first_check=first_check,
|
||||
)
|
||||
async_cleanup_deprecated(hass, SWITCH_DOMAIN, config_entry.entry_id, entities)
|
||||
async_add_entities(entities)
|
||||
|
||||
_check_device()
|
||||
|
@ -197,10 +197,12 @@ def _mocked_device(
|
||||
mod.get_feature.side_effect = device_features.get
|
||||
mod.has_feature.side_effect = lambda id: id in device_features
|
||||
|
||||
device.parent = None
|
||||
device.children = []
|
||||
if children:
|
||||
for child in children:
|
||||
child.mac = mac
|
||||
child.parent = device
|
||||
device.children = children
|
||||
device.device_type = device_type if device_type else DeviceType.Unknown
|
||||
if (
|
||||
|
@ -1052,6 +1052,10 @@ async def test_automatic_module_device_addition_and_removal(
|
||||
ip_address=IP_ADDRESS3,
|
||||
mac=MAC_ADDRESS3,
|
||||
)
|
||||
# Set the parent property for the dynamic children as mock_device only sets
|
||||
# it on initialization
|
||||
for child in children.values():
|
||||
child.parent = mock_device
|
||||
|
||||
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
||||
mock_camera_config_entry.add_to_hass(hass)
|
||||
@ -1150,3 +1154,73 @@ async def test_automatic_module_device_addition_and_removal(
|
||||
)
|
||||
assert device_entry
|
||||
assert device_entry.via_device_id == parent_device.id
|
||||
|
||||
|
||||
async def test_automatic_device_addition_does_not_remove_disabled_default(
|
||||
hass: HomeAssistant,
|
||||
mock_camera_config_entry: MockConfigEntry,
|
||||
mock_connect: AsyncMock,
|
||||
mock_discovery: AsyncMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test for automatic device addition does not remove disabled default entities."""
|
||||
|
||||
features = ["ssid", "signal_level"]
|
||||
children = {
|
||||
f"child{index}": _mocked_device(
|
||||
alias=f"child {index}",
|
||||
features=features,
|
||||
device_id=f"child{index}",
|
||||
)
|
||||
for index in range(1, 5)
|
||||
}
|
||||
|
||||
mock_device = _mocked_device(
|
||||
alias="hub",
|
||||
children=[children["child1"], children["child2"]],
|
||||
features=features,
|
||||
device_type=DeviceType.Hub,
|
||||
device_id="hub_parent",
|
||||
ip_address=IP_ADDRESS3,
|
||||
mac=MAC_ADDRESS3,
|
||||
)
|
||||
# Set the parent property for the dynamic children as mock_device only sets
|
||||
# it on initialization
|
||||
for child in children.values():
|
||||
child.parent = mock_device
|
||||
|
||||
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
||||
mock_camera_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_camera_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def check_entities(entity_id_device):
|
||||
entity_id = f"sensor.{entity_id_device}_signal_level"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
reg_ent = entity_registry.async_get(entity_id)
|
||||
assert reg_ent
|
||||
assert reg_ent.disabled is False
|
||||
|
||||
entity_id = f"sensor.{entity_id_device}_ssid"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is None
|
||||
reg_ent = entity_registry.async_get(entity_id)
|
||||
assert reg_ent
|
||||
assert reg_ent.disabled is True
|
||||
assert reg_ent.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
check_entities("hub")
|
||||
for child_id in (1, 2):
|
||||
check_entities(f"child_{child_id}")
|
||||
|
||||
# Add child devices
|
||||
mock_device.children = [children["child1"], children["child2"], children["child3"]]
|
||||
freezer.tick(5)
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
check_entities("hub")
|
||||
for child_id in (1, 2, 3):
|
||||
check_entities(f"child_{child_id}")
|
||||
|
Loading…
x
Reference in New Issue
Block a user