diff --git a/homeassistant/components/tplink/entity.py b/homeassistant/components/tplink/entity.py index 6c21ab63285..15c07655e69 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -110,6 +110,9 @@ class TPLinkModuleEntityDescription(TPLinkEntityDescription): unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = ( lambda device, desc: f"{legacy_device_id(device)}-{desc.key}" ) + entity_name_fn: ( + Callable[[Device, TPLinkModuleEntityDescription], str | None] | None + ) = None def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P]( @@ -550,7 +553,9 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC): # the description should have a translation key. # HA logic is to name entities based on the following logic: # _attr_name > translation.name > description.name - if not description.translation_key: + if entity_name_fn := description.entity_name_fn: + self._attr_name = entity_name_fn(device, description) + elif not description.translation_key: if parent is None or parent.device_type is Device.Type.Hub: self._attr_name = None else: diff --git a/homeassistant/components/tplink/strings.json b/homeassistant/components/tplink/strings.json index fe661fa2529..fe1560b75d5 100644 --- a/homeassistant/components/tplink/strings.json +++ b/homeassistant/components/tplink/strings.json @@ -283,6 +283,21 @@ "clean_count": { "name": "Clean count" } + }, + "vacuum": { + "vacuum": { + "state_attributes": { + "fan_speed": { + "state": { + "quiet": "Quiet", + "standard": "Standard", + "turbo": "Turbo", + "max": "Max", + "ultra": "Ultra" + } + } + } + } } }, "device": { diff --git a/homeassistant/components/tplink/vacuum.py b/homeassistant/components/tplink/vacuum.py index 666584f4980..c62cd1d27c8 100644 --- a/homeassistant/components/tplink/vacuum.py +++ b/homeassistant/components/tplink/vacuum.py @@ -3,9 +3,9 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, cast +from typing import Any -from kasa import Device, Feature, Module +from kasa import Device, Module from kasa.smart.modules.clean import Clean, Status from homeassistant.components.vacuum import ( @@ -52,7 +52,10 @@ class TPLinkVacuumEntityDescription( VACUUM_DESCRIPTIONS: tuple[TPLinkVacuumEntityDescription, ...] = ( TPLinkVacuumEntityDescription( - key="vacuum", exists_fn=lambda dev, _: Module.Clean in dev.modules + key="vacuum", + translation_key="vacuum", + exists_fn=lambda dev, _: Module.Clean in dev.modules, + entity_name_fn=lambda _, __: None, ), ) @@ -97,7 +100,6 @@ class TPLinkVacuumEntity(CoordinatedTPLinkModuleEntity, StateVacuumEntity): | VacuumEntityFeature.START | VacuumEntityFeature.PAUSE | VacuumEntityFeature.RETURN_HOME - | VacuumEntityFeature.FAN_SPEED ) entity_description: TPLinkVacuumEntityDescription @@ -117,8 +119,11 @@ class TPLinkVacuumEntity(CoordinatedTPLinkModuleEntity, StateVacuumEntity): self._speaker_module = speaker self._attr_supported_features |= VacuumEntityFeature.LOCATE - # Needs to be initialized empty, as vacuumentity's capability_attributes accesses it - self._attr_fan_speed_list: list[str] = [] + if ( + fanspeed_feat := self._vacuum_module.get_feature("fan_speed_preset") + ) and fanspeed_feat.choices: + self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED + self._attr_fan_speed_list = [c.lower() for c in fanspeed_feat.choices] @async_refresh_after async def async_start(self) -> None: @@ -138,7 +143,7 @@ class TPLinkVacuumEntity(CoordinatedTPLinkModuleEntity, StateVacuumEntity): @async_refresh_after async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: """Set fan speed.""" - await self._vacuum_module.set_fan_speed_preset(fan_speed) + await self._vacuum_module.set_fan_speed_preset(fan_speed.capitalize()) async def async_locate(self, **kwargs: Any) -> None: """Locate the device.""" @@ -152,7 +157,6 @@ class TPLinkVacuumEntity(CoordinatedTPLinkModuleEntity, StateVacuumEntity): def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_activity = STATUS_TO_ACTIVITY.get(self._vacuum_module.status) - fanspeeds = cast(Feature, self._vacuum_module.get_feature("fan_speed_preset")) - self._attr_fan_speed_list = cast(list[str], fanspeeds.choices) - self._attr_fan_speed = self._vacuum_module.fan_speed_preset + if self._vacuum_module.has_feature("fan_speed_preset"): + self._attr_fan_speed = self._vacuum_module.fan_speed_preset.lower() return True diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index 028215dc157..4737d7432df 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -106,7 +106,7 @@ async def snapshot_platform( if entity_entry.translation_key: key = f"component.{DOMAIN}.entity.{entity_entry.domain}.{entity_entry.translation_key}.name" single_device_class_translation = False - if key not in translations and entity_entry.original_device_class: + if key not in translations: # No name translation if entity_entry.original_device_class not in unique_device_classes: single_device_class_translation = True unique_device_classes.append(entity_entry.original_device_class) diff --git a/tests/components/tplink/snapshots/test_vacuum.ambr b/tests/components/tplink/snapshots/test_vacuum.ambr index a28a7d80ab4..c0a48327e26 100644 --- a/tests/components/tplink/snapshots/test_vacuum.ambr +++ b/tests/components/tplink/snapshots/test_vacuum.ambr @@ -42,8 +42,8 @@ 'area_id': None, 'capabilities': dict({ 'fan_speed_list': list([ - 'Quiet', - 'Max', + 'quiet', + 'max', ]), }), 'config_entry_id': , @@ -68,7 +68,7 @@ 'platform': 'tplink', 'previous_unique_id': None, 'supported_features': , - 'translation_key': None, + 'translation_key': 'vacuum', 'unique_id': '123456789ABCDEFGH-vacuum', 'unit_of_measurement': None, }) @@ -78,10 +78,10 @@ 'attributes': ReadOnlyDict({ 'battery_icon': 'mdi:battery-charging-100', 'battery_level': 100, - 'fan_speed': 'Max', + 'fan_speed': 'max', 'fan_speed_list': list([ - 'Quiet', - 'Max', + 'quiet', + 'max', ]), 'friendly_name': 'my_vacuum', 'supported_features': , diff --git a/tests/components/tplink/test_vacuum.py b/tests/components/tplink/test_vacuum.py index aac7c4f7fc8..55bb8c0b504 100644 --- a/tests/components/tplink/test_vacuum.py +++ b/tests/components/tplink/test_vacuum.py @@ -19,7 +19,11 @@ from homeassistant.components.vacuum import ( ) from homeassistant.const import ATTR_ENTITY_ID, Platform 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, + translation, +) from . import DEVICE_ID, _mocked_device, setup_platform_for_device, snapshot_platform @@ -59,8 +63,12 @@ async def test_vacuum( state = hass.states.get(ENTITY_ID) assert state.state == VacuumActivity.DOCKED - assert state.attributes[ATTR_FAN_SPEED] == "Max" + assert state.attributes[ATTR_FAN_SPEED] == "max" assert state.attributes[ATTR_BATTERY_LEVEL] == 100 + result = translation.async_translate_state( + hass, "max", "vacuum", "tplink", "vacuum.state_attributes.fan_speed", None + ) + assert result == "Max" async def test_states( @@ -90,7 +98,7 @@ async def test_states( SERVICE_SET_FAN_SPEED, Module.Clean, "set_fan_speed_preset", - {ATTR_FAN_SPEED: "Quiet"}, + {ATTR_FAN_SPEED: "quiet"}, ), (SERVICE_LOCATE, Module.Speaker, "locate", {}), ],