Home Assistant Unofficial Reference 2024.12.1
domestic_hot_water_production.py
Go to the documentation of this file.
1 """Support for DomesticHotWaterProduction."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, cast
6 
7 from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState
8 
10  STATE_ECO,
11  STATE_HIGH_DEMAND,
12  STATE_PERFORMANCE,
13  WaterHeaterEntity,
14  WaterHeaterEntityFeature,
15 )
16 from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, STATE_ON, UnitOfTemperature
17 
18 from ..coordinator import OverkizDataUpdateCoordinator
19 from ..entity import OverkizEntity
20 
21 OVERKIZ_TO_OPERATION_MODE: dict[str, str] = {
22  OverkizCommandParam.STANDARD: STATE_ON,
23  OverkizCommandParam.HIGH_DEMAND: STATE_HIGH_DEMAND,
24  OverkizCommandParam.STOP: STATE_OFF,
25  OverkizCommandParam.MANUAL_ECO_ACTIVE: STATE_ECO,
26  OverkizCommandParam.MANUAL_ECO_INACTIVE: STATE_OFF,
27  OverkizCommandParam.ECO: STATE_ECO,
28  OverkizCommandParam.AUTO: STATE_ECO,
29  OverkizCommandParam.AUTO_MODE: STATE_ECO,
30  OverkizCommandParam.BOOST: STATE_PERFORMANCE,
31 }
32 
33 DHWP_AWAY_MODES = [
34  OverkizCommandParam.ABSENCE,
35  OverkizCommandParam.AWAY,
36  OverkizCommandParam.FROSTPROTECTION,
37 ]
38 
39 DEFAULT_MIN_TEMP: float = 30
40 DEFAULT_MAX_TEMP: float = 70
41 
42 
44  """Representation of a DomesticHotWaterProduction Water Heater."""
45 
46  _attr_temperature_unit = UnitOfTemperature.CELSIUS
47  _attr_supported_features = (
48  WaterHeaterEntityFeature.TARGET_TEMPERATURE
49  | WaterHeaterEntityFeature.OPERATION_MODE
50  )
51 
52  def __init__(
53  self, device_url: str, coordinator: OverkizDataUpdateCoordinator
54  ) -> None:
55  """Init method."""
56  super().__init__(device_url, coordinator)
57 
58  # Init operation mode to set for this specific device
59  self.operation_mode_to_overkiz: dict[str, str] = {}
60  self._attr_operation_list_attr_operation_list = []
61  state_mode_definition = self.executorexecutor.select_definition_state(
62  OverkizState.IO_DHW_MODE, OverkizState.MODBUSLINK_DHW_MODE
63  )
64  for param, mode in OVERKIZ_TO_OPERATION_MODE.items():
65  # Filter only for mode allowed by this device
66  # or allow all if no mode definition found
67  if (
68  not state_mode_definition
69  or state_mode_definition.values
70  and param in state_mode_definition.values
71  ):
72  self.operation_mode_to_overkiz[mode] = param
73  self._attr_operation_list_attr_operation_list.append(param)
74 
75  @property
76  def _is_boost_mode_on(self) -> bool:
77  """Return true if boost mode is on."""
78 
79  if self.executorexecutor.has_state(OverkizState.IO_DHW_BOOST_MODE):
80  return (
81  self.executorexecutor.select_state(OverkizState.IO_DHW_BOOST_MODE)
82  == OverkizCommandParam.ON
83  )
84 
85  if self.executorexecutor.has_state(OverkizState.MODBUSLINK_DHW_BOOST_MODE):
86  return (
87  self.executorexecutor.select_state(OverkizState.MODBUSLINK_DHW_BOOST_MODE)
88  == OverkizCommandParam.ON
89  )
90 
91  if self.executorexecutor.has_state(OverkizState.CORE_BOOST_MODE_DURATION):
92  return (
93  cast(
94  float,
95  self.executorexecutor.select_state(OverkizState.CORE_BOOST_MODE_DURATION),
96  )
97  > 0
98  )
99 
100  operating_mode = self.executorexecutor.select_state(OverkizState.CORE_OPERATING_MODE)
101 
102  if operating_mode:
103  if isinstance(operating_mode, dict):
104  if operating_mode.get(OverkizCommandParam.RELAUNCH):
105  return (
106  cast(
107  str,
108  operating_mode.get(OverkizCommandParam.RELAUNCH),
109  )
110  == OverkizCommandParam.ON
111  )
112  return False
113 
114  return cast(str, operating_mode) == OverkizCommandParam.BOOST
115 
116  return False
117 
118  @property
119  def is_away_mode_on(self) -> bool | None:
120  """Return true if away mode is on."""
121 
122  if self.executorexecutor.has_state(OverkizState.IO_DHW_ABSENCE_MODE):
123  return (
124  self.executorexecutor.select_state(OverkizState.IO_DHW_ABSENCE_MODE)
125  == OverkizCommandParam.ON
126  )
127 
128  if self.executorexecutor.has_state(OverkizState.MODBUSLINK_DHW_ABSENCE_MODE):
129  return (
130  self.executorexecutor.select_state(OverkizState.MODBUSLINK_DHW_ABSENCE_MODE)
131  == OverkizCommandParam.ON
132  )
133 
134  operating_mode = self.executorexecutor.select_state(OverkizState.CORE_OPERATING_MODE)
135 
136  if operating_mode:
137  if isinstance(operating_mode, dict):
138  if operating_mode.get(OverkizCommandParam.ABSENCE):
139  return (
140  cast(
141  str,
142  operating_mode.get(OverkizCommandParam.ABSENCE),
143  )
144  == OverkizCommandParam.ON
145  )
146  if operating_mode.get(OverkizCommandParam.AWAY):
147  return (
148  cast(
149  str,
150  operating_mode.get(OverkizCommandParam.AWAY),
151  )
152  == OverkizCommandParam.ON
153  )
154  return False
155 
156  return cast(str, operating_mode) in DHWP_AWAY_MODES
157 
158  return None
159 
160  @property
161  def min_temp(self) -> float:
162  """Return the minimum temperature."""
163  min_temp = self.devicedevice.states[OverkizState.CORE_MINIMAL_TEMPERATURE_MANUAL_MODE]
164  if min_temp:
165  return cast(float, min_temp.value_as_float)
166  return DEFAULT_MIN_TEMP
167 
168  @property
169  def max_temp(self) -> float:
170  """Return the maximum temperature."""
171  max_temp = self.devicedevice.states[OverkizState.CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE]
172  if max_temp:
173  return cast(float, max_temp.value_as_float)
174  return DEFAULT_MAX_TEMP
175 
176  @property
177  def current_temperature(self) -> float | None:
178  """Return the current temperature."""
179  current_temperature = self.devicedevice.states[
180  OverkizState.IO_MIDDLE_WATER_TEMPERATURE
181  ]
182  if current_temperature:
183  return current_temperature.value_as_float
184  current_temperature = self.devicedevice.states[
185  OverkizState.MODBUSLINK_MIDDLE_WATER_TEMPERATURE
186  ]
187  if current_temperature:
188  return current_temperature.value_as_float
189  return None
190 
191  @property
192  def target_temperature(self) -> float | None:
193  """Return the temperature we try to reach."""
194 
195  target_temperature = self.devicedevice.states[
196  OverkizState.CORE_WATER_TARGET_TEMPERATURE
197  ]
198  if target_temperature:
199  return target_temperature.value_as_float
200 
201  target_temperature = self.devicedevice.states[
202  OverkizState.CORE_TARGET_DWH_TEMPERATURE
203  ]
204  if target_temperature:
205  return target_temperature.value_as_float
206 
207  target_temperature = self.devicedevice.states[OverkizState.CORE_TARGET_TEMPERATURE]
208  if target_temperature:
209  return target_temperature.value_as_float
210 
211  return None
212 
213  @property
214  def target_temperature_high(self) -> float | None:
215  """Return the highbound target temperature we try to reach."""
216  target_temperature_high = self.devicedevice.states[
217  OverkizState.CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE
218  ]
219  if target_temperature_high:
220  return target_temperature_high.value_as_float
221  return None
222 
223  @property
224  def target_temperature_low(self) -> float | None:
225  """Return the lowbound target temperature we try to reach."""
226  target_temperature_low = self.devicedevice.states[
227  OverkizState.CORE_MINIMAL_TEMPERATURE_MANUAL_MODE
228  ]
229  if target_temperature_low:
230  return target_temperature_low.value_as_float
231  return None
232 
233  async def async_set_temperature(self, **kwargs: Any) -> None:
234  """Set new target temperature."""
235  target_temperature = kwargs[ATTR_TEMPERATURE]
236 
237  if self.executorexecutor.has_command(OverkizCommand.SET_TARGET_TEMPERATURE):
238  await self.executorexecutor.async_execute_command(
239  OverkizCommand.SET_TARGET_TEMPERATURE, target_temperature
240  )
241  elif self.executorexecutor.has_command(OverkizCommand.SET_WATER_TARGET_TEMPERATURE):
242  await self.executorexecutor.async_execute_command(
243  OverkizCommand.SET_WATER_TARGET_TEMPERATURE, target_temperature
244  )
245 
246  if self.executorexecutor.has_command(OverkizCommand.REFRESH_TARGET_TEMPERATURE):
247  await self.executorexecutor.async_execute_command(
248  OverkizCommand.REFRESH_TARGET_TEMPERATURE
249  )
250  elif self.executorexecutor.has_command(OverkizCommand.REFRESH_WATER_TARGET_TEMPERATURE):
251  await self.executorexecutor.async_execute_command(
252  OverkizCommand.REFRESH_WATER_TARGET_TEMPERATURE
253  )
254 
255  @property
256  def current_operation(self) -> str | None:
257  """Return current operation ie. eco, electric, performance, ..."""
258  if self._is_boost_mode_on_is_boost_mode_on:
259  return OVERKIZ_TO_OPERATION_MODE[OverkizCommandParam.BOOST]
260 
261  current_dwh_mode = cast(
262  str,
263  self.executorexecutor.select_state(
264  OverkizState.IO_DHW_MODE, OverkizState.MODBUSLINK_DHW_MODE
265  ),
266  )
267  if current_dwh_mode in OVERKIZ_TO_OPERATION_MODE:
268  return OVERKIZ_TO_OPERATION_MODE[current_dwh_mode]
269 
270  return None
271 
272  async def async_set_operation_mode(self, operation_mode: str) -> None:
273  """Set new target operation mode."""
274 
275  if operation_mode == STATE_PERFORMANCE:
276  if self.executorexecutor.has_command(OverkizCommand.SET_BOOST_MODE):
277  await self.executorexecutor.async_execute_command(
278  OverkizCommand.SET_BOOST_MODE, OverkizCommand.ON
279  )
280 
281  if self.executorexecutor.has_command(OverkizCommand.SET_BOOST_MODE_DURATION):
282  await self.executorexecutor.async_execute_command(
283  OverkizCommand.SET_BOOST_MODE_DURATION, 7
284  )
285  await self.executorexecutor.async_execute_command(
286  OverkizCommand.REFRESH_BOOST_MODE_DURATION
287  )
288 
289  if self.executorexecutor.has_command(OverkizCommand.SET_CURRENT_OPERATING_MODE):
290  current_operating_mode = self.executorexecutor.select_state(
291  OverkizState.CORE_OPERATING_MODE
292  )
293 
294  if current_operating_mode and isinstance(current_operating_mode, dict):
295  await self.executorexecutor.async_execute_command(
296  OverkizCommand.SET_CURRENT_OPERATING_MODE,
297  {
298  OverkizCommandParam.RELAUNCH: OverkizCommandParam.ON,
299  OverkizCommandParam.ABSENCE: OverkizCommandParam.OFF,
300  },
301  )
302 
303  return
304 
305  if self._is_boost_mode_on_is_boost_mode_on:
306  # We're setting a non Boost mode and the device is currently in Boost mode
307  # The following code removes all boost operations
308  if self.executorexecutor.has_command(OverkizCommand.SET_BOOST_MODE):
309  await self.executorexecutor.async_execute_command(
310  OverkizCommand.SET_BOOST_MODE, OverkizCommand.OFF
311  )
312 
313  if self.executorexecutor.has_command(OverkizCommand.SET_CURRENT_OPERATING_MODE):
314  current_operating_mode = self.executorexecutor.select_state(
315  OverkizState.CORE_OPERATING_MODE
316  )
317 
318  if current_operating_mode and isinstance(current_operating_mode, dict):
319  await self.executorexecutor.async_execute_command(
320  OverkizCommand.SET_CURRENT_OPERATING_MODE,
321  {
322  OverkizCommandParam.RELAUNCH: OverkizCommandParam.OFF,
323  OverkizCommandParam.ABSENCE: OverkizCommandParam.OFF,
324  },
325  )
326 
327  await self.executorexecutor.async_execute_command(
328  OverkizCommand.SET_DHW_MODE, self.operation_mode_to_overkiz[operation_mode]
329  )
330 
331  if self.executorexecutor.has_command(OverkizCommand.REFRESH_BOOST_MODE_DURATION):
332  await self.executorexecutor.async_execute_command(
333  OverkizCommand.REFRESH_BOOST_MODE_DURATION
334  )
335 
336  if self.executorexecutor.has_command(OverkizCommand.REFRESH_DHW_MODE):
337  await self.executorexecutor.async_execute_command(OverkizCommand.REFRESH_DHW_MODE)