@camstack/addon-provider-hikvision 1.2.119 → 1.2.120

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-vl6nBE8d.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).
@@ -31593,6 +31593,47 @@ var ptzCapability = {
31593
31593
  auth: "admin"
31594
31594
  }),
31595
31595
  /**
31596
+ * Make WHERE THE HEAD IS POINTING RIGHT NOW this camera's home.
31597
+ *
31598
+ * `setHomePreset` takes a preset id, and a preset id is the wrong shape for
31599
+ * the most natural thing an operator does: aim the camera, then say "here".
31600
+ * It is also the only shape a camera whose home is NOT a preset can accept
31601
+ * -- Reolink's home is a guard point, a stored position with no id at all,
31602
+ * and hiding the control for that case (as the UI briefly did) left the one
31603
+ * vendor with a native home unable to set it.
31604
+ *
31605
+ * Each provider does it its own way:
31606
+ * reolink `setGrd` with `needSetPos`, pinning the guard point here
31607
+ * hikvision / amcrest save a preset, then pin it as home
31608
+ * onvif REFUSES -- its home is the PTZ origin and cannot be moved
31609
+ *
31610
+ * A provider that cannot honour it throws rather than silently doing
31611
+ * nothing: an operator who pressed "set home here" and saw no error would
31612
+ * believe it took.
31613
+ */
31614
+ captureHomeHere: method(object({ deviceId: number() }), _void(), {
31615
+ kind: "mutation",
31616
+ auth: "admin"
31617
+ }),
31618
+ /**
31619
+ * How long the camera waits, idle, before returning home on its own --
31620
+ * `null` when it has no such behaviour.
31621
+ *
31622
+ * Reolink's guard point carries one (`timeout`, freely settable in
31623
+ * seconds). It is a real function of the camera that we did not expose at
31624
+ * all, and it belongs next to the home it governs rather than in a vendor
31625
+ * settings page nobody associates with Home.
31626
+ */
31627
+ getHomeReturnSeconds: method(object({ deviceId: number() }), number().int().nullable()),
31628
+ /** Set the idle-return delay. Refused by a camera that has no such timer. */
31629
+ setHomeReturnSeconds: method(object({
31630
+ deviceId: number(),
31631
+ seconds: number().int().min(0).max(3600)
31632
+ }), _void(), {
31633
+ kind: "mutation",
31634
+ auth: "admin"
31635
+ }),
31636
+ /**
31596
31637
  * Pull the current PTZ position. Redundant with the auto-injected
31597
31638
  * `getStatus` method (see `status` below); kept for callers that
31598
31639
  * haven't migrated. Will be folded into `getStatus` in a future
@@ -41305,6 +41346,12 @@ Object.freeze({
41305
41346
  addonId: null,
41306
41347
  access: "create"
41307
41348
  },
41349
+ "ptz.captureHomeHere": {
41350
+ capName: "ptz",
41351
+ capScope: "device",
41352
+ addonId: null,
41353
+ access: "create"
41354
+ },
41308
41355
  "ptz.continuousMove": {
41309
41356
  capName: "ptz",
41310
41357
  capScope: "device",
@@ -41323,6 +41370,12 @@ Object.freeze({
41323
41370
  addonId: null,
41324
41371
  access: "view"
41325
41372
  },
41373
+ "ptz.getHomeReturnSeconds": {
41374
+ capName: "ptz",
41375
+ capScope: "device",
41376
+ addonId: null,
41377
+ access: "view"
41378
+ },
41326
41379
  "ptz.getOptions": {
41327
41380
  capName: "ptz",
41328
41381
  capScope: "device",
@@ -41377,6 +41430,12 @@ Object.freeze({
41377
41430
  addonId: null,
41378
41431
  access: "create"
41379
41432
  },
41433
+ "ptz.setHomeReturnSeconds": {
41434
+ capName: "ptz",
41435
+ capScope: "device",
41436
+ addonId: null,
41437
+ access: "create"
41438
+ },
41380
41439
  "ptz.stop": {
41381
41440
  capName: "ptz",
41382
41441
  capScope: "device",
@@ -44540,6 +44599,11 @@ Object.freeze({
44540
44599
  form: "single",
44541
44600
  optional: false
44542
44601
  }],
44602
+ "ptz.captureHomeHere": [{
44603
+ name: "deviceId",
44604
+ form: "single",
44605
+ optional: false
44606
+ }],
44543
44607
  "ptz.continuousMove": [{
44544
44608
  name: "deviceId",
44545
44609
  form: "single",
@@ -44555,6 +44619,11 @@ Object.freeze({
44555
44619
  form: "single",
44556
44620
  optional: false
44557
44621
  }],
44622
+ "ptz.getHomeReturnSeconds": [{
44623
+ name: "deviceId",
44624
+ form: "single",
44625
+ optional: false
44626
+ }],
44558
44627
  "ptz.getOptions": [{
44559
44628
  name: "deviceId",
44560
44629
  form: "single",
@@ -44600,6 +44669,11 @@ Object.freeze({
44600
44669
  form: "single",
44601
44670
  optional: false
44602
44671
  }],
44672
+ "ptz.setHomeReturnSeconds": [{
44673
+ name: "deviceId",
44674
+ form: "single",
44675
+ optional: false
44676
+ }],
44603
44677
  "ptz.stop": [{
44604
44678
  name: "deviceId",
44605
44679
  form: "single",
@@ -49975,6 +50049,43 @@ function assertPresetWritable(presetId, action) {
49975
50049
  throw new Error(`Preset ${presetId} is a firmware function on this camera (${fn}) and cannot be ${action}` + (action === "overwritten" ? " — pick an unreserved id" : ""));
49976
50050
  }
49977
50051
  //#endregion
50052
+ //#region src/free-preset-slot.ts
50053
+ /**
50054
+ * The lowest preset id this camera will actually accept a write on.
50055
+ *
50056
+ * "Set home here" has to land somewhere, and on a camera whose home IS a preset
50057
+ * that means claiming a slot. Two things make the choice non-obvious:
50058
+ *
50059
+ * - the reserved FUNCTION band shares the preset namespace (34 "Back to
50060
+ * origin", 94 "Remote reboot", 95 "Call OSD menu" and friends), and writing
50061
+ * one answers HTTP 500;
50062
+ * - the operator's own presets must not be overwritten, ever. Losing
50063
+ * "Credenza" to make room for a home is a worse trade than refusing.
50064
+ *
50065
+ * So: the first id that is neither taken nor reserved, within the camera's
50066
+ * advertised budget. `null` when there is no room, and the caller must then
50067
+ * REFUSE rather than pick a victim.
50068
+ */
50069
+ function findFreePresetSlot(taken, maxPresets) {
50070
+ const used = new Set(taken.map((p) => p.id));
50071
+ const ceiling = maxPresets === void 0 ? 32 : Math.min(maxPresets, 300);
50072
+ for (let id = 1; id <= ceiling; id += 1) {
50073
+ const candidate = String(id);
50074
+ if (used.has(candidate)) continue;
50075
+ if (!isPresetWritable(candidate)) continue;
50076
+ return candidate;
50077
+ }
50078
+ return null;
50079
+ }
50080
+ /**
50081
+ * The name written on a preset claimed by "set home here".
50082
+ *
50083
+ * Named rather than left a literal so the slot is recognisable in the camera's
50084
+ * own web UI — an operator who finds an unexplained preset has no way to know
50085
+ * it is ours.
50086
+ */
50087
+ var HOME_PRESET_NAME = "CamStack Home";
50088
+ //#endregion
49978
50089
  //#region src/schema.ts
49979
50090
  /**
49980
50091
  * Single source of truth for the addon id used in event-source tags +
@@ -51750,6 +51861,27 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
51750
51861
  };
51751
51862
  return this.resolveHomePreset();
51752
51863
  },
51864
+ captureHomeHere: async ({ deviceId }) => {
51865
+ if (deviceId !== this.id) return;
51866
+ const [presets, caps] = await Promise.all([isapi.listPtzPresets(cameraNumber), isapi.getPtzCapabilities(cameraNumber)]);
51867
+ const slot = findFreePresetSlot(presets.map((p) => ({
51868
+ id: p.id,
51869
+ writable: isPresetWritable(p.id)
51870
+ })), caps?.maxPresetCount ?? void 0);
51871
+ if (slot === null) throw new Error("No free preset slot on this camera to store a home position — free one first");
51872
+ await isapi.savePtzPreset(cameraNumber, slot, HOME_PRESET_NAME);
51873
+ await this.config.setAll({ homePresetId: slot });
51874
+ this.ctx.logger.info("hikvision: home pinned to the current position", {
51875
+ tags: { deviceId: this.id },
51876
+ meta: { presetId: slot }
51877
+ });
51878
+ },
51879
+ getHomeReturnSeconds: async () => {
51880
+ return null;
51881
+ },
51882
+ setHomeReturnSeconds: async () => {
51883
+ throw new Error("Hikvision: the idle-return timer is not wired on this driver");
51884
+ },
51753
51885
  setHomePreset: async ({ deviceId, presetId }) => {
51754
51886
  if (deviceId !== this.id) return;
51755
51887
  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-vl6nBE8d.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).
@@ -31594,6 +31594,47 @@ var ptzCapability = {
31594
31594
  auth: "admin"
31595
31595
  }),
31596
31596
  /**
31597
+ * Make WHERE THE HEAD IS POINTING RIGHT NOW this camera's home.
31598
+ *
31599
+ * `setHomePreset` takes a preset id, and a preset id is the wrong shape for
31600
+ * the most natural thing an operator does: aim the camera, then say "here".
31601
+ * It is also the only shape a camera whose home is NOT a preset can accept
31602
+ * -- Reolink's home is a guard point, a stored position with no id at all,
31603
+ * and hiding the control for that case (as the UI briefly did) left the one
31604
+ * vendor with a native home unable to set it.
31605
+ *
31606
+ * Each provider does it its own way:
31607
+ * reolink `setGrd` with `needSetPos`, pinning the guard point here
31608
+ * hikvision / amcrest save a preset, then pin it as home
31609
+ * onvif REFUSES -- its home is the PTZ origin and cannot be moved
31610
+ *
31611
+ * A provider that cannot honour it throws rather than silently doing
31612
+ * nothing: an operator who pressed "set home here" and saw no error would
31613
+ * believe it took.
31614
+ */
31615
+ captureHomeHere: method(object({ deviceId: number() }), _void(), {
31616
+ kind: "mutation",
31617
+ auth: "admin"
31618
+ }),
31619
+ /**
31620
+ * How long the camera waits, idle, before returning home on its own --
31621
+ * `null` when it has no such behaviour.
31622
+ *
31623
+ * Reolink's guard point carries one (`timeout`, freely settable in
31624
+ * seconds). It is a real function of the camera that we did not expose at
31625
+ * all, and it belongs next to the home it governs rather than in a vendor
31626
+ * settings page nobody associates with Home.
31627
+ */
31628
+ getHomeReturnSeconds: method(object({ deviceId: number() }), number().int().nullable()),
31629
+ /** Set the idle-return delay. Refused by a camera that has no such timer. */
31630
+ setHomeReturnSeconds: method(object({
31631
+ deviceId: number(),
31632
+ seconds: number().int().min(0).max(3600)
31633
+ }), _void(), {
31634
+ kind: "mutation",
31635
+ auth: "admin"
31636
+ }),
31637
+ /**
31597
31638
  * Pull the current PTZ position. Redundant with the auto-injected
31598
31639
  * `getStatus` method (see `status` below); kept for callers that
31599
31640
  * haven't migrated. Will be folded into `getStatus` in a future
@@ -41306,6 +41347,12 @@ Object.freeze({
41306
41347
  addonId: null,
41307
41348
  access: "create"
41308
41349
  },
41350
+ "ptz.captureHomeHere": {
41351
+ capName: "ptz",
41352
+ capScope: "device",
41353
+ addonId: null,
41354
+ access: "create"
41355
+ },
41309
41356
  "ptz.continuousMove": {
41310
41357
  capName: "ptz",
41311
41358
  capScope: "device",
@@ -41324,6 +41371,12 @@ Object.freeze({
41324
41371
  addonId: null,
41325
41372
  access: "view"
41326
41373
  },
41374
+ "ptz.getHomeReturnSeconds": {
41375
+ capName: "ptz",
41376
+ capScope: "device",
41377
+ addonId: null,
41378
+ access: "view"
41379
+ },
41327
41380
  "ptz.getOptions": {
41328
41381
  capName: "ptz",
41329
41382
  capScope: "device",
@@ -41378,6 +41431,12 @@ Object.freeze({
41378
41431
  addonId: null,
41379
41432
  access: "create"
41380
41433
  },
41434
+ "ptz.setHomeReturnSeconds": {
41435
+ capName: "ptz",
41436
+ capScope: "device",
41437
+ addonId: null,
41438
+ access: "create"
41439
+ },
41381
41440
  "ptz.stop": {
41382
41441
  capName: "ptz",
41383
41442
  capScope: "device",
@@ -44541,6 +44600,11 @@ Object.freeze({
44541
44600
  form: "single",
44542
44601
  optional: false
44543
44602
  }],
44603
+ "ptz.captureHomeHere": [{
44604
+ name: "deviceId",
44605
+ form: "single",
44606
+ optional: false
44607
+ }],
44544
44608
  "ptz.continuousMove": [{
44545
44609
  name: "deviceId",
44546
44610
  form: "single",
@@ -44556,6 +44620,11 @@ Object.freeze({
44556
44620
  form: "single",
44557
44621
  optional: false
44558
44622
  }],
44623
+ "ptz.getHomeReturnSeconds": [{
44624
+ name: "deviceId",
44625
+ form: "single",
44626
+ optional: false
44627
+ }],
44559
44628
  "ptz.getOptions": [{
44560
44629
  name: "deviceId",
44561
44630
  form: "single",
@@ -44601,6 +44670,11 @@ Object.freeze({
44601
44670
  form: "single",
44602
44671
  optional: false
44603
44672
  }],
44673
+ "ptz.setHomeReturnSeconds": [{
44674
+ name: "deviceId",
44675
+ form: "single",
44676
+ optional: false
44677
+ }],
44604
44678
  "ptz.stop": [{
44605
44679
  name: "deviceId",
44606
44680
  form: "single",
@@ -49976,6 +50050,43 @@ function assertPresetWritable(presetId, action) {
49976
50050
  throw new Error(`Preset ${presetId} is a firmware function on this camera (${fn}) and cannot be ${action}` + (action === "overwritten" ? " — pick an unreserved id" : ""));
49977
50051
  }
49978
50052
  //#endregion
50053
+ //#region src/free-preset-slot.ts
50054
+ /**
50055
+ * The lowest preset id this camera will actually accept a write on.
50056
+ *
50057
+ * "Set home here" has to land somewhere, and on a camera whose home IS a preset
50058
+ * that means claiming a slot. Two things make the choice non-obvious:
50059
+ *
50060
+ * - the reserved FUNCTION band shares the preset namespace (34 "Back to
50061
+ * origin", 94 "Remote reboot", 95 "Call OSD menu" and friends), and writing
50062
+ * one answers HTTP 500;
50063
+ * - the operator's own presets must not be overwritten, ever. Losing
50064
+ * "Credenza" to make room for a home is a worse trade than refusing.
50065
+ *
50066
+ * So: the first id that is neither taken nor reserved, within the camera's
50067
+ * advertised budget. `null` when there is no room, and the caller must then
50068
+ * REFUSE rather than pick a victim.
50069
+ */
50070
+ function findFreePresetSlot(taken, maxPresets) {
50071
+ const used = new Set(taken.map((p) => p.id));
50072
+ const ceiling = maxPresets === void 0 ? 32 : Math.min(maxPresets, 300);
50073
+ for (let id = 1; id <= ceiling; id += 1) {
50074
+ const candidate = String(id);
50075
+ if (used.has(candidate)) continue;
50076
+ if (!isPresetWritable(candidate)) continue;
50077
+ return candidate;
50078
+ }
50079
+ return null;
50080
+ }
50081
+ /**
50082
+ * The name written on a preset claimed by "set home here".
50083
+ *
50084
+ * Named rather than left a literal so the slot is recognisable in the camera's
50085
+ * own web UI — an operator who finds an unexplained preset has no way to know
50086
+ * it is ours.
50087
+ */
50088
+ var HOME_PRESET_NAME = "CamStack Home";
50089
+ //#endregion
49979
50090
  //#region src/schema.ts
49980
50091
  /**
49981
50092
  * Single source of truth for the addon id used in event-source tags +
@@ -51751,6 +51862,27 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
51751
51862
  };
51752
51863
  return this.resolveHomePreset();
51753
51864
  },
51865
+ captureHomeHere: async ({ deviceId }) => {
51866
+ if (deviceId !== this.id) return;
51867
+ const [presets, caps] = await Promise.all([isapi.listPtzPresets(cameraNumber), isapi.getPtzCapabilities(cameraNumber)]);
51868
+ const slot = findFreePresetSlot(presets.map((p) => ({
51869
+ id: p.id,
51870
+ writable: isPresetWritable(p.id)
51871
+ })), caps?.maxPresetCount ?? void 0);
51872
+ if (slot === null) throw new Error("No free preset slot on this camera to store a home position — free one first");
51873
+ await isapi.savePtzPreset(cameraNumber, slot, HOME_PRESET_NAME);
51874
+ await this.config.setAll({ homePresetId: slot });
51875
+ this.ctx.logger.info("hikvision: home pinned to the current position", {
51876
+ tags: { deviceId: this.id },
51877
+ meta: { presetId: slot }
51878
+ });
51879
+ },
51880
+ getHomeReturnSeconds: async () => {
51881
+ return null;
51882
+ },
51883
+ setHomeReturnSeconds: async () => {
51884
+ throw new Error("Hikvision: the idle-return timer is not wired on this driver");
51885
+ },
51754
51886
  setHomePreset: async ({ deviceId, presetId }) => {
51755
51887
  if (deviceId !== this.id) return;
51756
51888
  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.120",
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",