diff --git a/homeassistant/components/adax/climate.py b/homeassistant/components/adax/climate.py index cc15872dafa..0db6a3615f6 100644 --- a/homeassistant/components/adax/climate.py +++ b/homeassistant/components/adax/climate.py @@ -1,7 +1,7 @@ """Support for Adax wifi-enabled home heaters.""" from __future__ import annotations -from typing import Any +from typing import Any, cast from adax import Adax from adax_local import Adax as AdaxLocal @@ -79,7 +79,10 @@ class AdaxDevice(ClimateEntity): self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, heater_data["id"])}, - name=self.name, + # Instead of setting the device name to the entity name, adax + # should be updated to set has_entity_name = True, and set the entity + # name to None + name=cast(str | None, self.name), manufacturer="Adax", ) diff --git a/homeassistant/components/amcrest/helpers.py b/homeassistant/components/amcrest/helpers.py index ff1a283769d..306c24a94ac 100644 --- a/homeassistant/components/amcrest/helpers.py +++ b/homeassistant/components/amcrest/helpers.py @@ -3,6 +3,8 @@ from __future__ import annotations import logging +from homeassistant.helpers.typing import UndefinedType + from .const import DOMAIN @@ -14,7 +16,7 @@ def service_signal(service: str, *args: str) -> str: def log_update_error( logger: logging.Logger, action: str, - name: str | None, + name: str | UndefinedType | None, entity_type: str, error: Exception, level: int = logging.ERROR, diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 600cc6013e4..7220842db91 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -330,7 +330,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): trace_config: ConfigType, ) -> None: """Initialize an automation entity.""" - self._attr_name = name + self._name = name self._trigger_config = trigger_config self._async_detach_triggers: CALLBACK_TYPE | None = None self._cond_func = cond_func @@ -348,6 +348,11 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self._trace_config = trace_config self._attr_unique_id = automation_id + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._name + @property def extra_state_attributes(self) -> dict[str, Any]: """Return the entity state attributes.""" diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index c642eb9112e..dd5366dee6a 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -115,10 +115,15 @@ class CupsSensor(SensorEntity): def __init__(self, data: CupsData, printer_name: str) -> None: """Initialize the CUPS sensor.""" self.data = data - self._attr_name = printer_name + self._name = printer_name self._printer: dict[str, Any] | None = None self._attr_available = False + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._name + @property def native_value(self): """Return the state of the sensor.""" @@ -149,7 +154,6 @@ class CupsSensor(SensorEntity): def update(self) -> None: """Get the latest data and updates the states.""" self.data.update() - assert self.name is not None assert self.data.printers is not None self._printer = self.data.printers.get(self.name) self._attr_available = self.data.available diff --git a/homeassistant/components/directv/entity.py b/homeassistant/components/directv/entity.py index 08b24a50a75..9d1fd68b742 100644 --- a/homeassistant/components/directv/entity.py +++ b/homeassistant/components/directv/entity.py @@ -1,6 +1,8 @@ """Base DirecTV Entity.""" from __future__ import annotations +from typing import cast + from directv import DIRECTV from homeassistant.helpers.entity import DeviceInfo, Entity @@ -24,7 +26,10 @@ class DIRECTVEntity(Entity): return DeviceInfo( identifiers={(DOMAIN, self._device_id)}, manufacturer=self.dtv.device.info.brand, - name=self.name, + # Instead of setting the device name to the entity name, directv + # should be updated to set has_entity_name = True, and set the entity + # name to None + name=cast(str | None, self.name), sw_version=self.dtv.device.info.version, via_device=(DOMAIN, self.dtv.device.info.receiver_id), ) diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index e0b381d2362..5735e1ab421 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable, Coroutine from datetime import datetime from functools import wraps import logging -from typing import Any, Concatenate, ParamSpec, TypeVar +from typing import Any, Concatenate, ParamSpec, TypeVar, cast import httpx from iaqualink.client import AqualinkClient @@ -243,6 +243,8 @@ class AqualinkEntity(Entity): identifiers={(DOMAIN, self.unique_id)}, manufacturer=self.dev.manufacturer, model=self.dev.model, - name=self.name, + # Instead of setting the device name to the entity name, iaqualink + # should be updated to set has_entity_name = True + name=cast(str | None, self.name), via_device=(DOMAIN, self.dev.system.serial), ) diff --git a/homeassistant/components/kaleidescape/entity.py b/homeassistant/components/kaleidescape/entity.py index 9a5e62bca94..cab55c20c02 100644 --- a/homeassistant/components/kaleidescape/entity.py +++ b/homeassistant/components/kaleidescape/entity.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo, Entity @@ -28,7 +28,9 @@ class KaleidescapeEntity(Entity): self._attr_name = f"{KALEIDESCAPE_NAME} {device.system.friendly_name}" self._attr_device_info = DeviceInfo( identifiers={(KALEIDESCAPE_DOMAIN, self._device.serial_number)}, - name=self.name, + # Instead of setting the device name to the entity name, kaleidescape + # should be updated to set has_entity_name = True + name=cast(str | None, self.name), model=self._device.system.type, manufacturer=KALEIDESCAPE_NAME, sw_version=f"{self._device.system.kos_version}", diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index be572679605..6585c011c2d 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Callable from functools import wraps import logging -from typing import Any, Concatenate, ParamSpec, TypeVar +from typing import Any, Concatenate, ParamSpec, TypeVar, cast import plexapi.exceptions import requests.exceptions @@ -535,7 +535,10 @@ class PlexMediaPlayer(MediaPlayerEntity): identifiers={(DOMAIN, self.machine_identifier)}, manufacturer=self.device_platform or "Plex", model=self.device_product or self.device_make, - name=self.name, + # Instead of setting the device name to the entity name, plex + # should be updated to set has_entity_name = True, and set the entity + # name to None + name=cast(str | None, self.name), sw_version=self.device_version, via_device=(DOMAIN, self.plex_server.machine_identifier), ) diff --git a/homeassistant/components/roon/media_player.py b/homeassistant/components/roon/media_player.py index 3bcafe4ba9a..6d096ea8b1a 100644 --- a/homeassistant/components/roon/media_player.py +++ b/homeassistant/components/roon/media_player.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, cast from roonapi import split_media_path import voluptuous as vol @@ -159,7 +159,10 @@ class RoonDevice(MediaPlayerEntity): dev_model = self.player_data["source_controls"][0].get("display_name") return DeviceInfo( identifiers={(DOMAIN, self.unique_id)}, - name=self.name, + # Instead of setting the device name to the entity name, roon + # should be updated to set has_entity_name = True, and set the entity + # name to None + name=cast(str | None, self.name), manufacturer="RoonLabs", model=dev_model, via_device=(DOMAIN, self._server.roon_id), diff --git a/homeassistant/components/samsungtv/entity.py b/homeassistant/components/samsungtv/entity.py index 418feecbf94..4d5ea3d5fab 100644 --- a/homeassistant/components/samsungtv/entity.py +++ b/homeassistant/components/samsungtv/entity.py @@ -1,6 +1,8 @@ """Base SamsungTV Entity.""" from __future__ import annotations +from typing import cast + from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MAC, CONF_MODEL, CONF_NAME from homeassistant.helpers import device_registry as dr @@ -20,7 +22,9 @@ class SamsungTVEntity(Entity): self._attr_name = config_entry.data.get(CONF_NAME) self._attr_unique_id = config_entry.unique_id self._attr_device_info = DeviceInfo( - name=self.name, + # Instead of setting the device name to the entity name, samsungtv + # should be updated to set has_entity_name = True + name=cast(str | None, self.name), manufacturer=config_entry.data.get(CONF_MANUFACTURER), model=config_entry.data.get(CONF_MODEL), ) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 254abbd0d63..8ee1b67020a 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -44,7 +44,7 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_call_later from homeassistant.helpers.network import get_url from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import UNDEFINED, ConfigType from homeassistant.util import dt as dt_util, language as language_util from .const import ( @@ -610,7 +610,7 @@ class SpeechManager: async def get_tts_data() -> str: """Handle data available.""" - if engine_instance.name is None: + if engine_instance.name is None or engine_instance.name is UNDEFINED: raise HomeAssistantError("TTS engine name is not set.") if isinstance(engine_instance, Provider): diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index baba8f7fd5b..29d6cafe3c8 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -15,6 +15,7 @@ from homeassistant.const import EntityCategory, Platform, UnitOfMass, UnitOfTemp from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import UndefinedType from .core import discovery from .core.const import ( @@ -334,7 +335,7 @@ class ZhaNumber(ZhaEntity, NumberEntity): return super().native_step @property - def name(self) -> str | None: + def name(self) -> str | UndefinedType | None: """Return the name of the number entity.""" description = self._analog_output_cluster_handler.description if description is not None and len(description) > 0: diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index cb947ac7604..41fe362ece3 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -258,6 +258,9 @@ class Entity(ABC): # it should be using async_write_ha_state. _async_update_ha_state_reported = False + # If we reported this entity is implicitly using device name + _implicit_device_name_reported = False + # If we reported this entity was added without its platform set _no_platform_reported = False @@ -319,6 +322,53 @@ class Entity(ABC): """Return a unique ID.""" return self._attr_unique_id + @property + def use_device_name(self) -> bool: + """Return if this entity does not have its own name. + + Should be True if the entity represents the single main feature of a device. + """ + + def report_implicit_device_name() -> None: + """Report entities which use implicit device name.""" + if self._implicit_device_name_reported: + return + report_issue = self._suggest_report_issue() + _LOGGER.warning( + ( + "Entity %s (%s) is implicitly using device name by not setting its " + "name. Instead, the name should be set to None, please %s" + ), + self.entity_id, + type(self), + report_issue, + ) + self._implicit_device_name_reported = True + + if hasattr(self, "_attr_name"): + return not self._attr_name + + if name_translation_key := self._name_translation_key(): + if name_translation_key in self.platform.platform_translations: + return False + + if hasattr(self, "entity_description"): + if not (name := self.entity_description.name): + return True + if name is UNDEFINED: + # Backwards compatibility with leaving EntityDescription.name unassigned + # for device name. + # Deprecated in HA Core 2023.6, remove in HA Core 2023.9 + report_implicit_device_name() + return True + return False + if self.name is UNDEFINED: + # Backwards compatibility with not overriding name property for device name. + # Deprecated in HA Core 2023.6, remove in HA Core 2023.9 + report_implicit_device_name() + return True + return not self.name + @property def has_entity_name(self) -> bool: """Return if the name of the entity is describing only the entity itself.""" @@ -344,16 +394,23 @@ class Entity(ABC): """Return True if an unnamed entity should be named by its device class.""" return False + def _name_translation_key(self) -> str | None: + """Return translation key for entity name.""" + if self.translation_key is None: + return None + return ( + f"component.{self.platform.platform_name}.entity.{self.platform.domain}" + f".{self.translation_key}.name" + ) + @property - def name(self) -> str | None: + def name(self) -> str | UndefinedType | None: """Return the name of the entity.""" if hasattr(self, "_attr_name"): return self._attr_name - if self.translation_key is not None and self.has_entity_name: - name_translation_key = ( - f"component.{self.platform.platform_name}.entity.{self.platform.domain}" - f".{self.translation_key}.name" - ) + if self.has_entity_name and ( + name_translation_key := self._name_translation_key() + ): if name_translation_key in self.platform.platform_translations: name: str = self.platform.platform_translations[name_translation_key] return name @@ -361,15 +418,13 @@ class Entity(ABC): description_name = self.entity_description.name if description_name is UNDEFINED and self._default_to_device_class_name(): return self._device_class_name() - if description_name is not UNDEFINED: - return description_name - return None + return description_name # The entity has no name set by _attr_name, translation_key or entity_description # Check if the entity should be named by its device class if self._default_to_device_class_name(): return self._device_class_name() - return None + return UNDEFINED @property def state(self) -> StateType: @@ -653,16 +708,20 @@ class Entity(ABC): If has_entity_name is False, this returns self.name If has_entity_name is True, this returns device.name + self.name """ + name = self.name + if name is UNDEFINED: + name = None + if not self.has_entity_name or not self.registry_entry: - return self.name + return name device_registry = dr.async_get(self.hass) if not (device_id := self.registry_entry.device_id) or not ( device_entry := device_registry.async_get(device_id) ): - return self.name + return name - if not (name := self.name): + if self.use_device_name: return device_entry.name_by_user or device_entry.name return f"{device_entry.name_by_user or device_entry.name} {name}" diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index ddc741b7d35..46cc46eb96f 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -45,7 +45,7 @@ from .device_registry import DeviceRegistry from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider from .event import async_call_later, async_track_time_interval from .issue_registry import IssueSeverity, async_create_issue -from .typing import ConfigType, DiscoveryInfoType +from .typing import UNDEFINED, ConfigType, DiscoveryInfoType if TYPE_CHECKING: from .entity import Entity @@ -552,6 +552,10 @@ class EntityPlatform: suggested_object_id: str | None = None generate_new_entity_id = False + entity_name = entity.name + if entity_name is UNDEFINED: + entity_name = None + # Get entity_id from unique ID registration if entity.unique_id is not None: registered_entity_id = entity_registry.async_get_entity_id( @@ -645,12 +649,12 @@ class EntityPlatform: else: if device and entity.has_entity_name: device_name = device.name_by_user or device.name - if not entity.name: + if entity.use_device_name: suggested_object_id = device_name else: - suggested_object_id = f"{device_name} {entity.name}" + suggested_object_id = f"{device_name} {entity_name}" if not suggested_object_id: - suggested_object_id = entity.name + suggested_object_id = entity_name if self.entity_namespace is not None: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" @@ -678,7 +682,7 @@ class EntityPlatform: known_object_ids=self.entities.keys(), original_device_class=entity.device_class, original_icon=entity.icon, - original_name=entity.name, + original_name=entity_name, suggested_object_id=suggested_object_id, supported_features=entity.supported_features, translation_key=entity.translation_key, @@ -705,7 +709,7 @@ class EntityPlatform: # Generate entity ID if entity.entity_id is None or generate_new_entity_id: suggested_object_id = ( - suggested_object_id or entity.name or DEVICE_DEFAULT_NAME + suggested_object_id or entity_name or DEVICE_DEFAULT_NAME ) if self.entity_namespace is not None: @@ -732,7 +736,7 @@ class EntityPlatform: self.logger.debug( "Not adding entity %s because it's disabled", entry.name - or entity.name + or entity_name or f'"{self.platform_name} {entity.unique_id}"', ) entity.add_to_platform_abort() diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 1d7bac65c19..0320ff2d4f4 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -573,7 +573,7 @@ _ENTITY_MATCH: list[TypeHintMatch] = [ ), TypeHintMatch( function_name="name", - return_type=["str", None], + return_type=["str", "UndefinedType", None], ), TypeHintMatch( function_name="state", diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 0d110f25b50..cedc4c7cae9 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -22,6 +22,7 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.typing import UNDEFINED from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.network import normalize_url @@ -68,7 +69,7 @@ async def test_default_entity_attributes() -> None: entity = DefaultEntity() assert entity.hass is None - assert entity.name is None + assert entity.name is UNDEFINED assert entity.default_language == DEFAULT_LANG assert entity.supported_languages == SUPPORT_LANGUAGES assert entity.supported_options is None diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index f1133e30483..1168f4b40f8 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -17,6 +17,7 @@ from homeassistant.const import ( ) from homeassistant.core import Context, HomeAssistant, HomeAssistantError from homeassistant.helpers import device_registry as dr, entity, entity_registry as er +from homeassistant.helpers.typing import UNDEFINED from tests.common import ( MockConfigEntry, @@ -948,39 +949,24 @@ async def test_entity_description_fallback() -> None: assert getattr(ent, field.name) == getattr(ent_with_description, field.name) -@pytest.mark.parametrize( - ("has_entity_name", "entity_name", "expected_friendly_name"), - ( - (False, "Entity Blu", "Entity Blu"), - (False, None, None), - (True, "Entity Blu", "Device Bla Entity Blu"), - (True, None, "Device Bla"), - ), -) -async def test_friendly_name( +async def _test_friendly_name( hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + ent: entity.Entity, has_entity_name: bool, entity_name: str | None, expected_friendly_name: str | None, + warn_implicit_name: bool, ) -> None: - """Test entity_id is influenced by entity name.""" + """Test friendly name.""" + + expected_warning = ( + f"Entity {ent.entity_id} ({type(ent)}) is implicitly using device name" + ) async def async_setup_entry(hass, config_entry, async_add_entities): """Mock setup entry method.""" - async_add_entities( - [ - MockEntity( - unique_id="qwer", - device_info={ - "identifiers": {("hue", "1234")}, - "connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")}, - "name": "Device Bla", - }, - has_entity_name=has_entity_name, - name=entity_name, - ), - ] - ) + async_add_entities([ent]) return True platform = MockPlatform(async_setup_entry=async_setup_entry) @@ -995,6 +981,132 @@ async def test_friendly_name( assert len(hass.states.async_entity_ids()) == 1 state = hass.states.async_all()[0] assert state.attributes.get(ATTR_FRIENDLY_NAME) == expected_friendly_name + assert (expected_warning in caplog.text) is warn_implicit_name + + +@pytest.mark.parametrize( + ("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"), + ( + (False, "Entity Blu", "Entity Blu", False), + (False, None, None, False), + (True, "Entity Blu", "Device Bla Entity Blu", False), + (True, None, "Device Bla", False), + ), +) +async def test_friendly_name_attr( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + has_entity_name: bool, + entity_name: str | None, + expected_friendly_name: str | None, + warn_implicit_name: bool, +) -> None: + """Test friendly name when the entity uses _attr_*.""" + + ent = MockEntity( + unique_id="qwer", + device_info={ + "identifiers": {("hue", "1234")}, + "connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")}, + "name": "Device Bla", + }, + ) + ent._attr_has_entity_name = has_entity_name + ent._attr_name = entity_name + await _test_friendly_name( + hass, + caplog, + ent, + has_entity_name, + entity_name, + expected_friendly_name, + warn_implicit_name, + ) + + +@pytest.mark.parametrize( + ("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"), + ( + (False, "Entity Blu", "Entity Blu", False), + (False, None, None, False), + (False, UNDEFINED, None, False), + (True, "Entity Blu", "Device Bla Entity Blu", False), + (True, None, "Device Bla", False), + (True, UNDEFINED, "Device Bla", True), + ), +) +async def test_friendly_name_description( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + has_entity_name: bool, + entity_name: str | None, + expected_friendly_name: str | None, + warn_implicit_name: bool, +) -> None: + """Test friendly name when the entity has an entity description.""" + + ent = MockEntity( + unique_id="qwer", + device_info={ + "identifiers": {("hue", "1234")}, + "connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")}, + "name": "Device Bla", + }, + ) + ent.entity_description = entity.EntityDescription( + "test", has_entity_name=has_entity_name, name=entity_name + ) + await _test_friendly_name( + hass, + caplog, + ent, + has_entity_name, + entity_name, + expected_friendly_name, + warn_implicit_name, + ) + + +@pytest.mark.parametrize( + ("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"), + ( + (False, "Entity Blu", "Entity Blu", False), + (False, None, None, False), + (False, UNDEFINED, None, False), + (True, "Entity Blu", "Device Bla Entity Blu", False), + (True, None, "Device Bla", False), + (True, UNDEFINED, "Device Bla", True), + ), +) +async def test_friendly_name_property( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + has_entity_name: bool, + entity_name: str | None, + expected_friendly_name: str | None, + warn_implicit_name: bool, +) -> None: + """Test friendly name when the entity has overridden the name property.""" + + ent = MockEntity( + unique_id="qwer", + device_info={ + "identifiers": {("hue", "1234")}, + "connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")}, + "name": "Device Bla", + }, + has_entity_name=has_entity_name, + name=entity_name, + ) + await _test_friendly_name( + hass, + caplog, + ent, + has_entity_name, + entity_name, + expected_friendly_name, + warn_implicit_name, + ) @pytest.mark.parametrize( @@ -1028,7 +1140,7 @@ async def test_friendly_name_updated( expected_friendly_name2: str, expected_friendly_name3: str, ) -> None: - """Test entity_id is influenced by entity name.""" + """Test friendly name is updated when device or entity registry updates.""" async def async_setup_entry(hass, config_entry, async_add_entities): """Mock setup entry method."""