mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +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 homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry
|
||||||
from .deprecate import async_cleanup_deprecated
|
|
||||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||||
|
|
||||||
|
|
||||||
@ -88,12 +87,10 @@ async def async_setup_entry(
|
|||||||
feature_type=Feature.Type.BinarySensor,
|
feature_type=Feature.Type.BinarySensor,
|
||||||
entity_class=TPLinkBinarySensorEntity,
|
entity_class=TPLinkBinarySensorEntity,
|
||||||
descriptions=BINARYSENSOR_DESCRIPTIONS_MAP,
|
descriptions=BINARYSENSOR_DESCRIPTIONS_MAP,
|
||||||
|
platform_domain=BINARY_SENSOR_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
async_cleanup_deprecated(
|
|
||||||
hass, BINARY_SENSOR_DOMAIN, config_entry.entry_id, entities
|
|
||||||
)
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
_check_device()
|
_check_device()
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry
|
||||||
from .deprecate import DeprecatedInfo, async_cleanup_deprecated
|
from .deprecate import DeprecatedInfo
|
||||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||||
|
|
||||||
|
|
||||||
@ -95,10 +95,10 @@ async def async_setup_entry(
|
|||||||
feature_type=Feature.Type.Action,
|
feature_type=Feature.Type.Action,
|
||||||
entity_class=TPLinkButtonEntity,
|
entity_class=TPLinkButtonEntity,
|
||||||
descriptions=BUTTON_DESCRIPTIONS_MAP,
|
descriptions=BUTTON_DESCRIPTIONS_MAP,
|
||||||
|
platform_domain=BUTTON_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
async_cleanup_deprecated(hass, BUTTON_DOMAIN, config_entry.entry_id, entities)
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
_check_device()
|
_check_device()
|
||||||
|
@ -11,6 +11,7 @@ from kasa import Device, Module, StreamResolution
|
|||||||
|
|
||||||
from homeassistant.components import ffmpeg, stream
|
from homeassistant.components import ffmpeg, stream
|
||||||
from homeassistant.components.camera import (
|
from homeassistant.components.camera import (
|
||||||
|
DOMAIN as CAMERA_DOMAIN,
|
||||||
Camera,
|
Camera,
|
||||||
CameraEntityDescription,
|
CameraEntityDescription,
|
||||||
CameraEntityFeature,
|
CameraEntityFeature,
|
||||||
@ -20,7 +21,7 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry, legacy_device_id
|
from . import TPLinkConfigEntry
|
||||||
from .const import CONF_CAMERA_CREDENTIALS
|
from .const import CONF_CAMERA_CREDENTIALS
|
||||||
from .coordinator import TPLinkDataUpdateCoordinator
|
from .coordinator import TPLinkDataUpdateCoordinator
|
||||||
from .entity import CoordinatedTPLinkModuleEntity, TPLinkModuleEntityDescription
|
from .entity import CoordinatedTPLinkModuleEntity, TPLinkModuleEntityDescription
|
||||||
@ -75,6 +76,7 @@ async def async_setup_entry(
|
|||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
entity_class=TPLinkCameraEntity,
|
entity_class=TPLinkCameraEntity,
|
||||||
descriptions=CAMERA_DESCRIPTIONS,
|
descriptions=CAMERA_DESCRIPTIONS,
|
||||||
|
platform_domain=CAMERA_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
@ -121,10 +123,6 @@ class TPLinkCameraEntity(CoordinatedTPLinkModuleEntity, Camera):
|
|||||||
self._can_stream = True
|
self._can_stream = True
|
||||||
self._http_mpeg_stream_running = False
|
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:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Call update attributes after the device is added to the platform."""
|
"""Call update attributes after the device is added to the platform."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
@ -11,6 +12,7 @@ from kasa.smart.modules.temperaturecontrol import ThermostatState
|
|||||||
|
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
|
DOMAIN as CLIMATE_DOMAIN,
|
||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
ClimateEntityDescription,
|
ClimateEntityDescription,
|
||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
@ -22,7 +24,7 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry, legacy_device_id
|
||||||
from .const import DOMAIN, UNIT_MAPPING
|
from .const import DOMAIN, UNIT_MAPPING
|
||||||
from .coordinator import TPLinkDataUpdateCoordinator
|
from .coordinator import TPLinkDataUpdateCoordinator
|
||||||
from .entity import (
|
from .entity import (
|
||||||
@ -52,6 +54,10 @@ class TPLinkClimateEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Base class for climate entity description."""
|
"""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, ...] = (
|
CLIMATE_DESCRIPTIONS: tuple[TPLinkClimateEntityDescription, ...] = (
|
||||||
TPLinkClimateEntityDescription(
|
TPLinkClimateEntityDescription(
|
||||||
@ -81,6 +87,7 @@ async def async_setup_entry(
|
|||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
entity_class=TPLinkClimateEntity,
|
entity_class=TPLinkClimateEntity,
|
||||||
descriptions=CLIMATE_DESCRIPTIONS,
|
descriptions=CLIMATE_DESCRIPTIONS,
|
||||||
|
platform_domain=CLIMATE_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
@ -182,7 +189,3 @@ class TPLinkClimateEntity(CoordinatedTPLinkModuleEntity, ClimateEntity):
|
|||||||
cast(ThermostatState, self._mode_feature.value)
|
cast(ThermostatState, self._mode_feature.value)
|
||||||
]
|
]
|
||||||
return True
|
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 dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from kasa import Device
|
||||||
|
|
||||||
from homeassistant.components.automation import automations_with_entity
|
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.components.script import scripts_with_entity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
|
|
||||||
|
from . import legacy_device_id
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
from .entity import CoordinatedTPLinkEntity, TPLinkEntityDescription
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
@ -30,7 +34,7 @@ class DeprecatedInfo:
|
|||||||
def async_check_create_deprecated(
|
def async_check_create_deprecated(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
unique_id: str,
|
unique_id: str,
|
||||||
entity_description: TPLinkFeatureEntityDescription,
|
entity_description: TPLinkEntityDescription,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Return true if the entity should be created based on the deprecated_info.
|
"""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
|
return not entity_entry.disabled
|
||||||
|
|
||||||
|
|
||||||
def async_cleanup_deprecated(
|
def async_process_deprecated(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
platform: str,
|
platform_domain: str,
|
||||||
entry_id: str,
|
entry_id: str,
|
||||||
entities: Sequence[CoordinatedTPLinkFeatureEntity],
|
entities: Sequence[CoordinatedTPLinkEntity],
|
||||||
|
device: Device,
|
||||||
) -> None:
|
) -> 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)
|
ent_reg = er.async_get(hass)
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
if not (deprecated_info := entity.entity_description.deprecated_info):
|
if not (deprecated_info := entity.entity_description.deprecated_info):
|
||||||
@ -72,7 +84,7 @@ def async_cleanup_deprecated(
|
|||||||
|
|
||||||
assert entity.unique_id
|
assert entity.unique_id
|
||||||
entity_id = ent_reg.async_get_entity_id(
|
entity_id = ent_reg.async_get_entity_id(
|
||||||
platform,
|
platform_domain,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
entity.unique_id,
|
entity.unique_id,
|
||||||
)
|
)
|
||||||
@ -94,17 +106,27 @@ def async_cleanup_deprecated(
|
|||||||
translation_placeholders={
|
translation_placeholders={
|
||||||
"entity": entity_id,
|
"entity": entity_id,
|
||||||
"info": item,
|
"info": item,
|
||||||
"platform": platform,
|
"platform": platform_domain,
|
||||||
"new_platform": deprecated_info.new_platform,
|
"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.
|
# 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}
|
unique_ids = {entity.unique_id for entity in entities}
|
||||||
for entity_entry in er.async_entries_for_config_entry(ent_reg, entry_id):
|
for entity_entry in er.async_entries_for_config_entry(ent_reg, entry_id):
|
||||||
if (
|
if (
|
||||||
entity_entry.domain == platform
|
entity_entry.domain == platform_domain
|
||||||
and entity_entry.disabled
|
and entity_entry.disabled
|
||||||
|
and entity_entry.unique_id.startswith(device_id)
|
||||||
and entity_entry.unique_id not in unique_ids
|
and entity_entry.unique_id not in unique_ids
|
||||||
):
|
):
|
||||||
ent_reg.async_remove(entity_entry.entity_id)
|
ent_reg.async_remove(entity_entry.entity_id)
|
||||||
|
@ -36,7 +36,11 @@ from .const import (
|
|||||||
PRIMARY_STATE_ID,
|
PRIMARY_STATE_ID,
|
||||||
)
|
)
|
||||||
from .coordinator import TPLinkConfigEntry, TPLinkDataUpdateCoordinator
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -102,6 +106,9 @@ class TPLinkModuleEntityDescription(TPLinkEntityDescription):
|
|||||||
"""Base class for a TPLink module based entity description."""
|
"""Base class for a TPLink module based entity description."""
|
||||||
|
|
||||||
exists_fn: Callable[[Device, TPLinkConfigEntry], bool]
|
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](
|
def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P](
|
||||||
@ -151,6 +158,8 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
|
|||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_device: Device
|
_device: Device
|
||||||
|
|
||||||
|
entity_description: TPLinkEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
device: Device,
|
device: Device,
|
||||||
@ -235,7 +244,7 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
|
|||||||
|
|
||||||
def _get_unique_id(self) -> str:
|
def _get_unique_id(self) -> str:
|
||||||
"""Return unique ID for the entity."""
|
"""Return unique ID for the entity."""
|
||||||
return legacy_device_id(self._device)
|
raise NotImplementedError
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Call update attributes after the device is added to the platform."""
|
"""Call update attributes after the device is added to the platform."""
|
||||||
@ -405,6 +414,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
feature_type: Feature.Type,
|
feature_type: Feature.Type,
|
||||||
entity_class: type[_E],
|
entity_class: type[_E],
|
||||||
descriptions: Mapping[str, _D],
|
descriptions: Mapping[str, _D],
|
||||||
|
platform_domain: str,
|
||||||
parent: Device | None = None,
|
parent: Device | None = None,
|
||||||
) -> list[_E]:
|
) -> list[_E]:
|
||||||
"""Return a list of entities to add.
|
"""Return a list of entities to add.
|
||||||
@ -439,6 +449,9 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
desc,
|
desc,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
async_process_deprecated(
|
||||||
|
hass, platform_domain, coordinator.config_entry.entry_id, entities, device
|
||||||
|
)
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -454,6 +467,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
feature_type: Feature.Type,
|
feature_type: Feature.Type,
|
||||||
entity_class: type[_E],
|
entity_class: type[_E],
|
||||||
descriptions: Mapping[str, _D],
|
descriptions: Mapping[str, _D],
|
||||||
|
platform_domain: str,
|
||||||
known_child_device_ids: set[str],
|
known_child_device_ids: set[str],
|
||||||
first_check: bool,
|
first_check: bool,
|
||||||
) -> list[_E]:
|
) -> list[_E]:
|
||||||
@ -473,6 +487,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
feature_type=feature_type,
|
feature_type=feature_type,
|
||||||
entity_class=entity_class,
|
entity_class=entity_class,
|
||||||
descriptions=descriptions,
|
descriptions=descriptions,
|
||||||
|
platform_domain=platform_domain,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -498,6 +513,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
feature_type=feature_type,
|
feature_type=feature_type,
|
||||||
entity_class=entity_class,
|
entity_class=entity_class,
|
||||||
descriptions=descriptions,
|
descriptions=descriptions,
|
||||||
|
platform_domain=platform_domain,
|
||||||
parent=device,
|
parent=device,
|
||||||
)
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@ -539,6 +555,11 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
else:
|
else:
|
||||||
self._attr_name = get_device_name(device)
|
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
|
@classmethod
|
||||||
def _entities_for_device[
|
def _entities_for_device[
|
||||||
_E: CoordinatedTPLinkModuleEntity,
|
_E: CoordinatedTPLinkModuleEntity,
|
||||||
@ -551,6 +572,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
*,
|
*,
|
||||||
entity_class: type[_E],
|
entity_class: type[_E],
|
||||||
descriptions: Iterable[_D],
|
descriptions: Iterable[_D],
|
||||||
|
platform_domain: str,
|
||||||
parent: Device | None = None,
|
parent: Device | None = None,
|
||||||
) -> list[_E]:
|
) -> list[_E]:
|
||||||
"""Return a list of entities to add."""
|
"""Return a list of entities to add."""
|
||||||
@ -563,7 +585,15 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
)
|
)
|
||||||
for description in descriptions
|
for description in descriptions
|
||||||
if description.exists_fn(device, coordinator.config_entry)
|
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
|
return entities
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -578,6 +608,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
*,
|
*,
|
||||||
entity_class: type[_E],
|
entity_class: type[_E],
|
||||||
descriptions: Iterable[_D],
|
descriptions: Iterable[_D],
|
||||||
|
platform_domain: str,
|
||||||
known_child_device_ids: set[str],
|
known_child_device_ids: set[str],
|
||||||
first_check: bool,
|
first_check: bool,
|
||||||
) -> list[_E]:
|
) -> list[_E]:
|
||||||
@ -597,6 +628,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
coordinator=coordinator,
|
coordinator=coordinator,
|
||||||
entity_class=entity_class,
|
entity_class=entity_class,
|
||||||
descriptions=descriptions,
|
descriptions=descriptions,
|
||||||
|
platform_domain=platform_domain,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
has_parent_entities = bool(entities)
|
has_parent_entities = bool(entities)
|
||||||
@ -621,6 +653,7 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
coordinator=child_coordinator,
|
coordinator=child_coordinator,
|
||||||
entity_class=entity_class,
|
entity_class=entity_class,
|
||||||
descriptions=descriptions,
|
descriptions=descriptions,
|
||||||
|
platform_domain=platform_domain,
|
||||||
parent=device,
|
parent=device,
|
||||||
)
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Support for TPLink Fan devices."""
|
"""Support for TPLink Fan devices."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
@ -8,6 +9,7 @@ from typing import Any
|
|||||||
from kasa import Device, Module
|
from kasa import Device, Module
|
||||||
|
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
|
DOMAIN as FAN_DOMAIN,
|
||||||
FanEntity,
|
FanEntity,
|
||||||
FanEntityDescription,
|
FanEntityDescription,
|
||||||
FanEntityFeature,
|
FanEntityFeature,
|
||||||
@ -20,7 +22,7 @@ from homeassistant.util.percentage import (
|
|||||||
)
|
)
|
||||||
from homeassistant.util.scaling import int_states_in_range
|
from homeassistant.util.scaling import int_states_in_range
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry, legacy_device_id
|
||||||
from .coordinator import TPLinkDataUpdateCoordinator
|
from .coordinator import TPLinkDataUpdateCoordinator
|
||||||
from .entity import (
|
from .entity import (
|
||||||
CoordinatedTPLinkModuleEntity,
|
CoordinatedTPLinkModuleEntity,
|
||||||
@ -39,6 +41,12 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class TPLinkFanEntityDescription(FanEntityDescription, TPLinkModuleEntityDescription):
|
class TPLinkFanEntityDescription(FanEntityDescription, TPLinkModuleEntityDescription):
|
||||||
"""Base class for fan entity description."""
|
"""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, ...] = (
|
FAN_DESCRIPTIONS: tuple[TPLinkFanEntityDescription, ...] = (
|
||||||
TPLinkFanEntityDescription(
|
TPLinkFanEntityDescription(
|
||||||
@ -68,6 +76,7 @@ async def async_setup_entry(
|
|||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
entity_class=TPLinkFanEntity,
|
entity_class=TPLinkFanEntity,
|
||||||
descriptions=FAN_DESCRIPTIONS,
|
descriptions=FAN_DESCRIPTIONS,
|
||||||
|
platform_domain=FAN_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.components.light import (
|
|||||||
ATTR_EFFECT,
|
ATTR_EFFECT,
|
||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
ATTR_TRANSITION,
|
ATTR_TRANSITION,
|
||||||
|
DOMAIN as LIGHT_DOMAIN,
|
||||||
EFFECT_OFF,
|
EFFECT_OFF,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
LightEntity,
|
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)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class TPLinkLightEntityDescription(
|
class TPLinkLightEntityDescription(
|
||||||
LightEntityDescription, TPLinkModuleEntityDescription
|
LightEntityDescription, TPLinkModuleEntityDescription
|
||||||
):
|
):
|
||||||
"""Base class for tplink light entity description."""
|
"""Base class for tplink light entity description."""
|
||||||
|
|
||||||
|
unique_id_fn = _get_backwards_compatible_light_unique_id
|
||||||
|
|
||||||
|
|
||||||
LIGHT_DESCRIPTIONS: tuple[TPLinkLightEntityDescription, ...] = (
|
LIGHT_DESCRIPTIONS: tuple[TPLinkLightEntityDescription, ...] = (
|
||||||
TPLinkLightEntityDescription(
|
TPLinkLightEntityDescription(
|
||||||
@ -186,6 +214,7 @@ async def async_setup_entry(
|
|||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
entity_class=TPLinkLightEntity,
|
entity_class=TPLinkLightEntity,
|
||||||
descriptions=LIGHT_DESCRIPTIONS,
|
descriptions=LIGHT_DESCRIPTIONS,
|
||||||
|
platform_domain=LIGHT_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids_light,
|
known_child_device_ids=known_child_device_ids_light,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
@ -196,6 +225,7 @@ async def async_setup_entry(
|
|||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
entity_class=TPLinkLightEffectEntity,
|
entity_class=TPLinkLightEffectEntity,
|
||||||
descriptions=LIGHT_EFFECT_DESCRIPTIONS,
|
descriptions=LIGHT_EFFECT_DESCRIPTIONS,
|
||||||
|
platform_domain=LIGHT_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids_light_effect,
|
known_child_device_ids=known_child_device_ids_light_effect,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
@ -242,29 +272,6 @@ class TPLinkLightEntity(CoordinatedTPLinkModuleEntity, LightEntity):
|
|||||||
# If the light supports only a single color mode, set it now
|
# If the light supports only a single color mode, set it now
|
||||||
self._fixed_color_mode = next(iter(self._attr_supported_color_modes))
|
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
|
@callback
|
||||||
def _async_extract_brightness_transition(
|
def _async_extract_brightness_transition(
|
||||||
self, **kwargs: Any
|
self, **kwargs: Any
|
||||||
|
@ -18,7 +18,6 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry
|
||||||
from .deprecate import async_cleanup_deprecated
|
|
||||||
from .entity import (
|
from .entity import (
|
||||||
CoordinatedTPLinkFeatureEntity,
|
CoordinatedTPLinkFeatureEntity,
|
||||||
TPLinkDataUpdateCoordinator,
|
TPLinkDataUpdateCoordinator,
|
||||||
@ -91,10 +90,10 @@ async def async_setup_entry(
|
|||||||
feature_type=Feature.Type.Number,
|
feature_type=Feature.Type.Number,
|
||||||
entity_class=TPLinkNumberEntity,
|
entity_class=TPLinkNumberEntity,
|
||||||
descriptions=NUMBER_DESCRIPTIONS_MAP,
|
descriptions=NUMBER_DESCRIPTIONS_MAP,
|
||||||
|
platform_domain=NUMBER_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
async_cleanup_deprecated(hass, NUMBER_DOMAIN, config_entry.entry_id, entities)
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
_check_device()
|
_check_device()
|
||||||
|
@ -16,7 +16,6 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry
|
||||||
from .deprecate import async_cleanup_deprecated
|
|
||||||
from .entity import (
|
from .entity import (
|
||||||
CoordinatedTPLinkFeatureEntity,
|
CoordinatedTPLinkFeatureEntity,
|
||||||
TPLinkDataUpdateCoordinator,
|
TPLinkDataUpdateCoordinator,
|
||||||
@ -71,10 +70,10 @@ async def async_setup_entry(
|
|||||||
feature_type=Feature.Type.Choice,
|
feature_type=Feature.Type.Choice,
|
||||||
entity_class=TPLinkSelectEntity,
|
entity_class=TPLinkSelectEntity,
|
||||||
descriptions=SELECT_DESCRIPTIONS_MAP,
|
descriptions=SELECT_DESCRIPTIONS_MAP,
|
||||||
|
platform_domain=SELECT_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
async_cleanup_deprecated(hass, SELECT_DOMAIN, config_entry.entry_id, entities)
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
_check_device()
|
_check_device()
|
||||||
|
@ -19,7 +19,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry
|
||||||
from .const import UNIT_MAPPING
|
from .const import UNIT_MAPPING
|
||||||
from .deprecate import async_cleanup_deprecated
|
|
||||||
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||||
|
|
||||||
|
|
||||||
@ -141,10 +140,10 @@ async def async_setup_entry(
|
|||||||
feature_type=Feature.Type.Sensor,
|
feature_type=Feature.Type.Sensor,
|
||||||
entity_class=TPLinkSensorEntity,
|
entity_class=TPLinkSensorEntity,
|
||||||
descriptions=SENSOR_DESCRIPTIONS_MAP,
|
descriptions=SENSOR_DESCRIPTIONS_MAP,
|
||||||
|
platform_domain=SENSOR_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
async_cleanup_deprecated(hass, SENSOR_DOMAIN, config_entry.entry_id, entities)
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
_check_device()
|
_check_device()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ from kasa import Device, Module
|
|||||||
from kasa.smart.modules.alarm import Alarm
|
from kasa.smart.modules.alarm import Alarm
|
||||||
|
|
||||||
from homeassistant.components.siren import (
|
from homeassistant.components.siren import (
|
||||||
|
DOMAIN as SIREN_DOMAIN,
|
||||||
SirenEntity,
|
SirenEntity,
|
||||||
SirenEntityDescription,
|
SirenEntityDescription,
|
||||||
SirenEntityFeature,
|
SirenEntityFeature,
|
||||||
@ -16,7 +18,7 @@ from homeassistant.components.siren import (
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry, legacy_device_id
|
||||||
from .coordinator import TPLinkDataUpdateCoordinator
|
from .coordinator import TPLinkDataUpdateCoordinator
|
||||||
from .entity import (
|
from .entity import (
|
||||||
CoordinatedTPLinkModuleEntity,
|
CoordinatedTPLinkModuleEntity,
|
||||||
@ -35,6 +37,12 @@ class TPLinkSirenEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Base class for siren entity description."""
|
"""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, ...] = (
|
SIREN_DESCRIPTIONS: tuple[TPLinkSirenEntityDescription, ...] = (
|
||||||
TPLinkSirenEntityDescription(
|
TPLinkSirenEntityDescription(
|
||||||
@ -64,6 +72,7 @@ async def async_setup_entry(
|
|||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
entity_class=TPLinkSirenEntity,
|
entity_class=TPLinkSirenEntity,
|
||||||
descriptions=SIREN_DESCRIPTIONS,
|
descriptions=SIREN_DESCRIPTIONS,
|
||||||
|
platform_domain=SIREN_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,6 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import TPLinkConfigEntry
|
from . import TPLinkConfigEntry
|
||||||
from .deprecate import async_cleanup_deprecated
|
|
||||||
from .entity import (
|
from .entity import (
|
||||||
CoordinatedTPLinkFeatureEntity,
|
CoordinatedTPLinkFeatureEntity,
|
||||||
TPLinkFeatureEntityDescription,
|
TPLinkFeatureEntityDescription,
|
||||||
@ -100,10 +99,10 @@ async def async_setup_entry(
|
|||||||
feature_type=Feature.Switch,
|
feature_type=Feature.Switch,
|
||||||
entity_class=TPLinkSwitch,
|
entity_class=TPLinkSwitch,
|
||||||
descriptions=SWITCH_DESCRIPTIONS_MAP,
|
descriptions=SWITCH_DESCRIPTIONS_MAP,
|
||||||
|
platform_domain=SWITCH_DOMAIN,
|
||||||
known_child_device_ids=known_child_device_ids,
|
known_child_device_ids=known_child_device_ids,
|
||||||
first_check=first_check,
|
first_check=first_check,
|
||||||
)
|
)
|
||||||
async_cleanup_deprecated(hass, SWITCH_DOMAIN, config_entry.entry_id, entities)
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
_check_device()
|
_check_device()
|
||||||
|
@ -197,10 +197,12 @@ def _mocked_device(
|
|||||||
mod.get_feature.side_effect = device_features.get
|
mod.get_feature.side_effect = device_features.get
|
||||||
mod.has_feature.side_effect = lambda id: id in device_features
|
mod.has_feature.side_effect = lambda id: id in device_features
|
||||||
|
|
||||||
|
device.parent = None
|
||||||
device.children = []
|
device.children = []
|
||||||
if children:
|
if children:
|
||||||
for child in children:
|
for child in children:
|
||||||
child.mac = mac
|
child.mac = mac
|
||||||
|
child.parent = device
|
||||||
device.children = children
|
device.children = children
|
||||||
device.device_type = device_type if device_type else DeviceType.Unknown
|
device.device_type = device_type if device_type else DeviceType.Unknown
|
||||||
if (
|
if (
|
||||||
|
@ -1052,6 +1052,10 @@ async def test_automatic_module_device_addition_and_removal(
|
|||||||
ip_address=IP_ADDRESS3,
|
ip_address=IP_ADDRESS3,
|
||||||
mac=MAC_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):
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mock_device):
|
||||||
mock_camera_config_entry.add_to_hass(hass)
|
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
|
||||||
assert device_entry.via_device_id == parent_device.id
|
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