Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for the Airzone climate."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, Final
6 
7 from aioairzone.common import OperationAction, OperationMode
8 from aioairzone.const import (
9  API_COOL_SET_POINT,
10  API_HEAT_SET_POINT,
11  API_MODE,
12  API_ON,
13  API_SET_POINT,
14  API_SPEED,
15  AZD_ACTION,
16  AZD_COOL_TEMP_SET,
17  AZD_DOUBLE_SET_POINT,
18  AZD_HEAT_TEMP_SET,
19  AZD_HUMIDITY,
20  AZD_MASTER,
21  AZD_MODE,
22  AZD_MODES,
23  AZD_ON,
24  AZD_SPEED,
25  AZD_SPEEDS,
26  AZD_TEMP,
27  AZD_TEMP_MAX,
28  AZD_TEMP_MIN,
29  AZD_TEMP_SET,
30  AZD_TEMP_UNIT,
31  AZD_ZONES,
32 )
33 
35  ATTR_HVAC_MODE,
36  ATTR_TARGET_TEMP_HIGH,
37  ATTR_TARGET_TEMP_LOW,
38  FAN_AUTO,
39  FAN_HIGH,
40  FAN_LOW,
41  FAN_MEDIUM,
42  ClimateEntity,
43  ClimateEntityFeature,
44  HVACAction,
45  HVACMode,
46 )
47 from homeassistant.config_entries import ConfigEntry
48 from homeassistant.const import ATTR_TEMPERATURE
49 from homeassistant.core import HomeAssistant, callback
50 from homeassistant.exceptions import HomeAssistantError
51 from homeassistant.helpers.entity_platform import AddEntitiesCallback
52 
53 from . import AirzoneConfigEntry
54 from .const import API_TEMPERATURE_STEP, TEMP_UNIT_LIB_TO_HASS
55 from .coordinator import AirzoneUpdateCoordinator
56 from .entity import AirzoneZoneEntity
57 
58 BASE_FAN_SPEEDS: Final[dict[int, str]] = {
59  0: FAN_AUTO,
60  1: FAN_LOW,
61 }
62 FAN_SPEED_MAPS: Final[dict[int, dict[int, str]]] = {
63  2: BASE_FAN_SPEEDS
64  | {
65  2: FAN_HIGH,
66  },
67  3: BASE_FAN_SPEEDS
68  | {
69  2: FAN_MEDIUM,
70  3: FAN_HIGH,
71  },
72 }
73 
74 HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = {
75  OperationAction.COOLING: HVACAction.COOLING,
76  OperationAction.DRYING: HVACAction.DRYING,
77  OperationAction.FAN: HVACAction.FAN,
78  OperationAction.HEATING: HVACAction.HEATING,
79  OperationAction.IDLE: HVACAction.IDLE,
80  OperationAction.OFF: HVACAction.OFF,
81 }
82 HVAC_MODE_LIB_TO_HASS: Final[dict[OperationMode, HVACMode]] = {
83  OperationMode.STOP: HVACMode.OFF,
84  OperationMode.COOLING: HVACMode.COOL,
85  OperationMode.HEATING: HVACMode.HEAT,
86  OperationMode.FAN: HVACMode.FAN_ONLY,
87  OperationMode.DRY: HVACMode.DRY,
88  OperationMode.AUX_HEATING: HVACMode.HEAT,
89  OperationMode.AUTO: HVACMode.HEAT_COOL,
90 }
91 HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
92  HVACMode.OFF: OperationMode.STOP,
93  HVACMode.COOL: OperationMode.COOLING,
94  HVACMode.HEAT: OperationMode.HEATING,
95  HVACMode.FAN_ONLY: OperationMode.FAN,
96  HVACMode.DRY: OperationMode.DRY,
97  HVACMode.HEAT_COOL: OperationMode.AUTO,
98 }
99 
100 
102  hass: HomeAssistant,
103  entry: AirzoneConfigEntry,
104  async_add_entities: AddEntitiesCallback,
105 ) -> None:
106  """Add Airzone climate from a config_entry."""
107  coordinator = entry.runtime_data
108 
109  added_zones: set[str] = set()
110 
111  def _async_entity_listener() -> None:
112  """Handle additions of climate."""
113 
114  zones_data = coordinator.data.get(AZD_ZONES, {})
115  received_zones = set(zones_data)
116  new_zones = received_zones - added_zones
117  if new_zones:
120  coordinator,
121  entry,
122  system_zone_id,
123  zones_data.get(system_zone_id),
124  )
125  for system_zone_id in new_zones
126  )
127  added_zones.update(new_zones)
128 
129  entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
130  _async_entity_listener()
131 
132 
134  """Define an Airzone sensor."""
135 
136  _attr_name = None
137  _speeds: dict[int, str] = {}
138  _speeds_reverse: dict[str, int] = {}
139  _enable_turn_on_off_backwards_compatibility = False
140 
141  def __init__(
142  self,
143  coordinator: AirzoneUpdateCoordinator,
144  entry: ConfigEntry,
145  system_zone_id: str,
146  zone_data: dict,
147  ) -> None:
148  """Initialize Airzone climate entity."""
149  super().__init__(coordinator, entry, system_zone_id, zone_data)
150 
151  self._attr_unique_id_attr_unique_id_attr_unique_id = f"{self._attr_unique_id}_{system_zone_id}"
152  self._attr_supported_features_attr_supported_features = (
153  ClimateEntityFeature.TARGET_TEMPERATURE
154  | ClimateEntityFeature.TURN_OFF
155  | ClimateEntityFeature.TURN_ON
156  )
157  self._attr_target_temperature_step_attr_target_temperature_step = API_TEMPERATURE_STEP
158  self._attr_temperature_unit_attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
159  self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_TEMP_UNIT)
160  ]
161  _attr_hvac_modes = [
162  HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_MODES)
163  ]
164  self._attr_hvac_modes_attr_hvac_modes = list(dict.fromkeys(_attr_hvac_modes))
165  if (
166  self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_SPEED) is not None
167  and self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_SPEEDS) is not None
168  ):
169  self._set_fan_speeds_set_fan_speeds()
170  if self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_DOUBLE_SET_POINT):
171  self._attr_supported_features_attr_supported_features |= (
172  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
173  )
174 
175  self._async_update_attrs_async_update_attrs()
176 
177  def _set_fan_speeds(self) -> None:
178  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.FAN_MODE
179 
180  speeds = self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_SPEEDS)
181  max_speed = max(speeds)
182  if _speeds := FAN_SPEED_MAPS.get(max_speed):
183  self._speeds_speeds = _speeds
184  else:
185  for speed in speeds:
186  if speed == 0:
187  self._speeds_speeds[speed] = FAN_AUTO
188  else:
189  self._speeds_speeds[speed] = f"{int(round((speed * 100) / max_speed, 0))}%"
190 
191  self._speeds_speeds[1] = FAN_LOW
192  self._speeds_speeds[int(round((max_speed + 1) / 2, 0))] = FAN_MEDIUM
193  self._speeds_speeds[max_speed] = FAN_HIGH
194 
195  self._speeds_reverse_speeds_reverse = {v: k for k, v in self._speeds_speeds.items()}
196  self._attr_fan_modes_attr_fan_modes = list(self._speeds_reverse_speeds_reverse)
197 
198  async def async_turn_on(self) -> None:
199  """Turn the entity on."""
200  params = {
201  API_ON: 1,
202  }
203  await self._async_update_hvac_params_async_update_hvac_params(params)
204 
205  async def async_turn_off(self) -> None:
206  """Turn the entity off."""
207  params = {
208  API_ON: 0,
209  }
210  await self._async_update_hvac_params_async_update_hvac_params(params)
211 
212  async def async_set_fan_mode(self, fan_mode: str) -> None:
213  """Set fan mode."""
214  params = {
215  API_SPEED: self._speeds_reverse_speeds_reverse.get(fan_mode),
216  }
217  await self._async_update_hvac_params_async_update_hvac_params(params)
218 
219  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
220  """Set hvac mode."""
221  slave_raise = False
222 
223  params = {}
224  if hvac_mode == HVACMode.OFF:
225  params[API_ON] = 0
226  else:
227  mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
228  if mode != self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_MODE):
229  if self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_MASTER):
230  params[API_MODE] = mode
231  else:
232  slave_raise = True
233  params[API_ON] = 1
234  await self._async_update_hvac_params_async_update_hvac_params(params)
235 
236  if slave_raise:
237  raise HomeAssistantError(
238  f"Mode can't be changed on slave zone {self.entity_id}"
239  )
240 
241  async def async_set_temperature(self, **kwargs: Any) -> None:
242  """Set new target temperature."""
243  params = {}
244  if ATTR_TEMPERATURE in kwargs:
245  params[API_SET_POINT] = kwargs[ATTR_TEMPERATURE]
246  if ATTR_TARGET_TEMP_LOW in kwargs and ATTR_TARGET_TEMP_HIGH in kwargs:
247  params[API_COOL_SET_POINT] = kwargs[ATTR_TARGET_TEMP_HIGH]
248  params[API_HEAT_SET_POINT] = kwargs[ATTR_TARGET_TEMP_LOW]
249  await self._async_update_hvac_params_async_update_hvac_params(params)
250 
251  if ATTR_HVAC_MODE in kwargs:
252  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(kwargs[ATTR_HVAC_MODE])
253 
254  @callback
255  def _handle_coordinator_update(self) -> None:
256  """Update attributes when the coordinator updates."""
257  self._async_update_attrs_async_update_attrs()
259 
260  @callback
261  def _async_update_attrs(self) -> None:
262  """Update climate attributes."""
263  self._attr_current_temperature_attr_current_temperature = self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_TEMP)
264  self._attr_current_humidity_attr_current_humidity = self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_HUMIDITY)
265  self._attr_hvac_action_attr_hvac_action = HVAC_ACTION_LIB_TO_HASS[
266  self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_ACTION)
267  ]
268  if self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_ON):
269  self._attr_hvac_mode_attr_hvac_mode = HVAC_MODE_LIB_TO_HASS[
270  self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_MODE)
271  ]
272  else:
273  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
274  self._attr_max_temp_attr_max_temp = self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_TEMP_MAX)
275  self._attr_min_temp_attr_min_temp = self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_TEMP_MIN)
276  if self.supported_featuressupported_featuressupported_features & ClimateEntityFeature.FAN_MODE:
277  self._attr_fan_mode_attr_fan_mode = self._speeds_speeds.get(self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_SPEED))
278  if (
279  self.supported_featuressupported_featuressupported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
280  and self._attr_hvac_mode_attr_hvac_mode == HVACMode.HEAT_COOL
281  ):
282  self._attr_target_temperature_high_attr_target_temperature_high = self.get_airzone_valueget_airzone_valueget_airzone_value(
283  AZD_COOL_TEMP_SET
284  )
285  self._attr_target_temperature_low_attr_target_temperature_low = self.get_airzone_valueget_airzone_valueget_airzone_value(
286  AZD_HEAT_TEMP_SET
287  )
288  self._attr_target_temperature_attr_target_temperature = None
289  else:
290  self._attr_target_temperature_high_attr_target_temperature_high = None
291  self._attr_target_temperature_low_attr_target_temperature_low = None
292  self._attr_target_temperature_attr_target_temperature = self.get_airzone_valueget_airzone_valueget_airzone_value(AZD_TEMP_SET)
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:219
None __init__(self, AirzoneUpdateCoordinator coordinator, ConfigEntry entry, str system_zone_id, dict zone_data)
Definition: climate.py:147
None _async_update_hvac_params(self, dict[str, Any] params)
Definition: entity.py:196
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, AirzoneConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:105
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88