@apocaliss92/scrypted-reolink-native 0.1.22 → 0.1.24

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.22",
3
+ "version": "0.1.24",
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",
@@ -9,6 +9,8 @@ import {
9
9
  } from "./common";
10
10
  import { DebugLogOption } from "./debug-options";
11
11
  import type ReolinkNativePlugin from "./main";
12
+ import { ReolinkNativeNvrDevice } from "./nvr";
13
+ import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
12
14
 
13
15
  export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
14
16
  private lastPicture: { mo: MediaObject; atMs: number } | undefined;
@@ -28,10 +30,16 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
28
30
  return debugLogs.includes(DebugLogOption.BatteryInfo);
29
31
  }
30
32
 
31
- constructor(nativeId: string, public plugin: ReolinkNativePlugin, nvrDevice?: any) {
33
+ constructor(
34
+ nativeId: string,
35
+ public plugin: ReolinkNativePlugin,
36
+ nvrDevice?: ReolinkNativeNvrDevice,
37
+ multiFocalDevice?: ReolinkNativeMultiFocalDevice
38
+ ) {
32
39
  super(nativeId, plugin, {
33
40
  type: 'battery',
34
41
  nvrDevice,
42
+ multiFocalDevice,
35
43
  });
36
44
  }
37
45
 
package/src/camera.ts CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  } from "./common";
7
7
  import { createBaichuanApi } from './connect';
8
8
  import ReolinkNativePlugin from "./main";
9
+ import { ReolinkNativeNvrDevice } from "./nvr";
10
+ import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
9
11
 
10
12
  export const moToB64 = async (mo: MediaObject) => {
11
13
  const bufferImage = await sdk.mediaManager.convertMediaObjectToBuffer(mo, 'image/jpeg');
@@ -28,10 +30,16 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
28
30
  private statusPollTimer: NodeJS.Timeout | undefined;
29
31
 
30
32
 
31
- constructor(nativeId: string, public plugin: ReolinkNativePlugin, nvrDevice?: any) {
33
+ constructor(
34
+ nativeId: string,
35
+ public plugin: ReolinkNativePlugin,
36
+ nvrDevice?: ReolinkNativeNvrDevice,
37
+ multiFocalDevice?: ReolinkNativeMultiFocalDevice
38
+ ) {
32
39
  super(nativeId, plugin, {
33
40
  type: 'regular',
34
41
  nvrDevice,
42
+ multiFocalDevice,
35
43
  });
36
44
  }
37
45
 
@@ -109,10 +117,6 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
109
117
  return api;
110
118
  }
111
119
 
112
- getClient(): ReolinkBaichuanApi | undefined {
113
- return this.baichuanApi;
114
- }
115
-
116
120
  private passiveRefreshTimer: ReturnType<typeof setTimeout> | undefined;
117
121
 
118
122
  async release() {
package/src/common.ts CHANGED
@@ -8,7 +8,7 @@ import { convertDebugLogsToApiOptions, DebugLogDisplayNames, DebugLogOption, get
8
8
  import { ReolinkBaichuanIntercom } from "./intercom";
9
9
  import ReolinkNativePlugin from "./main";
10
10
  import { ReolinkNativeNvrDevice } from "./nvr";
11
- import { ReolinkNativeMultiFocalDevice } from "./multifocal";
11
+ import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
12
12
  import { ReolinkPtzPresets } from "./presets";
13
13
  import {
14
14
  createRfc4571MediaObjectFromStreamManager,
@@ -526,7 +526,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
526
526
  protected multiFocalDevice?: ReolinkNativeMultiFocalDevice;
527
527
  thisDevice: Settings
528
528
 
529
- constructor(nativeId: string, public plugin: ReolinkNativePlugin, public options: CommonCameraMixinOptions) {
529
+ constructor(
530
+ nativeId: string,
531
+ public plugin: ReolinkNativePlugin,
532
+ public options: CommonCameraMixinOptions
533
+ ) {
530
534
  super(nativeId);
531
535
  this.protocol = !options.nvrDevice && !options.multiFocalDevice && options.type === 'battery' ? 'udp' : 'tcp';
532
536
 
@@ -644,12 +648,17 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
644
648
  }
645
649
  }
646
650
  }
651
+
647
652
  createStreamClient(): Promise<ReolinkBaichuanApi> {
648
653
  throw new Error("Method not implemented.");
649
654
  }
650
655
 
651
656
  public getAbilities(): DeviceCapabilities {
652
- return this.storageSettings.values.capabilities;
657
+ if(this.options.multiFocalDevice) {
658
+ return this.options.multiFocalDevice.getInterfaces(this.storageSettings.values.rtspChannel).capabilities;
659
+ } else {
660
+ return this.storageSettings.values.capabilities;
661
+ }
653
662
  }
654
663
 
655
664
  getBaichuanDebugOptions(): any | undefined {
@@ -1357,21 +1366,16 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1357
1366
  const vsos = await this.getVideoStreamOptions();
1358
1367
  const selected = selectStreamOption(vsos, vso);
1359
1368
 
1360
- // Check if this is a native stream (prefixed with "native_")
1361
-
1362
- // If stream has RTSP/RTMP URL (not native), add credentials and create MediaStreamUrl
1363
1369
  if (selected.url && (selected.container === 'rtsp' || selected.container === 'rtmp')) {
1364
1370
  const urlWithCredentials = this.addRtspCredentials(selected.url);
1365
1371
  const ret: MediaStreamUrl = {
1366
1372
  container: selected.container,
1367
- // url: selected.url,
1368
1373
  url: urlWithCredentials,
1369
1374
  mediaStreamOptions: selected,
1370
1375
  };
1371
1376
  return await this.createMediaObject(ret, ScryptedMimeTypes.MediaStreamUrl);
1372
1377
  }
1373
1378
 
1374
- // Use streamManager for native Baichuan streams (native_* or streams without URL)
1375
1379
  if (!this.streamManager) {
1376
1380
  throw new Error('StreamManager not initialized');
1377
1381
  }
@@ -1406,9 +1410,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1406
1410
  return await this.withBaichuanRetry(createStreamFn);
1407
1411
  }
1408
1412
 
1409
- // Client management
1410
1413
  async ensureClient(): Promise<ReolinkBaichuanApi> {
1411
- // If camera is connected to NVR, use NVR's shared Baichuan connection
1412
1414
  if (this.nvrDevice) {
1413
1415
  return await this.nvrDevice.ensureBaichuanClient();
1414
1416
  }
package/src/connect.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { BaichuanClientOptions, ReolinkBaichuanApi } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
1
+ import type { BaichuanTransport as BaichuanTransportParent, BaichuanClientOptions, ReolinkBaichuanApi } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
2
2
 
3
- export type BaichuanTransport = "tcp" | "udp";
3
+ export type BaichuanTransport = BaichuanTransportParent;
4
4
 
5
5
  export type BaichuanConnectInputs = {
6
6
  host: string;
package/src/main.ts CHANGED
@@ -4,9 +4,10 @@ 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";
7
+ import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
8
8
  import { ReolinkNativeNvrDevice } from "./nvr";
9
9
  import { batteryCameraSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, nvrSuffix } from "./utils";
10
+ import { BaichuanTransport } from "./connect";
10
11
 
11
12
  class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
12
13
  devices = new Map<string, BaseBaichuanClass>();
@@ -73,7 +74,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
73
74
  name,
74
75
  interfaces: [
75
76
  ScryptedInterface.Settings,
76
- ScryptedInterface.DeviceDiscovery,
77
77
  ScryptedInterface.DeviceProvider,
78
78
  ScryptedInterface.Reboot,
79
79
  ],
@@ -89,6 +89,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
89
89
  device.storageSettings.values.username = username;
90
90
  device.storageSettings.values.password = password;
91
91
  device.storageSettings.values.uid = detection.uid || '';
92
+ device.storageSettings.values.protocol = detection.transport || 'tcp' as BaichuanTransport;
92
93
 
93
94
  return nativeId;
94
95
  }
@@ -237,7 +238,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
237
238
  } else if (nativeId.endsWith(nvrSuffix)) {
238
239
  return new ReolinkNativeNvrDevice(nativeId, this);
239
240
  } else if (nativeId.endsWith(multifocalSuffix)) {
240
- return new ReolinkNativeMultiFocalDevice(nativeId, this, 'tcp');
241
+ return new ReolinkNativeMultiFocalDevice(nativeId, this);
241
242
  } else {
242
243
  return new ReolinkNativeCamera(nativeId, this);
243
244
  }
@@ -0,0 +1,371 @@
1
+ import type { DeviceCapabilities, DualLensChannelAnalysis, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
2
+ import sdk, { Device, DeviceProvider, Reboot, ScryptedDeviceType, Setting, Settings, SettingValue } from "@scrypted/sdk";
3
+ import { StorageSettings } from "@scrypted/sdk/storage-settings";
4
+ import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
5
+ import { ReolinkNativeCamera } from "./camera";
6
+ import { ReolinkNativeBatteryCamera } from "./camera-battery";
7
+ import { normalizeUid } from "./connect";
8
+ import ReolinkNativePlugin from "./main";
9
+ import { batteryCameraSuffix, cameraSuffix, getDeviceInterfaces, updateDeviceInfo } from "./utils";
10
+
11
+ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements Settings, DeviceProvider, Reboot {
12
+ storageSettings = new StorageSettings(this, {
13
+ debugEvents: {
14
+ title: 'Debug Events',
15
+ type: 'boolean',
16
+ immediate: true,
17
+ },
18
+ ipAddress: {
19
+ title: 'IP address',
20
+ type: 'string',
21
+ onPut: async () => await this.reinit()
22
+ },
23
+ username: {
24
+ title: 'Username',
25
+ placeholder: 'admin',
26
+ defaultValue: 'admin',
27
+ type: 'string',
28
+ onPut: async () => await this.reinit()
29
+ },
30
+ password: {
31
+ title: 'Password',
32
+ type: 'password',
33
+ onPut: async () => await this.reinit()
34
+ },
35
+ uid: {
36
+ title: 'UID',
37
+ description: 'Reolink UID (required for UDP/battery multi-focal devices)',
38
+ type: 'string',
39
+ hide: true,
40
+ onPut: async () => await this.reinit()
41
+ },
42
+ protocol: {
43
+ type: 'string',
44
+ hide: true,
45
+ },
46
+ diagnosticsRun: {
47
+ subgroup: 'Diagnostics',
48
+ title: 'Run Diagnostics',
49
+ description: 'Collect diagnostics and display results in logs.',
50
+ type: 'button',
51
+ immediate: true,
52
+ onPut: async () => {
53
+ await this.runDiagnostics();
54
+ },
55
+ },
56
+ multifocalInfo: {
57
+ json: true,
58
+ hide: true,
59
+ },
60
+ capabilities: {
61
+ json: true,
62
+ hide: true,
63
+ }
64
+ });
65
+
66
+ plugin: ReolinkNativePlugin;
67
+ cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
68
+ private channelToNativeIdMap = new Map<number, string>();
69
+ private initReinitTimeout: NodeJS.Timeout | undefined;
70
+ isBattery: boolean;
71
+
72
+ constructor(nativeId: string, plugin: ReolinkNativePlugin) {
73
+ super(nativeId);
74
+ this.plugin = plugin;
75
+
76
+ this.isBattery = this.storageSettings.values.protocol === 'udp';
77
+
78
+ this.scheduleInit();
79
+ }
80
+
81
+ async reboot(): Promise<void> {
82
+ const api = await this.ensureBaichuanClient();
83
+ await api.reboot();
84
+ }
85
+
86
+ protected getConnectionConfig(): BaichuanConnectionConfig {
87
+ const { ipAddress, username, password, uid } = this.storageSettings.values;
88
+ if (!ipAddress || !username || !password) {
89
+ throw new Error('Missing device credentials');
90
+ }
91
+
92
+ const { protocol } = this.storageSettings.values;
93
+
94
+ const normalizedUid = this.isBattery ? normalizeUid(uid) : undefined;
95
+
96
+ if (protocol === 'udp' && !normalizedUid) {
97
+ throw new Error('UID is required for UDP multi-focal devices (BCUDP)');
98
+ }
99
+
100
+ return {
101
+ host: ipAddress,
102
+ username,
103
+ password,
104
+ uid: normalizedUid,
105
+ transport: protocol,
106
+ logger: this.console,
107
+ };
108
+ }
109
+
110
+ protected getConnectionCallbacks(): BaichuanConnectionCallbacks {
111
+ return {
112
+ onError: undefined, // Use default error handling
113
+ onClose: async () => {
114
+ // Reinit after cleanup
115
+ await this.reinit();
116
+ if (!this.isBattery) {
117
+ setTimeout(async () => {
118
+ try {
119
+ await this.subscribeToEvents();
120
+ } catch (e) {
121
+ const logger = this.getBaichuanLogger();
122
+ logger.warn('Failed to resubscribe to events after reconnection', e);
123
+ }
124
+ }, 1000);
125
+ }
126
+ },
127
+ onSimpleEvent: (ev) => this.forwardNativeEvent(ev),
128
+ getEventSubscriptionEnabled: () => true,
129
+ };
130
+ }
131
+
132
+ protected async onBeforeCleanup(): Promise<void> {
133
+ await this.unsubscribeFromAllEvents();
134
+ }
135
+
136
+ protected isDebugEnabled(): boolean {
137
+ return this.storageSettings.values.debugEvents;
138
+ }
139
+
140
+ protected getDeviceName(): string {
141
+ return this.name || 'Multi-Focal Device';
142
+ }
143
+
144
+ async reinit(): Promise<void> {
145
+ // Cancel any pending init/reinit
146
+ if (this.initReinitTimeout) {
147
+ clearTimeout(this.initReinitTimeout);
148
+ this.initReinitTimeout = undefined;
149
+ }
150
+
151
+ // Schedule reinit with debounce
152
+ this.scheduleInit(true);
153
+ }
154
+
155
+ private scheduleInit(isReinit: boolean = false): void {
156
+ // Cancel any pending init/reinit
157
+ if (this.initReinitTimeout) {
158
+ clearTimeout(this.initReinitTimeout);
159
+ }
160
+
161
+ this.initReinitTimeout = setTimeout(async () => {
162
+ const logger = this.getBaichuanLogger();
163
+ if (isReinit) {
164
+ logger.log('Reinitializing multi-focal device...');
165
+ await this.cleanupBaichuanApi();
166
+ }
167
+ await this.init();
168
+ this.initReinitTimeout = undefined;
169
+ }, isReinit ? 500 : 2000);
170
+ }
171
+
172
+ async init(): Promise<void> {
173
+ const logger = this.getBaichuanLogger();
174
+ try {
175
+ this.storageSettings.settings.uid.hide = !this.isBattery;
176
+
177
+ await this.ensureBaichuanClient();
178
+ await this.updateDeviceInfo();
179
+ await this.reportDevices();
180
+ await this.subscribeToEvents();
181
+ } catch (e) {
182
+ logger.error('Failed to initialize multi-focal device', e);
183
+ if (e instanceof Error) {
184
+ logger.error(`Error message: ${e.message}`);
185
+ logger.error(`Error stack: ${e.stack}`);
186
+ } else {
187
+ logger.error(`Error details: ${JSON.stringify(e)}`);
188
+ }
189
+ }
190
+ }
191
+
192
+ async updateDeviceInfo(): Promise<void> {
193
+ const logger = this.getBaichuanLogger();
194
+ try {
195
+ const api = await this.ensureBaichuanClient();
196
+ const deviceData = await api.getInfo();
197
+
198
+ await updateDeviceInfo({
199
+ device: this,
200
+ deviceData,
201
+ ipAddress: this.storageSettings.values.ipAddress,
202
+ });
203
+
204
+ logger.log(`Device info updated: ${JSON.stringify(deviceData)}`);
205
+ } catch (e) {
206
+ logger.warn('Failed to fetch device info', e);
207
+ }
208
+ }
209
+
210
+ getInterfaces(channel: number) {
211
+ const logger = this.getBaichuanLogger();
212
+ const { capabilities: caps, multifocalInfo } = this.storageSettings.values;
213
+ const channelInfo = (multifocalInfo as DualLensChannelAnalysis).channels.find(c => c.channel === channel);
214
+
215
+ const capabilities: DeviceCapabilities = {
216
+ ...caps,
217
+ hasPan: channelInfo.hasPan,
218
+ hasTilt: channelInfo.hasTilt,
219
+ hasZoom: channelInfo.hasZoom,
220
+ hasPresets: channelInfo.hasPresets,
221
+ hasIntercom: channelInfo.hasIntercom,
222
+ };
223
+
224
+ const { interfaces } = getDeviceInterfaces({
225
+ capabilities,
226
+ logger,
227
+ });
228
+
229
+ return { interfaces, capabilities };
230
+ }
231
+
232
+ async reportDevices(): Promise<void> {
233
+ const api = await this.ensureBaichuanClient();
234
+ const logger = this.getBaichuanLogger();
235
+ const { protocol, username, password, ipAddress, uid } = this.storageSettings.values;
236
+
237
+ const { capabilities, support, abilities, features, objects, presets } = await api.getDeviceCapabilities();
238
+
239
+ const multifocalInfo = await api.getDualLensChannelInfo();
240
+ logger.log(`Sync entities from remote for ${multifocalInfo.channels.length} channels`);
241
+
242
+ this.storageSettings.values.multifocalInfo = multifocalInfo;
243
+ this.storageSettings.values.capabilities = capabilities;
244
+
245
+ logger.debug(`Multichannel info: ${JSON.stringify({ multifocalInfo, capabilities, support, abilities, features, objects, presets })}`);
246
+
247
+ for (const channelInfo of multifocalInfo?.channels ?? []) {
248
+ const { channel, lensType } = channelInfo;
249
+
250
+ const name = `${this.name} - ${lensType}`;
251
+ const nativeId = this.buildNativeId(channel);
252
+
253
+ this.channelToNativeIdMap.set(channel, nativeId);
254
+ const { interfaces, capabilities: deviceCapabilities } = this.getInterfaces(channel);
255
+
256
+ const device: Device = {
257
+ providerNativeId: this.nativeId,
258
+ name,
259
+ nativeId,
260
+ info: {
261
+ ...this.info,
262
+ metadata: {
263
+ channel,
264
+ lensType
265
+ }
266
+ },
267
+ interfaces,
268
+ type: ScryptedDeviceType.Camera,
269
+ };
270
+
271
+ await sdk.deviceManager.onDeviceDiscovered(device);
272
+
273
+ const camera = await this.getDevice(nativeId);
274
+
275
+ camera.storageSettings.values.rtspChannel = channel;
276
+ camera.classes = objects;
277
+ camera.presets = presets;
278
+ camera.storageSettings.values.username = username;
279
+ camera.storageSettings.values.password = password;
280
+ camera.storageSettings.values.ipAddress = ipAddress;
281
+ camera.storageSettings.values.capabilities = deviceCapabilities;
282
+ if (this.isBattery) {
283
+ camera.storageSettings.values.uid = uid;
284
+ }
285
+ }
286
+ }
287
+
288
+ async getDevice(nativeId: string) {
289
+ let device = this.cameraNativeMap.get(nativeId);
290
+ if (!device) {
291
+ if (nativeId.endsWith(batteryCameraSuffix)) {
292
+ device = new ReolinkNativeBatteryCamera(nativeId, this.plugin, undefined, this);
293
+ } else {
294
+ device = new ReolinkNativeCamera(nativeId, this.plugin, undefined, this);
295
+ }
296
+ }
297
+
298
+ return device;
299
+ }
300
+
301
+ async getSettings(): Promise<Setting[]> {
302
+ const settings = await this.storageSettings.getSettings();
303
+ return settings;
304
+ }
305
+
306
+ async putSetting(key: string, value: SettingValue): Promise<void> {
307
+ return this.storageSettings.putSetting(key, value);
308
+ }
309
+
310
+ async releaseDevice(id: string, nativeId: string) {
311
+ this.cameraNativeMap.delete(nativeId);
312
+ }
313
+
314
+ buildNativeId(channel: number): string {
315
+ const { protocol } = this.storageSettings.values;
316
+ return `${this.nativeId}-channel${channel}${protocol === "udp" ? batteryCameraSuffix : cameraSuffix}`;
317
+ }
318
+
319
+ forwardNativeEvent(ev: ReolinkSimpleEvent): void {
320
+ const logger = this.getBaichuanLogger();
321
+ const channel = ev?.channel;
322
+
323
+ if (channel === undefined) {
324
+ logger.debug('Event missing channel, ignoring');
325
+ return;
326
+ }
327
+
328
+ const nativeId = this.channelToNativeIdMap.get(channel);
329
+ if (!nativeId) {
330
+ logger.debug(`No camera found for channel ${channel}, ignoring event`);
331
+ return;
332
+ }
333
+
334
+ const camera = this.cameraNativeMap.get(nativeId);
335
+ if (!camera) {
336
+ logger.debug(`Camera ${nativeId} not yet initialized, ignoring event`);
337
+ return;
338
+ }
339
+
340
+ // Forward event to camera
341
+ if (camera.onSimpleEvent) {
342
+ camera.onSimpleEvent(ev);
343
+ }
344
+ }
345
+ async unsubscribeFromAllEvents(): Promise<void> {
346
+ await super.unsubscribeFromEvents();
347
+ }
348
+
349
+ private async runDiagnostics(): Promise<void> {
350
+ const logger = this.getBaichuanLogger();
351
+ logger.log(`Starting Multifocal diagnostics...`);
352
+
353
+ try {
354
+ const { ipAddress, username, password } = this.storageSettings.values;
355
+ if (!ipAddress || !username || !password) {
356
+ throw new Error('Missing device credentials');
357
+ }
358
+
359
+ const api = await this.ensureBaichuanClient();
360
+
361
+ const multifocalDiagnostics = await api.collectMultifocalDiagnostics(logger);
362
+
363
+ logger.log(`NVR diagnostics completed successfully.`);
364
+ logger.log(JSON.stringify(multifocalDiagnostics));
365
+ } catch (e) {
366
+ logger.error('Failed to run NVR diagnostics', e);
367
+ throw e;
368
+ }
369
+ }
370
+ }
371
+
package/src/nvr.ts CHANGED
@@ -124,12 +124,10 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
124
124
  }
125
125
 
126
126
  protected async onBeforeCleanup(): Promise<void> {
127
- // Unsubscribe from events if needed
128
127
  await this.unsubscribeFromAllEvents();
129
128
  }
130
129
 
131
130
  async reinit() {
132
- // Cancel any pending init/reinit
133
131
  if (this.initReinitTimeout) {
134
132
  clearTimeout(this.initReinitTimeout);
135
133
  this.initReinitTimeout = undefined;
@@ -307,7 +305,6 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
307
305
  if (eventSource !== 'Native') {
308
306
  await this.unsubscribeFromAllEvents();
309
307
  } else {
310
-
311
308
  this.subscribeToAllEvents().catch((e) => {
312
309
  logger.warn('Failed to subscribe to Native events', e);
313
310
  });
@@ -343,7 +340,6 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
343
340
 
344
341
  await this.updateDeviceInfo();
345
342
 
346
- // Initialize event subscriptions based on selected source
347
343
  await this.reinitEventSubscriptions();
348
344
 
349
345
  setInterval(async () => {