Compare commits
4 commits
dev
...
entity_exp
Author | SHA1 | Date | |
---|---|---|---|
|
cee882f1e8 | ||
|
5d934509df | ||
|
eea1798a1d | ||
|
f2f76c2b8c |
11 changed files with 174 additions and 55 deletions
|
@ -28,7 +28,7 @@ from homeassistant.const import (
|
|||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceInfo
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DEVICE_NAME, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
|
@ -107,10 +107,11 @@ def sensor_update_to_bluetooth_data_update(
|
|||
"""Convert a sensor update to a Bluetooth data update."""
|
||||
entity_names: dict[PassiveBluetoothEntityKey, str | None] = {}
|
||||
for key, desc in SENSOR_DESCRIPTIONS.items():
|
||||
# PassiveBluetoothDataUpdate does not support DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
# PassiveBluetoothDataUpdate does not support DEVICE_CLASS_NAME or DEVICE_NAME.
|
||||
# The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME in the entity descriptions.
|
||||
assert desc.name is not DEVICE_CLASS_NAME
|
||||
assert desc.name is not DEVICE_NAME
|
||||
entity_names[_device_key_to_bluetooth_entity_key(adv.device, key)] = desc.name
|
||||
return PassiveBluetoothDataUpdate(
|
||||
devices={adv.device.address: _sensor_device_info_to_hass(adv)},
|
||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.components.binary_sensor import (
|
|||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DEVICE_NAME
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
|
@ -83,6 +84,9 @@ class BalboaBinarySensorEntity(BalboaEntity, BinarySensorEntity):
|
|||
self, spa: SpaClient, description: BalboaBinarySensorEntityDescription
|
||||
) -> None:
|
||||
"""Initialize a Balboa binary sensor entity."""
|
||||
# The assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_NAME in the entity descriptions.
|
||||
assert description.name is not DEVICE_NAME
|
||||
super().__init__(spa, description.name)
|
||||
self.entity_description = description
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ from homeassistant.const import (
|
|||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DEVICE_NAME
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
@ -75,10 +75,11 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity):
|
|||
# polling is done with the base class
|
||||
name = self._attr_name if self._attr_name else "modbus_sensor"
|
||||
|
||||
# DataUpdateCoordinator does not support DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in _attr_name.
|
||||
# DataUpdateCoordinator does not support DEVICE_CLASS_NAME or DEVICE_NAME
|
||||
# The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME in _attr_name.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
assert name is not DEVICE_NAME
|
||||
self._coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
|
|
|
@ -17,7 +17,7 @@ from homeassistant.const import (
|
|||
CONF_UNIT_OF_MEASUREMENT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DEVICE_NAME
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
|
@ -81,10 +81,11 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
|
|||
# polling is done with the base class
|
||||
name = self._attr_name if self._attr_name else "modbus_sensor"
|
||||
|
||||
# DataUpdateCoordinator does not support DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in _attr_name.
|
||||
# DataUpdateCoordinator does not support DEVICE_CLASS_NAME or DEVICE_NAME
|
||||
# The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME in _attr_name.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
assert name is not DEVICE_NAME
|
||||
self._coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
|
|
|
@ -19,7 +19,12 @@ from homeassistant.helpers.device_registry import (
|
|||
async_get as dr_async_get,
|
||||
format_mac,
|
||||
)
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceClassName
|
||||
from homeassistant.helpers.entity import (
|
||||
DEVICE_CLASS_NAME,
|
||||
DEVICE_NAME,
|
||||
DeviceClassName,
|
||||
DeviceName,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import async_get as er_async_get
|
||||
from homeassistant.helpers.typing import EventType
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
@ -73,16 +78,17 @@ def get_number_of_channels(device: BlockDevice, block: Block) -> int:
|
|||
def get_block_entity_name(
|
||||
device: BlockDevice,
|
||||
block: Block | None,
|
||||
description: str | DeviceClassName | None = None,
|
||||
description: str | DeviceClassName | DeviceName | None = None,
|
||||
) -> str:
|
||||
"""Naming for block based switch and sensors."""
|
||||
channel_name = get_block_channel_name(device, block)
|
||||
|
||||
if description:
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME as description.
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME or
|
||||
# DEVICE_NAME. The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME as descriptions.
|
||||
assert description is not DEVICE_CLASS_NAME
|
||||
assert description is not DEVICE_NAME
|
||||
return f"{channel_name} {description.lower()}"
|
||||
|
||||
return channel_name
|
||||
|
@ -306,16 +312,19 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str:
|
|||
|
||||
|
||||
def get_rpc_entity_name(
|
||||
device: RpcDevice, key: str, description: str | DeviceClassName | None = None
|
||||
device: RpcDevice,
|
||||
key: str,
|
||||
description: str | DeviceClassName | DeviceName | None = None,
|
||||
) -> str:
|
||||
"""Naming for RPC based switch and sensors."""
|
||||
channel_name = get_rpc_channel_name(device, key)
|
||||
|
||||
if description:
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME as description.
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME or
|
||||
# DEVICE_NAME. The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME as descriptions.
|
||||
assert description is not DEVICE_CLASS_NAME
|
||||
assert description is not DEVICE_NAME
|
||||
return f"{channel_name} {description.lower()}"
|
||||
|
||||
return channel_name
|
||||
|
|
|
@ -30,7 +30,13 @@ from homeassistant.core import HomeAssistant, ServiceCall
|
|||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceClassName, DeviceInfo
|
||||
from homeassistant.helpers.entity import (
|
||||
DEVICE_CLASS_NAME,
|
||||
DEVICE_NAME,
|
||||
DeviceClassName,
|
||||
DeviceInfo,
|
||||
DeviceName,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MODULES
|
||||
|
@ -279,17 +285,18 @@ class SystemBridgeEntity(CoordinatorEntity[SystemBridgeDataUpdateCoordinator]):
|
|||
coordinator: SystemBridgeDataUpdateCoordinator,
|
||||
api_port: int,
|
||||
key: str,
|
||||
name: str | DeviceClassName | None,
|
||||
name: str | DeviceClassName | DeviceName | None,
|
||||
) -> None:
|
||||
"""Initialize the System Bridge entity."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._hostname = coordinator.data.system.hostname
|
||||
self._key = f"{self._hostname}_{key}"
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME as name.
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME or
|
||||
# DEVICE_NAME. The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME as name.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
assert name is not DEVICE_NAME
|
||||
self._name = f"{self._hostname} {name}"
|
||||
self._configuration_url = (
|
||||
f"http://{self._hostname}:{api_port}/app/settings.html"
|
||||
|
|
|
@ -32,7 +32,7 @@ from homeassistant.const import (
|
|||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DEVICE_NAME
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.unit_conversion import DistanceConverter, SpeedConverter
|
||||
|
@ -350,10 +350,11 @@ class BaseTomorrowioSensorEntity(TomorrowioEntity, SensorEntity):
|
|||
"""Initialize Tomorrow.io Sensor Entity."""
|
||||
super().__init__(config_entry, coordinator, api_version)
|
||||
self.entity_description = description
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME or
|
||||
# DEVICE_NAME. The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME in the entity descriptions.
|
||||
assert description.name is not DEVICE_CLASS_NAME
|
||||
assert description.name is not DEVICE_NAME
|
||||
self._attr_name = f"{self._config_entry.data[CONF_NAME]} - {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{self._config_entry.unique_id}_{slugify(description.name)}"
|
||||
|
|
|
@ -24,6 +24,7 @@ from homeassistant.core import callback
|
|||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.entity import (
|
||||
DEVICE_CLASS_NAME,
|
||||
DEVICE_NAME,
|
||||
DeviceInfo,
|
||||
Entity,
|
||||
EntityDescription,
|
||||
|
@ -204,10 +205,11 @@ class ProtectDeviceEntity(Entity):
|
|||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self.device.mac}_{description.key}"
|
||||
name = description.name or ""
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME or
|
||||
# DEVICE_NAME. The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME in the entity descriptions.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
assert name is not DEVICE_NAME
|
||||
self._attr_name = f"{self.device.display_name} {name.title()}"
|
||||
|
||||
self._attr_attribution = DEFAULT_ATTRIBUTION
|
||||
|
|
|
@ -8,7 +8,12 @@ from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceInfo, Entity
|
||||
from homeassistant.helpers.entity import (
|
||||
DEVICE_CLASS_NAME,
|
||||
DEVICE_NAME,
|
||||
DeviceInfo,
|
||||
Entity,
|
||||
)
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .discovery import ZwaveDiscoveryInfo
|
||||
|
@ -136,10 +141,11 @@ class ZWaveBaseEntity(Entity):
|
|||
and self.entity_description
|
||||
and self.entity_description.name
|
||||
):
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME or
|
||||
# DEVICE_NAME. The asserts satisfy the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME or DEVICE_NAME in the entity descriptions.
|
||||
assert self.entity_description.name is not DEVICE_CLASS_NAME
|
||||
assert self.entity_description.name is not DEVICE_NAME
|
||||
name = self.entity_description.name
|
||||
|
||||
if name_prefix:
|
||||
|
|
|
@ -53,6 +53,15 @@ SOURCE_CONFIG_ENTRY = "config_entry"
|
|||
SOURCE_PLATFORM_CONFIG = "platform_config"
|
||||
|
||||
|
||||
class DeviceName(Enum):
|
||||
"""Singleton to use device name."""
|
||||
|
||||
_singleton = 0
|
||||
|
||||
|
||||
DEVICE_NAME = DeviceName._singleton # pylint: disable=protected-access
|
||||
|
||||
|
||||
class DeviceClassName(Enum):
|
||||
"""Singleton to use device class name."""
|
||||
|
||||
|
@ -229,7 +238,7 @@ class EntityDescription:
|
|||
force_update: bool = False
|
||||
icon: str | None = None
|
||||
has_entity_name: bool = False
|
||||
name: str | DeviceClassName | None = None
|
||||
name: str | DeviceClassName | DeviceName | None = None
|
||||
translation_key: str | None = None
|
||||
unit_of_measurement: str | None = None
|
||||
|
||||
|
@ -263,6 +272,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
|
||||
|
||||
# Protect for multiple updates
|
||||
_update_staged = False
|
||||
|
||||
|
@ -298,7 +310,7 @@ class Entity(ABC):
|
|||
_attr_extra_state_attributes: MutableMapping[str, Any]
|
||||
_attr_force_update: bool
|
||||
_attr_icon: str | None
|
||||
_attr_name: str | DeviceClassName | None
|
||||
_attr_name: str | DeviceClassName | DeviceName | None
|
||||
_attr_should_poll: bool = True
|
||||
_attr_state: StateType = STATE_UNKNOWN
|
||||
_attr_supported_features: int | None = None
|
||||
|
@ -319,6 +331,66 @@ 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 setting its"
|
||||
" name to None. Instead, the name should be set to DEVICE_NAME"
|
||||
", please %s"
|
||||
),
|
||||
self.entity_id,
|
||||
type(self),
|
||||
report_issue,
|
||||
)
|
||||
self._implicit_device_name_reported = True
|
||||
|
||||
if not self.has_entity_name:
|
||||
return False
|
||||
if hasattr(self, "_attr_name"):
|
||||
if (name := self._attr_name) is DEVICE_NAME:
|
||||
return True
|
||||
if not name:
|
||||
# Backwards compatibility with setting _attr_name to None to indicate
|
||||
# device name.
|
||||
# Deprecated in HA Core 2023.6, remove in HA Core 2023.7
|
||||
report_implicit_device_name()
|
||||
return True
|
||||
return False
|
||||
|
||||
if name_translation_key := self.__name_translation_key():
|
||||
assert self.platform
|
||||
if name_translation_key in self.platform.platform_translations:
|
||||
return False
|
||||
|
||||
if hasattr(self, "entity_description"):
|
||||
if (name := self.entity_description.name) is DEVICE_NAME:
|
||||
return True
|
||||
if not name:
|
||||
# Backwards compatibility with setting EntityDescription.name to None
|
||||
# for device name.
|
||||
# Deprecated in HA Core 2023.6, remove in HA Core 2023.7
|
||||
report_implicit_device_name()
|
||||
return True
|
||||
return False
|
||||
if not self.name:
|
||||
# Backwards compatibility with setting EntityDescription.name to None
|
||||
# for device name.
|
||||
# Deprecated in HA Core 2023.6, remove in HA Core 2023.7
|
||||
report_implicit_device_name()
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def has_entity_name(self) -> bool:
|
||||
"""Return if the name of the entity is describing only the entity itself."""
|
||||
|
@ -340,26 +412,41 @@ class Entity(ABC):
|
|||
)
|
||||
return self.platform.component_translations.get(name_translation_key)
|
||||
|
||||
def __name_translation_key(self) -> str | None:
|
||||
"""Return translation key for entity name."""
|
||||
if self.translation_key is None:
|
||||
return None
|
||||
assert self.platform
|
||||
return (
|
||||
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
||||
f".{self.translation_key}.name"
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
"""Return the name of the entity."""
|
||||
"""Return the name of the entity.
|
||||
|
||||
Returns None if the name is set to DEVICE_NAME.
|
||||
"""
|
||||
if hasattr(self, "_attr_name"):
|
||||
if self._attr_name is DEVICE_CLASS_NAME:
|
||||
if (name := self._attr_name) is DEVICE_CLASS_NAME:
|
||||
return self._device_class_name()
|
||||
return self._attr_name
|
||||
if self.translation_key is not None and self.has_entity_name:
|
||||
if name is DEVICE_NAME:
|
||||
return None
|
||||
return name
|
||||
if self.has_entity_name and (
|
||||
name_translation_key := self.__name_translation_key()
|
||||
):
|
||||
assert self.platform
|
||||
name_translation_key = (
|
||||
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
||||
f".{self.translation_key}.name"
|
||||
)
|
||||
if name_translation_key in self.platform.platform_translations:
|
||||
name: str = self.platform.platform_translations[name_translation_key]
|
||||
name = self.platform.platform_translations[name_translation_key]
|
||||
return name
|
||||
if hasattr(self, "entity_description"):
|
||||
if self.entity_description.name is DEVICE_CLASS_NAME:
|
||||
if (name := self.entity_description.name) is DEVICE_CLASS_NAME:
|
||||
return self._device_class_name()
|
||||
return self.entity_description.name
|
||||
if name is DEVICE_NAME:
|
||||
return None
|
||||
return name
|
||||
return None
|
||||
|
||||
@property
|
||||
|
@ -637,9 +724,9 @@ class Entity(ABC):
|
|||
):
|
||||
return self.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}"
|
||||
return f"{device_entry.name_by_user or device_entry.name} {self.name}"
|
||||
|
||||
@callback
|
||||
def _async_write_ha_state(self) -> None:
|
||||
|
|
|
@ -125,8 +125,8 @@ class EntityPlatform:
|
|||
self.entity_namespace = entity_namespace
|
||||
self.config_entry: config_entries.ConfigEntry | None = None
|
||||
self.entities: dict[str, Entity] = {}
|
||||
self.component_translations: dict[str, Any] = {}
|
||||
self.platform_translations: dict[str, Any] = {}
|
||||
self.component_translations: dict[str, str] = {}
|
||||
self.platform_translations: dict[str, str] = {}
|
||||
self._tasks: list[asyncio.Task[None]] = []
|
||||
# Stop tracking tasks after setup is completed
|
||||
self._setup_complete = False
|
||||
|
@ -627,7 +627,7 @@ class EntityPlatform:
|
|||
else:
|
||||
if device and entity.has_entity_name: # type: ignore[unreachable]
|
||||
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}"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue