Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Provides functionality to interact with humidifier devices."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 from enum import StrEnum
7 from functools import partial
8 import logging
9 from typing import Any, final
10 
11 from propcache import cached_property
12 import voluptuous as vol
13 
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.const import (
16  ATTR_MODE,
17  SERVICE_TOGGLE,
18  SERVICE_TURN_OFF,
19  SERVICE_TURN_ON,
20  STATE_ON,
21 )
22 from homeassistant.core import HomeAssistant, ServiceCall
23 from homeassistant.exceptions import ServiceValidationError
24 from homeassistant.helpers import config_validation as cv
26  all_with_deprecated_constants,
27  check_if_deprecated_constant,
28  dir_with_deprecated_constants,
29 )
30 from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
31 from homeassistant.helpers.entity_component import EntityComponent
32 from homeassistant.helpers.typing import ConfigType
33 from homeassistant.loader import bind_hass
34 from homeassistant.util.hass_dict import HassKey
35 
36 from .const import ( # noqa: F401
37  _DEPRECATED_DEVICE_CLASS_DEHUMIDIFIER,
38  _DEPRECATED_DEVICE_CLASS_HUMIDIFIER,
39  _DEPRECATED_SUPPORT_MODES,
40  ATTR_ACTION,
41  ATTR_AVAILABLE_MODES,
42  ATTR_CURRENT_HUMIDITY,
43  ATTR_HUMIDITY,
44  ATTR_MAX_HUMIDITY,
45  ATTR_MIN_HUMIDITY,
46  DEFAULT_MAX_HUMIDITY,
47  DEFAULT_MIN_HUMIDITY,
48  DOMAIN,
49  MODE_AUTO,
50  MODE_AWAY,
51  MODE_BABY,
52  MODE_BOOST,
53  MODE_COMFORT,
54  MODE_ECO,
55  MODE_HOME,
56  MODE_NORMAL,
57  MODE_SLEEP,
58  SERVICE_SET_HUMIDITY,
59  SERVICE_SET_MODE,
60  HumidifierAction,
61  HumidifierEntityFeature,
62 )
63 
64 _LOGGER = logging.getLogger(__name__)
65 
66 DATA_COMPONENT: HassKey[EntityComponent[HumidifierEntity]] = HassKey(DOMAIN)
67 ENTITY_ID_FORMAT = DOMAIN + ".{}"
68 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
69 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
70 SCAN_INTERVAL = timedelta(seconds=60)
71 
72 
73 class HumidifierDeviceClass(StrEnum):
74  """Device class for humidifiers."""
75 
76  HUMIDIFIER = "humidifier"
77  DEHUMIDIFIER = "dehumidifier"
78 
79 
80 DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(HumidifierDeviceClass))
81 
82 # DEVICE_CLASSES below is deprecated as of 2021.12
83 # use the HumidifierDeviceClass enum instead.
84 DEVICE_CLASSES = [cls.value for cls in HumidifierDeviceClass]
85 
86 # mypy: disallow-any-generics
87 
88 
89 @bind_hass
90 def is_on(hass: HomeAssistant, entity_id: str) -> bool:
91  """Return if the humidifier is on based on the statemachine.
92 
93  Async friendly.
94  """
95  return hass.states.is_state(entity_id, STATE_ON)
96 
97 
98 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
99  """Set up humidifier devices."""
100  component = hass.data[DATA_COMPONENT] = EntityComponent[HumidifierEntity](
101  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
102  )
103  await component.async_setup(config)
104 
105  component.async_register_entity_service(SERVICE_TURN_ON, None, "async_turn_on")
106  component.async_register_entity_service(SERVICE_TURN_OFF, None, "async_turn_off")
107  component.async_register_entity_service(SERVICE_TOGGLE, None, "async_toggle")
108  component.async_register_entity_service(
109  SERVICE_SET_MODE,
110  {vol.Required(ATTR_MODE): cv.string},
111  "async_set_mode",
112  [HumidifierEntityFeature.MODES],
113  )
114  component.async_register_entity_service(
115  SERVICE_SET_HUMIDITY,
116  {
117  vol.Required(ATTR_HUMIDITY): vol.All(
118  vol.Coerce(int), vol.Range(min=0, max=100)
119  )
120  },
121  async_service_humidity_set,
122  )
123 
124  return True
125 
126 
127 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
128  """Set up a config entry."""
129  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
130 
131 
132 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
133  """Unload a config entry."""
134  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
135 
136 
138  """A class that describes humidifier entities."""
139 
140  device_class: HumidifierDeviceClass | None = None
141 
142 
143 CACHED_PROPERTIES_WITH_ATTR_ = {
144  "device_class",
145  "action",
146  "current_humidity",
147  "target_humidity",
148  "mode",
149  "available_modes",
150  "min_humidity",
151  "max_humidity",
152  "supported_features",
153 }
154 
155 
156 class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
157  """Base class for humidifier entities."""
158 
159  _entity_component_unrecorded_attributes = frozenset(
160  {ATTR_MIN_HUMIDITY, ATTR_MAX_HUMIDITY, ATTR_AVAILABLE_MODES}
161  )
162 
163  entity_description: HumidifierEntityDescription
164  _attr_action: HumidifierAction | None = None
165  _attr_available_modes: list[str] | None
166  _attr_current_humidity: float | None = None
167  _attr_device_class: HumidifierDeviceClass | None
168  _attr_max_humidity: float = DEFAULT_MAX_HUMIDITY
169  _attr_min_humidity: float = DEFAULT_MIN_HUMIDITY
170  _attr_mode: str | None
171  _attr_supported_features: HumidifierEntityFeature = HumidifierEntityFeature(0)
172  _attr_target_humidity: float | None = None
173 
174  @property
175  def capability_attributes(self) -> dict[str, Any]:
176  """Return capability attributes."""
177  data: dict[str, Any] = {
178  ATTR_MIN_HUMIDITY: self.min_humiditymin_humidity,
179  ATTR_MAX_HUMIDITY: self.max_humiditymax_humidity,
180  }
181 
182  if HumidifierEntityFeature.MODES in self.supported_features_compatsupported_features_compat:
183  data[ATTR_AVAILABLE_MODES] = self.available_modesavailable_modes
184 
185  return data
186 
187  @cached_property
188  def device_class(self) -> HumidifierDeviceClass | None:
189  """Return the class of this entity."""
190  if hasattr(self, "_attr_device_class"):
191  return self._attr_device_class
192  if hasattr(self, "entity_description"):
193  return self.entity_description.device_class
194  return None
195 
196  @final
197  @property
198  def state_attributes(self) -> dict[str, Any]:
199  """Return the optional state attributes."""
200  data: dict[str, Any] = {}
201 
202  if self.actionaction is not None:
203  data[ATTR_ACTION] = self.actionaction if self.is_onis_on else HumidifierAction.OFF
204 
205  if self.current_humiditycurrent_humidity is not None:
206  data[ATTR_CURRENT_HUMIDITY] = self.current_humiditycurrent_humidity
207 
208  if self.target_humiditytarget_humidity is not None:
209  data[ATTR_HUMIDITY] = self.target_humiditytarget_humidity
210 
211  if HumidifierEntityFeature.MODES in self.supported_features_compatsupported_features_compat:
212  data[ATTR_MODE] = self.modemode
213 
214  return data
215 
216  @cached_property
217  def action(self) -> HumidifierAction | None:
218  """Return the current action."""
219  return self._attr_action
220 
221  @cached_property
222  def current_humidity(self) -> float | None:
223  """Return the current humidity."""
224  return self._attr_current_humidity
225 
226  @cached_property
227  def target_humidity(self) -> float | None:
228  """Return the humidity we try to reach."""
229  return self._attr_target_humidity
230 
231  @cached_property
232  def mode(self) -> str | None:
233  """Return the current mode, e.g., home, auto, baby.
234 
235  Requires HumidifierEntityFeature.MODES.
236  """
237  return self._attr_mode
238 
239  @cached_property
240  def available_modes(self) -> list[str] | None:
241  """Return a list of available modes.
242 
243  Requires HumidifierEntityFeature.MODES.
244  """
245  return self._attr_available_modes
246 
247  def set_humidity(self, humidity: int) -> None:
248  """Set new target humidity."""
249  raise NotImplementedError
250 
251  async def async_set_humidity(self, humidity: int) -> None:
252  """Set new target humidity."""
253  await self.hasshass.async_add_executor_job(self.set_humidityset_humidity, humidity)
254 
255  def set_mode(self, mode: str) -> None:
256  """Set new mode."""
257  raise NotImplementedError
258 
259  async def async_set_mode(self, mode: str) -> None:
260  """Set new mode."""
261  await self.hasshass.async_add_executor_job(self.set_modeset_mode, mode)
262 
263  @cached_property
264  def min_humidity(self) -> float:
265  """Return the minimum humidity."""
266  return self._attr_min_humidity
267 
268  @cached_property
269  def max_humidity(self) -> float:
270  """Return the maximum humidity."""
271  return self._attr_max_humidity
272 
273  @cached_property
274  def supported_features(self) -> HumidifierEntityFeature:
275  """Return the list of supported features."""
276  return self._attr_supported_features
277 
278  @property
279  def supported_features_compat(self) -> HumidifierEntityFeature:
280  """Return the supported features as HumidifierEntityFeature.
281 
282  Remove this compatibility shim in 2025.1 or later.
283  """
284  features = self.supported_featuressupported_featuressupported_features
285  if type(features) is int: # noqa: E721
286  new_features = HumidifierEntityFeature(features)
287  self._report_deprecated_supported_features_values_report_deprecated_supported_features_values(new_features)
288  return new_features
289  return features
290 
291 
293  entity: HumidifierEntity, service_call: ServiceCall
294 ) -> None:
295  """Handle set humidity service."""
296  humidity = service_call.data[ATTR_HUMIDITY]
297  min_humidity = entity.min_humidity
298  max_humidity = entity.max_humidity
299  _LOGGER.debug(
300  "Check valid humidity %d in range %d - %d",
301  humidity,
302  min_humidity,
303  max_humidity,
304  )
305  if humidity < min_humidity or humidity > max_humidity:
307  translation_domain=DOMAIN,
308  translation_key="humidity_out_of_range",
309  translation_placeholders={
310  "humidity": str(humidity),
311  "min_humidity": str(min_humidity),
312  "max_humidity": str(max_humidity),
313  },
314  )
315 
316  await entity.async_set_humidity(humidity)
317 
318 
319 # As we import deprecated constants from the const module, we need to add these two functions
320 # otherwise this module will be logged for using deprecated constants and not the custom component
321 # These can be removed if no deprecated constant are in this module anymore
322 __getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
323 __dir__ = partial(
324  dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
325 )
326 __all__ = all_with_deprecated_constants(globals())
HumidifierEntityFeature supported_features_compat(self)
Definition: __init__.py:279
HumidifierEntityFeature supported_features(self)
Definition: __init__.py:274
HumidifierDeviceClass|None device_class(self)
Definition: __init__.py:188
None _report_deprecated_supported_features_values(self, IntFlag replacement)
Definition: entity.py:1645
int|None supported_features(self)
Definition: entity.py:861
None async_service_humidity_set(HumidifierEntity entity, ServiceCall service_call)
Definition: __init__.py:294
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:127
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:132
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:98
bool is_on(HomeAssistant hass, str entity_id)
Definition: __init__.py:90
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
Definition: deprecation.py:356