1 """Common code for tplink."""
3 from __future__
import annotations
5 from abc
import ABC, abstractmethod
6 from collections.abc
import Awaitable, Callable, Coroutine, Mapping
7 from dataclasses
import dataclass, replace
9 from typing
import Any, Concatenate
29 from .
import get_device_name, legacy_device_id
33 ATTR_TODAY_ENERGY_KWH,
34 ATTR_TOTAL_ENERGY_KWH,
38 from .coordinator
import TPLinkDataUpdateCoordinator
39 from .deprecate
import DeprecatedInfo, async_check_create_deprecated
41 _LOGGER = logging.getLogger(__name__)
44 FEATURE_CATEGORY_TO_ENTITY_CATEGORY = {
45 Feature.Category.Config: EntityCategory.CONFIG,
46 Feature.Category.Info: EntityCategory.DIAGNOSTIC,
47 Feature.Category.Debug: EntityCategory.DIAGNOSTIC,
52 DEVICETYPES_WITH_SPECIALIZED_PLATFORMS = {
54 DeviceType.LightStrip,
57 DeviceType.Thermostat,
61 FEATURES_ALLOW_LIST = {
70 "current_firmware_version",
71 "available_firmware_version",
73 "check_latest_firmware",
79 LEGACY_KEY_MAPPING = {
80 "current": ATTR_CURRENT_A,
81 "current_consumption": ATTR_CURRENT_POWER_W,
82 "consumption_today": ATTR_TODAY_ENERGY_KWH,
83 "consumption_total": ATTR_TOTAL_ENERGY_KWH,
87 @dataclass(frozen=True, kw_only=True)
89 """Base class for a TPLink feature based entity description."""
91 deprecated_info: DeprecatedInfo |
None =
None
94 def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P](
95 func: Callable[Concatenate[_T, _P], Awaitable[
None]],
96 ) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any,
None]]:
97 """Define a wrapper to raise HA errors and refresh after."""
99 async
def _async_wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) ->
None:
101 await func(self, *args, **kwargs)
102 except AuthenticationError
as ex:
103 self.coordinator.config_entry.async_start_reauth(self.hass)
105 translation_domain=DOMAIN,
106 translation_key=
"device_authentication",
107 translation_placeholders={
108 "func": func.__name__,
112 except TimeoutError
as ex:
114 translation_domain=DOMAIN,
115 translation_key=
"device_timeout",
116 translation_placeholders={
117 "func": func.__name__,
121 except KasaException
as ex:
123 translation_domain=DOMAIN,
124 translation_key=
"device_error",
125 translation_placeholders={
126 "func": func.__name__,
130 await self.coordinator.async_request_refresh()
136 """Common base class for all coordinated tplink entities."""
138 _attr_has_entity_name =
True
144 coordinator: TPLinkDataUpdateCoordinator,
146 feature: Feature |
None =
None,
147 parent: Device |
None =
None,
149 """Initialize the entity."""
151 self._device: Device = device
154 registry_device = device
156 if parent
and parent.device_type
is not Device.Type.Hub:
157 if not feature
or feature.id == PRIMARY_STATE_ID:
161 registry_device = parent
171 device_name = f
"{get_device_name(parent)} {get_device_name(device, parent=parent)}"
174 identifiers={(DOMAIN,
str(registry_device.device_id))},
175 manufacturer=
"TP-Link",
176 model=registry_device.model,
178 sw_version=registry_device.hw_info[
"sw_ver"],
179 hw_version=registry_device.hw_info[
"hw_ver"],
184 and parent != registry_device
185 and parent.device_type
is not Device.Type.WallSwitch
187 self.
_attr_device_info_attr_device_info[
"via_device"] = (DOMAIN, parent.device_id)
190 (dr.CONNECTION_NETWORK_MAC, device.mac)
198 """Return unique ID for the entity."""
204 """Platforms implement this to update the entity internals."""
205 raise NotImplementedError
209 """Call update_attrs and make entity unavailable on errors."""
212 except Exception
as ex:
215 "Unable to read data for %s %s: %s",
226 """Handle updated data from the coordinator."""
232 """Return if entity is available."""
233 return self.coordinator.last_update_success
and self.
_attr_available_attr_available
237 """Common base class for all coordinated tplink feature entities."""
239 entity_description: TPLinkFeatureEntityDescription
245 coordinator: TPLinkDataUpdateCoordinator,
248 description: TPLinkFeatureEntityDescription,
249 parent: Device |
None =
None,
251 """Initialize the entity."""
253 super().
__init__(device, coordinator, parent=parent, feature=feature)
256 """Return unique ID for the entity."""
261 device: Device, entity_description: TPLinkFeatureEntityDescription
263 """Return unique ID for the entity."""
264 key = entity_description.key
267 if key == PRIMARY_STATE_ID:
274 key = LEGACY_KEY_MAPPING.get(key, key)
275 return f
"{legacy_device_id(device)}_{key}"
279 """Return entity category for a feature."""
281 if feature
is None or feature.category
is Feature.Category.Primary:
285 entity_category := FEATURE_CATEGORY_TO_ENTITY_CATEGORY.get(feature.category)
288 "Unhandled category %s, fallback to DIAGNOSTIC", feature.category
290 entity_category = EntityCategory.DIAGNOSTIC
292 return entity_category
295 def _description_for_feature[_D: EntityDescription](
298 descriptions: Mapping[str, _D],
301 parent: Device |
None =
None,
303 """Return description object for the given feature.
305 This is responsible for setting the common parameters & deciding
306 based on feature id which additional parameters are passed.
309 if descriptions
and (desc := descriptions.get(feature.id)):
310 translation_key: str |
None = feature.id
314 name: str |
None | UndefinedType = UNDEFINED
318 if feature.id == PRIMARY_STATE_ID:
319 translation_key =
None
325 translation_key=translation_key,
329 entity_registry_enabled_default=feature.category
330 is not Feature.Category.Debug
331 and desc.entity_registry_enabled_default,
335 "Device feature: %s (%s) needs an entity description defined in HA",
342 def _entities_for_device[
343 _E: CoordinatedTPLinkFeatureEntity,
344 _D: TPLinkFeatureEntityDescription,
349 coordinator: TPLinkDataUpdateCoordinator,
351 feature_type: Feature.Type,
352 entity_class: type[_E],
353 descriptions: Mapping[str, _D],
354 parent: Device |
None =
None,
356 """Return a list of entities to add.
358 This filters out unwanted features to avoid creating unnecessary entities
359 for device features that are implemented by specialized platforms like light.
361 entities: list[_E] = [
369 for feat
in device.features.values()
370 if feat.type == feature_type
371 and feat.id
not in EXCLUDED_FEATURES
373 feat.category
is not Feature.Category.Primary
374 or device.device_type
not in DEVICETYPES_WITH_SPECIALIZED_PLATFORMS
375 or feat.id
in FEATURES_ALLOW_LIST
378 desc := cls._description_for_feature(
379 feat, descriptions, device=device, parent=parent
391 def entities_for_device_and_its_children[
392 _E: CoordinatedTPLinkFeatureEntity,
393 _D: TPLinkFeatureEntityDescription,
398 coordinator: TPLinkDataUpdateCoordinator,
400 feature_type: Feature.Type,
401 entity_class: type[_E],
402 descriptions: Mapping[str, _D],
403 child_coordinators: list[TPLinkDataUpdateCoordinator] |
None =
None,
405 """Create entities for device and its children.
407 This is a helper that calls *_entities_for_device* for the device and its children.
409 entities: list[_E] = []
412 cls._entities_for_device(
415 coordinator=coordinator,
416 feature_type=feature_type,
417 entity_class=entity_class,
418 descriptions=descriptions,
422 _LOGGER.debug(
"Initializing device with %s children", len(device.children))
423 for idx, child
in enumerate(device.children):
427 if child_coordinators:
428 child_coordinator = child_coordinators[idx]
430 child_coordinator = coordinator
432 cls._entities_for_device(
435 coordinator=child_coordinator,
436 feature_type=feature_type,
437 entity_class=entity_class,
438 descriptions=descriptions,
None _async_call_update_attrs(self)
None _handle_coordinator_update(self)
None _async_update_attrs(self)
None __init__(self, Device device, TPLinkDataUpdateCoordinator coordinator, *Feature|None feature=None, Device|None parent=None)
str _get_feature_unique_id(Device device, TPLinkFeatureEntityDescription entity_description)
EntityCategory|None _category_for_feature(cls, Feature|None feature)
None __init__(self, Device device, TPLinkDataUpdateCoordinator coordinator, *Feature feature, TPLinkFeatureEntityDescription description, Device|None parent=None)
bool async_check_create_deprecated(HomeAssistant hass, Platform platform, str unique_id, RingEntityDescription entity_description)
str get_device_name(Device device, Device|None parent=None)
str legacy_device_id(Device device)