Skip to content

Commit b5eee58

Browse files
authored
feat: Add out of the box support for Reolink PTZ (#1982)
- Closes: #1964
1 parent f6bb800 commit b5eee58

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+950
-676
lines changed

src/camera-manager/browse-media/camera.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { HomeAssistant } from '../../ha/types';
22
import { localize } from '../../localize/localize';
3-
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
4-
import { Entity } from '../../utils/ha/registry/entity/types';
3+
import { Entity, EntityRegistryManager } from '../../utils/ha/registry/entity/types';
54
import { Camera, CameraInitializationOptions } from '../camera';
65
import { CameraInitializationError } from '../error';
76

src/camera-manager/browse-media/engine-browse-media.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { HomeAssistant } from '../../ha/types';
44
import { canonicalizeHAURL } from '../../utils/ha';
55
import { BrowseMediaManager } from '../../utils/ha/browse-media/browse-media-manager';
66
import { BROWSE_MEDIA_CACHE_SECONDS } from '../../utils/ha/browse-media/types';
7-
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
7+
import { EntityRegistryManager } from '../../utils/ha/registry/entity/types';
88
import { ResolvedMediaCache, resolveMedia } from '../../utils/ha/resolved-media';
99
import { ViewMedia } from '../../view/media';
1010
import { RequestCache } from '../cache';

src/camera-manager/camera.ts

+19
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { ActionsExecutor } from '../card-controller/actions/types';
12
import { StateWatcherSubscriptionInterface } from '../card-controller/hass/state-watcher';
3+
import { PTZAction, PTZActionPhase } from '../config/schema/actions/custom/ptz';
24
import { CameraConfig } from '../config/schema/cameras';
35
import { localize } from '../localize/localize';
46
import { HassStateDifference, isTriggeredState } from '../utils/ha';
57
import { Capabilities } from './capabilities';
68
import { CameraManagerEngine } from './engine';
79
import { CameraNoIDError } from './error';
810
import { CameraEventCallback, CameraProxyConfig } from './types';
11+
import { getConfiguredPTZAction } from './utils/ptz';
912

1013
export interface CameraInitializationOptions {
1114
stateWatcher: StateWatcherSubscriptionInterface;
@@ -83,6 +86,22 @@ export class Camera {
8386
};
8487
}
8588

89+
public async executePTZAction(
90+
executor: ActionsExecutor,
91+
action: PTZAction,
92+
options?: {
93+
phase?: PTZActionPhase;
94+
preset?: string;
95+
},
96+
): Promise<boolean> {
97+
const configuredAction = getConfiguredPTZAction(this.getConfig(), action, options);
98+
if (configuredAction) {
99+
await executor.executeActions({ actions: configuredAction });
100+
return true;
101+
}
102+
return false;
103+
}
104+
86105
protected _stateChangeHandler = (difference: HassStateDifference): void => {
87106
this._eventCallback?.({
88107
cameraID: this.getID(),

src/camera-manager/engine-factory.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CameraConfig } from '../config/schema/cameras';
33
import { HomeAssistant } from '../ha/types';
44
import { localize } from '../localize/localize';
55
import { BrowseMediaManager } from '../utils/ha/browse-media/browse-media-manager';
6-
import { EntityRegistryManager } from '../utils/ha/registry/entity';
6+
import { EntityRegistryManager } from '../utils/ha/registry/entity/types';
77
import { ResolvedMediaCache } from '../utils/ha/resolved-media';
88
import { RecordingSegmentsCache, RequestCache } from './cache';
99
import { CameraManagerEngine } from './engine';

src/camera-manager/engine.ts

-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { PTZAction, PTZActionPhase } from '../config/schema/actions/custom/ptz';
21
import { CameraConfig } from '../config/schema/cameras';
32
import { HomeAssistant } from '../ha/types';
43
import { ViewMedia } from '../view/media';
@@ -128,14 +127,4 @@ export interface CameraManagerEngine {
128127
cameraConfig: CameraConfig,
129128
context?: CameraEndpointsContext,
130129
): CameraEndpoints | null;
131-
132-
executePTZAction(
133-
hass: HomeAssistant,
134-
cameraConfig: CameraConfig,
135-
action: PTZAction,
136-
options?: {
137-
phase?: PTZActionPhase;
138-
preset?: string;
139-
},
140-
): Promise<void>;
141130
}

src/camera-manager/frigate/camera.ts

+54-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import uniq from 'lodash-es/uniq';
2+
import { ActionsExecutor } from '../../card-controller/actions/types';
23
import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher';
4+
import { PTZAction, PTZActionPhase } from '../../config/schema/actions/custom/ptz';
35
import { CameraConfig } from '../../config/schema/cameras';
46
import { HomeAssistant } from '../../ha/types';
57
import { localize } from '../../localize/localize';
68
import { PTZCapabilities, PTZMovementType } from '../../types';
79
import { errorToConsole } from '../../utils/basic';
8-
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
9-
import { Entity } from '../../utils/ha/registry/entity/types';
10+
import { Entity, EntityRegistryManager } from '../../utils/ha/registry/entity/types';
1011
import { Camera, CameraInitializationOptions } from '../camera';
1112
import { Capabilities } from '../capabilities';
1213
import { CameraManagerEngine } from '../engine';
@@ -57,6 +58,55 @@ export class FrigateCamera extends Camera {
5758
return await super.initialize(options);
5859
}
5960

61+
public async executePTZAction(
62+
executor: ActionsExecutor,
63+
action: PTZAction,
64+
options?: {
65+
phase?: PTZActionPhase;
66+
preset?: string;
67+
},
68+
): Promise<boolean> {
69+
if (await super.executePTZAction(executor, action, options)) {
70+
return true;
71+
}
72+
73+
const cameraEntity = this.getConfig().camera_entity;
74+
if ((action === 'preset' && !options?.preset) || !cameraEntity) {
75+
return false;
76+
}
77+
78+
// Awkward translation between card action and service parameters:
79+
// https://github.com/blakeblackshear/frigate-hass-integration/blob/dev/custom_components/frigate/services.yaml
80+
await executor.executeActions({
81+
actions: {
82+
action: 'perform-action',
83+
perform_action: 'frigate.ptz',
84+
data: {
85+
action:
86+
options?.phase === 'stop'
87+
? 'stop'
88+
: action === 'zoom_in' || action === 'zoom_out'
89+
? 'zoom'
90+
: action === 'preset'
91+
? 'preset'
92+
: 'move',
93+
...(options?.phase !== 'stop' && {
94+
argument:
95+
action === 'zoom_in'
96+
? 'in'
97+
: action === 'zoom_out'
98+
? 'out'
99+
: action === 'preset'
100+
? options?.preset
101+
: action,
102+
}),
103+
},
104+
target: { entity_id: cameraEntity },
105+
},
106+
});
107+
return true;
108+
}
109+
60110
protected async _initializeConfig(
61111
hass: HomeAssistant,
62112
entityRegistryManager: EntityRegistryManager,
@@ -194,10 +244,10 @@ export class FrigateCamera extends Camera {
194244
// Note: The Frigate integration only supports continuous PTZ movements
195245
// (regardless of the actual underlying camera capability).
196246
const panTilt: PTZMovementType[] = [
197-
...(ptzInfo.features?.includes('pt') ? ['continuous' as const] : []),
247+
...(ptzInfo.features?.includes('pt') ? [PTZMovementType.Continuous] : []),
198248
];
199249
const zoom: PTZMovementType[] = [
200-
...(ptzInfo.features?.includes('zoom') ? ['continuous' as const] : []),
250+
...(ptzInfo.features?.includes('zoom') ? [PTZMovementType.Continuous] : []),
201251
];
202252
const presets = ptzInfo.presets;
203253

src/camera-manager/frigate/engine-frigate.ts

+1-42
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import orderBy from 'lodash-es/orderBy';
44
import throttle from 'lodash-es/throttle';
55
import uniqWith from 'lodash-es/uniqWith';
66
import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher';
7-
import { PTZAction, PTZActionPhase } from '../../config/schema/actions/custom/ptz';
87
import { CameraConfig } from '../../config/schema/cameras';
98
import { HomeAssistant } from '../../ha/types';
109
import {
@@ -14,7 +13,7 @@ import {
1413
runWhenIdleIfSupported,
1514
} from '../../utils/basic';
1615
import { getEntityTitle } from '../../utils/ha';
17-
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
16+
import { EntityRegistryManager } from '../../utils/ha/registry/entity/types';
1817
import { ViewMedia } from '../../view/media';
1918
import { ViewMediaClassifier } from '../../view/media-classifier';
2019
import { RecordingSegmentsCache, RequestCache } from '../cache';
@@ -1005,44 +1004,4 @@ export class FrigateCameraManagerEngine
10051004
...(jsmpeg && { jsmpeg: jsmpeg }),
10061005
};
10071006
}
1008-
1009-
public async executePTZAction(
1010-
hass: HomeAssistant,
1011-
cameraConfig: CameraConfig,
1012-
action: PTZAction,
1013-
options?: {
1014-
phase?: PTZActionPhase;
1015-
preset?: string;
1016-
},
1017-
): Promise<void> {
1018-
const cameraEntity = cameraConfig.camera_entity;
1019-
1020-
if (action === 'preset' && !options?.preset) {
1021-
return;
1022-
}
1023-
1024-
// Awkward translation between card action and service parameters:
1025-
// https://github.com/blakeblackshear/frigate-hass-integration/blob/dev/custom_components/frigate/services.yaml
1026-
await hass.callService('frigate', 'ptz', {
1027-
entity_id: cameraEntity,
1028-
action:
1029-
options?.phase === 'stop'
1030-
? 'stop'
1031-
: action === 'zoom_in' || action === 'zoom_out'
1032-
? 'zoom'
1033-
: action === 'preset'
1034-
? 'preset'
1035-
: 'move',
1036-
...(options?.phase !== 'stop' && {
1037-
argument:
1038-
action === 'zoom_in'
1039-
? 'in'
1040-
: action === 'zoom_out'
1041-
? 'out'
1042-
: action === 'preset'
1043-
? options?.preset
1044-
: action,
1045-
}),
1046-
});
1047-
}
10481007
}

src/camera-manager/generic/engine-generic.ts

-12
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,4 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
235235
}
236236
: null;
237237
}
238-
239-
public async executePTZAction(
240-
_hass: HomeAssistant,
241-
_cameraConfig: CameraConfig,
242-
_action: PTZAction,
243-
_options?: {
244-
phase?: PTZActionPhase;
245-
preset?: string;
246-
},
247-
): Promise<void> {
248-
// Pass.
249-
}
250238
}

src/camera-manager/manager.ts

+4-16
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ import {
5555
RecordingSegmentsQueryResultsMap,
5656
ResultsMap,
5757
} from './types.js';
58-
import { getConfiguredPTZAction } from './utils/ptz.js';
5958
import { sortMedia } from './utils/sort-media.js';
6059

6160
export class QueryClassifier {
@@ -789,23 +788,12 @@ export class CameraManager {
789788
preset?: string;
790789
},
791790
): Promise<void> {
792-
const cameraConfig = this._store.getCameraConfig(cameraID);
793-
if (!cameraConfig) {
791+
const camera = this._store.getCamera(cameraID);
792+
if (!camera) {
794793
return;
795794
}
796-
const configuredAction = getConfiguredPTZAction(cameraConfig, action, options);
797-
if (configuredAction) {
798-
return await this._api.getActionsManager().executeActions(configuredAction);
799-
}
800-
801-
const hass = this._api.getHASSManager().getHASS();
802-
const engine = this._store.getEngineForCameraID(cameraID);
803-
804-
if (!engine || !hass) {
805-
return;
806-
}
807-
return await this._requestLimit.add(() =>
808-
engine.executePTZAction(hass, cameraConfig, action, options),
795+
await this._requestLimit.add(() =>
796+
camera.executePTZAction(this._api.getActionsManager(), action, options),
809797
);
810798
}
811799
}

0 commit comments

Comments
 (0)