@apocaliss92/scrypted-reolink-native 0.1.17 → 0.1.18

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.17",
3
+ "version": "0.1.18",
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",
package/src/connect.ts CHANGED
@@ -175,7 +175,7 @@ export async function autoDetectDeviceType(
175
175
 
176
176
  // Get device info to check device type
177
177
  const deviceInfo = await tcpApi.getInfo();
178
- const { support } = await tcpApi.getDeviceCapabilities(0);
178
+ const { support } = await tcpApi.getDeviceCapabilities();
179
179
  const channelNum = support?.channelNum ?? 1;
180
180
 
181
181
  logger.log(`[AutoDetect] TCP connection successful. channelNum=${channelNum}`);
@@ -248,7 +248,7 @@ export async function autoDetectDeviceType(
248
248
  await udpApi.login();
249
249
 
250
250
  const deviceInfo = await udpApi.getInfo();
251
- const { support } = await udpApi.getDeviceCapabilities(0);
251
+ const { support } = await udpApi.getDeviceCapabilities();
252
252
  const channelNum = support?.channelNum ?? 1;
253
253
 
254
254
  // Multi-focal devices can also be UDP (battery multi-focal cameras)
package/src/multifocal.ts CHANGED
@@ -61,15 +61,14 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
61
61
  cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
62
62
  private channelToNativeIdMap = new Map<number, string>();
63
63
  processing = false;
64
+ private initReinitTimeout: NodeJS.Timeout | undefined;
64
65
 
65
66
  constructor(nativeId: string, plugin: ReolinkNativePlugin, transport: BaichuanTransport = 'tcp') {
66
67
  super(nativeId);
67
68
  this.plugin = plugin;
68
69
  this.protocol = transport;
69
70
 
70
- setTimeout(async () => {
71
- await this.init();
72
- }, 2000);
71
+ this.scheduleInit();
73
72
  }
74
73
 
75
74
  async reboot(): Promise<void> {
@@ -121,10 +120,31 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
121
120
  }
122
121
 
123
122
  async reinit(): Promise<void> {
124
- const logger = this.getBaichuanLogger();
125
- logger.log('Reinitializing multi-focal device...');
126
- await this.cleanupBaichuanApi();
127
- await this.init();
123
+ // Cancel any pending init/reinit
124
+ if (this.initReinitTimeout) {
125
+ clearTimeout(this.initReinitTimeout);
126
+ this.initReinitTimeout = undefined;
127
+ }
128
+
129
+ // Schedule reinit with debounce
130
+ this.scheduleInit(true);
131
+ }
132
+
133
+ private scheduleInit(isReinit: boolean = false): void {
134
+ // Cancel any pending init/reinit
135
+ if (this.initReinitTimeout) {
136
+ clearTimeout(this.initReinitTimeout);
137
+ }
138
+
139
+ this.initReinitTimeout = setTimeout(async () => {
140
+ const logger = this.getBaichuanLogger();
141
+ if (isReinit) {
142
+ logger.log('Reinitializing multi-focal device...');
143
+ await this.cleanupBaichuanApi();
144
+ }
145
+ await this.init();
146
+ this.initReinitTimeout = undefined;
147
+ }, isReinit ? 500 : 2000);
128
148
  }
129
149
 
130
150
  async init(): Promise<void> {
@@ -132,13 +152,29 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
132
152
  try {
133
153
  // Update UID setting visibility based on transport
134
154
  this.storageSettings.settings.uid.hide = this.protocol === 'tcp';
135
-
155
+
156
+ logger.debug('Initializing: ensuring Baichuan client...');
136
157
  await this.ensureBaichuanClient();
158
+
159
+ logger.debug('Initializing: updating device info...');
137
160
  await this.updateDeviceInfo();
161
+
162
+ logger.debug('Initializing: subscribing to events...');
138
163
  await this.subscribeToEvents();
164
+
165
+ logger.debug('Initializing: discovering devices...');
139
166
  await this.discoverDevices(true);
167
+
168
+ logger.log('Initialization completed successfully');
140
169
  } catch (e) {
141
170
  logger.error('Failed to initialize multi-focal device', e);
171
+ // Log more details about the error
172
+ if (e instanceof Error) {
173
+ logger.error(`Error message: ${e.message}`);
174
+ logger.error(`Error stack: ${e.stack}`);
175
+ } else {
176
+ logger.error(`Error details: ${JSON.stringify(e)}`);
177
+ }
142
178
  }
143
179
  }
144
180
 
@@ -164,61 +200,76 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
164
200
  const api = await this.ensureBaichuanClient();
165
201
  const logger = this.getBaichuanLogger();
166
202
 
167
- const { devicesData, channels } = await (api as any).getDevicesInfo();
168
- logger.log(`Sync entities from remote for ${channels.length} channels`);
169
-
170
- // Process each channel that was successfully discovered
171
- for (const channel of channels) {
172
- try {
173
- const { channelStatus, channelInfo, abilities } = devicesData[channel];
174
- const name = channelStatus?.name || `Channel ${channel}`;
175
- const uid = channelStatus?.uid;
176
- const isBattery = !!(abilities?.battery?.ver ?? 0);
177
-
178
- const nativeId = this.buildNativeId(channel, uid, isBattery);
179
- const interfaces = [ScryptedInterface.VideoCamera];
180
- if (isBattery) {
181
- interfaces.push(ScryptedInterface.Battery);
182
- }
183
- const type = abilities.supportDoorbellLight ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera;
184
-
185
- const device: Device = {
186
- nativeId,
187
- name,
188
- providerNativeId: this.nativeId,
189
- interfaces,
190
- type,
191
- info: {
192
- manufacturer: 'Reolink',
193
- model: channelInfo?.typeInfo,
194
- serialNumber: uid,
195
- }
196
- };
197
-
198
- this.channelToNativeIdMap.set(channel, nativeId);
199
-
200
- if (sdk.deviceManager.getNativeIds().includes(nativeId)) {
201
- continue;
202
- }
203
-
204
- if (this.discoveredDevices.has(nativeId)) {
205
- continue;
206
- }
207
-
208
- this.discoveredDevices.set(nativeId, {
209
- device,
210
- description: `${name} (Channel ${channel})`,
211
- rtspChannel: channel,
212
- deviceData: devicesData[channel],
213
- });
214
-
215
- logger.debug(`Discovered channel ${channel}: ${name}`);
216
- } catch (e: any) {
217
- logger.debug(`Error processing channel ${channel}: ${e?.message || String(e)}`);
203
+ try {
204
+ const channelsInfo = await api.getNvrChannelsInfo();
205
+ const deviceInfo = await api.getInfo();
206
+ const { support } = await api.getDeviceCapabilities();
207
+ const channelNum = support?.channelNum ?? 1;
208
+ logger.log(`Sync entities from remote for ${channelNum} channels`);
209
+ const channels = Array.from({ length: channelNum }, (_, i) => i + 1);
210
+
211
+ logger.log(JSON.stringify({ channelsInfo, deviceInfo, channels }));
212
+
213
+ // for (const channel of channels) {
214
+ // try {
215
+ // const name = deviceInfo?.name || `Channel ${channel}`;
216
+ // const uid = deviceInfo?.uid;
217
+ // const isBattery = !!(abilities?.battery?.ver ?? 0);
218
+
219
+ // const nativeId = this.buildNativeId(channel, uid, isBattery);
220
+ // const interfaces = [ScryptedInterface.VideoCamera];
221
+ // if (isBattery) {
222
+ // interfaces.push(ScryptedInterface.Battery);
223
+ // }
224
+ // const type = abilities.supportDoorbellLight ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera;
225
+
226
+ // const device: Device = {
227
+ // nativeId,
228
+ // name,
229
+ // providerNativeId: this.nativeId,
230
+ // interfaces,
231
+ // type,
232
+ // info: {
233
+ // manufacturer: 'Reolink',
234
+ // model: channelInfo?.typeInfo,
235
+ // serialNumber: uid,
236
+ // }
237
+ // };
238
+
239
+ // this.channelToNativeIdMap.set(channel, nativeId);
240
+
241
+ // if (sdk.deviceManager.getNativeIds().includes(nativeId)) {
242
+ // continue;
243
+ // }
244
+
245
+ // if (this.discoveredDevices.has(nativeId)) {
246
+ // continue;
247
+ // }
248
+
249
+ // this.discoveredDevices.set(nativeId, {
250
+ // device,
251
+ // description: `${name} (Channel ${channel})`,
252
+ // rtspChannel: channel,
253
+ // deviceData: devicesData[channel],
254
+ // });
255
+
256
+ // logger.debug(`Discovered channel ${channel}: ${name}`);
257
+ // } catch (e: any) {
258
+ // logger.debug(`Error processing channel ${channel}: ${e?.message || String(e)}`);
259
+ // }
260
+ // }
261
+
262
+ // logger.log(`Channel discovery completed. ${JSON.stringify({ devicesData, channels })}`);
263
+ } catch (e) {
264
+ logger.error('Failed to sync entities from remote', e);
265
+ if (e instanceof Error) {
266
+ logger.error(`Error in syncEntitiesFromRemote: ${e.message}`);
267
+ logger.error(`Stack: ${e.stack}`);
268
+ } else {
269
+ logger.error(`Error details: ${JSON.stringify(e)}`);
218
270
  }
271
+ throw e;
219
272
  }
220
-
221
- logger.log(`Channel discovery completed. ${JSON.stringify({ devicesData, channels })}`);
222
273
  }
223
274
 
224
275
  async discoverDevices(scan?: boolean): Promise<DiscoveredDevice[]> {
@@ -278,7 +329,7 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
278
329
  device.storageSettings.values.password = this.storageSettings.values.password;
279
330
  device.storageSettings.values.rtspChannel = entry.rtspChannel;
280
331
  // Set multiFocalDevice reference through options (similar to how NVR does it)
281
- (device as any).options = { ...(device as any).options, multiFocalDevice: this, nvrDevice: undefined };
332
+ (device).options = { ...(device).options, multiFocalDevice: this, nvrDevice: undefined };
282
333
  device.classes = objects;
283
334
  device.presets = presets;
284
335
  }
@@ -386,7 +437,7 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
386
437
  });
387
438
 
388
439
  logger.log(`NVR diagnostics completed successfully.`);
389
-
440
+
390
441
  // Print diagnostics to console
391
442
  cgiApi.printNvrDiagnostics(diagnostics, this.console);
392
443
  } catch (e) {
package/src/nvr.ts CHANGED
@@ -69,14 +69,13 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
69
69
  cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
70
70
  private channelToNativeIdMap = new Map<number, string>();
71
71
  processing = false;
72
+ private initReinitTimeout: NodeJS.Timeout | undefined;
72
73
 
73
74
  constructor(nativeId: string, plugin: ReolinkNativePlugin) {
74
75
  super(nativeId);
75
76
  this.plugin = plugin;
76
77
 
77
- setTimeout(async () => {
78
- await this.init();
79
- }, 2000);
78
+ this.scheduleInit();
80
79
  }
81
80
 
82
81
  async reboot(): Promise<void> {
@@ -130,18 +129,40 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
130
129
  }
131
130
 
132
131
  async reinit() {
133
- // Cleanup CGI API
134
- if (this.nvrApi) {
135
- try {
136
- await this.nvrApi.logout();
137
- } catch {
138
- // ignore
139
- }
132
+ // Cancel any pending init/reinit
133
+ if (this.initReinitTimeout) {
134
+ clearTimeout(this.initReinitTimeout);
135
+ this.initReinitTimeout = undefined;
136
+ }
137
+
138
+ // Schedule reinit with debounce
139
+ this.scheduleInit(true);
140
+ }
141
+
142
+ private scheduleInit(isReinit: boolean = false): void {
143
+ // Cancel any pending init/reinit
144
+ if (this.initReinitTimeout) {
145
+ clearTimeout(this.initReinitTimeout);
140
146
  }
141
- this.nvrApi = undefined;
142
147
 
143
- // Cleanup Baichuan API (this handles all listeners and connection)
144
- await super.cleanupBaichuanApi();
148
+ this.initReinitTimeout = setTimeout(async () => {
149
+ if (isReinit) {
150
+ // Cleanup CGI API
151
+ if (this.nvrApi) {
152
+ try {
153
+ await this.nvrApi.logout();
154
+ } catch {
155
+ // ignore
156
+ }
157
+ }
158
+ this.nvrApi = undefined;
159
+
160
+ // Cleanup Baichuan API (this handles all listeners and connection)
161
+ await super.cleanupBaichuanApi();
162
+ }
163
+ await this.init();
164
+ this.initReinitTimeout = undefined;
165
+ }, isReinit ? 500 : 2000);
145
166
  }
146
167
 
147
168
  async ensureClient(): Promise<ReolinkCgiApi> {