Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Representation of Z-Wave thermostats."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, cast
6 
7 from zwave_js_server.client import Client as ZwaveClient
8 from zwave_js_server.const import CommandClass
9 from zwave_js_server.const.command_class.thermostat import (
10  THERMOSTAT_CURRENT_TEMP_PROPERTY,
11  THERMOSTAT_HUMIDITY_PROPERTY,
12  THERMOSTAT_MODE_PROPERTY,
13  THERMOSTAT_MODE_SETPOINT_MAP,
14  THERMOSTAT_OPERATING_STATE_PROPERTY,
15  THERMOSTAT_SETPOINT_PROPERTY,
16  ThermostatMode,
17  ThermostatOperatingState,
18  ThermostatSetpointType,
19 )
20 from zwave_js_server.model.driver import Driver
21 from zwave_js_server.model.value import Value as ZwaveValue
22 
24  ATTR_HVAC_MODE,
25  ATTR_TARGET_TEMP_HIGH,
26  ATTR_TARGET_TEMP_LOW,
27  DOMAIN as CLIMATE_DOMAIN,
28  PRESET_NONE,
29  ClimateEntity,
30  ClimateEntityFeature,
31  HVACAction,
32  HVACMode,
33 )
34 from homeassistant.config_entries import ConfigEntry
35 from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, UnitOfTemperature
36 from homeassistant.core import HomeAssistant, callback
37 from homeassistant.helpers.dispatcher import async_dispatcher_connect
38 from homeassistant.helpers.entity_platform import AddEntitiesCallback
39 from homeassistant.util.unit_conversion import TemperatureConverter
40 
41 from .const import DATA_CLIENT, DOMAIN
42 from .discovery import ZwaveDiscoveryInfo
43 from .discovery_data_template import DynamicCurrentTempClimateDataTemplate
44 from .entity import ZWaveBaseEntity
45 from .helpers import get_value_of_zwave_value
46 
47 PARALLEL_UPDATES = 0
48 
49 THERMOSTAT_MODES = [
50  ThermostatMode.OFF,
51  ThermostatMode.HEAT,
52  ThermostatMode.COOL,
53  ThermostatMode.AUTO,
54  ThermostatMode.AUTO_CHANGE_OVER,
55  ThermostatMode.FAN,
56  ThermostatMode.DRY,
57 ]
58 
59 # Map Z-Wave HVAC Mode to Home Assistant value
60 # Note: We treat "auto" as "heat_cool" as most Z-Wave devices
61 # report auto_changeover as auto without schedule support.
62 ZW_HVAC_MODE_MAP: dict[int, HVACMode] = {
63  ThermostatMode.OFF: HVACMode.OFF,
64  ThermostatMode.HEAT: HVACMode.HEAT,
65  ThermostatMode.COOL: HVACMode.COOL,
66  # Z-Wave auto mode is actually heat/cool in the hass world
67  ThermostatMode.AUTO: HVACMode.HEAT_COOL,
68  ThermostatMode.AUXILIARY: HVACMode.HEAT,
69  ThermostatMode.FAN: HVACMode.FAN_ONLY,
70  ThermostatMode.FURNACE: HVACMode.HEAT,
71  ThermostatMode.DRY: HVACMode.DRY,
72  ThermostatMode.AUTO_CHANGE_OVER: HVACMode.HEAT_COOL,
73  ThermostatMode.HEATING_ECON: HVACMode.HEAT,
74  ThermostatMode.COOLING_ECON: HVACMode.COOL,
75  ThermostatMode.AWAY: HVACMode.HEAT_COOL,
76  ThermostatMode.FULL_POWER: HVACMode.HEAT,
77 }
78 
79 HVAC_CURRENT_MAP: dict[int, HVACAction] = {
80  ThermostatOperatingState.IDLE: HVACAction.IDLE,
81  ThermostatOperatingState.PENDING_HEAT: HVACAction.IDLE,
82  ThermostatOperatingState.HEATING: HVACAction.HEATING,
83  ThermostatOperatingState.PENDING_COOL: HVACAction.IDLE,
84  ThermostatOperatingState.COOLING: HVACAction.COOLING,
85  ThermostatOperatingState.FAN_ONLY: HVACAction.FAN,
86  ThermostatOperatingState.VENT_ECONOMIZER: HVACAction.FAN,
87  ThermostatOperatingState.AUX_HEATING: HVACAction.HEATING,
88  ThermostatOperatingState.SECOND_STAGE_HEATING: HVACAction.HEATING,
89  ThermostatOperatingState.SECOND_STAGE_COOLING: HVACAction.COOLING,
90  ThermostatOperatingState.SECOND_STAGE_AUX_HEAT: HVACAction.HEATING,
91  ThermostatOperatingState.THIRD_STAGE_AUX_HEAT: HVACAction.HEATING,
92 }
93 
94 ATTR_FAN_STATE = "fan_state"
95 
96 
98  hass: HomeAssistant,
99  config_entry: ConfigEntry,
100  async_add_entities: AddEntitiesCallback,
101 ) -> None:
102  """Set up Z-Wave climate from config entry."""
103  client: ZwaveClient = config_entry.runtime_data[DATA_CLIENT]
104 
105  @callback
106  def async_add_climate(info: ZwaveDiscoveryInfo) -> None:
107  """Add Z-Wave Climate."""
108  driver = client.driver
109  assert driver is not None # Driver is ready before platforms are loaded.
110  entities: list[ZWaveBaseEntity] = []
111  if info.platform_hint == "dynamic_current_temp":
112  entities.append(DynamicCurrentTempClimate(config_entry, driver, info))
113  else:
114  entities.append(ZWaveClimate(config_entry, driver, info))
115 
116  async_add_entities(entities)
117 
118  config_entry.async_on_unload(
120  hass,
121  f"{DOMAIN}_{config_entry.entry_id}_add_{CLIMATE_DOMAIN}",
122  async_add_climate,
123  )
124  )
125 
126 
128  """Representation of a Z-Wave climate."""
129 
130  _attr_precision = PRECISION_TENTHS
131  _enable_turn_on_off_backwards_compatibility = False
132 
133  def __init__(
134  self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
135  ) -> None:
136  """Initialize thermostat."""
137  super().__init__(config_entry, driver, info)
138  self._hvac_modes_hvac_modes: dict[HVACMode, int | None] = {}
139  self._hvac_presets_hvac_presets: dict[str, int | None] = {}
140  self._unit_value_unit_value: ZwaveValue | None = None
141  self._last_hvac_mode_id_before_off_last_hvac_mode_id_before_off: int | None = None
142 
143  self._current_mode_current_mode = self.get_zwave_valueget_zwave_value(
144  THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE
145  )
146  self._supports_resume: bool = bool(
147  self._current_mode_current_mode
148  and (
149  str(ThermostatMode.RESUME_ON.value)
150  in self._current_mode_current_mode.metadata.states
151  )
152  )
153 
154  self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue | None] = {}
155  for enum in ThermostatSetpointType:
156  self._setpoint_values[enum] = self.get_zwave_valueget_zwave_value(
157  THERMOSTAT_SETPOINT_PROPERTY,
158  command_class=CommandClass.THERMOSTAT_SETPOINT,
159  value_property_key=enum.value,
160  add_to_watched_value_ids=True,
161  )
162  # Use the first found non N/A setpoint value to always determine the
163  # temperature unit
164  if (
165  not self._unit_value_unit_value
166  and enum != ThermostatSetpointType.NA
167  and self._setpoint_values[enum]
168  ):
169  self._unit_value_unit_value = self._setpoint_values[enum]
170  self._operating_state_operating_state = self.get_zwave_valueget_zwave_value(
171  THERMOSTAT_OPERATING_STATE_PROPERTY,
172  command_class=CommandClass.THERMOSTAT_OPERATING_STATE,
173  add_to_watched_value_ids=True,
174  check_all_endpoints=True,
175  )
176  self._current_temp_current_temp = self.get_zwave_valueget_zwave_value(
177  THERMOSTAT_CURRENT_TEMP_PROPERTY,
178  command_class=CommandClass.SENSOR_MULTILEVEL,
179  add_to_watched_value_ids=True,
180  check_all_endpoints=True,
181  )
182  if not self._unit_value_unit_value:
183  self._unit_value_unit_value = self._current_temp_current_temp
184  self._current_humidity_current_humidity = self.get_zwave_valueget_zwave_value(
185  THERMOSTAT_HUMIDITY_PROPERTY,
186  command_class=CommandClass.SENSOR_MULTILEVEL,
187  add_to_watched_value_ids=True,
188  check_all_endpoints=True,
189  )
190  self._fan_mode_fan_mode = self.get_zwave_valueget_zwave_value(
191  THERMOSTAT_MODE_PROPERTY,
192  CommandClass.THERMOSTAT_FAN_MODE,
193  add_to_watched_value_ids=True,
194  check_all_endpoints=True,
195  )
196  self._fan_state_fan_state = self.get_zwave_valueget_zwave_value(
197  THERMOSTAT_OPERATING_STATE_PROPERTY,
198  CommandClass.THERMOSTAT_FAN_STATE,
199  add_to_watched_value_ids=True,
200  check_all_endpoints=True,
201  )
202  self._set_modes_and_presets_set_modes_and_presets()
203  if self._current_mode_current_mode and len(self._hvac_presets_hvac_presets) > 1:
204  self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
205  if HVACMode.OFF in self._hvac_modes_hvac_modes:
206  self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
207  # We can only support turn on if we are able to turn the device off,
208  # otherwise the device can be considered always on
209  if len(self._hvac_modes_hvac_modes) > 1:
210  self._attr_supported_features |= ClimateEntityFeature.TURN_ON
211  # If any setpoint value exists, we can assume temperature
212  # can be set
213  if any(self._setpoint_values.values()):
214  self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
215  if HVACMode.HEAT_COOL in self.hvac_modeshvac_modeshvac_modes:
216  self._attr_supported_features |= (
217  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
218  )
219  if self._fan_mode_fan_mode:
220  self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
221 
223  self, setpoint_type: ThermostatSetpointType
224  ) -> ZwaveValue:
225  """Return a ZwaveValue for a setpoint or raise if not available."""
226  if (val := self._setpoint_values[setpoint_type]) is None:
227  raise ValueError("Value requested is not available")
228 
229  return val
230 
232  self, setpoint_type: ThermostatSetpointType
233  ) -> float | None:
234  """Optionally return the temperature value of a setpoint."""
235  try:
236  temp = self._setpoint_value_or_raise_setpoint_value_or_raise(setpoint_type)
237  except (IndexError, ValueError):
238  return None
239  return get_value_of_zwave_value(temp)
240 
241  def _set_modes_and_presets(self) -> None:
242  """Convert Z-Wave Thermostat modes into Home Assistant modes and presets."""
243  all_modes: dict[HVACMode, int | None] = {}
244  all_presets: dict[str, int | None] = {PRESET_NONE: None}
245 
246  # Z-Wave uses one list for both modes and presets.
247  # Iterate over all Z-Wave ThermostatModes
248  # and extract the hvac modes and presets.
249  if self._current_mode_current_mode is None:
250  self._hvac_modes_hvac_modes = {
251  ZW_HVAC_MODE_MAP[ThermostatMode.HEAT]: ThermostatMode.HEAT
252  }
253  return
254  for mode_id, mode_name in self._current_mode_current_mode.metadata.states.items():
255  mode_id = int(mode_id)
256  if mode_id in THERMOSTAT_MODES:
257  # treat value as hvac mode
258  if hass_mode := ZW_HVAC_MODE_MAP.get(mode_id):
259  all_modes[hass_mode] = mode_id
260  else:
261  # treat value as hvac preset
262  all_presets[mode_name] = mode_id
263 
264  self._hvac_modes_hvac_modes = all_modes
265  self._hvac_presets_hvac_presets = all_presets
266 
267  @property
268  def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType]:
269  """Return the list of enums that are relevant to the current thermostat mode."""
270  if self._current_mode_current_mode is None or self._current_mode_current_mode.value is None:
271  # Thermostat(valve) with no support for setting a mode
272  # is considered heating-only
273  return [ThermostatSetpointType.HEATING]
274  return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode_current_mode.value), [])
275 
276  @property
277  def temperature_unit(self) -> str:
278  """Return the unit of measurement used by the platform."""
279  if (
280  self._unit_value_unit_value
281  and self._unit_value_unit_value.metadata.unit
282  and "f" in self._unit_value_unit_value.metadata.unit.lower()
283  ):
284  return UnitOfTemperature.FAHRENHEIT
285  return UnitOfTemperature.CELSIUS
286 
287  @property
288  def hvac_mode(self) -> HVACMode:
289  """Return hvac operation ie. heat, cool mode."""
290  if self._current_mode_current_mode is None:
291  # Thermostat(valve) with no support for setting
292  # a mode is considered heating-only
293  return HVACMode.HEAT
294  if self._current_mode_current_mode.value is None:
295  # guard missing value
296  return HVACMode.HEAT
297  return ZW_HVAC_MODE_MAP.get(int(self._current_mode_current_mode.value), HVACMode.HEAT_COOL)
298 
299  @property
300  def hvac_modes(self) -> list[HVACMode]:
301  """Return the list of available hvac operation modes."""
302  return list(self._hvac_modes_hvac_modes)
303 
304  @property
305  def hvac_action(self) -> HVACAction | None:
306  """Return the current running hvac operation if supported."""
307  if not self._operating_state_operating_state:
308  return None
309  if self._operating_state_operating_state.value is None:
310  # guard missing value
311  return None
312  return HVAC_CURRENT_MAP.get(int(self._operating_state_operating_state.value))
313 
314  @property
315  def current_humidity(self) -> int | None:
316  """Return the current humidity level."""
317  return get_value_of_zwave_value(self._current_humidity_current_humidity)
318 
319  @property
320  def current_temperature(self) -> float | None:
321  """Return the current temperature."""
322  return get_value_of_zwave_value(self._current_temp_current_temp)
323 
324  @property
325  def target_temperature(self) -> float | None:
326  """Return the temperature we try to reach."""
327  if (
328  self._current_mode_current_mode and self._current_mode_current_mode.value is None
329  ) or not self._current_mode_setpoint_enums_current_mode_setpoint_enums:
330  # guard missing value
331  return None
332  if len(self._current_mode_setpoint_enums_current_mode_setpoint_enums) > 1:
333  # current mode has a temperature range
334  return None
335 
336  return self._setpoint_temperature_setpoint_temperature(self._current_mode_setpoint_enums_current_mode_setpoint_enums[0])
337 
338  @property
339  def target_temperature_high(self) -> float | None:
340  """Return the highbound target temperature we try to reach."""
341  if (
342  self._current_mode_current_mode and self._current_mode_current_mode.value is None
343  ) or not self._current_mode_setpoint_enums_current_mode_setpoint_enums:
344  # guard missing value
345  return None
346  if len(self._current_mode_setpoint_enums_current_mode_setpoint_enums) < 2:
347  # current mode has a single temperature
348  return None
349 
350  return self._setpoint_temperature_setpoint_temperature(self._current_mode_setpoint_enums_current_mode_setpoint_enums[1])
351 
352  @property
353  def target_temperature_low(self) -> float | None:
354  """Return the lowbound target temperature we try to reach."""
355  if (
356  self._current_mode_current_mode and self._current_mode_current_mode.value is None
357  ) or not self._current_mode_setpoint_enums_current_mode_setpoint_enums:
358  # guard missing value
359  return None
360  if len(self._current_mode_setpoint_enums_current_mode_setpoint_enums) < 2:
361  # current mode has a single temperature
362  return None
363 
364  return self._setpoint_temperature_setpoint_temperature(self._current_mode_setpoint_enums_current_mode_setpoint_enums[0])
365 
366  @property
367  def preset_mode(self) -> str | None:
368  """Return the current preset mode, e.g., home, away, temp."""
369  if self._current_mode_current_mode is None or self._current_mode_current_mode.value is None:
370  # guard missing value
371  return None
372  if int(self._current_mode_current_mode.value) not in THERMOSTAT_MODES:
373  return_val: str = cast(
374  str,
375  self._current_mode_current_mode.metadata.states.get(str(self._current_mode_current_mode.value)),
376  )
377  return return_val
378  return PRESET_NONE
379 
380  @property
381  def preset_modes(self) -> list[str] | None:
382  """Return a list of available preset modes."""
383  return list(self._hvac_presets_hvac_presets)
384 
385  @property
386  def fan_mode(self) -> str | None:
387  """Return the fan setting."""
388  if (
389  self._fan_mode_fan_mode
390  and self._fan_mode_fan_mode.value is not None
391  and str(self._fan_mode_fan_mode.value) in self._fan_mode_fan_mode.metadata.states
392  ):
393  return cast(str, self._fan_mode_fan_mode.metadata.states[str(self._fan_mode_fan_mode.value)])
394  return None
395 
396  @property
397  def fan_modes(self) -> list[str] | None:
398  """Return the list of available fan modes."""
399  if self._fan_mode_fan_mode and self._fan_mode_fan_mode.metadata.states:
400  return list(self._fan_mode_fan_mode.metadata.states.values())
401  return None
402 
403  @property
404  def extra_state_attributes(self) -> dict[str, str] | None:
405  """Return the optional state attributes."""
406  if (
407  self._fan_state_fan_state
408  and self._fan_state_fan_state.value is not None
409  and str(self._fan_state_fan_state.value) in self._fan_state_fan_state.metadata.states
410  ):
411  return {
412  ATTR_FAN_STATE: self._fan_state_fan_state.metadata.states[
413  str(self._fan_state_fan_state.value)
414  ]
415  }
416 
417  return None
418 
419  @property
420  def min_temp(self) -> float:
421  """Return the minimum temperature."""
422  min_temp = 0.0 # Not using DEFAULT_MIN_TEMP to allow wider range
423  base_unit: str = UnitOfTemperature.CELSIUS
424  try:
425  temp = self._setpoint_value_or_raise_setpoint_value_or_raise(self._current_mode_setpoint_enums_current_mode_setpoint_enums[0])
426  if temp.metadata.min:
427  min_temp = temp.metadata.min
428  base_unit = self.temperature_unittemperature_unittemperature_unit
429  # In case of any error, we fallback to the default
430  except (IndexError, ValueError, TypeError):
431  pass
432 
433  return TemperatureConverter.convert(min_temp, base_unit, self.temperature_unittemperature_unittemperature_unit)
434 
435  @property
436  def max_temp(self) -> float:
437  """Return the maximum temperature."""
438  max_temp = 50.0 # Not using DEFAULT_MAX_TEMP to allow wider range
439  base_unit: str = UnitOfTemperature.CELSIUS
440  try:
441  temp = self._setpoint_value_or_raise_setpoint_value_or_raise(self._current_mode_setpoint_enums_current_mode_setpoint_enums[0])
442  if temp.metadata.max:
443  max_temp = temp.metadata.max
444  base_unit = self.temperature_unittemperature_unittemperature_unit
445  # In case of any error, we fallback to the default
446  except (IndexError, ValueError, TypeError):
447  pass
448 
449  return TemperatureConverter.convert(max_temp, base_unit, self.temperature_unittemperature_unittemperature_unit)
450 
451  async def async_set_fan_mode(self, fan_mode: str) -> None:
452  """Set new target fan mode."""
453  assert self._fan_mode_fan_mode is not None
454  try:
455  new_state = int(
456  next(
457  state
458  for state, label in self._fan_mode_fan_mode.metadata.states.items()
459  if label == fan_mode
460  )
461  )
462  except StopIteration:
463  raise ValueError(f"Received an invalid fan mode: {fan_mode}") from None
464 
465  await self._async_set_value_async_set_value(self._fan_mode_fan_mode, new_state)
466 
467  async def async_set_temperature(self, **kwargs: Any) -> None:
468  """Set new target temperature."""
469  hvac_mode: HVACMode | None = kwargs.get(ATTR_HVAC_MODE)
470 
471  if hvac_mode is not None:
472  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(hvac_mode)
473  if len(self._current_mode_setpoint_enums_current_mode_setpoint_enums) == 1:
474  setpoint: ZwaveValue = self._setpoint_value_or_raise_setpoint_value_or_raise(
475  self._current_mode_setpoint_enums_current_mode_setpoint_enums[0]
476  )
477  target_temp: float | None = kwargs.get(ATTR_TEMPERATURE)
478  if target_temp is not None:
479  await self._async_set_value_async_set_value(setpoint, target_temp)
480  elif len(self._current_mode_setpoint_enums_current_mode_setpoint_enums) == 2:
481  setpoint_low: ZwaveValue = self._setpoint_value_or_raise_setpoint_value_or_raise(
482  self._current_mode_setpoint_enums_current_mode_setpoint_enums[0]
483  )
484  setpoint_high: ZwaveValue = self._setpoint_value_or_raise_setpoint_value_or_raise(
485  self._current_mode_setpoint_enums_current_mode_setpoint_enums[1]
486  )
487  target_temp_low: float | None = kwargs.get(ATTR_TARGET_TEMP_LOW)
488  target_temp_high: float | None = kwargs.get(ATTR_TARGET_TEMP_HIGH)
489  if target_temp_low is not None:
490  await self._async_set_value_async_set_value(setpoint_low, target_temp_low)
491  if target_temp_high is not None:
492  await self._async_set_value_async_set_value(setpoint_high, target_temp_high)
493 
494  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
495  """Set new target hvac mode."""
496  if (hvac_mode_id := self._hvac_modes_hvac_modes.get(hvac_mode)) is None:
497  raise ValueError(f"Received an invalid hvac mode: {hvac_mode}")
498 
499  if not self._current_mode_current_mode:
500  # Thermostat(valve) has no support for setting a mode, so we make it a no-op
501  return
502 
503  # When turning the HVAC off from an on state, store the last HVAC mode ID so we
504  # can set it again when turning the device back on.
505  if hvac_mode == HVACMode.OFF and self._current_mode_current_mode.value != ThermostatMode.OFF:
506  self._last_hvac_mode_id_before_off_last_hvac_mode_id_before_off = self._current_mode_current_mode.value
507  await self._async_set_value_async_set_value(self._current_mode_current_mode, hvac_mode_id)
508 
509  async def async_turn_off(self) -> None:
510  """Turn the entity off."""
511  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(HVACMode.OFF)
512 
513  async def async_turn_on(self) -> None:
514  """Turn the entity on."""
515  # If current mode is not off, do nothing
516  if self.hvac_modehvac_modehvac_modehvac_mode != HVACMode.OFF:
517  return
518 
519  # We can safely assert here because this function can only be called if the
520  # device can be turned off and on which would require the device to have the
521  # current mode Z-Wave Value
522  assert self._current_mode_current_mode
523 
524  # If the device supports resume, use resume to get to the right mode
525  if self._supports_resume:
526  await self._async_set_value_async_set_value(self._current_mode_current_mode, ThermostatMode.RESUME_ON)
527  return
528 
529  # If we have an HVAC mode ID from before the device was turned off, set it to
530  # that mode
531  if self._last_hvac_mode_id_before_off_last_hvac_mode_id_before_off is not None:
532  await self._async_set_value_async_set_value(
533  self._current_mode_current_mode, self._last_hvac_mode_id_before_off_last_hvac_mode_id_before_off
534  )
535  self._last_hvac_mode_id_before_off_last_hvac_mode_id_before_off = None
536  return
537 
538  # Attempt to set the device to the first available mode among heat_cool, heat,
539  # and cool to mirror previous behavior. If none of those are available, set it
540  # to the first available mode that is not off.
541  try:
542  hvac_mode = next(
543  mode
544  for mode in (HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.COOL)
545  if mode in self._hvac_modes_hvac_modes
546  )
547  except StopIteration:
548  hvac_mode = next(mode for mode in self._hvac_modes_hvac_modes if mode != HVACMode.OFF)
549  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(hvac_mode)
550 
551  async def async_set_preset_mode(self, preset_mode: str) -> None:
552  """Set new target preset mode."""
553  assert self._current_mode_current_mode is not None
554  if preset_mode == PRESET_NONE:
555  # try to restore to the (translated) main hvac mode
556  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(self.hvac_modehvac_modehvac_modehvac_mode)
557  return
558  preset_mode_value = self._hvac_presets_hvac_presets.get(preset_mode)
559  if preset_mode_value is None:
560  raise ValueError(f"Received an invalid preset mode: {preset_mode}")
561 
562  await self._async_set_value_async_set_value(self._current_mode_current_mode, preset_mode_value)
563 
564 
566  """Representation of a thermostat that can dynamically use a different Zwave Value for current temp."""
567 
568  def __init__(
569  self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
570  ) -> None:
571  """Initialize thermostat."""
572  super().__init__(config_entry, driver, info)
573  self.data_templatedata_template = cast(
574  DynamicCurrentTempClimateDataTemplate, self.infoinfo.platform_data_template
575  )
576 
577  @property
578  def current_temperature(self) -> float | None:
579  """Return the current temperature."""
580  assert self.infoinfo.platform_data
582  self.data_templatedata_template.current_temperature_value(self.infoinfo.platform_data)
583  )
584  return val if val is not None else super().current_temperature
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:813
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
Definition: climate.py:570
ZwaveValue _setpoint_value_or_raise(self, ThermostatSetpointType setpoint_type)
Definition: climate.py:224
None async_set_preset_mode(self, str preset_mode)
Definition: climate.py:551
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
Definition: climate.py:135
list[ThermostatSetpointType] _current_mode_setpoint_enums(self)
Definition: climate.py:268
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:494
float|None _setpoint_temperature(self, ThermostatSetpointType setpoint_type)
Definition: climate.py:233
SetValueResult|None _async_set_value(self, ZwaveValue value, Any new_value, dict|None options=None, bool|None wait_for_result=None)
Definition: entity.py:330
ZwaveValue|None get_zwave_value(self, str|int value_property, int|None command_class=None, int|None endpoint=None, int|str|None value_property_key=None, bool add_to_watched_value_ids=True, bool check_all_endpoints=False)
Definition: entity.py:280
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:101
Any|None get_value_of_zwave_value(ZwaveValue|None value)
Definition: helpers.py:129
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103