mirror of https://github.com/home-assistant/core
335 lines
11 KiB
Python
335 lines
11 KiB
Python
"""Support for climate entities."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import logging
|
|
from typing import Any
|
|
|
|
from thinqconnect import DeviceType
|
|
from thinqconnect.integration import ExtendedProperty
|
|
|
|
from homeassistant.components.climate import (
|
|
ATTR_TARGET_TEMP_HIGH,
|
|
ATTR_TARGET_TEMP_LOW,
|
|
FAN_OFF,
|
|
ClimateEntity,
|
|
ClimateEntityDescription,
|
|
ClimateEntityFeature,
|
|
HVACMode,
|
|
)
|
|
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.temperature import display_temp
|
|
|
|
from . import ThinqConfigEntry
|
|
from .coordinator import DeviceDataUpdateCoordinator
|
|
from .entity import ThinQEntity
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class ThinQClimateEntityDescription(ClimateEntityDescription):
|
|
"""Describes ThinQ climate entity."""
|
|
|
|
min_temp: float | None = None
|
|
max_temp: float | None = None
|
|
step: float | None = None
|
|
|
|
|
|
DEVIE_TYPE_CLIMATE_MAP: dict[DeviceType, tuple[ThinQClimateEntityDescription, ...]] = {
|
|
DeviceType.AIR_CONDITIONER: (
|
|
ThinQClimateEntityDescription(
|
|
key=ExtendedProperty.CLIMATE_AIR_CONDITIONER,
|
|
name=None,
|
|
translation_key=ExtendedProperty.CLIMATE_AIR_CONDITIONER,
|
|
),
|
|
),
|
|
DeviceType.SYSTEM_BOILER: (
|
|
ThinQClimateEntityDescription(
|
|
key=ExtendedProperty.CLIMATE_SYSTEM_BOILER,
|
|
name=None,
|
|
min_temp=16,
|
|
max_temp=30,
|
|
step=1,
|
|
),
|
|
),
|
|
}
|
|
|
|
STR_TO_HVAC: dict[str, HVACMode] = {
|
|
"air_dry": HVACMode.DRY,
|
|
"auto": HVACMode.AUTO,
|
|
"cool": HVACMode.COOL,
|
|
"fan": HVACMode.FAN_ONLY,
|
|
"heat": HVACMode.HEAT,
|
|
}
|
|
|
|
HVAC_TO_STR: dict[HVACMode, str] = {
|
|
HVACMode.AUTO: "auto",
|
|
HVACMode.COOL: "cool",
|
|
HVACMode.DRY: "air_dry",
|
|
HVACMode.FAN_ONLY: "fan",
|
|
HVACMode.HEAT: "heat",
|
|
}
|
|
|
|
THINQ_PRESET_MODE: list[str] = ["air_clean", "aroma", "energy_saving"]
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ThinqConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up an entry for climate platform."""
|
|
entities: list[ThinQClimateEntity] = []
|
|
for coordinator in entry.runtime_data.coordinators.values():
|
|
if (
|
|
descriptions := DEVIE_TYPE_CLIMATE_MAP.get(
|
|
coordinator.api.device.device_type
|
|
)
|
|
) is not None:
|
|
for description in descriptions:
|
|
entities.extend(
|
|
ThinQClimateEntity(coordinator, description, property_id)
|
|
for property_id in coordinator.api.get_active_idx(description.key)
|
|
)
|
|
|
|
if entities:
|
|
async_add_entities(entities)
|
|
|
|
|
|
class ThinQClimateEntity(ThinQEntity, ClimateEntity):
|
|
"""Represent a thinq climate platform."""
|
|
|
|
entity_description: ThinQClimateEntityDescription
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: DeviceDataUpdateCoordinator,
|
|
entity_description: ThinQClimateEntityDescription,
|
|
property_id: str,
|
|
) -> None:
|
|
"""Initialize a climate entity."""
|
|
super().__init__(coordinator, entity_description, property_id)
|
|
|
|
self._attr_supported_features = (
|
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
|
| ClimateEntityFeature.TURN_ON
|
|
| ClimateEntityFeature.TURN_OFF
|
|
)
|
|
self._attr_hvac_modes = [HVACMode.OFF]
|
|
self._attr_hvac_mode = HVACMode.OFF
|
|
self._attr_preset_modes = []
|
|
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
|
self._requested_hvac_mode: str | None = None
|
|
|
|
# Set up HVAC modes.
|
|
for mode in self.data.hvac_modes:
|
|
if mode in STR_TO_HVAC:
|
|
self._attr_hvac_modes.append(STR_TO_HVAC[mode])
|
|
elif mode in THINQ_PRESET_MODE:
|
|
self._attr_preset_modes.append(mode)
|
|
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
|
|
|
# Set up fan modes.
|
|
self._attr_fan_modes = self.data.fan_modes
|
|
if self.fan_modes:
|
|
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
|
|
|
|
# Supports target temperature range.
|
|
if self.data.support_temperature_range:
|
|
self._attr_supported_features |= (
|
|
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
|
)
|
|
|
|
def _update_status(self) -> None:
|
|
"""Update status itself."""
|
|
super()._update_status()
|
|
|
|
# Update fan, hvac and preset mode.
|
|
if self.data.is_on:
|
|
if self.supported_features & ClimateEntityFeature.FAN_MODE:
|
|
self._attr_fan_mode = self.data.fan_mode
|
|
|
|
hvac_mode = self._requested_hvac_mode or self.data.hvac_mode
|
|
if hvac_mode in STR_TO_HVAC:
|
|
self._attr_hvac_mode = STR_TO_HVAC.get(hvac_mode)
|
|
self._attr_preset_mode = None
|
|
elif hvac_mode in THINQ_PRESET_MODE:
|
|
self._attr_preset_mode = hvac_mode
|
|
else:
|
|
if self.supported_features & ClimateEntityFeature.FAN_MODE:
|
|
self._attr_fan_mode = FAN_OFF
|
|
|
|
self._attr_hvac_mode = HVACMode.OFF
|
|
self._attr_preset_mode = None
|
|
|
|
self.reset_requested_hvac_mode()
|
|
self._attr_current_humidity = self.data.humidity
|
|
self._attr_current_temperature = self.data.current_temp
|
|
|
|
if (max_temp := self.entity_description.max_temp) is not None or (
|
|
max_temp := self.data.max
|
|
) is not None:
|
|
self._attr_max_temp = max_temp
|
|
if (min_temp := self.entity_description.min_temp) is not None or (
|
|
min_temp := self.data.min
|
|
) is not None:
|
|
self._attr_min_temp = min_temp
|
|
if (step := self.entity_description.step) is not None or (
|
|
step := self.data.step
|
|
) is not None:
|
|
self._attr_target_temperature_step = step
|
|
|
|
# Update target temperatures.
|
|
if (
|
|
self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
|
and self.hvac_mode == HVACMode.AUTO
|
|
):
|
|
self._attr_target_temperature = None
|
|
self._attr_target_temperature_high = self.data.target_temp_high
|
|
self._attr_target_temperature_low = self.data.target_temp_low
|
|
else:
|
|
self._attr_target_temperature = self.data.target_temp
|
|
self._attr_target_temperature_high = None
|
|
self._attr_target_temperature_low = None
|
|
|
|
_LOGGER.debug(
|
|
"[%s:%s] update status: %s/%s -> %s/%s, hvac:%s, unit:%s, step:%s",
|
|
self.coordinator.device_name,
|
|
self.property_id,
|
|
self.data.current_temp,
|
|
self.data.target_temp,
|
|
self.current_temperature,
|
|
self.target_temperature,
|
|
self.hvac_mode,
|
|
self.temperature_unit,
|
|
self.target_temperature_step,
|
|
)
|
|
|
|
def reset_requested_hvac_mode(self) -> None:
|
|
"""Cancel request to set hvac mode."""
|
|
self._requested_hvac_mode = None
|
|
|
|
async def async_turn_on(self) -> None:
|
|
"""Turn the entity on."""
|
|
_LOGGER.debug(
|
|
"[%s:%s] async_turn_on", self.coordinator.device_name, self.property_id
|
|
)
|
|
await self.async_call_api(self.coordinator.api.async_turn_on(self.property_id))
|
|
|
|
async def async_turn_off(self) -> None:
|
|
"""Turn the entity off."""
|
|
_LOGGER.debug(
|
|
"[%s:%s] async_turn_off", self.coordinator.device_name, self.property_id
|
|
)
|
|
await self.async_call_api(self.coordinator.api.async_turn_off(self.property_id))
|
|
|
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
|
"""Set new target hvac mode."""
|
|
if hvac_mode == HVACMode.OFF:
|
|
await self.async_turn_off()
|
|
return
|
|
|
|
# If device is off, turn on first.
|
|
if not self.data.is_on:
|
|
await self.async_turn_on()
|
|
|
|
# When we request hvac mode while turning on the device, the previously set
|
|
# hvac mode is displayed first and then switches to the requested hvac mode.
|
|
# To prevent this, set the requested hvac mode here so that it will be set
|
|
# immediately on the next update.
|
|
self._requested_hvac_mode = HVAC_TO_STR.get(hvac_mode)
|
|
|
|
_LOGGER.debug(
|
|
"[%s:%s] async_set_hvac_mode: %s",
|
|
self.coordinator.device_name,
|
|
self.property_id,
|
|
hvac_mode,
|
|
)
|
|
await self.async_call_api(
|
|
self.coordinator.api.async_set_hvac_mode(
|
|
self.property_id, self._requested_hvac_mode
|
|
),
|
|
self.reset_requested_hvac_mode,
|
|
)
|
|
|
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
|
"""Set new preset mode."""
|
|
_LOGGER.debug(
|
|
"[%s:%s] async_set_preset_mode: %s",
|
|
self.coordinator.device_name,
|
|
self.property_id,
|
|
preset_mode,
|
|
)
|
|
await self.async_call_api(
|
|
self.coordinator.api.async_set_hvac_mode(self.property_id, preset_mode)
|
|
)
|
|
|
|
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
|
"""Set new target fan mode."""
|
|
_LOGGER.debug(
|
|
"[%s:%s] async_set_fan_mode: %s",
|
|
self.coordinator.device_name,
|
|
self.property_id,
|
|
fan_mode,
|
|
)
|
|
await self.async_call_api(
|
|
self.coordinator.api.async_set_fan_mode(self.property_id, fan_mode)
|
|
)
|
|
|
|
def _round_by_step(self, temperature: float) -> float:
|
|
"""Round the value by step."""
|
|
if (
|
|
target_temp := display_temp(
|
|
self.coordinator.hass,
|
|
temperature,
|
|
self.coordinator.hass.config.units.temperature_unit,
|
|
self.target_temperature_step or 1,
|
|
)
|
|
) is not None:
|
|
return target_temp
|
|
|
|
return temperature
|
|
|
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
|
"""Set new target temperature."""
|
|
_LOGGER.debug(
|
|
"[%s:%s] async_set_temperature: %s",
|
|
self.coordinator.device_name,
|
|
self.property_id,
|
|
kwargs,
|
|
)
|
|
|
|
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is not None:
|
|
if (
|
|
target_temp := self._round_by_step(temperature)
|
|
) != self.target_temperature:
|
|
await self.async_call_api(
|
|
self.coordinator.api.async_set_target_temperature(
|
|
self.property_id, target_temp
|
|
)
|
|
)
|
|
|
|
if (temperature_low := kwargs.get(ATTR_TARGET_TEMP_LOW)) is not None:
|
|
if (
|
|
target_temp_low := self._round_by_step(temperature_low)
|
|
) != self.target_temperature_low:
|
|
await self.async_call_api(
|
|
self.coordinator.api.async_set_target_temperature_low(
|
|
self.property_id, target_temp_low
|
|
)
|
|
)
|
|
|
|
if (temperature_high := kwargs.get(ATTR_TARGET_TEMP_HIGH)) is not None:
|
|
if (
|
|
target_temp_high := self._round_by_step(temperature_high)
|
|
) != self.target_temperature_high:
|
|
await self.async_call_api(
|
|
self.coordinator.api.async_set_target_temperature_high(
|
|
self.property_id, target_temp_high
|
|
)
|
|
)
|