From 724574d336598034ca51c1dc107e4394e778a6cb Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 11 Mar 2021 11:48:48 +0100 Subject: [PATCH] Add Xiaomi Miio sensor config flow (#46964) * add config flow * fix styling * Add air_quality platform * fix imports * fix black * Update homeassistant/components/xiaomi_miio/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/air_quality.py Co-authored-by: Martin Hjelmare * process revieuw feedback * remove unused import * fix formatting Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 5 + .../components/xiaomi_miio/air_quality.py | 95 +++++++++---------- homeassistant/components/xiaomi_miio/const.py | 17 ++-- .../components/xiaomi_miio/sensor.py | 83 +++++++--------- 4 files changed, 94 insertions(+), 106 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 139ac017f66..5b67d21b481 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -16,6 +16,7 @@ from .const import ( CONF_MODEL, DOMAIN, KEY_COORDINATOR, + MODELS_AIR_MONITOR, MODELS_FAN, MODELS_SWITCH, MODELS_VACUUM, @@ -28,6 +29,7 @@ GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] VACUUM_PLATFORMS = ["vacuum"] +AIR_MONITOR_PLATFORMS = ["air_quality", "sensor"] async def async_setup(hass: core.HomeAssistant, config: dict): @@ -129,6 +131,9 @@ async def async_setup_device_entry( for vacuum_model in MODELS_VACUUM: if model.startswith(vacuum_model): platforms = VACUUM_PLATFORMS + for air_monitor_model in MODELS_AIR_MONITOR: + if model.startswith(air_monitor_model): + platforms = AIR_MONITOR_PLATFORMS if not platforms: return False diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index 1e1e1b58632..b278a60bd48 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -1,19 +1,24 @@ """Support for Xiaomi Mi Air Quality Monitor (PM2.5).""" import logging -from miio import AirQualityMonitor, Device, DeviceException +from miio import AirQualityMonitor, DeviceException import voluptuous as vol from homeassistant.components.air_quality import PLATFORM_SCHEMA, AirQualityEntity +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN -from homeassistant.exceptions import NoEntitySpecifiedError, PlatformNotReady import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, + CONF_MODEL, + DOMAIN, MODEL_AIRQUALITYMONITOR_B1, MODEL_AIRQUALITYMONITOR_S1, MODEL_AIRQUALITYMONITOR_V1, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) @@ -41,52 +46,54 @@ PROP_TO_ATTR = { async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the sensor from config.""" - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - - miio_device = Device(host, token) - - try: - device_info = await hass.async_add_executor_job(miio_device.info) - except DeviceException as ex: - raise PlatformNotReady from ex - - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.debug( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Air Quality via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) ) - device = AirQualityMonitor(host, token, model=model) - if model == MODEL_AIRQUALITYMONITOR_S1: - entity = AirMonitorS1(name, device, unique_id) - elif model == MODEL_AIRQUALITYMONITOR_B1: - entity = AirMonitorB1(name, device, unique_id) - elif model == MODEL_AIRQUALITYMONITOR_V1: - entity = AirMonitorV1(name, device, unique_id) - else: - raise NoEntitySpecifiedError(f"Not support for entity {unique_id}") +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Xiaomi Air Quality from a config entry.""" + entities = [] - async_add_entities([entity], update_before_add=True) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + 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 + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + device = AirQualityMonitor(host, token, model=model) + + if model == MODEL_AIRQUALITYMONITOR_S1: + entities.append(AirMonitorS1(name, device, config_entry, unique_id)) + elif model == MODEL_AIRQUALITYMONITOR_B1: + entities.append(AirMonitorB1(name, device, config_entry, unique_id)) + elif model == MODEL_AIRQUALITYMONITOR_V1: + entities.append(AirMonitorV1(name, device, config_entry, unique_id)) + else: + _LOGGER.warning("AirQualityMonitor model '%s' is not yet supported", model) + + async_add_entities(entities, update_before_add=True) -class AirMonitorB1(AirQualityEntity): +class AirMonitorB1(XiaomiMiioEntity, AirQualityEntity): """Air Quality class for Xiaomi cgllc.airmonitor.b1 device.""" - def __init__(self, name, device, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the entity.""" - self._name = name - self._device = device - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) + self._icon = "mdi:cloud" self._available = None self._air_quality_index = None @@ -112,11 +119,6 @@ class AirMonitorB1(AirQualityEntity): self._available = False _LOGGER.error("Got exception while fetching the state: %s", ex) - @property - def name(self): - """Return the name of this entity, if any.""" - return self._name - @property def icon(self): """Return the icon to use for device if any.""" @@ -127,11 +129,6 @@ class AirMonitorB1(AirQualityEntity): """Return true when state is known.""" return self._available - @property - def unique_id(self): - """Return the unique ID.""" - return self._unique_id - @property def air_quality_index(self): """Return the Air Quality Index (AQI).""" diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index da422986900..6bf2e2f69d1 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -55,6 +55,11 @@ MODELS_FAN_MIIO = [ MODEL_AIRFRESH_VA2, ] +# AirQuality Models +MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" +MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" +MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" + # Model lists MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ @@ -71,8 +76,13 @@ MODELS_SWITCH = [ ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] +MODELS_AIR_MONITOR = [ + MODEL_AIRQUALITYMONITOR_V1, + MODEL_AIRQUALITYMONITOR_B1, + MODEL_AIRQUALITYMONITOR_S1, +] -MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_FAN + MODELS_VACUUM +MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services @@ -126,8 +136,3 @@ SERVICE_STOP_REMOTE_CONTROL = "vacuum_remote_control_stop" SERVICE_CLEAN_SEGMENT = "vacuum_clean_segment" SERVICE_CLEAN_ZONE = "vacuum_clean_zone" SERVICE_GOTO = "vacuum_goto" - -# AirQuality Model -MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" -MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" -MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index a7cfdd788a5..6d43b835f1c 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -13,6 +13,7 @@ from miio.gateway import ( import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONF_HOST, @@ -26,17 +27,16 @@ from homeassistant.const import ( PRESSURE_HPA, TEMP_CELSIUS, ) -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from .const import CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR +from .const import CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR +from .device import XiaomiMiioEntity from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Sensor" -DATA_KEY = "sensor.xiaomi_miio" UNIT_LUMEN = "lm" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -54,7 +54,6 @@ ATTR_NIGHT_MODE = "night_mode" ATTR_NIGHT_TIME_BEGIN = "night_time_begin" ATTR_NIGHT_TIME_END = "night_time_end" ATTR_SENSOR_STATE = "sensor_state" -ATTR_MODEL = "model" SUCCESS = ["ok"] @@ -81,6 +80,21 @@ GATEWAY_SENSOR_TYPES = { } +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Sensor via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Xiaomi sensor from a config entry.""" entities = [] @@ -114,48 +128,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ] ) + if 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 + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + device = AirQualityMonitor(host, token) + entities.append(XiaomiAirQualityMonitor(name, device, config_entry, unique_id)) + async_add_entities(entities, update_before_add=True) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the sensor from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - - try: - air_quality_monitor = AirQualityMonitor(host, token) - device_info = await hass.async_add_executor_job(air_quality_monitor.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, - ) - device = XiaomiAirQualityMonitor(name, air_quality_monitor, model, unique_id) - except DeviceException as ex: - raise PlatformNotReady from ex - - hass.data[DATA_KEY][host] = device - async_add_entities([device], update_before_add=True) - - -class XiaomiAirQualityMonitor(Entity): +class XiaomiAirQualityMonitor(XiaomiMiioEntity): """Representation of a Xiaomi Air Quality Monitor.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the entity.""" - self._name = name - self._device = device - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._icon = "mdi:cloud" self._unit_of_measurement = "AQI" @@ -170,19 +162,8 @@ class XiaomiAirQualityMonitor(Entity): ATTR_NIGHT_TIME_BEGIN: None, ATTR_NIGHT_TIME_END: None, ATTR_SENSOR_STATE: None, - ATTR_MODEL: self._model, } - @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 unit_of_measurement(self): """Return the unit of measurement of this entity, if any."""