Home Assistant Unofficial Reference 2024.12.1
fan.py
Go to the documentation of this file.
1 """Support for ESPHome fans."""
2 
3 from __future__ import annotations
4 
5 from functools import partial
6 import math
7 from typing import Any
8 
9 from aioesphomeapi import EntityInfo, FanDirection, FanInfo, FanSpeed, FanState
10 
11 from homeassistant.components.fan import (
12  DIRECTION_FORWARD,
13  DIRECTION_REVERSE,
14  FanEntity,
15  FanEntityFeature,
16 )
17 from homeassistant.core import callback
19  ordered_list_item_to_percentage,
20  percentage_to_ordered_list_item,
21  percentage_to_ranged_value,
22  ranged_value_to_percentage,
23 )
24 
25 from .entity import (
26  EsphomeEntity,
27  convert_api_error_ha_error,
28  esphome_state_property,
29  platform_async_setup_entry,
30 )
31 from .enum_mapper import EsphomeEnumMapper
32 
33 ORDERED_NAMED_FAN_SPEEDS = [FanSpeed.LOW, FanSpeed.MEDIUM, FanSpeed.HIGH]
34 
35 
36 _FAN_DIRECTIONS: EsphomeEnumMapper[FanDirection, str] = EsphomeEnumMapper(
37  {
38  FanDirection.FORWARD: DIRECTION_FORWARD,
39  FanDirection.REVERSE: DIRECTION_REVERSE,
40  }
41 )
42 
43 
44 class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity):
45  """A fan implementation for ESPHome."""
46 
47  _supports_speed_levels: bool = True
48  _enable_turn_on_off_backwards_compatibility = False
49 
50  async def async_set_percentage(self, percentage: int) -> None:
51  """Set the speed percentage of the fan."""
52  await self._async_set_percentage_async_set_percentage(percentage)
53 
54  @convert_api_error_ha_error
55  async def _async_set_percentage(self, percentage: int | None) -> None:
56  if percentage == 0:
57  await self.async_turn_offasync_turn_offasync_turn_off()
58  return
59 
60  data: dict[str, Any] = {"key": self._key_key, "state": True}
61  if percentage is not None:
62  if self._supports_speed_levels_supports_speed_levels:
63  data["speed_level"] = math.ceil(
65  (1, self._static_info_static_info.supported_speed_levels), percentage
66  )
67  )
68  else:
69  named_speed = percentage_to_ordered_list_item(
70  ORDERED_NAMED_FAN_SPEEDS, percentage
71  )
72  data["speed"] = named_speed
73  self._client_client.fan_command(**data)
74 
75  async def async_turn_on(
76  self,
77  percentage: int | None = None,
78  preset_mode: str | None = None,
79  **kwargs: Any,
80  ) -> None:
81  """Turn on the fan."""
82  await self._async_set_percentage_async_set_percentage(percentage)
83 
84  @convert_api_error_ha_error
85  async def async_turn_off(self, **kwargs: Any) -> None:
86  """Turn off the fan."""
87  self._client_client.fan_command(key=self._key_key, state=False)
88 
89  @convert_api_error_ha_error
90  async def async_oscillate(self, oscillating: bool) -> None:
91  """Oscillate the fan."""
92  self._client_client.fan_command(key=self._key_key, oscillating=oscillating)
93 
94  @convert_api_error_ha_error
95  async def async_set_direction(self, direction: str) -> None:
96  """Set direction of the fan."""
97  self._client_client.fan_command(
98  key=self._key_key, direction=_FAN_DIRECTIONS.from_hass(direction)
99  )
100 
101  @convert_api_error_ha_error
102  async def async_set_preset_mode(self, preset_mode: str) -> None:
103  """Set the preset mode of the fan."""
104  self._client_client.fan_command(key=self._key_key, preset_mode=preset_mode)
105 
106  @property
107  @esphome_state_property
108  def is_on(self) -> bool | None:
109  """Return true if the entity is on."""
110  return self._state_state.state
111 
112  @property
113  @esphome_state_property
114  def percentage(self) -> int | None:
115  """Return the current speed percentage."""
116  if not self._supports_speed_levels_supports_speed_levels:
117  return ordered_list_item_to_percentage(
118  ORDERED_NAMED_FAN_SPEEDS,
119  self._state_state.speed, # type: ignore[misc]
120  )
121 
123  (1, self._static_info_static_info.supported_speed_levels), self._state_state.speed_level
124  )
125 
126  @property
127  @esphome_state_property
128  def oscillating(self) -> bool | None:
129  """Return the oscillation state."""
130  return self._state_state.oscillating
131 
132  @property
133  @esphome_state_property
134  def current_direction(self) -> str | None:
135  """Return the current fan direction."""
136  return _FAN_DIRECTIONS.from_esphome(self._state_state.direction)
137 
138  @property
139  @esphome_state_property
140  def preset_mode(self) -> str | None:
141  """Return the current fan preset mode."""
142  return self._state_state.preset_mode
143 
144  @callback
145  def _on_static_info_update(self, static_info: EntityInfo) -> None:
146  """Set attrs from static info."""
147  super()._on_static_info_update(static_info)
148  static_info = self._static_info_static_info
149  api_version = self._api_version_api_version
150  supports_speed_levels = api_version.major == 1 and api_version.minor > 3
151  self._supports_speed_levels_supports_speed_levels = supports_speed_levels
152  flags = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
153  if static_info.supports_oscillation:
154  flags |= FanEntityFeature.OSCILLATE
155  if static_info.supports_speed:
156  flags |= FanEntityFeature.SET_SPEED
157  if static_info.supports_direction:
158  flags |= FanEntityFeature.DIRECTION
159  if static_info.supported_preset_modes:
160  flags |= FanEntityFeature.PRESET_MODE
161  self._attr_supported_features_attr_supported_features = flags
162  self._attr_preset_modes_attr_preset_modes = static_info.supported_preset_modes
163  if not supports_speed_levels:
164  self._attr_speed_count_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
165  else:
166  self._attr_speed_count_attr_speed_count = static_info.supported_speed_levels
167 
168 
169 async_setup_entry = partial(
170  platform_async_setup_entry,
171  info_type=FanInfo,
172  entity_type=EsphomeFan,
173  state_type=FanState,
174 )
None async_set_percentage(self, int percentage)
Definition: fan.py:50
None async_oscillate(self, bool oscillating)
Definition: fan.py:90
None _async_set_percentage(self, int|None percentage)
Definition: fan.py:55
None async_set_direction(self, str direction)
Definition: fan.py:95
None _on_static_info_update(self, EntityInfo static_info)
Definition: fan.py:145
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:102
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: fan.py:80
None async_turn_off(self, **Any kwargs)
Definition: fan.py:85
None async_turn_off(self, **Any kwargs)
Definition: entity.py:1709
float percentage_to_ranged_value(tuple[float, float] low_high_range, float percentage)
Definition: percentage.py:81
int ranged_value_to_percentage(tuple[float, float] low_high_range, float value)
Definition: percentage.py:64