@apocaliss92/scrypted-reolink-native 0.1.42 → 0.2.1
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/build-lib.sh +31 -0
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +2 -1
- package/src/baichuan-base.ts +149 -30
- package/src/camera.ts +3004 -89
- package/src/intercom.ts +5 -7
- package/src/main.ts +18 -27
- package/src/multiFocal.ts +194 -172
- package/src/nvr.ts +96 -238
- package/src/presets.ts +2 -2
- package/src/stream-utils.ts +232 -101
- package/src/utils.ts +22 -23
- package/src/camera-battery.ts +0 -336
- package/src/common.ts +0 -2551
package/src/nvr.ts
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReolinkBaichuanApi, ReolinkBaichuanDeviceSummary, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
2
|
import sdk, { AdoptDevice, Device, DeviceDiscovery, DeviceProvider, DiscoveredDevice, Reboot, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from "@scrypted/sdk";
|
|
3
3
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
4
4
|
import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
|
|
5
|
-
import {
|
|
6
|
-
import { ReolinkNativeBatteryCamera } from "./camera-battery";
|
|
7
|
-
import { normalizeUid } from "./connect";
|
|
5
|
+
import { ReolinkCamera } from "./camera";
|
|
8
6
|
import { convertDebugLogsToApiOptions, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
|
|
9
7
|
import ReolinkNativePlugin from "./main";
|
|
10
|
-
import {
|
|
8
|
+
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
9
|
+
import { batteryCameraSuffix, batteryMultifocalSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, updateDeviceInfo } from "./utils";
|
|
11
10
|
|
|
12
11
|
export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Settings, DeviceDiscovery, DeviceProvider, Reboot {
|
|
12
|
+
private readonly onSimpleEventBound = (ev: ReolinkSimpleEvent) => this.onSimpleEvent(ev);
|
|
13
|
+
|
|
13
14
|
storageSettings = new StorageSettings(this, {
|
|
14
15
|
debugLogs: {
|
|
15
16
|
title: 'Debug Events',
|
|
16
17
|
type: 'boolean',
|
|
17
18
|
immediate: true,
|
|
18
19
|
},
|
|
19
|
-
eventSource: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
20
|
+
// eventSource: {
|
|
21
|
+
// title: 'Event Source',
|
|
22
|
+
// description: 'Select the source for camera events: Native (Baichuan) or CGI (HTTP polling)',
|
|
23
|
+
// type: 'string',
|
|
24
|
+
// choices: ['Native', 'CGI'],
|
|
25
|
+
// defaultValue: 'Native',
|
|
26
|
+
// immediate: true,
|
|
27
|
+
// onPut: async () => {
|
|
28
|
+
// await this.reinitEventSubscriptions();
|
|
29
|
+
// }
|
|
30
|
+
// },
|
|
30
31
|
ipAddress: {
|
|
31
32
|
title: 'IP address',
|
|
32
33
|
type: 'string',
|
|
@@ -79,17 +80,14 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
79
80
|
this.debugLogsResetTimeout = undefined;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
// Defer reset by 2 seconds to allow settings to settle
|
|
83
83
|
this.debugLogsResetTimeout = setTimeout(async () => {
|
|
84
84
|
this.debugLogsResetTimeout = undefined;
|
|
85
85
|
try {
|
|
86
|
-
// Force reconnection with new debug options
|
|
87
86
|
this.baichuanApi = undefined;
|
|
88
87
|
this.ensureClientPromise = undefined;
|
|
89
|
-
// Trigger reconnection
|
|
90
88
|
await this.ensureBaichuanClient();
|
|
91
89
|
} catch (e) {
|
|
92
|
-
logger.warn('Failed to reset client after debug logs change', e);
|
|
90
|
+
logger.warn('Failed to reset client after debug logs change', e?.message || String(e));
|
|
93
91
|
}
|
|
94
92
|
}, 2000);
|
|
95
93
|
}
|
|
@@ -103,10 +101,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
103
101
|
rtspChannel: number;
|
|
104
102
|
deviceData: ReolinkBaichuanDeviceSummary;
|
|
105
103
|
}>();
|
|
106
|
-
|
|
107
|
-
lastErrorsCheck: number | undefined;
|
|
108
|
-
lastDevicesStatusCheck: number | undefined;
|
|
109
|
-
cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
|
|
104
|
+
cameraNativeMap = new Map<string, ReolinkCamera>();
|
|
110
105
|
private channelToNativeIdMap = new Map<number, string>();
|
|
111
106
|
private discoverDevicesPromise: Promise<DiscoveredDevice[]> | undefined;
|
|
112
107
|
processing = false;
|
|
@@ -114,7 +109,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
114
109
|
private debugLogsResetTimeout: NodeJS.Timeout | undefined;
|
|
115
110
|
|
|
116
111
|
constructor(nativeId: string, plugin: ReolinkNativePlugin) {
|
|
117
|
-
super(nativeId);
|
|
112
|
+
super(nativeId, "tcp");
|
|
118
113
|
this.plugin = plugin;
|
|
119
114
|
|
|
120
115
|
this.scheduleInit();
|
|
@@ -142,6 +137,23 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
142
137
|
};
|
|
143
138
|
}
|
|
144
139
|
|
|
140
|
+
protected getStreamClientInputs(): BaichuanConnectionConfig {
|
|
141
|
+
const { ipAddress, username, password } = this.storageSettings.values;
|
|
142
|
+
if (!ipAddress || !username || !password) {
|
|
143
|
+
throw new Error('Missing NVR credentials');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const debugOptions = this.getBaichuanDebugOptions();
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
host: ipAddress,
|
|
150
|
+
username,
|
|
151
|
+
password,
|
|
152
|
+
transport: 'tcp',
|
|
153
|
+
debugOptions,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
145
157
|
getBaichuanDebugOptions(): any | undefined {
|
|
146
158
|
const socketDebugLogs = this.storageSettings.values.socketApiDebugLogs || [];
|
|
147
159
|
return convertDebugLogsToApiOptions(socketDebugLogs);
|
|
@@ -152,15 +164,11 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
152
164
|
onClose: async () => {
|
|
153
165
|
await this.reinit();
|
|
154
166
|
},
|
|
155
|
-
onSimpleEvent:
|
|
156
|
-
getEventSubscriptionEnabled: () =>
|
|
157
|
-
const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
158
|
-
return eventSource === 'Native';
|
|
159
|
-
},
|
|
167
|
+
onSimpleEvent: this.onSimpleEventBound,
|
|
168
|
+
getEventSubscriptionEnabled: () => true,
|
|
160
169
|
};
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
|
|
164
172
|
protected isDebugEnabled(): boolean {
|
|
165
173
|
return this.storageSettings.values.debugLogs || false;
|
|
166
174
|
}
|
|
@@ -170,7 +178,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
protected async onBeforeCleanup(): Promise<void> {
|
|
173
|
-
await this.
|
|
181
|
+
await this.unsubscribeFromEvents();
|
|
174
182
|
}
|
|
175
183
|
|
|
176
184
|
async reinit() {
|
|
@@ -179,7 +187,6 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
179
187
|
this.initReinitTimeout = undefined;
|
|
180
188
|
}
|
|
181
189
|
|
|
182
|
-
// Schedule reinit with debounce
|
|
183
190
|
this.scheduleInit(true);
|
|
184
191
|
}
|
|
185
192
|
|
|
@@ -198,18 +205,12 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
198
205
|
}, isReinit ? 500 : 2000);
|
|
199
206
|
}
|
|
200
207
|
|
|
201
|
-
|
|
208
|
+
onSimpleEvent(ev: ReolinkSimpleEvent) {
|
|
202
209
|
const logger = this.getBaichuanLogger();
|
|
203
210
|
|
|
204
|
-
const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
205
|
-
if (eventSource !== 'Native') {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
211
|
try {
|
|
210
|
-
logger.debug(`Baichuan event: ${JSON.stringify(ev)}`);
|
|
212
|
+
logger.debug(`Baichuan event on nvr: ${JSON.stringify(ev)}`);
|
|
211
213
|
|
|
212
|
-
// Find camera for this channel
|
|
213
214
|
const channel = ev?.channel;
|
|
214
215
|
if (channel === undefined) {
|
|
215
216
|
logger.error('Event has no channel, ignoring');
|
|
@@ -217,89 +218,26 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
const nativeId = this.channelToNativeIdMap.get(channel);
|
|
220
|
-
const
|
|
221
|
+
const targetDevice = nativeId ? this.cameraNativeMap.get(nativeId) : undefined;
|
|
221
222
|
|
|
222
|
-
if (!
|
|
223
|
-
logger.debug(`No
|
|
223
|
+
if (!targetDevice) {
|
|
224
|
+
logger.debug(`No device found for channel ${channel} (nativeId: ${nativeId}), ignoring event`);
|
|
224
225
|
return;
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
|
|
228
|
-
const objects: string[] = [];
|
|
229
|
-
let motion = false;
|
|
230
|
-
let isSleepingEvent = false;
|
|
231
|
-
let isOnlineEvent = false;
|
|
232
|
-
|
|
233
|
-
switch (ev?.type) {
|
|
234
|
-
case 'motion':
|
|
235
|
-
motion = true;
|
|
236
|
-
break;
|
|
237
|
-
case 'doorbell':
|
|
238
|
-
// Handle doorbell if camera supports it
|
|
239
|
-
try {
|
|
240
|
-
targetCamera.handleDoorbellEvent();
|
|
241
|
-
}
|
|
242
|
-
catch (e) {
|
|
243
|
-
logger.warn(`Error handling doorbell event for camera channel ${channel}`, e);
|
|
244
|
-
}
|
|
245
|
-
motion = true;
|
|
246
|
-
break;
|
|
247
|
-
case 'people':
|
|
248
|
-
case 'vehicle':
|
|
249
|
-
case 'animal':
|
|
250
|
-
case 'face':
|
|
251
|
-
case 'package':
|
|
252
|
-
case 'other':
|
|
253
|
-
objects.push(ev.type);
|
|
254
|
-
motion = true;
|
|
255
|
-
break;
|
|
256
|
-
case 'awake':
|
|
257
|
-
case 'sleeping':
|
|
258
|
-
isSleepingEvent = true;
|
|
259
|
-
break;
|
|
260
|
-
case 'offline':
|
|
261
|
-
case 'online':
|
|
262
|
-
isOnlineEvent = true;
|
|
263
|
-
break;
|
|
264
|
-
default:
|
|
265
|
-
logger.error(`Unknown event type: ${ev?.type}`);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (isSleepingEvent) {
|
|
270
|
-
(targetCamera as ReolinkNativeBatteryCamera).updateSleepingState({
|
|
271
|
-
reason: 'NVR',
|
|
272
|
-
state: ev.type === 'sleeping' ? 'sleeping' : 'awake',
|
|
273
|
-
}).catch(() => { });
|
|
274
|
-
} if (isSleepingEvent) {
|
|
275
|
-
(targetCamera as ReolinkNativeBatteryCamera).updateOnlineState(
|
|
276
|
-
ev.type === 'online' ? true : false
|
|
277
|
-
).catch(() => { });
|
|
278
|
-
} else {
|
|
279
|
-
// Process events on the target camera
|
|
280
|
-
targetCamera.processEvents({ motion, objects }).catch((e) => {
|
|
281
|
-
logger.warn(`Error processing events for camera channel ${channel}`, e);
|
|
282
|
-
});
|
|
283
|
-
}
|
|
228
|
+
targetDevice.onSimpleEvent(ev);
|
|
284
229
|
}
|
|
285
230
|
catch (e) {
|
|
286
|
-
logger.warn('Error in NVR Native event forwarder', e);
|
|
231
|
+
logger.warn('Error in NVR Native event forwarder', e?.message || String(e));
|
|
287
232
|
}
|
|
288
233
|
}
|
|
289
234
|
|
|
290
235
|
async ensureBaichuanClient(): Promise<ReolinkBaichuanApi> {
|
|
291
|
-
// Use base class implementation
|
|
292
236
|
return await super.ensureBaichuanClient();
|
|
293
237
|
}
|
|
294
238
|
|
|
295
|
-
async
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (eventSource !== 'Native') {
|
|
299
|
-
await this.unsubscribeFromAllEvents();
|
|
300
|
-
} else {
|
|
301
|
-
await super.subscribeToEvents();
|
|
302
|
-
}
|
|
239
|
+
async ensureClient(): Promise<ReolinkBaichuanApi> {
|
|
240
|
+
return await this.ensureBaichuanClient();
|
|
303
241
|
}
|
|
304
242
|
|
|
305
243
|
private async runNvrDiagnostics(): Promise<void> {
|
|
@@ -313,117 +251,17 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
313
251
|
logger: this.console,
|
|
314
252
|
});
|
|
315
253
|
} catch (e) {
|
|
316
|
-
logger.error('Failed to run NVR diagnostics', e);
|
|
254
|
+
logger.error('Failed to run NVR diagnostics', e?.message || String(e));
|
|
317
255
|
throw e;
|
|
318
256
|
}
|
|
319
257
|
}
|
|
320
258
|
|
|
321
|
-
async unsubscribeFromAllEvents(): Promise<void> {
|
|
322
|
-
// Use base class implementation
|
|
323
|
-
await super.unsubscribeFromEvents();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Reinitialize event subscriptions based on selected event source
|
|
328
|
-
*/
|
|
329
|
-
private async reinitEventSubscriptions(): Promise<void> {
|
|
330
|
-
const logger = this.getBaichuanLogger();
|
|
331
|
-
const { eventSource } = this.storageSettings.values;
|
|
332
|
-
|
|
333
|
-
// Unsubscribe from Native events if switching away
|
|
334
|
-
if (eventSource !== 'Native') {
|
|
335
|
-
await this.unsubscribeFromAllEvents();
|
|
336
|
-
} else {
|
|
337
|
-
this.subscribeToAllEvents().catch((e) => {
|
|
338
|
-
logger.warn('Failed to subscribe to Native events', e);
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
logger.log(`Event source set to: ${eventSource}`);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Forward events from CGI source to cameras
|
|
347
|
-
*/
|
|
348
|
-
private forwardCgiEvents(eventsRes: Record<number, EventsResponse>): void {
|
|
349
|
-
const logger = this.getBaichuanLogger();
|
|
350
|
-
|
|
351
|
-
logger.debug(`CGI Events call result: ${JSON.stringify(eventsRes)}`);
|
|
352
|
-
|
|
353
|
-
// Use channel map for efficient lookup
|
|
354
|
-
for (const [channel, nativeId] of this.channelToNativeIdMap.entries()) {
|
|
355
|
-
const targetCamera = nativeId ? this.cameraNativeMap.get(nativeId) : undefined;
|
|
356
|
-
const cameraEventsData = eventsRes[channel];
|
|
357
|
-
if (cameraEventsData && targetCamera) {
|
|
358
|
-
targetCamera.processEvents(cameraEventsData);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
259
|
async init() {
|
|
364
|
-
const logger = this.getBaichuanLogger();
|
|
365
260
|
await this.ensureBaichuanClient();
|
|
261
|
+
await this.subscribeToEvents();
|
|
262
|
+
await this.discoverDevices(true);
|
|
366
263
|
|
|
367
264
|
await this.updateDeviceInfo();
|
|
368
|
-
|
|
369
|
-
await this.reinitEventSubscriptions();
|
|
370
|
-
|
|
371
|
-
setInterval(async () => {
|
|
372
|
-
if (this.processing) {
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
this.processing = true;
|
|
376
|
-
try {
|
|
377
|
-
const now = Date.now();
|
|
378
|
-
|
|
379
|
-
if (!this.lastErrorsCheck || (now - this.lastErrorsCheck > 60 * 1000)) {
|
|
380
|
-
this.lastErrorsCheck = now;
|
|
381
|
-
// Note: ReolinkCgiApi doesn't have checkErrors, skip for now
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (!this.lastNvrInfoCheck || now - this.lastNvrInfoCheck > 1000 * 60 * 5) {
|
|
385
|
-
this.lastNvrInfoCheck = now;
|
|
386
|
-
// const { nvrData } = await api.getNvrInfo();
|
|
387
|
-
// const { devicesData, channelsResponse, response } = await api.getDevicesInfo();
|
|
388
|
-
// logger.log(`NVR info data fetched`);
|
|
389
|
-
// logger.debug(`${JSON.stringify({ nvrData, devicesData, channelsResponse, response })}`);
|
|
390
|
-
|
|
391
|
-
await this.discoverDevices(true);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const api = await this.ensureBaichuanClient();
|
|
395
|
-
|
|
396
|
-
const { eventSource } = this.storageSettings.values;
|
|
397
|
-
|
|
398
|
-
if (eventSource === 'CGI') {
|
|
399
|
-
const eventsRes = await api.getAllChannelsEvents();
|
|
400
|
-
this.forwardCgiEvents(eventsRes.parsed);
|
|
401
|
-
|
|
402
|
-
const { batteryInfoData, response } = await api.getAllChannelsBatteryInfo();
|
|
403
|
-
|
|
404
|
-
logger.debug(`Battery info call result: ${JSON.stringify({ batteryInfoData, response })}`);
|
|
405
|
-
|
|
406
|
-
this.cameraNativeMap.forEach((camera) => {
|
|
407
|
-
if (camera) {
|
|
408
|
-
const channel = camera.storageSettings.values.rtspChannel;
|
|
409
|
-
const cameraBatteryData = batteryInfoData[channel];
|
|
410
|
-
if (cameraBatteryData) {
|
|
411
|
-
(camera as ReolinkNativeBatteryCamera).updateSleepingState({
|
|
412
|
-
reason: 'NVR',
|
|
413
|
-
state: cameraBatteryData.sleeping ? 'sleeping' : 'awake',
|
|
414
|
-
idleMs: 0,
|
|
415
|
-
lastRxAtMs: 0,
|
|
416
|
-
}).catch(() => { });
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
} catch (e) {
|
|
422
|
-
logger.error('Error on events flow', e);
|
|
423
|
-
} finally {
|
|
424
|
-
this.processing = false;
|
|
425
|
-
}
|
|
426
|
-
}, 1000);
|
|
427
265
|
}
|
|
428
266
|
|
|
429
267
|
async updateDeviceInfo(): Promise<void> {
|
|
@@ -441,7 +279,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
441
279
|
logger
|
|
442
280
|
});
|
|
443
281
|
} catch (e) {
|
|
444
|
-
logger.warn('Failed to fetch device info', e);
|
|
282
|
+
logger.warn('Failed to fetch device info', e?.message || String(e));
|
|
445
283
|
}
|
|
446
284
|
}
|
|
447
285
|
|
|
@@ -458,27 +296,38 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
458
296
|
this.cameraNativeMap.delete(nativeId);
|
|
459
297
|
}
|
|
460
298
|
|
|
461
|
-
async getDevice(nativeId: string): Promise<
|
|
299
|
+
async getDevice(nativeId: string): Promise<ReolinkCamera> {
|
|
462
300
|
let device = this.cameraNativeMap.get(nativeId);
|
|
463
301
|
|
|
464
302
|
if (!device) {
|
|
465
|
-
if (nativeId.endsWith(
|
|
466
|
-
device = new
|
|
303
|
+
if (nativeId.endsWith(batteryCameraSuffix)) {
|
|
304
|
+
device = new ReolinkCamera(nativeId, this.plugin, { type: 'battery', nvrDevice: this });
|
|
305
|
+
} else if (nativeId.endsWith(batteryMultifocalSuffix)) {
|
|
306
|
+
device = new ReolinkNativeMultiFocalDevice(nativeId, this.plugin, "multi-focal-battery", this);
|
|
307
|
+
} else if (nativeId.endsWith(multifocalSuffix)) {
|
|
308
|
+
device = new ReolinkNativeMultiFocalDevice(nativeId, this.plugin, "multi-focal", this);
|
|
467
309
|
} else {
|
|
468
|
-
device = new
|
|
310
|
+
device = new ReolinkCamera(nativeId, this.plugin, { type: 'regular', nvrDevice: this });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (device) {
|
|
314
|
+
this.cameraNativeMap.set(nativeId, device);
|
|
469
315
|
}
|
|
470
|
-
this.cameraNativeMap.set(nativeId, device);
|
|
471
316
|
}
|
|
472
317
|
|
|
473
318
|
return device;
|
|
474
319
|
}
|
|
475
320
|
|
|
476
|
-
buildNativeId(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
321
|
+
buildNativeId(props: {
|
|
322
|
+
identifier?: string, isBattery?: boolean, isMultifocal?: boolean
|
|
323
|
+
}): string {
|
|
324
|
+
const { identifier, isBattery, isMultifocal } = props;
|
|
325
|
+
|
|
326
|
+
const suffix = isBattery ?
|
|
327
|
+
(isMultifocal ? batteryMultifocalSuffix : batteryCameraSuffix) :
|
|
328
|
+
(isMultifocal ? multifocalSuffix : cameraSuffix)
|
|
329
|
+
|
|
330
|
+
return `${this.nativeId}-${identifier}${suffix}`;
|
|
482
331
|
}
|
|
483
332
|
|
|
484
333
|
getCameraInterfaces() {
|
|
@@ -495,23 +344,31 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
495
344
|
|
|
496
345
|
async syncEntitiesFromRemote() {
|
|
497
346
|
const logger = this.getBaichuanLogger();
|
|
347
|
+
// const { ipAddress } = this.storageSettings.values;
|
|
498
348
|
|
|
499
349
|
const api = await this.ensureBaichuanClient();
|
|
500
|
-
const { devices, channels } = await api.
|
|
501
|
-
logger.log(devices, channels);
|
|
350
|
+
const { devices, channels } = await api.getNvrChannelsSummary({ source: "cgi" });
|
|
502
351
|
|
|
503
352
|
if (!channels.length) {
|
|
504
353
|
logger.debug(`No channels found, ${JSON.stringify({ channels, devices })}`);
|
|
354
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
355
|
+
await this.syncEntitiesFromRemote();
|
|
505
356
|
return;
|
|
506
357
|
}
|
|
507
358
|
|
|
508
359
|
logger.log(`Sync entities from remote for ${channels.length} channels`);
|
|
509
360
|
|
|
510
361
|
for (const deviceData of devices) {
|
|
511
|
-
const { isBattery, serialNumber, name, model, isDoorbell, uid, channel } = deviceData
|
|
362
|
+
const { isBattery, serialNumber, name, model, isDoorbell, uid, channel, isMultifocal } = deviceData;
|
|
363
|
+
const identifier = uid || name || `channel-${channel}`;
|
|
364
|
+
// const identifier = uid || mac || (ip !== ipAddress ? ip : undefined) || name || randomBytes(4).toString('hex');
|
|
512
365
|
|
|
513
366
|
try {
|
|
514
|
-
const nativeId = this.buildNativeId(
|
|
367
|
+
const nativeId = this.buildNativeId({
|
|
368
|
+
isBattery,
|
|
369
|
+
isMultifocal,
|
|
370
|
+
identifier,
|
|
371
|
+
});
|
|
515
372
|
const interfaces = [ScryptedInterface.VideoCamera];
|
|
516
373
|
if (isBattery) {
|
|
517
374
|
interfaces.push(ScryptedInterface.Battery);
|
|
@@ -527,7 +384,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
527
384
|
info: {
|
|
528
385
|
manufacturer: 'Reolink',
|
|
529
386
|
model,
|
|
530
|
-
serialNumber
|
|
387
|
+
serialNumber,
|
|
531
388
|
}
|
|
532
389
|
};
|
|
533
390
|
|
|
@@ -538,7 +395,10 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
538
395
|
if (
|
|
539
396
|
allNativeIds.some(
|
|
540
397
|
nid => nid.includes(uid) ||
|
|
541
|
-
nid.includes(
|
|
398
|
+
nid.includes(`channel-${channel}`) ||
|
|
399
|
+
// nid.includes(mac) ||
|
|
400
|
+
// nid.includes(ip) ||
|
|
401
|
+
nid.includes(name) ||
|
|
542
402
|
nid === nativeId)
|
|
543
403
|
) {
|
|
544
404
|
continue;
|
|
@@ -601,29 +461,28 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
601
461
|
|
|
602
462
|
await this.onDeviceEvent(ScryptedInterface.DeviceDiscovery, await this.discoverDevices());
|
|
603
463
|
|
|
604
|
-
const isBattery = entry.device.interfaces.includes(ScryptedInterface.Battery);
|
|
605
464
|
const { uid } = entry.deviceData;
|
|
606
465
|
|
|
607
466
|
const { ReolinkBaichuanApi } = await import("@apocaliss92/reolink-baichuan-js");
|
|
608
467
|
const transport = 'tcp';
|
|
609
|
-
const normalizedUid = isBattery && uid ? normalizeUid(uid) : undefined;
|
|
610
468
|
const baichuanApi = new ReolinkBaichuanApi({
|
|
611
469
|
host: this.storageSettings.values.ipAddress,
|
|
612
470
|
username: this.storageSettings.values.username,
|
|
613
471
|
password: this.storageSettings.values.password,
|
|
614
472
|
transport,
|
|
615
473
|
channel: entry.rtspChannel,
|
|
616
|
-
|
|
474
|
+
uid,
|
|
617
475
|
});
|
|
618
476
|
await baichuanApi.login();
|
|
619
477
|
const { capabilities, objects, presets } = await baichuanApi.getDeviceCapabilities(entry.rtspChannel);
|
|
620
478
|
const { interfaces, type } = getDeviceInterfaces({
|
|
621
479
|
capabilities,
|
|
622
|
-
logger: this.
|
|
480
|
+
logger: this.getBaichuanLogger(),
|
|
623
481
|
});
|
|
624
482
|
|
|
625
483
|
const actualDevice: Device = {
|
|
626
484
|
...entry.device,
|
|
485
|
+
providerNativeId: this.nativeId,
|
|
627
486
|
interfaces,
|
|
628
487
|
type
|
|
629
488
|
};
|
|
@@ -632,8 +491,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
632
491
|
|
|
633
492
|
const device = await this.getDevice(adopt.nativeId);
|
|
634
493
|
const logger = this.getBaichuanLogger();
|
|
635
|
-
logger.log('Adopted device', device?.name);
|
|
636
|
-
logger.log(JSON.stringify(entry));
|
|
494
|
+
logger.log('Adopted device', device?.name, JSON.stringify(actualDevice));
|
|
637
495
|
const { username, password, ipAddress } = this.storageSettings.values;
|
|
638
496
|
|
|
639
497
|
device.storageSettings.values.rtspChannel = entry.rtspChannel;
|
package/src/presets.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PtzPreset } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
|
-
import type {
|
|
2
|
+
import type { ReolinkCamera } from "./camera";
|
|
3
3
|
|
|
4
4
|
export type PtzCapabilitiesShape = {
|
|
5
5
|
presets?: Record<string, string>;
|
|
@@ -7,7 +7,7 @@ export type PtzCapabilitiesShape = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export class ReolinkPtzPresets {
|
|
10
|
-
constructor(private camera:
|
|
10
|
+
constructor(private camera: ReolinkCamera & { ptzCapabilities?: any }) { }
|
|
11
11
|
|
|
12
12
|
private get storageSettings() {
|
|
13
13
|
return this.camera.storageSettings;
|