mirror of
https://github.com/home-assistant/core.git
synced 2025-08-02 10:08:23 +00:00
Calculate suggested object id from entity properties
This commit is contained in:
parent
9f039002ff
commit
0924740cb4
@ -13,6 +13,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_component,
|
||||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
@ -345,7 +346,8 @@ def websocket_get_automatic_entity_ids(
|
||||
continue
|
||||
automatic_entity_ids[entity_id] = registry.async_generate_entity_id(
|
||||
entry.domain,
|
||||
entry.suggested_object_id or f"{entry.platform}_{entry.unique_id}",
|
||||
entity_component.async_get_entity_suggested_object_id(hass, entity_id)
|
||||
or f"{entry.platform}_{entry.unique_id}",
|
||||
)
|
||||
|
||||
connection.send_message(
|
||||
|
@ -29,20 +29,27 @@ from homeassistant.core import (
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.loader import async_get_integration, bind_hass
|
||||
from homeassistant.setup import async_prepare_setup_platform
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from . import config_validation as cv, discovery, entity, service
|
||||
from .entity_platform import EntityPlatform
|
||||
from . import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
discovery,
|
||||
entity,
|
||||
entity_registry as er,
|
||||
service,
|
||||
)
|
||||
from .entity_platform import EntityPlatform, async_calculate_suggested_object_id
|
||||
from .typing import ConfigType, DiscoveryInfoType, VolDictType, VolSchemaType
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=15)
|
||||
DATA_INSTANCES = "entity_components"
|
||||
DATA_INSTANCES: HassKey[dict[str, EntityComponent]] = HassKey("entity_components")
|
||||
|
||||
|
||||
@bind_hass
|
||||
async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
"""Trigger an update for an entity."""
|
||||
domain = entity_id.partition(".")[0]
|
||||
entity_comp: EntityComponent[entity.Entity] | None
|
||||
entity_comp = hass.data.get(DATA_INSTANCES, {}).get(domain)
|
||||
|
||||
if entity_comp is None:
|
||||
@ -60,6 +67,34 @@ async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
await entity_obj.async_update_ha_state(True)
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_entity_suggested_object_id(
|
||||
hass: HomeAssistant, entity_id: str
|
||||
) -> str | None:
|
||||
"""Get the suggested object id for an entity.
|
||||
|
||||
Raises HomeAssistantError if the entity is not in the registry.
|
||||
"""
|
||||
entity_registry = er.async_get(hass)
|
||||
if not (entity_entry := entity_registry.async_get(entity_id)):
|
||||
raise HomeAssistantError(f"Entity {entity_id} is not in the registry.")
|
||||
|
||||
domain = entity_id.partition(".")[0]
|
||||
|
||||
if entity_entry.suggested_object_id:
|
||||
return entity_entry.suggested_object_id
|
||||
|
||||
entity_comp = hass.data.get(DATA_INSTANCES, {}).get(domain)
|
||||
entity_obj = entity_comp.get_entity(entity_id) if entity_comp else None
|
||||
if entity_obj:
|
||||
device: dr.DeviceEntry | None = None
|
||||
if device_id := entity_entry.device_id:
|
||||
device = dr.async_get(hass).async_get(device_id)
|
||||
return async_calculate_suggested_object_id(entity_obj, device)
|
||||
|
||||
return entity_entry.suggested_object_id
|
||||
|
||||
|
||||
class EntityComponent[_EntityT: entity.Entity = entity.Entity]:
|
||||
"""The EntityComponent manages platforms that manage entities.
|
||||
|
||||
@ -95,7 +130,7 @@ class EntityComponent[_EntityT: entity.Entity = entity.Entity]:
|
||||
self.async_add_entities = domain_platform.async_add_entities
|
||||
self.add_entities = domain_platform.add_entities
|
||||
self._entities: dict[str, entity.Entity] = domain_platform.domain_entities
|
||||
hass.data.setdefault(DATA_INSTANCES, {})[domain] = self
|
||||
hass.data.setdefault(DATA_INSTANCES, {})[domain] = self # type: ignore[assignment]
|
||||
|
||||
@property
|
||||
def entities(self) -> Iterable[_EntityT]:
|
||||
|
@ -764,7 +764,7 @@ class EntityPlatform:
|
||||
already_exists = True
|
||||
return (already_exists, restored)
|
||||
|
||||
async def _async_add_entity( # noqa: C901
|
||||
async def _async_add_entity(
|
||||
self,
|
||||
entity: Entity,
|
||||
update_before_add: bool,
|
||||
@ -843,31 +843,18 @@ class EntityPlatform:
|
||||
else:
|
||||
device = None
|
||||
|
||||
if not registered_entity_id:
|
||||
# Do not bother working out a suggested_object_id
|
||||
# if the entity is already registered as it will
|
||||
# be ignored.
|
||||
#
|
||||
# An entity may suggest the entity_id by setting entity_id itself
|
||||
suggested_entity_id: str | None = entity.entity_id
|
||||
if suggested_entity_id is not None:
|
||||
suggested_object_id = split_entity_id(entity.entity_id)[1]
|
||||
else:
|
||||
if device and entity.has_entity_name:
|
||||
device_name = device.name_by_user or device.name
|
||||
if entity.use_device_name:
|
||||
suggested_object_id = device_name
|
||||
else:
|
||||
suggested_object_id = (
|
||||
f"{device_name} {entity.suggested_object_id}"
|
||||
)
|
||||
if not suggested_object_id:
|
||||
suggested_object_id = entity.suggested_object_id
|
||||
# An entity may suggest the entity_id by setting entity_id itself
|
||||
calculated_object_id: str | None = None
|
||||
suggested_entity_id: str | None = entity.entity_id
|
||||
if suggested_entity_id is not None:
|
||||
suggested_object_id = split_entity_id(entity.entity_id)[1]
|
||||
else:
|
||||
calculated_object_id = async_calculate_suggested_object_id(
|
||||
entity, device
|
||||
)
|
||||
|
||||
if self.entity_namespace is not None:
|
||||
suggested_object_id = (
|
||||
f"{self.entity_namespace} {suggested_object_id}"
|
||||
)
|
||||
if self.entity_namespace is not None and suggested_object_id is not None:
|
||||
suggested_object_id = f"{self.entity_namespace} {suggested_object_id}"
|
||||
|
||||
disabled_by: RegistryEntryDisabler | None = None
|
||||
if not entity.entity_registry_enabled_default:
|
||||
@ -881,6 +868,7 @@ class EntityPlatform:
|
||||
self.domain,
|
||||
self.platform_name,
|
||||
entity.unique_id,
|
||||
calculated_object_id=calculated_object_id,
|
||||
capabilities=entity.capability_attributes,
|
||||
config_entry=self.config_entry,
|
||||
config_subentry_id=config_subentry_id,
|
||||
@ -1124,6 +1112,27 @@ class EntityPlatform:
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
@callback
|
||||
def async_calculate_suggested_object_id(
|
||||
entity: Entity, device: dev_reg.DeviceEntry | None
|
||||
) -> str | None:
|
||||
"""Calculate the suggested object ID for an entity."""
|
||||
calculated_object_id: str | None = None
|
||||
if device and entity.has_entity_name:
|
||||
device_name = device.name_by_user or device.name
|
||||
if entity.use_device_name:
|
||||
calculated_object_id = device_name
|
||||
else:
|
||||
calculated_object_id = f"{device_name} {entity.suggested_object_id}"
|
||||
if not calculated_object_id:
|
||||
calculated_object_id = entity.suggested_object_id
|
||||
|
||||
if (platform := entity.platform) and platform.entity_namespace is not None:
|
||||
calculated_object_id = f"{platform.entity_namespace} {calculated_object_id}"
|
||||
|
||||
return calculated_object_id
|
||||
|
||||
|
||||
current_platform: ContextVar[EntityPlatform | None] = ContextVar(
|
||||
"current_platform", default=None
|
||||
)
|
||||
|
@ -195,6 +195,7 @@ class RegistryEntry:
|
||||
name: str | None = attr.ib(default=None)
|
||||
options: ReadOnlyEntityOptionsType = attr.ib(converter=_protect_entity_options)
|
||||
# As set by integration
|
||||
calculated_object_id: str | None = attr.ib()
|
||||
original_device_class: str | None = attr.ib()
|
||||
original_icon: str | None = attr.ib()
|
||||
original_name: str | None = attr.ib()
|
||||
@ -338,6 +339,7 @@ class RegistryEntry:
|
||||
{
|
||||
"aliases": list(self.aliases),
|
||||
"area_id": self.area_id,
|
||||
"calculated_object_id": self.calculated_object_id,
|
||||
"categories": self.categories,
|
||||
"capabilities": self.capabilities,
|
||||
"config_entry_id": self.config_entry_id,
|
||||
@ -551,8 +553,9 @@ class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]):
|
||||
entity["config_subentry_id"] = None
|
||||
|
||||
if old_minor_version < 17:
|
||||
# Version 1.17 adds suggested_object_id
|
||||
# Version 1.17 adds calculated_object_id and suggested_object_id
|
||||
for entity in data["entities"]:
|
||||
entity["calculated_object_id"] = None
|
||||
entity["suggested_object_id"] = None
|
||||
|
||||
if old_major_version > 1:
|
||||
@ -843,6 +846,7 @@ class EntityRegistry(BaseRegistry):
|
||||
unique_id: str,
|
||||
*,
|
||||
# To influence entity ID generation
|
||||
calculated_object_id: str | None = None,
|
||||
suggested_object_id: str | None = None,
|
||||
# To disable or hide an entity if it gets created
|
||||
disabled_by: RegistryEntryDisabler | None = None,
|
||||
@ -915,7 +919,7 @@ class EntityRegistry(BaseRegistry):
|
||||
|
||||
entity_id = self.async_generate_entity_id(
|
||||
domain,
|
||||
suggested_object_id or f"{platform}_{unique_id}",
|
||||
suggested_object_id or calculated_object_id or f"{platform}_{unique_id}",
|
||||
)
|
||||
|
||||
if (
|
||||
@ -949,6 +953,7 @@ class EntityRegistry(BaseRegistry):
|
||||
original_icon=none_if_undefined(original_icon),
|
||||
original_name=none_if_undefined(original_name),
|
||||
platform=platform,
|
||||
calculated_object_id=calculated_object_id,
|
||||
suggested_object_id=suggested_object_id,
|
||||
supported_features=none_if_undefined(supported_features) or 0,
|
||||
translation_key=none_if_undefined(translation_key),
|
||||
@ -1358,6 +1363,7 @@ class EntityRegistry(BaseRegistry):
|
||||
entities[entity["entity_id"]] = RegistryEntry(
|
||||
aliases=set(entity["aliases"]),
|
||||
area_id=entity["area_id"],
|
||||
calculated_object_id=entity["calculated_object_id"],
|
||||
categories=entity["categories"],
|
||||
capabilities=entity["capabilities"],
|
||||
config_entry_id=entity["config_entry_id"],
|
||||
|
@ -651,6 +651,7 @@ class RegistryEntryWithDefaults(er.RegistryEntry):
|
||||
"""Helper to create a registry entry with defaults."""
|
||||
|
||||
capabilities: Mapping[str, Any] | None = attr.ib(default=None)
|
||||
calculated_object_id: str | None = attr.ib(default=None)
|
||||
config_entry_id: str | None = attr.ib(default=None)
|
||||
config_subentry_id: str | None = attr.ib(default=None)
|
||||
created_at: datetime = attr.ib(factory=dt_util.utcnow)
|
||||
|
@ -1532,6 +1532,7 @@ async def test_entity_info_added_to_entity_registry(
|
||||
entity_id="test_domain.best_name",
|
||||
unique_id="default",
|
||||
platform="test_domain",
|
||||
calculated_object_id="best name",
|
||||
capabilities={"max": 100},
|
||||
config_entry_id=None,
|
||||
config_subentry_id=None,
|
||||
@ -1550,7 +1551,7 @@ async def test_entity_info_added_to_entity_registry(
|
||||
original_icon="nice:icon",
|
||||
original_name="best name",
|
||||
options=None,
|
||||
suggested_object_id="best name",
|
||||
suggested_object_id=None,
|
||||
supported_features=5,
|
||||
translation_key="my_translation_key",
|
||||
unit_of_measurement=PERCENTAGE,
|
||||
|
@ -126,6 +126,7 @@ def test_get_or_create_updates_data(
|
||||
entity_id="light.hue_5678",
|
||||
unique_id="5678",
|
||||
platform="hue",
|
||||
calculated_object_id=None,
|
||||
capabilities={"max": 100},
|
||||
config_entry_id=orig_config_entry.entry_id,
|
||||
config_subentry_id=config_subentry_id,
|
||||
@ -185,6 +186,7 @@ def test_get_or_create_updates_data(
|
||||
platform="hue",
|
||||
aliases=set(),
|
||||
area_id=None,
|
||||
calculated_object_id=None,
|
||||
capabilities={"new-max": 150},
|
||||
config_entry_id=new_config_entry.entry_id,
|
||||
config_subentry_id=None,
|
||||
@ -238,6 +240,7 @@ def test_get_or_create_updates_data(
|
||||
platform="hue",
|
||||
aliases=set(),
|
||||
area_id=None,
|
||||
calculated_object_id=None,
|
||||
capabilities=None,
|
||||
config_entry_id=None,
|
||||
config_subentry_id=None,
|
||||
@ -517,6 +520,7 @@ async def test_load_bad_data(
|
||||
{
|
||||
"aliases": [],
|
||||
"area_id": None,
|
||||
"calculated_object_id": None,
|
||||
"capabilities": None,
|
||||
"categories": {},
|
||||
"config_entry_id": None,
|
||||
@ -549,6 +553,7 @@ async def test_load_bad_data(
|
||||
{
|
||||
"aliases": [],
|
||||
"area_id": None,
|
||||
"calculated_object_id": None,
|
||||
"capabilities": None,
|
||||
"categories": {},
|
||||
"config_entry_id": None,
|
||||
@ -905,6 +910,7 @@ async def test_migration_1_1(hass: HomeAssistant, hass_storage: dict[str, Any])
|
||||
{
|
||||
"aliases": [],
|
||||
"area_id": None,
|
||||
"calculated_object_id": None,
|
||||
"capabilities": {},
|
||||
"categories": {},
|
||||
"config_entry_id": None,
|
||||
@ -1085,6 +1091,7 @@ async def test_migration_1_11(
|
||||
{
|
||||
"aliases": [],
|
||||
"area_id": None,
|
||||
"calculated_object_id": None,
|
||||
"capabilities": {},
|
||||
"categories": {},
|
||||
"config_entry_id": None,
|
||||
|
Loading…
x
Reference in New Issue
Block a user