feat: Trigger cameras on HA bus events (#2512)

This commit is contained in:
Dermot Duffy
2026-06-30 17:45:13 -07:00
committed by dermotduffy
parent 406176eebc
commit 0a36358394
49 changed files with 1376 additions and 269 deletions
+32
View File
@@ -877,6 +877,29 @@ const frigateCardToAdvancedCameraCardStyleTransform = (data: unknown): unknown =
return newStyleOverrides;
};
// Legacy `triggers.events: string[]` (Frigate engine media-availability filter)
// was renamed to `triggers.media_events` to free up `triggers.events` for the
// new HA-bus-event trigger list (object shape). Distinguish old from new by
// element type: an all-string array is legacy; any non-string element marks
// the new shape and must not be touched. If `media_events` already exists we
// refuse to overwrite it -- but we still drop the legacy `events` (otherwise
// it would fail the new schema, which expects objects).
const triggersEventsToMediaEventsTransform = (triggers: unknown): unknown => {
if (typeof triggers !== 'object' || !triggers) {
return undefined;
}
const events = triggers['events'];
if (!Array.isArray(events) || events.some((x) => typeof x !== 'string')) {
return undefined;
}
const result = { ...triggers };
delete result['events'];
if (!('media_events' in result)) {
result['media_events'] = events;
}
return result;
};
const UPGRADES = [
// v5.2.0 -> v6.0.0
(data: unknown): boolean => {
@@ -1090,4 +1113,13 @@ const UPGRADES = [
typeof data === 'object' && data ? (data as RawAdvancedCameraCardConfig) : {},
);
},
// Legacy `triggers.events: string[]` → `triggers.media_events`. Targets the
// two known places a camera config lives: `cameras_global` and `cameras[]`.
// Mirrors the PTZ rename migration above.
upgradeWithOverrides('cameras_global.triggers', triggersEventsToMediaEventsTransform),
upgradeArrayOfObjects(
CONF_CAMERAS,
upgradeWithOverrides('triggers', triggersEventsToMediaEventsTransform),
),
];
+15 -6
View File
@@ -8,7 +8,7 @@ import { imageBaseConfigDefault, imageBaseConfigSchema } from './common/image';
import { proxyBaseConfigDefault, proxyBaseConfigSchema } from './common/proxy';
import { severitySchema } from './common/severity';
const CAMERA_TRIGGER_EVENT_TYPES = [
const CAMERA_TRIGGER_MEDIA_EVENT_TYPES = [
// An event whether or not it has any media yet associated with it.
'events',
@@ -16,7 +16,8 @@ const CAMERA_TRIGGER_EVENT_TYPES = [
'clips',
'snapshots',
] as const;
export type CameraTriggerEventType = (typeof CAMERA_TRIGGER_EVENT_TYPES)[number];
export type CameraTriggerMediaEventType =
(typeof CAMERA_TRIGGER_MEDIA_EVENT_TYPES)[number];
// *************************************************************************
// Live Provider Configuration
@@ -149,8 +150,9 @@ export const cameraConfigDefault = {
motion: false,
occupancy: false,
doorbell: false,
events: [],
media_events: [],
entities: [],
events: [],
reviews: {
severities: ['high' as const],
description: true,
@@ -217,6 +219,12 @@ const cameraMediaConfigSchema = z.object({
.default(cameraMediaConfigDefault.reviewed),
});
const triggerEventSchema = z.object({
event_type: z.string().min(1),
event_data: z.record(z.string(), z.unknown()).optional(),
});
export type TriggerEvent = z.infer<typeof triggerEventSchema>;
export const cameraConfigSchema = z
.looseObject({
camera_entity: z.string().optional(),
@@ -251,10 +259,11 @@ export const cameraConfigSchema = z
occupancy: z.boolean().default(cameraConfigDefault.triggers.occupancy),
doorbell: z.boolean().default(cameraConfigDefault.triggers.doorbell),
entities: z.string().array().default(cameraConfigDefault.triggers.entities),
events: z
.enum(CAMERA_TRIGGER_EVENT_TYPES)
events: triggerEventSchema.array().default(cameraConfigDefault.triggers.events),
media_events: z
.enum(CAMERA_TRIGGER_MEDIA_EVENT_TYPES)
.array()
.default(cameraConfigDefault.triggers.events),
.default(cameraConfigDefault.triggers.media_events),
reviews: z
.object({
severities: severitySchema
+2 -4
View File
@@ -72,7 +72,7 @@ export const viewConfigDefault = {
},
untrigger_delay_seconds: 0,
untrigger_force_seconds: 0,
signal_hold_seconds: 30,
event_hold_seconds: 30,
},
keyboard_shortcuts: keyboardShortcutsDefault,
issues: {
@@ -110,9 +110,7 @@ export const triggersSchema = z.object({
untrigger_force_seconds: z
.number()
.default(viewConfigDefault.triggers.untrigger_force_seconds),
signal_hold_seconds: z
.number()
.default(viewConfigDefault.triggers.signal_hold_seconds),
event_hold_seconds: z.number().default(viewConfigDefault.triggers.event_hold_seconds),
});
export type TriggersOptions = z.infer<typeof triggersSchema>;