@apocaliss92/scrypted-reolink-native 0.1.24 → 0.1.26

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.24",
3
+ "version": "0.1.26",
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",
@@ -248,11 +248,10 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
248
248
  username: config.username,
249
249
  password: config.password,
250
250
  uid: config.uid,
251
- logger: logger as Console,
251
+ logger,
252
252
  debugOptions: config.debugOptions,
253
253
  },
254
254
  transport: config.transport,
255
- logger: logger as Console,
256
255
  });
257
256
 
258
257
  await api.login();
@@ -120,8 +120,7 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
120
120
 
121
121
  logger.log('Starting periodic tasks for battery camera');
122
122
 
123
- // Check sleeping state every 5 seconds (non-blocking)
124
- if (!this.nvrDevice) {
123
+ if (!this.nvrDevice && !this.multiFocalDevice) {
125
124
  this.sleepCheckTimer = setInterval(async () => {
126
125
  try {
127
126
  const api = this.baichuanApi;
@@ -375,18 +374,12 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
375
374
  }
376
375
  }
377
376
 
378
- async withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T> {
379
- return await fn();
380
- }
381
-
382
377
  protected async withBaichuanClient<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T> {
383
378
  const client = await this.ensureClient();
384
379
  return fn(client);
385
380
  }
386
381
 
387
382
  async createStreamClient(): Promise<ReolinkBaichuanApi> {
388
- // Reuse the main Baichuan client connection instead of creating a new one
389
- // This ensures we use a single session for everything (general + streams)
390
383
  return await this.ensureClient();
391
384
  }
392
385
  }
package/src/camera.ts CHANGED
@@ -31,8 +31,8 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
31
31
 
32
32
 
33
33
  constructor(
34
- nativeId: string,
35
- public plugin: ReolinkNativePlugin,
34
+ nativeId: string,
35
+ public plugin: ReolinkNativePlugin,
36
36
  nvrDevice?: ReolinkNativeNvrDevice,
37
37
  multiFocalDevice?: ReolinkNativeMultiFocalDevice
38
38
  ) {
@@ -67,27 +67,6 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
67
67
  }
68
68
  }
69
69
 
70
- async withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T> {
71
- try {
72
- return await fn();
73
- }
74
- catch (e) {
75
- if (!this.isRecoverableBaichuanError(e)) {
76
- throw e;
77
- }
78
-
79
- // Reset client and clear cache on recoverable error
80
- await this.resetBaichuanClient(e);
81
-
82
- // Important: callers must re-acquire the client inside fn.
83
- try {
84
- return await fn();
85
- } catch (retryError) {
86
- throw retryError;
87
- }
88
- }
89
- }
90
-
91
70
 
92
71
  async init() {
93
72
  this.startPeriodicTasks();
@@ -97,6 +76,7 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
97
76
 
98
77
  async createStreamClient(): Promise<ReolinkBaichuanApi> {
99
78
  const { ipAddress, username, password } = this.storageSettings.values;
79
+ const logger = this.getBaichuanLogger();
100
80
 
101
81
  const debugOptions = this.getBaichuanDebugOptions();
102
82
  const api = await createBaichuanApi(
@@ -105,11 +85,10 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
105
85
  host: ipAddress,
106
86
  username: username,
107
87
  password: password,
108
- logger: this.console,
88
+ logger,
109
89
  debugOptions
110
90
  },
111
91
  transport: 'tcp',
112
- logger: this.console,
113
92
  },
114
93
  );
115
94
  await api.login();
package/src/common.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, ReolinkSupportedStream, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
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
+ import path from 'path';
4
5
  import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
5
6
  import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
6
7
  import { normalizeUid, type BaichuanTransport } from "./connect";
7
8
  import { convertDebugLogsToApiOptions, DebugLogDisplayNames, DebugLogOption, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
8
9
  import { ReolinkBaichuanIntercom } from "./intercom";
9
10
  import ReolinkNativePlugin from "./main";
10
- import { ReolinkNativeNvrDevice } from "./nvr";
11
11
  import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
12
+ import { ReolinkNativeNvrDevice } from "./nvr";
12
13
  import { ReolinkPtzPresets } from "./presets";
13
14
  import {
14
15
  createRfc4571MediaObjectFromStreamManager,
@@ -17,10 +18,9 @@ import {
17
18
  selectStreamOption,
18
19
  StreamManager
19
20
  } from "./stream-utils";
20
- import { getDeviceInterfaces, updateDeviceInfo } from "./utils";
21
- import path from 'path';
21
+ import { floodlightSuffix, getDeviceInterfaces, pirSuffix, sirenSuffix, updateDeviceInfo } from "./utils";
22
22
 
23
- export type CameraType = 'battery' | 'regular';
23
+ export type CameraType = 'battery' | 'regular' | 'multi-focal' | 'multi-focal-battery';
24
24
 
25
25
  export interface CommonCameraMixinOptions {
26
26
  type: CameraType;
@@ -197,6 +197,12 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
197
197
  await this.credentialsChanged();
198
198
  }
199
199
  },
200
+ debugEvents: {
201
+ title: 'Debug Events',
202
+ type: 'boolean',
203
+ immediate: true,
204
+ hide: true,
205
+ },
200
206
  username: {
201
207
  type: 'string',
202
208
  title: 'Username',
@@ -220,7 +226,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
220
226
  json: true,
221
227
  hide: true,
222
228
  },
223
- operationChannels: {
229
+ multifocalInfo: {
224
230
  json: true,
225
231
  hide: true,
226
232
  },
@@ -515,7 +521,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
515
521
  // Abstract init method that subclasses must implement
516
522
  abstract init(): Promise<void>;
517
523
 
518
- abstract withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T>;
519
524
  protected withBaichuanClient?<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T>;
520
525
  motionTimeout?: NodeJS.Timeout;
521
526
  doorbellBinaryTimeout?: NodeJS.Timeout;
@@ -527,18 +532,20 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
527
532
  thisDevice: Settings
528
533
 
529
534
  constructor(
530
- nativeId: string,
531
- public plugin: ReolinkNativePlugin,
535
+ nativeId: string,
536
+ public plugin: ReolinkNativePlugin,
532
537
  public options: CommonCameraMixinOptions
533
538
  ) {
534
539
  super(nativeId);
535
- this.protocol = !options.nvrDevice && !options.multiFocalDevice && options.type === 'battery' ? 'udp' : 'tcp';
536
540
 
537
541
  // Store NVR device reference if provided
538
542
  this.nvrDevice = options.nvrDevice;
539
543
  this.multiFocalDevice = options.multiFocalDevice;
540
544
  this.thisDevice = sdk.systemManager.getDeviceById<Settings>(this.id);
541
545
 
546
+ const isBattery = options.type === 'battery' || options.type === 'multi-focal-battery';
547
+ this.protocol = isBattery ? 'udp' : 'tcp';
548
+
542
549
  setTimeout(async () => {
543
550
  await this.parentInit();
544
551
  }, 2000);
@@ -601,7 +608,31 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
601
608
  return this.name || 'Camera';
602
609
  }
603
610
 
604
- private async runDiagnostics(): Promise<void> {
611
+ async withBaichuanRetry<T>(fn: () => Promise<T>): Promise<T> {
612
+ if (this.protocol === 'udp') {
613
+ return await fn();
614
+ } else {
615
+ try {
616
+ return await fn();
617
+ } catch (e) {
618
+ if (!this.isRecoverableBaichuanError(e)) {
619
+ throw e;
620
+ }
621
+
622
+ // Reset client and clear cache on recoverable error
623
+ await this.resetBaichuanClient(e);
624
+
625
+ // Important: callers must re-acquire the client inside fn.
626
+ try {
627
+ return await fn();
628
+ } catch (retryError) {
629
+ throw retryError;
630
+ }
631
+ }
632
+ }
633
+ }
634
+
635
+ async runDiagnostics(): Promise<void> {
605
636
  const logger = this.getBaichuanLogger();
606
637
  const outputPath = this.storageSettings.values.diagnosticsOutputPath || process.env.SCRYPTED_PLUGIN_VOLUME || "";
607
638
 
@@ -654,8 +685,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
654
685
  }
655
686
 
656
687
  public getAbilities(): DeviceCapabilities {
657
- if(this.options.multiFocalDevice) {
658
- return this.options.multiFocalDevice.getInterfaces(this.storageSettings.values.rtspChannel).capabilities;
688
+ if (this.multiFocalDevice) {
689
+ return this.multiFocalDevice.getInterfaces(this.storageSettings.values.rtspChannel).capabilities;
659
690
  } else {
660
691
  return this.storageSettings.values.capabilities;
661
692
  }
@@ -690,9 +721,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
690
721
 
691
722
  // Event subscription methods
692
723
  unsubscribedToEvents(): void {
693
- // Use base class unsubscribe
694
724
  this.unsubscribeFromEvents().catch(() => {
695
- // ignore
696
725
  });
697
726
 
698
727
  if (this.motionDetected) {
@@ -1024,20 +1053,17 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1024
1053
  this.binaryState = false;
1025
1054
  }
1026
1055
 
1027
- // Report devices (siren, floodlight, PIR)
1028
1056
  async reportDevices(): Promise<void> {
1029
- if (!this.nativeId || !this.name) {
1030
- return;
1031
- }
1057
+ const abilities = this.getAbilities();
1032
1058
 
1033
- const { hasSiren, hasFloodlight, hasPir } = this.getAbilities();
1059
+ const { hasSiren, hasFloodlight, hasPir } = abilities;
1034
1060
 
1035
1061
  const devices: Device[] = [];
1036
1062
 
1037
1063
  if (hasSiren) {
1038
- const sirenNativeId = `${this.nativeId}-siren`;
1064
+ const sirenNativeId = `${this.nativeId}${sirenSuffix}`;
1039
1065
  devices.push({
1040
- providerNativeId: this.plugin?.nativeId,
1066
+ providerNativeId: this.nativeId,
1041
1067
  name: `${this.name} Siren`,
1042
1068
  nativeId: sirenNativeId,
1043
1069
  info: {
@@ -1049,9 +1075,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1049
1075
  }
1050
1076
 
1051
1077
  if (hasFloodlight) {
1052
- const floodlightNativeId = `${this.nativeId}-floodlight`;
1078
+ const floodlightNativeId = `${this.nativeId}${floodlightSuffix}`;
1053
1079
  devices.push({
1054
- providerNativeId: this.plugin?.nativeId,
1080
+ providerNativeId: this.nativeId,
1055
1081
  name: `${this.name} Floodlight`,
1056
1082
  nativeId: floodlightNativeId,
1057
1083
  info: {
@@ -1063,9 +1089,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1063
1089
  }
1064
1090
 
1065
1091
  if (hasPir) {
1066
- const pirNativeId = `${this.nativeId}-pir`;
1092
+ const pirNativeId = `${this.nativeId}${pirSuffix}`;
1067
1093
  devices.push({
1068
- providerNativeId: this.plugin?.nativeId,
1094
+ providerNativeId: this.nativeId,
1069
1095
  name: `${this.name} PIR`,
1070
1096
  nativeId: pirNativeId,
1071
1097
  info: {
@@ -1116,6 +1142,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1116
1142
  async updateDeviceInfo(): Promise<void> {
1117
1143
  const logger = this.getBaichuanLogger();
1118
1144
 
1145
+ if (this.multiFocalDevice) {
1146
+ this.info = this.multiFocalDevice.info;
1147
+ return;
1148
+ }
1149
+
1119
1150
  const { ipAddress, rtspChannel } = this.storageSettings.values;
1120
1151
  try {
1121
1152
  const api = await this.ensureClient();
@@ -1125,9 +1156,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1125
1156
  device: this,
1126
1157
  ipAddress,
1127
1158
  deviceData,
1159
+ logger,
1128
1160
  });
1129
1161
 
1130
- logger.log(`Device info updated: ${JSON.stringify(deviceData)}`);
1131
1162
  } catch (e) {
1132
1163
  logger.warn('Failed to fetch device info', e);
1133
1164
  }
@@ -1135,24 +1166,24 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1135
1166
 
1136
1167
  // Device provider methods
1137
1168
  async getDevice(nativeId: string): Promise<any> {
1138
- if (nativeId.endsWith('-siren')) {
1169
+ if (nativeId.endsWith(sirenSuffix)) {
1139
1170
  this.siren ||= new ReolinkCameraSiren(this, nativeId);
1140
1171
  return this.siren;
1141
- } else if (nativeId.endsWith('-floodlight')) {
1172
+ } else if (nativeId.endsWith(floodlightSuffix)) {
1142
1173
  this.floodlight ||= new ReolinkCameraFloodlight(this, nativeId);
1143
1174
  return this.floodlight;
1144
- } else if (nativeId.endsWith('-pir')) {
1175
+ } else if (nativeId.endsWith(pirSuffix)) {
1145
1176
  this.pirSensor ||= new ReolinkCameraPirSensor(this, nativeId);
1146
1177
  return this.pirSensor;
1147
1178
  }
1148
1179
  }
1149
1180
 
1150
1181
  async releaseDevice(id: string, nativeId: string): Promise<void> {
1151
- if (nativeId.endsWith('-siren')) {
1182
+ if (nativeId.endsWith(sirenSuffix)) {
1152
1183
  this.siren = undefined;
1153
- } else if (nativeId.endsWith('-floodlight')) {
1184
+ } else if (nativeId.endsWith(floodlightSuffix)) {
1154
1185
  this.floodlight = undefined;
1155
- } else if (nativeId.endsWith('-pir')) {
1186
+ } else if (nativeId.endsWith(pirSuffix)) {
1156
1187
  this.pirSensor = undefined;
1157
1188
  }
1158
1189
  }
@@ -1352,7 +1383,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1352
1383
  }
1353
1384
 
1354
1385
  if (streams.length) {
1355
- logger.log('Fetched video stream options', { streams });
1386
+ logger.log('Fetched video stream options', streams.map((s) => s.name).join(', '));
1387
+ logger.debug(JSON.stringify(streams));
1356
1388
  this.cachedVideoStreamOptions = streams;
1357
1389
  return streams;
1358
1390
  }
@@ -1452,7 +1484,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1452
1484
  });
1453
1485
  this.classes = objects;
1454
1486
  this.presets = presets;
1455
- this.storageSettings.values.capabilities = capabilities;
1456
1487
  this.ptzPresets.setCachedPtzPresets(presets);
1457
1488
 
1458
1489
  try {
@@ -1463,21 +1494,25 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1463
1494
 
1464
1495
  const device: Device = {
1465
1496
  nativeId: this.nativeId,
1466
- providerNativeId: this.nvrDevice?.nativeId ?? this.plugin?.nativeId,
1497
+ providerNativeId: this.nvrDevice?.nativeId ??
1498
+ this.multiFocalDevice?.nativeId ??
1499
+ this.plugin?.nativeId,
1467
1500
  name: this.name,
1468
1501
  interfaces,
1469
1502
  type,
1470
1503
  info: this.info,
1471
1504
  };
1472
1505
 
1473
- logger.log(`Updating device interfaces: ${JSON.stringify(device)}`);
1474
-
1475
1506
  await sdk.deviceManager.onDeviceDiscovered(device);
1507
+
1508
+ logger.log(`Device interfaces updated`);
1509
+ logger.debug(`${JSON.stringify(device)}`);
1476
1510
  } catch (e) {
1477
1511
  logger.error('Failed to update device interfaces', e);
1478
1512
  }
1479
1513
 
1480
- logger.log(`Refreshed device capabilities: ${JSON.stringify({ capabilities, abilities, support, presets })}`);
1514
+ logger.log(`Refreshed device capabilities: ${JSON.stringify(capabilities)}`);
1515
+ logger.debug(`Refreshed device capabilities: ${JSON.stringify({ abilities, support, presets, objects })}`);
1481
1516
  }
1482
1517
  catch (e) {
1483
1518
  logger.error('Failed to refresh abilities', e);
@@ -1497,65 +1532,28 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1497
1532
  logger.warn('Failed to update device info during init', e);
1498
1533
  }
1499
1534
 
1500
- try {
1501
- await this.refreshDeviceState();
1502
- }
1503
- catch (e) {
1504
- logger.warn('Failed to connect/refresh during init', e);
1505
- }
1506
-
1507
- try {
1508
- await this.reportDevices();
1509
- }
1510
- catch (e) {
1511
- logger.warn('Failed to report devices during init', e);
1512
- }
1513
-
1514
- const { hasIntercom, hasPtz } = this.getAbilities();
1515
-
1516
- if (hasIntercom) {
1517
- this.intercom = new ReolinkBaichuanIntercom(this);
1518
- }
1519
-
1520
- if (hasPtz) {
1521
- const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
1522
-
1523
- this.storageSettings.settings.presets.choices = choices;
1524
- this.storageSettings.settings.ptzSelectedPreset.choices = choices;
1525
-
1526
- this.storageSettings.settings.presets.hide = false;
1527
- this.storageSettings.settings.ptzMoveDurationMs.hide = false;
1528
- this.storageSettings.settings.ptzZoomStep.hide = false;
1529
- this.storageSettings.settings.ptzCreatePreset.hide = false;
1530
- this.storageSettings.settings.ptzSelectedPreset.hide = false;
1531
- this.storageSettings.settings.ptzUpdateSelectedPreset.hide = false;
1532
- this.storageSettings.settings.ptzDeleteSelectedPreset.hide = false;
1533
-
1534
- this.updatePtzCaps();
1535
+ if (!this.multiFocalDevice) {
1536
+ try {
1537
+ await this.refreshDeviceState();
1538
+ await this.reportDevices();
1539
+ }
1540
+ catch (e) {
1541
+ logger.warn('Failed to connect/refresh during init', e);
1542
+ }
1535
1543
  }
1536
1544
 
1537
- const isBattery = this.options.type === 'battery';
1538
1545
  const { username, password } = this.storageSettings.values;
1546
+ const isCamera = this.options.type === 'regular' || this.options.type === 'battery';
1547
+ const isBatteryCamera = this.options.type === 'battery';
1548
+ const isBatteryMultiFocal = this.options.type === 'multi-focal-battery';
1549
+ const isBattery = isBatteryCamera || isBatteryMultiFocal;
1539
1550
 
1540
- this.streamManager = new StreamManager({
1541
- createStreamClient: () => this.createStreamClient(),
1542
- getLogger: () => logger as Console,
1543
- credentials: {
1544
- username,
1545
- password
1546
- },
1547
- // For battery cameras, we use a shared connection
1548
- sharedConnection: isBattery,
1549
- });
1550
-
1551
-
1552
- // this.storageSettings.settings.snapshotCacheMinutes.hide = !isBattery;
1553
1551
  this.storageSettings.settings.uid.hide = !isBattery;
1554
1552
  this.storageSettings.settings.batteryUpdateIntervalMinutes.hide = !isBattery;
1555
1553
  this.storageSettings.settings.lowThresholdBatteryRecording.hide = !isBattery;
1556
1554
  this.storageSettings.settings.highThresholdBatteryRecording.hide = !isBattery;
1557
1555
 
1558
- if (isBattery && !this.storageSettings.values.mixinsSetup) {
1556
+ if (isBatteryCamera && !this.storageSettings.values.mixinsSetup) {
1559
1557
  try {
1560
1558
  const device = sdk.systemManager.getDeviceById<Settings>(this.id);
1561
1559
  if (device) {
@@ -1577,30 +1575,54 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1577
1575
  logger.warn('Failed to subscribe to Baichuan events', e);
1578
1576
  }
1579
1577
 
1578
+ if (isCamera) {
1579
+ this.streamManager = new StreamManager({
1580
+ createStreamClient: () => this.createStreamClient(),
1581
+ getLogger: () => logger as Console,
1582
+ credentials: {
1583
+ username,
1584
+ password
1585
+ },
1586
+ sharedConnection: isBattery,
1587
+ });
1580
1588
 
1581
- if (this.nvrDevice) {
1582
- this.storageSettings.settings.username.hide = true;
1583
- this.storageSettings.settings.password.hide = true;
1584
- this.storageSettings.settings.ipAddress.hide = true;
1585
- this.storageSettings.settings.uid.hide = true;
1589
+ const { hasIntercom, hasPtz } = this.getAbilities();
1586
1590
 
1587
- this.storageSettings.settings.username.defaultValue = this.nvrDevice.storageSettings.values.username;
1588
- this.storageSettings.settings.password.defaultValue = this.nvrDevice.storageSettings.values.password;
1589
- this.storageSettings.settings.ipAddress.defaultValue = this.nvrDevice.storageSettings.values.ipAddress;
1591
+ if (hasIntercom) {
1592
+ this.intercom = new ReolinkBaichuanIntercom(this);
1593
+ }
1594
+
1595
+ if (hasPtz) {
1596
+ const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
1597
+
1598
+ this.storageSettings.settings.presets.choices = choices;
1599
+ this.storageSettings.settings.ptzSelectedPreset.choices = choices;
1600
+
1601
+ this.storageSettings.settings.presets.hide = false;
1602
+ this.storageSettings.settings.ptzMoveDurationMs.hide = false;
1603
+ this.storageSettings.settings.ptzZoomStep.hide = false;
1604
+ this.storageSettings.settings.ptzCreatePreset.hide = false;
1605
+ this.storageSettings.settings.ptzSelectedPreset.hide = false;
1606
+ this.storageSettings.settings.ptzUpdateSelectedPreset.hide = false;
1607
+ this.storageSettings.settings.ptzDeleteSelectedPreset.hide = false;
1608
+
1609
+ this.updatePtzCaps();
1610
+ }
1590
1611
  }
1591
1612
 
1592
- if (this.multiFocalDevice) {
1613
+ if (this.nvrDevice || this.multiFocalDevice) {
1593
1614
  this.storageSettings.settings.username.hide = true;
1594
1615
  this.storageSettings.settings.password.hide = true;
1595
1616
  this.storageSettings.settings.ipAddress.hide = true;
1596
1617
  this.storageSettings.settings.uid.hide = true;
1597
1618
 
1598
- this.storageSettings.settings.username.defaultValue = this.multiFocalDevice.storageSettings.values.username;
1599
- this.storageSettings.settings.password.defaultValue = this.multiFocalDevice.storageSettings.values.password;
1600
- this.storageSettings.settings.ipAddress.defaultValue = this.multiFocalDevice.storageSettings.values.ipAddress;
1619
+ this.storageSettings.settings.username.defaultValue = this.nvrDevice.storageSettings.values.username;
1620
+ this.storageSettings.settings.password.defaultValue = this.nvrDevice.storageSettings.values.password;
1621
+ this.storageSettings.settings.ipAddress.defaultValue = this.nvrDevice.storageSettings.values.ipAddress;
1601
1622
  }
1602
1623
 
1603
1624
  await this.init();
1625
+
1604
1626
  this.initComplete = true;
1605
1627
  }
1606
1628
  }
package/src/connect.ts CHANGED
@@ -19,16 +19,16 @@ export function normalizeUid(uid?: string): string | undefined {
19
19
  export async function createBaichuanApi(props: {
20
20
  inputs: BaichuanConnectInputs,
21
21
  transport: BaichuanTransport,
22
- logger: Console,
23
22
  }): Promise<ReolinkBaichuanApi> {
24
- const { inputs, transport, logger } = props;
23
+ const { inputs, transport } = props;
24
+ const { logger } = inputs;
25
25
  const { ReolinkBaichuanApi } = await import("@apocaliss92/reolink-baichuan-js");
26
26
 
27
27
  const base: BaichuanClientOptions = {
28
28
  host: inputs.host,
29
29
  username: inputs.username,
30
30
  password: inputs.password,
31
- logger: logger, // Use the logger passed to createBaichuanApi, not inputs.logger
31
+ logger,
32
32
  debugOptions: inputs.debugOptions ?? {}
33
33
  };
34
34
 
package/src/main.ts CHANGED
@@ -4,10 +4,9 @@ import { ReolinkNativeCamera } from "./camera";
4
4
  import { ReolinkNativeBatteryCamera } from "./camera-battery";
5
5
  import { CommonCameraMixin } from "./common";
6
6
  import { createBaichuanApi } from "./connect";
7
- import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
8
7
  import { ReolinkNativeNvrDevice } from "./nvr";
9
- import { batteryCameraSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, nvrSuffix } from "./utils";
10
- import { BaichuanTransport } from "./connect";
8
+ import { batteryCameraSuffix, batteryMultifocalSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, nvrSuffix } from "./utils";
9
+ import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
11
10
 
12
11
  class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
13
12
  devices = new Map<string, BaseBaichuanClass>();
@@ -65,18 +64,28 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
65
64
  const deviceInfo = detection.deviceInfo || {};
66
65
  const name = deviceInfo.name || 'Reolink Multi-Focal';
67
66
  const serialNumber = deviceInfo.serialNumber || deviceInfo.itemNo || `multifocal-${Date.now()}`;
68
- nativeId = `${serialNumber}${multifocalSuffix}`;
67
+ const isBattery = detection.transport === 'udp';
68
+ nativeId = `${serialNumber}${isBattery ? batteryMultifocalSuffix : multifocalSuffix}`;
69
69
 
70
70
  settings.newCamera ||= name;
71
71
 
72
+ const interfaces = [
73
+ ScryptedInterface.Settings,
74
+ ScryptedInterface.DeviceProvider,
75
+ ScryptedInterface.Reboot,
76
+ ];
77
+
78
+ if (isBattery) {
79
+ interfaces.push(
80
+ ScryptedInterface.Battery,
81
+ ScryptedInterface.Sleep
82
+ );
83
+ }
84
+
72
85
  await sdk.deviceManager.onDeviceDiscovered({
73
86
  nativeId,
74
87
  name,
75
- interfaces: [
76
- ScryptedInterface.Settings,
77
- ScryptedInterface.DeviceProvider,
78
- ScryptedInterface.Reboot,
79
- ],
88
+ interfaces,
80
89
  type: ScryptedDeviceType.DeviceProvider,
81
90
  providerNativeId: this.nativeId,
82
91
  });
@@ -89,7 +98,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
89
98
  device.storageSettings.values.username = username;
90
99
  device.storageSettings.values.password = password;
91
100
  device.storageSettings.values.uid = detection.uid || '';
92
- device.storageSettings.values.protocol = detection.transport || 'tcp' as BaichuanTransport;
93
101
 
94
102
  return nativeId;
95
103
  }
@@ -151,15 +159,12 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
151
159
  logger: this.console,
152
160
  },
153
161
  transport: detection.transport,
154
- logger: this.console,
155
162
  });
156
163
 
157
164
  try {
158
165
  await api.login();
159
166
  const rtspChannel = 0;
160
- const { abilities, capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel);
161
-
162
- this.console.log(nativeId, JSON.stringify({ abilities, capabilities, deviceInfo }));
167
+ const { capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel);
163
168
 
164
169
  const { interfaces, type } = getDeviceInterfaces({
165
170
  capabilities,
@@ -175,7 +180,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
175
180
  });
176
181
 
177
182
  const device = await this.getDevice(nativeId) as CommonCameraMixin;
178
- this.console.log(name, interfaces, type, device);
179
183
 
180
184
  device.info = deviceInfo;
181
185
  device.classes = objects;
@@ -214,10 +218,12 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
214
218
  key: 'ip',
215
219
  title: 'IP Address',
216
220
  placeholder: '192.168.2.222',
221
+ value: '192.168.',
217
222
  },
218
223
  {
219
224
  key: 'username',
220
225
  title: 'Username',
226
+ value: 'admin',
221
227
  },
222
228
  {
223
229
  key: 'password',
@@ -237,8 +243,10 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
237
243
  return new ReolinkNativeBatteryCamera(nativeId, this);
238
244
  } else if (nativeId.endsWith(nvrSuffix)) {
239
245
  return new ReolinkNativeNvrDevice(nativeId, this);
246
+ } else if (nativeId.endsWith(batteryMultifocalSuffix)) {
247
+ return new ReolinkNativeMultiFocalDevice(nativeId, this, "multi-focal-battery");
240
248
  } else if (nativeId.endsWith(multifocalSuffix)) {
241
- return new ReolinkNativeMultiFocalDevice(nativeId, this);
249
+ return new ReolinkNativeMultiFocalDevice(nativeId, this, "multi-focal");
242
250
  } else {
243
251
  return new ReolinkNativeCamera(nativeId, this);
244
252
  }