Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """The Nibe Heat Pump climate."""
2 
3 from __future__ import annotations
4 
5 from datetime import date
6 from typing import Any
7 
8 from nibe.coil import Coil
9 from nibe.coil_groups import (
10  CLIMATE_COILGROUPS,
11  UNIT_COILGROUPS,
12  ClimateCoilGroup,
13  UnitCoilGroup,
14 )
15 from nibe.exceptions import CoilNotFoundException
16 
18  ATTR_HVAC_MODE,
19  ATTR_TARGET_TEMP_HIGH,
20  ATTR_TARGET_TEMP_LOW,
21  ATTR_TEMPERATURE,
22  ClimateEntity,
23  ClimateEntityFeature,
24  HVACAction,
25  HVACMode,
26 )
27 from homeassistant.config_entries import ConfigEntry
28 from homeassistant.core import HomeAssistant, callback
29 from homeassistant.exceptions import ServiceValidationError
30 from homeassistant.helpers.entity_platform import AddEntitiesCallback
31 from homeassistant.helpers.update_coordinator import CoordinatorEntity
32 
33 from .const import (
34  DOMAIN,
35  LOGGER,
36  VALUES_COOL_WITH_ROOM_SENSOR_OFF,
37  VALUES_MIXING_VALVE_CLOSED_STATE,
38  VALUES_PRIORITY_COOLING,
39  VALUES_PRIORITY_HEATING,
40 )
41 from .coordinator import CoilCoordinator
42 
43 
45  hass: HomeAssistant,
46  config_entry: ConfigEntry,
47  async_add_entities: AddEntitiesCallback,
48 ) -> None:
49  """Set up platform."""
50 
51  coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
52 
53  main_unit = UNIT_COILGROUPS[coordinator.series]["main"]
54 
55  def climate_systems():
56  for key, group in CLIMATE_COILGROUPS.get(coordinator.series, ()).items():
57  try:
58  yield NibeClimateEntity(coordinator, key, main_unit, group)
59  except CoilNotFoundException as exception:
60  LOGGER.debug("Skipping climate: %s due to %s", key, exception)
61 
62  async_add_entities(climate_systems())
63 
64 
65 class NibeClimateEntity(CoordinatorEntity[CoilCoordinator], ClimateEntity):
66  """Climate entity."""
67 
68  _attr_entity_category = None
69  _attr_supported_features = (
70  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
71  | ClimateEntityFeature.TARGET_TEMPERATURE
72  )
73  _attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.HEAT_COOL]
74  _attr_target_temperature_step = 0.5
75  _attr_max_temp = 35.0
76  _attr_min_temp = 5.0
77  _enable_turn_on_off_backwards_compatibility = False
78 
79  def __init__(
80  self,
81  coordinator: CoilCoordinator,
82  key: str,
83  unit: UnitCoilGroup,
84  climate: ClimateCoilGroup,
85  ) -> None:
86  """Initialize entity."""
87  super().__init__(
88  coordinator,
89  {
90  unit.prio,
91  unit.cooling_with_room_sensor,
92  climate.current,
93  climate.setpoint_heat,
94  climate.setpoint_cool,
95  climate.mixing_valve_state,
96  climate.active_accessory,
97  climate.use_room_sensor,
98  },
99  )
100  self._attr_available_attr_available = False
101  self._attr_name_attr_name = climate.name
102  self._attr_unique_id_attr_unique_id = f"{coordinator.unique_id}-{key}"
103  self._attr_device_info_attr_device_info = coordinator.device_info
104  self._attr_hvac_action_attr_hvac_action = HVACAction.IDLE
105  self._attr_hvac_mode_attr_hvac_mode = HVACMode.AUTO
106  self._attr_target_temperature_high_attr_target_temperature_high = None
107  self._attr_target_temperature_low_attr_target_temperature_low = None
108  self._attr_target_temperature_attr_target_temperature = None
109  self._attr_entity_registry_enabled_default_attr_entity_registry_enabled_default = climate.active_accessory is None
110 
111  def _get(address: int) -> Coil:
112  return coordinator.heatpump.get_coil_by_address(address)
113 
114  self._coil_current_coil_current = _get(climate.current)
115  self._coil_setpoint_heat_coil_setpoint_heat = _get(climate.setpoint_heat)
116  self._coil_setpoint_cool_coil_setpoint_cool: Coil | None
117  try:
118  self._coil_setpoint_cool_coil_setpoint_cool = _get(climate.setpoint_cool)
119  except CoilNotFoundException:
120  self._coil_setpoint_cool_coil_setpoint_cool = None
121  self._attr_hvac_modes_attr_hvac_modes_attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT]
122  self._coil_prio_coil_prio = _get(unit.prio)
123  self._coil_mixing_valve_state_coil_mixing_valve_state = _get(climate.mixing_valve_state)
124  if climate.active_accessory is None:
125  self._coil_active_accessory_coil_active_accessory = None
126  else:
127  self._coil_active_accessory_coil_active_accessory = _get(climate.active_accessory)
128  self._coil_use_room_sensor_coil_use_room_sensor = _get(climate.use_room_sensor)
129  self._coil_cooling_with_room_sensor_coil_cooling_with_room_sensor = _get(unit.cooling_with_room_sensor)
130 
131  if self._coil_current_coil_current:
132  self._attr_temperature_unit_attr_temperature_unit = self._coil_current_coil_current.unit
133 
134  @callback
135  def _handle_coordinator_update(self) -> None:
136  def _get_value(coil: Coil) -> int | str | float | date | None:
137  return self.coordinator.get_coil_value(coil)
138 
139  def _get_float(coil: Coil) -> float | None:
140  return self.coordinator.get_coil_float(coil)
141 
142  self._attr_current_temperature_attr_current_temperature = _get_float(self._coil_current_coil_current)
143 
144  mode = HVACMode.AUTO
145  if _get_value(self._coil_use_room_sensor_coil_use_room_sensor) == "ON":
146  if (
147  _get_value(self._coil_cooling_with_room_sensor_coil_cooling_with_room_sensor)
148  in VALUES_COOL_WITH_ROOM_SENSOR_OFF
149  ):
150  mode = HVACMode.HEAT
151  else:
152  mode = HVACMode.HEAT_COOL
153  self._attr_hvac_mode_attr_hvac_mode = mode
154 
155  setpoint_heat = _get_float(self._coil_setpoint_heat_coil_setpoint_heat)
156  if self._coil_setpoint_cool_coil_setpoint_cool:
157  setpoint_cool = _get_float(self._coil_setpoint_cool_coil_setpoint_cool)
158  else:
159  setpoint_cool = None
160  if mode == HVACMode.HEAT_COOL:
161  self._attr_target_temperature_attr_target_temperature = None
162  self._attr_target_temperature_low_attr_target_temperature_low = setpoint_heat
163  self._attr_target_temperature_high_attr_target_temperature_high = setpoint_cool
164  elif mode == HVACMode.HEAT:
165  self._attr_target_temperature_attr_target_temperature = setpoint_heat
166  self._attr_target_temperature_low_attr_target_temperature_low = None
167  self._attr_target_temperature_high_attr_target_temperature_high = None
168  else:
169  self._attr_target_temperature_attr_target_temperature = None
170  self._attr_target_temperature_low_attr_target_temperature_low = None
171  self._attr_target_temperature_high_attr_target_temperature_high = None
172 
173  if prio := _get_value(self._coil_prio_coil_prio):
174  if (
175  _get_value(self._coil_mixing_valve_state_coil_mixing_valve_state)
176  in VALUES_MIXING_VALVE_CLOSED_STATE
177  ):
178  self._attr_hvac_action_attr_hvac_action = HVACAction.IDLE
179  elif prio in VALUES_PRIORITY_HEATING:
180  self._attr_hvac_action_attr_hvac_action = HVACAction.HEATING
181  elif prio in VALUES_PRIORITY_COOLING:
182  self._attr_hvac_action_attr_hvac_action = HVACAction.COOLING
183  else:
184  self._attr_hvac_action_attr_hvac_action = HVACAction.IDLE
185  else:
186  self._attr_hvac_action_attr_hvac_action = HVACAction.OFF
187 
188  self.async_write_ha_stateasync_write_ha_state()
189 
190  @property
191  def available(self) -> bool:
192  """Return if entity is available."""
193  coordinator = self.coordinator
194  active = self._coil_active_accessory_coil_active_accessory
195 
196  if not coordinator.last_update_success:
197  return False
198 
199  if not active:
200  return True
201 
202  if active_accessory := coordinator.get_coil_value(active):
203  return active_accessory == "ON"
204 
205  return False
206 
207  async def async_set_temperature(self, **kwargs: Any) -> None:
208  """Set target temperatures."""
209  coordinator = self.coordinator
210  hvac_mode = kwargs.get(ATTR_HVAC_MODE, self._attr_hvac_mode_attr_hvac_mode)
211 
212  if (temperature := kwargs.get(ATTR_TEMPERATURE)) is not None:
213  if hvac_mode == HVACMode.HEAT:
214  await coordinator.async_write_coil(
215  self._coil_setpoint_heat_coil_setpoint_heat, temperature
216  )
217  elif hvac_mode == HVACMode.COOL:
218  if self._coil_setpoint_cool_coil_setpoint_cool:
219  await coordinator.async_write_coil(
220  self._coil_setpoint_cool_coil_setpoint_cool, temperature
221  )
222  else:
224  f"{hvac_mode} mode not supported for {self.name}"
225  )
226  else:
228  "'set_temperature' requires 'hvac_mode' when passing"
229  " 'temperature' and 'hvac_mode' is not already set to"
230  " 'heat' or 'cool'"
231  )
232 
233  if (temperature := kwargs.get(ATTR_TARGET_TEMP_LOW)) is not None:
234  await coordinator.async_write_coil(self._coil_setpoint_heat_coil_setpoint_heat, temperature)
235 
236  if (
237  self._coil_setpoint_cool_coil_setpoint_cool
238  and (temperature := kwargs.get(ATTR_TARGET_TEMP_HIGH)) is not None
239  ):
240  await coordinator.async_write_coil(self._coil_setpoint_cool_coil_setpoint_cool, temperature)
241 
242  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
243  """Set new target hvac mode."""
244  coordinator = self.coordinator
245 
246  if hvac_mode == HVACMode.HEAT_COOL:
247  await coordinator.async_write_coil(
248  self._coil_cooling_with_room_sensor_coil_cooling_with_room_sensor, "ON"
249  )
250  await coordinator.async_write_coil(self._coil_use_room_sensor_coil_use_room_sensor, "ON")
251  elif hvac_mode == HVACMode.HEAT:
252  await coordinator.async_write_coil(
253  self._coil_cooling_with_room_sensor_coil_cooling_with_room_sensor, "OFF"
254  )
255  await coordinator.async_write_coil(self._coil_use_room_sensor_coil_use_room_sensor, "ON")
256  elif hvac_mode == HVACMode.AUTO:
257  await coordinator.async_write_coil(
258  self._coil_cooling_with_room_sensor_coil_cooling_with_room_sensor, "OFF"
259  )
260  await coordinator.async_write_coil(self._coil_use_room_sensor_coil_use_room_sensor, "OFF")
261  else:
263  f"{hvac_mode} mode not supported for {self.name}"
264  )
None __init__(self, CoilCoordinator coordinator, str key, UnitCoilGroup unit, ClimateCoilGroup climate)
Definition: climate.py:85
int|str|float|date|None get_coil_value(self, Coil coil)
Definition: coordinator.py:117
str|None _get_value(int value, list[str] values)
Definition: select.py:100
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:48