@apocaliss92/scrypted-reolink-native 0.1.19 → 0.1.21

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.19",
3
+ "version": "0.1.21",
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",
@@ -21,7 +21,8 @@
21
21
  "scrypted",
22
22
  "plugin",
23
23
  "reolink",
24
- "neolink",
24
+ "native",
25
+ "native",
25
26
  "camera"
26
27
  ],
27
28
  "scrypted": {
package/src/camera.ts CHANGED
@@ -70,17 +70,11 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
70
70
 
71
71
  // Reset client and clear cache on recoverable error
72
72
  await this.resetBaichuanClient(e);
73
- this.cachedNetPort = undefined;
74
73
 
75
74
  // Important: callers must re-acquire the client inside fn.
76
75
  try {
77
76
  return await fn();
78
77
  } catch (retryError) {
79
- // If retry also fails with recoverable error, don't spam logs
80
- if (this.isRecoverableBaichuanError(retryError)) {
81
- // Silently fail to avoid spam, but still throw to caller
82
- throw retryError;
83
- }
84
78
  throw retryError;
85
79
  }
86
80
  }
package/src/common.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
1
+ import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, ReolinkSupportedStream, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
2
2
  import sdk, { BinarySensor, Brightness, Camera, Device, DeviceProvider, Intercom, MediaObject, MediaStreamUrl, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, RequestMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
3
3
  import { StorageSettings } from "@scrypted/sdk/storage-settings";
4
4
  import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
@@ -11,10 +11,8 @@ import { ReolinkNativeNvrDevice } from "./nvr";
11
11
  import { ReolinkNativeMultiFocalDevice } from "./multifocal";
12
12
  import { ReolinkPtzPresets } from "./presets";
13
13
  import {
14
- buildVideoStreamOptions,
15
14
  createRfc4571MediaObjectFromStreamManager,
16
15
  expectedVideoTypeFromUrlMediaStreamOptions,
17
- isNativeStreamId,
18
16
  parseStreamProfileFromId,
19
17
  selectStreamOption,
20
18
  StreamManager
@@ -222,6 +220,10 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
222
220
  json: true,
223
221
  hide: true,
224
222
  },
223
+ operationChannels: {
224
+ json: true,
225
+ hide: true,
226
+ },
225
227
  // Battery camera specific
226
228
  uid: {
227
229
  title: 'UID',
@@ -503,7 +505,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
503
505
  // Video stream properties
504
506
  protected cachedVideoStreamOptions?: UrlMediaStreamOptions[];
505
507
  protected fetchingStreams = false;
506
- protected cachedNetPort?: { rtsp?: { port?: number; enable?: number }; rtmp?: { port?: number; enable?: number } };
507
508
  protected lastNetPortCacheAttempt: number = 0;
508
509
  protected netPortCacheBackoffMs: number = 5000; // 5 seconds backoff on failure
509
510
 
@@ -752,7 +753,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
752
753
  const selection = Array.from(this.getDispatchEventsSelection?.() ?? new Set()).sort();
753
754
  const enabled = selection.length > 0;
754
755
 
755
- logger.log(`subscribeToEvents called: enabled=${enabled}, selection=[${selection.join(', ')}], protocol=${this.protocol}`);
756
+ logger.debug(`subscribeToEvents called: enabled=${enabled}, selection=[${selection.join(', ')}], protocol=${this.protocol}`);
756
757
 
757
758
  this.unsubscribedToEvents();
758
759
 
@@ -1288,54 +1289,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1288
1289
  }
1289
1290
  }
1290
1291
 
1291
- protected getRtspAddress(): string {
1292
- const { ipAddress } = this.storageSettings.values;
1293
- const rtspPort = this.cachedNetPort?.rtsp?.port ?? 554;
1294
- return `${ipAddress}:${rtspPort}`;
1295
- }
1296
-
1297
- protected getRtmpAddress(): string {
1298
- const { ipAddress } = this.storageSettings.values;
1299
- const rtmpPort = this.cachedNetPort?.rtmp?.port ?? 1935;
1300
- return `${ipAddress}:${rtmpPort}`;
1301
- }
1302
-
1303
- protected async ensureNetPortCache(): Promise<void> {
1304
- const logger = this.getBaichuanLogger();
1305
-
1306
- if (this.cachedNetPort) {
1307
- return;
1308
- }
1309
-
1310
- // Implement backoff to avoid spam when socket is closed
1311
- const now = Date.now();
1312
- if (now - this.lastNetPortCacheAttempt < this.netPortCacheBackoffMs) {
1313
- // Use defaults if we're in backoff period
1314
- this.cachedNetPort = {
1315
- rtsp: { port: 554, enable: 1 },
1316
- rtmp: { port: 1935, enable: 1 },
1317
- };
1318
- return;
1319
- }
1320
-
1321
- this.lastNetPortCacheAttempt = now;
1322
-
1323
- try {
1324
- const client = await this.ensureClient();
1325
- this.cachedNetPort = await client.getNetPort();
1326
- } catch (e) {
1327
- // Only log if it's not a recoverable error to avoid spam
1328
- if (!this.isRecoverableBaichuanError?.(e)) {
1329
- logger.warn('Failed to get net port, using defaults', e);
1330
- }
1331
- // Use defaults if we can't get the ports
1332
- this.cachedNetPort = {
1333
- rtsp: { port: 554, enable: 1 },
1334
- rtmp: { port: 1935, enable: 1 },
1335
- };
1336
- }
1337
- }
1338
-
1339
1292
  async getVideoStreamOptions(): Promise<UrlMediaStreamOptions[]> {
1340
1293
  const logger = this.getBaichuanLogger();
1341
1294
 
@@ -1353,36 +1306,44 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1353
1306
 
1354
1307
  const client = await this.ensureClient();
1355
1308
 
1356
- const { ipAddress, rtspChannel } = this.storageSettings.values;
1309
+ const { rtspChannel } = this.storageSettings.values;
1357
1310
 
1358
1311
  try {
1359
- await this.ensureNetPortCache();
1360
- } catch (e) {
1361
- if (!this.isRecoverableBaichuanError?.(e)) {
1362
- logger.warn('Failed to ensure net port cache, falling back to Native', e);
1312
+ const { nativeStreams, rtmpStreams, rtspStreams } = await client.buildVideoStreamOptions(rtspChannel);
1313
+
1314
+ let supportedStreams: ReolinkSupportedStream[] = [];
1315
+ if (this.nvrDevice && this.nvrDevice.info.model === 'HOMEHUB') {
1316
+ supportedStreams = [...nativeStreams, ...rtspStreams, ...rtmpStreams];
1317
+ } else {
1318
+ supportedStreams = [...rtspStreams, ...rtmpStreams, ...nativeStreams];
1363
1319
  }
1364
- }
1365
1320
 
1366
- try {
1367
- streams = await buildVideoStreamOptions(
1368
- {
1369
- client,
1370
- ipAddress,
1371
- cachedNetPort: this.cachedNetPort,
1372
- nvrDevice: this.nvrDevice,
1373
- rtspChannel,
1374
- logger,
1375
- },
1376
- );
1321
+ for (const supportedStream of supportedStreams) {
1322
+ const { id, metadata, url, name, container } = supportedStream;
1323
+
1324
+ const codec = String(metadata.videoEncType || "").includes("264")
1325
+ ? "h264"
1326
+ : String(metadata.videoEncType || "").includes("265")
1327
+ ? "h265"
1328
+ : String(metadata.videoEncType || "").toLowerCase();
1329
+
1330
+ streams.push({
1331
+ id,
1332
+ name,
1333
+ url,
1334
+ container,
1335
+ video: { codec, width: metadata.width, height: metadata.height },
1336
+ // audio: { codec: metadata.audioCodec }
1337
+ })
1338
+ }
1377
1339
  } catch (e) {
1378
1340
  if (!this.isRecoverableBaichuanError?.(e)) {
1379
1341
  logger.warn('Failed to build RTSP/RTMP stream options, falling back to Native', e);
1380
1342
  }
1381
- this.cachedNetPort = undefined;
1382
1343
  }
1383
1344
 
1384
1345
  if (streams.length) {
1385
- logger.log('Fetched video stream options', { streams, netPort: this.cachedNetPort });
1346
+ logger.log('Fetched video stream options', { streams });
1386
1347
  this.cachedVideoStreamOptions = streams;
1387
1348
  return streams;
1388
1349
  }
@@ -1397,13 +1358,13 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1397
1358
  const selected = selectStreamOption(vsos, vso);
1398
1359
 
1399
1360
  // Check if this is a native stream (prefixed with "native_")
1400
- const isNative = isNativeStreamId(selected.id);
1401
1361
 
1402
1362
  // If stream has RTSP/RTMP URL (not native), add credentials and create MediaStreamUrl
1403
- if (!isNative && selected.url && (selected.container === 'rtsp' || selected.container === 'rtmp')) {
1363
+ if (selected.url && (selected.container === 'rtsp' || selected.container === 'rtmp')) {
1404
1364
  const urlWithCredentials = this.addRtspCredentials(selected.url);
1405
1365
  const ret: MediaStreamUrl = {
1406
1366
  container: selected.container,
1367
+ // url: selected.url,
1407
1368
  url: urlWithCredentials,
1408
1369
  mediaStreamOptions: selected,
1409
1370
  };
@@ -1429,22 +1390,16 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1429
1390
  expectedVideoType,
1430
1391
  selected,
1431
1392
  sourceId: this.id,
1432
- onDetectedCodec: (detectedCodec) => {
1433
- // Update cached stream options with detected codec
1434
- const nativeId = `native_${profile}`;
1435
- const name = `Native ${profile}`;
1436
-
1437
- const prev = this.cachedVideoStreamOptions ?? [];
1438
- const next = prev.filter((s) => s.id !== nativeId);
1439
- next.push({
1440
- name,
1441
- id: nativeId,
1442
- container: 'rtp',
1443
- video: { codec: detectedCodec },
1444
- url: ``
1445
- });
1446
- this.cachedVideoStreamOptions = next;
1447
- },
1393
+ // onDetectedCodec: (detectedCodec) => {
1394
+ // const prev = this.cachedVideoStreamOptions ?? [];
1395
+ // const next = prev.filter((s) => s.id !== nativeId);
1396
+ // next.push({
1397
+ // container: 'rtp',
1398
+ // video: { codec: detectedCodec },
1399
+ // url: ``
1400
+ // });
1401
+ // this.cachedVideoStreamOptions = next;
1402
+ // },
1448
1403
  });
1449
1404
  };
1450
1405
 
@@ -1467,7 +1422,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
1467
1422
 
1468
1423
  async credentialsChanged(): Promise<void> {
1469
1424
  this.cachedVideoStreamOptions = undefined;
1470
- this.cachedNetPort = undefined;
1471
1425
  }
1472
1426
 
1473
1427
  // PTZ Presets methods
package/src/connect.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BaichuanClientOptions, ReolinkBaichuanApi, ReolinkDeviceInfo } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
1
+ import type { BaichuanClientOptions, ReolinkBaichuanApi } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
2
2
 
3
3
  export type BaichuanTransport = "tcp" | "udp";
4
4
 
@@ -16,29 +16,6 @@ export function normalizeUid(uid?: string): string | undefined {
16
16
  return v ? v : undefined;
17
17
  }
18
18
 
19
- export function maskUid(uid: string): string {
20
- const v = uid.trim();
21
- if (v.length <= 8) return v;
22
- return `${v.slice(0, 4)}…${v.slice(-4)}`;
23
- }
24
-
25
- export function isTcpFailureThatShouldFallbackToUdp(e: unknown): boolean {
26
- const message = (e as any)?.message || (e as any)?.toString?.() || "";
27
- if (typeof message !== "string") return false;
28
-
29
- // Fallback only on transport/connection style failures.
30
- // Wrong credentials won't be fixed by switching to UDP.
31
- return (
32
- message.includes("ECONNREFUSED") ||
33
- message.includes("ETIMEDOUT") ||
34
- message.includes("EHOSTUNREACH") ||
35
- message.includes("ENETUNREACH") ||
36
- message.includes("socket hang up") ||
37
- message.includes("TCP connection timeout") ||
38
- message.includes("Baichuan socket closed")
39
- );
40
- }
41
-
42
19
  export async function createBaichuanApi(props: {
43
20
  inputs: BaichuanConnectInputs,
44
21
  transport: BaichuanTransport,
@@ -108,178 +85,3 @@ export async function createBaichuanApi(props: {
108
85
  attachErrorHandler(api);
109
86
  return api;
110
87
  }
111
-
112
- export type UdpFallbackInfo = {
113
- host: string;
114
- uid?: string;
115
- uidMissing: boolean;
116
- tcpError: unknown;
117
- };
118
-
119
- export type DeviceType = 'camera' | 'battery-cam' | 'nvr' | 'multifocal';
120
-
121
- export type AutoDetectResult = {
122
- type: DeviceType;
123
- transport: BaichuanTransport;
124
- uid: string;
125
- deviceInfo?: ReolinkDeviceInfo;
126
- channelNum?: number;
127
- };
128
-
129
- /**
130
- * Simple ping check to verify IP is reachable
131
- */
132
- async function pingHost(host: string, timeoutMs: number = 3000): Promise<boolean> {
133
- return new Promise((resolve) => {
134
- const { exec } = require('child_process');
135
- const platform = process.platform;
136
- const pingCmd = platform === 'win32' ? `ping -n 1 -w ${timeoutMs} ${host}` : `ping -c 1 -W ${Math.floor(timeoutMs / 1000)} ${host}`;
137
-
138
- exec(pingCmd, (error: any) => {
139
- resolve(!error);
140
- });
141
- });
142
- }
143
-
144
- /**
145
- * Auto-detect device type by trying TCP first, then UDP if needed.
146
- * - First: Ping the IP to verify it's reachable
147
- * - TCP success: Check if NVR (multiple channels) or regular camera
148
- * - TCP failure: Try UDP (always battery camera)
149
- */
150
- export async function autoDetectDeviceType(
151
- inputs: BaichuanConnectInputs,
152
- logger: Console,
153
- ): Promise<AutoDetectResult> {
154
- const { host, username, password, uid } = inputs;
155
-
156
- // Ping the host first to verify it's reachable
157
- logger.log(`[AutoDetect] Pinging ${host}...`);
158
- const isReachable = await pingHost(host);
159
- if (!isReachable) {
160
- logger.warn(`[AutoDetect] Host ${host} is not reachable via ping, but continuing with connection attempt...`);
161
- } else {
162
- logger.log(`[AutoDetect] Host ${host} is reachable`);
163
- }
164
-
165
- // Try TCP first
166
- let tcpApi: ReolinkBaichuanApi | undefined;
167
- try {
168
- logger.log(`[AutoDetect] Trying TCP connection to ${host}...`);
169
- tcpApi = await createBaichuanApi({
170
- inputs: { host, username, password, logger },
171
- transport: 'tcp',
172
- logger,
173
- });
174
- await tcpApi.login();
175
-
176
- // Get device info to check device type
177
- const deviceInfo = await tcpApi.getInfo();
178
- const { support } = await tcpApi.getDeviceCapabilities();
179
- const channelNum = support?.channelNum ?? 1;
180
-
181
- logger.log(`[AutoDetect] TCP connection successful. channelNum=${channelNum}`);
182
-
183
- // Multi-focal devices have 2 or 3 channels
184
- if (channelNum === 2 || channelNum === 3) {
185
- logger.log(`[AutoDetect] Detected multi-focal device (${channelNum} channels, channelNum=${channelNum})`);
186
- await tcpApi.close();
187
- return {
188
- type: 'multifocal',
189
- transport: 'tcp',
190
- uid: uid || '',
191
- deviceInfo,
192
- channelNum,
193
- };
194
- }
195
-
196
- // If channelNum > 1, it's likely an NVR
197
- if (channelNum > 1) {
198
- logger.log(`[AutoDetect] Detected NVR (${channelNum} channels)`);
199
- await tcpApi.close();
200
- return {
201
- type: 'nvr',
202
- transport: 'tcp',
203
- uid: uid || '',
204
- deviceInfo,
205
- channelNum,
206
- };
207
- }
208
-
209
- // Single channel device - regular camera
210
- logger.log(`[AutoDetect] Detected regular camera (single channel)`);
211
- await tcpApi.close();
212
- return {
213
- type: 'camera',
214
- transport: 'tcp',
215
- uid: uid || '',
216
- deviceInfo,
217
- channelNum: 1,
218
- };
219
- } catch (tcpError) {
220
- // TCP failed, try UDP (battery camera)
221
- if (tcpApi) {
222
- try {
223
- await tcpApi.close();
224
- } catch {
225
- // ignore
226
- }
227
- }
228
-
229
- if (!isTcpFailureThatShouldFallbackToUdp(tcpError)) {
230
- // Not a transport error, rethrow
231
- throw tcpError;
232
- }
233
-
234
- logger.log(`[AutoDetect] TCP failed, trying UDP (battery camera)...`);
235
- const normalizedUid = normalizeUid(uid);
236
- if (!normalizedUid) {
237
- throw new Error(
238
- `TCP connection failed and device likely requires UDP/BCUDP. UID is required for battery cameras (ip=${host}).`
239
- );
240
- }
241
-
242
- try {
243
- const udpApi = await createBaichuanApi({
244
- inputs: { host, username, password, uid: normalizedUid, logger },
245
- transport: 'udp',
246
- logger,
247
- });
248
- await udpApi.login();
249
-
250
- const deviceInfo = await udpApi.getInfo();
251
- const { support } = await udpApi.getDeviceCapabilities();
252
- const channelNum = support?.channelNum ?? 1;
253
-
254
- // Multi-focal devices can also be UDP (battery multi-focal cameras)
255
- if (channelNum === 2 || channelNum === 3) {
256
- logger.log(`[AutoDetect] UDP connection successful. Detected multi-focal device (${channelNum} channels).`);
257
- await udpApi.close();
258
- return {
259
- type: 'multifocal',
260
- transport: 'udp',
261
- uid: normalizedUid,
262
- deviceInfo,
263
- channelNum,
264
- };
265
- }
266
-
267
- // Regular battery camera
268
- logger.log(`[AutoDetect] UDP connection successful. Detected battery camera.`);
269
- await udpApi.close();
270
-
271
- return {
272
- type: 'battery-cam',
273
- transport: 'udp',
274
- uid: normalizedUid,
275
- deviceInfo,
276
- channelNum: 1,
277
- };
278
- } catch (udpError) {
279
- logger.error(`[AutoDetect] Both TCP and UDP failed. TCP error: ${tcpError}, UDP error: ${udpError}`);
280
- throw new Error(
281
- `Failed to connect via both TCP and UDP. TCP: ${(tcpError as any)?.message || tcpError}, UDP: ${(udpError as any)?.message || udpError}`
282
- );
283
- }
284
- }
285
- }
package/src/main.ts CHANGED
@@ -1,12 +1,12 @@
1
- import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceInformation, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, Settings } from "@scrypted/sdk";
1
+ import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting } from "@scrypted/sdk";
2
+ import { BaseBaichuanClass } from "./baichuan-base";
2
3
  import { ReolinkNativeCamera } from "./camera";
3
4
  import { ReolinkNativeBatteryCamera } from "./camera-battery";
4
- import { ReolinkNativeNvrDevice } from "./nvr";
5
+ import { CommonCameraMixin } from "./common";
6
+ import { createBaichuanApi } from "./connect";
5
7
  import { ReolinkNativeMultiFocalDevice } from "./multifocal";
6
- import { autoDetectDeviceType, createBaichuanApi, type BaichuanTransport } from "./connect";
8
+ import { ReolinkNativeNvrDevice } from "./nvr";
7
9
  import { batteryCameraSuffix, cameraSuffix, getDeviceInterfaces, multifocalSuffix, nvrSuffix } from "./utils";
8
- import { BaseBaichuanClass } from "./baichuan-base";
9
- import { CommonCameraMixin } from "./common";
10
10
 
11
11
  class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
12
12
  devices = new Map<string, BaseBaichuanClass>();
@@ -45,6 +45,8 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
45
45
 
46
46
  // Auto-detect device type (camera, battery-cam, or nvr)
47
47
  this.console.log(`[AutoDetect] Starting device type detection for ${ipAddress}...`);
48
+ const { autoDetectDeviceType } = await import("@apocaliss92/reolink-baichuan-js");
49
+
48
50
  const detection = await autoDetectDeviceType(
49
51
  {
50
52
  host: ipAddress,
@@ -53,7 +55,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
53
55
  uid,
54
56
  logger: this.console,
55
57
  },
56
- this.console
57
58
  );
58
59
 
59
60
  this.console.log(`[AutoDetect] Detected device type: ${detection.type} (transport: ${detection.transport})`);
@@ -88,11 +89,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
88
89
  device.storageSettings.values.username = username;
89
90
  device.storageSettings.values.password = password;
90
91
  device.storageSettings.values.uid = detection.uid || '';
91
-
92
- // Update the protocol based on detection result
93
- // Note: This requires updating the protocol property, but it's readonly
94
- // The transport is already set in the constructor during createDevice
95
- // For now, we'll rely on the constructor parameter
96
92
 
97
93
  return nativeId;
98
94
  }
@@ -162,7 +158,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
162
158
  const rtspChannel = 0;
163
159
  const { abilities, capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel);
164
160
 
165
- this.console.log(JSON.stringify({ abilities, capabilities, deviceInfo }));
161
+ this.console.log(nativeId, JSON.stringify({ abilities, capabilities, deviceInfo }));
166
162
 
167
163
  const { interfaces, type } = getDeviceInterfaces({
168
164
  capabilities,
@@ -178,6 +174,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
178
174
  });
179
175
 
180
176
  const device = await this.getDevice(nativeId) as CommonCameraMixin;
177
+ this.console.log(name, interfaces, type, device);
181
178
 
182
179
  device.info = deviceInfo;
183
180
  device.classes = objects;
@@ -240,10 +237,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
240
237
  } else if (nativeId.endsWith(nvrSuffix)) {
241
238
  return new ReolinkNativeNvrDevice(nativeId, this);
242
239
  } else if (nativeId.endsWith(multifocalSuffix)) {
243
- // Get transport from device settings if available, otherwise default to TCP
244
- // The transport is determined during autoDetect and should be stored
245
- // For now, we'll try to infer from UID presence (if UID is set, likely UDP)
246
- // Default to TCP for now - the transport will be set correctly during createDevice
247
240
  return new ReolinkNativeMultiFocalDevice(nativeId, this, 'tcp');
248
241
  } else {
249
242
  return new ReolinkNativeCamera(nativeId, this);