Allow setting an entity's name by its device class (#90767)
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
337b59ba23
commit
67c1051305
21 changed files with 141 additions and 35 deletions
|
@ -28,7 +28,7 @@ from homeassistant.const import (
|
||||||
UnitOfTime,
|
UnitOfTime,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
@ -105,6 +105,13 @@ def sensor_update_to_bluetooth_data_update(
|
||||||
adv: Aranet4Advertisement,
|
adv: Aranet4Advertisement,
|
||||||
) -> PassiveBluetoothDataUpdate:
|
) -> PassiveBluetoothDataUpdate:
|
||||||
"""Convert a sensor update to a 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.
|
||||||
|
assert desc.name is not DEVICE_CLASS_NAME
|
||||||
|
entity_names[_device_key_to_bluetooth_entity_key(adv.device, key)] = desc.name
|
||||||
return PassiveBluetoothDataUpdate(
|
return PassiveBluetoothDataUpdate(
|
||||||
devices={adv.device.address: _sensor_device_info_to_hass(adv)},
|
devices={adv.device.address: _sensor_device_info_to_hass(adv)},
|
||||||
entity_descriptions={
|
entity_descriptions={
|
||||||
|
@ -117,10 +124,7 @@ def sensor_update_to_bluetooth_data_update(
|
||||||
)
|
)
|
||||||
for key in SENSOR_DESCRIPTIONS
|
for key in SENSOR_DESCRIPTIONS
|
||||||
},
|
},
|
||||||
entity_names={
|
entity_names=entity_names,
|
||||||
_device_key_to_bluetooth_entity_key(adv.device, key): desc.name
|
|
||||||
for key, desc in SENSOR_DESCRIPTIONS.items()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||||
from pybalboa import EVENT_UPDATE, SpaClient
|
from pybalboa import EVENT_UPDATE, SpaClient
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
from homeassistant.helpers.entity import DeviceClassName, DeviceInfo, Entity
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
@ -12,7 +12,9 @@ from .const import DOMAIN
|
||||||
class BalboaBaseEntity(Entity):
|
class BalboaBaseEntity(Entity):
|
||||||
"""Balboa base entity."""
|
"""Balboa base entity."""
|
||||||
|
|
||||||
def __init__(self, client: SpaClient, name: str | None = None) -> None:
|
def __init__(
|
||||||
|
self, client: SpaClient, name: str | DeviceClassName | None = None
|
||||||
|
) -> None:
|
||||||
"""Initialize the control."""
|
"""Initialize the control."""
|
||||||
mac = client.mac_address
|
mac = client.mac_address
|
||||||
model = client.model
|
model = client.model
|
||||||
|
|
|
@ -35,6 +35,11 @@ class BondButtonEntityDescription(
|
||||||
):
|
):
|
||||||
"""Class to describe a Bond Button entity."""
|
"""Class to describe a Bond Button entity."""
|
||||||
|
|
||||||
|
# BondEntity does not support DEVICE_CLASS_NAME
|
||||||
|
# Restrict the type to satisfy the type checker and catch attempts
|
||||||
|
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||||
|
name: str | None = None
|
||||||
|
|
||||||
|
|
||||||
STOP_BUTTON = BondButtonEntityDescription(
|
STOP_BUTTON = BondButtonEntityDescription(
|
||||||
key=Action.STOP,
|
key=Action.STOP,
|
||||||
|
|
|
@ -88,7 +88,7 @@ class BruntDevice(
|
||||||
self._attr_attribution = ATTRIBUTION
|
self._attr_attribution = ATTRIBUTION
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, self._attr_unique_id)}, # type: ignore[arg-type]
|
identifiers={(DOMAIN, self._attr_unique_id)}, # type: ignore[arg-type]
|
||||||
name=self._attr_name,
|
name=self._thing.name,
|
||||||
via_device=(DOMAIN, self._entry_id),
|
via_device=(DOMAIN, self._entry_id),
|
||||||
manufacturer="Brunt",
|
manufacturer="Brunt",
|
||||||
sw_version=self._thing.fw_version,
|
sw_version=self._thing.fw_version,
|
||||||
|
|
|
@ -30,8 +30,8 @@ class FreeboxHomeEntity(Entity):
|
||||||
self._node = node
|
self._node = node
|
||||||
self._sub_node = sub_node
|
self._sub_node = sub_node
|
||||||
self._id = node["id"]
|
self._id = node["id"]
|
||||||
self._attr_name = node["label"].strip()
|
self._device_name = node["label"].strip()
|
||||||
self._device_name = self._attr_name
|
self._attr_name = self._device_name
|
||||||
self._attr_unique_id = f"{self._router.mac}-node_{self._id}"
|
self._attr_unique_id = f"{self._router.mac}-node_{self._id}"
|
||||||
|
|
||||||
if sub_node is not None:
|
if sub_node is not None:
|
||||||
|
|
|
@ -28,6 +28,10 @@ class IncomfortSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Describes Incomfort sensor entity."""
|
"""Describes Incomfort sensor entity."""
|
||||||
|
|
||||||
extra_key: str | None = None
|
extra_key: str | None = None
|
||||||
|
# IncomfortSensor does not support DEVICE_CLASS_NAME
|
||||||
|
# Restrict the type to satisfy the type checker and catch attempts
|
||||||
|
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||||
|
name: str | None = None
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES: tuple[IncomfortSensorEntityDescription, ...] = (
|
SENSOR_TYPES: tuple[IncomfortSensorEntityDescription, ...] = (
|
||||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.const import (
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
@ -73,6 +74,11 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity):
|
||||||
# this ensures that idx = bit position of value in result
|
# this ensures that idx = bit position of value in result
|
||||||
# polling is done with the base class
|
# polling is done with the base class
|
||||||
name = self._attr_name if self._attr_name else "modbus_sensor"
|
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.
|
||||||
|
assert name is not DEVICE_CLASS_NAME
|
||||||
self._coordinator = DataUpdateCoordinator(
|
self._coordinator = DataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
|
|
|
@ -17,6 +17,7 @@ from homeassistant.const import (
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
@ -79,6 +80,11 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
|
||||||
# this ensures that idx = bit position of value in result
|
# this ensures that idx = bit position of value in result
|
||||||
# polling is done with the base class
|
# polling is done with the base class
|
||||||
name = self._attr_name if self._attr_name else "modbus_sensor"
|
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.
|
||||||
|
assert name is not DEVICE_CLASS_NAME
|
||||||
self._coordinator = DataUpdateCoordinator(
|
self._coordinator = DataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
|
|
|
@ -67,7 +67,7 @@ class MinutPointAlarmControl(AlarmControlPanelEntity):
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(POINT_DOMAIN, home_id)},
|
identifiers={(POINT_DOMAIN, home_id)},
|
||||||
manufacturer="Minut",
|
manufacturer="Minut",
|
||||||
name=self._attr_name,
|
name=self._home["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
|
|
|
@ -19,6 +19,7 @@ from homeassistant.helpers.device_registry import (
|
||||||
async_get as dr_async_get,
|
async_get as dr_async_get,
|
||||||
format_mac,
|
format_mac,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceClassName
|
||||||
from homeassistant.helpers.entity_registry import async_get as er_async_get
|
from homeassistant.helpers.entity_registry import async_get as er_async_get
|
||||||
from homeassistant.helpers.typing import EventType
|
from homeassistant.helpers.typing import EventType
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
@ -72,12 +73,16 @@ def get_number_of_channels(device: BlockDevice, block: Block) -> int:
|
||||||
def get_block_entity_name(
|
def get_block_entity_name(
|
||||||
device: BlockDevice,
|
device: BlockDevice,
|
||||||
block: Block | None,
|
block: Block | None,
|
||||||
description: str | None = None,
|
description: str | DeviceClassName | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Naming for block based switch and sensors."""
|
"""Naming for block based switch and sensors."""
|
||||||
channel_name = get_block_channel_name(device, block)
|
channel_name = get_block_channel_name(device, block)
|
||||||
|
|
||||||
if description:
|
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.
|
||||||
|
assert description is not DEVICE_CLASS_NAME
|
||||||
return f"{channel_name} {description.lower()}"
|
return f"{channel_name} {description.lower()}"
|
||||||
|
|
||||||
return channel_name
|
return channel_name
|
||||||
|
@ -301,12 +306,16 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_rpc_entity_name(
|
def get_rpc_entity_name(
|
||||||
device: RpcDevice, key: str, description: str | None = None
|
device: RpcDevice, key: str, description: str | DeviceClassName | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Naming for RPC based switch and sensors."""
|
"""Naming for RPC based switch and sensors."""
|
||||||
channel_name = get_rpc_channel_name(device, key)
|
channel_name = get_rpc_channel_name(device, key)
|
||||||
|
|
||||||
if description:
|
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.
|
||||||
|
assert description is not DEVICE_CLASS_NAME
|
||||||
return f"{channel_name} {description.lower()}"
|
return f"{channel_name} {description.lower()}"
|
||||||
|
|
||||||
return channel_name
|
return channel_name
|
||||||
|
|
|
@ -30,7 +30,7 @@ from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceClassName, DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import DOMAIN, MODULES
|
from .const import DOMAIN, MODULES
|
||||||
|
@ -279,13 +279,17 @@ class SystemBridgeEntity(CoordinatorEntity[SystemBridgeDataUpdateCoordinator]):
|
||||||
coordinator: SystemBridgeDataUpdateCoordinator,
|
coordinator: SystemBridgeDataUpdateCoordinator,
|
||||||
api_port: int,
|
api_port: int,
|
||||||
key: str,
|
key: str,
|
||||||
name: str | None,
|
name: str | DeviceClassName | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the System Bridge entity."""
|
"""Initialize the System Bridge entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
|
||||||
self._hostname = coordinator.data.system.hostname
|
self._hostname = coordinator.data.system.hostname
|
||||||
self._key = f"{self._hostname}_{key}"
|
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.
|
||||||
|
assert name is not DEVICE_CLASS_NAME
|
||||||
self._name = f"{self._hostname} {name}"
|
self._name = f"{self._hostname} {name}"
|
||||||
self._configuration_url = (
|
self._configuration_url = (
|
||||||
f"http://{self._hostname}:{api_port}/app/settings.html"
|
f"http://{self._hostname}:{api_port}/app/settings.html"
|
||||||
|
|
|
@ -32,6 +32,7 @@ from homeassistant.const import (
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
from homeassistant.util.unit_conversion import DistanceConverter, SpeedConverter
|
from homeassistant.util.unit_conversion import DistanceConverter, SpeedConverter
|
||||||
|
@ -349,6 +350,10 @@ class BaseTomorrowioSensorEntity(TomorrowioEntity, SensorEntity):
|
||||||
"""Initialize Tomorrow.io Sensor Entity."""
|
"""Initialize Tomorrow.io Sensor Entity."""
|
||||||
super().__init__(config_entry, coordinator, api_version)
|
super().__init__(config_entry, coordinator, api_version)
|
||||||
self.entity_description = description
|
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.
|
||||||
|
assert description.name is not DEVICE_CLASS_NAME
|
||||||
self._attr_name = f"{self._config_entry.data[CONF_NAME]} - {description.name}"
|
self._attr_name = f"{self._config_entry.data[CONF_NAME]} - {description.name}"
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{self._config_entry.unique_id}_{slugify(description.name)}"
|
f"{self._config_entry.unique_id}_{slugify(description.name)}"
|
||||||
|
|
|
@ -22,7 +22,12 @@ from pyunifiprotect.data import (
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
import homeassistant.helpers.device_registry as dr
|
import homeassistant.helpers.device_registry as dr
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
from homeassistant.helpers.entity import (
|
||||||
|
DEVICE_CLASS_NAME,
|
||||||
|
DeviceInfo,
|
||||||
|
Entity,
|
||||||
|
EntityDescription,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_EVENT_ID,
|
ATTR_EVENT_ID,
|
||||||
|
@ -199,6 +204,10 @@ class ProtectDeviceEntity(Entity):
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_unique_id = f"{self.device.mac}_{description.key}"
|
self._attr_unique_id = f"{self.device.mac}_{description.key}"
|
||||||
name = description.name or ""
|
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.
|
||||||
|
assert name is not DEVICE_CLASS_NAME
|
||||||
self._attr_name = f"{self.device.display_name} {name.title()}"
|
self._attr_name = f"{self.device.display_name} {name.title()}"
|
||||||
|
|
||||||
self._attr_attribution = DEFAULT_ATTRIBUTION
|
self._attr_attribution = DEFAULT_ATTRIBUTION
|
||||||
|
|
|
@ -131,6 +131,8 @@ async def async_setup_entry(
|
||||||
class VizioDevice(MediaPlayerEntity):
|
class VizioDevice(MediaPlayerEntity):
|
||||||
"""Media Player implementation which performs REST requests to device."""
|
"""Media Player implementation which performs REST requests to device."""
|
||||||
|
|
||||||
|
_attr_name: str
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
|
|
|
@ -28,6 +28,10 @@ from .wemo_device import DeviceCoordinator
|
||||||
class AttributeSensorDescription(SensorEntityDescription):
|
class AttributeSensorDescription(SensorEntityDescription):
|
||||||
"""SensorEntityDescription for WeMo AttributeSensor entities."""
|
"""SensorEntityDescription for WeMo AttributeSensor entities."""
|
||||||
|
|
||||||
|
# AttributeSensor 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.
|
||||||
|
name: str | None = None
|
||||||
state_conversion: Callable[[StateType], StateType] | None = None
|
state_conversion: Callable[[StateType], StateType] | None = None
|
||||||
unique_id_suffix: str | None = None
|
unique_id_suffix: str | None = None
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceInfo, Entity
|
||||||
|
|
||||||
from .const import DOMAIN, LOGGER
|
from .const import DOMAIN, LOGGER
|
||||||
from .discovery import ZwaveDiscoveryInfo
|
from .discovery import ZwaveDiscoveryInfo
|
||||||
|
@ -136,6 +136,10 @@ class ZWaveBaseEntity(Entity):
|
||||||
and self.entity_description
|
and self.entity_description
|
||||||
and self.entity_description.name
|
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.
|
||||||
|
assert self.entity_description.name is not DEVICE_CLASS_NAME
|
||||||
name = self.entity_description.name
|
name = self.entity_description.name
|
||||||
|
|
||||||
if name_prefix:
|
if name_prefix:
|
||||||
|
|
|
@ -115,7 +115,7 @@ async def async_setup_platforms(
|
||||||
class ZWaveMeEntity(Entity):
|
class ZWaveMeEntity(Entity):
|
||||||
"""Representation of a ZWaveMe device."""
|
"""Representation of a ZWaveMe device."""
|
||||||
|
|
||||||
def __init__(self, controller, device):
|
def __init__(self, controller: ZWaveMeController, device: ZWaveMeData) -> None:
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.device = device
|
self.device = device
|
||||||
|
@ -124,13 +124,9 @@ class ZWaveMeEntity(Entity):
|
||||||
f"{self.controller.config.unique_id}-{self.device.id}"
|
f"{self.controller.config.unique_id}-{self.device.id}"
|
||||||
)
|
)
|
||||||
self._attr_should_poll = False
|
self._attr_should_poll = False
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return device specific attributes."""
|
|
||||||
return DeviceInfo(
|
|
||||||
identifiers={(DOMAIN, self.device.deviceIdentifier)},
|
identifiers={(DOMAIN, self.device.deviceIdentifier)},
|
||||||
name=self._attr_name,
|
name=device.title,
|
||||||
manufacturer=self.device.manufacturer,
|
manufacturer=self.device.manufacturer,
|
||||||
sw_version=self.device.firmware,
|
sw_version=self.device.firmware,
|
||||||
suggested_area=self.device.locationName,
|
suggested_area=self.device.locationName,
|
||||||
|
|
|
@ -52,6 +52,16 @@ DATA_ENTITY_SOURCE = "entity_info"
|
||||||
SOURCE_CONFIG_ENTRY = "config_entry"
|
SOURCE_CONFIG_ENTRY = "config_entry"
|
||||||
SOURCE_PLATFORM_CONFIG = "platform_config"
|
SOURCE_PLATFORM_CONFIG = "platform_config"
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceClassName(Enum):
|
||||||
|
"""Singleton to use device class name."""
|
||||||
|
|
||||||
|
_singleton = 0
|
||||||
|
|
||||||
|
|
||||||
|
DEVICE_CLASS_NAME = DeviceClassName._singleton # pylint: disable=protected-access
|
||||||
|
|
||||||
|
|
||||||
# Used when converting float states to string: limit precision according to machine
|
# Used when converting float states to string: limit precision according to machine
|
||||||
# epsilon to make the string representation readable
|
# epsilon to make the string representation readable
|
||||||
FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1
|
FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1
|
||||||
|
@ -219,7 +229,7 @@ class EntityDescription:
|
||||||
force_update: bool = False
|
force_update: bool = False
|
||||||
icon: str | None = None
|
icon: str | None = None
|
||||||
has_entity_name: bool = False
|
has_entity_name: bool = False
|
||||||
name: str | None = None
|
name: str | DeviceClassName | None = None
|
||||||
translation_key: str | None = None
|
translation_key: str | None = None
|
||||||
unit_of_measurement: str | None = None
|
unit_of_measurement: str | None = None
|
||||||
|
|
||||||
|
@ -288,7 +298,7 @@ class Entity(ABC):
|
||||||
_attr_extra_state_attributes: MutableMapping[str, Any]
|
_attr_extra_state_attributes: MutableMapping[str, Any]
|
||||||
_attr_force_update: bool
|
_attr_force_update: bool
|
||||||
_attr_icon: str | None
|
_attr_icon: str | None
|
||||||
_attr_name: str | None
|
_attr_name: str | DeviceClassName | None
|
||||||
_attr_should_poll: bool = True
|
_attr_should_poll: bool = True
|
||||||
_attr_state: StateType = STATE_UNKNOWN
|
_attr_state: StateType = STATE_UNKNOWN
|
||||||
_attr_supported_features: int | None = None
|
_attr_supported_features: int | None = None
|
||||||
|
@ -318,10 +328,24 @@ class Entity(ABC):
|
||||||
return self.entity_description.has_entity_name
|
return self.entity_description.has_entity_name
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _device_class_name(self) -> str | None:
|
||||||
|
"""Return a translated name of the entity based on its device class."""
|
||||||
|
assert self.platform
|
||||||
|
if not self.has_entity_name:
|
||||||
|
return None
|
||||||
|
device_class_key = self.device_class or "_"
|
||||||
|
name_translation_key = (
|
||||||
|
f"component.{self.platform.domain}.entity_component."
|
||||||
|
f"{device_class_key}.name"
|
||||||
|
)
|
||||||
|
return self.platform.component_translations.get(name_translation_key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str | None:
|
def name(self) -> str | None:
|
||||||
"""Return the name of the entity."""
|
"""Return the name of the entity."""
|
||||||
if hasattr(self, "_attr_name"):
|
if hasattr(self, "_attr_name"):
|
||||||
|
if self._attr_name is DEVICE_CLASS_NAME:
|
||||||
|
return self._device_class_name()
|
||||||
return self._attr_name
|
return self._attr_name
|
||||||
if self.translation_key is not None and self.has_entity_name:
|
if self.translation_key is not None and self.has_entity_name:
|
||||||
assert self.platform
|
assert self.platform
|
||||||
|
@ -329,10 +353,12 @@ class Entity(ABC):
|
||||||
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
||||||
f".{self.translation_key}.name"
|
f".{self.translation_key}.name"
|
||||||
)
|
)
|
||||||
if name_translation_key in self.platform.entity_translations:
|
if name_translation_key in self.platform.platform_translations:
|
||||||
name: str = self.platform.entity_translations[name_translation_key]
|
name: str = self.platform.platform_translations[name_translation_key]
|
||||||
return name
|
return name
|
||||||
if hasattr(self, "entity_description"):
|
if hasattr(self, "entity_description"):
|
||||||
|
if self.entity_description.name is DEVICE_CLASS_NAME:
|
||||||
|
return self._device_class_name()
|
||||||
return self.entity_description.name
|
return self.entity_description.name
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,8 @@ class EntityPlatform:
|
||||||
self.entity_namespace = entity_namespace
|
self.entity_namespace = entity_namespace
|
||||||
self.config_entry: config_entries.ConfigEntry | None = None
|
self.config_entry: config_entries.ConfigEntry | None = None
|
||||||
self.entities: dict[str, Entity] = {}
|
self.entities: dict[str, Entity] = {}
|
||||||
self.entity_translations: dict[str, Any] = {}
|
self.component_translations: dict[str, Any] = {}
|
||||||
|
self.platform_translations: dict[str, Any] = {}
|
||||||
self._tasks: list[asyncio.Task[None]] = []
|
self._tasks: list[asyncio.Task[None]] = []
|
||||||
# Stop tracking tasks after setup is completed
|
# Stop tracking tasks after setup is completed
|
||||||
self._setup_complete = False
|
self._setup_complete = False
|
||||||
|
@ -279,7 +280,15 @@ class EntityPlatform:
|
||||||
full_name = f"{self.domain}.{self.platform_name}"
|
full_name = f"{self.domain}.{self.platform_name}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.entity_translations = await translation.async_get_translations(
|
self.component_translations = await translation.async_get_translations(
|
||||||
|
hass, hass.config.language, "entity_component", {self.domain}
|
||||||
|
)
|
||||||
|
except Exception as err: # pylint: disable=broad-exception-caught
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Could not load translations for %s", self.domain, exc_info=err
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.platform_translations = await translation.async_get_translations(
|
||||||
hass, hass.config.language, "entity", {self.platform_name}
|
hass, hass.config.language, "entity", {self.platform_name}
|
||||||
)
|
)
|
||||||
except Exception as err: # pylint: disable=broad-exception-caught
|
except Exception as err: # pylint: disable=broad-exception-caught
|
||||||
|
|
|
@ -172,6 +172,8 @@ class _TemplateAttribute:
|
||||||
class TemplateEntity(Entity):
|
class TemplateEntity(Entity):
|
||||||
"""Entity that uses templates to calculate attributes."""
|
"""Entity that uses templates to calculate attributes."""
|
||||||
|
|
||||||
|
_attr_name: str | None
|
||||||
|
|
||||||
_attr_available = True
|
_attr_available = True
|
||||||
_attr_entity_picture = None
|
_attr_entity_picture = None
|
||||||
_attr_icon = None
|
_attr_icon = None
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
"""esphome session fixtures."""
|
"""esphome session fixtures."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from asyncio import Event
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from aioesphomeapi import APIClient, APIVersion, DeviceInfo
|
from aioesphomeapi import APIClient, APIVersion, DeviceInfo, ReconnectLogic
|
||||||
import pytest
|
import pytest
|
||||||
from zeroconf import Zeroconf
|
from zeroconf import Zeroconf
|
||||||
|
|
||||||
|
@ -158,10 +159,18 @@ async def mock_voice_assistant_v1_entry(
|
||||||
mock_client.device_info = AsyncMock(return_value=device_info)
|
mock_client.device_info = AsyncMock(return_value=device_info)
|
||||||
mock_client.subscribe_voice_assistant = AsyncMock(return_value=Mock())
|
mock_client.subscribe_voice_assistant = AsyncMock(return_value=Mock())
|
||||||
|
|
||||||
await hass.config_entries.async_setup(entry.entry_id)
|
try_connect_done = Event()
|
||||||
await hass.async_block_till_done()
|
real_try_connect = ReconnectLogic._try_connect
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_block_till_done()
|
async def mock_try_connect(self):
|
||||||
|
"""Set an event when ReconnectLogic._try_connect has been awaited."""
|
||||||
|
result = await real_try_connect(self)
|
||||||
|
try_connect_done.set()
|
||||||
|
return result
|
||||||
|
|
||||||
|
with patch.object(ReconnectLogic, "_try_connect", mock_try_connect):
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await try_connect_done.wait()
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue