@camstack/addon-provider-reolink 1.2.40 → 1.2.42

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.
Files changed (3) hide show
  1. package/dist/addon.js +110 -28
  2. package/dist/addon.mjs +110 -28
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -26,7 +26,7 @@ let fs_promises = require("fs/promises");
26
26
  fs_promises = require_chunk.__toESM(fs_promises, 1);
27
27
  let node_os = require("node:os");
28
28
  node_os = require_chunk.__toESM(node_os);
29
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
29
+ //#region ../types/dist/event-category-C0lyLd5U.mjs
30
30
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
31
31
  EventCategory["SystemBoot"] = "system.boot";
32
32
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -43,9 +43,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
43
43
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
44
44
  /**
45
45
  * A newer addon or server-root package version was found by the
46
- * authoritative registry check. Emitted once per
47
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
48
- * polling of the same result is deduplicated by the checker.
46
+ * authoritative registry check. Emitted once when any observed
47
+ * `latestVersion` changes (or a package/node first appears behind);
48
+ * the payload carries the full currently-available list. Repeated
49
+ * polling of the same latests is silent.
49
50
  */
50
51
  EventCategory["UpdateAvailable"] = "update.available";
51
52
  /**
@@ -7587,6 +7588,10 @@ var RecordingBandSchema = object({
7587
7588
  preBufferSec: number().min(0).optional(),
7588
7589
  postBufferSec: number().min(0).optional()
7589
7590
  });
7591
+ ({
7592
+ preBufferSec: 10,
7593
+ postBufferSec: 30
7594
+ }).postBufferSec * 1e3;
7590
7595
  /**
7591
7596
  * Per-device retention overrides. Every field is optional; an unset or `0`
7592
7597
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -14773,6 +14778,9 @@ var NcSystemEventKindSchema = _enum([
14773
14778
  "alarm-disarmed",
14774
14779
  "alarm-arming",
14775
14780
  "alarm-arm-refused",
14781
+ "addon-updated",
14782
+ "server-updated",
14783
+ "export-completed",
14776
14784
  "camera-online",
14777
14785
  "camera-offline",
14778
14786
  "camera-disabled",
@@ -16666,13 +16674,19 @@ var RetrainStatusSchema = _enum([
16666
16674
  * "never marked" from "already trained" must read `retrainStatus`.
16667
16675
  *
16668
16676
  * `debug` does NOT pin; it is attention, not durability.
16677
+ *
16678
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16679
+ * A favourited track is skipped by retention the same way `staging` is, but
16680
+ * it does not enter `none|staging|trained` and has no staging budget.
16669
16681
  */
16670
16682
  var TrackFlagFields = {
16671
16683
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16672
16684
  * `'staging'`. */
16673
16685
  markForTrain: boolean().optional(),
16674
16686
  /** Operator marked this track for diagnostic attention. */
16675
- debug: boolean().optional()
16687
+ debug: boolean().optional(),
16688
+ /** Operator favourited this track. Pins it against pruning. */
16689
+ favourited: boolean().optional()
16676
16690
  };
16677
16691
  /**
16678
16692
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16697,6 +16711,7 @@ var TrackFlagsSchema = object({
16697
16711
  trackId: string(),
16698
16712
  markForTrain: boolean(),
16699
16713
  debug: boolean(),
16714
+ favourited: boolean(),
16700
16715
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16701
16716
  * a track row) because this shape is only ever produced by the write body,
16702
16717
  * which always knows it — and a surface that has just written needs to render
@@ -20493,8 +20508,28 @@ var ClipSchema = object({
20493
20508
  startMs: number(),
20494
20509
  endMs: number()
20495
20510
  }),
20496
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
20497
- thumbnail: string().optional()
20511
+ /**
20512
+ * Lazy thumbnail URL, never inlined.
20513
+ *
20514
+ * Recording-derived clips (events-mode keep-window, and the prepared
20515
+ * continuous event+fragment visit) MUST use the snapshot of the **main
20516
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
20517
+ * of the event that owns `kind` (object > motion > audio). Do not extract
20518
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20519
+ * mint their own stills.
20520
+ */
20521
+ thumbnail: string().optional(),
20522
+ /** Analytics event ids that overlap this visit. Empty on footage-only clips.
20523
+ * The default provider's visit grain puts many motion heartbeats on one clip
20524
+ * instead of minting one clip per marker. */
20525
+ eventIds: array(string()).optional(),
20526
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20527
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20528
+ * bar keeps showing them via `recording.getAvailability`. */
20529
+ holes: array(object({
20530
+ startMs: number(),
20531
+ endMs: number()
20532
+ })).optional()
20498
20533
  });
20499
20534
  var ClipPlaybackSchema = object({
20500
20535
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -26867,7 +26902,9 @@ var ExportOptionsSchema = object({
26867
26902
  includeAudio: boolean(),
26868
26903
  maxLifeMs: number().int().positive(),
26869
26904
  deleteAfterDownload: boolean(),
26870
- title: string().max(200).optional()
26905
+ title: string().max(200).optional(),
26906
+ /** Notification-output target ids to ping when this export becomes ready. */
26907
+ notifyTargetIds: array(string().min(1)).max(20).optional()
26871
26908
  }).superRefine((v, ctx) => {
26872
26909
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
26873
26910
  code: ZodIssueCode$16.custom,
@@ -26926,10 +26963,18 @@ var ExportBytesSchema = object({
26926
26963
  });
26927
26964
  method(object({
26928
26965
  deviceId: number(),
26929
- profile: string(),
26966
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
26967
+ profile: string().optional(),
26968
+ profiles: array(string()).min(1).optional(),
26930
26969
  fromMs: number(),
26931
26970
  toMs: number(),
26932
26971
  options: ExportOptionsSchema
26972
+ }).superRefine((v, ctx) => {
26973
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
26974
+ code: ZodIssueCode$16.custom,
26975
+ message: "pass profiles[] (min 1) or legacy profile",
26976
+ path: ["profiles"]
26977
+ });
26933
26978
  }), ExportRecordSchema, {
26934
26979
  kind: "mutation",
26935
26980
  auth: "protected"
@@ -30084,15 +30129,6 @@ var BaseDeviceProvider = class extends BaseAddon {
30084
30129
  labels: ["probe not implemented"]
30085
30130
  };
30086
30131
  }
30087
- /**
30088
- * Top-level devices restored at once in {@link onRestoreDevices}.
30089
- *
30090
- * Four covers the fleets this ships to without turning a boot into a burst a
30091
- * camera NVR answers with a refusal. A provider whose upstream is a single
30092
- * session with a serial command channel (a Baichuan hub, an NVR that
30093
- * serialises ISAPI) should lower it; nothing needs to raise it.
30094
- */
30095
- restoreConcurrency = 4;
30096
30132
  async restoreDevices(savedDevices) {
30097
30133
  await this.onRestoreDevices(savedDevices);
30098
30134
  if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
@@ -30147,14 +30183,7 @@ var BaseDeviceProvider = class extends BaseAddon {
30147
30183
  });
30148
30184
  }
30149
30185
  };
30150
- let nextTopLevel = 0;
30151
- await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
30152
- for (;;) {
30153
- const saved = topLevel[nextTopLevel++];
30154
- if (saved === void 0) return;
30155
- await restoreOne(saved);
30156
- }
30157
- }));
30186
+ await Promise.all(topLevel.map((saved) => restoreOne(saved)));
30158
30187
  const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
30159
30188
  for (const saved of childRows) {
30160
30189
  const Class = this.deviceClasses[saved.type];
@@ -236782,6 +236811,26 @@ function computeHealthCheckBackoffMs(consecutive) {
236782
236811
  return 15 * 6e4;
236783
236812
  }
236784
236813
  //#endregion
236814
+ //#region src/simple-event-dispatch-trace.ts
236815
+ /**
236816
+ * Gate for hub-side simpleEvent traces.
236817
+ *
236818
+ * Hub-child events arrive on the parent's Baichuan socket. Logging them on
236819
+ * the child logger only is invisible when the operator filters Logs to the
236820
+ * Hub; logging every channel unconditionally floods the Hub (battery ticks).
236821
+ * Trace when the target camera — or the hub itself — is under debug.
236822
+ */
236823
+ function shouldTraceSimpleEventDispatch(input) {
236824
+ return wantsEventTrace(input.hubDebugGeneral, input.hubSocketFlags) || wantsEventTrace(input.childDebugGeneral, input.childSocketFlags);
236825
+ }
236826
+ function wantsEventTrace(debugGeneral, socketFlags) {
236827
+ return debugGeneral || socketFlags.includes("traceEvents");
236828
+ }
236829
+ /** Narrow a persisted `debugSocketLogs` blob to string flags. */
236830
+ function asDebugSocketFlags(value) {
236831
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
236832
+ }
236833
+ //#endregion
236785
236834
  //#region src/reolink-hub.ts
236786
236835
  var HUB_DISCOVERY_REFRESH_TIMEOUT_MS = 8e3;
236787
236836
  /**
@@ -237324,8 +237373,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237324
237373
  const cgi = info.entries[0];
237325
237374
  child.applyHubBatteryInfo({
237326
237375
  batteryPercent: info.batteryLevel,
237327
- ...typeof cgi?.["chargeStatus"] === "string" ? { chargeStatus: cgi["chargeStatus"] } : {},
237328
- ...typeof cgi?.["adapterStatus"] === "string" ? { adapterStatus: cgi["adapterStatus"] } : {}
237376
+ ...mapHubChargeStatus(cgi?.["chargeStatus"]) !== void 0 ? { chargeStatus: mapHubChargeStatus(cgi?.["chargeStatus"]) } : {},
237377
+ ...mapHubAdapterStatus(cgi?.["adapterStatus"]) !== void 0 ? { adapterStatus: mapHubAdapterStatus(cgi?.["adapterStatus"]) } : {}
237329
237378
  });
237330
237379
  }
237331
237380
  } catch (err) {
@@ -237550,6 +237599,18 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237550
237599
  }
237551
237600
  this.ctx.devices.getAll().then((all) => all.find((d) => d.id === deviceId)).then((child) => {
237552
237601
  if (!(child instanceof ReolinkCamera)) return;
237602
+ if (shouldTraceSimpleEventDispatch({
237603
+ hubDebugGeneral: this.config.get("debugGeneral") === true,
237604
+ hubSocketFlags: asDebugSocketFlags(this.config.get("debugSocketLogs")),
237605
+ childDebugGeneral: child.config.get("debugGeneral") === true,
237606
+ childSocketFlags: asDebugSocketFlags(child.config.get("debugSocketLogs"))
237607
+ })) this.ctx.logger.info("Reolink Hub: simpleEvent dispatch", { meta: {
237608
+ type: ev?.type ?? "unknown",
237609
+ channel,
237610
+ timestamp: ev?.timestamp,
237611
+ deviceId: child.id,
237612
+ name: child.name
237613
+ } });
237553
237614
  child.handleSimpleEvent(ev);
237554
237615
  }).catch((err) => {
237555
237616
  this.ctx.logger.debug("Hub simpleEvent dispatch failed", { meta: {
@@ -237687,6 +237748,27 @@ function discoveredStatusFor(d) {
237687
237748
  if (typeof d.state === "string" && d.state.length > 0) return "online";
237688
237749
  return "unknown";
237689
237750
  }
237751
+ /**
237752
+ * The hub CGI reports charge state as NUMERIC codes (measured live on the
237753
+ * Home Hub, 2026-08-16: a full, plugged Argus 3E reads chargeStatus 2 /
237754
+ * adapterStatus 1) while `mapBatteryInfo` speaks the standalone Baichuan
237755
+ * string vocabulary ('charging' / 'chargeComplete' / 'solarPanel'). Translate
237756
+ * at the hub boundary so the shared mapper stays untouched.
237757
+ * chargeStatus: 0 none · 1 charging · 2 charge complete
237758
+ * adapterStatus: 0 none · 1 DC adapter · 2 solar panel
237759
+ */
237760
+ function mapHubChargeStatus(value) {
237761
+ if (typeof value === "string") return value;
237762
+ if (value === 0) return "none";
237763
+ if (value === 1) return "charging";
237764
+ if (value === 2) return "chargeComplete";
237765
+ }
237766
+ function mapHubAdapterStatus(value) {
237767
+ if (typeof value === "string") return value;
237768
+ if (value === 0) return "none";
237769
+ if (value === 1) return "dc";
237770
+ if (value === 2) return "solarPanel";
237771
+ }
237690
237772
  //#endregion
237691
237773
  //#region src/creation-schema.ts
237692
237774
  /**
package/dist/addon.mjs CHANGED
@@ -21,7 +21,7 @@ import netImpl from "net";
21
21
  import { fileURLToPath } from "url";
22
22
  import { mkdir } from "fs/promises";
23
23
  import os from "node:os";
24
- //#region ../types/dist/event-category-Bxo5yJjt.mjs
24
+ //#region ../types/dist/event-category-C0lyLd5U.mjs
25
25
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
26
26
  EventCategory["SystemBoot"] = "system.boot";
27
27
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -38,9 +38,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
38
38
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
39
39
  /**
40
40
  * A newer addon or server-root package version was found by the
41
- * authoritative registry check. Emitted once per
42
- * `(target, packageName, currentVersion, latestVersion)` transition; repeated
43
- * polling of the same result is deduplicated by the checker.
41
+ * authoritative registry check. Emitted once when any observed
42
+ * `latestVersion` changes (or a package/node first appears behind);
43
+ * the payload carries the full currently-available list. Repeated
44
+ * polling of the same latests is silent.
44
45
  */
45
46
  EventCategory["UpdateAvailable"] = "update.available";
46
47
  /**
@@ -7582,6 +7583,10 @@ var RecordingBandSchema = object({
7582
7583
  preBufferSec: number().min(0).optional(),
7583
7584
  postBufferSec: number().min(0).optional()
7584
7585
  });
7586
+ ({
7587
+ preBufferSec: 10,
7588
+ postBufferSec: 30
7589
+ }).postBufferSec * 1e3;
7585
7590
  /**
7586
7591
  * Per-device retention overrides. Every field is optional; an unset or `0`
7587
7592
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -14768,6 +14773,9 @@ var NcSystemEventKindSchema = _enum([
14768
14773
  "alarm-disarmed",
14769
14774
  "alarm-arming",
14770
14775
  "alarm-arm-refused",
14776
+ "addon-updated",
14777
+ "server-updated",
14778
+ "export-completed",
14771
14779
  "camera-online",
14772
14780
  "camera-offline",
14773
14781
  "camera-disabled",
@@ -16661,13 +16669,19 @@ var RetrainStatusSchema = _enum([
16661
16669
  * "never marked" from "already trained" must read `retrainStatus`.
16662
16670
  *
16663
16671
  * `debug` does NOT pin; it is attention, not durability.
16672
+ *
16673
+ * `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
16674
+ * A favourited track is skipped by retention the same way `staging` is, but
16675
+ * it does not enter `none|staging|trained` and has no staging budget.
16664
16676
  */
16665
16677
  var TrackFlagFields = {
16666
16678
  /** Operator marked this track as training material — i.e. `retrainStatus` is
16667
16679
  * `'staging'`. */
16668
16680
  markForTrain: boolean().optional(),
16669
16681
  /** Operator marked this track for diagnostic attention. */
16670
- debug: boolean().optional()
16682
+ debug: boolean().optional(),
16683
+ /** Operator favourited this track. Pins it against pruning. */
16684
+ favourited: boolean().optional()
16671
16685
  };
16672
16686
  /**
16673
16687
  * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
@@ -16692,6 +16706,7 @@ var TrackFlagsSchema = object({
16692
16706
  trackId: string(),
16693
16707
  markForTrain: boolean(),
16694
16708
  debug: boolean(),
16709
+ favourited: boolean(),
16695
16710
  /** The lifecycle state the boolean was derived from. Required here (unlike on
16696
16711
  * a track row) because this shape is only ever produced by the write body,
16697
16712
  * which always knows it — and a surface that has just written needs to render
@@ -20488,8 +20503,28 @@ var ClipSchema = object({
20488
20503
  startMs: number(),
20489
20504
  endMs: number()
20490
20505
  }),
20491
- /** Thumbnail URL (lazy; e.g. analytics `getEventMedia`). Never inlined. */
20492
- thumbnail: string().optional()
20506
+ /**
20507
+ * Lazy thumbnail URL, never inlined.
20508
+ *
20509
+ * Recording-derived clips (events-mode keep-window, and the prepared
20510
+ * continuous event+fragment visit) MUST use the snapshot of the **main
20511
+ * event of the interval** — `getEventMedia({ eventId, kind: 'snapshot' })`
20512
+ * of the event that owns `kind` (object > motion > audio). Do not extract
20513
+ * a keyframe from the recorded segments. Other providers (onboard, HKSV)
20514
+ * mint their own stills.
20515
+ */
20516
+ thumbnail: string().optional(),
20517
+ /** Analytics event ids that overlap this visit. Empty on footage-only clips.
20518
+ * The default provider's visit grain puts many motion heartbeats on one clip
20519
+ * instead of minting one clip per marker. */
20520
+ eventIds: array(string()).optional(),
20521
+ /** Intra-visit footage holes (GOP rolls, discarded segments) still shorter
20522
+ * than `VISIT_MERGE_GAP_MS`. Playback concatenates around them; the timeline
20523
+ * bar keeps showing them via `recording.getAvailability`. */
20524
+ holes: array(object({
20525
+ startMs: number(),
20526
+ endMs: number()
20527
+ })).optional()
20493
20528
  });
20494
20529
  var ClipPlaybackSchema = object({
20495
20530
  /** HLS master URL through the hub data-plane (Range + token in path). */
@@ -26862,7 +26897,9 @@ var ExportOptionsSchema = object({
26862
26897
  includeAudio: boolean(),
26863
26898
  maxLifeMs: number().int().positive(),
26864
26899
  deleteAfterDownload: boolean(),
26865
- title: string().max(200).optional()
26900
+ title: string().max(200).optional(),
26901
+ /** Notification-output target ids to ping when this export becomes ready. */
26902
+ notifyTargetIds: array(string().min(1)).max(20).optional()
26866
26903
  }).superRefine((v, ctx) => {
26867
26904
  if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
26868
26905
  code: ZodIssueCode$16.custom,
@@ -26921,10 +26958,18 @@ var ExportBytesSchema = object({
26921
26958
  });
26922
26959
  method(object({
26923
26960
  deviceId: number(),
26924
- profile: string(),
26961
+ /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
26962
+ profile: string().optional(),
26963
+ profiles: array(string()).min(1).optional(),
26925
26964
  fromMs: number(),
26926
26965
  toMs: number(),
26927
26966
  options: ExportOptionsSchema
26967
+ }).superRefine((v, ctx) => {
26968
+ if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
26969
+ code: ZodIssueCode$16.custom,
26970
+ message: "pass profiles[] (min 1) or legacy profile",
26971
+ path: ["profiles"]
26972
+ });
26928
26973
  }), ExportRecordSchema, {
26929
26974
  kind: "mutation",
26930
26975
  auth: "protected"
@@ -30079,15 +30124,6 @@ var BaseDeviceProvider = class extends BaseAddon {
30079
30124
  labels: ["probe not implemented"]
30080
30125
  };
30081
30126
  }
30082
- /**
30083
- * Top-level devices restored at once in {@link onRestoreDevices}.
30084
- *
30085
- * Four covers the fleets this ships to without turning a boot into a burst a
30086
- * camera NVR answers with a refusal. A provider whose upstream is a single
30087
- * session with a serial command channel (a Baichuan hub, an NVR that
30088
- * serialises ISAPI) should lower it; nothing needs to raise it.
30089
- */
30090
- restoreConcurrency = 4;
30091
30127
  async restoreDevices(savedDevices) {
30092
30128
  await this.onRestoreDevices(savedDevices);
30093
30129
  if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
@@ -30142,14 +30178,7 @@ var BaseDeviceProvider = class extends BaseAddon {
30142
30178
  });
30143
30179
  }
30144
30180
  };
30145
- let nextTopLevel = 0;
30146
- await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
30147
- for (;;) {
30148
- const saved = topLevel[nextTopLevel++];
30149
- if (saved === void 0) return;
30150
- await restoreOne(saved);
30151
- }
30152
- }));
30181
+ await Promise.all(topLevel.map((saved) => restoreOne(saved)));
30153
30182
  const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
30154
30183
  for (const saved of childRows) {
30155
30184
  const Class = this.deviceClasses[saved.type];
@@ -236762,6 +236791,26 @@ function computeHealthCheckBackoffMs(consecutive) {
236762
236791
  return 15 * 6e4;
236763
236792
  }
236764
236793
  //#endregion
236794
+ //#region src/simple-event-dispatch-trace.ts
236795
+ /**
236796
+ * Gate for hub-side simpleEvent traces.
236797
+ *
236798
+ * Hub-child events arrive on the parent's Baichuan socket. Logging them on
236799
+ * the child logger only is invisible when the operator filters Logs to the
236800
+ * Hub; logging every channel unconditionally floods the Hub (battery ticks).
236801
+ * Trace when the target camera — or the hub itself — is under debug.
236802
+ */
236803
+ function shouldTraceSimpleEventDispatch(input) {
236804
+ return wantsEventTrace(input.hubDebugGeneral, input.hubSocketFlags) || wantsEventTrace(input.childDebugGeneral, input.childSocketFlags);
236805
+ }
236806
+ function wantsEventTrace(debugGeneral, socketFlags) {
236807
+ return debugGeneral || socketFlags.includes("traceEvents");
236808
+ }
236809
+ /** Narrow a persisted `debugSocketLogs` blob to string flags. */
236810
+ function asDebugSocketFlags(value) {
236811
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
236812
+ }
236813
+ //#endregion
236765
236814
  //#region src/reolink-hub.ts
236766
236815
  var HUB_DISCOVERY_REFRESH_TIMEOUT_MS = 8e3;
236767
236816
  /**
@@ -237304,8 +237353,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237304
237353
  const cgi = info.entries[0];
237305
237354
  child.applyHubBatteryInfo({
237306
237355
  batteryPercent: info.batteryLevel,
237307
- ...typeof cgi?.["chargeStatus"] === "string" ? { chargeStatus: cgi["chargeStatus"] } : {},
237308
- ...typeof cgi?.["adapterStatus"] === "string" ? { adapterStatus: cgi["adapterStatus"] } : {}
237356
+ ...mapHubChargeStatus(cgi?.["chargeStatus"]) !== void 0 ? { chargeStatus: mapHubChargeStatus(cgi?.["chargeStatus"]) } : {},
237357
+ ...mapHubAdapterStatus(cgi?.["adapterStatus"]) !== void 0 ? { adapterStatus: mapHubAdapterStatus(cgi?.["adapterStatus"]) } : {}
237309
237358
  });
237310
237359
  }
237311
237360
  } catch (err) {
@@ -237530,6 +237579,18 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237530
237579
  }
237531
237580
  this.ctx.devices.getAll().then((all) => all.find((d) => d.id === deviceId)).then((child) => {
237532
237581
  if (!(child instanceof ReolinkCamera)) return;
237582
+ if (shouldTraceSimpleEventDispatch({
237583
+ hubDebugGeneral: this.config.get("debugGeneral") === true,
237584
+ hubSocketFlags: asDebugSocketFlags(this.config.get("debugSocketLogs")),
237585
+ childDebugGeneral: child.config.get("debugGeneral") === true,
237586
+ childSocketFlags: asDebugSocketFlags(child.config.get("debugSocketLogs"))
237587
+ })) this.ctx.logger.info("Reolink Hub: simpleEvent dispatch", { meta: {
237588
+ type: ev?.type ?? "unknown",
237589
+ channel,
237590
+ timestamp: ev?.timestamp,
237591
+ deviceId: child.id,
237592
+ name: child.name
237593
+ } });
237533
237594
  child.handleSimpleEvent(ev);
237534
237595
  }).catch((err) => {
237535
237596
  this.ctx.logger.debug("Hub simpleEvent dispatch failed", { meta: {
@@ -237667,6 +237728,27 @@ function discoveredStatusFor(d) {
237667
237728
  if (typeof d.state === "string" && d.state.length > 0) return "online";
237668
237729
  return "unknown";
237669
237730
  }
237731
+ /**
237732
+ * The hub CGI reports charge state as NUMERIC codes (measured live on the
237733
+ * Home Hub, 2026-08-16: a full, plugged Argus 3E reads chargeStatus 2 /
237734
+ * adapterStatus 1) while `mapBatteryInfo` speaks the standalone Baichuan
237735
+ * string vocabulary ('charging' / 'chargeComplete' / 'solarPanel'). Translate
237736
+ * at the hub boundary so the shared mapper stays untouched.
237737
+ * chargeStatus: 0 none · 1 charging · 2 charge complete
237738
+ * adapterStatus: 0 none · 1 DC adapter · 2 solar panel
237739
+ */
237740
+ function mapHubChargeStatus(value) {
237741
+ if (typeof value === "string") return value;
237742
+ if (value === 0) return "none";
237743
+ if (value === 1) return "charging";
237744
+ if (value === 2) return "chargeComplete";
237745
+ }
237746
+ function mapHubAdapterStatus(value) {
237747
+ if (typeof value === "string") return value;
237748
+ if (value === 0) return "none";
237749
+ if (value === 1) return "dc";
237750
+ if (value === 2) return "solarPanel";
237751
+ }
237670
237752
  //#endregion
237671
237753
  //#region src/creation-schema.ts
237672
237754
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.40",
3
+ "version": "1.2.42",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",