2019-10-28 06:43:01 +00:00
|
|
|
"""Support for Xiaomi Mi Air Quality Monitor (PM2.5)."""
|
2020-01-21 16:06:17 +00:00
|
|
|
import logging
|
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
from miio import AirQualityMonitor, DeviceException
|
2019-10-28 06:43:01 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2020-01-21 16:06:17 +00:00
|
|
|
from homeassistant.components.air_quality import PLATFORM_SCHEMA, AirQualityEntity
|
2021-03-11 10:48:48 +00:00
|
|
|
from homeassistant.config_entries import SOURCE_IMPORT
|
2020-04-14 21:15:08 +00:00
|
|
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
2019-10-28 06:43:01 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
|
2020-01-21 16:06:17 +00:00
|
|
|
from .const import (
|
2021-03-11 10:48:48 +00:00
|
|
|
CONF_DEVICE,
|
|
|
|
CONF_FLOW_TYPE,
|
|
|
|
CONF_MODEL,
|
|
|
|
DOMAIN,
|
2020-01-21 16:06:17 +00:00
|
|
|
MODEL_AIRQUALITYMONITOR_B1,
|
|
|
|
MODEL_AIRQUALITYMONITOR_S1,
|
|
|
|
MODEL_AIRQUALITYMONITOR_V1,
|
|
|
|
)
|
2021-03-11 10:48:48 +00:00
|
|
|
from .device import XiaomiMiioEntity
|
2020-01-21 16:06:17 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-10-28 06:43:01 +00:00
|
|
|
DEFAULT_NAME = "Xiaomi Miio Air Quality Monitor"
|
|
|
|
|
|
|
|
ATTR_CO2E = "carbon_dioxide_equivalent"
|
|
|
|
ATTR_TVOC = "total_volatile_organic_compounds"
|
2020-02-01 19:52:28 +00:00
|
|
|
ATTR_TEMP = "temperature"
|
|
|
|
ATTR_HUM = "humidity"
|
2019-10-28 06:43:01 +00:00
|
|
|
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_HOST): cv.string,
|
|
|
|
vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)),
|
|
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
PROP_TO_ATTR = {
|
|
|
|
"carbon_dioxide_equivalent": ATTR_CO2E,
|
|
|
|
"total_volatile_organic_compounds": ATTR_TVOC,
|
2020-02-01 19:52:28 +00:00
|
|
|
"temperature": ATTR_TEMP,
|
|
|
|
"humidity": ATTR_HUM,
|
2019-10-28 06:43:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
2021-03-11 10:48:48 +00:00
|
|
|
"""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,
|
|
|
|
)
|
|
|
|
)
|
2019-10-28 06:43:01 +00:00
|
|
|
|
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
|
|
"""Set up the Xiaomi Air Quality from a config entry."""
|
|
|
|
entities = []
|
2019-10-28 06:43:01 +00:00
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
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
|
2019-10-28 06:43:01 +00:00
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
2019-10-28 06:43:01 +00:00
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
device = AirQualityMonitor(host, token, model=model)
|
2019-10-29 21:17:09 +00:00
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
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)
|
2020-01-21 16:06:17 +00:00
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
async_add_entities(entities, update_before_add=True)
|
2020-01-21 16:06:17 +00:00
|
|
|
|
2019-10-28 06:43:01 +00:00
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
class AirMonitorB1(XiaomiMiioEntity, AirQualityEntity):
|
2019-10-28 06:43:01 +00:00
|
|
|
"""Air Quality class for Xiaomi cgllc.airmonitor.b1 device."""
|
|
|
|
|
2021-03-11 10:48:48 +00:00
|
|
|
def __init__(self, name, device, entry, unique_id):
|
2019-10-28 06:43:01 +00:00
|
|
|
"""Initialize the entity."""
|
2021-03-11 10:48:48 +00:00
|
|
|
super().__init__(name, device, entry, unique_id)
|
|
|
|
|
2019-10-28 06:43:01 +00:00
|
|
|
self._icon = "mdi:cloud"
|
2020-01-21 16:06:17 +00:00
|
|
|
self._available = None
|
|
|
|
self._air_quality_index = None
|
|
|
|
self._carbon_dioxide = None
|
2019-10-28 06:43:01 +00:00
|
|
|
self._carbon_dioxide_equivalent = None
|
|
|
|
self._particulate_matter_2_5 = None
|
|
|
|
self._total_volatile_organic_compounds = None
|
2020-02-01 19:52:28 +00:00
|
|
|
self._temperature = None
|
|
|
|
self._humidity = None
|
2019-10-28 06:43:01 +00:00
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Fetch state from the miio device."""
|
|
|
|
try:
|
|
|
|
state = await self.hass.async_add_executor_job(self._device.status)
|
|
|
|
_LOGGER.debug("Got new state: %s", state)
|
|
|
|
self._carbon_dioxide_equivalent = state.co2e
|
|
|
|
self._particulate_matter_2_5 = round(state.pm25, 1)
|
|
|
|
self._total_volatile_organic_compounds = round(state.tvoc, 3)
|
2020-02-01 19:52:28 +00:00
|
|
|
self._temperature = round(state.temperature, 2)
|
|
|
|
self._humidity = round(state.humidity, 2)
|
2020-01-21 16:06:17 +00:00
|
|
|
self._available = True
|
2019-10-28 06:43:01 +00:00
|
|
|
except DeviceException as ex:
|
2020-01-21 16:06:17 +00:00
|
|
|
self._available = False
|
2019-10-28 06:43:01 +00:00
|
|
|
_LOGGER.error("Got exception while fetching the state: %s", ex)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def icon(self):
|
|
|
|
"""Return the icon to use for device if any."""
|
|
|
|
return self._icon
|
|
|
|
|
2020-01-21 16:06:17 +00:00
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return true when state is known."""
|
|
|
|
return self._available
|
|
|
|
|
|
|
|
@property
|
|
|
|
def air_quality_index(self):
|
|
|
|
"""Return the Air Quality Index (AQI)."""
|
|
|
|
return self._air_quality_index
|
|
|
|
|
|
|
|
@property
|
|
|
|
def carbon_dioxide(self):
|
|
|
|
"""Return the CO2 (carbon dioxide) level."""
|
|
|
|
return self._carbon_dioxide
|
|
|
|
|
2019-10-28 06:43:01 +00:00
|
|
|
@property
|
|
|
|
def carbon_dioxide_equivalent(self):
|
|
|
|
"""Return the CO2e (carbon dioxide equivalent) level."""
|
|
|
|
return self._carbon_dioxide_equivalent
|
|
|
|
|
|
|
|
@property
|
|
|
|
def particulate_matter_2_5(self):
|
|
|
|
"""Return the particulate matter 2.5 level."""
|
|
|
|
return self._particulate_matter_2_5
|
|
|
|
|
|
|
|
@property
|
|
|
|
def total_volatile_organic_compounds(self):
|
|
|
|
"""Return the total volatile organic compounds."""
|
|
|
|
return self._total_volatile_organic_compounds
|
|
|
|
|
2020-02-01 19:52:28 +00:00
|
|
|
@property
|
|
|
|
def temperature(self):
|
|
|
|
"""Return the current temperature."""
|
|
|
|
return self._temperature
|
|
|
|
|
|
|
|
@property
|
|
|
|
def humidity(self):
|
|
|
|
"""Return the current humidity."""
|
|
|
|
return self._humidity
|
|
|
|
|
2019-10-28 06:43:01 +00:00
|
|
|
@property
|
2021-03-11 19:16:26 +00:00
|
|
|
def extra_state_attributes(self):
|
2019-10-28 06:43:01 +00:00
|
|
|
"""Return the state attributes."""
|
|
|
|
data = {}
|
|
|
|
|
|
|
|
for prop, attr in PROP_TO_ATTR.items():
|
|
|
|
value = getattr(self, prop)
|
|
|
|
if value is not None:
|
|
|
|
data[attr] = value
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
2020-01-21 16:06:17 +00:00
|
|
|
|
|
|
|
class AirMonitorS1(AirMonitorB1):
|
|
|
|
"""Air Quality class for Xiaomi cgllc.airmonitor.s1 device."""
|
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Fetch state from the miio device."""
|
|
|
|
try:
|
|
|
|
state = await self.hass.async_add_executor_job(self._device.status)
|
|
|
|
_LOGGER.debug("Got new state: %s", state)
|
|
|
|
self._carbon_dioxide = state.co2
|
|
|
|
self._particulate_matter_2_5 = state.pm25
|
|
|
|
self._total_volatile_organic_compounds = state.tvoc
|
2020-02-01 19:52:28 +00:00
|
|
|
self._temperature = state.temperature
|
|
|
|
self._humidity = state.humidity
|
2020-01-21 16:06:17 +00:00
|
|
|
self._available = True
|
|
|
|
except DeviceException as ex:
|
2020-10-24 23:53:36 +00:00
|
|
|
if self._available:
|
|
|
|
self._available = False
|
|
|
|
_LOGGER.error("Got exception while fetching the state: %s", ex)
|
2020-01-21 16:06:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AirMonitorV1(AirMonitorB1):
|
|
|
|
"""Air Quality class for Xiaomi cgllc.airmonitor.s1 device."""
|
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Fetch state from the miio device."""
|
|
|
|
try:
|
|
|
|
state = await self.hass.async_add_executor_job(self._device.status)
|
|
|
|
_LOGGER.debug("Got new state: %s", state)
|
|
|
|
self._air_quality_index = state.aqi
|
|
|
|
self._available = True
|
|
|
|
except DeviceException as ex:
|
2020-10-24 23:53:36 +00:00
|
|
|
if self._available:
|
|
|
|
self._available = False
|
|
|
|
_LOGGER.error("Got exception while fetching the state: %s", ex)
|
2020-01-21 16:06:17 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
|
|
|
"""Return the unit of measurement."""
|
|
|
|
return None
|