core/homeassistant/components/miele/climate.py

252 lines
8.1 KiB
Python

"""Platform for Miele integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Any, Final, cast
import aiohttp
from pymiele import MieleDevice
from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityDescription,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DEVICE_TYPE_TAGS, DISABLED_TEMP_ENTITIES, DOMAIN, MieleAppliance
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
from .entity import MieleEntity
PARALLEL_UPDATES = 1
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True, kw_only=True)
class MieleClimateDescription(ClimateEntityDescription):
"""Class describing Miele climate entities."""
value_fn: Callable[[MieleDevice], StateType]
target_fn: Callable[[MieleDevice], StateType]
zone: int = 1
@dataclass
class MieleClimateDefinition:
"""Class for defining climate entities."""
types: tuple[MieleAppliance, ...]
description: MieleClimateDescription
CLIMATE_TYPES: Final[tuple[MieleClimateDefinition, ...]] = (
MieleClimateDefinition(
types=(
MieleAppliance.FRIDGE,
MieleAppliance.FREEZER,
MieleAppliance.FRIDGE_FREEZER,
MieleAppliance.WINE_CABINET,
MieleAppliance.WINE_CONDITIONING_UNIT,
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
MieleAppliance.WINE_CABINET_FREEZER,
),
description=MieleClimateDescription(
key="thermostat",
value_fn=(
lambda value: cast(int, value.state_temperatures[0].temperature) / 100.0
),
target_fn=(
lambda value: cast(int, value.state_target_temperature[0].temperature)
/ 100.0
),
zone=1,
),
),
MieleClimateDefinition(
types=(
MieleAppliance.FRIDGE,
MieleAppliance.FREEZER,
MieleAppliance.FRIDGE_FREEZER,
MieleAppliance.WINE_CABINET,
MieleAppliance.WINE_CONDITIONING_UNIT,
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
MieleAppliance.WINE_CABINET_FREEZER,
),
description=MieleClimateDescription(
key="thermostat2",
value_fn=(
lambda value: cast(int, value.state_temperatures[1].temperature) / 100.0
),
target_fn=(
lambda value: cast(int, value.state_target_temperature[1].temperature)
/ 100.0
),
translation_key="zone_2",
zone=2,
),
),
MieleClimateDefinition(
types=(
MieleAppliance.FRIDGE,
MieleAppliance.FREEZER,
MieleAppliance.FRIDGE_FREEZER,
MieleAppliance.WINE_CABINET,
MieleAppliance.WINE_CONDITIONING_UNIT,
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
MieleAppliance.WINE_CABINET_FREEZER,
),
description=MieleClimateDescription(
key="thermostat3",
value_fn=(
lambda value: cast(int, value.state_temperatures[2].temperature) / 100.0
),
target_fn=(
lambda value: cast(int, value.state_target_temperature[2].temperature)
/ 100.0
),
translation_key="zone_3",
zone=3,
),
),
)
ZONE1_DEVICES = {
MieleAppliance.FRIDGE: DEVICE_TYPE_TAGS[MieleAppliance.FRIDGE],
MieleAppliance.FRIDGE_FREEZER: DEVICE_TYPE_TAGS[MieleAppliance.FRIDGE],
MieleAppliance.FREEZER: DEVICE_TYPE_TAGS[MieleAppliance.FREEZER],
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: MieleConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the climate platform."""
coordinator = config_entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities(
MieleClimate(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items()
for definition in CLIMATE_TYPES
if (
device_id in new_devices_set
and device.device_type in definition.types
and (
definition.description.value_fn(device)
not in DISABLED_TEMP_ENTITIES
)
)
)
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleClimate(MieleEntity, ClimateEntity):
"""Representation of a climate entity."""
entity_description: MieleClimateDescription
_attr_precision = PRECISION_WHOLE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = 1.0
_attr_hvac_modes = [HVACMode.COOL]
_attr_hvac_mode = HVACMode.COOL
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return cast(float, self.entity_description.value_fn(self.device))
def __init__(
self,
coordinator: MieleDataUpdateCoordinator,
device_id: str,
description: MieleClimateDescription,
) -> None:
"""Initialize the climate entity."""
super().__init__(coordinator, device_id, description)
t_key = self.entity_description.translation_key
if description.zone == 1:
t_key = ZONE1_DEVICES.get(
cast(MieleAppliance, self.device.device_type), "zone_1"
)
if self.device.device_type in (
MieleAppliance.FRIDGE,
MieleAppliance.FREEZER,
):
self._attr_name = None
if description.zone == 2:
t_key = "zone_2"
if self.device.device_type in (
MieleAppliance.FRIDGE_FREEZER,
MieleAppliance.WINE_CABINET_FREEZER,
):
t_key = DEVICE_TYPE_TAGS[MieleAppliance.FREEZER]
elif description.zone == 3:
t_key = "zone_3"
self._attr_translation_key = t_key
self._attr_unique_id = f"{device_id}-{description.key}-{description.zone}"
@property
def target_temperature(self) -> float | None:
"""Return the target temperature."""
return cast(float | None, self.entity_description.target_fn(self.device))
@property
def max_temp(self) -> float:
"""Return the maximum target temperature."""
return cast(
float,
self.action.target_temperature[self.entity_description.zone - 1].max,
)
@property
def min_temp(self) -> float:
"""Return the minimum target temperature."""
return cast(
float,
self.action.target_temperature[self.entity_description.zone - 1].min,
)
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
try:
await self.api.set_target_temperature(
self._device_id,
cast(float, kwargs.get(ATTR_TEMPERATURE)),
self.entity_description.zone,
)
except aiohttp.ClientError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_state_error",
translation_placeholders={
"entity": self.entity_id,
},
) from err
await self.coordinator.async_request_refresh()