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

300 lines
10 KiB
Python

"""Sensor platform for MELCloud Home."""
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from itertools import chain
from typing import override
from aiomelcloudhome import ATAUnit, ATWUnit
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
StateType,
)
from homeassistant.const import (
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfEnergy,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.dt import utcnow
from .common import async_setup_unit_entities
from .coordinator import (
MelCloudHomeConfigEntry,
MelCloudHomeCoordinator,
MelCloudHomeEnergyCoordinator,
)
from .entity import MelCloudHomeATAUnitEntity, MelCloudHomeATWUnitEntity
PARALLEL_UPDATES = 0
ENERGY_CONSUMED_DESCRIPTION = SensorEntityDescription(
key="energy_consumed",
translation_key="energy_consumed",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
)
def _has_energy_meter(unit: ATAUnit | ATWUnit) -> bool:
"""Return whether a unit reports an energy consumption meter."""
return bool(unit.capabilities and unit.capabilities.has_energy_consumed_meter)
@dataclass(frozen=True, kw_only=True)
class MelCloudHomeSensorEntityDescription[_UnitT: ATAUnit | ATWUnit](
SensorEntityDescription
):
"""Class to hold MELCloud Home sensor description."""
value_fn: Callable[[_UnitT, MelCloudHomeCoordinator], StateType]
exists_fn: Callable[[_UnitT], bool] = lambda _: True
def _common_sensor_descriptions[_UnitT: ATAUnit | ATWUnit](
unit_type: type[_UnitT],
) -> tuple[MelCloudHomeSensorEntityDescription[_UnitT], ...]:
"""Return the sensor descriptions shared by ATA and ATW units."""
return (
MelCloudHomeSensorEntityDescription(
key="rssi",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=lambda unit, _: unit.rssi,
),
)
ATA_SENSORS: tuple[MelCloudHomeSensorEntityDescription[ATAUnit], ...] = (
MelCloudHomeSensorEntityDescription(
key="room_temperature",
translation_key="room_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=1,
value_fn=lambda unit, _: unit.room_temperature,
),
*_common_sensor_descriptions(ATAUnit),
)
ATW_SENSORS: tuple[MelCloudHomeSensorEntityDescription[ATWUnit], ...] = (
MelCloudHomeSensorEntityDescription(
key="room_temperature_zone_1",
translation_key="room_temperature_zone_1",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=1,
value_fn=lambda unit, _: unit.room_temperature_zone1,
),
MelCloudHomeSensorEntityDescription(
key="room_temperature_zone_2",
translation_key="room_temperature_zone_2",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=1,
value_fn=lambda unit, _: unit.room_temperature_zone2,
exists_fn=lambda unit: bool(
(unit.capabilities and unit.capabilities.has_zone2)
or (unit.capabilities is None and unit.has_zone2)
),
),
MelCloudHomeSensorEntityDescription(
key="tank_water_temperature",
translation_key="tank_water_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=1,
value_fn=lambda unit, _: unit.tank_water_temperature,
),
*_common_sensor_descriptions(ATWUnit),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: MelCloudHomeConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up MELCloud Home sensors."""
coordinator = entry.runtime_data.coordinator
energy_coordinator = entry.runtime_data.energy_coordinator
async_setup_unit_entities(
coordinator,
async_add_entities,
lambda units: chain(
(
ATASensor(coordinator, entity_description, unit)
for entity_description in ATA_SENSORS
for unit in units
if entity_description.exists_fn(unit)
),
(
ATAEnergySensor(coordinator, energy_coordinator, unit)
for unit in units
if _has_energy_meter(unit)
),
),
lambda units: chain(
(
ATWSensor(coordinator, entity_description, unit)
for entity_description in ATW_SENSORS
for unit in units
if entity_description.exists_fn(unit)
),
(
ATWEnergySensor(coordinator, energy_coordinator, unit)
for unit in units
if _has_energy_meter(unit)
),
),
)
class ATASensor(MelCloudHomeATAUnitEntity, SensorEntity):
"""Representation of a MELCloud Home ATA sensor."""
entity_description: MelCloudHomeSensorEntityDescription[ATAUnit]
def __init__(
self,
coordinator: MelCloudHomeCoordinator,
entity_description: MelCloudHomeSensorEntityDescription[ATAUnit],
unit: ATAUnit,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, unit)
self.entity_description = entity_description
self._attr_unique_id = f"{unit.id}_{entity_description.key}"
@property
@override
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.unit, self.coordinator)
class ATWSensor(MelCloudHomeATWUnitEntity, SensorEntity):
"""Representation of a MELCloud Home ATW sensor."""
entity_description: MelCloudHomeSensorEntityDescription[ATWUnit]
def __init__(
self,
coordinator: MelCloudHomeCoordinator,
entity_description: MelCloudHomeSensorEntityDescription[ATWUnit],
unit: ATWUnit,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, unit)
self.entity_description = entity_description
self._attr_unique_id = f"{unit.id}_{entity_description.key}"
@property
@override
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.unit, self.coordinator)
class ATAEnergySensor(MelCloudHomeATAUnitEntity, SensorEntity):
"""Representation of a MELCloud Home ATA energy sensor."""
entity_description = ENERGY_CONSUMED_DESCRIPTION
def __init__(
self,
coordinator: MelCloudHomeCoordinator,
energy_coordinator: MelCloudHomeEnergyCoordinator,
unit: ATAUnit,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, unit)
self._energy_coordinator = energy_coordinator
self._attr_unique_id = f"{unit.id}_{ENERGY_CONSUMED_DESCRIPTION.key}"
@override
async def async_added_to_hass(self) -> None:
"""Also react to updates from the energy coordinator."""
await super().async_added_to_hass()
self.async_on_remove(
self._energy_coordinator.async_add_listener(self._handle_coordinator_update)
)
@property
@override
def available(self) -> bool:
"""Return if the entity is available."""
return super().available and self._energy_coordinator.last_update_success
@property
@override
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self._energy_coordinator.data.get(self._unit_id)
@property
@override
def last_reset(self) -> datetime:
"""Return start of month."""
return utcnow().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
class ATWEnergySensor(MelCloudHomeATWUnitEntity, SensorEntity):
"""Representation of a MELCloud Home ATW energy sensor."""
entity_description = ENERGY_CONSUMED_DESCRIPTION
def __init__(
self,
coordinator: MelCloudHomeCoordinator,
energy_coordinator: MelCloudHomeEnergyCoordinator,
unit: ATWUnit,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, unit)
self._energy_coordinator = energy_coordinator
self._attr_unique_id = f"{unit.id}_{ENERGY_CONSUMED_DESCRIPTION.key}"
@override
async def async_added_to_hass(self) -> None:
"""Also react to updates from the energy coordinator."""
await super().async_added_to_hass()
self.async_on_remove(
self._energy_coordinator.async_add_listener(self._handle_coordinator_update)
)
@property
@override
def available(self) -> bool:
"""Return if the entity is available."""
return super().available and self._energy_coordinator.last_update_success
@property
@override
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self._energy_coordinator.data.get(self._unit_id)
@property
@override
def last_reset(self) -> datetime:
"""Return start of month."""
return utcnow().replace(day=1, hour=0, minute=0, second=0, microsecond=0)