Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for Fibaro thermostats."""
2 
3 from __future__ import annotations
4 
5 from contextlib import suppress
6 import logging
7 from typing import Any
8 
9 from pyfibaro.fibaro_device import DeviceModel
10 
12  ENTITY_ID_FORMAT,
13  PRESET_AWAY,
14  PRESET_BOOST,
15  ClimateEntity,
16  ClimateEntityFeature,
17  HVACAction,
18  HVACMode,
19 )
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 
25 from . import FibaroController
26 from .const import DOMAIN
27 from .entity import FibaroEntity
28 
29 PRESET_RESUME = "resume"
30 PRESET_MOIST = "moist"
31 PRESET_FURNACE = "furnace"
32 PRESET_CHANGEOVER = "changeover"
33 PRESET_ECO_HEAT = "eco_heat"
34 PRESET_ECO_COOL = "eco_cool"
35 PRESET_FORCE_OPEN = "force_open"
36 
37 _LOGGER = logging.getLogger(__name__)
38 
39 # SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
40 # Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding
41 FANMODES = {
42  0: "off",
43  1: "low",
44  2: "auto_high",
45  3: "medium",
46  4: "auto_medium",
47  5: "high",
48  6: "circulation",
49  7: "humidity_circulation",
50  8: "left_right",
51  9: "up_down",
52  10: "quiet",
53  128: "auto",
54 }
55 
56 HA_FANMODES = {v: k for k, v in FANMODES.items()}
57 
58 # SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
59 # Table 130, Thermostat Mode Set version 3::Mode encoding.
60 # 4 AUXILIARY
61 OPMODES_PRESET = {
62  5: PRESET_RESUME,
63  7: PRESET_FURNACE,
64  9: PRESET_MOIST,
65  10: PRESET_CHANGEOVER,
66  11: PRESET_ECO_HEAT,
67  12: PRESET_ECO_COOL,
68  13: PRESET_AWAY,
69  15: PRESET_BOOST,
70  31: PRESET_FORCE_OPEN,
71 }
72 
73 HA_OPMODES_PRESET = {v: k for k, v in OPMODES_PRESET.items()}
74 
75 OPMODES_HVAC = {
76  0: HVACMode.OFF,
77  1: HVACMode.HEAT,
78  2: HVACMode.COOL,
79  3: HVACMode.AUTO,
80  4: HVACMode.HEAT,
81  5: HVACMode.AUTO,
82  6: HVACMode.FAN_ONLY,
83  7: HVACMode.HEAT,
84  8: HVACMode.DRY,
85  9: HVACMode.DRY,
86  10: HVACMode.AUTO,
87  11: HVACMode.HEAT,
88  12: HVACMode.COOL,
89  13: HVACMode.AUTO,
90  15: HVACMode.AUTO,
91  31: HVACMode.HEAT,
92 }
93 
94 HA_OPMODES_HVAC = {
95  HVACMode.OFF: 0,
96  HVACMode.HEAT: 1,
97  HVACMode.COOL: 2,
98  HVACMode.AUTO: 3,
99  HVACMode.FAN_ONLY: 6,
100  HVACMode.DRY: 8,
101 }
102 
103 TARGET_TEMP_ACTIONS = (
104  "setTargetLevel",
105  "setThermostatSetpoint",
106  "setHeatingThermostatSetpoint",
107 )
108 
109 OP_MODE_ACTIONS = ("setMode", "setOperatingMode", "setThermostatMode")
110 
111 
113  hass: HomeAssistant,
114  entry: ConfigEntry,
115  async_add_entities: AddEntitiesCallback,
116 ) -> None:
117  """Perform the setup for Fibaro controller devices."""
118  controller: FibaroController = hass.data[DOMAIN][entry.entry_id]
120  [
121  FibaroThermostat(device)
122  for device in controller.fibaro_devices[Platform.CLIMATE]
123  ],
124  True,
125  )
126 
127 
129  """Representation of a Fibaro Thermostat."""
130 
131  _enable_turn_on_off_backwards_compatibility = False
132 
133  def __init__(self, fibaro_device: DeviceModel) -> None:
134  """Initialize the Fibaro device."""
135  super().__init__(fibaro_device)
136  self._temp_sensor_device_temp_sensor_device: FibaroEntity | None = None
137  self._target_temp_device_target_temp_device: FibaroEntity | None = None
138  self._op_mode_device_op_mode_device: FibaroEntity | None = None
139  self._fan_mode_device_fan_mode_device: FibaroEntity | None = None
140  self.entity_identity_identity_id = ENTITY_ID_FORMAT.format(self.ha_idha_id)
141 
142  siblings = fibaro_device.fibaro_controller.get_siblings(fibaro_device)
143  _LOGGER.debug("%s siblings: %s", fibaro_device.ha_id, siblings)
144  tempunit = "C"
145  for device in siblings:
146  # Detecting temperature device, one strong and one weak way of
147  # doing so, so we prefer the hard evidence, if there is such.
148  if device.type == "com.fibaro.temperatureSensor" or (
149  self._temp_sensor_device_temp_sensor_device is None
150  and device.has_unit
151  and (device.value.has_value or device.has_heating_thermostat_setpoint)
152  and device.unit in ("C", "F")
153  ):
154  self._temp_sensor_device_temp_sensor_device = FibaroEntity(device)
155  tempunit = device.unit
156 
157  if any(
158  action for action in TARGET_TEMP_ACTIONS if action in device.actions
159  ):
160  self._target_temp_device_target_temp_device = FibaroEntity(device)
161  self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
162  if device.has_unit:
163  tempunit = device.unit
164 
165  if any(action for action in OP_MODE_ACTIONS if action in device.actions):
166  self._op_mode_device_op_mode_device = FibaroEntity(device)
167  self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
168 
169  if "setFanMode" in device.actions:
170  self._fan_mode_device_fan_mode_device = FibaroEntity(device)
171  self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
172 
173  if tempunit == "F":
174  self._attr_temperature_unit_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
175  else:
176  self._attr_temperature_unit_attr_temperature_unit = UnitOfTemperature.CELSIUS
177 
178  if self._fan_mode_device_fan_mode_device:
179  fan_modes = self._fan_mode_device_fan_mode_device.fibaro_device.supported_modes
180  self._attr_fan_modes_attr_fan_modes = []
181  for mode in fan_modes:
182  if mode not in FANMODES:
183  _LOGGER.warning("%d unknown fan mode", mode)
184  continue
185  self._attr_fan_modes_attr_fan_modes.append(FANMODES[int(mode)])
186 
187  self._attr_hvac_modes_attr_hvac_modes = [HVACMode.AUTO] # default
188  if self._op_mode_device_op_mode_device:
189  self._attr_preset_modes_attr_preset_modes = []
190  self._attr_hvac_modes_attr_hvac_modes: list[HVACMode] = []
191  device = self._op_mode_device_op_mode_device.fibaro_device
192  if device.has_supported_thermostat_modes:
193  for mode in device.supported_thermostat_modes:
194  try:
195  self._attr_hvac_modes_attr_hvac_modes.append(HVACMode(mode.lower()))
196  except ValueError:
197  self._attr_preset_modes_attr_preset_modes.append(mode)
198  else:
199  if device.has_supported_operating_modes:
200  op_modes = device.supported_operating_modes
201  else:
202  op_modes = device.supported_modes
203  for mode in op_modes:
204  if (
205  mode in OPMODES_HVAC
206  and (mode_ha := OPMODES_HVAC.get(mode))
207  and mode_ha not in self._attr_hvac_modes_attr_hvac_modes
208  ):
209  self._attr_hvac_modes_attr_hvac_modes.append(mode_ha)
210  if mode in OPMODES_PRESET:
211  self._attr_preset_modes_attr_preset_modes.append(OPMODES_PRESET[mode])
212 
213  if HVACMode.OFF in self._attr_hvac_modes_attr_hvac_modes and len(self._attr_hvac_modes_attr_hvac_modes) > 1:
214  self._attr_supported_features |= (
215  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
216  )
217 
218  async def async_added_to_hass(self) -> None:
219  """Call when entity is added to hass."""
220  _LOGGER.debug(
221  (
222  "Climate %s\n"
223  "- _temp_sensor_device %s\n"
224  "- _target_temp_device %s\n"
225  "- _op_mode_device %s\n"
226  "- _fan_mode_device %s"
227  ),
228  self.ha_idha_id,
229  self._temp_sensor_device_temp_sensor_device.ha_id if self._temp_sensor_device_temp_sensor_device else "None",
230  self._target_temp_device_target_temp_device.ha_id if self._target_temp_device_target_temp_device else "None",
231  self._op_mode_device_op_mode_device.ha_id if self._op_mode_device_op_mode_device else "None",
232  self._fan_mode_device_fan_mode_device.ha_id if self._fan_mode_device_fan_mode_device else "None",
233  )
234  await super().async_added_to_hass()
235 
236  # Register update callback for child devices
237  siblings = self.fibaro_devicefibaro_device.fibaro_controller.get_siblings(self.fibaro_devicefibaro_device)
238  for device in siblings:
239  if device != self.fibaro_devicefibaro_device:
240  self.controllercontroller.register(device.fibaro_id, self._update_callback_update_callback)
241 
242  @property
243  def fan_mode(self) -> str | None:
244  """Return the fan setting."""
245  if not self._fan_mode_device_fan_mode_device:
246  return None
247  mode = self._fan_mode_device_fan_mode_device.fibaro_device.mode
248  return FANMODES[mode]
249 
250  def set_fan_mode(self, fan_mode: str) -> None:
251  """Set new target fan mode."""
252  if not self._fan_mode_device_fan_mode_device:
253  return
254  self._fan_mode_device_fan_mode_device.action("setFanMode", HA_FANMODES[fan_mode])
255 
256  @property
257  def fibaro_op_mode(self) -> str | int:
258  """Return the operating mode of the device."""
259  if not self._op_mode_device_op_mode_device:
260  return HA_OPMODES_HVAC[HVACMode.AUTO]
261 
262  device = self._op_mode_device_op_mode_device.fibaro_device
263 
264  if device.has_operating_mode:
265  return device.operating_mode
266  if device.has_thermostat_mode:
267  return device.thermostat_mode
268  return device.mode
269 
270  @property
271  def hvac_mode(self) -> HVACMode | None:
272  """Return hvac operation ie. heat, cool, idle."""
273  fibaro_operation_mode = self.fibaro_op_modefibaro_op_mode
274  if isinstance(fibaro_operation_mode, str):
275  with suppress(ValueError):
276  return HVACMode(fibaro_operation_mode.lower())
277  elif fibaro_operation_mode in OPMODES_HVAC:
278  return OPMODES_HVAC[fibaro_operation_mode]
279  return None
280 
281  def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
282  """Set new target operation mode."""
283  if not self._op_mode_device_op_mode_device:
284  return
285  if self.preset_modepreset_modepreset_mode:
286  return
287 
288  if "setOperatingMode" in self._op_mode_device_op_mode_device.fibaro_device.actions:
289  self._op_mode_device_op_mode_device.action("setOperatingMode", HA_OPMODES_HVAC[hvac_mode])
290  elif "setThermostatMode" in self._op_mode_device_op_mode_device.fibaro_device.actions:
291  device = self._op_mode_device_op_mode_device.fibaro_device
292  if device.has_supported_thermostat_modes:
293  for mode in device.supported_thermostat_modes:
294  if mode.lower() == hvac_mode:
295  self._op_mode_device_op_mode_device.action("setThermostatMode", mode)
296  break
297  elif "setMode" in self._op_mode_device_op_mode_device.fibaro_device.actions:
298  self._op_mode_device_op_mode_device.action("setMode", HA_OPMODES_HVAC[hvac_mode])
299 
300  @property
301  def hvac_action(self) -> HVACAction | None:
302  """Return the current running hvac operation if supported."""
303  if not self._op_mode_device_op_mode_device:
304  return None
305 
306  device = self._op_mode_device_op_mode_device.fibaro_device
307  if device.has_thermostat_operating_state:
308  with suppress(ValueError):
309  return HVACAction(device.thermostat_operating_state.lower())
310 
311  return None
312 
313  @property
314  def preset_mode(self) -> str | None:
315  """Return the current preset mode, e.g., home, away, temp.
316 
317  Requires ClimateEntityFeature.PRESET_MODE.
318  """
319  if not self._op_mode_device_op_mode_device:
320  return None
321 
322  if self._op_mode_device_op_mode_device.fibaro_device.has_thermostat_mode:
323  mode = self._op_mode_device_op_mode_device.fibaro_device.thermostat_mode
324  if self.preset_modespreset_modes is not None and mode in self.preset_modespreset_modes:
325  return mode
326  return None
327  if self._op_mode_device_op_mode_device.fibaro_device.has_operating_mode:
328  mode = self._op_mode_device_op_mode_device.fibaro_device.operating_mode
329  else:
330  mode = self._op_mode_device_op_mode_device.fibaro_device.mode
331 
332  if mode not in OPMODES_PRESET:
333  return None
334  return OPMODES_PRESET[mode]
335 
336  def set_preset_mode(self, preset_mode: str) -> None:
337  """Set new preset mode."""
338  if self._op_mode_device_op_mode_device is None:
339  return
340 
341  if "setThermostatMode" in self._op_mode_device_op_mode_device.fibaro_device.actions:
342  self._op_mode_device_op_mode_device.action("setThermostatMode", preset_mode)
343  elif "setOperatingMode" in self._op_mode_device_op_mode_device.fibaro_device.actions:
344  self._op_mode_device_op_mode_device.action(
345  "setOperatingMode", HA_OPMODES_PRESET[preset_mode]
346  )
347  elif "setMode" in self._op_mode_device_op_mode_device.fibaro_device.actions:
348  self._op_mode_device_op_mode_device.action("setMode", HA_OPMODES_PRESET[preset_mode])
349 
350  @property
351  def current_temperature(self) -> float | None:
352  """Return the current temperature."""
353  if self._temp_sensor_device_temp_sensor_device:
354  device = self._temp_sensor_device_temp_sensor_device.fibaro_device
355  if device.has_heating_thermostat_setpoint:
356  return device.heating_thermostat_setpoint
357  return device.value.float_value()
358  return None
359 
360  @property
361  def target_temperature(self) -> float | None:
362  """Return the temperature we try to reach."""
363  if self._target_temp_device_target_temp_device:
364  device = self._target_temp_device_target_temp_device.fibaro_device
365  if device.has_heating_thermostat_setpoint_future:
366  return device.heating_thermostat_setpoint_future
367  return device.target_level
368  return None
369 
370  def set_temperature(self, **kwargs: Any) -> None:
371  """Set new target temperatures."""
372  temperature = kwargs.get(ATTR_TEMPERATURE)
373  target = self._target_temp_device_target_temp_device
374  if target is not None and temperature is not None:
375  if "setThermostatSetpoint" in target.fibaro_device.actions:
376  target.action("setThermostatSetpoint", self.fibaro_op_modefibaro_op_mode, temperature)
377  elif "setHeatingThermostatSetpoint" in target.fibaro_device.actions:
378  target.action("setHeatingThermostatSetpoint", temperature)
379  else:
380  target.action("setTargetLevel", temperature)
None set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:281
None __init__(self, DeviceModel fibaro_device)
Definition: climate.py:133
None action(self, str cmd, *Any args)
Definition: entity.py:98
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:116
def register(HomeAssistant hass, Heos controller)
Definition: services.py:29