Add SensorEntityDescription class (#53357)

This commit is contained in:
Franck Nijhof 2021-07-26 22:00:43 +02:00 committed by GitHub
parent 88cffc86bb
commit ee452d415d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 293 additions and 247 deletions

View File

@ -5,12 +5,11 @@ from datetime import timedelta
import logging import logging
from typing import Final from typing import Final
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntityDescription,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_NAME,
ATTR_UNIT_OF_MEASUREMENT,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -18,13 +17,10 @@ from homeassistant.const import (
DEVICE_CLASS_CO, DEVICE_CLASS_CO,
) )
from .models import AmbeeSensor
DOMAIN: Final = "ambee" DOMAIN: Final = "ambee"
LOGGER = logging.getLogger(__package__) LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(hours=1) SCAN_INTERVAL = timedelta(hours=1)
ATTR_ENABLED_BY_DEFAULT: Final = "enabled_by_default"
ATTR_ENTRY_TYPE: Final = "entry_type" ATTR_ENTRY_TYPE: Final = "entry_type"
ENTRY_TYPE_SERVICE: Final = "service" ENTRY_TYPE_SERVICE: Final = "service"
@ -38,175 +34,202 @@ SERVICES: dict[str, str] = {
SERVICE_POLLEN: "Pollen", SERVICE_POLLEN: "Pollen",
} }
SENSORS: dict[str, dict[str, AmbeeSensor]] = { SENSORS: dict[str, list[SensorEntityDescription]] = {
SERVICE_AIR_QUALITY: { SERVICE_AIR_QUALITY: [
"particulate_matter_2_5": { SensorEntityDescription(
ATTR_NAME: "Particulate Matter < 2.5 μm", key="particulate_matter_2_5",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, name="Particulate Matter < 2.5 μm",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
}, state_class=STATE_CLASS_MEASUREMENT,
"particulate_matter_10": { ),
ATTR_NAME: "Particulate Matter < 10 μm", SensorEntityDescription(
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, key="particulate_matter_10",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, name="Particulate Matter < 10 μm",
}, unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"sulphur_dioxide": { state_class=STATE_CLASS_MEASUREMENT,
ATTR_NAME: "Sulphur Dioxide (SO2)", ),
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, SensorEntityDescription(
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, key="sulphur_dioxide",
}, name="Sulphur Dioxide (SO2)",
"nitrogen_dioxide": { unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
ATTR_NAME: "Nitrogen Dioxide (NO2)", state_class=STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, ),
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, SensorEntityDescription(
}, key="nitrogen_dioxide",
"ozone": { name="Nitrogen Dioxide (NO2)",
ATTR_NAME: "Ozone", unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION, state_class=STATE_CLASS_MEASUREMENT,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ),
}, SensorEntityDescription(
"carbon_monoxide": { key="ozone",
ATTR_NAME: "Carbon Monoxide (CO)", name="Ozone",
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO, unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_MILLION, state_class=STATE_CLASS_MEASUREMENT,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ),
}, SensorEntityDescription(
"air_quality_index": { key="carbon_monoxide",
ATTR_NAME: "Air Quality Index (AQI)", name="Carbon Monoxide (CO)",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, device_class=DEVICE_CLASS_CO,
}, unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
}, state_class=STATE_CLASS_MEASUREMENT,
SERVICE_POLLEN: { ),
"grass": { SensorEntityDescription(
ATTR_NAME: "Grass Pollen", key="air_quality_index",
ATTR_ICON: "mdi:grass", name="Air Quality Index (AQI)",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, ),
}, ],
"tree": { SERVICE_POLLEN: [
ATTR_NAME: "Tree Pollen", SensorEntityDescription(
ATTR_ICON: "mdi:tree", key="grass",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, name="Grass Pollen",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, icon="mdi:grass",
}, state_class=STATE_CLASS_MEASUREMENT,
"weed": { unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_NAME: "Weed Pollen", ),
ATTR_ICON: "mdi:sprout", SensorEntityDescription(
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, key="tree",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, name="Tree Pollen",
}, icon="mdi:tree",
"grass_risk": { state_class=STATE_CLASS_MEASUREMENT,
ATTR_NAME: "Grass Pollen Risk", unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ICON: "mdi:grass", ),
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK, SensorEntityDescription(
}, key="weed",
"tree_risk": { name="Weed Pollen",
ATTR_NAME: "Tree Pollen Risk", icon="mdi:sprout",
ATTR_ICON: "mdi:tree", state_class=STATE_CLASS_MEASUREMENT,
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
}, ),
"weed_risk": { SensorEntityDescription(
ATTR_NAME: "Weed Pollen Risk", key="grass_risk",
ATTR_ICON: "mdi:sprout", name="Grass Pollen Risk",
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK, icon="mdi:grass",
}, device_class=DEVICE_CLASS_AMBEE_RISK,
"grass_poaceae": { ),
ATTR_NAME: "Poaceae Grass Pollen", SensorEntityDescription(
ATTR_ICON: "mdi:grass", key="tree_risk",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, name="Tree Pollen Risk",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, icon="mdi:tree",
ATTR_ENABLED_BY_DEFAULT: False, device_class=DEVICE_CLASS_AMBEE_RISK,
}, ),
"tree_alder": { SensorEntityDescription(
ATTR_NAME: "Alder Tree Pollen", key="weed_risk",
ATTR_ICON: "mdi:tree", name="Weed Pollen Risk",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, icon="mdi:sprout",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, device_class=DEVICE_CLASS_AMBEE_RISK,
ATTR_ENABLED_BY_DEFAULT: False, ),
}, SensorEntityDescription(
"tree_birch": { key="grass_poaceae",
ATTR_NAME: "Birch Tree Pollen", name="Poaceae Grass Pollen",
ATTR_ICON: "mdi:tree", icon="mdi:grass",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False, entity_registry_enabled_default=False,
}, ),
"tree_cypress": { SensorEntityDescription(
ATTR_NAME: "Cypress Tree Pollen", key="tree_alder",
ATTR_ICON: "mdi:tree", name="Alder Tree Pollen",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, icon="mdi:tree",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, state_class=STATE_CLASS_MEASUREMENT,
ATTR_ENABLED_BY_DEFAULT: False, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
}, entity_registry_enabled_default=False,
"tree_elm": { ),
ATTR_NAME: "Elm Tree Pollen", SensorEntityDescription(
ATTR_ICON: "mdi:tree", key="tree_birch",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, name="Birch Tree Pollen",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, icon="mdi:tree",
ATTR_ENABLED_BY_DEFAULT: False, state_class=STATE_CLASS_MEASUREMENT,
}, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
"tree_hazel": { entity_registry_enabled_default=False,
ATTR_NAME: "Hazel Tree Pollen", ),
ATTR_ICON: "mdi:tree", SensorEntityDescription(
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, key="tree_cypress",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, name="Cypress Tree Pollen",
ATTR_ENABLED_BY_DEFAULT: False, icon="mdi:tree",
}, state_class=STATE_CLASS_MEASUREMENT,
"tree_oak": { unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_NAME: "Oak Tree Pollen", entity_registry_enabled_default=False,
ATTR_ICON: "mdi:tree", ),
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, SensorEntityDescription(
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, key="tree_elm",
ATTR_ENABLED_BY_DEFAULT: False, name="Elm Tree Pollen",
}, icon="mdi:tree",
"tree_pine": { state_class=STATE_CLASS_MEASUREMENT,
ATTR_NAME: "Pine Tree Pollen", unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ICON: "mdi:tree", entity_registry_enabled_default=False,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ),
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, SensorEntityDescription(
ATTR_ENABLED_BY_DEFAULT: False, key="tree_hazel",
}, name="Hazel Tree Pollen",
"tree_plane": { icon="mdi:tree",
ATTR_NAME: "Plane Tree Pollen", state_class=STATE_CLASS_MEASUREMENT,
ATTR_ICON: "mdi:tree", unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, entity_registry_enabled_default=False,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, ),
ATTR_ENABLED_BY_DEFAULT: False, SensorEntityDescription(
}, key="tree_oak",
"tree_poplar": { name="Oak Tree Pollen",
ATTR_NAME: "Poplar Tree Pollen", icon="mdi:tree",
ATTR_ICON: "mdi:tree", state_class=STATE_CLASS_MEASUREMENT,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, entity_registry_enabled_default=False,
ATTR_ENABLED_BY_DEFAULT: False, ),
}, SensorEntityDescription(
"weed_chenopod": { key="tree_pine",
ATTR_NAME: "Chenopod Weed Pollen", name="Pine Tree Pollen",
ATTR_ICON: "mdi:sprout", icon="mdi:tree",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
ATTR_ENABLED_BY_DEFAULT: False, entity_registry_enabled_default=False,
}, ),
"weed_mugwort": { SensorEntityDescription(
ATTR_NAME: "Mugwort Weed Pollen", key="tree_plane",
ATTR_ICON: "mdi:sprout", name="Plane Tree Pollen",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, icon="mdi:tree",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, state_class=STATE_CLASS_MEASUREMENT,
ATTR_ENABLED_BY_DEFAULT: False, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
}, entity_registry_enabled_default=False,
"weed_nettle": { ),
ATTR_NAME: "Nettle Weed Pollen", SensorEntityDescription(
ATTR_ICON: "mdi:sprout", key="tree_poplar",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, name="Poplar Tree Pollen",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, icon="mdi:tree",
ATTR_ENABLED_BY_DEFAULT: False, state_class=STATE_CLASS_MEASUREMENT,
}, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
"weed_ragweed": { entity_registry_enabled_default=False,
ATTR_NAME: "Ragweed Weed Pollen", ),
ATTR_ICON: "mdi:sprout", SensorEntityDescription(
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, key="weed_chenopod",
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER, name="Chenopod Weed Pollen",
ATTR_ENABLED_BY_DEFAULT: False, icon="mdi:sprout",
}, state_class=STATE_CLASS_MEASUREMENT,
}, unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="weed_mugwort",
name="Mugwort Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="weed_nettle",
name="Nettle Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="weed_ragweed",
name="Ragweed Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
],
} }

View File

@ -1,15 +0,0 @@
"""Models helper class for the Ambee integration."""
from __future__ import annotations
from typing import TypedDict
class AmbeeSensor(TypedDict, total=False):
"""Represent an Ambee Sensor."""
device_class: str
enabled_by_default: bool
icon: str
name: str
state_class: str
unit_of_measurement: str

View File

@ -2,19 +2,12 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
DOMAIN as SENSOR_DOMAIN, DOMAIN as SENSOR_DOMAIN,
SensorEntity, SensorEntity,
SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import ATTR_IDENTIFIERS, ATTR_MANUFACTURER, ATTR_NAME
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_IDENTIFIERS,
ATTR_MANUFACTURER,
ATTR_NAME,
ATTR_UNIT_OF_MEASUREMENT,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
@ -23,15 +16,7 @@ from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator, DataUpdateCoordinator,
) )
from .const import ( from .const import ATTR_ENTRY_TYPE, DOMAIN, ENTRY_TYPE_SERVICE, SENSORS, SERVICES
ATTR_ENABLED_BY_DEFAULT,
ATTR_ENTRY_TYPE,
DOMAIN,
ENTRY_TYPE_SERVICE,
SENSORS,
SERVICES,
)
from .models import AmbeeSensor
async def async_setup_entry( async def async_setup_entry(
@ -44,13 +29,12 @@ async def async_setup_entry(
AmbeeSensorEntity( AmbeeSensorEntity(
coordinator=hass.data[DOMAIN][entry.entry_id][service_key], coordinator=hass.data[DOMAIN][entry.entry_id][service_key],
entry_id=entry.entry_id, entry_id=entry.entry_id,
sensor_key=sensor_key, description=description,
sensor=sensor,
service_key=service_key, service_key=service_key,
service=SERVICES[service_key], service=SERVICES[service_key],
) )
for service_key, service_sensors in SENSORS.items() for service_key, service_sensors in SENSORS.items()
for sensor_key, sensor in service_sensors.items() for description in service_sensors
) )
@ -62,26 +46,17 @@ class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
*, *,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
entry_id: str, entry_id: str,
sensor_key: str, description: SensorEntityDescription,
sensor: AmbeeSensor,
service_key: str, service_key: str,
service: str, service: str,
) -> None: ) -> None:
"""Initialize Ambee sensor.""" """Initialize Ambee sensor."""
super().__init__(coordinator=coordinator) super().__init__(coordinator=coordinator)
self._sensor_key = sensor_key
self._service_key = service_key self._service_key = service_key
self.entity_id = f"{SENSOR_DOMAIN}.{service_key}_{sensor_key}" self.entity_id = f"{SENSOR_DOMAIN}.{service_key}_{description.key}"
self._attr_device_class = sensor.get(ATTR_DEVICE_CLASS) self.entity_description = description
self._attr_entity_registry_enabled_default = sensor.get( self._attr_unique_id = f"{entry_id}_{service_key}_{description.key}"
ATTR_ENABLED_BY_DEFAULT, True
)
self._attr_icon = sensor.get(ATTR_ICON)
self._attr_name = sensor.get(ATTR_NAME)
self._attr_state_class = sensor.get(ATTR_STATE_CLASS)
self._attr_unique_id = f"{entry_id}_{service_key}_{sensor_key}"
self._attr_unit_of_measurement = sensor.get(ATTR_UNIT_OF_MEASUREMENT)
self._attr_device_info = { self._attr_device_info = {
ATTR_IDENTIFIERS: {(DOMAIN, f"{entry_id}_{service_key}")}, ATTR_IDENTIFIERS: {(DOMAIN, f"{entry_id}_{service_key}")},
@ -93,7 +68,7 @@ class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
@property @property
def state(self) -> StateType: def state(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
value = getattr(self.coordinator.data, self._sensor_key) value = getattr(self.coordinator.data, self.entity_description.key)
if isinstance(value, str): if isinstance(value, str):
return value.lower() return value.lower()
return value # type: ignore[no-any-return] return value # type: ignore[no-any-return]

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from typing import Any, Final, cast, final from typing import Any, Final, cast, final
@ -31,7 +32,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_BASE,
) )
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -95,21 +96,38 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return await component.async_unload_entry(entry) return await component.async_unload_entry(entry)
@dataclass
class SensorEntityDescription(EntityDescription):
"""An class that describes sensor entities."""
state_class: str | None = None
last_reset: datetime | None = None
class SensorEntity(Entity): class SensorEntity(Entity):
"""Base class for sensor entities.""" """Base class for sensor entities."""
_attr_state_class: str | None = None entity_description: SensorEntityDescription
_attr_last_reset: datetime | None = None _attr_state_class: str | None
_attr_last_reset: datetime | None
@property @property
def state_class(self) -> str | None: def state_class(self) -> str | None:
"""Return the state class of this entity, from STATE_CLASSES, if any.""" """Return the state class of this entity, from STATE_CLASSES, if any."""
return self._attr_state_class if hasattr(self, "_attr_state_class"):
return self._attr_state_class
if hasattr(self, "entity_description"):
return self.entity_description.state_class
return None
@property @property
def last_reset(self) -> datetime | None: def last_reset(self) -> datetime | None:
"""Return the time when the sensor was last reset, if any.""" """Return the time when the sensor was last reset, if any."""
return self._attr_last_reset if hasattr(self, "_attr_last_reset"):
return self._attr_last_reset
if hasattr(self, "entity_description"):
return self.entity_description.last_reset
return None
@property @property
def capability_attributes(self) -> Mapping[str, Any] | None: def capability_attributes(self) -> Mapping[str, Any] | None:

View File

@ -112,6 +112,9 @@ class _TemplateAttribute:
class TemplateEntity(Entity): class TemplateEntity(Entity):
"""Entity that uses templates to calculate attributes.""" """Entity that uses templates to calculate attributes."""
_attr_available = True
_attr_entity_picture = None
_attr_icon = None
_attr_should_poll = False _attr_should_poll = False
def __init__( def __init__(
@ -128,7 +131,6 @@ class TemplateEntity(Entity):
self._attribute_templates = attribute_templates self._attribute_templates = attribute_templates
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
self._availability_template = availability_template self._availability_template = availability_template
self._attr_available = True
self._icon_template = icon_template self._icon_template = icon_template
self._entity_picture_template = entity_picture_template self._entity_picture_template = entity_picture_template
self._self_ref_update_count = 0 self._self_ref_update_count = 0

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from abc import ABC from abc import ABC
import asyncio import asyncio
from collections.abc import Awaitable, Iterable, Mapping, MutableMapping from collections.abc import Awaitable, Iterable, Mapping, MutableMapping
from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
import functools as ft import functools as ft
import logging import logging
@ -178,6 +179,21 @@ class DeviceInfo(TypedDict, total=False):
default_model: str default_model: str
@dataclass
class EntityDescription:
"""An class that describes Home Assistant entities."""
# This is the key identifier for this entity
key: str
device_class: str | None = None
entity_registry_enabled_default: bool = True
force_update: bool = False
icon: str | None = None
name: str | None = None
unit_of_measurement: str | None = None
class Entity(ABC): class Entity(ABC):
"""An abstract class for Home Assistant entities.""" """An abstract class for Home Assistant entities."""
@ -194,6 +210,9 @@ class Entity(ABC):
# Owning platform instance. Will be set by EntityPlatform # Owning platform instance. Will be set by EntityPlatform
platform: EntityPlatform | None = None platform: EntityPlatform | None = None
# Entity description instance for this Entity
entity_description: EntityDescription
# If we reported if this entity was slow # If we reported if this entity was slow
_slow_reported = False _slow_reported = False
@ -223,19 +242,19 @@ class Entity(ABC):
_attr_assumed_state: bool = False _attr_assumed_state: bool = False
_attr_available: bool = True _attr_available: bool = True
_attr_context_recent_time: timedelta = timedelta(seconds=5) _attr_context_recent_time: timedelta = timedelta(seconds=5)
_attr_device_class: str | None = None _attr_device_class: str | None
_attr_device_info: DeviceInfo | None = None _attr_device_info: DeviceInfo | None = None
_attr_entity_picture: str | None = None _attr_entity_picture: str | None = None
_attr_entity_registry_enabled_default: bool = True _attr_entity_registry_enabled_default: bool
_attr_extra_state_attributes: MutableMapping[str, Any] | None = None _attr_extra_state_attributes: MutableMapping[str, Any] | None = None
_attr_force_update: bool = False _attr_force_update: bool
_attr_icon: str | None = None _attr_icon: str | None
_attr_name: str | None = None _attr_name: str | 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
_attr_unique_id: str | None = None _attr_unique_id: str | None = None
_attr_unit_of_measurement: str | None = None _attr_unit_of_measurement: str | None
@property @property
def should_poll(self) -> bool: def should_poll(self) -> bool:
@ -253,7 +272,11 @@ class Entity(ABC):
@property @property
def name(self) -> str | None: def name(self) -> str | None:
"""Return the name of the entity.""" """Return the name of the entity."""
return self._attr_name if hasattr(self, "_attr_name"):
return self._attr_name
if hasattr(self, "entity_description"):
return self.entity_description.name
return None
@property @property
def state(self) -> StateType: def state(self) -> StateType:
@ -309,17 +332,29 @@ class Entity(ABC):
@property @property
def device_class(self) -> str | None: def device_class(self) -> str | None:
"""Return the class of this device, from component DEVICE_CLASSES.""" """Return the class of this device, from component DEVICE_CLASSES."""
return self._attr_device_class if hasattr(self, "_attr_device_class"):
return self._attr_device_class
if hasattr(self, "entity_description"):
return self.entity_description.device_class
return None
@property @property
def unit_of_measurement(self) -> str | None: def unit_of_measurement(self) -> str | None:
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement of this entity, if any."""
return self._attr_unit_of_measurement if hasattr(self, "_attr_unit_of_measurement"):
return self._attr_unit_of_measurement
if hasattr(self, "entity_description"):
return self.entity_description.unit_of_measurement
return None
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
"""Return the icon to use in the frontend, if any.""" """Return the icon to use in the frontend, if any."""
return self._attr_icon if hasattr(self, "_attr_icon"):
return self._attr_icon
if hasattr(self, "entity_description"):
return self.entity_description.icon
return None
@property @property
def entity_picture(self) -> str | None: def entity_picture(self) -> str | None:
@ -343,7 +378,11 @@ class Entity(ABC):
If True, a state change will be triggered anytime the state property is If True, a state change will be triggered anytime the state property is
updated, not just when the value changes. updated, not just when the value changes.
""" """
return self._attr_force_update if hasattr(self, "_attr_force_update"):
return self._attr_force_update
if hasattr(self, "entity_description"):
return self.entity_description.force_update
return False
@property @property
def supported_features(self) -> int | None: def supported_features(self) -> int | None:
@ -358,7 +397,11 @@ class Entity(ABC):
@property @property
def entity_registry_enabled_default(self) -> bool: def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry.""" """Return if the entity should be enabled when first added to the entity registry."""
return self._attr_entity_registry_enabled_default if hasattr(self, "_attr_entity_registry_enabled_default"):
return self._attr_entity_registry_enabled_default
if hasattr(self, "entity_description"):
return self.entity_description.entity_registry_enabled_default
return True
# DO NOT OVERWRITE # DO NOT OVERWRITE
# These properties and methods are either managed by Home Assistant or they # These properties and methods are either managed by Home Assistant or they