Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Adds support for generic thermostat units."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Mapping
7 from datetime import datetime, timedelta
8 import logging
9 import math
10 from typing import Any
11 
12 import voluptuous as vol
13 
15  ATTR_PRESET_MODE,
16  PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA,
17  PRESET_NONE,
18  ClimateEntity,
19  ClimateEntityFeature,
20  HVACAction,
21  HVACMode,
22 )
23 from homeassistant.config_entries import ConfigEntry
24 from homeassistant.const import (
25  ATTR_ENTITY_ID,
26  ATTR_TEMPERATURE,
27  CONF_NAME,
28  CONF_UNIQUE_ID,
29  EVENT_HOMEASSISTANT_START,
30  PRECISION_HALVES,
31  PRECISION_TENTHS,
32  PRECISION_WHOLE,
33  SERVICE_TURN_OFF,
34  SERVICE_TURN_ON,
35  STATE_ON,
36  STATE_UNAVAILABLE,
37  STATE_UNKNOWN,
38  UnitOfTemperature,
39 )
40 from homeassistant.core import (
41  DOMAIN as HOMEASSISTANT_DOMAIN,
42  CoreState,
43  Event,
44  EventStateChangedData,
45  HomeAssistant,
46  State,
47  callback,
48 )
49 from homeassistant.exceptions import ConditionError
50 from homeassistant.helpers import condition, config_validation as cv
51 from homeassistant.helpers.device import async_device_info_to_link_from_entity
52 from homeassistant.helpers.entity_platform import AddEntitiesCallback
53 from homeassistant.helpers.event import (
54  async_track_state_change_event,
55  async_track_time_interval,
56 )
57 from homeassistant.helpers.reload import async_setup_reload_service
58 from homeassistant.helpers.restore_state import RestoreEntity
59 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, VolDictType
60 
61 from .const import (
62  CONF_AC_MODE,
63  CONF_COLD_TOLERANCE,
64  CONF_HEATER,
65  CONF_HOT_TOLERANCE,
66  CONF_MIN_DUR,
67  CONF_PRESETS,
68  CONF_SENSOR,
69  DEFAULT_TOLERANCE,
70  DOMAIN,
71  PLATFORMS,
72 )
73 
74 _LOGGER = logging.getLogger(__name__)
75 
76 DEFAULT_NAME = "Generic Thermostat"
77 
78 CONF_INITIAL_HVAC_MODE = "initial_hvac_mode"
79 CONF_KEEP_ALIVE = "keep_alive"
80 CONF_MIN_TEMP = "min_temp"
81 CONF_MAX_TEMP = "max_temp"
82 CONF_PRECISION = "precision"
83 CONF_TARGET_TEMP = "target_temp"
84 CONF_TEMP_STEP = "target_temp_step"
85 
86 
87 PRESETS_SCHEMA: VolDictType = {
88  vol.Optional(v): vol.Coerce(float) for v in CONF_PRESETS.values()
89 }
90 
91 PLATFORM_SCHEMA_COMMON = vol.Schema(
92  {
93  vol.Required(CONF_HEATER): cv.entity_id,
94  vol.Required(CONF_SENSOR): cv.entity_id,
95  vol.Optional(CONF_AC_MODE): cv.boolean,
96  vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
97  vol.Optional(CONF_MIN_DUR): cv.positive_time_period,
98  vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
99  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
100  vol.Optional(CONF_COLD_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float),
101  vol.Optional(CONF_HOT_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float),
102  vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
103  vol.Optional(CONF_KEEP_ALIVE): cv.positive_time_period,
104  vol.Optional(CONF_INITIAL_HVAC_MODE): vol.In(
105  [HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF]
106  ),
107  vol.Optional(CONF_PRECISION): vol.All(
108  vol.Coerce(float),
109  vol.In([PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]),
110  ),
111  vol.Optional(CONF_TEMP_STEP): vol.All(
112  vol.In([PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE])
113  ),
114  vol.Optional(CONF_UNIQUE_ID): cv.string,
115  **PRESETS_SCHEMA,
116  }
117 )
118 
119 
120 PLATFORM_SCHEMA = CLIMATE_PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_COMMON.schema)
121 
122 
124  hass: HomeAssistant,
125  config_entry: ConfigEntry,
126  async_add_entities: AddEntitiesCallback,
127 ) -> None:
128  """Initialize config entry."""
129  await _async_setup_config(
130  hass,
131  PLATFORM_SCHEMA_COMMON(dict(config_entry.options)),
132  config_entry.entry_id,
133  async_add_entities,
134  )
135 
136 
138  hass: HomeAssistant,
139  config: ConfigType,
140  async_add_entities: AddEntitiesCallback,
141  discovery_info: DiscoveryInfoType | None = None,
142 ) -> None:
143  """Set up the generic thermostat platform."""
144 
145  await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
146  await _async_setup_config(
147  hass, config, config.get(CONF_UNIQUE_ID), async_add_entities
148  )
149 
150 
152  hass: HomeAssistant,
153  config: Mapping[str, Any],
154  unique_id: str | None,
155  async_add_entities: AddEntitiesCallback,
156 ) -> None:
157  """Set up the generic thermostat platform."""
158 
159  name: str = config[CONF_NAME]
160  heater_entity_id: str = config[CONF_HEATER]
161  sensor_entity_id: str = config[CONF_SENSOR]
162  min_temp: float | None = config.get(CONF_MIN_TEMP)
163  max_temp: float | None = config.get(CONF_MAX_TEMP)
164  target_temp: float | None = config.get(CONF_TARGET_TEMP)
165  ac_mode: bool | None = config.get(CONF_AC_MODE)
166  min_cycle_duration: timedelta | None = config.get(CONF_MIN_DUR)
167  cold_tolerance: float = config[CONF_COLD_TOLERANCE]
168  hot_tolerance: float = config[CONF_HOT_TOLERANCE]
169  keep_alive: timedelta | None = config.get(CONF_KEEP_ALIVE)
170  initial_hvac_mode: HVACMode | None = config.get(CONF_INITIAL_HVAC_MODE)
171  presets: dict[str, float] = {
172  key: config[value] for key, value in CONF_PRESETS.items() if value in config
173  }
174  precision: float | None = config.get(CONF_PRECISION)
175  target_temperature_step: float | None = config.get(CONF_TEMP_STEP)
176  unit = hass.config.units.temperature_unit
177 
179  [
181  hass,
182  name,
183  heater_entity_id,
184  sensor_entity_id,
185  min_temp,
186  max_temp,
187  target_temp,
188  ac_mode,
189  min_cycle_duration,
190  cold_tolerance,
191  hot_tolerance,
192  keep_alive,
193  initial_hvac_mode,
194  presets,
195  precision,
196  target_temperature_step,
197  unit,
198  unique_id,
199  )
200  ]
201  )
202 
203 
205  """Representation of a Generic Thermostat device."""
206 
207  _attr_should_poll = False
208  _enable_turn_on_off_backwards_compatibility = False
209 
210  def __init__(
211  self,
212  hass: HomeAssistant,
213  name: str,
214  heater_entity_id: str,
215  sensor_entity_id: str,
216  min_temp: float | None,
217  max_temp: float | None,
218  target_temp: float | None,
219  ac_mode: bool | None,
220  min_cycle_duration: timedelta | None,
221  cold_tolerance: float,
222  hot_tolerance: float,
223  keep_alive: timedelta | None,
224  initial_hvac_mode: HVACMode | None,
225  presets: dict[str, float],
226  precision: float | None,
227  target_temperature_step: float | None,
228  unit: UnitOfTemperature,
229  unique_id: str | None,
230  ) -> None:
231  """Initialize the thermostat."""
232  self._attr_name_attr_name = name
233  self.heater_entity_idheater_entity_id = heater_entity_id
234  self.sensor_entity_idsensor_entity_id = sensor_entity_id
236  hass,
237  heater_entity_id,
238  )
239  self.ac_modeac_mode = ac_mode
240  self.min_cycle_durationmin_cycle_duration = min_cycle_duration
241  self._cold_tolerance_cold_tolerance = cold_tolerance
242  self._hot_tolerance_hot_tolerance = hot_tolerance
243  self._keep_alive_keep_alive = keep_alive
244  self._hvac_mode_hvac_mode = initial_hvac_mode
245  self._saved_target_temp_saved_target_temp = target_temp or next(iter(presets.values()), None)
246  self._temp_precision_temp_precision = precision
247  self._temp_target_temperature_step_temp_target_temperature_step = target_temperature_step
248  if self.ac_modeac_mode:
249  self._attr_hvac_modes_attr_hvac_modes = [HVACMode.COOL, HVACMode.OFF]
250  else:
251  self._attr_hvac_modes_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
252  self._active_active = False
253  self._cur_temp_cur_temp: float | None = None
254  self._temp_lock_temp_lock = asyncio.Lock()
255  self._min_temp_min_temp = min_temp
256  self._max_temp_max_temp = max_temp
257  self._attr_preset_mode_attr_preset_mode = PRESET_NONE
258  self._target_temp_target_temp = target_temp
259  self._attr_temperature_unit_attr_temperature_unit = unit
260  self._attr_unique_id_attr_unique_id = unique_id
261  self._attr_supported_features_attr_supported_features = (
262  ClimateEntityFeature.TARGET_TEMPERATURE
263  | ClimateEntityFeature.TURN_OFF
264  | ClimateEntityFeature.TURN_ON
265  )
266  if len(presets):
267  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.PRESET_MODE
268  self._attr_preset_modes_attr_preset_modes = [PRESET_NONE, *presets.keys()]
269  else:
270  self._attr_preset_modes_attr_preset_modes = [PRESET_NONE]
271  self._presets_presets = presets
272 
273  async def async_added_to_hass(self) -> None:
274  """Run when entity about to be added."""
275  await super().async_added_to_hass()
276 
277  # Add listener
278  self.async_on_removeasync_on_remove(
280  self.hasshass, [self.sensor_entity_idsensor_entity_id], self._async_sensor_changed_async_sensor_changed
281  )
282  )
283  self.async_on_removeasync_on_remove(
285  self.hasshass, [self.heater_entity_idheater_entity_id], self._async_switch_changed_async_switch_changed
286  )
287  )
288 
289  if self._keep_alive_keep_alive:
290  self.async_on_removeasync_on_remove(
292  self.hasshass, self._async_control_heating_async_control_heating, self._keep_alive_keep_alive
293  )
294  )
295 
296  @callback
297  def _async_startup(_: Event | None = None) -> None:
298  """Init on startup."""
299  sensor_state = self.hasshass.states.get(self.sensor_entity_idsensor_entity_id)
300  if sensor_state and sensor_state.state not in (
301  STATE_UNAVAILABLE,
302  STATE_UNKNOWN,
303  ):
304  self._async_update_temp_async_update_temp(sensor_state)
305  self.async_write_ha_stateasync_write_ha_state()
306  switch_state = self.hasshass.states.get(self.heater_entity_idheater_entity_id)
307  if switch_state and switch_state.state not in (
308  STATE_UNAVAILABLE,
309  STATE_UNKNOWN,
310  ):
311  self.hasshass.async_create_task(
312  self._check_switch_initial_state_check_switch_initial_state(), eager_start=True
313  )
314 
315  if self.hasshass.state is CoreState.running:
316  _async_startup()
317  else:
318  self.hasshass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_startup)
319 
320  # Check If we have an old state
321  if (old_state := await self.async_get_last_stateasync_get_last_state()) is not None:
322  # If we have no initial temperature, restore
323  if self._target_temp_target_temp is None:
324  # If we have a previously saved temperature
325  if old_state.attributes.get(ATTR_TEMPERATURE) is None:
326  if self.ac_modeac_mode:
327  self._target_temp_target_temp = self.max_tempmax_tempmax_temp
328  else:
329  self._target_temp_target_temp = self.min_tempmin_tempmin_temp
330  _LOGGER.warning(
331  "Undefined target temperature, falling back to %s",
332  self._target_temp_target_temp,
333  )
334  else:
335  self._target_temp_target_temp = float(old_state.attributes[ATTR_TEMPERATURE])
336  if (
337  self.preset_modespreset_modes
338  and old_state.attributes.get(ATTR_PRESET_MODE) in self.preset_modespreset_modes
339  ):
340  self._attr_preset_mode_attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
341  if not self._hvac_mode_hvac_mode and old_state.state:
342  self._hvac_mode_hvac_mode = HVACMode(old_state.state)
343 
344  else:
345  # No previous state, try and restore defaults
346  if self._target_temp_target_temp is None:
347  if self.ac_modeac_mode:
348  self._target_temp_target_temp = self.max_tempmax_tempmax_temp
349  else:
350  self._target_temp_target_temp = self.min_tempmin_tempmin_temp
351  _LOGGER.warning(
352  "No previously saved temperature, setting to %s", self._target_temp_target_temp
353  )
354 
355  # Set default state to off
356  if not self._hvac_mode_hvac_mode:
357  self._hvac_mode_hvac_mode = HVACMode.OFF
358 
359  @property
360  def precision(self) -> float:
361  """Return the precision of the system."""
362  if self._temp_precision_temp_precision is not None:
363  return self._temp_precision_temp_precision
364  return super().precision
365 
366  @property
367  def target_temperature_step(self) -> float:
368  """Return the supported step of target temperature."""
369  if self._temp_target_temperature_step_temp_target_temperature_step is not None:
370  return self._temp_target_temperature_step_temp_target_temperature_step
371  # if a target_temperature_step is not defined, fallback to equal the precision
372  return self.precisionprecisionprecision
373 
374  @property
375  def current_temperature(self) -> float | None:
376  """Return the sensor temperature."""
377  return self._cur_temp_cur_temp
378 
379  @property
380  def hvac_mode(self) -> HVACMode | None:
381  """Return current operation."""
382  return self._hvac_mode_hvac_mode
383 
384  @property
385  def hvac_action(self) -> HVACAction:
386  """Return the current running hvac operation if supported.
387 
388  Need to be one of CURRENT_HVAC_*.
389  """
390  if self._hvac_mode_hvac_mode == HVACMode.OFF:
391  return HVACAction.OFF
392  if not self._is_device_active_is_device_active:
393  return HVACAction.IDLE
394  if self.ac_modeac_mode:
395  return HVACAction.COOLING
396  return HVACAction.HEATING
397 
398  @property
399  def target_temperature(self) -> float | None:
400  """Return the temperature we try to reach."""
401  return self._target_temp_target_temp
402 
403  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
404  """Set hvac mode."""
405  if hvac_mode == HVACMode.HEAT:
406  self._hvac_mode_hvac_mode = HVACMode.HEAT
407  await self._async_control_heating_async_control_heating(force=True)
408  elif hvac_mode == HVACMode.COOL:
409  self._hvac_mode_hvac_mode = HVACMode.COOL
410  await self._async_control_heating_async_control_heating(force=True)
411  elif hvac_mode == HVACMode.OFF:
412  self._hvac_mode_hvac_mode = HVACMode.OFF
413  if self._is_device_active_is_device_active:
414  await self._async_heater_turn_off_async_heater_turn_off()
415  else:
416  _LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
417  return
418  # Ensure we update the current operation after changing the mode
419  self.async_write_ha_stateasync_write_ha_state()
420 
421  async def async_set_temperature(self, **kwargs: Any) -> None:
422  """Set new target temperature."""
423  if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
424  return
425  self._target_temp_target_temp = temperature
426  await self._async_control_heating_async_control_heating(force=True)
427  self.async_write_ha_stateasync_write_ha_state()
428 
429  @property
430  def min_temp(self) -> float:
431  """Return the minimum temperature."""
432  if self._min_temp_min_temp is not None:
433  return self._min_temp_min_temp
434 
435  # get default temp from super class
436  return super().min_temp
437 
438  @property
439  def max_temp(self) -> float:
440  """Return the maximum temperature."""
441  if self._max_temp_max_temp is not None:
442  return self._max_temp_max_temp
443 
444  # Get default temp from super class
445  return super().max_temp
446 
447  async def _async_sensor_changed(self, event: Event[EventStateChangedData]) -> None:
448  """Handle temperature changes."""
449  new_state = event.data["new_state"]
450  if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
451  return
452 
453  self._async_update_temp_async_update_temp(new_state)
454  await self._async_control_heating_async_control_heating()
455  self.async_write_ha_stateasync_write_ha_state()
456 
457  async def _check_switch_initial_state(self) -> None:
458  """Prevent the device from keep running if HVACMode.OFF."""
459  if self._hvac_mode_hvac_mode == HVACMode.OFF and self._is_device_active_is_device_active:
460  _LOGGER.warning(
461  (
462  "The climate mode is OFF, but the switch device is ON. Turning off"
463  " device %s"
464  ),
465  self.heater_entity_idheater_entity_id,
466  )
467  await self._async_heater_turn_off_async_heater_turn_off()
468 
469  @callback
470  def _async_switch_changed(self, event: Event[EventStateChangedData]) -> None:
471  """Handle heater switch state changes."""
472  new_state = event.data["new_state"]
473  old_state = event.data["old_state"]
474  if new_state is None:
475  return
476  if old_state is None:
477  self.hasshass.async_create_task(
478  self._check_switch_initial_state_check_switch_initial_state(), eager_start=True
479  )
480  self.async_write_ha_stateasync_write_ha_state()
481 
482  @callback
483  def _async_update_temp(self, state: State) -> None:
484  """Update thermostat with latest state from sensor."""
485  try:
486  cur_temp = float(state.state)
487  if not math.isfinite(cur_temp):
488  raise ValueError(f"Sensor has illegal state {state.state}") # noqa: TRY301
489  self._cur_temp_cur_temp = cur_temp
490  except ValueError as ex:
491  _LOGGER.error("Unable to update from sensor: %s", ex)
492 
494  self, time: datetime | None = None, force: bool = False
495  ) -> None:
496  """Check if we need to turn heating on or off."""
497  async with self._temp_lock_temp_lock:
498  if not self._active_active and None not in (
499  self._cur_temp_cur_temp,
500  self._target_temp_target_temp,
501  ):
502  self._active_active = True
503  _LOGGER.debug(
504  (
505  "Obtained current and target temperature. "
506  "Generic thermostat active. %s, %s"
507  ),
508  self._cur_temp_cur_temp,
509  self._target_temp_target_temp,
510  )
511 
512  if not self._active_active or self._hvac_mode_hvac_mode == HVACMode.OFF:
513  return
514 
515  # If the `force` argument is True, we
516  # ignore `min_cycle_duration`.
517  # If the `time` argument is not none, we were invoked for
518  # keep-alive purposes, and `min_cycle_duration` is irrelevant.
519  if not force and time is None and self.min_cycle_durationmin_cycle_duration:
520  if self._is_device_active_is_device_active:
521  current_state = STATE_ON
522  else:
523  current_state = HVACMode.OFF
524  try:
525  long_enough = condition.state(
526  self.hasshass,
527  self.heater_entity_idheater_entity_id,
528  current_state,
529  self.min_cycle_durationmin_cycle_duration,
530  )
531  except ConditionError:
532  long_enough = False
533 
534  if not long_enough:
535  return
536 
537  assert self._cur_temp_cur_temp is not None and self._target_temp_target_temp is not None
538  too_cold = self._target_temp_target_temp >= self._cur_temp_cur_temp + self._cold_tolerance_cold_tolerance
539  too_hot = self._cur_temp_cur_temp >= self._target_temp_target_temp + self._hot_tolerance_hot_tolerance
540  if self._is_device_active_is_device_active:
541  if (self.ac_modeac_mode and too_cold) or (not self.ac_modeac_mode and too_hot):
542  _LOGGER.debug("Turning off heater %s", self.heater_entity_idheater_entity_id)
543  await self._async_heater_turn_off_async_heater_turn_off()
544  elif time is not None:
545  # The time argument is passed only in keep-alive case
546  _LOGGER.debug(
547  "Keep-alive - Turning on heater heater %s",
548  self.heater_entity_idheater_entity_id,
549  )
550  await self._async_heater_turn_on_async_heater_turn_on()
551  elif (self.ac_modeac_mode and too_hot) or (not self.ac_modeac_mode and too_cold):
552  _LOGGER.debug("Turning on heater %s", self.heater_entity_idheater_entity_id)
553  await self._async_heater_turn_on_async_heater_turn_on()
554  elif time is not None:
555  # The time argument is passed only in keep-alive case
556  _LOGGER.debug(
557  "Keep-alive - Turning off heater %s", self.heater_entity_idheater_entity_id
558  )
559  await self._async_heater_turn_off_async_heater_turn_off()
560 
561  @property
562  def _is_device_active(self) -> bool | None:
563  """If the toggleable device is currently active."""
564  if not self.hasshass.states.get(self.heater_entity_idheater_entity_id):
565  return None
566 
567  return self.hasshass.states.is_state(self.heater_entity_idheater_entity_id, STATE_ON)
568 
569  async def _async_heater_turn_on(self) -> None:
570  """Turn heater toggleable device on."""
571  data = {ATTR_ENTITY_ID: self.heater_entity_idheater_entity_id}
572  await self.hasshass.services.async_call(
573  HOMEASSISTANT_DOMAIN, SERVICE_TURN_ON, data, context=self._context_context
574  )
575 
576  async def _async_heater_turn_off(self) -> None:
577  """Turn heater toggleable device off."""
578  data = {ATTR_ENTITY_ID: self.heater_entity_idheater_entity_id}
579  await self.hasshass.services.async_call(
580  HOMEASSISTANT_DOMAIN, SERVICE_TURN_OFF, data, context=self._context_context
581  )
582 
583  async def async_set_preset_mode(self, preset_mode: str) -> None:
584  """Set new preset mode."""
585  if preset_mode not in (self.preset_modespreset_modes or []):
586  raise ValueError(
587  f"Got unsupported preset_mode {preset_mode}. Must be one of"
588  f" {self.preset_modes}"
589  )
590  if preset_mode == self._attr_preset_mode_attr_preset_mode:
591  # I don't think we need to call async_write_ha_state if we didn't change the state
592  return
593  if preset_mode == PRESET_NONE:
594  self._attr_preset_mode_attr_preset_mode = PRESET_NONE
595  self._target_temp_target_temp = self._saved_target_temp_saved_target_temp
596  await self._async_control_heating_async_control_heating(force=True)
597  else:
598  if self._attr_preset_mode_attr_preset_mode == PRESET_NONE:
599  self._saved_target_temp_saved_target_temp = self._target_temp_target_temp
600  self._attr_preset_mode_attr_preset_mode = preset_mode
601  self._target_temp_target_temp = self._presets_presets[preset_mode]
602  await self._async_control_heating_async_control_heating(force=True)
603 
604  self.async_write_ha_stateasync_write_ha_state()
None _async_switch_changed(self, Event[EventStateChangedData] event)
Definition: climate.py:470
None _async_sensor_changed(self, Event[EventStateChangedData] event)
Definition: climate.py:447
None __init__(self, HomeAssistant hass, str name, str heater_entity_id, str sensor_entity_id, float|None min_temp, float|None max_temp, float|None target_temp, bool|None ac_mode, timedelta|None min_cycle_duration, float cold_tolerance, float hot_tolerance, timedelta|None keep_alive, HVACMode|None initial_hvac_mode, dict[str, float] presets, float|None precision, float|None target_temperature_step, UnitOfTemperature unit, str|None unique_id)
Definition: climate.py:230
None _async_control_heating(self, datetime|None time=None, bool force=False)
Definition: climate.py:495
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: climate.py:142
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:127
None _async_setup_config(HomeAssistant hass, Mapping[str, Any] config, str|None unique_id, AddEntitiesCallback async_add_entities)
Definition: climate.py:156
dr.DeviceInfo|None async_device_info_to_link_from_entity(HomeAssistant hass, str entity_id_or_uuid)
Definition: device.py:28
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)
Definition: event.py:314
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
Definition: event.py:1679
None async_setup_reload_service(HomeAssistant hass, str domain, Iterable[str] platforms)
Definition: reload.py:191