Add support for MJJSQ humidifiers for Xiaomi MIIO integration (#53807)

This commit is contained in:
Maciej Bieniek 2021-08-02 22:08:37 +02:00 committed by GitHub
parent d4cb819e1f
commit 938ec27a86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 234 additions and 105 deletions

View File

@ -3,7 +3,7 @@ from datetime import timedelta
import logging import logging
import async_timeout import async_timeout
from miio import AirHumidifier, AirHumidifierMiot, DeviceException from miio import AirHumidifier, AirHumidifierMiot, AirHumidifierMjjsq, DeviceException
from miio.gateway.gateway import GatewayException from miio.gateway.gateway import GatewayException
from homeassistant import config_entries, core from homeassistant import config_entries, core
@ -25,6 +25,7 @@ from .const import (
MODELS_FAN, MODELS_FAN,
MODELS_HUMIDIFIER, MODELS_HUMIDIFIER,
MODELS_HUMIDIFIER_MIOT, MODELS_HUMIDIFIER_MIOT,
MODELS_HUMIDIFIER_MJJSQ,
MODELS_LIGHT, MODELS_LIGHT,
MODELS_SWITCH, MODELS_SWITCH,
MODELS_VACUUM, MODELS_VACUUM,
@ -107,6 +108,8 @@ async def async_create_miio_device_and_coordinator(
if model in MODELS_HUMIDIFIER_MIOT: if model in MODELS_HUMIDIFIER_MIOT:
device = AirHumidifierMiot(host, token) device = AirHumidifierMiot(host, token)
elif model in MODELS_HUMIDIFIER_MJJSQ:
device = AirHumidifierMjjsq(host, token, model=model)
else: else:
device = AirHumidifier(host, token, model=model) device = AirHumidifier(host, token, model=model)
@ -123,7 +126,9 @@ async def async_create_miio_device_and_coordinator(
"""Fetch data from the device using async_add_executor_job.""" """Fetch data from the device using async_add_executor_job."""
try: try:
async with async_timeout.timeout(10): async with async_timeout.timeout(10):
return await hass.async_add_executor_job(device.status) state = await hass.async_add_executor_job(device.status)
_LOGGER.debug("Got new state: %s", state)
return state
except DeviceException as ex: except DeviceException as ex:
raise UpdateFailed(ex) from ex raise UpdateFailed(ex) from ex

View File

@ -52,6 +52,9 @@ MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1"
MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1"
MODEL_AIRHUMIDIFIER_CA4 = "zhimi.humidifier.ca4" MODEL_AIRHUMIDIFIER_CA4 = "zhimi.humidifier.ca4"
MODEL_AIRHUMIDIFIER_CB1 = "zhimi.humidifier.cb1" MODEL_AIRHUMIDIFIER_CB1 = "zhimi.humidifier.cb1"
MODEL_AIRHUMIDIFIER_JSQ = "deerma.humidifier.jsq"
MODEL_AIRHUMIDIFIER_JSQ1 = "deerma.humidifier.jsq1"
MODEL_AIRHUMIDIFIER_MJJSQ = "deerma.humidifier.mjjsq"
MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2"
@ -60,7 +63,6 @@ MODELS_PURIFIER_MIOT = [
MODEL_AIRPURIFIER_3H, MODEL_AIRPURIFIER_3H,
MODEL_AIRPURIFIER_PROH, MODEL_AIRPURIFIER_PROH,
] ]
MODELS_HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4]
MODELS_FAN_MIIO = [ MODELS_FAN_MIIO = [
MODEL_AIRPURIFIER_V1, MODEL_AIRPURIFIER_V1,
MODEL_AIRPURIFIER_V2, MODEL_AIRPURIFIER_V2,
@ -83,6 +85,12 @@ MODELS_HUMIDIFIER_MIIO = [
MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CA1,
MODEL_AIRHUMIDIFIER_CB1, MODEL_AIRHUMIDIFIER_CB1,
] ]
MODELS_HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4]
MODELS_HUMIDIFIER_MJJSQ = [
MODEL_AIRHUMIDIFIER_JSQ,
MODEL_AIRHUMIDIFIER_JSQ1,
MODEL_AIRHUMIDIFIER_MJJSQ,
]
# AirQuality Models # AirQuality Models
MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1"
@ -117,7 +125,9 @@ MODELS_SWITCH = [
"chuangmi.plug.hmi206", "chuangmi.plug.hmi206",
] ]
MODELS_FAN = MODELS_FAN_MIIO + MODELS_PURIFIER_MIOT MODELS_FAN = MODELS_FAN_MIIO + MODELS_PURIFIER_MIOT
MODELS_HUMIDIFIER = MODELS_HUMIDIFIER_MIOT + MODELS_HUMIDIFIER_MIIO MODELS_HUMIDIFIER = (
MODELS_HUMIDIFIER_MIOT + MODELS_HUMIDIFIER_MIIO + MODELS_HUMIDIFIER_MJJSQ
)
MODELS_LIGHT = ( MODELS_LIGHT = (
MODELS_LIGHT_EYECARE MODELS_LIGHT_EYECARE
+ MODELS_LIGHT_CEILING + MODELS_LIGHT_CEILING
@ -146,15 +156,12 @@ MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY
# Fan/Humidifier Services # Fan/Humidifier Services
SERVICE_SET_BUZZER_ON = "fan_set_buzzer_on" SERVICE_SET_BUZZER_ON = "fan_set_buzzer_on"
SERVICE_SET_BUZZER_OFF = "fan_set_buzzer_off" SERVICE_SET_BUZZER_OFF = "fan_set_buzzer_off"
SERVICE_SET_BUZZER = "set_buzzer"
SERVICE_SET_CLEAN = "set_clean"
SERVICE_SET_FAN_LED_ON = "fan_set_led_on" SERVICE_SET_FAN_LED_ON = "fan_set_led_on"
SERVICE_SET_FAN_LED_OFF = "fan_set_led_off" SERVICE_SET_FAN_LED_OFF = "fan_set_led_off"
SERVICE_SET_FAN_LED = "fan_set_led" SERVICE_SET_FAN_LED = "fan_set_led"
SERVICE_SET_LED_BRIGHTNESS = "set_led_brightness" SERVICE_SET_LED_BRIGHTNESS = "set_led_brightness"
SERVICE_SET_CHILD_LOCK_ON = "fan_set_child_lock_on" SERVICE_SET_CHILD_LOCK_ON = "fan_set_child_lock_on"
SERVICE_SET_CHILD_LOCK_OFF = "fan_set_child_lock_off" 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_LED_BRIGHTNESS = "fan_set_led_brightness"
SERVICE_SET_FAVORITE_LEVEL = "fan_set_favorite_level" SERVICE_SET_FAVORITE_LEVEL = "fan_set_favorite_level"
SERVICE_SET_FAN_LEVEL = "fan_set_fan_level" SERVICE_SET_FAN_LEVEL = "fan_set_fan_level"
@ -270,6 +277,10 @@ FEATURE_FLAGS_AIRHUMIDIFIER = (
FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB = FEATURE_FLAGS_AIRHUMIDIFIER | FEATURE_SET_DRY FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB = FEATURE_FLAGS_AIRHUMIDIFIER | FEATURE_SET_DRY
FEATURE_FLAGS_AIRHUMIDIFIER_MJSSQ = (
FEATURE_SET_BUZZER | FEATURE_SET_LED | FEATURE_SET_TARGET_HUMIDITY
)
FEATURE_FLAGS_AIRHUMIDIFIER_CA4 = ( FEATURE_FLAGS_AIRHUMIDIFIER_CA4 = (
FEATURE_SET_BUZZER FEATURE_SET_BUZZER
| FEATURE_SET_CHILD_LOCK | FEATURE_SET_CHILD_LOCK

View File

@ -5,6 +5,7 @@ import math
from miio.airhumidifier import OperationMode as AirhumidifierOperationMode from miio.airhumidifier import OperationMode as AirhumidifierOperationMode
from miio.airhumidifier_miot import OperationMode as AirhumidifierMiotOperationMode from miio.airhumidifier_miot import OperationMode as AirhumidifierMiotOperationMode
from miio.airhumidifier_mjjsq import OperationMode as AirhumidifierMjjsqOperationMode
from homeassistant.components.humidifier import HumidifierEntity from homeassistant.components.humidifier import HumidifierEntity
from homeassistant.components.humidifier.const import ( from homeassistant.components.humidifier.const import (
@ -28,6 +29,7 @@ from .const import (
MODEL_AIRHUMIDIFIER_CA4, MODEL_AIRHUMIDIFIER_CA4,
MODEL_AIRHUMIDIFIER_CB1, MODEL_AIRHUMIDIFIER_CB1,
MODELS_HUMIDIFIER_MIOT, MODELS_HUMIDIFIER_MIOT,
MODELS_HUMIDIFIER_MJJSQ,
) )
from .device import XiaomiCoordinatedMiioEntity from .device import XiaomiCoordinatedMiioEntity
@ -41,6 +43,23 @@ AVAILABLE_ATTRIBUTES = {
ATTR_TARGET_HUMIDITY: "target_humidity", ATTR_TARGET_HUMIDITY: "target_humidity",
} }
AVAILABLE_MODES_CA1_CB1 = [
mode.name
for mode in AirhumidifierOperationMode
if mode is not AirhumidifierOperationMode.Strong
]
AVAILABLE_MODES_CA4 = [mode.name for mode in AirhumidifierMiotOperationMode]
AVAILABLE_MODES_MJJSQ = [
mode.name
for mode in AirhumidifierMjjsqOperationMode
if mode is not AirhumidifierMjjsqOperationMode.WetAndProtect
]
AVAILABLE_MODES_OTHER = [
mode.name
for mode in AirhumidifierOperationMode
if mode is not AirhumidifierOperationMode.Auto
]
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Humidifier from a config entry.""" """Set up the Humidifier from a config entry."""
@ -62,6 +81,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
unique_id, unique_id,
coordinator, coordinator,
) )
elif model in MODELS_HUMIDIFIER_MJJSQ:
air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
entity = XiaomiAirHumidifierMjjsq(
name,
air_humidifier,
config_entry,
unique_id,
coordinator,
)
else: else:
air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
entity = XiaomiAirHumidifier( entity = XiaomiAirHumidifier(
@ -169,28 +197,22 @@ class XiaomiAirHumidifier(XiaomiGenericHumidifier, HumidifierEntity):
"""Initialize the plug switch.""" """Initialize the plug switch."""
super().__init__(name, device, entry, unique_id, coordinator) super().__init__(name, device, entry, unique_id, coordinator)
if self._model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]: if self._model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]:
self._available_modes = [] self._available_modes = AVAILABLE_MODES_CA1_CB1
self._available_modes = [
mode.name
for mode in AirhumidifierOperationMode
if mode is not AirhumidifierOperationMode.Strong
]
self._min_humidity = 30 self._min_humidity = 30
self._max_humidity = 80 self._max_humidity = 80
self._humidity_steps = 10 self._humidity_steps = 10
elif self._model in [MODEL_AIRHUMIDIFIER_CA4]: elif self._model in [MODEL_AIRHUMIDIFIER_CA4]:
self._available_modes = [ self._available_modes = AVAILABLE_MODES_CA4
mode.name for mode in AirhumidifierMiotOperationMode
]
self._min_humidity = 30 self._min_humidity = 30
self._max_humidity = 80 self._max_humidity = 80
self._humidity_steps = 100 self._humidity_steps = 100
elif self._model in MODELS_HUMIDIFIER_MJJSQ:
self._available_modes = AVAILABLE_MODES_MJJSQ
self._min_humidity = 30
self._max_humidity = 80
self._humidity_steps = 10
else: else:
self._available_modes = [ self._available_modes = AVAILABLE_MODES_OTHER
mode.name
for mode in AirhumidifierOperationMode
if mode is not AirhumidifierOperationMode.Auto
]
self._min_humidity = 30 self._min_humidity = 30
self._max_humidity = 80 self._max_humidity = 80
self._humidity_steps = 10 self._humidity_steps = 10
@ -364,3 +386,75 @@ class XiaomiAirHumidifierMiot(XiaomiAirHumidifier):
): ):
self._mode = self.REVERSE_MODE_MAPPING[mode].value self._mode = self.REVERSE_MODE_MAPPING[mode].value
self.async_write_ha_state() self.async_write_ha_state()
class XiaomiAirHumidifierMjjsq(XiaomiAirHumidifier):
"""Representation of a Xiaomi Air MJJSQ Humidifier."""
MODE_MAPPING = {
"Low": AirhumidifierMjjsqOperationMode.Low,
"Medium": AirhumidifierMjjsqOperationMode.Medium,
"High": AirhumidifierMjjsqOperationMode.High,
"Humidity": AirhumidifierMjjsqOperationMode.Humidity,
}
@property
def mode(self):
"""Return the current mode."""
return AirhumidifierMjjsqOperationMode(self._mode).name
@property
def target_humidity(self):
"""Return the target humidity."""
if self._state:
if (
AirhumidifierMjjsqOperationMode(self._mode)
== AirhumidifierMjjsqOperationMode.Humidity
):
return self._target_humidity
return None
async def async_set_humidity(self, humidity: int) -> None:
"""Set the target humidity of the humidifier and set the mode to Humidity."""
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 AirhumidifierMjjsqOperationMode(self._attributes[ATTR_MODE])
== AirhumidifierMjjsqOperationMode.Humidity
):
self.async_write_ha_state()
return
_LOGGER.debug("Setting the operation mode to: Humidity")
if await self._try_command(
"Setting operation mode of the miio device to MODE_HUMIDITY failed.",
self._device.set_mode,
AirhumidifierMjjsqOperationMode.Humidity,
):
self._mode = 3
self.async_write_ha_state()
async def async_set_mode(self, mode: str) -> None:
"""Set the mode of the fan."""
if mode not in self.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.MODE_MAPPING[mode],
):
self._mode = self.MODE_MAPPING[mode].value
self.async_write_ha_state()

View File

@ -16,10 +16,8 @@ from .const import (
FEATURE_SET_LED_BRIGHTNESS, FEATURE_SET_LED_BRIGHTNESS,
KEY_COORDINATOR, KEY_COORDINATOR,
KEY_DEVICE, KEY_DEVICE,
MODEL_AIRHUMIDIFIER_CA1, MODELS_HUMIDIFIER_MIIO,
MODEL_AIRHUMIDIFIER_CA4, MODELS_HUMIDIFIER_MIOT,
MODEL_AIRHUMIDIFIER_CB1,
MODELS_HUMIDIFIER,
SERVICE_SET_LED_BRIGHTNESS, SERVICE_SET_LED_BRIGHTNESS,
) )
from .device import XiaomiCoordinatedMiioEntity from .device import XiaomiCoordinatedMiioEntity
@ -65,18 +63,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = [] entities = []
model = config_entry.data[CONF_MODEL] model = config_entry.data[CONF_MODEL]
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
if model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]: if model in MODELS_HUMIDIFIER_MIIO:
entity_class = XiaomiAirHumidifierSelector entity_class = XiaomiAirHumidifierSelector
elif model in [MODEL_AIRHUMIDIFIER_CA4]: elif model in MODELS_HUMIDIFIER_MIOT:
entity_class = XiaomiAirHumidifierMiotSelector entity_class = XiaomiAirHumidifierMiotSelector
elif model in MODELS_HUMIDIFIER:
entity_class = XiaomiAirHumidifierSelector
else: else:
return return
for selector in SELECTOR_TYPES.values(): selector = SELECTOR_TYPES[FEATURE_SET_LED_BRIGHTNESS]
entities.append( entities.append(
entity_class( entity_class(
f"{config_entry.title} {selector.name}", f"{config_entry.title} {selector.name}",
@ -84,7 +79,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
config_entry, config_entry,
f"{selector.short_name}_{config_entry.unique_id}", f"{selector.short_name}_{config_entry.unique_id}",
selector, selector,
coordinator, hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR],
) )
) )

View File

@ -46,6 +46,7 @@ from .const import (
KEY_COORDINATOR, KEY_COORDINATOR,
KEY_DEVICE, KEY_DEVICE,
MODELS_HUMIDIFIER_MIOT, MODELS_HUMIDIFIER_MIOT,
MODELS_HUMIDIFIER_MJJSQ,
) )
from .device import XiaomiCoordinatedMiioEntity, XiaomiMiioEntity from .device import XiaomiCoordinatedMiioEntity, XiaomiMiioEntity
from .gateway import XiaomiGatewayDevice from .gateway import XiaomiGatewayDevice
@ -135,6 +136,11 @@ HUMIDIFIER_SENSORS_MIOT = {
ATTR_ACTUAL_MOTOR_SPEED: "actual_speed", ATTR_ACTUAL_MOTOR_SPEED: "actual_speed",
} }
HUMIDIFIER_SENSORS_MJJSQ = {
ATTR_HUMIDITY: "humidity",
ATTR_TEMPERATURE: "temperature",
}
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Import Miio configuration from YAML.""" """Import Miio configuration from YAML."""
@ -191,11 +197,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
sensors = [] sensors = []
if model in MODELS_HUMIDIFIER_MIOT: if model in MODELS_HUMIDIFIER_MIOT:
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
sensors = HUMIDIFIER_SENSORS_MIOT sensors = HUMIDIFIER_SENSORS_MIOT
elif model in MODELS_HUMIDIFIER_MJJSQ:
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
sensors = HUMIDIFIER_SENSORS_MJJSQ
elif model.startswith("zhimi.humidifier."): elif model.startswith("zhimi.humidifier."):
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
sensors = HUMIDIFIER_SENSORS sensors = HUMIDIFIER_SENSORS
else: else:
unique_id = config_entry.unique_id unique_id = config_entry.unique_id

View File

@ -1,4 +1,6 @@
"""Support for Xiaomi Smart WiFi Socket and Smart Power Strip.""" """Support for Xiaomi Smart WiFi Socket and Smart Power Strip."""
from __future__ import annotations
import asyncio import asyncio
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
@ -13,6 +15,7 @@ from homeassistant.components.switch import (
DEVICE_CLASS_SWITCH, DEVICE_CLASS_SWITCH,
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
SwitchEntity, SwitchEntity,
SwitchEntityDescription,
) )
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
@ -35,20 +38,19 @@ from .const import (
FEATURE_FLAGS_AIRHUMIDIFIER, FEATURE_FLAGS_AIRHUMIDIFIER,
FEATURE_FLAGS_AIRHUMIDIFIER_CA4, FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB, FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
FEATURE_FLAGS_AIRHUMIDIFIER_MJSSQ,
FEATURE_SET_BUZZER, FEATURE_SET_BUZZER,
FEATURE_SET_CHILD_LOCK, FEATURE_SET_CHILD_LOCK,
FEATURE_SET_CLEAN, FEATURE_SET_CLEAN,
FEATURE_SET_DRY, FEATURE_SET_DRY,
FEATURE_SET_LED,
KEY_COORDINATOR, KEY_COORDINATOR,
KEY_DEVICE, KEY_DEVICE,
MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CA1,
MODEL_AIRHUMIDIFIER_CA4, MODEL_AIRHUMIDIFIER_CA4,
MODEL_AIRHUMIDIFIER_CB1, MODEL_AIRHUMIDIFIER_CB1,
MODELS_HUMIDIFIER, MODELS_HUMIDIFIER,
SERVICE_SET_BUZZER, MODELS_HUMIDIFIER_MJJSQ,
SERVICE_SET_CHILD_LOCK,
SERVICE_SET_CLEAN,
SERVICE_SET_DRY,
SERVICE_SET_POWER_MODE, SERVICE_SET_POWER_MODE,
SERVICE_SET_POWER_PRICE, SERVICE_SET_POWER_PRICE,
SERVICE_SET_WIFI_LED_OFF, SERVICE_SET_WIFI_LED_OFF,
@ -96,17 +98,18 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
} }
) )
ATTR_POWER = "power"
ATTR_LOAD_POWER = "load_power"
ATTR_MODEL = "model"
ATTR_POWER_MODE = "power_mode"
ATTR_WIFI_LED = "wifi_led"
ATTR_POWER_PRICE = "power_price"
ATTR_PRICE = "price"
ATTR_BUZZER = "buzzer" ATTR_BUZZER = "buzzer"
ATTR_CHILD_LOCK = "child_lock" ATTR_CHILD_LOCK = "child_lock"
ATTR_DRY = "dry"
ATTR_CLEAN = "clean_mode" ATTR_CLEAN = "clean_mode"
ATTR_DRY = "dry"
ATTR_LED = "led"
ATTR_LOAD_POWER = "load_power"
ATTR_MODEL = "model"
ATTR_POWER = "power"
ATTR_POWER_MODE = "power_mode"
ATTR_POWER_PRICE = "power_price"
ATTR_PRICE = "price"
ATTR_WIFI_LED = "wifi_led"
FEATURE_SET_POWER_MODE = 1 FEATURE_SET_POWER_MODE = 1
FEATURE_SET_WIFI_LED = 2 FEATURE_SET_WIFI_LED = 2
@ -143,63 +146,62 @@ SERVICE_TO_METHOD = {
"method": "async_set_power_price", "method": "async_set_power_price",
"schema": SERVICE_SCHEMA_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 @dataclass
class SwitchType: class XiaomiMiioSwitchDescription(SwitchEntityDescription):
"""Class that holds device specific info for a xiaomi aqara or humidifiers.""" """A class that describes switch entities."""
name: str = None feature: int | None = None
short_name: str = None method_on: str | None = None
icon: str = None method_off: str | None = None
service: str = None
available_with_device_off: bool = True available_with_device_off: bool = True
SWITCH_TYPES = { SWITCH_TYPES = (
FEATURE_SET_BUZZER: SwitchType( XiaomiMiioSwitchDescription(
key=ATTR_BUZZER,
feature=FEATURE_SET_BUZZER,
name="Buzzer", name="Buzzer",
icon="mdi:volume-high", icon="mdi:volume-high",
short_name=ATTR_BUZZER, method_on="async_set_buzzer_on",
service=SERVICE_SET_BUZZER, method_off="async_set_buzzer_off",
), ),
FEATURE_SET_CHILD_LOCK: SwitchType( XiaomiMiioSwitchDescription(
key=ATTR_CHILD_LOCK,
feature=FEATURE_SET_CHILD_LOCK,
name="Child Lock", name="Child Lock",
icon="mdi:lock", icon="mdi:lock",
short_name=ATTR_CHILD_LOCK, method_on="async_set_child_lock_on",
service=SERVICE_SET_CHILD_LOCK, method_off="async_set_child_lock_off",
), ),
FEATURE_SET_DRY: SwitchType( XiaomiMiioSwitchDescription(
key=ATTR_DRY,
feature=FEATURE_SET_DRY,
name="Dry Mode", name="Dry Mode",
icon="mdi:hair-dryer", icon="mdi:hair-dryer",
short_name=ATTR_DRY, method_on="async_set_dry_on",
service=SERVICE_SET_DRY, method_off="async_set_dry_off",
), ),
FEATURE_SET_CLEAN: SwitchType( XiaomiMiioSwitchDescription(
key=ATTR_CLEAN,
feature=FEATURE_SET_CLEAN,
name="Clean Mode", name="Clean Mode",
icon="mdi:sparkles", icon="mdi:sparkles",
short_name=ATTR_CLEAN, method_on="async_set_clean_on",
service=SERVICE_SET_CLEAN, method_off="async_set_clean_off",
available_with_device_off=False, available_with_device_off=False,
), ),
} XiaomiMiioSwitchDescription(
key=ATTR_LED,
feature=FEATURE_SET_LED,
name="Led",
icon="mdi:led-outline",
method_on="async_set_led_on",
method_off="async_set_led_off",
),
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
@ -241,19 +243,21 @@ async def async_setup_coordinated_entry(hass, config_entry, async_add_entities):
device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB
elif model in [MODEL_AIRHUMIDIFIER_CA4]: elif model in [MODEL_AIRHUMIDIFIER_CA4]:
device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA4 device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA4
elif model in MODELS_HUMIDIFIER_MJJSQ:
device_features = FEATURE_FLAGS_AIRHUMIDIFIER_MJSSQ
elif model in MODELS_HUMIDIFIER: elif model in MODELS_HUMIDIFIER:
device_features = FEATURE_FLAGS_AIRHUMIDIFIER device_features = FEATURE_FLAGS_AIRHUMIDIFIER
for feature, switch in SWITCH_TYPES.items(): for description in SWITCH_TYPES:
if feature & device_features: if description.feature & device_features:
entities.append( entities.append(
XiaomiGenericCoordinatedSwitch( XiaomiGenericCoordinatedSwitch(
f"{config_entry.title} {switch.name}", f"{config_entry.title} {description.name}",
device, device,
config_entry, config_entry,
f"{switch.short_name}_{unique_id}", f"{description.key}_{unique_id}",
switch,
coordinator, coordinator,
description,
) )
) )
@ -382,22 +386,21 @@ async def async_setup_other_entry(hass, config_entry, async_add_entities):
class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity): class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity):
"""Representation of a Xiaomi Plug Generic.""" """Representation of a Xiaomi Plug Generic."""
def __init__(self, name, device, entry, unique_id, switch, coordinator): def __init__(self, name, device, entry, unique_id, coordinator, description):
"""Initialize the plug switch.""" """Initialize the plug switch."""
super().__init__(name, device, entry, unique_id, coordinator) 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._attr_is_on = self._extract_value_from_attribute(
self.coordinator.data, self._controller.short_name self.coordinator.data, description.key
) )
self.entity_description = description
@callback @callback
def _handle_coordinator_update(self): def _handle_coordinator_update(self):
"""Fetch state from the device.""" """Fetch state from the device."""
# On state change the device doesn't provide the new state immediately. # On state change the device doesn't provide the new state immediately.
self._attr_is_on = self._extract_value_from_attribute( self._attr_is_on = self._extract_value_from_attribute(
self.coordinator.data, self._controller.short_name self.coordinator.data, self.entity_description.key
) )
self.async_write_ha_state() self.async_write_ha_state()
@ -407,7 +410,7 @@ class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity):
if ( if (
super().available super().available
and not self.coordinator.data.is_on and not self.coordinator.data.is_on
and not self._controller.available_with_device_off and not self.entity_description.available_with_device_off
): ):
return False return False
return super().available return super().available
@ -422,7 +425,7 @@ class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity):
async def async_turn_on(self, **kwargs) -> None: async def async_turn_on(self, **kwargs) -> None:
"""Turn on an option of the miio device.""" """Turn on an option of the miio device."""
method = getattr(self, SERVICE_TO_METHOD[self._controller.service]["method_on"]) method = getattr(self, self.entity_description.method_on)
if await method(): if await method():
# Write state back to avoid switch flips with a slow response # Write state back to avoid switch flips with a slow response
self._attr_is_on = True self._attr_is_on = True
@ -430,9 +433,7 @@ class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity):
async def async_turn_off(self, **kwargs) -> None: async def async_turn_off(self, **kwargs) -> None:
"""Turn off an option of the miio device.""" """Turn off an option of the miio device."""
method = getattr( method = getattr(self, self.entity_description.method_off)
self, SERVICE_TO_METHOD[self._controller.service]["method_off"]
)
if await method(): if await method():
# Write state back to avoid switch flips with a slow response # Write state back to avoid switch flips with a slow response
self._attr_is_on = False self._attr_is_on = False
@ -502,6 +503,22 @@ class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity):
False, False,
) )
async def async_set_led_on(self) -> bool:
"""Turn the led on."""
return await self._try_command(
"Turning the led of the miio device on failed.",
self._device.set_led,
True,
)
async def async_set_led_off(self) -> bool:
"""Turn the led off."""
return await self._try_command(
"Turning the led of the miio device off failed.",
self._device.set_led,
False,
)
class XiaomiGatewaySwitch(XiaomiGatewayDevice, SwitchEntity): class XiaomiGatewaySwitch(XiaomiGatewayDevice, SwitchEntity):
"""Representation of a XiaomiGatewaySwitch.""" """Representation of a XiaomiGatewaySwitch."""