mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add and cleanup tplink translations (#135120)
This commit is contained in:
parent
15e785b974
commit
0d9ac25257
@ -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:
|
||||
|
@ -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",
|
||||
),
|
||||
|
@ -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:
|
||||
|
@ -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).
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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}
|
||||
|
@ -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": {
|
||||
|
@ -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,
|
||||
|
@ -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>,
|
||||
|
@ -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"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user