@camstack/addon-provider-hikvision 1.2.119 → 1.2.121

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
@@ -5367,7 +5367,7 @@ var ZodIssueCode = {
5367
5367
  var ZodFirstPartyTypeKind;
5368
5368
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5369
5369
  //#endregion
5370
- //#region ../types/dist/sleep-B1y0Fo1U.mjs
5370
+ //#region ../types/dist/sleep-Dk-BdnBf.mjs
5371
5371
  /**
5372
5372
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5373
5373
  * window to float samples (D455).
@@ -31534,6 +31534,26 @@ var PtzHomePresetSchema = object({
31534
31534
  "none"
31535
31535
  ])
31536
31536
  });
31537
+ /**
31538
+ * The camera's own idle-return behaviour: whether it goes home by itself, and
31539
+ * after how long.
31540
+ *
31541
+ * ONE shape, not two scalars, because on the camera they are ONE fact —
31542
+ * Reolink's guard point carries `benable` and `timeout` in the same element,
31543
+ * written by the same command. Reading them apart means two round-trips that
31544
+ * can disagree.
31545
+ *
31546
+ * `enabled` and `seconds` are INDEPENDENT: a camera can hold a 68 s delay with
31547
+ * the behaviour switched off, which is exactly what the vendor app leaves when
31548
+ * you turn the guard point off without clearing it. The first version of this
31549
+ * cap reported only the delay, so that state was invisible — a camera could be
31550
+ * configured to return after 68 seconds with no way to say whether it should
31551
+ * return at all.
31552
+ */
31553
+ var PtzHomeReturnSchema = object({
31554
+ enabled: boolean(),
31555
+ seconds: number().int()
31556
+ });
31537
31557
  var ptzCapability = {
31538
31558
  name: "ptz",
31539
31559
  scope: "device",
@@ -31593,6 +31613,54 @@ var ptzCapability = {
31593
31613
  auth: "admin"
31594
31614
  }),
31595
31615
  /**
31616
+ * Make WHERE THE HEAD IS POINTING RIGHT NOW this camera's home.
31617
+ *
31618
+ * `setHomePreset` takes a preset id, and a preset id is the wrong shape for
31619
+ * the most natural thing an operator does: aim the camera, then say "here".
31620
+ * It is also the only shape a camera whose home is NOT a preset can accept
31621
+ * -- Reolink's home is a guard point, a stored position with no id at all,
31622
+ * and hiding the control for that case (as the UI briefly did) left the one
31623
+ * vendor with a native home unable to set it.
31624
+ *
31625
+ * Each provider does it its own way:
31626
+ * reolink `setGrd` with `needSetPos`, pinning the guard point here
31627
+ * hikvision / amcrest save a preset, then pin it as home
31628
+ * onvif REFUSES -- its home is the PTZ origin and cannot be moved
31629
+ *
31630
+ * A provider that cannot honour it throws rather than silently doing
31631
+ * nothing: an operator who pressed "set home here" and saw no error would
31632
+ * believe it took.
31633
+ */
31634
+ captureHomeHere: method(object({ deviceId: number() }), _void(), {
31635
+ kind: "mutation",
31636
+ auth: "admin"
31637
+ }),
31638
+ /**
31639
+ * Whether the camera returns home on its own when left idle, and after how
31640
+ * long. `null` when it has no such behaviour at all.
31641
+ *
31642
+ * Reolink's guard point carries one (`timeout`, freely settable in
31643
+ * seconds). It is a real function of the camera that we did not expose at
31644
+ * all, and it belongs next to the home it governs rather than in a vendor
31645
+ * settings page nobody associates with Home.
31646
+ */
31647
+ getHomeReturn: method(object({ deviceId: number() }), PtzHomeReturnSchema.nullable()),
31648
+ /**
31649
+ * Set both together. Refused by a camera with no idle-return behaviour.
31650
+ *
31651
+ * They travel together for the same reason they are read together: the
31652
+ * underlying write carries both, so setting one alone means reading the
31653
+ * other back first and racing whoever else is writing.
31654
+ */
31655
+ setHomeReturn: method(object({
31656
+ deviceId: number(),
31657
+ enabled: boolean(),
31658
+ seconds: number().int().min(0).max(3600)
31659
+ }), _void(), {
31660
+ kind: "mutation",
31661
+ auth: "admin"
31662
+ }),
31663
+ /**
31596
31664
  * Pull the current PTZ position. Redundant with the auto-injected
31597
31665
  * `getStatus` method (see `status` below); kept for callers that
31598
31666
  * haven't migrated. Will be folded into `getStatus` in a future
@@ -41305,6 +41373,12 @@ Object.freeze({
41305
41373
  addonId: null,
41306
41374
  access: "create"
41307
41375
  },
41376
+ "ptz.captureHomeHere": {
41377
+ capName: "ptz",
41378
+ capScope: "device",
41379
+ addonId: null,
41380
+ access: "create"
41381
+ },
41308
41382
  "ptz.continuousMove": {
41309
41383
  capName: "ptz",
41310
41384
  capScope: "device",
@@ -41323,6 +41397,12 @@ Object.freeze({
41323
41397
  addonId: null,
41324
41398
  access: "view"
41325
41399
  },
41400
+ "ptz.getHomeReturn": {
41401
+ capName: "ptz",
41402
+ capScope: "device",
41403
+ addonId: null,
41404
+ access: "view"
41405
+ },
41326
41406
  "ptz.getOptions": {
41327
41407
  capName: "ptz",
41328
41408
  capScope: "device",
@@ -41377,6 +41457,12 @@ Object.freeze({
41377
41457
  addonId: null,
41378
41458
  access: "create"
41379
41459
  },
41460
+ "ptz.setHomeReturn": {
41461
+ capName: "ptz",
41462
+ capScope: "device",
41463
+ addonId: null,
41464
+ access: "create"
41465
+ },
41380
41466
  "ptz.stop": {
41381
41467
  capName: "ptz",
41382
41468
  capScope: "device",
@@ -44540,6 +44626,11 @@ Object.freeze({
44540
44626
  form: "single",
44541
44627
  optional: false
44542
44628
  }],
44629
+ "ptz.captureHomeHere": [{
44630
+ name: "deviceId",
44631
+ form: "single",
44632
+ optional: false
44633
+ }],
44543
44634
  "ptz.continuousMove": [{
44544
44635
  name: "deviceId",
44545
44636
  form: "single",
@@ -44555,6 +44646,11 @@ Object.freeze({
44555
44646
  form: "single",
44556
44647
  optional: false
44557
44648
  }],
44649
+ "ptz.getHomeReturn": [{
44650
+ name: "deviceId",
44651
+ form: "single",
44652
+ optional: false
44653
+ }],
44558
44654
  "ptz.getOptions": [{
44559
44655
  name: "deviceId",
44560
44656
  form: "single",
@@ -44600,6 +44696,11 @@ Object.freeze({
44600
44696
  form: "single",
44601
44697
  optional: false
44602
44698
  }],
44699
+ "ptz.setHomeReturn": [{
44700
+ name: "deviceId",
44701
+ form: "single",
44702
+ optional: false
44703
+ }],
44603
44704
  "ptz.stop": [{
44604
44705
  name: "deviceId",
44605
44706
  form: "single",
@@ -49975,6 +50076,43 @@ function assertPresetWritable(presetId, action) {
49975
50076
  throw new Error(`Preset ${presetId} is a firmware function on this camera (${fn}) and cannot be ${action}` + (action === "overwritten" ? " — pick an unreserved id" : ""));
49976
50077
  }
49977
50078
  //#endregion
50079
+ //#region src/free-preset-slot.ts
50080
+ /**
50081
+ * The lowest preset id this camera will actually accept a write on.
50082
+ *
50083
+ * "Set home here" has to land somewhere, and on a camera whose home IS a preset
50084
+ * that means claiming a slot. Two things make the choice non-obvious:
50085
+ *
50086
+ * - the reserved FUNCTION band shares the preset namespace (34 "Back to
50087
+ * origin", 94 "Remote reboot", 95 "Call OSD menu" and friends), and writing
50088
+ * one answers HTTP 500;
50089
+ * - the operator's own presets must not be overwritten, ever. Losing
50090
+ * "Credenza" to make room for a home is a worse trade than refusing.
50091
+ *
50092
+ * So: the first id that is neither taken nor reserved, within the camera's
50093
+ * advertised budget. `null` when there is no room, and the caller must then
50094
+ * REFUSE rather than pick a victim.
50095
+ */
50096
+ function findFreePresetSlot(taken, maxPresets) {
50097
+ const used = new Set(taken.map((p) => p.id));
50098
+ const ceiling = maxPresets === void 0 ? 32 : Math.min(maxPresets, 300);
50099
+ for (let id = 1; id <= ceiling; id += 1) {
50100
+ const candidate = String(id);
50101
+ if (used.has(candidate)) continue;
50102
+ if (!isPresetWritable(candidate)) continue;
50103
+ return candidate;
50104
+ }
50105
+ return null;
50106
+ }
50107
+ /**
50108
+ * The name written on a preset claimed by "set home here".
50109
+ *
50110
+ * Named rather than left a literal so the slot is recognisable in the camera's
50111
+ * own web UI — an operator who finds an unexplained preset has no way to know
50112
+ * it is ours.
50113
+ */
50114
+ var HOME_PRESET_NAME = "CamStack Home";
50115
+ //#endregion
49978
50116
  //#region src/schema.ts
49979
50117
  /**
49980
50118
  * Single source of truth for the addon id used in event-source tags +
@@ -51750,6 +51888,27 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
51750
51888
  };
51751
51889
  return this.resolveHomePreset();
51752
51890
  },
51891
+ captureHomeHere: async ({ deviceId }) => {
51892
+ if (deviceId !== this.id) return;
51893
+ const [presets, caps] = await Promise.all([isapi.listPtzPresets(cameraNumber), isapi.getPtzCapabilities(cameraNumber)]);
51894
+ const slot = findFreePresetSlot(presets.map((p) => ({
51895
+ id: p.id,
51896
+ writable: isPresetWritable(p.id)
51897
+ })), caps?.maxPresetCount ?? void 0);
51898
+ if (slot === null) throw new Error("No free preset slot on this camera to store a home position — free one first");
51899
+ await isapi.savePtzPreset(cameraNumber, slot, HOME_PRESET_NAME);
51900
+ await this.config.setAll({ homePresetId: slot });
51901
+ this.ctx.logger.info("hikvision: home pinned to the current position", {
51902
+ tags: { deviceId: this.id },
51903
+ meta: { presetId: slot }
51904
+ });
51905
+ },
51906
+ getHomeReturn: async () => {
51907
+ return null;
51908
+ },
51909
+ setHomeReturn: async () => {
51910
+ throw new Error("Hikvision: the idle-return timer is not wired on this driver");
51911
+ },
51753
51912
  setHomePreset: async ({ deviceId, presetId }) => {
51754
51913
  if (deviceId !== this.id) return;
51755
51914
  await this.config.setAll({ homePresetId: presetId ?? "" });
package/dist/addon.mjs CHANGED
@@ -5368,7 +5368,7 @@ var ZodIssueCode = {
5368
5368
  var ZodFirstPartyTypeKind;
5369
5369
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5370
5370
  //#endregion
5371
- //#region ../types/dist/sleep-B1y0Fo1U.mjs
5371
+ //#region ../types/dist/sleep-Dk-BdnBf.mjs
5372
5372
  /**
5373
5373
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5374
5374
  * window to float samples (D455).
@@ -31535,6 +31535,26 @@ var PtzHomePresetSchema = object({
31535
31535
  "none"
31536
31536
  ])
31537
31537
  });
31538
+ /**
31539
+ * The camera's own idle-return behaviour: whether it goes home by itself, and
31540
+ * after how long.
31541
+ *
31542
+ * ONE shape, not two scalars, because on the camera they are ONE fact —
31543
+ * Reolink's guard point carries `benable` and `timeout` in the same element,
31544
+ * written by the same command. Reading them apart means two round-trips that
31545
+ * can disagree.
31546
+ *
31547
+ * `enabled` and `seconds` are INDEPENDENT: a camera can hold a 68 s delay with
31548
+ * the behaviour switched off, which is exactly what the vendor app leaves when
31549
+ * you turn the guard point off without clearing it. The first version of this
31550
+ * cap reported only the delay, so that state was invisible — a camera could be
31551
+ * configured to return after 68 seconds with no way to say whether it should
31552
+ * return at all.
31553
+ */
31554
+ var PtzHomeReturnSchema = object({
31555
+ enabled: boolean(),
31556
+ seconds: number().int()
31557
+ });
31538
31558
  var ptzCapability = {
31539
31559
  name: "ptz",
31540
31560
  scope: "device",
@@ -31594,6 +31614,54 @@ var ptzCapability = {
31594
31614
  auth: "admin"
31595
31615
  }),
31596
31616
  /**
31617
+ * Make WHERE THE HEAD IS POINTING RIGHT NOW this camera's home.
31618
+ *
31619
+ * `setHomePreset` takes a preset id, and a preset id is the wrong shape for
31620
+ * the most natural thing an operator does: aim the camera, then say "here".
31621
+ * It is also the only shape a camera whose home is NOT a preset can accept
31622
+ * -- Reolink's home is a guard point, a stored position with no id at all,
31623
+ * and hiding the control for that case (as the UI briefly did) left the one
31624
+ * vendor with a native home unable to set it.
31625
+ *
31626
+ * Each provider does it its own way:
31627
+ * reolink `setGrd` with `needSetPos`, pinning the guard point here
31628
+ * hikvision / amcrest save a preset, then pin it as home
31629
+ * onvif REFUSES -- its home is the PTZ origin and cannot be moved
31630
+ *
31631
+ * A provider that cannot honour it throws rather than silently doing
31632
+ * nothing: an operator who pressed "set home here" and saw no error would
31633
+ * believe it took.
31634
+ */
31635
+ captureHomeHere: method(object({ deviceId: number() }), _void(), {
31636
+ kind: "mutation",
31637
+ auth: "admin"
31638
+ }),
31639
+ /**
31640
+ * Whether the camera returns home on its own when left idle, and after how
31641
+ * long. `null` when it has no such behaviour at all.
31642
+ *
31643
+ * Reolink's guard point carries one (`timeout`, freely settable in
31644
+ * seconds). It is a real function of the camera that we did not expose at
31645
+ * all, and it belongs next to the home it governs rather than in a vendor
31646
+ * settings page nobody associates with Home.
31647
+ */
31648
+ getHomeReturn: method(object({ deviceId: number() }), PtzHomeReturnSchema.nullable()),
31649
+ /**
31650
+ * Set both together. Refused by a camera with no idle-return behaviour.
31651
+ *
31652
+ * They travel together for the same reason they are read together: the
31653
+ * underlying write carries both, so setting one alone means reading the
31654
+ * other back first and racing whoever else is writing.
31655
+ */
31656
+ setHomeReturn: method(object({
31657
+ deviceId: number(),
31658
+ enabled: boolean(),
31659
+ seconds: number().int().min(0).max(3600)
31660
+ }), _void(), {
31661
+ kind: "mutation",
31662
+ auth: "admin"
31663
+ }),
31664
+ /**
31597
31665
  * Pull the current PTZ position. Redundant with the auto-injected
31598
31666
  * `getStatus` method (see `status` below); kept for callers that
31599
31667
  * haven't migrated. Will be folded into `getStatus` in a future
@@ -41306,6 +41374,12 @@ Object.freeze({
41306
41374
  addonId: null,
41307
41375
  access: "create"
41308
41376
  },
41377
+ "ptz.captureHomeHere": {
41378
+ capName: "ptz",
41379
+ capScope: "device",
41380
+ addonId: null,
41381
+ access: "create"
41382
+ },
41309
41383
  "ptz.continuousMove": {
41310
41384
  capName: "ptz",
41311
41385
  capScope: "device",
@@ -41324,6 +41398,12 @@ Object.freeze({
41324
41398
  addonId: null,
41325
41399
  access: "view"
41326
41400
  },
41401
+ "ptz.getHomeReturn": {
41402
+ capName: "ptz",
41403
+ capScope: "device",
41404
+ addonId: null,
41405
+ access: "view"
41406
+ },
41327
41407
  "ptz.getOptions": {
41328
41408
  capName: "ptz",
41329
41409
  capScope: "device",
@@ -41378,6 +41458,12 @@ Object.freeze({
41378
41458
  addonId: null,
41379
41459
  access: "create"
41380
41460
  },
41461
+ "ptz.setHomeReturn": {
41462
+ capName: "ptz",
41463
+ capScope: "device",
41464
+ addonId: null,
41465
+ access: "create"
41466
+ },
41381
41467
  "ptz.stop": {
41382
41468
  capName: "ptz",
41383
41469
  capScope: "device",
@@ -44541,6 +44627,11 @@ Object.freeze({
44541
44627
  form: "single",
44542
44628
  optional: false
44543
44629
  }],
44630
+ "ptz.captureHomeHere": [{
44631
+ name: "deviceId",
44632
+ form: "single",
44633
+ optional: false
44634
+ }],
44544
44635
  "ptz.continuousMove": [{
44545
44636
  name: "deviceId",
44546
44637
  form: "single",
@@ -44556,6 +44647,11 @@ Object.freeze({
44556
44647
  form: "single",
44557
44648
  optional: false
44558
44649
  }],
44650
+ "ptz.getHomeReturn": [{
44651
+ name: "deviceId",
44652
+ form: "single",
44653
+ optional: false
44654
+ }],
44559
44655
  "ptz.getOptions": [{
44560
44656
  name: "deviceId",
44561
44657
  form: "single",
@@ -44601,6 +44697,11 @@ Object.freeze({
44601
44697
  form: "single",
44602
44698
  optional: false
44603
44699
  }],
44700
+ "ptz.setHomeReturn": [{
44701
+ name: "deviceId",
44702
+ form: "single",
44703
+ optional: false
44704
+ }],
44604
44705
  "ptz.stop": [{
44605
44706
  name: "deviceId",
44606
44707
  form: "single",
@@ -49976,6 +50077,43 @@ function assertPresetWritable(presetId, action) {
49976
50077
  throw new Error(`Preset ${presetId} is a firmware function on this camera (${fn}) and cannot be ${action}` + (action === "overwritten" ? " — pick an unreserved id" : ""));
49977
50078
  }
49978
50079
  //#endregion
50080
+ //#region src/free-preset-slot.ts
50081
+ /**
50082
+ * The lowest preset id this camera will actually accept a write on.
50083
+ *
50084
+ * "Set home here" has to land somewhere, and on a camera whose home IS a preset
50085
+ * that means claiming a slot. Two things make the choice non-obvious:
50086
+ *
50087
+ * - the reserved FUNCTION band shares the preset namespace (34 "Back to
50088
+ * origin", 94 "Remote reboot", 95 "Call OSD menu" and friends), and writing
50089
+ * one answers HTTP 500;
50090
+ * - the operator's own presets must not be overwritten, ever. Losing
50091
+ * "Credenza" to make room for a home is a worse trade than refusing.
50092
+ *
50093
+ * So: the first id that is neither taken nor reserved, within the camera's
50094
+ * advertised budget. `null` when there is no room, and the caller must then
50095
+ * REFUSE rather than pick a victim.
50096
+ */
50097
+ function findFreePresetSlot(taken, maxPresets) {
50098
+ const used = new Set(taken.map((p) => p.id));
50099
+ const ceiling = maxPresets === void 0 ? 32 : Math.min(maxPresets, 300);
50100
+ for (let id = 1; id <= ceiling; id += 1) {
50101
+ const candidate = String(id);
50102
+ if (used.has(candidate)) continue;
50103
+ if (!isPresetWritable(candidate)) continue;
50104
+ return candidate;
50105
+ }
50106
+ return null;
50107
+ }
50108
+ /**
50109
+ * The name written on a preset claimed by "set home here".
50110
+ *
50111
+ * Named rather than left a literal so the slot is recognisable in the camera's
50112
+ * own web UI — an operator who finds an unexplained preset has no way to know
50113
+ * it is ours.
50114
+ */
50115
+ var HOME_PRESET_NAME = "CamStack Home";
50116
+ //#endregion
49979
50117
  //#region src/schema.ts
49980
50118
  /**
49981
50119
  * Single source of truth for the addon id used in event-source tags +
@@ -51751,6 +51889,27 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
51751
51889
  };
51752
51890
  return this.resolveHomePreset();
51753
51891
  },
51892
+ captureHomeHere: async ({ deviceId }) => {
51893
+ if (deviceId !== this.id) return;
51894
+ const [presets, caps] = await Promise.all([isapi.listPtzPresets(cameraNumber), isapi.getPtzCapabilities(cameraNumber)]);
51895
+ const slot = findFreePresetSlot(presets.map((p) => ({
51896
+ id: p.id,
51897
+ writable: isPresetWritable(p.id)
51898
+ })), caps?.maxPresetCount ?? void 0);
51899
+ if (slot === null) throw new Error("No free preset slot on this camera to store a home position — free one first");
51900
+ await isapi.savePtzPreset(cameraNumber, slot, HOME_PRESET_NAME);
51901
+ await this.config.setAll({ homePresetId: slot });
51902
+ this.ctx.logger.info("hikvision: home pinned to the current position", {
51903
+ tags: { deviceId: this.id },
51904
+ meta: { presetId: slot }
51905
+ });
51906
+ },
51907
+ getHomeReturn: async () => {
51908
+ return null;
51909
+ },
51910
+ setHomeReturn: async () => {
51911
+ throw new Error("Hikvision: the idle-return timer is not wired on this driver");
51912
+ },
51754
51913
  setHomePreset: async ({ deviceId, presetId }) => {
51755
51914
  if (deviceId !== this.id) return;
51756
51915
  await this.config.setAll({ homePresetId: presetId ?? "" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.119",
3
+ "version": "1.2.121",
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",