@apocaliss92/scrypted-reolink-native 0.1.25 → 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/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
  }
@@ -286,16 +225,19 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
286
225
  }
287
226
 
288
227
  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);
228
+ if (nativeId.endsWith(cameraSuffix) || nativeId.endsWith(batteryCameraSuffix)) {
229
+ let device = this.cameraNativeMap.get(nativeId);
230
+ if (!device) {
231
+ if (nativeId.endsWith(batteryCameraSuffix)) {
232
+ device = new ReolinkNativeBatteryCamera(nativeId, this.plugin, undefined, this);
233
+ } else {
234
+ device = new ReolinkNativeCamera(nativeId, this.plugin, undefined, this);
235
+ }
295
236
  }
237
+ return device;
238
+ } else {
239
+ return super.getDevice(nativeId);
296
240
  }
297
-
298
- return device;
299
241
  }
300
242
 
301
243
  async getSettings(): Promise<Setting[]> {
@@ -309,11 +251,11 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
309
251
 
310
252
  async releaseDevice(id: string, nativeId: string) {
311
253
  this.cameraNativeMap.delete(nativeId);
254
+ super.releaseDevice(id, nativeId);
312
255
  }
313
256
 
314
257
  buildNativeId(channel: number): string {
315
- const { protocol } = this.storageSettings.values;
316
- return `${this.nativeId}-channel${channel}${protocol === "udp" ? batteryCameraSuffix : cameraSuffix}`;
258
+ return `${this.nativeId}-channel${channel}`;
317
259
  }
318
260
 
319
261
  forwardNativeEvent(ev: ReolinkSimpleEvent): void {
@@ -337,16 +279,14 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
337
279
  return;
338
280
  }
339
281
 
340
- // Forward event to camera
341
- if (camera.onSimpleEvent) {
342
- camera.onSimpleEvent(ev);
343
- }
282
+ camera.onSimpleEvent(ev);
344
283
  }
284
+
345
285
  async unsubscribeFromAllEvents(): Promise<void> {
346
286
  await super.unsubscribeFromEvents();
347
287
  }
348
288
 
349
- private async runDiagnostics(): Promise<void> {
289
+ public async runDiagnostics(): Promise<void> {
350
290
  const logger = this.getBaichuanLogger();
351
291
  logger.log(`Starting Multifocal diagnostics...`);
352
292
 
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
  }