1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-23 12:29:55 +00:00
Files
core/homeassistant/components/tradfri/entity.py
cs12ag ee486c269c Fix unique identifiers where multiple IKEA Tradfri gateways are in use (#136060)
* Create unique identifiers where multiple gateways are in use

Resolving issue https://github.com/home-assistant/core/issues/134497

* Added migration function to __init__.py

Added migration function to execute upon initialisation, to:
a) remove the erroneously-added config)_entry added to the device (gateway B gets added as a config_entry to a device associated to gateway A), and
b) swap out the non-unique identifiers for genuinely unique identifiers.

* Added tests to simulate migration from bad data scenario (i.e. explicitly executing migrate_entity_unique_ids() from __init__.py)

* Ammendments suggested in first review

* Changes after second review

* Rewrite of test_migrate_config_entry_and_identifiers after feedback

* Converted migrate function into major version, updated tests

* Finalised variable naming convention per feedback, added test to validate config entry migrated to v2

* Hopefully final changes for cosmetic / comment stucture

* Further code-coverage in test_migrate_config_entry_and_identifiers()

* Minor test corrections

* Added test for non-tradfri identifiers
2025-03-03 14:06:25 +01:00

88 lines
2.5 KiB
Python

"""Base class for IKEA TRADFRI."""
from __future__ import annotations
from abc import abstractmethod
from collections.abc import Callable, Coroutine
from functools import wraps
from typing import Any, cast
from pytradfri.command import Command
from pytradfri.device import Device
from pytradfri.error import RequestError
from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, LOGGER
from .coordinator import TradfriDeviceDataUpdateCoordinator
def handle_error(
func: Callable[[Command | list[Command]], Any],
) -> Callable[[Command | list[Command]], Coroutine[Any, Any, None]]:
"""Handle tradfri api call error."""
@wraps(func)
async def wrapper(command: Command | list[Command]) -> None:
"""Decorate api call."""
try:
await func(command)
except RequestError as err:
LOGGER.error("Unable to execute command %s: %s", command, err)
return wrapper
class TradfriBaseEntity(CoordinatorEntity[TradfriDeviceDataUpdateCoordinator]):
"""Base Tradfri device."""
_attr_has_entity_name = True
def __init__(
self,
device_coordinator: TradfriDeviceDataUpdateCoordinator,
gateway_id: str,
api: Callable[[Command | list[Command]], Any],
) -> None:
"""Initialize a device."""
super().__init__(device_coordinator)
self._gateway_id = gateway_id
self._device: Device = device_coordinator.data
self._device_id = self._device.id
self._api = handle_error(api)
info = self._device.device_info
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{gateway_id}-{self._device_id}")},
manufacturer=info.manufacturer,
model=info.model_number,
name=self._device.name,
sw_version=info.firmware_version,
via_device=(DOMAIN, gateway_id),
)
self._attr_unique_id = f"{gateway_id}-{self._device_id}"
@abstractmethod
@callback
def _refresh(self) -> None:
"""Refresh device data."""
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator.
Tests fails without this method.
"""
self._refresh()
super()._handle_coordinator_update()
@property
def available(self) -> bool:
"""Return if entity is available."""
return cast(bool, self._device.reachable) and super().available