@apocaliss92/scrypted-reolink-native 0.1.30 → 0.1.31

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.30",
3
+ "version": "0.1.31",
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",
@@ -58,10 +58,6 @@ export class BaichuanLogger implements Console {
58
58
  }
59
59
  }
60
60
 
61
- isDebugEnabled(): boolean {
62
- return this.isDebugEnabledCallback();
63
- }
64
-
65
61
  // Console interface implementation - delegate to baseLogger
66
62
  assert(condition?: boolean, ...data: any[]): void {
67
63
  this.baseLogger.assert(condition, ...data);
@@ -13,8 +13,6 @@ import { ReolinkNativeNvrDevice } from "./nvr";
13
13
  import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
14
14
 
15
15
  export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
16
- private lastPicture: { mo: MediaObject; atMs: number } | undefined;
17
- private takePictureInFlight: Promise<MediaObject> | undefined;
18
16
  doorbellBinaryTimeout?: NodeJS.Timeout;
19
17
  motionDetected: boolean = false;
20
18
  motionTimeout: NodeJS.Timeout | undefined;
@@ -22,17 +20,11 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
22
20
  private sleepCheckTimer: NodeJS.Timeout | undefined;
23
21
  private batteryUpdateTimer: NodeJS.Timeout | undefined;
24
22
  private lastBatteryLevel: number | undefined;
25
- private forceNewSnapshot: boolean = false;
26
23
  private batteryUpdateInProgress: boolean = false;
27
24
 
28
- private isBatteryInfoLoggingEnabled(): boolean {
29
- const debugLogs = this.storageSettings.values.debugLogs || [];
30
- return debugLogs.includes(DebugLogOption.BatteryInfo);
31
- }
32
-
33
25
  constructor(
34
- nativeId: string,
35
- public plugin: ReolinkNativePlugin,
26
+ nativeId: string,
27
+ public plugin: ReolinkNativePlugin,
36
28
  nvrDevice?: ReolinkNativeNvrDevice,
37
29
  multiFocalDevice?: ReolinkNativeMultiFocalDevice
38
30
  ) {
@@ -43,53 +35,6 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
43
35
  });
44
36
  }
45
37
 
46
- async takePicture(options?: RequestPictureOptions): Promise<MediaObject> {
47
- const logger = this.getBaichuanLogger();
48
- // Allow new snapshot if:
49
- // 1. forceNewSnapshot is true, OR
50
- // 2. Camera is awake AND last snapshot was taken at least 10 seconds ago
51
- // const minSnapshotIntervalMs = 10_000; // 10 seconds
52
- // const now = Date.now();
53
- const shouldTakeNewSnapshot = this.forceNewSnapshot;
54
- // const now = Date.now();
55
- // const shouldTakeNewSnapshot = this.forceNewSnapshot ||
56
- // (!this.sleeping && this.lastPicture && (now - this.lastPicture.atMs >= minSnapshotIntervalMs));
57
-
58
- if (!shouldTakeNewSnapshot && this.lastPicture) {
59
- logger.debug(`Returning cached snapshot, taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
60
- return this.lastPicture.mo;
61
- }
62
-
63
- if (this.takePictureInFlight) {
64
- return await this.takePictureInFlight;
65
- }
66
-
67
- logger.log(`Taking new snapshot from camera (forceNewSnapshot: ${this.forceNewSnapshot})`);
68
- this.forceNewSnapshot = false;
69
-
70
- this.takePictureInFlight = (async () => {
71
- const channel = this.storageSettings.values.rtspChannel;
72
- const snapshotBuffer = await this.withBaichuanClient(async (api) => {
73
- return await api.getSnapshot(channel);
74
- });
75
- const mo = await sdk.mediaManager.createMediaObject(snapshotBuffer, 'image/jpeg');
76
- this.lastPicture = { mo, atMs: Date.now() };
77
- logger.log(`Snapshot taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
78
- return mo;
79
- })();
80
-
81
- try {
82
- return await this.takePictureInFlight;
83
- }
84
- finally {
85
- this.takePictureInFlight = undefined;
86
- }
87
- }
88
-
89
- async getPictureOptions(): Promise<ResponsePictureOptions[]> {
90
- return [];
91
- }
92
-
93
38
  async init(): Promise<void> {
94
39
  this.startPeriodicTasks();
95
40
  await this.alignAuxDevicesState();
@@ -154,7 +99,7 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
154
99
 
155
100
  async updateSleepingState(sleepStatus: SleepStatus): Promise<void> {
156
101
  try {
157
- if (this.isBatteryInfoLoggingEnabled()) {
102
+ if (this.isDebugEnabled()) {
158
103
  this.getBaichuanLogger().debug('getSleepStatus result:', JSON.stringify(sleepStatus));
159
104
  }
160
105
 
@@ -212,7 +157,7 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
212
157
  const channel = this.storageSettings.values.rtspChannel;
213
158
 
214
159
  const batteryInfo = await api.getBatteryInfo(channel);
215
- if (this.isBatteryInfoLoggingEnabled()) {
160
+ if (this.isDebugEnabled()) {
216
161
  this.getBaichuanLogger().debug('getBatteryInfo result:', JSON.stringify(batteryInfo));
217
162
  }
218
163
 
package/src/camera.ts CHANGED
@@ -175,21 +175,6 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
175
175
  return fn(client);
176
176
  }
177
177
 
178
- async takePicture(options?: RequestPictureOptions) {
179
- try {
180
- return this.withBaichuanRetry(async () => {
181
- const client = await this.ensureClient();
182
- const snapshotBuffer = await client.getSnapshot(this.storageSettings.values.rtspChannel);
183
- const mo = await this.createMediaObject(snapshotBuffer, 'image/jpeg');
184
-
185
- return mo;
186
- });
187
- } catch (e) {
188
- this.getBaichuanLogger().error('Error taking snapshot', e);
189
- throw e;
190
- }
191
- }
192
-
193
178
  async getPictureOptions(): Promise<ResponsePictureOptions[]> {
194
179
  return [];
195
180
  }
package/src/common.ts CHANGED
@@ -1,17 +1,18 @@
1
1
  import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, ReolinkSupportedStream, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
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";
2
+ import sdk, { BinarySensor, Brightness, Camera, Device, DeviceProvider, Intercom, MediaObject, MediaStreamUrl, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, Reboot, RequestMediaStreamOptions, RequestPictureOptions, ResponsePictureOptions, 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 path from 'path';
5
5
  import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
6
6
  import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
7
7
  import { normalizeUid, type BaichuanTransport } from "./connect";
8
- import { convertDebugLogsToApiOptions, DebugLogDisplayNames, DebugLogOption, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
8
+ import { convertDebugLogsToApiOptions, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
9
9
  import { ReolinkBaichuanIntercom } from "./intercom";
10
10
  import ReolinkNativePlugin from "./main";
11
11
  import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
12
12
  import { ReolinkNativeNvrDevice } from "./nvr";
13
13
  import { ReolinkPtzPresets } from "./presets";
14
14
  import {
15
+ createRfc4571CompositeMediaObjectFromStreamManager,
15
16
  createRfc4571MediaObjectFromStreamManager,
16
17
  expectedVideoTypeFromUrlMediaStreamOptions,
17
18
  parseStreamProfileFromId,
@@ -134,7 +135,8 @@ class ReolinkCameraPirSensor extends ScryptedDeviceBase implements OnOff, Settin
134
135
  }
135
136
 
136
137
  async getSettings(): Promise<Setting[]> {
137
- return this.storageSettings.getSettings();
138
+ const settings = await this.storageSettings.getSettings();
139
+ return settings;
138
140
  }
139
141
 
140
142
  async putSetting(key: string, value: SettingValue): Promise<void> {
@@ -187,7 +189,7 @@ class ReolinkCameraPirSensor extends ScryptedDeviceBase implements OnOff, Settin
187
189
  }
188
190
  }
189
191
 
190
- export abstract class CommonCameraMixin extends BaseBaichuanClass implements VideoCamera, Camera, Settings, DeviceProvider, ObjectDetector, PanTiltZoom, VideoTextOverlays, BinarySensor, Intercom {
192
+ export abstract class CommonCameraMixin extends BaseBaichuanClass implements VideoCamera, Camera, Settings, DeviceProvider, ObjectDetector, PanTiltZoom, VideoTextOverlays, BinarySensor, Intercom, Reboot {
191
193
  storageSettings = new StorageSettings(this, {
192
194
  // Basic connection settings
193
195
  ipAddress: {
@@ -197,12 +199,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
197
199
  await this.credentialsChanged();
198
200
  }
199
201
  },
200
- debugEvents: {
201
- title: 'Debug Events',
202
- type: 'boolean',
203
- immediate: true,
204
- hide: true,
205
- },
206
202
  username: {
207
203
  type: 'string',
208
204
  title: 'Username',
@@ -230,6 +226,53 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
230
226
  json: true,
231
227
  hide: true,
232
228
  },
229
+ // Multifocal composite stream PIP settings
230
+ pipPosition: {
231
+ title: 'PIP Position',
232
+ description: 'Position of the tele lens overlay on the wider lens view',
233
+ type: 'string',
234
+ defaultValue: 'bottom-right',
235
+ choices: [
236
+ 'top-left',
237
+ 'top-right',
238
+ 'bottom-left',
239
+ 'bottom-right',
240
+ 'center',
241
+ 'top-center',
242
+ 'bottom-center',
243
+ 'left-center',
244
+ 'right-center',
245
+ ],
246
+ hide: true, // Only show for multifocal devices via getAdditionalSettings
247
+ },
248
+ pipSize: {
249
+ title: 'PIP Size',
250
+ description: 'Relative size of the PIP overlay (0.1 = 10%, 0.3 = 30%, etc.)',
251
+ type: 'number',
252
+ defaultValue: 0.25,
253
+ hide: true, // Only show for multifocal devices via getAdditionalSettings
254
+ },
255
+ pipMargin: {
256
+ title: 'PIP Margin',
257
+ description: 'Margin from edge in pixels',
258
+ type: 'number',
259
+ defaultValue: 10,
260
+ hide: true, // Only show for multifocal devices via getAdditionalSettings
261
+ },
262
+ widerChannel: {
263
+ title: 'Wider Channel',
264
+ description: 'Channel number for wider lens (typically 0)',
265
+ type: 'number',
266
+ defaultValue: 0,
267
+ hide: true, // Only show for multifocal devices via getAdditionalSettings
268
+ },
269
+ teleChannel: {
270
+ title: 'Tele Channel',
271
+ description: 'Channel number for tele lens (typically 1)',
272
+ type: 'number',
273
+ defaultValue: 1,
274
+ hide: true, // Only show for multifocal devices via getAdditionalSettings
275
+ },
233
276
  // Battery camera specific
234
277
  uid: {
235
278
  title: 'UID',
@@ -240,6 +283,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
240
283
  await this.credentialsChanged();
241
284
  }
242
285
  },
286
+ debugLogs: {
287
+ title: 'Debug logs',
288
+ type: 'boolean',
289
+ immediate: true,
290
+ },
243
291
  mixinsSetup: {
244
292
  type: 'boolean',
245
293
  hide: true,
@@ -258,10 +306,10 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
258
306
  await this.subscribeToEvents();
259
307
  },
260
308
  },
261
- debugLogs: {
309
+ socketApiDebugLogs: {
262
310
  subgroup: 'Advanced',
263
- title: 'Debug Logs',
264
- description: 'Enable specific debug logs. Baichuan client logs require reconnect; event logs are immediate.',
311
+ title: 'Socket API Debug Logs',
312
+ description: 'Enable specific debug logs.',
265
313
  multiple: true,
266
314
  combobox: true,
267
315
  immediate: true,
@@ -508,6 +556,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
508
556
  floodlight?: ReolinkCameraFloodlight;
509
557
  pirSensor?: ReolinkCameraPirSensor;
510
558
 
559
+
560
+ private lastPicture: { mo: MediaObject; atMs: number } | undefined;
561
+ private takePictureInFlight: Promise<MediaObject> | undefined;
562
+ forceNewSnapshot: boolean = false;
563
+
511
564
  // Video stream properties
512
565
  protected cachedVideoStreamOptions?: UrlMediaStreamOptions[];
513
566
  protected fetchingStreams = false;
@@ -551,6 +604,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
551
604
  }, 2000);
552
605
  }
553
606
 
607
+ async reboot(): Promise<void> {
608
+ const api = await this.ensureBaichuanClient();
609
+ await api.reboot();
610
+ }
611
+
554
612
  // BaseBaichuanClass abstract methods implementation
555
613
  protected getConnectionConfig(): BaichuanConnectionConfig {
556
614
  const { ipAddress, username, password, uid } = this.storageSettings.values;
@@ -599,9 +657,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
599
657
  };
600
658
  }
601
659
 
602
-
603
660
  protected isDebugEnabled(): boolean {
604
- return this.isEventLogsEnabled();
661
+ return this.storageSettings.values.debugLogs;
605
662
  }
606
663
 
607
664
  protected getDeviceName(): string {
@@ -1029,11 +1086,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1029
1086
  }
1030
1087
  }
1031
1088
 
1032
- isEventLogsEnabled(): boolean {
1033
- const debugLogs = this.storageSettings.values.debugLogs || [];
1034
- return debugLogs.includes(DebugLogDisplayNames[DebugLogOption.EventLogs]);
1035
- }
1036
-
1037
1089
  // BinarySensor interface implementation (for doorbell)
1038
1090
  handleDoorbellEvent(): void {
1039
1091
  if (!this.doorbellBinaryTimeout) {
@@ -1114,9 +1166,59 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1114
1166
  await this.storageSettings.putSetting(key, value);
1115
1167
  }
1116
1168
 
1117
- // Camera interface methods (must be implemented by subclasses)
1118
- abstract takePicture(options?: any): Promise<MediaObject>;
1119
- abstract getPictureOptions(): Promise<any[]>;
1169
+ async takePicture(options?: RequestPictureOptions) {
1170
+ if (this.protocol === 'tcp') {
1171
+ try {
1172
+ return this.withBaichuanRetry(async () => {
1173
+ const client = await this.ensureClient();
1174
+ const snapshotBuffer = await client.getSnapshot(this.storageSettings.values.rtspChannel);
1175
+ const mo = await this.createMediaObject(snapshotBuffer, 'image/jpeg');
1176
+
1177
+ return mo;
1178
+ });
1179
+ } catch (e) {
1180
+ this.getBaichuanLogger().error('Error taking snapshot', e);
1181
+ throw e;
1182
+ }
1183
+ } else {
1184
+ const logger = this.getBaichuanLogger();
1185
+ const shouldTakeNewSnapshot = this.forceNewSnapshot;
1186
+
1187
+ if (!shouldTakeNewSnapshot && this.lastPicture) {
1188
+ logger.log(`Returning cached snapshot, taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
1189
+ return this.lastPicture.mo;
1190
+ }
1191
+
1192
+ if (this.takePictureInFlight) {
1193
+ return await this.takePictureInFlight;
1194
+ }
1195
+
1196
+ logger.log(`Taking new snapshot from camera (forceNewSnapshot: ${this.forceNewSnapshot})`);
1197
+ this.forceNewSnapshot = false;
1198
+
1199
+ this.takePictureInFlight = (async () => {
1200
+ const channel = this.storageSettings.values.rtspChannel;
1201
+ const snapshotBuffer = await this.withBaichuanClient(async (api) => {
1202
+ return await api.getSnapshot(channel);
1203
+ });
1204
+ const mo = await sdk.mediaManager.createMediaObject(snapshotBuffer, 'image/jpeg');
1205
+ this.lastPicture = { mo, atMs: Date.now() };
1206
+ logger.log(`Snapshot taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
1207
+ return mo;
1208
+ })();
1209
+
1210
+ try {
1211
+ return await this.takePictureInFlight;
1212
+ }
1213
+ finally {
1214
+ this.takePictureInFlight = undefined;
1215
+ }
1216
+ }
1217
+ }
1218
+
1219
+ async getPictureOptions(): Promise<ResponsePictureOptions[]> {
1220
+ return [];
1221
+ }
1120
1222
 
1121
1223
  // Intercom interface methods
1122
1224
  async startIntercom(media: MediaObject): Promise<void> {
@@ -1342,12 +1444,15 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1342
1444
 
1343
1445
  const client = await this.ensureClient();
1344
1446
 
1345
- const { rtspChannel } = this.storageSettings.values;
1447
+ // For multifocal devices, use undefined channel to get composite streams
1448
+ const isMultiFocal = this.options.type === 'multi-focal' || this.options.type === 'multi-focal-battery';
1449
+ const channel = isMultiFocal ? undefined : this.storageSettings.values.rtspChannel;
1346
1450
 
1347
1451
  try {
1348
- const { nativeStreams, rtmpStreams, rtspStreams } = await client.buildVideoStreamOptions(rtspChannel);
1452
+ const { nativeStreams, rtmpStreams, rtspStreams } = await client.buildVideoStreamOptions(channel);
1349
1453
 
1350
1454
  let supportedStreams: ReolinkSupportedStream[] = [];
1455
+ // Homehub RTMP is not efficient, crashes, offers native streams to not overload the hub
1351
1456
  if (this.nvrDevice && this.nvrDevice.info.model === 'HOMEHUB') {
1352
1457
  supportedStreams = [...nativeStreams, ...rtspStreams, ...rtmpStreams];
1353
1458
  } else {
@@ -1408,6 +1513,28 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1408
1513
  throw new Error('StreamManager not initialized');
1409
1514
  }
1410
1515
 
1516
+ // Check if this is a composite stream request (for multifocal devices)
1517
+ const isComposite = selected.id?.startsWith('composite_');
1518
+ if (isComposite && this.options && (this.options.type === 'multi-focal' || this.options.type === 'multi-focal-battery')) {
1519
+ const profile = parseStreamProfileFromId(selected.id.replace('composite_', '')) || 'main';
1520
+ const streamKey = `composite_${profile}`;
1521
+ const expectedVideoType = expectedVideoTypeFromUrlMediaStreamOptions(selected);
1522
+
1523
+ const createStreamFn = async () => {
1524
+ return await createRfc4571CompositeMediaObjectFromStreamManager({
1525
+ streamManager: this.streamManager!,
1526
+ profile,
1527
+ streamKey,
1528
+ expectedVideoType,
1529
+ selected,
1530
+ sourceId: this.id,
1531
+ });
1532
+ };
1533
+
1534
+ return await this.withBaichuanRetry(createStreamFn);
1535
+ }
1536
+
1537
+ // Regular stream for single channel
1411
1538
  const profile = parseStreamProfileFromId(selected.id) || 'main';
1412
1539
  const channel = this.storageSettings.values.rtspChannel;
1413
1540
  const streamKey = `${channel}_${profile}`;
@@ -1539,17 +1666,22 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1539
1666
  }
1540
1667
 
1541
1668
  const { username, password } = this.storageSettings.values;
1542
- const isCamera = this.options.type === 'regular' || this.options.type === 'battery';
1543
- const isBatteryCamera = this.options.type === 'battery';
1544
- const isBatteryMultiFocal = this.options.type === 'multi-focal-battery';
1545
- const isBattery = isBatteryCamera || isBatteryMultiFocal;
1669
+ const isBattery = ['multi-focal-battery', 'battery'].includes(this.options.type);
1670
+ const isMultiFocal = ['multi-focal', 'multi-focal'].includes(this.options.type);
1546
1671
 
1547
1672
  this.storageSettings.settings.uid.hide = !isBattery;
1548
1673
  this.storageSettings.settings.batteryUpdateIntervalMinutes.hide = !isBattery;
1549
1674
  this.storageSettings.settings.lowThresholdBatteryRecording.hide = !isBattery;
1550
1675
  this.storageSettings.settings.highThresholdBatteryRecording.hide = !isBattery;
1551
1676
 
1552
- if (isBatteryCamera && !this.storageSettings.values.mixinsSetup) {
1677
+ // Show PIP settings only for multifocal devices
1678
+ this.storageSettings.settings.pipPosition.hide = !isMultiFocal;
1679
+ this.storageSettings.settings.pipSize.hide = !isMultiFocal;
1680
+ this.storageSettings.settings.pipMargin.hide = !isMultiFocal;
1681
+ this.storageSettings.settings.widerChannel.hide = !isMultiFocal;
1682
+ this.storageSettings.settings.teleChannel.hide = !isMultiFocal;
1683
+
1684
+ if (isBattery && !this.storageSettings.values.mixinsSetup) {
1553
1685
  try {
1554
1686
  const device = sdk.systemManager.getDeviceById<Settings>(this.id);
1555
1687
  if (device) {
@@ -1571,39 +1703,37 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1571
1703
  logger.warn('Failed to subscribe to Baichuan events', e);
1572
1704
  }
1573
1705
 
1574
- if (isCamera) {
1575
- this.streamManager = new StreamManager({
1576
- createStreamClient: () => this.createStreamClient(),
1577
- getLogger: () => logger as Console,
1578
- credentials: {
1579
- username,
1580
- password
1581
- },
1582
- sharedConnection: isBattery,
1583
- });
1706
+ this.streamManager = new StreamManager({
1707
+ createStreamClient: () => this.createStreamClient(),
1708
+ getLogger: () => logger,
1709
+ credentials: {
1710
+ username,
1711
+ password
1712
+ },
1713
+ sharedConnection: isBattery,
1714
+ });
1584
1715
 
1585
- const { hasIntercom, hasPtz } = this.getAbilities();
1716
+ const { hasIntercom, hasPtz } = this.getAbilities();
1586
1717
 
1587
- if (hasIntercom) {
1588
- this.intercom = new ReolinkBaichuanIntercom(this);
1589
- }
1718
+ if (hasIntercom) {
1719
+ this.intercom = new ReolinkBaichuanIntercom(this);
1720
+ }
1590
1721
 
1591
- if (hasPtz) {
1592
- const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
1722
+ if (hasPtz) {
1723
+ const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
1593
1724
 
1594
- this.storageSettings.settings.presets.choices = choices;
1595
- this.storageSettings.settings.ptzSelectedPreset.choices = choices;
1725
+ this.storageSettings.settings.presets.choices = choices;
1726
+ this.storageSettings.settings.ptzSelectedPreset.choices = choices;
1596
1727
 
1597
- this.storageSettings.settings.presets.hide = false;
1598
- this.storageSettings.settings.ptzMoveDurationMs.hide = false;
1599
- this.storageSettings.settings.ptzZoomStep.hide = false;
1600
- this.storageSettings.settings.ptzCreatePreset.hide = false;
1601
- this.storageSettings.settings.ptzSelectedPreset.hide = false;
1602
- this.storageSettings.settings.ptzUpdateSelectedPreset.hide = false;
1603
- this.storageSettings.settings.ptzDeleteSelectedPreset.hide = false;
1728
+ this.storageSettings.settings.presets.hide = false;
1729
+ this.storageSettings.settings.ptzMoveDurationMs.hide = false;
1730
+ this.storageSettings.settings.ptzZoomStep.hide = false;
1731
+ this.storageSettings.settings.ptzCreatePreset.hide = false;
1732
+ this.storageSettings.settings.ptzSelectedPreset.hide = false;
1733
+ this.storageSettings.settings.ptzUpdateSelectedPreset.hide = false;
1734
+ this.storageSettings.settings.ptzDeleteSelectedPreset.hide = false;
1604
1735
 
1605
- this.updatePtzCaps();
1606
- }
1736
+ this.updatePtzCaps();
1607
1737
  }
1608
1738
 
1609
1739
  if (this.nvrDevice || this.multiFocalDevice) {
@@ -18,10 +18,6 @@ export enum DebugLogOption {
18
18
  DebugH264 = 'debugH264',
19
19
  /** SPS/PPS parameter sets debug logs */
20
20
  DebugParamSets = 'debugParamSets',
21
- /** Event logs (plugin-specific, not passed to API) */
22
- EventLogs = 'eventLogs',
23
- /** Battery info logs (plugin-specific, not passed to API) */
24
- BatteryInfo = 'batteryInfo',
25
21
  }
26
22
 
27
23
  /**
@@ -36,8 +32,6 @@ export function mapDebugLogToApiOption(option: DebugLogOption): keyof DebugOptio
36
32
  [DebugLogOption.TraceEvents]: 'traceEvents',
37
33
  [DebugLogOption.DebugH264]: 'debugH264',
38
34
  [DebugLogOption.DebugParamSets]: 'debugParamSets',
39
- [DebugLogOption.EventLogs]: null, // Plugin-specific, not passed to API
40
- [DebugLogOption.BatteryInfo]: null, // Plugin-specific, not passed to API
41
35
  };
42
36
  return mapping[option];
43
37
  }
@@ -88,8 +82,6 @@ export const DebugLogDisplayNames: Record<DebugLogOption, string> = {
88
82
  [DebugLogOption.TraceEvents]: 'Trace events XML',
89
83
  [DebugLogOption.DebugH264]: 'H264',
90
84
  [DebugLogOption.DebugParamSets]: 'Video param sets',
91
- [DebugLogOption.EventLogs]: 'Object detection events',
92
- [DebugLogOption.BatteryInfo]: 'Battery info update',
93
85
  };
94
86
 
95
87
  /**
package/src/main.ts CHANGED
@@ -57,7 +57,10 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
57
57
  },
58
58
  );
59
59
 
60
- this.console.log(`[AutoDetect] Detected device type ${detection.type} on transport ${detection.transport}: ${JSON.stringify(detection.deviceInfo)}`);
60
+ this.console.log(`[AutoDetect] Detected device type: ${detection.type} (transport: ${detection.transport})`);
61
+
62
+ // Use the API that was successfully used for detection
63
+ const detectedApi = detection.api;
61
64
 
62
65
  // Handle multi-focal device case
63
66
  if (detection.type === 'multifocal') {
@@ -69,18 +72,12 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
69
72
 
70
73
  settings.newCamera ||= name;
71
74
 
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
- }
75
+ const { capabilities, objects, presets } = await detectedApi.getDeviceCapabilities();
76
+
77
+ const { interfaces } = getDeviceInterfaces({
78
+ capabilities,
79
+ logger: this.console,
80
+ });
84
81
 
85
82
  await sdk.deviceManager.onDeviceDiscovered({
86
83
  nativeId,
@@ -94,10 +91,13 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
94
91
  if (!(device instanceof ReolinkNativeMultiFocalDevice)) {
95
92
  throw new Error('Expected multi-focal device but got different type');
96
93
  }
94
+ device.classes = objects;
95
+ device.presets = presets;
97
96
  device.storageSettings.values.ipAddress = ipAddress;
98
97
  device.storageSettings.values.username = username;
99
98
  device.storageSettings.values.password = password;
100
99
  device.storageSettings.values.uid = detection.uid || '';
100
+ device.storageSettings.values.capabilities = capabilities;
101
101
 
102
102
  return nativeId;
103
103
  }
@@ -149,22 +149,10 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
149
149
 
150
150
  settings.newCamera ||= name;
151
151
 
152
- // Create API connection to get capabilities
153
- const api = await createBaichuanApi({
154
- inputs: {
155
- host: ipAddress,
156
- username,
157
- password,
158
- uid: detection.uid,
159
- logger: this.console,
160
- },
161
- transport: detection.transport,
162
- });
163
-
152
+ // Use the API that was successfully used for detection
164
153
  try {
165
- await api.login();
166
154
  const rtspChannel = 0;
167
- const { capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel);
155
+ const { capabilities, objects, presets } = await detectedApi.getDeviceCapabilities(rtspChannel);
168
156
 
169
157
  const { interfaces, type } = getDeviceInterfaces({
170
158
  capabilities,
@@ -197,9 +185,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
197
185
  this.console.error('Error adding Reolink device', e);
198
186
  throw e;
199
187
  }
200
- finally {
201
- await api.close();
202
- }
203
188
  }
204
189
 
205
190
  async releaseDevice(id: string, nativeId: ScryptedNativeId): Promise<void> {