Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for HomematicIP Cloud climate devices."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from homematicip.aio.device import (
8  AsyncHeatingThermostat,
9  AsyncHeatingThermostatCompact,
10  AsyncHeatingThermostatEvo,
11 )
12 from homematicip.aio.group import AsyncHeatingGroup
13 from homematicip.base.enums import AbsenceType
14 from homematicip.device import Switch
15 from homematicip.functionalHomes import IndoorClimateHome
16 from homematicip.group import HeatingCoolingProfile
17 
19  PRESET_AWAY,
20  PRESET_BOOST,
21  PRESET_ECO,
22  PRESET_NONE,
23  ClimateEntity,
24  ClimateEntityFeature,
25  HVACAction,
26  HVACMode,
27 )
28 from homeassistant.config_entries import ConfigEntry
29 from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
30 from homeassistant.core import HomeAssistant
31 from homeassistant.helpers.device_registry import DeviceInfo
32 from homeassistant.helpers.entity_platform import AddEntitiesCallback
33 
34 from .const import DOMAIN
35 from .entity import HomematicipGenericEntity
36 from .hap import HomematicipHAP
37 
38 HEATING_PROFILES = {"PROFILE_1": 0, "PROFILE_2": 1, "PROFILE_3": 2}
39 COOLING_PROFILES = {"PROFILE_4": 3, "PROFILE_5": 4, "PROFILE_6": 5}
40 NICE_PROFILE_NAMES = {
41  "PROFILE_1": "Default",
42  "PROFILE_2": "Alternative 1",
43  "PROFILE_3": "Alternative 2",
44  "PROFILE_4": "Cooling 1",
45  "PROFILE_5": "Cooling 2",
46  "PROFILE_6": "Cooling 3",
47 }
48 
49 ATTR_PRESET_END_TIME = "preset_end_time"
50 PERMANENT_END_TIME = "permanent"
51 
52 HMIP_AUTOMATIC_CM = "AUTOMATIC"
53 HMIP_MANUAL_CM = "MANUAL"
54 HMIP_ECO_CM = "ECO"
55 
56 
58  hass: HomeAssistant,
59  config_entry: ConfigEntry,
60  async_add_entities: AddEntitiesCallback,
61 ) -> None:
62  """Set up the HomematicIP climate from a config entry."""
63  hap = hass.data[DOMAIN][config_entry.unique_id]
64 
66  HomematicipHeatingGroup(hap, device)
67  for device in hap.home.groups
68  if isinstance(device, AsyncHeatingGroup)
69  )
70 
71 
73  """Representation of the HomematicIP heating group.
74 
75  Heat mode is supported for all heating devices incl. their defined profiles.
76  Boost is available for radiator thermostats only.
77  Cool mode is only available for floor heating systems, if basically enabled in the hmip app.
78  """
79 
80  _attr_supported_features = (
81  ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
82  )
83  _attr_temperature_unit = UnitOfTemperature.CELSIUS
84  _enable_turn_on_off_backwards_compatibility = False
85 
86  def __init__(self, hap: HomematicipHAP, device: AsyncHeatingGroup) -> None:
87  """Initialize heating group."""
88  device.modelType = "HmIP-Heating-Group"
89  super().__init__(hap, device)
90  self._simple_heating_simple_heating = None
91  if device.actualTemperature is None:
92  self._simple_heating_simple_heating = self._first_radiator_thermostat_first_radiator_thermostat
93 
94  @property
95  def device_info(self) -> DeviceInfo:
96  """Return device specific attributes."""
97  return DeviceInfo(
98  identifiers={(DOMAIN, self._device_device.id)},
99  manufacturer="eQ-3",
100  model=self._device_device.modelType,
101  name=self._device_device.label,
102  via_device=(DOMAIN, self._device_device.homeId),
103  )
104 
105  @property
106  def target_temperature(self) -> float:
107  """Return the temperature we try to reach."""
108  return self._device_device.setPointTemperature
109 
110  @property
111  def current_temperature(self) -> float:
112  """Return the current temperature."""
113  if self._simple_heating_simple_heating:
114  return self._simple_heating_simple_heating.valveActualTemperature
115  return self._device_device.actualTemperature
116 
117  @property
118  def current_humidity(self) -> int:
119  """Return the current humidity."""
120  return self._device_device.humidity
121 
122  @property
123  def hvac_mode(self) -> HVACMode:
124  """Return hvac operation ie."""
125  if self._disabled_by_cooling_mode_disabled_by_cooling_mode and not self._has_switch_has_switch:
126  return HVACMode.OFF
127  if self._device_device.boostMode:
128  return HVACMode.HEAT
129  if self._device_device.controlMode == HMIP_MANUAL_CM:
130  return HVACMode.HEAT if self._heat_mode_enabled_heat_mode_enabled else HVACMode.COOL
131 
132  return HVACMode.AUTO
133 
134  @property
135  def hvac_modes(self) -> list[HVACMode]:
136  """Return the list of available hvac operation modes."""
137  if self._disabled_by_cooling_mode_disabled_by_cooling_mode and not self._has_switch_has_switch:
138  return [HVACMode.OFF]
139 
140  if self._heat_mode_enabled_heat_mode_enabled:
141  return [HVACMode.AUTO, HVACMode.HEAT]
142  return [HVACMode.AUTO, HVACMode.COOL]
143 
144  @property
145  def hvac_action(self) -> HVACAction | None:
146  """Return the current hvac_action.
147 
148  This is only relevant for radiator thermostats.
149  """
150  if (
151  self._device_device.floorHeatingMode == "RADIATOR"
152  and self._has_radiator_thermostat_has_radiator_thermostat
153  and self._heat_mode_enabled_heat_mode_enabled
154  ):
155  return HVACAction.HEATING if self._device_device.valvePosition else HVACAction.IDLE
156 
157  return None
158 
159  @property
160  def preset_mode(self) -> str | None:
161  """Return the current preset mode."""
162  if self._device_device.boostMode:
163  return PRESET_BOOST
164  if self.hvac_modehvac_modehvac_modehvac_mode in (HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF):
165  return PRESET_NONE
166  if self._device_device.controlMode == HMIP_ECO_CM:
167  if self._indoor_climate_indoor_climate.absenceType == AbsenceType.VACATION:
168  return PRESET_AWAY
169  if self._indoor_climate_indoor_climate.absenceType in [
170  AbsenceType.PARTY,
171  AbsenceType.PERIOD,
172  AbsenceType.PERMANENT,
173  ]:
174  return PRESET_ECO
175 
176  return (
177  self._get_qualified_profile_name_get_qualified_profile_name(self._device_device.activeProfile)
178  if self._get_qualified_profile_name_get_qualified_profile_name(self._device_device.activeProfile)
179  in self._device_profile_names_device_profile_names
180  else None
181  )
182 
183  @property
184  def preset_modes(self) -> list[str]:
185  """Return a list of available preset modes incl. hmip profiles."""
186  # Boost is only available if a radiator thermostat is in the room,
187  # and heat mode is enabled.
188  profile_names = self._device_profile_names_device_profile_names
189 
190  presets = []
191  if (
192  self._heat_mode_enabled_heat_mode_enabled and self._has_radiator_thermostat_has_radiator_thermostat
193  ) or self._has_switch_has_switch:
194  if not profile_names:
195  presets.append(PRESET_NONE)
196  presets.extend([PRESET_BOOST, PRESET_ECO])
197 
198  presets.extend(profile_names)
199 
200  return presets
201 
202  @property
203  def min_temp(self) -> float:
204  """Return the minimum temperature."""
205  return self._device_device.minTemperature
206 
207  @property
208  def max_temp(self) -> float:
209  """Return the maximum temperature."""
210  return self._device_device.maxTemperature
211 
212  async def async_set_temperature(self, **kwargs: Any) -> None:
213  """Set new target temperature."""
214  if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
215  return
216 
217  if self.min_tempmin_tempmin_temp <= temperature <= self.max_tempmax_tempmax_temp:
218  await self._device_device.set_point_temperature(temperature)
219 
220  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
221  """Set new target hvac mode."""
222  if hvac_mode not in self.hvac_modeshvac_modeshvac_modes:
223  return
224 
225  if hvac_mode == HVACMode.AUTO:
226  await self._device_device.set_control_mode(HMIP_AUTOMATIC_CM)
227  else:
228  await self._device_device.set_control_mode(HMIP_MANUAL_CM)
229 
230  async def async_set_preset_mode(self, preset_mode: str) -> None:
231  """Set new preset mode."""
232  if self._device_device.boostMode and preset_mode != PRESET_BOOST:
233  await self._device_device.set_boost(False)
234  if preset_mode == PRESET_BOOST:
235  await self._device_device.set_boost()
236  if preset_mode == PRESET_ECO:
237  await self._device_device.set_control_mode(HMIP_ECO_CM)
238  if preset_mode in self._device_profile_names_device_profile_names:
239  profile_idx = self._get_profile_idx_by_name_get_profile_idx_by_name(preset_mode)
240  if self._device_device.controlMode != HMIP_AUTOMATIC_CM:
241  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(HVACMode.AUTO)
242  await self._device_device.set_active_profile(profile_idx)
243 
244  @property
245  def extra_state_attributes(self) -> dict[str, Any]:
246  """Return the state attributes of the access point."""
247  state_attr = super().extra_state_attributes
248 
249  if self._device_device.controlMode == HMIP_ECO_CM:
250  if self._indoor_climate_indoor_climate.absenceType in [
251  AbsenceType.PARTY,
252  AbsenceType.PERIOD,
253  AbsenceType.VACATION,
254  ]:
255  state_attr[ATTR_PRESET_END_TIME] = self._indoor_climate_indoor_climate.absenceEndTime
256  elif self._indoor_climate_indoor_climate.absenceType == AbsenceType.PERMANENT:
257  state_attr[ATTR_PRESET_END_TIME] = PERMANENT_END_TIME
258 
259  return state_attr
260 
261  @property
262  def _indoor_climate(self) -> IndoorClimateHome:
263  """Return the hmip indoor climate functional home of this group."""
264  return self._home.get_functionalHome(IndoorClimateHome)
265 
266  @property
267  def _device_profiles(self) -> list[HeatingCoolingProfile]:
268  """Return the relevant profiles."""
269  return [
270  profile
271  for profile in self._device_device.profiles
272  if profile.visible and profile.index in self._relevant_profile_group_relevant_profile_group
273  ]
274 
275  @property
276  def _device_profile_names(self) -> list[str]:
277  """Return a collection of profile names."""
278  return [
279  self._get_qualified_profile_name_get_qualified_profile_name(profile)
280  for profile in self._device_profiles_device_profiles
281  ]
282 
283  def _get_qualified_profile_name(self, profile: HeatingCoolingProfile) -> str:
284  """Get a name for the given profile. If exists, this is the name of the profile."""
285  if profile.name != "":
286  return profile.name
287  if profile.index in NICE_PROFILE_NAMES:
288  return NICE_PROFILE_NAMES[profile.index]
289 
290  return profile.index
291 
292  def _get_profile_idx_by_name(self, profile_name: str) -> int:
293  """Return a profile index by name."""
294  relevant_index = self._relevant_profile_group_relevant_profile_group
295  index_name = [
296  profile.index
297  for profile in self._device_profiles_device_profiles
298  if self._get_qualified_profile_name_get_qualified_profile_name(profile) == profile_name
299  ]
300 
301  return relevant_index[index_name[0]]
302 
303  @property
304  def _heat_mode_enabled(self) -> bool:
305  """Return, if heating mode is enabled."""
306  return not self._device_device.cooling
307 
308  @property
309  def _disabled_by_cooling_mode(self) -> bool:
310  """Return, if group is disabled by the cooling mode."""
311  return self._device_device.cooling and (
312  self._device_device.coolingIgnored or not self._device_device.coolingAllowed
313  )
314 
315  @property
316  def _relevant_profile_group(self) -> dict[str, int]:
317  """Return the relevant profile groups."""
318  if self._disabled_by_cooling_mode_disabled_by_cooling_mode:
319  return {}
320 
321  return HEATING_PROFILES if self._heat_mode_enabled_heat_mode_enabled else COOLING_PROFILES
322 
323  @property
324  def _has_switch(self) -> bool:
325  """Return, if a switch is in the hmip heating group."""
326  return any(isinstance(device, Switch) for device in self._device_device.devices)
327 
328  @property
329  def _has_radiator_thermostat(self) -> bool:
330  """Return, if a radiator thermostat is in the hmip heating group."""
331  return bool(self._first_radiator_thermostat_first_radiator_thermostat)
332 
333  @property
335  self,
336  ) -> (
337  AsyncHeatingThermostat
338  | AsyncHeatingThermostatCompact
339  | AsyncHeatingThermostatEvo
340  | None
341  ):
342  """Return the first radiator thermostat from the hmip heating group."""
343  for device in self._device_device.devices:
344  if isinstance(
345  device,
346  (
347  AsyncHeatingThermostat,
348  AsyncHeatingThermostatCompact,
349  AsyncHeatingThermostatEvo,
350  ),
351  ):
352  return device
353 
354  return None
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:813
str _get_qualified_profile_name(self, HeatingCoolingProfile profile)
Definition: climate.py:283
( AsyncHeatingThermostat|AsyncHeatingThermostatCompact|AsyncHeatingThermostatEvo|None) _first_radiator_thermostat(self)
Definition: climate.py:341
None __init__(self, HomematicipHAP hap, AsyncHeatingGroup device)
Definition: climate.py:86
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:61