Home Assistant Unofficial Reference 2024.12.1
fan.py
Go to the documentation of this file.
1 """Support for Fjäråskupan fans."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from fjaraskupan import (
8  COMMAND_AFTERCOOKINGTIMERAUTO,
9  COMMAND_AFTERCOOKINGTIMERMANUAL,
10  COMMAND_AFTERCOOKINGTIMEROFF,
11  COMMAND_STOP_FAN,
12  State,
13 )
14 
15 from homeassistant.components.fan import FanEntity, FanEntityFeature
16 from homeassistant.config_entries import ConfigEntry
17 from homeassistant.core import HomeAssistant, callback
18 from homeassistant.exceptions import HomeAssistantError
19 from homeassistant.helpers.device_registry import DeviceInfo
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.update_coordinator import CoordinatorEntity
23  ordered_list_item_to_percentage,
24  percentage_to_ordered_list_item,
25 )
26 
27 from . import async_setup_entry_platform
28 from .coordinator import FjaraskupanCoordinator
29 
30 ORDERED_NAMED_FAN_SPEEDS = ["1", "2", "3", "4", "5", "6", "7", "8"]
31 
32 PRESET_MODE_NORMAL = "normal"
33 PRESET_MODE_AFTER_COOKING_MANUAL = "after_cooking_manual"
34 PRESET_MODE_AFTER_COOKING_AUTO = "after_cooking_auto"
35 PRESET_MODES = [
36  PRESET_MODE_NORMAL,
37  PRESET_MODE_AFTER_COOKING_AUTO,
38  PRESET_MODE_AFTER_COOKING_MANUAL,
39 ]
40 
41 PRESET_TO_COMMAND = {
42  PRESET_MODE_AFTER_COOKING_MANUAL: COMMAND_AFTERCOOKINGTIMERMANUAL,
43  PRESET_MODE_AFTER_COOKING_AUTO: COMMAND_AFTERCOOKINGTIMERAUTO,
44  PRESET_MODE_NORMAL: COMMAND_AFTERCOOKINGTIMEROFF,
45 }
46 
47 
49  """The preset is unsupported."""
50 
51 
53  hass: HomeAssistant,
54  config_entry: ConfigEntry,
55  async_add_entities: AddEntitiesCallback,
56 ) -> None:
57  """Set up sensors dynamically through discovery."""
58 
59  def _constructor(coordinator: FjaraskupanCoordinator):
60  return [Fan(coordinator, coordinator.device_info)]
61 
62  async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor)
63 
64 
65 class Fan(CoordinatorEntity[FjaraskupanCoordinator], FanEntity):
66  """Fan entity."""
67 
68  _attr_supported_features = (
69  FanEntityFeature.SET_SPEED
70  | FanEntityFeature.PRESET_MODE
71  | FanEntityFeature.TURN_OFF
72  | FanEntityFeature.TURN_ON
73  )
74  _enable_turn_on_off_backwards_compatibility = False
75  _attr_has_entity_name = True
76  _attr_name = None
77 
78  def __init__(
79  self,
80  coordinator: FjaraskupanCoordinator,
81  device_info: DeviceInfo,
82  ) -> None:
83  """Init fan entity."""
84  super().__init__(coordinator)
85  self._default_on_speed_default_on_speed = 25
86  self._attr_unique_id_attr_unique_id = coordinator.device.address
87  self._attr_device_info_attr_device_info = device_info
88  self._percentage_percentage = 0
89  self._preset_mode_preset_mode = PRESET_MODE_NORMAL
90  self._update_from_device_data_update_from_device_data(coordinator.data)
91 
92  async def async_set_percentage(self, percentage: int) -> None:
93  """Set speed."""
94 
95  # Proactively update percentage to manage successive increases
96  self._percentage_percentage = percentage
97 
98  async with self.coordinator.async_connect_and_update() as device:
99  if percentage == 0:
100  await device.send_command(COMMAND_STOP_FAN)
101  else:
102  new_speed = percentage_to_ordered_list_item(
103  ORDERED_NAMED_FAN_SPEEDS, percentage
104  )
105  await device.send_fan_speed(int(new_speed))
106 
107  async def async_turn_on(
108  self,
109  percentage: int | None = None,
110  preset_mode: str | None = None,
111  **kwargs: Any,
112  ) -> None:
113  """Turn on the fan."""
114 
115  if preset_mode is None:
116  preset_mode = self._preset_mode_preset_mode
117 
118  if percentage is None:
119  percentage = self._default_on_speed_default_on_speed
120 
121  new_speed = percentage_to_ordered_list_item(
122  ORDERED_NAMED_FAN_SPEEDS, percentage
123  )
124 
125  async with self.coordinator.async_connect_and_update() as device:
126  if preset_mode != self._preset_mode_preset_mode:
127  if command := PRESET_TO_COMMAND.get(preset_mode):
128  await device.send_command(command)
129  else:
130  raise UnsupportedPreset(f"The preset {preset_mode} is unsupported")
131 
132  if preset_mode == PRESET_MODE_NORMAL:
133  await device.send_fan_speed(int(new_speed))
134  elif preset_mode == PRESET_MODE_AFTER_COOKING_MANUAL:
135  await device.send_after_cooking(int(new_speed))
136  elif preset_mode == PRESET_MODE_AFTER_COOKING_AUTO:
137  await device.send_after_cooking(0)
138 
139  async def async_set_preset_mode(self, preset_mode: str) -> None:
140  """Set new preset mode."""
141  command = PRESET_TO_COMMAND[preset_mode]
142  async with self.coordinator.async_connect_and_update() as device:
143  await device.send_command(command)
144 
145  async def async_turn_off(self, **kwargs: Any) -> None:
146  """Turn the entity off."""
147  async with self.coordinator.async_connect_and_update() as device:
148  await device.send_command(COMMAND_STOP_FAN)
149 
150  @property
151  def speed_count(self) -> int:
152  """Return the number of speeds the fan supports."""
153  return len(ORDERED_NAMED_FAN_SPEEDS)
154 
155  @property
156  def percentage(self) -> int | None:
157  """Return the current speed."""
158  return self._percentage_percentage
159 
160  @property
161  def is_on(self) -> bool:
162  """Return true if fan is on."""
163  return self._percentage_percentage != 0
164 
165  @property
166  def preset_mode(self) -> str | None:
167  """Return the current preset mode."""
168  return self._preset_mode_preset_mode
169 
170  @property
171  def preset_modes(self) -> list[str] | None:
172  """Return a list of available preset modes."""
173  return PRESET_MODES
174 
175  def _update_from_device_data(self, data: State | None) -> None:
176  """Handle data update."""
177  if not data:
178  self._percentage_percentage = 0
179  return
180 
181  if data.fan_speed:
182  self._percentage_percentage = ordered_list_item_to_percentage(
183  ORDERED_NAMED_FAN_SPEEDS, str(data.fan_speed)
184  )
185  else:
186  self._percentage_percentage = 0
187 
188  if data.after_cooking_on:
189  if data.after_cooking_fan_speed:
190  self._preset_mode_preset_mode = PRESET_MODE_AFTER_COOKING_MANUAL
191  else:
192  self._preset_mode_preset_mode = PRESET_MODE_AFTER_COOKING_AUTO
193  else:
194  self._preset_mode_preset_mode = PRESET_MODE_NORMAL
195 
196  @callback
197  def _handle_coordinator_update(self) -> None:
198  """Handle data update."""
199 
200  self._update_from_device_data_update_from_device_data(self.coordinator.data)
201  self.async_write_ha_stateasync_write_ha_state()
None __init__(self, FjaraskupanCoordinator coordinator, DeviceInfo device_info)
Definition: fan.py:82
None _update_from_device_data(self, State|None data)
Definition: fan.py:175
None async_set_percentage(self, int percentage)
Definition: fan.py:92
None async_turn_off(self, **Any kwargs)
Definition: fan.py:145
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:139
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: fan.py:112
list[str]|None preset_modes(self)
Definition: fan.py:171
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: fan.py:56
None async_setup_entry_platform(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities, Callable[[FjaraskupanCoordinator], list[Entity]] constructor)
Definition: __init__.py:111