Use translations for fan_speed in tplink vacuum entity (#136718)

This commit is contained in:
Steven B. 2025-01-29 09:55:19 +00:00 committed by GitHub
parent 60b6a11d4e
commit 6b4ec3f3f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 53 additions and 21 deletions

View File

@ -110,6 +110,9 @@ class TPLinkModuleEntityDescription(TPLinkEntityDescription):
unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = ( unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = (
lambda device, desc: f"{legacy_device_id(device)}-{desc.key}" 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]( def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P](
@ -550,7 +553,9 @@ class CoordinatedTPLinkModuleEntity(CoordinatedTPLinkEntity, ABC):
# the description should have a translation key. # the description should have a translation key.
# HA logic is to name entities based on the following logic: # HA logic is to name entities based on the following logic:
# _attr_name > translation.name > description.name # _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: if parent is None or parent.device_type is Device.Type.Hub:
self._attr_name = None self._attr_name = None
else: else:

View File

@ -283,6 +283,21 @@
"clean_count": { "clean_count": {
"name": "Clean count" "name": "Clean count"
} }
},
"vacuum": {
"vacuum": {
"state_attributes": {
"fan_speed": {
"state": {
"quiet": "Quiet",
"standard": "Standard",
"turbo": "Turbo",
"max": "Max",
"ultra": "Ultra"
}
}
}
}
} }
}, },
"device": { "device": {

View File

@ -3,9 +3,9 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass 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 kasa.smart.modules.clean import Clean, Status
from homeassistant.components.vacuum import ( from homeassistant.components.vacuum import (
@ -52,7 +52,10 @@ class TPLinkVacuumEntityDescription(
VACUUM_DESCRIPTIONS: tuple[TPLinkVacuumEntityDescription, ...] = ( VACUUM_DESCRIPTIONS: tuple[TPLinkVacuumEntityDescription, ...] = (
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.START
| VacuumEntityFeature.PAUSE | VacuumEntityFeature.PAUSE
| VacuumEntityFeature.RETURN_HOME | VacuumEntityFeature.RETURN_HOME
| VacuumEntityFeature.FAN_SPEED
) )
entity_description: TPLinkVacuumEntityDescription entity_description: TPLinkVacuumEntityDescription
@ -117,8 +119,11 @@ class TPLinkVacuumEntity(CoordinatedTPLinkModuleEntity, StateVacuumEntity):
self._speaker_module = speaker self._speaker_module = speaker
self._attr_supported_features |= VacuumEntityFeature.LOCATE self._attr_supported_features |= VacuumEntityFeature.LOCATE
# Needs to be initialized empty, as vacuumentity's capability_attributes accesses it if (
self._attr_fan_speed_list: list[str] = [] 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_refresh_after
async def async_start(self) -> None: async def async_start(self) -> None:
@ -138,7 +143,7 @@ class TPLinkVacuumEntity(CoordinatedTPLinkModuleEntity, StateVacuumEntity):
@async_refresh_after @async_refresh_after
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
"""Set fan speed.""" """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: async def async_locate(self, **kwargs: Any) -> None:
"""Locate the device.""" """Locate the device."""
@ -152,7 +157,6 @@ class TPLinkVacuumEntity(CoordinatedTPLinkModuleEntity, StateVacuumEntity):
def _async_update_attrs(self) -> bool: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_activity = STATUS_TO_ACTIVITY.get(self._vacuum_module.status) self._attr_activity = STATUS_TO_ACTIVITY.get(self._vacuum_module.status)
fanspeeds = cast(Feature, self._vacuum_module.get_feature("fan_speed_preset")) if self._vacuum_module.has_feature("fan_speed_preset"):
self._attr_fan_speed_list = cast(list[str], fanspeeds.choices) self._attr_fan_speed = self._vacuum_module.fan_speed_preset.lower()
self._attr_fan_speed = self._vacuum_module.fan_speed_preset
return True return True

View File

@ -106,7 +106,7 @@ async def snapshot_platform(
if entity_entry.translation_key: if entity_entry.translation_key:
key = f"component.{DOMAIN}.entity.{entity_entry.domain}.{entity_entry.translation_key}.name" key = f"component.{DOMAIN}.entity.{entity_entry.domain}.{entity_entry.translation_key}.name"
single_device_class_translation = False 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: if entity_entry.original_device_class not in unique_device_classes:
single_device_class_translation = True single_device_class_translation = True
unique_device_classes.append(entity_entry.original_device_class) unique_device_classes.append(entity_entry.original_device_class)

View File

@ -42,8 +42,8 @@
'area_id': None, 'area_id': None,
'capabilities': dict({ 'capabilities': dict({
'fan_speed_list': list([ 'fan_speed_list': list([
'Quiet', 'quiet',
'Max', 'max',
]), ]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
@ -68,7 +68,7 @@
'platform': 'tplink', 'platform': 'tplink',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <VacuumEntityFeature: 12916>, 'supported_features': <VacuumEntityFeature: 12916>,
'translation_key': None, 'translation_key': 'vacuum',
'unique_id': '123456789ABCDEFGH-vacuum', 'unique_id': '123456789ABCDEFGH-vacuum',
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
@ -78,10 +78,10 @@
'attributes': ReadOnlyDict({ 'attributes': ReadOnlyDict({
'battery_icon': 'mdi:battery-charging-100', 'battery_icon': 'mdi:battery-charging-100',
'battery_level': 100, 'battery_level': 100,
'fan_speed': 'Max', 'fan_speed': 'max',
'fan_speed_list': list([ 'fan_speed_list': list([
'Quiet', 'quiet',
'Max', 'max',
]), ]),
'friendly_name': 'my_vacuum', 'friendly_name': 'my_vacuum',
'supported_features': <VacuumEntityFeature: 12916>, 'supported_features': <VacuumEntityFeature: 12916>,

View File

@ -19,7 +19,11 @@ from homeassistant.components.vacuum import (
) )
from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant 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 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) state = hass.states.get(ENTITY_ID)
assert state.state == VacuumActivity.DOCKED 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 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( async def test_states(
@ -90,7 +98,7 @@ async def test_states(
SERVICE_SET_FAN_SPEED, SERVICE_SET_FAN_SPEED,
Module.Clean, Module.Clean,
"set_fan_speed_preset", "set_fan_speed_preset",
{ATTR_FAN_SPEED: "Quiet"}, {ATTR_FAN_SPEED: "quiet"},
), ),
(SERVICE_LOCATE, Module.Speaker, "locate", {}), (SERVICE_LOCATE, Module.Speaker, "locate", {}),
], ],