Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for the Escea Fireplace."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Coroutine
6 import logging
7 from typing import Any
8 
9 from pescea import Controller
10 
12  FAN_AUTO,
13  FAN_HIGH,
14  FAN_LOW,
15  ClimateEntity,
16  ClimateEntityFeature,
17  HVACMode,
18 )
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
21 from homeassistant.core import HomeAssistant, callback
22 from homeassistant.helpers.device_registry import DeviceInfo
23 from homeassistant.helpers.dispatcher import async_dispatcher_connect
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 
26 from .const import (
27  DATA_DISCOVERY_SERVICE,
28  DISPATCH_CONTROLLER_DISCONNECTED,
29  DISPATCH_CONTROLLER_DISCOVERED,
30  DISPATCH_CONTROLLER_RECONNECTED,
31  DISPATCH_CONTROLLER_UPDATE,
32  DOMAIN,
33  ESCEA_FIREPLACE,
34  ESCEA_MANUFACTURER,
35 )
36 
37 _LOGGER = logging.getLogger(__name__)
38 
39 _ESCEA_FAN_TO_HA = {
40  Controller.Fan.FLAME_EFFECT: FAN_LOW,
41  Controller.Fan.FAN_BOOST: FAN_HIGH,
42  Controller.Fan.AUTO: FAN_AUTO,
43 }
44 _HA_FAN_TO_ESCEA = {v: k for k, v in _ESCEA_FAN_TO_HA.items()}
45 
46 
48  hass: HomeAssistant,
49  config_entry: ConfigEntry,
50  async_add_entities: AddEntitiesCallback,
51 ) -> None:
52  """Initialize an Escea Controller."""
53  discovery_service = hass.data[DATA_DISCOVERY_SERVICE]
54 
55  @callback
56  def init_controller(ctrl: Controller) -> None:
57  """Register the controller device."""
58 
59  _LOGGER.debug("Controller UID=%s discovered", ctrl.device_uid)
60 
61  entity = ControllerEntity(ctrl)
62  async_add_entities([entity])
63 
64  # create any components not yet created
65  for controller in discovery_service.controllers.values():
66  init_controller(controller)
67 
68  # connect to register any further components
69  config_entry.async_on_unload(
70  async_dispatcher_connect(hass, DISPATCH_CONTROLLER_DISCOVERED, init_controller)
71  )
72 
73 
75  """Representation of Escea Controller."""
76 
77  _attr_fan_modes = list(_HA_FAN_TO_ESCEA)
78  _attr_has_entity_name = True
79  _attr_name = None
80  _attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
81  _attr_translation_key = "fireplace"
82  _attr_precision = PRECISION_WHOLE
83  _attr_should_poll = False
84  _attr_supported_features = (
85  ClimateEntityFeature.TARGET_TEMPERATURE
86  | ClimateEntityFeature.FAN_MODE
87  | ClimateEntityFeature.TURN_OFF
88  | ClimateEntityFeature.TURN_ON
89  )
90  _attr_target_temperature_step = PRECISION_WHOLE
91  _attr_temperature_unit = UnitOfTemperature.CELSIUS
92  _enable_turn_on_off_backwards_compatibility = False
93 
94  def __init__(self, controller: Controller) -> None:
95  """Initialise ControllerDevice."""
96  self._controller_controller = controller
97 
98  self._attr_min_temp_attr_min_temp = controller.min_temp
99  self._attr_max_temp_attr_max_temp = controller.max_temp
100 
101  self._attr_unique_id_attr_unique_id = controller.device_uid
102 
103  # temporary assignment to get past mypy checker
104  unique_id: str = controller.device_uid
105 
106  self._attr_device_info_attr_device_info = DeviceInfo(
107  identifiers={(DOMAIN, unique_id)},
108  manufacturer=ESCEA_MANUFACTURER,
109  name=ESCEA_FIREPLACE,
110  )
111 
112  self._attr_available_attr_available = True
113 
114  async def async_added_to_hass(self) -> None:
115  """Call on adding to hass.
116 
117  Registers for connect/disconnect/update events
118  """
119 
120  @callback
121  def controller_disconnected(ctrl: Controller, ex: Exception) -> None:
122  """Disconnected from controller."""
123  if ctrl is not self._controller_controller:
124  return
125  self.set_availableset_available(False, ex)
126 
127  self.async_on_removeasync_on_remove(
129  self.hasshass, DISPATCH_CONTROLLER_DISCONNECTED, controller_disconnected
130  )
131  )
132 
133  @callback
134  def controller_reconnected(ctrl: Controller) -> None:
135  """Reconnected to controller."""
136  if ctrl is not self._controller_controller:
137  return
138  self.set_availableset_available(True)
139 
140  self.async_on_removeasync_on_remove(
142  self.hasshass, DISPATCH_CONTROLLER_RECONNECTED, controller_reconnected
143  )
144  )
145 
146  @callback
147  def controller_update(ctrl: Controller) -> None:
148  """Handle controller data updates."""
149  if ctrl is not self._controller_controller:
150  return
151  self.async_write_ha_stateasync_write_ha_state()
152 
153  self.async_on_removeasync_on_remove(
155  self.hasshass, DISPATCH_CONTROLLER_UPDATE, controller_update
156  )
157  )
158 
159  @callback
160  def set_available(self, available: bool, ex: Exception | None = None) -> None:
161  """Set availability for the controller."""
162  if self._attr_available_attr_available == available:
163  return
164 
165  if available:
166  _LOGGER.debug("Reconnected controller %s ", self._controller_controller.device_uid)
167  else:
168  _LOGGER.debug(
169  "Controller %s disconnected due to exception: %s",
170  self._controller_controller.device_uid,
171  ex,
172  )
173 
174  self._attr_available_attr_available = available
175  self.async_write_ha_stateasync_write_ha_state()
176 
177  @property
178  def hvac_mode(self) -> HVACMode:
179  """Return current operation ie. heat, cool, idle."""
180  return HVACMode.HEAT if self._controller_controller.is_on else HVACMode.OFF
181 
182  @property
183  def current_temperature(self) -> float | None:
184  """Return the current temperature."""
185  return self._controller_controller.current_temp
186 
187  @property
188  def target_temperature(self) -> float | None:
189  """Return the temperature we try to reach."""
190  return self._controller_controller.desired_temp
191 
192  @property
193  def fan_mode(self) -> str | None:
194  """Return the fan setting."""
195  return _ESCEA_FAN_TO_HA[self._controller_controller.fan]
196 
197  async def wrap_and_catch(self, coro: Coroutine) -> None:
198  """Catch any connection errors and set unavailable."""
199  try:
200  await coro
201  except ConnectionError as ex:
202  self.set_availableset_available(False, ex)
203  else:
204  self.set_availableset_available(True)
205 
206  async def async_set_temperature(self, **kwargs: Any) -> None:
207  """Set new target temperature."""
208  temp = kwargs.get(ATTR_TEMPERATURE)
209  if temp is not None:
210  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_desired_temp(temp))
211 
212  async def async_set_fan_mode(self, fan_mode: str) -> None:
213  """Set new target fan mode."""
214  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_fan(_HA_FAN_TO_ESCEA[fan_mode]))
215 
216  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
217  """Set new target operation mode."""
218  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_on(hvac_mode == HVACMode.HEAT))
219 
220  async def async_turn_on(self) -> None:
221  """Turn the entity on."""
222  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_on(True))
223 
224  async def async_turn_off(self) -> None:
225  """Turn the entity off."""
226  await self.wrap_and_catchwrap_and_catch(self._controller_controller.set_on(False))
None __init__(self, Controller controller)
Definition: climate.py:94
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:216
None set_available(self, bool available, Exception|None ex=None)
Definition: climate.py:160
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:51
FibaroController init_controller(Mapping[str, Any] data)
Definition: __init__.py:377
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103