Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for the iZone HVAC."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Mapping
6 import logging
7 from typing import Any, Concatenate
8 
9 from pizone import Controller, Zone
10 import voluptuous as vol
11 
13  FAN_AUTO,
14  FAN_HIGH,
15  FAN_LOW,
16  FAN_MEDIUM,
17  FAN_TOP,
18  PRESET_ECO,
19  PRESET_NONE,
20  ClimateEntity,
21  ClimateEntityFeature,
22  HVACMode,
23 )
24 from homeassistant.config_entries import ConfigEntry
25 from homeassistant.const import (
26  ATTR_TEMPERATURE,
27  CONF_EXCLUDE,
28  PRECISION_HALVES,
29  PRECISION_TENTHS,
30  UnitOfTemperature,
31 )
32 from homeassistant.core import HomeAssistant, callback
33 from homeassistant.helpers import entity_platform
34 from homeassistant.helpers.device_registry import DeviceInfo
35 from homeassistant.helpers.dispatcher import async_dispatcher_connect
36 from homeassistant.helpers.entity_platform import AddEntitiesCallback
37 from homeassistant.helpers.temperature import display_temp as show_temp
38 from homeassistant.helpers.typing import ConfigType, VolDictType
39 
40 from .const import (
41  DATA_CONFIG,
42  DATA_DISCOVERY_SERVICE,
43  DISPATCH_CONTROLLER_DISCONNECTED,
44  DISPATCH_CONTROLLER_DISCOVERED,
45  DISPATCH_CONTROLLER_RECONNECTED,
46  DISPATCH_CONTROLLER_UPDATE,
47  DISPATCH_ZONE_UPDATE,
48  IZONE,
49 )
50 
51 type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], _R]
52 
53 _LOGGER = logging.getLogger(__name__)
54 
55 _IZONE_FAN_TO_HA = {
56  Controller.Fan.LOW: FAN_LOW,
57  Controller.Fan.MED: FAN_MEDIUM,
58  Controller.Fan.HIGH: FAN_HIGH,
59  Controller.Fan.TOP: FAN_TOP,
60  Controller.Fan.AUTO: FAN_AUTO,
61 }
62 
63 ATTR_AIRFLOW = "airflow"
64 
65 IZONE_SERVICE_AIRFLOW_MIN = "airflow_min"
66 IZONE_SERVICE_AIRFLOW_MAX = "airflow_max"
67 
68 IZONE_SERVICE_AIRFLOW_SCHEMA: VolDictType = {
69  vol.Required(ATTR_AIRFLOW): vol.All(
70  vol.Coerce(int), vol.Range(min=0, max=100), msg="invalid airflow"
71  ),
72 }
73 
74 
76  hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback
77 ) -> None:
78  """Initialize an IZone Controller."""
79  disco = hass.data[DATA_DISCOVERY_SERVICE]
80 
81  @callback
82  def init_controller(ctrl: Controller):
83  """Register the controller device and the containing zones."""
84  conf: ConfigType | None = hass.data.get(DATA_CONFIG)
85 
86  # Filter out any entities excluded in the config file
87  if conf and ctrl.device_uid in conf[CONF_EXCLUDE]:
88  _LOGGER.debug("Controller UID=%s ignored as excluded", ctrl.device_uid)
89  return
90  _LOGGER.debug("Controller UID=%s discovered", ctrl.device_uid)
91 
92  device = ControllerDevice(ctrl)
93  async_add_entities([device])
94  async_add_entities(device.zones.values())
95 
96  # create any components not yet created
97  for controller in disco.pi_disco.controllers.values():
98  init_controller(controller)
99 
100  # connect to register any further components
101  config.async_on_unload(
102  async_dispatcher_connect(hass, DISPATCH_CONTROLLER_DISCOVERED, init_controller)
103  )
104 
105  platform = entity_platform.async_get_current_platform()
106  platform.async_register_entity_service(
107  IZONE_SERVICE_AIRFLOW_MIN,
108  IZONE_SERVICE_AIRFLOW_SCHEMA,
109  "async_set_airflow_min",
110  )
111  platform.async_register_entity_service(
112  IZONE_SERVICE_AIRFLOW_MAX,
113  IZONE_SERVICE_AIRFLOW_SCHEMA,
114  "async_set_airflow_max",
115  )
116 
117 
118 def _return_on_connection_error[_DeviceT: ControllerDevice | ZoneDevice, **_P, _R, _T](
119  ret: _T = None, # type: ignore[assignment]
120 ) -> Callable[[_FuncType[_DeviceT, _P, _R]], _FuncType[_DeviceT, _P, _R | _T]]:
121  def wrap(func: _FuncType[_DeviceT, _P, _R]) -> _FuncType[_DeviceT, _P, _R | _T]:
122  def wrapped_f(self: _DeviceT, *args: _P.args, **kwargs: _P.kwargs) -> _R | _T:
123  if not self.available:
124  return ret
125  try:
126  return func(self, *args, **kwargs)
127  except ConnectionError:
128  return ret
129 
130  return wrapped_f
131 
132  return wrap
133 
134 
136  """Representation of iZone Controller."""
137 
138  _attr_precision = PRECISION_TENTHS
139  _attr_should_poll = False
140  _attr_temperature_unit = UnitOfTemperature.CELSIUS
141  _attr_has_entity_name = True
142  _attr_name = None
143  _attr_target_temperature_step = 0.5
144  _enable_turn_on_off_backwards_compatibility = False
145 
146  def __init__(self, controller: Controller) -> None:
147  """Initialise ControllerDevice."""
148  self._controller_controller = controller
149 
150  self._attr_supported_features_attr_supported_features = (
151  ClimateEntityFeature.FAN_MODE
152  | ClimateEntityFeature.TURN_OFF
153  | ClimateEntityFeature.TURN_ON
154  )
155 
156  # If mode RAS, or mode master with CtrlZone 13 then can set master temperature,
157  # otherwise the unit determines which zone to use as target. See interface manual p. 8
158  # It appears some systems may have a different numbering system, so will trigger
159  # this if the control zone is > total zones.
160  if (
161  controller.ras_mode == "master"
162  and controller.zone_ctrl > controller.zones_total
163  ) or controller.ras_mode == "RAS":
164  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
165 
166  self._state_to_pizone_state_to_pizone = {
167  HVACMode.COOL: Controller.Mode.COOL,
168  HVACMode.HEAT: Controller.Mode.HEAT,
169  HVACMode.HEAT_COOL: Controller.Mode.AUTO,
170  HVACMode.FAN_ONLY: Controller.Mode.VENT,
171  HVACMode.DRY: Controller.Mode.DRY,
172  }
173  if controller.free_air_enabled:
174  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.PRESET_MODE
175 
176  self._fan_to_pizone_fan_to_pizone = {}
177  for fan in controller.fan_modes:
178  self._fan_to_pizone_fan_to_pizone[_IZONE_FAN_TO_HA[fan]] = fan
179 
180  self._attr_unique_id_attr_unique_id = controller.device_uid
181  self._attr_device_info_attr_device_info = DeviceInfo(
182  identifiers={(IZONE, controller.device_uid)},
183  manufacturer="IZone",
184  model=controller.sys_type,
185  name=f"iZone Controller {controller.device_uid}",
186  )
187 
188  # Create the zones
189  self.zoneszones = {}
190  for zone in controller.zones:
191  self.zoneszones[zone] = ZoneDevice(self, zone)
192 
193  async def async_added_to_hass(self) -> None:
194  """Call on adding to hass."""
195 
196  # Register for connect/disconnect/update events
197  @callback
198  def controller_disconnected(ctrl: Controller, ex: Exception) -> None:
199  """Disconnected from controller."""
200  if ctrl is not self._controller_controller:
201  return
202  self.set_availableset_available(False, ex)
203 
204  self.async_on_removeasync_on_remove(
206  self.hasshass, DISPATCH_CONTROLLER_DISCONNECTED, controller_disconnected
207  )
208  )
209 
210  @callback
211  def controller_reconnected(ctrl: Controller) -> None:
212  """Reconnected to controller."""
213  if ctrl is not self._controller_controller:
214  return
215  self.set_availableset_available(True)
216 
217  self.async_on_removeasync_on_remove(
219  self.hasshass, DISPATCH_CONTROLLER_RECONNECTED, controller_reconnected
220  )
221  )
222 
223  @callback
224  def controller_update(ctrl: Controller) -> None:
225  """Handle controller data updates."""
226  if ctrl is not self._controller_controller:
227  return
228  if not self.availableavailableavailable:
229  return
230  self.async_write_ha_stateasync_write_ha_state()
231 
232  self.async_on_removeasync_on_remove(
234  self.hasshass, DISPATCH_CONTROLLER_UPDATE, controller_update
235  )
236  )
237 
238  @callback
239  def set_available(self, available: bool, ex: Exception | None = None) -> None:
240  """Set availability for the controller.
241 
242  Also sets zone availability as they follow the same availability.
243  """
244  if self.availableavailableavailable == available:
245  return
246 
247  if available:
248  _LOGGER.warning("Reconnected controller %s ", self._controller_controller.device_uid)
249  else:
250  _LOGGER.warning(
251  "Controller %s disconnected due to exception: %s",
252  self._controller_controller.device_uid,
253  ex,
254  )
255 
256  self._attr_available_attr_available = available
257  self.async_write_ha_stateasync_write_ha_state()
258  for zone in self.zoneszones.values():
259  if zone.hass is not None:
260  zone.async_schedule_update_ha_state()
261 
262  @property
263  def extra_state_attributes(self) -> Mapping[str, Any]:
264  """Return the optional state attributes."""
265  return {
266  "supply_temperature": show_temp(
267  self.hasshass,
268  self.supply_temperaturesupply_temperature,
269  self.temperature_unittemperature_unit,
270  self.precisionprecision,
271  ),
272  "temp_setpoint": show_temp(
273  self.hasshass,
274  self._controller_controller.temp_setpoint,
275  self.temperature_unittemperature_unit,
276  PRECISION_HALVES,
277  ),
278  "control_zone": self._controller_controller.zone_ctrl,
279  "control_zone_name": self.control_zone_namecontrol_zone_name,
280  # Feature ClimateEntityFeature.TARGET_TEMPERATURE controls both displaying
281  # target temp & setting it as the feature is turned off for zone control,
282  # report target temp as extra state attribute
283  "control_zone_setpoint": show_temp(
284  self.hasshass,
285  self.control_zone_setpointcontrol_zone_setpoint,
286  self.temperature_unittemperature_unit,
287  PRECISION_HALVES,
288  ),
289  }
290 
291  @property
292  def hvac_mode(self) -> HVACMode:
293  """Return current operation ie. heat, cool, idle."""
294  if not self._controller_controller.is_on:
295  return HVACMode.OFF
296  if (mode := self._controller_controller.mode) == Controller.Mode.FREE_AIR:
297  return HVACMode.FAN_ONLY
298  for key, value in self._state_to_pizone_state_to_pizone.items():
299  if value == mode:
300  return key
301  raise RuntimeError("Should be unreachable")
302 
303  @property
304  @_return_on_connection_error([])
305  def hvac_modes(self) -> list[HVACMode]:
306  """Return the list of available operation modes."""
307  if self._controller_controller.free_air:
308  return [HVACMode.OFF, HVACMode.FAN_ONLY]
309  return [HVACMode.OFF, *self._state_to_pizone_state_to_pizone]
310 
311  @property
312  @_return_on_connection_error(PRESET_NONE)
313  def preset_mode(self) -> str:
314  """Eco mode is external air."""
315  return PRESET_ECO if self._controller_controller.free_air else PRESET_NONE
316 
317  @property
318  @_return_on_connection_error([PRESET_NONE])
319  def preset_modes(self) -> list[str]:
320  """Available preset modes, normal or eco."""
321  if self._controller_controller.free_air_enabled:
322  return [PRESET_NONE, PRESET_ECO]
323  return [PRESET_NONE]
324 
325  @property
326  @_return_on_connection_error()
327  def current_temperature(self) -> float | None:
328  """Return the current temperature."""
329  if self._controller_controller.mode == Controller.Mode.FREE_AIR:
330  return self._controller_controller.temp_supply
331  return self._controller_controller.temp_return
332 
333  @property
334  def control_zone_name(self):
335  """Return the zone that currently controls the AC unit (if target temp not set by controller)."""
336  if self._attr_supported_features_attr_supported_features & ClimateEntityFeature.TARGET_TEMPERATURE:
337  return None
338  zone_ctrl = self._controller_controller.zone_ctrl
339  zone = next((z for z in self.zoneszones.values() if z.zone_index == zone_ctrl), None)
340  if zone is None:
341  return None
342  return zone.name
343 
344  @property
345  def control_zone_setpoint(self) -> float | None:
346  """Return the temperature setpoint of the zone that currently controls the AC unit (if target temp not set by controller)."""
347  if self._attr_supported_features_attr_supported_features & ClimateEntityFeature.TARGET_TEMPERATURE:
348  return None
349  zone_ctrl = self._controller_controller.zone_ctrl
350  zone = next((z for z in self.zoneszones.values() if z.zone_index == zone_ctrl), None)
351  if zone is None:
352  return None
353  return zone.target_temperature
354 
355  @property
356  @_return_on_connection_error()
357  def target_temperature(self) -> float | None:
358  """Return the temperature we try to reach (either from control zone or master unit)."""
359  if self._attr_supported_features_attr_supported_features & ClimateEntityFeature.TARGET_TEMPERATURE:
360  return self._controller_controller.temp_setpoint
361  return self.control_zone_setpointcontrol_zone_setpoint
362 
363  @property
364  def supply_temperature(self) -> float:
365  """Return the current supply, or in duct, temperature."""
366  return self._controller_controller.temp_supply
367 
368  @property
369  def fan_mode(self) -> str | None:
370  """Return the fan setting."""
371  return _IZONE_FAN_TO_HA[self._controller_controller.fan]
372 
373  @property
374  def fan_modes(self) -> list[str] | None:
375  """Return the list of available fan modes."""
376  return list(self._fan_to_pizone_fan_to_pizone)
377 
378  @property
379  @_return_on_connection_error(0.0)
380  def min_temp(self) -> float:
381  """Return the minimum temperature."""
382  return self._controller_controller.temp_min
383 
384  @property
385  @_return_on_connection_error(50.0)
386  def max_temp(self) -> float:
387  """Return the maximum temperature."""
388  return self._controller_controller.temp_max
389 
390  async def wrap_and_catch(self, coro):
391  """Catch any connection errors and set unavailable."""
392  try:
393  await coro
394  except ConnectionError as ex:
395  self.set_availableset_available(False, ex)
396  else:
397  self.set_availableset_available(True)
398 
399  async def async_set_temperature(self, **kwargs: Any) -> None:
400  """Set new target temperature."""
401  if not self.supported_featuressupported_featuressupported_features & ClimateEntityFeature.TARGET_TEMPERATURE:
402  self.async_schedule_update_ha_stateasync_schedule_update_ha_state(True)
403  return
404  if (temp := kwargs.get(ATTR_TEMPERATURE)) is not None:
405  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_temp_setpoint(temp))
406 
407  async def async_set_fan_mode(self, fan_mode: str) -> None:
408  """Set new target fan mode."""
409  fan = self._fan_to_pizone_fan_to_pizone[fan_mode]
410  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_fan(fan))
411 
412  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
413  """Set new target operation mode."""
414  if hvac_mode == HVACMode.OFF:
415  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_on(False))
416  return
417  if not self._controller_controller.is_on:
418  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_on(True))
419  if self._controller_controller.free_air:
420  return
421  mode = self._state_to_pizone_state_to_pizone[hvac_mode]
422  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_mode(mode))
423 
424  async def async_set_preset_mode(self, preset_mode: str) -> None:
425  """Set the preset mode."""
426  await self.wrap_and_catchwrap_and_catch(
427  self._controller_controller.set_free_air(preset_mode == PRESET_ECO)
428  )
429 
430  async def async_turn_on(self) -> None:
431  """Turn the entity on."""
432  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_on(True))
433 
434 
436  """Representation of iZone Zone."""
437 
438  _attr_precision = PRECISION_TENTHS
439  _attr_should_poll = False
440  _attr_has_entity_name = True
441  _attr_name = None
442  _attr_temperature_unit = UnitOfTemperature.CELSIUS
443  _attr_target_temperature_step = 0.5
444  _attr_supported_features = (
445  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
446  )
447 
448  def __init__(self, controller: ControllerDevice, zone: Zone) -> None:
449  """Initialise ZoneDevice."""
450  self._controller_controller = controller
451  self._zone_zone = zone
452 
453  if zone.type != Zone.Type.AUTO:
454  self._state_to_pizone_state_to_pizone = {
455  HVACMode.OFF: Zone.Mode.CLOSE,
456  HVACMode.FAN_ONLY: Zone.Mode.OPEN,
457  }
458  else:
459  self._state_to_pizone_state_to_pizone = {
460  HVACMode.OFF: Zone.Mode.CLOSE,
461  HVACMode.FAN_ONLY: Zone.Mode.OPEN,
462  HVACMode.HEAT_COOL: Zone.Mode.AUTO,
463  }
464  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
465  self._attr_unique_id_attr_unique_id = f"{controller.unique_id}_z{zone.index + 1}"
466  assert controller.unique_id
467  self._attr_device_info_attr_device_info = DeviceInfo(
468  identifiers={
469  (IZONE, controller.unique_id, zone.index) # type:ignore[arg-type]
470  },
471  manufacturer="IZone",
472  model=zone.type.name.title(),
473  name=zone.name.title(),
474  via_device=(IZONE, controller.unique_id),
475  )
476 
477  async def async_added_to_hass(self) -> None:
478  """Call on adding to hass."""
479 
480  @callback
481  def controller_update(ctrl: Controller) -> None:
482  """Handle controller data updates."""
483  if ctrl.device_uid != self._controller_controller.unique_id:
484  return
485  if not self.availableavailableavailable:
486  return
487  self.async_write_ha_stateasync_write_ha_state()
488 
489  self.async_on_removeasync_on_remove(
491  self.hasshass, DISPATCH_CONTROLLER_UPDATE, controller_update
492  )
493  )
494 
495  @callback
496  def zone_update(ctrl: Controller, zone: Zone) -> None:
497  """Handle zone data updates."""
498  if zone is not self._zone_zone:
499  return
500  if not self.availableavailableavailable:
501  return
502  self.async_write_ha_stateasync_write_ha_state()
503 
504  self.async_on_removeasync_on_remove(
505  async_dispatcher_connect(self.hasshass, DISPATCH_ZONE_UPDATE, zone_update)
506  )
507 
508  @property
509  def available(self) -> bool:
510  """Return True if entity is available."""
511  return self._controller_controller.available
512 
513  @property
514  @_return_on_connection_error(ClimateEntityFeature(0))
515  def supported_features(self) -> ClimateEntityFeature:
516  """Return the list of supported features."""
517  if self._zone_zone.mode == Zone.Mode.AUTO:
518  return self._attr_supported_features_attr_supported_features
519  return self._attr_supported_features_attr_supported_features & ~ClimateEntityFeature.TARGET_TEMPERATURE
520 
521  @property
522  def hvac_mode(self) -> HVACMode | None:
523  """Return current operation ie. heat, cool, idle."""
524  mode = self._zone_zone.mode
525  for key, value in self._state_to_pizone_state_to_pizone.items():
526  if value == mode:
527  return key
528  return None
529 
530  @property
531  def hvac_modes(self) -> list[HVACMode]:
532  """Return the list of available operation modes."""
533  return list(self._state_to_pizone_state_to_pizone)
534 
535  @property
536  def current_temperature(self) -> float:
537  """Return the current temperature."""
538  return self._zone_zone.temp_current
539 
540  @property
541  def target_temperature(self) -> float | None:
542  """Return the temperature we try to reach."""
543  if self._zone_zone.type != Zone.Type.AUTO:
544  return None
545  return self._zone_zone.temp_setpoint
546 
547  @property
548  def min_temp(self) -> float:
549  """Return the minimum temperature."""
550  return self._controller_controller.min_temp
551 
552  @property
553  def max_temp(self) -> float:
554  """Return the maximum temperature."""
555  return self._controller_controller.max_temp
556 
557  @property
558  def airflow_min(self):
559  """Return the minimum air flow."""
560  return self._zone_zone.airflow_min
561 
562  @property
563  def airflow_max(self):
564  """Return the maximum air flow."""
565  return self._zone_zone.airflow_max
566 
567  async def async_set_airflow_min(self, **kwargs):
568  """Set new airflow minimum."""
569  await self._controller_controller.wrap_and_catch(
570  self._zone_zone.set_airflow_min(int(kwargs[ATTR_AIRFLOW]))
571  )
572  self.async_write_ha_stateasync_write_ha_state()
573 
574  async def async_set_airflow_max(self, **kwargs):
575  """Set new airflow maximum."""
576  await self._controller_controller.wrap_and_catch(
577  self._zone_zone.set_airflow_max(int(kwargs[ATTR_AIRFLOW]))
578  )
579  self.async_write_ha_stateasync_write_ha_state()
580 
581  async def async_set_temperature(self, **kwargs: Any) -> None:
582  """Set new target temperature."""
583  if self._zone_zone.mode != Zone.Mode.AUTO:
584  return
585  if (temp := kwargs.get(ATTR_TEMPERATURE)) is not None:
586  await self._controller_controller.wrap_and_catch(self._zone_zone.set_temp_setpoint(temp))
587 
588  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
589  """Set new target operation mode."""
590  mode = self._state_to_pizone_state_to_pizone[hvac_mode]
591  await self._controller_controller.wrap_and_catch(self._zone_zone.set_mode(mode))
592  self.async_write_ha_stateasync_write_ha_state()
593 
594  @property
595  def is_on(self):
596  """Return true if on."""
597  return self._zone_zone.mode != Zone.Mode.CLOSE
598 
599  async def async_turn_on(self) -> None:
600  """Turn device on (open zone)."""
601  if self._zone_zone.type == Zone.Type.AUTO:
602  await self._controller_controller.wrap_and_catch(self._zone_zone.set_mode(Zone.Mode.AUTO))
603  else:
604  await self._controller_controller.wrap_and_catch(self._zone_zone.set_mode(Zone.Mode.OPEN))
605  self.async_write_ha_stateasync_write_ha_state()
606 
607  async def async_turn_off(self) -> None:
608  """Turn device off (close zone)."""
609  await self._controller_controller.wrap_and_catch(self._zone_zone.set_mode(Zone.Mode.CLOSE))
610  self.async_write_ha_stateasync_write_ha_state()
611 
612  @property
613  def zone_index(self):
614  """Return the zone index for matching to CtrlZone."""
615  return self._zone_zone.index
616 
617  @property
618  def extra_state_attributes(self) -> Mapping[str, Any]:
619  """Return the optional state attributes."""
620  return {
621  "airflow_max": self._zone_zone.airflow_max,
622  "airflow_min": self._zone_zone.airflow_min,
623  "zone_index": self.zone_indexzone_index,
624  }
ClimateEntityFeature supported_features(self)
Definition: __init__.py:945
None __init__(self, Controller controller)
Definition: climate.py:146
None async_set_preset_mode(self, str preset_mode)
Definition: climate.py:424
None set_available(self, bool available, Exception|None ex=None)
Definition: climate.py:239
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:412
None __init__(self, ControllerDevice controller, Zone zone)
Definition: climate.py:448
None async_set_temperature(self, **Any kwargs)
Definition: climate.py:581
ClimateEntityFeature supported_features(self)
Definition: climate.py:515
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:588
Mapping[str, Any] extra_state_attributes(self)
Definition: climate.py:618
None async_schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1265
int|None supported_features(self)
Definition: entity.py:861
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
FibaroController init_controller(Mapping[str, Any] data)
Definition: __init__.py:377
None async_setup_entry(HomeAssistant hass, ConfigEntry config, AddEntitiesCallback async_add_entities)
Definition: climate.py:77
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103