diff --git a/homeassistant/components/tplink/binary_sensor.py b/homeassistant/components/tplink/binary_sensor.py index 6153ec31de1..e08495f5c88 100644 --- a/homeassistant/components/tplink/binary_sensor.py +++ b/homeassistant/components/tplink/binary_sensor.py @@ -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() diff --git a/homeassistant/components/tplink/button.py b/homeassistant/components/tplink/button.py index 990f0a608d3..0a4517b967d 100644 --- a/homeassistant/components/tplink/button.py +++ b/homeassistant/components/tplink/button.py @@ -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() diff --git a/homeassistant/components/tplink/camera.py b/homeassistant/components/tplink/camera.py index 61a08887f5f..b0f1f1a62c1 100644 --- a/homeassistant/components/tplink/camera.py +++ b/homeassistant/components/tplink/camera.py @@ -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() diff --git a/homeassistant/components/tplink/climate.py b/homeassistant/components/tplink/climate.py index a7dd865e7bb..d4800d9e951 100644 --- a/homeassistant/components/tplink/climate.py +++ b/homeassistant/components/tplink/climate.py @@ -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" diff --git a/homeassistant/components/tplink/deprecate.py b/homeassistant/components/tplink/deprecate.py index 738f3d24c38..86d4f66cdc0 100644 --- a/homeassistant/components/tplink/deprecate.py +++ b/homeassistant/components/tplink/deprecate.py @@ -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) diff --git a/homeassistant/components/tplink/entity.py b/homeassistant/components/tplink/entity.py index e7c3600acc2..edef8bd83a0 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -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( diff --git a/homeassistant/components/tplink/fan.py b/homeassistant/components/tplink/fan.py index cb17955fbcb..1c31d84b778 100644 --- a/homeassistant/components/tplink/fan.py +++ b/homeassistant/components/tplink/fan.py @@ -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, ) diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index bc4d792b3f8..c1311c256df 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -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 diff --git a/homeassistant/components/tplink/number.py b/homeassistant/components/tplink/number.py index 97152ef4da8..0af2b7403e8 100644 --- a/homeassistant/components/tplink/number.py +++ b/homeassistant/components/tplink/number.py @@ -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() diff --git a/homeassistant/components/tplink/select.py b/homeassistant/components/tplink/select.py index a443546fdaa..8e9dee7b964 100644 --- a/homeassistant/components/tplink/select.py +++ b/homeassistant/components/tplink/select.py @@ -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() diff --git a/homeassistant/components/tplink/sensor.py b/homeassistant/components/tplink/sensor.py index 0898a3379d1..aaba6b2674d 100644 --- a/homeassistant/components/tplink/sensor.py +++ b/homeassistant/components/tplink/sensor.py @@ -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() diff --git a/homeassistant/components/tplink/siren.py b/homeassistant/components/tplink/siren.py index 0c15477ee78..5931a508d6c 100644 --- a/homeassistant/components/tplink/siren.py +++ b/homeassistant/components/tplink/siren.py @@ -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, ) diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 92ecd7992de..04ca95273af 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -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() diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index 23e36eacdd5..81ee679a251 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -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 ( diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index ef0ae3b6827..01f422636b2 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -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}")