feat: Add event-based automation triggers (#2537)

This commit is contained in:
Dermot Duffy
2026-06-30 17:45:13 -07:00
committed by dermotduffy
parent b701366762
commit a31816c168
109 changed files with 4607 additions and 1623 deletions
@@ -3,6 +3,7 @@ import { CallTrigger } from './triggers/call';
import { CameraTrigger } from './triggers/camera';
import { ConfigTrigger } from './triggers/config';
import { DisplayModeTrigger } from './triggers/display-mode';
import { EventTrigger } from './triggers/event';
import { ExpandTrigger } from './triggers/expand';
import { FullscreenTrigger } from './triggers/fullscreen';
import { InitializedTrigger } from './triggers/initialized';
@@ -30,6 +31,8 @@ export const createTriggerEvaluator = (
return new NumericStateTrigger(trigger, context);
case 'template':
return new TemplateTrigger(trigger, context);
case 'event':
return new EventTrigger(trigger, context);
// `screen` watches window.matchMedia.
case 'screen':
+7 -1
View File
@@ -1,3 +1,4 @@
import { HASSManagerReadonlyInterface } from '../../card-controller/hass/types';
import { TemplateRenderer } from '../../card-controller/templates';
import { Trigger } from '../../config/schema/condition-trigger/triggers/types';
import { isEnabled } from '../common/is-enabled';
@@ -30,8 +31,13 @@ export class TriggersManager {
constructor(
triggers: Trigger[],
stateManager: ConditionStateManagerReadonlyInterface,
hassManager: HASSManagerReadonlyInterface,
) {
this._context = { stateManager, templateRenderer: new TemplateRenderer() };
this._context = {
stateManager,
templateRenderer: new TemplateRenderer(),
hassManager,
};
this._triggers = triggers.map((config) => ({
config,
evaluator: createTriggerEvaluator(config, this._context),
@@ -0,0 +1,57 @@
import { uniq } from 'lodash-es';
import {
EventSubscriptionRequest,
EventWatcherSubscriptionInterface,
} from '../../../card-controller/hass/event-watcher';
import { matchesEventContext, matchesEventData } from '../../../ha/event-match';
import { arrayify } from '../../../utils/basic';
import {
TriggerCallback,
TriggerEvaluator,
TriggerEvaluatorContext,
TriggerOfType,
} from './types';
// Subscribes via the shared EventWatcher to one or more HA bus event types and
// fires every time a matching event arrives. List-form `event_type` expands
// into one EventWatcher subscription per (de-duplicated) type sharing the same
// data/context matcher; `event_data` and `context` filters are AND-gated.
//
// https://www.home-assistant.io/docs/automation/trigger/#event-trigger
export class EventTrigger implements TriggerEvaluator {
private _trigger: TriggerOfType<'event'>;
private _eventWatcher: EventWatcherSubscriptionInterface;
private _unsubscribeCallback: (() => void) | null = null;
constructor(trigger: TriggerOfType<'event'>, context: TriggerEvaluatorContext) {
this._trigger = trigger;
this._eventWatcher = context.hassManager.getEventWatcher();
}
public subscribe(callback: TriggerCallback): void {
const dataFilter = this._trigger.event_data;
const contextFilter = this._trigger.context;
const requests = uniq(arrayify(this._trigger.event_type)).map(
(eventType): EventSubscriptionRequest => ({
event_type: eventType,
...((dataFilter || contextFilter) && {
matcher: (evt) =>
(!dataFilter || matchesEventData(dataFilter, evt.data)) &&
(!contextFilter || matchesEventContext(contextFilter, evt.context)),
}),
callback: (event) => callback({ platform: 'event', event }),
}),
);
requests.forEach((request) => this._eventWatcher.subscribe(request));
this._unsubscribeCallback = () =>
requests.forEach((request) => this._eventWatcher.unsubscribe(request));
}
public destroy(): void {
this._unsubscribeCallback?.();
this._unsubscribeCallback = null;
}
}
@@ -1,3 +1,4 @@
import { HASSManagerReadonlyInterface } from '../../../card-controller/hass/types';
import { TemplateRenderer } from '../../../card-controller/templates';
import { Trigger } from '../../../config/schema/condition-trigger/triggers/types';
import { ConditionStateManagerReadonlyInterface } from '../../conditions/types';
@@ -8,6 +9,7 @@ export type TriggerCallback = (data: TriggerData) => void;
export interface TriggerEvaluatorContext {
stateManager: ConditionStateManagerReadonlyInterface;
templateRenderer: TemplateRenderer;
hassManager: HASSManagerReadonlyInterface;
}
export type TriggerOfType<T extends string> = Extract<Trigger, { trigger: T }>;
+5 -1
View File
@@ -1,4 +1,4 @@
import { HassEntity } from 'home-assistant-js-websocket';
import { HassEntity, HassEvent } from 'home-assistant-js-websocket';
import { TemplateAdvancedCameraCardState } from '../../card-controller/templates/types';
// The top-level `trigger` template variable produced each time an evaluator
@@ -15,6 +15,10 @@ export interface TriggerData {
from_state?: HassEntity;
to_state?: HassEntity;
// For `platform: 'event'` (HA event trigger) -- the full HA event, surfaced
// as `trigger.event.*` to mirror HA's event-trigger template variables.
event?: HassEvent;
// Card (`acc` platform) fields -- full before/after card-state trigger data:
from_acc?: TemplateAdvancedCameraCardState;
to_acc?: TemplateAdvancedCameraCardState;