@camstack/addon-provider-hikvision 1.2.73 → 1.2.75

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
@@ -7548,7 +7548,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
7548
7548
  var CameraSwitchUnavailableReasonSchema = _enum([
7549
7549
  "no-provider",
7550
7550
  "source-unreachable",
7551
- "not-configured"
7551
+ "not-configured",
7552
+ "device-disabled"
7552
7553
  ]);
7553
7554
  /**
7554
7555
  * One switch, resolved for one camera.
@@ -23987,12 +23988,20 @@ var BatteryStatusSchema = object({
23987
23988
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
23988
23989
  * Reolink-specific for the Solar Panel 2 accessory (will become
23989
23990
  * common on other battery cams). `'none'` means running on battery
23990
- * alone.
23991
+ * alone — a NEGATIVE the firmware actually reported.
23992
+ *
23993
+ * `'unknown'` is the absence of an answer: the provider has not read the
23994
+ * power fields yet, or the firmware reported neither. Before it existed,
23995
+ * two unreadable fields produced `'none'`, and a camera nobody had reached
23996
+ * was drawn "on battery alone" — the same D315 defect `percentage` closed
23997
+ * by going nullable. Consumers SKIP it (no charger state published, no
23998
+ * glyph, no HomeKit ChargingState) rather than coerce it either way.
23991
23999
  */
23992
24000
  charging: _enum([
23993
24001
  "dc",
23994
24002
  "solar",
23995
- "none"
24003
+ "none",
24004
+ "unknown"
23996
24005
  ]),
23997
24006
  /**
23998
24007
  * True when the camera firmware has gone into low-power mode. Battery
@@ -24082,7 +24091,7 @@ onStatusChanged: { data: object({
24082
24091
  kind: "push",
24083
24092
  empty: {
24084
24093
  percentage: 0,
24085
- charging: "none",
24094
+ charging: "unknown",
24086
24095
  sleeping: false,
24087
24096
  lastUpdated: 0
24088
24097
  }
@@ -33836,13 +33845,30 @@ var BaseDeviceProvider = class extends BaseAddon {
33836
33845
  async onRestoreDevices(savedDevices) {
33837
33846
  const restored = /* @__PURE__ */ new Set();
33838
33847
  const failures = [];
33848
+ const liveStableIds = new Set((await this.ctx.kernel.devices.getAll()).map((device) => device.stableId));
33839
33849
  const attemptRestore = async (saved) => {
33840
33850
  if (restored.has(saved.id)) return;
33851
+ if (liveStableIds.has(saved.stableId)) {
33852
+ this.ctx.logger.debug("Restore row already live — not dialled again", { tags: {
33853
+ deviceId: saved.id,
33854
+ stableId: saved.stableId
33855
+ } });
33856
+ restored.add(saved.id);
33857
+ return;
33858
+ }
33841
33859
  const Class = this.deviceClasses[saved.type];
33842
33860
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33843
33861
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33844
33862
  await this.healSavedConfig(saved, savedDevices);
33845
- await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33863
+ try {
33864
+ await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33865
+ } catch (err) {
33866
+ if (!(await this.ctx.kernel.devices.getAll()).some((device) => device.stableId === saved.stableId)) throw err;
33867
+ this.ctx.logger.debug("Restore row came to life during its own dial — kept", { tags: {
33868
+ deviceId: saved.id,
33869
+ stableId: saved.stableId
33870
+ } });
33871
+ }
33846
33872
  restored.add(saved.id);
33847
33873
  };
33848
33874
  const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
package/dist/addon.mjs CHANGED
@@ -7549,7 +7549,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
7549
7549
  var CameraSwitchUnavailableReasonSchema = _enum([
7550
7550
  "no-provider",
7551
7551
  "source-unreachable",
7552
- "not-configured"
7552
+ "not-configured",
7553
+ "device-disabled"
7553
7554
  ]);
7554
7555
  /**
7555
7556
  * One switch, resolved for one camera.
@@ -23988,12 +23989,20 @@ var BatteryStatusSchema = object({
23988
23989
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
23989
23990
  * Reolink-specific for the Solar Panel 2 accessory (will become
23990
23991
  * common on other battery cams). `'none'` means running on battery
23991
- * alone.
23992
+ * alone — a NEGATIVE the firmware actually reported.
23993
+ *
23994
+ * `'unknown'` is the absence of an answer: the provider has not read the
23995
+ * power fields yet, or the firmware reported neither. Before it existed,
23996
+ * two unreadable fields produced `'none'`, and a camera nobody had reached
23997
+ * was drawn "on battery alone" — the same D315 defect `percentage` closed
23998
+ * by going nullable. Consumers SKIP it (no charger state published, no
23999
+ * glyph, no HomeKit ChargingState) rather than coerce it either way.
23992
24000
  */
23993
24001
  charging: _enum([
23994
24002
  "dc",
23995
24003
  "solar",
23996
- "none"
24004
+ "none",
24005
+ "unknown"
23997
24006
  ]),
23998
24007
  /**
23999
24008
  * True when the camera firmware has gone into low-power mode. Battery
@@ -24083,7 +24092,7 @@ onStatusChanged: { data: object({
24083
24092
  kind: "push",
24084
24093
  empty: {
24085
24094
  percentage: 0,
24086
- charging: "none",
24095
+ charging: "unknown",
24087
24096
  sleeping: false,
24088
24097
  lastUpdated: 0
24089
24098
  }
@@ -33837,13 +33846,30 @@ var BaseDeviceProvider = class extends BaseAddon {
33837
33846
  async onRestoreDevices(savedDevices) {
33838
33847
  const restored = /* @__PURE__ */ new Set();
33839
33848
  const failures = [];
33849
+ const liveStableIds = new Set((await this.ctx.kernel.devices.getAll()).map((device) => device.stableId));
33840
33850
  const attemptRestore = async (saved) => {
33841
33851
  if (restored.has(saved.id)) return;
33852
+ if (liveStableIds.has(saved.stableId)) {
33853
+ this.ctx.logger.debug("Restore row already live — not dialled again", { tags: {
33854
+ deviceId: saved.id,
33855
+ stableId: saved.stableId
33856
+ } });
33857
+ restored.add(saved.id);
33858
+ return;
33859
+ }
33842
33860
  const Class = this.deviceClasses[saved.type];
33843
33861
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33844
33862
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33845
33863
  await this.healSavedConfig(saved, savedDevices);
33846
- await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33864
+ try {
33865
+ await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33866
+ } catch (err) {
33867
+ if (!(await this.ctx.kernel.devices.getAll()).some((device) => device.stableId === saved.stableId)) throw err;
33868
+ this.ctx.logger.debug("Restore row came to life during its own dial — kept", { tags: {
33869
+ deviceId: saved.id,
33870
+ stableId: saved.stableId
33871
+ } });
33872
+ }
33847
33873
  restored.add(saved.id);
33848
33874
  };
33849
33875
  const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.73",
3
+ "version": "1.2.75",
4
4
  "description": "Hikvision camera device provider addon for CamStack — ISAPI over HTTP(S) with digest auth (snapshot, alarm stream, RTSP discovery)",
5
5
  "keywords": [
6
6
  "camstack",