"""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 from aiohttp import ClientResponseError from pymiele import MieleDevice, MieleTemperature 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__) def _get_temperature_value( temperatures: list[MieleTemperature], index: int ) -> float | None: """Return the temperature value for the given index.""" if len(temperatures) > index: return cast(int, temperatures[index].temperature) / 100.0 return None @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: _get_temperature_value(value.state_temperatures, 0) ), target_fn=( lambda value: _get_temperature_value(value.state_target_temperature, 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: _get_temperature_value(value.state_temperatures, 1) ), target_fn=( lambda value: _get_temperature_value(value.state_target_temperature, 1) ), 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: _get_temperature_value(value.state_temperatures, 2) ), target_fn=( lambda value: _get_temperature_value(value.state_target_temperature, 2) ), 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.""" if len(self.action.target_temperature) < self.entity_description.zone: return super().max_temp return cast( float, self.action.target_temperature[self.entity_description.zone - 1].max, ) @property def min_temp(self) -> float: """Return the minimum target temperature.""" if len(self.action.target_temperature) < self.entity_description.zone: return super().min_temp 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 ClientResponseError as err: _LOGGER.debug("Error setting climate state for %s: %s", self.entity_id, 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()