mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Deprecate tplink alarm button entities (#126349)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
a1e6d4b693
commit
4f0211cdd8
@ -75,6 +75,7 @@ async def async_setup_entry(
|
|||||||
device = parent_coordinator.device
|
device = parent_coordinator.device
|
||||||
|
|
||||||
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
||||||
|
hass=hass,
|
||||||
device=device,
|
device=device,
|
||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
feature_type=Feature.Type.BinarySensor,
|
feature_type=Feature.Type.BinarySensor,
|
||||||
|
@ -7,11 +7,17 @@ from typing import Final
|
|||||||
|
|
||||||
from kasa import Feature
|
from kasa import Feature
|
||||||
|
|
||||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
from homeassistant.components.button import (
|
||||||
|
DOMAIN as BUTTON_DOMAIN,
|
||||||
|
ButtonEntity,
|
||||||
|
ButtonEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.components.siren import DOMAIN as SIREN_DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
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 .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||||
|
|
||||||
|
|
||||||
@ -25,9 +31,19 @@ class TPLinkButtonEntityDescription(
|
|||||||
BUTTON_DESCRIPTIONS: Final = [
|
BUTTON_DESCRIPTIONS: Final = [
|
||||||
TPLinkButtonEntityDescription(
|
TPLinkButtonEntityDescription(
|
||||||
key="test_alarm",
|
key="test_alarm",
|
||||||
|
deprecated_info=DeprecatedInfo(
|
||||||
|
platform=BUTTON_DOMAIN,
|
||||||
|
new_platform=SIREN_DOMAIN,
|
||||||
|
breaks_in_ha_version="2025.4.0",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TPLinkButtonEntityDescription(
|
TPLinkButtonEntityDescription(
|
||||||
key="stop_alarm",
|
key="stop_alarm",
|
||||||
|
deprecated_info=DeprecatedInfo(
|
||||||
|
platform=BUTTON_DOMAIN,
|
||||||
|
new_platform=SIREN_DOMAIN,
|
||||||
|
breaks_in_ha_version="2025.4.0",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -46,6 +62,7 @@ async def async_setup_entry(
|
|||||||
device = parent_coordinator.device
|
device = parent_coordinator.device
|
||||||
|
|
||||||
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
||||||
|
hass=hass,
|
||||||
device=device,
|
device=device,
|
||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
feature_type=Feature.Type.Action,
|
feature_type=Feature.Type.Action,
|
||||||
@ -53,6 +70,7 @@ async def async_setup_entry(
|
|||||||
descriptions=BUTTON_DESCRIPTIONS_MAP,
|
descriptions=BUTTON_DESCRIPTIONS_MAP,
|
||||||
child_coordinators=children_coordinators,
|
child_coordinators=children_coordinators,
|
||||||
)
|
)
|
||||||
|
async_cleanup_deprecated(hass, BUTTON_DOMAIN, config_entry.entry_id, entities)
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
111
homeassistant/components/tplink/deprecate.py
Normal file
111
homeassistant/components/tplink/deprecate.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""Helper class for deprecating entities."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from homeassistant.components.automation import automations_with_entity
|
||||||
|
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 .const import DOMAIN
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class DeprecatedInfo:
|
||||||
|
"""Class to define deprecation info for deprecated entities."""
|
||||||
|
|
||||||
|
platform: str
|
||||||
|
new_platform: str
|
||||||
|
breaks_in_ha_version: str
|
||||||
|
|
||||||
|
|
||||||
|
def async_check_create_deprecated(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
unique_id: str,
|
||||||
|
entity_description: TPLinkFeatureEntityDescription,
|
||||||
|
) -> bool:
|
||||||
|
"""Return true if the entity should be created based on the deprecated_info.
|
||||||
|
|
||||||
|
If deprecated_info is not defined will return true.
|
||||||
|
If entity not yet created will return false.
|
||||||
|
If entity disabled will return false.
|
||||||
|
"""
|
||||||
|
if not entity_description.deprecated_info:
|
||||||
|
return True
|
||||||
|
|
||||||
|
deprecated_info = entity_description.deprecated_info
|
||||||
|
platform = deprecated_info.platform
|
||||||
|
|
||||||
|
ent_reg = er.async_get(hass)
|
||||||
|
entity_id = ent_reg.async_get_entity_id(
|
||||||
|
platform,
|
||||||
|
DOMAIN,
|
||||||
|
unique_id,
|
||||||
|
)
|
||||||
|
if not entity_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
entity_entry = ent_reg.async_get(entity_id)
|
||||||
|
assert entity_entry
|
||||||
|
return not entity_entry.disabled
|
||||||
|
|
||||||
|
|
||||||
|
def async_cleanup_deprecated(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
platform: str,
|
||||||
|
entry_id: str,
|
||||||
|
entities: Sequence[CoordinatedTPLinkFeatureEntity],
|
||||||
|
) -> None:
|
||||||
|
"""Remove disabled deprecated entities or create issues if necessary."""
|
||||||
|
ent_reg = er.async_get(hass)
|
||||||
|
for entity in entities:
|
||||||
|
if not (deprecated_info := entity.entity_description.deprecated_info):
|
||||||
|
continue
|
||||||
|
|
||||||
|
assert entity.unique_id
|
||||||
|
entity_id = ent_reg.async_get_entity_id(
|
||||||
|
platform,
|
||||||
|
DOMAIN,
|
||||||
|
entity.unique_id,
|
||||||
|
)
|
||||||
|
assert entity_id
|
||||||
|
# Check for issues that need to be created
|
||||||
|
entity_automations = automations_with_entity(hass, entity_id)
|
||||||
|
entity_scripts = scripts_with_entity(hass, entity_id)
|
||||||
|
|
||||||
|
for item in entity_automations + entity_scripts:
|
||||||
|
async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
f"deprecated_entity_{entity_id}_{item}",
|
||||||
|
breaks_in_ha_version=deprecated_info.breaks_in_ha_version,
|
||||||
|
is_fixable=False,
|
||||||
|
is_persistent=False,
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
translation_key="deprecated_entity",
|
||||||
|
translation_placeholders={
|
||||||
|
"entity": entity_id,
|
||||||
|
"info": item,
|
||||||
|
"platform": platform,
|
||||||
|
"new_platform": deprecated_info.new_platform,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove entities that are no longer provided and have been disabled.
|
||||||
|
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
|
||||||
|
and entity_entry.disabled
|
||||||
|
and entity_entry.unique_id not in unique_ids
|
||||||
|
):
|
||||||
|
ent_reg.async_remove(entity_entry.entity_id)
|
||||||
|
continue
|
@ -18,7 +18,7 @@ from kasa import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
@ -36,6 +36,7 @@ from .const import (
|
|||||||
PRIMARY_STATE_ID,
|
PRIMARY_STATE_ID,
|
||||||
)
|
)
|
||||||
from .coordinator import TPLinkDataUpdateCoordinator
|
from .coordinator import TPLinkDataUpdateCoordinator
|
||||||
|
from .deprecate import DeprecatedInfo, async_check_create_deprecated
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -87,6 +88,8 @@ LEGACY_KEY_MAPPING = {
|
|||||||
class TPLinkFeatureEntityDescription(EntityDescription):
|
class TPLinkFeatureEntityDescription(EntityDescription):
|
||||||
"""Base class for a TPLink feature based entity description."""
|
"""Base class for a TPLink feature based entity description."""
|
||||||
|
|
||||||
|
deprecated_info: DeprecatedInfo | None = None
|
||||||
|
|
||||||
|
|
||||||
def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P](
|
def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P](
|
||||||
func: Callable[Concatenate[_T, _P], Awaitable[None]],
|
func: Callable[Concatenate[_T, _P], Awaitable[None]],
|
||||||
@ -251,18 +254,25 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
|
|
||||||
def _get_unique_id(self) -> str:
|
def _get_unique_id(self) -> str:
|
||||||
"""Return unique ID for the entity."""
|
"""Return unique ID for the entity."""
|
||||||
key = self.entity_description.key
|
return self._get_feature_unique_id(self._device, self.entity_description)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_feature_unique_id(
|
||||||
|
device: Device, entity_description: TPLinkFeatureEntityDescription
|
||||||
|
) -> str:
|
||||||
|
"""Return unique ID for the entity."""
|
||||||
|
key = entity_description.key
|
||||||
# The unique id for the state feature in the switch platform is the
|
# The unique id for the state feature in the switch platform is the
|
||||||
# device_id
|
# device_id
|
||||||
if key == PRIMARY_STATE_ID:
|
if key == PRIMARY_STATE_ID:
|
||||||
return legacy_device_id(self._device)
|
return legacy_device_id(device)
|
||||||
|
|
||||||
# Historically the legacy device emeter attributes which are now
|
# Historically the legacy device emeter attributes which are now
|
||||||
# replaced with features used slightly different keys. This ensures
|
# replaced with features used slightly different keys. This ensures
|
||||||
# that those entities are not orphaned. Returns the mapped key or the
|
# that those entities are not orphaned. Returns the mapped key or the
|
||||||
# provided key if not mapped.
|
# provided key if not mapped.
|
||||||
key = LEGACY_KEY_MAPPING.get(key, key)
|
key = LEGACY_KEY_MAPPING.get(key, key)
|
||||||
return f"{legacy_device_id(self._device)}_{key}"
|
return f"{legacy_device_id(device)}_{key}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _category_for_feature(cls, feature: Feature | None) -> EntityCategory | None:
|
def _category_for_feature(cls, feature: Feature | None) -> EntityCategory | None:
|
||||||
@ -334,6 +344,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
_D: TPLinkFeatureEntityDescription,
|
_D: TPLinkFeatureEntityDescription,
|
||||||
](
|
](
|
||||||
cls,
|
cls,
|
||||||
|
hass: HomeAssistant,
|
||||||
device: Device,
|
device: Device,
|
||||||
coordinator: TPLinkDataUpdateCoordinator,
|
coordinator: TPLinkDataUpdateCoordinator,
|
||||||
*,
|
*,
|
||||||
@ -368,6 +379,11 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
feat, descriptions, device=device, parent=parent
|
feat, descriptions, device=device, parent=parent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
and async_check_create_deprecated(
|
||||||
|
hass,
|
||||||
|
cls._get_feature_unique_id(device, desc),
|
||||||
|
desc,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
@ -377,6 +393,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
_D: TPLinkFeatureEntityDescription,
|
_D: TPLinkFeatureEntityDescription,
|
||||||
](
|
](
|
||||||
cls,
|
cls,
|
||||||
|
hass: HomeAssistant,
|
||||||
device: Device,
|
device: Device,
|
||||||
coordinator: TPLinkDataUpdateCoordinator,
|
coordinator: TPLinkDataUpdateCoordinator,
|
||||||
*,
|
*,
|
||||||
@ -393,6 +410,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
# Add parent entities before children so via_device id works.
|
# Add parent entities before children so via_device id works.
|
||||||
entities.extend(
|
entities.extend(
|
||||||
cls._entities_for_device(
|
cls._entities_for_device(
|
||||||
|
hass,
|
||||||
device,
|
device,
|
||||||
coordinator=coordinator,
|
coordinator=coordinator,
|
||||||
feature_type=feature_type,
|
feature_type=feature_type,
|
||||||
@ -412,6 +430,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
|
|||||||
child_coordinator = coordinator
|
child_coordinator = coordinator
|
||||||
entities.extend(
|
entities.extend(
|
||||||
cls._entities_for_device(
|
cls._entities_for_device(
|
||||||
|
hass,
|
||||||
child,
|
child,
|
||||||
coordinator=child_coordinator,
|
coordinator=child_coordinator,
|
||||||
feature_type=feature_type,
|
feature_type=feature_type,
|
||||||
|
@ -67,6 +67,7 @@ async def async_setup_entry(
|
|||||||
children_coordinators = data.children_coordinators
|
children_coordinators = data.children_coordinators
|
||||||
device = parent_coordinator.device
|
device = parent_coordinator.device
|
||||||
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
||||||
|
hass=hass,
|
||||||
device=device,
|
device=device,
|
||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
feature_type=Feature.Type.Number,
|
feature_type=Feature.Type.Number,
|
||||||
|
@ -54,6 +54,7 @@ async def async_setup_entry(
|
|||||||
device = parent_coordinator.device
|
device = parent_coordinator.device
|
||||||
|
|
||||||
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
||||||
|
hass=hass,
|
||||||
device=device,
|
device=device,
|
||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
feature_type=Feature.Type.Choice,
|
feature_type=Feature.Type.Choice,
|
||||||
|
@ -8,6 +8,7 @@ from typing import cast
|
|||||||
from kasa import Feature
|
from kasa import Feature
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
@ -18,6 +19,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@ -128,6 +130,7 @@ async def async_setup_entry(
|
|||||||
device = parent_coordinator.device
|
device = parent_coordinator.device
|
||||||
|
|
||||||
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
||||||
|
hass=hass,
|
||||||
device=device,
|
device=device,
|
||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
feature_type=Feature.Type.Sensor,
|
feature_type=Feature.Type.Sensor,
|
||||||
@ -135,6 +138,7 @@ async def async_setup_entry(
|
|||||||
descriptions=SENSOR_DESCRIPTIONS_MAP,
|
descriptions=SENSOR_DESCRIPTIONS_MAP,
|
||||||
child_coordinators=children_coordinators,
|
child_coordinators=children_coordinators,
|
||||||
)
|
)
|
||||||
|
async_cleanup_deprecated(hass, SENSOR_DOMAIN, config_entry.entry_id, entities)
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
@ -311,5 +311,11 @@
|
|||||||
"device_authentication": {
|
"device_authentication": {
|
||||||
"message": "Device authentication error {func}: {exc}"
|
"message": "Device authentication error {func}: {exc}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"deprecated_entity": {
|
||||||
|
"title": "Detected deprecated `{platform}` entity usage",
|
||||||
|
"description": "We detected that entity `{entity}` is being used in `{info}`\n\nWe have created a new `{new_platform}` entity and you should migrate `{info}` to use this new entity.\n\nWhen you are done migrating `{info}` and are ready to have the deprecated `{entity}` entity removed, disable the entity and restart Home Assistant."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,8 @@ async def async_setup_entry(
|
|||||||
device = parent_coordinator.device
|
device = parent_coordinator.device
|
||||||
|
|
||||||
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
entities = CoordinatedTPLinkFeatureEntity.entities_for_device_and_its_children(
|
||||||
device,
|
hass=hass,
|
||||||
|
device=device,
|
||||||
coordinator=parent_coordinator,
|
coordinator=parent_coordinator,
|
||||||
feature_type=Feature.Switch,
|
feature_type=Feature.Switch,
|
||||||
entity_class=TPLinkSwitch,
|
entity_class=TPLinkSwitch,
|
||||||
|
@ -21,6 +21,7 @@ from kasa.protocol import BaseProtocol
|
|||||||
from kasa.smart.modules.alarm import Alarm
|
from kasa.smart.modules.alarm import Alarm
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN
|
||||||
from homeassistant.components.tplink import (
|
from homeassistant.components.tplink import (
|
||||||
CONF_AES_KEYS,
|
CONF_AES_KEYS,
|
||||||
CONF_ALIAS,
|
CONF_ALIAS,
|
||||||
@ -184,6 +185,21 @@ async def snapshot_platform(
|
|||||||
), f"state snapshot failed for {entity_entry.entity_id}"
|
), f"state snapshot failed for {entity_entry.entity_id}"
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_automation(hass: HomeAssistant, alias: str, entity_id: str) -> None:
|
||||||
|
"""Set up an automation for tests."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
AUTOMATION_DOMAIN,
|
||||||
|
{
|
||||||
|
AUTOMATION_DOMAIN: {
|
||||||
|
"alias": alias,
|
||||||
|
"trigger": {"platform": "state", "entity_id": entity_id, "to": "on"},
|
||||||
|
"action": {"action": "notify.notify", "metadata": {}, "data": {}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _mock_protocol() -> BaseProtocol:
|
def _mock_protocol() -> BaseProtocol:
|
||||||
protocol = MagicMock(spec=BaseProtocol)
|
protocol = MagicMock(spec=BaseProtocol)
|
||||||
protocol.close = AsyncMock()
|
protocol.close = AsyncMock()
|
||||||
|
@ -11,7 +11,11 @@ from homeassistant.components.tplink.const import DOMAIN
|
|||||||
from homeassistant.components.tplink.entity import EXCLUDED_FEATURES
|
from homeassistant.components.tplink.entity import EXCLUDED_FEATURES
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, Platform
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import (
|
||||||
|
device_registry as dr,
|
||||||
|
entity_registry as er,
|
||||||
|
issue_registry as ir,
|
||||||
|
)
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
@ -22,6 +26,7 @@ from . import (
|
|||||||
_mocked_strip_children,
|
_mocked_strip_children,
|
||||||
_patch_connect,
|
_patch_connect,
|
||||||
_patch_discovery,
|
_patch_discovery,
|
||||||
|
setup_automation,
|
||||||
setup_platform_for_device,
|
setup_platform_for_device,
|
||||||
snapshot_platform,
|
snapshot_platform,
|
||||||
)
|
)
|
||||||
@ -29,6 +34,53 @@ from . import (
|
|||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def create_deprecated_button_entities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
):
|
||||||
|
"""Create the entity so it is not ignored by the deprecation check."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
def create_entry(device_name, device_id, key):
|
||||||
|
unique_id = f"{device_id}_{key}"
|
||||||
|
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
domain=BUTTON_DOMAIN,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=unique_id,
|
||||||
|
suggested_object_id=f"{device_name}_{key}",
|
||||||
|
config_entry=mock_config_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
create_entry("my_device", "123456789ABCDEFGH", "stop_alarm")
|
||||||
|
create_entry("my_device", "123456789ABCDEFGH", "test_alarm")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def create_deprecated_child_button_entities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
):
|
||||||
|
"""Create the entity so it is not ignored by the deprecation check."""
|
||||||
|
|
||||||
|
def create_entry(device_name, key):
|
||||||
|
for plug_id in range(2):
|
||||||
|
unique_id = f"PLUG{plug_id}DEVICEID_{key}"
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
domain=BUTTON_DOMAIN,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=unique_id,
|
||||||
|
suggested_object_id=f"my_device_plug{plug_id}_{key}",
|
||||||
|
config_entry=mock_config_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
create_entry("my_device", "stop_alarm")
|
||||||
|
create_entry("my_device", "test_alarm")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mocked_feature_button() -> Feature:
|
def mocked_feature_button() -> Feature:
|
||||||
"""Return mocked tplink binary sensor feature."""
|
"""Return mocked tplink binary sensor feature."""
|
||||||
@ -47,6 +99,7 @@ async def test_states(
|
|||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
|
create_deprecated_button_entities,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test a sensor unique ids."""
|
"""Test a sensor unique ids."""
|
||||||
features = {description.key for description in BUTTON_DESCRIPTIONS}
|
features = {description.key for description in BUTTON_DESCRIPTIONS}
|
||||||
@ -66,6 +119,7 @@ async def test_button(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
mocked_feature_button: Feature,
|
mocked_feature_button: Feature,
|
||||||
|
create_deprecated_button_entities,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test a sensor unique ids."""
|
"""Test a sensor unique ids."""
|
||||||
mocked_feature = mocked_feature_button
|
mocked_feature = mocked_feature_button
|
||||||
@ -74,13 +128,13 @@ async def test_button(
|
|||||||
)
|
)
|
||||||
already_migrated_config_entry.add_to_hass(hass)
|
already_migrated_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
plug = _mocked_device(alias="my_plug", features=[mocked_feature])
|
plug = _mocked_device(alias="my_device", features=[mocked_feature])
|
||||||
with _patch_discovery(device=plug), _patch_connect(device=plug):
|
with _patch_discovery(device=plug), _patch_connect(device=plug):
|
||||||
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# The entity_id is based on standard name from core.
|
# The entity_id is based on standard name from core.
|
||||||
entity_id = "button.my_plug_test_alarm"
|
entity_id = "button.my_device_test_alarm"
|
||||||
entity = entity_registry.async_get(entity_id)
|
entity = entity_registry.async_get(entity_id)
|
||||||
assert entity
|
assert entity
|
||||||
assert entity.unique_id == f"{DEVICE_ID}_{mocked_feature.id}"
|
assert entity.unique_id == f"{DEVICE_ID}_{mocked_feature.id}"
|
||||||
@ -91,6 +145,8 @@ async def test_button_children(
|
|||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
mocked_feature_button: Feature,
|
mocked_feature_button: Feature,
|
||||||
|
create_deprecated_button_entities,
|
||||||
|
create_deprecated_child_button_entities,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test a sensor unique ids."""
|
"""Test a sensor unique ids."""
|
||||||
mocked_feature = mocked_feature_button
|
mocked_feature = mocked_feature_button
|
||||||
@ -99,7 +155,7 @@ async def test_button_children(
|
|||||||
)
|
)
|
||||||
already_migrated_config_entry.add_to_hass(hass)
|
already_migrated_config_entry.add_to_hass(hass)
|
||||||
plug = _mocked_device(
|
plug = _mocked_device(
|
||||||
alias="my_plug",
|
alias="my_device",
|
||||||
features=[mocked_feature],
|
features=[mocked_feature],
|
||||||
children=_mocked_strip_children(features=[mocked_feature]),
|
children=_mocked_strip_children(features=[mocked_feature]),
|
||||||
)
|
)
|
||||||
@ -107,13 +163,13 @@ async def test_button_children(
|
|||||||
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
entity_id = "button.my_plug_test_alarm"
|
entity_id = "button.my_device_test_alarm"
|
||||||
entity = entity_registry.async_get(entity_id)
|
entity = entity_registry.async_get(entity_id)
|
||||||
assert entity
|
assert entity
|
||||||
device = device_registry.async_get(entity.device_id)
|
device = device_registry.async_get(entity.device_id)
|
||||||
|
|
||||||
for plug_id in range(2):
|
for plug_id in range(2):
|
||||||
child_entity_id = f"button.my_plug_plug{plug_id}_test_alarm"
|
child_entity_id = f"button.my_device_plug{plug_id}_test_alarm"
|
||||||
child_entity = entity_registry.async_get(child_entity_id)
|
child_entity = entity_registry.async_get(child_entity_id)
|
||||||
assert child_entity
|
assert child_entity
|
||||||
assert child_entity.unique_id == f"PLUG{plug_id}DEVICEID_{mocked_feature.id}"
|
assert child_entity.unique_id == f"PLUG{plug_id}DEVICEID_{mocked_feature.id}"
|
||||||
@ -127,6 +183,7 @@ async def test_button_press(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
mocked_feature_button: Feature,
|
mocked_feature_button: Feature,
|
||||||
|
create_deprecated_button_entities,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test a number entity limits and setting values."""
|
"""Test a number entity limits and setting values."""
|
||||||
mocked_feature = mocked_feature_button
|
mocked_feature = mocked_feature_button
|
||||||
@ -134,12 +191,12 @@ async def test_button_press(
|
|||||||
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
|
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
|
||||||
)
|
)
|
||||||
already_migrated_config_entry.add_to_hass(hass)
|
already_migrated_config_entry.add_to_hass(hass)
|
||||||
plug = _mocked_device(alias="my_plug", features=[mocked_feature])
|
plug = _mocked_device(alias="my_device", features=[mocked_feature])
|
||||||
with _patch_discovery(device=plug), _patch_connect(device=plug):
|
with _patch_discovery(device=plug), _patch_connect(device=plug):
|
||||||
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
entity_id = "button.my_plug_test_alarm"
|
entity_id = "button.my_device_test_alarm"
|
||||||
entity = entity_registry.async_get(entity_id)
|
entity = entity_registry.async_get(entity_id)
|
||||||
assert entity
|
assert entity
|
||||||
assert entity.unique_id == f"{DEVICE_ID}_test_alarm"
|
assert entity.unique_id == f"{DEVICE_ID}_test_alarm"
|
||||||
@ -151,3 +208,84 @@ async def test_button_press(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
mocked_feature.set_value.assert_called_with(True)
|
mocked_feature.set_value.assert_called_with(True)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_button_not_exists_with_deprecation(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
mocked_feature_button: Feature,
|
||||||
|
) -> None:
|
||||||
|
"""Test deprecated buttons are not created if they don't previously exist."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
entity_id = "button.my_device_test_alarm"
|
||||||
|
|
||||||
|
assert not hass.states.get(entity_id)
|
||||||
|
mocked_feature = mocked_feature_button
|
||||||
|
dev = _mocked_device(alias="my_device", features=[mocked_feature])
|
||||||
|
with _patch_discovery(device=dev), _patch_connect(device=dev):
|
||||||
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert not entity_registry.async_get(entity_id)
|
||||||
|
assert not er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
|
||||||
|
assert not hass.states.get(entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("entity_disabled", "entity_has_automations"),
|
||||||
|
[
|
||||||
|
pytest.param(False, False, id="without-automations"),
|
||||||
|
pytest.param(False, True, id="with-automations"),
|
||||||
|
pytest.param(True, False, id="disabled"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_button_exists_with_deprecation(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
issue_registry: ir.IssueRegistry,
|
||||||
|
mocked_feature_button: Feature,
|
||||||
|
entity_disabled: bool,
|
||||||
|
entity_has_automations: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Test the deprecated buttons are deleted or raise issues."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
object_id = "my_device_test_alarm"
|
||||||
|
entity_id = f"button.{object_id}"
|
||||||
|
unique_id = f"{DEVICE_ID}_test_alarm"
|
||||||
|
issue_id = f"deprecated_entity_{entity_id}_automation.test_automation"
|
||||||
|
|
||||||
|
if entity_has_automations:
|
||||||
|
await setup_automation(hass, "test_automation", entity_id)
|
||||||
|
|
||||||
|
entity = entity_registry.async_get_or_create(
|
||||||
|
domain=BUTTON_DOMAIN,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=unique_id,
|
||||||
|
suggested_object_id=object_id,
|
||||||
|
config_entry=config_entry,
|
||||||
|
disabled_by=er.RegistryEntryDisabler.USER if entity_disabled else None,
|
||||||
|
)
|
||||||
|
assert entity.entity_id == entity_id
|
||||||
|
assert not hass.states.get(entity_id)
|
||||||
|
|
||||||
|
mocked_feature = mocked_feature_button
|
||||||
|
dev = _mocked_device(alias="my_device", features=[mocked_feature])
|
||||||
|
with _patch_discovery(device=dev), _patch_connect(device=dev):
|
||||||
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity = entity_registry.async_get(entity_id)
|
||||||
|
# entity and state will be none if removed from registry
|
||||||
|
assert (entity is None) == entity_disabled
|
||||||
|
assert (hass.states.get(entity_id) is None) == entity_disabled
|
||||||
|
|
||||||
|
assert (
|
||||||
|
issue_registry.async_get_issue(DOMAIN, issue_id) is not None
|
||||||
|
) == entity_has_automations
|
||||||
|
Loading…
x
Reference in New Issue
Block a user