Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for the Airzone Cloud climate."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, Final
6 
7 from aioairzone_cloud.common import OperationAction, OperationMode, TemperatureUnit
8 from aioairzone_cloud.const import (
9  API_MODE,
10  API_OPTS,
11  API_PARAMS,
12  API_POWER,
13  API_SETPOINT,
14  API_SP_AIR_COOL,
15  API_SP_AIR_HEAT,
16  API_SPEED_CONF,
17  API_UNITS,
18  API_VALUE,
19  AZD_ACTION,
20  AZD_AIDOOS,
21  AZD_DOUBLE_SET_POINT,
22  AZD_GROUPS,
23  AZD_HUMIDITY,
24  AZD_INSTALLATIONS,
25  AZD_MASTER,
26  AZD_MODE,
27  AZD_MODES,
28  AZD_NUM_DEVICES,
29  AZD_NUM_GROUPS,
30  AZD_POWER,
31  AZD_SPEED,
32  AZD_SPEEDS,
33  AZD_TEMP,
34  AZD_TEMP_SET,
35  AZD_TEMP_SET_COOL_AIR,
36  AZD_TEMP_SET_HOT_AIR,
37  AZD_TEMP_SET_MAX,
38  AZD_TEMP_SET_MIN,
39  AZD_TEMP_STEP,
40  AZD_ZONES,
41 )
42 
44  ATTR_HVAC_MODE,
45  ATTR_TARGET_TEMP_HIGH,
46  ATTR_TARGET_TEMP_LOW,
47  FAN_AUTO,
48  FAN_HIGH,
49  FAN_LOW,
50  FAN_MEDIUM,
51  ClimateEntity,
52  ClimateEntityFeature,
53  HVACAction,
54  HVACMode,
55 )
56 from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
57 from homeassistant.core import HomeAssistant, callback
58 from homeassistant.exceptions import HomeAssistantError
59 from homeassistant.helpers.entity_platform import AddEntitiesCallback
60 
61 from . import AirzoneCloudConfigEntry
62 from .coordinator import AirzoneUpdateCoordinator
63 from .entity import (
64  AirzoneAidooEntity,
65  AirzoneEntity,
66  AirzoneGroupEntity,
67  AirzoneInstallationEntity,
68  AirzoneZoneEntity,
69 )
70 
71 FAN_SPEED_AUTO: dict[int, str] = {
72  0: FAN_AUTO,
73 }
74 
75 FAN_SPEED_MAPS: Final[dict[int, dict[int, str]]] = {
76  2: {
77  1: FAN_LOW,
78  2: FAN_HIGH,
79  },
80  3: {
81  1: FAN_LOW,
82  2: FAN_MEDIUM,
83  3: FAN_HIGH,
84  },
85 }
86 
87 HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = {
88  OperationAction.COOLING: HVACAction.COOLING,
89  OperationAction.DRYING: HVACAction.DRYING,
90  OperationAction.FAN: HVACAction.FAN,
91  OperationAction.HEATING: HVACAction.HEATING,
92  OperationAction.IDLE: HVACAction.IDLE,
93  OperationAction.OFF: HVACAction.OFF,
94 }
95 HVAC_MODE_LIB_TO_HASS: Final[dict[OperationMode, HVACMode]] = {
96  OperationMode.STOP: HVACMode.OFF,
97  OperationMode.COOLING: HVACMode.COOL,
98  OperationMode.COOLING_AIR: HVACMode.COOL,
99  OperationMode.COOLING_RADIANT: HVACMode.COOL,
100  OperationMode.COOLING_COMBINED: HVACMode.COOL,
101  OperationMode.HEATING: HVACMode.HEAT,
102  OperationMode.HEAT_AIR: HVACMode.HEAT,
103  OperationMode.HEAT_RADIANT: HVACMode.HEAT,
104  OperationMode.HEAT_COMBINED: HVACMode.HEAT,
105  OperationMode.EMERGENCY_HEAT: HVACMode.HEAT,
106  OperationMode.VENTILATION: HVACMode.FAN_ONLY,
107  OperationMode.DRY: HVACMode.DRY,
108  OperationMode.AUTO: HVACMode.HEAT_COOL,
109 }
110 HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
111  HVACMode.OFF: OperationMode.STOP,
112  HVACMode.COOL: OperationMode.COOLING,
113  HVACMode.HEAT: OperationMode.HEATING,
114  HVACMode.FAN_ONLY: OperationMode.VENTILATION,
115  HVACMode.DRY: OperationMode.DRY,
116  HVACMode.HEAT_COOL: OperationMode.AUTO,
117 }
118 
119 
121  hass: HomeAssistant,
122  entry: AirzoneCloudConfigEntry,
123  async_add_entities: AddEntitiesCallback,
124 ) -> None:
125  """Add Airzone climate from a config_entry."""
126  coordinator = entry.runtime_data
127 
128  entities: list[AirzoneClimate] = []
129 
130  # Aidoos
131  for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items():
132  entities.append(
134  coordinator,
135  aidoo_id,
136  aidoo_data,
137  )
138  )
139 
140  # Groups
141  for group_id, group_data in coordinator.data.get(AZD_GROUPS, {}).items():
142  if group_data[AZD_NUM_DEVICES] > 1:
143  entities.append(
145  coordinator,
146  group_id,
147  group_data,
148  )
149  )
150 
151  # Installations
152  for inst_id, inst_data in coordinator.data.get(AZD_INSTALLATIONS, {}).items():
153  if inst_data[AZD_NUM_GROUPS] > 1:
154  entities.append(
156  coordinator,
157  inst_id,
158  inst_data,
159  )
160  )
161 
162  # Zones
163  for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items():
164  entities.append(
166  coordinator,
167  zone_id,
168  zone_data,
169  )
170  )
171 
172  async_add_entities(entities)
173 
174 
176  """Define an Airzone Cloud climate."""
177 
178  _attr_name = None
179  _attr_temperature_unit = UnitOfTemperature.CELSIUS
180  _enable_turn_on_off_backwards_compatibility = False
181 
182  def _init_attributes(self) -> None:
183  """Init common climate device attributes."""
184  self._attr_target_temperature_step_attr_target_temperature_step = self.get_airzone_valueget_airzone_value(AZD_TEMP_STEP)
185 
186  self._attr_hvac_modes_attr_hvac_modes = [
187  HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_valueget_airzone_value(AZD_MODES)
188  ]
189  if HVACMode.OFF not in self._attr_hvac_modes_attr_hvac_modes:
190  self._attr_hvac_modes_attr_hvac_modes += [HVACMode.OFF]
191 
192  if self.get_airzone_valueget_airzone_value(AZD_DOUBLE_SET_POINT):
193  self._attr_supported_features |= (
194  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
195  )
196 
197  if (
198  self.get_airzone_valueget_airzone_value(AZD_SPEED) is not None
199  and self.get_airzone_valueget_airzone_value(AZD_SPEEDS) is not None
200  ):
201  self._initialize_fan_speeds()
202 
203  @callback
204  def _handle_coordinator_update(self) -> None:
205  """Update attributes when the coordinator updates."""
206  self._async_update_attrs_async_update_attrs()
208 
209  @callback
210  def _async_update_attrs(self) -> None:
211  """Update climate attributes."""
212  self._attr_current_temperature_attr_current_temperature = self.get_airzone_valueget_airzone_value(AZD_TEMP)
213  self._attr_current_humidity_attr_current_humidity = self.get_airzone_valueget_airzone_value(AZD_HUMIDITY)
214  self._attr_hvac_action_attr_hvac_action = HVAC_ACTION_LIB_TO_HASS[
215  self.get_airzone_valueget_airzone_value(AZD_ACTION)
216  ]
217  if self.supported_featuressupported_featuressupported_features & ClimateEntityFeature.FAN_MODE:
218  self._attr_fan_mode_attr_fan_mode = self._speeds.get(self.get_airzone_valueget_airzone_value(AZD_SPEED))
219  if self.get_airzone_valueget_airzone_value(AZD_POWER):
220  self._attr_hvac_mode_attr_hvac_mode = HVAC_MODE_LIB_TO_HASS[
221  self.get_airzone_valueget_airzone_value(AZD_MODE)
222  ]
223  else:
224  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
225  self._attr_max_temp_attr_max_temp = self.get_airzone_valueget_airzone_value(AZD_TEMP_SET_MAX)
226  self._attr_min_temp_attr_min_temp = self.get_airzone_valueget_airzone_value(AZD_TEMP_SET_MIN)
227  if (
228  self.supported_featuressupported_featuressupported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
229  and self._attr_hvac_mode_attr_hvac_mode == HVACMode.HEAT_COOL
230  ):
231  self._attr_target_temperature_high_attr_target_temperature_high = self.get_airzone_valueget_airzone_value(
232  AZD_TEMP_SET_COOL_AIR
233  )
234  self._attr_target_temperature_low_attr_target_temperature_low = self.get_airzone_valueget_airzone_value(
235  AZD_TEMP_SET_HOT_AIR
236  )
237  self._attr_target_temperature_attr_target_temperature = None
238  else:
239  self._attr_target_temperature_high_attr_target_temperature_high = None
240  self._attr_target_temperature_low_attr_target_temperature_low = None
241  self._attr_target_temperature_attr_target_temperature = self.get_airzone_valueget_airzone_value(AZD_TEMP_SET)
242 
243 
245  """Define an Airzone Cloud Device base class."""
246 
247  _attr_supported_features = (
248  ClimateEntityFeature.TARGET_TEMPERATURE
249  | ClimateEntityFeature.TURN_OFF
250  | ClimateEntityFeature.TURN_ON
251  )
252  _speeds: dict[int, str]
253  _speeds_reverse: dict[str, int]
254 
255  def _initialize_fan_speeds(self) -> None:
256  """Initialize fan speeds."""
257  azd_speeds: dict[int, int] = self.get_airzone_valueget_airzone_value(AZD_SPEEDS)
258  max_speed = max(azd_speeds)
259 
260  fan_speeds: dict[int, str]
261  if speeds_map := FAN_SPEED_MAPS.get(max_speed):
262  fan_speeds = speeds_map
263  else:
264  fan_speeds = {}
265 
266  for speed in azd_speeds:
267  if speed != 0:
268  fan_speeds[speed] = f"{int(round((speed * 100) / max_speed, 0))}%"
269 
270  if 0 in azd_speeds:
271  fan_speeds = FAN_SPEED_AUTO | fan_speeds
272 
273  self._speeds_speeds = {}
274  for key, value in fan_speeds.items():
275  _key = azd_speeds.get(key)
276  if _key is not None:
277  self._speeds_speeds[_key] = value
278 
279  self._speeds_reverse_speeds_reverse = {v: k for k, v in self._speeds_speeds.items()}
280  self._attr_fan_modes_attr_fan_modes = list(self._speeds_reverse_speeds_reverse)
281 
282  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.FAN_MODE
283 
284  async def async_turn_on(self) -> None:
285  """Turn the entity on."""
286  params = {
287  API_POWER: {
288  API_VALUE: True,
289  },
290  }
291  await self._async_update_params_async_update_params(params)
292 
293  async def async_turn_off(self) -> None:
294  """Turn the entity off."""
295  params = {
296  API_POWER: {
297  API_VALUE: False,
298  },
299  }
300  await self._async_update_params_async_update_params(params)
301 
302  async def async_set_fan_mode(self, fan_mode: str) -> None:
303  """Set new fan mode."""
304  params: dict[str, Any] = {
305  API_SPEED_CONF: {
306  API_VALUE: self._speeds_reverse_speeds_reverse.get(fan_mode),
307  }
308  }
309  await self._async_update_params_async_update_params(params)
310 
311  async def async_set_temperature(self, **kwargs: Any) -> None:
312  """Set new target temperature."""
313  hvac_mode = kwargs.get(ATTR_HVAC_MODE)
314  if hvac_mode is not None:
315  await self.async_set_hvac_modeasync_set_hvac_mode(hvac_mode)
316 
317  params: dict[str, Any] = {}
318  if ATTR_TEMPERATURE in kwargs:
319  params[API_SETPOINT] = {
320  API_VALUE: kwargs[ATTR_TEMPERATURE],
321  API_OPTS: {
322  API_UNITS: TemperatureUnit.CELSIUS.value,
323  },
324  }
325  if ATTR_TARGET_TEMP_LOW in kwargs and ATTR_TARGET_TEMP_HIGH in kwargs:
326  params[API_SP_AIR_COOL] = {
327  API_VALUE: kwargs[ATTR_TARGET_TEMP_HIGH],
328  API_OPTS: {
329  API_UNITS: TemperatureUnit.CELSIUS.value,
330  },
331  }
332  params[API_SP_AIR_HEAT] = {
333  API_VALUE: kwargs[ATTR_TARGET_TEMP_LOW],
334  API_OPTS: {
335  API_UNITS: TemperatureUnit.CELSIUS.value,
336  },
337  }
338  await self._async_update_params_async_update_params(params)
339 
340 
342  """Define an Airzone Cloud DeviceGroup base class."""
343 
344  _attr_supported_features = (
345  ClimateEntityFeature.TARGET_TEMPERATURE
346  | ClimateEntityFeature.TURN_OFF
347  | ClimateEntityFeature.TURN_ON
348  )
349 
350  async def async_turn_on(self) -> None:
351  """Turn the entity on."""
352  params = {
353  API_PARAMS: {
354  API_POWER: True,
355  },
356  }
357  await self._async_update_params_async_update_params(params)
358 
359  async def async_turn_off(self) -> None:
360  """Turn the entity off."""
361  params = {
362  API_PARAMS: {
363  API_POWER: False,
364  },
365  }
366  await self._async_update_params_async_update_params(params)
367 
368  async def async_set_temperature(self, **kwargs: Any) -> None:
369  """Set new target temperature."""
370  hvac_mode = kwargs.get(ATTR_HVAC_MODE)
371  if hvac_mode is not None:
372  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(hvac_mode)
373 
374  params: dict[str, Any] = {}
375  if ATTR_TEMPERATURE in kwargs:
376  params[API_PARAMS] = {
377  API_SETPOINT: kwargs[ATTR_TEMPERATURE],
378  }
379  params[API_OPTS] = {
380  API_UNITS: TemperatureUnit.CELSIUS.value,
381  }
382  await self._async_update_params_async_update_params(params)
383 
384  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
385  """Set hvac mode."""
386  params: dict[str, Any] = {
387  API_PARAMS: {},
388  }
389  if hvac_mode == HVACMode.OFF:
390  params[API_PARAMS][API_POWER] = False
391  else:
392  mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
393  params[API_PARAMS][API_MODE] = mode.value
394  params[API_PARAMS][API_POWER] = True
395  await self._async_update_params_async_update_params(params)
396 
397 
399  """Define an Airzone Cloud Aidoo climate."""
400 
401  def __init__(
402  self,
403  coordinator: AirzoneUpdateCoordinator,
404  aidoo_id: str,
405  aidoo_data: dict,
406  ) -> None:
407  """Initialize Airzone Cloud Aidoo climate."""
408  super().__init__(coordinator, aidoo_id, aidoo_data)
409 
410  self._attr_unique_id_attr_unique_id = aidoo_id
411  self._init_attributes_init_attributes()
412 
413  self._async_update_attrs_async_update_attrs()
414 
415  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
416  """Set hvac mode."""
417  params: dict[str, Any] = {}
418  if hvac_mode == HVACMode.OFF:
419  params[API_POWER] = {
420  API_VALUE: False,
421  }
422  else:
423  mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
424  params[API_MODE] = {
425  API_VALUE: mode.value,
426  }
427  params[API_POWER] = {
428  API_VALUE: True,
429  }
430  await self._async_update_params_async_update_params_async_update_params(params)
431 
432 
434  """Define an Airzone Cloud Group climate."""
435 
436  def __init__(
437  self,
438  coordinator: AirzoneUpdateCoordinator,
439  group_id: str,
440  group_data: dict,
441  ) -> None:
442  """Initialize Airzone Cloud Group climate."""
443  super().__init__(coordinator, group_id, group_data)
444 
445  self._attr_unique_id_attr_unique_id = group_id
446  self._init_attributes_init_attributes()
447 
448  self._async_update_attrs_async_update_attrs()
449 
450 
452  """Define an Airzone Cloud Installation climate."""
453 
454  def __init__(
455  self,
456  coordinator: AirzoneUpdateCoordinator,
457  inst_id: str,
458  inst_data: dict,
459  ) -> None:
460  """Initialize Airzone Cloud Installation climate."""
461  super().__init__(coordinator, inst_id, inst_data)
462 
463  self._attr_unique_id_attr_unique_id = inst_id
464  self._init_attributes_init_attributes()
465 
466  self._async_update_attrs_async_update_attrs()
467 
468 
470  """Define an Airzone Cloud Zone climate."""
471 
472  def __init__(
473  self,
474  coordinator: AirzoneUpdateCoordinator,
475  system_zone_id: str,
476  zone_data: dict,
477  ) -> None:
478  """Initialize Airzone Cloud Zone climate."""
479  super().__init__(coordinator, system_zone_id, zone_data)
480 
481  self._attr_unique_id_attr_unique_id = system_zone_id
482  self._init_attributes_init_attributes()
483 
484  self._async_update_attrs_async_update_attrs()
485 
486  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
487  """Set hvac mode."""
488  slave_raise = False
489 
490  params: dict[str, Any] = {}
491  if hvac_mode == HVACMode.OFF:
492  params[API_POWER] = {
493  API_VALUE: False,
494  }
495  else:
496  mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
497  cur_mode = self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_MODE)
498  if hvac_mode != HVAC_MODE_LIB_TO_HASS[cur_mode]:
499  if self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_MASTER):
500  params[API_MODE] = {
501  API_VALUE: mode.value,
502  }
503  else:
504  slave_raise = True
505  params[API_POWER] = {
506  API_VALUE: True,
507  }
508 
509  await self._async_update_params_async_update_params_async_update_params(params)
510 
511  if slave_raise:
512  raise HomeAssistantError(
513  f"Mode can't be changed on slave zone {self.entity_id}"
514  )
None __init__(self, AirzoneUpdateCoordinator coordinator, str aidoo_id, dict aidoo_data)
Definition: climate.py:406
None __init__(self, AirzoneUpdateCoordinator coordinator, str group_id, dict group_data)
Definition: climate.py:441
None __init__(self, AirzoneUpdateCoordinator coordinator, str inst_id, dict inst_data)
Definition: climate.py:459
None __init__(self, AirzoneUpdateCoordinator coordinator, str system_zone_id, dict zone_data)
Definition: climate.py:477
None _async_update_params(self, dict[str, Any] params)
Definition: entity.py:87
None _async_update_params(self, dict[str, Any] params)
Definition: entity.py:53
None _async_update_params(self, dict[str, Any] params)
Definition: entity.py:327
ClimateEntityFeature supported_features(self)
Definition: __init__.py:945
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:813
int|None supported_features(self)
Definition: entity.py:861
None async_setup_entry(HomeAssistant hass, AirzoneCloudConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:124
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88