@apocaliss92/scrypted-reolink-native 0.1.37 → 0.1.39
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/README.md +1 -1
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/camera-battery.ts +16 -1
- package/src/common.ts +4 -3
- package/src/connect.ts +1 -1
- package/src/nvr.ts +70 -97
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/camera-battery.ts
CHANGED
|
@@ -128,7 +128,22 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
128
128
|
}
|
|
129
129
|
} catch (e) {
|
|
130
130
|
// Silently ignore errors in sleep check to avoid spam
|
|
131
|
-
this.getBaichuanLogger().debug('Error in
|
|
131
|
+
this.getBaichuanLogger().debug('Error in updateSleepingState:', e);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async updateOnlineState(isOnline: boolean): Promise<void> {
|
|
136
|
+
try {
|
|
137
|
+
if (this.isDebugEnabled()) {
|
|
138
|
+
this.getBaichuanLogger().debug('updateOnlineState result:', isOnline);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (isOnline !== this.online) {
|
|
142
|
+
this.online = isOnline;
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
// Silently ignore errors in sleep check to avoid spam
|
|
146
|
+
this.getBaichuanLogger().debug('Error in updateOnlineState:', e);
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
|
package/src/common.ts
CHANGED
|
@@ -304,8 +304,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
304
304
|
title: 'Discovery Method',
|
|
305
305
|
description: 'UDP discovery method for battery cameras (BCUDP).',
|
|
306
306
|
type: 'string',
|
|
307
|
-
choices: ['local', 'remote', 'map', 'relay'],
|
|
308
|
-
defaultValue: 'local',
|
|
307
|
+
choices: ['local-direct', 'local-broadcast', 'remote', 'map', 'relay'],
|
|
308
|
+
defaultValue: 'local-direct',
|
|
309
309
|
hide: true,
|
|
310
310
|
onPut: async () => {
|
|
311
311
|
await this.credentialsChanged();
|
|
@@ -761,6 +761,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
761
761
|
start,
|
|
762
762
|
end,
|
|
763
763
|
streamType: "main",
|
|
764
|
+
source: "baichuan"
|
|
764
765
|
});
|
|
765
766
|
|
|
766
767
|
logger.debug(`[NVR VOD] Found ${enrichedRecordings.length} enriched recordings from NVR`);
|
|
@@ -2478,7 +2479,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
2478
2479
|
this.storageSettings.settings.teleChannel.hide = !this.isMultiFocal;
|
|
2479
2480
|
|
|
2480
2481
|
this.storageSettings.settings.uid.hide = !this.isBattery;
|
|
2481
|
-
this.storageSettings.settings.discoveryMethod.hide = !this.isBattery;
|
|
2482
|
+
this.storageSettings.settings.discoveryMethod.hide = !this.isBattery && !this.nvrDevice;
|
|
2482
2483
|
|
|
2483
2484
|
if (this.isBattery && !this.storageSettings.values.mixinsSetup) {
|
|
2484
2485
|
try {
|
package/src/connect.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type BaichuanConnectInputs = {
|
|
|
9
9
|
uid?: string;
|
|
10
10
|
logger?: Console;
|
|
11
11
|
debugOptions?: BaichuanClientOptions['debugOptions'];
|
|
12
|
-
udpDiscoveryMethod?: "
|
|
12
|
+
udpDiscoveryMethod?: BaichuanClientOptions["udpDiscoveryMethod"];
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export function normalizeUid(uid?: string): string | undefined {
|
package/src/nvr.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EventsResponse, 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
5
|
import { ReolinkNativeCamera } from "./camera";
|
|
6
6
|
import { ReolinkNativeBatteryCamera } from "./camera-battery";
|
|
7
|
-
import { convertDebugLogsToApiOptions, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
|
|
8
7
|
import { normalizeUid } from "./connect";
|
|
8
|
+
import { convertDebugLogsToApiOptions, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
|
|
9
9
|
import ReolinkNativePlugin from "./main";
|
|
10
10
|
import { getDeviceInterfaces, updateDeviceInfo } from "./utils";
|
|
11
11
|
|
|
@@ -97,13 +97,11 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
97
97
|
},
|
|
98
98
|
});
|
|
99
99
|
plugin: ReolinkNativePlugin;
|
|
100
|
-
nvrApi: ReolinkCgiApi | undefined;
|
|
101
|
-
// baichuanApi, ensureClientPromise, connectionTime inherited from BaseBaichuanClass
|
|
102
100
|
discoveredDevices = new Map<string, {
|
|
103
101
|
device: Device;
|
|
104
102
|
description: string;
|
|
105
103
|
rtspChannel: number;
|
|
106
|
-
deviceData:
|
|
104
|
+
deviceData: ReolinkBaichuanDeviceSummary;
|
|
107
105
|
}>();
|
|
108
106
|
lastNvrInfoCheck: number | undefined;
|
|
109
107
|
lastErrorsCheck: number | undefined;
|
|
@@ -151,9 +149,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
151
149
|
|
|
152
150
|
protected getConnectionCallbacks(): BaichuanConnectionCallbacks {
|
|
153
151
|
return {
|
|
154
|
-
onError: undefined, // Use default error handling
|
|
155
152
|
onClose: async () => {
|
|
156
|
-
// Reinit after cleanup
|
|
157
153
|
await this.reinit();
|
|
158
154
|
},
|
|
159
155
|
onSimpleEvent: (ev) => this.forwardNativeEvent(ev),
|
|
@@ -195,17 +191,6 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
195
191
|
|
|
196
192
|
this.initReinitTimeout = setTimeout(async () => {
|
|
197
193
|
if (isReinit) {
|
|
198
|
-
// Cleanup CGI API
|
|
199
|
-
if (this.nvrApi) {
|
|
200
|
-
try {
|
|
201
|
-
await this.nvrApi.logout();
|
|
202
|
-
} catch {
|
|
203
|
-
// ignore
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
this.nvrApi = undefined;
|
|
207
|
-
|
|
208
|
-
// Cleanup Baichuan API (this handles all listeners and connection)
|
|
209
194
|
await super.cleanupBaichuanApi();
|
|
210
195
|
}
|
|
211
196
|
await this.init();
|
|
@@ -213,29 +198,6 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
213
198
|
}, isReinit ? 500 : 2000);
|
|
214
199
|
}
|
|
215
200
|
|
|
216
|
-
async ensureClient(): Promise<ReolinkCgiApi> {
|
|
217
|
-
if (this.nvrApi) {
|
|
218
|
-
return this.nvrApi;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const { ipAddress, username, password } = this.storageSettings.values;
|
|
222
|
-
if (!ipAddress || !username || !password) {
|
|
223
|
-
throw new Error('Missing NVR credentials');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const { ReolinkCgiApi } = await import("@apocaliss92/reolink-baichuan-js");
|
|
227
|
-
const logger = this.getBaichuanLogger();
|
|
228
|
-
this.nvrApi = new ReolinkCgiApi({
|
|
229
|
-
host: ipAddress,
|
|
230
|
-
username,
|
|
231
|
-
password,
|
|
232
|
-
logger,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
await this.nvrApi.login();
|
|
236
|
-
return this.nvrApi;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
201
|
private forwardNativeEvent(ev: ReolinkSimpleEvent): void {
|
|
240
202
|
const logger = this.getBaichuanLogger();
|
|
241
203
|
|
|
@@ -258,13 +220,15 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
258
220
|
const targetCamera = nativeId ? this.cameraNativeMap.get(nativeId) : undefined;
|
|
259
221
|
|
|
260
222
|
if (!targetCamera) {
|
|
261
|
-
logger.debug(`No camera found for channel ${channel}, ignoring event`);
|
|
223
|
+
logger.debug(`No camera found for channel ${channel} (nativeId: ${nativeId}), ignoring event`);
|
|
262
224
|
return;
|
|
263
225
|
}
|
|
264
226
|
|
|
265
227
|
// Convert event to camera's processEvents format
|
|
266
228
|
const objects: string[] = [];
|
|
267
229
|
let motion = false;
|
|
230
|
+
let isSleepingEvent = false;
|
|
231
|
+
let isOnlineEvent = false;
|
|
268
232
|
|
|
269
233
|
switch (ev?.type) {
|
|
270
234
|
case 'motion':
|
|
@@ -289,15 +253,34 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
289
253
|
objects.push(ev.type);
|
|
290
254
|
motion = true;
|
|
291
255
|
break;
|
|
256
|
+
case 'awake':
|
|
257
|
+
case 'sleeping':
|
|
258
|
+
isSleepingEvent = true;
|
|
259
|
+
break;
|
|
260
|
+
case 'offline':
|
|
261
|
+
case 'online':
|
|
262
|
+
isOnlineEvent = true;
|
|
263
|
+
break;
|
|
292
264
|
default:
|
|
293
265
|
logger.error(`Unknown event type: ${ev?.type}`);
|
|
294
266
|
return;
|
|
295
267
|
}
|
|
296
268
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
}
|
|
301
284
|
}
|
|
302
285
|
catch (e) {
|
|
303
286
|
logger.warn('Error in NVR Native event forwarder', e);
|
|
@@ -324,13 +307,11 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
324
307
|
logger.log(`Starting NVR diagnostics...`);
|
|
325
308
|
|
|
326
309
|
try {
|
|
327
|
-
const
|
|
310
|
+
const api = await this.ensureBaichuanClient();
|
|
328
311
|
|
|
329
|
-
|
|
312
|
+
await api.collectNvrDiagnostics({
|
|
330
313
|
logger: this.console,
|
|
331
314
|
});
|
|
332
|
-
|
|
333
|
-
logger.log(`NVR diagnostics completed successfully.`);
|
|
334
315
|
} catch (e) {
|
|
335
316
|
logger.error('Failed to run NVR diagnostics', e);
|
|
336
317
|
throw e;
|
|
@@ -381,9 +362,6 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
381
362
|
|
|
382
363
|
async init() {
|
|
383
364
|
const logger = this.getBaichuanLogger();
|
|
384
|
-
|
|
385
|
-
// Ensure both APIs are ready before proceeding
|
|
386
|
-
const api = await this.ensureClient();
|
|
387
365
|
await this.ensureBaichuanClient();
|
|
388
366
|
|
|
389
367
|
await this.updateDeviceInfo();
|
|
@@ -391,7 +369,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
391
369
|
await this.reinitEventSubscriptions();
|
|
392
370
|
|
|
393
371
|
setInterval(async () => {
|
|
394
|
-
if (this.processing
|
|
372
|
+
if (this.processing) {
|
|
395
373
|
return;
|
|
396
374
|
}
|
|
397
375
|
this.processing = true;
|
|
@@ -405,39 +383,41 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
405
383
|
|
|
406
384
|
if (!this.lastNvrInfoCheck || now - this.lastNvrInfoCheck > 1000 * 60 * 5) {
|
|
407
385
|
this.lastNvrInfoCheck = now;
|
|
408
|
-
const { nvrData } = await api.getNvrInfo();
|
|
409
|
-
const { devicesData, channelsResponse, response } = await api.getDevicesInfo();
|
|
410
|
-
logger.log(`NVR info data fetched`);
|
|
411
|
-
logger.debug(`${JSON.stringify({ nvrData, devicesData, channelsResponse, response })}`);
|
|
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 })}`);
|
|
412
390
|
|
|
413
391
|
await this.discoverDevices(true);
|
|
414
392
|
}
|
|
415
393
|
|
|
416
|
-
|
|
394
|
+
const api = await this.ensureBaichuanClient();
|
|
395
|
+
|
|
417
396
|
const { eventSource } = this.storageSettings.values;
|
|
397
|
+
|
|
418
398
|
if (eventSource === 'CGI') {
|
|
419
399
|
const eventsRes = await api.getAllChannelsEvents();
|
|
420
400
|
this.forwardCgiEvents(eventsRes.parsed);
|
|
421
|
-
}
|
|
422
401
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
+
}
|
|
438
418
|
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
441
421
|
} catch (e) {
|
|
442
422
|
logger.error('Error on events flow', e);
|
|
443
423
|
} finally {
|
|
@@ -516,33 +496,27 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
516
496
|
async syncEntitiesFromRemote() {
|
|
517
497
|
const logger = this.getBaichuanLogger();
|
|
518
498
|
|
|
519
|
-
const
|
|
520
|
-
const {
|
|
521
|
-
|
|
522
|
-
// const api = await this.ensureBaichuanClient();
|
|
523
|
-
// const devicesMap = api.getDevicesInfo();
|
|
524
|
-
// const deviceEntries = Object.entries(devicesMap);
|
|
499
|
+
const api = await this.ensureBaichuanClient();
|
|
500
|
+
const { devices, channels } = await api.getDevicesInfo();
|
|
501
|
+
logger.log(devices, channels);
|
|
525
502
|
|
|
526
503
|
if (!channels.length) {
|
|
527
|
-
logger.debug(`No channels found, ${JSON.stringify({ channels,
|
|
504
|
+
logger.debug(`No channels found, ${JSON.stringify({ channels, devices })}`);
|
|
528
505
|
return;
|
|
529
506
|
}
|
|
530
507
|
|
|
531
508
|
logger.log(`Sync entities from remote for ${channels.length} channels`);
|
|
532
509
|
|
|
533
|
-
for (const
|
|
534
|
-
|
|
535
|
-
const { channelStatus, channelInfo, abilities } = devicesData[channel];
|
|
536
|
-
const name = channelStatus?.name;
|
|
537
|
-
const uid = channelStatus?.uid;
|
|
538
|
-
const isBattery = !!(abilities?.battery?.ver ?? 0);
|
|
510
|
+
for (const deviceData of devices) {
|
|
511
|
+
const { isBattery, name, model, isDoorbell, uid, channel } = deviceData
|
|
539
512
|
|
|
513
|
+
try {
|
|
540
514
|
const nativeId = this.buildNativeId(channel, uid, isBattery);
|
|
541
515
|
const interfaces = [ScryptedInterface.VideoCamera];
|
|
542
516
|
if (isBattery) {
|
|
543
517
|
interfaces.push(ScryptedInterface.Battery);
|
|
544
518
|
}
|
|
545
|
-
const type =
|
|
519
|
+
const type = isDoorbell ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera;
|
|
546
520
|
|
|
547
521
|
const device: Device = {
|
|
548
522
|
nativeId,
|
|
@@ -552,7 +526,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
552
526
|
type,
|
|
553
527
|
info: {
|
|
554
528
|
manufacturer: 'Reolink',
|
|
555
|
-
model
|
|
529
|
+
model,
|
|
556
530
|
serialNumber: uid,
|
|
557
531
|
}
|
|
558
532
|
};
|
|
@@ -571,7 +545,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
571
545
|
device,
|
|
572
546
|
description: `${name} (Channel ${channel})`,
|
|
573
547
|
rtspChannel: channel,
|
|
574
|
-
deviceData
|
|
548
|
+
deviceData,
|
|
575
549
|
});
|
|
576
550
|
|
|
577
551
|
logger.debug(`Discovered channel ${channel}: ${name}`);
|
|
@@ -580,7 +554,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
580
554
|
}
|
|
581
555
|
}
|
|
582
556
|
|
|
583
|
-
logger.debug(`Channel discovery completed. ${JSON.stringify({
|
|
557
|
+
logger.debug(`Channel discovery completed. ${JSON.stringify({ devices, channels })}`);
|
|
584
558
|
}
|
|
585
559
|
|
|
586
560
|
async discoverDevices(scan?: boolean): Promise<DiscoveredDevice[]> {
|
|
@@ -621,11 +595,10 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
621
595
|
await this.onDeviceEvent(ScryptedInterface.DeviceDiscovery, await this.discoverDevices());
|
|
622
596
|
|
|
623
597
|
const isBattery = entry.device.interfaces.includes(ScryptedInterface.Battery);
|
|
624
|
-
const {
|
|
598
|
+
const { uid } = entry.deviceData;
|
|
625
599
|
|
|
626
600
|
const { ReolinkBaichuanApi } = await import("@apocaliss92/reolink-baichuan-js");
|
|
627
601
|
const transport = 'tcp';
|
|
628
|
-
const uid = channelStatus?.uid;
|
|
629
602
|
const normalizedUid = isBattery && uid ? normalizeUid(uid) : undefined;
|
|
630
603
|
const baichuanApi = new ReolinkBaichuanApi({
|
|
631
604
|
host: this.storageSettings.values.ipAddress,
|
|
@@ -664,7 +637,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
664
637
|
device.storageSettings.values.rtspChannel = entry.rtspChannel;
|
|
665
638
|
device.storageSettings.values.ipAddress = ipAddress;
|
|
666
639
|
device.storageSettings.values.capabilities = capabilities;
|
|
667
|
-
device.storageSettings.values.uid =
|
|
640
|
+
device.storageSettings.values.uid = uid;
|
|
668
641
|
|
|
669
642
|
this.discoveredDevices.delete(adopt.nativeId);
|
|
670
643
|
return device?.id;
|