mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Name unnamed binary sensors by their device class (#92940)
* Name unnamed binary sensors by their device class * Update type annotations * Fix loading of entity component translations * Add test * Update integrations * Set abode and rfxtrx binary_sensor name to None * Revert changes in homekit_controller
This commit is contained in:
parent
223394eaee
commit
2406b235b4
@ -42,6 +42,7 @@ async def async_setup_entry(
|
||||
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
|
||||
"""A binary sensor implementation for Abode device."""
|
||||
|
||||
_attr_name = None
|
||||
_device: ABBinarySensor
|
||||
|
||||
@property
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""Support for Aranet sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from aranet4.client import Aranet4Advertisement
|
||||
from bleak.backends.device import BLEDevice
|
||||
|
||||
@ -33,43 +35,54 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
@dataclass
|
||||
class AranetSensorEntityDescription(SensorEntityDescription):
|
||||
"""Class to describe an Aranet sensor entity."""
|
||||
|
||||
# PassiveBluetoothDataUpdate does not support UNDEFINED
|
||||
# Restrict the type to satisfy the type checker and catch attempts
|
||||
# to use UNDEFINED in the entity descriptions.
|
||||
name: str | None = None
|
||||
|
||||
|
||||
SENSOR_DESCRIPTIONS = {
|
||||
"temperature": SensorEntityDescription(
|
||||
"temperature": AranetSensorEntityDescription(
|
||||
key="temperature",
|
||||
name="Temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"humidity": SensorEntityDescription(
|
||||
"humidity": AranetSensorEntityDescription(
|
||||
key="humidity",
|
||||
name="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"pressure": SensorEntityDescription(
|
||||
"pressure": AranetSensorEntityDescription(
|
||||
key="pressure",
|
||||
name="Pressure",
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"co2": SensorEntityDescription(
|
||||
"co2": AranetSensorEntityDescription(
|
||||
key="co2",
|
||||
name="Carbon Dioxide",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"battery": SensorEntityDescription(
|
||||
"battery": AranetSensorEntityDescription(
|
||||
key="battery",
|
||||
name="Battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"interval": SensorEntityDescription(
|
||||
"interval": AranetSensorEntityDescription(
|
||||
key="update_interval",
|
||||
name="Update Interval",
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
|
@ -5,7 +5,6 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from yalexs.activity import (
|
||||
ACTION_DOORBELL_CALL_MISSED,
|
||||
@ -104,7 +103,16 @@ def _native_datetime() -> datetime:
|
||||
|
||||
|
||||
@dataclass
|
||||
class AugustRequiredKeysMixin:
|
||||
class AugustBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Describes August binary_sensor entity."""
|
||||
|
||||
# AugustBinarySensor does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class AugustDoorbellRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[AugustData, DoorbellDetail], bool]
|
||||
@ -112,41 +120,45 @@ class AugustRequiredKeysMixin:
|
||||
|
||||
|
||||
@dataclass
|
||||
class AugustBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, AugustRequiredKeysMixin
|
||||
class AugustDoorbellBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, AugustDoorbellRequiredKeysMixin
|
||||
):
|
||||
"""Describes August binary_sensor entity."""
|
||||
|
||||
# AugustDoorbellBinarySensor does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
SENSOR_TYPE_DOOR = BinarySensorEntityDescription(
|
||||
|
||||
SENSOR_TYPE_DOOR = AugustBinarySensorEntityDescription(
|
||||
key="door_open",
|
||||
name="Open",
|
||||
)
|
||||
|
||||
|
||||
SENSOR_TYPES_DOORBELL: tuple[AugustBinarySensorEntityDescription, ...] = (
|
||||
AugustBinarySensorEntityDescription(
|
||||
SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] = (
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_ding",
|
||||
name="Ding",
|
||||
device_class=BinarySensorDeviceClass.OCCUPANCY,
|
||||
value_fn=_retrieve_ding_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_motion",
|
||||
name="Motion",
|
||||
device_class=BinarySensorDeviceClass.MOTION,
|
||||
value_fn=_retrieve_motion_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_image_capture",
|
||||
name="Image Capture",
|
||||
icon="mdi:file-image",
|
||||
value_fn=_retrieve_image_capture_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
AugustDoorbellBinarySensorEntityDescription(
|
||||
key="doorbell_online",
|
||||
name="Online",
|
||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||
@ -199,7 +211,10 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
_attr_device_class = BinarySensorDeviceClass.DOOR
|
||||
|
||||
def __init__(
|
||||
self, data: AugustData, device: Lock, description: BinarySensorEntityDescription
|
||||
self,
|
||||
data: AugustData,
|
||||
device: Lock,
|
||||
description: AugustBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(data, device)
|
||||
@ -207,9 +222,7 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
self._data = data
|
||||
self._device = device
|
||||
self._attr_name = f"{device.device_name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{self._device_id}_{cast(str, description.name).lower()}"
|
||||
)
|
||||
self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
|
||||
|
||||
@callback
|
||||
def _update_from_data(self):
|
||||
@ -243,13 +256,13 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
"""Representation of an August binary sensor."""
|
||||
|
||||
entity_description: AugustBinarySensorEntityDescription
|
||||
entity_description: AugustDoorbellBinarySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: AugustData,
|
||||
device: Doorbell,
|
||||
description: AugustBinarySensorEntityDescription,
|
||||
description: AugustDoorbellBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(data, device)
|
||||
@ -257,9 +270,7 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
||||
self._check_for_off_update_listener = None
|
||||
self._data = data
|
||||
self._attr_name = f"{device.device_name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{self._device_id}_{cast(str, description.name).lower()}"
|
||||
)
|
||||
self._attr_unique_id = f"{self._device_id}_{description.name.lower()}"
|
||||
|
||||
@callback
|
||||
def _update_from_data(self):
|
||||
|
@ -47,6 +47,10 @@ class BalboaBinarySensorEntityDescription(
|
||||
):
|
||||
"""A class that describes Balboa binary sensor entities."""
|
||||
|
||||
# BalboaBinarySensorEntity does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
|
||||
FILTER_CYCLE_ICONS = ("mdi:sync", "mdi:sync-off")
|
||||
BINARY_SENSOR_DESCRIPTIONS = (
|
||||
|
@ -190,6 +190,13 @@ class BinarySensorEntity(Entity):
|
||||
_attr_is_on: bool | None = None
|
||||
_attr_state: None = None
|
||||
|
||||
def _default_to_device_class_name(self) -> bool:
|
||||
"""Return True if an unnamed entity should be named by its device class.
|
||||
|
||||
For binary sensors this is True if the entity has a device class.
|
||||
"""
|
||||
return self.device_class is not None
|
||||
|
||||
@property
|
||||
def device_class(self) -> BinarySensorDeviceClass | None:
|
||||
"""Return the class of this entity."""
|
||||
|
@ -35,6 +35,10 @@ class BondButtonEntityDescription(
|
||||
):
|
||||
"""Class to describe a Bond Button entity."""
|
||||
|
||||
# BondEntity does not support UNDEFINED,
|
||||
# restrict the type to str | None
|
||||
name: str | None = None
|
||||
|
||||
|
||||
STOP_BUTTON = BondButtonEntityDescription(
|
||||
key=Action.STOP,
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.typing import UNDEFINED, StateType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
@ -80,7 +80,7 @@ class EmonitorPowerSensor(CoordinatorEntity, SensorEntity):
|
||||
mac_address = self.emonitor_status.network.mac_address
|
||||
device_name = name_short_mac(mac_address[-6:])
|
||||
label = self.channel_data.label or f"{device_name} {channel_number}"
|
||||
if description.name:
|
||||
if description.name is not UNDEFINED:
|
||||
self._attr_name = f"{label} {description.name}"
|
||||
self._attr_unique_id = f"{mac_address}_{channel_number}_{description.key}"
|
||||
else:
|
||||
|
@ -18,6 +18,7 @@ from homeassistant.const import UnitOfPower
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import UNDEFINED
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
@ -168,7 +169,7 @@ class EnvoyInverter(CoordinatorEntity, SensorEntity):
|
||||
"""Initialize Envoy inverter entity."""
|
||||
self.entity_description = description
|
||||
self._serial_number = serial_number
|
||||
if description.name:
|
||||
if description.name is not UNDEFINED:
|
||||
self._attr_name = (
|
||||
f"{envoy_name} Inverter {serial_number} {description.name}"
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
from greeclimate.device import Device
|
||||
|
||||
@ -33,6 +33,10 @@ class GreeRequiredKeysMixin:
|
||||
class GreeSwitchEntityDescription(SwitchEntityDescription, GreeRequiredKeysMixin):
|
||||
"""Describes Gree switch entity."""
|
||||
|
||||
# GreeSwitch does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
|
||||
def _set_light(device: Device, value: bool) -> None:
|
||||
"""Typed helper to set device light property."""
|
||||
@ -130,7 +134,7 @@ class GreeSwitch(GreeEntity, SwitchEntity):
|
||||
"""Initialize the Gree device."""
|
||||
self.entity_description = description
|
||||
|
||||
super().__init__(coordinator, cast(str, description.name))
|
||||
super().__init__(coordinator, description.name)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
|
@ -117,6 +117,10 @@ class HuaweiSensorGroup:
|
||||
class HuaweiSensorEntityDescription(SensorEntityDescription):
|
||||
"""Class describing Huawei LTE sensor entities."""
|
||||
|
||||
# HuaweiLteSensor does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
format_fn: Callable[[str], tuple[StateType, str | None]] = format_default
|
||||
icon_fn: Callable[[StateType], str] | None = None
|
||||
device_class_fn: Callable[[StateType], SensorDeviceClass | None] | None = None
|
||||
|
@ -28,6 +28,9 @@ class IncomfortSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes Incomfort sensor entity."""
|
||||
|
||||
extra_key: str | None = None
|
||||
# IncomfortSensor does not support UNDEFINED or None,
|
||||
# restrict the type to str
|
||||
name: str = ""
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[IncomfortSensorEntityDescription, ...] = (
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Support for ISY switches."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pyisy.constants import (
|
||||
@ -22,7 +23,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -30,6 +31,15 @@ from .entity import ISYAuxControlEntity, ISYNodeEntity, ISYProgramEntity
|
||||
from .models import IsyData
|
||||
|
||||
|
||||
@dataclass
|
||||
class ISYSwitchEntityDescription(SwitchEntityDescription):
|
||||
"""Describes IST switch."""
|
||||
|
||||
# ISYEnableSwitchEntity does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
@ -53,7 +63,7 @@ async def async_setup_entry(
|
||||
for node, control in isy_data.aux_properties[Platform.SWITCH]:
|
||||
# Currently only used for enable switches, will need to be updated for
|
||||
# NS support by making sure control == TAG_ENABLED
|
||||
description = SwitchEntityDescription(
|
||||
description = ISYSwitchEntityDescription(
|
||||
key=control,
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
name=control.title(),
|
||||
@ -135,7 +145,7 @@ class ISYEnableSwitchEntity(ISYAuxControlEntity, SwitchEntity):
|
||||
node: Node,
|
||||
control: str,
|
||||
unique_id: str,
|
||||
description: EntityDescription,
|
||||
description: ISYSwitchEntityDescription,
|
||||
device_info: DeviceInfo | None,
|
||||
) -> None:
|
||||
"""Initialize the ISY Aux Control Number entity."""
|
||||
|
@ -9,7 +9,7 @@ from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
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 ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.typing import UNDEFINED, ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import REPETIER_API, SENSOR_TYPES, UPDATE_SIGNAL, RepetierSensorEntityDescription
|
||||
|
||||
@ -45,7 +45,8 @@ def setup_platform(
|
||||
sensor_type = info["sensor_type"]
|
||||
temp_id = info["temp_id"]
|
||||
description = SENSOR_TYPES[sensor_type]
|
||||
name = f"{info['name']}{description.name or ''}"
|
||||
name_suffix = "" if description.name is UNDEFINED else description.name
|
||||
name = f"{info['name']}{name_suffix}"
|
||||
if temp_id is not None:
|
||||
_LOGGER.debug("%s Temp_id: %s", sensor_type, temp_id)
|
||||
name = f"{name}{temp_id}"
|
||||
|
@ -130,6 +130,7 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
|
||||
"""
|
||||
|
||||
_attr_force_update = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -275,6 +275,10 @@ def async_setup_entry_rest(
|
||||
class BlockEntityDescription(EntityDescription):
|
||||
"""Class to describe a BLOCK entity."""
|
||||
|
||||
# BlockEntity does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
icon_fn: Callable[[dict], str] | None = None
|
||||
unit_fn: Callable[[dict], str] | None = None
|
||||
value: Callable[[Any], Any] = lambda val: val
|
||||
@ -295,6 +299,10 @@ class RpcEntityRequiredKeysMixin:
|
||||
class RpcEntityDescription(EntityDescription, RpcEntityRequiredKeysMixin):
|
||||
"""Class to describe a RPC entity."""
|
||||
|
||||
# BlockEntity does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
value: Callable[[Any, Any], Any] | None = None
|
||||
available: Callable[[dict], bool] | None = None
|
||||
removal_condition: Callable[[dict, dict, str], bool] | None = None
|
||||
@ -307,6 +315,10 @@ class RpcEntityDescription(EntityDescription, RpcEntityRequiredKeysMixin):
|
||||
class RestEntityDescription(EntityDescription):
|
||||
"""Class to describe a REST entity."""
|
||||
|
||||
# BlockEntity does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
value: Callable[[dict, Any], Any] | None = None
|
||||
extra_state_attributes: Callable[[dict], dict | None] | None = None
|
||||
|
||||
|
@ -94,7 +94,6 @@ class SwitchBotBinarySensor(SwitchbotEntity, BinarySensorEntity):
|
||||
self._sensor = binary_sensor
|
||||
self._attr_unique_id = f"{coordinator.base_unique_id}-{binary_sensor}"
|
||||
self.entity_description = BINARY_SENSOR_TYPES[binary_sensor]
|
||||
self._attr_name = self.entity_description.name
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
|
@ -23,6 +23,10 @@ from .coordinator import SystemBridgeDataUpdateCoordinator
|
||||
class SystemBridgeBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Class describing System Bridge binary sensor entities."""
|
||||
|
||||
# SystemBridgeBinarySensor does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
value: Callable = round
|
||||
|
||||
|
||||
|
@ -46,6 +46,10 @@ PIXELS: Final = "px"
|
||||
class SystemBridgeSensorEntityDescription(SensorEntityDescription):
|
||||
"""Class describing System Bridge sensor entities."""
|
||||
|
||||
# SystemBridgeSensor does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
value: Callable = round
|
||||
|
||||
|
||||
|
@ -72,6 +72,10 @@ from .const import (
|
||||
class TomorrowioSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes a Tomorrow.io sensor entity."""
|
||||
|
||||
# TomorrowioSensor does not support UNDEFINED or None,
|
||||
# restrict the type to str.
|
||||
name: str = ""
|
||||
|
||||
unit_imperial: str | None = None
|
||||
unit_metric: str | None = None
|
||||
multiplication_factor: Callable[[float], float] | float | None = None
|
||||
|
@ -24,6 +24,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import UNDEFINED
|
||||
|
||||
from .base_class import TradfriBaseEntity
|
||||
from .const import (
|
||||
@ -202,7 +203,7 @@ class TradfriSensor(TradfriBaseEntity, SensorEntity):
|
||||
|
||||
self._attr_unique_id = f"{self._attr_unique_id}-{description.key}"
|
||||
|
||||
if description.name:
|
||||
if description.name is not UNDEFINED:
|
||||
self._attr_name = f"{self._attr_name}: {description.name}"
|
||||
|
||||
self._refresh() # Set initial state
|
||||
|
@ -23,6 +23,7 @@ from pyunifiprotect.data import (
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
||||
from homeassistant.helpers.typing import UNDEFINED
|
||||
|
||||
from .const import (
|
||||
ATTR_EVENT_ID,
|
||||
@ -201,7 +202,11 @@ class ProtectDeviceEntity(Entity):
|
||||
else:
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self.device.mac}_{description.key}"
|
||||
name = description.name or ""
|
||||
name = (
|
||||
description.name
|
||||
if description.name and description.name is not UNDEFINED
|
||||
else ""
|
||||
)
|
||||
self._attr_name = f"{self.device.display_name} {name.title()}"
|
||||
if isinstance(description, ProtectRequiredKeysMixin):
|
||||
self._async_get_ufp_enabled = description.get_ufp_enabled
|
||||
|
@ -28,6 +28,9 @@ from .wemo_device import DeviceCoordinator
|
||||
class AttributeSensorDescription(SensorEntityDescription):
|
||||
"""SensorEntityDescription for WeMo AttributeSensor entities."""
|
||||
|
||||
# AttributeSensor does not support UNDEFINED,
|
||||
# restrict the type to str | None.
|
||||
name: str | None = None
|
||||
state_conversion: Callable[[StateType], StateType] | None = None
|
||||
unique_id_suffix: str | None = None
|
||||
|
||||
|
@ -14,6 +14,7 @@ from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
from homeassistant.helpers.typing import UNDEFINED
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .discovery import ZwaveDiscoveryInfo
|
||||
@ -161,6 +162,7 @@ class ZWaveBaseEntity(Entity):
|
||||
hasattr(self, "entity_description")
|
||||
and self.entity_description
|
||||
and self.entity_description.name
|
||||
and self.entity_description.name is not UNDEFINED
|
||||
):
|
||||
name = self.entity_description.name
|
||||
|
||||
|
@ -48,7 +48,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.typing import UNDEFINED, StateType
|
||||
|
||||
from .const import (
|
||||
ATTR_METER_TYPE,
|
||||
@ -610,7 +610,7 @@ class ZwaveSensor(ZWaveBaseEntity, SensorEntity):
|
||||
|
||||
# Entity class attributes
|
||||
self._attr_force_update = True
|
||||
if not entity_description.name:
|
||||
if not entity_description.name or entity_description.name is UNDEFINED:
|
||||
self._attr_name = self.generate_name(include_value_name=True)
|
||||
|
||||
@property
|
||||
|
@ -44,7 +44,7 @@ from .event import (
|
||||
async_track_device_registry_updated_event,
|
||||
async_track_entity_registry_updated_event,
|
||||
)
|
||||
from .typing import StateType
|
||||
from .typing import UNDEFINED, StateType, UndefinedType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .entity_platform import EntityPlatform
|
||||
@ -222,7 +222,7 @@ class EntityDescription:
|
||||
force_update: bool = False
|
||||
icon: str | None = None
|
||||
has_entity_name: bool = False
|
||||
name: str | None = None
|
||||
name: str | UndefinedType | None = UNDEFINED
|
||||
translation_key: str | None = None
|
||||
unit_of_measurement: str | None = None
|
||||
|
||||
@ -328,6 +328,22 @@ class Entity(ABC):
|
||||
return self.entity_description.has_entity_name
|
||||
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)
|
||||
|
||||
def _default_to_device_class_name(self) -> bool:
|
||||
"""Return True if an unnamed entity should be named by its device class."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
"""Return the name of the entity."""
|
||||
@ -338,11 +354,21 @@ class Entity(ABC):
|
||||
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
||||
f".{self.translation_key}.name"
|
||||
)
|
||||
if name_translation_key in self.platform.entity_translations:
|
||||
name: str = self.platform.entity_translations[name_translation_key]
|
||||
if name_translation_key in self.platform.platform_translations:
|
||||
name: str = self.platform.platform_translations[name_translation_key]
|
||||
return name
|
||||
if hasattr(self, "entity_description"):
|
||||
return self.entity_description.name
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
@property
|
||||
|
@ -126,7 +126,8 @@ class EntityPlatform:
|
||||
self.entity_namespace = entity_namespace
|
||||
self.config_entry: config_entries.ConfigEntry | None = None
|
||||
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]] = []
|
||||
# Stop tracking tasks after setup is completed
|
||||
self._setup_complete = False
|
||||
@ -295,7 +296,15 @@ class EntityPlatform:
|
||||
full_name = f"{self.domain}.{self.platform_name}"
|
||||
|
||||
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}
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
|
@ -1,8 +1,25 @@
|
||||
"""The tests for the Binary sensor component."""
|
||||
from collections.abc import Generator
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import binary_sensor
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockModule,
|
||||
MockPlatform,
|
||||
mock_config_flow,
|
||||
mock_integration,
|
||||
mock_platform,
|
||||
)
|
||||
|
||||
TEST_DOMAIN = "test"
|
||||
|
||||
|
||||
def test_state() -> None:
|
||||
@ -19,3 +36,93 @@ def test_state() -> None:
|
||||
new=True,
|
||||
):
|
||||
assert binary_sensor.BinarySensorEntity().state == STATE_ON
|
||||
|
||||
|
||||
class STTFlow(ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
|
||||
"""Mock config flow."""
|
||||
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
|
||||
|
||||
with mock_config_flow(TEST_DOMAIN, STTFlow):
|
||||
yield
|
||||
|
||||
|
||||
async def test_name(hass: HomeAssistant) -> None:
|
||||
"""Test binary sensor name."""
|
||||
|
||||
async def async_setup_entry_init(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> bool:
|
||||
"""Set up test config entry."""
|
||||
await hass.config_entries.async_forward_entry_setup(
|
||||
config_entry, binary_sensor.DOMAIN
|
||||
)
|
||||
return True
|
||||
|
||||
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
TEST_DOMAIN,
|
||||
async_setup_entry=async_setup_entry_init,
|
||||
),
|
||||
)
|
||||
|
||||
# Unnamed binary sensor without device class -> no name
|
||||
entity1 = binary_sensor.BinarySensorEntity()
|
||||
entity1.entity_id = "binary_sensor.test1"
|
||||
|
||||
# Unnamed binary sensor with device class but has_entity_name False -> no name
|
||||
entity2 = binary_sensor.BinarySensorEntity()
|
||||
entity2.entity_id = "binary_sensor.test2"
|
||||
entity2._attr_device_class = binary_sensor.BinarySensorDeviceClass.BATTERY
|
||||
|
||||
# Unnamed binary sensor with device class and has_entity_name True -> named
|
||||
entity3 = binary_sensor.BinarySensorEntity()
|
||||
entity3.entity_id = "binary_sensor.test3"
|
||||
entity3._attr_device_class = binary_sensor.BinarySensorDeviceClass.BATTERY
|
||||
entity3._attr_has_entity_name = True
|
||||
|
||||
# Unnamed binary sensor with device class and has_entity_name True -> named
|
||||
entity4 = binary_sensor.BinarySensorEntity()
|
||||
entity4.entity_id = "binary_sensor.test4"
|
||||
entity4.entity_description = binary_sensor.BinarySensorEntityDescription(
|
||||
"test",
|
||||
binary_sensor.BinarySensorDeviceClass.BATTERY,
|
||||
has_entity_name=True,
|
||||
)
|
||||
|
||||
async def async_setup_entry_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up test stt platform via config entry."""
|
||||
async_add_entities([entity1, entity2, entity3, entity4])
|
||||
|
||||
mock_platform(
|
||||
hass,
|
||||
f"{TEST_DOMAIN}.{binary_sensor.DOMAIN}",
|
||||
MockPlatform(async_setup_entry=async_setup_entry_platform),
|
||||
)
|
||||
|
||||
config_entry = MockConfigEntry(domain=TEST_DOMAIN)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes == {}
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state.attributes == {"device_class": "battery"}
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes == {"device_class": "battery", "friendly_name": "Battery"}
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state.attributes == {"device_class": "battery", "friendly_name": "Battery"}
|
||||
|
Loading…
x
Reference in New Issue
Block a user