feat: Add event-based automation triggers (#2537)
This commit is contained in:
committed by
dermotduffy
parent
b701366762
commit
a31816c168
@@ -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':
|
||||
|
||||
@@ -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 }>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user