@apocaliss92/scrypted-reolink-native 0.1.25 → 0.1.27

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/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
  }
package/src/multiFocal.ts CHANGED
@@ -1,110 +1,50 @@
1
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";
2
+ import sdk, { Device, DeviceProvider, MediaObject, Reboot, ScryptedDeviceType, Setting, Settings, SettingValue } from "@scrypted/sdk";
3
+ import { type BaichuanConnectionCallbacks } from "./baichuan-base";
5
4
  import { ReolinkNativeCamera } from "./camera";
6
5
  import { ReolinkNativeBatteryCamera } from "./camera-battery";
7
- import { normalizeUid } from "./connect";
6
+ import { CameraType, CommonCameraMixin } from "./common";
8
7
  import ReolinkNativePlugin from "./main";
9
8
  import { batteryCameraSuffix, cameraSuffix, getDeviceInterfaces, updateDeviceInfo } from "./utils";
10
9
 
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
-
10
+ export class ReolinkNativeMultiFocalDevice extends CommonCameraMixin implements Settings, DeviceProvider, Reboot {
66
11
  plugin: ReolinkNativePlugin;
67
12
  cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
68
13
  private channelToNativeIdMap = new Map<number, string>();
69
14
  private initReinitTimeout: NodeJS.Timeout | undefined;
70
15
  isBattery: boolean;
71
16
 
72
- constructor(nativeId: string, plugin: ReolinkNativePlugin) {
73
- super(nativeId);
17
+ constructor(nativeId: string, plugin: ReolinkNativePlugin, type: CameraType) {
18
+ super(nativeId, plugin, { type });
74
19
  this.plugin = plugin;
75
20
 
76
- this.isBattery = this.storageSettings.values.protocol === 'udp';
77
-
78
21
  this.scheduleInit();
79
22
  }
80
23
 
24
+ getAbilities(): DeviceCapabilities {
25
+ const { capabilities } = this.storageSettings.values;
26
+
27
+ return {
28
+ ...capabilities,
29
+ hasPan: false,
30
+ hasTilt: false,
31
+ hasZoom: false,
32
+ hasPresets: false,
33
+ hasIntercom: false,
34
+ }
35
+ }
36
+
81
37
  async reboot(): Promise<void> {
82
38
  const api = await this.ensureBaichuanClient();
83
39
  await api.reboot();
84
40
  }
85
41
 
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
- }
42
+ takePicture(options?: any): Promise<MediaObject> {
43
+ throw new Error("Method not implemented.");
44
+ }
99
45
 
100
- return {
101
- host: ipAddress,
102
- username,
103
- password,
104
- uid: normalizedUid,
105
- transport: protocol,
106
- logger: this.console,
107
- };
46
+ getPictureOptions(): Promise<any[]> {
47
+ throw new Error("Method not implemented.");
108
48
  }
109
49
 
110
50
  protected getConnectionCallbacks(): BaichuanConnectionCallbacks {
@@ -134,7 +74,7 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
134
74
  }
135
75
 
136
76
  protected isDebugEnabled(): boolean {
137
- return this.storageSettings.values.debugEvents;
77
+ return this.storageSettings.values.debugEvents || false;
138
78
  }
139
79
 
140
80
  protected getDeviceName(): string {
@@ -199,9 +139,8 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
199
139
  device: this,
200
140
  deviceData,
201
141
  ipAddress: this.storageSettings.values.ipAddress,
142
+ logger,
202
143
  });
203
-
204
- logger.log(`Device info updated: ${JSON.stringify(deviceData)}`);
205
144
  } catch (e) {
206
145
  logger.warn('Failed to fetch device info', e);
207
146
  }
@@ -242,13 +181,15 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
242
181
  this.storageSettings.values.multifocalInfo = multifocalInfo;
243
182
  this.storageSettings.values.capabilities = capabilities;
244
183
 
245
- logger.debug(`Multichannel info: ${JSON.stringify({ multifocalInfo, capabilities, support, abilities, features, objects, presets })}`);
184
+ // TODO: Remove this after debugging
185
+ logger.log(`Multichannel info: ${JSON.stringify({ multifocalInfo, capabilities, support, abilities, features, objects, presets })}`);
186
+ // logger.debug(`Multichannel info: ${JSON.stringify({ multifocalInfo, capabilities, support, abilities, features, objects, presets })}`);
246
187
 
247
188
  for (const channelInfo of multifocalInfo?.channels ?? []) {
248
189
  const { channel, lensType } = channelInfo;
249
190
 
250
191
  const name = `${this.name} - ${lensType}`;
251
- const nativeId = this.buildNativeId(channel);
192
+ const nativeId = `${this.nativeId}-channel${channel}${this.isBattery ? batteryCameraSuffix : cameraSuffix}`;
252
193
 
253
194
  this.channelToNativeIdMap.set(channel, nativeId);
254
195
  const { interfaces, capabilities: deviceCapabilities } = this.getInterfaces(channel);
@@ -270,6 +211,9 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
270
211
 
271
212
  await sdk.deviceManager.onDeviceDiscovered(device);
272
213
 
214
+ // TODO: Remove this after debugging
215
+ logger.log(`Discovering lens device ${nativeId}: ${JSON.stringify({ interfaces, deviceCapabilities })}`);
216
+
273
217
  const camera = await this.getDevice(nativeId);
274
218
 
275
219
  camera.storageSettings.values.rtspChannel = channel;
@@ -286,16 +230,19 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
286
230
  }
287
231
 
288
232
  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);
233
+ if (nativeId.endsWith(cameraSuffix) || nativeId.endsWith(batteryCameraSuffix)) {
234
+ let device = this.cameraNativeMap.get(nativeId);
235
+ if (!device) {
236
+ if (nativeId.endsWith(batteryCameraSuffix)) {
237
+ device = new ReolinkNativeBatteryCamera(nativeId, this.plugin, undefined, this);
238
+ } else {
239
+ device = new ReolinkNativeCamera(nativeId, this.plugin, undefined, this);
240
+ }
295
241
  }
242
+ return device;
243
+ } else {
244
+ return super.getDevice(nativeId);
296
245
  }
297
-
298
- return device;
299
246
  }
300
247
 
301
248
  async getSettings(): Promise<Setting[]> {
@@ -309,11 +256,7 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
309
256
 
310
257
  async releaseDevice(id: string, nativeId: string) {
311
258
  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}`;
259
+ super.releaseDevice(id, nativeId);
317
260
  }
318
261
 
319
262
  forwardNativeEvent(ev: ReolinkSimpleEvent): void {
@@ -337,16 +280,14 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
337
280
  return;
338
281
  }
339
282
 
340
- // Forward event to camera
341
- if (camera.onSimpleEvent) {
342
- camera.onSimpleEvent(ev);
343
- }
283
+ camera.onSimpleEvent(ev);
344
284
  }
285
+
345
286
  async unsubscribeFromAllEvents(): Promise<void> {
346
287
  await super.unsubscribeFromEvents();
347
288
  }
348
289
 
349
- private async runDiagnostics(): Promise<void> {
290
+ public async runDiagnostics(): Promise<void> {
350
291
  const logger = this.getBaichuanLogger();
351
292
  logger.log(`Starting Multifocal diagnostics...`);
352
293
 
package/src/nvr.ts CHANGED
@@ -360,7 +360,8 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
360
360
  this.lastNvrInfoCheck = now;
361
361
  const { nvrData } = await api.getNvrInfo();
362
362
  const { devicesData, channelsResponse, response } = await api.getDevicesInfo();
363
- logger.log(`NVR info data fetched: ${JSON.stringify({ nvrData, devicesData, channelsResponse, response })}`);
363
+ logger.log(`NVR info data fetched`);
364
+ logger.debug(`${JSON.stringify({ nvrData, devicesData, channelsResponse, response })}`);
364
365
 
365
366
  await this.discoverDevices(true);
366
367
  }
@@ -372,7 +373,6 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
372
373
  this.forwardCgiEvents(eventsRes.parsed);
373
374
  }
374
375
 
375
- // Always fetch battery info (not event-related)
376
376
  const { batteryInfoData, response } = await api.getAllChannelsBatteryInfo();
377
377
 
378
378
  logger.debug(`Battery info call result: ${JSON.stringify({ batteryInfoData, response })}`);
@@ -411,9 +411,8 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
411
411
  device: this,
412
412
  ipAddress,
413
413
  deviceData,
414
+ logger
414
415
  });
415
-
416
- logger.log(`Device info updated: ${JSON.stringify(deviceData)}`);
417
416
  } catch (e) {
418
417
  logger.warn('Failed to fetch device info', e);
419
418
  }
@@ -552,7 +551,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
552
551
  }
553
552
  }
554
553
 
555
- logger.log(`Channel discovery completed. ${JSON.stringify({ devicesData, channels })}`);
554
+ logger.debug(`Channel discovery completed. ${JSON.stringify({ devicesData, channels })}`);
556
555
  }
557
556
 
558
557
  async discoverDevices(scan?: boolean): Promise<DiscoveredDevice[]> {
@@ -606,7 +605,8 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
606
605
 
607
606
  const device = await this.getDevice(adopt.nativeId);
608
607
  const logger = this.getBaichuanLogger();
609
- logger.log('Adopted device', entry, device?.name);
608
+ logger.log('Adopted device', device?.name);
609
+ logger.log(JSON.stringify(entry));
610
610
  const { username, password, ipAddress } = this.storageSettings.values;
611
611
 
612
612
  device.storageSettings.values.rtspChannel = entry.rtspChannel;
package/src/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { DeviceCapabilities, ReolinkDeviceInfo } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
2
- import { DeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
2
+ import sdk, { Device, DeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
3
3
 
4
4
  /**
5
5
  * Enumeration of operation types that may require specific channel assignments
@@ -23,7 +23,11 @@ export type OperationChannelMap = Partial<Record<OperationChannelType, number>>;
23
23
  export const nvrSuffix = `-nvr`;
24
24
  export const batteryCameraSuffix = `-battery-cam`;
25
25
  export const multifocalSuffix = `-multifocal`;
26
+ export const batteryMultifocalSuffix = `-battery-multifocal`;
26
27
  export const cameraSuffix = `-cam`;
28
+ export const sirenSuffix = `-siren`;
29
+ export const floodlightSuffix = `-floodlight`;
30
+ export const pirSuffix = `-pir`;
27
31
 
28
32
  export const getDeviceInterfaces = (props: {
29
33
  capabilities: DeviceCapabilities,
@@ -78,9 +82,10 @@ export const getDeviceInterfaces = (props: {
78
82
  export const updateDeviceInfo = async (props: {
79
83
  device: DeviceBase,
80
84
  ipAddress: string,
81
- deviceData: ReolinkDeviceInfo
85
+ deviceData: ReolinkDeviceInfo,
86
+ logger: Console
82
87
  }) => {
83
- const { device, ipAddress, deviceData } = props;
88
+ const { device, ipAddress, deviceData, logger } = props;
84
89
  try {
85
90
  const info = device.info || {};
86
91
 
@@ -101,5 +106,9 @@ export const updateDeviceInfo = async (props: {
101
106
  device.info = info;
102
107
 
103
108
  throw e;
109
+ } finally {
110
+
111
+ logger.log(`Device info updated`);
112
+ logger.debug(`${JSON.stringify({ newInfo: device.info, deviceData })}`);
104
113
  }
105
114
  }