Add and cleanup tplink translations (#135120)

This commit is contained in:
Steven B. 2025-01-09 10:28:10 +00:00 committed by GitHub
parent 15e785b974
commit 0d9ac25257
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 97 additions and 76 deletions

View File

@ -6,7 +6,7 @@ import asyncio
from collections.abc import Iterable
from datetime import timedelta
import logging
from typing import Any
from typing import Any, cast
from aiohttp import ClientSession
from kasa import (
@ -178,9 +178,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: TPLinkConfigEntry) -> bo
if not credentials and entry_credentials_hash:
data = {k: v for k, v in entry.data.items() if k != CONF_CREDENTIALS_HASH}
hass.config_entries.async_update_entry(entry, data=data)
raise ConfigEntryAuthFailed from ex
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="device_authentication",
translation_placeholders={
"func": "connect",
"exc": str(ex),
},
) from ex
except KasaException as ex:
raise ConfigEntryNotReady from ex
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="device_error",
translation_placeholders={
"func": "connect",
"exc": str(ex),
},
) from ex
device_credentials_hash = device.credentials_hash
@ -212,7 +226,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: TPLinkConfigEntry) -> bo
# wait for the next discovery to find the device at its new address
# and update the config entry so we do not mix up devices.
raise ConfigEntryNotReady(
f"Unexpected device found at {host}; expected {entry.unique_id}, found {found_mac}"
translation_domain=DOMAIN,
translation_key="unexpected_device",
translation_placeholders={
"host": host,
# all entries have a unique id
"expected": cast(str, entry.unique_id),
"found": found_mac,
},
)
parent_coordinator = TPLinkDataUpdateCoordinator(hass, device, timedelta(seconds=5))
@ -263,7 +284,7 @@ def legacy_device_id(device: Device) -> str:
return device_id.split("_")[1]
def get_device_name(device: Device, parent: Device | None = None) -> str:
def get_device_name(device: Device, parent: Device | None = None) -> str | None:
"""Get a name for the device. alias can be none on some devices."""
if device.alias:
return device.alias
@ -278,7 +299,7 @@ def get_device_name(device: Device, parent: Device | None = None) -> str:
]
suffix = f" {devices.index(device.device_id) + 1}" if len(devices) > 1 else ""
return f"{device.device_type.value.capitalize()}{suffix}"
return f"Unnamed {device.model}"
return None
async def get_credentials(hass: HomeAssistant) -> Credentials | None:

View File

@ -42,11 +42,6 @@ BINARY_SENSOR_DESCRIPTIONS: Final = (
key="cloud_connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
),
# To be replaced & disabled per default by the upcoming update platform.
TPLinkBinarySensorEntityDescription(
key="update_available",
device_class=BinarySensorDeviceClass.UPDATE,
),
TPLinkBinarySensorEntityDescription(
key="temperature_warning",
),

View File

@ -21,7 +21,7 @@ from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TPLinkConfigEntry
from .const import UNIT_MAPPING
from .const import DOMAIN, UNIT_MAPPING
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import CoordinatedTPLinkEntity, async_refresh_after
@ -104,7 +104,13 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity):
elif hvac_mode is HVACMode.OFF:
await self._state_feature.set_value(False)
else:
raise ServiceValidationError(f"Tried to set unsupported mode: {hvac_mode}")
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="unsupported_mode",
translation_placeholders={
"mode": hvac_mode,
},
)
@async_refresh_after
async def async_turn_on(self) -> None:

View File

@ -162,6 +162,9 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
registry_device = device
device_name = get_device_name(device, parent=parent)
translation_key: str | None = None
translation_placeholders: Mapping[str, str] | None = None
if parent and parent.device_type is not Device.Type.Hub:
if not feature or feature.id == PRIMARY_STATE_ID:
# Entity will be added to parent if not a hub and no feature
@ -169,6 +172,9 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
# is the primary state
registry_device = parent
device_name = get_device_name(registry_device)
if not device_name:
translation_key = "unnamed_device"
translation_placeholders = {"model": parent.model}
else:
# Prefix the device name with the parent name unless it is a
# hub attached device. Sensible default for child devices like
@ -177,13 +183,28 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
# Bedroom Ceiling Fan; Child device aliases will be Ceiling Fan
# and Dimmer Switch for both so should be distinguished by the
# parent name.
device_name = f"{get_device_name(parent)} {get_device_name(device, parent=parent)}"
parent_device_name = get_device_name(parent)
child_device_name = get_device_name(device, parent=parent)
if parent_device_name:
device_name = f"{parent_device_name} {child_device_name}"
else:
device_name = None
translation_key = "unnamed_device"
translation_placeholders = {
"model": f"{parent.model} {child_device_name}"
}
if device_name is None and not translation_key:
translation_key = "unnamed_device"
translation_placeholders = {"model": device.model}
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, str(registry_device.device_id))},
manufacturer="TP-Link",
model=registry_device.model,
name=device_name,
translation_key=translation_key,
translation_placeholders=translation_placeholders,
sw_version=registry_device.hw_info["sw_ver"],
hw_version=registry_device.hw_info["hw_ver"],
)
@ -320,6 +341,7 @@ class CoordinatedTPLinkFeatureEntity(CoordinatedTPLinkEntity, ABC):
if descriptions and (desc := descriptions.get(feature.id)):
translation_key: str | None = feature.id
# HA logic is to name entities based on the following logic:
# _attr_name > translation.name > description.name
# > device_class (if base platform supports).

View File

@ -125,12 +125,6 @@
"signal_level": {
"default": "mdi:signal"
},
"current_firmware_version": {
"default": "mdi:information"
},
"available_firmware_version": {
"default": "mdi:information-outline"
},
"alarm_source": {
"default": "mdi:bell"
},

View File

@ -116,11 +116,6 @@ SENSOR_DESCRIPTIONS: tuple[TPLinkSensorEntityDescription, ...] = (
TPLinkSensorEntityDescription(
key="alarm_source",
),
TPLinkSensorEntityDescription(
key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
)
SENSOR_DESCRIPTIONS_MAP = {desc.key: desc for desc in SENSOR_DESCRIPTIONS}

View File

@ -109,26 +109,9 @@
"overheated": {
"name": "Overheated"
},
"battery_low": {
"name": "Battery low"
},
"cloud_connection": {
"name": "Cloud connection"
},
"update_available": {
"name": "[%key:component::binary_sensor::entity_component::update::name%]",
"state": {
"off": "[%key:component::binary_sensor::entity_component::update::state::off%]",
"on": "[%key:component::binary_sensor::entity_component::update::state::on%]"
}
},
"is_open": {
"name": "[%key:component::binary_sensor::entity_component::door::name%]",
"state": {
"off": "[%key:common::state::closed%]",
"on": "[%key:common::state::open%]"
}
},
"water_alert": {
"name": "[%key:component::binary_sensor::entity_component::moisture::name%]",
"state": {
@ -195,27 +178,6 @@
"signal_level": {
"name": "Signal level"
},
"current_firmware_version": {
"name": "Current firmware version"
},
"available_firmware_version": {
"name": "Available firmware version"
},
"battery_level": {
"name": "Battery level"
},
"temperature": {
"name": "[%key:component::sensor::entity_component::temperature::name%]"
},
"voltage": {
"name": "[%key:component::sensor::entity_component::voltage::name%]"
},
"current": {
"name": "[%key:component::sensor::entity_component::current::name%]"
},
"humidity": {
"name": "[%key:component::sensor::entity_component::humidity::name%]"
},
"device_time": {
"name": "Device time"
},
@ -230,9 +192,6 @@
},
"alarm_source": {
"name": "Alarm source"
},
"rssi": {
"name": "[%key:component::sensor::entity_component::signal_strength::name%]"
}
},
"switch": {
@ -291,6 +250,11 @@
}
}
},
"device": {
"unnamed_device": {
"name": "Unnamed {model}"
}
},
"services": {
"sequence_effect": {
"name": "Sequence effect",
@ -397,6 +361,12 @@
},
"set_custom_effect": {
"message": "Error trying to set custom effect {effect}: {exc}"
},
"unexpected_device": {
"message": "Unexpected device found at {host}; expected {expected}, found {found}"
},
"unsupported_mode": {
"message": "Tried to set unsupported mode: {mode}"
}
},
"issues": {

View File

@ -1,5 +1,5 @@
# serializer version: 1
# name: test_states[binary_sensor.my_device_battery_low-entry]
# name: test_states[binary_sensor.my_device_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@ -11,7 +11,7 @@
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.my_device_battery_low',
'entity_id': 'binary_sensor.my_device_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@ -23,7 +23,7 @@
}),
'original_device_class': <BinarySensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'Battery low',
'original_name': 'Battery',
'platform': 'tplink',
'previous_unique_id': None,
'supported_features': 0,

View File

@ -115,7 +115,7 @@
'state': '2024-06-24T09:03:11+00:00',
})
# ---
# name: test_states[sensor.my_device_battery_level-entry]
# name: test_states[sensor.my_device_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@ -129,7 +129,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_battery_level',
'entity_id': 'sensor.my_device_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@ -141,7 +141,7 @@
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'Battery level',
'original_name': 'Battery',
'platform': 'tplink',
'previous_unique_id': None,
'supported_features': 0,
@ -150,16 +150,16 @@
'unit_of_measurement': '%',
})
# ---
# name: test_states[sensor.my_device_battery_level-state]
# name: test_states[sensor.my_device_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'my_device Battery level',
'friendly_name': 'my_device Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.my_device_battery_level',
'entity_id': 'sensor.my_device_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,

View File

@ -260,7 +260,9 @@ async def test_strip_unique_ids(
async def test_strip_blank_alias(
hass: HomeAssistant, entity_registry: er.EntityRegistry
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test a strip unique id."""
already_migrated_config_entry = MockConfigEntry(
@ -277,11 +279,27 @@ async def test_strip_blank_alias(
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
await hass.async_block_till_done()
strip_entity_id = "switch.unnamed_ks123"
state = hass.states.get(strip_entity_id)
assert state.name == "Unnamed KS123"
reg_ent = entity_registry.async_get(strip_entity_id)
assert reg_ent
reg_dev = device_registry.async_get(reg_ent.device_id)
assert reg_dev
assert reg_dev.name == "Unnamed KS123"
for plug_id in range(2):
entity_id = f"switch.unnamed_ks123_stripsocket_{plug_id + 1}"
state = hass.states.get(entity_id)
assert state.name == f"Unnamed KS123 Stripsocket {plug_id + 1}"
reg_ent = entity_registry.async_get(entity_id)
assert reg_ent
reg_dev = device_registry.async_get(reg_ent.device_id)
assert reg_dev
# Switch is a primary feature so entities go on the parent device.
assert reg_dev.name == "Unnamed KS123"
@pytest.mark.parametrize(
("exception_type", "msg", "reauth_expected"),