Home Assistant Unofficial Reference 2024.12.1
number.py
Go to the documentation of this file.
1 """Number for ViCare."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from contextlib import suppress
7 from dataclasses import dataclass
8 import logging
9 from typing import Any
10 
11 from PyViCare.PyViCareDevice import Device as PyViCareDevice
12 from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig
13 from PyViCare.PyViCareHeatingDevice import (
14  HeatingDeviceWithComponent as PyViCareHeatingDeviceComponent,
15 )
16 from PyViCare.PyViCareUtils import (
17  PyViCareInvalidDataError,
18  PyViCareNotSupportedFeatureError,
19  PyViCareRateLimitError,
20 )
21 from requests.exceptions import ConnectionError as RequestConnectionError
22 
24  NumberDeviceClass,
25  NumberEntity,
26  NumberEntityDescription,
27 )
28 from homeassistant.config_entries import ConfigEntry
29 from homeassistant.const import EntityCategory, UnitOfTemperature
30 from homeassistant.core import HomeAssistant
31 from homeassistant.helpers.entity_platform import AddEntitiesCallback
32 
33 from .const import DEVICE_LIST, DOMAIN
34 from .entity import ViCareEntity
35 from .types import HeatingProgram, ViCareDevice, ViCareRequiredKeysMixin
36 from .utils import get_circuits, get_device_serial, is_supported
37 
38 _LOGGER = logging.getLogger(__name__)
39 
40 
41 @dataclass(frozen=True)
43  """Describes ViCare number entity."""
44 
45  value_getter: Callable[[PyViCareDevice], float]
46  value_setter: Callable[[PyViCareDevice, float], Any] | None = None
47  min_value_getter: Callable[[PyViCareDevice], float | None] | None = None
48  max_value_getter: Callable[[PyViCareDevice], float | None] | None = None
49  stepping_getter: Callable[[PyViCareDevice], float | None] | None = None
50 
51 
52 DEVICE_ENTITY_DESCRIPTIONS: tuple[ViCareNumberEntityDescription, ...] = (
54  key="dhw_temperature",
55  translation_key="dhw_temperature",
56  entity_category=EntityCategory.CONFIG,
57  device_class=NumberDeviceClass.TEMPERATURE,
58  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
59  value_getter=lambda api: api.getDomesticHotWaterConfiguredTemperature(),
60  value_setter=lambda api, value: api.setDomesticHotWaterTemperature(value),
61  min_value_getter=lambda api: api.getDomesticHotWaterMinTemperature(),
62  max_value_getter=lambda api: api.getDomesticHotWaterMaxTemperature(),
63  native_step=1,
64  ),
66  key="dhw_secondary_temperature",
67  translation_key="dhw_secondary_temperature",
68  entity_category=EntityCategory.CONFIG,
69  device_class=NumberDeviceClass.TEMPERATURE,
70  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
71  value_getter=lambda api: api.getDomesticHotWaterConfiguredTemperature2(),
72  value_setter=lambda api, value: api.setDomesticHotWaterTemperature2(value),
73  # no getters for min, max, stepping exposed yet, using static values
74  native_min_value=10,
75  native_max_value=60,
76  native_step=1,
77  ),
79  key="dhw_hysteresis_switch_on",
80  translation_key="dhw_hysteresis_switch_on",
81  entity_category=EntityCategory.CONFIG,
82  device_class=NumberDeviceClass.TEMPERATURE,
83  native_unit_of_measurement=UnitOfTemperature.KELVIN,
84  value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOn(),
85  value_setter=lambda api, value: api.setDomesticHotWaterHysteresisSwitchOn(
86  value
87  ),
88  min_value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOnMin(),
89  max_value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOnMax(),
90  stepping_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOnStepping(),
91  ),
93  key="dhw_hysteresis_switch_off",
94  translation_key="dhw_hysteresis_switch_off",
95  entity_category=EntityCategory.CONFIG,
96  device_class=NumberDeviceClass.TEMPERATURE,
97  native_unit_of_measurement=UnitOfTemperature.KELVIN,
98  value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOff(),
99  value_setter=lambda api, value: api.setDomesticHotWaterHysteresisSwitchOff(
100  value
101  ),
102  min_value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOffMin(),
103  max_value_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOffMax(),
104  stepping_getter=lambda api: api.getDomesticHotWaterHysteresisSwitchOffStepping(),
105  ),
106 )
107 
108 
109 CIRCUIT_ENTITY_DESCRIPTIONS: tuple[ViCareNumberEntityDescription, ...] = (
111  key="heating curve shift",
112  translation_key="heating_curve_shift",
113  entity_category=EntityCategory.CONFIG,
114  device_class=NumberDeviceClass.TEMPERATURE,
115  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
116  value_getter=lambda api: api.getHeatingCurveShift(),
117  value_setter=lambda api, shift: (
118  api.setHeatingCurve(shift, api.getHeatingCurveSlope())
119  ),
120  min_value_getter=lambda api: api.getHeatingCurveShiftMin(),
121  max_value_getter=lambda api: api.getHeatingCurveShiftMax(),
122  stepping_getter=lambda api: api.getHeatingCurveShiftStepping(),
123  native_min_value=-13,
124  native_max_value=40,
125  native_step=1,
126  ),
128  key="heating curve slope",
129  translation_key="heating_curve_slope",
130  entity_category=EntityCategory.CONFIG,
131  value_getter=lambda api: api.getHeatingCurveSlope(),
132  value_setter=lambda api, slope: (
133  api.setHeatingCurve(api.getHeatingCurveShift(), slope)
134  ),
135  min_value_getter=lambda api: api.getHeatingCurveSlopeMin(),
136  max_value_getter=lambda api: api.getHeatingCurveSlopeMax(),
137  stepping_getter=lambda api: api.getHeatingCurveSlopeStepping(),
138  native_min_value=0.2,
139  native_max_value=3.5,
140  native_step=0.1,
141  ),
143  key="normal_temperature",
144  translation_key="normal_temperature",
145  entity_category=EntityCategory.CONFIG,
146  device_class=NumberDeviceClass.TEMPERATURE,
147  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
148  value_getter=lambda api: api.getDesiredTemperatureForProgram(
149  HeatingProgram.NORMAL
150  ),
151  value_setter=lambda api, value: api.setProgramTemperature(
152  HeatingProgram.NORMAL, value
153  ),
154  min_value_getter=lambda api: api.getProgramMinTemperature(
155  HeatingProgram.NORMAL
156  ),
157  max_value_getter=lambda api: api.getProgramMaxTemperature(
158  HeatingProgram.NORMAL
159  ),
160  stepping_getter=lambda api: api.getProgramStepping(HeatingProgram.NORMAL),
161  ),
163  key="reduced_temperature",
164  translation_key="reduced_temperature",
165  entity_category=EntityCategory.CONFIG,
166  device_class=NumberDeviceClass.TEMPERATURE,
167  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
168  value_getter=lambda api: api.getDesiredTemperatureForProgram(
169  HeatingProgram.REDUCED
170  ),
171  value_setter=lambda api, value: api.setProgramTemperature(
172  HeatingProgram.REDUCED, value
173  ),
174  min_value_getter=lambda api: api.getProgramMinTemperature(
175  HeatingProgram.REDUCED
176  ),
177  max_value_getter=lambda api: api.getProgramMaxTemperature(
178  HeatingProgram.REDUCED
179  ),
180  stepping_getter=lambda api: api.getProgramStepping(HeatingProgram.REDUCED),
181  ),
183  key="comfort_temperature",
184  translation_key="comfort_temperature",
185  entity_category=EntityCategory.CONFIG,
186  device_class=NumberDeviceClass.TEMPERATURE,
187  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
188  value_getter=lambda api: api.getDesiredTemperatureForProgram(
189  HeatingProgram.COMFORT
190  ),
191  value_setter=lambda api, value: api.setProgramTemperature(
192  HeatingProgram.COMFORT, value
193  ),
194  min_value_getter=lambda api: api.getProgramMinTemperature(
195  HeatingProgram.COMFORT
196  ),
197  max_value_getter=lambda api: api.getProgramMaxTemperature(
198  HeatingProgram.COMFORT
199  ),
200  stepping_getter=lambda api: api.getProgramStepping(HeatingProgram.COMFORT),
201  ),
203  key="normal_heating_temperature",
204  translation_key="normal_heating_temperature",
205  entity_category=EntityCategory.CONFIG,
206  device_class=NumberDeviceClass.TEMPERATURE,
207  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
208  value_getter=lambda api: api.getDesiredTemperatureForProgram(
209  HeatingProgram.NORMAL_HEATING
210  ),
211  value_setter=lambda api, value: api.setProgramTemperature(
212  HeatingProgram.NORMAL_HEATING, value
213  ),
214  min_value_getter=lambda api: api.getProgramMinTemperature(
215  HeatingProgram.NORMAL_HEATING
216  ),
217  max_value_getter=lambda api: api.getProgramMaxTemperature(
218  HeatingProgram.NORMAL_HEATING
219  ),
220  stepping_getter=lambda api: api.getProgramStepping(
221  HeatingProgram.NORMAL_HEATING
222  ),
223  ),
225  key="reduced_heating_temperature",
226  translation_key="reduced_heating_temperature",
227  entity_category=EntityCategory.CONFIG,
228  device_class=NumberDeviceClass.TEMPERATURE,
229  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
230  value_getter=lambda api: api.getDesiredTemperatureForProgram(
231  HeatingProgram.REDUCED_HEATING
232  ),
233  value_setter=lambda api, value: api.setProgramTemperature(
234  HeatingProgram.REDUCED_HEATING, value
235  ),
236  min_value_getter=lambda api: api.getProgramMinTemperature(
237  HeatingProgram.REDUCED_HEATING
238  ),
239  max_value_getter=lambda api: api.getProgramMaxTemperature(
240  HeatingProgram.REDUCED_HEATING
241  ),
242  stepping_getter=lambda api: api.getProgramStepping(
243  HeatingProgram.REDUCED_HEATING
244  ),
245  ),
247  key="comfort_heating_temperature",
248  translation_key="comfort_heating_temperature",
249  entity_category=EntityCategory.CONFIG,
250  device_class=NumberDeviceClass.TEMPERATURE,
251  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
252  value_getter=lambda api: api.getDesiredTemperatureForProgram(
253  HeatingProgram.COMFORT_HEATING
254  ),
255  value_setter=lambda api, value: api.setProgramTemperature(
256  HeatingProgram.COMFORT_HEATING, value
257  ),
258  min_value_getter=lambda api: api.getProgramMinTemperature(
259  HeatingProgram.COMFORT_HEATING
260  ),
261  max_value_getter=lambda api: api.getProgramMaxTemperature(
262  HeatingProgram.COMFORT_HEATING
263  ),
264  stepping_getter=lambda api: api.getProgramStepping(
265  HeatingProgram.COMFORT_HEATING
266  ),
267  ),
269  key="normal_cooling_temperature",
270  translation_key="normal_cooling_temperature",
271  entity_category=EntityCategory.CONFIG,
272  device_class=NumberDeviceClass.TEMPERATURE,
273  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
274  value_getter=lambda api: api.getDesiredTemperatureForProgram(
275  HeatingProgram.NORMAL_COOLING
276  ),
277  value_setter=lambda api, value: api.setProgramTemperature(
278  HeatingProgram.NORMAL_COOLING, value
279  ),
280  min_value_getter=lambda api: api.getProgramMinTemperature(
281  HeatingProgram.NORMAL_COOLING
282  ),
283  max_value_getter=lambda api: api.getProgramMaxTemperature(
284  HeatingProgram.NORMAL_COOLING
285  ),
286  stepping_getter=lambda api: api.getProgramStepping(
287  HeatingProgram.NORMAL_COOLING
288  ),
289  ),
291  key="reduced_cooling_temperature",
292  translation_key="reduced_cooling_temperature",
293  entity_category=EntityCategory.CONFIG,
294  device_class=NumberDeviceClass.TEMPERATURE,
295  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
296  value_getter=lambda api: api.getDesiredTemperatureForProgram(
297  HeatingProgram.REDUCED_COOLING
298  ),
299  value_setter=lambda api, value: api.setProgramTemperature(
300  HeatingProgram.REDUCED_COOLING, value
301  ),
302  min_value_getter=lambda api: api.getProgramMinTemperature(
303  HeatingProgram.REDUCED_COOLING
304  ),
305  max_value_getter=lambda api: api.getProgramMaxTemperature(
306  HeatingProgram.REDUCED_COOLING
307  ),
308  stepping_getter=lambda api: api.getProgramStepping(
309  HeatingProgram.REDUCED_COOLING
310  ),
311  ),
313  key="comfort_cooling_temperature",
314  translation_key="comfort_cooling_temperature",
315  entity_category=EntityCategory.CONFIG,
316  device_class=NumberDeviceClass.TEMPERATURE,
317  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
318  value_getter=lambda api: api.getDesiredTemperatureForProgram(
319  HeatingProgram.COMFORT_COOLING
320  ),
321  value_setter=lambda api, value: api.setProgramTemperature(
322  HeatingProgram.COMFORT_COOLING, value
323  ),
324  min_value_getter=lambda api: api.getProgramMinTemperature(
325  HeatingProgram.COMFORT_COOLING
326  ),
327  max_value_getter=lambda api: api.getProgramMaxTemperature(
328  HeatingProgram.COMFORT_COOLING
329  ),
330  stepping_getter=lambda api: api.getProgramStepping(
331  HeatingProgram.COMFORT_COOLING
332  ),
333  ),
334 )
335 
336 
338  device_list: list[ViCareDevice],
339 ) -> list[ViCareNumber]:
340  """Create ViCare number entities for a device."""
341 
342  entities: list[ViCareNumber] = []
343  for device in device_list:
344  # add device entities
345  entities.extend(
346  ViCareNumber(
347  description,
348  get_device_serial(device.api),
349  device.config,
350  device.api,
351  )
352  for description in DEVICE_ENTITY_DESCRIPTIONS
353  if is_supported(description.key, description, device.api)
354  )
355  # add component entities
356  entities.extend(
357  ViCareNumber(
358  description,
359  get_device_serial(device.api),
360  device.config,
361  device.api,
362  circuit,
363  )
364  for circuit in get_circuits(device.api)
365  for description in CIRCUIT_ENTITY_DESCRIPTIONS
366  if is_supported(description.key, description, circuit)
367  )
368  return entities
369 
370 
372  hass: HomeAssistant,
373  config_entry: ConfigEntry,
374  async_add_entities: AddEntitiesCallback,
375 ) -> None:
376  """Create the ViCare number devices."""
377  device_list = hass.data[DOMAIN][config_entry.entry_id][DEVICE_LIST]
378 
380  await hass.async_add_executor_job(
381  _build_entities,
382  device_list,
383  )
384  )
385 
386 
388  """Representation of a ViCare number."""
389 
390  entity_description: ViCareNumberEntityDescription
391 
392  def __init__(
393  self,
394  description: ViCareNumberEntityDescription,
395  device_serial: str | None,
396  device_config: PyViCareDeviceConfig,
397  device: PyViCareDevice,
398  component: PyViCareHeatingDeviceComponent | None = None,
399  ) -> None:
400  """Initialize the number."""
401  super().__init__(
402  description.key, device_serial, device_config, device, component
403  )
404  self.entity_descriptionentity_description = description
405 
406  @property
407  def available(self) -> bool:
408  """Return True if entity is available."""
409  return self._attr_native_value_attr_native_value is not None
410 
411  def set_native_value(self, value: float) -> None:
412  """Set new value."""
413  if self.entity_descriptionentity_description.value_setter:
414  self.entity_descriptionentity_description.value_setter(self._api, value)
415  self.schedule_update_ha_stateschedule_update_ha_state()
416 
417  def update(self) -> None:
418  """Update state of number."""
419  try:
420  with suppress(PyViCareNotSupportedFeatureError):
421  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.value_getter(
422  self._api
423  )
424 
425  if min_value := _get_value(
426  self.entity_descriptionentity_description.min_value_getter, self._api
427  ):
428  self._attr_native_min_value_attr_native_min_value = min_value
429 
430  if max_value := _get_value(
431  self.entity_descriptionentity_description.max_value_getter, self._api
432  ):
433  self._attr_native_max_value_attr_native_max_value = max_value
434 
435  if stepping_value := _get_value(
436  self.entity_descriptionentity_description.stepping_getter, self._api
437  ):
438  self._attr_native_step_attr_native_step = stepping_value
439  except RequestConnectionError:
440  _LOGGER.error("Unable to retrieve data from ViCare server")
441  except ValueError:
442  _LOGGER.error("Unable to decode data from ViCare server")
443  except PyViCareRateLimitError as limit_exception:
444  _LOGGER.error("Vicare API rate limit exceeded: %s", limit_exception)
445  except PyViCareInvalidDataError as invalid_data_exception:
446  _LOGGER.error("Invalid data from Vicare server: %s", invalid_data_exception)
447 
448 
450  fn: Callable[[PyViCareDevice], float | None] | None,
451  api: PyViCareHeatingDeviceComponent,
452 ) -> float | None:
453  return None if fn is None else fn(api)
None __init__(self, ViCareNumberEntityDescription description, str|None device_serial, PyViCareDeviceConfig device_config, PyViCareDevice device, PyViCareHeatingDeviceComponent|None component=None)
Definition: number.py:399
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
float|None _get_value(Callable[[PyViCareDevice], float|None]|None fn, PyViCareHeatingDeviceComponent api)
Definition: number.py:452
list[ViCareNumber] _build_entities(list[ViCareDevice] device_list)
Definition: number.py:339
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: number.py:375
str|None get_device_serial(PyViCareDevice device)
Definition: utils.py:35
bool is_supported(str name, ViCareRequiredKeysMixin entity_description, vicare_device)
Definition: utils.py:56
list[PyViCareHeatingDeviceComponent] get_circuits(PyViCareDevice device)
Definition: utils.py:81