@camstack/addon-provider-amcrest 0.2.111 → 0.2.113
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 +250 -15
- package/dist/addon.mjs +250 -15
- 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-B1y0Fo1U.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).
|
|
@@ -31059,14 +31059,81 @@ function summarisePrivacyAudio(profiles) {
|
|
|
31059
31059
|
if (profiles.length === 0) return null;
|
|
31060
31060
|
return profiles.some((p) => p.audioEnabled);
|
|
31061
31061
|
}
|
|
31062
|
+
/**
|
|
31063
|
+
* What a preset id DOES on this camera.
|
|
31064
|
+
*
|
|
31065
|
+
* On several PTZ firmwares the preset namespace is shared: low ids are stored
|
|
31066
|
+
* positions, a reserved band triggers camera FUNCTIONS. The dispensa camera
|
|
31067
|
+
* (Hikvision) returns its own reserved band in the preset list, named by the
|
|
31068
|
+
* firmware:
|
|
31069
|
+
*
|
|
31070
|
+
* 34 Back to origin · 39 Day mode · 40 Night mode · 46 Day/Night Auto Mode
|
|
31071
|
+
* 92 Set manual limits · 93 Save manual limits · 94 Remote reboot
|
|
31072
|
+
* 95 Call OSD menu
|
|
31073
|
+
*
|
|
31074
|
+
* We used to hand all of these to the UI as ordinary presets. Saving over one
|
|
31075
|
+
* is what produced `PUT /ISAPI/PTZCtrl/channels/1/presets/34 -> HTTP 500`: the
|
|
31076
|
+
* camera refusing to let "Back to origin" be overwritten, surfaced to the
|
|
31077
|
+
* operator as a transport failure.
|
|
31078
|
+
*
|
|
31079
|
+
* A function preset stays fully usable as a DESTINATION -- `goToPreset('34')`
|
|
31080
|
+
* is the single most useful thing that camera can do. Only `savePreset` and
|
|
31081
|
+
* `deletePreset` are gated.
|
|
31082
|
+
*/
|
|
31083
|
+
var PtzPresetFunctionSchema = _enum([
|
|
31084
|
+
"home",
|
|
31085
|
+
"day-mode",
|
|
31086
|
+
"night-mode",
|
|
31087
|
+
"day-night-auto",
|
|
31088
|
+
"set-limits",
|
|
31089
|
+
"save-limits",
|
|
31090
|
+
"reboot",
|
|
31091
|
+
"osd-menu"
|
|
31092
|
+
]);
|
|
31062
31093
|
var PtzPresetSchema = object({
|
|
31063
31094
|
id: string(),
|
|
31064
|
-
name: string()
|
|
31095
|
+
name: string(),
|
|
31096
|
+
/**
|
|
31097
|
+
* The firmware function this id performs, when the provider KNOWS the id is
|
|
31098
|
+
* reserved. `null` = an ordinary stored position, OR a reserved id this
|
|
31099
|
+
* provider does not recognise -- the two are told apart by `writable`.
|
|
31100
|
+
*/
|
|
31101
|
+
fn: PtzPresetFunctionSchema.nullable(),
|
|
31102
|
+
/**
|
|
31103
|
+
* Whether `savePreset` / `deletePreset` may target this id. A provider sets
|
|
31104
|
+
* it false only for a band it KNOWS is reserved; where the firmware is not
|
|
31105
|
+
* documented it stays true and a refusal comes from the camera, named.
|
|
31106
|
+
*/
|
|
31107
|
+
writable: boolean()
|
|
31065
31108
|
});
|
|
31109
|
+
/**
|
|
31110
|
+
* Where the head is pointing -- per axis, and `null` when the driver cannot
|
|
31111
|
+
* read it.
|
|
31112
|
+
*
|
|
31113
|
+
* It used to be three REQUIRED numbers, which left a provider that cannot read
|
|
31114
|
+
* position no way to say so. All four said `0`:
|
|
31115
|
+
*
|
|
31116
|
+
* reolink `return { pan: 0, tilt: 0, zoom: 0 }` "until a position-query
|
|
31117
|
+
* path lands upstream"
|
|
31118
|
+
* hikvision stub, "firmware-dependent and frequently absent"
|
|
31119
|
+
* amcrest stub, "no generic absolute-position read for this model family"
|
|
31120
|
+
* onvif throws
|
|
31121
|
+
*
|
|
31122
|
+
* So every consumer asking where a camera points was told "perfectly centred"
|
|
31123
|
+
* by four cameras that had never looked. That is D393 -- a measurement that
|
|
31124
|
+
* FAILED is `null`, never `0`, and the type says so all the way to the
|
|
31125
|
+
* decision -- the same shape as the unreadable `statfs` folded into "0 bytes of
|
|
31126
|
+
* headroom", which evacuated a healthy disk.
|
|
31127
|
+
*
|
|
31128
|
+
* D393 also says to check for a narrower structural TWIN of the result type.
|
|
31129
|
+
* There is one: `PtzStatusSchema` extends this, so the lie had already
|
|
31130
|
+
* propagated into `getStatus`, which is the surface `getPosition`'s own comment
|
|
31131
|
+
* tells callers to migrate to.
|
|
31132
|
+
*/
|
|
31066
31133
|
var PtzPositionSchema = object({
|
|
31067
|
-
pan: number(),
|
|
31068
|
-
tilt: number(),
|
|
31069
|
-
zoom: number()
|
|
31134
|
+
pan: number().nullable(),
|
|
31135
|
+
tilt: number().nullable(),
|
|
31136
|
+
zoom: number().nullable()
|
|
31070
31137
|
});
|
|
31071
31138
|
var PtzMoveCommandSchema = object({
|
|
31072
31139
|
pan: number().optional(),
|
|
@@ -31088,7 +31155,72 @@ var PtzOptionsSchema = object({
|
|
|
31088
31155
|
maxPresets: number().optional(),
|
|
31089
31156
|
/** Whether the camera exposes a controllable autofocus toggle
|
|
31090
31157
|
* (boolean `hasX` per the getOptions availability convention). */
|
|
31091
|
-
hasAutofocus: boolean()
|
|
31158
|
+
hasAutofocus: boolean(),
|
|
31159
|
+
/**
|
|
31160
|
+
* How many distinct speeds this camera's NATIVE scale offers.
|
|
31161
|
+
*
|
|
31162
|
+
* The cap's `speed` is normalized 0..1 and each provider maps it down --
|
|
31163
|
+
* Baichuan 1..63, Dahua 1..8, ISAPI's percentage -100..100. The normalized
|
|
31164
|
+
* value hides how coarse that really is: asking an amcrest for `0.37` is
|
|
31165
|
+
* meaningless, because it has eight steps and three of them round to the
|
|
31166
|
+
* same one. A loop that tunes its own gain has to know the granularity of
|
|
31167
|
+
* the knob it is turning.
|
|
31168
|
+
*
|
|
31169
|
+
* `null` = the driver does not know, or the scale is continuous (ONVIF takes
|
|
31170
|
+
* a float). Never a made-up number.
|
|
31171
|
+
*/
|
|
31172
|
+
speedSteps: number().int().positive().nullable(),
|
|
31173
|
+
/**
|
|
31174
|
+
* How long ONE `move` pulse runs on this driver, ms.
|
|
31175
|
+
*
|
|
31176
|
+
* `move` is a self-terminating burst everywhere, but every provider hardcoded
|
|
31177
|
+
* its own duration in private -- 200 reolink, 350 amcrest, 500 hikvision,
|
|
31178
|
+
* 1000 onvif -- so no caller could read it. It is the DENOMINATOR of "how far
|
|
31179
|
+
* did the head travel per pulse": without it a measured displacement has no
|
|
31180
|
+
* gain to be divided into.
|
|
31181
|
+
*
|
|
31182
|
+
* `null` = the driver cannot say.
|
|
31183
|
+
*/
|
|
31184
|
+
moveImpulseMs: number().int().positive().nullable()
|
|
31185
|
+
});
|
|
31186
|
+
/**
|
|
31187
|
+
* Which preset `goHome` goes to, and WHO decided.
|
|
31188
|
+
*
|
|
31189
|
+
* `goHome` used to be three vendor "conventions" written in three comments --
|
|
31190
|
+
* Reolink preset 0, Hikvision preset '1', Dahua preset 1 -- and on this fleet
|
|
31191
|
+
* not one of the three cameras honours its own:
|
|
31192
|
+
*
|
|
31193
|
+
* 592 Videocamera camera Daniel (reolink) preset 0 holds "stanza"
|
|
31194
|
+
* 1438 Videocamera dispensa (hikvision) preset 1 holds "Credenza"
|
|
31195
|
+
* 3836 Videocamera studio (amcrest) preset 1 holds "Preset1"
|
|
31196
|
+
*
|
|
31197
|
+
* So `goHome` meant "go wherever the operator happened to save in slot 0 or 1",
|
|
31198
|
+
* and two of the three swallowed the failure in a bare `catch`. For autotrack
|
|
31199
|
+
* this is the HOTTEST path -- it runs every time a subject is released -- so it
|
|
31200
|
+
* needs an answer that is true per camera and audible when it is missing.
|
|
31201
|
+
*
|
|
31202
|
+
* `source` is what makes it honest:
|
|
31203
|
+
* - `operator` -- picked in the UI, stored by the provider.
|
|
31204
|
+
* - `firmware` -- the camera itself named a preset `fn: 'home'` (Hikvision's
|
|
31205
|
+
* "Back to origin"), adopted as the default with nothing to configure.
|
|
31206
|
+
* - `native` -- the driver homes WITHOUT a preset at all. ONVIF does:
|
|
31207
|
+
* `goHome` is `ptzAbsoluteMove({x:0, y:0, zoom:0})`. `presetId` is null and
|
|
31208
|
+
* that is not a failure -- there is nothing to pick, and the UI must offer
|
|
31209
|
+
* no picker.
|
|
31210
|
+
* - `none` -- nothing is configured and the camera names nothing.
|
|
31211
|
+
* `goHome` REFUSES rather than moving the head somewhere arbitrary.
|
|
31212
|
+
*
|
|
31213
|
+
* `source === 'none'` is the refusal condition, NOT `presetId === null`: the
|
|
31214
|
+
* native case has no preset and homes perfectly well.
|
|
31215
|
+
*/
|
|
31216
|
+
var PtzHomePresetSchema = object({
|
|
31217
|
+
presetId: string().nullable(),
|
|
31218
|
+
source: _enum([
|
|
31219
|
+
"operator",
|
|
31220
|
+
"firmware",
|
|
31221
|
+
"native",
|
|
31222
|
+
"none"
|
|
31223
|
+
])
|
|
31092
31224
|
});
|
|
31093
31225
|
var ptzCapability = {
|
|
31094
31226
|
name: "ptz",
|
|
@@ -31129,7 +31261,25 @@ var ptzCapability = {
|
|
|
31129
31261
|
auth: "admin"
|
|
31130
31262
|
}),
|
|
31131
31263
|
getOptions: method(object({ deviceId: number() }), PtzOptionsSchema),
|
|
31264
|
+
/**
|
|
31265
|
+
* Move to the camera's configured home preset. THROWS when
|
|
31266
|
+
* `getHomePreset()` resolves to `none` -- a head that did not move must
|
|
31267
|
+
* never look like a head that went home.
|
|
31268
|
+
*/
|
|
31132
31269
|
goHome: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
|
|
31270
|
+
/** Which preset `goHome` targets, and who decided it. */
|
|
31271
|
+
getHomePreset: method(object({ deviceId: number() }), PtzHomePresetSchema),
|
|
31272
|
+
/**
|
|
31273
|
+
* Pin the home preset for this camera. `null` clears the operator's choice
|
|
31274
|
+
* and falls back to whatever the firmware names, or to `none`.
|
|
31275
|
+
*/
|
|
31276
|
+
setHomePreset: method(object({
|
|
31277
|
+
deviceId: number(),
|
|
31278
|
+
presetId: string().nullable()
|
|
31279
|
+
}), _void(), {
|
|
31280
|
+
kind: "mutation",
|
|
31281
|
+
auth: "admin"
|
|
31282
|
+
}),
|
|
31133
31283
|
/**
|
|
31134
31284
|
* Pull the current PTZ position. Redundant with the auto-injected
|
|
31135
31285
|
* `getStatus` method (see `status` below); kept for callers that
|
|
@@ -40803,6 +40953,12 @@ Object.freeze({
|
|
|
40803
40953
|
addonId: null,
|
|
40804
40954
|
access: "delete"
|
|
40805
40955
|
},
|
|
40956
|
+
"ptz.getHomePreset": {
|
|
40957
|
+
capName: "ptz",
|
|
40958
|
+
capScope: "device",
|
|
40959
|
+
addonId: null,
|
|
40960
|
+
access: "view"
|
|
40961
|
+
},
|
|
40806
40962
|
"ptz.getOptions": {
|
|
40807
40963
|
capName: "ptz",
|
|
40808
40964
|
capScope: "device",
|
|
@@ -40851,6 +41007,12 @@ Object.freeze({
|
|
|
40851
41007
|
addonId: null,
|
|
40852
41008
|
access: "create"
|
|
40853
41009
|
},
|
|
41010
|
+
"ptz.setHomePreset": {
|
|
41011
|
+
capName: "ptz",
|
|
41012
|
+
capScope: "device",
|
|
41013
|
+
addonId: null,
|
|
41014
|
+
access: "create"
|
|
41015
|
+
},
|
|
40854
41016
|
"ptz.stop": {
|
|
40855
41017
|
capName: "ptz",
|
|
40856
41018
|
capScope: "device",
|
|
@@ -44024,6 +44186,11 @@ Object.freeze({
|
|
|
44024
44186
|
form: "single",
|
|
44025
44187
|
optional: false
|
|
44026
44188
|
}],
|
|
44189
|
+
"ptz.getHomePreset": [{
|
|
44190
|
+
name: "deviceId",
|
|
44191
|
+
form: "single",
|
|
44192
|
+
optional: false
|
|
44193
|
+
}],
|
|
44027
44194
|
"ptz.getOptions": [{
|
|
44028
44195
|
name: "deviceId",
|
|
44029
44196
|
form: "single",
|
|
@@ -44064,6 +44231,11 @@ Object.freeze({
|
|
|
44064
44231
|
form: "single",
|
|
44065
44232
|
optional: false
|
|
44066
44233
|
}],
|
|
44234
|
+
"ptz.setHomePreset": [{
|
|
44235
|
+
name: "deviceId",
|
|
44236
|
+
form: "single",
|
|
44237
|
+
optional: false
|
|
44238
|
+
}],
|
|
44067
44239
|
"ptz.stop": [{
|
|
44068
44240
|
name: "deviceId",
|
|
44069
44241
|
form: "single",
|
|
@@ -46139,6 +46311,17 @@ var amcrestDeviceCacheSchema = object({
|
|
|
46139
46311
|
* carries only the technical connection knobs.
|
|
46140
46312
|
*/
|
|
46141
46313
|
var amcrestCameraSchema = object({
|
|
46314
|
+
/**
|
|
46315
|
+
* Which preset `ptz.goHome` targets on THIS camera.
|
|
46316
|
+
*
|
|
46317
|
+
* Empty = nothing configured, and `goHome` then REFUSES. It used to go to a
|
|
46318
|
+
* hardcoded slot on a vendor "convention" this fleet contradicts: reolink
|
|
46319
|
+
* went to preset 0, which on device 592 holds "stanza"; amcrest went to
|
|
46320
|
+
* preset 1, which on device 3836 holds "Preset1". Both swallowed the failure
|
|
46321
|
+
* in a bare catch, so a head that never moved was indistinguishable from a
|
|
46322
|
+
* head that went home.
|
|
46323
|
+
*/
|
|
46324
|
+
homePresetId: string().default("").describe("Preset id ptz.goHome targets (empty = goHome refuses)"),
|
|
46142
46325
|
host: string().describe("Camera IP / hostname"),
|
|
46143
46326
|
port: number().int().min(1).max(65535).default(80).describe("HTTP CGI port"),
|
|
46144
46327
|
https: boolean().default(false).describe("Use HTTPS for CGI"),
|
|
@@ -46246,6 +46429,24 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46246
46429
|
get channel() {
|
|
46247
46430
|
return this.config.get("channel") ?? 1;
|
|
46248
46431
|
}
|
|
46432
|
+
/**
|
|
46433
|
+
* Which preset `goHome` targets, and who decided it.
|
|
46434
|
+
*
|
|
46435
|
+
* Dahua names no home of its own that we have probed, so the operator's pick
|
|
46436
|
+
* is the only answer. With none, `none` -- and `goHome` refuses rather than
|
|
46437
|
+
* moving the head to whatever happens to sit in slot 1.
|
|
46438
|
+
*/
|
|
46439
|
+
async resolveHomePreset() {
|
|
46440
|
+
const chosen = this.config.get("homePresetId") ?? "";
|
|
46441
|
+
if (chosen === "") return {
|
|
46442
|
+
presetId: null,
|
|
46443
|
+
source: "none"
|
|
46444
|
+
};
|
|
46445
|
+
return {
|
|
46446
|
+
presetId: chosen,
|
|
46447
|
+
source: "operator"
|
|
46448
|
+
};
|
|
46449
|
+
}
|
|
46249
46450
|
ensureClient() {
|
|
46250
46451
|
if (this.client) return this.client;
|
|
46251
46452
|
this.client = this.buildClient();
|
|
@@ -46751,6 +46952,19 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46751
46952
|
* auto-stop fires. The `usePTZ` UI hook already issues its own paired
|
|
46752
46953
|
* `stop` after a client-side pulse window; this timer is a backstop so
|
|
46753
46954
|
* a lone `move` (no following `stop`) can't leave the head gliding. */
|
|
46955
|
+
/**
|
|
46956
|
+
* Dahua's native PTZ speed scale is 1..8; `resolvePtzCodes` maps the cap's
|
|
46957
|
+
* normalized 0..1 onto it. Eight steps is COARSE -- three neighbouring
|
|
46958
|
+
* normalized requests round to the same one -- which is exactly why the
|
|
46959
|
+
* granularity is reported through `getOptions` instead of being hidden
|
|
46960
|
+
* behind a 0..1 float.
|
|
46961
|
+
*/
|
|
46962
|
+
static DAHUA_SPEED_STEPS = 8;
|
|
46963
|
+
/**
|
|
46964
|
+
* How long one `ptz.move` pulse runs, ms. The DENOMINATOR of "how far did
|
|
46965
|
+
* the head travel per pulse", so it leaves the provider through
|
|
46966
|
+
* `getOptions` rather than staying a private literal.
|
|
46967
|
+
*/
|
|
46754
46968
|
static PTZ_PULSE_MS = 350;
|
|
46755
46969
|
/** Short per-call deadline (ms) for the PTZ start/stop CGI so a slow or
|
|
46756
46970
|
* blocked camera round-trip fails fast instead of exceeding the
|
|
@@ -46876,7 +47090,9 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46876
47090
|
if (deviceId !== this.id) return [];
|
|
46877
47091
|
return (await this.ensureClient().getPtzPresets(this.channel)).map((p) => ({
|
|
46878
47092
|
id: String(p.id),
|
|
46879
|
-
name: p.name
|
|
47093
|
+
name: p.name,
|
|
47094
|
+
fn: null,
|
|
47095
|
+
writable: true
|
|
46880
47096
|
}));
|
|
46881
47097
|
},
|
|
46882
47098
|
goToPreset: async ({ deviceId, presetId }) => {
|
|
@@ -46912,7 +47128,9 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46912
47128
|
hasTilt: false,
|
|
46913
47129
|
hasZoom: false,
|
|
46914
47130
|
supportsPresets: false,
|
|
46915
|
-
hasAutofocus: false
|
|
47131
|
+
hasAutofocus: false,
|
|
47132
|
+
speedSteps: AmcrestCamera.DAHUA_SPEED_STEPS,
|
|
47133
|
+
moveImpulseMs: AmcrestCamera.PTZ_PULSE_MS
|
|
46916
47134
|
};
|
|
46917
47135
|
if (deviceId !== this.id) return cold;
|
|
46918
47136
|
const caps = await this.ensureClient().getPtzCaps(this.channel);
|
|
@@ -46923,26 +47141,43 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46923
47141
|
hasZoom: caps.hasZoom,
|
|
46924
47142
|
supportsPresets: caps.supportsPreset,
|
|
46925
47143
|
hasAutofocus: false,
|
|
47144
|
+
speedSteps: AmcrestCamera.DAHUA_SPEED_STEPS,
|
|
47145
|
+
moveImpulseMs: AmcrestCamera.PTZ_PULSE_MS,
|
|
46926
47146
|
...caps.maxPresets !== null ? { maxPresets: caps.maxPresets } : {}
|
|
46927
47147
|
};
|
|
46928
47148
|
},
|
|
46929
47149
|
goHome: async ({ deviceId }) => {
|
|
46930
47150
|
if (deviceId !== this.id) return;
|
|
47151
|
+
const home = await this.resolveHomePreset();
|
|
47152
|
+
if (home.source === "none" || home.presetId === null) throw new Error("No home preset configured for this camera — set one in the PTZ settings");
|
|
47153
|
+
const id = Number.parseInt(home.presetId, 10);
|
|
47154
|
+
if (!Number.isFinite(id) || id < 1) throw new Error(`Configured home preset is not a Dahua preset id: ${home.presetId}`);
|
|
46931
47155
|
const client = this.ensureClient();
|
|
46932
|
-
this.enqueuePtz("goHome", () => client.ptzGotoPreset(this.channel,
|
|
47156
|
+
this.enqueuePtz("goHome", () => client.ptzGotoPreset(this.channel, id, AmcrestCamera.PTZ_CGI_TIMEOUT_MS));
|
|
47157
|
+
},
|
|
47158
|
+
getHomePreset: async ({ deviceId }) => {
|
|
47159
|
+
if (deviceId !== this.id) return {
|
|
47160
|
+
presetId: null,
|
|
47161
|
+
source: "none"
|
|
47162
|
+
};
|
|
47163
|
+
return this.resolveHomePreset();
|
|
47164
|
+
},
|
|
47165
|
+
setHomePreset: async ({ deviceId, presetId }) => {
|
|
47166
|
+
if (deviceId !== this.id) return;
|
|
47167
|
+
await this.config.setAll({ homePresetId: presetId ?? "" });
|
|
46933
47168
|
},
|
|
46934
47169
|
getPosition: async () => {
|
|
46935
47170
|
return {
|
|
46936
|
-
pan:
|
|
46937
|
-
tilt:
|
|
46938
|
-
zoom:
|
|
47171
|
+
pan: null,
|
|
47172
|
+
tilt: null,
|
|
47173
|
+
zoom: null
|
|
46939
47174
|
};
|
|
46940
47175
|
},
|
|
46941
47176
|
getStatus: async () => {
|
|
46942
47177
|
return {
|
|
46943
|
-
pan:
|
|
46944
|
-
tilt:
|
|
46945
|
-
zoom:
|
|
47178
|
+
pan: null,
|
|
47179
|
+
tilt: null,
|
|
47180
|
+
zoom: null,
|
|
46946
47181
|
autofocus: false
|
|
46947
47182
|
};
|
|
46948
47183
|
},
|
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-B1y0Fo1U.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).
|
|
@@ -31060,14 +31060,81 @@ function summarisePrivacyAudio(profiles) {
|
|
|
31060
31060
|
if (profiles.length === 0) return null;
|
|
31061
31061
|
return profiles.some((p) => p.audioEnabled);
|
|
31062
31062
|
}
|
|
31063
|
+
/**
|
|
31064
|
+
* What a preset id DOES on this camera.
|
|
31065
|
+
*
|
|
31066
|
+
* On several PTZ firmwares the preset namespace is shared: low ids are stored
|
|
31067
|
+
* positions, a reserved band triggers camera FUNCTIONS. The dispensa camera
|
|
31068
|
+
* (Hikvision) returns its own reserved band in the preset list, named by the
|
|
31069
|
+
* firmware:
|
|
31070
|
+
*
|
|
31071
|
+
* 34 Back to origin · 39 Day mode · 40 Night mode · 46 Day/Night Auto Mode
|
|
31072
|
+
* 92 Set manual limits · 93 Save manual limits · 94 Remote reboot
|
|
31073
|
+
* 95 Call OSD menu
|
|
31074
|
+
*
|
|
31075
|
+
* We used to hand all of these to the UI as ordinary presets. Saving over one
|
|
31076
|
+
* is what produced `PUT /ISAPI/PTZCtrl/channels/1/presets/34 -> HTTP 500`: the
|
|
31077
|
+
* camera refusing to let "Back to origin" be overwritten, surfaced to the
|
|
31078
|
+
* operator as a transport failure.
|
|
31079
|
+
*
|
|
31080
|
+
* A function preset stays fully usable as a DESTINATION -- `goToPreset('34')`
|
|
31081
|
+
* is the single most useful thing that camera can do. Only `savePreset` and
|
|
31082
|
+
* `deletePreset` are gated.
|
|
31083
|
+
*/
|
|
31084
|
+
var PtzPresetFunctionSchema = _enum([
|
|
31085
|
+
"home",
|
|
31086
|
+
"day-mode",
|
|
31087
|
+
"night-mode",
|
|
31088
|
+
"day-night-auto",
|
|
31089
|
+
"set-limits",
|
|
31090
|
+
"save-limits",
|
|
31091
|
+
"reboot",
|
|
31092
|
+
"osd-menu"
|
|
31093
|
+
]);
|
|
31063
31094
|
var PtzPresetSchema = object({
|
|
31064
31095
|
id: string(),
|
|
31065
|
-
name: string()
|
|
31096
|
+
name: string(),
|
|
31097
|
+
/**
|
|
31098
|
+
* The firmware function this id performs, when the provider KNOWS the id is
|
|
31099
|
+
* reserved. `null` = an ordinary stored position, OR a reserved id this
|
|
31100
|
+
* provider does not recognise -- the two are told apart by `writable`.
|
|
31101
|
+
*/
|
|
31102
|
+
fn: PtzPresetFunctionSchema.nullable(),
|
|
31103
|
+
/**
|
|
31104
|
+
* Whether `savePreset` / `deletePreset` may target this id. A provider sets
|
|
31105
|
+
* it false only for a band it KNOWS is reserved; where the firmware is not
|
|
31106
|
+
* documented it stays true and a refusal comes from the camera, named.
|
|
31107
|
+
*/
|
|
31108
|
+
writable: boolean()
|
|
31066
31109
|
});
|
|
31110
|
+
/**
|
|
31111
|
+
* Where the head is pointing -- per axis, and `null` when the driver cannot
|
|
31112
|
+
* read it.
|
|
31113
|
+
*
|
|
31114
|
+
* It used to be three REQUIRED numbers, which left a provider that cannot read
|
|
31115
|
+
* position no way to say so. All four said `0`:
|
|
31116
|
+
*
|
|
31117
|
+
* reolink `return { pan: 0, tilt: 0, zoom: 0 }` "until a position-query
|
|
31118
|
+
* path lands upstream"
|
|
31119
|
+
* hikvision stub, "firmware-dependent and frequently absent"
|
|
31120
|
+
* amcrest stub, "no generic absolute-position read for this model family"
|
|
31121
|
+
* onvif throws
|
|
31122
|
+
*
|
|
31123
|
+
* So every consumer asking where a camera points was told "perfectly centred"
|
|
31124
|
+
* by four cameras that had never looked. That is D393 -- a measurement that
|
|
31125
|
+
* FAILED is `null`, never `0`, and the type says so all the way to the
|
|
31126
|
+
* decision -- the same shape as the unreadable `statfs` folded into "0 bytes of
|
|
31127
|
+
* headroom", which evacuated a healthy disk.
|
|
31128
|
+
*
|
|
31129
|
+
* D393 also says to check for a narrower structural TWIN of the result type.
|
|
31130
|
+
* There is one: `PtzStatusSchema` extends this, so the lie had already
|
|
31131
|
+
* propagated into `getStatus`, which is the surface `getPosition`'s own comment
|
|
31132
|
+
* tells callers to migrate to.
|
|
31133
|
+
*/
|
|
31067
31134
|
var PtzPositionSchema = object({
|
|
31068
|
-
pan: number(),
|
|
31069
|
-
tilt: number(),
|
|
31070
|
-
zoom: number()
|
|
31135
|
+
pan: number().nullable(),
|
|
31136
|
+
tilt: number().nullable(),
|
|
31137
|
+
zoom: number().nullable()
|
|
31071
31138
|
});
|
|
31072
31139
|
var PtzMoveCommandSchema = object({
|
|
31073
31140
|
pan: number().optional(),
|
|
@@ -31089,7 +31156,72 @@ var PtzOptionsSchema = object({
|
|
|
31089
31156
|
maxPresets: number().optional(),
|
|
31090
31157
|
/** Whether the camera exposes a controllable autofocus toggle
|
|
31091
31158
|
* (boolean `hasX` per the getOptions availability convention). */
|
|
31092
|
-
hasAutofocus: boolean()
|
|
31159
|
+
hasAutofocus: boolean(),
|
|
31160
|
+
/**
|
|
31161
|
+
* How many distinct speeds this camera's NATIVE scale offers.
|
|
31162
|
+
*
|
|
31163
|
+
* The cap's `speed` is normalized 0..1 and each provider maps it down --
|
|
31164
|
+
* Baichuan 1..63, Dahua 1..8, ISAPI's percentage -100..100. The normalized
|
|
31165
|
+
* value hides how coarse that really is: asking an amcrest for `0.37` is
|
|
31166
|
+
* meaningless, because it has eight steps and three of them round to the
|
|
31167
|
+
* same one. A loop that tunes its own gain has to know the granularity of
|
|
31168
|
+
* the knob it is turning.
|
|
31169
|
+
*
|
|
31170
|
+
* `null` = the driver does not know, or the scale is continuous (ONVIF takes
|
|
31171
|
+
* a float). Never a made-up number.
|
|
31172
|
+
*/
|
|
31173
|
+
speedSteps: number().int().positive().nullable(),
|
|
31174
|
+
/**
|
|
31175
|
+
* How long ONE `move` pulse runs on this driver, ms.
|
|
31176
|
+
*
|
|
31177
|
+
* `move` is a self-terminating burst everywhere, but every provider hardcoded
|
|
31178
|
+
* its own duration in private -- 200 reolink, 350 amcrest, 500 hikvision,
|
|
31179
|
+
* 1000 onvif -- so no caller could read it. It is the DENOMINATOR of "how far
|
|
31180
|
+
* did the head travel per pulse": without it a measured displacement has no
|
|
31181
|
+
* gain to be divided into.
|
|
31182
|
+
*
|
|
31183
|
+
* `null` = the driver cannot say.
|
|
31184
|
+
*/
|
|
31185
|
+
moveImpulseMs: number().int().positive().nullable()
|
|
31186
|
+
});
|
|
31187
|
+
/**
|
|
31188
|
+
* Which preset `goHome` goes to, and WHO decided.
|
|
31189
|
+
*
|
|
31190
|
+
* `goHome` used to be three vendor "conventions" written in three comments --
|
|
31191
|
+
* Reolink preset 0, Hikvision preset '1', Dahua preset 1 -- and on this fleet
|
|
31192
|
+
* not one of the three cameras honours its own:
|
|
31193
|
+
*
|
|
31194
|
+
* 592 Videocamera camera Daniel (reolink) preset 0 holds "stanza"
|
|
31195
|
+
* 1438 Videocamera dispensa (hikvision) preset 1 holds "Credenza"
|
|
31196
|
+
* 3836 Videocamera studio (amcrest) preset 1 holds "Preset1"
|
|
31197
|
+
*
|
|
31198
|
+
* So `goHome` meant "go wherever the operator happened to save in slot 0 or 1",
|
|
31199
|
+
* and two of the three swallowed the failure in a bare `catch`. For autotrack
|
|
31200
|
+
* this is the HOTTEST path -- it runs every time a subject is released -- so it
|
|
31201
|
+
* needs an answer that is true per camera and audible when it is missing.
|
|
31202
|
+
*
|
|
31203
|
+
* `source` is what makes it honest:
|
|
31204
|
+
* - `operator` -- picked in the UI, stored by the provider.
|
|
31205
|
+
* - `firmware` -- the camera itself named a preset `fn: 'home'` (Hikvision's
|
|
31206
|
+
* "Back to origin"), adopted as the default with nothing to configure.
|
|
31207
|
+
* - `native` -- the driver homes WITHOUT a preset at all. ONVIF does:
|
|
31208
|
+
* `goHome` is `ptzAbsoluteMove({x:0, y:0, zoom:0})`. `presetId` is null and
|
|
31209
|
+
* that is not a failure -- there is nothing to pick, and the UI must offer
|
|
31210
|
+
* no picker.
|
|
31211
|
+
* - `none` -- nothing is configured and the camera names nothing.
|
|
31212
|
+
* `goHome` REFUSES rather than moving the head somewhere arbitrary.
|
|
31213
|
+
*
|
|
31214
|
+
* `source === 'none'` is the refusal condition, NOT `presetId === null`: the
|
|
31215
|
+
* native case has no preset and homes perfectly well.
|
|
31216
|
+
*/
|
|
31217
|
+
var PtzHomePresetSchema = object({
|
|
31218
|
+
presetId: string().nullable(),
|
|
31219
|
+
source: _enum([
|
|
31220
|
+
"operator",
|
|
31221
|
+
"firmware",
|
|
31222
|
+
"native",
|
|
31223
|
+
"none"
|
|
31224
|
+
])
|
|
31093
31225
|
});
|
|
31094
31226
|
var ptzCapability = {
|
|
31095
31227
|
name: "ptz",
|
|
@@ -31130,7 +31262,25 @@ var ptzCapability = {
|
|
|
31130
31262
|
auth: "admin"
|
|
31131
31263
|
}),
|
|
31132
31264
|
getOptions: method(object({ deviceId: number() }), PtzOptionsSchema),
|
|
31265
|
+
/**
|
|
31266
|
+
* Move to the camera's configured home preset. THROWS when
|
|
31267
|
+
* `getHomePreset()` resolves to `none` -- a head that did not move must
|
|
31268
|
+
* never look like a head that went home.
|
|
31269
|
+
*/
|
|
31133
31270
|
goHome: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
|
|
31271
|
+
/** Which preset `goHome` targets, and who decided it. */
|
|
31272
|
+
getHomePreset: method(object({ deviceId: number() }), PtzHomePresetSchema),
|
|
31273
|
+
/**
|
|
31274
|
+
* Pin the home preset for this camera. `null` clears the operator's choice
|
|
31275
|
+
* and falls back to whatever the firmware names, or to `none`.
|
|
31276
|
+
*/
|
|
31277
|
+
setHomePreset: method(object({
|
|
31278
|
+
deviceId: number(),
|
|
31279
|
+
presetId: string().nullable()
|
|
31280
|
+
}), _void(), {
|
|
31281
|
+
kind: "mutation",
|
|
31282
|
+
auth: "admin"
|
|
31283
|
+
}),
|
|
31134
31284
|
/**
|
|
31135
31285
|
* Pull the current PTZ position. Redundant with the auto-injected
|
|
31136
31286
|
* `getStatus` method (see `status` below); kept for callers that
|
|
@@ -40804,6 +40954,12 @@ Object.freeze({
|
|
|
40804
40954
|
addonId: null,
|
|
40805
40955
|
access: "delete"
|
|
40806
40956
|
},
|
|
40957
|
+
"ptz.getHomePreset": {
|
|
40958
|
+
capName: "ptz",
|
|
40959
|
+
capScope: "device",
|
|
40960
|
+
addonId: null,
|
|
40961
|
+
access: "view"
|
|
40962
|
+
},
|
|
40807
40963
|
"ptz.getOptions": {
|
|
40808
40964
|
capName: "ptz",
|
|
40809
40965
|
capScope: "device",
|
|
@@ -40852,6 +41008,12 @@ Object.freeze({
|
|
|
40852
41008
|
addonId: null,
|
|
40853
41009
|
access: "create"
|
|
40854
41010
|
},
|
|
41011
|
+
"ptz.setHomePreset": {
|
|
41012
|
+
capName: "ptz",
|
|
41013
|
+
capScope: "device",
|
|
41014
|
+
addonId: null,
|
|
41015
|
+
access: "create"
|
|
41016
|
+
},
|
|
40855
41017
|
"ptz.stop": {
|
|
40856
41018
|
capName: "ptz",
|
|
40857
41019
|
capScope: "device",
|
|
@@ -44025,6 +44187,11 @@ Object.freeze({
|
|
|
44025
44187
|
form: "single",
|
|
44026
44188
|
optional: false
|
|
44027
44189
|
}],
|
|
44190
|
+
"ptz.getHomePreset": [{
|
|
44191
|
+
name: "deviceId",
|
|
44192
|
+
form: "single",
|
|
44193
|
+
optional: false
|
|
44194
|
+
}],
|
|
44028
44195
|
"ptz.getOptions": [{
|
|
44029
44196
|
name: "deviceId",
|
|
44030
44197
|
form: "single",
|
|
@@ -44065,6 +44232,11 @@ Object.freeze({
|
|
|
44065
44232
|
form: "single",
|
|
44066
44233
|
optional: false
|
|
44067
44234
|
}],
|
|
44235
|
+
"ptz.setHomePreset": [{
|
|
44236
|
+
name: "deviceId",
|
|
44237
|
+
form: "single",
|
|
44238
|
+
optional: false
|
|
44239
|
+
}],
|
|
44068
44240
|
"ptz.stop": [{
|
|
44069
44241
|
name: "deviceId",
|
|
44070
44242
|
form: "single",
|
|
@@ -46140,6 +46312,17 @@ var amcrestDeviceCacheSchema = object({
|
|
|
46140
46312
|
* carries only the technical connection knobs.
|
|
46141
46313
|
*/
|
|
46142
46314
|
var amcrestCameraSchema = object({
|
|
46315
|
+
/**
|
|
46316
|
+
* Which preset `ptz.goHome` targets on THIS camera.
|
|
46317
|
+
*
|
|
46318
|
+
* Empty = nothing configured, and `goHome` then REFUSES. It used to go to a
|
|
46319
|
+
* hardcoded slot on a vendor "convention" this fleet contradicts: reolink
|
|
46320
|
+
* went to preset 0, which on device 592 holds "stanza"; amcrest went to
|
|
46321
|
+
* preset 1, which on device 3836 holds "Preset1". Both swallowed the failure
|
|
46322
|
+
* in a bare catch, so a head that never moved was indistinguishable from a
|
|
46323
|
+
* head that went home.
|
|
46324
|
+
*/
|
|
46325
|
+
homePresetId: string().default("").describe("Preset id ptz.goHome targets (empty = goHome refuses)"),
|
|
46143
46326
|
host: string().describe("Camera IP / hostname"),
|
|
46144
46327
|
port: number().int().min(1).max(65535).default(80).describe("HTTP CGI port"),
|
|
46145
46328
|
https: boolean().default(false).describe("Use HTTPS for CGI"),
|
|
@@ -46247,6 +46430,24 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46247
46430
|
get channel() {
|
|
46248
46431
|
return this.config.get("channel") ?? 1;
|
|
46249
46432
|
}
|
|
46433
|
+
/**
|
|
46434
|
+
* Which preset `goHome` targets, and who decided it.
|
|
46435
|
+
*
|
|
46436
|
+
* Dahua names no home of its own that we have probed, so the operator's pick
|
|
46437
|
+
* is the only answer. With none, `none` -- and `goHome` refuses rather than
|
|
46438
|
+
* moving the head to whatever happens to sit in slot 1.
|
|
46439
|
+
*/
|
|
46440
|
+
async resolveHomePreset() {
|
|
46441
|
+
const chosen = this.config.get("homePresetId") ?? "";
|
|
46442
|
+
if (chosen === "") return {
|
|
46443
|
+
presetId: null,
|
|
46444
|
+
source: "none"
|
|
46445
|
+
};
|
|
46446
|
+
return {
|
|
46447
|
+
presetId: chosen,
|
|
46448
|
+
source: "operator"
|
|
46449
|
+
};
|
|
46450
|
+
}
|
|
46250
46451
|
ensureClient() {
|
|
46251
46452
|
if (this.client) return this.client;
|
|
46252
46453
|
this.client = this.buildClient();
|
|
@@ -46752,6 +46953,19 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46752
46953
|
* auto-stop fires. The `usePTZ` UI hook already issues its own paired
|
|
46753
46954
|
* `stop` after a client-side pulse window; this timer is a backstop so
|
|
46754
46955
|
* a lone `move` (no following `stop`) can't leave the head gliding. */
|
|
46956
|
+
/**
|
|
46957
|
+
* Dahua's native PTZ speed scale is 1..8; `resolvePtzCodes` maps the cap's
|
|
46958
|
+
* normalized 0..1 onto it. Eight steps is COARSE -- three neighbouring
|
|
46959
|
+
* normalized requests round to the same one -- which is exactly why the
|
|
46960
|
+
* granularity is reported through `getOptions` instead of being hidden
|
|
46961
|
+
* behind a 0..1 float.
|
|
46962
|
+
*/
|
|
46963
|
+
static DAHUA_SPEED_STEPS = 8;
|
|
46964
|
+
/**
|
|
46965
|
+
* How long one `ptz.move` pulse runs, ms. The DENOMINATOR of "how far did
|
|
46966
|
+
* the head travel per pulse", so it leaves the provider through
|
|
46967
|
+
* `getOptions` rather than staying a private literal.
|
|
46968
|
+
*/
|
|
46755
46969
|
static PTZ_PULSE_MS = 350;
|
|
46756
46970
|
/** Short per-call deadline (ms) for the PTZ start/stop CGI so a slow or
|
|
46757
46971
|
* blocked camera round-trip fails fast instead of exceeding the
|
|
@@ -46877,7 +47091,9 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46877
47091
|
if (deviceId !== this.id) return [];
|
|
46878
47092
|
return (await this.ensureClient().getPtzPresets(this.channel)).map((p) => ({
|
|
46879
47093
|
id: String(p.id),
|
|
46880
|
-
name: p.name
|
|
47094
|
+
name: p.name,
|
|
47095
|
+
fn: null,
|
|
47096
|
+
writable: true
|
|
46881
47097
|
}));
|
|
46882
47098
|
},
|
|
46883
47099
|
goToPreset: async ({ deviceId, presetId }) => {
|
|
@@ -46913,7 +47129,9 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46913
47129
|
hasTilt: false,
|
|
46914
47130
|
hasZoom: false,
|
|
46915
47131
|
supportsPresets: false,
|
|
46916
|
-
hasAutofocus: false
|
|
47132
|
+
hasAutofocus: false,
|
|
47133
|
+
speedSteps: AmcrestCamera.DAHUA_SPEED_STEPS,
|
|
47134
|
+
moveImpulseMs: AmcrestCamera.PTZ_PULSE_MS
|
|
46917
47135
|
};
|
|
46918
47136
|
if (deviceId !== this.id) return cold;
|
|
46919
47137
|
const caps = await this.ensureClient().getPtzCaps(this.channel);
|
|
@@ -46924,26 +47142,43 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
46924
47142
|
hasZoom: caps.hasZoom,
|
|
46925
47143
|
supportsPresets: caps.supportsPreset,
|
|
46926
47144
|
hasAutofocus: false,
|
|
47145
|
+
speedSteps: AmcrestCamera.DAHUA_SPEED_STEPS,
|
|
47146
|
+
moveImpulseMs: AmcrestCamera.PTZ_PULSE_MS,
|
|
46927
47147
|
...caps.maxPresets !== null ? { maxPresets: caps.maxPresets } : {}
|
|
46928
47148
|
};
|
|
46929
47149
|
},
|
|
46930
47150
|
goHome: async ({ deviceId }) => {
|
|
46931
47151
|
if (deviceId !== this.id) return;
|
|
47152
|
+
const home = await this.resolveHomePreset();
|
|
47153
|
+
if (home.source === "none" || home.presetId === null) throw new Error("No home preset configured for this camera — set one in the PTZ settings");
|
|
47154
|
+
const id = Number.parseInt(home.presetId, 10);
|
|
47155
|
+
if (!Number.isFinite(id) || id < 1) throw new Error(`Configured home preset is not a Dahua preset id: ${home.presetId}`);
|
|
46932
47156
|
const client = this.ensureClient();
|
|
46933
|
-
this.enqueuePtz("goHome", () => client.ptzGotoPreset(this.channel,
|
|
47157
|
+
this.enqueuePtz("goHome", () => client.ptzGotoPreset(this.channel, id, AmcrestCamera.PTZ_CGI_TIMEOUT_MS));
|
|
47158
|
+
},
|
|
47159
|
+
getHomePreset: async ({ deviceId }) => {
|
|
47160
|
+
if (deviceId !== this.id) return {
|
|
47161
|
+
presetId: null,
|
|
47162
|
+
source: "none"
|
|
47163
|
+
};
|
|
47164
|
+
return this.resolveHomePreset();
|
|
47165
|
+
},
|
|
47166
|
+
setHomePreset: async ({ deviceId, presetId }) => {
|
|
47167
|
+
if (deviceId !== this.id) return;
|
|
47168
|
+
await this.config.setAll({ homePresetId: presetId ?? "" });
|
|
46934
47169
|
},
|
|
46935
47170
|
getPosition: async () => {
|
|
46936
47171
|
return {
|
|
46937
|
-
pan:
|
|
46938
|
-
tilt:
|
|
46939
|
-
zoom:
|
|
47172
|
+
pan: null,
|
|
47173
|
+
tilt: null,
|
|
47174
|
+
zoom: null
|
|
46940
47175
|
};
|
|
46941
47176
|
},
|
|
46942
47177
|
getStatus: async () => {
|
|
46943
47178
|
return {
|
|
46944
|
-
pan:
|
|
46945
|
-
tilt:
|
|
46946
|
-
zoom:
|
|
47179
|
+
pan: null,
|
|
47180
|
+
tilt: null,
|
|
47181
|
+
zoom: null,
|
|
46947
47182
|
autofocus: false
|
|
46948
47183
|
};
|
|
46949
47184
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-amcrest",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.113",
|
|
4
4
|
"description": "Amcrest/Dahua camera device provider addon for CamStack — Dahua CGI over HTTP(S) with digest auth (snapshot, RTSP catalog, PTZ, image/day-night config)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|