Home Assistant Unofficial Reference 2024.12.1
fan.py
Go to the documentation of this file.
1 """Support for the Vallox ventilation unit fan."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from typing import Any, NamedTuple
7 
8 from vallox_websocket_api import Vallox, ValloxApiException, ValloxInvalidInputException
9 
10 from homeassistant.components.fan import FanEntity, FanEntityFeature
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.core import HomeAssistant
13 from homeassistant.exceptions import HomeAssistantError
14 from homeassistant.helpers.entity_platform import AddEntitiesCallback
15 from homeassistant.helpers.typing import StateType
16 
17 from .const import (
18  DOMAIN,
19  METRIC_KEY_MODE,
20  METRIC_KEY_PROFILE_FAN_SPEED_AWAY,
21  METRIC_KEY_PROFILE_FAN_SPEED_BOOST,
22  METRIC_KEY_PROFILE_FAN_SPEED_HOME,
23  MODE_OFF,
24  MODE_ON,
25  PRESET_MODE_TO_VALLOX_PROFILE,
26  VALLOX_PROFILE_TO_PRESET_MODE,
27 )
28 from .coordinator import ValloxDataUpdateCoordinator
29 from .entity import ValloxEntity
30 
31 
32 class ExtraStateAttributeDetails(NamedTuple):
33  """Extra state attribute details."""
34 
35  description: str
36  metric_key: str
37 
38 
39 EXTRA_STATE_ATTRIBUTES = (
41  description="fan_speed_home", metric_key=METRIC_KEY_PROFILE_FAN_SPEED_HOME
42  ),
44  description="fan_speed_away", metric_key=METRIC_KEY_PROFILE_FAN_SPEED_AWAY
45  ),
47  description="fan_speed_boost", metric_key=METRIC_KEY_PROFILE_FAN_SPEED_BOOST
48  ),
49 )
50 
51 
52 def _convert_to_int(value: StateType) -> int | None:
53  if isinstance(value, (int, float)):
54  return int(value)
55 
56  return None
57 
58 
60  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
61 ) -> None:
62  """Set up the fan device."""
63  data = hass.data[DOMAIN][entry.entry_id]
64 
65  client = data["client"]
66 
67  device = ValloxFanEntity(
68  data["name"],
69  client,
70  data["coordinator"],
71  )
72 
73  async_add_entities([device])
74 
75 
77  """Representation of the fan."""
78 
79  _attr_name = None
80  _attr_supported_features = (
81  FanEntityFeature.PRESET_MODE
82  | FanEntityFeature.SET_SPEED
83  | FanEntityFeature.TURN_OFF
84  | FanEntityFeature.TURN_ON
85  )
86  _enable_turn_on_off_backwards_compatibility = False
87 
88  def __init__(
89  self,
90  name: str,
91  client: Vallox,
92  coordinator: ValloxDataUpdateCoordinator,
93  ) -> None:
94  """Initialize the fan."""
95  super().__init__(name, coordinator)
96 
97  self._client_client = client
98 
99  self._attr_unique_id_attr_unique_id = str(self._device_uuid_device_uuid)
100  self._attr_preset_modes_attr_preset_modes = list(PRESET_MODE_TO_VALLOX_PROFILE)
101 
102  @property
103  def is_on(self) -> bool:
104  """Return if device is on."""
105  return self.coordinator.data.get(METRIC_KEY_MODE) == MODE_ON
106 
107  @property
108  def preset_mode(self) -> str | None:
109  """Return the current preset mode."""
110  vallox_profile = self.coordinator.data.profile
111  return VALLOX_PROFILE_TO_PRESET_MODE.get(vallox_profile)
112 
113  @property
114  def percentage(self) -> int | None:
115  """Return the current speed as a percentage."""
116 
117  vallox_profile = self.coordinator.data.profile
118  try:
119  return _convert_to_int(self.coordinator.data.get_fan_speed(vallox_profile))
120  except ValloxInvalidInputException:
121  return None
122 
123  @property
124  def extra_state_attributes(self) -> Mapping[str, int | None]:
125  """Return device specific state attributes."""
126  data = self.coordinator.data
127 
128  return {
129  attr.description: _convert_to_int(data.get(attr.metric_key))
130  for attr in EXTRA_STATE_ATTRIBUTES
131  }
132 
133  async def async_set_preset_mode(self, preset_mode: str) -> None:
134  """Set new preset mode."""
135  update_needed = await self._async_set_preset_mode_internal_async_set_preset_mode_internal(preset_mode)
136 
137  if update_needed:
138  # This state change affects other entities like sensors. Force an immediate update that
139  # can be observed by all parties involved.
140  await self.coordinator.async_request_refresh()
141 
142  async def async_turn_on(
143  self,
144  percentage: int | None = None,
145  preset_mode: str | None = None,
146  **kwargs: Any,
147  ) -> None:
148  """Turn the device on."""
149  update_needed = False
150 
151  if not self.is_onis_onis_onis_on:
152  update_needed |= await self._async_set_power_async_set_power(True)
153 
154  if preset_mode:
155  update_needed |= await self._async_set_preset_mode_internal_async_set_preset_mode_internal(preset_mode)
156 
157  if percentage is not None:
158  update_needed |= await self._async_set_percentage_internal_async_set_percentage_internal(
159  percentage, preset_mode
160  )
161 
162  if update_needed:
163  # This state change affects other entities like sensors. Force an immediate update that
164  # can be observed by all parties involved.
165  await self.coordinator.async_request_refresh()
166 
167  async def async_turn_off(self, **kwargs: Any) -> None:
168  """Turn the device off."""
169  if not self.is_onis_onis_onis_on:
170  return
171 
172  update_needed = await self._async_set_power_async_set_power(False)
173 
174  if update_needed:
175  await self.coordinator.async_request_refresh()
176 
177  async def async_set_percentage(self, percentage: int) -> None:
178  """Set the speed of the fan, as a percentage."""
179  if percentage == 0:
180  await self.async_turn_offasync_turn_offasync_turn_off()
181  return
182 
183  update_needed = await self._async_set_percentage_internal_async_set_percentage_internal(percentage)
184 
185  if update_needed:
186  await self.coordinator.async_request_refresh()
187 
188  async def _async_set_power(self, mode: bool) -> bool:
189  try:
190  await self._client_client.set_values(
191  {METRIC_KEY_MODE: MODE_ON if mode else MODE_OFF}
192  )
193  except ValloxApiException as err:
194  raise HomeAssistantError("Failed to set power mode") from err
195 
196  return True
197 
198  async def _async_set_preset_mode_internal(self, preset_mode: str) -> bool:
199  """Set new preset mode.
200 
201  Returns true if the mode has been changed, false otherwise.
202  """
203  if preset_mode == self.preset_modepreset_modepreset_mode:
204  return False
205 
206  try:
207  profile = PRESET_MODE_TO_VALLOX_PROFILE[preset_mode]
208  await self._client_client.set_profile(profile)
209 
210  except ValloxApiException as err:
211  raise HomeAssistantError(f"Failed to set profile: {preset_mode}") from err
212 
213  return True
214 
216  self, percentage: int, preset_mode: str | None = None
217  ) -> bool:
218  """Set fan speed percentage for current profile.
219 
220  Returns true if speed has been changed, false otherwise.
221  """
222  vallox_profile = (
223  PRESET_MODE_TO_VALLOX_PROFILE[preset_mode]
224  if preset_mode is not None
225  else self.coordinator.data.profile
226  )
227 
228  try:
229  await self._client_client.set_fan_speed(vallox_profile, percentage)
230  except ValloxInvalidInputException as err:
231  # This can happen if current profile does not support setting the fan speed.
232  raise ValueError(
233  f"{vallox_profile} profile does not support setting the fan speed"
234  ) from err
235  except ValloxApiException as err:
236  raise HomeAssistantError("Failed to set fan speed") from err
237 
238  return True
None async_set_percentage(self, int percentage)
Definition: fan.py:177
None async_turn_off(self, **Any kwargs)
Definition: fan.py:167
bool _async_set_percentage_internal(self, int percentage, str|None preset_mode=None)
Definition: fan.py:217
bool _async_set_preset_mode_internal(self, str preset_mode)
Definition: fan.py:198
None __init__(self, str name, Vallox client, ValloxDataUpdateCoordinator coordinator)
Definition: fan.py:93
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:133
Mapping[str, int|None] extra_state_attributes(self)
Definition: fan.py:124
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: fan.py:147
None async_turn_off(self, **Any kwargs)
Definition: entity.py:1709
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: fan.py:61
int|None _convert_to_int(StateType value)
Definition: fan.py:52