Skip to content

Commit

Permalink
Add and cleanup tplink translations (home-assistant#135120)
Browse files Browse the repository at this point in the history
  • Loading branch information
sdb9696 authored Jan 9, 2025
1 parent 15e785b commit 0d9ac25
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 76 deletions.
33 changes: 27 additions & 6 deletions homeassistant/components/tplink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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 (
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
5 changes: 0 additions & 5 deletions homeassistant/components/tplink/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ class TPLinkBinarySensorEntityDescription(
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",
),
Expand Down
10 changes: 8 additions & 2 deletions homeassistant/components/tplink/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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

Expand Down Expand Up @@ -104,7 +104,13 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
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:
Expand Down
24 changes: 23 additions & 1 deletion homeassistant/components/tplink/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,19 @@ def __init__(

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
# parameter (i.e. core platform like Light, Fan) or the feature
# 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
Expand All @@ -177,13 +183,28 @@ def __init__(
# 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"],
)
Expand Down Expand Up @@ -320,6 +341,7 @@ def _description_for_feature[_D: EntityDescription](

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).
Expand Down
6 changes: 0 additions & 6 deletions homeassistant/components/tplink/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
5 changes: 0 additions & 5 deletions homeassistant/components/tplink/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,6 @@ class 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}
Expand Down
52 changes: 11 additions & 41 deletions homeassistant/components/tplink/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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"
},
Expand All @@ -230,9 +192,6 @@
},
"alarm_source": {
"name": "Alarm source"
},
"rssi": {
"name": "[%key:component::sensor::entity_component::signal_strength::name%]"
}
},
"switch": {
Expand Down Expand Up @@ -291,6 +250,11 @@
}
}
},
"device": {
"unnamed_device": {
"name": "Unnamed {model}"
}
},
"services": {
"sequence_effect": {
"name": "Sequence effect",
Expand Down Expand Up @@ -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": {
Expand Down
6 changes: 3 additions & 3 deletions tests/components/tplink/snapshots/test_binary_sensor.ambr
Original file line number Diff line number Diff line change
@@ -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({
}),
Expand All @@ -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,
Expand All @@ -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,
Expand Down
12 changes: 6 additions & 6 deletions tests/components/tplink/snapshots/test_sensor.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -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({
}),
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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>,
Expand Down
20 changes: 19 additions & 1 deletion tests/components/tplink/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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"),
Expand Down

0 comments on commit 0d9ac25

Please sign in to comment.