Use SensorEntityDescription in Airly integration (#53540)

This commit is contained in:
Maciej Bieniek 2021-07-27 12:08:31 +02:00 committed by GitHub
parent 4506b41022
commit c0e84a7b32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 88 additions and 108 deletions

View File

@ -3,10 +3,8 @@ from __future__ import annotations
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
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
@ -16,7 +14,7 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from .model import SensorDescription from .model import AirlySensorEntityDescription
ATTR_API_ADVICE: Final = "ADVICE" ATTR_API_ADVICE: Final = "ADVICE"
ATTR_API_CAQI: Final = "CAQI" ATTR_API_CAQI: Final = "CAQI"
@ -31,12 +29,9 @@ ATTR_API_TEMPERATURE: Final = "TEMPERATURE"
ATTR_ADVICE: Final = "advice" ATTR_ADVICE: Final = "advice"
ATTR_DESCRIPTION: Final = "description" ATTR_DESCRIPTION: Final = "description"
ATTR_LABEL: Final = "label"
ATTR_LEVEL: Final = "level" ATTR_LEVEL: Final = "level"
ATTR_LIMIT: Final = "limit" ATTR_LIMIT: Final = "limit"
ATTR_PERCENT: Final = "percent" ATTR_PERCENT: Final = "percent"
ATTR_UNIT: Final = "unit"
ATTR_VALUE: Final = "value"
SUFFIX_PERCENT: Final = "PERCENT" SUFFIX_PERCENT: Final = "PERCENT"
SUFFIX_LIMIT: Final = "LIMIT" SUFFIX_LIMIT: Final = "LIMIT"
@ -51,52 +46,54 @@ MAX_UPDATE_INTERVAL: Final = 90
MIN_UPDATE_INTERVAL: Final = 5 MIN_UPDATE_INTERVAL: Final = 5
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet." NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
SENSOR_TYPES: dict[str, SensorDescription] = { SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
ATTR_API_CAQI: { AirlySensorEntityDescription(
ATTR_LABEL: ATTR_API_CAQI, key=ATTR_API_CAQI,
ATTR_UNIT: "CAQI", name=ATTR_API_CAQI,
ATTR_VALUE: round, unit_of_measurement="CAQI",
}, ),
ATTR_API_PM1: { AirlySensorEntityDescription(
ATTR_ICON: "mdi:blur", key=ATTR_API_PM1,
ATTR_LABEL: ATTR_API_PM1, icon="mdi:blur",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, name=ATTR_API_PM1,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_VALUE: round, state_class=STATE_CLASS_MEASUREMENT,
}, ),
ATTR_API_PM25: { AirlySensorEntityDescription(
ATTR_ICON: "mdi:blur", key=ATTR_API_PM25,
ATTR_LABEL: "PM2.5", icon="mdi:blur",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, name="PM2.5",
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_VALUE: round, state_class=STATE_CLASS_MEASUREMENT,
}, ),
ATTR_API_PM10: { AirlySensorEntityDescription(
ATTR_ICON: "mdi:blur", key=ATTR_API_PM10,
ATTR_LABEL: ATTR_API_PM10, icon="mdi:blur",
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, name=ATTR_API_PM10,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_VALUE: round, state_class=STATE_CLASS_MEASUREMENT,
}, ),
ATTR_API_HUMIDITY: { AirlySensorEntityDescription(
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY, key=ATTR_API_HUMIDITY,
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(), device_class=DEVICE_CLASS_HUMIDITY,
ATTR_UNIT: PERCENTAGE, name=ATTR_API_HUMIDITY.capitalize(),
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, unit_of_measurement=PERCENTAGE,
ATTR_VALUE: lambda value: round(value, 1), state_class=STATE_CLASS_MEASUREMENT,
}, value=lambda value: round(value, 1),
ATTR_API_PRESSURE: { ),
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, AirlySensorEntityDescription(
ATTR_LABEL: ATTR_API_PRESSURE.capitalize(), key=ATTR_API_PRESSURE,
ATTR_UNIT: PRESSURE_HPA, device_class=DEVICE_CLASS_PRESSURE,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, name=ATTR_API_PRESSURE.capitalize(),
ATTR_VALUE: round, unit_of_measurement=PRESSURE_HPA,
}, state_class=STATE_CLASS_MEASUREMENT,
ATTR_API_TEMPERATURE: { ),
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, AirlySensorEntityDescription(
ATTR_LABEL: ATTR_API_TEMPERATURE.capitalize(), key=ATTR_API_TEMPERATURE,
ATTR_UNIT: TEMP_CELSIUS, device_class=DEVICE_CLASS_TEMPERATURE,
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, name=ATTR_API_TEMPERATURE.capitalize(),
ATTR_VALUE: lambda value: round(value, 1), unit_of_measurement=TEMP_CELSIUS,
}, state_class=STATE_CLASS_MEASUREMENT,
} value=lambda value: round(value, 1),
),
)

View File

@ -1,15 +1,14 @@
"""Type definitions for Airly integration.""" """Type definitions for Airly integration."""
from __future__ import annotations from __future__ import annotations
from typing import Callable, TypedDict from dataclasses import dataclass
from typing import Callable
from homeassistant.components.sensor import SensorEntityDescription
class SensorDescription(TypedDict, total=False): @dataclass
"""Sensor description class.""" class AirlySensorEntityDescription(SensorEntityDescription):
"""Class describing Airly sensor entities."""
device_class: str | None value: Callable = round
icon: str | None
label: str
unit: str
state_class: str | None
value: Callable

View File

@ -3,16 +3,10 @@ from __future__ import annotations
from typing import Any, cast from typing import Any, cast
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
ATTR_ICON,
CONF_NAME,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
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
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -27,12 +21,9 @@ from .const import (
ATTR_API_PM10, ATTR_API_PM10,
ATTR_API_PM25, ATTR_API_PM25,
ATTR_DESCRIPTION, ATTR_DESCRIPTION,
ATTR_LABEL,
ATTR_LEVEL, ATTR_LEVEL,
ATTR_LIMIT, ATTR_LIMIT,
ATTR_PERCENT, ATTR_PERCENT,
ATTR_UNIT,
ATTR_VALUE,
ATTRIBUTION, ATTRIBUTION,
DEFAULT_NAME, DEFAULT_NAME,
DOMAIN, DOMAIN,
@ -41,6 +32,7 @@ from .const import (
SUFFIX_LIMIT, SUFFIX_LIMIT,
SUFFIX_PERCENT, SUFFIX_PERCENT,
) )
from .model import AirlySensorEntityDescription
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@ -54,10 +46,10 @@ async def async_setup_entry(
coordinator = hass.data[DOMAIN][entry.entry_id] coordinator = hass.data[DOMAIN][entry.entry_id]
sensors = [] sensors = []
for sensor in SENSOR_TYPES: for description in SENSOR_TYPES:
# When we use the nearest method, we are not sure which sensors are available # When we use the nearest method, we are not sure which sensors are available
if coordinator.data.get(sensor): if coordinator.data.get(description.key):
sensors.append(AirlySensor(coordinator, name, sensor)) sensors.append(AirlySensor(coordinator, name, description))
async_add_entities(sensors, False) async_add_entities(sensors, False)
@ -66,47 +58,54 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
"""Define an Airly sensor.""" """Define an Airly sensor."""
coordinator: AirlyDataUpdateCoordinator coordinator: AirlyDataUpdateCoordinator
entity_description: AirlySensorEntityDescription
def __init__( def __init__(
self, coordinator: AirlyDataUpdateCoordinator, name: str, kind: str self,
coordinator: AirlyDataUpdateCoordinator,
name: str,
description: AirlySensorEntityDescription,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._description = description = SENSOR_TYPES[kind] self._attr_device_info = {
self._attr_device_class = description.get(ATTR_DEVICE_CLASS) "identifiers": {
self._attr_icon = description.get(ATTR_ICON) (DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")
self._attr_name = f"{name} {description[ATTR_LABEL]}" },
self._attr_state_class = description.get(ATTR_STATE_CLASS) "name": DEFAULT_NAME,
"manufacturer": MANUFACTURER,
"entry_type": "service",
}
self._attr_name = f"{name} {description.name}"
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinator.latitude}-{coordinator.longitude}-{kind.lower()}" f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()
) )
self._attr_unit_of_measurement = description.get(ATTR_UNIT)
self._attrs: dict[str, Any] = {ATTR_ATTRIBUTION: ATTRIBUTION} self._attrs: dict[str, Any] = {ATTR_ATTRIBUTION: ATTRIBUTION}
self.kind = kind self.entity_description = description
@property @property
def state(self) -> StateType: def state(self) -> StateType:
"""Return the state.""" """Return the state."""
state = self.coordinator.data[self.kind] state = self.coordinator.data[self.entity_description.key]
return cast(StateType, self._description[ATTR_VALUE](state)) return cast(StateType, self.entity_description.value(state))
@property @property
def extra_state_attributes(self) -> dict[str, Any]: def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes.""" """Return the state attributes."""
if self.kind == ATTR_API_CAQI: if self.entity_description.key == ATTR_API_CAQI:
self._attrs[ATTR_LEVEL] = self.coordinator.data[ATTR_API_CAQI_LEVEL] self._attrs[ATTR_LEVEL] = self.coordinator.data[ATTR_API_CAQI_LEVEL]
self._attrs[ATTR_ADVICE] = self.coordinator.data[ATTR_API_ADVICE] self._attrs[ATTR_ADVICE] = self.coordinator.data[ATTR_API_ADVICE]
self._attrs[ATTR_DESCRIPTION] = self.coordinator.data[ self._attrs[ATTR_DESCRIPTION] = self.coordinator.data[
ATTR_API_CAQI_DESCRIPTION ATTR_API_CAQI_DESCRIPTION
] ]
if self.kind == ATTR_API_PM25: if self.entity_description.key == ATTR_API_PM25:
self._attrs[ATTR_LIMIT] = self.coordinator.data[ self._attrs[ATTR_LIMIT] = self.coordinator.data[
f"{ATTR_API_PM25}_{SUFFIX_LIMIT}" f"{ATTR_API_PM25}_{SUFFIX_LIMIT}"
] ]
self._attrs[ATTR_PERCENT] = round( self._attrs[ATTR_PERCENT] = round(
self.coordinator.data[f"{ATTR_API_PM25}_{SUFFIX_PERCENT}"] self.coordinator.data[f"{ATTR_API_PM25}_{SUFFIX_PERCENT}"]
) )
if self.kind == ATTR_API_PM10: if self.entity_description.key == ATTR_API_PM10:
self._attrs[ATTR_LIMIT] = self.coordinator.data[ self._attrs[ATTR_LIMIT] = self.coordinator.data[
f"{ATTR_API_PM10}_{SUFFIX_LIMIT}" f"{ATTR_API_PM10}_{SUFFIX_LIMIT}"
] ]
@ -114,18 +113,3 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
self.coordinator.data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"] self.coordinator.data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"]
) )
return self._attrs return self._attrs
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return {
"identifiers": {
(
DOMAIN,
f"{self.coordinator.latitude}-{self.coordinator.longitude}",
)
},
"name": DEFAULT_NAME,
"manufacturer": MANUFACTURER,
"entry_type": "service",
}