Skip to content

Event

event

event domain implementation (read-only, stateless triggers).

Home Assistant's event domain (added in 2023.8) represents stateless trigger entities such as button presses, doorbell rings, and remote control actions. The entity's state is an ISO-8601 timestamp of the most recent event, while event_type (the kind of event that just fired) and event_types (all possible event types) live in the attributes.

This module exposes those values as typed properties and provides a single intent-driven listener decorator, Event.on_event, that can be used either bare (@button.on_event) or with a filter (@button.on_event(event_type="double_press")).

SPEC module-attribute

SPEC: DomainSpec[Event] = register_domain(DomainSpec(name='event', entity_cls=Event))

The DomainSpec registered with the shared DomainRegistry.

Event

Bases: Entity

A read-only Home Assistant event entity.

Event entities represent discrete, stateless triggers (e.g. a button press). Each fire updates the entity's state to the new event timestamp and populates the event_type attribute with the kind of event that occurred.

Listener callbacks registered via on_event receive a single positional argument: the event_type string of the event that just fired (e.g. "single_press").

Source code in src/haclient/domains/event.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
class Event(Entity):
    """A read-only Home Assistant event entity.

    Event entities represent discrete, stateless triggers (e.g. a
    button press). Each fire updates the entity's ``state`` to the new
    event timestamp and populates the ``event_type`` attribute with the
    kind of event that occurred.

    Listener callbacks registered via `on_event` receive a single
    positional argument: the ``event_type`` string of the event that
    just fired (e.g. ``"single_press"``).
    """

    domain = "event"

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        # Listeners keyed by event_type filter. ``None`` is the
        # catch-all bucket that receives every event.
        self._event_listeners: dict[str | None, list[Callable[[str], Any]]] = {}

    # -- State properties ---------------------------------------------

    @property
    def event_type(self) -> str | None:
        """Type of the most recent event (e.g. ``"single_press"``).

        Returns
        -------
        str or None
            The event type from the entity's attributes, or ``None`` if
            the entity has not fired yet (or the attribute is missing).
        """
        value = self.attributes.get("event_type")
        return str(value) if value is not None else None

    @property
    def event_types(self) -> list[str] | None:
        """All event types this entity is capable of firing.

        Returns
        -------
        list of str or None
            The declared event-type catalogue, or ``None`` if the
            attribute is absent.
        """
        value = self.attributes.get("event_types")
        if value is None:
            return None
        if isinstance(value, list):
            return [str(item) for item in value]
        return None

    @property
    def device_class(self) -> str | None:
        """Device class (e.g. ``"button"``, ``"doorbell"``)."""
        value = self.attributes.get("device_class")
        return str(value) if value is not None else None

    # -- Listener decorator -------------------------------------------

    @overload
    def on_event(self, func: Callable[[str], Any]) -> Callable[[str], Any]: ...

    @overload
    def on_event(
        self, *, event_type: str
    ) -> Callable[[Callable[[str], Any]], Callable[[str], Any]]: ...

    def on_event(
        self,
        func: Callable[[str], Any] | None = None,
        *,
        event_type: str | None = None,
    ) -> Any:
        """Register a listener for events fired by this entity.

        May be used either as a bare decorator to receive every event,
        or as a parameterised decorator to filter by ``event_type``.

        Parameters
        ----------
        func : callable, optional
            Sync or async callable receiving a single positional
            argument: the event-type string that just fired. Supplied
            implicitly when used as ``@entity.on_event``.
        event_type : str or None, keyword-only
            Restrict the listener to a specific event-type. When
            ``None`` (the default), the listener receives every event.

        Returns
        -------
        callable
            When called as a bare decorator, returns *func*. When called
            with ``event_type=...``, returns a decorator that registers
            and then returns the wrapped function.

        Examples
        --------
        Listen to every event::

            @button.on_event
            async def any_press(event_type):
                ...

        Filter to a specific event type::

            @button.on_event(event_type="double_press")
            async def double(event_type):
                ...
        """
        if func is not None and event_type is not None:
            raise TypeError("on_event accepts either a function or event_type, not both")

        if func is not None:
            # Bare-decorator form: @entity.on_event
            self._event_listeners.setdefault(None, []).append(func)
            return func

        # Parameterised form: @entity.on_event(event_type="...")
        def decorator(inner: Callable[[str], Any]) -> Callable[[str], Any]:
            self._event_listeners.setdefault(event_type, []).append(inner)
            return inner

        return decorator

    def remove_event_listener(self, func: Callable[[str], Any]) -> None:
        """Remove a previously registered event listener.

        Searches all registered buckets (catch-all and per-event-type)
        and removes the first match. Unknown callables are silently
        ignored.

        Parameters
        ----------
        func : callable
            The exact callable previously registered via `on_event`.
        """
        for listeners in self._event_listeners.values():
            try:
                listeners.remove(func)
                return
            except ValueError:
                continue

    # -- Dispatch -----------------------------------------------------

    def _handle_state_changed(
        self,
        old_state: dict[str, Any] | None,
        new_state: dict[str, Any] | None,
    ) -> None:
        """Apply state, then dispatch typed event listeners.

        An event "fires" whenever the state timestamp changes. The
        ``event_type`` attribute on the *new* state describes what kind
        of event occurred and is the payload delivered to listeners.
        """
        old_ts = (old_state or {}).get("state")
        new_ts = (new_state or {}).get("state")
        super()._handle_state_changed(old_state, new_state)

        if new_state is None or new_ts == old_ts:
            return
        if new_ts in (None, "unknown", "unavailable"):
            return

        new_attrs = new_state.get("attributes") or {}
        event_type_raw = new_attrs.get("event_type")
        if event_type_raw is None:
            return
        event_type = str(event_type_raw)

        # Catch-all listeners always fire.
        for listener in list(self._event_listeners.get(None, [])):
            self._dispatch_event(listener, event_type)
        # Typed listeners only fire when the event_type matches.
        for listener in list(self._event_listeners.get(event_type, [])):
            self._dispatch_event(listener, event_type)

    def _dispatch_event(self, handler: Callable[[str], Any], event_type: str) -> None:
        """Invoke an event handler with the event_type payload.

        Reuses the base class's value-dispatch path so that async
        handlers are scheduled via the registered `Clock` while sync
        ones run immediately.
        """
        # The base ``_schedule_value`` helper passes ``(old, new)`` to
        # the callback; here we only have a single payload, so we adapt
        # via a thin lambda. Errors are caught inside ``_schedule_value``.
        adapter: ValueChangeHandler = lambda _old, new: handler(new)  # noqa: E731
        self._schedule_value(adapter, None, event_type)

event_type property

event_type: str | None

Type of the most recent event (e.g. "single_press").

Returns:

Type Description
str or None

The event type from the entity's attributes, or None if the entity has not fired yet (or the attribute is missing).

event_types property

event_types: list[str] | None

All event types this entity is capable of firing.

Returns:

Type Description
list of str or None

The declared event-type catalogue, or None if the attribute is absent.

device_class property

device_class: str | None

Device class (e.g. "button", "doorbell").

on_event

on_event(func: Callable[[str], Any]) -> Callable[[str], Any]
on_event(*, event_type: str) -> Callable[[Callable[[str], Any]], Callable[[str], Any]]
on_event(func: Callable[[str], Any] | None = None, *, event_type: str | None = None) -> Any

Register a listener for events fired by this entity.

May be used either as a bare decorator to receive every event, or as a parameterised decorator to filter by event_type.

Parameters:

Name Type Description Default
func callable

Sync or async callable receiving a single positional argument: the event-type string that just fired. Supplied implicitly when used as @entity.on_event.

None
event_type (str or None, keyword - only)

Restrict the listener to a specific event-type. When None (the default), the listener receives every event.

None

Returns:

Type Description
callable

When called as a bare decorator, returns func. When called with event_type=..., returns a decorator that registers and then returns the wrapped function.

Examples:

Listen to every event::

@button.on_event
async def any_press(event_type):
    ...

Filter to a specific event type::

@button.on_event(event_type="double_press")
async def double(event_type):
    ...
Source code in src/haclient/domains/event.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def on_event(
    self,
    func: Callable[[str], Any] | None = None,
    *,
    event_type: str | None = None,
) -> Any:
    """Register a listener for events fired by this entity.

    May be used either as a bare decorator to receive every event,
    or as a parameterised decorator to filter by ``event_type``.

    Parameters
    ----------
    func : callable, optional
        Sync or async callable receiving a single positional
        argument: the event-type string that just fired. Supplied
        implicitly when used as ``@entity.on_event``.
    event_type : str or None, keyword-only
        Restrict the listener to a specific event-type. When
        ``None`` (the default), the listener receives every event.

    Returns
    -------
    callable
        When called as a bare decorator, returns *func*. When called
        with ``event_type=...``, returns a decorator that registers
        and then returns the wrapped function.

    Examples
    --------
    Listen to every event::

        @button.on_event
        async def any_press(event_type):
            ...

    Filter to a specific event type::

        @button.on_event(event_type="double_press")
        async def double(event_type):
            ...
    """
    if func is not None and event_type is not None:
        raise TypeError("on_event accepts either a function or event_type, not both")

    if func is not None:
        # Bare-decorator form: @entity.on_event
        self._event_listeners.setdefault(None, []).append(func)
        return func

    # Parameterised form: @entity.on_event(event_type="...")
    def decorator(inner: Callable[[str], Any]) -> Callable[[str], Any]:
        self._event_listeners.setdefault(event_type, []).append(inner)
        return inner

    return decorator

remove_event_listener

remove_event_listener(func: Callable[[str], Any]) -> None

Remove a previously registered event listener.

Searches all registered buckets (catch-all and per-event-type) and removes the first match. Unknown callables are silently ignored.

Parameters:

Name Type Description Default
func callable

The exact callable previously registered via on_event.

required
Source code in src/haclient/domains/event.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def remove_event_listener(self, func: Callable[[str], Any]) -> None:
    """Remove a previously registered event listener.

    Searches all registered buckets (catch-all and per-event-type)
    and removes the first match. Unknown callables are silently
    ignored.

    Parameters
    ----------
    func : callable
        The exact callable previously registered via `on_event`.
    """
    for listeners in self._event_listeners.values():
        try:
            listeners.remove(func)
            return
        except ValueError:
            continue