252 lines
8.1 KiB
Python
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()
|