Home Assistant Unofficial Reference 2024.12.1
humidifier.py
Go to the documentation of this file.
1 """Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier with humidifier entity."""
2 
3 import logging
4 import math
5 from typing import Any
6 
7 from miio.integrations.humidifier.deerma.airhumidifier_mjjsq import (
8  OperationMode as AirhumidifierMjjsqOperationMode,
9 )
10 from miio.integrations.humidifier.zhimi.airhumidifier import (
11  OperationMode as AirhumidifierOperationMode,
12 )
13 from miio.integrations.humidifier.zhimi.airhumidifier_miot import (
14  OperationMode as AirhumidifierMiotOperationMode,
15 )
16 
18  ATTR_HUMIDITY,
19  HumidifierDeviceClass,
20  HumidifierEntity,
21  HumidifierEntityFeature,
22 )
23 from homeassistant.config_entries import ConfigEntry
24 from homeassistant.const import ATTR_MODE, CONF_DEVICE, CONF_MODEL
25 from homeassistant.core import HomeAssistant, callback
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.util.percentage import percentage_to_ranged_value
28 
29 from .const import (
30  CONF_FLOW_TYPE,
31  DOMAIN,
32  KEY_COORDINATOR,
33  KEY_DEVICE,
34  MODEL_AIRHUMIDIFIER_CA1,
35  MODEL_AIRHUMIDIFIER_CA4,
36  MODEL_AIRHUMIDIFIER_CB1,
37  MODELS_HUMIDIFIER_MIOT,
38  MODELS_HUMIDIFIER_MJJSQ,
39 )
40 from .entity import XiaomiCoordinatedMiioEntity
41 
42 _LOGGER = logging.getLogger(__name__)
43 
44 # Air Humidifier
45 ATTR_TARGET_HUMIDITY = "target_humidity"
46 
47 AVAILABLE_ATTRIBUTES = {
48  ATTR_MODE: "mode",
49  ATTR_TARGET_HUMIDITY: "target_humidity",
50  ATTR_HUMIDITY: "humidity",
51 }
52 
53 AVAILABLE_MODES_CA1_CB1 = [
54  mode.name
55  for mode in AirhumidifierOperationMode
56  if mode is not AirhumidifierOperationMode.Strong
57 ]
58 AVAILABLE_MODES_CA4 = [mode.name for mode in AirhumidifierMiotOperationMode]
59 AVAILABLE_MODES_MJJSQ = [
60  mode.name
61  for mode in AirhumidifierMjjsqOperationMode
62  if mode is not AirhumidifierMjjsqOperationMode.WetAndProtect
63 ]
64 AVAILABLE_MODES_OTHER = [
65  mode.name
66  for mode in AirhumidifierOperationMode
67  if mode is not AirhumidifierOperationMode.Auto
68 ]
69 
70 
72  hass: HomeAssistant,
73  config_entry: ConfigEntry,
74  async_add_entities: AddEntitiesCallback,
75 ) -> None:
76  """Set up the Humidifier from a config entry."""
77  if config_entry.data[CONF_FLOW_TYPE] != CONF_DEVICE:
78  return
79 
80  entities: list[HumidifierEntity] = []
81  entity: HumidifierEntity
82  model = config_entry.data[CONF_MODEL]
83  unique_id = config_entry.unique_id
84  coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
85 
86  if model in MODELS_HUMIDIFIER_MIOT:
87  air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
88  entity = XiaomiAirHumidifierMiot(
89  air_humidifier,
90  config_entry,
91  unique_id,
92  coordinator,
93  )
94  elif model in MODELS_HUMIDIFIER_MJJSQ:
95  air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
96  entity = XiaomiAirHumidifierMjjsq(
97  air_humidifier,
98  config_entry,
99  unique_id,
100  coordinator,
101  )
102  else:
103  air_humidifier = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
104  entity = XiaomiAirHumidifier(
105  air_humidifier,
106  config_entry,
107  unique_id,
108  coordinator,
109  )
110 
111  entities.append(entity)
112 
113  async_add_entities(entities)
114 
115 
117  """Representation of a generic Xiaomi humidifier device."""
118 
119  _attr_device_class = HumidifierDeviceClass.HUMIDIFIER
120  _attr_supported_features = HumidifierEntityFeature.MODES
121  _attr_name = None
122 
123  def __init__(self, device, entry, unique_id, coordinator):
124  """Initialize the generic Xiaomi device."""
125  super().__init__(device, entry, unique_id, coordinator=coordinator)
126 
127  self._state_state = None
128  self._attributes_attributes = {}
129  self._mode_mode = None
130  self._humidity_steps_humidity_steps = 100
131  self._target_humidity_target_humidity = None
132 
133  @property
134  def is_on(self):
135  """Return true if device is on."""
136  return self._state_state
137 
138  @property
139  def mode(self):
140  """Get the current mode."""
141  return self._mode_mode
142 
143  async def async_turn_on(self, **kwargs: Any) -> None:
144  """Turn the device on."""
145  result = await self._try_command(
146  "Turning the miio device on failed.", self._device.on
147  )
148  if result:
149  self._state_state = True
150  self.async_write_ha_stateasync_write_ha_state()
151 
152  async def async_turn_off(self, **kwargs: Any) -> None:
153  """Turn the device off."""
154  result = await self._try_command(
155  "Turning the miio device off failed.", self._device.off
156  )
157 
158  if result:
159  self._state_state = False
160  self.async_write_ha_stateasync_write_ha_state()
161 
162  def translate_humidity(self, humidity: float) -> float | None:
163  """Translate the target humidity to the first valid step."""
164  return (
165  math.ceil(percentage_to_ranged_value((1, self._humidity_steps_humidity_steps), humidity))
166  * 100
167  / self._humidity_steps_humidity_steps
168  if 0 < humidity <= 100
169  else None
170  )
171 
172 
174  """Representation of a Xiaomi Air Humidifier."""
175 
176  available_modes: list[str]
177 
178  def __init__(self, device, entry, unique_id, coordinator):
179  """Initialize the plug switch."""
180  super().__init__(device, entry, unique_id, coordinator)
181 
182  self._attr_min_humidity_attr_min_humidity = 30
183  self._attr_max_humidity_attr_max_humidity = 80
184  if self._model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]:
185  self._attr_available_modes_attr_available_modes = AVAILABLE_MODES_CA1_CB1
186  self._humidity_steps_humidity_steps_humidity_steps = 10
187  elif self._model in [MODEL_AIRHUMIDIFIER_CA4]:
188  self._attr_available_modes_attr_available_modes = AVAILABLE_MODES_CA4
189  self._humidity_steps_humidity_steps_humidity_steps = 100
190  elif self._model in MODELS_HUMIDIFIER_MJJSQ:
191  self._attr_available_modes_attr_available_modes = AVAILABLE_MODES_MJJSQ
192  self._humidity_steps_humidity_steps_humidity_steps = 100
193  else:
194  self._attr_available_modes_attr_available_modes = AVAILABLE_MODES_OTHER
195  self._humidity_steps_humidity_steps_humidity_steps = 10
196 
197  self._state_state_state = self.coordinator.data.is_on
198  self._attributes_attributes.update(
199  {
200  key: self._extract_value_from_attribute(self.coordinator.data, value)
201  for key, value in AVAILABLE_ATTRIBUTES.items()
202  }
203  )
204  self._target_humidity_target_humidity_target_humidity = self._attributes_attributes[ATTR_TARGET_HUMIDITY]
205  self._attr_current_humidity_attr_current_humidity = self._attributes_attributes[ATTR_HUMIDITY]
206  self._mode_mode_mode = self._attributes_attributes[ATTR_MODE]
207 
208  @property
209  def is_on(self):
210  """Return true if device is on."""
211  return self._state_state_state
212 
213  @callback
215  """Fetch state from the device."""
216  self._state_state_state = self.coordinator.data.is_on
217  self._attributes_attributes.update(
218  {
219  key: self._extract_value_from_attribute(self.coordinator.data, value)
220  for key, value in AVAILABLE_ATTRIBUTES.items()
221  }
222  )
223  self._target_humidity_target_humidity_target_humidity = self._attributes_attributes[ATTR_TARGET_HUMIDITY]
224  self._attr_current_humidity_attr_current_humidity = self._attributes_attributes[ATTR_HUMIDITY]
225  self._mode_mode_mode = self._attributes_attributes[ATTR_MODE]
226  self.async_write_ha_stateasync_write_ha_state()
227 
228  @property
229  def mode(self):
230  """Return the current mode."""
231  return AirhumidifierOperationMode(self._mode_mode_mode).name
232 
233  @property
234  def target_humidity(self):
235  """Return the target humidity."""
236  return (
237  self._target_humidity_target_humidity_target_humidity
238  if self._mode_mode_mode == AirhumidifierOperationMode.Auto.value
239  or AirhumidifierOperationMode.Auto.name not in self.available_modesavailable_modes
240  else None
241  )
242 
243  async def async_set_humidity(self, humidity: float) -> None:
244  """Set the target humidity of the humidifier and set the mode to auto."""
245  target_humidity = self.translate_humiditytranslate_humidity(humidity)
246  if not target_humidity:
247  return
248 
249  _LOGGER.debug("Setting the target humidity to: %s", target_humidity)
250  if await self._try_command(
251  "Setting target humidity of the miio device failed.",
252  self._device.set_target_humidity,
253  target_humidity,
254  ):
255  self._target_humidity_target_humidity_target_humidity = target_humidity
256  if (
257  self.supported_featuressupported_featuressupported_features & HumidifierEntityFeature.MODES == 0
258  or AirhumidifierOperationMode(self._attributes_attributes[ATTR_MODE])
259  == AirhumidifierOperationMode.Auto
260  or AirhumidifierOperationMode.Auto.name not in self.available_modesavailable_modes
261  ):
262  self.async_write_ha_stateasync_write_ha_state()
263  return
264  _LOGGER.debug("Setting the operation mode to: Auto")
265  if await self._try_command(
266  "Setting operation mode of the miio device to MODE_AUTO failed.",
267  self._device.set_mode,
268  AirhumidifierOperationMode.Auto,
269  ):
270  self._mode_mode_mode = AirhumidifierOperationMode.Auto.value
271  self.async_write_ha_stateasync_write_ha_state()
272 
273  async def async_set_mode(self, mode: str) -> None:
274  """Set the mode of the humidifier."""
275  if self.supported_featuressupported_featuressupported_features & HumidifierEntityFeature.MODES == 0 or not mode:
276  return
277 
278  if mode not in self.available_modesavailable_modes:
279  _LOGGER.warning("Mode %s is not a valid operation mode", mode)
280  return
281 
282  _LOGGER.debug("Setting the operation mode to: %s", mode)
283  if await self._try_command(
284  "Setting operation mode of the miio device failed.",
285  self._device.set_mode,
286  AirhumidifierOperationMode[mode],
287  ):
288  self._mode_mode_mode = mode.lower()
289  self.async_write_ha_stateasync_write_ha_state()
290 
291 
293  """Representation of a Xiaomi Air Humidifier (MiOT protocol)."""
294 
295  MODE_MAPPING = {
296  AirhumidifierMiotOperationMode.Auto: "Auto",
297  AirhumidifierMiotOperationMode.Low: "Low",
298  AirhumidifierMiotOperationMode.Mid: "Mid",
299  AirhumidifierMiotOperationMode.High: "High",
300  }
301 
302  REVERSE_MODE_MAPPING = {v: k for k, v in MODE_MAPPING.items()}
303 
304  @property
305  def mode(self):
306  """Return the current mode."""
307  return AirhumidifierMiotOperationMode(self._mode_mode_mode_mode).name
308 
309  @property
310  def target_humidity(self):
311  """Return the target humidity."""
312  if self._state_state_state:
313  return (
315  if AirhumidifierMiotOperationMode(self._mode_mode_mode_mode)
316  == AirhumidifierMiotOperationMode.Auto
317  else None
318  )
319  return None
320 
321  async def async_set_humidity(self, humidity: float) -> None:
322  """Set the target humidity of the humidifier and set the mode to auto."""
323  target_humidity = self.translate_humiditytranslate_humidity(humidity)
324  if not target_humidity:
325  return
326 
327  _LOGGER.debug("Setting the humidity to: %s", target_humidity)
328  if await self._try_command(
329  "Setting operation mode of the miio device failed.",
330  self._device.set_target_humidity,
331  target_humidity,
332  ):
333  self._target_humidity_target_humidity_target_humidity_target_humidity = target_humidity
334  if (
335  self.supported_featuressupported_featuressupported_features & HumidifierEntityFeature.MODES == 0
336  or AirhumidifierMiotOperationMode(self._attributes_attributes[ATTR_MODE])
337  == AirhumidifierMiotOperationMode.Auto
338  ):
339  self.async_write_ha_stateasync_write_ha_state()
340  return
341  _LOGGER.debug("Setting the operation mode to: Auto")
342  if await self._try_command(
343  "Setting operation mode of the miio device to MODE_AUTO failed.",
344  self._device.set_mode,
345  AirhumidifierMiotOperationMode.Auto,
346  ):
347  self._mode_mode_mode_mode = 0
348  self.async_write_ha_stateasync_write_ha_state()
349 
350  async def async_set_mode(self, mode: str) -> None:
351  """Set the mode of the fan."""
352  if self.supported_featuressupported_featuressupported_features & HumidifierEntityFeature.MODES == 0 or not mode:
353  return
354 
355  if mode not in self.REVERSE_MODE_MAPPINGREVERSE_MODE_MAPPING:
356  _LOGGER.warning("Mode %s is not a valid operation mode", mode)
357  return
358 
359  _LOGGER.debug("Setting the operation mode to: %s", mode)
360  if self._state_state_state:
361  if await self._try_command(
362  "Setting operation mode of the miio device failed.",
363  self._device.set_mode,
364  self.REVERSE_MODE_MAPPINGREVERSE_MODE_MAPPING[mode],
365  ):
366  self._mode_mode_mode_mode = self.REVERSE_MODE_MAPPINGREVERSE_MODE_MAPPING[mode].value
367  self.async_write_ha_stateasync_write_ha_state()
368 
369 
371  """Representation of a Xiaomi Air MJJSQ Humidifier."""
372 
373  MODE_MAPPING = {
374  "Low": AirhumidifierMjjsqOperationMode.Low,
375  "Medium": AirhumidifierMjjsqOperationMode.Medium,
376  "High": AirhumidifierMjjsqOperationMode.High,
377  "Humidity": AirhumidifierMjjsqOperationMode.Humidity,
378  }
379 
380  @property
381  def mode(self):
382  """Return the current mode."""
383  return AirhumidifierMjjsqOperationMode(self._mode_mode_mode_mode).name
384 
385  @property
386  def target_humidity(self):
387  """Return the target humidity."""
388  if self._state_state_state:
389  if (
390  AirhumidifierMjjsqOperationMode(self._mode_mode_mode_mode)
391  == AirhumidifierMjjsqOperationMode.Humidity
392  ):
393  return self._target_humidity_target_humidity_target_humidity_target_humidity
394  return None
395 
396  async def async_set_humidity(self, humidity: float) -> None:
397  """Set the target humidity of the humidifier and set the mode to Humidity."""
398  target_humidity = self.translate_humiditytranslate_humidity(humidity)
399  if not target_humidity:
400  return
401 
402  _LOGGER.debug("Setting the humidity to: %s", target_humidity)
403  if await self._try_command(
404  "Setting operation mode of the miio device failed.",
405  self._device.set_target_humidity,
406  target_humidity,
407  ):
408  self._target_humidity_target_humidity_target_humidity_target_humidity = target_humidity
409  if (
410  self.supported_featuressupported_featuressupported_features & HumidifierEntityFeature.MODES == 0
411  or AirhumidifierMjjsqOperationMode(self._attributes_attributes[ATTR_MODE])
412  == AirhumidifierMjjsqOperationMode.Humidity
413  ):
414  self.async_write_ha_stateasync_write_ha_state()
415  return
416  _LOGGER.debug("Setting the operation mode to: Humidity")
417  if await self._try_command(
418  "Setting operation mode of the miio device to MODE_HUMIDITY failed.",
419  self._device.set_mode,
420  AirhumidifierMjjsqOperationMode.Humidity,
421  ):
422  self._mode_mode_mode_mode = 3
423  self.async_write_ha_stateasync_write_ha_state()
424 
425  async def async_set_mode(self, mode: str) -> None:
426  """Set the mode of the fan."""
427  if mode not in self.MODE_MAPPINGMODE_MAPPING:
428  _LOGGER.warning("Mode %s is not a valid operation mode", mode)
429  return
430 
431  _LOGGER.debug("Setting the operation mode to: %s", mode)
432  if self._state_state_state:
433  if await self._try_command(
434  "Setting operation mode of the miio device failed.",
435  self._device.set_mode,
436  self.MODE_MAPPINGMODE_MAPPING[mode],
437  ):
438  self._mode_mode_mode_mode = self.MODE_MAPPINGMODE_MAPPING[mode].value
439  self.async_write_ha_stateasync_write_ha_state()
HumidifierEntityFeature supported_features(self)
Definition: __init__.py:274
def __init__(self, device, entry, unique_id, coordinator)
Definition: humidifier.py:178
def __init__(self, device, entry, unique_id, coordinator)
Definition: humidifier.py:123
int|None supported_features(self)
Definition: entity.py:861
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: humidifier.py:75
float percentage_to_ranged_value(tuple[float, float] low_high_range, float percentage)
Definition: percentage.py:81