From 8d44fcecf1ff6c075cf28cff69b5b3652ce4c8d6 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 26 Jul 2026 15:37:59 -0700 Subject: [PATCH] fix: Ignore startup triggers from cameras without the trigger capability (#2621) - Closes: #2103 --- .../camera-triggers-manager.ts | 7 ++ .../camera-triggers-manager.test.ts | 85 +++++++++++++++++++ tests/test-utils.ts | 1 + 3 files changed, 93 insertions(+) diff --git a/src/card-controller/camera-triggers-manager.ts b/src/card-controller/camera-triggers-manager.ts index bf5e5f5d..4652ce1d 100644 --- a/src/card-controller/camera-triggers-manager.ts +++ b/src/card-controller/camera-triggers-manager.ts @@ -71,6 +71,13 @@ export class CameraTriggersManager { .getCameraManager() .getStore() .getCameras()) { + // A camera without the trigger capability never subscribes to its + // configured trigger entities, so its already-on entities must not + // synthesize a trigger here either. + if (!camera.getCapabilities()?.has('trigger')) { + continue; + } + for (const entityID of camera.getConfig().triggers.entities) { if (isTriggeredState(hass?.states[entityID]?.state)) { triggered = true; diff --git a/tests/card-controller/camera-triggers-manager.test.ts b/tests/card-controller/camera-triggers-manager.test.ts index d340afd0..d0ba1a74 100644 --- a/tests/card-controller/camera-triggers-manager.test.ts +++ b/tests/card-controller/camera-triggers-manager.test.ts @@ -9,6 +9,7 @@ import { triggersSchema, type TriggersOptions } from '../../src/config/schema/vi import { createCameraConfig, createCameraManager, + createCapabilities, createCardAPI, createConfig, createHASS, @@ -1566,6 +1567,90 @@ describe('CameraTriggersManager', () => { expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled(); expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled(); }); + + it('should not trigger a camera without the trigger capability', async () => { + const api = createTriggerAPI(); + vi.mocked(api.getCameraManager().getStore).mockReturnValue( + createStore([ + { + cameraID: 'camera_1', + capabilities: createCapabilities({ trigger: false }), + config: createCameraConfig({ + triggers: { + entities: ['binary_sensor.motion'], + }, + }), + }, + ]), + ); + + const hass = createHASS({ + 'binary_sensor.motion': createStateEntity({ + state: 'on', + }), + }); + vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass); + + const manager = new CameraTriggersManager(api); + const result = await manager.handleInitialCameraTriggers(); + + expect(result).toBeFalsy(); + expect(manager.isTriggered()).toBeFalsy(); + expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled(); + }); + + it('should target the startup action at a camera that has the trigger capability', async () => { + const api = createTriggerAPI({ + config: { + actions: { + trigger: 'live', + }, + }, + }); + vi.mocked(api.getCameraManager().getStore).mockReturnValue( + createStore([ + { + cameraID: 'substream', + capabilities: createCapabilities({ substream: true, trigger: false }), + config: createCameraConfig({ + triggers: { + entities: ['binary_sensor.motion'], + }, + }), + }, + { + cameraID: 'camera_1', + config: createCameraConfig({ + dependencies: { + cameras: ['substream'], + }, + triggers: { + entities: ['binary_sensor.motion'], + }, + }), + }, + ]), + ); + + const hass = createHASS({ + 'binary_sensor.motion': createStateEntity({ + state: 'on', + }), + }); + vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass); + + const manager = new CameraTriggersManager(api); + const result = await manager.handleInitialCameraTriggers(); + + expect(result).toBeTruthy(); + expect(manager.getTriggeredCameraIDs()).toEqual(new Set(['camera_1'])); + expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith({ + params: { + view: 'live', + camera: 'camera_1', + }, + }); + }); }); it('should take actions with human interactions when interaction mode is active', async () => { diff --git a/tests/test-utils.ts b/tests/test-utils.ts index e874cfad..80497ad1 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -381,6 +381,7 @@ export const createCapabilities = (capabilities?: CapabilitiesRaw): Capabilities recordings: false, seek: false, snapshots: false, + trigger: true, ...capabilities, }); };