Home Assistant Unofficial Reference 2024.12.1
fan.py
Go to the documentation of this file.
1 """Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier."""
2 
3 from __future__ import annotations
4 
5 from abc import abstractmethod
6 import asyncio
7 import logging
8 import math
9 from typing import Any
10 
11 from miio.fan_common import (
12  MoveDirection as FanMoveDirection,
13  OperationMode as FanOperationMode,
14 )
15 from miio.integrations.airpurifier.dmaker.airfresh_t2017 import (
16  OperationMode as AirfreshOperationModeT2017,
17 )
18 from miio.integrations.airpurifier.zhimi.airfresh import (
19  OperationMode as AirfreshOperationMode,
20 )
21 from miio.integrations.airpurifier.zhimi.airpurifier import (
22  OperationMode as AirpurifierOperationMode,
23 )
24 from miio.integrations.airpurifier.zhimi.airpurifier_miot import (
25  OperationMode as AirpurifierMiotOperationMode,
26 )
27 from miio.integrations.fan.zhimi.zhimi_miot import (
28  OperationModeFanZA5 as FanZA5OperationMode,
29 )
30 import voluptuous as vol
31 
32 from homeassistant.components.fan import FanEntity, FanEntityFeature
33 from homeassistant.config_entries import ConfigEntry
34 from homeassistant.const import ATTR_ENTITY_ID, CONF_DEVICE, CONF_MODEL
35 from homeassistant.core import HomeAssistant, ServiceCall, callback
37 from homeassistant.helpers.entity_platform import AddEntitiesCallback
39  percentage_to_ranged_value,
40  ranged_value_to_percentage,
41 )
42 
43 from .const import (
44  CONF_FLOW_TYPE,
45  DOMAIN,
46  FEATURE_FLAGS_AIRFRESH,
47  FEATURE_FLAGS_AIRFRESH_A1,
48  FEATURE_FLAGS_AIRFRESH_T2017,
49  FEATURE_FLAGS_AIRPURIFIER_2S,
50  FEATURE_FLAGS_AIRPURIFIER_3C,
51  FEATURE_FLAGS_AIRPURIFIER_4,
52  FEATURE_FLAGS_AIRPURIFIER_4_LITE,
53  FEATURE_FLAGS_AIRPURIFIER_MIIO,
54  FEATURE_FLAGS_AIRPURIFIER_MIOT,
55  FEATURE_FLAGS_AIRPURIFIER_PRO,
56  FEATURE_FLAGS_AIRPURIFIER_PRO_V7,
57  FEATURE_FLAGS_AIRPURIFIER_V3,
58  FEATURE_FLAGS_AIRPURIFIER_ZA1,
59  FEATURE_FLAGS_FAN,
60  FEATURE_FLAGS_FAN_1C,
61  FEATURE_FLAGS_FAN_P5,
62  FEATURE_FLAGS_FAN_P9,
63  FEATURE_FLAGS_FAN_P10_P11_P18,
64  FEATURE_FLAGS_FAN_ZA5,
65  FEATURE_RESET_FILTER,
66  FEATURE_SET_EXTRA_FEATURES,
67  KEY_COORDINATOR,
68  KEY_DEVICE,
69  MODEL_AIRFRESH_A1,
70  MODEL_AIRFRESH_T2017,
71  MODEL_AIRPURIFIER_2H,
72  MODEL_AIRPURIFIER_2S,
73  MODEL_AIRPURIFIER_3C,
74  MODEL_AIRPURIFIER_3C_REV_A,
75  MODEL_AIRPURIFIER_4,
76  MODEL_AIRPURIFIER_4_LITE_RMA1,
77  MODEL_AIRPURIFIER_4_LITE_RMB1,
78  MODEL_AIRPURIFIER_4_PRO,
79  MODEL_AIRPURIFIER_PRO,
80  MODEL_AIRPURIFIER_PRO_V7,
81  MODEL_AIRPURIFIER_V3,
82  MODEL_AIRPURIFIER_ZA1,
83  MODEL_FAN_1C,
84  MODEL_FAN_P5,
85  MODEL_FAN_P9,
86  MODEL_FAN_P10,
87  MODEL_FAN_P11,
88  MODEL_FAN_P18,
89  MODEL_FAN_ZA5,
90  MODELS_FAN_MIIO,
91  MODELS_FAN_MIOT,
92  MODELS_PURIFIER_MIOT,
93  SERVICE_RESET_FILTER,
94  SERVICE_SET_EXTRA_FEATURES,
95 )
96 from .entity import XiaomiCoordinatedMiioEntity
97 from .typing import ServiceMethodDetails
98 
99 _LOGGER = logging.getLogger(__name__)
100 
101 DATA_KEY = "fan.xiaomi_miio"
102 
103 ATTR_MODE_NATURE = "nature"
104 ATTR_MODE_NORMAL = "normal"
105 
106 # Air Purifier
107 ATTR_BRIGHTNESS = "brightness"
108 ATTR_FAN_LEVEL = "fan_level"
109 ATTR_SLEEP_TIME = "sleep_time"
110 ATTR_SLEEP_LEARN_COUNT = "sleep_mode_learn_count"
111 ATTR_EXTRA_FEATURES = "extra_features"
112 ATTR_FEATURES = "features"
113 ATTR_TURBO_MODE_SUPPORTED = "turbo_mode_supported"
114 ATTR_SLEEP_MODE = "sleep_mode"
115 ATTR_USE_TIME = "use_time"
116 ATTR_BUTTON_PRESSED = "button_pressed"
117 
118 # Air Fresh A1
119 ATTR_FAVORITE_SPEED = "favorite_speed"
120 
121 # Air Purifier 3C
122 ATTR_FAVORITE_RPM = "favorite_rpm"
123 ATTR_MOTOR_SPEED = "motor_speed"
124 
125 # Map attributes to properties of the state object
126 AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON = {
127  ATTR_EXTRA_FEATURES: "extra_features",
128  ATTR_TURBO_MODE_SUPPORTED: "turbo_mode_supported",
129  ATTR_BUTTON_PRESSED: "button_pressed",
130 }
131 
132 AVAILABLE_ATTRIBUTES_AIRPURIFIER = {
133  **AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON,
134  ATTR_SLEEP_TIME: "sleep_time",
135  ATTR_SLEEP_LEARN_COUNT: "sleep_mode_learn_count",
136  ATTR_USE_TIME: "use_time",
137  ATTR_SLEEP_MODE: "sleep_mode",
138 }
139 
140 AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO = {
141  **AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON,
142  ATTR_USE_TIME: "use_time",
143  ATTR_SLEEP_TIME: "sleep_time",
144  ATTR_SLEEP_LEARN_COUNT: "sleep_mode_learn_count",
145 }
146 
147 AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT = {ATTR_USE_TIME: "use_time"}
148 
149 AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7 = AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON
150 
151 AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 = {
152  # Common set isn't used here. It's a very basic version of the device.
153  ATTR_SLEEP_TIME: "sleep_time",
154  ATTR_SLEEP_LEARN_COUNT: "sleep_mode_learn_count",
155  ATTR_EXTRA_FEATURES: "extra_features",
156  ATTR_USE_TIME: "use_time",
157  ATTR_BUTTON_PRESSED: "button_pressed",
158 }
159 
160 AVAILABLE_ATTRIBUTES_AIRFRESH = {
161  ATTR_USE_TIME: "use_time",
162  ATTR_EXTRA_FEATURES: "extra_features",
163 }
164 
165 PRESET_MODES_AIRPURIFIER = ["Auto", "Silent", "Favorite", "Idle"]
166 PRESET_MODES_AIRPURIFIER_4_LITE = ["Auto", "Silent", "Favorite"]
167 PRESET_MODES_AIRPURIFIER_MIOT = ["Auto", "Silent", "Favorite", "Fan"]
168 PRESET_MODES_AIRPURIFIER_PRO = ["Auto", "Silent", "Favorite"]
169 PRESET_MODES_AIRPURIFIER_PRO_V7 = PRESET_MODES_AIRPURIFIER_PRO
170 PRESET_MODES_AIRPURIFIER_2S = ["Auto", "Silent", "Favorite"]
171 PRESET_MODES_AIRPURIFIER_3C = ["Auto", "Silent", "Favorite"]
172 PRESET_MODES_AIRPURIFIER_ZA1 = ["Auto", "Silent", "Favorite"]
173 PRESET_MODES_AIRPURIFIER_V3 = [
174  "Auto",
175  "Silent",
176  "Favorite",
177  "Idle",
178  "Medium",
179  "High",
180  "Strong",
181 ]
182 PRESET_MODES_AIRFRESH = ["Auto", "Interval"]
183 PRESET_MODES_AIRFRESH_A1 = ["Auto", "Sleep", "Favorite"]
184 
185 AIRPURIFIER_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids})
186 
187 SERVICE_SCHEMA_EXTRA_FEATURES = AIRPURIFIER_SERVICE_SCHEMA.extend(
188  {vol.Required(ATTR_FEATURES): cv.positive_int}
189 )
190 
191 SERVICE_TO_METHOD = {
192  SERVICE_RESET_FILTER: ServiceMethodDetails(method="async_reset_filter"),
193  SERVICE_SET_EXTRA_FEATURES: ServiceMethodDetails(
194  method="async_set_extra_features",
195  schema=SERVICE_SCHEMA_EXTRA_FEATURES,
196  ),
197 }
198 
199 FAN_DIRECTIONS_MAP = {
200  "forward": "right",
201  "reverse": "left",
202 }
203 
204 
206  hass: HomeAssistant,
207  config_entry: ConfigEntry,
208  async_add_entities: AddEntitiesCallback,
209 ) -> None:
210  """Set up the Fan from a config entry."""
211  entities: list[FanEntity] = []
212  entity: FanEntity
213 
214  if config_entry.data[CONF_FLOW_TYPE] != CONF_DEVICE:
215  return
216 
217  hass.data.setdefault(DATA_KEY, {})
218 
219  model = config_entry.data[CONF_MODEL]
220  unique_id = config_entry.unique_id
221  coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
222  device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
223 
224  if model in (MODEL_AIRPURIFIER_3C, MODEL_AIRPURIFIER_3C_REV_A):
225  entity = XiaomiAirPurifierMB4(
226  device,
227  config_entry,
228  unique_id,
229  coordinator,
230  )
231  elif model in MODELS_PURIFIER_MIOT:
232  entity = XiaomiAirPurifierMiot(
233  device,
234  config_entry,
235  unique_id,
236  coordinator,
237  )
238  elif model.startswith("zhimi.airpurifier."):
239  entity = XiaomiAirPurifier(device, config_entry, unique_id, coordinator)
240  elif model.startswith("zhimi.airfresh."):
241  entity = XiaomiAirFresh(device, config_entry, unique_id, coordinator)
242  elif model == MODEL_AIRFRESH_A1:
243  entity = XiaomiAirFreshA1(device, config_entry, unique_id, coordinator)
244  elif model == MODEL_AIRFRESH_T2017:
245  entity = XiaomiAirFreshT2017(device, config_entry, unique_id, coordinator)
246  elif model == MODEL_FAN_P5:
247  entity = XiaomiFanP5(device, config_entry, unique_id, coordinator)
248  elif model in MODELS_FAN_MIIO:
249  entity = XiaomiFan(device, config_entry, unique_id, coordinator)
250  elif model == MODEL_FAN_ZA5:
251  entity = XiaomiFanZA5(device, config_entry, unique_id, coordinator)
252  elif model == MODEL_FAN_1C:
253  entity = XiaomiFan1C(device, config_entry, unique_id, coordinator)
254  elif model in MODELS_FAN_MIOT:
255  entity = XiaomiFanMiot(device, config_entry, unique_id, coordinator)
256  else:
257  return
258 
259  hass.data[DATA_KEY][unique_id] = entity
260 
261  entities.append(entity)
262 
263  async def async_service_handler(service: ServiceCall) -> None:
264  """Map services to methods on XiaomiAirPurifier."""
265  method = SERVICE_TO_METHOD[service.service]
266  params = {
267  key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID
268  }
269  if entity_ids := service.data.get(ATTR_ENTITY_ID):
270  filtered_entities = [
271  entity
272  for entity in hass.data[DATA_KEY].values()
273  if entity.entity_id in entity_ids
274  ]
275  else:
276  filtered_entities = hass.data[DATA_KEY].values()
277 
278  update_tasks = []
279 
280  for entity in filtered_entities:
281  entity_method = getattr(entity, method.method, None)
282  if not entity_method:
283  continue
284  await entity_method(**params)
285  update_tasks.append(asyncio.create_task(entity.async_update_ha_state(True)))
286 
287  if update_tasks:
288  await asyncio.wait(update_tasks)
289 
290  for air_purifier_service, method in SERVICE_TO_METHOD.items():
291  schema = method.schema or AIRPURIFIER_SERVICE_SCHEMA
292  hass.services.async_register(
293  DOMAIN, air_purifier_service, async_service_handler, schema=schema
294  )
295 
296  async_add_entities(entities)
297 
298 
300  """Representation of a generic Xiaomi device."""
301 
302  _attr_name = None
303  _enable_turn_on_off_backwards_compatibility = False
304 
305  def __init__(self, device, entry, unique_id, coordinator):
306  """Initialize the generic Xiaomi device."""
307  super().__init__(device, entry, unique_id, coordinator)
308 
309  self._available_attributes_available_attributes = {}
310  self._state_state = None
311  self._mode_mode = None
312  self._fan_level_fan_level = None
313  self._state_attrs_state_attrs = {}
314  self._device_features_device_features = 0
315  self._preset_modes_preset_modes = []
316 
317  @property
318  @abstractmethod
320  """Hold operation mode class."""
321 
322  @property
323  def preset_modes(self) -> list[str]:
324  """Get the list of available preset modes."""
325  return self._preset_modes_preset_modes
326 
327  @property
328  def percentage(self) -> int | None:
329  """Return the percentage based speed of the fan."""
330  return None
331 
332  @property
333  def extra_state_attributes(self) -> dict[str, Any]:
334  """Return the state attributes of the device."""
335  return self._state_attrs_state_attrs
336 
337  @property
338  def is_on(self) -> bool | None:
339  """Return true if device is on."""
340  return self._state_state
341 
342  async def async_turn_on(
343  self,
344  percentage: int | None = None,
345  preset_mode: str | None = None,
346  **kwargs: Any,
347  ) -> None:
348  """Turn the device on."""
349  result = await self._try_command(
350  "Turning the miio device on failed.", self._device.on
351  )
352 
353  # If operation mode was set the device must not be turned on.
354  if percentage:
355  await self.async_set_percentageasync_set_percentage(percentage)
356  if preset_mode:
357  await self.async_set_preset_modeasync_set_preset_mode(preset_mode)
358 
359  if result:
360  self._state_state = True
361  self.async_write_ha_stateasync_write_ha_state()
362 
363  async def async_turn_off(self, **kwargs: Any) -> None:
364  """Turn the device off."""
365  result = await self._try_command(
366  "Turning the miio device off failed.", self._device.off
367  )
368 
369  if result:
370  self._state_state = False
371  self.async_write_ha_stateasync_write_ha_state()
372 
373 
375  """Representation of a generic AirPurifier device."""
376 
377  def __init__(self, device, entry, unique_id, coordinator):
378  """Initialize the generic AirPurifier device."""
379  super().__init__(device, entry, unique_id, coordinator)
380 
381  self._speed_count_speed_count = 100
382 
383  @property
384  def speed_count(self) -> int:
385  """Return the number of speeds of the fan supported."""
386  return self._speed_count_speed_count
387 
388  @property
389  def preset_mode(self) -> str | None:
390  """Get the active preset mode."""
391  if self._state_state_state:
392  preset_mode = self.operation_mode_classoperation_mode_class(self._mode_mode_mode).name
393  return preset_mode if preset_mode in self._preset_modes_preset_modes else None
394 
395  return None
396 
397  @callback
399  """Fetch state from the device."""
400  self._state_state_state = self.coordinator.data.is_on
401  self._state_attrs_state_attrs.update(
402  {
403  key: self._extract_value_from_attribute(self.coordinator.data, value)
404  for key, value in self._available_attributes_available_attributes.items()
405  }
406  )
407  self._mode_mode_mode = self.coordinator.data.mode.value
408  self._fan_level_fan_level_fan_level = getattr(self.coordinator.data, ATTR_FAN_LEVEL, None)
409  self.async_write_ha_stateasync_write_ha_state()
410 
411 
413  """Representation of a Xiaomi Air Purifier."""
414 
415  SPEED_MODE_MAPPING = {
416  1: AirpurifierOperationMode.Silent,
417  2: AirpurifierOperationMode.Medium,
418  3: AirpurifierOperationMode.High,
419  4: AirpurifierOperationMode.Strong,
420  }
421 
422  REVERSE_SPEED_MODE_MAPPING = {v: k for k, v in SPEED_MODE_MAPPING.items()}
423 
424  def __init__(self, device, entry, unique_id, coordinator):
425  """Initialize the plug switch."""
426  super().__init__(device, entry, unique_id, coordinator)
427 
428  if self._model_model == MODEL_AIRPURIFIER_PRO:
429  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_PRO
430  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO
431  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_PRO
432  self._attr_supported_features_attr_supported_features = FanEntityFeature.PRESET_MODE
433  self._speed_count_speed_count_speed_count = 1
434  elif self._model_model in [MODEL_AIRPURIFIER_4, MODEL_AIRPURIFIER_4_PRO]:
435  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_4
436  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT
437  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_MIOT
438  self._attr_supported_features_attr_supported_features = (
439  FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE
440  )
441  self._speed_count_speed_count_speed_count = 3
442  elif self._model_model in [
443  MODEL_AIRPURIFIER_4_LITE_RMA1,
444  MODEL_AIRPURIFIER_4_LITE_RMB1,
445  ]:
446  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_4_LITE
447  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT
448  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_4_LITE
449  self._attr_supported_features_attr_supported_features = FanEntityFeature.PRESET_MODE
450  self._speed_count_speed_count_speed_count = 1
451  elif self._model_model == MODEL_AIRPURIFIER_PRO_V7:
452  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_PRO_V7
453  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7
454  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_PRO_V7
455  self._attr_supported_features_attr_supported_features = FanEntityFeature.PRESET_MODE
456  self._speed_count_speed_count_speed_count = 1
457  elif self._model_model in [MODEL_AIRPURIFIER_2S, MODEL_AIRPURIFIER_2H]:
458  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_2S
459  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON
460  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_2S
461  self._attr_supported_features_attr_supported_features = FanEntityFeature.PRESET_MODE
462  self._speed_count_speed_count_speed_count = 1
463  elif self._model_model == MODEL_AIRPURIFIER_ZA1:
464  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_ZA1
465  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT
466  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_ZA1
467  self._attr_supported_features_attr_supported_features = FanEntityFeature.PRESET_MODE
468  self._speed_count_speed_count_speed_count = 1
469  elif self._model_model in MODELS_PURIFIER_MIOT:
470  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_MIOT
471  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT
472  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_MIOT
473  self._attr_supported_features_attr_supported_features = (
474  FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE
475  )
476  self._speed_count_speed_count_speed_count = 3
477  elif self._model_model == MODEL_AIRPURIFIER_V3:
478  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_V3
479  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3
480  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_V3
481  self._attr_supported_features_attr_supported_features = FanEntityFeature.PRESET_MODE
482  self._speed_count_speed_count_speed_count = 1
483  else:
484  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_MIIO
485  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER
486  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER
487  self._attr_supported_features_attr_supported_features = FanEntityFeature.PRESET_MODE
488  self._speed_count_speed_count_speed_count = 1
489  self._attr_supported_features_attr_supported_features |= (
490  FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
491  )
492 
493  self._state_state_state_state = self.coordinator.data.is_on
494  self._state_attrs_state_attrs.update(
495  {
496  key: self._extract_value_from_attribute(self.coordinator.data, value)
497  for key, value in self._available_attributes_available_attributes_available_attributes.items()
498  }
499  )
500  self._mode_mode_mode_mode = self.coordinator.data.mode.value
501  self._fan_level_fan_level_fan_level_fan_level = getattr(self.coordinator.data, ATTR_FAN_LEVEL, None)
502 
503  @property
505  """Hold operation mode class."""
506  return AirpurifierOperationMode
507 
508  @property
509  def percentage(self) -> int | None:
510  """Return the current percentage based speed."""
511  if self._state_state_state_state:
512  mode = self.operation_mode_classoperation_mode_classoperation_mode_class(self._mode_mode_mode_mode)
513  if mode in self.REVERSE_SPEED_MODE_MAPPINGREVERSE_SPEED_MODE_MAPPING:
515  (1, self._speed_count_speed_count_speed_count), self.REVERSE_SPEED_MODE_MAPPINGREVERSE_SPEED_MODE_MAPPING[mode]
516  )
517 
518  return None
519 
520  async def async_set_percentage(self, percentage: int) -> None:
521  """Set the percentage of the fan.
522 
523  This method is a coroutine.
524  """
525  if percentage == 0:
526  await self.async_turn_offasync_turn_offasync_turn_off()
527  return
528 
529  speed_mode = math.ceil(
530  percentage_to_ranged_value((1, self._speed_count_speed_count_speed_count), percentage)
531  )
532  if speed_mode:
533  await self._try_command(
534  "Setting operation mode of the miio device failed.",
535  self._device.set_mode,
536  self.operation_mode_classoperation_mode_classoperation_mode_class(self.SPEED_MODE_MAPPINGSPEED_MODE_MAPPING[speed_mode]),
537  )
538 
539  async def async_set_preset_mode(self, preset_mode: str) -> None:
540  """Set the preset mode of the fan.
541 
542  This method is a coroutine.
543  """
544  if await self._try_command(
545  "Setting operation mode of the miio device failed.",
546  self._device.set_mode,
547  self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode],
548  ):
549  self._mode_mode_mode_mode = self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode].value
550  self.async_write_ha_stateasync_write_ha_state()
551 
552  async def async_set_extra_features(self, features: int = 1):
553  """Set the extra features."""
554  if self._device_features_device_features_device_features & FEATURE_SET_EXTRA_FEATURES == 0:
555  return
556 
557  await self._try_command(
558  "Setting the extra features of the miio device failed.",
559  self._device.set_extra_features,
560  features,
561  )
562 
563  async def async_reset_filter(self):
564  """Reset the filter lifetime and usage."""
565  if self._device_features_device_features_device_features & FEATURE_RESET_FILTER == 0:
566  return
567 
568  await self._try_command(
569  "Resetting the filter lifetime of the miio device failed.",
570  self._device.reset_filter,
571  )
572 
573 
575  """Representation of a Xiaomi Air Purifier (MiOT protocol)."""
576 
577  @property
579  """Hold operation mode class."""
580  return AirpurifierMiotOperationMode
581 
582  @property
583  def percentage(self) -> int | None:
584  """Return the current percentage based speed."""
585  if self._fan_level_fan_level_fan_level_fan_level_fan_level is None:
586  return None
587  if self._state_state_state_state:
589 
590  return None
591 
592  async def async_set_percentage(self, percentage: int) -> None:
593  """Set the percentage of the fan.
594 
595  This method is a coroutine.
596  """
597  if percentage == 0:
598  await self.async_turn_offasync_turn_offasync_turn_off()
599  return
600 
601  fan_level = math.ceil(percentage_to_ranged_value((1, 3), percentage))
602  if not fan_level:
603  return
604  if await self._try_command(
605  "Setting fan level of the miio device failed.",
606  self._device.set_fan_level,
607  fan_level,
608  ):
609  self._fan_level_fan_level_fan_level_fan_level_fan_level = fan_level
610  self.async_write_ha_stateasync_write_ha_state()
611 
612 
614  """Representation of a Xiaomi Air Purifier MB4."""
615 
616  def __init__(self, device, entry, unique_id, coordinator) -> None:
617  """Initialize Air Purifier MB4."""
618  super().__init__(device, entry, unique_id, coordinator)
619 
620  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRPURIFIER_3C
621  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRPURIFIER_3C
622  self._attr_supported_features_attr_supported_features = (
623  FanEntityFeature.SET_SPEED
624  | FanEntityFeature.PRESET_MODE
625  | FanEntityFeature.TURN_OFF
626  | FanEntityFeature.TURN_ON
627  )
628 
629  self._state_state_state_state = self.coordinator.data.is_on
630  self._mode_mode_mode_mode = self.coordinator.data.mode.value
631  self._favorite_rpm_favorite_rpm: int | None = None
632  self._speed_range_speed_range = (300, 2200)
633  self._motor_speed_motor_speed = 0
634 
635  @property
637  """Hold operation mode class."""
638  return AirpurifierMiotOperationMode
639 
640  @property
641  def percentage(self) -> int | None:
642  """Return the current percentage based speed."""
643  # show the actual fan speed in silent or auto preset mode
644  if self._mode_mode_mode_mode != self.operation_mode_classoperation_mode_classoperation_mode_class["Favorite"].value:
645  return ranged_value_to_percentage(self._speed_range_speed_range, self._motor_speed_motor_speed)
646  if self._favorite_rpm_favorite_rpm is None:
647  return None
648  if self._state_state_state_state:
649  return ranged_value_to_percentage(self._speed_range_speed_range, self._favorite_rpm_favorite_rpm)
650 
651  return None
652 
653  async def async_set_percentage(self, percentage: int) -> None:
654  """Set the percentage of the fan. This method is a coroutine."""
655  if percentage == 0:
656  await self.async_turn_offasync_turn_offasync_turn_off()
657  return
658 
659  favorite_rpm = int(
660  round(percentage_to_ranged_value(self._speed_range_speed_range, percentage), -1)
661  )
662  if not favorite_rpm:
663  return
664  if await self._try_command(
665  "Setting fan level of the miio device failed.",
666  self._device.set_favorite_rpm,
667  favorite_rpm,
668  ):
669  self._favorite_rpm_favorite_rpm = favorite_rpm
670  self._mode_mode_mode_mode = self.operation_mode_classoperation_mode_classoperation_mode_class["Favorite"].value
671  self.async_write_ha_stateasync_write_ha_state()
672 
673  async def async_set_preset_mode(self, preset_mode: str) -> None:
674  """Set the preset mode of the fan."""
675  if not self._state_state_state_state:
676  await self.async_turn_onasync_turn_onasync_turn_onasync_turn_on()
677 
678  if await self._try_command(
679  "Setting operation mode of the miio device failed.",
680  self._device.set_mode,
681  self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode],
682  ):
683  self._mode_mode_mode_mode = self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode].value
684  self.async_write_ha_stateasync_write_ha_state()
685 
686  @callback
688  """Fetch state from the device."""
689  self._state_state_state_state = self.coordinator.data.is_on
690  self._mode_mode_mode_mode = self.coordinator.data.mode.value
691  self._favorite_rpm_favorite_rpm = getattr(self.coordinator.data, ATTR_FAVORITE_RPM, None)
692  self._motor_speed_motor_speed = min(
693  self._speed_range_speed_range[1],
694  max(
695  self._speed_range_speed_range[0],
696  getattr(self.coordinator.data, ATTR_MOTOR_SPEED, 0),
697  ),
698  )
699  self.async_write_ha_stateasync_write_ha_state()
700 
701 
703  """Representation of a Xiaomi Air Fresh."""
704 
705  SPEED_MODE_MAPPING = {
706  1: AirfreshOperationMode.Silent,
707  2: AirfreshOperationMode.Low,
708  3: AirfreshOperationMode.Middle,
709  4: AirfreshOperationMode.Strong,
710  }
711 
712  REVERSE_SPEED_MODE_MAPPING = {v: k for k, v in SPEED_MODE_MAPPING.items()}
713 
714  PRESET_MODE_MAPPING = {
715  "Auto": AirfreshOperationMode.Auto,
716  "Interval": AirfreshOperationMode.Interval,
717  }
718 
719  def __init__(self, device, entry, unique_id, coordinator):
720  """Initialize the miio device."""
721  super().__init__(device, entry, unique_id, coordinator)
722 
723  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRFRESH
724  self._available_attributes_available_attributes_available_attributes = AVAILABLE_ATTRIBUTES_AIRFRESH
725  self._speed_count_speed_count_speed_count = 4
726  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRFRESH
727  self._attr_supported_features_attr_supported_features = (
728  FanEntityFeature.SET_SPEED
729  | FanEntityFeature.PRESET_MODE
730  | FanEntityFeature.TURN_OFF
731  | FanEntityFeature.TURN_ON
732  )
733 
734  self._state_state_state_state = self.coordinator.data.is_on
735  self._state_attrs_state_attrs.update(
736  {
737  key: getattr(self.coordinator.data, value)
738  for key, value in self._available_attributes_available_attributes_available_attributes.items()
739  }
740  )
741  self._mode_mode_mode_mode = self.coordinator.data.mode.value
742 
743  @property
745  """Hold operation mode class."""
746  return AirfreshOperationMode
747 
748  @property
749  def percentage(self) -> int | None:
750  """Return the current percentage based speed."""
751  if self._state_state_state_state:
752  mode = AirfreshOperationMode(self._mode_mode_mode_mode)
753  if mode in self.REVERSE_SPEED_MODE_MAPPINGREVERSE_SPEED_MODE_MAPPING:
755  (1, self._speed_count_speed_count_speed_count), self.REVERSE_SPEED_MODE_MAPPINGREVERSE_SPEED_MODE_MAPPING[mode]
756  )
757 
758  return None
759 
760  async def async_set_percentage(self, percentage: int) -> None:
761  """Set the percentage of the fan.
762 
763  This method is a coroutine.
764  """
765  speed_mode = math.ceil(
766  percentage_to_ranged_value((1, self._speed_count_speed_count_speed_count), percentage)
767  )
768  if speed_mode:
769  if await self._try_command(
770  "Setting operation mode of the miio device failed.",
771  self._device.set_mode,
772  AirfreshOperationMode(self.SPEED_MODE_MAPPINGSPEED_MODE_MAPPING[speed_mode]),
773  ):
774  self._mode_mode_mode_mode = AirfreshOperationMode(
775  self.SPEED_MODE_MAPPINGSPEED_MODE_MAPPING[speed_mode]
776  ).value
777  self.async_write_ha_stateasync_write_ha_state()
778 
779  async def async_set_preset_mode(self, preset_mode: str) -> None:
780  """Set the preset mode of the fan.
781 
782  This method is a coroutine.
783  """
784  if await self._try_command(
785  "Setting operation mode of the miio device failed.",
786  self._device.set_mode,
787  self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode],
788  ):
789  self._mode_mode_mode_mode = self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode].value
790  self.async_write_ha_stateasync_write_ha_state()
791 
792  async def async_set_extra_features(self, features: int = 1):
793  """Set the extra features."""
794  if self._device_features_device_features_device_features & FEATURE_SET_EXTRA_FEATURES == 0:
795  return
796 
797  await self._try_command(
798  "Setting the extra features of the miio device failed.",
799  self._device.set_extra_features,
800  features,
801  )
802 
803  async def async_reset_filter(self):
804  """Reset the filter lifetime and usage."""
805  if self._device_features_device_features_device_features & FEATURE_RESET_FILTER == 0:
806  return
807 
808  await self._try_command(
809  "Resetting the filter lifetime of the miio device failed.",
810  self._device.reset_filter,
811  )
812 
813 
815  """Representation of a Xiaomi Air Fresh A1."""
816 
817  def __init__(self, device, entry, unique_id, coordinator):
818  """Initialize the miio device."""
819  super().__init__(device, entry, unique_id, coordinator)
820  self._favorite_speed_favorite_speed = None
821  self._device_features_device_features_device_features = FEATURE_FLAGS_AIRFRESH_A1
822  self._preset_modes_preset_modes_preset_modes = PRESET_MODES_AIRFRESH_A1
823  self._attr_supported_features_attr_supported_features = (
824  FanEntityFeature.SET_SPEED
825  | FanEntityFeature.PRESET_MODE
826  | FanEntityFeature.TURN_OFF
827  | FanEntityFeature.TURN_ON
828  )
829 
830  self._state_state_state_state = self.coordinator.data.is_on
831  self._mode_mode_mode_mode = self.coordinator.data.mode.value
832  self._speed_range_speed_range = (60, 150)
833 
834  @property
836  """Hold operation mode class."""
837  return AirfreshOperationModeT2017
838 
839  @property
840  def percentage(self) -> int | None:
841  """Return the current percentage based speed."""
842  if self._favorite_speed_favorite_speed is None:
843  return None
844  if self._state_state_state_state:
845  return ranged_value_to_percentage(self._speed_range_speed_range, self._favorite_speed_favorite_speed)
846 
847  return None
848 
849  async def async_set_percentage(self, percentage: int) -> None:
850  """Set the percentage of the fan. This method is a coroutine."""
851  if percentage == 0:
852  await self.async_turn_offasync_turn_offasync_turn_off()
853  return
854 
855  await self.async_set_preset_modeasync_set_preset_modeasync_set_preset_mode("Favorite")
856 
857  favorite_speed = math.ceil(
858  percentage_to_ranged_value(self._speed_range_speed_range, percentage)
859  )
860  if not favorite_speed:
861  return
862  if await self._try_command(
863  "Setting fan level of the miio device failed.",
864  self._device.set_favorite_speed,
865  favorite_speed,
866  ):
867  self._favorite_speed_favorite_speed = favorite_speed
868  self.async_write_ha_stateasync_write_ha_state()
869 
870  async def async_set_preset_mode(self, preset_mode: str) -> None:
871  """Set the preset mode of the fan. This method is a coroutine."""
872  if await self._try_command(
873  "Setting operation mode of the miio device failed.",
874  self._device.set_mode,
875  self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode],
876  ):
877  self._mode_mode_mode_mode = self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode].value
878  self.async_write_ha_stateasync_write_ha_state()
879 
880  @callback
882  """Fetch state from the device."""
883  self._state_state_state_state = self.coordinator.data.is_on
884  self._mode_mode_mode_mode = self.coordinator.data.mode.value
885  self._favorite_speed_favorite_speed = getattr(self.coordinator.data, ATTR_FAVORITE_SPEED, None)
886  self.async_write_ha_stateasync_write_ha_state()
887 
888 
890  """Representation of a Xiaomi Air Fresh T2017."""
891 
892  def __init__(self, device, entry, unique_id, coordinator):
893  """Initialize the miio device."""
894  super().__init__(device, entry, unique_id, coordinator)
895  self._device_features_device_features_device_features_device_features = FEATURE_FLAGS_AIRFRESH_T2017
896  self._speed_range_speed_range_speed_range = (60, 300)
897 
898 
900  """Representation of a generic Xiaomi Fan."""
901 
902  _attr_translation_key = "generic_fan"
903 
904  def __init__(self, device, entry, unique_id, coordinator):
905  """Initialize the fan."""
906  super().__init__(device, entry, unique_id, coordinator)
907 
908  if self._model_model == MODEL_FAN_P5:
909  self._device_features_device_features_device_features = FEATURE_FLAGS_FAN_P5
910  elif self._model_model == MODEL_FAN_ZA5:
911  self._device_features_device_features_device_features = FEATURE_FLAGS_FAN_ZA5
912  elif self._model_model == MODEL_FAN_1C:
913  self._device_features_device_features_device_features = FEATURE_FLAGS_FAN_1C
914  elif self._model_model == MODEL_FAN_P9:
915  self._device_features_device_features_device_features = FEATURE_FLAGS_FAN_P9
916  elif self._model_model in (MODEL_FAN_P10, MODEL_FAN_P11, MODEL_FAN_P18):
917  self._device_features_device_features_device_features = FEATURE_FLAGS_FAN_P10_P11_P18
918  else:
919  self._device_features_device_features_device_features = FEATURE_FLAGS_FAN
920  self._attr_supported_features_attr_supported_features = (
921  FanEntityFeature.SET_SPEED
922  | FanEntityFeature.OSCILLATE
923  | FanEntityFeature.PRESET_MODE
924  | FanEntityFeature.TURN_OFF
925  | FanEntityFeature.TURN_ON
926  )
927  if self._model_model != MODEL_FAN_1C:
928  self._attr_supported_features_attr_supported_features |= FanEntityFeature.DIRECTION
929  self._preset_mode_preset_mode = None
930  self._oscillating_oscillating = None
931  self._percentage_percentage = None
932 
933  @property
934  def preset_mode(self) -> str | None:
935  """Get the active preset mode."""
936  return self._preset_mode_preset_mode
937 
938  @property
939  def preset_modes(self) -> list[str]:
940  """Get the list of available preset modes."""
941  return [mode.name for mode in self.operation_mode_classoperation_mode_class]
942 
943  @property
944  def percentage(self) -> int | None:
945  """Return the current speed as a percentage."""
946  if self._state_state:
947  return self._percentage_percentage
948 
949  return None
950 
951  @property
952  def oscillating(self) -> bool | None:
953  """Return whether or not the fan is currently oscillating."""
954  return self._oscillating_oscillating
955 
956  async def async_oscillate(self, oscillating: bool) -> None:
957  """Set oscillation."""
958  await self._try_command(
959  "Setting oscillate on/off of the miio device failed.",
960  self._device.set_oscillate,
961  oscillating,
962  )
963  self._oscillating_oscillating = oscillating
964  self.async_write_ha_stateasync_write_ha_state()
965 
966  async def async_set_direction(self, direction: str) -> None:
967  """Set the direction of the fan."""
968  if self._oscillating_oscillating:
969  await self.async_oscillateasync_oscillateasync_oscillate(oscillating=False)
970 
971  await self._try_command(
972  "Setting move direction of the miio device failed.",
973  self._device.set_rotate,
974  FanMoveDirection(FAN_DIRECTIONS_MAP[direction]),
975  )
976 
977 
979  """Representation of a Xiaomi Fan."""
980 
981  def __init__(self, device, entry, unique_id, coordinator):
982  """Initialize the fan."""
983  super().__init__(device, entry, unique_id, coordinator)
984 
985  self._state_state_state = self.coordinator.data.is_on
986  self._oscillating_oscillating_oscillating = self.coordinator.data.oscillate
987  self._nature_mode_nature_mode = self.coordinator.data.natural_speed != 0
988  if self._nature_mode_nature_mode:
989  self._percentage_percentage_percentage = self.coordinator.data.natural_speed
990  else:
991  self._percentage_percentage_percentage = self.coordinator.data.direct_speed
992 
993  @property
995  """Hold operation mode class."""
996 
997  @property
998  def preset_mode(self) -> str:
999  """Get the active preset mode."""
1000  return ATTR_MODE_NATURE if self._nature_mode_nature_mode else ATTR_MODE_NORMAL
1001 
1002  @property
1003  def preset_modes(self) -> list[str]:
1004  """Get the list of available preset modes."""
1005  return [ATTR_MODE_NATURE, ATTR_MODE_NORMAL]
1006 
1007  @callback
1009  """Fetch state from the device."""
1010  self._state_state_state = self.coordinator.data.is_on
1011  self._oscillating_oscillating_oscillating = self.coordinator.data.oscillate
1012  self._nature_mode_nature_mode = self.coordinator.data.natural_speed != 0
1013  if self._nature_mode_nature_mode:
1014  self._percentage_percentage_percentage = self.coordinator.data.natural_speed
1015  else:
1016  self._percentage_percentage_percentage = self.coordinator.data.direct_speed
1017 
1018  self.async_write_ha_stateasync_write_ha_state()
1019 
1020  async def async_set_preset_mode(self, preset_mode: str) -> None:
1021  """Set the preset mode of the fan."""
1022  if preset_mode == ATTR_MODE_NATURE:
1023  await self._try_command(
1024  "Setting natural fan speed percentage of the miio device failed.",
1025  self._device.set_natural_speed,
1026  self._percentage_percentage_percentage,
1027  )
1028  else:
1029  await self._try_command(
1030  "Setting direct fan speed percentage of the miio device failed.",
1031  self._device.set_direct_speed,
1032  self._percentage_percentage_percentage,
1033  )
1034 
1035  self._preset_mode_preset_mode_preset_mode = preset_mode
1036  self.async_write_ha_stateasync_write_ha_state()
1037 
1038  async def async_set_percentage(self, percentage: int) -> None:
1039  """Set the percentage of the fan."""
1040  if percentage == 0:
1041  self._percentage_percentage_percentage = 0
1042  await self.async_turn_offasync_turn_offasync_turn_off()
1043  return
1044 
1045  if self._nature_mode_nature_mode:
1046  await self._try_command(
1047  "Setting fan speed percentage of the miio device failed.",
1048  self._device.set_natural_speed,
1049  percentage,
1050  )
1051  else:
1052  await self._try_command(
1053  "Setting fan speed percentage of the miio device failed.",
1054  self._device.set_direct_speed,
1055  percentage,
1056  )
1057  self._percentage_percentage_percentage = percentage
1058 
1059  if not self.is_onis_onis_onis_on:
1060  await self.async_turn_onasync_turn_onasync_turn_onasync_turn_on()
1061  else:
1062  self.async_write_ha_stateasync_write_ha_state()
1063 
1064 
1066  """Representation of a Xiaomi Fan P5."""
1067 
1068  def __init__(self, device, entry, unique_id, coordinator):
1069  """Initialize the fan."""
1070  super().__init__(device, entry, unique_id, coordinator)
1071 
1072  self._state_state_state = self.coordinator.data.is_on
1073  self._preset_mode_preset_mode_preset_mode = self.coordinator.data.mode.name
1074  self._oscillating_oscillating_oscillating = self.coordinator.data.oscillate
1075  self._percentage_percentage_percentage = self.coordinator.data.speed
1076 
1077  @property
1079  """Hold operation mode class."""
1080  return FanOperationMode
1081 
1082  @callback
1084  """Fetch state from the device."""
1085  self._state_state_state = self.coordinator.data.is_on
1086  self._preset_mode_preset_mode_preset_mode = self.coordinator.data.mode.name
1087  self._oscillating_oscillating_oscillating = self.coordinator.data.oscillate
1088  self._percentage_percentage_percentage = self.coordinator.data.speed
1089 
1090  self.async_write_ha_stateasync_write_ha_state()
1091 
1092  async def async_set_preset_mode(self, preset_mode: str) -> None:
1093  """Set the preset mode of the fan."""
1094  await self._try_command(
1095  "Setting operation mode of the miio device failed.",
1096  self._device.set_mode,
1097  self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode],
1098  )
1099  self._preset_mode_preset_mode_preset_mode = preset_mode
1100  self.async_write_ha_stateasync_write_ha_state()
1101 
1102  async def async_set_percentage(self, percentage: int) -> None:
1103  """Set the percentage of the fan."""
1104  if percentage == 0:
1105  self._percentage_percentage_percentage = 0
1106  await self.async_turn_offasync_turn_offasync_turn_off()
1107  return
1108 
1109  await self._try_command(
1110  "Setting fan speed percentage of the miio device failed.",
1111  self._device.set_speed,
1112  percentage,
1113  )
1114  self._percentage_percentage_percentage = percentage
1115 
1116  if not self.is_onis_onis_onis_on:
1117  await self.async_turn_onasync_turn_onasync_turn_onasync_turn_on()
1118  else:
1119  self.async_write_ha_stateasync_write_ha_state()
1120 
1121 
1123  """Representation of a Xiaomi Fan Miot."""
1124 
1125  @property
1127  """Hold operation mode class."""
1128  return FanOperationMode
1129 
1130  @property
1131  def preset_mode(self) -> str | None:
1132  """Get the active preset mode."""
1133  return self._preset_mode_preset_mode_preset_mode
1134 
1135  @callback
1137  """Fetch state from the device."""
1138  self._state_state_state = self.coordinator.data.is_on
1139  self._preset_mode_preset_mode_preset_mode = self.coordinator.data.mode.name
1140  self._oscillating_oscillating_oscillating = self.coordinator.data.oscillate
1141  if self.coordinator.data.is_on:
1142  self._percentage_percentage_percentage = self.coordinator.data.speed
1143  else:
1144  self._percentage_percentage_percentage = 0
1145 
1146  self.async_write_ha_stateasync_write_ha_state()
1147 
1148  async def async_set_preset_mode(self, preset_mode: str) -> None:
1149  """Set the preset mode of the fan."""
1150  await self._try_command(
1151  "Setting operation mode of the miio device failed.",
1152  self._device.set_mode,
1153  self.operation_mode_classoperation_mode_classoperation_mode_class[preset_mode],
1154  )
1155  self._preset_mode_preset_mode_preset_mode = preset_mode
1156  self.async_write_ha_stateasync_write_ha_state()
1157 
1158  async def async_set_percentage(self, percentage: int) -> None:
1159  """Set the percentage of the fan."""
1160  if percentage == 0:
1161  self._percentage_percentage_percentage = 0
1162  await self.async_turn_offasync_turn_offasync_turn_off()
1163  return
1164 
1165  result = await self._try_command(
1166  "Setting fan speed percentage of the miio device failed.",
1167  self._device.set_speed,
1168  percentage,
1169  )
1170  if result:
1171  self._percentage_percentage_percentage = percentage
1172 
1173  if not self.is_onis_onis_onis_on:
1174  await self.async_turn_onasync_turn_onasync_turn_onasync_turn_on()
1175  elif result:
1176  self.async_write_ha_stateasync_write_ha_state()
1177 
1178 
1180  """Representation of a Xiaomi Fan ZA5."""
1181 
1182  @property
1184  """Hold operation mode class."""
1185  return FanZA5OperationMode
1186 
1187 
1189  """Representation of a Xiaomi Fan 1C (Standing Fan 2 Lite)."""
1190 
1191  def __init__(self, device, entry, unique_id, coordinator):
1192  """Initialize MIOT fan with speed count."""
1193  super().__init__(device, entry, unique_id, coordinator)
1194  self._speed_count_speed_count = 3
1195 
1196  @callback
1198  """Fetch state from the device."""
1199  self._state_state_state_state = self.coordinator.data.is_on
1200  self._preset_mode_preset_mode_preset_mode_preset_mode = self.coordinator.data.mode.name
1201  self._oscillating_oscillating_oscillating_oscillating = self.coordinator.data.oscillate
1202  if self.coordinator.data.is_on:
1204  (1, self._speed_count_speed_count), self.coordinator.data.speed
1205  )
1206  else:
1207  self._percentage_percentage_percentage_percentage = 0
1208 
1209  self.async_write_ha_stateasync_write_ha_state()
1210 
1211  async def async_set_percentage(self, percentage: int) -> None:
1212  """Set the percentage of the fan."""
1213  if percentage == 0:
1214  self._percentage_percentage_percentage_percentage = 0
1215  await self.async_turn_offasync_turn_offasync_turn_off()
1216  return
1217 
1218  speed = math.ceil(
1219  percentage_to_ranged_value((1, self._speed_count_speed_count), percentage)
1220  )
1221 
1222  # if the fan is not on, we have to turn it on first
1223  if not self.is_onis_onis_onis_on:
1224  await self.async_turn_onasync_turn_onasync_turn_onasync_turn_on()
1225 
1226  result = await self._try_command(
1227  "Setting fan speed percentage of the miio device failed.",
1228  self._device.set_speed,
1229  speed,
1230  )
1231 
1232  if result:
1233  self._percentage_percentage_percentage_percentage = ranged_value_to_percentage((1, self._speed_count_speed_count), speed)
1234  self.async_write_ha_stateasync_write_ha_state()
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: __init__.py:437
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_oscillate(self, bool oscillating)
Definition: __init__.py:452
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:870
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:817
None async_set_percentage(self, int percentage)
Definition: fan.py:849
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:892
def async_set_extra_features(self, int features=1)
Definition: fan.py:792
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:719
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:779
None async_set_percentage(self, int percentage)
Definition: fan.py:760
None __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:616
def async_set_extra_features(self, int features=1)
Definition: fan.py:552
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:424
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:539
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:1191
None async_set_percentage(self, int percentage)
Definition: fan.py:1211
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:1148
None async_set_percentage(self, int percentage)
Definition: fan.py:1158
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:1068
None async_set_percentage(self, int percentage)
Definition: fan.py:1102
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:1092
None async_set_preset_mode(self, str preset_mode)
Definition: fan.py:1020
None async_set_percentage(self, int percentage)
Definition: fan.py:1038
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:981
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:377
None async_turn_on(self, int|None percentage=None, str|None preset_mode=None, **Any kwargs)
Definition: fan.py:347
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:305
None async_oscillate(self, bool oscillating)
Definition: fan.py:956
def __init__(self, device, entry, unique_id, coordinator)
Definition: fan.py:904
None async_turn_off(self, **Any kwargs)
Definition: entity.py:1709
None async_turn_on(self, **Any kwargs)
Definition: entity.py:1701
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: fan.py:209
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