Xiaomi_Miio Humidifier rework (#52366)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com> Co-authored-by: Teemu R. <tpr@iki.fi> Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
f3e7fb5798
commit
781015fb19
10 changed files with 1379 additions and 527 deletions
|
@ -1205,8 +1205,11 @@ omit =
|
|||
homeassistant/components/xiaomi_miio/device_tracker.py
|
||||
homeassistant/components/xiaomi_miio/fan.py
|
||||
homeassistant/components/xiaomi_miio/gateway.py
|
||||
homeassistant/components/xiaomi_miio/humidifier.py
|
||||
homeassistant/components/xiaomi_miio/light.py
|
||||
homeassistant/components/xiaomi_miio/number.py
|
||||
homeassistant/components/xiaomi_miio/remote.py
|
||||
homeassistant/components/xiaomi_miio/select.py
|
||||
homeassistant/components/xiaomi_miio/sensor.py
|
||||
homeassistant/components/xiaomi_miio/switch.py
|
||||
homeassistant/components/xiaomi_miio/vacuum.py
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import async_timeout
|
||||
from miio import AirHumidifier, AirHumidifierMiot, DeviceException
|
||||
from miio.gateway.gateway import GatewayException
|
||||
|
||||
from homeassistant import config_entries, core
|
||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
ATTR_AVAILABLE,
|
||||
|
@ -17,8 +20,12 @@ from .const import (
|
|||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
KEY_COORDINATOR,
|
||||
KEY_DEVICE,
|
||||
KEY_MIGRATE_ENTITY_NAME,
|
||||
MODELS_AIR_MONITOR,
|
||||
MODELS_FAN,
|
||||
MODELS_HUMIDIFIER,
|
||||
MODELS_HUMIDIFIER_MIOT,
|
||||
MODELS_LIGHT,
|
||||
MODELS_SWITCH,
|
||||
MODELS_VACUUM,
|
||||
|
@ -30,6 +37,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
GATEWAY_PLATFORMS = ["alarm_control_panel", "light", "sensor", "switch"]
|
||||
SWITCH_PLATFORMS = ["switch"]
|
||||
FAN_PLATFORMS = ["fan"]
|
||||
HUMIDIFIER_PLATFORMS = ["humidifier", "number", "select", "sensor", "switch"]
|
||||
LIGHT_PLATFORMS = ["light"]
|
||||
VACUUM_PLATFORMS = ["vacuum"]
|
||||
AIR_MONITOR_PLATFORMS = ["air_quality", "sensor"]
|
||||
|
@ -51,6 +59,7 @@ async def async_setup_entry(
|
|||
)
|
||||
|
||||
|
||||
@callback
|
||||
def get_platforms(config_entry):
|
||||
"""Return the platforms belonging to a config_entry."""
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
|
@ -61,6 +70,8 @@ def get_platforms(config_entry):
|
|||
if flow_type == CONF_DEVICE:
|
||||
if model in MODELS_SWITCH:
|
||||
return SWITCH_PLATFORMS
|
||||
if model in MODELS_HUMIDIFIER:
|
||||
return HUMIDIFIER_PLATFORMS
|
||||
if model in MODELS_FAN:
|
||||
return FAN_PLATFORMS
|
||||
if model in MODELS_LIGHT:
|
||||
|
@ -71,10 +82,70 @@ def get_platforms(config_entry):
|
|||
for air_monitor_model in MODELS_AIR_MONITOR:
|
||||
if model.startswith(air_monitor_model):
|
||||
return AIR_MONITOR_PLATFORMS
|
||||
|
||||
_LOGGER.error(
|
||||
"Unsupported device found! Please create an issue at "
|
||||
"https://github.com/syssi/xiaomi_airpurifier/issues "
|
||||
"and provide the following data: %s",
|
||||
model,
|
||||
)
|
||||
return []
|
||||
|
||||
|
||||
async def async_create_miio_device_and_coordinator(
|
||||
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
|
||||
):
|
||||
"""Set up a data coordinator and one miio device to service multiple entities."""
|
||||
model = entry.data[CONF_MODEL]
|
||||
host = entry.data[CONF_HOST]
|
||||
token = entry.data[CONF_TOKEN]
|
||||
name = entry.title
|
||||
device = None
|
||||
migrate_entity_name = None
|
||||
|
||||
if model not in MODELS_HUMIDIFIER:
|
||||
return
|
||||
if model in MODELS_HUMIDIFIER_MIOT:
|
||||
device = AirHumidifierMiot(host, token)
|
||||
else:
|
||||
device = AirHumidifier(host, token, model=model)
|
||||
|
||||
# Removing fan platform entity for humidifiers and cache the name and entity name for migration
|
||||
entity_registry = er.async_get(hass)
|
||||
entity_id = entity_registry.async_get_entity_id("fan", DOMAIN, entry.unique_id)
|
||||
if entity_id:
|
||||
# This check is entities that have a platform migration only and should be removed in the future
|
||||
migrate_entity_name = entity_registry.async_get(entity_id).name
|
||||
entity_registry.async_remove(entity_id)
|
||||
|
||||
async def async_update_data():
|
||||
"""Fetch data from the device using async_add_executor_job."""
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
return await hass.async_add_executor_job(device.status)
|
||||
|
||||
except DeviceException as ex:
|
||||
raise UpdateFailed(ex) from ex
|
||||
|
||||
# Create update miio device and coordinator
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=name,
|
||||
update_method=async_update_data,
|
||||
# Polling interval. Will only be polled if there are subscribers.
|
||||
update_interval=timedelta(seconds=60),
|
||||
)
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
KEY_DEVICE: device,
|
||||
KEY_COORDINATOR: coordinator,
|
||||
}
|
||||
if migrate_entity_name:
|
||||
hass.data[DOMAIN][entry.entry_id][KEY_MIGRATE_ENTITY_NAME] = migrate_entity_name
|
||||
|
||||
# Trigger first data fetch
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
|
||||
async def async_setup_gateway_entry(
|
||||
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
|
||||
):
|
||||
|
@ -130,7 +201,6 @@ async def async_setup_gateway_entry(
|
|||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
# Name of the data. For logging purposes.
|
||||
name=name,
|
||||
update_method=async_update_data,
|
||||
# Polling interval. Will only be polled if there are subscribers.
|
||||
|
@ -155,6 +225,7 @@ async def async_setup_device_entry(
|
|||
):
|
||||
"""Set up the Xiaomi Miio device component from a config entry."""
|
||||
platforms = get_platforms(entry)
|
||||
await async_create_miio_device_and_coordinator(hass, entry)
|
||||
|
||||
if not platforms:
|
||||
return False
|
||||
|
|
|
@ -15,10 +15,17 @@ CONF_MANUAL = "manual"
|
|||
# Options flow
|
||||
CONF_CLOUD_SUBDEVICES = "cloud_subdevices"
|
||||
|
||||
# Keys
|
||||
KEY_COORDINATOR = "coordinator"
|
||||
KEY_DEVICE = "device"
|
||||
KEY_MIGRATE_ENTITY_NAME = "migrate_entity_name"
|
||||
|
||||
# Attributes
|
||||
ATTR_AVAILABLE = "available"
|
||||
|
||||
# Status
|
||||
SUCCESS = ["ok"]
|
||||
|
||||
# Cloud
|
||||
SERVER_COUNTRY_CODES = ["cn", "de", "i2", "ru", "sg", "us"]
|
||||
DEFAULT_CLOUD_COUNTRY = "cn"
|
||||
|
@ -70,10 +77,12 @@ MODELS_FAN_MIIO = [
|
|||
MODEL_AIRPURIFIER_SA2,
|
||||
MODEL_AIRPURIFIER_2S,
|
||||
MODEL_AIRPURIFIER_2H,
|
||||
MODEL_AIRFRESH_VA2,
|
||||
]
|
||||
MODELS_HUMIDIFIER_MIIO = [
|
||||
MODEL_AIRHUMIDIFIER_V1,
|
||||
MODEL_AIRHUMIDIFIER_CA1,
|
||||
MODEL_AIRHUMIDIFIER_CB1,
|
||||
MODEL_AIRFRESH_VA2,
|
||||
]
|
||||
|
||||
# AirQuality Models
|
||||
|
@ -108,7 +117,8 @@ MODELS_SWITCH = [
|
|||
"chuangmi.plug.hmi205",
|
||||
"chuangmi.plug.hmi206",
|
||||
]
|
||||
MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT
|
||||
MODELS_FAN = MODELS_FAN_MIIO + MODELS_PURIFIER_MIOT
|
||||
MODELS_HUMIDIFIER = MODELS_HUMIDIFIER_MIOT + MODELS_HUMIDIFIER_MIIO
|
||||
MODELS_LIGHT = (
|
||||
MODELS_LIGHT_EYECARE
|
||||
+ MODELS_LIGHT_CEILING
|
||||
|
@ -125,17 +135,29 @@ MODELS_AIR_MONITOR = [
|
|||
]
|
||||
|
||||
MODELS_ALL_DEVICES = (
|
||||
MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN + MODELS_LIGHT
|
||||
MODELS_SWITCH
|
||||
+ MODELS_VACUUM
|
||||
+ MODELS_AIR_MONITOR
|
||||
+ MODELS_FAN
|
||||
+ MODELS_HUMIDIFIER
|
||||
+ MODELS_LIGHT
|
||||
)
|
||||
MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY
|
||||
|
||||
# Fan Services
|
||||
# Fan/Humidifier Services
|
||||
SERVICE_SET_BUZZER_ON = "fan_set_buzzer_on"
|
||||
SERVICE_SET_BUZZER_OFF = "fan_set_buzzer_off"
|
||||
SERVICE_SET_BUZZER = "set_buzzer"
|
||||
SERVICE_SET_CLEAN_ON = "set_clean_on"
|
||||
SERVICE_SET_CLEAN_OFF = "set_clean_off"
|
||||
SERVICE_SET_CLEAN = "set_clean"
|
||||
SERVICE_SET_FAN_LED_ON = "fan_set_led_on"
|
||||
SERVICE_SET_FAN_LED_OFF = "fan_set_led_off"
|
||||
SERVICE_SET_FAN_LED = "fan_set_led"
|
||||
SERVICE_SET_LED_BRIGHTNESS = "set_led_brightness"
|
||||
SERVICE_SET_CHILD_LOCK_ON = "fan_set_child_lock_on"
|
||||
SERVICE_SET_CHILD_LOCK_OFF = "fan_set_child_lock_off"
|
||||
SERVICE_SET_CHILD_LOCK = "set_child_lock"
|
||||
SERVICE_SET_LED_BRIGHTNESS = "fan_set_led_brightness"
|
||||
SERVICE_SET_FAVORITE_LEVEL = "fan_set_favorite_level"
|
||||
SERVICE_SET_FAN_LEVEL = "fan_set_fan_level"
|
||||
|
@ -149,6 +171,7 @@ SERVICE_SET_EXTRA_FEATURES = "fan_set_extra_features"
|
|||
SERVICE_SET_TARGET_HUMIDITY = "fan_set_target_humidity"
|
||||
SERVICE_SET_DRY_ON = "fan_set_dry_on"
|
||||
SERVICE_SET_DRY_OFF = "fan_set_dry_off"
|
||||
SERVICE_SET_DRY = "set_dry"
|
||||
SERVICE_SET_MOTOR_SPEED = "fan_set_motor_speed"
|
||||
|
||||
# Light Services
|
||||
|
@ -180,3 +203,95 @@ SERVICE_STOP_REMOTE_CONTROL = "vacuum_remote_control_stop"
|
|||
SERVICE_CLEAN_SEGMENT = "vacuum_clean_segment"
|
||||
SERVICE_CLEAN_ZONE = "vacuum_clean_zone"
|
||||
SERVICE_GOTO = "vacuum_goto"
|
||||
|
||||
# Features
|
||||
FEATURE_SET_BUZZER = 1
|
||||
FEATURE_SET_LED = 2
|
||||
FEATURE_SET_CHILD_LOCK = 4
|
||||
FEATURE_SET_LED_BRIGHTNESS = 8
|
||||
FEATURE_SET_FAVORITE_LEVEL = 16
|
||||
FEATURE_SET_AUTO_DETECT = 32
|
||||
FEATURE_SET_LEARN_MODE = 64
|
||||
FEATURE_SET_VOLUME = 128
|
||||
FEATURE_RESET_FILTER = 256
|
||||
FEATURE_SET_EXTRA_FEATURES = 512
|
||||
FEATURE_SET_TARGET_HUMIDITY = 1024
|
||||
FEATURE_SET_DRY = 2048
|
||||
FEATURE_SET_FAN_LEVEL = 4096
|
||||
FEATURE_SET_MOTOR_SPEED = 8192
|
||||
FEATURE_SET_CLEAN = 16384
|
||||
|
||||
FEATURE_FLAGS_AIRPURIFIER = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_LED_BRIGHTNESS
|
||||
| FEATURE_SET_FAVORITE_LEVEL
|
||||
| FEATURE_SET_LEARN_MODE
|
||||
| FEATURE_RESET_FILTER
|
||||
| FEATURE_SET_EXTRA_FEATURES
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRPURIFIER_PRO = (
|
||||
FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_FAVORITE_LEVEL
|
||||
| FEATURE_SET_AUTO_DETECT
|
||||
| FEATURE_SET_VOLUME
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRPURIFIER_PRO_V7 = (
|
||||
FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_FAVORITE_LEVEL
|
||||
| FEATURE_SET_VOLUME
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRPURIFIER_2S = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_FAVORITE_LEVEL
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRPURIFIER_3 = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_FAVORITE_LEVEL
|
||||
| FEATURE_SET_FAN_LEVEL
|
||||
| FEATURE_SET_LED_BRIGHTNESS
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRPURIFIER_V3 = (
|
||||
FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK | FEATURE_SET_LED
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_LED_BRIGHTNESS
|
||||
| FEATURE_SET_TARGET_HUMIDITY
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB = FEATURE_FLAGS_AIRHUMIDIFIER | FEATURE_SET_DRY
|
||||
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER_CA4 = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED_BRIGHTNESS
|
||||
| FEATURE_SET_TARGET_HUMIDITY
|
||||
| FEATURE_SET_DRY
|
||||
| FEATURE_SET_MOTOR_SPEED
|
||||
| FEATURE_SET_CLEAN
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRFRESH = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_LED_BRIGHTNESS
|
||||
| FEATURE_RESET_FILTER
|
||||
| FEATURE_SET_EXTRA_FEATURES
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Code to handle a Xiaomi Device."""
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
from construct.core import ChecksumError
|
||||
|
@ -7,6 +8,7 @@ from miio import Device, DeviceException
|
|||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import CONF_MAC, CONF_MODEL, DOMAIN
|
||||
|
||||
|
@ -73,6 +75,7 @@ class XiaomiMiioEntity(Entity):
|
|||
self._device_id = entry.unique_id
|
||||
self._unique_id = unique_id
|
||||
self._name = name
|
||||
self._available = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
|
@ -98,3 +101,59 @@ class XiaomiMiioEntity(Entity):
|
|||
device_info["connections"] = {(dr.CONNECTION_NETWORK_MAC, self._mac)}
|
||||
|
||||
return device_info
|
||||
|
||||
|
||||
class XiaomiCoordinatedMiioEntity(CoordinatorEntity):
|
||||
"""Representation of a base a coordinated Xiaomi Miio Entity."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, coordinator):
|
||||
"""Initialize the coordinated Xiaomi Miio Device."""
|
||||
super().__init__(coordinator)
|
||||
self._device = device
|
||||
self._model = entry.data[CONF_MODEL]
|
||||
self._mac = entry.data[CONF_MAC]
|
||||
self._device_id = entry.unique_id
|
||||
self._device_name = entry.title
|
||||
self._unique_id = unique_id
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return an unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this entity, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info."""
|
||||
device_info = {
|
||||
"identifiers": {(DOMAIN, self._device_id)},
|
||||
"manufacturer": "Xiaomi",
|
||||
"name": self._device_name,
|
||||
"model": self._model,
|
||||
}
|
||||
|
||||
if self._mac is not None:
|
||||
device_info["connections"] = {(dr.CONNECTION_NETWORK_MAC, self._mac)}
|
||||
|
||||
return device_info
|
||||
|
||||
async def _try_command(self, mask_error, func, *args, **kwargs):
|
||||
"""Call a miio device command handling error messages."""
|
||||
try:
|
||||
result = await self.hass.async_add_executor_job(
|
||||
partial(func, *args, **kwargs)
|
||||
)
|
||||
|
||||
_LOGGER.debug("Response received from miio device: %s", result)
|
||||
|
||||
return True
|
||||
except DeviceException as exc:
|
||||
if self.available:
|
||||
_LOGGER.error(mask_error, exc)
|
||||
|
||||
return False
|
||||
|
|
|
@ -5,27 +5,11 @@ from functools import partial
|
|||
import logging
|
||||
import math
|
||||
|
||||
from miio import (
|
||||
AirFresh,
|
||||
AirHumidifier,
|
||||
AirHumidifierMiot,
|
||||
AirPurifier,
|
||||
AirPurifierMiot,
|
||||
DeviceException,
|
||||
)
|
||||
from miio import AirFresh, AirPurifier, AirPurifierMiot, DeviceException
|
||||
from miio.airfresh import (
|
||||
LedBrightness as AirfreshLedBrightness,
|
||||
OperationMode as AirfreshOperationMode,
|
||||
)
|
||||
from miio.airhumidifier import (
|
||||
LedBrightness as AirhumidifierLedBrightness,
|
||||
OperationMode as AirhumidifierOperationMode,
|
||||
)
|
||||
from miio.airhumidifier_miot import (
|
||||
LedBrightness as AirhumidifierMiotLedBrightness,
|
||||
OperationMode as AirhumidifierMiotOperationMode,
|
||||
PressedButton as AirhumidifierPressedButton,
|
||||
)
|
||||
from miio.airpurifier import (
|
||||
LedBrightness as AirpurifierLedBrightness,
|
||||
OperationMode as AirpurifierOperationMode,
|
||||
|
@ -38,9 +22,6 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.fan import (
|
||||
PLATFORM_SCHEMA,
|
||||
SPEED_HIGH,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_SET_SPEED,
|
||||
FanEntity,
|
||||
|
@ -64,16 +45,23 @@ from .const import (
|
|||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
DOMAIN,
|
||||
MODEL_AIRHUMIDIFIER_CA1,
|
||||
MODEL_AIRHUMIDIFIER_CA4,
|
||||
MODEL_AIRHUMIDIFIER_CB1,
|
||||
FEATURE_RESET_FILTER,
|
||||
FEATURE_SET_AUTO_DETECT,
|
||||
FEATURE_SET_BUZZER,
|
||||
FEATURE_SET_CHILD_LOCK,
|
||||
FEATURE_SET_EXTRA_FEATURES,
|
||||
FEATURE_SET_FAN_LEVEL,
|
||||
FEATURE_SET_FAVORITE_LEVEL,
|
||||
FEATURE_SET_LEARN_MODE,
|
||||
FEATURE_SET_LED,
|
||||
FEATURE_SET_LED_BRIGHTNESS,
|
||||
FEATURE_SET_VOLUME,
|
||||
MODEL_AIRPURIFIER_2H,
|
||||
MODEL_AIRPURIFIER_2S,
|
||||
MODEL_AIRPURIFIER_PRO,
|
||||
MODEL_AIRPURIFIER_PRO_V7,
|
||||
MODEL_AIRPURIFIER_V3,
|
||||
MODELS_FAN,
|
||||
MODELS_HUMIDIFIER_MIOT,
|
||||
MODELS_PURIFIER_MIOT,
|
||||
SERVICE_RESET_FILTER,
|
||||
SERVICE_SET_AUTO_DETECT_OFF,
|
||||
|
@ -82,8 +70,6 @@ from .const import (
|
|||
SERVICE_SET_BUZZER_ON,
|
||||
SERVICE_SET_CHILD_LOCK_OFF,
|
||||
SERVICE_SET_CHILD_LOCK_ON,
|
||||
SERVICE_SET_DRY_OFF,
|
||||
SERVICE_SET_DRY_ON,
|
||||
SERVICE_SET_EXTRA_FEATURES,
|
||||
SERVICE_SET_FAN_LED_OFF,
|
||||
SERVICE_SET_FAN_LED_ON,
|
||||
|
@ -92,9 +78,8 @@ from .const import (
|
|||
SERVICE_SET_LEARN_MODE_OFF,
|
||||
SERVICE_SET_LEARN_MODE_ON,
|
||||
SERVICE_SET_LED_BRIGHTNESS,
|
||||
SERVICE_SET_MOTOR_SPEED,
|
||||
SERVICE_SET_TARGET_HUMIDITY,
|
||||
SERVICE_SET_VOLUME,
|
||||
SUCCESS,
|
||||
)
|
||||
from .device import XiaomiMiioEntity
|
||||
|
||||
|
@ -150,20 +135,6 @@ ATTR_VOLUME = "volume"
|
|||
ATTR_USE_TIME = "use_time"
|
||||
ATTR_BUTTON_PRESSED = "button_pressed"
|
||||
|
||||
# Air Humidifier
|
||||
ATTR_TARGET_HUMIDITY = "target_humidity"
|
||||
ATTR_TRANS_LEVEL = "trans_level"
|
||||
ATTR_HARDWARE_VERSION = "hardware_version"
|
||||
|
||||
# Air Humidifier CA
|
||||
# ATTR_MOTOR_SPEED = "motor_speed"
|
||||
ATTR_DEPTH = "depth"
|
||||
ATTR_DRY = "dry"
|
||||
|
||||
# Air Humidifier CA4
|
||||
ATTR_ACTUAL_MOTOR_SPEED = "actual_speed"
|
||||
ATTR_FAHRENHEIT = "fahrenheit"
|
||||
|
||||
# Air Fresh
|
||||
ATTR_CO2 = "co2"
|
||||
|
||||
|
@ -283,41 +254,6 @@ AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 = {
|
|||
ATTR_BUTTON_PRESSED: "button_pressed",
|
||||
}
|
||||
|
||||
AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON = {
|
||||
ATTR_TEMPERATURE: "temperature",
|
||||
ATTR_HUMIDITY: "humidity",
|
||||
ATTR_MODE: "mode",
|
||||
ATTR_BUZZER: "buzzer",
|
||||
ATTR_CHILD_LOCK: "child_lock",
|
||||
ATTR_TARGET_HUMIDITY: "target_humidity",
|
||||
ATTR_LED_BRIGHTNESS: "led_brightness",
|
||||
ATTR_USE_TIME: "use_time",
|
||||
}
|
||||
|
||||
AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER = {
|
||||
**AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON,
|
||||
ATTR_TRANS_LEVEL: "trans_level",
|
||||
ATTR_BUTTON_PRESSED: "button_pressed",
|
||||
ATTR_HARDWARE_VERSION: "hardware_version",
|
||||
}
|
||||
|
||||
AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_CA_AND_CB = {
|
||||
**AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON,
|
||||
ATTR_MOTOR_SPEED: "motor_speed",
|
||||
ATTR_DEPTH: "depth",
|
||||
ATTR_DRY: "dry",
|
||||
ATTR_HARDWARE_VERSION: "hardware_version",
|
||||
}
|
||||
|
||||
AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_CA4 = {
|
||||
**AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON,
|
||||
ATTR_ACTUAL_MOTOR_SPEED: "actual_speed",
|
||||
ATTR_BUTTON_PRESSED: "button_pressed",
|
||||
ATTR_DRY: "dry",
|
||||
ATTR_FAHRENHEIT: "fahrenheit",
|
||||
ATTR_MOTOR_SPEED: "motor_speed",
|
||||
}
|
||||
|
||||
AVAILABLE_ATTRIBUTES_AIRFRESH = {
|
||||
ATTR_TEMPERATURE: "temperature",
|
||||
ATTR_AIR_QUALITY_INDEX: "aqi",
|
||||
|
@ -365,25 +301,6 @@ PRESET_MODES_AIRPURIFIER_V3 = [
|
|||
]
|
||||
OPERATION_MODES_AIRFRESH = ["Auto", "Silent", "Interval", "Low", "Middle", "Strong"]
|
||||
PRESET_MODES_AIRFRESH = ["Auto", "Interval"]
|
||||
PRESET_MODES_AIRHUMIDIFIER = ["Auto"]
|
||||
PRESET_MODES_AIRHUMIDIFIER_CA4 = ["Auto"]
|
||||
|
||||
SUCCESS = ["ok"]
|
||||
|
||||
FEATURE_SET_BUZZER = 1
|
||||
FEATURE_SET_LED = 2
|
||||
FEATURE_SET_CHILD_LOCK = 4
|
||||
FEATURE_SET_LED_BRIGHTNESS = 8
|
||||
FEATURE_SET_FAVORITE_LEVEL = 16
|
||||
FEATURE_SET_AUTO_DETECT = 32
|
||||
FEATURE_SET_LEARN_MODE = 64
|
||||
FEATURE_SET_VOLUME = 128
|
||||
FEATURE_RESET_FILTER = 256
|
||||
FEATURE_SET_EXTRA_FEATURES = 512
|
||||
FEATURE_SET_TARGET_HUMIDITY = 1024
|
||||
FEATURE_SET_DRY = 2048
|
||||
FEATURE_SET_FAN_LEVEL = 4096
|
||||
FEATURE_SET_MOTOR_SPEED = 8192
|
||||
|
||||
FEATURE_FLAGS_AIRPURIFIER = (
|
||||
FEATURE_SET_BUZZER
|
||||
|
@ -431,25 +348,6 @@ FEATURE_FLAGS_AIRPURIFIER_V3 = (
|
|||
FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK | FEATURE_SET_LED
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED
|
||||
| FEATURE_SET_LED_BRIGHTNESS
|
||||
| FEATURE_SET_TARGET_HUMIDITY
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB = FEATURE_FLAGS_AIRHUMIDIFIER | FEATURE_SET_DRY
|
||||
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER_CA4 = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
| FEATURE_SET_LED_BRIGHTNESS
|
||||
| FEATURE_SET_TARGET_HUMIDITY
|
||||
| FEATURE_SET_DRY
|
||||
| FEATURE_SET_MOTOR_SPEED
|
||||
)
|
||||
|
||||
FEATURE_FLAGS_AIRFRESH = (
|
||||
FEATURE_SET_BUZZER
|
||||
| FEATURE_SET_CHILD_LOCK
|
||||
|
@ -481,22 +379,6 @@ SERVICE_SCHEMA_EXTRA_FEATURES = AIRPURIFIER_SERVICE_SCHEMA.extend(
|
|||
{vol.Required(ATTR_FEATURES): cv.positive_int}
|
||||
)
|
||||
|
||||
SERVICE_SCHEMA_TARGET_HUMIDITY = AIRPURIFIER_SERVICE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(ATTR_HUMIDITY): vol.All(
|
||||
vol.Coerce(int), vol.In([30, 40, 50, 60, 70, 80])
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_SCHEMA_MOTOR_SPEED = AIRPURIFIER_SERVICE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(ATTR_MOTOR_SPEED): vol.All(
|
||||
vol.Coerce(int), vol.Clamp(min=200, max=2000)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_TO_METHOD = {
|
||||
SERVICE_SET_BUZZER_ON: {"method": "async_set_buzzer_on"},
|
||||
SERVICE_SET_BUZZER_OFF: {"method": "async_set_buzzer_off"},
|
||||
|
@ -526,16 +408,6 @@ SERVICE_TO_METHOD = {
|
|||
"method": "async_set_extra_features",
|
||||
"schema": SERVICE_SCHEMA_EXTRA_FEATURES,
|
||||
},
|
||||
SERVICE_SET_TARGET_HUMIDITY: {
|
||||
"method": "async_set_target_humidity",
|
||||
"schema": SERVICE_SCHEMA_TARGET_HUMIDITY,
|
||||
},
|
||||
SERVICE_SET_DRY_ON: {"method": "async_set_dry_on"},
|
||||
SERVICE_SET_DRY_OFF: {"method": "async_set_dry_off"},
|
||||
SERVICE_SET_MOTOR_SPEED: {
|
||||
"method": "async_set_motor_speed",
|
||||
"schema": SERVICE_SCHEMA_MOTOR_SPEED,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -578,14 +450,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
elif model.startswith("zhimi.airpurifier."):
|
||||
air_purifier = AirPurifier(host, token)
|
||||
entity = XiaomiAirPurifier(name, air_purifier, config_entry, unique_id)
|
||||
elif model in MODELS_HUMIDIFIER_MIOT:
|
||||
air_humidifier = AirHumidifierMiot(host, token)
|
||||
entity = XiaomiAirHumidifierMiot(
|
||||
name, air_humidifier, config_entry, unique_id
|
||||
)
|
||||
elif model.startswith("zhimi.humidifier."):
|
||||
air_humidifier = AirHumidifier(host, token, model=model)
|
||||
entity = XiaomiAirHumidifier(name, air_humidifier, config_entry, unique_id)
|
||||
elif model.startswith("zhimi.airfresh."):
|
||||
air_fresh = AirFresh(host, token)
|
||||
entity = XiaomiAirFresh(name, air_fresh, config_entry, unique_id)
|
||||
|
@ -1247,345 +1111,6 @@ class XiaomiAirPurifierMiot(XiaomiAirPurifier):
|
|||
)
|
||||
|
||||
|
||||
class XiaomiAirHumidifier(XiaomiGenericDevice):
|
||||
"""Representation of a Xiaomi Air Humidifier."""
|
||||
|
||||
SPEED_MODE_MAPPING = {
|
||||
1: AirhumidifierOperationMode.Silent,
|
||||
2: AirhumidifierOperationMode.Medium,
|
||||
3: AirhumidifierOperationMode.High,
|
||||
4: AirhumidifierOperationMode.Strong,
|
||||
}
|
||||
|
||||
REVERSE_SPEED_MODE_MAPPING = {v: k for k, v in SPEED_MODE_MAPPING.items()}
|
||||
|
||||
PRESET_MODE_MAPPING = {
|
||||
"Auto": AirhumidifierOperationMode.Auto,
|
||||
}
|
||||
|
||||
def __init__(self, name, device, entry, unique_id):
|
||||
"""Initialize the plug switch."""
|
||||
super().__init__(name, device, entry, unique_id)
|
||||
self._percentage = None
|
||||
self._preset_mode = None
|
||||
self._supported_features = SUPPORT_SET_SPEED
|
||||
self._preset_modes = []
|
||||
if self._model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]:
|
||||
self._device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB
|
||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_CA_AND_CB
|
||||
# the speed_list attribute is deprecated, support will end with release 2021.7
|
||||
self._speed_list = [
|
||||
mode.name
|
||||
for mode in AirhumidifierOperationMode
|
||||
if mode is not AirhumidifierOperationMode.Strong
|
||||
]
|
||||
self._supported_features |= SUPPORT_PRESET_MODE
|
||||
self._preset_modes = PRESET_MODES_AIRHUMIDIFIER
|
||||
self._speed_count = 3
|
||||
elif self._model in [MODEL_AIRHUMIDIFIER_CA4]:
|
||||
self._device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA4
|
||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_CA4
|
||||
# the speed_list attribute is deprecated, support will end with release 2021.7
|
||||
self._speed_list = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
self._supported_features |= SUPPORT_PRESET_MODE
|
||||
self._preset_modes = PRESET_MODES_AIRHUMIDIFIER
|
||||
self._speed_count = 3
|
||||
else:
|
||||
self._device_features = FEATURE_FLAGS_AIRHUMIDIFIER
|
||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER
|
||||
# the speed_list attribute is deprecated, support will end with release 2021.7
|
||||
self._speed_list = [
|
||||
mode.name
|
||||
for mode in AirhumidifierOperationMode
|
||||
if mode is not AirhumidifierOperationMode.Auto
|
||||
]
|
||||
self._supported_features |= SUPPORT_PRESET_MODE
|
||||
self._preset_modes = PRESET_MODES_AIRHUMIDIFIER
|
||||
self._speed_count = 4
|
||||
|
||||
self._state_attrs.update(
|
||||
{attribute: None for attribute in self._available_attributes}
|
||||
)
|
||||
|
||||
async def async_update(self):
|
||||
"""Fetch state from the device."""
|
||||
# On state change the device doesn't provide the new state immediately.
|
||||
if self._skip_update:
|
||||
self._skip_update = False
|
||||
return
|
||||
|
||||
try:
|
||||
state = await self.hass.async_add_executor_job(self._device.status)
|
||||
_LOGGER.debug("Got new state: %s", state)
|
||||
|
||||
self._available = True
|
||||
self._state = state.is_on
|
||||
self._state_attrs.update(
|
||||
{
|
||||
key: self._extract_value_from_attribute(state, value)
|
||||
for key, value in self._available_attributes.items()
|
||||
}
|
||||
)
|
||||
|
||||
except DeviceException as ex:
|
||||
if self._available:
|
||||
self._available = False
|
||||
_LOGGER.error("Got exception while fetching the state: %s", ex)
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Get the active preset mode."""
|
||||
if self._state:
|
||||
preset_mode = AirhumidifierOperationMode(self._state_attrs[ATTR_MODE]).name
|
||||
return preset_mode if preset_mode in self._preset_modes else None
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def percentage(self):
|
||||
"""Return the current percentage based speed."""
|
||||
if self._state:
|
||||
mode = AirhumidifierOperationMode(self._state_attrs[ATTR_MODE])
|
||||
if mode in self.REVERSE_SPEED_MODE_MAPPING:
|
||||
return ranged_value_to_percentage(
|
||||
(1, self._speed_count), self.REVERSE_SPEED_MODE_MAPPING[mode]
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
# the speed attribute is deprecated, support will end with release 2021.7
|
||||
@property
|
||||
def speed(self):
|
||||
"""Return the current speed."""
|
||||
if self._state:
|
||||
return AirhumidifierOperationMode(self._state_attrs[ATTR_MODE]).name
|
||||
|
||||
return None
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the percentage of the fan.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
speed_mode = math.ceil(
|
||||
percentage_to_ranged_value((1, self._speed_count), percentage)
|
||||
)
|
||||
if speed_mode:
|
||||
await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
AirhumidifierOperationMode(self.SPEED_MODE_MAPPING[speed_mode]),
|
||||
)
|
||||
|
||||
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
|
||||
await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
self.PRESET_MODE_MAPPING[preset_mode],
|
||||
)
|
||||
|
||||
# the async_set_speed function is deprecated, support will end with release 2021.7
|
||||
# it is added here only for compatibility with legacy speeds
|
||||
async def async_set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
if self.supported_features & SUPPORT_SET_SPEED == 0:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Setting the operation mode to: %s", speed)
|
||||
|
||||
await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
AirhumidifierOperationMode[speed.title()],
|
||||
)
|
||||
|
||||
async def async_set_led_brightness(self, brightness: int = 2):
|
||||
"""Set the led brightness."""
|
||||
if self._device_features & FEATURE_SET_LED_BRIGHTNESS == 0:
|
||||
return
|
||||
|
||||
await self._try_command(
|
||||
"Setting the led brightness of the miio device failed.",
|
||||
self._device.set_led_brightness,
|
||||
AirhumidifierLedBrightness(brightness),
|
||||
)
|
||||
|
||||
async def async_set_target_humidity(self, humidity: int = 40):
|
||||
"""Set the target humidity."""
|
||||
if self._device_features & FEATURE_SET_TARGET_HUMIDITY == 0:
|
||||
return
|
||||
|
||||
await self._try_command(
|
||||
"Setting the target humidity of the miio device failed.",
|
||||
self._device.set_target_humidity,
|
||||
humidity,
|
||||
)
|
||||
|
||||
async def async_set_dry_on(self):
|
||||
"""Turn the dry mode on."""
|
||||
if self._device_features & FEATURE_SET_DRY == 0:
|
||||
return
|
||||
|
||||
await self._try_command(
|
||||
"Turning the dry mode of the miio device off failed.",
|
||||
self._device.set_dry,
|
||||
True,
|
||||
)
|
||||
|
||||
async def async_set_dry_off(self):
|
||||
"""Turn the dry mode off."""
|
||||
if self._device_features & FEATURE_SET_DRY == 0:
|
||||
return
|
||||
|
||||
await self._try_command(
|
||||
"Turning the dry mode of the miio device off failed.",
|
||||
self._device.set_dry,
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
class XiaomiAirHumidifierMiot(XiaomiAirHumidifier):
|
||||
"""Representation of a Xiaomi Air Humidifier (MiOT protocol)."""
|
||||
|
||||
PRESET_MODE_MAPPING = {
|
||||
AirhumidifierMiotOperationMode.Auto: "Auto",
|
||||
}
|
||||
|
||||
REVERSE_PRESET_MODE_MAPPING = {v: k for k, v in PRESET_MODE_MAPPING.items()}
|
||||
|
||||
SPEED_MAPPING = {
|
||||
AirhumidifierMiotOperationMode.Low: SPEED_LOW,
|
||||
AirhumidifierMiotOperationMode.Mid: SPEED_MEDIUM,
|
||||
AirhumidifierMiotOperationMode.High: SPEED_HIGH,
|
||||
}
|
||||
|
||||
REVERSE_SPEED_MAPPING = {v: k for k, v in SPEED_MAPPING.items()}
|
||||
|
||||
SPEEDS = [
|
||||
AirhumidifierMiotOperationMode.Low,
|
||||
AirhumidifierMiotOperationMode.Mid,
|
||||
AirhumidifierMiotOperationMode.High,
|
||||
]
|
||||
|
||||
# the speed attribute is deprecated, support will end with release 2021.7
|
||||
# it is added here for compatibility
|
||||
@property
|
||||
def speed(self):
|
||||
"""Return current legacy speed."""
|
||||
if (
|
||||
self.state
|
||||
and AirhumidifierMiotOperationMode(self._state_attrs[ATTR_MODE])
|
||||
in self.SPEED_MAPPING
|
||||
):
|
||||
return self.SPEED_MAPPING[
|
||||
AirhumidifierMiotOperationMode(self._state_attrs[ATTR_MODE])
|
||||
]
|
||||
return None
|
||||
|
||||
@property
|
||||
def percentage(self):
|
||||
"""Return the current percentage based speed."""
|
||||
if (
|
||||
self.state
|
||||
and AirhumidifierMiotOperationMode(self._state_attrs[ATTR_MODE])
|
||||
in self.SPEEDS
|
||||
):
|
||||
return ranged_value_to_percentage(
|
||||
(1, self.speed_count), self._state_attrs[ATTR_MODE]
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset_mode."""
|
||||
if self._state:
|
||||
mode = self.PRESET_MODE_MAPPING.get(
|
||||
AirhumidifierMiotOperationMode(self._state_attrs[ATTR_MODE])
|
||||
)
|
||||
if mode in self._preset_modes:
|
||||
return mode
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def button_pressed(self):
|
||||
"""Return the last button pressed."""
|
||||
if self._state:
|
||||
return AirhumidifierPressedButton(
|
||||
self._state_attrs[ATTR_BUTTON_PRESSED]
|
||||
).name
|
||||
|
||||
return None
|
||||
|
||||
# the async_set_speed function is deprecated, support will end with release 2021.7
|
||||
# it is added here only for compatibility with legacy speeds
|
||||
async def async_set_speed(self, speed: str) -> None:
|
||||
"""Override for set async_set_speed of the super() class."""
|
||||
if speed and speed in self.REVERSE_SPEED_MAPPING:
|
||||
await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
self.REVERSE_SPEED_MAPPING[speed],
|
||||
)
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the percentage of the fan.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
mode = math.ceil(percentage_to_ranged_value((1, 3), percentage))
|
||||
if mode:
|
||||
await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
AirhumidifierMiotOperationMode(mode),
|
||||
)
|
||||
|
||||
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
|
||||
await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
self.REVERSE_PRESET_MODE_MAPPING[preset_mode],
|
||||
)
|
||||
|
||||
async def async_set_led_brightness(self, brightness: int = 2):
|
||||
"""Set the led brightness."""
|
||||
if self._device_features & FEATURE_SET_LED_BRIGHTNESS == 0:
|
||||
return
|
||||
|
||||
await self._try_command(
|
||||
"Setting the led brightness of the miio device failed.",
|
||||
self._device.set_led_brightness,
|
||||
AirhumidifierMiotLedBrightness(brightness),
|
||||
)
|
||||
|
||||
async def async_set_motor_speed(self, motor_speed: int = 400):
|
||||
"""Set the target motor speed."""
|
||||
if self._device_features & FEATURE_SET_MOTOR_SPEED == 0:
|
||||
return
|
||||
|
||||
await self._try_command(
|
||||
"Setting the target motor speed of the miio device failed.",
|
||||
self._device.set_speed,
|
||||
motor_speed,
|
||||
)
|
||||
|
||||
|
||||
class XiaomiAirFresh(XiaomiGenericDevice):
|
||||
"""Representation of a Xiaomi Air Fresh."""
|
||||
|
||||
|
|
372
homeassistant/components/xiaomi_miio/humidifier.py
Normal file
372
homeassistant/components/xiaomi_miio/humidifier.py
Normal file
|
@ -0,0 +1,372 @@
|
|||
"""Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier with humidifier entity."""
|
||||
from enum import Enum
|
||||
import logging
|
||||
import math
|
||||
|
||||
from miio.airhumidifier import OperationMode as AirhumidifierOperationMode
|
||||
from miio.airhumidifier_miot import OperationMode as AirhumidifierMiotOperationMode
|
||||
|
||||
from homeassistant.components.humidifier import HumidifierEntity
|
||||
from homeassistant.components.humidifier.const import (
|
||||
DEFAULT_MAX_HUMIDITY,
|
||||
DEFAULT_MIN_HUMIDITY,
|
||||
DEVICE_CLASS_HUMIDIFIER,
|
||||
SUPPORT_MODES,
|
||||
)
|
||||
from homeassistant.const import ATTR_MODE, CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util.percentage import percentage_to_ranged_value
|
||||
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
KEY_COORDINATOR,
|
||||
KEY_DEVICE,
|
||||
KEY_MIGRATE_ENTITY_NAME,
|
||||
MODEL_AIRHUMIDIFIER_CA1,
|
||||
MODEL_AIRHUMIDIFIER_CA4,
|
||||
MODEL_AIRHUMIDIFIER_CB1,
|
||||
MODELS_HUMIDIFIER_MIOT,
|
||||
)
|
||||
from .device import XiaomiCoordinatedMiioEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Air Humidifier
|
||||
ATTR_TARGET_HUMIDITY = "target_humidity"
|
||||
|
||||
AVAILABLE_ATTRIBUTES = {
|
||||
ATTR_MODE: "mode",
|
||||
ATTR_TARGET_HUMIDITY: "target_humidity",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the Humidifier from a config entry."""
|
||||
if not config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
return
|
||||
|
||||
entities = []
|
||||
host = config_entry.data[CONF_HOST]
|
||||
token = config_entry.data[CONF_TOKEN]
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
unique_id = config_entry.unique_id
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
if KEY_MIGRATE_ENTITY_NAME in hass.data[DOMAIN][config_entry.entry_id]:
|
||||
name = hass.data[DOMAIN][config_entry.entry_id][KEY_MIGRATE_ENTITY_NAME]
|
||||
else:
|
||||
name = config_entry.title
|
||||
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
|
||||
if model in MODELS_HUMIDIFIER_MIOT:
|
||||
air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
entity = XiaomiAirHumidifierMiot(
|
||||
name,
|
||||
air_humidifier,
|
||||
config_entry,
|
||||
unique_id,
|
||||
coordinator,
|
||||
)
|
||||
else:
|
||||
air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
entity = XiaomiAirHumidifier(
|
||||
name,
|
||||
air_humidifier,
|
||||
config_entry,
|
||||
unique_id,
|
||||
coordinator,
|
||||
)
|
||||
|
||||
entities.append(entity)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class XiaomiGenericHumidifier(XiaomiCoordinatedMiioEntity, HumidifierEntity):
|
||||
"""Representation of a generic Xiaomi humidifier device."""
|
||||
|
||||
_attr_device_class = DEVICE_CLASS_HUMIDIFIER
|
||||
_attr_supported_features = SUPPORT_MODES
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, coordinator):
|
||||
"""Initialize the generic Xiaomi device."""
|
||||
super().__init__(name, device, entry, unique_id, coordinator=coordinator)
|
||||
|
||||
self._state = None
|
||||
self._attributes = {}
|
||||
self._available_modes = []
|
||||
self._mode = None
|
||||
self._min_humidity = DEFAULT_MIN_HUMIDITY
|
||||
self._max_humidity = DEFAULT_MAX_HUMIDITY
|
||||
self._humidity_steps = 100
|
||||
self._target_humidity = None
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._state
|
||||
|
||||
@staticmethod
|
||||
def _extract_value_from_attribute(state, attribute):
|
||||
value = getattr(state, attribute)
|
||||
if isinstance(value, Enum):
|
||||
return value.value
|
||||
|
||||
return value
|
||||
|
||||
@property
|
||||
def available_modes(self) -> list:
|
||||
"""Get the list of available modes."""
|
||||
return self._available_modes
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Get the current mode."""
|
||||
return self._mode
|
||||
|
||||
@property
|
||||
def min_humidity(self):
|
||||
"""Return the minimum target humidity."""
|
||||
return self._min_humidity
|
||||
|
||||
@property
|
||||
def max_humidity(self):
|
||||
"""Return the maximum target humidity."""
|
||||
return self._max_humidity
|
||||
|
||||
async def async_turn_on(
|
||||
self,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn the device on."""
|
||||
result = await self._try_command(
|
||||
"Turning the miio device on failed.", self._device.on
|
||||
)
|
||||
if result:
|
||||
self._state = True
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn the device off."""
|
||||
result = await self._try_command(
|
||||
"Turning the miio device off failed.", self._device.off
|
||||
)
|
||||
|
||||
if result:
|
||||
self._state = False
|
||||
|
||||
def translate_humidity(self, humidity):
|
||||
"""Translate the target humidity to the first valid step."""
|
||||
return (
|
||||
math.ceil(percentage_to_ranged_value((1, self._humidity_steps), humidity))
|
||||
* 100
|
||||
/ self._humidity_steps
|
||||
if 0 < humidity <= 100
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
class XiaomiAirHumidifier(XiaomiGenericHumidifier, HumidifierEntity):
|
||||
"""Representation of a Xiaomi Air Humidifier."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, coordinator):
|
||||
"""Initialize the plug switch."""
|
||||
super().__init__(name, device, entry, unique_id, coordinator)
|
||||
if self._model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]:
|
||||
self._available_modes = []
|
||||
self._available_modes = [
|
||||
mode.name
|
||||
for mode in AirhumidifierOperationMode
|
||||
if mode is not AirhumidifierOperationMode.Strong
|
||||
]
|
||||
self._min_humidity = 30
|
||||
self._max_humidity = 80
|
||||
self._humidity_steps = 10
|
||||
elif self._model in [MODEL_AIRHUMIDIFIER_CA4]:
|
||||
self._available_modes = [
|
||||
mode.name for mode in AirhumidifierMiotOperationMode
|
||||
]
|
||||
self._min_humidity = 30
|
||||
self._max_humidity = 80
|
||||
self._humidity_steps = 100
|
||||
else:
|
||||
self._available_modes = [
|
||||
mode.name
|
||||
for mode in AirhumidifierOperationMode
|
||||
if mode is not AirhumidifierOperationMode.Auto
|
||||
]
|
||||
self._min_humidity = 30
|
||||
self._max_humidity = 80
|
||||
self._humidity_steps = 10
|
||||
|
||||
self._state = self.coordinator.data.is_on
|
||||
self._attributes.update(
|
||||
{
|
||||
key: self._extract_value_from_attribute(self.coordinator.data, value)
|
||||
for key, value in AVAILABLE_ATTRIBUTES.items()
|
||||
}
|
||||
)
|
||||
self._target_humidity = self._attributes[ATTR_TARGET_HUMIDITY]
|
||||
self._mode = self._attributes[ATTR_MODE]
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._state
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
"""Fetch state from the device."""
|
||||
self._state = self.coordinator.data.is_on
|
||||
self._attributes.update(
|
||||
{
|
||||
key: self._extract_value_from_attribute(self.coordinator.data, value)
|
||||
for key, value in AVAILABLE_ATTRIBUTES.items()
|
||||
}
|
||||
)
|
||||
self._target_humidity = self._attributes[ATTR_TARGET_HUMIDITY]
|
||||
self._mode = self._attributes[ATTR_MODE]
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Return the current mode."""
|
||||
return AirhumidifierOperationMode(self._mode).name
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
"""Return the target humidity."""
|
||||
return (
|
||||
self._target_humidity
|
||||
if self._mode == AirhumidifierOperationMode.Auto.name
|
||||
or AirhumidifierOperationMode.Auto.name not in self.available_modes
|
||||
else None
|
||||
)
|
||||
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set the target humidity of the humidifier and set the mode to auto."""
|
||||
target_humidity = self.translate_humidity(humidity)
|
||||
if not target_humidity:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Setting the target humidity to: %s", target_humidity)
|
||||
if await self._try_command(
|
||||
"Setting target humidity of the miio device failed.",
|
||||
self._device.set_target_humidity,
|
||||
target_humidity,
|
||||
):
|
||||
self._target_humidity = target_humidity
|
||||
if (
|
||||
self.supported_features & SUPPORT_MODES == 0
|
||||
or AirhumidifierOperationMode(self._attributes[ATTR_MODE])
|
||||
== AirhumidifierOperationMode.Auto
|
||||
or AirhumidifierOperationMode.Auto.name not in self.available_modes
|
||||
):
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
_LOGGER.debug("Setting the operation mode to: Auto")
|
||||
if await self._try_command(
|
||||
"Setting operation mode of the miio device to MODE_AUTO failed.",
|
||||
self._device.set_mode,
|
||||
AirhumidifierOperationMode.Auto,
|
||||
):
|
||||
self._mode = AirhumidifierOperationMode.Auto.name
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set the mode of the humidifier."""
|
||||
if self.supported_features & SUPPORT_MODES == 0 or not mode:
|
||||
return
|
||||
|
||||
if mode not in self.available_modes:
|
||||
_LOGGER.warning("Mode %s is not a valid operation mode", mode)
|
||||
return
|
||||
|
||||
_LOGGER.debug("Setting the operation mode to: %s", mode)
|
||||
if await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
AirhumidifierOperationMode[mode.title()],
|
||||
):
|
||||
self._mode = mode.title()
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class XiaomiAirHumidifierMiot(XiaomiAirHumidifier):
|
||||
"""Representation of a Xiaomi Air Humidifier (MiOT protocol)."""
|
||||
|
||||
MODE_MAPPING = {
|
||||
AirhumidifierMiotOperationMode.Auto: "Auto",
|
||||
AirhumidifierMiotOperationMode.Low: "Low",
|
||||
AirhumidifierMiotOperationMode.Mid: "Mid",
|
||||
AirhumidifierMiotOperationMode.High: "High",
|
||||
}
|
||||
|
||||
REVERSE_MODE_MAPPING = {v: k for k, v in MODE_MAPPING.items()}
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Return the current mode."""
|
||||
return AirhumidifierMiotOperationMode(self._mode).name
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
"""Return the target humidity."""
|
||||
if self._state:
|
||||
return (
|
||||
self._target_humidity
|
||||
if AirhumidifierMiotOperationMode(self._mode)
|
||||
== AirhumidifierMiotOperationMode.Auto
|
||||
else None
|
||||
)
|
||||
return None
|
||||
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set the target humidity of the humidifier and set the mode to auto."""
|
||||
target_humidity = self.translate_humidity(humidity)
|
||||
if not target_humidity:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Setting the humidity to: %s", target_humidity)
|
||||
if await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_target_humidity,
|
||||
target_humidity,
|
||||
):
|
||||
self._target_humidity = target_humidity
|
||||
if (
|
||||
self.supported_features & SUPPORT_MODES == 0
|
||||
or AirhumidifierMiotOperationMode(self._attributes[ATTR_MODE])
|
||||
== AirhumidifierMiotOperationMode.Auto
|
||||
):
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
_LOGGER.debug("Setting the operation mode to: Auto")
|
||||
if await self._try_command(
|
||||
"Setting operation mode of the miio device to MODE_AUTO failed.",
|
||||
self._device.set_mode,
|
||||
AirhumidifierMiotOperationMode.Auto,
|
||||
):
|
||||
self._mode = 0
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set the mode of the fan."""
|
||||
if self.supported_features & SUPPORT_MODES == 0 or not mode:
|
||||
return
|
||||
|
||||
if mode not in self.REVERSE_MODE_MAPPING:
|
||||
_LOGGER.warning("Mode %s is not a valid operation mode", mode)
|
||||
return
|
||||
|
||||
_LOGGER.debug("Setting the operation mode to: %s", mode)
|
||||
if self._state:
|
||||
if await self._try_command(
|
||||
"Setting operation mode of the miio device failed.",
|
||||
self._device.set_mode,
|
||||
self.REVERSE_MODE_MAPPING[mode],
|
||||
):
|
||||
self._mode = self.REVERSE_MODE_MAPPING[mode].value
|
||||
self.async_write_ha_state()
|
156
homeassistant/components/xiaomi_miio/number.py
Normal file
156
homeassistant/components/xiaomi_miio/number.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
"""Motor speed support for Xiaomi Mi Air Humidifier."""
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
FEATURE_SET_MOTOR_SPEED,
|
||||
KEY_COORDINATOR,
|
||||
KEY_DEVICE,
|
||||
KEY_MIGRATE_ENTITY_NAME,
|
||||
MODEL_AIRHUMIDIFIER_CA4,
|
||||
)
|
||||
from .device import XiaomiCoordinatedMiioEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_MOTOR_SPEED = "motor_speed"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NumberType:
|
||||
"""Class that holds device specific info for a xiaomi aqara or humidifier number controller types."""
|
||||
|
||||
name: str = None
|
||||
short_name: str = None
|
||||
unit_of_measurement: str = None
|
||||
icon: str = None
|
||||
device_class: str = None
|
||||
min: float = None
|
||||
max: float = None
|
||||
step: float = None
|
||||
available_with_device_off: bool = True
|
||||
|
||||
|
||||
NUMBER_TYPES = {
|
||||
FEATURE_SET_MOTOR_SPEED: NumberType(
|
||||
name="Motor Speed",
|
||||
icon="mdi:fast-forward-outline",
|
||||
short_name=ATTR_MOTOR_SPEED,
|
||||
unit_of_measurement="rpm",
|
||||
min=200,
|
||||
max=2000,
|
||||
step=10,
|
||||
available_with_device_off=False,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the Selectors from a config entry."""
|
||||
entities = []
|
||||
if not config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
return
|
||||
host = config_entry.data[CONF_HOST]
|
||||
token = config_entry.data[CONF_TOKEN]
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
if KEY_MIGRATE_ENTITY_NAME in hass.data[DOMAIN][config_entry.entry_id]:
|
||||
name = hass.data[DOMAIN][config_entry.entry_id][KEY_MIGRATE_ENTITY_NAME]
|
||||
else:
|
||||
name = config_entry.title
|
||||
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
if model not in [MODEL_AIRHUMIDIFIER_CA4]:
|
||||
return
|
||||
|
||||
for number in NUMBER_TYPES.values():
|
||||
entities.append(
|
||||
XiaomiAirHumidifierNumber(
|
||||
f"{name} {number.name}",
|
||||
device,
|
||||
config_entry,
|
||||
f"{number.short_name}_{config_entry.unique_id}",
|
||||
number,
|
||||
coordinator,
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class XiaomiAirHumidifierNumber(XiaomiCoordinatedMiioEntity, NumberEntity):
|
||||
"""Representation of a generic Xiaomi attribute selector."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, number, coordinator):
|
||||
"""Initialize the generic Xiaomi attribute selector."""
|
||||
super().__init__(name, device, entry, unique_id, coordinator)
|
||||
self._attr_icon = number.icon
|
||||
self._attr_unit_of_measurement = number.unit_of_measurement
|
||||
self._attr_min_value = number.min
|
||||
self._attr_max_value = number.max
|
||||
self._attr_step = number.step
|
||||
self._controller = number
|
||||
self._attr_value = self._extract_value_from_attribute(
|
||||
self.coordinator.data, self._controller.short_name
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return the number controller availability."""
|
||||
if (
|
||||
super().available
|
||||
and not self.coordinator.data.is_on
|
||||
and not self._controller.available_with_device_off
|
||||
):
|
||||
return False
|
||||
return super().available
|
||||
|
||||
@staticmethod
|
||||
def _extract_value_from_attribute(state, attribute):
|
||||
value = getattr(state, attribute)
|
||||
if isinstance(value, Enum):
|
||||
return value.value
|
||||
|
||||
return value
|
||||
|
||||
async def async_set_value(self, value):
|
||||
"""Set an option of the miio device."""
|
||||
if (
|
||||
self.min_value
|
||||
and value < self.min_value
|
||||
or self.max_value
|
||||
and value > self.max_value
|
||||
):
|
||||
raise ValueError(
|
||||
f"Value {value} not a valid {self.name} within the range {self.min_value} - {self.max_value}"
|
||||
)
|
||||
if await self.async_set_motor_speed(value):
|
||||
self._attr_value = value
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
"""Fetch state from the device."""
|
||||
# On state change the device doesn't provide the new state immediately.
|
||||
self._attr_value = self._extract_value_from_attribute(
|
||||
self.coordinator.data, self._controller.short_name
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_motor_speed(self, motor_speed: int = 400):
|
||||
"""Set the target motor speed."""
|
||||
return await self._try_command(
|
||||
"Setting the target motor speed of the miio device failed.",
|
||||
self._device.set_speed,
|
||||
motor_speed,
|
||||
)
|
189
homeassistant/components/xiaomi_miio/select.py
Normal file
189
homeassistant/components/xiaomi_miio/select.py
Normal file
|
@ -0,0 +1,189 @@
|
|||
"""Support led_brightness for Mi Air Humidifier."""
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
from miio.airhumidifier import LedBrightness as AirhumidifierLedBrightness
|
||||
from miio.airhumidifier_miot import LedBrightness as AirhumidifierMiotLedBrightness
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
FEATURE_SET_LED_BRIGHTNESS,
|
||||
KEY_COORDINATOR,
|
||||
KEY_DEVICE,
|
||||
KEY_MIGRATE_ENTITY_NAME,
|
||||
MODEL_AIRHUMIDIFIER_CA1,
|
||||
MODEL_AIRHUMIDIFIER_CA4,
|
||||
MODEL_AIRHUMIDIFIER_CB1,
|
||||
MODELS_HUMIDIFIER,
|
||||
SERVICE_SET_LED_BRIGHTNESS,
|
||||
)
|
||||
from .device import XiaomiCoordinatedMiioEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_LED_BRIGHTNESS = "led_brightness"
|
||||
|
||||
|
||||
LED_BRIGHTNESS_MAP = {"Bright": 0, "Dim": 1, "Off": 2}
|
||||
LED_BRIGHTNESS_MAP_MIOT = {"Bright": 2, "Dim": 1, "Off": 0}
|
||||
LED_BRIGHTNESS_REVERSE_MAP = {val: key for key, val in LED_BRIGHTNESS_MAP.items()}
|
||||
LED_BRIGHTNESS_REVERSE_MAP_MIOT = {
|
||||
val: key for key, val in LED_BRIGHTNESS_MAP_MIOT.items()
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class SelectorType:
|
||||
"""Class that holds device specific info for a xiaomi aqara or humidifier selectors."""
|
||||
|
||||
name: str = None
|
||||
icon: str = None
|
||||
short_name: str = None
|
||||
options: list = None
|
||||
service: str = None
|
||||
|
||||
|
||||
SELECTOR_TYPES = {
|
||||
FEATURE_SET_LED_BRIGHTNESS: SelectorType(
|
||||
name="Led brightness",
|
||||
icon="mdi:brightness-6",
|
||||
short_name=ATTR_LED_BRIGHTNESS,
|
||||
options=["Bright", "Dim", "Off"],
|
||||
service=SERVICE_SET_LED_BRIGHTNESS,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the Selectors from a config entry."""
|
||||
if not config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
return
|
||||
|
||||
entities = []
|
||||
host = config_entry.data[CONF_HOST]
|
||||
token = config_entry.data[CONF_TOKEN]
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
if KEY_MIGRATE_ENTITY_NAME in hass.data[DOMAIN][config_entry.entry_id]:
|
||||
name = hass.data[DOMAIN][config_entry.entry_id][KEY_MIGRATE_ENTITY_NAME]
|
||||
else:
|
||||
name = config_entry.title
|
||||
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
|
||||
if model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]:
|
||||
entity_class = XiaomiAirHumidifierSelector
|
||||
elif model in [MODEL_AIRHUMIDIFIER_CA4]:
|
||||
entity_class = XiaomiAirHumidifierMiotSelector
|
||||
elif model in MODELS_HUMIDIFIER:
|
||||
entity_class = XiaomiAirHumidifierSelector
|
||||
else:
|
||||
return
|
||||
|
||||
for selector in SELECTOR_TYPES.values():
|
||||
entities.append(
|
||||
entity_class(
|
||||
f"{name} {selector.name}",
|
||||
device,
|
||||
config_entry,
|
||||
f"{selector.short_name}_{config_entry.unique_id}",
|
||||
selector,
|
||||
coordinator,
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class XiaomiSelector(XiaomiCoordinatedMiioEntity, SelectEntity):
|
||||
"""Representation of a generic Xiaomi attribute selector."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, selector, coordinator):
|
||||
"""Initialize the generic Xiaomi attribute selector."""
|
||||
super().__init__(name, device, entry, unique_id, coordinator)
|
||||
self._attr_icon = selector.icon
|
||||
self._controller = selector
|
||||
self._attr_options = self._controller.options
|
||||
|
||||
@staticmethod
|
||||
def _extract_value_from_attribute(state, attribute):
|
||||
value = getattr(state, attribute)
|
||||
if isinstance(value, Enum):
|
||||
return value.value
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class XiaomiAirHumidifierSelector(XiaomiSelector):
|
||||
"""Representation of a Xiaomi Air Humidifier selector."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, controller, coordinator):
|
||||
"""Initialize the plug switch."""
|
||||
super().__init__(name, device, entry, unique_id, controller, coordinator)
|
||||
self._current_led_brightness = self._extract_value_from_attribute(
|
||||
self.coordinator.data, self._controller.short_name
|
||||
)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
"""Fetch state from the device."""
|
||||
self._current_led_brightness = self._extract_value_from_attribute(
|
||||
self.coordinator.data, self._controller.short_name
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def current_option(self):
|
||||
"""Return the current option."""
|
||||
return self.led_brightness
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set an option of the miio device."""
|
||||
if option not in self.options:
|
||||
raise ValueError(
|
||||
f"Selection '{option}' is not a valid {self._controller.name}"
|
||||
)
|
||||
await self.async_set_led_brightness(option)
|
||||
|
||||
@property
|
||||
def led_brightness(self):
|
||||
"""Return the current led brightness."""
|
||||
return LED_BRIGHTNESS_REVERSE_MAP.get(self._current_led_brightness)
|
||||
|
||||
async def async_set_led_brightness(self, brightness: str):
|
||||
"""Set the led brightness."""
|
||||
if await self._try_command(
|
||||
"Setting the led brightness of the miio device failed.",
|
||||
self._device.set_led_brightness,
|
||||
AirhumidifierLedBrightness(LED_BRIGHTNESS_MAP[brightness]),
|
||||
):
|
||||
self._current_led_brightness = LED_BRIGHTNESS_MAP[brightness]
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class XiaomiAirHumidifierMiotSelector(XiaomiAirHumidifierSelector):
|
||||
"""Representation of a Xiaomi Air Humidifier (MiOT protocol) selector."""
|
||||
|
||||
@property
|
||||
def led_brightness(self):
|
||||
"""Return the current led brightness."""
|
||||
return LED_BRIGHTNESS_REVERSE_MAP_MIOT.get(self._current_led_brightness)
|
||||
|
||||
async def async_set_led_brightness(self, brightness: str):
|
||||
"""Set the led brightness."""
|
||||
if await self._try_command(
|
||||
"Setting the led brightness of the miio device failed.",
|
||||
self._device.set_led_brightness,
|
||||
AirhumidifierMiotLedBrightness(LED_BRIGHTNESS_MAP_MIOT[brightness]),
|
||||
):
|
||||
self._current_led_brightness = LED_BRIGHTNESS_MAP_MIOT[brightness]
|
||||
self.async_write_ha_state()
|
|
@ -1,5 +1,6 @@
|
|||
"""Support for Xiaomi Mi Air Quality Monitor (PM2.5)."""
|
||||
"""Support for Xiaomi Mi Air Quality Monitor (PM2.5) and Humidifier."""
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
from miio import AirQualityMonitor, DeviceException
|
||||
|
@ -20,6 +21,7 @@ from homeassistant.components.sensor import (
|
|||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL,
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_TOKEN,
|
||||
|
@ -35,8 +37,18 @@ from homeassistant.const import (
|
|||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR
|
||||
from .device import XiaomiMiioEntity
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
CONF_FLOW_TYPE,
|
||||
CONF_GATEWAY,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
KEY_COORDINATOR,
|
||||
KEY_DEVICE,
|
||||
KEY_MIGRATE_ENTITY_NAME,
|
||||
MODELS_HUMIDIFIER_MIOT,
|
||||
)
|
||||
from .device import XiaomiCoordinatedMiioEntity, XiaomiMiioEntity
|
||||
from .gateway import XiaomiGatewayDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -59,42 +71,69 @@ ATTR_NIGHT_MODE = "night_mode"
|
|||
ATTR_NIGHT_TIME_BEGIN = "night_time_begin"
|
||||
ATTR_NIGHT_TIME_END = "night_time_end"
|
||||
ATTR_SENSOR_STATE = "sensor_state"
|
||||
|
||||
SUCCESS = ["ok"]
|
||||
ATTR_WATER_LEVEL = "water_level"
|
||||
ATTR_HUMIDITY = "humidity"
|
||||
ATTR_ACTUAL_MOTOR_SPEED = "actual_speed"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SensorType:
|
||||
"""Class that holds device specific info for a xiaomi aqara sensor."""
|
||||
"""Class that holds device specific info for a xiaomi aqara or humidifier sensor."""
|
||||
|
||||
unit: str = None
|
||||
icon: str = None
|
||||
device_class: str = None
|
||||
state_class: str = None
|
||||
valid_min_value: float = None
|
||||
valid_max_value: float = None
|
||||
|
||||
|
||||
GATEWAY_SENSOR_TYPES = {
|
||||
SENSOR_TYPES = {
|
||||
"temperature": SensorType(
|
||||
unit=TEMP_CELSIUS,
|
||||
icon=None,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
"humidity": SensorType(
|
||||
unit=PERCENTAGE,
|
||||
icon=None,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
"pressure": SensorType(
|
||||
unit=PRESSURE_HPA,
|
||||
icon=None,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
"load_power": SensorType(
|
||||
unit=POWER_WATT, icon=None, device_class=DEVICE_CLASS_POWER
|
||||
unit=POWER_WATT,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
"water_level": SensorType(
|
||||
unit=PERCENTAGE,
|
||||
icon="mdi:water-check",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
valid_min_value=0.0,
|
||||
valid_max_value=100.0,
|
||||
),
|
||||
"actual_speed": SensorType(
|
||||
unit="rpm",
|
||||
icon="mdi:fast-forward",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
valid_min_value=200.0,
|
||||
valid_max_value=2000.0,
|
||||
),
|
||||
}
|
||||
|
||||
HUMIDIFIER_SENSORS = {
|
||||
ATTR_HUMIDITY: "humidity",
|
||||
ATTR_TEMPERATURE: "temperature",
|
||||
}
|
||||
|
||||
HUMIDIFIER_SENSORS_MIOT = {
|
||||
ATTR_HUMIDITY: "humidity",
|
||||
ATTR_TEMPERATURE: "temperature",
|
||||
ATTR_WATER_LEVEL: "water_level",
|
||||
ATTR_ACTUAL_MOTOR_SPEED: "actual_speed",
|
||||
}
|
||||
|
||||
|
||||
|
@ -135,7 +174,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
sub_devices = gateway.devices
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
for sub_device in sub_devices.values():
|
||||
sensor_variables = set(sub_device.status) & set(GATEWAY_SENSOR_TYPES)
|
||||
sensor_variables = set(sub_device.status) & set(SENSOR_TYPES)
|
||||
if sensor_variables:
|
||||
entities.extend(
|
||||
[
|
||||
|
@ -145,19 +184,90 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
for variable in sensor_variables
|
||||
]
|
||||
)
|
||||
|
||||
if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
elif config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
|
||||
host = config_entry.data[CONF_HOST]
|
||||
token = config_entry.data[CONF_TOKEN]
|
||||
name = config_entry.title
|
||||
unique_id = config_entry.unique_id
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
device = None
|
||||
sensors = []
|
||||
if KEY_MIGRATE_ENTITY_NAME in hass.data[DOMAIN][config_entry.entry_id]:
|
||||
name = hass.data[DOMAIN][config_entry.entry_id][KEY_MIGRATE_ENTITY_NAME]
|
||||
else:
|
||||
name = config_entry.title
|
||||
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
if model in MODELS_HUMIDIFIER_MIOT:
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
sensors = HUMIDIFIER_SENSORS_MIOT
|
||||
elif model.startswith("zhimi.humidifier."):
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
sensors = HUMIDIFIER_SENSORS
|
||||
else:
|
||||
unique_id = config_entry.unique_id
|
||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||
|
||||
device = AirQualityMonitor(host, token)
|
||||
entities.append(XiaomiAirQualityMonitor(name, device, config_entry, unique_id))
|
||||
device = AirQualityMonitor(host, token)
|
||||
entities.append(
|
||||
XiaomiAirQualityMonitor(name, device, config_entry, unique_id)
|
||||
)
|
||||
for sensor in sensors:
|
||||
entities.append(
|
||||
XiaomiGenericSensor(
|
||||
f"{name} {sensor.replace('_', ' ').title()}",
|
||||
device,
|
||||
config_entry,
|
||||
f"{sensor}_{config_entry.unique_id}",
|
||||
sensor,
|
||||
hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR],
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities, update_before_add=True)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class XiaomiGenericSensor(XiaomiCoordinatedMiioEntity, SensorEntity):
|
||||
"""Representation of a Xiaomi Humidifier sensor."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, attribute, coordinator):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(name, device, entry, unique_id, coordinator)
|
||||
|
||||
self._sensor_config = SENSOR_TYPES[attribute]
|
||||
self._attr_device_class = self._sensor_config.device_class
|
||||
self._attr_state_class = self._sensor_config.state_class
|
||||
self._attr_icon = self._sensor_config.icon
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_unit_of_measurement = self._sensor_config.unit
|
||||
self._device = device
|
||||
self._entry = entry
|
||||
self._attribute = attribute
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
self._state = self._extract_value_from_attribute(
|
||||
self.coordinator.data, self._attribute
|
||||
)
|
||||
if (
|
||||
self._sensor_config.valid_min_value
|
||||
and self._state < self._sensor_config.valid_min_value
|
||||
) or (
|
||||
self._sensor_config.valid_max_value
|
||||
and self._state > self._sensor_config.valid_max_value
|
||||
):
|
||||
return None
|
||||
return self._state
|
||||
|
||||
@staticmethod
|
||||
def _extract_value_from_attribute(state, attribute):
|
||||
value = getattr(state, attribute)
|
||||
if isinstance(value, Enum):
|
||||
return value.value
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity):
|
||||
|
@ -189,7 +299,7 @@ class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity):
|
|||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use for device if any."""
|
||||
"""Return the icon to use in the frontend."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
|
@ -247,22 +357,22 @@ class XiaomiGatewaySensor(XiaomiGatewayDevice, SensorEntity):
|
|||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return GATEWAY_SENSOR_TYPES[self._data_key].icon
|
||||
return SENSOR_TYPES[self._data_key].icon
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
return GATEWAY_SENSOR_TYPES[self._data_key].unit
|
||||
return SENSOR_TYPES[self._data_key].unit
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class of this entity."""
|
||||
return GATEWAY_SENSOR_TYPES[self._data_key].device_class
|
||||
return SENSOR_TYPES[self._data_key].device_class
|
||||
|
||||
@property
|
||||
def state_class(self):
|
||||
"""Return the state class of this entity."""
|
||||
return GATEWAY_SENSOR_TYPES[self._data_key].state_class
|
||||
return SENSOR_TYPES[self._data_key].state_class
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Support for Xiaomi Smart WiFi Socket and Smart Power Strip."""
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
|
@ -21,6 +23,7 @@ from homeassistant.const import (
|
|||
CONF_NAME,
|
||||
CONF_TOKEN,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import (
|
||||
|
@ -29,13 +32,31 @@ from .const import (
|
|||
CONF_GATEWAY,
|
||||
CONF_MODEL,
|
||||
DOMAIN,
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER,
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
|
||||
FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
|
||||
FEATURE_SET_BUZZER,
|
||||
FEATURE_SET_CHILD_LOCK,
|
||||
FEATURE_SET_CLEAN,
|
||||
FEATURE_SET_DRY,
|
||||
KEY_COORDINATOR,
|
||||
KEY_DEVICE,
|
||||
KEY_MIGRATE_ENTITY_NAME,
|
||||
MODEL_AIRHUMIDIFIER_CA1,
|
||||
MODEL_AIRHUMIDIFIER_CA4,
|
||||
MODEL_AIRHUMIDIFIER_CB1,
|
||||
MODELS_HUMIDIFIER,
|
||||
SERVICE_SET_BUZZER,
|
||||
SERVICE_SET_CHILD_LOCK,
|
||||
SERVICE_SET_CLEAN,
|
||||
SERVICE_SET_DRY,
|
||||
SERVICE_SET_POWER_MODE,
|
||||
SERVICE_SET_POWER_PRICE,
|
||||
SERVICE_SET_WIFI_LED_OFF,
|
||||
SERVICE_SET_WIFI_LED_ON,
|
||||
SUCCESS,
|
||||
)
|
||||
from .device import XiaomiMiioEntity
|
||||
from .device import XiaomiCoordinatedMiioEntity, XiaomiMiioEntity
|
||||
from .gateway import XiaomiGatewayDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -83,8 +104,10 @@ ATTR_POWER_MODE = "power_mode"
|
|||
ATTR_WIFI_LED = "wifi_led"
|
||||
ATTR_POWER_PRICE = "power_price"
|
||||
ATTR_PRICE = "price"
|
||||
|
||||
SUCCESS = ["ok"]
|
||||
ATTR_BUZZER = "buzzer"
|
||||
ATTR_CHILD_LOCK = "child_lock"
|
||||
ATTR_DRY = "dry"
|
||||
ATTR_CLEAN = "clean_mode"
|
||||
|
||||
FEATURE_SET_POWER_MODE = 1
|
||||
FEATURE_SET_WIFI_LED = 2
|
||||
|
@ -121,6 +144,62 @@ SERVICE_TO_METHOD = {
|
|||
"method": "async_set_power_price",
|
||||
"schema": SERVICE_SCHEMA_POWER_PRICE,
|
||||
},
|
||||
SERVICE_SET_BUZZER: {
|
||||
"method_on": "async_set_buzzer_on",
|
||||
"method_off": "async_set_buzzer_off",
|
||||
},
|
||||
SERVICE_SET_CHILD_LOCK: {
|
||||
"method_on": "async_set_child_lock_on",
|
||||
"method_off": "async_set_child_lock_off",
|
||||
},
|
||||
SERVICE_SET_DRY: {
|
||||
"method_on": "async_set_dry_on",
|
||||
"method_off": "async_set_dry_off",
|
||||
},
|
||||
SERVICE_SET_CLEAN: {
|
||||
"method_on": "async_set_clean_on",
|
||||
"method_off": "async_set_clean_off",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class SwitchType:
|
||||
"""Class that holds device specific info for a xiaomi aqara or humidifiers."""
|
||||
|
||||
name: str = None
|
||||
short_name: str = None
|
||||
icon: str = None
|
||||
service: str = None
|
||||
available_with_device_off: bool = True
|
||||
|
||||
|
||||
SWITCH_TYPES = {
|
||||
FEATURE_SET_BUZZER: SwitchType(
|
||||
name="Buzzer",
|
||||
icon="mdi:volume-high",
|
||||
short_name=ATTR_BUZZER,
|
||||
service=SERVICE_SET_BUZZER,
|
||||
),
|
||||
FEATURE_SET_CHILD_LOCK: SwitchType(
|
||||
name="Child Lock",
|
||||
icon="mdi:lock",
|
||||
short_name=ATTR_CHILD_LOCK,
|
||||
service=SERVICE_SET_CHILD_LOCK,
|
||||
),
|
||||
FEATURE_SET_DRY: SwitchType(
|
||||
name="Dry Mode",
|
||||
icon="mdi:hair-dryer",
|
||||
short_name=ATTR_DRY,
|
||||
service=SERVICE_SET_DRY,
|
||||
),
|
||||
FEATURE_SET_CLEAN: SwitchType(
|
||||
name="Clean Mode",
|
||||
icon="mdi:sparkles",
|
||||
short_name=ATTR_CLEAN,
|
||||
service=SERVICE_SET_CLEAN,
|
||||
available_with_device_off=False,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
@ -140,14 +219,63 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the switch from a config entry."""
|
||||
entities = []
|
||||
if (
|
||||
config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY
|
||||
or config_entry.data[CONF_MODEL] == "lumi.acpartner.v3"
|
||||
):
|
||||
await async_setup_other_entry(hass, config_entry, async_add_entities)
|
||||
else:
|
||||
await async_setup_coordinated_entry(hass, config_entry, async_add_entities)
|
||||
|
||||
|
||||
async def async_setup_coordinated_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the coordinated switch from a config entry."""
|
||||
entities = []
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
unique_id = config_entry.unique_id
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
if KEY_MIGRATE_ENTITY_NAME in hass.data[DOMAIN][config_entry.entry_id]:
|
||||
name = hass.data[DOMAIN][config_entry.entry_id][KEY_MIGRATE_ENTITY_NAME]
|
||||
else:
|
||||
name = config_entry.title
|
||||
|
||||
if DATA_KEY not in hass.data:
|
||||
hass.data[DATA_KEY] = {}
|
||||
|
||||
device_features = 0
|
||||
|
||||
if model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]:
|
||||
device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB
|
||||
elif model in [MODEL_AIRHUMIDIFIER_CA4]:
|
||||
device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA4
|
||||
elif model in MODELS_HUMIDIFIER:
|
||||
device_features = FEATURE_FLAGS_AIRHUMIDIFIER
|
||||
|
||||
for feature, switch in SWITCH_TYPES.items():
|
||||
if feature & device_features:
|
||||
entities.append(
|
||||
XiaomiGenericCoordinatedSwitch(
|
||||
f"{name} {switch.name}",
|
||||
device,
|
||||
config_entry,
|
||||
f"{switch.short_name}_{unique_id}",
|
||||
switch,
|
||||
coordinator,
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
async def async_setup_other_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the other type switch from a config entry."""
|
||||
entities = []
|
||||
host = config_entry.data[CONF_HOST]
|
||||
token = config_entry.data[CONF_TOKEN]
|
||||
name = config_entry.title
|
||||
model = config_entry.data[CONF_MODEL]
|
||||
unique_id = config_entry.unique_id
|
||||
|
||||
if config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY:
|
||||
gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY]
|
||||
# Gateway sub devices
|
||||
|
@ -256,7 +384,131 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
DOMAIN, plug_service, async_service_handler, schema=schema
|
||||
)
|
||||
|
||||
async_add_entities(entities, update_before_add=True)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity):
|
||||
"""Representation of a Xiaomi Plug Generic."""
|
||||
|
||||
def __init__(self, name, device, entry, unique_id, switch, coordinator):
|
||||
"""Initialize the plug switch."""
|
||||
super().__init__(name, device, entry, unique_id, coordinator)
|
||||
|
||||
self._attr_icon = switch.icon
|
||||
self._controller = switch
|
||||
self._attr_is_on = self._extract_value_from_attribute(
|
||||
self.coordinator.data, self._controller.short_name
|
||||
)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
"""Fetch state from the device."""
|
||||
# On state change the device doesn't provide the new state immediately.
|
||||
self._attr_is_on = self._extract_value_from_attribute(
|
||||
self.coordinator.data, self._controller.short_name
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return true when state is known."""
|
||||
if (
|
||||
super().available
|
||||
and not self.coordinator.data.is_on
|
||||
and not self._controller.available_with_device_off
|
||||
):
|
||||
return False
|
||||
return super().available
|
||||
|
||||
@staticmethod
|
||||
def _extract_value_from_attribute(state, attribute):
|
||||
value = getattr(state, attribute)
|
||||
if isinstance(value, Enum):
|
||||
return value.value
|
||||
|
||||
return value
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
"""Turn on an option of the miio device."""
|
||||
method = getattr(self, SERVICE_TO_METHOD[self._controller.service]["method_on"])
|
||||
if await method():
|
||||
# Write state back to avoid switch flips with a slow response
|
||||
self._attr_is_on = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn off an option of the miio device."""
|
||||
method = getattr(
|
||||
self, SERVICE_TO_METHOD[self._controller.service]["method_off"]
|
||||
)
|
||||
if await method():
|
||||
# Write state back to avoid switch flips with a slow response
|
||||
self._attr_is_on = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_buzzer_on(self) -> bool:
|
||||
"""Turn the buzzer on."""
|
||||
return await self._try_command(
|
||||
"Turning the buzzer of the miio device on failed.",
|
||||
self._device.set_buzzer,
|
||||
True,
|
||||
)
|
||||
|
||||
async def async_set_buzzer_off(self) -> bool:
|
||||
"""Turn the buzzer off."""
|
||||
return await self._try_command(
|
||||
"Turning the buzzer of the miio device off failed.",
|
||||
self._device.set_buzzer,
|
||||
False,
|
||||
)
|
||||
|
||||
async def async_set_child_lock_on(self) -> bool:
|
||||
"""Turn the child lock on."""
|
||||
return await self._try_command(
|
||||
"Turning the child lock of the miio device on failed.",
|
||||
self._device.set_child_lock,
|
||||
True,
|
||||
)
|
||||
|
||||
async def async_set_child_lock_off(self) -> bool:
|
||||
"""Turn the child lock off."""
|
||||
return await self._try_command(
|
||||
"Turning the child lock of the miio device off failed.",
|
||||
self._device.set_child_lock,
|
||||
False,
|
||||
)
|
||||
|
||||
async def async_set_dry_on(self) -> bool:
|
||||
"""Turn the dry mode on."""
|
||||
return await self._try_command(
|
||||
"Turning the dry mode of the miio device on failed.",
|
||||
self._device.set_dry,
|
||||
True,
|
||||
)
|
||||
|
||||
async def async_set_dry_off(self) -> bool:
|
||||
"""Turn the dry mode off."""
|
||||
return await self._try_command(
|
||||
"Turning the dry mode of the miio device off failed.",
|
||||
self._device.set_dry,
|
||||
False,
|
||||
)
|
||||
|
||||
async def async_set_clean_on(self) -> bool:
|
||||
"""Turn the dry mode on."""
|
||||
return await self._try_command(
|
||||
"Turning the clean mode of the miio device on failed.",
|
||||
self._device.set_clean_mode,
|
||||
True,
|
||||
)
|
||||
|
||||
async def async_set_clean_off(self) -> bool:
|
||||
"""Turn the dry mode off."""
|
||||
return await self._try_command(
|
||||
"Turning the clean mode of the miio device off failed.",
|
||||
self._device.set_clean_mode,
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
class XiaomiGatewaySwitch(XiaomiGatewayDevice, SwitchEntity):
|
||||
|
|
Loading…
Add table
Reference in a new issue