Initial xiaomi_miio support for dmaker.airfresh.a1/t2017 (#66331)

* Initial support for dmaker.airfresh.a1/t2017

* fix typo
This commit is contained in:
Igor Pakhomov 2022-02-12 00:28:22 +02:00 committed by GitHub
parent cc0fb5d9db
commit 2ffb46dc93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 2 deletions

View file

@ -8,6 +8,8 @@ import logging
import async_timeout
from miio import (
AirFresh,
AirFreshA1,
AirFreshT2017,
AirHumidifier,
AirHumidifierMiot,
AirHumidifierMjjsq,
@ -48,6 +50,8 @@ from .const import (
DOMAIN,
KEY_COORDINATOR,
KEY_DEVICE,
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_T2017,
MODEL_AIRPURIFIER_3C,
MODEL_FAN_1C,
MODEL_FAN_P5,
@ -310,7 +314,7 @@ async def async_create_miio_device_and_coordinator(
device = AirHumidifier(host, token, model=model)
migrate = True
# Airpurifiers and Airfresh
elif model in MODEL_AIRPURIFIER_3C:
elif model == MODEL_AIRPURIFIER_3C:
device = AirPurifierMB4(host, token)
elif model in MODELS_PURIFIER_MIOT:
device = AirPurifierMiot(host, token)
@ -318,6 +322,10 @@ async def async_create_miio_device_and_coordinator(
device = AirPurifier(host, token)
elif model.startswith("zhimi.airfresh."):
device = AirFresh(host, token)
elif model == MODEL_AIRFRESH_A1:
device = AirFreshA1(host, token)
elif model == MODEL_AIRFRESH_T2017:
device = AirFreshT2017(host, token)
elif (
model in MODELS_VACUUM
or model.startswith(ROBOROCK_GENERIC)

View file

@ -75,7 +75,9 @@ MODEL_AIRHUMIDIFIER_JSQ = "deerma.humidifier.jsq"
MODEL_AIRHUMIDIFIER_JSQ1 = "deerma.humidifier.jsq1"
MODEL_AIRHUMIDIFIER_MJJSQ = "deerma.humidifier.mjjsq"
MODEL_AIRFRESH_A1 = "dmaker.airfresh.a1"
MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2"
MODEL_AIRFRESH_T2017 = "dmaker.airfresh.t2017"
MODEL_FAN_1C = "dmaker.fan.1c"
MODEL_FAN_P10 = "dmaker.fan.p10"
@ -129,7 +131,9 @@ MODELS_PURIFIER_MIIO = [
MODEL_AIRPURIFIER_SA2,
MODEL_AIRPURIFIER_2S,
MODEL_AIRPURIFIER_2H,
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_VA2,
MODEL_AIRFRESH_T2017,
]
MODELS_HUMIDIFIER_MIIO = [
MODEL_AIRHUMIDIFIER_V1,
@ -383,6 +387,8 @@ FEATURE_FLAGS_AIRHUMIDIFIER_CA4 = (
| FEATURE_SET_CLEAN
)
FEATURE_FLAGS_AIRFRESH_A1 = FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK
FEATURE_FLAGS_AIRFRESH = (
FEATURE_SET_BUZZER
| FEATURE_SET_CHILD_LOCK
@ -392,6 +398,8 @@ FEATURE_FLAGS_AIRFRESH = (
| FEATURE_SET_EXTRA_FEATURES
)
FEATURE_FLAGS_AIRFRESH_T2017 = FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK
FEATURE_FLAGS_FAN_P5 = (
FEATURE_SET_BUZZER
| FEATURE_SET_CHILD_LOCK

View file

@ -5,6 +5,7 @@ import logging
import math
from miio.airfresh import OperationMode as AirfreshOperationMode
from miio.airfresh_t2017 import OperationMode as AirfreshOperationModeT2017
from miio.airpurifier import OperationMode as AirpurifierOperationMode
from miio.airpurifier_miot import OperationMode as AirpurifierMiotOperationMode
from miio.fan import (
@ -39,6 +40,8 @@ from .const import (
CONF_FLOW_TYPE,
DOMAIN,
FEATURE_FLAGS_AIRFRESH,
FEATURE_FLAGS_AIRFRESH_A1,
FEATURE_FLAGS_AIRFRESH_T2017,
FEATURE_FLAGS_AIRPURIFIER_2S,
FEATURE_FLAGS_AIRPURIFIER_3C,
FEATURE_FLAGS_AIRPURIFIER_MIIO,
@ -56,6 +59,8 @@ from .const import (
FEATURE_SET_EXTRA_FEATURES,
KEY_COORDINATOR,
KEY_DEVICE,
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_T2017,
MODEL_AIRPURIFIER_2H,
MODEL_AIRPURIFIER_2S,
MODEL_AIRPURIFIER_3C,
@ -97,6 +102,9 @@ ATTR_SLEEP_MODE = "sleep_mode"
ATTR_USE_TIME = "use_time"
ATTR_BUTTON_PRESSED = "button_pressed"
# Air Fresh A1
ATTR_FAVORITE_SPEED = "favorite_speed"
# Map attributes to properties of the state object
AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON = {
ATTR_EXTRA_FEATURES: "extra_features",
@ -153,6 +161,7 @@ PRESET_MODES_AIRPURIFIER_V3 = [
"Strong",
]
PRESET_MODES_AIRFRESH = ["Auto", "Interval"]
PRESET_MODES_AIRFRESH_A1 = ["Auto", "Sleep", "Favorite"]
AIRPURIFIER_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids})
@ -213,6 +222,10 @@ async def async_setup_entry(
entity = XiaomiAirPurifier(name, device, config_entry, unique_id, coordinator)
elif model.startswith("zhimi.airfresh."):
entity = XiaomiAirFresh(name, device, config_entry, unique_id, coordinator)
elif model == MODEL_AIRFRESH_A1:
entity = XiaomiAirFreshA1(name, device, config_entry, unique_id, coordinator)
elif model == MODEL_AIRFRESH_T2017:
entity = XiaomiAirFreshT2017(name, device, config_entry, unique_id, coordinator)
elif model == MODEL_FAN_P5:
entity = XiaomiFanP5(name, device, config_entry, unique_id, coordinator)
elif model in MODELS_FAN_MIIO:
@ -709,6 +722,89 @@ class XiaomiAirFresh(XiaomiGenericAirPurifier):
)
class XiaomiAirFreshA1(XiaomiGenericAirPurifier):
"""Representation of a Xiaomi Air Fresh A1."""
def __init__(self, name, device, entry, unique_id, coordinator):
"""Initialize the miio device."""
super().__init__(name, device, entry, unique_id, coordinator)
self._favorite_speed = None
self._device_features = FEATURE_FLAGS_AIRFRESH_A1
self._preset_modes = PRESET_MODES_AIRFRESH_A1
self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE
self._state = self.coordinator.data.is_on
self._mode = self.coordinator.data.mode.value
self._speed_range = (60, 150)
@property
def operation_mode_class(self):
"""Hold operation mode class."""
return AirfreshOperationModeT2017
@property
def percentage(self):
"""Return the current percentage based speed."""
if self._favorite_speed is None:
return None
if self._state:
return ranged_value_to_percentage(self._speed_range, self._favorite_speed)
return None
async def async_set_percentage(self, percentage: int) -> None:
"""Set the percentage of the fan. This method is a coroutine."""
if percentage == 0:
await self.async_turn_off()
return
await self.async_set_preset_mode("Favorite")
favorite_speed = math.ceil(
percentage_to_ranged_value(self._speed_range, percentage)
)
if not favorite_speed:
return
if await self._try_command(
"Setting fan level of the miio device failed.",
self._device.set_favorite_speed,
favorite_speed,
):
self._favorite_speed = favorite_speed
self.async_write_ha_state()
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan. This method is a coroutine."""
if preset_mode not in self.preset_modes:
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
return
if await self._try_command(
"Setting operation mode of the miio device failed.",
self._device.set_mode,
self.operation_mode_class[preset_mode],
):
self._mode = self.operation_mode_class[preset_mode].value
self.async_write_ha_state()
@callback
def _handle_coordinator_update(self):
"""Fetch state from the device."""
self._state = self.coordinator.data.is_on
self._mode = self.coordinator.data.mode.value
self._favorite_speed = getattr(self.coordinator.data, ATTR_FAVORITE_SPEED, None)
self.async_write_ha_state()
class XiaomiAirFreshT2017(XiaomiAirFreshA1):
"""Representation of a Xiaomi Air Fresh T2017."""
def __init__(self, name, device, entry, unique_id, coordinator):
"""Initialize the miio device."""
super().__init__(name, device, entry, unique_id, coordinator)
self._device_features = FEATURE_FLAGS_AIRFRESH_T2017
self._speed_range = (60, 300)
class XiaomiGenericFan(XiaomiGenericDevice):
"""Representation of a generic Xiaomi Fan."""

View file

@ -18,6 +18,8 @@ from .const import (
CONF_MODEL,
DOMAIN,
FEATURE_FLAGS_AIRFRESH,
FEATURE_FLAGS_AIRFRESH_A1,
FEATURE_FLAGS_AIRFRESH_T2017,
FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
FEATURE_FLAGS_AIRPURIFIER_2S,
@ -45,6 +47,8 @@ from .const import (
FEATURE_SET_VOLUME,
KEY_COORDINATOR,
KEY_DEVICE,
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_T2017,
MODEL_AIRFRESH_VA2,
MODEL_AIRHUMIDIFIER_CA1,
MODEL_AIRHUMIDIFIER_CA4,
@ -199,7 +203,9 @@ NUMBER_TYPES = {
}
MODEL_TO_FEATURES_MAP = {
MODEL_AIRFRESH_A1: FEATURE_FLAGS_AIRFRESH_A1,
MODEL_AIRFRESH_VA2: FEATURE_FLAGS_AIRFRESH,
MODEL_AIRFRESH_T2017: FEATURE_FLAGS_AIRFRESH_T2017,
MODEL_AIRHUMIDIFIER_CA1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
MODEL_AIRHUMIDIFIER_CA4: FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
MODEL_AIRHUMIDIFIER_CB1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,

View file

@ -52,6 +52,8 @@ from .const import (
DOMAIN,
KEY_COORDINATOR,
KEY_DEVICE,
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_T2017,
MODEL_AIRFRESH_VA2,
MODEL_AIRHUMIDIFIER_CA1,
MODEL_AIRHUMIDIFIER_CB1,
@ -375,6 +377,14 @@ AIRFRESH_SENSORS = (
ATTR_TEMPERATURE,
ATTR_USE_TIME,
)
AIRFRESH_SENSORS_A1 = (
ATTR_CARBON_DIOXIDE,
ATTR_TEMPERATURE,
)
AIRFRESH_SENSORS_T2017 = (
ATTR_CARBON_DIOXIDE,
ATTR_TEMPERATURE,
)
FAN_V2_V3_SENSORS = (
ATTR_BATTERY,
ATTR_HUMIDITY,
@ -384,7 +394,9 @@ FAN_V2_V3_SENSORS = (
FAN_ZA5_SENSORS = (ATTR_HUMIDITY, ATTR_TEMPERATURE)
MODEL_TO_SENSORS_MAP = {
MODEL_AIRFRESH_A1: AIRFRESH_SENSORS_A1,
MODEL_AIRFRESH_VA2: AIRFRESH_SENSORS,
MODEL_AIRFRESH_T2017: AIRFRESH_SENSORS_T2017,
MODEL_AIRHUMIDIFIER_CA1: HUMIDIFIER_CA1_CB1_SENSORS,
MODEL_AIRHUMIDIFIER_CB1: HUMIDIFIER_CA1_CB1_SENSORS,
MODEL_AIRPURIFIER_3C: PURIFIER_3C_SENSORS,
@ -808,7 +820,6 @@ class XiaomiGatewayIlluminanceSensor(SensorEntity):
def __init__(self, gateway_device, gateway_name, gateway_device_id, description):
"""Initialize the entity."""
self._attr_name = f"{gateway_name} {description.name}"
self._attr_unique_id = f"{gateway_device_id}-{description.key}"
self._attr_device_info = {"identifiers": {(DOMAIN, gateway_device_id)}}

View file

@ -35,6 +35,8 @@ from .const import (
CONF_MODEL,
DOMAIN,
FEATURE_FLAGS_AIRFRESH,
FEATURE_FLAGS_AIRFRESH_A1,
FEATURE_FLAGS_AIRFRESH_T2017,
FEATURE_FLAGS_AIRHUMIDIFIER,
FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
@ -63,6 +65,8 @@ from .const import (
FEATURE_SET_LED,
KEY_COORDINATOR,
KEY_DEVICE,
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_T2017,
MODEL_AIRFRESH_VA2,
MODEL_AIRHUMIDIFIER_CA1,
MODEL_AIRHUMIDIFIER_CA4,
@ -167,7 +171,9 @@ SERVICE_TO_METHOD = {
}
MODEL_TO_FEATURES_MAP = {
MODEL_AIRFRESH_A1: FEATURE_FLAGS_AIRFRESH_A1,
MODEL_AIRFRESH_VA2: FEATURE_FLAGS_AIRFRESH,
MODEL_AIRFRESH_T2017: FEATURE_FLAGS_AIRFRESH_T2017,
MODEL_AIRHUMIDIFIER_CA1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
MODEL_AIRHUMIDIFIER_CA4: FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
MODEL_AIRHUMIDIFIER_CB1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,