Files
core/homeassistant/components/guntamatic/sensor.py
T

437 lines
16 KiB
Python

"""Support for Guntamatic sensors in Home Assistant."""
from datetime import date, timedelta
import re
from typing import override
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
StateType,
)
from homeassistant.const import (
PERCENTAGE,
EntityCategory,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import ChildDeviceInfo, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import DOMAIN
from .coordinator import GuntamaticConfigEntry, GuntamaticCoordinator
PARALLEL_UPDATES = 0
HEATING_CIRCUIT_REGEX = re.compile(
r"^(?:room|circuit|heating_circulation_pump|heating_circulation_program)_(\d+)"
)
GUNTAMATIC_SENSORS: list[SensorEntityDescription] = [
SensorEntityDescription(
key="program",
translation_key="program",
device_class=SensorDeviceClass.ENUM,
options=[
"off",
"timer",
"dhw",
"heat",
"hibernate",
"hibernate_to",
"dhw_boost",
],
),
SensorEntityDescription(
key="status",
translation_key="status",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="boiler_temperature",
translation_key="boiler_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(
key="outdoor_temperature",
translation_key="outdoor_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(
key="buffer_top_temperature",
translation_key="buffer_top_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(
key="buffer_top_0_temperature",
translation_key="buffer_stage_top_temperature",
translation_placeholders={"tank": "1"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="buffer_top_1_temperature",
translation_key="buffer_stage_top_temperature",
translation_placeholders={"tank": "2"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="buffer_top_2_temperature",
translation_key="buffer_stage_top_temperature",
translation_placeholders={"tank": "3"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="buffer_center_temperature",
translation_key="buffer_center_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(
key="buffer_bottom_temperature",
translation_key="buffer_bottom_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(
key="buffer_bottom_0_temperature",
translation_key="buffer_stage_bottom_temperature",
translation_placeholders={"tank": "1"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="buffer_bottom_1_temperature",
translation_key="buffer_stage_bottom_temperature",
translation_placeholders={"tank": "2"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="buffer_bottom_2_temperature",
translation_key="buffer_stage_bottom_temperature",
translation_placeholders={"tank": "3"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="domestic_hot_water_0_temperature",
translation_key="domestic_hot_water_temperature",
translation_placeholders={"circuit": "1"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(
key="domestic_hot_water_1_temperature",
translation_key="domestic_hot_water_temperature",
translation_placeholders={"circuit": "2"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(
key="domestic_hot_water_2_temperature",
translation_key="domestic_hot_water_temperature",
translation_placeholders={"circuit": "3"},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
*[
SensorEntityDescription(
key=f"room_{i}_temperature",
translation_key="room_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
)
for i in range(9)
],
SensorEntityDescription(
key="buffer_load",
translation_key="buffer_load",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key="boiler_shunt_pump",
translation_key="boiler_shunt_pump",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
),
*[
SensorEntityDescription(
key=f"auxiliary_pump_{nr}",
translation_key="auxiliary_pump",
translation_placeholders={"pump": str(nr + 1)},
device_class=SensorDeviceClass.ENUM,
options=[
"auto",
"off",
"nonstop",
],
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
)
for nr in range(3)
],
SensorEntityDescription(
key="suction_fan",
translation_key="suction_fan",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key="primary_air",
translation_key="primary_air",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key="secondary_air",
translation_key="secondary_air",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
# This is CO2 content in a flue. It is measured in % and goes really high.
# It does not make sense to measure this as ppm as one does for air quality.
key="co2_content",
translation_key="co2_content",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
*[
SensorEntityDescription(
key=f"dhw_pump_{nr}",
translation_key="dhw_pump",
translation_placeholders={"circuit": str(nr + 1)},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
)
for nr in range(3)
],
*[
SensorEntityDescription(
key=f"extra_dhw_{nr}_temperature",
translation_key="extra_dhw_temperature",
translation_placeholders={"circuit": str(nr)},
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_registry_enabled_default=False,
)
for nr in range(3)
],
*[
SensorEntityDescription(
key=f"extra_dhw_boost_{nr}",
translation_key="extra_dhw_boost",
translation_placeholders={"pump": str(nr + 1)},
device_class=SensorDeviceClass.ENUM,
options=[
"auto",
"off",
"nonstop",
],
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
)
for nr in range(3)
],
*[
SensorEntityDescription(
key=f"heating_circulation_pump_{i}",
translation_key="heating_circulation_pump",
device_class=SensorDeviceClass.ENUM,
options=[
"auto",
"off",
"nonstop",
],
entity_category=EntityCategory.DIAGNOSTIC,
)
for i in range(9)
],
*[
SensorEntityDescription(
key=f"circuit_{i}_temp",
translation_key="circuit_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_category=EntityCategory.DIAGNOSTIC,
)
for i in range(9)
],
*[
SensorEntityDescription(
key=f"heating_circulation_program_{i}",
translation_key="heating_circulation_program",
device_class=SensorDeviceClass.ENUM,
options=[
"off",
"timer",
"heat",
"hibernate",
"hibernate_to",
],
entity_registry_enabled_default=False,
)
for i in range(9)
],
SensorEntityDescription(
key="interruption_1",
translation_key="interruption",
translation_placeholders={"number": "1"},
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="interruption_2",
translation_key="interruption",
translation_placeholders={"number": "2"},
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="operating_time",
translation_key="operating_time",
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.HOURS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="service_days",
translation_key="service_date",
device_class=SensorDeviceClass.DATE,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
]
async def async_setup_entry(
hass: HomeAssistant,
entry: GuntamaticConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Guntamatic sensors from config entry."""
coordinator = entry.runtime_data
serial = coordinator.data["serial"][0]
device_registry = dr.async_get(hass)
main_device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, serial)},
manufacturer="Guntamatic",
serial_number=serial,
sw_version=coordinator.data["version"][0],
)
entities: list[GuntamaticSensor] = []
for description in GUNTAMATIC_SENSORS:
if description.key not in coordinator.data:
continue
if match := HEATING_CIRCUIT_REGEX.match(description.key):
circuit = int(match.group(1))
identifiers = {(DOMAIN, f"{serial}_hc{circuit}")}
device_info: DeviceInfo | ChildDeviceInfo = ChildDeviceInfo(
identifiers=identifiers,
parent_device_id=main_device.id,
name=f"Heating circuit {circuit}",
)
else:
device_info = DeviceInfo(identifiers={(DOMAIN, serial)})
entities.append(GuntamaticSensor(coordinator, description, device_info))
async_add_entities(entities)
class GuntamaticSensor(CoordinatorEntity[GuntamaticCoordinator], SensorEntity):
"""Representation of a single Guntamatic sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: GuntamaticCoordinator,
entity_description: SensorEntityDescription,
device_info: DeviceInfo | ChildDeviceInfo,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = entity_description
serial = coordinator.data["serial"][0]
self._attr_unique_id = f"{serial.replace('.', '_')}_{entity_description.key}"
self._attr_device_info = device_info
@property
@override
def available(self) -> bool:
"""Return whether the entity is available."""
return (
super().available and self.entity_description.key in self.coordinator.data
)
@property
@override
def native_value(self) -> StateType | date:
"""Return the current value of the sensor."""
value = self.coordinator.data[self.entity_description.key][0]
if self.entity_description.device_class is SensorDeviceClass.DATE:
return (dt_util.now() + timedelta(days=float(value))).date()
if (
self.entity_description.device_class is SensorDeviceClass.ENUM
and value not in (self.entity_description.options or [])
):
# The library passes through unmapped values of new firmware
# languages; expose them as unknown instead of raising.
return None
return value