Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Cover devices."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from datetime import timedelta
7 from enum import IntFlag, StrEnum
8 import functools as ft
9 import logging
10 from typing import Any, final
11 
12 from propcache import cached_property
13 import voluptuous as vol
14 
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import ( # noqa: F401
17  SERVICE_CLOSE_COVER,
18  SERVICE_CLOSE_COVER_TILT,
19  SERVICE_OPEN_COVER,
20  SERVICE_OPEN_COVER_TILT,
21  SERVICE_SET_COVER_POSITION,
22  SERVICE_SET_COVER_TILT_POSITION,
23  SERVICE_STOP_COVER,
24  SERVICE_STOP_COVER_TILT,
25  SERVICE_TOGGLE,
26  SERVICE_TOGGLE_COVER_TILT,
27  STATE_CLOSED,
28  STATE_CLOSING,
29  STATE_OPEN,
30  STATE_OPENING,
31 )
32 from homeassistant.core import HomeAssistant
33 from homeassistant.helpers import config_validation as cv
35  DeprecatedConstantEnum,
36  all_with_deprecated_constants,
37  check_if_deprecated_constant,
38  dir_with_deprecated_constants,
39 )
40 from homeassistant.helpers.entity import Entity, EntityDescription
41 from homeassistant.helpers.entity_component import EntityComponent
42 from homeassistant.helpers.typing import ConfigType
43 from homeassistant.loader import bind_hass
44 from homeassistant.util.hass_dict import HassKey
45 
46 from .const import DOMAIN, INTENT_CLOSE_COVER, INTENT_OPEN_COVER # noqa: F401
47 
48 _LOGGER = logging.getLogger(__name__)
49 
50 DATA_COMPONENT: HassKey[EntityComponent[CoverEntity]] = HassKey(DOMAIN)
51 ENTITY_ID_FORMAT = DOMAIN + ".{}"
52 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
53 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
54 SCAN_INTERVAL = timedelta(seconds=15)
55 
56 
57 class CoverState(StrEnum):
58  """State of Cover entities."""
59 
60  CLOSED = "closed"
61  CLOSING = "closing"
62  OPEN = "open"
63  OPENING = "opening"
64 
65 
66 # STATE_* below are deprecated as of 2024.11
67 # when imported from homeassistant.components.cover
68 # use the CoverState enum instead.
69 _DEPRECATED_STATE_CLOSED = DeprecatedConstantEnum(CoverState.CLOSED, "2025.11")
70 _DEPRECATED_STATE_CLOSING = DeprecatedConstantEnum(CoverState.CLOSING, "2025.11")
71 _DEPRECATED_STATE_OPEN = DeprecatedConstantEnum(CoverState.OPEN, "2025.11")
72 _DEPRECATED_STATE_OPENING = DeprecatedConstantEnum(CoverState.OPENING, "2025.11")
73 
74 
75 class CoverDeviceClass(StrEnum):
76  """Device class for cover."""
77 
78  # Refer to the cover dev docs for device class descriptions
79  AWNING = "awning"
80  BLIND = "blind"
81  CURTAIN = "curtain"
82  DAMPER = "damper"
83  DOOR = "door"
84  GARAGE = "garage"
85  GATE = "gate"
86  SHADE = "shade"
87  SHUTTER = "shutter"
88  WINDOW = "window"
89 
90 
91 DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(CoverDeviceClass))
92 
93 # DEVICE_CLASS* below are deprecated as of 2021.12
94 # use the CoverDeviceClass enum instead.
95 DEVICE_CLASSES = [cls.value for cls in CoverDeviceClass]
96 _DEPRECATED_DEVICE_CLASS_AWNING = DeprecatedConstantEnum(
97  CoverDeviceClass.AWNING, "2025.1"
98 )
99 _DEPRECATED_DEVICE_CLASS_BLIND = DeprecatedConstantEnum(
100  CoverDeviceClass.BLIND, "2025.1"
101 )
102 _DEPRECATED_DEVICE_CLASS_CURTAIN = DeprecatedConstantEnum(
103  CoverDeviceClass.CURTAIN, "2025.1"
104 )
105 _DEPRECATED_DEVICE_CLASS_DAMPER = DeprecatedConstantEnum(
106  CoverDeviceClass.DAMPER, "2025.1"
107 )
108 _DEPRECATED_DEVICE_CLASS_DOOR = DeprecatedConstantEnum(CoverDeviceClass.DOOR, "2025.1")
109 _DEPRECATED_DEVICE_CLASS_GARAGE = DeprecatedConstantEnum(
110  CoverDeviceClass.GARAGE, "2025.1"
111 )
112 _DEPRECATED_DEVICE_CLASS_GATE = DeprecatedConstantEnum(CoverDeviceClass.GATE, "2025.1")
113 _DEPRECATED_DEVICE_CLASS_SHADE = DeprecatedConstantEnum(
114  CoverDeviceClass.SHADE, "2025.1"
115 )
116 _DEPRECATED_DEVICE_CLASS_SHUTTER = DeprecatedConstantEnum(
117  CoverDeviceClass.SHUTTER, "2025.1"
118 )
119 _DEPRECATED_DEVICE_CLASS_WINDOW = DeprecatedConstantEnum(
120  CoverDeviceClass.WINDOW, "2025.1"
121 )
122 
123 # mypy: disallow-any-generics
124 
125 
126 class CoverEntityFeature(IntFlag):
127  """Supported features of the cover entity."""
128 
129  OPEN = 1
130  CLOSE = 2
131  SET_POSITION = 4
132  STOP = 8
133  OPEN_TILT = 16
134  CLOSE_TILT = 32
135  STOP_TILT = 64
136  SET_TILT_POSITION = 128
137 
138 
139 # These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
140 # Please use the CoverEntityFeature enum instead.
141 _DEPRECATED_SUPPORT_OPEN = DeprecatedConstantEnum(CoverEntityFeature.OPEN, "2025.1")
142 _DEPRECATED_SUPPORT_CLOSE = DeprecatedConstantEnum(CoverEntityFeature.CLOSE, "2025.1")
143 _DEPRECATED_SUPPORT_SET_POSITION = DeprecatedConstantEnum(
144  CoverEntityFeature.SET_POSITION, "2025.1"
145 )
146 _DEPRECATED_SUPPORT_STOP = DeprecatedConstantEnum(CoverEntityFeature.STOP, "2025.1")
147 _DEPRECATED_SUPPORT_OPEN_TILT = DeprecatedConstantEnum(
148  CoverEntityFeature.OPEN_TILT, "2025.1"
149 )
150 _DEPRECATED_SUPPORT_CLOSE_TILT = DeprecatedConstantEnum(
151  CoverEntityFeature.CLOSE_TILT, "2025.1"
152 )
153 _DEPRECATED_SUPPORT_STOP_TILT = DeprecatedConstantEnum(
154  CoverEntityFeature.STOP_TILT, "2025.1"
155 )
156 _DEPRECATED_SUPPORT_SET_TILT_POSITION = DeprecatedConstantEnum(
157  CoverEntityFeature.SET_TILT_POSITION, "2025.1"
158 )
159 
160 ATTR_CURRENT_POSITION = "current_position"
161 ATTR_CURRENT_TILT_POSITION = "current_tilt_position"
162 ATTR_POSITION = "position"
163 ATTR_TILT_POSITION = "tilt_position"
164 
165 
166 @bind_hass
167 def is_closed(hass: HomeAssistant, entity_id: str) -> bool:
168  """Return if the cover is closed based on the statemachine."""
169  return hass.states.is_state(entity_id, CoverState.CLOSED)
170 
171 
172 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
173  """Track states and offer events for covers."""
174  component = hass.data[DATA_COMPONENT] = EntityComponent[CoverEntity](
175  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
176  )
177 
178  await component.async_setup(config)
179 
180  component.async_register_entity_service(
181  SERVICE_OPEN_COVER, None, "async_open_cover", [CoverEntityFeature.OPEN]
182  )
183 
184  component.async_register_entity_service(
185  SERVICE_CLOSE_COVER, None, "async_close_cover", [CoverEntityFeature.CLOSE]
186  )
187 
188  component.async_register_entity_service(
189  SERVICE_SET_COVER_POSITION,
190  {
191  vol.Required(ATTR_POSITION): vol.All(
192  vol.Coerce(int), vol.Range(min=0, max=100)
193  )
194  },
195  "async_set_cover_position",
196  [CoverEntityFeature.SET_POSITION],
197  )
198 
199  component.async_register_entity_service(
200  SERVICE_STOP_COVER, None, "async_stop_cover", [CoverEntityFeature.STOP]
201  )
202 
203  component.async_register_entity_service(
204  SERVICE_TOGGLE,
205  None,
206  "async_toggle",
207  [CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE],
208  )
209 
210  component.async_register_entity_service(
211  SERVICE_OPEN_COVER_TILT,
212  None,
213  "async_open_cover_tilt",
214  [CoverEntityFeature.OPEN_TILT],
215  )
216 
217  component.async_register_entity_service(
218  SERVICE_CLOSE_COVER_TILT,
219  None,
220  "async_close_cover_tilt",
221  [CoverEntityFeature.CLOSE_TILT],
222  )
223 
224  component.async_register_entity_service(
225  SERVICE_STOP_COVER_TILT,
226  None,
227  "async_stop_cover_tilt",
228  [CoverEntityFeature.STOP_TILT],
229  )
230 
231  component.async_register_entity_service(
232  SERVICE_SET_COVER_TILT_POSITION,
233  {
234  vol.Required(ATTR_TILT_POSITION): vol.All(
235  vol.Coerce(int), vol.Range(min=0, max=100)
236  )
237  },
238  "async_set_cover_tilt_position",
239  [CoverEntityFeature.SET_TILT_POSITION],
240  )
241 
242  component.async_register_entity_service(
243  SERVICE_TOGGLE_COVER_TILT,
244  None,
245  "async_toggle_tilt",
246  [CoverEntityFeature.OPEN_TILT | CoverEntityFeature.CLOSE_TILT],
247  )
248 
249  return True
250 
251 
252 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
253  """Set up a config entry."""
254  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
255 
256 
257 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
258  """Unload a config entry."""
259  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
260 
261 
262 class CoverEntityDescription(EntityDescription, frozen_or_thawed=True):
263  """A class that describes cover entities."""
264 
265  device_class: CoverDeviceClass | None = None
266 
267 
268 CACHED_PROPERTIES_WITH_ATTR_ = {
269  "current_cover_position",
270  "current_cover_tilt_position",
271  "device_class",
272  "is_opening",
273  "is_closing",
274  "is_closed",
275 }
276 
277 
278 class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
279  """Base class for cover entities."""
280 
281  entity_description: CoverEntityDescription
282  _attr_current_cover_position: int | None = None
283  _attr_current_cover_tilt_position: int | None = None
284  _attr_device_class: CoverDeviceClass | None
285  _attr_is_closed: bool | None
286  _attr_is_closing: bool | None = None
287  _attr_is_opening: bool | None = None
288  _attr_state: None = None
289  _attr_supported_features: CoverEntityFeature | None
290 
291  _cover_is_last_toggle_direction_open = True
292 
293  @cached_property
294  def current_cover_position(self) -> int | None:
295  """Return current position of cover.
296 
297  None is unknown, 0 is closed, 100 is fully open.
298  """
299  return self._attr_current_cover_position
300 
301  @cached_property
302  def current_cover_tilt_position(self) -> int | None:
303  """Return current position of cover tilt.
304 
305  None is unknown, 0 is closed, 100 is fully open.
306  """
307  return self._attr_current_cover_tilt_position
308 
309  @cached_property
310  def device_class(self) -> CoverDeviceClass | None:
311  """Return the class of this entity."""
312  if hasattr(self, "_attr_device_class"):
313  return self._attr_device_class
314  if hasattr(self, "entity_description"):
315  return self.entity_description.device_class
316  return None
317 
318  @property
319  @final
320  def state(self) -> str | None:
321  """Return the state of the cover."""
322  if self.is_openingis_opening:
323  self._cover_is_last_toggle_direction_open_cover_is_last_toggle_direction_open = True
324  return CoverState.OPENING
325  if self.is_closingis_closing:
326  self._cover_is_last_toggle_direction_open_cover_is_last_toggle_direction_open = False
327  return CoverState.CLOSING
328 
329  if (closed := self.is_closedis_closed) is None:
330  return None
331 
332  return CoverState.CLOSED if closed else CoverState.OPEN
333 
334  @final
335  @property
336  def state_attributes(self) -> dict[str, Any]:
337  """Return the state attributes."""
338  data = {}
339 
340  if (current := self.current_cover_positioncurrent_cover_positioncurrent_cover_position) is not None:
341  data[ATTR_CURRENT_POSITION] = current
342 
343  if (current_tilt := self.current_cover_tilt_positioncurrent_cover_tilt_positioncurrent_cover_tilt_position) is not None:
344  data[ATTR_CURRENT_TILT_POSITION] = current_tilt
345 
346  return data
347 
348  @property
349  def supported_features(self) -> CoverEntityFeature:
350  """Flag supported features."""
351  if (features := self._attr_supported_features) is not None:
352  if type(features) is int: # noqa: E721
353  new_features = CoverEntityFeature(features)
354  self._report_deprecated_supported_features_values_report_deprecated_supported_features_values(new_features)
355  return new_features
356  return features
357 
358  supported_features = (
359  CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
360  )
361 
362  if self.current_cover_positioncurrent_cover_positioncurrent_cover_position is not None:
363  supported_features |= CoverEntityFeature.SET_POSITION
364 
365  if self.current_cover_tilt_positioncurrent_cover_tilt_positioncurrent_cover_tilt_position is not None:
366  supported_features |= (
367  CoverEntityFeature.OPEN_TILT
368  | CoverEntityFeature.CLOSE_TILT
369  | CoverEntityFeature.STOP_TILT
370  | CoverEntityFeature.SET_TILT_POSITION
371  )
372 
373  return supported_features
374 
375  @cached_property
376  def is_opening(self) -> bool | None:
377  """Return if the cover is opening or not."""
378  return self._attr_is_opening
379 
380  @cached_property
381  def is_closing(self) -> bool | None:
382  """Return if the cover is closing or not."""
383  return self._attr_is_closing
384 
385  @cached_property
386  def is_closed(self) -> bool | None:
387  """Return if the cover is closed or not."""
388  return self._attr_is_closed
389 
390  def open_cover(self, **kwargs: Any) -> None:
391  """Open the cover."""
392  raise NotImplementedError
393 
394  async def async_open_cover(self, **kwargs: Any) -> None:
395  """Open the cover."""
396  await self.hasshass.async_add_executor_job(ft.partial(self.open_coveropen_cover, **kwargs))
397 
398  def close_cover(self, **kwargs: Any) -> None:
399  """Close cover."""
400  raise NotImplementedError
401 
402  async def async_close_cover(self, **kwargs: Any) -> None:
403  """Close cover."""
404  await self.hasshass.async_add_executor_job(ft.partial(self.close_coverclose_cover, **kwargs))
405 
406  def toggle(self, **kwargs: Any) -> None:
407  """Toggle the entity."""
408  fns = {
409  "open": self.open_coveropen_cover,
410  "close": self.close_coverclose_cover,
411  "stop": self.stop_coverstop_cover,
412  }
413  function = self._get_toggle_function(fns)
414  function(**kwargs)
415 
416  async def async_toggle(self, **kwargs: Any) -> None:
417  """Toggle the entity."""
418  fns = {
419  "open": self.async_open_coverasync_open_cover,
420  "close": self.async_close_coverasync_close_cover,
421  "stop": self.async_stop_coverasync_stop_cover,
422  }
423  function = self._get_toggle_function(fns)
424  await function(**kwargs)
425 
426  def set_cover_position(self, **kwargs: Any) -> None:
427  """Move the cover to a specific position."""
428 
429  async def async_set_cover_position(self, **kwargs: Any) -> None:
430  """Move the cover to a specific position."""
431  await self.hasshass.async_add_executor_job(
432  ft.partial(self.set_cover_positionset_cover_position, **kwargs)
433  )
434 
435  def stop_cover(self, **kwargs: Any) -> None:
436  """Stop the cover."""
437 
438  async def async_stop_cover(self, **kwargs: Any) -> None:
439  """Stop the cover."""
440  await self.hasshass.async_add_executor_job(ft.partial(self.stop_coverstop_cover, **kwargs))
441 
442  def open_cover_tilt(self, **kwargs: Any) -> None:
443  """Open the cover tilt."""
444 
445  async def async_open_cover_tilt(self, **kwargs: Any) -> None:
446  """Open the cover tilt."""
447  await self.hasshass.async_add_executor_job(
448  ft.partial(self.open_cover_tiltopen_cover_tilt, **kwargs)
449  )
450 
451  def close_cover_tilt(self, **kwargs: Any) -> None:
452  """Close the cover tilt."""
453 
454  async def async_close_cover_tilt(self, **kwargs: Any) -> None:
455  """Close the cover tilt."""
456  await self.hasshass.async_add_executor_job(
457  ft.partial(self.close_cover_tiltclose_cover_tilt, **kwargs)
458  )
459 
460  def set_cover_tilt_position(self, **kwargs: Any) -> None:
461  """Move the cover tilt to a specific position."""
462 
463  async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
464  """Move the cover tilt to a specific position."""
465  await self.hasshass.async_add_executor_job(
466  ft.partial(self.set_cover_tilt_positionset_cover_tilt_position, **kwargs)
467  )
468 
469  def stop_cover_tilt(self, **kwargs: Any) -> None:
470  """Stop the cover."""
471 
472  async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
473  """Stop the cover."""
474  await self.hasshass.async_add_executor_job(
475  ft.partial(self.stop_cover_tiltstop_cover_tilt, **kwargs)
476  )
477 
478  def toggle_tilt(self, **kwargs: Any) -> None:
479  """Toggle the entity."""
480  if self.current_cover_tilt_positioncurrent_cover_tilt_positioncurrent_cover_tilt_position == 0:
481  self.open_cover_tiltopen_cover_tilt(**kwargs)
482  else:
483  self.close_cover_tiltclose_cover_tilt(**kwargs)
484 
485  async def async_toggle_tilt(self, **kwargs: Any) -> None:
486  """Toggle the entity."""
487  if self.current_cover_tilt_positioncurrent_cover_tilt_positioncurrent_cover_tilt_position == 0:
488  await self.async_open_cover_tiltasync_open_cover_tilt(**kwargs)
489  else:
490  await self.async_close_cover_tiltasync_close_cover_tilt(**kwargs)
491 
492  def _get_toggle_function[**_P, _R](
493  self, fns: dict[str, Callable[_P, _R]]
494  ) -> Callable[_P, _R]:
495  # If we are opening or closing and we support stopping, then we should stop
496  if self.supported_featuressupported_featuressupported_features & CoverEntityFeature.STOP and (
497  self.is_closingis_closing or self.is_openingis_opening
498  ):
499  return fns["stop"]
500 
501  # If we are fully closed or in the process of closing, then we should open
502  if self.is_closedis_closed or self.is_closingis_closing:
503  return fns["open"]
504 
505  # If we are fully open or in the process of opening, then we should close
506  if self.current_cover_positioncurrent_cover_positioncurrent_cover_position == 100 or self.is_openingis_opening:
507  return fns["close"]
508 
509  # We are any of:
510  # * fully open but do not report `current_cover_position`
511  # * stopped partially open
512  # * either opening or closing, but do not report them
513  # If we previously reported opening/closing, we should move in the opposite direction.
514  # Otherwise, we must assume we are (partially) open and should always close.
515  # Note: _cover_is_last_toggle_direction_open will always remain True if we never report opening/closing.
516  return (
517  fns["close"] if self._cover_is_last_toggle_direction_open_cover_is_last_toggle_direction_open else fns["open"]
518  )
519 
520 
521 # These can be removed if no deprecated constant are in this module anymore
522 __getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
523 __dir__ = ft.partial(
524  dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
525 )
526 __all__ = all_with_deprecated_constants(globals())
None close_cover(self, **Any kwargs)
Definition: __init__.py:398
CoverDeviceClass|None device_class(self)
Definition: __init__.py:310
None async_toggle_tilt(self, **Any kwargs)
Definition: __init__.py:485
None open_cover(self, **Any kwargs)
Definition: __init__.py:390
None async_toggle(self, **Any kwargs)
Definition: __init__.py:416
None async_open_cover_tilt(self, **Any kwargs)
Definition: __init__.py:445
None async_open_cover(self, **Any kwargs)
Definition: __init__.py:394
CoverEntityFeature supported_features(self)
Definition: __init__.py:349
None async_close_cover(self, **Any kwargs)
Definition: __init__.py:402
None async_set_cover_tilt_position(self, **Any kwargs)
Definition: __init__.py:463
None async_set_cover_position(self, **Any kwargs)
Definition: __init__.py:429
None async_stop_cover_tilt(self, **Any kwargs)
Definition: __init__.py:472
None stop_cover(self, **Any kwargs)
Definition: __init__.py:435
None toggle_tilt(self, **Any kwargs)
Definition: __init__.py:478
None close_cover_tilt(self, **Any kwargs)
Definition: __init__.py:451
None stop_cover_tilt(self, **Any kwargs)
Definition: __init__.py:469
None async_stop_cover(self, **Any kwargs)
Definition: __init__.py:438
None set_cover_tilt_position(self, **Any kwargs)
Definition: __init__.py:460
None toggle(self, **Any kwargs)
Definition: __init__.py:406
None async_close_cover_tilt(self, **Any kwargs)
Definition: __init__.py:454
None set_cover_position(self, **Any kwargs)
Definition: __init__.py:426
None open_cover_tilt(self, **Any kwargs)
Definition: __init__.py:442
None _report_deprecated_supported_features_values(self, IntFlag replacement)
Definition: entity.py:1645
int|None supported_features(self)
Definition: entity.py:861
bool is_closed(HomeAssistant hass, str entity_id)
Definition: __init__.py:167
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:252
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:172
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:257
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
Definition: deprecation.py:356