Minor polishing for tplink (#120868)

This commit is contained in:
Teemu R 2024-07-02 08:23:07 +02:00 committed by GitHub
parent 0ffebd4853
commit 90d622cd02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 51 additions and 78 deletions

View File

@ -77,16 +77,17 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity):
parent: Device,
) -> None:
"""Initialize the climate entity."""
super().__init__(device, coordinator, parent=parent)
self._state_feature = self._device.features["state"]
self._mode_feature = self._device.features["thermostat_mode"]
self._temp_feature = self._device.features["temperature"]
self._target_feature = self._device.features["target_temperature"]
self._state_feature = device.features["state"]
self._mode_feature = device.features["thermostat_mode"]
self._temp_feature = device.features["temperature"]
self._target_feature = device.features["target_temperature"]
self._attr_min_temp = self._target_feature.minimum_value
self._attr_max_temp = self._target_feature.maximum_value
self._attr_temperature_unit = UNIT_MAPPING[cast(str, self._temp_feature.unit)]
super().__init__(device, coordinator, parent=parent)
@async_refresh_after
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set target temperature."""

View File

@ -56,15 +56,21 @@ DEVICETYPES_WITH_SPECIALIZED_PLATFORMS = {
DeviceType.Thermostat,
}
# Primary features to always include even when the device type has its own platform
FEATURES_ALLOW_LIST = {
# lights have current_consumption and a specialized platform
"current_consumption"
}
# Features excluded due to future platform additions
EXCLUDED_FEATURES = {
# update
"current_firmware_version",
"available_firmware_version",
# fan
"fan_speed_level",
}
LEGACY_KEY_MAPPING = {
"current": ATTR_CURRENT_A,
"current_consumption": ATTR_CURRENT_POWER_W,
@ -179,15 +185,12 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
self._attr_unique_id = self._get_unique_id()
self._async_call_update_attrs()
def _get_unique_id(self) -> str:
"""Return unique ID for the entity."""
return legacy_device_id(self._device)
async def async_added_to_hass(self) -> None:
"""Handle being added to hass."""
self._async_call_update_attrs()
return await super().async_added_to_hass()
@abstractmethod
@callback
def _async_update_attrs(self) -> None:
@ -196,11 +199,7 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
@callback
def _async_call_update_attrs(self) -> None:
"""Call update_attrs and make entity unavailable on error.
update_attrs can sometimes fail if a device firmware update breaks the
downstream library.
"""
"""Call update_attrs and make entity unavailable on errors."""
try:
self._async_update_attrs()
except Exception as ex: # noqa: BLE001
@ -358,6 +357,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
and (
feat.category is not Feature.Category.Primary
or device.device_type not in DEVICETYPES_WITH_SPECIALIZED_PLATFORMS
or feat.id in FEATURES_ALLOW_LIST
)
and (
desc := cls._description_for_feature(

View File

@ -69,11 +69,12 @@ class TPLinkFanEntity(CoordinatedTPLinkEntity, FanEntity):
parent: Device | None = None,
) -> None:
"""Initialize the fan."""
super().__init__(device, coordinator, parent=parent)
self.fan_module = fan_module
# If _attr_name is None the entity name will be the device name
self._attr_name = None if parent is None else device.alias
super().__init__(device, coordinator, parent=parent)
@async_refresh_after
async def async_turn_on(
self,

View File

@ -140,9 +140,7 @@ async def async_setup_entry(
parent_coordinator = data.parent_coordinator
device = parent_coordinator.device
entities: list[TPLinkLightEntity | TPLinkLightEffectEntity] = []
if (
effect_module := device.modules.get(Module.LightEffect)
) and effect_module.has_custom_effects:
if effect_module := device.modules.get(Module.LightEffect):
entities.append(
TPLinkLightEffectEntity(
device,
@ -151,17 +149,18 @@ async def async_setup_entry(
effect_module=effect_module,
)
)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_RANDOM_EFFECT,
RANDOM_EFFECT_DICT,
"async_set_random_effect",
)
platform.async_register_entity_service(
SERVICE_SEQUENCE_EFFECT,
SEQUENCE_EFFECT_DICT,
"async_set_sequence_effect",
)
if effect_module.has_custom_effects:
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_RANDOM_EFFECT,
RANDOM_EFFECT_DICT,
"async_set_random_effect",
)
platform.async_register_entity_service(
SERVICE_SEQUENCE_EFFECT,
SEQUENCE_EFFECT_DICT,
"async_set_sequence_effect",
)
elif Module.Light in device.modules:
entities.append(
TPLinkLightEntity(
@ -197,7 +196,6 @@ class TPLinkLightEntity(CoordinatedTPLinkEntity, LightEntity):
) -> None:
"""Initialize the light."""
self._parent = parent
super().__init__(device, coordinator, parent=parent)
self._light_module = light_module
# If _attr_name is None the entity name will be the device name
self._attr_name = None if parent is None else device.alias
@ -215,7 +213,8 @@ class TPLinkLightEntity(CoordinatedTPLinkEntity, LightEntity):
if len(self._attr_supported_color_modes) == 1:
# If the light supports only a single color mode, set it now
self._fixed_color_mode = next(iter(self._attr_supported_color_modes))
self._async_call_update_attrs()
super().__init__(device, coordinator, parent=parent)
def _get_unique_id(self) -> str:
"""Return unique ID for the entity."""
@ -371,6 +370,7 @@ class TPLinkLightEffectEntity(TPLinkLightEntity):
effect_module = self._effect_module
if effect_module.effect != LightEffect.LIGHT_EFFECTS_OFF:
self._attr_effect = effect_module.effect
self._attr_color_mode = ColorMode.BRIGHTNESS
else:
self._attr_effect = EFFECT_OFF
if effect_list := effect_module.effect_list:

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import cast
from kasa import Device, Feature
from kasa import Feature
from homeassistant.components.sensor import (
SensorDeviceClass,
@ -18,7 +18,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TPLinkConfigEntry
from .const import UNIT_MAPPING
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import CoordinatedTPLinkFeatureEntity, TPLinkFeatureEntityDescription
@ -144,21 +143,6 @@ class TPLinkSensorEntity(CoordinatedTPLinkFeatureEntity, SensorEntity):
entity_description: TPLinkSensorEntityDescription
def __init__(
self,
device: Device,
coordinator: TPLinkDataUpdateCoordinator,
*,
feature: Feature,
description: TPLinkSensorEntityDescription,
parent: Device | None = None,
) -> None:
"""Initialize the sensor."""
super().__init__(
device, coordinator, description=description, feature=feature, parent=parent
)
self._async_call_update_attrs()
@callback
def _async_update_attrs(self) -> None:
"""Update the entity's attributes."""

View File

@ -6,14 +6,13 @@ from dataclasses import dataclass
import logging
from typing import Any
from kasa import Device, Feature
from kasa import Feature
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TPLinkConfigEntry
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import (
CoordinatedTPLinkFeatureEntity,
TPLinkFeatureEntityDescription,
@ -80,22 +79,6 @@ class TPLinkSwitch(CoordinatedTPLinkFeatureEntity, SwitchEntity):
entity_description: TPLinkSwitchEntityDescription
def __init__(
self,
device: Device,
coordinator: TPLinkDataUpdateCoordinator,
*,
feature: Feature,
description: TPLinkSwitchEntityDescription,
parent: Device | None = None,
) -> None:
"""Initialize the switch."""
super().__init__(
device, coordinator, description=description, feature=feature, parent=parent
)
self._async_call_update_attrs()
@async_refresh_after
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""

View File

@ -73,7 +73,7 @@
"value": 121.1,
"type": "Sensor",
"category": "Primary",
"unit": "v",
"unit": "V",
"precision_hint": 1
},
"device_id": {

View File

@ -770,7 +770,7 @@
'supported_features': 0,
'translation_key': 'voltage',
'unique_id': '123456789ABCDEFGH_voltage',
'unit_of_measurement': 'v',
'unit_of_measurement': 'V',
})
# ---
# name: test_states[sensor.my_device_voltage-state]
@ -779,7 +779,7 @@
'device_class': 'voltage',
'friendly_name': 'my_device Voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'v',
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.my_device_voltage',

View File

@ -140,13 +140,17 @@ async def test_color_light(
assert state.state == "on"
attributes = state.attributes
assert attributes[ATTR_BRIGHTNESS] == 128
assert attributes[ATTR_COLOR_MODE] == "hs"
assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs"]
assert attributes[ATTR_MIN_MIREDS] == 111
assert attributes[ATTR_MAX_MIREDS] == 250
assert attributes[ATTR_HS_COLOR] == (10, 30)
assert attributes[ATTR_RGB_COLOR] == (255, 191, 178)
assert attributes[ATTR_XY_COLOR] == (0.42, 0.336)
# If effect is active, only the brightness can be controlled
if attributes.get(ATTR_EFFECT) is not None:
assert attributes[ATTR_COLOR_MODE] == "brightness"
else:
assert attributes[ATTR_COLOR_MODE] == "hs"
assert attributes[ATTR_MIN_MIREDS] == 111
assert attributes[ATTR_MAX_MIREDS] == 250
assert attributes[ATTR_HS_COLOR] == (10, 30)
assert attributes[ATTR_RGB_COLOR] == (255, 191, 178)
assert attributes[ATTR_XY_COLOR] == (0.42, 0.336)
await hass.services.async_call(
LIGHT_DOMAIN, "turn_off", BASE_PAYLOAD, blocking=True