Name devices by type alone, so entity_ids stay readable
Home Assistant mints an entity_id from the device name plus the entity name, never from the unique_id, so the OCF device UUID the entries are now keyed on (#381) was never at risk of reaching one. What did reach one is the model: `device_display_name` folded modelNum into the device name, and modelNum is a board string, so a fridge registered 46 entities along the lines of `sensor.samsung_refrigerator_artik051_dongle_ref_energy`. The name is now the appliance type alone -- `sensor.samsung_refrigerator_ energy`. The model is unchanged on DeviceInfo's own `model` field, where the device page shows it and nothing derives an entity_id from it. Two registry type names get an explicit display spelling, since they are SmartThings/Samsung strings rather than English: 'airconditioner' and 'ehs'. Subdevice devices (#177) had the same problem twice over, taking the sibling's own board string ('Samsung Air Conditioner Tp2X Fac Bora Rac 21K'), so they are numbered by position instead: the master is unit 1 and the first sibling is Unit 2. Upgrading renames devices, which is display-only -- an entity_id is assigned once, at first registration, so existing ids and the automations referencing them are untouched. Only newly discovered entities get the shorter form. Three tests read entity_ids out of the registry after a real entry setup, which is the only place the device name and the translated entity name are combined: one pins the readable form, one asserts no id carries the device key (or the literal 'None' HA appends when an entity has no name at all), and one asserts the board string stays off.
This commit is contained in:
8 files changed
+130
-30
No files matched your search
@@ -840,7 +840,7 @@ class LocalThingsConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
from .registry.identity import device_display_name
|
||||
|
||||
return self.async_create_entry(
|
||||
title=f"{device_display_name(info['device_type_name'], '')} ({self._host})",
|
||||
title=f"{device_display_name(info['device_type_name'])} ({self._host})",
|
||||
data={
|
||||
CONF_HOST: self._host,
|
||||
CONF_PORT: info["port"],
|
||||
|
||||
@@ -324,9 +324,7 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
)
|
||||
self.device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self.device_key)},
|
||||
name=device_display_name(
|
||||
entry.data.get(CONF_DEVICE_TYPE), entry.data.get(CONF_MODEL) or ""
|
||||
),
|
||||
name=device_display_name(entry.data.get(CONF_DEVICE_TYPE)),
|
||||
manufacturer=entry.data.get(CONF_MANUFACTURER) or "Samsung",
|
||||
model=entry.data.get(CONF_MODEL) or None,
|
||||
)
|
||||
@@ -760,22 +758,16 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
model = model_num.split("|", 1)[0] if model_num else ""
|
||||
serial = info.get("x.com.samsung.da.serialNum") or None
|
||||
base_name = self.device_info.get("name") or "Samsung Appliance"
|
||||
if model:
|
||||
label = model.replace("_", " ").title()
|
||||
else:
|
||||
# No identity resource yet (or ever) for this subdevice -- fall
|
||||
# back to a generic label. 'Subdevice <n>' only applies to an
|
||||
# indexed subdevice; UUID-prefixed ones are never more than one
|
||||
# per connection today.
|
||||
label = (
|
||||
f"Subdevice {subdevice.key}"
|
||||
if subdevice.kind == "indexed"
|
||||
else "Secondary Subdevice"
|
||||
)
|
||||
# Positional, not this subdevice's own modelNum: that is a board
|
||||
# string ('TP2X_FAC_BORA_RAC_21K') and this name is what HA
|
||||
# slugifies into the subdevice's entity_ids, same rule as
|
||||
# device_display_name. The master is unit 1 -- `subdevices` never
|
||||
# includes it -- so the first sibling is unit 2.
|
||||
ordinal = self.subdevices.index(subdevice) + 2 if subdevice in self.subdevices else 2
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{self.device_key}_{subdevice.key}")},
|
||||
via_device=(DOMAIN, self.device_key),
|
||||
name=f"{base_name} {label}",
|
||||
name=f"{base_name} Unit {ordinal}",
|
||||
manufacturer=self.device_info.get("manufacturer") or "Samsung",
|
||||
model=model or None,
|
||||
serial_number=serial,
|
||||
@@ -1452,7 +1444,7 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
ident = self._identity
|
||||
model = resolve_model(model_num, ident)
|
||||
name = device_display_name(device_type_name, model)
|
||||
name = device_display_name(device_type_name)
|
||||
mfr = (ident.manufacturer if ident else "") or "Samsung"
|
||||
|
||||
self.device_info = DeviceInfo(
|
||||
|
||||
@@ -135,14 +135,37 @@ def resolve_model(model_num: str, identity: DeviceIdentity | None) -> str:
|
||||
return identity.model if identity else ""
|
||||
|
||||
|
||||
def device_display_name(device_type_name: str | None, model: str) -> str:
|
||||
"""The HA device name for a resolved device type + model. Shared by the
|
||||
config flow and the coordinator's post-discovery rebuild, so the name a
|
||||
device is first registered under matches what discovery produces later
|
||||
-- otherwise every setup would rename the device once the first poll
|
||||
landed."""
|
||||
device_type = device_type_name.replace("_", " ").title() if device_type_name else "Appliance"
|
||||
return f"Samsung {device_type} ({model})" if model else f"Samsung {device_type}"
|
||||
# Registry type names whose title-cased form isn't what the appliance is
|
||||
# called: 'airconditioner' is one word only because SmartThings' own type
|
||||
# string is, and 'ehs' is Samsung's internal name for the air-to-water
|
||||
# heat pump (see by_type/ehs.py).
|
||||
_DISPLAY_TYPE_NAMES = {
|
||||
"airconditioner": "Air Conditioner",
|
||||
"ehs": "Heat Pump",
|
||||
}
|
||||
|
||||
|
||||
def device_display_name(device_type_name: str | None) -> str:
|
||||
"""The HA device name for a resolved device type.
|
||||
|
||||
Deliberately carries no model: HA slugifies this name into the
|
||||
entity_id of every entity the device registers, and modelNum is a
|
||||
board string, so folding it in produced ids like
|
||||
`sensor.samsung_refrigerator_artik051_dongle_ref_energy`. The model
|
||||
still reaches the UI through DeviceInfo's own `model` field, which
|
||||
nothing derives an entity_id from.
|
||||
|
||||
Shared by the config flow and the coordinator's post-discovery
|
||||
rebuild, so the name a device is first registered under matches what
|
||||
discovery produces later -- otherwise every setup would rename the
|
||||
device once the first poll landed.
|
||||
"""
|
||||
if not device_type_name:
|
||||
return "Samsung Appliance"
|
||||
device_type = _DISPLAY_TYPE_NAMES.get(
|
||||
device_type_name, device_type_name.replace("_", " ").title()
|
||||
)
|
||||
return f"Samsung {device_type}"
|
||||
|
||||
|
||||
def _get(sess, path) -> dict:
|
||||
|
||||
Reference in new issue
Block a user