@apocaliss92/scrypted-reolink-native 0.1.8 → 0.1.10

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/src/common.ts CHANGED
@@ -2,6 +2,7 @@ import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, Reo
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
4
  import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
5
+ import { BaseBaichuanClass, type BaichuanConnectionConfig, type BaichuanConnectionCallbacks } from "./baichuan-base";
5
6
  import { createBaichuanApi, normalizeUid, type BaichuanTransport } from "./connect";
6
7
  import { convertDebugLogsToApiOptions, DebugLogDisplayNames, DebugLogOption, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
7
8
  import { ReolinkBaichuanIntercom } from "./intercom";
@@ -32,27 +33,27 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
32
33
  }
33
34
 
34
35
  async turnOff(): Promise<void> {
35
- this.camera.getLogger().log(`Siren toggle: turnOff (device=${this.nativeId})`);
36
+ this.camera.getBaichuanLogger().log(`Siren toggle: turnOff (device=${this.nativeId})`);
36
37
  this.on = false;
37
38
  try {
38
39
  await this.camera.setSirenEnabled(false);
39
- this.camera.getLogger().log(`Siren toggle: turnOff ok (device=${this.nativeId})`);
40
+ this.camera.getBaichuanLogger().log(`Siren toggle: turnOff ok (device=${this.nativeId})`);
40
41
  }
41
42
  catch (e) {
42
- this.camera.getLogger().warn(`Siren toggle: turnOff failed (device=${this.nativeId})`, e);
43
+ this.camera.getBaichuanLogger().warn(`Siren toggle: turnOff failed (device=${this.nativeId})`, e);
43
44
  throw e;
44
45
  }
45
46
  }
46
47
 
47
48
  async turnOn(): Promise<void> {
48
- this.camera.getLogger().log(`Siren toggle: turnOn (device=${this.nativeId})`);
49
+ this.camera.getBaichuanLogger().log(`Siren toggle: turnOn (device=${this.nativeId})`);
49
50
  this.on = true;
50
51
  try {
51
52
  await this.camera.setSirenEnabled(true);
52
- this.camera.getLogger().log(`Siren toggle: turnOn ok (device=${this.nativeId})`);
53
+ this.camera.getBaichuanLogger().log(`Siren toggle: turnOn ok (device=${this.nativeId})`);
53
54
  }
54
55
  catch (e) {
55
- this.camera.getLogger().warn(`Siren toggle: turnOn failed (device=${this.nativeId})`, e);
56
+ this.camera.getBaichuanLogger().warn(`Siren toggle: turnOn failed (device=${this.nativeId})`, e);
56
57
  throw e;
57
58
  }
58
59
  }
@@ -64,40 +65,40 @@ class ReolinkCameraFloodlight extends ScryptedDeviceBase implements OnOff, Brigh
64
65
  }
65
66
 
66
67
  async setBrightness(brightness: number): Promise<void> {
67
- this.camera.getLogger().log(`Floodlight toggle: setBrightness (device=${this.nativeId} brightness=${brightness})`);
68
+ this.camera.getBaichuanLogger().log(`Floodlight toggle: setBrightness (device=${this.nativeId} brightness=${brightness})`);
68
69
  this.brightness = brightness;
69
70
  try {
70
71
  await this.camera.setFloodlightState(undefined, brightness);
71
- this.camera.getLogger().log(`Floodlight toggle: setBrightness ok (device=${this.nativeId} brightness=${brightness})`);
72
+ this.camera.getBaichuanLogger().log(`Floodlight toggle: setBrightness ok (device=${this.nativeId} brightness=${brightness})`);
72
73
  }
73
74
  catch (e) {
74
- this.camera.getLogger().warn(`Floodlight toggle: setBrightness failed (device=${this.nativeId} brightness=${brightness})`, e);
75
+ this.camera.getBaichuanLogger().warn(`Floodlight toggle: setBrightness failed (device=${this.nativeId} brightness=${brightness})`, e);
75
76
  throw e;
76
77
  }
77
78
  }
78
79
 
79
80
  async turnOff(): Promise<void> {
80
- this.camera.getLogger().log(`Floodlight toggle: turnOff (device=${this.nativeId})`);
81
+ this.camera.getBaichuanLogger().log(`Floodlight toggle: turnOff (device=${this.nativeId})`);
81
82
  this.on = false;
82
83
  try {
83
84
  await this.camera.setFloodlightState(false);
84
- this.camera.getLogger().log(`Floodlight toggle: turnOff ok (device=${this.nativeId})`);
85
+ this.camera.getBaichuanLogger().log(`Floodlight toggle: turnOff ok (device=${this.nativeId})`);
85
86
  }
86
87
  catch (e) {
87
- this.camera.getLogger().warn(`Floodlight toggle: turnOff failed (device=${this.nativeId})`, e);
88
+ this.camera.getBaichuanLogger().warn(`Floodlight toggle: turnOff failed (device=${this.nativeId})`, e);
88
89
  throw e;
89
90
  }
90
91
  }
91
92
 
92
93
  async turnOn(): Promise<void> {
93
- this.camera.getLogger().log(`Floodlight toggle: turnOn (device=${this.nativeId})`);
94
+ this.camera.getBaichuanLogger().log(`Floodlight toggle: turnOn (device=${this.nativeId})`);
94
95
  this.on = true;
95
96
  try {
96
97
  await this.camera.setFloodlightState(true);
97
- this.camera.getLogger().log(`Floodlight toggle: turnOn ok (device=${this.nativeId})`);
98
+ this.camera.getBaichuanLogger().log(`Floodlight toggle: turnOn ok (device=${this.nativeId})`);
98
99
  }
99
100
  catch (e) {
100
- this.camera.getLogger().warn(`Floodlight toggle: turnOn failed (device=${this.nativeId})`, e);
101
+ this.camera.getBaichuanLogger().warn(`Floodlight toggle: turnOn failed (device=${this.nativeId})`, e);
101
102
  throw e;
102
103
  }
103
104
  }
@@ -185,7 +186,7 @@ class ReolinkCameraPirSensor extends ScryptedDeviceBase implements OnOff, Settin
185
186
  }
186
187
  }
187
188
 
188
- export abstract class CommonCameraMixin extends ScryptedDeviceBase implements VideoCamera, Camera, Settings, DeviceProvider, ObjectDetector, PanTiltZoom, VideoTextOverlays, BinarySensor, Intercom {
189
+ export abstract class CommonCameraMixin extends BaseBaichuanClass implements VideoCamera, Camera, Settings, DeviceProvider, ObjectDetector, PanTiltZoom, VideoTextOverlays, BinarySensor, Intercom {
189
190
  storageSettings = new StorageSettings(this, {
190
191
  // Basic connection settings
191
192
  ipAddress: {
@@ -302,7 +303,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
302
303
  // Trigger reconnection
303
304
  await this.ensureClient();
304
305
  } catch (e) {
305
- this.getLogger().warn('Failed to reset client after debug logs change', e);
306
+ this.getBaichuanLogger().warn('Failed to reset client after debug logs change', e);
306
307
  }
307
308
  }, 2000);
308
309
  }
@@ -386,7 +387,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
386
387
  return;
387
388
  }
388
389
 
389
- const logger = this.getLogger();
390
+ const logger = this.getBaichuanLogger();
390
391
  logger.log(`PTZ presets: create preset requested (name=${name})`);
391
392
 
392
393
  const preset = await this.withBaichuanRetry(async () => {
@@ -427,7 +428,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
427
428
  throw new Error('No preset selected');
428
429
  }
429
430
 
430
- const logger = this.getLogger();
431
+ const logger = this.getBaichuanLogger();
431
432
  logger.log(`PTZ presets: update position requested (presetId=${presetId})`);
432
433
 
433
434
  await this.withBaichuanRetry(async () => {
@@ -450,7 +451,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
450
451
  throw new Error('No preset selected');
451
452
  }
452
453
 
453
- const logger = this.getLogger();
454
+ const logger = this.getBaichuanLogger();
454
455
  logger.log(`PTZ presets: delete requested (presetId=${presetId})`);
455
456
 
456
457
  await this.withBaichuanRetry(async () => {
@@ -482,13 +483,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
482
483
  protected lastNetPortCacheAttempt: number = 0;
483
484
  protected netPortCacheBackoffMs: number = 5000; // 5 seconds backoff on failure
484
485
 
485
- // Client management
486
- protected baichuanApi: ReolinkBaichuanApi | undefined;
487
- protected ensureClientPromise: Promise<ReolinkBaichuanApi> | undefined;
488
- protected connectionTime: number | undefined;
489
- private closeListener?: () => void;
490
- private lastDisconnectTime: number = 0;
491
- private readonly reconnectBackoffMs: number = 2000; // 2 seconds minimum between reconnects
486
+ // Client management (inherited from BaseBaichuanClass)
492
487
  protected readonly protocol: BaichuanTransport;
493
488
  private debugLogsResetTimeout: NodeJS.Timeout | undefined;
494
489
 
@@ -516,6 +511,60 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
516
511
  await this.parentInit();
517
512
  }, 2000);
518
513
  }
514
+
515
+ // BaseBaichuanClass abstract methods implementation
516
+ protected getConnectionConfig(): BaichuanConnectionConfig {
517
+ const { ipAddress, username, password, uid } = this.storageSettings.values;
518
+ const debugOptions = this.getBaichuanDebugOptions();
519
+ const normalizedUid = this.protocol === 'udp' ? normalizeUid(uid) : undefined;
520
+
521
+ if (this.protocol === 'udp' && !normalizedUid) {
522
+ throw new Error('UID is required for battery cameras (BCUDP)');
523
+ }
524
+
525
+ return {
526
+ host: ipAddress,
527
+ username,
528
+ password,
529
+ uid: normalizedUid,
530
+ transport: this.protocol,
531
+ logger: this.console,
532
+ debugOptions,
533
+ };
534
+ }
535
+
536
+ protected getConnectionCallbacks(): BaichuanConnectionCallbacks {
537
+ return {
538
+ onError: undefined, // Use default error handling
539
+ onClose: async () => {
540
+ // Reset client state on close
541
+ // The base class already handles cleanup
542
+ },
543
+ onSimpleEvent: this.onSimpleEvent,
544
+ getEventSubscriptionEnabled: () => this.isEventDispatchEnabled?.() ?? false,
545
+ };
546
+ }
547
+
548
+
549
+ protected isDebugEnabled(): boolean {
550
+ return this.isEventLogsEnabled();
551
+ }
552
+
553
+ protected getDeviceName(): string {
554
+ return this.name || 'Camera';
555
+ }
556
+
557
+ protected async onBeforeCleanup(): Promise<void> {
558
+ // Unsubscribe from events if needed
559
+ if (this.onSimpleEvent && this.baichuanApi) {
560
+ try {
561
+ this.baichuanApi.offSimpleEvent(this.onSimpleEvent);
562
+ }
563
+ catch {
564
+ // ignore
565
+ }
566
+ }
567
+ }
519
568
  createStreamClient(): Promise<ReolinkBaichuanApi> {
520
569
  throw new Error("Method not implemented.");
521
570
  }
@@ -524,10 +573,6 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
524
573
  return this.storageSettings.values.capabilities;
525
574
  }
526
575
 
527
- getLogger(): Console {
528
- return this.console;
529
- }
530
-
531
576
  getBaichuanDebugOptions(): any | undefined {
532
577
  const debugLogs = this.storageSettings.values.debugLogs || [];
533
578
  return convertDebugLogsToApiOptions(debugLogs);
@@ -557,14 +602,10 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
557
602
 
558
603
  // Event subscription methods
559
604
  unsubscribedToEvents(): void {
560
- const api = (this as any).baichuanApi;
561
- if (!api) return;
562
- try {
563
- api.offSimpleEvent(this.onSimpleEvent);
564
- }
565
- catch {
605
+ // Use base class unsubscribe
606
+ this.unsubscribeFromEvents().catch(() => {
566
607
  // ignore
567
- }
608
+ });
568
609
 
569
610
  if (this.motionDetected) {
570
611
  this.motionDetected = false;
@@ -573,24 +614,18 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
573
614
 
574
615
  onSimpleEvent = (ev: ReolinkSimpleEvent) => {
575
616
  try {
576
- const logger = this.getLogger();
617
+ const logger = this.getBaichuanLogger();
577
618
 
578
- if (this.isEventLogsEnabled()) {
579
- logger.log(`Baichuan event: ${JSON.stringify(ev)}`);
580
- }
619
+ logger.debug(`Baichuan event: ${JSON.stringify(ev)}`);
581
620
 
582
621
  if (!this.isEventDispatchEnabled()) {
583
- if (this.isEventLogsEnabled()) {
584
- logger.debug('Event dispatch is disabled, ignoring event');
585
- }
622
+ logger.debug('Event dispatch is disabled, ignoring event');
586
623
  return;
587
624
  }
588
625
 
589
626
  const channel = this.storageSettings.values.rtspChannel;
590
627
  if (ev?.channel !== undefined && ev.channel !== channel) {
591
- if (this.isEventLogsEnabled()) {
592
- logger.debug(`Event channel ${ev.channel} does not match camera channel ${channel}, ignoring`);
593
- }
628
+ logger.error(`Event channel ${ev.channel} does not match camera channel ${channel}, ignoring`);
594
629
  return;
595
630
  }
596
631
 
@@ -600,9 +635,6 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
600
635
  switch (ev?.type) {
601
636
  case 'motion':
602
637
  motion = true;
603
- if (this.isEventLogsEnabled()) {
604
- logger.log(`Motion event received (may be PIR or MD)`);
605
- }
606
638
  break;
607
639
  case 'doorbell':
608
640
  this.handleDoorbellEvent();
@@ -618,9 +650,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
618
650
  motion = true;
619
651
  break;
620
652
  default:
621
- if (this.isEventLogsEnabled()) {
622
- logger.debug(`Unknown event type: ${ev?.type}`);
623
- }
653
+ logger.error(`Unknown event type: ${ev?.type}`);
624
654
  return;
625
655
  }
626
656
 
@@ -629,7 +659,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
629
659
  });
630
660
  }
631
661
  catch (e) {
632
- this.getLogger().warn('Error in onSimpleEvent handler', e);
662
+ this.getBaichuanLogger().warn('Error in onSimpleEvent handler', e);
633
663
  }
634
664
  }
635
665
 
@@ -638,7 +668,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
638
668
  return;
639
669
  }
640
670
 
641
- const logger = this.getLogger();
671
+ const logger = this.getBaichuanLogger();
642
672
  const selection = Array.from(this.getDispatchEventsSelection?.() ?? new Set()).sort();
643
673
  const enabled = selection.length > 0;
644
674
 
@@ -736,13 +766,13 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
736
766
  if (preset !== undefined && preset !== null) {
737
767
  const presetId = Number(preset);
738
768
  if (!Number.isFinite(presetId)) {
739
- this.getLogger().warn(`Invalid PTZ preset id: ${preset}`);
769
+ this.getBaichuanLogger().warn(`Invalid PTZ preset id: ${preset}`);
740
770
  return;
741
771
  }
742
772
  if (this.ptzPresets) {
743
773
  await this.ptzPresets.moveToPreset(presetId);
744
774
  } else {
745
- this.getLogger().warn('PTZ presets not available');
775
+ this.getBaichuanLogger().warn('PTZ presets not available');
746
776
  }
747
777
  return;
748
778
  }
@@ -777,14 +807,14 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
777
807
 
778
808
  const step = Number(this.storageSettings.values.ptzZoomStep);
779
809
  if (!Number.isFinite(step) || step <= 0) {
780
- this.getLogger().warn('Invalid PTZ zoom step, using default 0.1');
810
+ this.getBaichuanLogger().warn('Invalid PTZ zoom step, using default 0.1');
781
811
  return;
782
812
  }
783
813
 
784
814
  // Get current zoom factor and apply step
785
815
  const info = await client.getZoomFocus(channel);
786
816
  if (!info?.zoom) {
787
- this.getLogger().warn('Zoom command requested but camera did not report zoom support.');
817
+ this.getBaichuanLogger().warn('Zoom command requested but camera did not report zoom support.');
788
818
  return;
789
819
  }
790
820
 
@@ -1003,7 +1033,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1003
1033
  deviceData,
1004
1034
  });
1005
1035
  } catch (e) {
1006
- this.getLogger().warn('Failed to fetch device info', e);
1036
+ this.getBaichuanLogger().warn('Failed to fetch device info', e);
1007
1037
  }
1008
1038
  }
1009
1039
 
@@ -1092,7 +1122,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1092
1122
  const sirenState = await api.getSiren(channel);
1093
1123
  this.siren.on = sirenState.enabled;
1094
1124
  } catch (e) {
1095
- this.getLogger().debug('Failed to align siren state', e);
1125
+ this.getBaichuanLogger().debug('Failed to align siren state', e);
1096
1126
  }
1097
1127
  }
1098
1128
 
@@ -1105,7 +1135,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1105
1135
  this.floodlight.brightness = wl.brightness;
1106
1136
  }
1107
1137
  } catch (e) {
1108
- this.getLogger().debug('Failed to align floodlight state', e);
1138
+ this.getBaichuanLogger().debug('Failed to align floodlight state', e);
1109
1139
  }
1110
1140
  }
1111
1141
 
@@ -1129,11 +1159,11 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1129
1159
  }
1130
1160
  }
1131
1161
  } catch (e) {
1132
- this.getLogger().debug('Failed to align PIR state', e);
1162
+ this.getBaichuanLogger().debug('Failed to align PIR state', e);
1133
1163
  }
1134
1164
  }
1135
1165
  } catch (e) {
1136
- this.getLogger().debug('Failed to align auxiliary devices state', e);
1166
+ this.getBaichuanLogger().debug('Failed to align auxiliary devices state', e);
1137
1167
  }
1138
1168
  }
1139
1169
 
@@ -1163,7 +1193,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1163
1193
  return url.toString();
1164
1194
  } catch (e) {
1165
1195
  // If URL parsing fails, return original URL
1166
- this.getLogger().warn('Failed to parse URL for credentials', e);
1196
+ this.getBaichuanLogger().warn('Failed to parse URL for credentials', e);
1167
1197
  return rtspUrl;
1168
1198
  }
1169
1199
  }
@@ -1204,7 +1234,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1204
1234
  } catch (e) {
1205
1235
  // Only log if it's not a recoverable error to avoid spam
1206
1236
  if (!this.isRecoverableBaichuanError?.(e)) {
1207
- this.getLogger().warn('Failed to get net port, using defaults', e);
1237
+ this.getBaichuanLogger().warn('Failed to get net port, using defaults', e);
1208
1238
  }
1209
1239
  // Use defaults if we can't get the ports
1210
1240
  this.cachedNetPort = {
@@ -1215,7 +1245,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1215
1245
  }
1216
1246
 
1217
1247
  async getVideoStreamOptions(): Promise<UrlMediaStreamOptions[]> {
1218
- const logger = this.getLogger();
1248
+ const logger = this.getBaichuanLogger();
1219
1249
 
1220
1250
  if (this.cachedVideoStreamOptions?.length) {
1221
1251
  return this.cachedVideoStreamOptions;
@@ -1260,7 +1290,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1260
1290
  }
1261
1291
 
1262
1292
  if (streams.length) {
1263
- logger.log('Fetched video stream options', { streams, netPort: this.cachedNetPort });
1293
+ this.getBaichuanLogger().log('Fetched video stream options', { streams, netPort: this.cachedNetPort });
1264
1294
  this.cachedVideoStreamOptions = streams;
1265
1295
  return streams;
1266
1296
  }
@@ -1333,165 +1363,11 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1333
1363
  async ensureClient(): Promise<ReolinkBaichuanApi> {
1334
1364
  // If camera is connected to NVR, use NVR's shared Baichuan connection
1335
1365
  if (this.nvrDevice) {
1336
- const logger = this.getLogger();
1337
1366
  return await this.nvrDevice.ensureBaichuanClient();
1338
1367
  }
1339
1368
 
1340
- // Reuse existing client if socket is still connected and logged in
1341
- if (this.baichuanApi && this.baichuanApi.client.isSocketConnected() && this.baichuanApi.client.loggedIn) {
1342
- return this.baichuanApi;
1343
- }
1344
-
1345
- // Prevent concurrent login storms
1346
- if (this.ensureClientPromise) return await this.ensureClientPromise;
1347
-
1348
- // Apply backoff to avoid aggressive reconnection after disconnection
1349
- if (this.lastDisconnectTime > 0) {
1350
- const timeSinceDisconnect = Date.now() - this.lastDisconnectTime;
1351
- if (timeSinceDisconnect < this.reconnectBackoffMs) {
1352
- const waitTime = this.reconnectBackoffMs - timeSinceDisconnect;
1353
- const logger = this.getLogger();
1354
- logger.log(`[BaichuanClient] Waiting ${waitTime}ms before reconnection (backoff)`);
1355
- await new Promise(resolve => setTimeout(resolve, waitTime));
1356
- }
1357
- }
1358
-
1359
- this.ensureClientPromise = (async () => {
1360
- const { ipAddress, username, password, uid } = this.storageSettings.values;
1361
-
1362
- // Only tear down previous session if it exists and is not connected
1363
- if (this.baichuanApi) {
1364
- // Remove close listener from old client
1365
- if (this.closeListener) {
1366
- try {
1367
- this.baichuanApi.client.off("close", this.closeListener);
1368
- }
1369
- catch {
1370
- // ignore
1371
- }
1372
- this.closeListener = undefined;
1373
- }
1374
-
1375
- const isConnected = this.baichuanApi.client.isSocketConnected();
1376
- if (!isConnected) {
1377
- try {
1378
- this.baichuanApi.offSimpleEvent(this.onSimpleEvent);
1379
- }
1380
- catch {
1381
- // ignore
1382
- }
1383
-
1384
- try {
1385
- await this.baichuanApi.close();
1386
- }
1387
- catch {
1388
- // ignore
1389
- }
1390
- } else {
1391
- // Socket is still connected, just re-attach event handler if needed
1392
- if (this.isEventDispatchEnabled?.() && this.onSimpleEvent) {
1393
- try {
1394
- this.baichuanApi.offSimpleEvent(this.onSimpleEvent);
1395
- this.baichuanApi.onSimpleEvent(this.onSimpleEvent);
1396
- }
1397
- catch {
1398
- // ignore
1399
- }
1400
- }
1401
- // Reuse existing client
1402
- this.connectionTime = Date.now();
1403
- return this.baichuanApi;
1404
- }
1405
- }
1406
-
1407
- // Create new client
1408
- const debugOptions = this.getBaichuanDebugOptions();
1409
- const normalizedUid = this.protocol === 'udp' ? normalizeUid(uid) : undefined;
1410
-
1411
- if (this.protocol === 'udp' && !normalizedUid) {
1412
- throw new Error('UID is required for battery cameras (BCUDP)');
1413
- }
1414
-
1415
- const api = await createBaichuanApi(
1416
- {
1417
- inputs: {
1418
- host: ipAddress,
1419
- username: username,
1420
- password: password,
1421
- uid: normalizedUid,
1422
- logger: this.console,
1423
- debugOptions,
1424
- },
1425
- transport: this.protocol,
1426
- logger: this.console,
1427
- }
1428
- );
1429
- await api.login();
1430
-
1431
- // Verify socket is connected before returning
1432
- if (!api.client.isSocketConnected()) {
1433
- throw new Error('Socket not connected after login');
1434
- }
1435
-
1436
- this.baichuanApi = api;
1437
- this.connectionTime = Date.now();
1438
-
1439
- // Listen for socket disconnection to reset client state
1440
- // This ensures ensureClient() will create a new connection on next call
1441
- this.closeListener = () => {
1442
- const logger = this.getLogger();
1443
- if (this.baichuanApi === api) {
1444
- const now = Date.now();
1445
- const timeSinceLastDisconnect = now - this.lastDisconnectTime;
1446
- this.lastDisconnectTime = now;
1447
-
1448
- logger.log(`[BaichuanClient] Socket closed, resetting client state for reconnection (last disconnect ${timeSinceLastDisconnect}ms ago)`);
1449
-
1450
- // Reset client state
1451
- this.baichuanApi = undefined;
1452
- this.ensureClientPromise = undefined;
1453
- this.closeListener = undefined;
1454
-
1455
- // Remove event handler to prevent operations during reconnection
1456
- try {
1457
- if (this.onSimpleEvent) {
1458
- api.offSimpleEvent(this.onSimpleEvent);
1459
- }
1460
- }
1461
- catch {
1462
- // ignore
1463
- }
1464
- }
1465
- };
1466
- api.client.on("close", this.closeListener);
1467
-
1468
- // Re-attach event handler if enabled
1469
- // Note: We don't reattach here immediately to avoid operations being called
1470
- // during reconnection. subscribeToEvents() will be called when needed.
1471
- // However, if events were already subscribed, we need to reattach them.
1472
- // We'll let subscribeToEvents() handle this, but we can also try here if needed.
1473
- if (this.isEventDispatchEnabled?.() && this.onSimpleEvent) {
1474
- try {
1475
- // Verify connection is fully ready before subscribing
1476
- if (api.client.isSocketConnected() && api.client.loggedIn) {
1477
- api.onSimpleEvent(this.onSimpleEvent);
1478
- }
1479
- }
1480
- catch (e) {
1481
- const logger = this.getLogger();
1482
- logger.warn(`[BaichuanClient] Failed to reattach event handler after reconnection, will retry via subscribeToEvents()`, e);
1483
- }
1484
- }
1485
- return api;
1486
- })();
1487
-
1488
- try {
1489
- return await this.ensureClientPromise;
1490
- }
1491
- finally {
1492
- // Allow future reconnects and avoid pinning rejected promises
1493
- this.ensureClientPromise = undefined;
1494
- }
1369
+ // Use base class implementation
1370
+ return await this.ensureBaichuanClient();
1495
1371
  }
1496
1372
 
1497
1373
  async credentialsChanged(): Promise<void> {
@@ -1515,7 +1391,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1515
1391
  }
1516
1392
  this.refreshingState = true;
1517
1393
 
1518
- const logger = this.getLogger();
1394
+ const logger = this.getBaichuanLogger();
1519
1395
  const channel = this.storageSettings.values.rtspChannel;
1520
1396
 
1521
1397
  try {
@@ -1543,14 +1419,14 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1543
1419
  info: this.info,
1544
1420
  };
1545
1421
 
1546
- logger.log(`Updating device interfaces: ${JSON.stringify(device)}`);
1422
+ this.getBaichuanLogger().log(`Updating device interfaces: ${JSON.stringify(device)}`);
1547
1423
 
1548
1424
  await sdk.deviceManager.onDeviceDiscovered(device);
1549
1425
  } catch (e) {
1550
- logger.error('Failed to update device interfaces', e);
1426
+ this.getBaichuanLogger().error('Failed to update device interfaces', e);
1551
1427
  }
1552
1428
 
1553
- this.console.log(`Refreshed device capabilities: ${JSON.stringify({ capabilities, abilities, support, presets })}`);
1429
+ this.getBaichuanLogger().log(`Refreshed device capabilities: ${JSON.stringify({ capabilities, abilities, support, presets })}`);
1554
1430
  }
1555
1431
  catch (e) {
1556
1432
  logger.error('Failed to refresh abilities', e);
@@ -1560,7 +1436,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1560
1436
  }
1561
1437
 
1562
1438
  async parentInit(): Promise<void> {
1563
- const logger = this.getLogger();
1439
+ const logger = this.getBaichuanLogger();
1564
1440
 
1565
1441
  try {
1566
1442
  await this.ensureClient();
@@ -1612,7 +1488,7 @@ export abstract class CommonCameraMixin extends ScryptedDeviceBase implements Vi
1612
1488
 
1613
1489
  this.streamManager = new StreamManager({
1614
1490
  createStreamClient: () => this.createStreamClient(),
1615
- getLogger: () => this.getLogger(),
1491
+ getLogger: () => this.getBaichuanLogger() as Console,
1616
1492
  credentials: {
1617
1493
  username,
1618
1494
  password
package/src/connect.ts CHANGED
@@ -51,7 +51,7 @@ export async function createBaichuanApi(props: {
51
51
  host: inputs.host,
52
52
  username: inputs.username,
53
53
  password: inputs.password,
54
- logger: inputs.logger,
54
+ logger: logger, // Use the logger passed to createBaichuanApi, not inputs.logger
55
55
  debugOptions: inputs.debugOptions ?? {}
56
56
  };
57
57
 
@@ -60,7 +60,6 @@ export async function createBaichuanApi(props: {
60
60
  // uncaught exception. Ensure we always have a listener.
61
61
  try {
62
62
  api.client.on("error", (err: unknown) => {
63
- const logger = inputs.logger;
64
63
  if (!logger) return;
65
64
  const msg = (err as any)?.message || (err as any)?.toString?.() || String(err);
66
65
  // Only log if it's not a recoverable error to avoid spam
@@ -60,7 +60,7 @@ export function convertDebugLogsToApiOptions(debugLogs: string[]): DebugOptions
60
60
  }
61
61
  }
62
62
 
63
- console.log(debugLogs, apiOptions);
63
+ // Removed debug log that was causing "[] {}" output
64
64
  return Object.keys(apiOptions).length > 0 ? apiOptions : undefined;
65
65
  }
66
66
 
package/src/intercom.ts CHANGED
@@ -31,7 +31,7 @@ export class ReolinkBaichuanIntercom {
31
31
  }
32
32
 
33
33
  async start(media: MediaObject): Promise<void> {
34
- const logger = this.camera.getLogger();
34
+ const logger = this.camera.getBaichuanLogger();
35
35
 
36
36
  const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(
37
37
  media,
@@ -177,7 +177,7 @@ export class ReolinkBaichuanIntercom {
177
177
  if (this.stopping) return this.stopping;
178
178
 
179
179
  this.stopping = (async () => {
180
- const logger = this.camera.getLogger();
180
+ const logger = this.camera.getBaichuanLogger();
181
181
 
182
182
  const ffmpeg = this.ffmpeg;
183
183
  this.ffmpeg = undefined;
@@ -243,7 +243,7 @@ export class ReolinkBaichuanIntercom {
243
243
  bytesNeeded: number,
244
244
  blockSize: number,
245
245
  ): void {
246
- const logger = this.camera.getLogger();
246
+ const logger = this.camera.getBaichuanLogger();
247
247
 
248
248
  this.sendChain = this.sendChain
249
249
  .then(async () => {