@apocaliss92/scrypted-reolink-native 0.1.37 → 0.1.38

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/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apocaliss92/scrypted-reolink-native",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "Use any reolink camera with Scrypted, even older/unsupported models without HTTP protocol support",
5
5
  "author": "@apocaliss92",
6
6
  "license": "Apache",
@@ -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 checkSleepingState:', e);
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/nvr.ts CHANGED
@@ -1,11 +1,11 @@
1
- import type { DeviceInfoResponse, EnrichedRecordingFile, EventsResponse, ListNvrRecordingsParams, ReolinkBaichuanApi, ReolinkCgiApi, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
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: DeviceInfoResponse;
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
- // Process events on the target camera
298
- targetCamera.processEvents({ motion, objects }).catch((e) => {
299
- logger.warn(`Error processing events for camera channel ${channel}`, e);
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 cgiApi = await this.ensureBaichuanClient();
310
+ const api = await this.ensureBaichuanClient();
328
311
 
329
- const diagnostics = await cgiApi.collectNvrDiagnostics({
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 || !api) {
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
- // Only fetch and forward CGI events if CGI is selected as event source
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
- const { batteryInfoData, response } = await api.getAllChannelsBatteryInfo();
424
-
425
- logger.debug(`Battery info call result: ${JSON.stringify({ batteryInfoData, response })}`);
426
-
427
- this.cameraNativeMap.forEach((camera) => {
428
- if (camera) {
429
- const channel = camera.storageSettings.values.rtspChannel;
430
- const cameraBatteryData = batteryInfoData[channel];
431
- if (cameraBatteryData) {
432
- (camera as ReolinkNativeBatteryCamera).updateSleepingState({
433
- reason: 'NVR',
434
- state: cameraBatteryData.sleeping ? 'sleeping' : 'awake',
435
- idleMs: 0,
436
- lastRxAtMs: 0,
437
- }).catch(() => { });
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 cgiApi = await this.ensureClient();
520
- const { devicesData, channels } = await cgiApi.getDevicesInfo();
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, devicesData })}`);
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 channel of channels) {
534
- try {
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 = abilities.supportDoorbellLight ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera;
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: channelInfo?.typeInfo,
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: devicesData[channel],
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({ devicesData, channels })}`);
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 { channelStatus } = entry.deviceData;
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 = entry.deviceData.channelStatus.uid;
640
+ device.storageSettings.values.uid = uid;
668
641
 
669
642
  this.discoveredDevices.delete(adopt.nativeId);
670
643
  return device?.id;