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 <marhje52@gmail.com>

* Update homeassistant/components/xiaomi_miio/air_quality.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* process revieuw feedback

* remove unused import

* fix formatting

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
starkillerOG 2021-03-11 11:48:48 +01:00 committed by GitHub
parent b9c2f80cab
commit 724574d336
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 106 deletions

View File

@ -16,6 +16,7 @@ from .const import (
CONF_MODEL, CONF_MODEL,
DOMAIN, DOMAIN,
KEY_COORDINATOR, KEY_COORDINATOR,
MODELS_AIR_MONITOR,
MODELS_FAN, MODELS_FAN,
MODELS_SWITCH, MODELS_SWITCH,
MODELS_VACUUM, MODELS_VACUUM,
@ -28,6 +29,7 @@ GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"]
SWITCH_PLATFORMS = ["switch"] SWITCH_PLATFORMS = ["switch"]
FAN_PLATFORMS = ["fan"] FAN_PLATFORMS = ["fan"]
VACUUM_PLATFORMS = ["vacuum"] VACUUM_PLATFORMS = ["vacuum"]
AIR_MONITOR_PLATFORMS = ["air_quality", "sensor"]
async def async_setup(hass: core.HomeAssistant, config: dict): 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: for vacuum_model in MODELS_VACUUM:
if model.startswith(vacuum_model): if model.startswith(vacuum_model):
platforms = VACUUM_PLATFORMS platforms = VACUUM_PLATFORMS
for air_monitor_model in MODELS_AIR_MONITOR:
if model.startswith(air_monitor_model):
platforms = AIR_MONITOR_PLATFORMS
if not platforms: if not platforms:
return False return False

View File

@ -1,19 +1,24 @@
"""Support for Xiaomi Mi Air Quality Monitor (PM2.5).""" """Support for Xiaomi Mi Air Quality Monitor (PM2.5)."""
import logging import logging
from miio import AirQualityMonitor, Device, DeviceException from miio import AirQualityMonitor, DeviceException
import voluptuous as vol import voluptuous as vol
from homeassistant.components.air_quality import PLATFORM_SCHEMA, AirQualityEntity 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.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.exceptions import NoEntitySpecifiedError, PlatformNotReady
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .const import ( from .const import (
CONF_DEVICE,
CONF_FLOW_TYPE,
CONF_MODEL,
DOMAIN,
MODEL_AIRQUALITYMONITOR_B1, MODEL_AIRQUALITYMONITOR_B1,
MODEL_AIRQUALITYMONITOR_S1, MODEL_AIRQUALITYMONITOR_S1,
MODEL_AIRQUALITYMONITOR_V1, MODEL_AIRQUALITYMONITOR_V1,
) )
from .device import XiaomiMiioEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -41,52 +46,54 @@ PROP_TO_ATTR = {
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):
"""Set up the sensor from config.""" """Import Miio configuration from YAML."""
_LOGGER.warning(
host = config[CONF_HOST] "Loading Xiaomi Miio Air Quality via platform setup is deprecated. "
token = config[CONF_TOKEN] "Please remove it from your configuration"
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,
) )
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 Air Quality from a config entry."""
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
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) device = AirQualityMonitor(host, token, model=model)
if model == MODEL_AIRQUALITYMONITOR_S1: if model == MODEL_AIRQUALITYMONITOR_S1:
entity = AirMonitorS1(name, device, unique_id) entities.append(AirMonitorS1(name, device, config_entry, unique_id))
elif model == MODEL_AIRQUALITYMONITOR_B1: elif model == MODEL_AIRQUALITYMONITOR_B1:
entity = AirMonitorB1(name, device, unique_id) entities.append(AirMonitorB1(name, device, config_entry, unique_id))
elif model == MODEL_AIRQUALITYMONITOR_V1: elif model == MODEL_AIRQUALITYMONITOR_V1:
entity = AirMonitorV1(name, device, unique_id) entities.append(AirMonitorV1(name, device, config_entry, unique_id))
else: else:
raise NoEntitySpecifiedError(f"Not support for entity {unique_id}") _LOGGER.warning("AirQualityMonitor model '%s' is not yet supported", model)
async_add_entities([entity], update_before_add=True) async_add_entities(entities, update_before_add=True)
class AirMonitorB1(AirQualityEntity): class AirMonitorB1(XiaomiMiioEntity, AirQualityEntity):
"""Air Quality class for Xiaomi cgllc.airmonitor.b1 device.""" """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.""" """Initialize the entity."""
self._name = name super().__init__(name, device, entry, unique_id)
self._device = device
self._unique_id = unique_id
self._icon = "mdi:cloud" self._icon = "mdi:cloud"
self._available = None self._available = None
self._air_quality_index = None self._air_quality_index = None
@ -112,11 +119,6 @@ class AirMonitorB1(AirQualityEntity):
self._available = False self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex) _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 @property
def icon(self): def icon(self):
"""Return the icon to use for device if any.""" """Return the icon to use for device if any."""
@ -127,11 +129,6 @@ class AirMonitorB1(AirQualityEntity):
"""Return true when state is known.""" """Return true when state is known."""
return self._available return self._available
@property
def unique_id(self):
"""Return the unique ID."""
return self._unique_id
@property @property
def air_quality_index(self): def air_quality_index(self):
"""Return the Air Quality Index (AQI).""" """Return the Air Quality Index (AQI)."""

View File

@ -55,6 +55,11 @@ MODELS_FAN_MIIO = [
MODEL_AIRFRESH_VA2, 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 # Model lists
MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"]
MODELS_SWITCH = [ MODELS_SWITCH = [
@ -71,8 +76,13 @@ MODELS_SWITCH = [
] ]
MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT
MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] 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 MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY
# Fan Services # Fan Services
@ -126,8 +136,3 @@ SERVICE_STOP_REMOTE_CONTROL = "vacuum_remote_control_stop"
SERVICE_CLEAN_SEGMENT = "vacuum_clean_segment" SERVICE_CLEAN_SEGMENT = "vacuum_clean_segment"
SERVICE_CLEAN_ZONE = "vacuum_clean_zone" SERVICE_CLEAN_ZONE = "vacuum_clean_zone"
SERVICE_GOTO = "vacuum_goto" SERVICE_GOTO = "vacuum_goto"
# AirQuality Model
MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1"
MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1"
MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1"

View File

@ -13,6 +13,7 @@ from miio.gateway import (
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import ( from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_BATTERY_LEVEL,
CONF_HOST, CONF_HOST,
@ -26,17 +27,16 @@ from homeassistant.const import (
PRESSURE_HPA, PRESSURE_HPA,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity 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 from .gateway import XiaomiGatewayDevice
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Xiaomi Miio Sensor" DEFAULT_NAME = "Xiaomi Miio Sensor"
DATA_KEY = "sensor.xiaomi_miio"
UNIT_LUMEN = "lm" UNIT_LUMEN = "lm"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
@ -54,7 +54,6 @@ ATTR_NIGHT_MODE = "night_mode"
ATTR_NIGHT_TIME_BEGIN = "night_time_begin" ATTR_NIGHT_TIME_BEGIN = "night_time_begin"
ATTR_NIGHT_TIME_END = "night_time_end" ATTR_NIGHT_TIME_END = "night_time_end"
ATTR_SENSOR_STATE = "sensor_state" ATTR_SENSOR_STATE = "sensor_state"
ATTR_MODEL = "model"
SUCCESS = ["ok"] 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): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Xiaomi sensor from a config entry.""" """Set up the Xiaomi sensor from a config entry."""
entities = [] 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_add_entities(entities, update_before_add=True)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): class XiaomiAirQualityMonitor(XiaomiMiioEntity):
"""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):
"""Representation of a Xiaomi Air Quality Monitor.""" """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.""" """Initialize the entity."""
self._name = name super().__init__(name, device, entry, unique_id)
self._device = device
self._model = model
self._unique_id = unique_id
self._icon = "mdi:cloud" self._icon = "mdi:cloud"
self._unit_of_measurement = "AQI" self._unit_of_measurement = "AQI"
@ -170,19 +162,8 @@ class XiaomiAirQualityMonitor(Entity):
ATTR_NIGHT_TIME_BEGIN: None, ATTR_NIGHT_TIME_BEGIN: None,
ATTR_NIGHT_TIME_END: None, ATTR_NIGHT_TIME_END: None,
ATTR_SENSOR_STATE: 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 @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement of this entity, if any."""