Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Plugwise Climate component for Home Assistant."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
8  ATTR_HVAC_MODE,
9  ATTR_TARGET_TEMP_HIGH,
10  ATTR_TARGET_TEMP_LOW,
11  ClimateEntity,
12  ClimateEntityFeature,
13  HVACAction,
14  HVACMode,
15 )
16 from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
17 from homeassistant.core import HomeAssistant, callback
18 from homeassistant.exceptions import HomeAssistantError
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
20 
21 from . import PlugwiseConfigEntry
22 from .const import DOMAIN, MASTER_THERMOSTATS
23 from .coordinator import PlugwiseDataUpdateCoordinator
24 from .entity import PlugwiseEntity
25 from .util import plugwise_command
26 
27 
29  hass: HomeAssistant,
30  entry: PlugwiseConfigEntry,
31  async_add_entities: AddEntitiesCallback,
32 ) -> None:
33  """Set up the Smile Thermostats from a config entry."""
34  coordinator = entry.runtime_data
35 
36  @callback
37  def _add_entities() -> None:
38  """Add Entities."""
39  if not coordinator.new_devices:
40  return
41 
42  if coordinator.data.gateway["smile_name"] == "Adam":
44  PlugwiseClimateEntity(coordinator, device_id)
45  for device_id in coordinator.new_devices
46  if coordinator.data.devices[device_id]["dev_class"] == "climate"
47  )
48  else:
50  PlugwiseClimateEntity(coordinator, device_id)
51  for device_id in coordinator.new_devices
52  if coordinator.data.devices[device_id]["dev_class"]
53  in MASTER_THERMOSTATS
54  )
55 
56  _add_entities()
57  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
58 
59 
61  """Representation of a Plugwise thermostat."""
62 
63  _attr_has_entity_name = True
64  _attr_name = None
65  _attr_temperature_unit = UnitOfTemperature.CELSIUS
66  _attr_translation_key = DOMAIN
67  _enable_turn_on_off_backwards_compatibility = False
68 
69  _previous_mode: str = "heating"
70 
71  def __init__(
72  self,
73  coordinator: PlugwiseDataUpdateCoordinator,
74  device_id: str,
75  ) -> None:
76  """Set up the Plugwise API."""
77  super().__init__(coordinator, device_id)
78  self._attr_extra_state_attributes_attr_extra_state_attributes = {}
79  self._attr_unique_id_attr_unique_id = f"{device_id}-climate"
80 
81  self._devices_devices = coordinator.data.devices
82  self._gateway_gateway = coordinator.data.gateway
83  gateway_id: str = self._gateway_gateway["gateway_id"]
84  self._gateway_data_gateway_data = self._devices_devices[gateway_id]
85 
86  self._location_location = device_id
87  if (location := self.devicedevice.get("location")) is not None:
88  self._location_location = location
89 
90  # Determine supported features
91  self._attr_supported_features_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
92  if self._gateway_gateway["cooling_present"] and self._gateway_gateway["smile_name"] != "Adam":
93  self._attr_supported_features_attr_supported_features = (
94  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
95  )
96  if HVACMode.OFF in self.hvac_modeshvac_modeshvac_modes:
97  self._attr_supported_features_attr_supported_features |= (
98  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
99  )
100  if presets := self.devicedevice.get("preset_modes"):
101  self._attr_supported_features_attr_supported_features |= ClimateEntityFeature.PRESET_MODE
102  self._attr_preset_modes_attr_preset_modes = presets
103 
104  self._attr_min_temp_attr_min_temp = self.devicedevice["thermostat"]["lower_bound"]
105  self._attr_max_temp_attr_max_temp = min(self.devicedevice["thermostat"]["upper_bound"], 35.0)
106  # Ensure we don't drop below 0.1
107  self._attr_target_temperature_step_attr_target_temperature_step = max(
108  self.devicedevice["thermostat"]["resolution"], 0.1
109  )
110 
111  def _previous_action_mode(self, coordinator: PlugwiseDataUpdateCoordinator) -> None:
112  """Return the previous action-mode when the regulation-mode is not heating or cooling.
113 
114  Helper for set_hvac_mode().
115  """
116  # When no cooling available, _previous_mode is always heating
117  if (
118  "regulation_modes" in self._gateway_data_gateway_data
119  and "cooling" in self._gateway_data_gateway_data["regulation_modes"]
120  ):
121  mode = self._gateway_data_gateway_data["select_regulation_mode"]
122  if mode in ("cooling", "heating"):
123  self._previous_mode_previous_mode = mode
124 
125  @property
126  def current_temperature(self) -> float:
127  """Return the current temperature."""
128  return self.devicedevice["sensors"]["temperature"]
129 
130  @property
131  def target_temperature(self) -> float:
132  """Return the temperature we try to reach.
133 
134  Connected to the HVACMode combination of AUTO-HEAT.
135  """
136 
137  return self.devicedevice["thermostat"]["setpoint"]
138 
139  @property
140  def target_temperature_high(self) -> float:
141  """Return the temperature we try to reach in case of cooling.
142 
143  Connected to the HVACMode combination of AUTO-HEAT_COOL.
144  """
145  return self.devicedevice["thermostat"]["setpoint_high"]
146 
147  @property
148  def target_temperature_low(self) -> float:
149  """Return the heating temperature we try to reach in case of heating.
150 
151  Connected to the HVACMode combination AUTO-HEAT_COOL.
152  """
153  return self.devicedevice["thermostat"]["setpoint_low"]
154 
155  @property
156  def hvac_mode(self) -> HVACMode:
157  """Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode."""
158  if (
159  mode := self.devicedevice.get("climate_mode")
160  ) is None or mode not in self.hvac_modeshvac_modeshvac_modes:
161  return HVACMode.HEAT
162  return HVACMode(mode)
163 
164  @property
165  def hvac_modes(self) -> list[HVACMode]:
166  """Return a list of available HVACModes."""
167  hvac_modes: list[HVACMode] = []
168  if "regulation_modes" in self._gateway_data_gateway_data:
169  hvac_modes.append(HVACMode.OFF)
170 
171  if "available_schedules" in self.devicedevice:
172  hvac_modes.append(HVACMode.AUTO)
173 
174  if self._gateway_gateway["cooling_present"]:
175  if "regulation_modes" in self._gateway_data_gateway_data:
176  if self._gateway_data_gateway_data["select_regulation_mode"] == "cooling":
177  hvac_modes.append(HVACMode.COOL)
178  if self._gateway_data_gateway_data["select_regulation_mode"] == "heating":
179  hvac_modes.append(HVACMode.HEAT)
180  else:
181  hvac_modes.append(HVACMode.HEAT_COOL)
182  else:
183  hvac_modes.append(HVACMode.HEAT)
184 
185  return hvac_modes
186 
187  @property
188  def hvac_action(self) -> HVACAction:
189  """Return the current running hvac operation if supported."""
190  # Keep track of the previous action-mode
191  self._previous_action_mode_previous_action_mode(self.coordinator)
192 
193  # Adam provides the hvac_action for each thermostat
194  if self._gateway_gateway["smile_name"] == "Adam":
195  if (control_state := self.devicedevice.get("control_state")) == "cooling":
196  return HVACAction.COOLING
197  if control_state == "heating":
198  return HVACAction.HEATING
199  if control_state == "preheating":
200  return HVACAction.PREHEATING
201  if control_state == "off":
202  return HVACAction.IDLE
203 
204  return HVACAction.IDLE
205 
206  # Anna
207  heater: str = self._gateway_gateway["heater_id"]
208  heater_data = self._devices_devices[heater]
209  if heater_data["binary_sensors"]["heating_state"]:
210  return HVACAction.HEATING
211  if heater_data["binary_sensors"].get("cooling_state", False):
212  return HVACAction.COOLING
213 
214  return HVACAction.IDLE
215 
216  @property
217  def preset_mode(self) -> str | None:
218  """Return the current preset mode."""
219  return self.devicedevice.get("active_preset")
220 
221  @plugwise_command
222  async def async_set_temperature(self, **kwargs: Any) -> None:
223  """Set new target temperature."""
224  data: dict[str, Any] = {}
225  if ATTR_TEMPERATURE in kwargs:
226  data["setpoint"] = kwargs.get(ATTR_TEMPERATURE)
227  if ATTR_TARGET_TEMP_HIGH in kwargs:
228  data["setpoint_high"] = kwargs.get(ATTR_TARGET_TEMP_HIGH)
229  if ATTR_TARGET_TEMP_LOW in kwargs:
230  data["setpoint_low"] = kwargs.get(ATTR_TARGET_TEMP_LOW)
231 
232  for temperature in data.values():
233  if temperature is None or not (
234  self._attr_min_temp_attr_min_temp <= temperature <= self._attr_max_temp_attr_max_temp
235  ):
236  raise ValueError("Invalid temperature change requested")
237 
238  if mode := kwargs.get(ATTR_HVAC_MODE):
239  await self.async_set_hvac_modeasync_set_hvac_modeasync_set_hvac_mode(mode)
240 
241  await self.coordinator.api.set_temperature(self._location_location, data)
242 
243  @plugwise_command
244  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
245  """Set the hvac mode."""
246  if hvac_mode not in self.hvac_modeshvac_modeshvac_modes:
247  raise HomeAssistantError("Unsupported hvac_mode")
248 
249  if hvac_mode == self.hvac_modehvac_modehvac_modehvac_modehvac_mode:
250  return
251 
252  if hvac_mode == HVACMode.OFF:
253  await self.coordinator.api.set_regulation_mode(hvac_mode)
254  else:
255  await self.coordinator.api.set_schedule_state(
256  self._location_location,
257  "on" if hvac_mode == HVACMode.AUTO else "off",
258  )
259  if self.hvac_modehvac_modehvac_modehvac_modehvac_mode == HVACMode.OFF:
260  await self.coordinator.api.set_regulation_mode(self._previous_mode_previous_mode)
261 
262  @plugwise_command
263  async def async_set_preset_mode(self, preset_mode: str) -> None:
264  """Set the preset mode."""
265  await self.coordinator.api.set_preset(self._location_location, preset_mode)
None async_set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:813
None __init__(self, PlugwiseDataUpdateCoordinator coordinator, str device_id)
Definition: climate.py:75
None _previous_action_mode(self, PlugwiseDataUpdateCoordinator coordinator)
Definition: climate.py:111
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, PlugwiseConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:32