@camstack/addon-provider-reolink 1.2.39 → 1.2.41

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 +145 -10
  2. package/dist/addon.mjs +145 -10
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -230195,6 +230195,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
230195
230195
  static WAKE_HYSTERESIS_MS = 3e4;
230196
230196
  static SLEEP_HYSTERESIS_MS = 5e3;
230197
230197
  /**
230198
+ * How long a freshly committed state is protected from a hub-summary
230199
+ * awake→sleeping flip. The summary's per-channel sleep flag lags real
230200
+ * pushes by over a minute (measured >75 s on 2026-08-16); 3 min covers
230201
+ * two full discovery-refresh cycles of lag without letting a genuinely
230202
+ * stuck "awake" slice stand forever — the reconcile still wins after
230203
+ * the grace, which is exactly its recovery role.
230204
+ */
230205
+ static HUB_SUMMARY_SLEEP_GRACE_MS = 18e4;
230206
+ /**
230198
230207
  * Wall-clock ms of the last PROACTIVE wake — one we issued on our own
230199
230208
  * initiative (background snapshot refresh), not because the operator
230200
230209
  * or a stream consumer asked for the camera. Drives
@@ -231174,15 +231183,35 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
231174
231183
  *
231175
231184
  * @param source - `'hub-summary'` is AUTHORITATIVE (the NVR reports
231176
231185
  * per-channel sleep state from its own firmware, not from socket
231177
- * I/O inference) and therefore bypasses the hysteresis. The other
231178
- * two sources are inference-derived and stay gated.
231186
+ * I/O inference) and therefore bypasses the hysteresis with ONE
231187
+ * exception: the summary LAGS real push state (measured >75 s behind
231188
+ * an `awake` push, 2026-08-16), so an awake→sleeping flip from the
231189
+ * summary is refused while a fresher committed state is still inside
231190
+ * `HUB_SUMMARY_SLEEP_GRACE_MS`. Without the guard the 60 s reconcile
231191
+ * clobbered the fresh awake right back to sleeping for a full cycle —
231192
+ * the viewer flashed the live frame, then the sleeping overlay, then
231193
+ * live again. Sleeping→awake from the summary is always honoured
231194
+ * (it strands nothing; the camera wakes regardless).
231179
231195
  * @returns `true` when the slice actually changed.
231180
231196
  */
231181
231197
  commitSleepState(next, source) {
231182
231198
  if (!this.isBattery) return false;
231183
231199
  if (this.sleeping === next) return false;
231184
231200
  if (source !== "hub-summary" && !this.acceptSleepingTransition(next)) return false;
231185
- if (source === "hub-summary") this.sleepStateChangedAt = Date.now();
231201
+ if (source === "hub-summary") {
231202
+ const sinceCommitMs = Date.now() - this.sleepStateChangedAt;
231203
+ if (next === true && this.sleepStateChangedAt > 0 && sinceCommitMs < ReolinkCamera.HUB_SUMMARY_SLEEP_GRACE_MS) {
231204
+ this.ctx.logger.debug("hub-summary sleep flip refused — fresher state inside grace", {
231205
+ tags: { deviceId: this.id },
231206
+ meta: {
231207
+ sinceCommitMs,
231208
+ graceMs: ReolinkCamera.HUB_SUMMARY_SLEEP_GRACE_MS
231209
+ }
231210
+ });
231211
+ return false;
231212
+ }
231213
+ this.sleepStateChangedAt = Date.now();
231214
+ }
231186
231215
  this.state.battery.sleeping = next;
231187
231216
  this.ctx.logger.info("battery sleep state committed", {
231188
231217
  tags: { deviceId: this.id },
@@ -231322,13 +231351,42 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
231322
231351
  });
231323
231352
  }
231324
231353
  /**
231354
+ * Hub-fed battery refresh for an adopted child. The parent's
231355
+ * `getAllChannelsBatteryInfo` CGI carries per-channel percent/charge
231356
+ * state over the hub's own mains-powered socket, so it costs the
231357
+ * battery channel nothing — this is how a hub child's
231358
+ * percentage/charging ever moves (a sleeping child answers no poll of
231359
+ * its own, and its pushes only arrive on firmware transitions).
231360
+ *
231361
+ * Deliberately does NOT stamp `lastContactAt`: the value is the HUB's
231362
+ * cached read of the channel, not proof the camera itself answered —
231363
+ * folding it into contact would mask a genuinely gone camera from the
231364
+ * unreachable derivation (that is what pushes and streams are for).
231365
+ * `sleeping` is likewise untouched — sleep state has its own writers
231366
+ * (pushes + the guarded hub-summary reconcile).
231367
+ */
231368
+ applyHubBatteryInfo(info) {
231369
+ if (!this.isBattery) return;
231370
+ const mapped = this.mapBatteryInfo({
231371
+ ...info.batteryPercent !== void 0 ? { batteryPercent: info.batteryPercent } : {},
231372
+ ...info.chargeStatus !== void 0 ? { chargeStatus: info.chargeStatus } : {},
231373
+ ...info.adapterStatus !== void 0 ? { adapterStatus: info.adapterStatus } : {}
231374
+ });
231375
+ this.setCapSlice(batteryCapability, {
231376
+ ...this.state.battery,
231377
+ percentage: mapped.percentage,
231378
+ charging: mapped.charging,
231379
+ lastUpdated: Date.now()
231380
+ });
231381
+ }
231382
+ /**
231325
231383
  * Battery cams require an explicit wake before cmd_id 109 will
231326
231384
  * answer reliably. The lib documents this pattern in
231327
- * `predownloadRecordingMp4` (`ensureAwake` → `wakeUp` →
231328
- * operation → retry with longer wait on failure). `getSnapshot`
231329
- * itself only re-`login()`s, which over BCUDP can complete before
231330
- * the camera firmware is fully up — the snapshot then times out
231331
- * because the video subsystem isn't ready yet.
231385
+ * `predownloadRecordingMp3`/`predownloadRecordingMp4` (`ensureAwake`
231386
+ * → `wakeUp` → operation → retry with longer wait on failure).
231387
+ * `getSnapshot` itself only re-`login()`s, which over BCUDP can
231388
+ * complete before the camera firmware is fully up — the snapshot then
231389
+ * times out because the video subsystem isn't ready yet.
231332
231390
  *
231333
231391
  * Probe live testing (scripts/probe-reolink-snapshot.mts) confirmed:
231334
231392
  * awake state: getSnapshot in ~1s
@@ -236782,6 +236840,12 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236782
236840
  * keeps the slice + map in sync after every change).
236783
236841
  */
236784
236842
  channelToDeviceId = /* @__PURE__ */ new Map();
236843
+ /** One-shot: the raw per-channel battery payload is logged once per
236844
+ * process so the firmware's charge-field naming is on record. */
236845
+ batteryShapeLogged = false;
236846
+ /** Channels the last successful discovery refresh listed as present but
236847
+ * NOT adopted — their pushes are expected drops (debug), not faults. */
236848
+ knownUnadoptedChannels = /* @__PURE__ */ new Set();
236785
236849
  /** Wall-clock ms of the last discovery refresh that included this
236786
236850
  * child's channel. Drives the offline marker — a child that hasn't
236787
236851
  * appeared in the firmware's channel list for `MISSING_OFFLINE_MS`
@@ -237195,6 +237259,11 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237195
237259
  };
237196
237260
  this.runtimeState.setCapState(deviceDiscoveryCapability.name, slice);
237197
237261
  if (lastError === null) {
237262
+ this.knownUnadoptedChannels.clear();
237263
+ for (const d of discovered) {
237264
+ const ch = d.metadata.rtspChannel;
237265
+ if (!d.alreadyAdopted && typeof ch === "number") this.knownUnadoptedChannels.add(ch);
237266
+ }
237198
237267
  const adoptedCount = discovered.filter((d) => d.alreadyAdopted).length;
237199
237268
  const availableCount = discovered.length - adoptedCount;
237200
237269
  const onlineCount = discovered.filter((d) => d.status === "online").length;
@@ -237218,6 +237287,49 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237218
237287
  if (lastError === null) {
237219
237288
  await this.reconcileAdoptedChildOnline(discovered);
237220
237289
  await this.reconcileAdoptedChildSleepState(discovered);
237290
+ await this.reconcileAdoptedChildBattery();
237291
+ }
237292
+ }
237293
+ /**
237294
+ * Push the hub's per-channel battery read into each adopted battery
237295
+ * child. `getAllChannelsBatteryInfo` is one CGI on the hub's own
237296
+ * mains-powered socket — it never touches a battery camera — and it is
237297
+ * the ONLY way a sleeping hub child's percentage/charging ever moves:
237298
+ * the child answers no poll of its own while asleep, and its battery
237299
+ * pushes arrive only on firmware transitions (measured live: 3628 sat
237300
+ * at 80% / charging:none for 3 h while plugged in, 2026-08-16).
237301
+ *
237302
+ * Independent of the discovery call's success path: a battery-read
237303
+ * failure must not fail discovery, so it is caught and logged here.
237304
+ */
237305
+ async reconcileAdoptedChildBattery() {
237306
+ if (this.channelToDeviceId.size === 0) return;
237307
+ try {
237308
+ const data = await (await this.ensureApi()).getAllChannelsBatteryInfo();
237309
+ const entries = Object.entries(data.batteryInfoData);
237310
+ if (entries.length === 0) return;
237311
+ if (!this.batteryShapeLogged) {
237312
+ this.batteryShapeLogged = true;
237313
+ this.ctx.logger.info("Reolink Hub: per-channel battery payload (first read)", { meta: { raw: entries.map(([ch, v]) => ({
237314
+ channel: Number(ch),
237315
+ ...v
237316
+ })) } });
237317
+ }
237318
+ const all = await this.ctx.devices.getAll();
237319
+ for (const [chKey, info] of entries) {
237320
+ const deviceId = this.channelToDeviceId.get(Number(chKey));
237321
+ if (deviceId === void 0) continue;
237322
+ const child = all.find((d) => d.id === deviceId);
237323
+ if (!(child instanceof ReolinkCamera)) continue;
237324
+ const cgi = info.entries[0];
237325
+ child.applyHubBatteryInfo({
237326
+ batteryPercent: info.batteryLevel,
237327
+ ...mapHubChargeStatus(cgi?.["chargeStatus"]) !== void 0 ? { chargeStatus: mapHubChargeStatus(cgi?.["chargeStatus"]) } : {},
237328
+ ...mapHubAdapterStatus(cgi?.["adapterStatus"]) !== void 0 ? { adapterStatus: mapHubAdapterStatus(cgi?.["adapterStatus"]) } : {}
237329
+ });
237330
+ }
237331
+ } catch (err) {
237332
+ this.ctx.logger.debug("Reolink Hub: per-channel battery refresh failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
237221
237333
  }
237222
237334
  }
237223
237335
  /**
@@ -237427,9 +237539,11 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237427
237539
  const now = Date.now();
237428
237540
  if (now - last > 6e4) {
237429
237541
  this.unroutedChannelLoggedAt.set(channel, now);
237430
- this.ctx.logger.warn("Reolink Hub: simpleEvent for an unmapped channel dropped", { meta: {
237542
+ const knownUnadopted = this.knownUnadoptedChannels.has(channel);
237543
+ this.ctx.logger[knownUnadopted ? "debug" : "warn"]("Reolink Hub: simpleEvent for an unmapped channel dropped", { meta: {
237431
237544
  channel,
237432
- type: ev?.type ?? "unknown"
237545
+ type: ev?.type ?? "unknown",
237546
+ knownUnadopted
237433
237547
  } });
237434
237548
  }
237435
237549
  return;
@@ -237573,6 +237687,27 @@ function discoveredStatusFor(d) {
237573
237687
  if (typeof d.state === "string" && d.state.length > 0) return "online";
237574
237688
  return "unknown";
237575
237689
  }
237690
+ /**
237691
+ * The hub CGI reports charge state as NUMERIC codes (measured live on the
237692
+ * Home Hub, 2026-08-16: a full, plugged Argus 3E reads chargeStatus 2 /
237693
+ * adapterStatus 1) while `mapBatteryInfo` speaks the standalone Baichuan
237694
+ * string vocabulary ('charging' / 'chargeComplete' / 'solarPanel'). Translate
237695
+ * at the hub boundary so the shared mapper stays untouched.
237696
+ * chargeStatus: 0 none · 1 charging · 2 charge complete
237697
+ * adapterStatus: 0 none · 1 DC adapter · 2 solar panel
237698
+ */
237699
+ function mapHubChargeStatus(value) {
237700
+ if (typeof value === "string") return value;
237701
+ if (value === 0) return "none";
237702
+ if (value === 1) return "charging";
237703
+ if (value === 2) return "chargeComplete";
237704
+ }
237705
+ function mapHubAdapterStatus(value) {
237706
+ if (typeof value === "string") return value;
237707
+ if (value === 0) return "none";
237708
+ if (value === 1) return "dc";
237709
+ if (value === 2) return "solarPanel";
237710
+ }
237576
237711
  //#endregion
237577
237712
  //#region src/creation-schema.ts
237578
237713
  /**
package/dist/addon.mjs CHANGED
@@ -230175,6 +230175,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
230175
230175
  static WAKE_HYSTERESIS_MS = 3e4;
230176
230176
  static SLEEP_HYSTERESIS_MS = 5e3;
230177
230177
  /**
230178
+ * How long a freshly committed state is protected from a hub-summary
230179
+ * awake→sleeping flip. The summary's per-channel sleep flag lags real
230180
+ * pushes by over a minute (measured >75 s on 2026-08-16); 3 min covers
230181
+ * two full discovery-refresh cycles of lag without letting a genuinely
230182
+ * stuck "awake" slice stand forever — the reconcile still wins after
230183
+ * the grace, which is exactly its recovery role.
230184
+ */
230185
+ static HUB_SUMMARY_SLEEP_GRACE_MS = 18e4;
230186
+ /**
230178
230187
  * Wall-clock ms of the last PROACTIVE wake — one we issued on our own
230179
230188
  * initiative (background snapshot refresh), not because the operator
230180
230189
  * or a stream consumer asked for the camera. Drives
@@ -231154,15 +231163,35 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
231154
231163
  *
231155
231164
  * @param source - `'hub-summary'` is AUTHORITATIVE (the NVR reports
231156
231165
  * per-channel sleep state from its own firmware, not from socket
231157
- * I/O inference) and therefore bypasses the hysteresis. The other
231158
- * two sources are inference-derived and stay gated.
231166
+ * I/O inference) and therefore bypasses the hysteresis with ONE
231167
+ * exception: the summary LAGS real push state (measured >75 s behind
231168
+ * an `awake` push, 2026-08-16), so an awake→sleeping flip from the
231169
+ * summary is refused while a fresher committed state is still inside
231170
+ * `HUB_SUMMARY_SLEEP_GRACE_MS`. Without the guard the 60 s reconcile
231171
+ * clobbered the fresh awake right back to sleeping for a full cycle —
231172
+ * the viewer flashed the live frame, then the sleeping overlay, then
231173
+ * live again. Sleeping→awake from the summary is always honoured
231174
+ * (it strands nothing; the camera wakes regardless).
231159
231175
  * @returns `true` when the slice actually changed.
231160
231176
  */
231161
231177
  commitSleepState(next, source) {
231162
231178
  if (!this.isBattery) return false;
231163
231179
  if (this.sleeping === next) return false;
231164
231180
  if (source !== "hub-summary" && !this.acceptSleepingTransition(next)) return false;
231165
- if (source === "hub-summary") this.sleepStateChangedAt = Date.now();
231181
+ if (source === "hub-summary") {
231182
+ const sinceCommitMs = Date.now() - this.sleepStateChangedAt;
231183
+ if (next === true && this.sleepStateChangedAt > 0 && sinceCommitMs < ReolinkCamera.HUB_SUMMARY_SLEEP_GRACE_MS) {
231184
+ this.ctx.logger.debug("hub-summary sleep flip refused — fresher state inside grace", {
231185
+ tags: { deviceId: this.id },
231186
+ meta: {
231187
+ sinceCommitMs,
231188
+ graceMs: ReolinkCamera.HUB_SUMMARY_SLEEP_GRACE_MS
231189
+ }
231190
+ });
231191
+ return false;
231192
+ }
231193
+ this.sleepStateChangedAt = Date.now();
231194
+ }
231166
231195
  this.state.battery.sleeping = next;
231167
231196
  this.ctx.logger.info("battery sleep state committed", {
231168
231197
  tags: { deviceId: this.id },
@@ -231302,13 +231331,42 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
231302
231331
  });
231303
231332
  }
231304
231333
  /**
231334
+ * Hub-fed battery refresh for an adopted child. The parent's
231335
+ * `getAllChannelsBatteryInfo` CGI carries per-channel percent/charge
231336
+ * state over the hub's own mains-powered socket, so it costs the
231337
+ * battery channel nothing — this is how a hub child's
231338
+ * percentage/charging ever moves (a sleeping child answers no poll of
231339
+ * its own, and its pushes only arrive on firmware transitions).
231340
+ *
231341
+ * Deliberately does NOT stamp `lastContactAt`: the value is the HUB's
231342
+ * cached read of the channel, not proof the camera itself answered —
231343
+ * folding it into contact would mask a genuinely gone camera from the
231344
+ * unreachable derivation (that is what pushes and streams are for).
231345
+ * `sleeping` is likewise untouched — sleep state has its own writers
231346
+ * (pushes + the guarded hub-summary reconcile).
231347
+ */
231348
+ applyHubBatteryInfo(info) {
231349
+ if (!this.isBattery) return;
231350
+ const mapped = this.mapBatteryInfo({
231351
+ ...info.batteryPercent !== void 0 ? { batteryPercent: info.batteryPercent } : {},
231352
+ ...info.chargeStatus !== void 0 ? { chargeStatus: info.chargeStatus } : {},
231353
+ ...info.adapterStatus !== void 0 ? { adapterStatus: info.adapterStatus } : {}
231354
+ });
231355
+ this.setCapSlice(batteryCapability, {
231356
+ ...this.state.battery,
231357
+ percentage: mapped.percentage,
231358
+ charging: mapped.charging,
231359
+ lastUpdated: Date.now()
231360
+ });
231361
+ }
231362
+ /**
231305
231363
  * Battery cams require an explicit wake before cmd_id 109 will
231306
231364
  * answer reliably. The lib documents this pattern in
231307
- * `predownloadRecordingMp4` (`ensureAwake` → `wakeUp` →
231308
- * operation → retry with longer wait on failure). `getSnapshot`
231309
- * itself only re-`login()`s, which over BCUDP can complete before
231310
- * the camera firmware is fully up — the snapshot then times out
231311
- * because the video subsystem isn't ready yet.
231365
+ * `predownloadRecordingMp3`/`predownloadRecordingMp4` (`ensureAwake`
231366
+ * → `wakeUp` → operation → retry with longer wait on failure).
231367
+ * `getSnapshot` itself only re-`login()`s, which over BCUDP can
231368
+ * complete before the camera firmware is fully up — the snapshot then
231369
+ * times out because the video subsystem isn't ready yet.
231312
231370
  *
231313
231371
  * Probe live testing (scripts/probe-reolink-snapshot.mts) confirmed:
231314
231372
  * awake state: getSnapshot in ~1s
@@ -236762,6 +236820,12 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236762
236820
  * keeps the slice + map in sync after every change).
236763
236821
  */
236764
236822
  channelToDeviceId = /* @__PURE__ */ new Map();
236823
+ /** One-shot: the raw per-channel battery payload is logged once per
236824
+ * process so the firmware's charge-field naming is on record. */
236825
+ batteryShapeLogged = false;
236826
+ /** Channels the last successful discovery refresh listed as present but
236827
+ * NOT adopted — their pushes are expected drops (debug), not faults. */
236828
+ knownUnadoptedChannels = /* @__PURE__ */ new Set();
236765
236829
  /** Wall-clock ms of the last discovery refresh that included this
236766
236830
  * child's channel. Drives the offline marker — a child that hasn't
236767
236831
  * appeared in the firmware's channel list for `MISSING_OFFLINE_MS`
@@ -237175,6 +237239,11 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237175
237239
  };
237176
237240
  this.runtimeState.setCapState(deviceDiscoveryCapability.name, slice);
237177
237241
  if (lastError === null) {
237242
+ this.knownUnadoptedChannels.clear();
237243
+ for (const d of discovered) {
237244
+ const ch = d.metadata.rtspChannel;
237245
+ if (!d.alreadyAdopted && typeof ch === "number") this.knownUnadoptedChannels.add(ch);
237246
+ }
237178
237247
  const adoptedCount = discovered.filter((d) => d.alreadyAdopted).length;
237179
237248
  const availableCount = discovered.length - adoptedCount;
237180
237249
  const onlineCount = discovered.filter((d) => d.status === "online").length;
@@ -237198,6 +237267,49 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237198
237267
  if (lastError === null) {
237199
237268
  await this.reconcileAdoptedChildOnline(discovered);
237200
237269
  await this.reconcileAdoptedChildSleepState(discovered);
237270
+ await this.reconcileAdoptedChildBattery();
237271
+ }
237272
+ }
237273
+ /**
237274
+ * Push the hub's per-channel battery read into each adopted battery
237275
+ * child. `getAllChannelsBatteryInfo` is one CGI on the hub's own
237276
+ * mains-powered socket — it never touches a battery camera — and it is
237277
+ * the ONLY way a sleeping hub child's percentage/charging ever moves:
237278
+ * the child answers no poll of its own while asleep, and its battery
237279
+ * pushes arrive only on firmware transitions (measured live: 3628 sat
237280
+ * at 80% / charging:none for 3 h while plugged in, 2026-08-16).
237281
+ *
237282
+ * Independent of the discovery call's success path: a battery-read
237283
+ * failure must not fail discovery, so it is caught and logged here.
237284
+ */
237285
+ async reconcileAdoptedChildBattery() {
237286
+ if (this.channelToDeviceId.size === 0) return;
237287
+ try {
237288
+ const data = await (await this.ensureApi()).getAllChannelsBatteryInfo();
237289
+ const entries = Object.entries(data.batteryInfoData);
237290
+ if (entries.length === 0) return;
237291
+ if (!this.batteryShapeLogged) {
237292
+ this.batteryShapeLogged = true;
237293
+ this.ctx.logger.info("Reolink Hub: per-channel battery payload (first read)", { meta: { raw: entries.map(([ch, v]) => ({
237294
+ channel: Number(ch),
237295
+ ...v
237296
+ })) } });
237297
+ }
237298
+ const all = await this.ctx.devices.getAll();
237299
+ for (const [chKey, info] of entries) {
237300
+ const deviceId = this.channelToDeviceId.get(Number(chKey));
237301
+ if (deviceId === void 0) continue;
237302
+ const child = all.find((d) => d.id === deviceId);
237303
+ if (!(child instanceof ReolinkCamera)) continue;
237304
+ const cgi = info.entries[0];
237305
+ child.applyHubBatteryInfo({
237306
+ batteryPercent: info.batteryLevel,
237307
+ ...mapHubChargeStatus(cgi?.["chargeStatus"]) !== void 0 ? { chargeStatus: mapHubChargeStatus(cgi?.["chargeStatus"]) } : {},
237308
+ ...mapHubAdapterStatus(cgi?.["adapterStatus"]) !== void 0 ? { adapterStatus: mapHubAdapterStatus(cgi?.["adapterStatus"]) } : {}
237309
+ });
237310
+ }
237311
+ } catch (err) {
237312
+ this.ctx.logger.debug("Reolink Hub: per-channel battery refresh failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
237201
237313
  }
237202
237314
  }
237203
237315
  /**
@@ -237407,9 +237519,11 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
237407
237519
  const now = Date.now();
237408
237520
  if (now - last > 6e4) {
237409
237521
  this.unroutedChannelLoggedAt.set(channel, now);
237410
- this.ctx.logger.warn("Reolink Hub: simpleEvent for an unmapped channel dropped", { meta: {
237522
+ const knownUnadopted = this.knownUnadoptedChannels.has(channel);
237523
+ this.ctx.logger[knownUnadopted ? "debug" : "warn"]("Reolink Hub: simpleEvent for an unmapped channel dropped", { meta: {
237411
237524
  channel,
237412
- type: ev?.type ?? "unknown"
237525
+ type: ev?.type ?? "unknown",
237526
+ knownUnadopted
237413
237527
  } });
237414
237528
  }
237415
237529
  return;
@@ -237553,6 +237667,27 @@ function discoveredStatusFor(d) {
237553
237667
  if (typeof d.state === "string" && d.state.length > 0) return "online";
237554
237668
  return "unknown";
237555
237669
  }
237670
+ /**
237671
+ * The hub CGI reports charge state as NUMERIC codes (measured live on the
237672
+ * Home Hub, 2026-08-16: a full, plugged Argus 3E reads chargeStatus 2 /
237673
+ * adapterStatus 1) while `mapBatteryInfo` speaks the standalone Baichuan
237674
+ * string vocabulary ('charging' / 'chargeComplete' / 'solarPanel'). Translate
237675
+ * at the hub boundary so the shared mapper stays untouched.
237676
+ * chargeStatus: 0 none · 1 charging · 2 charge complete
237677
+ * adapterStatus: 0 none · 1 DC adapter · 2 solar panel
237678
+ */
237679
+ function mapHubChargeStatus(value) {
237680
+ if (typeof value === "string") return value;
237681
+ if (value === 0) return "none";
237682
+ if (value === 1) return "charging";
237683
+ if (value === 2) return "chargeComplete";
237684
+ }
237685
+ function mapHubAdapterStatus(value) {
237686
+ if (typeof value === "string") return value;
237687
+ if (value === 0) return "none";
237688
+ if (value === 1) return "dc";
237689
+ if (value === 2) return "solarPanel";
237690
+ }
237556
237691
  //#endregion
237557
237692
  //#region src/creation-schema.ts
237558
237693
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.39",
3
+ "version": "1.2.41",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",