Add initial keyboard shortcut support.

This commit is contained in:
Dermot Duffy
2024-06-05 21:44:17 -07:00
parent 904f6d8142
commit 7ab545738d
186 changed files with 9564 additions and 3658 deletions
@@ -31,6 +31,7 @@ import {
import { BrowseMediaCamera } from './camera';
import { BrowseMediaViewMediaFactory } from './media';
import { BrowseMediaMetadata } from './types';
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';
/**
* A utility method to determine if a browse media object matches against a
@@ -154,6 +155,7 @@ export class BrowseMediaCameraManagerEngine
seek: false,
snapshots: true,
substream: true,
ptz: getPTZCapabilitiesFromCameraConfig(cameraConfig) ?? undefined,
},
{
disable: cameraConfig.capabilities?.disable,
+1 -1
View File
@@ -8,10 +8,10 @@ import {
subscribeToTrigger,
} from '../utils/ha';
import { EntityRegistryManager } from '../utils/ha/entity-registry';
import { Capabilities } from './capabilities';
import { CameraManagerEngine } from './engine';
import { CameraNoIDError } from './error';
import { CameraEventCallback } from './types';
import { Capabilities } from './capabilities';
type DestroyCallback = () => Promise<void>;
+12
View File
@@ -54,6 +54,18 @@ export class Capabilities {
return this._capabilities.ptz ?? null;
}
public hasPTZCapability(): boolean {
return !!(
this._capabilities.ptz?.down?.length ||
this._capabilities.ptz?.up?.length ||
this._capabilities.ptz?.left?.length ||
this._capabilities.ptz?.right?.length ||
this._capabilities.ptz?.zoomIn?.length ||
this._capabilities.ptz?.zoomOut?.length ||
this._capabilities.ptz?.presets?.length
);
}
public getRawCapabilities(): CapabilitiesRaw {
return this._capabilities;
}
+3 -2
View File
@@ -1,5 +1,5 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { CameraConfig, PTZAction, PTZPhase } from '../config/types';
import { CameraConfig, ActionPhase } from '../config/types';
import { ExtendedHomeAssistant } from '../types';
import { EntityRegistryManager } from '../utils/ha/entity-registry';
import { ViewMedia } from '../view/media';
@@ -27,6 +27,7 @@ import {
RecordingSegmentsQuery,
RecordingSegmentsQueryResultsMap,
} from './types';
import { PTZAction } from '../config/ptz';
export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;
@@ -139,7 +140,7 @@ export interface CameraManagerEngine {
cameraConfig: CameraConfig,
action: PTZAction,
options?: {
phase?: PTZPhase;
phase?: ActionPhase;
preset?: string;
},
): Promise<void>;
+27 -7
View File
@@ -3,7 +3,10 @@ import uniq from 'lodash-es/uniq';
import { CameraConfig } from '../../config/types';
import { localize } from '../../localize/localize';
import { PTZCapabilities, PTZMovementType } from '../../types';
import { errorToConsole } from '../../utils/basic';
import {
errorToConsole,
recursivelyMergeObjectsConcatenatingArraysUniquely,
} from '../../utils/basic';
import { subscribeToTrigger } from '../../utils/ha';
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
import { Entity } from '../../utils/ha/entity-registry/types';
@@ -13,6 +16,7 @@ import { CameraInitializationError } from '../error';
import { getCameraEntityFromConfig } from '../utils/camera-entity-from-config';
import { getPTZInfo } from './requests';
import { PTZInfo, frigateEventChangeTriggerResponseSchema } from './types';
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';
const CAMERA_BIRDSEYE = 'birdseye' as const;
@@ -97,7 +101,18 @@ export class FrigateCamera extends Camera {
protected async _initializeCapabilities(hass: HomeAssistant): Promise<void> {
const config = this.getConfig();
const ptz = await this._getPTZCapabilities(hass, config);
const configPTZCapabilities = getPTZCapabilitiesFromCameraConfig(this.getConfig());
const frigatePTZCapabilities = await this._getPTZCapabilities(hass, config);
const combinedPTZCapabilities =
configPTZCapabilities || frigatePTZCapabilities
? recursivelyMergeObjectsConcatenatingArraysUniquely(
{},
configPTZCapabilities,
frigatePTZCapabilities,
)
: null;
const birdseye = isBirdseye(config);
this._capabilities = new Capabilities(
{
@@ -110,7 +125,7 @@ export class FrigateCamera extends Camera {
live: true,
menu: true,
substream: true,
...(ptz && { ptz: ptz }),
...(combinedPTZCapabilities && { ptz: combinedPTZCapabilities }),
},
{
disable: config.capabilities?.disable,
@@ -153,20 +168,25 @@ export class FrigateCamera extends Camera {
return null;
}
// Note: The Frigate integration only supports continuous PTZ movements
// (regardless of the actual underlying camera capability).
const panTilt: PTZMovementType[] = [
...(ptzInfo.features?.includes('pt') ? ['continuous' as const] : []),
...(ptzInfo.features?.includes('pt-r') ? ['relative' as const] : []),
];
const zoom: PTZMovementType[] = [
...(ptzInfo.features?.includes('zoom') ? ['continuous' as const] : []),
...(ptzInfo.features?.includes('zoom-r') ? ['relative' as const] : []),
];
const presets = ptzInfo.presets;
if (panTilt.length || zoom.length || presets?.length) {
return {
...(panTilt && { panTilt: panTilt }),
...(zoom && { zoom: zoom }),
...(panTilt && {
left: panTilt,
right: panTilt,
up: panTilt,
down: panTilt,
}),
...(zoom && { zoomIn: zoom, zoomOut: zoom }),
...(presets && { presets: presets }),
};
}
+7 -6
View File
@@ -4,7 +4,8 @@ import isEqual from 'lodash-es/isEqual';
import orderBy from 'lodash-es/orderBy';
import throttle from 'lodash-es/throttle';
import uniqWith from 'lodash-es/uniqWith';
import { CameraConfig, PTZAction, PTZPhase } from '../../config/types';
import { PTZAction } from '../../config/ptz';
import { ActionPhase, CameraConfig } from '../../config/types';
import { ExtendedHomeAssistant } from '../../types';
import {
allPromises,
@@ -19,8 +20,8 @@ import { ViewMediaClassifier } from '../../view/media-classifier';
import { RecordingSegmentsCache, RequestCache } from '../cache';
import { Camera } from '../camera';
import {
CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT,
CameraManagerEngine,
CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT,
} from '../engine';
import { GenericCameraManagerEngine } from '../generic/engine-generic';
import { DateRange } from '../range';
@@ -61,12 +62,12 @@ import { FrigateCamera, isBirdseye } from './camera';
import { FrigateViewMediaFactory } from './media';
import { FrigateViewMediaClassifier } from './media-classifier';
import {
NativeFrigateEventQuery,
NativeFrigateRecordingSegmentsQuery,
getEventSummary,
getEvents,
getEventSummary,
getRecordingSegments,
getRecordingsSummary,
NativeFrigateEventQuery,
NativeFrigateRecordingSegmentsQuery,
retainEvent,
} from './requests';
import {
@@ -1015,7 +1016,7 @@ export class FrigateCameraManagerEngine
cameraConfig: CameraConfig,
action: PTZAction,
options?: {
phase?: PTZPhase;
phase?: ActionPhase;
preset?: string;
},
): Promise<void> {
+6 -3
View File
@@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { CameraConfig, PTZAction, PTZPhase } from '../../config/types';
import { ExtendedHomeAssistant } from '../../types';
import { PTZAction, PTZ_PAN_TILT_ACTIONS, PTZ_ZOOM_ACTIONS } from '../../config/ptz';
import { ActionPhase, CameraConfig } from '../../config/types';
import { ExtendedHomeAssistant, PTZCapabilities, PTZMovementType } from '../../types';
import { getEntityIcon, getEntityTitle } from '../../utils/ha';
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
import { ViewMedia } from '../../view/media';
@@ -35,6 +36,7 @@ import {
} from '../types';
import { getCameraEntityFromConfig } from '../utils/camera-entity-from-config';
import { getDefaultGo2RTCEndpoint } from '../utils/go2rtc-endpoint';
import { getPTZCapabilitiesFromCameraConfig } from '../utils/ptz';
export class GenericCameraManagerEngine implements CameraManagerEngine {
protected _eventCallback?: CameraEventCallback;
@@ -64,6 +66,7 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
seek: false,
snapshots: false,
substream: true,
ptz: getPTZCapabilitiesFromCameraConfig(cameraConfig) ?? undefined,
},
{
disable: cameraConfig.capabilities?.disable,
@@ -222,7 +225,7 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
_cameraConfig: CameraConfig,
_action: PTZAction,
_options?: {
phase?: PTZPhase;
phase?: ActionPhase;
preset?: string;
},
): Promise<void> {
+18 -8
View File
@@ -3,7 +3,8 @@ import cloneDeep from 'lodash-es/cloneDeep';
import sum from 'lodash-es/sum';
import PQueue from 'p-queue';
import { CardCameraAPI } from '../card-controller/types.js';
import { CameraConfig, CamerasConfig, PTZAction, PTZPhase } from '../config/types.js';
import { PTZAction } from '../config/ptz.js';
import { ActionPhase, CameraConfig, CamerasConfig } from '../config/types.js';
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js';
import { localize } from '../localize/localize.js';
import {
@@ -15,6 +16,7 @@ import {
} from '../utils/basic.js';
import { getCameraID } from '../utils/camera.js';
import { log } from '../utils/debug.js';
import { getConfiguredPTZAction } from './utils/ptz.js';
import { ViewMedia } from '../view/media.js';
import { Capabilities } from './capabilities.js';
import { CameraManagerEngineFactory } from './engine-factory.js';
@@ -771,18 +773,26 @@ export class CameraManager {
public async executePTZAction(
cameraID: string,
action: PTZAction,
options: {
phase?: PTZPhase;
options?: {
phase?: ActionPhase;
preset?: string;
},
): Promise<void> {
const hass = this._api.getHASSManager().getHASS();
const engine = this._store.getEngineForCameraID(cameraID);
const cameraConfig = this._store.getCameraConfig(cameraID);
if (!engine || !cameraConfig || !hass) {
if (!cameraConfig) {
return;
}
return engine.executePTZAction(hass, cameraConfig, action, options);
const configuredAction = getConfiguredPTZAction(cameraConfig, action, options);
if (configuredAction) {
return await this._api.getActionsManager().executeActions(configuredAction);
}
const hass = this._api.getHASSManager().getHASS();
const engine = this._store.getEngineForCameraID(cameraID);
if (!engine || !hass) {
return;
}
return await engine.executePTZAction(hass, cameraConfig, action, options);
}
}
+82
View File
@@ -0,0 +1,82 @@
import { PTZAction, PTZBaseAction } from '../../config/ptz';
import { ActionPhase, ActionType, CameraConfig } from '../../config/types';
import { PTZCapabilities, PTZMovementType } from '../../types';
export const getConfiguredPTZAction = (
cameraConfig: CameraConfig,
action: PTZAction,
options?: {
phase?: ActionPhase;
preset?: string;
},
): ActionType | ActionType[] | null => {
if (action === 'preset') {
return (options?.preset ? cameraConfig.ptz.presets?.[options.preset] : null) ?? null;
}
if (options?.phase) {
return cameraConfig.ptz[`actions_${action}_${options.phase}`] ?? null;
}
return cameraConfig.ptz[`actions_${action}`] ?? null;
};
const hasConfiguredPTZAction = (
cameraConfig: CameraConfig,
action: PTZBaseAction,
options?: {
phase?: ActionPhase;
preset?: string;
},
): boolean => {
return !!getConfiguredPTZAction(cameraConfig, action, options);
};
export const getConfiguredPTZMovementType = (
cameraConfig: CameraConfig,
action: PTZBaseAction,
): PTZMovementType[] | null => {
const continuous =
hasConfiguredPTZAction(cameraConfig, action, { phase: 'start' }) &&
hasConfiguredPTZAction(cameraConfig, action, { phase: 'stop' });
const relative = hasConfiguredPTZAction(cameraConfig, action);
return continuous || relative
? [
...(continuous ? ['continuous' as const] : []),
...(relative ? ['relative' as const] : []),
]
: null;
};
export const getPTZCapabilitiesFromCameraConfig = (
cameraConfig: CameraConfig,
): PTZCapabilities | null => {
const left = getConfiguredPTZMovementType(cameraConfig, 'left');
const right = getConfiguredPTZMovementType(cameraConfig, 'right');
const up = getConfiguredPTZMovementType(cameraConfig, 'up');
const down = getConfiguredPTZMovementType(cameraConfig, 'down');
const zoomIn = getConfiguredPTZMovementType(cameraConfig, 'zoom_in');
const zoomOut = getConfiguredPTZMovementType(cameraConfig, 'zoom_out');
const presets = cameraConfig.ptz.presets
? Object.keys(cameraConfig.ptz.presets)
: undefined;
return left?.length ||
right?.length ||
up?.length ||
down?.length ||
zoomIn?.length ||
zoomOut?.length ||
presets?.length
? {
left: left ?? undefined,
right: right ?? undefined,
up: up ?? undefined,
down: down ?? undefined,
zoomIn: zoomIn ?? undefined,
zoomOut: zoomOut ?? undefined,
presets: presets,
}
: null;
};