@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 +160 -1
- package/dist/addon.mjs +160 -1
- package/package.json +1 -1
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-
|
|
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-
|
|
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.
|
|
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",
|