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
+50 -117
View File
@@ -4,14 +4,13 @@ import { CameraManagerEngine } from '../../../src/camera-manager/engine';
import { ReolinkCamera } from '../../../src/camera-manager/reolink/camera';
import { CameraProxyConfig } from '../../../src/camera-manager/types';
import { ActionsExecutor } from '../../../src/card-controller/actions/types';
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
import { DeviceRegistryManager } from '../../../src/ha/registry/device';
import { EntityRegistryManagerLive } from '../../../src/ha/registry/entity';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASS,
createHASSManager,
createRegistryEntity,
createStateEntity,
} from '../../test-utils';
@@ -103,11 +102,9 @@ describe('ReolinkCamera', () => {
expect(
async () =>
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: mock<EntityRegistryManagerLive>(),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
}),
).rejects.toThrowError('Could not find camera entity');
});
@@ -128,11 +125,9 @@ describe('ReolinkCamera', () => {
expect(
async () =>
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
}),
).rejects.toThrowError('Could not initialize Reolink camera');
});
@@ -153,11 +148,9 @@ describe('ReolinkCamera', () => {
expect(
async () =>
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
}),
).rejects.toThrowError('Could not initialize Reolink camera');
});
@@ -170,11 +163,9 @@ describe('ReolinkCamera', () => {
const entityRegistryManager = new EntityRegistryManagerMock([cameraEntity]);
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(0);
@@ -203,11 +194,9 @@ describe('ReolinkCamera', () => {
});
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager,
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(3);
@@ -227,11 +216,9 @@ describe('ReolinkCamera', () => {
]);
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(7);
@@ -251,11 +238,9 @@ describe('ReolinkCamera', () => {
]);
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(0);
@@ -275,11 +260,9 @@ describe('ReolinkCamera', () => {
]);
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(7);
@@ -301,11 +284,9 @@ describe('ReolinkCamera', () => {
]);
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(0);
@@ -334,11 +315,9 @@ describe('ReolinkCamera', () => {
});
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager,
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(0);
@@ -367,11 +346,9 @@ describe('ReolinkCamera', () => {
});
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager,
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(0);
@@ -400,11 +377,9 @@ describe('ReolinkCamera', () => {
});
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager,
deviceRegistryManager,
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getChannel()).toBe(0);
@@ -420,11 +395,9 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: ptzPopulatedEntityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
@@ -444,7 +417,7 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
createRegistryEntity({
entity_id: 'camera.office_reolink',
@@ -463,8 +436,6 @@ describe('ReolinkCamera', () => {
}),
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
@@ -480,18 +451,18 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS({
'select.office_reolink_ptz_preset': createStateEntity({
state: 'foo',
attributes: {
options: ['preset-one', 'preset-two'],
},
hassManager: createHASSManager({
hass: createHASS({
'select.office_reolink_ptz_preset': createStateEntity({
state: 'foo',
attributes: {
options: ['preset-one', 'preset-two'],
},
}),
}),
}),
entityRegistryManager: ptzPopulatedEntityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
@@ -521,11 +492,9 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: ptzPopulatedEntityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
@@ -709,11 +678,9 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -739,14 +706,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
buttonEntityPTZLeft,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
await camera.executePTZAction(executor, 'left', { phase: 'start' });
@@ -770,11 +735,9 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: ptzPopulatedEntityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -812,11 +775,9 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: ptzPopulatedEntityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -832,18 +793,18 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS({
'select.office_reolink_ptz_preset': createStateEntity({
state: 'foo',
attributes: {
options: ['preset-one', 'preset-two'],
},
hassManager: createHASSManager({
hass: createHASS({
'select.office_reolink_ptz_preset': createStateEntity({
state: 'foo',
attributes: {
options: ['preset-one', 'preset-two'],
},
}),
}),
}),
entityRegistryManager: ptzPopulatedEntityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -871,11 +832,9 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: ptzPopulatedEntityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -892,14 +851,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
@@ -915,7 +872,7 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
createRegistryEntity({
@@ -926,8 +883,6 @@ describe('ReolinkCamera', () => {
}),
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getCapabilities()?.getPTZCapabilities()).toBeNull();
@@ -940,7 +895,7 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
buttonEntityPTZZoomIn,
@@ -949,8 +904,6 @@ describe('ReolinkCamera', () => {
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
@@ -966,14 +919,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1005,14 +956,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1044,14 +993,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1083,14 +1030,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1122,14 +1067,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1152,14 +1095,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1181,14 +1122,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1204,7 +1143,7 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
buttonEntityPTZZoomIn,
@@ -1213,8 +1152,6 @@ describe('ReolinkCamera', () => {
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1238,15 +1175,13 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
buttonEntityPTZLeft,
buttonEntityPTZStop,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -1262,14 +1197,12 @@ describe('ReolinkCamera', () => {
const camera = new ReolinkCamera(config, mock<CameraManagerEngine>());
await camera.initialize({
hass: createHASS(),
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock([
cameraEntity,
numberEntityZoom,
]),
deviceRegistryManager: mock<DeviceRegistryManager>(),
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
});
const executor = mock<ActionsExecutor>();
@@ -23,8 +23,6 @@ import {
QueryReturnType,
QueryType,
} from '../../../src/camera-manager/types';
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
import { BrowseMedia, browseMediaSchema } from '../../../src/ha/browse-media/types';
import { BrowseMediaWalker } from '../../../src/ha/browse-media/walker';
import { DeviceRegistryManager } from '../../../src/ha/registry/device';
@@ -36,6 +34,7 @@ import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASS,
createHASSManager,
createInitializedCamera,
createRegistryEntity,
createStore,
@@ -187,8 +186,7 @@ const createEngine = (options?: {
return new ReolinkCameraManagerEngine(
options?.entityRegistryManager ?? new EntityRegistryManagerMock(),
mock<DeviceRegistryManager>(),
mock<StateWatcherSubscriptionInterface>(),
mock<EventWatcherSubscriptionInterface>(),
createHASSManager(),
options?.browseMediaManager ?? new BrowseMediaWalker(),
new ResolvedMediaCache(),
new CameraManagerRequestCache(),
@@ -212,7 +210,6 @@ const createStoreWithReolinkCamera = async (
): Promise<CameraManagerStore> => {
const store = new CameraManagerStore();
const camera = await engine.createCamera(
createHASS(),
createCameraConfig({ camera_entity: 'camera.office', id: 'office' }),
);
store.addCamera(camera);
@@ -252,7 +249,7 @@ describe('ReolinkCameraManagerEngine', () => {
unique_id: 'office',
});
const camera = await engine.createCamera(createHASS(), config);
const camera = await engine.createCamera(config);
expect(camera.getConfig()).toBe(config);
expect(camera.getEngine()).toBe(engine);
@@ -289,7 +286,6 @@ describe('ReolinkCameraManagerEngine', () => {
it('should return ui endpoint', async () => {
const engine = createPopulatedEngine();
const camera = await engine.createCamera(
createHASS(),
createCameraConfig({
camera_entity: 'camera.office',
reolink: {
@@ -308,7 +304,6 @@ describe('ReolinkCameraManagerEngine', () => {
it('should return go2rtc endpoint', async () => {
const engine = createPopulatedEngine();
const camera = await engine.createCamera(
createHASS(),
createCameraConfig({
camera_entity: 'camera.office',
go2rtc: {
@@ -497,7 +492,6 @@ describe('ReolinkCameraManagerEngine', () => {
it('should request high resolution if configured', async () => {
const engine = createPopulatedEngine();
const camera = await engine.createCamera(
createHASS(),
createCameraConfig({
camera_entity: 'camera.office',
id: 'office',