Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Component for handling incoming events as a platform."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import asdict, dataclass
6 from datetime import datetime, timedelta
7 from enum import StrEnum
8 import logging
9 from typing import Any, Self, final
10 
11 from propcache import cached_property
12 
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.core import HomeAssistant
15 from homeassistant.helpers import config_validation as cv
16 from homeassistant.helpers.entity import EntityDescription
17 from homeassistant.helpers.entity_component import EntityComponent
18 from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
19 from homeassistant.helpers.typing import ConfigType
20 from homeassistant.util import dt as dt_util
21 from homeassistant.util.hass_dict import HassKey
22 
23 from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN
24 
25 _LOGGER = logging.getLogger(__name__)
26 DATA_COMPONENT: HassKey[EntityComponent[EventEntity]] = HassKey(DOMAIN)
27 ENTITY_ID_FORMAT = DOMAIN + ".{}"
28 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
29 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
30 SCAN_INTERVAL = timedelta(seconds=30)
31 
32 
33 class EventDeviceClass(StrEnum):
34  """Device class for events."""
35 
36  DOORBELL = "doorbell"
37  BUTTON = "button"
38  MOTION = "motion"
39 
40 
41 __all__ = [
42  "ATTR_EVENT_TYPE",
43  "ATTR_EVENT_TYPES",
44  "DOMAIN",
45  "PLATFORM_SCHEMA_BASE",
46  "PLATFORM_SCHEMA",
47  "EventDeviceClass",
48  "EventEntity",
49  "EventEntityDescription",
50 ]
51 
52 # mypy: disallow-any-generics
53 
54 
55 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
56  """Set up Event entities."""
57  component = hass.data[DATA_COMPONENT] = EntityComponent[EventEntity](
58  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
59  )
60  await component.async_setup(config)
61  return True
62 
63 
64 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
65  """Set up a config entry."""
66  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
67 
68 
69 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
70  """Unload a config entry."""
71  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
72 
73 
74 class EventEntityDescription(EntityDescription, frozen_or_thawed=True):
75  """A class that describes event entities."""
76 
77  device_class: EventDeviceClass | None = None
78  event_types: list[str] | None = None
79 
80 
81 @dataclass
83  """Object to hold extra stored data."""
84 
85  last_event_type: str | None
86  last_event_attributes: dict[str, Any] | None
87 
88  def as_dict(self) -> dict[str, Any]:
89  """Return a dict representation of the event data."""
90  return asdict(self)
91 
92  @classmethod
93  def from_dict(cls, restored: dict[str, Any]) -> Self | None:
94  """Initialize a stored event state from a dict."""
95  try:
96  return cls(
97  restored["last_event_type"],
98  restored["last_event_attributes"],
99  )
100  except KeyError:
101  return None
102 
103 
104 CACHED_PROPERTIES_WITH_ATTR_ = {
105  "device_class",
106  "event_types",
107 }
108 
109 
110 class EventEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
111  """Representation of an Event entity."""
112 
113  _entity_component_unrecorded_attributes = frozenset({ATTR_EVENT_TYPES})
114 
115  entity_description: EventEntityDescription
116  _attr_device_class: EventDeviceClass | None
117  _attr_event_types: list[str]
118  _attr_state: None
119 
120  __last_event_triggered: datetime | None = None
121  __last_event_type: str | None = None
122  __last_event_attributes: dict[str, Any] | None = None
123 
124  @cached_property
125  def device_class(self) -> EventDeviceClass | None:
126  """Return the class of this entity."""
127  if hasattr(self, "_attr_device_class"):
128  return self._attr_device_class
129  if hasattr(self, "entity_description"):
130  return self.entity_description.device_class
131  return None
132 
133  @cached_property
134  def event_types(self) -> list[str]:
135  """Return a list of possible events."""
136  if hasattr(self, "_attr_event_types"):
137  return self._attr_event_types
138  if (
139  hasattr(self, "entity_description")
140  and self.entity_description.event_types is not None
141  ):
142  return self.entity_description.event_types
143  raise AttributeError
144 
145  @final
147  self, event_type: str, event_attributes: dict[str, Any] | None = None
148  ) -> None:
149  """Process a new event."""
150  if event_type not in self.event_typesevent_types:
151  raise ValueError(f"Invalid event type {event_type} for {self.entity_id}")
152  self.__last_event_triggered__last_event_triggered = dt_util.utcnow()
153  self.__last_event_type__last_event_type = event_type
154  self.__last_event_attributes__last_event_attributes = event_attributes
155 
156  def _default_to_device_class_name(self) -> bool:
157  """Return True if an unnamed entity should be named by its device class.
158 
159  For events this is True if the entity has a device class.
160  """
161  return self.device_classdevice_classdevice_class is not None
162 
163  @property
164  @final
165  def capability_attributes(self) -> dict[str, list[str]]:
166  """Return capability attributes."""
167  return {
168  ATTR_EVENT_TYPES: self.event_typesevent_types,
169  }
170 
171  @property
172  @final
173  def state(self) -> str | None:
174  """Return the entity state."""
175  if (last_event := self.__last_event_triggered__last_event_triggered) is None:
176  return None
177  return last_event.isoformat(timespec="milliseconds")
178 
179  @final
180  @property
181  def state_attributes(self) -> dict[str, Any]:
182  """Return the state attributes."""
183  attributes = {ATTR_EVENT_TYPE: self.__last_event_type__last_event_type}
184  if last_event_attributes := self.__last_event_attributes__last_event_attributes:
185  attributes |= last_event_attributes
186  return attributes
187 
188  @final
189  async def async_internal_added_to_hass(self) -> None:
190  """Call when the event entity is added to hass."""
191  await super().async_internal_added_to_hass()
192  if (
193  (state := await self.async_get_last_stateasync_get_last_state())
194  and state.state is not None
195  and (event_data := await self.async_get_last_event_dataasync_get_last_event_data())
196  ):
197  self.__last_event_triggered__last_event_triggered = dt_util.parse_datetime(state.state)
198  self.__last_event_type__last_event_type = event_data.last_event_type
199  self.__last_event_attributes__last_event_attributes = event_data.last_event_attributes
200 
201  @property
202  def extra_restore_state_data(self) -> EventExtraStoredData:
203  """Return event specific state data to be restored."""
204  return EventExtraStoredData(
205  self.__last_event_type__last_event_type,
206  self.__last_event_attributes__last_event_attributes,
207  )
208 
209  async def async_get_last_event_data(self) -> EventExtraStoredData | None:
210  """Restore event specific state date."""
211  if (restored_last_extra_data := await self.async_get_last_extra_dataasync_get_last_extra_data()) is None:
212  return None
213  return EventExtraStoredData.from_dict(restored_last_extra_data.as_dict())
EventExtraStoredData|None async_get_last_event_data(self)
Definition: __init__.py:209
EventDeviceClass|None device_class(self)
Definition: __init__.py:125
dict[str, list[str]] capability_attributes(self)
Definition: __init__.py:165
EventExtraStoredData extra_restore_state_data(self)
Definition: __init__.py:202
None _trigger_event(self, str event_type, dict[str, Any]|None event_attributes=None)
Definition: __init__.py:148
Self|None from_dict(cls, dict[str, Any] restored)
Definition: __init__.py:93
ExtraStoredData|None async_get_last_extra_data(self)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:64
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:69
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:55