diff --git a/homeassistant/components/tplink/climate.py b/homeassistant/components/tplink/climate.py index 99a8c43fac3..3bd6aba5c26 100644 --- a/homeassistant/components/tplink/climate.py +++ b/homeassistant/components/tplink/climate.py @@ -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.""" diff --git a/homeassistant/components/tplink/entity.py b/homeassistant/components/tplink/entity.py index 4e8ec0e0779..4ec0480cf82 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -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( diff --git a/homeassistant/components/tplink/fan.py b/homeassistant/components/tplink/fan.py index 947a9072329..292240bca94 100644 --- a/homeassistant/components/tplink/fan.py +++ b/homeassistant/components/tplink/fan.py @@ -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, diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 633648bbf23..a736a0ba1e1 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -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: diff --git a/homeassistant/components/tplink/sensor.py b/homeassistant/components/tplink/sensor.py index 474ee6bfacf..3da414d74d3 100644 --- a/homeassistant/components/tplink/sensor.py +++ b/homeassistant/components/tplink/sensor.py @@ -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.""" diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 2520de9dd3e..62957d48ac4 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -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.""" diff --git a/tests/components/tplink/fixtures/features.json b/tests/components/tplink/fixtures/features.json index daf86a74643..7cfe979ea25 100644 --- a/tests/components/tplink/fixtures/features.json +++ b/tests/components/tplink/fixtures/features.json @@ -73,7 +73,7 @@ "value": 121.1, "type": "Sensor", "category": "Primary", - "unit": "v", + "unit": "V", "precision_hint": 1 }, "device_id": { diff --git a/tests/components/tplink/snapshots/test_sensor.ambr b/tests/components/tplink/snapshots/test_sensor.ambr index 46fe897500f..9ea22af45fd 100644 --- a/tests/components/tplink/snapshots/test_sensor.ambr +++ b/tests/components/tplink/snapshots/test_sensor.ambr @@ -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': , - 'unit_of_measurement': 'v', + 'unit_of_measurement': 'V', }), 'context': , 'entity_id': 'sensor.my_device_voltage', diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index c2f40f47e3d..6fce04ec454 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -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