Files

294 lines
11 KiB
Python

"""Number platform for MELCloud Home."""
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any, override
from aiomelcloudhome import ATAUnit, ATWUnit, MELCloudHome
from homeassistant.components.number import (
NumberDeviceClass,
NumberEntity,
NumberEntityDescription,
)
from homeassistant.const import EntityCategory, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .common import async_setup_unit_entities, perform_action, unit_ids
from .const import DOMAIN
from .coordinator import MelCloudHomeConfigEntry, MelCloudHomeCoordinator
from .entity import MelCloudHomeATAUnitEntity, MelCloudHomeATWUnitEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class MelCloudHomeNumberEntityDescription[_UnitT: ATAUnit | ATWUnit](
NumberEntityDescription
):
"""Class to hold MELCloud Home number description."""
available_fn: Callable[[_UnitT], bool]
value_fn: Callable[[_UnitT], float | None]
set_value_fn: Callable[[MELCloudHome, _UnitT, float], Coroutine[Any, Any, None]]
validate_fn: Callable[[_UnitT, float], str | None] | None = None
def _number_descriptions[_UnitT: ATAUnit | ATWUnit](
unit_type: type[_UnitT],
*,
overheat_min_temp: float,
overheat_max_temp: float,
) -> tuple[MelCloudHomeNumberEntityDescription[_UnitT], ...]:
"""Return the number descriptions for a unit type."""
return (
MelCloudHomeNumberEntityDescription(
key="frost_protection_min_temp",
translation_key="frost_protection_min_temp",
device_class=NumberDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_category=EntityCategory.CONFIG,
native_min_value=0.0,
native_max_value=30.0,
native_step=0.5,
available_fn=lambda unit: (
unit.frost_protection is not None and unit.frost_protection.enabled
),
value_fn=lambda unit: (
unit.frost_protection.min if unit.frost_protection else None
),
set_value_fn=lambda client, unit, value: client.set_frost_protection(
enabled=unit.frost_protection.enabled
if unit.frost_protection
else False,
min_temp=value,
max_temp=unit.frost_protection.max if unit.frost_protection else 0.0,
**unit_ids(unit),
),
validate_fn=lambda unit, value: (
"temperature_min_exceeds_max"
if unit.frost_protection and value >= unit.frost_protection.max
else None
),
),
MelCloudHomeNumberEntityDescription(
key="frost_protection_max_temp",
translation_key="frost_protection_max_temp",
device_class=NumberDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_category=EntityCategory.CONFIG,
native_min_value=0.0,
native_max_value=30.0,
native_step=0.5,
available_fn=lambda unit: (
unit.frost_protection is not None and unit.frost_protection.enabled
),
value_fn=lambda unit: (
unit.frost_protection.max if unit.frost_protection else None
),
set_value_fn=lambda client, unit, value: client.set_frost_protection(
enabled=unit.frost_protection.enabled
if unit.frost_protection
else False,
min_temp=unit.frost_protection.min if unit.frost_protection else 0.0,
max_temp=value,
**unit_ids(unit),
),
validate_fn=lambda unit, value: (
"temperature_max_below_min"
if unit.frost_protection and value <= unit.frost_protection.min
else None
),
),
MelCloudHomeNumberEntityDescription(
key="overheat_protection_min_temp",
translation_key="overheat_protection_min_temp",
device_class=NumberDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_category=EntityCategory.CONFIG,
native_min_value=overheat_min_temp,
native_max_value=overheat_max_temp,
native_step=0.5,
available_fn=lambda unit: (
unit.overheat_protection is not None
and unit.overheat_protection.enabled
),
value_fn=lambda unit: (
unit.overheat_protection.min if unit.overheat_protection else None
),
set_value_fn=lambda client, unit, value: client.set_overheat_protection(
enabled=unit.overheat_protection.enabled
if unit.overheat_protection
else False,
min_temp=value,
max_temp=unit.overheat_protection.max
if unit.overheat_protection
else 0.0,
**unit_ids(unit),
),
validate_fn=lambda unit, value: (
"temperature_min_exceeds_max"
if unit.overheat_protection and value >= unit.overheat_protection.max
else None
),
),
MelCloudHomeNumberEntityDescription(
key="overheat_protection_max_temp",
translation_key="overheat_protection_max_temp",
device_class=NumberDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
entity_category=EntityCategory.CONFIG,
native_min_value=overheat_min_temp,
native_max_value=overheat_max_temp,
native_step=0.5,
available_fn=lambda unit: (
unit.overheat_protection is not None
and unit.overheat_protection.enabled
),
value_fn=lambda unit: (
unit.overheat_protection.max if unit.overheat_protection else None
),
set_value_fn=lambda client, unit, value: client.set_overheat_protection(
enabled=unit.overheat_protection.enabled
if unit.overheat_protection
else False,
min_temp=unit.overheat_protection.min
if unit.overheat_protection
else 0.0,
max_temp=value,
**unit_ids(unit),
),
validate_fn=lambda unit, value: (
"temperature_max_below_min"
if unit.overheat_protection and value <= unit.overheat_protection.min
else None
),
),
)
ATA_NUMBERS: tuple[MelCloudHomeNumberEntityDescription[ATAUnit], ...] = (
_number_descriptions(ATAUnit, overheat_min_temp=31.0, overheat_max_temp=40.0)
)
ATW_NUMBERS: tuple[MelCloudHomeNumberEntityDescription[ATWUnit], ...] = (
_number_descriptions(ATWUnit, overheat_min_temp=20.0, overheat_max_temp=60.0)
)
async def async_setup_entry(
hass: HomeAssistant,
entry: MelCloudHomeConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up MELCloud Home numbers."""
coordinator = entry.runtime_data.coordinator
async_setup_unit_entities(
coordinator,
async_add_entities,
lambda units: (
ATANumber(coordinator, entity_description, unit)
for entity_description in ATA_NUMBERS
for unit in units
),
lambda units: (
ATWNumber(coordinator, entity_description, unit)
for entity_description in ATW_NUMBERS
for unit in units
),
)
class ATANumber(MelCloudHomeATAUnitEntity, NumberEntity):
"""Representation of a MELCloud Home ATA number."""
entity_description: MelCloudHomeNumberEntityDescription[ATAUnit]
def __init__(
self,
coordinator: MelCloudHomeCoordinator,
entity_description: MelCloudHomeNumberEntityDescription[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 available(self) -> bool:
"""Return if the entity is available."""
return super().available and self.entity_description.available_fn(self.unit)
@property
@override
def native_value(self) -> float | None:
"""Return the current value."""
return self.entity_description.value_fn(self.unit)
@override
async def async_set_native_value(self, value: float) -> None:
"""Set the protection temperature threshold."""
if self.entity_description.validate_fn and (
error_key := self.entity_description.validate_fn(self.unit, value)
):
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key=error_key,
)
await perform_action(
self.coordinator,
self.entity_description.set_value_fn(
self.coordinator.client, self.unit, value
),
)
class ATWNumber(MelCloudHomeATWUnitEntity, NumberEntity):
"""Representation of a MELCloud Home ATW number."""
entity_description: MelCloudHomeNumberEntityDescription[ATWUnit]
def __init__(
self,
coordinator: MelCloudHomeCoordinator,
entity_description: MelCloudHomeNumberEntityDescription[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 available(self) -> bool:
"""Return if the entity is available."""
return super().available and self.entity_description.available_fn(self.unit)
@property
@override
def native_value(self) -> float | None:
"""Return the current value."""
return self.entity_description.value_fn(self.unit)
@override
async def async_set_native_value(self, value: float) -> None:
"""Set the protection temperature threshold."""
if self.entity_description.validate_fn and (
error_key := self.entity_description.validate_fn(self.unit, value)
):
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key=error_key,
)
await perform_action(
self.coordinator,
self.entity_description.set_value_fn(
self.coordinator.client, self.unit, value
),
)