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