mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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:
parent
b9c2f80cab
commit
724574d336
@ -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
|
||||||
|
@ -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)."""
|
||||||
|
@ -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"
|
|
||||||
|
@ -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."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user