mirror of
https://github.com/home-assistant/core.git
synced 2025-07-10 23:07:09 +00:00
Add sensors for Aprilaire integration (#113194)
* Add sensors for Aprilaire integration * Exclude from coverage * Exclude from coverage * Add comment * Update homeassistant/components/aprilaire/sensor.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Code review updates * Code review updates * Code review updates * Code review updates * Remove temperature conversion * Add suggested display precision * Code review updates * Merge fix * Code review fixes * Fix type errors * Revert change * Fix type errors * Type errors * Use common keys --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
1c2aa9a49b
commit
ea8d0ba2ce
@ -87,6 +87,7 @@ omit =
|
|||||||
homeassistant/components/aprilaire/climate.py
|
homeassistant/components/aprilaire/climate.py
|
||||||
homeassistant/components/aprilaire/coordinator.py
|
homeassistant/components/aprilaire/coordinator.py
|
||||||
homeassistant/components/aprilaire/entity.py
|
homeassistant/components/aprilaire/entity.py
|
||||||
|
homeassistant/components/aprilaire/sensor.py
|
||||||
homeassistant/components/apsystems/__init__.py
|
homeassistant/components/apsystems/__init__.py
|
||||||
homeassistant/components/apsystems/coordinator.py
|
homeassistant/components/apsystems/coordinator.py
|
||||||
homeassistant/components/apsystems/entity.py
|
homeassistant/components/apsystems/entity.py
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.helpers.device_registry import format_mac
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import AprilaireCoordinator
|
from .coordinator import AprilaireCoordinator
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SENSOR]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
308
homeassistant/components/aprilaire/sensor.py
Normal file
308
homeassistant/components/aprilaire/sensor.py
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
"""The Aprilaire sensor component."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from pyaprilaire.const import Attribute
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import PERCENTAGE, UnitOfTemperature
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AprilaireCoordinator
|
||||||
|
from .entity import BaseAprilaireEntity
|
||||||
|
|
||||||
|
DEHUMIDIFICATION_STATUS_MAP: dict[StateType, str] = {
|
||||||
|
0: "idle",
|
||||||
|
1: "idle",
|
||||||
|
2: "on",
|
||||||
|
3: "on",
|
||||||
|
4: "off",
|
||||||
|
}
|
||||||
|
|
||||||
|
HUMIDIFICATION_STATUS_MAP: dict[StateType, str] = {
|
||||||
|
0: "idle",
|
||||||
|
1: "idle",
|
||||||
|
2: "on",
|
||||||
|
3: "off",
|
||||||
|
}
|
||||||
|
|
||||||
|
VENTILATION_STATUS_MAP: dict[StateType, str] = {
|
||||||
|
0: "idle",
|
||||||
|
1: "idle",
|
||||||
|
2: "on",
|
||||||
|
3: "idle",
|
||||||
|
4: "idle",
|
||||||
|
5: "idle",
|
||||||
|
6: "off",
|
||||||
|
}
|
||||||
|
|
||||||
|
AIR_CLEANING_STATUS_MAP: dict[StateType, str] = {
|
||||||
|
0: "idle",
|
||||||
|
1: "idle",
|
||||||
|
2: "on",
|
||||||
|
3: "off",
|
||||||
|
}
|
||||||
|
|
||||||
|
FAN_STATUS_MAP: dict[StateType, str] = {0: "off", 1: "on"}
|
||||||
|
|
||||||
|
|
||||||
|
def get_entities(
|
||||||
|
entity_class: type[BaseAprilaireSensor],
|
||||||
|
coordinator: AprilaireCoordinator,
|
||||||
|
unique_id: str,
|
||||||
|
descriptions: tuple[AprilaireSensorDescription, ...],
|
||||||
|
) -> list[BaseAprilaireSensor]:
|
||||||
|
"""Get the entities for a list of sensor descriptions."""
|
||||||
|
|
||||||
|
entities = (
|
||||||
|
entity_class(coordinator, description, unique_id)
|
||||||
|
for description in descriptions
|
||||||
|
)
|
||||||
|
|
||||||
|
return [entity for entity in entities if entity.exists]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Aprilaire sensor devices."""
|
||||||
|
|
||||||
|
coordinator: AprilaireCoordinator = hass.data[DOMAIN][config_entry.unique_id]
|
||||||
|
|
||||||
|
assert config_entry.unique_id is not None
|
||||||
|
|
||||||
|
entities = (
|
||||||
|
get_entities(
|
||||||
|
AprilaireHumiditySensor,
|
||||||
|
coordinator,
|
||||||
|
config_entry.unique_id,
|
||||||
|
HUMIDITY_SENSORS,
|
||||||
|
)
|
||||||
|
+ get_entities(
|
||||||
|
AprilaireTemperatureSensor,
|
||||||
|
coordinator,
|
||||||
|
config_entry.unique_id,
|
||||||
|
TEMPERATURE_SENSORS,
|
||||||
|
)
|
||||||
|
+ get_entities(
|
||||||
|
AprilaireStatusSensor, coordinator, config_entry.unique_id, STATUS_SENSORS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class AprilaireSensorDescription(SensorEntityDescription):
|
||||||
|
"""Class describing Aprilaire sensor entities."""
|
||||||
|
|
||||||
|
status_key: str | None
|
||||||
|
value_key: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class AprilaireStatusSensorDescription(AprilaireSensorDescription):
|
||||||
|
"""Class describing Aprilaire status sensor entities."""
|
||||||
|
|
||||||
|
status_map: dict[StateType, str]
|
||||||
|
|
||||||
|
|
||||||
|
HUMIDITY_SENSORS: tuple[AprilaireSensorDescription, ...] = (
|
||||||
|
AprilaireSensorDescription(
|
||||||
|
key="indoor_humidity_controlling_sensor",
|
||||||
|
translation_key="indoor_humidity_controlling_sensor",
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
status_key=Attribute.INDOOR_HUMIDITY_CONTROLLING_SENSOR_STATUS,
|
||||||
|
value_key=Attribute.INDOOR_HUMIDITY_CONTROLLING_SENSOR_VALUE,
|
||||||
|
),
|
||||||
|
AprilaireSensorDescription(
|
||||||
|
key="outdoor_humidity_controlling_sensor",
|
||||||
|
translation_key="outdoor_humidity_controlling_sensor",
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
status_key=Attribute.OUTDOOR_HUMIDITY_CONTROLLING_SENSOR_STATUS,
|
||||||
|
value_key=Attribute.OUTDOOR_HUMIDITY_CONTROLLING_SENSOR_VALUE,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEMPERATURE_SENSORS: tuple[AprilaireSensorDescription, ...] = (
|
||||||
|
AprilaireSensorDescription(
|
||||||
|
key="indoor_temperature_controlling_sensor",
|
||||||
|
translation_key="indoor_temperature_controlling_sensor",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
status_key=Attribute.INDOOR_TEMPERATURE_CONTROLLING_SENSOR_STATUS,
|
||||||
|
value_key=Attribute.INDOOR_TEMPERATURE_CONTROLLING_SENSOR_VALUE,
|
||||||
|
),
|
||||||
|
AprilaireSensorDescription(
|
||||||
|
key="outdoor_temperature_controlling_sensor",
|
||||||
|
translation_key="outdoor_temperature_controlling_sensor",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
status_key=Attribute.OUTDOOR_TEMPERATURE_CONTROLLING_SENSOR_STATUS,
|
||||||
|
value_key=Attribute.OUTDOOR_TEMPERATURE_CONTROLLING_SENSOR_VALUE,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
STATUS_SENSORS: tuple[AprilaireSensorDescription, ...] = (
|
||||||
|
AprilaireStatusSensorDescription(
|
||||||
|
key="dehumidification_status",
|
||||||
|
translation_key="dehumidification_status",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
status_key=Attribute.DEHUMIDIFICATION_AVAILABLE,
|
||||||
|
value_key=Attribute.DEHUMIDIFICATION_STATUS,
|
||||||
|
status_map=DEHUMIDIFICATION_STATUS_MAP,
|
||||||
|
options=list(set(DEHUMIDIFICATION_STATUS_MAP.values())),
|
||||||
|
),
|
||||||
|
AprilaireStatusSensorDescription(
|
||||||
|
key="humidification_status",
|
||||||
|
translation_key="humidification_status",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
status_key=Attribute.HUMIDIFICATION_AVAILABLE,
|
||||||
|
value_key=Attribute.HUMIDIFICATION_STATUS,
|
||||||
|
status_map=HUMIDIFICATION_STATUS_MAP,
|
||||||
|
options=list(set(HUMIDIFICATION_STATUS_MAP.values())),
|
||||||
|
),
|
||||||
|
AprilaireStatusSensorDescription(
|
||||||
|
key="ventilation_status",
|
||||||
|
translation_key="ventilation_status",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
status_key=Attribute.VENTILATION_AVAILABLE,
|
||||||
|
value_key=Attribute.VENTILATION_STATUS,
|
||||||
|
status_map=VENTILATION_STATUS_MAP,
|
||||||
|
options=list(set(VENTILATION_STATUS_MAP.values())),
|
||||||
|
),
|
||||||
|
AprilaireStatusSensorDescription(
|
||||||
|
key="air_cleaning_status",
|
||||||
|
translation_key="air_cleaning_status",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
status_key=Attribute.AIR_CLEANING_AVAILABLE,
|
||||||
|
value_key=Attribute.AIR_CLEANING_STATUS,
|
||||||
|
status_map=AIR_CLEANING_STATUS_MAP,
|
||||||
|
options=list(set(AIR_CLEANING_STATUS_MAP.values())),
|
||||||
|
),
|
||||||
|
AprilaireStatusSensorDescription(
|
||||||
|
key="fan_status",
|
||||||
|
translation_key="fan_status",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
status_key=None,
|
||||||
|
value_key=Attribute.FAN_STATUS,
|
||||||
|
status_map=FAN_STATUS_MAP,
|
||||||
|
options=list(set(FAN_STATUS_MAP.values())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAprilaireSensor(BaseAprilaireEntity, SensorEntity):
|
||||||
|
"""Base sensor entity for Aprilaire."""
|
||||||
|
|
||||||
|
entity_description: AprilaireSensorDescription
|
||||||
|
status_sensor_available_value: int | None = None
|
||||||
|
status_sensor_exists_values: list[int]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AprilaireCoordinator,
|
||||||
|
description: AprilaireSensorDescription,
|
||||||
|
unique_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a sensor for an Aprilaire device."""
|
||||||
|
|
||||||
|
self.entity_description = description
|
||||||
|
|
||||||
|
super().__init__(coordinator, unique_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exists(self) -> bool:
|
||||||
|
"""Return True if the sensor exists."""
|
||||||
|
|
||||||
|
if self.entity_description.status_key is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.coordinator.data.get(self.entity_description.status_key)
|
||||||
|
in self.status_sensor_exists_values
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if the sensor is available."""
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.entity_description.status_key is None
|
||||||
|
or self.status_sensor_available_value is None
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not super().available:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.coordinator.data.get(self.entity_description.status_key)
|
||||||
|
== self.status_sensor_available_value
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> StateType:
|
||||||
|
"""Return the value reported by the sensor."""
|
||||||
|
|
||||||
|
# Valid cast as pyaprilaire only provides str | int | float
|
||||||
|
return cast(
|
||||||
|
StateType, self.coordinator.data.get(self.entity_description.value_key)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AprilaireHumiditySensor(BaseAprilaireSensor):
|
||||||
|
"""Humidity sensor entity for Aprilaire."""
|
||||||
|
|
||||||
|
status_sensor_available_value = 0
|
||||||
|
status_sensor_exists_values = [0, 1, 2]
|
||||||
|
|
||||||
|
|
||||||
|
class AprilaireTemperatureSensor(BaseAprilaireSensor):
|
||||||
|
"""Temperature sensor entity for Aprilaire."""
|
||||||
|
|
||||||
|
status_sensor_available_value = 0
|
||||||
|
status_sensor_exists_values = [0, 1, 2]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def suggested_display_precision(self) -> int | None:
|
||||||
|
"""Return the suggested number of decimal digits for display."""
|
||||||
|
if self.unit_of_measurement == UnitOfTemperature.CELSIUS:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class AprilaireStatusSensor(BaseAprilaireSensor):
|
||||||
|
"""Status sensor entity for Aprilaire."""
|
||||||
|
|
||||||
|
status_sensor_exists_values = [1, 2]
|
||||||
|
entity_description: AprilaireStatusSensorDescription
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> StateType:
|
||||||
|
"""Return the value reported by the sensor mapped to the status option."""
|
||||||
|
|
||||||
|
raw_value = super().native_value
|
||||||
|
|
||||||
|
return self.entity_description.status_map.get(raw_value)
|
@ -23,6 +23,59 @@
|
|||||||
"thermostat": {
|
"thermostat": {
|
||||||
"name": "Thermostat"
|
"name": "Thermostat"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sensor": {
|
||||||
|
"indoor_humidity_controlling_sensor": {
|
||||||
|
"name": "Indoor humidity controlling sensor"
|
||||||
|
},
|
||||||
|
"outdoor_humidity_controlling_sensor": {
|
||||||
|
"name": "Outdoor humidity controlling sensor"
|
||||||
|
},
|
||||||
|
"indoor_temperature_controlling_sensor": {
|
||||||
|
"name": "Indoor temperature controlling sensor"
|
||||||
|
},
|
||||||
|
"outdoor_temperature_controlling_sensor": {
|
||||||
|
"name": "Outdoor temperature controlling sensor"
|
||||||
|
},
|
||||||
|
"dehumidification_status": {
|
||||||
|
"name": "Dehumidification status",
|
||||||
|
"state": {
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"off": "[%key:common::state::off%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"humidification_status": {
|
||||||
|
"name": "Humidification status",
|
||||||
|
"state": {
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"off": "[%key:common::state::off%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ventilation_status": {
|
||||||
|
"name": "Ventilation status",
|
||||||
|
"state": {
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"off": "[%key:common::state::off%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"air_cleaning_status": {
|
||||||
|
"name": "Air cleaning status",
|
||||||
|
"state": {
|
||||||
|
"idle": "[%key:common::state::idle%]",
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"off": "[%key:common::state::off%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fan_status": {
|
||||||
|
"name": "Fan status",
|
||||||
|
"state": {
|
||||||
|
"on": "[%key:common::state::on%]",
|
||||||
|
"off": "[%key:common::state::off%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user