@apocaliss92/scrypted-reolink-native 0.2.9 → 0.2.10

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.2.9",
3
+ "version": "0.2.10",
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",
@@ -366,7 +366,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
366
366
  }
367
367
  }
368
368
  catch (e) {
369
- logger.debug(`Could not get connection state: ${e}`);
369
+ logger.debug(`Could not get connection state: ${e?.message || String(e)}`);
370
370
  }
371
371
 
372
372
  const now = Date.now();
@@ -500,7 +500,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
500
500
  logger.log('Subscribed to Baichuan events');
501
501
  }
502
502
  catch (e) {
503
- logger.warn('Failed to subscribe to events', e);
503
+ logger.warn('Failed to subscribe to events', e?.message || String(e));
504
504
  this.eventSubscriptionActive = false;
505
505
  }
506
506
  }
@@ -519,7 +519,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
519
519
  logger.debug('Unsubscribed from Baichuan events');
520
520
  }
521
521
  catch (e) {
522
- logger.warn('Error unsubscribing from events', e);
522
+ logger.warn('Error unsubscribing from events', e?.message || String(e));
523
523
  }
524
524
  }
525
525
 
package/src/camera.ts CHANGED
@@ -17,10 +17,7 @@ import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
17
17
  import { ReolinkNativeNvrDevice } from "./nvr";
18
18
  import { ReolinkPtzPresets } from "./presets";
19
19
  import {
20
- createRfc4571CompositeMediaObjectFromStreamManager,
21
20
  createRfc4571MediaObjectFromStreamManager,
22
- extractVariantFromStreamId,
23
- parseStreamProfileFromId,
24
21
  selectStreamOption,
25
22
  StreamManager,
26
23
  StreamManagerOptions
@@ -48,7 +45,7 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
48
45
  this.camera.getBaichuanLogger().log(`Siren toggle: turnOff ok (device=${this.nativeId})`);
49
46
  }
50
47
  catch (e) {
51
- this.camera.getBaichuanLogger().warn(`Siren toggle: turnOff failed (device=${this.nativeId})`, e?.message || String(e));
48
+ this.camera.getBaichuanLogger().error(`Siren toggle: turnOff failed (device=${this.nativeId})`, e?.message || String(e));
52
49
  throw e;
53
50
  }
54
51
  }
@@ -61,7 +58,7 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
61
58
  this.camera.getBaichuanLogger().log(`Siren toggle: turnOn ok (device=${this.nativeId})`);
62
59
  }
63
60
  catch (e) {
64
- this.camera.getBaichuanLogger().warn(`Siren toggle: turnOn failed (device=${this.nativeId})`, e?.message || String(e));
61
+ this.camera.getBaichuanLogger().error(`Siren toggle: turnOn failed (device=${this.nativeId})`, e?.message || String(e));
65
62
  throw e;
66
63
  }
67
64
  }
@@ -513,6 +510,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
513
510
  subgroup: 'Diagnostics',
514
511
  description: "Directory where diagnostics files will be saved (default: plugin volume).",
515
512
  type: "string",
513
+ hide: true,
516
514
  defaultValue: path.join(process.env.SCRYPTED_PLUGIN_VOLUME, 'diagnostics', this.name),
517
515
  },
518
516
  enableVideoclips: {
@@ -521,6 +519,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
521
519
  type: "boolean",
522
520
  defaultValue: false,
523
521
  immediate: true,
522
+ hide: true,
524
523
  onPut: async () => {
525
524
  this.updateVideoClipsAutoLoad();
526
525
  },
@@ -532,6 +531,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
532
531
  type: "string",
533
532
  choices: ["NVR", "Device"],
534
533
  immediate: true,
534
+ hide: true,
535
535
  },
536
536
  loadVideoclips: {
537
537
  title: "Auto-load Video Clips",
@@ -540,6 +540,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
540
540
  type: "boolean",
541
541
  defaultValue: false,
542
542
  immediate: true,
543
+ hide: true,
543
544
  onPut: async () => {
544
545
  this.updateVideoClipsAutoLoad();
545
546
  },
@@ -550,6 +551,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
550
551
  description: "How often to check for new video clips and download thumbnails (default: 30 minutes).",
551
552
  type: "number",
552
553
  defaultValue: 30,
554
+ hide: true,
553
555
  onPut: async () => {
554
556
  this.updateVideoClipsAutoLoad();
555
557
  },
@@ -561,6 +563,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
561
563
  type: "boolean",
562
564
  defaultValue: false,
563
565
  immediate: true,
566
+ hide: true,
564
567
  onPut: async () => {
565
568
  this.updateVideoClipsAutoLoad();
566
569
  },
@@ -571,6 +574,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
571
574
  description: "Number of days to preload video clips and thumbnails (default: 1, only today).",
572
575
  type: "number",
573
576
  defaultValue: 3,
577
+ hide: true,
574
578
  onPut: async () => {
575
579
  this.updateVideoClipsAutoLoad();
576
580
  },
@@ -580,6 +584,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
580
584
  title: 'Run Diagnostics',
581
585
  description: 'Run all diagnostics and save results to the output path.',
582
586
  type: 'button',
587
+ hide: true,
583
588
  immediate: true,
584
589
  onPut: async () => {
585
590
  await this.runDiagnostics();
@@ -785,18 +790,16 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
785
790
  // Fetch from NVR using listEnrichedVodFiles (library handles parsing correctly)
786
791
  const channel = this.storageSettings.values.rtspChannel ?? 0;
787
792
 
788
- // Use listEnrichedVodFiles which properly parses filenames and extracts detection info
789
- logger.debug(`[NVR VOD] Searching for video clips: channel=${channel}, start=${start.toISOString()}, end=${end.toISOString()}`);
790
- // Filter to only include recordings within the requested time window
791
- const enrichedRecordings = await api.listNvrRecordings({
792
- channel,
793
+ // Prefer Hub-like event listing (alarm events) instead of full VOD.
794
+ logger.debug(`[NVR EVENTS] Searching for alarm events: channel=${channel}, start=${start.toISOString()}, end=${end.toISOString()}`);
795
+ const enrichedRecordings = await api.listNvrAlarmEventsEnrichedViaBaichuan({
793
796
  start,
794
797
  end,
795
- streamType: "main",
796
- source: "baichuan"
798
+ channels: [channel],
799
+ streamType: "mainStream",
797
800
  });
798
801
 
799
- logger.debug(`[NVR VOD] Found ${enrichedRecordings.length} enriched recordings from NVR`);
802
+ logger.debug(`[NVR EVENTS] Found ${enrichedRecordings.length} enriched alarm events from NVR`);
800
803
 
801
804
  // Convert enriched recordings to VideoClip array using the shared parser
802
805
  const clips = await recordingsToVideoClips(enrichedRecordings, {
@@ -808,7 +811,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
808
811
  count,
809
812
  });
810
813
 
811
- logger.debug(`[NVR VOD] Converted ${clips.length} video clips (limit: ${count || 'none'})`);
814
+ logger.debug(`[NVR EVENTS] Converted ${clips.length} video clips (limit: ${count || 'none'})`);
812
815
 
813
816
  return clips;
814
817
  } else {
@@ -893,7 +896,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
893
896
  try {
894
897
  await fs.promises.unlink(cachePath);
895
898
  } catch (unlinkErr) {
896
- logger.warn(`[VideoClip] Failed to delete small cached file: fileId=${videoId}`, unlinkErr);
899
+ logger.warn(`[VideoClip] Failed to delete small cached file: fileId=${videoId}`, unlinkErr?.message || String(unlinkErr));
897
900
  }
898
901
  } else {
899
902
  logger.debug(`[VideoClip] Using cached file: fileId=${videoId}, size=${stats.size} bytes`);
@@ -1011,7 +1014,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
1011
1014
  });
1012
1015
 
1013
1016
  ffmpeg.on('error', (error) => {
1014
- logger.error(`ffmpeg spawn error for video clip ${videoId}`, error);
1017
+ logger.error(`ffmpeg spawn error for video clip ${videoId}`, error?.message || String(error));
1015
1018
  reject(error);
1016
1019
  });
1017
1020
 
@@ -1079,7 +1082,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
1079
1082
  try {
1080
1083
  await fs.promises.unlink(cachePath);
1081
1084
  } catch (unlinkErr) {
1082
- logger.warn(`[Thumbnail] Failed to delete small cached thumbnail: fileId=${thumbnailId}`, unlinkErr);
1085
+ logger.warn(`[Thumbnail] Failed to delete small cached thumbnail: fileId=${thumbnailId}`, unlinkErr?.message || String(unlinkErr));
1083
1086
  }
1084
1087
  } else {
1085
1088
  logger.debug(`[Thumbnail] Using cached: fileId=${thumbnailId}, size=${stats.size} bytes`);
@@ -1645,14 +1648,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
1645
1648
  this.lastPicture = undefined;
1646
1649
  }
1647
1650
 
1648
- // Notify consumers (e.g. prebuffer) that stream configuration changed.
1649
- try {
1650
- this.onDeviceEvent(ScryptedInterface.VideoCamera, undefined);
1651
- } catch {
1652
- // best-effort
1653
- }
1651
+ this.onDeviceEvent(ScryptedInterface.VideoCamera, undefined);
1654
1652
  } catch (e) {
1655
- logger.warn('Failed to restart StreamManager after settings change', e);
1653
+ logger.error('Failed to restart StreamManager after settings change', e?.message || String(e));
1656
1654
  }
1657
1655
  }, 500);
1658
1656
  }
@@ -1713,14 +1711,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
1713
1711
  reason: ev?.type === 'sleeping' ? 'sleeping' : 'awake',
1714
1712
  state: ev.type === 'sleeping' ? 'sleeping' : 'awake',
1715
1713
  }).catch((e) => {
1716
- logger.warn('Error updating sleeping state', e);
1714
+ logger.warn('Error updating sleeping state', e?.message || String(e));
1717
1715
  });
1718
1716
  return;
1719
1717
 
1720
1718
  case 'offline':
1721
1719
  case 'online':
1722
1720
  this.updateOnlineState(ev.type === 'online').catch((e) => {
1723
- logger.warn('Error updating online state', e);
1721
+ logger.warn('Error updating online state', e?.message || String(e));
1724
1722
  });
1725
1723
  return;
1726
1724
 
@@ -1749,11 +1747,11 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
1749
1747
  }
1750
1748
 
1751
1749
  this.processEvents({ motion, objects }).catch((e) => {
1752
- logger.warn('Error processing events', e);
1750
+ logger.warn('Error processing events', e?.message || String(e));
1753
1751
  });
1754
1752
  }
1755
1753
  catch (e) {
1756
- logger.warn('Error in onSimpleEvent handler', e);
1754
+ logger.warn('Error in onSimpleEvent handler', e?.message || String(e));
1757
1755
  }
1758
1756
  }
1759
1757
 
@@ -1801,7 +1799,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
1801
1799
  logger.log(`Subscribed to events (${selection.join(', ')}) on ${this.protocol} connection`);
1802
1800
  }
1803
1801
  catch (e) {
1804
- logger.warn('Failed to subscribe to Baichuan events', e);
1802
+ logger.warn('Failed to subscribe to Baichuan events', e?.message || String(e));
1805
1803
  return;
1806
1804
  }
1807
1805
  }
@@ -2141,7 +2139,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2141
2139
  return await this.takePictureInternal(client);
2142
2140
  });
2143
2141
  } catch (e) {
2144
- this.getBaichuanLogger().error('Error taking snapshot', e);
2142
+ this.getBaichuanLogger().error('Error taking snapshot', e?.message || String(e));
2145
2143
  throw e;
2146
2144
  }
2147
2145
  } else {
@@ -2217,7 +2215,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2217
2215
  });
2218
2216
 
2219
2217
  } catch (e) {
2220
- logger.warn('Failed to fetch device info', e);
2218
+ logger.warn('Failed to fetch device info', e?.message || String(e));
2221
2219
  }
2222
2220
  }
2223
2221
 
@@ -2440,37 +2438,32 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2440
2438
  // logger.log({ supportedStreams, variantType, lensParam, rtspChannel, onNvr: this.isOnNvr, nativeStreams: nativeStreams.map(s => ({ id: s.id, nativeVariant: s.nativeVariant, lens: s.lens })), rtspStreams: rtspStreams.map(s => ({ id: s.id, lens: s.lens })), rtmpStreams: rtmpStreams.map(s => ({ id: s.id, lens: s.lens })) });
2441
2439
 
2442
2440
  for (const supportedStream of supportedStreams) {
2443
- const { id, metadata, url, name, container, nativeVariant, lens } = supportedStream;
2441
+ const { id, metadata, url, name, container, lens } = supportedStream;
2444
2442
 
2445
2443
  // Composite streams are re-encoded to H.264 by the library (ffmpeg/libx264).
2446
2444
  // Do not infer codec from underlying camera metadata.
2447
2445
  const isComposite = id.startsWith('composite_') || lens === 'composite';
2448
- const codec = isComposite
2449
- ? 'h264'
2450
- : String(metadata.videoEncType || "").includes("264")
2451
- ? "h264"
2452
- : String(metadata.videoEncType || "").includes("265")
2453
- ? "h265"
2454
- : String(metadata.videoEncType || "").toLowerCase();
2455
-
2456
- // Preserve variant information for native RTP streams by ensuring the URL contains it.
2457
- let finalUrl = url;
2458
- const variantFromIdOrUrl = extractVariantFromStreamId(id, url);
2459
- const variantToInject = (nativeVariant && nativeVariant !== 'default')
2460
- ? nativeVariant
2461
- : variantFromIdOrUrl;
2462
-
2463
- if (variantToInject && container === 'rtp') {
2464
- try {
2465
- const urlObj = new URL(url);
2466
- if (!urlObj.searchParams.has('variant')) {
2467
- urlObj.searchParams.set('variant', variantToInject);
2468
- finalUrl = urlObj.toString();
2469
- }
2470
- } catch {
2471
- // Invalid URL, use original
2446
+ const codec = (() => {
2447
+ if (isComposite) return 'h264';
2448
+
2449
+ const enc = (metadata as any)?.videoEncType;
2450
+ // Many firmwares expose videoEncType as a numeric enum.
2451
+ // Observed: 0 => H.264, 1 => H.265.
2452
+ if (typeof enc === 'number') {
2453
+ if (enc === 0) return 'h264';
2454
+ if (enc === 1) return 'h265';
2472
2455
  }
2473
- }
2456
+
2457
+ const s = String(enc ?? '').toLowerCase();
2458
+ if (s === '0') return 'h264';
2459
+ if (s === '1') return 'h265';
2460
+ if (s.includes('264')) return 'h264';
2461
+ if (s.includes('265')) return 'h265';
2462
+ return s;
2463
+ })();
2464
+
2465
+ // For RTP (native RFC4571), stream identification happens via `id` (streamKey), not URL.
2466
+ const finalUrl = url;
2474
2467
 
2475
2468
  streams.push({
2476
2469
  id,
@@ -2513,43 +2506,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2513
2506
 
2514
2507
  const selected = selectStreamOption(vsos, vso);
2515
2508
 
2516
- // If the request explicitly asks for a variant (e.g. native_telephoto_main),
2517
- // never override it with the device's variantType preference.
2518
- // const requestedVariant = vso?.id ? extractVariantFromStreamId(vso.id, undefined) : undefined;
2519
-
2520
- // If we have variantType set and the selected stream doesn't have the variant,
2521
- // try to find a stream with the correct variant that matches the profile
2522
- // const variantType = this.storageSettings.values.variantType;
2523
- // if (!requestedVariant && variantType && variantType !== 'default') {
2524
- // const profile = parseStreamProfileFromId(selected.id) || 'main';
2525
-
2526
- // // On NVR, firmwares vary: some expose the tele lens as 'autotrack', others as 'telephoto'.
2527
- // // When variantType is set, prefer that variant but fall back to the other tele variant if present.
2528
- // const preferred = variantType as 'autotrack' | 'telephoto';
2529
- // const fallbacks: Array<'autotrack' | 'telephoto'> = this.isOnNvr && preferred === 'telephoto'
2530
- // ? ['telephoto', 'autotrack']
2531
- // : this.isOnNvr && preferred === 'autotrack'
2532
- // ? ['autotrack', 'telephoto']
2533
- // : [preferred];
2534
-
2535
- // const extractedVariant = extractVariantFromStreamId(selected.id, selected.url);
2536
- // for (const v of fallbacks) {
2537
- // const variantId = `native_${v}_${profile}`;
2538
- // const variantStream = vsos?.find(s => s.id === variantId);
2539
- // if (!variantStream) {
2540
- // logger.debug(`Variant stream '${variantId}' not found in available streams`);
2541
- // continue;
2542
- // }
2543
- // // Only use variant stream if the selected one doesn't already have a variant,
2544
- // // or if the selected one has a different variant than what we want.
2545
- // if (!extractedVariant || extractedVariant !== v) {
2546
- // logger.log(`Preferring variant stream: '${variantId}' over '${selected.id}' (variantType='${variantType}')`);
2547
- // selected = variantStream;
2548
- // }
2549
- // break;
2550
- // }
2551
- // }
2552
-
2553
2509
  logger.log(`Selected stream: id='${selected.id}', url='${selected.url}'`);
2554
2510
 
2555
2511
  if (selected.url && (selected.container === 'rtsp' || selected.container === 'rtmp')) {
@@ -2566,76 +2522,21 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2566
2522
  throw new Error('StreamManager not initialized');
2567
2523
  }
2568
2524
 
2569
- // Check if this is a composite stream request (for multifocal devices)
2570
- // const isComposite = selected.id?.startsWith('composite_');
2571
- // if (isComposite && this.options && (this.options.type === 'multi-focal' || this.options.type === 'multi-focal-battery')) {
2572
- if (selected.id?.startsWith('composite_')) {
2573
- const profile = parseStreamProfileFromId(selected.id.replace('composite_', '')) || 'main';
2574
- // Include variantType in streamKey to ensure each variantType has its own unique socket
2575
- // This is important for multifocal devices where different variantTypes may request composite streams
2576
- const variantType = this.storageSettings.values.variantType || 'default';
2577
- const streamKey = `composite_${variantType}_${profile}`;
2578
-
2579
- logger.log(`Creating composite stream: profile=${profile}, variantType=${variantType}, streamKey=${streamKey}`);
2580
-
2581
- const createStreamFn = async () => {
2582
- return await createRfc4571CompositeMediaObjectFromStreamManager({
2583
- streamManager: this.streamManager,
2584
- profile,
2585
- streamKey,
2586
- selected,
2587
- sourceId: this.id,
2588
- variantType,
2589
- });
2590
- };
2591
-
2592
- return await this.withBaichuanRetry(createStreamFn);
2525
+ const streamKey = selected.id;
2526
+ if (!streamKey) {
2527
+ throw new Error('Missing streamKey (selected.id) for RTP stream');
2593
2528
  }
2594
2529
 
2595
- // Regular stream for single channel
2596
- const profile = parseStreamProfileFromId(selected.id) || 'main';
2597
- const channel = this.storageSettings.values.rtspChannel;
2598
- // Extract variant from stream ID or URL if present (e.g., "autotrack" from "native_autotrack_main" or "?variant=autotrack")
2599
- let variant = extractVariantFromStreamId(selected.id, selected.url);
2600
-
2601
- // Fallback: if no variant found in stream ID/URL, use variantType from device settings.
2602
- // IMPORTANT:
2603
- // - On NVR/Hub multifocal setups, the tele lens is often selected via a variant (autotrack/telephoto) on the same channel.
2604
- // - On standalone TrackMix (no NVR), the tele lens is selected via channel=1 (no variant).
2605
- // Forcing a variant on standalone can result in a started stream with no frames.
2606
- if (!variant && this.storageSettings.values.variantType && this.storageSettings.values.variantType !== 'default') {
2607
- if (this.isOnNvr) {
2608
- variant = this.storageSettings.values.variantType as 'autotrack' | 'telephoto';
2609
- logger.log(`Using variant from device settings: '${variant}' (not found in stream ID/URL)`);
2610
- } else {
2611
- logger.log(
2612
- `Ignoring device variantType '${this.storageSettings.values.variantType}' for standalone stream (channel-based lens selection)`
2613
- );
2614
- }
2615
- }
2616
-
2617
- logger.log(`Stream selection: id='${selected.id}', profile='${profile}', channel=${channel}, variant='${variant || 'default'}'`);
2618
-
2619
- // Include variant in streamKey to distinguish streams with different variants
2620
- const streamKey = variant ? `${channel}_${variant}_${profile}` : `${channel}_${profile}`;
2621
-
2622
- const createStreamFn = async () => {
2623
- // Honor the requested variant. Some NVR firmwares label the tele lens as either
2624
- // 'autotrack' or 'telephoto', and the library exposes both when available.
2625
- logger.log(`Creating RFC4571 stream: channel=${channel}, profile=${profile}, variant=${variant || 'default'}, streamKey=${streamKey}`);
2530
+ logger.log(`Creating RFC4571 stream: streamKey='${streamKey}'`);
2626
2531
 
2532
+ return await this.withBaichuanRetry(async () => {
2627
2533
  return await createRfc4571MediaObjectFromStreamManager({
2628
2534
  streamManager: this.streamManager!,
2629
- channel,
2630
- profile,
2631
2535
  streamKey,
2632
- variant,
2633
2536
  selected,
2634
2537
  sourceId: this.id,
2635
2538
  });
2636
- };
2637
-
2638
- return await this.withBaichuanRetry(createStreamFn);
2539
+ });
2639
2540
  }
2640
2541
 
2641
2542
  async ensureClient(): Promise<ReolinkBaichuanApi> {
@@ -2646,7 +2547,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2646
2547
  return await this.multiFocalDevice.ensureClient();
2647
2548
  }
2648
2549
 
2649
- // Use base class implementation
2650
2550
  return await this.ensureBaichuanClient();
2651
2551
  }
2652
2552
 
@@ -2654,7 +2554,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2654
2554
  this.cachedVideoStreamOptions = undefined;
2655
2555
  }
2656
2556
 
2657
- // PTZ Presets methods
2658
2557
  getSelectedPresetId(): number | undefined {
2659
2558
  const s = this.storageSettings.values.ptzSelectedPreset;
2660
2559
  if (!s) return undefined;
@@ -2767,16 +2666,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2767
2666
  this.storageSettings.settings.clipsSource.hide = !this.nvrDevice;
2768
2667
  this.storageSettings.settings.clipsSource.defaultValue = this.nvrDevice ? "NVR" : "Device";
2769
2668
 
2770
- // if (!!this.multiFocalDevice) {
2771
- // const allSettingKeys = Object.keys(this.storageSettings.settings);
2669
+ this.storageSettings.settings.diagnosticsRun.hide = !!this.multiFocalDevice;
2670
+ this.storageSettings.settings.diagnosticsOutputPath.hide = !!this.multiFocalDevice;
2772
2671
 
2773
- // for (const key of allSettingKeys) {
2774
- // const setting = this.storageSettings.settings[key];
2775
- // if (['Videoclips', 'PTZ'].includes(setting.subgroup)) {
2776
- // setting.hide = true;
2777
- // }
2778
- // }
2779
- // }
2780
2672
  this.storageSettings.settings.enableVideoclips.hide = !!this.multiFocalDevice;
2781
2673
  this.storageSettings.settings.videoclipsDaysToPreload.hide = !!this.multiFocalDevice;
2782
2674
  this.storageSettings.settings.videoclipsRegularChecks.hide = !!this.multiFocalDevice;
@@ -2817,14 +2709,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2817
2709
  await this.subscribeToEvents();
2818
2710
  }
2819
2711
  catch (e) {
2820
- logger.warn('Failed to subscribe to Baichuan events', e);
2712
+ logger.error('Failed to subscribe to Baichuan events', e?.message || String(e));
2821
2713
  }
2822
2714
 
2823
2715
  try {
2824
2716
  this.initStreamManager();
2825
2717
  }
2826
2718
  catch (e) {
2827
- logger.warn('Failed to initialize StreamManager', e);
2719
+ logger.error('Failed to initialize StreamManager', e?.message || String(e));
2828
2720
  }
2829
2721
 
2830
2722
  const { hasIntercom, hasPtz } = this.getAbilities();
@@ -2833,7 +2725,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2833
2725
  this.intercom = new ReolinkBaichuanIntercom(this);
2834
2726
  }
2835
2727
 
2836
- if (hasPtz && !this.multiFocalDevice) {
2728
+ if (hasPtz) {
2837
2729
  const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
2838
2730
 
2839
2731
  this.storageSettings.settings.presets.choices = choices;
@@ -2897,7 +2789,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2897
2789
  }
2898
2790
  } catch (e) {
2899
2791
  // Silently ignore errors in sleep check to avoid spam
2900
- this.getBaichuanLogger().debug('Error in updateSleepingState:', e);
2792
+ this.getBaichuanLogger().debug('Error in updateSleepingState:', e?.message || String(e));
2901
2793
  }
2902
2794
  }
2903
2795
 
@@ -2912,7 +2804,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2912
2804
  }
2913
2805
  } catch (e) {
2914
2806
  // Silently ignore errors in sleep check to avoid spam
2915
- this.getBaichuanLogger().debug('Error in updateOnlineState:', e);
2807
+ this.getBaichuanLogger().debug('Error in updateOnlineState:', e?.message || String(e));
2916
2808
  }
2917
2809
  }
2918
2810
 
@@ -2922,7 +2814,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2922
2814
  await this.baichuanApi?.close();
2923
2815
  }
2924
2816
  catch (e) {
2925
- this.getBaichuanLogger().warn('Error closing Baichuan client during reset', e?.message || String(e));
2817
+ this.getBaichuanLogger().error('Error closing Baichuan client during reset', e?.message || String(e));
2926
2818
  }
2927
2819
  finally {
2928
2820
  this.baichuanApi = undefined;
@@ -2932,7 +2824,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
2932
2824
 
2933
2825
  if (reason) {
2934
2826
  const message = reason?.message || reason?.toString?.() || reason;
2935
- this.getBaichuanLogger().warn(`Baichuan client reset requested: ${message}`);
2827
+ this.getBaichuanLogger().error(`Baichuan client reset requested: ${message}`);
2936
2828
  }
2937
2829
  }
2938
2830
 
@@ -3022,7 +2914,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3022
2914
  // Ensure we have a client connection
3023
2915
  const api = await this.ensureClient();
3024
2916
  if (!api) {
3025
- this.getBaichuanLogger().warn('Failed to ensure client connection for battery update');
2917
+ this.getBaichuanLogger().error('Failed to ensure client connection for battery update');
3026
2918
  return;
3027
2919
  }
3028
2920
 
@@ -3036,7 +2928,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3036
2928
  await api.wakeUp(channel, { waitAfterWakeMs: 2000 });
3037
2929
  logger.log('Wake command sent, waiting for camera to wake up...');
3038
2930
  } catch (wakeError) {
3039
- logger.warn('Failed to wake up camera:', wakeError);
2931
+ logger.error('Failed to wake up camera:', wakeError?.message || String(wakeError));
3040
2932
  return;
3041
2933
  }
3042
2934
 
@@ -3057,7 +2949,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3057
2949
  }
3058
2950
 
3059
2951
  if (!awake) {
3060
- logger.warn('Camera did not wake up within timeout, skipping update');
2952
+ logger.error('Camera did not wake up within timeout, skipping update');
3061
2953
  return;
3062
2954
  }
3063
2955
  } else if (sleepStatus.state === 'awake') {
@@ -3069,14 +2961,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3069
2961
  try {
3070
2962
  await this.updateBatteryInfo();
3071
2963
  } catch (e) {
3072
- logger.warn('Failed to get battery info during periodic update:', e);
2964
+ logger.error('Failed to get battery info during periodic update:', e?.message || String(e));
3073
2965
  }
3074
2966
 
3075
2967
  // 2. Align auxiliary devices state
3076
2968
  try {
3077
2969
  await this.alignAuxDevicesState();
3078
2970
  } catch (e) {
3079
- logger.warn('Failed to align auxiliary devices state:', e);
2971
+ logger.error('Failed to align auxiliary devices state:', e?.message || String(e));
3080
2972
  }
3081
2973
 
3082
2974
  // 3. Update snapshot
@@ -3085,10 +2977,10 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3085
2977
  await this.takePicture();
3086
2978
  logger.log('Snapshot updated during periodic update');
3087
2979
  } catch (snapshotError) {
3088
- logger.warn('Failed to update snapshot during periodic update:', snapshotError);
2980
+ logger.error('Failed to update snapshot during periodic update:', snapshotError?.message || String(snapshotError));
3089
2981
  }
3090
2982
  } catch (e) {
3091
- logger.warn('Failed to update battery and snapshot', e);
2983
+ logger.error('Failed to update battery and snapshot', e?.message || String(e));
3092
2984
  } finally {
3093
2985
  // Clear the promise when done (success or failure)
3094
2986
  this.batteryUpdatePromise = undefined;
@@ -3123,7 +3015,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3123
3015
  const sleepStatus = api.getSleepStatus({ channel });
3124
3016
  await this.updateSleepingState(sleepStatus);
3125
3017
  } catch (e) {
3126
- logger.warn('Error checking sleeping state:', e?.message || String(e));
3018
+ logger.error('Error checking sleeping state:', e?.message || String(e));
3127
3019
  }
3128
3020
  }, 5_000);
3129
3021
  }
@@ -3135,7 +3027,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3135
3027
  try {
3136
3028
  await this.updateBatteryAndSnapshot();
3137
3029
  } catch (e) {
3138
- logger.warn('Error updating battery and snapshot:', e?.message || String(e));
3030
+ logger.error('Error updating battery and snapshot:', e?.message || String(e));
3139
3031
  }
3140
3032
  }, updateIntervalMs);
3141
3033
 
@@ -3145,7 +3037,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
3145
3037
  try {
3146
3038
  await this.alignAuxDevicesState();
3147
3039
  } catch (e) {
3148
- logger.warn('Error aligning auxiliary devices state:', e?.message || String(e));
3040
+ logger.error('Error aligning auxiliary devices state:', e?.message || String(e));
3149
3041
  }
3150
3042
  }, 10_000);
3151
3043