Home Assistant Unofficial Reference 2024.12.1
fan.py
Go to the documentation of this file.
1 """Support for Bond fans."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import math
7 from typing import Any
8 
9 from aiohttp.client_exceptions import ClientResponseError
10 from bond_async import Action, DeviceType, Direction
11 import voluptuous as vol
12 
13 from homeassistant.components.fan import (
14  DIRECTION_FORWARD,
15  DIRECTION_REVERSE,
16  FanEntity,
17  FanEntityFeature,
18 )
19 from homeassistant.core import HomeAssistant
20 from homeassistant.exceptions import HomeAssistantError
21 from homeassistant.helpers import entity_platform
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24  percentage_to_ranged_value,
25  ranged_value_to_percentage,
26 )
27 from homeassistant.util.scaling import int_states_in_range
28 
29 from . import BondConfigEntry
30 from .const import SERVICE_SET_FAN_SPEED_TRACKED_STATE
31 from .entity import BondEntity
32 from .models import BondData
33 from .utils import BondDevice
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 PRESET_MODE_BREEZE = "Breeze"
38 
39 
41  hass: HomeAssistant,
42  entry: BondConfigEntry,
43  async_add_entities: AddEntitiesCallback,
44 ) -> None:
45  """Set up Bond fan devices."""
46  data = entry.runtime_data
47  platform = entity_platform.async_get_current_platform()
48  platform.async_register_entity_service(
49  SERVICE_SET_FAN_SPEED_TRACKED_STATE,
50  {vol.Required("speed"): vol.All(vol.Number(scale=0), vol.Range(0, 100))},
51  "async_set_speed_belief",
52  )
53 
55  BondFan(data, device)
56  for device in data.hub.devices
57  if DeviceType.is_fan(device.type)
58  )
59 
60 
62  """Representation of a Bond fan."""
63 
64  def __init__(self, data: BondData, device: BondDevice) -> None:
65  """Create HA entity representing Bond fan."""
66  self._power_power: bool | None = None
67  self._speed_speed: int | None = None
68  self._direction_direction: int | None = None
69  super().__init__(data, device)
70  if self._device_device.has_action(Action.BREEZE_ON):
71  self._attr_preset_modes_attr_preset_modes = [PRESET_MODE_BREEZE]
72  features = FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
73  if self._device_device.supports_speed():
74  features |= FanEntityFeature.SET_SPEED
75  if self._device_device.supports_direction():
76  features |= FanEntityFeature.DIRECTION
77  if self._device_device.has_action(Action.BREEZE_ON):
78  features |= FanEntityFeature.PRESET_MODE
79  self._attr_supported_features_attr_supported_features = features
80 
81  def _apply_state(self) -> None:
82  state = self._device_device.state
83  self._power_power = state.get("power")
84  self._speed_speed = state.get("speed")
85  self._direction_direction = state.get("direction")
86  breeze = state.get("breeze", [0, 0, 0])
87  self._attr_preset_mode_attr_preset_mode = PRESET_MODE_BREEZE if breeze[0] else None
88 
89  @property
90  def _speed_range(self) -> tuple[int, int]:
91  """Return the range of speeds."""
92  return (1, self._device_device.props.get("max_speed", 3))
93 
94  @property
95  def percentage(self) -> int:
96  """Return the current speed percentage for the fan."""
97  if not self._speed_speed or not self._power_power:
98  return 0
99  return min(
100  100, max(0, ranged_value_to_percentage(self._speed_range_speed_range, self._speed_speed))
101  )
102 
103  @property
104  def speed_count(self) -> int:
105  """Return the number of speeds the fan supports."""
106  return int_states_in_range(self._speed_range_speed_range)
107 
108  @property
109  def current_direction(self) -> str | None:
110  """Return fan rotation direction."""
111  direction = None
112  if self._direction_direction == Direction.FORWARD:
113  direction = DIRECTION_FORWARD
114  elif self._direction_direction == Direction.REVERSE:
115  direction = DIRECTION_REVERSE
116 
117  return direction
118 
119  async def async_set_percentage(self, percentage: int) -> None:
120  """Set the desired speed for the fan."""
121  _LOGGER.debug("async_set_percentage called with percentage %s", percentage)
122 
123  if percentage == 0:
124  await self.async_turn_offasync_turn_offasync_turn_off()
125  return
126 
127  bond_speed = math.ceil(
128  percentage_to_ranged_value(self._speed_range_speed_range, percentage)
129  )
130  _LOGGER.debug(
131  "async_set_percentage converted percentage %s to bond speed %s",
132  percentage,
133  bond_speed,
134  )
135 
136  await self._bond_bond.action(self._device_id_device_id, Action.set_speed(bond_speed))
137 
138  async def async_set_power_belief(self, power_state: bool) -> None:
139  """Set the believed state to on or off."""
140  try:
141  await self._bond_bond.action(
142  self._device_id_device_id, Action.set_power_state_belief(power_state)
143  )
144  except ClientResponseError as ex:
145  raise HomeAssistantError(
146  "The bond API returned an error calling set_power_state_belief for"
147  f" {self.entity_id}. Code: {ex.status} Message: {ex.message}"
148  ) from ex
149 
150  async def async_set_speed_belief(self, speed: int) -> None:
151  """Set the believed speed for the fan."""
152  _LOGGER.debug("async_set_speed_belief called with percentage %s", speed)
153  if speed == 0:
154  await self.async_set_power_beliefasync_set_power_belief(False)
155  return
156 
157  await self.async_set_power_beliefasync_set_power_belief(True)
158 
159  bond_speed = math.ceil(percentage_to_ranged_value(self._speed_range_speed_range, speed))
160  _LOGGER.debug(
161  "async_set_percentage converted percentage %s to bond speed %s",
162  speed,
163  bond_speed,
164  )
165  try:
166  await self._bond_bond.action(
167  self._device_id_device_id, Action.set_speed_belief(bond_speed)
168  )
169  except ClientResponseError as ex:
170  raise HomeAssistantError(
171  "The bond API returned an error calling set_speed_belief for"
172  f" {self.entity_id}. Code: {ex.code} Message: {ex.message}"
173  ) from ex
174 
175  async def async_turn_on(
176  self,
177  percentage: int | None = None,
178  preset_mode: str | None = None,
179  **kwargs: Any,
180  ) -> None:
181  """Turn on the fan."""
182  _LOGGER.debug("Fan async_turn_on called with percentage %s", percentage)
183 
184  if preset_mode is not None:
185  await self.async_set_preset_modeasync_set_preset_modeasync_set_preset_mode(preset_mode)
186  elif percentage is not None:
187  await self.async_set_percentageasync_set_percentageasync_set_percentage(percentage)
188  else:
189  await self._bond_bond.action(self._device_id_device_id, Action.turn_on())
190 
191  async def async_set_preset_mode(self, preset_mode: str) -> None:
192  """Set the preset mode of the fan."""
193  await self._bond_bond.action(self._device_id_device_id, Action(Action.BREEZE_ON))
194 
195  async def async_turn_off(self, **kwargs: Any) -> None:
196  """Turn the fan off."""
197  if self.preset_modepreset_modepreset_mode == PRESET_MODE_BREEZE:
198  await self._bond_bond.action(self._device_id_device_id, Action(Action.BREEZE_OFF))
199  await self._bond_bond.action(self._device_id_device_id, Action.turn_off())
200 
201  async def async_set_direction(self, direction: str) -> None:
202  """Set fan rotation direction."""
203  bond_direction = (
204  Direction.REVERSE if direction == DIRECTION_REVERSE else Direction.FORWARD
205  )
206  await self._bond_bond.action(self._device_id_device_id, Action.set_direction(bond_direction))
None async_set_speed_belief(self, int speed)
Definition: fan.py:150
None __init__(self, BondData data, BondDevice device)
Definition: fan.py:64
tuple[int, int] _speed_range(self)
Definition: fan.py:90
None async_set_direction(self, str direction)
Definition: fan.py:201
None async_set_percentage(self, int percentage)
Definition: fan.py:119
None async_set_power_belief(self, bool power_state)
Definition: fan.py:138
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: fan.py:180
None async_turn_off(self, **Any kwargs)
Definition: fan.py:195
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:191
None async_set_percentage(self, int percentage)
Definition: __init__.py:340
None async_set_preset_mode(self, str preset_mode)
Definition: __init__.py:385
None async_turn_off(self, **Any kwargs)
Definition: entity.py:1709
None async_setup_entry(HomeAssistant hass, BondConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: fan.py:44
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
int int_states_in_range(tuple[float, float] low_high_range)
Definition: scaling.py:61