@camstack/addon-provider-reolink 1.2.36 → 1.2.38

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/addon.js CHANGED
@@ -19675,6 +19675,39 @@ var snapshotCapability = {
19675
19675
  etag: string().nullable()
19676
19676
  }))),
19677
19677
  /**
19678
+ * The full decision chain for ONE device, for the viewer's debug readout —
19679
+ * the answer to "why does this tile show what it shows" in a single poll:
19680
+ * the battery slice the state was derived from, the resolved state + its
19681
+ * reason, the cached frame's identity/age, whether a wake window is open,
19682
+ * and whether a fresh capture is in flight. Cache-only and capture-free:
19683
+ * a debug read must never wake a battery camera.
19684
+ */
19685
+ getDebugState: systemMethod(object({ deviceId: number() }), object({
19686
+ /** The battery slice as read, or null when the device has none. */
19687
+ battery: object({
19688
+ sleeping: boolean(),
19689
+ lastUpdated: number(),
19690
+ lastContactAt: number().optional()
19691
+ }).nullable(),
19692
+ /** The resolved snapshot state (what the overlay decision used). */
19693
+ state: object({
19694
+ isBattery: boolean(),
19695
+ reason: _enum([
19696
+ "disabled",
19697
+ "sleeping",
19698
+ "unreachable",
19699
+ "waking"
19700
+ ]).nullable()
19701
+ }),
19702
+ /** The cached frame behind the next paint. */
19703
+ frame: object({
19704
+ capturedAt: number().nullable(),
19705
+ ageMs: number().nullable()
19706
+ }),
19707
+ /** A wake window is currently open (the Waking overlay's source). */
19708
+ waking: boolean()
19709
+ })),
19710
+ /**
19678
19711
  * Signed, expiring links to a CLIENT-SIZED frame — and the demand signal
19679
19712
  * that makes those frames current.
19680
19713
  *
@@ -34676,6 +34709,12 @@ Object.freeze({
34676
34709
  addonId: null,
34677
34710
  access: "view"
34678
34711
  },
34712
+ "snapshot.getDebugState": {
34713
+ capName: "snapshot",
34714
+ capScope: "device",
34715
+ addonId: null,
34716
+ access: "view"
34717
+ },
34679
34718
  "snapshot.getSnapshot": {
34680
34719
  capName: "snapshot",
34681
34720
  capScope: "device",
@@ -37273,6 +37312,11 @@ Object.freeze({
37273
37312
  form: "single",
37274
37313
  optional: false
37275
37314
  }],
37315
+ "snapshot.getDebugState": [{
37316
+ name: "deviceId",
37317
+ form: "single",
37318
+ optional: false
37319
+ }],
37276
37320
  "snapshot.getSnapshot": [{
37277
37321
  name: "deviceId",
37278
37322
  form: "single",
@@ -231012,6 +231056,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
231012
231056
  durationMs: Date.now() - startedAt
231013
231057
  };
231014
231058
  }
231059
+ if (confirmed && !observedAwake && parent !== null) this.watchHubChildAwake(parent).catch(() => {});
231015
231060
  return {
231016
231061
  awoke: true,
231017
231062
  durationMs: Date.now() - startedAt
@@ -231190,6 +231235,29 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
231190
231235
  this.lastProactiveWakeAt = Date.now();
231191
231236
  }
231192
231237
  /**
231238
+ * Post-confirm watch for a hub child whose wake lands on the consumer's
231239
+ * relay dial: re-read the parent's channel summary a few times over the
231240
+ * dial's wake window and commit `sleeping: false` the moment the firmware
231241
+ * reports it. Ends EARLY once committed; bounded hard so a dead link can't
231242
+ * leak a timer. Costs the camera nothing — the summary is the hub's own
231243
+ * mains-powered read.
231244
+ */
231245
+ async watchHubChildAwake(parent) {
231246
+ const deadline = Date.now() + 45e3;
231247
+ while (Date.now() < deadline) {
231248
+ await sleep$1(5e3);
231249
+ try {
231250
+ if ((await (await parent.getApi()).getNvrChannelsSummary({ channels: [this.getChannel()] })).devices.find((d) => d.channel === this.getChannel())?.sleeping === false) {
231251
+ if (this.commitSleepState(false, "hub-summary")) {
231252
+ this.ctx.logger.info("battery wakeForStream: hub child observed awake after the dial", { tags: { deviceId: this.id } });
231253
+ this.onWakeTransition("hub-summary").catch(() => {});
231254
+ }
231255
+ return;
231256
+ }
231257
+ } catch {}
231258
+ }
231259
+ }
231260
+ /**
231193
231261
  * Close the transient "waking" window on a wake that did NOT produce a
231194
231262
  * committed transition (ACK with no awake evidence, or a timeout).
231195
231263
  * `BatteryOnWakeStarted` opens it; normally the `sleeping: false` commit
@@ -236651,6 +236719,14 @@ function computeHealthCheckBackoffMs(consecutive) {
236651
236719
  //#endregion
236652
236720
  //#region src/reolink-hub.ts
236653
236721
  var HUB_DISCOVERY_REFRESH_TIMEOUT_MS = 8e3;
236722
+ /**
236723
+ * How often the hub re-reads its own channels summary to reconcile the
236724
+ * adopted children's sleep slices. 60 s: a naturally-waking camera (PIR,
236725
+ * schedule) is caught within a minute, and the play path has its own
236726
+ * faster confirm + post-dial watch, so this only ever carries background
236727
+ * drift. Never wakes a child — the summary is answered by the hub itself.
236728
+ */
236729
+ var HUB_CHILD_SLEEP_RECONCILE_MS = 6e4;
236654
236730
  var HUB_RECONNECT_BASE_MS = 2e3;
236655
236731
  var HUB_RECONNECT_MAX_MS = 3e4;
236656
236732
  /**
@@ -236684,6 +236760,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236684
236760
  connectInFlight = null;
236685
236761
  reconnectAttempts = 0;
236686
236762
  reconnectTimer = null;
236763
+ /** Periodic child-sleep reconcile timer (armed in `onActivate`). */
236764
+ sleepReconcileTimer = null;
236687
236765
  shutdownRequested = false;
236688
236766
  /** Diagnostic Sessions snapshot — same shape as the camera's. */
236689
236767
  sessionsSnapshot = null;
@@ -236768,6 +236846,10 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236768
236846
  this.ctx.logger.info("Reolink Hub: child unregistered externally — refreshing discovery", cid !== null ? { tags: { deviceId: cid } } : {});
236769
236847
  this.refreshDiscoveryFromCamera().catch(() => {});
236770
236848
  }));
236849
+ this.sleepReconcileTimer = setInterval(() => {
236850
+ this.refreshDiscoveryFromCamera().catch(() => {});
236851
+ }, HUB_CHILD_SLEEP_RECONCILE_MS);
236852
+ if (typeof this.sleepReconcileTimer.unref === "function") this.sleepReconcileTimer.unref();
236771
236853
  }
236772
236854
  eventUnsubscribers = [];
236773
236855
  /** Per-channel throttle for the unrouted-event warning (see
@@ -236779,6 +236861,10 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236779
236861
  clearTimeout(this.reconnectTimer);
236780
236862
  this.reconnectTimer = null;
236781
236863
  }
236864
+ if (this.sleepReconcileTimer) {
236865
+ clearInterval(this.sleepReconcileTimer);
236866
+ this.sleepReconcileTimer = null;
236867
+ }
236782
236868
  for (const unsub of this.eventUnsubscribers) try {
236783
236869
  unsub();
236784
236870
  } catch {}
package/dist/addon.mjs CHANGED
@@ -19670,6 +19670,39 @@ var snapshotCapability = {
19670
19670
  etag: string().nullable()
19671
19671
  }))),
19672
19672
  /**
19673
+ * The full decision chain for ONE device, for the viewer's debug readout —
19674
+ * the answer to "why does this tile show what it shows" in a single poll:
19675
+ * the battery slice the state was derived from, the resolved state + its
19676
+ * reason, the cached frame's identity/age, whether a wake window is open,
19677
+ * and whether a fresh capture is in flight. Cache-only and capture-free:
19678
+ * a debug read must never wake a battery camera.
19679
+ */
19680
+ getDebugState: systemMethod(object({ deviceId: number() }), object({
19681
+ /** The battery slice as read, or null when the device has none. */
19682
+ battery: object({
19683
+ sleeping: boolean(),
19684
+ lastUpdated: number(),
19685
+ lastContactAt: number().optional()
19686
+ }).nullable(),
19687
+ /** The resolved snapshot state (what the overlay decision used). */
19688
+ state: object({
19689
+ isBattery: boolean(),
19690
+ reason: _enum([
19691
+ "disabled",
19692
+ "sleeping",
19693
+ "unreachable",
19694
+ "waking"
19695
+ ]).nullable()
19696
+ }),
19697
+ /** The cached frame behind the next paint. */
19698
+ frame: object({
19699
+ capturedAt: number().nullable(),
19700
+ ageMs: number().nullable()
19701
+ }),
19702
+ /** A wake window is currently open (the Waking overlay's source). */
19703
+ waking: boolean()
19704
+ })),
19705
+ /**
19673
19706
  * Signed, expiring links to a CLIENT-SIZED frame — and the demand signal
19674
19707
  * that makes those frames current.
19675
19708
  *
@@ -34671,6 +34704,12 @@ Object.freeze({
34671
34704
  addonId: null,
34672
34705
  access: "view"
34673
34706
  },
34707
+ "snapshot.getDebugState": {
34708
+ capName: "snapshot",
34709
+ capScope: "device",
34710
+ addonId: null,
34711
+ access: "view"
34712
+ },
34674
34713
  "snapshot.getSnapshot": {
34675
34714
  capName: "snapshot",
34676
34715
  capScope: "device",
@@ -37268,6 +37307,11 @@ Object.freeze({
37268
37307
  form: "single",
37269
37308
  optional: false
37270
37309
  }],
37310
+ "snapshot.getDebugState": [{
37311
+ name: "deviceId",
37312
+ form: "single",
37313
+ optional: false
37314
+ }],
37271
37315
  "snapshot.getSnapshot": [{
37272
37316
  name: "deviceId",
37273
37317
  form: "single",
@@ -230992,6 +231036,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
230992
231036
  durationMs: Date.now() - startedAt
230993
231037
  };
230994
231038
  }
231039
+ if (confirmed && !observedAwake && parent !== null) this.watchHubChildAwake(parent).catch(() => {});
230995
231040
  return {
230996
231041
  awoke: true,
230997
231042
  durationMs: Date.now() - startedAt
@@ -231170,6 +231215,29 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
231170
231215
  this.lastProactiveWakeAt = Date.now();
231171
231216
  }
231172
231217
  /**
231218
+ * Post-confirm watch for a hub child whose wake lands on the consumer's
231219
+ * relay dial: re-read the parent's channel summary a few times over the
231220
+ * dial's wake window and commit `sleeping: false` the moment the firmware
231221
+ * reports it. Ends EARLY once committed; bounded hard so a dead link can't
231222
+ * leak a timer. Costs the camera nothing — the summary is the hub's own
231223
+ * mains-powered read.
231224
+ */
231225
+ async watchHubChildAwake(parent) {
231226
+ const deadline = Date.now() + 45e3;
231227
+ while (Date.now() < deadline) {
231228
+ await sleep$1(5e3);
231229
+ try {
231230
+ if ((await (await parent.getApi()).getNvrChannelsSummary({ channels: [this.getChannel()] })).devices.find((d) => d.channel === this.getChannel())?.sleeping === false) {
231231
+ if (this.commitSleepState(false, "hub-summary")) {
231232
+ this.ctx.logger.info("battery wakeForStream: hub child observed awake after the dial", { tags: { deviceId: this.id } });
231233
+ this.onWakeTransition("hub-summary").catch(() => {});
231234
+ }
231235
+ return;
231236
+ }
231237
+ } catch {}
231238
+ }
231239
+ }
231240
+ /**
231173
231241
  * Close the transient "waking" window on a wake that did NOT produce a
231174
231242
  * committed transition (ACK with no awake evidence, or a timeout).
231175
231243
  * `BatteryOnWakeStarted` opens it; normally the `sleeping: false` commit
@@ -236631,6 +236699,14 @@ function computeHealthCheckBackoffMs(consecutive) {
236631
236699
  //#endregion
236632
236700
  //#region src/reolink-hub.ts
236633
236701
  var HUB_DISCOVERY_REFRESH_TIMEOUT_MS = 8e3;
236702
+ /**
236703
+ * How often the hub re-reads its own channels summary to reconcile the
236704
+ * adopted children's sleep slices. 60 s: a naturally-waking camera (PIR,
236705
+ * schedule) is caught within a minute, and the play path has its own
236706
+ * faster confirm + post-dial watch, so this only ever carries background
236707
+ * drift. Never wakes a child — the summary is answered by the hub itself.
236708
+ */
236709
+ var HUB_CHILD_SLEEP_RECONCILE_MS = 6e4;
236634
236710
  var HUB_RECONNECT_BASE_MS = 2e3;
236635
236711
  var HUB_RECONNECT_MAX_MS = 3e4;
236636
236712
  /**
@@ -236664,6 +236740,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236664
236740
  connectInFlight = null;
236665
236741
  reconnectAttempts = 0;
236666
236742
  reconnectTimer = null;
236743
+ /** Periodic child-sleep reconcile timer (armed in `onActivate`). */
236744
+ sleepReconcileTimer = null;
236667
236745
  shutdownRequested = false;
236668
236746
  /** Diagnostic Sessions snapshot — same shape as the camera's. */
236669
236747
  sessionsSnapshot = null;
@@ -236748,6 +236826,10 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236748
236826
  this.ctx.logger.info("Reolink Hub: child unregistered externally — refreshing discovery", cid !== null ? { tags: { deviceId: cid } } : {});
236749
236827
  this.refreshDiscoveryFromCamera().catch(() => {});
236750
236828
  }));
236829
+ this.sleepReconcileTimer = setInterval(() => {
236830
+ this.refreshDiscoveryFromCamera().catch(() => {});
236831
+ }, HUB_CHILD_SLEEP_RECONCILE_MS);
236832
+ if (typeof this.sleepReconcileTimer.unref === "function") this.sleepReconcileTimer.unref();
236751
236833
  }
236752
236834
  eventUnsubscribers = [];
236753
236835
  /** Per-channel throttle for the unrouted-event warning (see
@@ -236759,6 +236841,10 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
236759
236841
  clearTimeout(this.reconnectTimer);
236760
236842
  this.reconnectTimer = null;
236761
236843
  }
236844
+ if (this.sleepReconcileTimer) {
236845
+ clearInterval(this.sleepReconcileTimer);
236846
+ this.sleepReconcileTimer = null;
236847
+ }
236762
236848
  for (const unsub of this.eventUnsubscribers) try {
236763
236849
  unsub();
236764
236850
  } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.36",
3
+ "version": "1.2.38",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",