Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for MAX! Thermostats via MAX! Cube."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from maxcube.device import (
9  MAX_DEVICE_MODE_AUTOMATIC,
10  MAX_DEVICE_MODE_BOOST,
11  MAX_DEVICE_MODE_MANUAL,
12  MAX_DEVICE_MODE_VACATION,
13 )
14 
16  PRESET_AWAY,
17  PRESET_BOOST,
18  PRESET_COMFORT,
19  PRESET_ECO,
20  PRESET_NONE,
21  ClimateEntity,
22  ClimateEntityFeature,
23  HVACAction,
24  HVACMode,
25 )
26 from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
27 from homeassistant.core import HomeAssistant
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
30 
31 from . import DATA_KEY
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 ATTR_VALVE_POSITION = "valve_position"
36 PRESET_ON = "on"
37 
38 # There are two magic temperature values, which indicate:
39 # Off (valve fully closed)
40 OFF_TEMPERATURE = 4.5
41 # On (valve fully open)
42 ON_TEMPERATURE = 30.5
43 
44 # Lowest Value without turning off
45 MIN_TEMPERATURE = 5.0
46 # Largest Value without fully opening
47 MAX_TEMPERATURE = 30.0
48 
49 
51  hass: HomeAssistant,
52  config: ConfigType,
53  add_entities: AddEntitiesCallback,
54  discovery_info: DiscoveryInfoType | None = None,
55 ) -> None:
56  """Iterate through all MAX! Devices and add thermostats."""
57 
59  MaxCubeClimate(handler, device)
60  for handler in hass.data[DATA_KEY].values()
61  for device in handler.cube.devices
62  if device.is_thermostat() or device.is_wallthermostat()
63  )
64 
65 
67  """MAX! Cube ClimateEntity."""
68 
69  _attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.HEAT]
70  _attr_supported_features = (
71  ClimateEntityFeature.TARGET_TEMPERATURE
72  | ClimateEntityFeature.PRESET_MODE
73  | ClimateEntityFeature.TURN_OFF
74  | ClimateEntityFeature.TURN_ON
75  )
76  _enable_turn_on_off_backwards_compatibility = False
77 
78  def __init__(self, handler, device):
79  """Initialize MAX! Cube ClimateEntity."""
80  room = handler.cube.room_by_id(device.room_id)
81  self._attr_name_attr_name = f"{room.name} {device.name}"
82  self._cubehandle_cubehandle = handler
83  self._device_device = device
84  self._attr_should_poll_attr_should_poll = True
85  self._attr_unique_id_attr_unique_id = self._device_device.serial
86  self._attr_temperature_unit_attr_temperature_unit = UnitOfTemperature.CELSIUS
87  self._attr_preset_modes_attr_preset_modes = [
88  PRESET_NONE,
89  PRESET_BOOST,
90  PRESET_COMFORT,
91  PRESET_ECO,
92  PRESET_AWAY,
93  PRESET_ON,
94  ]
95 
96  @property
97  def min_temp(self):
98  """Return the minimum temperature."""
99  temp = self._device_device.min_temperature or MIN_TEMPERATURE
100  # OFF_TEMPERATURE (always off) a is valid temperature to maxcube but not to Home Assistant.
101  # We use HVACMode.OFF instead to represent a turned off thermostat.
102  return max(temp, MIN_TEMPERATURE)
103 
104  @property
105  def max_temp(self):
106  """Return the maximum temperature."""
107  return self._device_device.max_temperature or MAX_TEMPERATURE
108 
109  @property
111  """Return the current temperature."""
112  return self._device_device.actual_temperature
113 
114  @property
115  def hvac_mode(self) -> HVACMode:
116  """Return current operation mode."""
117  mode = self._device_device.mode
118  if mode in (MAX_DEVICE_MODE_AUTOMATIC, MAX_DEVICE_MODE_BOOST):
119  return HVACMode.AUTO
120  if (
121  mode == MAX_DEVICE_MODE_MANUAL
122  and self._device_device.target_temperature == OFF_TEMPERATURE
123  ):
124  return HVACMode.OFF
125 
126  return HVACMode.HEAT
127 
128  def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
129  """Set new target hvac mode."""
130  if hvac_mode == HVACMode.OFF:
131  self._set_target_set_target(MAX_DEVICE_MODE_MANUAL, OFF_TEMPERATURE)
132  elif hvac_mode == HVACMode.HEAT:
133  temp = max(self._device_device.target_temperature, self.min_tempmin_tempmin_temp)
134  self._set_target_set_target(MAX_DEVICE_MODE_MANUAL, temp)
135  elif hvac_mode == HVACMode.AUTO:
136  self._set_target_set_target(MAX_DEVICE_MODE_AUTOMATIC, None)
137  else:
138  raise ValueError(f"unsupported HVAC mode {hvac_mode}")
139 
140  def _set_target(self, mode: int | None, temp: float | None) -> None:
141  """Set the mode and/or temperature of the thermostat.
142 
143  @param mode: this is the mode to change to.
144  @param temp: the temperature to target.
145 
146  Both parameters are optional. When mode is undefined, it keeps
147  the previous mode. When temp is undefined, it fetches the
148  temperature from the weekly schedule when mode is
149  MAX_DEVICE_MODE_AUTOMATIC and keeps the previous
150  temperature otherwise.
151  """
152  with self._cubehandle_cubehandle.mutex:
153  try:
154  self._cubehandle_cubehandle.cube.set_temperature_mode(self._device_device, temp, mode)
155  except (TimeoutError, OSError):
156  _LOGGER.error("Setting HVAC mode failed")
157 
158  @property
159  def hvac_action(self) -> HVACAction | None:
160  """Return the current running hvac operation if supported."""
161  valve = 0
162 
163  if self._device_device.is_thermostat():
164  valve = self._device_device.valve_position
165  elif self._device_device.is_wallthermostat():
166  cube = self._cubehandle_cubehandle.cube
167  room = cube.room_by_id(self._device_device.room_id)
168  for device in cube.devices_by_room(room):
169  if device.is_thermostat() and device.valve_position > 0:
170  valve = device.valve_position
171  break
172  else:
173  return None
174 
175  # Assume heating when valve is open
176  if valve > 0:
177  return HVACAction.HEATING
178 
179  return HVACAction.OFF if self.hvac_modehvac_modehvac_modehvac_modehvac_mode == HVACMode.OFF else HVACAction.IDLE
180 
181  @property
183  """Return the temperature we try to reach."""
184  temp = self._device_device.target_temperature
185  if temp is None or temp < self.min_tempmin_tempmin_temp or temp > self.max_tempmax_tempmax_temp:
186  return None
187  return temp
188 
189  def set_temperature(self, **kwargs: Any) -> None:
190  """Set new target temperatures."""
191  if (temp := kwargs.get(ATTR_TEMPERATURE)) is None:
192  raise ValueError(
193  f"No {ATTR_TEMPERATURE} parameter passed to set_temperature method."
194  )
195  self._set_target_set_target(None, temp)
196 
197  @property
198  def preset_mode(self):
199  """Return the current preset mode."""
200  if self._device_device.mode == MAX_DEVICE_MODE_MANUAL:
201  if self._device_device.target_temperature == self._device_device.comfort_temperature:
202  return PRESET_COMFORT
203  if self._device_device.target_temperature == self._device_device.eco_temperature:
204  return PRESET_ECO
205  if self._device_device.target_temperature == ON_TEMPERATURE:
206  return PRESET_ON
207  elif self._device_device.mode == MAX_DEVICE_MODE_BOOST:
208  return PRESET_BOOST
209  elif self._device_device.mode == MAX_DEVICE_MODE_VACATION:
210  return PRESET_AWAY
211  return PRESET_NONE
212 
213  def set_preset_mode(self, preset_mode: str) -> None:
214  """Set new operation mode."""
215  if preset_mode == PRESET_COMFORT:
216  self._set_target_set_target(MAX_DEVICE_MODE_MANUAL, self._device_device.comfort_temperature)
217  elif preset_mode == PRESET_ECO:
218  self._set_target_set_target(MAX_DEVICE_MODE_MANUAL, self._device_device.eco_temperature)
219  elif preset_mode == PRESET_ON:
220  self._set_target_set_target(MAX_DEVICE_MODE_MANUAL, ON_TEMPERATURE)
221  elif preset_mode == PRESET_AWAY:
222  self._set_target_set_target(MAX_DEVICE_MODE_VACATION, None)
223  elif preset_mode == PRESET_BOOST:
224  self._set_target_set_target(MAX_DEVICE_MODE_BOOST, None)
225  elif preset_mode == PRESET_NONE:
226  self._set_target_set_target(MAX_DEVICE_MODE_AUTOMATIC, None)
227  else:
228  raise ValueError(f"unsupported preset mode {preset_mode}")
229 
230  @property
232  """Return the optional state attributes."""
233  if not self._device_device.is_thermostat():
234  return {}
235  return {ATTR_VALVE_POSITION: self._device_device.valve_position}
236 
237  def update(self) -> None:
238  """Get latest data from MAX! Cube."""
239  self._cubehandle_cubehandle.update()
None set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:128
None _set_target(self, int|None mode, float|None temp)
Definition: climate.py:140
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: climate.py:55