Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for climate entities."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 import logging
7 from typing import Any
8 
9 from thinqconnect import DeviceType
10 from thinqconnect.integration import ExtendedProperty
11 
13  ATTR_TARGET_TEMP_HIGH,
14  ATTR_TARGET_TEMP_LOW,
15  ClimateEntity,
16  ClimateEntityDescription,
17  ClimateEntityFeature,
18  HVACMode,
19 )
20 from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
21 from homeassistant.core import HomeAssistant
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
23 from homeassistant.helpers.temperature import display_temp
24 
25 from . import ThinqConfigEntry
26 from .coordinator import DeviceDataUpdateCoordinator
27 from .entity import ThinQEntity
28 
29 
30 @dataclass(frozen=True, kw_only=True)
32  """Describes ThinQ climate entity."""
33 
34  min_temp: float | None = None
35  max_temp: float | None = None
36  step: float | None = None
37 
38 
39 DEVICE_TYPE_CLIMATE_MAP: dict[DeviceType, tuple[ThinQClimateEntityDescription, ...]] = {
40  DeviceType.AIR_CONDITIONER: (
42  key=ExtendedProperty.CLIMATE_AIR_CONDITIONER,
43  name=None,
44  translation_key=ExtendedProperty.CLIMATE_AIR_CONDITIONER,
45  ),
46  ),
47  DeviceType.SYSTEM_BOILER: (
49  key=ExtendedProperty.CLIMATE_SYSTEM_BOILER,
50  name=None,
51  min_temp=16,
52  max_temp=30,
53  step=1,
54  ),
55  ),
56 }
57 
58 STR_TO_HVAC: dict[str, HVACMode] = {
59  "air_dry": HVACMode.DRY,
60  "auto": HVACMode.AUTO,
61  "cool": HVACMode.COOL,
62  "fan": HVACMode.FAN_ONLY,
63  "heat": HVACMode.HEAT,
64 }
65 
66 HVAC_TO_STR: dict[HVACMode, str] = {
67  HVACMode.AUTO: "auto",
68  HVACMode.COOL: "cool",
69  HVACMode.DRY: "air_dry",
70  HVACMode.FAN_ONLY: "fan",
71  HVACMode.HEAT: "heat",
72 }
73 
74 THINQ_PRESET_MODE: list[str] = ["air_clean", "aroma", "energy_saving"]
75 
76 _LOGGER = logging.getLogger(__name__)
77 
78 
80  hass: HomeAssistant,
81  entry: ThinqConfigEntry,
82  async_add_entities: AddEntitiesCallback,
83 ) -> None:
84  """Set up an entry for climate platform."""
85  entities: list[ThinQClimateEntity] = []
86  for coordinator in entry.runtime_data.coordinators.values():
87  if (
88  descriptions := DEVICE_TYPE_CLIMATE_MAP.get(
89  coordinator.api.device.device_type
90  )
91  ) is not None:
92  for description in descriptions:
93  entities.extend(
94  ThinQClimateEntity(coordinator, description, property_id)
95  for property_id in coordinator.api.get_active_idx(description.key)
96  )
97 
98  if entities:
99  async_add_entities(entities)
100 
101 
103  """Represent a thinq climate platform."""
104 
105  entity_description: ThinQClimateEntityDescription
106 
107  def __init__(
108  self,
109  coordinator: DeviceDataUpdateCoordinator,
110  entity_description: ThinQClimateEntityDescription,
111  property_id: str,
112  ) -> None:
113  """Initialize a climate entity."""
114  super().__init__(coordinator, entity_description, property_id)
115 
116  self._attr_supported_features_attr_supported_features = (
117  ClimateEntityFeature.TARGET_TEMPERATURE
118  | ClimateEntityFeature.TURN_ON
119  | ClimateEntityFeature.TURN_OFF
120  )
121  self._attr_hvac_modes_attr_hvac_modes = [HVACMode.OFF]
122  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
123  self._attr_preset_modes_attr_preset_modes = []
124  self._attr_temperature_unit_attr_temperature_unit = UnitOfTemperature.CELSIUS
125  self._requested_hvac_mode_requested_hvac_mode: str | None = None
126 
127  # Set up HVAC modes.
128  for mode in self.datadatadatadata.hvac_modes:
129  if mode in STR_TO_HVAC:
130  self._attr_hvac_modes_attr_hvac_modes.append(STR_TO_HVAC[mode])
131  elif mode in THINQ_PRESET_MODE:
132  self._attr_preset_modes_attr_preset_modes.append(mode)
133  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.PRESET_MODE
134 
135  # Set up fan modes.
136  self._attr_fan_modes_attr_fan_modes = self.datadatadatadata.fan_modes
137  if self.fan_modesfan_modes:
138  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.FAN_MODE
139 
140  # Supports target temperature range.
141  if self.datadatadatadata.support_temperature_range:
142  self._attr_supported_features_attr_supported_features |= (
143  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
144  )
145 
146  def _update_status(self) -> None:
147  """Update status itself."""
148  super()._update_status()
149 
150  # Update fan, hvac and preset mode.
151  if self.supported_featuressupported_featuressupported_features & ClimateEntityFeature.FAN_MODE:
152  self._attr_fan_mode_attr_fan_mode = self.datadatadatadata.fan_mode
153  if self.datadatadatadata.is_on:
154  hvac_mode = self._requested_hvac_mode_requested_hvac_mode or self.datadatadatadata.hvac_mode
155  if hvac_mode in STR_TO_HVAC:
156  self._attr_hvac_mode_attr_hvac_mode = STR_TO_HVAC.get(hvac_mode)
157  self._attr_preset_mode_attr_preset_mode = None
158  elif hvac_mode in THINQ_PRESET_MODE:
159  self._attr_preset_mode_attr_preset_mode = hvac_mode
160  else:
161  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
162  self._attr_preset_mode_attr_preset_mode = None
163 
164  self.reset_requested_hvac_modereset_requested_hvac_mode()
165  self._attr_current_humidity_attr_current_humidity = self.datadatadatadata.humidity
166  self._attr_current_temperature_attr_current_temperature = self.datadatadatadata.current_temp
167 
168  # Update min, max and step.
169  if (max_temp := self.entity_descriptionentity_description.max_temp) is not None or (
170  max_temp := self.datadatadatadata.max
171  ) is not None:
172  self._attr_max_temp_attr_max_temp = max_temp
173  if (min_temp := self.entity_descriptionentity_description.min_temp) is not None or (
174  min_temp := self.datadatadatadata.min
175  ) is not None:
176  self._attr_min_temp_attr_min_temp = min_temp
177  if (step := self.entity_descriptionentity_description.step) is not None or (
178  step := self.datadatadatadata.step
179  ) is not None:
180  self._attr_target_temperature_step_attr_target_temperature_step = step
181 
182  # Update target temperatures.
183  self._attr_target_temperature_attr_target_temperature = self.datadatadatadata.target_temp
184  self._attr_target_temperature_high_attr_target_temperature_high = self.datadatadatadata.target_temp_high
185  self._attr_target_temperature_low_attr_target_temperature_low = self.datadatadatadata.target_temp_low
186 
187  _LOGGER.debug(
188  "[%s:%s] update status: c:%s, t:%s, l:%s, h:%s, hvac:%s, unit:%s, step:%s",
189  self.coordinator.device_name,
190  self.property_idproperty_id,
191  self.current_temperaturecurrent_temperature,
192  self.target_temperaturetarget_temperature,
193  self.target_temperature_lowtarget_temperature_low,
194  self.target_temperature_hightarget_temperature_high,
195  self.hvac_modehvac_modehvac_mode,
196  self.temperature_unittemperature_unit,
197  self.target_temperature_steptarget_temperature_step,
198  )
199 
200  def reset_requested_hvac_mode(self) -> None:
201  """Cancel request to set hvac mode."""
202  self._requested_hvac_mode_requested_hvac_mode = None
203 
204  async def async_turn_on(self) -> None:
205  """Turn the entity on."""
206  _LOGGER.debug(
207  "[%s:%s] async_turn_on", self.coordinator.device_name, self.property_idproperty_id
208  )
209  await self.async_call_apiasync_call_api(self.coordinator.api.async_turn_on(self.property_idproperty_id))
210 
211  async def async_turn_off(self) -> None:
212  """Turn the entity off."""
213  _LOGGER.debug(
214  "[%s:%s] async_turn_off", self.coordinator.device_name, self.property_idproperty_id
215  )
216  await self.async_call_apiasync_call_api(self.coordinator.api.async_turn_off(self.property_idproperty_id))
217 
218  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
219  """Set new target hvac mode."""
220  if hvac_mode == HVACMode.OFF:
221  await self.async_turn_offasync_turn_offasync_turn_off()
222  return
223 
224  # If device is off, turn on first.
225  if not self.datadatadatadata.is_on:
226  await self.async_turn_onasync_turn_onasync_turn_on()
227 
228  # When we request hvac mode while turning on the device, the previously set
229  # hvac mode is displayed first and then switches to the requested hvac mode.
230  # To prevent this, set the requested hvac mode here so that it will be set
231  # immediately on the next update.
232  self._requested_hvac_mode_requested_hvac_mode = HVAC_TO_STR.get(hvac_mode)
233 
234  _LOGGER.debug(
235  "[%s:%s] async_set_hvac_mode: %s",
236  self.coordinator.device_name,
237  self.property_idproperty_id,
238  hvac_mode,
239  )
240  await self.async_call_apiasync_call_api(
241  self.coordinator.api.async_set_hvac_mode(
242  self.property_idproperty_id, self._requested_hvac_mode_requested_hvac_mode
243  ),
244  self.reset_requested_hvac_modereset_requested_hvac_mode,
245  )
246 
247  async def async_set_preset_mode(self, preset_mode: str) -> None:
248  """Set new preset mode."""
249  _LOGGER.debug(
250  "[%s:%s] async_set_preset_mode: %s",
251  self.coordinator.device_name,
252  self.property_idproperty_id,
253  preset_mode,
254  )
255  await self.async_call_apiasync_call_api(
256  self.coordinator.api.async_set_hvac_mode(self.property_idproperty_id, preset_mode)
257  )
258 
259  async def async_set_fan_mode(self, fan_mode: str) -> None:
260  """Set new target fan mode."""
261  _LOGGER.debug(
262  "[%s:%s] async_set_fan_mode: %s",
263  self.coordinator.device_name,
264  self.property_idproperty_id,
265  fan_mode,
266  )
267  await self.async_call_apiasync_call_api(
268  self.coordinator.api.async_set_fan_mode(self.property_idproperty_id, fan_mode)
269  )
270 
271  def _round_by_step(self, temperature: float) -> float:
272  """Round the value by step."""
273  if (
274  target_temp := display_temp(
275  self.coordinator.hass,
276  temperature,
277  self.coordinator.hass.config.units.temperature_unit,
278  self.target_temperature_steptarget_temperature_step or 1,
279  )
280  ) is not None:
281  return target_temp
282 
283  return temperature
284 
285  async def async_set_temperature(self, **kwargs: Any) -> None:
286  """Set new target temperature."""
287  _LOGGER.debug(
288  "[%s:%s] async_set_temperature: %s",
289  self.coordinator.device_name,
290  self.property_idproperty_id,
291  kwargs,
292  )
293 
294  if (temperature := kwargs.get(ATTR_TEMPERATURE)) is not None:
295  if (
296  target_temp := self._round_by_step_round_by_step(temperature)
297  ) != self.target_temperaturetarget_temperature:
298  await self.async_call_apiasync_call_api(
299  self.coordinator.api.async_set_target_temperature(
300  self.property_idproperty_id, target_temp
301  )
302  )
303 
304  if (temperature_low := kwargs.get(ATTR_TARGET_TEMP_LOW)) is not None:
305  if (
306  target_temp_low := self._round_by_step_round_by_step(temperature_low)
307  ) != self.target_temperature_lowtarget_temperature_low:
308  await self.async_call_apiasync_call_api(
309  self.coordinator.api.async_set_target_temperature_low(
310  self.property_idproperty_id, target_temp_low
311  )
312  )
313 
314  if (temperature_high := kwargs.get(ATTR_TARGET_TEMP_HIGH)) is not None:
315  if (
316  target_temp_high := self._round_by_step_round_by_step(temperature_high)
317  ) != self.target_temperature_hightarget_temperature_high:
318  await self.async_call_apiasync_call_api(
319  self.coordinator.api.async_set_target_temperature_high(
320  self.property_idproperty_id, target_temp_high
321  )
322  )
ClimateEntityFeature supported_features(self)
Definition: __init__.py:945
None __init__(self, DeviceDataUpdateCoordinator coordinator, ThinQClimateEntityDescription entity_description, str property_id)
Definition: climate.py:112
None async_call_api(self, Coroutine[Any, Any, Any] target, Callable[[], None]|None on_fail_method=None)
Definition: entity.py:101
int|None supported_features(self)
Definition: entity.py:861
None async_setup_entry(HomeAssistant hass, ThinqConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:83
float|None display_temp(HomeAssistant hass, float|None temperature, str unit, float precision)
Definition: temperature.py:14