Home Assistant Unofficial Reference 2024.12.1
water_heater.py
Go to the documentation of this file.
1 """The Nibe Heat Pump sensors."""
2 
3 from __future__ import annotations
4 
5 from datetime import date
6 
7 from nibe.coil import Coil
8 from nibe.coil_groups import WATER_HEATER_COILGROUPS, WaterHeaterCoilGroup
9 from nibe.exceptions import CoilNotFoundException
10 
12  STATE_HEAT_PUMP,
13  STATE_HIGH_DEMAND,
14  WaterHeaterEntity,
15  WaterHeaterEntityFeature,
16 )
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.core import HomeAssistant, callback
19 from homeassistant.exceptions import HomeAssistantError
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.update_coordinator import CoordinatorEntity
22 
23 from .const import (
24  DOMAIN,
25  LOGGER,
26  VALUES_TEMPORARY_LUX_INACTIVE,
27  VALUES_TEMPORARY_LUX_ONE_TIME_INCREASE,
28 )
29 from .coordinator import CoilCoordinator
30 
31 
33  hass: HomeAssistant,
34  config_entry: ConfigEntry,
35  async_add_entities: AddEntitiesCallback,
36 ) -> None:
37  """Set up platform."""
38 
39  coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
40 
41  def water_heaters():
42  for key, group in WATER_HEATER_COILGROUPS.get(coordinator.series, ()).items():
43  try:
44  yield WaterHeater(coordinator, key, group)
45  except CoilNotFoundException as exception:
46  LOGGER.debug("Skipping water heater: %r", exception)
47 
48  async_add_entities(water_heaters())
49 
50 
51 class WaterHeater(CoordinatorEntity[CoilCoordinator], WaterHeaterEntity):
52  """Sensor entity."""
53 
54  _attr_entity_category = None
55  _attr_has_entity_name = True
56  _attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE
57  _attr_max_temp = 35.0
58  _attr_min_temp = 5.0
59 
60  def __init__(
61  self,
62  coordinator: CoilCoordinator,
63  key: str,
64  desc: WaterHeaterCoilGroup,
65  ) -> None:
66  """Initialize entity."""
67 
68  super().__init__(
69  coordinator,
70  {
71  desc.hot_water_load,
72  desc.hot_water_comfort_mode,
73  *set(desc.start_temperature.values()),
74  *set(desc.stop_temperature.values()),
75  desc.active_accessory,
76  desc.temporary_lux,
77  },
78  )
79  self._attr_entity_registry_enabled_default_attr_entity_registry_enabled_default = desc.active_accessory is None
80  self._attr_available_attr_available = False
81  self._attr_name_attr_name = desc.name
82  self._attr_unique_id_attr_unique_id = f"{coordinator.unique_id}-{key}"
83  self._attr_device_info_attr_device_info = coordinator.device_info
84 
85  self._attr_current_operation_attr_current_operation = None
86  self._attr_target_temperature_high_attr_target_temperature_high = None
87  self._attr_target_temperature_low_attr_target_temperature_low = None
88  self._attr_operation_list_attr_operation_list = []
89  self._operation_mode_to_lux: dict[str, str] = {}
90 
91  def _get(address: int) -> Coil:
92  return coordinator.heatpump.get_coil_by_address(address)
93 
94  def _map(data: dict[str, int]) -> dict[str, Coil]:
95  return {key: _get(address) for key, address in data.items()}
96 
97  self._coil_current_coil_current = _get(desc.hot_water_load)
98  self._coil_start_temperature_coil_start_temperature = _map(desc.start_temperature)
99  self._coil_stop_temperature_coil_stop_temperature = _map(desc.stop_temperature)
100  self._coil_temporary_lux_coil_temporary_lux: Coil | None = None
101  if desc.temporary_lux:
102  self._coil_temporary_lux_coil_temporary_lux = _get(desc.temporary_lux)
103  self._coil_active_accessory_coil_active_accessory: Coil | None = None
104  if address := desc.active_accessory:
105  self._coil_active_accessory_coil_active_accessory = _get(address)
106 
107  self._coil_hot_water_comfort_mode_coil_hot_water_comfort_mode = _get(desc.hot_water_comfort_mode)
108 
109  def _add_lux_mode(temporary_lux: str, operation_mode: str) -> None:
110  assert self._attr_operation_list_attr_operation_list is not None
111  if (
112  not self._coil_temporary_lux_coil_temporary_lux
113  or not self._coil_temporary_lux_coil_temporary_lux.reverse_mappings
114  ):
115  return
116 
117  if temporary_lux not in self._coil_temporary_lux_coil_temporary_lux.reverse_mappings:
118  return
119 
120  self._attr_operation_list_attr_operation_list.append(operation_mode)
121  self._operation_mode_to_lux[operation_mode] = temporary_lux
122 
123  _add_lux_mode(VALUES_TEMPORARY_LUX_ONE_TIME_INCREASE, STATE_HIGH_DEMAND)
124  _add_lux_mode(VALUES_TEMPORARY_LUX_INACTIVE, STATE_HEAT_PUMP)
125 
126  self._attr_temperature_unit_attr_temperature_unit = self._coil_current_coil_current.unit
127 
128  @callback
129  def _handle_coordinator_update(self) -> None:
130  if not self.coordinator.data:
131  return
132 
133  def _get_float(coil: Coil | None) -> float | None:
134  if coil is None:
135  return None
136  return self.coordinator.get_coil_float(coil)
137 
138  def _get_value(coil: Coil | None) -> int | str | float | date | None:
139  if coil is None:
140  return None
141  return self.coordinator.get_coil_value(coil)
142 
143  self._attr_current_temperature_attr_current_temperature = _get_float(self._coil_current_coil_current)
144 
145  if (mode := _get_value(self._coil_hot_water_comfort_mode_coil_hot_water_comfort_mode)) and isinstance(
146  mode, str
147  ):
148  self._attr_target_temperature_low_attr_target_temperature_low = _get_float(
149  self._coil_start_temperature_coil_start_temperature.get(mode)
150  )
151  self._attr_target_temperature_high_attr_target_temperature_high = _get_float(
152  self._coil_stop_temperature_coil_stop_temperature.get(mode)
153  )
154  else:
155  self._attr_target_temperature_low_attr_target_temperature_low = None
156  self._attr_target_temperature_high_attr_target_temperature_high = None
157 
158  if (
159  _get_value(self._coil_temporary_lux_coil_temporary_lux)
160  == VALUES_TEMPORARY_LUX_ONE_TIME_INCREASE
161  ):
162  self._attr_current_operation_attr_current_operation = STATE_HIGH_DEMAND
163  else:
164  self._attr_current_operation_attr_current_operation = STATE_HEAT_PUMP
165 
167 
168  @property
169  def available(self) -> bool:
170  """Return if entity is available."""
171  if not self.coordinator.last_update_success:
172  return False
173 
174  if not self._coil_active_accessory_coil_active_accessory:
175  return True
176 
177  if active_accessory := self.coordinator.get_coil_value(
178  self._coil_active_accessory_coil_active_accessory
179  ):
180  return active_accessory == "ON"
181 
182  return False
183 
184  async def async_set_operation_mode(self, operation_mode: str) -> None:
185  """Set new target operation mode."""
186  if not self._coil_temporary_lux_coil_temporary_lux:
187  raise HomeAssistantError("Not supported")
188 
189  lux = self._operation_mode_to_lux.get(operation_mode)
190  if not lux:
191  raise ValueError(f"Unsupported operation mode {operation_mode}")
192 
193  await self.coordinator.async_write_coil(self._coil_temporary_lux_coil_temporary_lux, lux)
int|str|float|date|None get_coil_value(self, Coil coil)
Definition: coordinator.py:117
None __init__(self, CoilCoordinator coordinator, str key, WaterHeaterCoilGroup desc)
Definition: water_heater.py:65
str|None _get_value(int value, list[str] values)
Definition: select.py:100
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: water_heater.py:36