@apocaliss92/scrypted-reolink-native 0.1.3 → 0.1.5

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
@@ -1,19 +1,20 @@
1
- import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceInformation, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedNativeId, Setting } from "@scrypted/sdk";
1
+ import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceInformation, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting } from "@scrypted/sdk";
2
2
  import { ReolinkNativeCamera } from "./camera";
3
3
  import { ReolinkNativeBatteryCamera } from "./camera-battery";
4
- import { createBaichuanApi } from "./connect";
4
+ import { ReolinkNativeNvrDevice } from "./nvr";
5
+ import { autoDetectDeviceType, createBaichuanApi } from "./connect";
5
6
  import { getDeviceInterfaces } from "./utils";
6
7
 
7
8
  class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
8
- devices = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
9
+ devices = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera | ReolinkNativeNvrDevice>();
9
10
 
10
11
  getScryptedDeviceCreator(): string {
11
12
  return 'Reolink Native camera';
12
13
  }
13
14
 
14
- async getDevice(nativeId: ScryptedNativeId): Promise<ReolinkNativeCamera | ReolinkNativeBatteryCamera> {
15
+ async getDevice(nativeId: ScryptedNativeId): Promise<ReolinkNativeCamera | ReolinkNativeBatteryCamera | ReolinkNativeNvrDevice> {
15
16
  if (this.devices.has(nativeId)) {
16
- return this.devices.get(nativeId);
17
+ return this.devices.get(nativeId)!;
17
18
  }
18
19
 
19
20
  const newCamera = this.createCamera(nativeId);
@@ -23,88 +24,143 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
23
24
 
24
25
  async createDevice(settings: DeviceCreatorSettings, nativeId?: string): Promise<string> {
25
26
  const ipAddress = settings.ip?.toString();
26
- let info: DeviceInformation = {};
27
-
28
27
  const username = settings.username?.toString();
29
28
  const password = settings.password?.toString();
30
29
  const uid = settings.uid?.toString();
31
- const isBatteryCam = settings.isBatteryCam === true || settings.isBatteryCam?.toString() === 'true';
32
30
 
33
- if (isBatteryCam && !uid) {
34
- throw new Error('UID is required for battery cameras (BCUDP)');
31
+ if (!ipAddress || !username || !password) {
32
+ throw new Error('IP address, username, and password are required');
35
33
  }
36
34
 
37
- if (ipAddress && username && password) {
38
- const api = await createBaichuanApi(
39
- {
40
- transport: isBatteryCam ? 'udp' : 'tcp',
41
- logger: this.console,
42
- inputs: {
43
- host: ipAddress,
44
- username,
45
- password,
46
- uid,
47
- logger: this.console,
48
- }
49
- }
50
- );
35
+ // Auto-detect device type (camera, battery-cam, or nvr)
36
+ this.console.log(`[AutoDetect] Starting device type detection for ${ipAddress}...`);
37
+ const detection = await autoDetectDeviceType(
38
+ {
39
+ host: ipAddress,
40
+ username,
41
+ password,
42
+ uid,
43
+ logger: this.console,
44
+ },
45
+ this.console
46
+ );
47
+
48
+ this.console.log(`[AutoDetect] Detected device type: ${detection.type} (transport: ${detection.transport})`);
49
+
50
+ // Handle NVR case
51
+ if (detection.type === 'nvr') {
52
+ const deviceInfo = detection.deviceInfo || {};
53
+ const name = deviceInfo?.name || 'Reolink NVR';
54
+ const serialNumber = deviceInfo?.serialNumber || deviceInfo?.itemNo || `nvr-${Date.now()}`;
55
+ nativeId = `${serialNumber}-nvr`;
56
+
57
+ settings.newCamera ||= name;
58
+
59
+ await sdk.deviceManager.onDeviceDiscovered({
60
+ nativeId,
61
+ name,
62
+ interfaces: [
63
+ ScryptedInterface.Settings,
64
+ ScryptedInterface.DeviceDiscovery,
65
+ ScryptedInterface.DeviceProvider,
66
+ ScryptedInterface.Reboot,
67
+ ],
68
+ type: ScryptedDeviceType.Builtin,
69
+ providerNativeId: this.nativeId,
70
+ });
71
+
72
+ const device = await this.getDevice(nativeId);
73
+ if (!(device instanceof ReolinkNativeNvrDevice)) {
74
+ throw new Error('Expected NVR device but got different type');
75
+ }
76
+ device.storageSettings.values.ipAddress = ipAddress;
77
+ device.storageSettings.values.username = username;
78
+ device.storageSettings.values.password = password;
51
79
 
52
- await api.login();
80
+ return nativeId;
81
+ }
53
82
 
54
- try {
55
- const deviceInfo = await api.getInfo();
56
- const name = deviceInfo?.name;
57
- const rtspChannel = 0;
58
- const { abilities, capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel);
59
-
60
- this.console.log(JSON.stringify({ abilities, capabilities, deviceInfo }));
61
-
62
- nativeId = `${deviceInfo.serialNumber}${isBatteryCam ? '-battery-cam' : '-cam'}`;
63
-
64
- settings.newCamera ||= name;
65
-
66
- const { interfaces, type } = getDeviceInterfaces({
67
- capabilities,
68
- logger: this.console,
69
- });
70
-
71
- await sdk.deviceManager.onDeviceDiscovered({
72
- nativeId,
73
- name,
74
- interfaces,
75
- type,
76
- providerNativeId: this.nativeId,
77
- });
78
-
79
- const device = await this.getDevice(nativeId);
80
- device.info = info;
81
- device.classes = objects;
82
- device.presets = presets;
83
- device.storageSettings.values.username = username;
84
- device.storageSettings.values.password = password;
85
- device.storageSettings.values.rtspChannel = rtspChannel;
86
- device.storageSettings.values.ipAddress = ipAddress;
87
- if (isBatteryCam && uid) (device as ReolinkNativeBatteryCamera).storageSettings.values.uid = uid;
88
- device.storageSettings.values.capabilities = capabilities;
89
- device.updateDeviceInfo();
90
-
91
- return nativeId;
92
- }
93
- catch (e) {
94
- this.console.error('Error adding Reolink camera', e);
95
- await api.close();
96
- throw e;
97
- }
98
- finally {
99
- await api.close();
83
+ // For camera and battery-cam, create the device
84
+ const deviceInfo = detection.deviceInfo || {};
85
+ const name = deviceInfo?.name || 'Reolink Camera';
86
+ const serialNumber = deviceInfo?.serialNumber || deviceInfo?.itemNo || `unknown-${Date.now()}`;
87
+
88
+ // Create nativeId based on device type
89
+ if (detection.type === 'battery-cam') {
90
+ nativeId = `${serialNumber}-battery-cam`;
91
+ } else {
92
+ nativeId = `${serialNumber}-cam`;
93
+ }
94
+
95
+ settings.newCamera ||= name;
96
+
97
+ // Create API connection to get capabilities
98
+ const api = await createBaichuanApi({
99
+ inputs: {
100
+ host: ipAddress,
101
+ username,
102
+ password,
103
+ uid: detection.uid,
104
+ logger: this.console,
105
+ },
106
+ transport: detection.transport,
107
+ logger: this.console,
108
+ });
109
+
110
+ try {
111
+ await api.login();
112
+ const rtspChannel = 0;
113
+ const { abilities, capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel);
114
+
115
+ this.console.log(JSON.stringify({ abilities, capabilities, deviceInfo }));
116
+
117
+ const { interfaces, type } = getDeviceInterfaces({
118
+ capabilities,
119
+ logger: this.console,
120
+ });
121
+
122
+ await sdk.deviceManager.onDeviceDiscovered({
123
+ nativeId,
124
+ name,
125
+ interfaces,
126
+ type,
127
+ providerNativeId: this.nativeId,
128
+ });
129
+
130
+ const device = await this.getDevice(nativeId);
131
+ if (device instanceof ReolinkNativeNvrDevice) {
132
+ // NVR devices are handled separately above
133
+ throw new Error('NVR device should not reach this code path');
100
134
  }
135
+
136
+ // Type guard: device is either ReolinkNativeCamera or ReolinkNativeBatteryCamera
137
+ device.info = deviceInfo;
138
+ device.classes = objects;
139
+ device.presets = presets;
140
+ device.storageSettings.values.username = username;
141
+ device.storageSettings.values.password = password;
142
+ device.storageSettings.values.rtspChannel = rtspChannel;
143
+ device.storageSettings.values.ipAddress = ipAddress;
144
+ device.storageSettings.values.capabilities = capabilities;
145
+ device.storageSettings.values.uid = detection.uid;
146
+
147
+ return nativeId;
148
+ }
149
+ catch (e) {
150
+ this.console.error('Error adding Reolink device', e);
151
+ throw e;
152
+ }
153
+ finally {
154
+ await api.close();
101
155
  }
102
156
  }
103
157
 
104
158
  async releaseDevice(id: string, nativeId: ScryptedNativeId): Promise<void> {
105
159
  if (this.devices.has(nativeId)) {
106
160
  const device = this.devices.get(nativeId);
107
- await device.release();
161
+ if (device && 'release' in device && typeof device.release === 'function') {
162
+ await device.release();
163
+ }
108
164
  this.devices.delete(nativeId);
109
165
  }
110
166
  }
@@ -116,12 +172,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
116
172
  title: 'IP Address',
117
173
  placeholder: '192.168.2.222',
118
174
  },
119
- {
120
- key: 'isBatteryCam',
121
- title: 'Is Battery Camera',
122
- description: 'Enable for Reolink battery cameras. Uses UDP/BCUDP for discovery and streaming. Requires UID.',
123
- type: 'boolean',
124
- },
125
175
  {
126
176
  key: 'username',
127
177
  title: 'Username',
@@ -134,7 +184,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
134
184
  {
135
185
  key: 'uid',
136
186
  title: 'UID',
137
- description: 'Reolink UID (required for battery cameras)',
187
+ description: 'Reolink UID (optional, required for battery cameras if TCP connection fails)',
138
188
  }
139
189
  ]
140
190
  }
@@ -142,8 +192,11 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
142
192
  createCamera(nativeId: string) {
143
193
  if (nativeId.endsWith('-battery-cam')) {
144
194
  return new ReolinkNativeBatteryCamera(nativeId, this);
195
+ } else if (nativeId.endsWith('-nvr')) {
196
+ return new ReolinkNativeNvrDevice(nativeId, this);
197
+ } else {
198
+ return new ReolinkNativeCamera(nativeId, this);
145
199
  }
146
- return new ReolinkNativeCamera(nativeId, this);
147
200
  }
148
201
  }
149
202
 
package/src/nvr.ts ADDED
@@ -0,0 +1,367 @@
1
+ import type { DeviceInfoResponse, DeviceInputData, ReolinkCgiApi } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
2
+ import sdk, { AdoptDevice, Device, DeviceDiscovery, DeviceProvider, DiscoveredDevice, Reboot, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from "@scrypted/sdk";
3
+ import { StorageSettings } from "@scrypted/sdk/storage-settings";
4
+ import { ReolinkNativeCamera } from "./camera";
5
+ import { ReolinkNativeBatteryCamera } from "./camera-battery";
6
+ import { normalizeUid } from "./connect";
7
+ import ReolinkNativePlugin from "./main";
8
+ import { getDeviceInterfaces, updateDeviceInfo } from "./utils";
9
+
10
+ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settings, DeviceDiscovery, DeviceProvider, Reboot {
11
+ storageSettings = new StorageSettings(this, {
12
+ debugEvents: {
13
+ title: 'Debug Events',
14
+ type: 'boolean',
15
+ immediate: true,
16
+ },
17
+ ipAddress: {
18
+ title: 'IP address',
19
+ type: 'string',
20
+ onPut: async () => await this.reinit()
21
+ },
22
+ username: {
23
+ title: 'Username',
24
+ placeholder: 'admin',
25
+ defaultValue: 'admin',
26
+ type: 'string',
27
+ onPut: async () => await this.reinit()
28
+ },
29
+ password: {
30
+ title: 'Password',
31
+ type: 'password',
32
+ onPut: async () => await this.reinit()
33
+ },
34
+ });
35
+ plugin: ReolinkNativePlugin;
36
+ nvrApi: ReolinkCgiApi | undefined;
37
+ discoveredDevices = new Map<string, {
38
+ device: Device;
39
+ description: string;
40
+ rtspChannel: number;
41
+ deviceData: DeviceInfoResponse;
42
+ }>();
43
+ lastHubInfoCheck: number | undefined;
44
+ lastErrorsCheck: number | undefined;
45
+ lastDevicesStatusCheck: number | undefined;
46
+ cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
47
+ processing = false;
48
+
49
+ constructor(nativeId: string, plugin: ReolinkNativePlugin) {
50
+ super(nativeId);
51
+ this.plugin = plugin;
52
+
53
+ setTimeout(async () => {
54
+ await this.init();
55
+ }, 5000);
56
+ }
57
+
58
+ async reboot(): Promise<void> {
59
+ const api = await this.ensureClient();
60
+ await api.Reboot();
61
+ }
62
+
63
+ getLogger() {
64
+ return this.console;
65
+ }
66
+
67
+ async reinit() {
68
+ if (this.nvrApi) {
69
+ try {
70
+ await this.nvrApi.logout();
71
+ } catch {
72
+ // ignore
73
+ }
74
+ }
75
+ this.nvrApi = undefined;
76
+ }
77
+
78
+ async ensureClient(): Promise<ReolinkCgiApi> {
79
+ if (this.nvrApi) {
80
+ return this.nvrApi;
81
+ }
82
+
83
+ const { ipAddress, username, password } = this.storageSettings.values;
84
+ if (!ipAddress || !username || !password) {
85
+ throw new Error('Missing NVR credentials');
86
+ }
87
+
88
+ const { ReolinkCgiApi } = await import("@apocaliss92/reolink-baichuan-js");
89
+ this.nvrApi = new ReolinkCgiApi({
90
+ host: ipAddress,
91
+ username,
92
+ password,
93
+ });
94
+
95
+ await this.nvrApi.login();
96
+ return this.nvrApi;
97
+ }
98
+
99
+ async init() {
100
+ const api = await this.ensureClient();
101
+ const logger = this.getLogger();
102
+ await this.updateDeviceInfo();
103
+
104
+ setInterval(async () => {
105
+ if (this.processing || !api) {
106
+ return;
107
+ }
108
+ this.processing = true;
109
+ try {
110
+ const now = Date.now();
111
+
112
+ if (!this.lastErrorsCheck || (now - this.lastErrorsCheck > 60 * 1000)) {
113
+ this.lastErrorsCheck = now;
114
+ // Note: ReolinkCgiApi doesn't have checkErrors, skip for now
115
+ }
116
+
117
+ if (!this.lastHubInfoCheck || now - this.lastHubInfoCheck > 1000 * 60 * 5) {
118
+ logger.log('Starting Hub info data fetch');
119
+ this.lastHubInfoCheck = now;
120
+ const { hubData } = await api.getHubInfo();
121
+ const { devicesData, channelsResponse, response } = await api.getDevicesInfo();
122
+ logger.log('Hub info data fetched');
123
+ if (this.storageSettings.values.debugEvents) {
124
+ logger.log(`${JSON.stringify({ hubData, devicesData, channelsResponse, response })}`);
125
+ }
126
+
127
+ await this.discoverDevices(true);
128
+ }
129
+
130
+ const eventsRes = await api.getAllChannelsEvents();
131
+
132
+ if (this.storageSettings.values.debugEvents) {
133
+ logger.debug(`Events call result: ${JSON.stringify(eventsRes)}`);
134
+ }
135
+ this.cameraNativeMap.forEach((camera) => {
136
+ if (camera) {
137
+ const channel = camera.storageSettings.values.rtspChannel;
138
+ const cameraEventsData = eventsRes?.parsed[channel];
139
+ if (cameraEventsData) {
140
+ camera.processEvents(cameraEventsData);
141
+ }
142
+ }
143
+ });
144
+
145
+ const { batteryInfoData, response } = await api.getAllChannelsBatteryInfo();
146
+
147
+ if (this.storageSettings.values.debugEvents) {
148
+ logger.debug(`Battery info call result: ${JSON.stringify({ batteryInfoData, response })}`);
149
+ }
150
+
151
+ this.cameraNativeMap.forEach((camera) => {
152
+ if (camera) {
153
+ const channel = camera.storageSettings.values.rtspChannel;
154
+ const cameraBatteryData = batteryInfoData[channel];
155
+ if (cameraBatteryData) {
156
+ (camera as ReolinkNativeBatteryCamera).updateSleepingState({
157
+ reason: 'NVR',
158
+ state: cameraBatteryData.sleeping ? 'sleeping' : 'awake',
159
+ idleMs: 0,
160
+ lastRxAtMs: 0,
161
+ }).catch(() => { });
162
+ }
163
+ }
164
+ });
165
+ } catch (e) {
166
+ this.console.error('Error on events flow', e);
167
+ } finally {
168
+ this.processing = false;
169
+ }
170
+ }, 1000);
171
+ }
172
+
173
+ async updateDeviceInfo(): Promise<void> {
174
+ const { ipAddress } = this.storageSettings.values;
175
+ try {
176
+ const api = await this.ensureClient();
177
+ const deviceData = await api.getInfo();
178
+
179
+ await updateDeviceInfo({
180
+ device: this,
181
+ ipAddress,
182
+ deviceData,
183
+ });
184
+ } catch (e) {
185
+ this.getLogger().warn('Failed to fetch device info', e);
186
+ }
187
+ }
188
+
189
+ async getSettings(): Promise<Setting[]> {
190
+ const settings = await this.storageSettings.getSettings();
191
+ return settings;
192
+ }
193
+
194
+ async putSetting(key: string, value: SettingValue): Promise<void> {
195
+ return this.storageSettings.putSetting(key, value);
196
+ }
197
+
198
+ async releaseDevice(id: string, nativeId: string) {
199
+ this.cameraNativeMap.delete(nativeId);
200
+ }
201
+
202
+ async getDevice(nativeId: string): Promise<ReolinkNativeCamera | ReolinkNativeBatteryCamera> {
203
+ let device = this.cameraNativeMap.get(nativeId);
204
+
205
+ if (!device) {
206
+ if (nativeId.endsWith('-battery-cam')) {
207
+ device = new ReolinkNativeBatteryCamera(nativeId, this.plugin, this);
208
+ } else {
209
+ device = new ReolinkNativeCamera(nativeId, this.plugin, this);
210
+ }
211
+ this.cameraNativeMap.set(nativeId, device);
212
+ }
213
+
214
+ return device;
215
+ }
216
+
217
+ buildNativeId(channel: number, serialNumber?: string, isBattery?: boolean): string {
218
+ const suffix = isBattery ? '-battery-cam' : '-cam';
219
+ if (serialNumber) {
220
+ return `${this.nativeId}-ch${channel}-${serialNumber}${suffix}`;
221
+ }
222
+ return `${this.nativeId}-ch${channel}${suffix}`;
223
+ }
224
+
225
+ getCameraInterfaces() {
226
+ return [
227
+ ScryptedInterface.VideoCameraConfiguration,
228
+ ScryptedInterface.Camera,
229
+ ScryptedInterface.MotionSensor,
230
+ ScryptedInterface.VideoTextOverlays,
231
+ ScryptedInterface.VideoCamera,
232
+ ScryptedInterface.Settings,
233
+ ScryptedInterface.ObjectDetector,
234
+ ];
235
+ }
236
+
237
+ async syncEntitiesFromRemote() {
238
+ const api = await this.ensureClient();
239
+ const logger = this.getLogger();
240
+
241
+ logger.log('Starting channels discovery using getDevicesInfo...');
242
+
243
+ const { devicesData, channels } = await api.getDevicesInfo();
244
+
245
+ logger.log(`getDevicesInfo completed. Found ${channels.length} channels.`);
246
+
247
+ // Process each channel that was successfully discovered
248
+ for (const channel of channels) {
249
+ try {
250
+ const { channelStatus, channelInfo, abilities } = devicesData[channel];
251
+ const name = channelStatus?.name;
252
+ const uid = channelStatus?.uid;
253
+ const isBattery = !!(abilities?.battery?.ver ?? 0);
254
+
255
+ const nativeId = this.buildNativeId(channel, uid, isBattery);
256
+ const interfaces = [ScryptedInterface.VideoCamera];
257
+ if (isBattery) {
258
+ interfaces.push(ScryptedInterface.Battery);
259
+ }
260
+ const type = abilities.supportDoorbellLight ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera;
261
+
262
+ const device: Device = {
263
+ nativeId,
264
+ name,
265
+ providerNativeId: this.nativeId,
266
+ interfaces,
267
+ type,
268
+ info: {
269
+ manufacturer: 'Reolink',
270
+ model: channelInfo?.typeInfo,
271
+ serialNumber: uid,
272
+ }
273
+ };
274
+
275
+ if (sdk.deviceManager.getNativeIds().includes(nativeId)) {
276
+ continue;
277
+ }
278
+
279
+ if (this.discoveredDevices.has(nativeId)) {
280
+ continue;
281
+ }
282
+
283
+ this.discoveredDevices.set(nativeId, {
284
+ device,
285
+ description: `${name} (Channel ${channel})`,
286
+ rtspChannel: channel,
287
+ deviceData: devicesData[channel],
288
+ });
289
+
290
+ logger.debug(`Discovered channel ${channel}: ${name}`);
291
+ } catch (e: any) {
292
+ logger.debug(`Error processing channel ${channel}: ${e?.message || String(e)}`);
293
+ }
294
+ }
295
+
296
+ logger.log(`Channel discovery completed. Found ${this.discoveredDevices.size} devices.`);
297
+ }
298
+
299
+ async discoverDevices(scan?: boolean): Promise<DiscoveredDevice[]> {
300
+ if (scan) {
301
+ await this.syncEntitiesFromRemote();
302
+ }
303
+
304
+ return [...this.discoveredDevices.values()].map(d => ({
305
+ ...d.device,
306
+ description: d.description,
307
+ }));
308
+ }
309
+
310
+ async adoptDevice(adopt: AdoptDevice): Promise<string> {
311
+ const entry = this.discoveredDevices.get(adopt.nativeId);
312
+
313
+ if (!entry)
314
+ throw new Error('device not found');
315
+
316
+ await this.onDeviceEvent(ScryptedInterface.DeviceDiscovery, await this.discoverDevices());
317
+
318
+ const isBattery = entry.device.interfaces.includes(ScryptedInterface.Battery);
319
+ const { channelStatus } = entry.deviceData;
320
+
321
+ const { ReolinkBaichuanApi } = await import("@apocaliss92/reolink-baichuan-js");
322
+ const transport = 'tcp';
323
+ const uid = channelStatus?.uid;
324
+ const normalizedUid = isBattery && uid ? normalizeUid(uid) : undefined;
325
+ const baichuanApi = new ReolinkBaichuanApi({
326
+ host: this.storageSettings.values.ipAddress,
327
+ username: this.storageSettings.values.username,
328
+ password: this.storageSettings.values.password,
329
+ transport,
330
+ channel: entry.rtspChannel,
331
+ ...(normalizedUid ? { uid: normalizedUid } : {}),
332
+ });
333
+ await baichuanApi.login();
334
+ const { capabilities, objects, presets } = await baichuanApi.getDeviceCapabilities(entry.rtspChannel);
335
+ const { interfaces, type } = getDeviceInterfaces({
336
+ capabilities,
337
+ logger: this.console,
338
+ });
339
+
340
+ const actualDevice: Device = {
341
+ ...entry.device,
342
+ interfaces,
343
+ type
344
+ };
345
+
346
+ await sdk.deviceManager.onDeviceDiscovered(actualDevice);
347
+
348
+ const device = await this.getDevice(adopt.nativeId);
349
+ this.console.log('Adopted device', entry, device?.name);
350
+ const { username, password, ipAddress } = this.storageSettings.values;
351
+
352
+ device.storageSettings.values.rtspChannel = entry.rtspChannel;
353
+ device.classes = objects;
354
+ device.presets = presets;
355
+ device.storageSettings.values.username = username;
356
+ device.storageSettings.values.password = password;
357
+ device.storageSettings.values.rtspChannel = entry.rtspChannel;
358
+ device.storageSettings.values.ipAddress = ipAddress;
359
+ device.storageSettings.values.capabilities = capabilities;
360
+ device.storageSettings.values.uid = entry.deviceData.channelStatus.uid;
361
+ device.storageSettings.values.isFromNvr = true;
362
+
363
+ this.discoveredDevices.delete(adopt.nativeId);
364
+ return device?.id;
365
+ }
366
+ }
367
+