Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Switcher integration Climate platform."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, cast
6 
7 from aioswitcher.api import SwitcherApi, SwitcherBaseResponse
8 from aioswitcher.api.remotes import SwitcherBreezeRemote
9 from aioswitcher.device import (
10  DeviceCategory,
11  DeviceState,
12  SwitcherThermostat,
13  ThermostatFanLevel,
14  ThermostatMode,
15  ThermostatSwing,
16 )
17 
19  FAN_AUTO,
20  FAN_HIGH,
21  FAN_LOW,
22  FAN_MEDIUM,
23  SWING_OFF,
24  SWING_VERTICAL,
25  ClimateEntity,
26  ClimateEntityFeature,
27  HVACMode,
28 )
29 from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
30 from homeassistant.core import HomeAssistant, callback
31 from homeassistant.exceptions import HomeAssistantError
32 from homeassistant.helpers.dispatcher import async_dispatcher_connect
33 from homeassistant.helpers.entity_platform import AddEntitiesCallback
34 
35 from . import SwitcherConfigEntry
36 from .const import SIGNAL_DEVICE_ADD
37 from .coordinator import SwitcherDataUpdateCoordinator
38 from .entity import SwitcherEntity
39 from .utils import get_breeze_remote_manager
40 
41 DEVICE_MODE_TO_HA = {
42  ThermostatMode.COOL: HVACMode.COOL,
43  ThermostatMode.HEAT: HVACMode.HEAT,
44  ThermostatMode.FAN: HVACMode.FAN_ONLY,
45  ThermostatMode.DRY: HVACMode.DRY,
46  ThermostatMode.AUTO: HVACMode.HEAT_COOL,
47 }
48 
49 HA_TO_DEVICE_MODE = {value: key for key, value in DEVICE_MODE_TO_HA.items()}
50 
51 DEVICE_FAN_TO_HA = {
52  ThermostatFanLevel.LOW: FAN_LOW,
53  ThermostatFanLevel.MEDIUM: FAN_MEDIUM,
54  ThermostatFanLevel.HIGH: FAN_HIGH,
55  ThermostatFanLevel.AUTO: FAN_AUTO,
56 }
57 
58 HA_TO_DEVICE_FAN = {value: key for key, value in DEVICE_FAN_TO_HA.items()}
59 
60 
62  hass: HomeAssistant,
63  config_entry: SwitcherConfigEntry,
64  async_add_entities: AddEntitiesCallback,
65 ) -> None:
66  """Set up Switcher climate from config entry."""
67 
68  async def async_add_climate(coordinator: SwitcherDataUpdateCoordinator) -> None:
69  """Get remote and add climate from Switcher device."""
70  data = cast(SwitcherThermostat, coordinator.data)
71  if coordinator.data.device_type.category == DeviceCategory.THERMOSTAT:
72  remote: SwitcherBreezeRemote = await hass.async_add_executor_job(
73  get_breeze_remote_manager(hass).get_remote, data.remote_id
74  )
75  async_add_entities([SwitcherClimateEntity(coordinator, remote)])
76 
77  config_entry.async_on_unload(
78  async_dispatcher_connect(hass, SIGNAL_DEVICE_ADD, async_add_climate)
79  )
80 
81 
83  """Representation of a Switcher climate entity."""
84 
85  _attr_name = None
86  _enable_turn_on_off_backwards_compatibility = False
87 
88  def __init__(
89  self, coordinator: SwitcherDataUpdateCoordinator, remote: SwitcherBreezeRemote
90  ) -> None:
91  """Initialize the entity."""
92  super().__init__(coordinator)
93  self._remote_remote = remote
94 
95  self._attr_unique_id_attr_unique_id = f"{coordinator.device_id}-{coordinator.mac_address}"
96 
97  self._attr_min_temp_attr_min_temp = remote.min_temperature
98  self._attr_max_temp_attr_max_temp = remote.max_temperature
99  self._attr_target_temperature_step_attr_target_temperature_step = 1
100  self._attr_temperature_unit_attr_temperature_unit = UnitOfTemperature.CELSIUS
101 
102  self._attr_hvac_modes_attr_hvac_modes = [HVACMode.OFF]
103  for mode in remote.modes_features:
104  self._attr_hvac_modes_attr_hvac_modes.append(DEVICE_MODE_TO_HA[mode])
105  features = remote.modes_features[mode]
106 
107  if features["temperature_control"]:
108  self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
109 
110  if features["fan_levels"]:
111  self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
112 
113  if features["swing"] and not remote.separated_swing_command:
114  self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
115 
116  # There is always support for off + minimum one other mode so no need to check
117  self._attr_supported_features |= (
118  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
119  )
120  self._update_data_update_data(True)
121 
122  @callback
123  def _handle_coordinator_update(self) -> None:
124  """Handle updated data from the coordinator."""
125  self._update_data_update_data()
126  self.async_write_ha_stateasync_write_ha_state()
127 
128  def _update_data(self, force_update: bool = False) -> None:
129  """Update data from device."""
130  data = cast(SwitcherThermostat, self.coordinator.data)
131  features = self._remote_remote.modes_features[data.mode]
132 
133  if data.target_temperature == 0 and not force_update:
134  return
135 
136  self._attr_current_temperature_attr_current_temperature = data.temperature
137  self._attr_target_temperature_attr_target_temperature = float(data.target_temperature)
138 
139  self._attr_hvac_mode_attr_hvac_mode = HVACMode.OFF
140  if data.device_state == DeviceState.ON:
141  self._attr_hvac_mode_attr_hvac_mode = DEVICE_MODE_TO_HA[data.mode]
142 
143  self._attr_fan_mode_attr_fan_mode = None
144  self._attr_fan_modes_attr_fan_modes = []
145  if features["fan_levels"]:
146  self._attr_fan_modes_attr_fan_modes = [DEVICE_FAN_TO_HA[x] for x in features["fan_levels"]]
147  self._attr_fan_mode_attr_fan_mode = DEVICE_FAN_TO_HA[data.fan_level]
148 
149  self._attr_swing_mode_attr_swing_mode = None
150  self._attr_swing_modes_attr_swing_modes = []
151  if features["swing"]:
152  self._attr_swing_mode_attr_swing_mode = SWING_OFF
153  self._attr_swing_modes_attr_swing_modes = [SWING_VERTICAL, SWING_OFF]
154  if data.swing == ThermostatSwing.ON:
155  self._attr_swing_mode_attr_swing_mode = SWING_VERTICAL
156 
157  async def _async_control_breeze_device(self, **kwargs: Any) -> None:
158  """Call Switcher Control Breeze API."""
159  response: SwitcherBaseResponse | None = None
160  error = None
161 
162  try:
163  async with SwitcherApi(
164  self.coordinator.data.device_type,
165  self.coordinator.data.ip_address,
166  self.coordinator.data.device_id,
167  self.coordinator.data.device_key,
168  ) as swapi:
169  response = await swapi.control_breeze_device(self._remote_remote, **kwargs)
170  except (TimeoutError, OSError, RuntimeError) as err:
171  error = repr(err)
172 
173  if error or not response or not response.successful:
174  self.coordinator.last_update_success = False
175  self.async_write_ha_stateasync_write_ha_state()
176  raise HomeAssistantError(
177  f"Call Breeze control for {self.name} failed, "
178  f"response/error: {response or error}"
179  )
180 
181  async def async_set_temperature(self, **kwargs: Any) -> None:
182  """Set new target temperature."""
183  data = cast(SwitcherThermostat, self.coordinator.data)
184  if not self._remote_remote.modes_features[data.mode]["temperature_control"]:
185  raise HomeAssistantError(
186  "Current mode doesn't support setting Target Temperature"
187  )
188 
189  if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
190  raise ValueError("No target temperature provided")
191 
192  await self._async_control_breeze_device_async_control_breeze_device(target_temp=int(temperature))
193 
194  async def async_set_fan_mode(self, fan_mode: str) -> None:
195  """Set new target fan mode."""
196  data = cast(SwitcherThermostat, self.coordinator.data)
197  if not self._remote_remote.modes_features[data.mode]["fan_levels"]:
198  raise HomeAssistantError("Current mode doesn't support setting Fan Mode")
199 
200  await self._async_control_breeze_device_async_control_breeze_device(fan_level=HA_TO_DEVICE_FAN[fan_mode])
201 
202  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
203  """Set new target operation mode."""
204  if hvac_mode == hvac_mode.OFF:
205  await self._async_control_breeze_device_async_control_breeze_device(state=DeviceState.OFF)
206  else:
207  await self._async_control_breeze_device_async_control_breeze_device(
208  state=DeviceState.ON, mode=HA_TO_DEVICE_MODE[hvac_mode]
209  )
210 
211  async def async_set_swing_mode(self, swing_mode: str) -> None:
212  """Set new target swing operation."""
213  data = cast(SwitcherThermostat, self.coordinator.data)
214  if not self._remote_remote.modes_features[data.mode]["swing"]:
215  raise HomeAssistantError("Current mode doesn't support setting Swing Mode")
216 
217  if swing_mode == SWING_VERTICAL:
218  await self._async_control_breeze_device_async_control_breeze_device(swing=ThermostatSwing.ON)
219  else:
220  await self._async_control_breeze_device_async_control_breeze_device(swing=ThermostatSwing.OFF)
None __init__(self, SwitcherDataUpdateCoordinator coordinator, SwitcherBreezeRemote remote)
Definition: climate.py:90
None async_setup_entry(HomeAssistant hass, SwitcherConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:65
SwitcherBreezeRemoteManager get_breeze_remote_manager(HomeAssistant hass)
Definition: utils.py:42
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103