Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for AVM FRITZ!SmartHome thermostat devices."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
8  ATTR_HVAC_MODE,
9  PRESET_COMFORT,
10  PRESET_ECO,
11  ClimateEntity,
12  ClimateEntityFeature,
13  HVACMode,
14 )
15 from homeassistant.const import (
16  ATTR_BATTERY_LEVEL,
17  ATTR_TEMPERATURE,
18  PRECISION_HALVES,
19  UnitOfTemperature,
20 )
21 from homeassistant.core import HomeAssistant, callback
22 from homeassistant.exceptions import HomeAssistantError
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 
25 from .const import (
26  ATTR_STATE_BATTERY_LOW,
27  ATTR_STATE_HOLIDAY_MODE,
28  ATTR_STATE_SUMMER_MODE,
29  ATTR_STATE_WINDOW_OPEN,
30  DOMAIN,
31  LOGGER,
32 )
33 from .coordinator import FritzboxConfigEntry, FritzboxDataUpdateCoordinator
34 from .entity import FritzBoxDeviceEntity
35 from .model import ClimateExtraAttributes
36 from .sensor import value_scheduled_preset
37 
38 HVAC_MODES = [HVACMode.HEAT, HVACMode.OFF]
39 PRESET_HOLIDAY = "holiday"
40 PRESET_SUMMER = "summer"
41 PRESET_MODES = [PRESET_ECO, PRESET_COMFORT]
42 SUPPORTED_FEATURES = (
43  ClimateEntityFeature.TARGET_TEMPERATURE
44  | ClimateEntityFeature.PRESET_MODE
45  | ClimateEntityFeature.TURN_OFF
46  | ClimateEntityFeature.TURN_ON
47 )
48 
49 MIN_TEMPERATURE = 8
50 MAX_TEMPERATURE = 28
51 
52 # special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
53 ON_API_TEMPERATURE = 127.0
54 OFF_API_TEMPERATURE = 126.5
55 ON_REPORT_SET_TEMPERATURE = 30.0
56 OFF_REPORT_SET_TEMPERATURE = 0.0
57 
58 
60  hass: HomeAssistant,
61  entry: FritzboxConfigEntry,
62  async_add_entities: AddEntitiesCallback,
63 ) -> None:
64  """Set up the FRITZ!SmartHome thermostat from ConfigEntry."""
65  coordinator = entry.runtime_data
66 
67  @callback
68  def _add_entities(devices: set[str] | None = None) -> None:
69  """Add devices."""
70  if devices is None:
71  devices = coordinator.new_devices
72  if not devices:
73  return
75  FritzboxThermostat(coordinator, ain)
76  for ain in devices
77  if coordinator.data.devices[ain].has_thermostat
78  )
79 
80  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
81 
82  _add_entities(set(coordinator.data.devices))
83 
84 
86  """The thermostat class for FRITZ!SmartHome thermostats."""
87 
88  _attr_precision = PRECISION_HALVES
89  _attr_temperature_unit = UnitOfTemperature.CELSIUS
90  _attr_translation_key = "thermostat"
91  _enable_turn_on_off_backwards_compatibility = False
92 
93  def __init__(
94  self,
95  coordinator: FritzboxDataUpdateCoordinator,
96  ain: str,
97  ) -> None:
98  """Initialize the thermostat."""
99  self._attr_supported_features_attr_supported_features = SUPPORTED_FEATURES
100  self._attr_hvac_modes_attr_hvac_modes = HVAC_MODES
101  self._attr_preset_modes_attr_preset_modes = PRESET_MODES
102  super().__init__(coordinator, ain)
103 
104  @callback
105  def async_write_ha_state(self) -> None:
106  """Write the state to the HASS state machine."""
107  if self.datadatadatadatadata.holiday_active:
108  self._attr_supported_features_attr_supported_features = ClimateEntityFeature.PRESET_MODE
109  self._attr_hvac_modes_attr_hvac_modes = [HVACMode.HEAT]
110  self._attr_preset_modes_attr_preset_modes = [PRESET_HOLIDAY]
111  elif self.datadatadatadatadata.summer_active:
112  self._attr_supported_features_attr_supported_features = ClimateEntityFeature.PRESET_MODE
113  self._attr_hvac_modes_attr_hvac_modes = [HVACMode.OFF]
114  self._attr_preset_modes_attr_preset_modes = [PRESET_SUMMER]
115  else:
116  self._attr_supported_features_attr_supported_features = SUPPORTED_FEATURES
117  self._attr_hvac_modes_attr_hvac_modes = HVAC_MODES
118  self._attr_preset_modes_attr_preset_modes = PRESET_MODES
119  return super().async_write_ha_state()
120 
121  @property
122  def current_temperature(self) -> float:
123  """Return the current temperature."""
124  if self.datadatadatadatadata.has_temperature_sensor and self.datadatadatadatadata.temperature is not None:
125  return self.datadatadatadatadata.temperature # type: ignore [no-any-return]
126  return self.datadatadatadatadata.actual_temperature # type: ignore [no-any-return]
127 
128  @property
129  def target_temperature(self) -> float:
130  """Return the temperature we try to reach."""
131  if self.datadatadatadatadata.target_temperature == ON_API_TEMPERATURE:
132  return ON_REPORT_SET_TEMPERATURE
133  if self.datadatadatadatadata.target_temperature == OFF_API_TEMPERATURE:
134  return OFF_REPORT_SET_TEMPERATURE
135  return self.datadatadatadatadata.target_temperature # type: ignore [no-any-return]
136 
137  async def async_set_temperature(self, **kwargs: Any) -> None:
138  """Set new target temperature."""
139  target_temp = kwargs.get(ATTR_TEMPERATURE)
140  hvac_mode = kwargs.get(ATTR_HVAC_MODE)
141  if hvac_mode == HVACMode.OFF:
142  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(hvac_mode)
143  elif target_temp is not None:
144  await self.hasshasshass.async_add_executor_job(
145  self.datadatadatadatadata.set_target_temperature, target_temp
146  )
147  else:
148  return
149  await self.coordinator.async_refresh()
150 
151  @property
152  def hvac_mode(self) -> HVACMode:
153  """Return the current operation mode."""
154  if self.datadatadatadatadata.holiday_active:
155  return HVACMode.HEAT
156  if self.datadatadatadatadata.summer_active:
157  return HVACMode.OFF
158  if self.datadatadatadatadata.target_temperature in (
159  OFF_REPORT_SET_TEMPERATURE,
160  OFF_API_TEMPERATURE,
161  ):
162  return HVACMode.OFF
163 
164  return HVACMode.HEAT
165 
166  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
167  """Set new operation mode."""
168  if self.datadatadatadatadata.holiday_active or self.datadatadatadatadata.summer_active:
169  raise HomeAssistantError(
170  translation_domain=DOMAIN,
171  translation_key="change_hvac_while_active_mode",
172  )
173  if self.hvac_modehvac_modehvac_modehvac_modehvac_mode == hvac_mode:
174  LOGGER.debug(
175  "%s is already in requested hvac mode %s", self.namenamename, hvac_mode
176  )
177  return
178  if hvac_mode == HVACMode.OFF:
179  await self.async_set_temperatureasync_set_temperatureasync_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
180  else:
181  if value_scheduled_preset(self.datadatadatadatadata) == PRESET_ECO:
182  target_temp = self.datadatadatadatadata.eco_temperature
183  else:
184  target_temp = self.datadatadatadatadata.comfort_temperature
185  await self.async_set_temperatureasync_set_temperatureasync_set_temperature(temperature=target_temp)
186 
187  @property
188  def preset_mode(self) -> str | None:
189  """Return current preset mode."""
190  if self.datadatadatadatadata.holiday_active:
191  return PRESET_HOLIDAY
192  if self.datadatadatadatadata.summer_active:
193  return PRESET_SUMMER
194  if self.datadatadatadatadata.target_temperature == self.datadatadatadatadata.comfort_temperature:
195  return PRESET_COMFORT
196  if self.datadatadatadatadata.target_temperature == self.datadatadatadatadata.eco_temperature:
197  return PRESET_ECO
198  return None
199 
200  async def async_set_preset_mode(self, preset_mode: str) -> None:
201  """Set preset mode."""
202  if self.datadatadatadatadata.holiday_active or self.datadatadatadatadata.summer_active:
203  raise HomeAssistantError(
204  translation_domain=DOMAIN,
205  translation_key="change_preset_while_active_mode",
206  )
207  if preset_mode == PRESET_COMFORT:
208  await self.async_set_temperatureasync_set_temperatureasync_set_temperature(temperature=self.datadatadatadatadata.comfort_temperature)
209  elif preset_mode == PRESET_ECO:
210  await self.async_set_temperatureasync_set_temperatureasync_set_temperature(temperature=self.datadatadatadatadata.eco_temperature)
211 
212  @property
213  def min_temp(self) -> int:
214  """Return the minimum temperature."""
215  return MIN_TEMPERATURE
216 
217  @property
218  def max_temp(self) -> int:
219  """Return the maximum temperature."""
220  return MAX_TEMPERATURE
221 
222  @property
223  def extra_state_attributes(self) -> ClimateExtraAttributes:
224  """Return the device specific state attributes."""
225  attrs: ClimateExtraAttributes = {
226  ATTR_STATE_BATTERY_LOW: self.datadatadatadatadata.battery_low,
227  }
228 
229  # the following attributes are available since fritzos 7
230  if self.datadatadatadatadata.battery_level is not None:
231  attrs[ATTR_BATTERY_LEVEL] = self.datadatadatadatadata.battery_level
232  if self.datadatadatadatadata.holiday_active is not None:
233  attrs[ATTR_STATE_HOLIDAY_MODE] = self.datadatadatadatadata.holiday_active
234  if self.datadatadatadatadata.summer_active is not None:
235  attrs[ATTR_STATE_SUMMER_MODE] = self.datadatadatadatadata.summer_active
236  if self.datadatadatadatadata.window_open is not None:
237  attrs[ATTR_STATE_WINDOW_OPEN] = self.datadatadatadatadata.window_open
238 
239  return attrs
None async_set_temperature(self, **Any kwargs)
Definition: __init__.py:775
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:813
None __init__(self, FritzboxDataUpdateCoordinator coordinator, str ain)
Definition: climate.py:97
str|UndefinedType|None name(self)
Definition: entity.py:738
None async_setup_entry(HomeAssistant hass, FritzboxConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:63
str|None value_scheduled_preset(FritzhomeDevice device)
Definition: sensor.py:95