Home Assistant Unofficial Reference 2024.12.1
air_quality.py
Go to the documentation of this file.
1 """Support for Xiaomi Mi Air Quality Monitor (PM2.5)."""
2 
3 from collections.abc import Callable
4 import logging
5 
6 from miio import AirQualityMonitor, AirQualityMonitorCGDN1, DeviceException
7 
8 from homeassistant.components.air_quality import AirQualityEntity
9 from homeassistant.config_entries import ConfigEntry
10 from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MODEL, CONF_TOKEN
11 from homeassistant.core import HomeAssistant
12 from homeassistant.helpers.entity_platform import AddEntitiesCallback
13 
14 from .const import (
15  CONF_FLOW_TYPE,
16  MODEL_AIRQUALITYMONITOR_B1,
17  MODEL_AIRQUALITYMONITOR_CGDN1,
18  MODEL_AIRQUALITYMONITOR_S1,
19  MODEL_AIRQUALITYMONITOR_V1,
20 )
21 from .entity import XiaomiMiioEntity
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 DEFAULT_NAME = "Xiaomi Miio Air Quality Monitor"
26 
27 ATTR_CO2E = "carbon_dioxide_equivalent"
28 ATTR_TVOC = "total_volatile_organic_compounds"
29 ATTR_TEMP = "temperature"
30 ATTR_HUM = "humidity"
31 
32 PROP_TO_ATTR = {
33  "carbon_dioxide_equivalent": ATTR_CO2E,
34  "total_volatile_organic_compounds": ATTR_TVOC,
35  "temperature": ATTR_TEMP,
36  "humidity": ATTR_HUM,
37 }
38 
39 
41  """Air Quality class for Xiaomi cgllc.airmonitor.b1 device."""
42 
43  def __init__(self, name, device, entry, unique_id):
44  """Initialize the entity."""
45  super().__init__(name, device, entry, unique_id)
46 
47  self._icon_icon = "mdi:cloud"
48  self._available_available_available = None
49  self._air_quality_index_air_quality_index = None
50  self._carbon_dioxide_carbon_dioxide = None
51  self._carbon_dioxide_equivalent_carbon_dioxide_equivalent = None
52  self._particulate_matter_2_5_particulate_matter_2_5 = None
53  self._total_volatile_organic_compounds_total_volatile_organic_compounds = None
54  self._temperature_temperature = None
55  self._humidity_humidity = None
56 
57  async def async_update(self):
58  """Fetch state from the miio device."""
59  try:
60  state = await self.hasshass.async_add_executor_job(self._device_device.status)
61  _LOGGER.debug("Got new state: %s", state)
62  self._carbon_dioxide_equivalent_carbon_dioxide_equivalent = state.co2e
63  self._particulate_matter_2_5_particulate_matter_2_5 = round(state.pm25, 1)
64  self._total_volatile_organic_compounds_total_volatile_organic_compounds = round(state.tvoc, 3)
65  self._temperature_temperature = round(state.temperature, 2)
66  self._humidity_humidity = round(state.humidity, 2)
67  self._available_available_available = True
68  except DeviceException as ex:
69  self._available_available_available = False
70  _LOGGER.error("Got exception while fetching the state: %s", ex)
71 
72  @property
73  def icon(self):
74  """Return the icon to use for device if any."""
75  return self._icon_icon
76 
77  @property
78  def available(self):
79  """Return true when state is known."""
80  return self._available_available_available
81 
82  @property
83  def air_quality_index(self):
84  """Return the Air Quality Index (AQI)."""
85  return self._air_quality_index_air_quality_index
86 
87  @property
88  def carbon_dioxide(self):
89  """Return the CO2 (carbon dioxide) level."""
90  return self._carbon_dioxide_carbon_dioxide
91 
92  @property
94  """Return the CO2e (carbon dioxide equivalent) level."""
95  return self._carbon_dioxide_equivalent_carbon_dioxide_equivalent
96 
97  @property
99  """Return the particulate matter 2.5 level."""
100  return self._particulate_matter_2_5_particulate_matter_2_5
101 
102  @property
104  """Return the total volatile organic compounds."""
105  return self._total_volatile_organic_compounds_total_volatile_organic_compounds
106 
107  @property
108  def temperature(self):
109  """Return the current temperature."""
110  return self._temperature_temperature
111 
112  @property
113  def humidity(self):
114  """Return the current humidity."""
115  return self._humidity_humidity
116 
117  @property
119  """Return the state attributes."""
120  data = {}
121 
122  for prop, attr in PROP_TO_ATTR.items():
123  if (value := getattr(self, prop)) is not None:
124  data[attr] = value
125 
126  return data
127 
128 
130  """Air Quality class for Xiaomi cgllc.airmonitor.s1 device."""
131 
132  async def async_update(self):
133  """Fetch state from the miio device."""
134  try:
135  state = await self.hasshass.async_add_executor_job(self._device_device.status)
136  _LOGGER.debug("Got new state: %s", state)
137  self._carbon_dioxide_carbon_dioxide_carbon_dioxide = state.co2
138  self._particulate_matter_2_5_particulate_matter_2_5_particulate_matter_2_5 = state.pm25
139  self._total_volatile_organic_compounds_total_volatile_organic_compounds_total_volatile_organic_compounds = state.tvoc
140  self._temperature_temperature_temperature = state.temperature
141  self._humidity_humidity_humidity = state.humidity
142  self._available_available_available_available = True
143  except DeviceException as ex:
144  if self._available_available_available_available:
145  self._available_available_available_available = False
146  _LOGGER.error("Got exception while fetching the state: %s", ex)
147 
148 
150  """Air Quality class for Xiaomi cgllc.airmonitor.s1 device."""
151 
152  async def async_update(self):
153  """Fetch state from the miio device."""
154  try:
155  state = await self.hasshass.async_add_executor_job(self._device_device.status)
156  _LOGGER.debug("Got new state: %s", state)
157  self._air_quality_index_air_quality_index_air_quality_index = state.aqi
158  self._available_available_available_available = True
159  except DeviceException as ex:
160  if self._available_available_available_available:
161  self._available_available_available_available = False
162  _LOGGER.error("Got exception while fetching the state: %s", ex)
163 
164  @property
166  """Return the unit of measurement."""
167  return None
168 
169 
171  """Air Quality class for cgllc.airm.cgdn1 device."""
172 
173  def __init__(self, name, device, entry, unique_id):
174  """Initialize the entity."""
175  super().__init__(name, device, entry, unique_id)
176 
177  self._icon_icon = "mdi:cloud"
178  self._available_available_available = None
179  self._carbon_dioxide_carbon_dioxide = None
180  self._particulate_matter_2_5_particulate_matter_2_5 = None
181  self._particulate_matter_10_particulate_matter_10 = None
182 
183  async def async_update(self):
184  """Fetch state from the miio device."""
185  try:
186  state = await self.hasshass.async_add_executor_job(self._device_device.status)
187  _LOGGER.debug("Got new state: %s", state)
188  self._carbon_dioxide_carbon_dioxide = state.co2
189  self._particulate_matter_2_5_particulate_matter_2_5 = round(state.pm25, 1)
190  self._particulate_matter_10_particulate_matter_10 = round(state.pm10, 1)
191  self._available_available_available = True
192  except DeviceException as ex:
193  self._available_available_available = False
194  _LOGGER.error("Got exception while fetching the state: %s", ex)
195 
196  @property
197  def icon(self):
198  """Return the icon to use for device if any."""
199  return self._icon_icon
200 
201  @property
202  def available(self):
203  """Return true when state is known."""
204  return self._available_available_available
205 
206  @property
207  def carbon_dioxide(self):
208  """Return the CO2 (carbon dioxide) level."""
209  return self._carbon_dioxide_carbon_dioxide
210 
211  @property
213  """Return the particulate matter 2.5 level."""
214  return self._particulate_matter_2_5_particulate_matter_2_5
215 
216  @property
218  """Return the particulate matter 10 level."""
219  return self._particulate_matter_10_particulate_matter_10
220 
221 
222 DEVICE_MAP: dict[str, dict[str, Callable]] = {
223  MODEL_AIRQUALITYMONITOR_S1: {
224  "device_class": AirQualityMonitor,
225  "entity_class": AirMonitorS1,
226  },
227  MODEL_AIRQUALITYMONITOR_B1: {
228  "device_class": AirQualityMonitor,
229  "entity_class": AirMonitorB1,
230  },
231  MODEL_AIRQUALITYMONITOR_V1: {
232  "device_class": AirQualityMonitor,
233  "entity_class": AirMonitorV1,
234  },
235  MODEL_AIRQUALITYMONITOR_CGDN1: {
236  "device_class": lambda host, token, model: AirQualityMonitorCGDN1(host, token),
237  "entity_class": AirMonitorCGDN1,
238  },
239 }
240 
241 
243  hass: HomeAssistant,
244  config_entry: ConfigEntry,
245  async_add_entities: AddEntitiesCallback,
246 ) -> None:
247  """Set up the Xiaomi Air Quality from a config entry."""
248  entities = []
249 
250  if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
251  host = config_entry.data[CONF_HOST]
252  token = config_entry.data[CONF_TOKEN]
253  name = config_entry.title
254  model = config_entry.data[CONF_MODEL]
255  unique_id = config_entry.unique_id
256 
257  _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
258 
259  if model in DEVICE_MAP:
260  device_entry = DEVICE_MAP[model]
261  entities.append(
262  device_entry["entity_class"](
263  name,
264  device_entry["device_class"](host, token, model=model),
265  config_entry,
266  unique_id,
267  )
268  )
269  else:
270  _LOGGER.warning("AirQualityMonitor model '%s' is not yet supported", model)
271 
272  async_add_entities(entities, update_before_add=True)
def __init__(self, name, device, entry, unique_id)
Definition: air_quality.py:43
def __init__(self, name, device, entry, unique_id)
Definition: air_quality.py:173
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: air_quality.py:246