@apocaliss92/scrypted-reolink-native 0.1.42 → 0.2.0
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-battery.ts +5 -58
- package/src/camera.ts +5 -2
- package/src/common.ts +471 -240
- package/src/intercom.ts +3 -3
- package/src/main.ts +38 -21
- package/src/multiFocal.ts +238 -144
- package/src/nvr.ts +194 -160
- package/src/stream-utils.ts +232 -101
- package/src/utils.ts +19 -19
package/src/nvr.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type { EventsResponse, ReolinkBaichuanApi, ReolinkBaichuanDeviceSummary, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
1
|
+
import type { EventsResponse, NativeVideoStreamVariant, ReolinkBaichuanApi, ReolinkBaichuanDeviceSummary, ReolinkSimpleEvent, StreamProfile } 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
5
|
import { ReolinkNativeCamera } from "./camera";
|
|
6
6
|
import { ReolinkNativeBatteryCamera } from "./camera-battery";
|
|
7
|
-
import {
|
|
7
|
+
import { CommonCameraMixin } from "./common";
|
|
8
8
|
import { convertDebugLogsToApiOptions, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
|
|
9
9
|
import ReolinkNativePlugin from "./main";
|
|
10
|
-
import {
|
|
10
|
+
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
11
|
+
import { batteryCameraSuffix, batteryMultifocalSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, updateDeviceInfo } from "./utils";
|
|
12
|
+
import { createBaichuanApi } from "./connect";
|
|
13
|
+
import { parseStreamProfileFromId } from "./stream-utils";
|
|
11
14
|
|
|
12
15
|
export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Settings, DeviceDiscovery, DeviceProvider, Reboot {
|
|
13
16
|
storageSettings = new StorageSettings(this, {
|
|
@@ -16,17 +19,17 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
16
19
|
type: 'boolean',
|
|
17
20
|
immediate: true,
|
|
18
21
|
},
|
|
19
|
-
eventSource: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
22
|
+
// eventSource: {
|
|
23
|
+
// title: 'Event Source',
|
|
24
|
+
// description: 'Select the source for camera events: Native (Baichuan) or CGI (HTTP polling)',
|
|
25
|
+
// type: 'string',
|
|
26
|
+
// choices: ['Native', 'CGI'],
|
|
27
|
+
// defaultValue: 'Native',
|
|
28
|
+
// immediate: true,
|
|
29
|
+
// onPut: async () => {
|
|
30
|
+
// await this.reinitEventSubscriptions();
|
|
31
|
+
// }
|
|
32
|
+
// },
|
|
30
33
|
ipAddress: {
|
|
31
34
|
title: 'IP address',
|
|
32
35
|
type: 'string',
|
|
@@ -89,7 +92,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
89
92
|
// Trigger reconnection
|
|
90
93
|
await this.ensureBaichuanClient();
|
|
91
94
|
} catch (e) {
|
|
92
|
-
logger.warn('Failed to reset client after debug logs change', e);
|
|
95
|
+
logger.warn('Failed to reset client after debug logs change', e?.message || String(e));
|
|
93
96
|
}
|
|
94
97
|
}, 2000);
|
|
95
98
|
}
|
|
@@ -103,10 +106,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
103
106
|
rtspChannel: number;
|
|
104
107
|
deviceData: ReolinkBaichuanDeviceSummary;
|
|
105
108
|
}>();
|
|
106
|
-
|
|
107
|
-
lastErrorsCheck: number | undefined;
|
|
108
|
-
lastDevicesStatusCheck: number | undefined;
|
|
109
|
-
cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
|
|
109
|
+
cameraNativeMap = new Map<string, CommonCameraMixin>();
|
|
110
110
|
private channelToNativeIdMap = new Map<number, string>();
|
|
111
111
|
private discoverDevicesPromise: Promise<DiscoveredDevice[]> | undefined;
|
|
112
112
|
processing = false;
|
|
@@ -114,7 +114,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
114
114
|
private debugLogsResetTimeout: NodeJS.Timeout | undefined;
|
|
115
115
|
|
|
116
116
|
constructor(nativeId: string, plugin: ReolinkNativePlugin) {
|
|
117
|
-
super(nativeId);
|
|
117
|
+
super(nativeId, "tcp");
|
|
118
118
|
this.plugin = plugin;
|
|
119
119
|
|
|
120
120
|
this.scheduleInit();
|
|
@@ -142,6 +142,23 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
protected getStreamClientInputs(): BaichuanConnectionConfig {
|
|
146
|
+
const { ipAddress, username, password } = this.storageSettings.values;
|
|
147
|
+
if (!ipAddress || !username || !password) {
|
|
148
|
+
throw new Error('Missing NVR credentials');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const debugOptions = this.getBaichuanDebugOptions();
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
host: ipAddress,
|
|
155
|
+
username,
|
|
156
|
+
password,
|
|
157
|
+
transport: 'tcp',
|
|
158
|
+
debugOptions,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
145
162
|
getBaichuanDebugOptions(): any | undefined {
|
|
146
163
|
const socketDebugLogs = this.storageSettings.values.socketApiDebugLogs || [];
|
|
147
164
|
return convertDebugLogsToApiOptions(socketDebugLogs);
|
|
@@ -153,14 +170,14 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
153
170
|
await this.reinit();
|
|
154
171
|
},
|
|
155
172
|
onSimpleEvent: (ev) => this.forwardNativeEvent(ev),
|
|
156
|
-
getEventSubscriptionEnabled: () =>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
getEventSubscriptionEnabled: () => true,
|
|
174
|
+
// getEventSubscriptionEnabled: () => {
|
|
175
|
+
// const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
176
|
+
// return eventSource === 'Native';
|
|
177
|
+
// },
|
|
160
178
|
};
|
|
161
179
|
}
|
|
162
180
|
|
|
163
|
-
|
|
164
181
|
protected isDebugEnabled(): boolean {
|
|
165
182
|
return this.storageSettings.values.debugLogs || false;
|
|
166
183
|
}
|
|
@@ -198,18 +215,24 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
198
215
|
}, isReinit ? 500 : 2000);
|
|
199
216
|
}
|
|
200
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Forward events received from Baichuan to the appropriate child device (Camera or MultiFocal).
|
|
220
|
+
* This ensures that only NVR (root device) subscribes to events, and events are forwarded down the hierarchy:
|
|
221
|
+
* - NVR → MultiFocal → Camera
|
|
222
|
+
* - NVR → Camera (directly)
|
|
223
|
+
*/
|
|
201
224
|
private forwardNativeEvent(ev: ReolinkSimpleEvent): void {
|
|
202
225
|
const logger = this.getBaichuanLogger();
|
|
203
226
|
|
|
204
|
-
const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
205
|
-
if (eventSource !== 'Native') {
|
|
206
|
-
|
|
207
|
-
}
|
|
227
|
+
// const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
228
|
+
// if (eventSource !== 'Native') {
|
|
229
|
+
// return;
|
|
230
|
+
// }
|
|
208
231
|
|
|
209
232
|
try {
|
|
210
233
|
logger.debug(`Baichuan event: ${JSON.stringify(ev)}`);
|
|
211
234
|
|
|
212
|
-
// Find camera for this channel
|
|
235
|
+
// Find device (camera or multifocal) for this channel
|
|
213
236
|
const channel = ev?.channel;
|
|
214
237
|
if (channel === undefined) {
|
|
215
238
|
logger.error('Event has no channel, ignoring');
|
|
@@ -217,10 +240,16 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
217
240
|
}
|
|
218
241
|
|
|
219
242
|
const nativeId = this.channelToNativeIdMap.get(channel);
|
|
220
|
-
const
|
|
243
|
+
const targetDevice = nativeId ? this.cameraNativeMap.get(nativeId) : undefined;
|
|
221
244
|
|
|
222
|
-
if (!
|
|
223
|
-
logger.debug(`No
|
|
245
|
+
if (!targetDevice) {
|
|
246
|
+
logger.debug(`No device found for channel ${channel} (nativeId: ${nativeId}), ignoring event`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// If target is a MultiFocal device, forward the event to it (it will forward to its camera children)
|
|
251
|
+
if (targetDevice instanceof ReolinkNativeMultiFocalDevice) {
|
|
252
|
+
targetDevice.forwardNativeEvent(ev);
|
|
224
253
|
return;
|
|
225
254
|
}
|
|
226
255
|
|
|
@@ -237,10 +266,10 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
237
266
|
case 'doorbell':
|
|
238
267
|
// Handle doorbell if camera supports it
|
|
239
268
|
try {
|
|
240
|
-
|
|
269
|
+
targetDevice.handleDoorbellEvent();
|
|
241
270
|
}
|
|
242
271
|
catch (e) {
|
|
243
|
-
logger.warn(`Error handling doorbell event for camera channel ${channel}`, e);
|
|
272
|
+
logger.warn(`Error handling doorbell event for camera channel ${channel}`, e?.message || String(e));
|
|
244
273
|
}
|
|
245
274
|
motion = true;
|
|
246
275
|
break;
|
|
@@ -267,41 +296,44 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
267
296
|
}
|
|
268
297
|
|
|
269
298
|
if (isSleepingEvent) {
|
|
270
|
-
|
|
299
|
+
targetDevice.updateSleepingState({
|
|
271
300
|
reason: 'NVR',
|
|
272
301
|
state: ev.type === 'sleeping' ? 'sleeping' : 'awake',
|
|
273
302
|
}).catch(() => { });
|
|
274
|
-
} if (
|
|
275
|
-
(
|
|
303
|
+
} else if (isOnlineEvent) {
|
|
304
|
+
(targetDevice as ReolinkNativeBatteryCamera).updateOnlineState(
|
|
276
305
|
ev.type === 'online' ? true : false
|
|
277
306
|
).catch(() => { });
|
|
278
307
|
} else {
|
|
279
308
|
// Process events on the target camera
|
|
280
|
-
|
|
281
|
-
logger.warn(`Error processing events for camera channel ${channel}`, e);
|
|
309
|
+
targetDevice.processEvents({ motion, objects }).catch((e) => {
|
|
310
|
+
logger.warn(`Error processing events for camera channel ${channel}`, e?.message || String(e));
|
|
282
311
|
});
|
|
283
312
|
}
|
|
284
313
|
}
|
|
285
314
|
catch (e) {
|
|
286
|
-
logger.warn('Error in NVR Native event forwarder', e);
|
|
315
|
+
logger.warn('Error in NVR Native event forwarder', e?.message || String(e));
|
|
287
316
|
}
|
|
288
317
|
}
|
|
289
318
|
|
|
290
319
|
async ensureBaichuanClient(): Promise<ReolinkBaichuanApi> {
|
|
291
|
-
// Use base class implementation
|
|
292
320
|
return await super.ensureBaichuanClient();
|
|
293
321
|
}
|
|
294
322
|
|
|
295
|
-
async
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (eventSource !== 'Native') {
|
|
299
|
-
await this.unsubscribeFromAllEvents();
|
|
300
|
-
} else {
|
|
301
|
-
await super.subscribeToEvents();
|
|
302
|
-
}
|
|
323
|
+
async ensureClient(): Promise<ReolinkBaichuanApi> {
|
|
324
|
+
return await this.ensureBaichuanClient();
|
|
303
325
|
}
|
|
304
326
|
|
|
327
|
+
// async subscribeToAllEvents(): Promise<void> {
|
|
328
|
+
// const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
329
|
+
|
|
330
|
+
// if (eventSource !== 'Native') {
|
|
331
|
+
// await this.unsubscribeFromAllEvents();
|
|
332
|
+
// } else {
|
|
333
|
+
// await super.subscribeToEvents();
|
|
334
|
+
// }
|
|
335
|
+
// }
|
|
336
|
+
|
|
305
337
|
private async runNvrDiagnostics(): Promise<void> {
|
|
306
338
|
const logger = this.getBaichuanLogger();
|
|
307
339
|
logger.log(`Starting NVR diagnostics...`);
|
|
@@ -313,7 +345,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
313
345
|
logger: this.console,
|
|
314
346
|
});
|
|
315
347
|
} catch (e) {
|
|
316
|
-
logger.error('Failed to run NVR diagnostics', e);
|
|
348
|
+
logger.error('Failed to run NVR diagnostics', e?.message || String(e));
|
|
317
349
|
throw e;
|
|
318
350
|
}
|
|
319
351
|
}
|
|
@@ -326,104 +358,88 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
326
358
|
/**
|
|
327
359
|
* Reinitialize event subscriptions based on selected event source
|
|
328
360
|
*/
|
|
329
|
-
private async reinitEventSubscriptions(): Promise<void> {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
361
|
+
// private async reinitEventSubscriptions(): Promise<void> {
|
|
362
|
+
// const logger = this.getBaichuanLogger();
|
|
363
|
+
// const { eventSource } = this.storageSettings.values;
|
|
364
|
+
|
|
365
|
+
// // Unsubscribe from Native events if switching away
|
|
366
|
+
// if (eventSource !== 'Native') {
|
|
367
|
+
// await this.unsubscribeFromAllEvents();
|
|
368
|
+
// } else {
|
|
369
|
+
// this.subscribeToAllEvents().catch((e) => {
|
|
370
|
+
// logger.warn('Failed to subscribe to Native events', e?.message || String(e));
|
|
371
|
+
// });
|
|
372
|
+
// }
|
|
373
|
+
|
|
374
|
+
// logger.log(`Event source set to: ${eventSource}`);
|
|
375
|
+
// }
|
|
344
376
|
|
|
345
377
|
/**
|
|
346
378
|
* Forward events from CGI source to cameras
|
|
347
379
|
*/
|
|
348
|
-
private forwardCgiEvents(eventsRes: Record<number, EventsResponse>): void {
|
|
349
|
-
|
|
380
|
+
// private forwardCgiEvents(eventsRes: Record<number, EventsResponse>): void {
|
|
381
|
+
// const logger = this.getBaichuanLogger();
|
|
350
382
|
|
|
351
|
-
|
|
383
|
+
// logger.debug(`CGI Events call result: ${JSON.stringify(eventsRes)}`);
|
|
352
384
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
385
|
+
// // Use channel map for efficient lookup
|
|
386
|
+
// for (const [channel, nativeId] of this.channelToNativeIdMap.entries()) {
|
|
387
|
+
// const targetCamera = nativeId ? this.cameraNativeMap.get(nativeId) : undefined;
|
|
388
|
+
// const cameraEventsData = eventsRes[channel];
|
|
389
|
+
// if (cameraEventsData && targetCamera) {
|
|
390
|
+
// targetCamera.processEvents(cameraEventsData);
|
|
391
|
+
// }
|
|
392
|
+
// }
|
|
393
|
+
// }
|
|
362
394
|
|
|
363
395
|
async init() {
|
|
364
|
-
const logger = this.getBaichuanLogger();
|
|
396
|
+
// const logger = this.getBaichuanLogger();
|
|
365
397
|
await this.ensureBaichuanClient();
|
|
366
398
|
|
|
367
399
|
await this.updateDeviceInfo();
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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);
|
|
400
|
+
await this.subscribeToEvents();
|
|
401
|
+
|
|
402
|
+
// await this.reinitEventSubscriptions();
|
|
403
|
+
|
|
404
|
+
// setInterval(async () => {
|
|
405
|
+
// if (this.processing) {
|
|
406
|
+
// return;
|
|
407
|
+
// }
|
|
408
|
+
// this.processing = true;
|
|
409
|
+
// try {
|
|
410
|
+
// const api = await this.ensureBaichuanClient();
|
|
411
|
+
|
|
412
|
+
// const { eventSource } = this.storageSettings.values;
|
|
413
|
+
|
|
414
|
+
// if (eventSource === 'CGI') {
|
|
415
|
+
// const eventsRes = await api.getAllChannelsEvents();
|
|
416
|
+
// this.forwardCgiEvents(eventsRes.parsed);
|
|
417
|
+
|
|
418
|
+
// const { batteryInfoData, response } = await api.getAllChannelsBatteryInfo();
|
|
419
|
+
|
|
420
|
+
// logger.debug(`Battery info call result: ${JSON.stringify({ batteryInfoData, response })}`);
|
|
421
|
+
|
|
422
|
+
// this.cameraNativeMap.forEach((camera) => {
|
|
423
|
+
// if (camera) {
|
|
424
|
+
// const channel = camera.storageSettings.values.rtspChannel;
|
|
425
|
+
// const cameraBatteryData = batteryInfoData[channel];
|
|
426
|
+
// if (cameraBatteryData) {
|
|
427
|
+
// camera.updateSleepingState({
|
|
428
|
+
// reason: 'NVR',
|
|
429
|
+
// state: cameraBatteryData.sleeping ? 'sleeping' : 'awake',
|
|
430
|
+
// idleMs: 0,
|
|
431
|
+
// lastRxAtMs: 0,
|
|
432
|
+
// }).catch(() => { });
|
|
433
|
+
// }
|
|
434
|
+
// }
|
|
435
|
+
// });
|
|
436
|
+
// }
|
|
437
|
+
// } catch (e) {
|
|
438
|
+
// logger.error('Error on events flow', e?.message || String(e));
|
|
439
|
+
// } finally {
|
|
440
|
+
// this.processing = false;
|
|
441
|
+
// }
|
|
442
|
+
// }, 1000);
|
|
427
443
|
}
|
|
428
444
|
|
|
429
445
|
async updateDeviceInfo(): Promise<void> {
|
|
@@ -441,7 +457,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
441
457
|
logger
|
|
442
458
|
});
|
|
443
459
|
} catch (e) {
|
|
444
|
-
logger.warn('Failed to fetch device info', e);
|
|
460
|
+
logger.warn('Failed to fetch device info', e?.message || String(e));
|
|
445
461
|
}
|
|
446
462
|
}
|
|
447
463
|
|
|
@@ -458,27 +474,38 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
458
474
|
this.cameraNativeMap.delete(nativeId);
|
|
459
475
|
}
|
|
460
476
|
|
|
461
|
-
async getDevice(nativeId: string): Promise<
|
|
477
|
+
async getDevice(nativeId: string): Promise<CommonCameraMixin> {
|
|
462
478
|
let device = this.cameraNativeMap.get(nativeId);
|
|
463
479
|
|
|
464
480
|
if (!device) {
|
|
465
|
-
if (nativeId.endsWith(
|
|
481
|
+
if (nativeId.endsWith(batteryCameraSuffix)) {
|
|
466
482
|
device = new ReolinkNativeBatteryCamera(nativeId, this.plugin, this);
|
|
483
|
+
} else if (nativeId.endsWith(batteryMultifocalSuffix)) {
|
|
484
|
+
device = new ReolinkNativeMultiFocalDevice(nativeId, this.plugin, "multi-focal-battery", this);
|
|
485
|
+
} else if (nativeId.endsWith(multifocalSuffix)) {
|
|
486
|
+
device = new ReolinkNativeMultiFocalDevice(nativeId, this.plugin, "multi-focal", this);
|
|
467
487
|
} else {
|
|
468
488
|
device = new ReolinkNativeCamera(nativeId, this.plugin, this);
|
|
469
489
|
}
|
|
470
|
-
|
|
490
|
+
|
|
491
|
+
if (device) {
|
|
492
|
+
this.cameraNativeMap.set(nativeId, device);
|
|
493
|
+
}
|
|
471
494
|
}
|
|
472
495
|
|
|
473
496
|
return device;
|
|
474
497
|
}
|
|
475
498
|
|
|
476
|
-
buildNativeId(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
499
|
+
buildNativeId(props: {
|
|
500
|
+
identifier?: string, isBattery?: boolean, isMultifocal?: boolean
|
|
501
|
+
}): string {
|
|
502
|
+
const { identifier, isBattery, isMultifocal } = props;
|
|
503
|
+
|
|
504
|
+
const suffix = isBattery ?
|
|
505
|
+
(isMultifocal ? batteryMultifocalSuffix : batteryCameraSuffix) :
|
|
506
|
+
(isMultifocal ? multifocalSuffix : cameraSuffix)
|
|
507
|
+
|
|
508
|
+
return `${this.nativeId}-${identifier}${suffix}`;
|
|
482
509
|
}
|
|
483
510
|
|
|
484
511
|
getCameraInterfaces() {
|
|
@@ -495,10 +522,10 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
495
522
|
|
|
496
523
|
async syncEntitiesFromRemote() {
|
|
497
524
|
const logger = this.getBaichuanLogger();
|
|
525
|
+
// const { ipAddress } = this.storageSettings.values;
|
|
498
526
|
|
|
499
527
|
const api = await this.ensureBaichuanClient();
|
|
500
|
-
const { devices, channels } = await api.
|
|
501
|
-
logger.log(devices, channels);
|
|
528
|
+
const { devices, channels } = await api.getNvrChannelsSummary({ source: "cgi" });
|
|
502
529
|
|
|
503
530
|
if (!channels.length) {
|
|
504
531
|
logger.debug(`No channels found, ${JSON.stringify({ channels, devices })}`);
|
|
@@ -508,10 +535,16 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
508
535
|
logger.log(`Sync entities from remote for ${channels.length} channels`);
|
|
509
536
|
|
|
510
537
|
for (const deviceData of devices) {
|
|
511
|
-
const { isBattery, serialNumber, name, model, isDoorbell, uid, channel } = deviceData
|
|
538
|
+
const { isBattery, serialNumber, name, model, isDoorbell, uid, channel, isMultifocal } = deviceData;
|
|
539
|
+
const identifier = uid || name || `channel-${channel}`;
|
|
540
|
+
// const identifier = uid || mac || (ip !== ipAddress ? ip : undefined) || name || randomBytes(4).toString('hex');
|
|
512
541
|
|
|
513
542
|
try {
|
|
514
|
-
const nativeId = this.buildNativeId(
|
|
543
|
+
const nativeId = this.buildNativeId({
|
|
544
|
+
isBattery,
|
|
545
|
+
isMultifocal,
|
|
546
|
+
identifier,
|
|
547
|
+
});
|
|
515
548
|
const interfaces = [ScryptedInterface.VideoCamera];
|
|
516
549
|
if (isBattery) {
|
|
517
550
|
interfaces.push(ScryptedInterface.Battery);
|
|
@@ -527,7 +560,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
527
560
|
info: {
|
|
528
561
|
manufacturer: 'Reolink',
|
|
529
562
|
model,
|
|
530
|
-
serialNumber
|
|
563
|
+
serialNumber,
|
|
531
564
|
}
|
|
532
565
|
};
|
|
533
566
|
|
|
@@ -538,7 +571,10 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
538
571
|
if (
|
|
539
572
|
allNativeIds.some(
|
|
540
573
|
nid => nid.includes(uid) ||
|
|
541
|
-
nid.includes(
|
|
574
|
+
nid.includes(`channel-${channel}`) ||
|
|
575
|
+
// nid.includes(mac) ||
|
|
576
|
+
// nid.includes(ip) ||
|
|
577
|
+
nid.includes(name) ||
|
|
542
578
|
nid === nativeId)
|
|
543
579
|
) {
|
|
544
580
|
continue;
|
|
@@ -601,29 +637,28 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
601
637
|
|
|
602
638
|
await this.onDeviceEvent(ScryptedInterface.DeviceDiscovery, await this.discoverDevices());
|
|
603
639
|
|
|
604
|
-
const isBattery = entry.device.interfaces.includes(ScryptedInterface.Battery);
|
|
605
640
|
const { uid } = entry.deviceData;
|
|
606
641
|
|
|
607
642
|
const { ReolinkBaichuanApi } = await import("@apocaliss92/reolink-baichuan-js");
|
|
608
643
|
const transport = 'tcp';
|
|
609
|
-
const normalizedUid = isBattery && uid ? normalizeUid(uid) : undefined;
|
|
610
644
|
const baichuanApi = new ReolinkBaichuanApi({
|
|
611
645
|
host: this.storageSettings.values.ipAddress,
|
|
612
646
|
username: this.storageSettings.values.username,
|
|
613
647
|
password: this.storageSettings.values.password,
|
|
614
648
|
transport,
|
|
615
649
|
channel: entry.rtspChannel,
|
|
616
|
-
|
|
650
|
+
uid,
|
|
617
651
|
});
|
|
618
652
|
await baichuanApi.login();
|
|
619
653
|
const { capabilities, objects, presets } = await baichuanApi.getDeviceCapabilities(entry.rtspChannel);
|
|
620
654
|
const { interfaces, type } = getDeviceInterfaces({
|
|
621
655
|
capabilities,
|
|
622
|
-
logger: this.
|
|
656
|
+
logger: this.getBaichuanLogger(),
|
|
623
657
|
});
|
|
624
658
|
|
|
625
659
|
const actualDevice: Device = {
|
|
626
660
|
...entry.device,
|
|
661
|
+
providerNativeId: this.nativeId,
|
|
627
662
|
interfaces,
|
|
628
663
|
type
|
|
629
664
|
};
|
|
@@ -632,8 +667,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
632
667
|
|
|
633
668
|
const device = await this.getDevice(adopt.nativeId);
|
|
634
669
|
const logger = this.getBaichuanLogger();
|
|
635
|
-
logger.log('Adopted device', device?.name);
|
|
636
|
-
logger.log(JSON.stringify(entry));
|
|
670
|
+
logger.log('Adopted device', device?.name, JSON.stringify(actualDevice));
|
|
637
671
|
const { username, password, ipAddress } = this.storageSettings.values;
|
|
638
672
|
|
|
639
673
|
device.storageSettings.values.rtspChannel = entry.rtspChannel;
|