From d951ed4d68ac3030245e842ad18517dca40c1e66 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Tue, 6 Nov 2018 00:09:15 +0100 Subject: [PATCH] Add Xiaomi Smartmi Fresh Air System support (#18097) * Add Xiaomi Air Fresh VA2 support * Add LED property again (available now) --- homeassistant/components/fan/xiaomi_miio.py | 170 ++++++++++++++++++-- 1 file changed, 161 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 35bb92fa6103..a19174957328 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -11,10 +11,10 @@ import voluptuous as vol -from homeassistant.components.fan import ( - DOMAIN, PLATFORM_SCHEMA, SUPPORT_SET_SPEED, FanEntity) -from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN) +from homeassistant.components.fan import (FanEntity, PLATFORM_SCHEMA, + SUPPORT_SET_SPEED, DOMAIN, ) +from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_TOKEN, + ATTR_ENTITY_ID, ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv @@ -30,6 +30,7 @@ MODEL_AIRPURIFIER_V3 = 'zhimi.airpurifier.v3' MODEL_AIRHUMIDIFIER_V1 = 'zhimi.humidifier.v1' MODEL_AIRHUMIDIFIER_CA = 'zhimi.humidifier.ca1' +MODEL_AIRFRESH_VA2 = 'zhimi.airfresh.va2' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, @@ -48,7 +49,8 @@ 'zhimi.airpurifier.v5', 'zhimi.airpurifier.v6', 'zhimi.humidifier.v1', - 'zhimi.humidifier.ca1']), + 'zhimi.humidifier.ca1', + 'zhimi.airfresh.va2']), }) ATTR_MODEL = 'model' @@ -97,6 +99,9 @@ ATTR_DEPTH = 'depth' ATTR_DRY = 'dry' +# Air Fresh +ATTR_CO2 = 'co2' + # Map attributes to properties of the state object AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON = { ATTR_TEMPERATURE: 'temperature', @@ -166,31 +171,55 @@ ATTR_BUTTON_PRESSED: 'button_pressed', } -AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER = { +AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON = { ATTR_TEMPERATURE: 'temperature', ATTR_HUMIDITY: 'humidity', ATTR_MODE: 'mode', ATTR_BUZZER: 'buzzer', ATTR_CHILD_LOCK: 'child_lock', - ATTR_TRANS_LEVEL: 'trans_level', ATTR_TARGET_HUMIDITY: 'target_humidity', ATTR_LED_BRIGHTNESS: 'led_brightness', - ATTR_BUTTON_PRESSED: 'button_pressed', ATTR_USE_TIME: 'use_time', ATTR_HARDWARE_VERSION: 'hardware_version', } +AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER = { + **AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON, + ATTR_TRANS_LEVEL: 'trans_level', + ATTR_BUTTON_PRESSED: 'button_pressed', +} + AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_CA = { - **AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER, + **AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON, ATTR_SPEED: 'speed', ATTR_DEPTH: 'depth', ATTR_DRY: 'dry', } +AVAILABLE_ATTRIBUTES_AIRFRESH = { + ATTR_TEMPERATURE: 'temperature', + ATTR_AIR_QUALITY_INDEX: 'aqi', + ATTR_AVERAGE_AIR_QUALITY_INDEX: 'average_aqi', + ATTR_CO2: 'co2', + ATTR_HUMIDITY: 'humidity', + ATTR_MODE: 'mode', + ATTR_LED: 'led', + ATTR_LED_BRIGHTNESS: 'led_brightness', + ATTR_BUZZER: 'buzzer', + ATTR_CHILD_LOCK: 'child_lock', + ATTR_FILTER_LIFE: 'filter_life_remaining', + ATTR_FILTER_HOURS_USED: 'filter_hours_used', + ATTR_USE_TIME: 'use_time', + ATTR_MOTOR_SPEED: 'motor_speed', + ATTR_EXTRA_FEATURES: 'extra_features', +} + OPERATION_MODES_AIRPURIFIER = ['Auto', 'Silent', 'Favorite', 'Idle'] OPERATION_MODES_AIRPURIFIER_PRO = ['Auto', 'Silent', 'Favorite'] OPERATION_MODES_AIRPURIFIER_V3 = ['Auto', 'Silent', 'Favorite', 'Idle', 'Medium', 'High', 'Strong'] +OPERATION_MODES_AIRFRESH = ['Auto', 'Silent', 'Interval', 'Low', + 'Middle', 'Strong'] SUCCESS = ['ok'] @@ -234,6 +263,12 @@ FEATURE_FLAGS_AIRHUMIDIFIER_CA = (FEATURE_FLAGS_AIRHUMIDIFIER | FEATURE_SET_DRY) +FEATURE_FLAGS_AIRFRESH = (FEATURE_FLAGS_GENERIC | + FEATURE_SET_LED | + FEATURE_SET_LED_BRIGHTNESS | + FEATURE_RESET_FILTER | + FEATURE_SET_EXTRA_FEATURES) + SERVICE_SET_BUZZER_ON = 'xiaomi_miio_set_buzzer_on' SERVICE_SET_BUZZER_OFF = 'xiaomi_miio_set_buzzer_off' SERVICE_SET_LED_ON = 'xiaomi_miio_set_led_on' @@ -350,6 +385,10 @@ async def async_setup_platform(hass, config, async_add_entities, from miio import AirHumidifier air_humidifier = AirHumidifier(host, token, model=model) device = XiaomiAirHumidifier(name, air_humidifier, model, unique_id) + elif model.startswith('zhimi.airfresh.'): + from miio import AirFresh + air_fresh = AirFresh(host, token) + device = XiaomiAirFresh(name, air_fresh, model, unique_id) else: _LOGGER.error( 'Unsupported device found! Please create an issue at ' @@ -812,3 +851,116 @@ async def async_set_dry_off(self): await self._try_command( "Turning the dry mode of the miio device off failed.", self._device.set_dry, False) + + +class XiaomiAirFresh(XiaomiGenericDevice): + """Representation of a Xiaomi Air Fresh.""" + + def __init__(self, name, device, model, unique_id): + """Initialize the miio device.""" + super().__init__(name, device, model, unique_id) + + self._device_features = FEATURE_FLAGS_AIRFRESH + self._available_attributes = AVAILABLE_ATTRIBUTES_AIRFRESH + self._speed_list = OPERATION_MODES_AIRFRESH + self._state_attrs.update( + {attribute: None for attribute in self._available_attributes}) + + async def async_update(self): + """Fetch state from the device.""" + from miio import DeviceException + + # 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_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: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + + @property + def speed_list(self) -> list: + """Get the list of available speeds.""" + return self._speed_list + + @property + def speed(self): + """Return the current speed.""" + if self._state: + from miio.airfresh import OperationMode + + return OperationMode(self._state_attrs[ATTR_MODE]).name + + return None + + async def async_set_speed(self, speed: str) -> None: + """Set the speed of the fan.""" + if self.supported_features & SUPPORT_SET_SPEED == 0: + return + + from miio.airfresh import OperationMode + + _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, OperationMode[speed.title()]) + + async def async_set_led_on(self): + """Turn the led on.""" + if self._device_features & FEATURE_SET_LED == 0: + return + + await self._try_command( + "Turning the led of the miio device off failed.", + self._device.set_led, True) + + async def async_set_led_off(self): + """Turn the led off.""" + if self._device_features & FEATURE_SET_LED == 0: + return + + await self._try_command( + "Turning the led of the miio device off failed.", + self._device.set_led, False) + + async def async_set_led_brightness(self, brightness: int = 2): + """Set the led brightness.""" + if self._device_features & FEATURE_SET_LED_BRIGHTNESS == 0: + return + + from miio.airfresh import LedBrightness + + await self._try_command( + "Setting the led brightness of the miio device failed.", + self._device.set_led_brightness, LedBrightness(brightness)) + + async def async_set_extra_features(self, features: int = 1): + """Set the extra features.""" + if self._device_features & FEATURE_SET_EXTRA_FEATURES == 0: + return + + await self._try_command( + "Setting the extra features of the miio device failed.", + self._device.set_extra_features, features) + + async def async_reset_filter(self): + """Reset the filter lifetime and usage.""" + if self._device_features & FEATURE_RESET_FILTER == 0: + return + + await self._try_command( + "Resetting the filter lifetime of the miio device failed.", + self._device.reset_filter)