Add support for pedestal MIOT fans to Xiaomi Miio integration (#56555)

* Add initial support for Xiaomi Fan ZA5

* Add sensor, number and switch platform

* Addionizer switch

* Improve ionizer icon

* Fix parent of XiaomiFanMiot class

* Add another MIOT models

* Fix consts

* Add powersupply attached binary sensor

* Simplify async_create_miio_device_and_coordinator

* Simplify XiaomiGenericFan

* Fix XiaomiFanZA5 parent

* Remove pass

* Remove unused _available variable

* 1C doesn't support direction

* Suggested change

* Use elif

* Clean up oscillation angle

* Fix typo
This commit is contained in:
Maciej Bieniek 2021-09-28 10:21:14 +02:00 committed by GitHub
parent b179301152
commit 0044fa9fb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 291 additions and 33 deletions

View file

@ -11,6 +11,10 @@ from miio.fan import (
MoveDirection as FanMoveDirection,
OperationMode as FanOperationMode,
)
from miio.fan_miot import (
OperationMode as FanMiotOperationMode,
OperationModeFanZA5 as FanZA5OperationMode,
)
import voluptuous as vol
from homeassistant.components.fan import (
@ -41,7 +45,11 @@ from .const import (
FEATURE_FLAGS_AIRPURIFIER_PRO_V7,
FEATURE_FLAGS_AIRPURIFIER_V3,
FEATURE_FLAGS_FAN,
FEATURE_FLAGS_FAN_1C,
FEATURE_FLAGS_FAN_P5,
FEATURE_FLAGS_FAN_P9,
FEATURE_FLAGS_FAN_P10_P11,
FEATURE_FLAGS_FAN_ZA5,
FEATURE_RESET_FILTER,
FEATURE_SET_EXTRA_FEATURES,
KEY_COORDINATOR,
@ -52,8 +60,14 @@ from .const import (
MODEL_AIRPURIFIER_PRO,
MODEL_AIRPURIFIER_PRO_V7,
MODEL_AIRPURIFIER_V3,
MODEL_FAN_1C,
MODEL_FAN_P5,
MODEL_FAN_P9,
MODEL_FAN_P10,
MODEL_FAN_P11,
MODEL_FAN_ZA5,
MODELS_FAN_MIIO,
MODELS_FAN_MIOT,
MODELS_PURIFIER_MIOT,
SERVICE_RESET_FILTER,
SERVICE_SET_EXTRA_FEATURES,
@ -197,6 +211,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entity = XiaomiFanP5(name, device, config_entry, unique_id, coordinator)
elif model in MODELS_FAN_MIIO:
entity = XiaomiFan(name, device, config_entry, unique_id, coordinator)
elif model == MODEL_FAN_ZA5:
entity = XiaomiFanZA5(name, device, config_entry, unique_id, coordinator)
elif model in MODELS_FAN_MIOT:
entity = XiaomiFanMiot(name, device, config_entry, unique_id, coordinator)
else:
return
@ -264,6 +282,11 @@ class XiaomiGenericDevice(XiaomiCoordinatedMiioEntity, FanEntity):
"""Flag supported features."""
return self._supported_features
@property
@abstractmethod
def operation_mode_class(self):
"""Hold operation mode class."""
@property
def preset_modes(self) -> list:
"""Get the list of available preset modes."""
@ -326,11 +349,6 @@ class XiaomiGenericAirPurifier(XiaomiGenericDevice):
self._speed_count = 100
@property
@abstractmethod
def operation_mode_class(self):
"""Hold operation mode class."""
@property
def speed_count(self):
"""Return the number of speeds of the fan supported."""
@ -695,16 +713,21 @@ class XiaomiGenericFan(XiaomiGenericDevice):
if self._model == MODEL_FAN_P5:
self._device_features = FEATURE_FLAGS_FAN_P5
self._preset_modes = [mode.name for mode in FanOperationMode]
elif self._model == MODEL_FAN_ZA5:
self._device_features = FEATURE_FLAGS_FAN_ZA5
elif self._model == MODEL_FAN_1C:
self._device_features = FEATURE_FLAGS_FAN_1C
elif self._model == MODEL_FAN_P9:
self._device_features = FEATURE_FLAGS_FAN_P9
elif self._model in (MODEL_FAN_P10, MODEL_FAN_P11):
self._device_features = FEATURE_FLAGS_FAN_P10_P11
else:
self._device_features = FEATURE_FLAGS_FAN
self._preset_modes = [ATTR_MODE_NATURE, ATTR_MODE_NORMAL]
self._supported_features = (
SUPPORT_SET_SPEED
| SUPPORT_OSCILLATE
| SUPPORT_PRESET_MODE
| SUPPORT_DIRECTION
SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_PRESET_MODE
)
if self._model != MODEL_FAN_1C:
self._supported_features |= SUPPORT_DIRECTION
self._preset_mode = None
self._oscillating = None
self._percentage = None
@ -714,6 +737,11 @@ class XiaomiGenericFan(XiaomiGenericDevice):
"""Get the active preset mode."""
return self._preset_mode
@property
def preset_modes(self) -> list:
"""Get the list of available preset modes."""
return [mode.name for mode in self.operation_mode_class]
@property
def percentage(self):
"""Return the current speed as a percentage."""
@ -764,11 +792,20 @@ class XiaomiFan(XiaomiGenericFan):
else:
self._percentage = self.coordinator.data.direct_speed
@property
def operation_mode_class(self):
"""Hold operation mode class."""
@property
def preset_mode(self):
"""Get the active preset mode."""
return ATTR_MODE_NATURE if self._nature_mode else ATTR_MODE_NORMAL
@property
def preset_modes(self) -> list:
"""Get the list of available preset modes."""
return [ATTR_MODE_NATURE, ATTR_MODE_NORMAL]
@callback
def _handle_coordinator_update(self):
"""Fetch state from the device."""
@ -843,6 +880,11 @@ class XiaomiFanP5(XiaomiGenericFan):
self._oscillating = self.coordinator.data.oscillate
self._percentage = self.coordinator.data.speed
@property
def operation_mode_class(self):
"""Hold operation mode class."""
return FanOperationMode
@callback
def _handle_coordinator_update(self):
"""Fetch state from the device."""
@ -861,7 +903,7 @@ class XiaomiFanP5(XiaomiGenericFan):
await self._try_command(
"Setting operation mode of the miio device failed.",
self._device.set_mode,
FanOperationMode[preset_mode],
self.operation_mode_class[preset_mode],
)
self._preset_mode = preset_mode
self.async_write_ha_state()
@ -884,3 +926,71 @@ class XiaomiFanP5(XiaomiGenericFan):
await self.async_turn_on()
else:
self.async_write_ha_state()
class XiaomiFanMiot(XiaomiGenericFan):
"""Representation of a Xiaomi Fan Miot."""
@property
def operation_mode_class(self):
"""Hold operation mode class."""
return FanMiotOperationMode
@property
def preset_mode(self):
"""Get the active preset mode."""
return self._preset_mode
@callback
def _handle_coordinator_update(self):
"""Fetch state from the device."""
self._state = self.coordinator.data.is_on
self._preset_mode = self.coordinator.data.mode.name
self._oscillating = self.coordinator.data.oscillate
if self.coordinator.data.is_on:
self._percentage = self.coordinator.data.fan_speed
else:
self._percentage = 0
self.async_write_ha_state()
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan."""
if preset_mode not in self.preset_modes:
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
return
await self._try_command(
"Setting operation mode of the miio device failed.",
self._device.set_mode,
self.operation_mode_class[preset_mode],
)
self._preset_mode = preset_mode
self.async_write_ha_state()
async def async_set_percentage(self, percentage: int) -> None:
"""Set the percentage of the fan."""
if percentage == 0:
self._percentage = 0
await self.async_turn_off()
return
await self._try_command(
"Setting fan speed percentage of the miio device failed.",
self._device.set_speed,
percentage,
)
self._percentage = percentage
if not self.is_on:
await self.async_turn_on()
else:
self.async_write_ha_state()
class XiaomiFanZA5(XiaomiFanMiot):
"""Representation of a Xiaomi Fan ZA5."""
@property
def operation_mode_class(self):
"""Hold operation mode class."""
return FanZA5OperationMode