mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
Add Air Quality PPB sensor to deCONZ integration (#64164)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
7c110eeef4
commit
7e40707288
@ -1,7 +1,8 @@
|
|||||||
"""Support for deCONZ sensors."""
|
"""Support for deCONZ sensors."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import ValuesView
|
from collections.abc import Callable, ValuesView
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from pydeconz.sensor import (
|
from pydeconz.sensor import (
|
||||||
AirQuality,
|
AirQuality,
|
||||||
@ -31,6 +32,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
ATTR_VOLTAGE,
|
ATTR_VOLTAGE,
|
||||||
|
CONCENTRATION_PARTS_PER_BILLION,
|
||||||
ENERGY_KILO_WATT_HOUR,
|
ENERGY_KILO_WATT_HOUR,
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
@ -69,6 +71,24 @@ ATTR_POWER = "power"
|
|||||||
ATTR_DAYLIGHT = "daylight"
|
ATTR_DAYLIGHT = "daylight"
|
||||||
ATTR_EVENT_ID = "event_id"
|
ATTR_EVENT_ID = "event_id"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeconzSensorDescriptionMixin:
|
||||||
|
"""Required values when describing secondary sensor attributes."""
|
||||||
|
|
||||||
|
suffix: str
|
||||||
|
update_key: str
|
||||||
|
value_fn: Callable[[PydeconzSensor], float | int | None]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeconzSensorDescription(
|
||||||
|
SensorEntityDescription,
|
||||||
|
DeconzSensorDescriptionMixin,
|
||||||
|
):
|
||||||
|
"""Class describing deCONZ binary sensor entities."""
|
||||||
|
|
||||||
|
|
||||||
ENTITY_DESCRIPTIONS = {
|
ENTITY_DESCRIPTIONS = {
|
||||||
Battery: SensorEntityDescription(
|
Battery: SensorEntityDescription(
|
||||||
key="battery",
|
key="battery",
|
||||||
@ -119,6 +139,27 @@ ENTITY_DESCRIPTIONS = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SENSOR_DESCRIPTIONS = [
|
||||||
|
DeconzSensorDescription(
|
||||||
|
key="temperature",
|
||||||
|
value_fn=lambda device: device.secondary_temperature,
|
||||||
|
suffix="Temperature",
|
||||||
|
update_key="temperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=TEMP_CELSIUS,
|
||||||
|
),
|
||||||
|
DeconzSensorDescription(
|
||||||
|
key="air_quality_ppb",
|
||||||
|
value_fn=lambda device: device.air_quality_ppb,
|
||||||
|
suffix="PPB",
|
||||||
|
update_key="airqualityppb",
|
||||||
|
device_class=SensorDeviceClass.AQI,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -141,7 +182,7 @@ async def async_setup_entry(
|
|||||||
Create DeconzBattery if sensor has a battery attribute.
|
Create DeconzBattery if sensor has a battery attribute.
|
||||||
Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor.
|
Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor.
|
||||||
"""
|
"""
|
||||||
entities: list[DeconzBattery | DeconzSensor | DeconzTemperature] = []
|
entities: list[DeconzBattery | DeconzSensor | DeconzPropertySensor] = []
|
||||||
|
|
||||||
for sensor in sensors:
|
for sensor in sensors:
|
||||||
|
|
||||||
@ -166,11 +207,18 @@ async def async_setup_entry(
|
|||||||
):
|
):
|
||||||
entities.append(DeconzSensor(sensor, gateway))
|
entities.append(DeconzSensor(sensor, gateway))
|
||||||
|
|
||||||
if sensor.secondary_temperature:
|
for sensor_description in SENSOR_DESCRIPTIONS:
|
||||||
known_temperature_sensors = set(gateway.entities[DOMAIN])
|
|
||||||
new_temperature_sensor = DeconzTemperature(sensor, gateway)
|
try:
|
||||||
if new_temperature_sensor.unique_id not in known_temperature_sensors:
|
if sensor_description.value_fn(sensor):
|
||||||
entities.append(new_temperature_sensor)
|
known_sensors = set(gateway.entities[DOMAIN])
|
||||||
|
new_sensor = DeconzPropertySensor(
|
||||||
|
sensor, gateway, sensor_description
|
||||||
|
)
|
||||||
|
if new_sensor.unique_id not in known_sensors:
|
||||||
|
entities.append(new_sensor)
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
|
||||||
if entities:
|
if entities:
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
@ -245,38 +293,41 @@ class DeconzSensor(DeconzDevice, SensorEntity):
|
|||||||
return attr
|
return attr
|
||||||
|
|
||||||
|
|
||||||
class DeconzTemperature(DeconzDevice, SensorEntity):
|
class DeconzPropertySensor(DeconzDevice, SensorEntity):
|
||||||
"""Representation of a deCONZ temperature sensor.
|
"""Representation of a deCONZ secondary attribute sensor."""
|
||||||
|
|
||||||
Extra temperature sensor on certain Xiaomi devices.
|
|
||||||
"""
|
|
||||||
|
|
||||||
TYPE = DOMAIN
|
TYPE = DOMAIN
|
||||||
_device: PydeconzSensor
|
_device: PydeconzSensor
|
||||||
|
entity_description: DeconzSensorDescription
|
||||||
|
|
||||||
def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None:
|
def __init__(
|
||||||
"""Initialize deCONZ temperature sensor."""
|
self,
|
||||||
|
device: PydeconzSensor,
|
||||||
|
gateway: DeconzGateway,
|
||||||
|
description: DeconzSensorDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize deCONZ sensor."""
|
||||||
|
self.entity_description = description
|
||||||
super().__init__(device, gateway)
|
super().__init__(device, gateway)
|
||||||
|
|
||||||
self.entity_description = ENTITY_DESCRIPTIONS[Temperature]
|
self._attr_name = f"{self._device.name} {description.suffix}"
|
||||||
self._attr_name = f"{self._device.name} Temperature"
|
self._update_keys = {description.update_key, "reachable"}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
"""Return a unique identifier for this device."""
|
"""Return a unique identifier for this device."""
|
||||||
return f"{self.serial}-temperature"
|
return f"{self.serial}-{self.entity_description.suffix.lower()}"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_callback(self) -> None:
|
def async_update_callback(self) -> None:
|
||||||
"""Update the sensor's state."""
|
"""Update the sensor's state."""
|
||||||
keys = {"temperature", "reachable"}
|
if self._device.changed_keys.intersection(self._update_keys):
|
||||||
if self._device.changed_keys.intersection(keys):
|
|
||||||
super().async_update_callback()
|
super().async_update_callback()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self._device.secondary_temperature # type: ignore[no-any-return]
|
return self.entity_description.value_fn(self._device)
|
||||||
|
|
||||||
|
|
||||||
class DeconzBattery(DeconzDevice, SensorEntity):
|
class DeconzBattery(DeconzDevice, SensorEntity):
|
||||||
|
@ -476,8 +476,9 @@ async def test_air_quality_sensor(hass, aioclient_mock):
|
|||||||
with patch.dict(DECONZ_WEB_REQUEST, data):
|
with patch.dict(DECONZ_WEB_REQUEST, data):
|
||||||
await setup_deconz_integration(hass, aioclient_mock)
|
await setup_deconz_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
assert len(hass.states.async_all()) == 1
|
assert len(hass.states.async_all()) == 2
|
||||||
assert hass.states.get("sensor.air_quality").state == "poor"
|
assert hass.states.get("sensor.air_quality").state == "poor"
|
||||||
|
assert hass.states.get("sensor.air_quality_ppb").state == "809"
|
||||||
|
|
||||||
|
|
||||||
async def test_daylight_sensor(hass, aioclient_mock):
|
async def test_daylight_sensor(hass, aioclient_mock):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user