@apocaliss92/scrypted-reolink-native 0.1.24 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/baichuan-base.ts +1 -2
- package/src/camera-battery.ts +1 -8
- package/src/camera.ts +4 -25
- package/src/common.ts +122 -100
- package/src/connect.ts +3 -3
- package/src/main.ts +24 -16
- package/src/multiFocal.ts +43 -103
- package/src/nvr.ts +6 -6
- package/src/utils.ts +12 -3
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/baichuan-base.ts
CHANGED
|
@@ -248,11 +248,10 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
248
248
|
username: config.username,
|
|
249
249
|
password: config.password,
|
|
250
250
|
uid: config.uid,
|
|
251
|
-
logger
|
|
251
|
+
logger,
|
|
252
252
|
debugOptions: config.debugOptions,
|
|
253
253
|
},
|
|
254
254
|
transport: config.transport,
|
|
255
|
-
logger: logger as Console,
|
|
256
255
|
});
|
|
257
256
|
|
|
258
257
|
await api.login();
|
package/src/camera-battery.ts
CHANGED
|
@@ -120,8 +120,7 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
120
120
|
|
|
121
121
|
logger.log('Starting periodic tasks for battery camera');
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
if (!this.nvrDevice) {
|
|
123
|
+
if (!this.nvrDevice && !this.multiFocalDevice) {
|
|
125
124
|
this.sleepCheckTimer = setInterval(async () => {
|
|
126
125
|
try {
|
|
127
126
|
const api = this.baichuanApi;
|
|
@@ -375,18 +374,12 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
375
374
|
}
|
|
376
375
|
}
|
|
377
376
|
|
|
378
|
-
async withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
379
|
-
return await fn();
|
|
380
|
-
}
|
|
381
|
-
|
|
382
377
|
protected async withBaichuanClient<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T> {
|
|
383
378
|
const client = await this.ensureClient();
|
|
384
379
|
return fn(client);
|
|
385
380
|
}
|
|
386
381
|
|
|
387
382
|
async createStreamClient(): Promise<ReolinkBaichuanApi> {
|
|
388
|
-
// Reuse the main Baichuan client connection instead of creating a new one
|
|
389
|
-
// This ensures we use a single session for everything (general + streams)
|
|
390
383
|
return await this.ensureClient();
|
|
391
384
|
}
|
|
392
385
|
}
|
package/src/camera.ts
CHANGED
|
@@ -31,8 +31,8 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
constructor(
|
|
34
|
-
nativeId: string,
|
|
35
|
-
public plugin: ReolinkNativePlugin,
|
|
34
|
+
nativeId: string,
|
|
35
|
+
public plugin: ReolinkNativePlugin,
|
|
36
36
|
nvrDevice?: ReolinkNativeNvrDevice,
|
|
37
37
|
multiFocalDevice?: ReolinkNativeMultiFocalDevice
|
|
38
38
|
) {
|
|
@@ -67,27 +67,6 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
71
|
-
try {
|
|
72
|
-
return await fn();
|
|
73
|
-
}
|
|
74
|
-
catch (e) {
|
|
75
|
-
if (!this.isRecoverableBaichuanError(e)) {
|
|
76
|
-
throw e;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Reset client and clear cache on recoverable error
|
|
80
|
-
await this.resetBaichuanClient(e);
|
|
81
|
-
|
|
82
|
-
// Important: callers must re-acquire the client inside fn.
|
|
83
|
-
try {
|
|
84
|
-
return await fn();
|
|
85
|
-
} catch (retryError) {
|
|
86
|
-
throw retryError;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
70
|
|
|
92
71
|
async init() {
|
|
93
72
|
this.startPeriodicTasks();
|
|
@@ -97,6 +76,7 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
97
76
|
|
|
98
77
|
async createStreamClient(): Promise<ReolinkBaichuanApi> {
|
|
99
78
|
const { ipAddress, username, password } = this.storageSettings.values;
|
|
79
|
+
const logger = this.getBaichuanLogger();
|
|
100
80
|
|
|
101
81
|
const debugOptions = this.getBaichuanDebugOptions();
|
|
102
82
|
const api = await createBaichuanApi(
|
|
@@ -105,11 +85,10 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
105
85
|
host: ipAddress,
|
|
106
86
|
username: username,
|
|
107
87
|
password: password,
|
|
108
|
-
logger
|
|
88
|
+
logger,
|
|
109
89
|
debugOptions
|
|
110
90
|
},
|
|
111
91
|
transport: 'tcp',
|
|
112
|
-
logger: this.console,
|
|
113
92
|
},
|
|
114
93
|
);
|
|
115
94
|
await api.login();
|
package/src/common.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, ReolinkSupportedStream, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
2
|
import sdk, { BinarySensor, Brightness, Camera, Device, DeviceProvider, Intercom, MediaObject, MediaStreamUrl, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, RequestMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
|
|
3
3
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
4
|
+
import path from 'path';
|
|
4
5
|
import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
|
|
5
6
|
import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
|
|
6
7
|
import { normalizeUid, type BaichuanTransport } from "./connect";
|
|
7
8
|
import { convertDebugLogsToApiOptions, DebugLogDisplayNames, DebugLogOption, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
|
|
8
9
|
import { ReolinkBaichuanIntercom } from "./intercom";
|
|
9
10
|
import ReolinkNativePlugin from "./main";
|
|
10
|
-
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
11
11
|
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
12
|
+
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
12
13
|
import { ReolinkPtzPresets } from "./presets";
|
|
13
14
|
import {
|
|
14
15
|
createRfc4571MediaObjectFromStreamManager,
|
|
@@ -17,10 +18,9 @@ import {
|
|
|
17
18
|
selectStreamOption,
|
|
18
19
|
StreamManager
|
|
19
20
|
} from "./stream-utils";
|
|
20
|
-
import { getDeviceInterfaces, updateDeviceInfo } from "./utils";
|
|
21
|
-
import path from 'path';
|
|
21
|
+
import { floodlightSuffix, getDeviceInterfaces, pirSuffix, sirenSuffix, updateDeviceInfo } from "./utils";
|
|
22
22
|
|
|
23
|
-
export type CameraType = 'battery' | 'regular';
|
|
23
|
+
export type CameraType = 'battery' | 'regular' | 'multi-focal' | 'multi-focal-battery';
|
|
24
24
|
|
|
25
25
|
export interface CommonCameraMixinOptions {
|
|
26
26
|
type: CameraType;
|
|
@@ -197,6 +197,12 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
197
197
|
await this.credentialsChanged();
|
|
198
198
|
}
|
|
199
199
|
},
|
|
200
|
+
debugEvents: {
|
|
201
|
+
title: 'Debug Events',
|
|
202
|
+
type: 'boolean',
|
|
203
|
+
immediate: true,
|
|
204
|
+
hide: true,
|
|
205
|
+
},
|
|
200
206
|
username: {
|
|
201
207
|
type: 'string',
|
|
202
208
|
title: 'Username',
|
|
@@ -220,7 +226,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
220
226
|
json: true,
|
|
221
227
|
hide: true,
|
|
222
228
|
},
|
|
223
|
-
|
|
229
|
+
multifocalInfo: {
|
|
224
230
|
json: true,
|
|
225
231
|
hide: true,
|
|
226
232
|
},
|
|
@@ -515,7 +521,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
515
521
|
// Abstract init method that subclasses must implement
|
|
516
522
|
abstract init(): Promise<void>;
|
|
517
523
|
|
|
518
|
-
abstract withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T>;
|
|
519
524
|
protected withBaichuanClient?<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T>;
|
|
520
525
|
motionTimeout?: NodeJS.Timeout;
|
|
521
526
|
doorbellBinaryTimeout?: NodeJS.Timeout;
|
|
@@ -527,18 +532,20 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
527
532
|
thisDevice: Settings
|
|
528
533
|
|
|
529
534
|
constructor(
|
|
530
|
-
nativeId: string,
|
|
531
|
-
public plugin: ReolinkNativePlugin,
|
|
535
|
+
nativeId: string,
|
|
536
|
+
public plugin: ReolinkNativePlugin,
|
|
532
537
|
public options: CommonCameraMixinOptions
|
|
533
538
|
) {
|
|
534
539
|
super(nativeId);
|
|
535
|
-
this.protocol = !options.nvrDevice && !options.multiFocalDevice && options.type === 'battery' ? 'udp' : 'tcp';
|
|
536
540
|
|
|
537
541
|
// Store NVR device reference if provided
|
|
538
542
|
this.nvrDevice = options.nvrDevice;
|
|
539
543
|
this.multiFocalDevice = options.multiFocalDevice;
|
|
540
544
|
this.thisDevice = sdk.systemManager.getDeviceById<Settings>(this.id);
|
|
541
545
|
|
|
546
|
+
const isBattery = options.type === 'battery' || options.type === 'multi-focal-battery';
|
|
547
|
+
this.protocol = isBattery ? 'udp' : 'tcp';
|
|
548
|
+
|
|
542
549
|
setTimeout(async () => {
|
|
543
550
|
await this.parentInit();
|
|
544
551
|
}, 2000);
|
|
@@ -601,7 +608,31 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
601
608
|
return this.name || 'Camera';
|
|
602
609
|
}
|
|
603
610
|
|
|
604
|
-
|
|
611
|
+
async withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
612
|
+
if (this.protocol === 'udp') {
|
|
613
|
+
return await fn();
|
|
614
|
+
} else {
|
|
615
|
+
try {
|
|
616
|
+
return await fn();
|
|
617
|
+
} catch (e) {
|
|
618
|
+
if (!this.isRecoverableBaichuanError(e)) {
|
|
619
|
+
throw e;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Reset client and clear cache on recoverable error
|
|
623
|
+
await this.resetBaichuanClient(e);
|
|
624
|
+
|
|
625
|
+
// Important: callers must re-acquire the client inside fn.
|
|
626
|
+
try {
|
|
627
|
+
return await fn();
|
|
628
|
+
} catch (retryError) {
|
|
629
|
+
throw retryError;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async runDiagnostics(): Promise<void> {
|
|
605
636
|
const logger = this.getBaichuanLogger();
|
|
606
637
|
const outputPath = this.storageSettings.values.diagnosticsOutputPath || process.env.SCRYPTED_PLUGIN_VOLUME || "";
|
|
607
638
|
|
|
@@ -654,8 +685,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
654
685
|
}
|
|
655
686
|
|
|
656
687
|
public getAbilities(): DeviceCapabilities {
|
|
657
|
-
if(this.
|
|
658
|
-
return this.
|
|
688
|
+
if (this.multiFocalDevice) {
|
|
689
|
+
return this.multiFocalDevice.getInterfaces(this.storageSettings.values.rtspChannel).capabilities;
|
|
659
690
|
} else {
|
|
660
691
|
return this.storageSettings.values.capabilities;
|
|
661
692
|
}
|
|
@@ -690,9 +721,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
690
721
|
|
|
691
722
|
// Event subscription methods
|
|
692
723
|
unsubscribedToEvents(): void {
|
|
693
|
-
// Use base class unsubscribe
|
|
694
724
|
this.unsubscribeFromEvents().catch(() => {
|
|
695
|
-
// ignore
|
|
696
725
|
});
|
|
697
726
|
|
|
698
727
|
if (this.motionDetected) {
|
|
@@ -1024,20 +1053,17 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1024
1053
|
this.binaryState = false;
|
|
1025
1054
|
}
|
|
1026
1055
|
|
|
1027
|
-
// Report devices (siren, floodlight, PIR)
|
|
1028
1056
|
async reportDevices(): Promise<void> {
|
|
1029
|
-
|
|
1030
|
-
return;
|
|
1031
|
-
}
|
|
1057
|
+
const abilities = this.getAbilities();
|
|
1032
1058
|
|
|
1033
|
-
const { hasSiren, hasFloodlight, hasPir } =
|
|
1059
|
+
const { hasSiren, hasFloodlight, hasPir } = abilities;
|
|
1034
1060
|
|
|
1035
1061
|
const devices: Device[] = [];
|
|
1036
1062
|
|
|
1037
1063
|
if (hasSiren) {
|
|
1038
|
-
const sirenNativeId = `${this.nativeId}
|
|
1064
|
+
const sirenNativeId = `${this.nativeId}${sirenSuffix}`;
|
|
1039
1065
|
devices.push({
|
|
1040
|
-
providerNativeId: this.
|
|
1066
|
+
providerNativeId: this.nativeId,
|
|
1041
1067
|
name: `${this.name} Siren`,
|
|
1042
1068
|
nativeId: sirenNativeId,
|
|
1043
1069
|
info: {
|
|
@@ -1049,9 +1075,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1049
1075
|
}
|
|
1050
1076
|
|
|
1051
1077
|
if (hasFloodlight) {
|
|
1052
|
-
const floodlightNativeId = `${this.nativeId}
|
|
1078
|
+
const floodlightNativeId = `${this.nativeId}${floodlightSuffix}`;
|
|
1053
1079
|
devices.push({
|
|
1054
|
-
providerNativeId: this.
|
|
1080
|
+
providerNativeId: this.nativeId,
|
|
1055
1081
|
name: `${this.name} Floodlight`,
|
|
1056
1082
|
nativeId: floodlightNativeId,
|
|
1057
1083
|
info: {
|
|
@@ -1063,9 +1089,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1063
1089
|
}
|
|
1064
1090
|
|
|
1065
1091
|
if (hasPir) {
|
|
1066
|
-
const pirNativeId = `${this.nativeId}
|
|
1092
|
+
const pirNativeId = `${this.nativeId}${pirSuffix}`;
|
|
1067
1093
|
devices.push({
|
|
1068
|
-
providerNativeId: this.
|
|
1094
|
+
providerNativeId: this.nativeId,
|
|
1069
1095
|
name: `${this.name} PIR`,
|
|
1070
1096
|
nativeId: pirNativeId,
|
|
1071
1097
|
info: {
|
|
@@ -1116,6 +1142,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1116
1142
|
async updateDeviceInfo(): Promise<void> {
|
|
1117
1143
|
const logger = this.getBaichuanLogger();
|
|
1118
1144
|
|
|
1145
|
+
if (this.multiFocalDevice) {
|
|
1146
|
+
this.info = this.multiFocalDevice.info;
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1119
1150
|
const { ipAddress, rtspChannel } = this.storageSettings.values;
|
|
1120
1151
|
try {
|
|
1121
1152
|
const api = await this.ensureClient();
|
|
@@ -1125,9 +1156,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1125
1156
|
device: this,
|
|
1126
1157
|
ipAddress,
|
|
1127
1158
|
deviceData,
|
|
1159
|
+
logger,
|
|
1128
1160
|
});
|
|
1129
1161
|
|
|
1130
|
-
logger.log(`Device info updated: ${JSON.stringify(deviceData)}`);
|
|
1131
1162
|
} catch (e) {
|
|
1132
1163
|
logger.warn('Failed to fetch device info', e);
|
|
1133
1164
|
}
|
|
@@ -1135,24 +1166,24 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1135
1166
|
|
|
1136
1167
|
// Device provider methods
|
|
1137
1168
|
async getDevice(nativeId: string): Promise<any> {
|
|
1138
|
-
if (nativeId.endsWith(
|
|
1169
|
+
if (nativeId.endsWith(sirenSuffix)) {
|
|
1139
1170
|
this.siren ||= new ReolinkCameraSiren(this, nativeId);
|
|
1140
1171
|
return this.siren;
|
|
1141
|
-
} else if (nativeId.endsWith(
|
|
1172
|
+
} else if (nativeId.endsWith(floodlightSuffix)) {
|
|
1142
1173
|
this.floodlight ||= new ReolinkCameraFloodlight(this, nativeId);
|
|
1143
1174
|
return this.floodlight;
|
|
1144
|
-
} else if (nativeId.endsWith(
|
|
1175
|
+
} else if (nativeId.endsWith(pirSuffix)) {
|
|
1145
1176
|
this.pirSensor ||= new ReolinkCameraPirSensor(this, nativeId);
|
|
1146
1177
|
return this.pirSensor;
|
|
1147
1178
|
}
|
|
1148
1179
|
}
|
|
1149
1180
|
|
|
1150
1181
|
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
|
1151
|
-
if (nativeId.endsWith(
|
|
1182
|
+
if (nativeId.endsWith(sirenSuffix)) {
|
|
1152
1183
|
this.siren = undefined;
|
|
1153
|
-
} else if (nativeId.endsWith(
|
|
1184
|
+
} else if (nativeId.endsWith(floodlightSuffix)) {
|
|
1154
1185
|
this.floodlight = undefined;
|
|
1155
|
-
} else if (nativeId.endsWith(
|
|
1186
|
+
} else if (nativeId.endsWith(pirSuffix)) {
|
|
1156
1187
|
this.pirSensor = undefined;
|
|
1157
1188
|
}
|
|
1158
1189
|
}
|
|
@@ -1352,7 +1383,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1352
1383
|
}
|
|
1353
1384
|
|
|
1354
1385
|
if (streams.length) {
|
|
1355
|
-
logger.log('Fetched video stream options',
|
|
1386
|
+
logger.log('Fetched video stream options', streams.map((s) => s.name).join(', '));
|
|
1387
|
+
logger.debug(JSON.stringify(streams));
|
|
1356
1388
|
this.cachedVideoStreamOptions = streams;
|
|
1357
1389
|
return streams;
|
|
1358
1390
|
}
|
|
@@ -1452,7 +1484,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1452
1484
|
});
|
|
1453
1485
|
this.classes = objects;
|
|
1454
1486
|
this.presets = presets;
|
|
1455
|
-
this.storageSettings.values.capabilities = capabilities;
|
|
1456
1487
|
this.ptzPresets.setCachedPtzPresets(presets);
|
|
1457
1488
|
|
|
1458
1489
|
try {
|
|
@@ -1463,21 +1494,25 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1463
1494
|
|
|
1464
1495
|
const device: Device = {
|
|
1465
1496
|
nativeId: this.nativeId,
|
|
1466
|
-
providerNativeId: this.nvrDevice?.nativeId ??
|
|
1497
|
+
providerNativeId: this.nvrDevice?.nativeId ??
|
|
1498
|
+
this.multiFocalDevice?.nativeId ??
|
|
1499
|
+
this.plugin?.nativeId,
|
|
1467
1500
|
name: this.name,
|
|
1468
1501
|
interfaces,
|
|
1469
1502
|
type,
|
|
1470
1503
|
info: this.info,
|
|
1471
1504
|
};
|
|
1472
1505
|
|
|
1473
|
-
logger.log(`Updating device interfaces: ${JSON.stringify(device)}`);
|
|
1474
|
-
|
|
1475
1506
|
await sdk.deviceManager.onDeviceDiscovered(device);
|
|
1507
|
+
|
|
1508
|
+
logger.log(`Device interfaces updated`);
|
|
1509
|
+
logger.debug(`${JSON.stringify(device)}`);
|
|
1476
1510
|
} catch (e) {
|
|
1477
1511
|
logger.error('Failed to update device interfaces', e);
|
|
1478
1512
|
}
|
|
1479
1513
|
|
|
1480
|
-
logger.log(`Refreshed device capabilities: ${JSON.stringify(
|
|
1514
|
+
logger.log(`Refreshed device capabilities: ${JSON.stringify(capabilities)}`);
|
|
1515
|
+
logger.debug(`Refreshed device capabilities: ${JSON.stringify({ abilities, support, presets, objects })}`);
|
|
1481
1516
|
}
|
|
1482
1517
|
catch (e) {
|
|
1483
1518
|
logger.error('Failed to refresh abilities', e);
|
|
@@ -1497,65 +1532,28 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1497
1532
|
logger.warn('Failed to update device info during init', e);
|
|
1498
1533
|
}
|
|
1499
1534
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
await this.reportDevices();
|
|
1509
|
-
}
|
|
1510
|
-
catch (e) {
|
|
1511
|
-
logger.warn('Failed to report devices during init', e);
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
const { hasIntercom, hasPtz } = this.getAbilities();
|
|
1515
|
-
|
|
1516
|
-
if (hasIntercom) {
|
|
1517
|
-
this.intercom = new ReolinkBaichuanIntercom(this);
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
if (hasPtz) {
|
|
1521
|
-
const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
|
|
1522
|
-
|
|
1523
|
-
this.storageSettings.settings.presets.choices = choices;
|
|
1524
|
-
this.storageSettings.settings.ptzSelectedPreset.choices = choices;
|
|
1525
|
-
|
|
1526
|
-
this.storageSettings.settings.presets.hide = false;
|
|
1527
|
-
this.storageSettings.settings.ptzMoveDurationMs.hide = false;
|
|
1528
|
-
this.storageSettings.settings.ptzZoomStep.hide = false;
|
|
1529
|
-
this.storageSettings.settings.ptzCreatePreset.hide = false;
|
|
1530
|
-
this.storageSettings.settings.ptzSelectedPreset.hide = false;
|
|
1531
|
-
this.storageSettings.settings.ptzUpdateSelectedPreset.hide = false;
|
|
1532
|
-
this.storageSettings.settings.ptzDeleteSelectedPreset.hide = false;
|
|
1533
|
-
|
|
1534
|
-
this.updatePtzCaps();
|
|
1535
|
+
if (!this.multiFocalDevice) {
|
|
1536
|
+
try {
|
|
1537
|
+
await this.refreshDeviceState();
|
|
1538
|
+
await this.reportDevices();
|
|
1539
|
+
}
|
|
1540
|
+
catch (e) {
|
|
1541
|
+
logger.warn('Failed to connect/refresh during init', e);
|
|
1542
|
+
}
|
|
1535
1543
|
}
|
|
1536
1544
|
|
|
1537
|
-
const isBattery = this.options.type === 'battery';
|
|
1538
1545
|
const { username, password } = this.storageSettings.values;
|
|
1546
|
+
const isCamera = this.options.type === 'regular' || this.options.type === 'battery';
|
|
1547
|
+
const isBatteryCamera = this.options.type === 'battery';
|
|
1548
|
+
const isBatteryMultiFocal = this.options.type === 'multi-focal-battery';
|
|
1549
|
+
const isBattery = isBatteryCamera || isBatteryMultiFocal;
|
|
1539
1550
|
|
|
1540
|
-
this.streamManager = new StreamManager({
|
|
1541
|
-
createStreamClient: () => this.createStreamClient(),
|
|
1542
|
-
getLogger: () => logger as Console,
|
|
1543
|
-
credentials: {
|
|
1544
|
-
username,
|
|
1545
|
-
password
|
|
1546
|
-
},
|
|
1547
|
-
// For battery cameras, we use a shared connection
|
|
1548
|
-
sharedConnection: isBattery,
|
|
1549
|
-
});
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
// this.storageSettings.settings.snapshotCacheMinutes.hide = !isBattery;
|
|
1553
1551
|
this.storageSettings.settings.uid.hide = !isBattery;
|
|
1554
1552
|
this.storageSettings.settings.batteryUpdateIntervalMinutes.hide = !isBattery;
|
|
1555
1553
|
this.storageSettings.settings.lowThresholdBatteryRecording.hide = !isBattery;
|
|
1556
1554
|
this.storageSettings.settings.highThresholdBatteryRecording.hide = !isBattery;
|
|
1557
1555
|
|
|
1558
|
-
if (
|
|
1556
|
+
if (isBatteryCamera && !this.storageSettings.values.mixinsSetup) {
|
|
1559
1557
|
try {
|
|
1560
1558
|
const device = sdk.systemManager.getDeviceById<Settings>(this.id);
|
|
1561
1559
|
if (device) {
|
|
@@ -1577,30 +1575,54 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1577
1575
|
logger.warn('Failed to subscribe to Baichuan events', e);
|
|
1578
1576
|
}
|
|
1579
1577
|
|
|
1578
|
+
if (isCamera) {
|
|
1579
|
+
this.streamManager = new StreamManager({
|
|
1580
|
+
createStreamClient: () => this.createStreamClient(),
|
|
1581
|
+
getLogger: () => logger as Console,
|
|
1582
|
+
credentials: {
|
|
1583
|
+
username,
|
|
1584
|
+
password
|
|
1585
|
+
},
|
|
1586
|
+
sharedConnection: isBattery,
|
|
1587
|
+
});
|
|
1580
1588
|
|
|
1581
|
-
|
|
1582
|
-
this.storageSettings.settings.username.hide = true;
|
|
1583
|
-
this.storageSettings.settings.password.hide = true;
|
|
1584
|
-
this.storageSettings.settings.ipAddress.hide = true;
|
|
1585
|
-
this.storageSettings.settings.uid.hide = true;
|
|
1589
|
+
const { hasIntercom, hasPtz } = this.getAbilities();
|
|
1586
1590
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1591
|
+
if (hasIntercom) {
|
|
1592
|
+
this.intercom = new ReolinkBaichuanIntercom(this);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
if (hasPtz) {
|
|
1596
|
+
const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
|
|
1597
|
+
|
|
1598
|
+
this.storageSettings.settings.presets.choices = choices;
|
|
1599
|
+
this.storageSettings.settings.ptzSelectedPreset.choices = choices;
|
|
1600
|
+
|
|
1601
|
+
this.storageSettings.settings.presets.hide = false;
|
|
1602
|
+
this.storageSettings.settings.ptzMoveDurationMs.hide = false;
|
|
1603
|
+
this.storageSettings.settings.ptzZoomStep.hide = false;
|
|
1604
|
+
this.storageSettings.settings.ptzCreatePreset.hide = false;
|
|
1605
|
+
this.storageSettings.settings.ptzSelectedPreset.hide = false;
|
|
1606
|
+
this.storageSettings.settings.ptzUpdateSelectedPreset.hide = false;
|
|
1607
|
+
this.storageSettings.settings.ptzDeleteSelectedPreset.hide = false;
|
|
1608
|
+
|
|
1609
|
+
this.updatePtzCaps();
|
|
1610
|
+
}
|
|
1590
1611
|
}
|
|
1591
1612
|
|
|
1592
|
-
if (this.multiFocalDevice) {
|
|
1613
|
+
if (this.nvrDevice || this.multiFocalDevice) {
|
|
1593
1614
|
this.storageSettings.settings.username.hide = true;
|
|
1594
1615
|
this.storageSettings.settings.password.hide = true;
|
|
1595
1616
|
this.storageSettings.settings.ipAddress.hide = true;
|
|
1596
1617
|
this.storageSettings.settings.uid.hide = true;
|
|
1597
1618
|
|
|
1598
|
-
this.storageSettings.settings.username.defaultValue = this.
|
|
1599
|
-
this.storageSettings.settings.password.defaultValue = this.
|
|
1600
|
-
this.storageSettings.settings.ipAddress.defaultValue = this.
|
|
1619
|
+
this.storageSettings.settings.username.defaultValue = this.nvrDevice.storageSettings.values.username;
|
|
1620
|
+
this.storageSettings.settings.password.defaultValue = this.nvrDevice.storageSettings.values.password;
|
|
1621
|
+
this.storageSettings.settings.ipAddress.defaultValue = this.nvrDevice.storageSettings.values.ipAddress;
|
|
1601
1622
|
}
|
|
1602
1623
|
|
|
1603
1624
|
await this.init();
|
|
1625
|
+
|
|
1604
1626
|
this.initComplete = true;
|
|
1605
1627
|
}
|
|
1606
1628
|
}
|
package/src/connect.ts
CHANGED
|
@@ -19,16 +19,16 @@ export function normalizeUid(uid?: string): string | undefined {
|
|
|
19
19
|
export async function createBaichuanApi(props: {
|
|
20
20
|
inputs: BaichuanConnectInputs,
|
|
21
21
|
transport: BaichuanTransport,
|
|
22
|
-
logger: Console,
|
|
23
22
|
}): Promise<ReolinkBaichuanApi> {
|
|
24
|
-
const { inputs, transport
|
|
23
|
+
const { inputs, transport } = props;
|
|
24
|
+
const { logger } = inputs;
|
|
25
25
|
const { ReolinkBaichuanApi } = await import("@apocaliss92/reolink-baichuan-js");
|
|
26
26
|
|
|
27
27
|
const base: BaichuanClientOptions = {
|
|
28
28
|
host: inputs.host,
|
|
29
29
|
username: inputs.username,
|
|
30
30
|
password: inputs.password,
|
|
31
|
-
logger
|
|
31
|
+
logger,
|
|
32
32
|
debugOptions: inputs.debugOptions ?? {}
|
|
33
33
|
};
|
|
34
34
|
|
package/src/main.ts
CHANGED
|
@@ -4,10 +4,9 @@ import { ReolinkNativeCamera } from "./camera";
|
|
|
4
4
|
import { ReolinkNativeBatteryCamera } from "./camera-battery";
|
|
5
5
|
import { CommonCameraMixin } from "./common";
|
|
6
6
|
import { createBaichuanApi } from "./connect";
|
|
7
|
-
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
8
7
|
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
9
|
-
import { batteryCameraSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, nvrSuffix } from "./utils";
|
|
10
|
-
import {
|
|
8
|
+
import { batteryCameraSuffix, batteryMultifocalSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, nvrSuffix } from "./utils";
|
|
9
|
+
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
11
10
|
|
|
12
11
|
class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
|
|
13
12
|
devices = new Map<string, BaseBaichuanClass>();
|
|
@@ -65,18 +64,28 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
65
64
|
const deviceInfo = detection.deviceInfo || {};
|
|
66
65
|
const name = deviceInfo.name || 'Reolink Multi-Focal';
|
|
67
66
|
const serialNumber = deviceInfo.serialNumber || deviceInfo.itemNo || `multifocal-${Date.now()}`;
|
|
68
|
-
|
|
67
|
+
const isBattery = detection.transport === 'udp';
|
|
68
|
+
nativeId = `${serialNumber}${isBattery ? batteryMultifocalSuffix : multifocalSuffix}`;
|
|
69
69
|
|
|
70
70
|
settings.newCamera ||= name;
|
|
71
71
|
|
|
72
|
+
const interfaces = [
|
|
73
|
+
ScryptedInterface.Settings,
|
|
74
|
+
ScryptedInterface.DeviceProvider,
|
|
75
|
+
ScryptedInterface.Reboot,
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
if (isBattery) {
|
|
79
|
+
interfaces.push(
|
|
80
|
+
ScryptedInterface.Battery,
|
|
81
|
+
ScryptedInterface.Sleep
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
await sdk.deviceManager.onDeviceDiscovered({
|
|
73
86
|
nativeId,
|
|
74
87
|
name,
|
|
75
|
-
interfaces
|
|
76
|
-
ScryptedInterface.Settings,
|
|
77
|
-
ScryptedInterface.DeviceProvider,
|
|
78
|
-
ScryptedInterface.Reboot,
|
|
79
|
-
],
|
|
88
|
+
interfaces,
|
|
80
89
|
type: ScryptedDeviceType.DeviceProvider,
|
|
81
90
|
providerNativeId: this.nativeId,
|
|
82
91
|
});
|
|
@@ -89,7 +98,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
89
98
|
device.storageSettings.values.username = username;
|
|
90
99
|
device.storageSettings.values.password = password;
|
|
91
100
|
device.storageSettings.values.uid = detection.uid || '';
|
|
92
|
-
device.storageSettings.values.protocol = detection.transport || 'tcp' as BaichuanTransport;
|
|
93
101
|
|
|
94
102
|
return nativeId;
|
|
95
103
|
}
|
|
@@ -151,15 +159,12 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
151
159
|
logger: this.console,
|
|
152
160
|
},
|
|
153
161
|
transport: detection.transport,
|
|
154
|
-
logger: this.console,
|
|
155
162
|
});
|
|
156
163
|
|
|
157
164
|
try {
|
|
158
165
|
await api.login();
|
|
159
166
|
const rtspChannel = 0;
|
|
160
|
-
const {
|
|
161
|
-
|
|
162
|
-
this.console.log(nativeId, JSON.stringify({ abilities, capabilities, deviceInfo }));
|
|
167
|
+
const { capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel);
|
|
163
168
|
|
|
164
169
|
const { interfaces, type } = getDeviceInterfaces({
|
|
165
170
|
capabilities,
|
|
@@ -175,7 +180,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
175
180
|
});
|
|
176
181
|
|
|
177
182
|
const device = await this.getDevice(nativeId) as CommonCameraMixin;
|
|
178
|
-
this.console.log(name, interfaces, type, device);
|
|
179
183
|
|
|
180
184
|
device.info = deviceInfo;
|
|
181
185
|
device.classes = objects;
|
|
@@ -214,10 +218,12 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
214
218
|
key: 'ip',
|
|
215
219
|
title: 'IP Address',
|
|
216
220
|
placeholder: '192.168.2.222',
|
|
221
|
+
value: '192.168.',
|
|
217
222
|
},
|
|
218
223
|
{
|
|
219
224
|
key: 'username',
|
|
220
225
|
title: 'Username',
|
|
226
|
+
value: 'admin',
|
|
221
227
|
},
|
|
222
228
|
{
|
|
223
229
|
key: 'password',
|
|
@@ -237,8 +243,10 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
237
243
|
return new ReolinkNativeBatteryCamera(nativeId, this);
|
|
238
244
|
} else if (nativeId.endsWith(nvrSuffix)) {
|
|
239
245
|
return new ReolinkNativeNvrDevice(nativeId, this);
|
|
246
|
+
} else if (nativeId.endsWith(batteryMultifocalSuffix)) {
|
|
247
|
+
return new ReolinkNativeMultiFocalDevice(nativeId, this, "multi-focal-battery");
|
|
240
248
|
} else if (nativeId.endsWith(multifocalSuffix)) {
|
|
241
|
-
return new ReolinkNativeMultiFocalDevice(nativeId, this);
|
|
249
|
+
return new ReolinkNativeMultiFocalDevice(nativeId, this, "multi-focal");
|
|
242
250
|
} else {
|
|
243
251
|
return new ReolinkNativeCamera(nativeId, this);
|
|
244
252
|
}
|