@camstack/addon-provider-amcrest 0.1.4 → 0.1.6
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 +304 -233
- package/dist/addon.mjs +304 -233
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -4633,7 +4633,7 @@ function _instanceof(cls, params = {}) {
|
|
|
4633
4633
|
return inst;
|
|
4634
4634
|
}
|
|
4635
4635
|
//#endregion
|
|
4636
|
-
//#region ../types/dist/sleep-
|
|
4636
|
+
//#region ../types/dist/sleep-Cc14_yxc.mjs
|
|
4637
4637
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4638
4638
|
EventCategory["SystemBoot"] = "system.boot";
|
|
4639
4639
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -4819,6 +4819,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
4819
4819
|
*/
|
|
4820
4820
|
EventCategory["PipelineCameraUpdated"] = "pipeline.camera-updated";
|
|
4821
4821
|
/**
|
|
4822
|
+
* The cluster camera-source OWNER changed (`clusterRoles.ingestNode`).
|
|
4823
|
+
* Emitted by addon-pipeline-orchestrator whenever it (re)derives node
|
|
4824
|
+
* capabilities — at boot, on agent online/offline, and on an ingest-node
|
|
4825
|
+
* flip. Carries the resolved `ownerNodeId`. The stream-broker consumes it to
|
|
4826
|
+
* keep its ingest-owner-gate decision current WITHOUT a per-`ensureBroker`
|
|
4827
|
+
* cross-process `getIngestOwner` query (push the authority's decision instead
|
|
4828
|
+
* of polling it on the hot path). Idempotent state — re-emitted on every
|
|
4829
|
+
* topology change, so a dropped event self-heals on the next one (plus the
|
|
4830
|
+
* broker's long backstop reconcile query).
|
|
4831
|
+
*/
|
|
4832
|
+
EventCategory["PipelineIngestOwnerChanged"] = "pipeline.ingest-owner-changed";
|
|
4833
|
+
/**
|
|
4822
4834
|
* Periodic snapshot of per-node pipeline-runner load
|
|
4823
4835
|
* (`RunnerLocalLoad`). Emitted ~1Hz by every runner so UI dashboards
|
|
4824
4836
|
* subscribe instead of polling `pipelineRunner.getLocalLoad`.
|
|
@@ -6902,6 +6914,36 @@ var ModelFormatsSchema = object({
|
|
|
6902
6914
|
tflite: ModelFormatEntrySchema.optional(),
|
|
6903
6915
|
pt: ModelFormatEntrySchema.optional()
|
|
6904
6916
|
});
|
|
6917
|
+
/**
|
|
6918
|
+
* Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
|
|
6919
|
+
* the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
|
|
6920
|
+
* grouped Family→Tier→Variant picker renders identically in the config UI and
|
|
6921
|
+
* in the pipeline/device steppers. The flat `id` stays the source of truth for
|
|
6922
|
+
* resolution/download/persistence; this is a presentation overlay resolved back
|
|
6923
|
+
* to an `id`.
|
|
6924
|
+
*/
|
|
6925
|
+
var ModelVariantGroupSchema = object({
|
|
6926
|
+
/** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
|
|
6927
|
+
family: string(),
|
|
6928
|
+
/** Size within the family, e.g. `n` | `s` | `m` | `l`. */
|
|
6929
|
+
tier: string(),
|
|
6930
|
+
/** Quantization axis. Omit ⇒ the fp32 base build. */
|
|
6931
|
+
precision: _enum(["fp32", "int8"]).optional(),
|
|
6932
|
+
/**
|
|
6933
|
+
* Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
|
|
6934
|
+
* latency-optimized export (e.g. ReLU-activation variant) — the slot the
|
|
6935
|
+
* future performance variants plug into.
|
|
6936
|
+
*/
|
|
6937
|
+
optimization: _enum(["standard", "fast"]).optional(),
|
|
6938
|
+
/**
|
|
6939
|
+
* Input-resolution axis (square input side, px). Omit ⇒ the family's native
|
|
6940
|
+
* resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
|
|
6941
|
+
* cheap latency lever — especially on Apple ANE and the Intel N100 — at a
|
|
6942
|
+
* small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
|
|
6943
|
+
* the group so the selector can offer it as a variant axis.
|
|
6944
|
+
*/
|
|
6945
|
+
resolution: number().int().positive().optional()
|
|
6946
|
+
});
|
|
6905
6947
|
var ModelCatalogEntrySchema = object({
|
|
6906
6948
|
id: string(),
|
|
6907
6949
|
name: string(),
|
|
@@ -6931,7 +6973,43 @@ var ModelCatalogEntrySchema = object({
|
|
|
6931
6973
|
* Auxiliary files required at runtime (labels JSON, charset dict, etc.).
|
|
6932
6974
|
* Downloaded into the same modelsDir alongside the model file.
|
|
6933
6975
|
*/
|
|
6934
|
-
extraFiles: array(ModelExtraFileSchema).readonly().optional()
|
|
6976
|
+
extraFiles: array(ModelExtraFileSchema).readonly().optional(),
|
|
6977
|
+
/**
|
|
6978
|
+
* LEGACY entry — retained in the catalog so a persisted operator selection
|
|
6979
|
+
* still RESOLVES (and can be re-activated), but hidden from the selectable
|
|
6980
|
+
* model list and excluded from the auto format-default pick. Set on the
|
|
6981
|
+
* superseded / consolidated models (older lineages, redundant fp16 IRs) so
|
|
6982
|
+
* the active lineup stays the coherent curated ladder without deleting a
|
|
6983
|
+
* model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
|
|
6984
|
+
* an explicit legacy id that has a build for the node's format.
|
|
6985
|
+
*/
|
|
6986
|
+
legacy: boolean().optional(),
|
|
6987
|
+
/**
|
|
6988
|
+
* Measured quality/latency metadata — populated from the benchmark addon on
|
|
6989
|
+
* the real node classes. Absent = not yet measured (most entries today; the
|
|
6990
|
+
* catalog historically carried only `sizeMB`, a poor cross-architecture
|
|
6991
|
+
* speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
|
|
6992
|
+
*/
|
|
6993
|
+
metrics: object({
|
|
6994
|
+
map50: number().optional(),
|
|
6995
|
+
p95LatencyMs: record(string(), number()).optional()
|
|
6996
|
+
}).optional(),
|
|
6997
|
+
/**
|
|
6998
|
+
* SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
|
|
6999
|
+
* YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
|
|
7000
|
+
* the retraining addon and any future commercial distribution.
|
|
7001
|
+
*/
|
|
7002
|
+
license: string().optional(),
|
|
7003
|
+
/**
|
|
7004
|
+
* Variant-selector grouping. The UI groups models by `family` + `tier` and
|
|
7005
|
+
* offers `precision` / `optimization` as variant axes WITHIN a tier — so all
|
|
7006
|
+
* of a family's sizes and quantizations collapse into one grouped picker
|
|
7007
|
+
* instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
|
|
7008
|
+
* (legacy / custom models) — never shown in the grouped selector. The flat
|
|
7009
|
+
* `id` stays the source of truth for resolution/download/persistence; grouping
|
|
7010
|
+
* is a presentation overlay resolved back to an `id`.
|
|
7011
|
+
*/
|
|
7012
|
+
group: ModelVariantGroupSchema.optional()
|
|
6935
7013
|
});
|
|
6936
7014
|
var ConvertTargetSchema = discriminatedUnion("format", [object({
|
|
6937
7015
|
format: literal("openvino"),
|
|
@@ -6992,8 +7070,8 @@ var RecordingModeSchema = _enum([
|
|
|
6992
7070
|
"onAudioThreshold"
|
|
6993
7071
|
]);
|
|
6994
7072
|
/**
|
|
6995
|
-
* First-class, authoritative per-camera storage mode — the
|
|
6996
|
-
* reads directly (never inferred from `rules`):
|
|
7073
|
+
* First-class, authoritative per-camera storage mode — the explicit choice the
|
|
7074
|
+
* UI reads directly (never inferred from `rules`):
|
|
6997
7075
|
* - `off` — not recording.
|
|
6998
7076
|
* - `events` — record only around triggers (motion / audio threshold),
|
|
6999
7077
|
* with pre/post-buffer.
|
|
@@ -8517,6 +8595,72 @@ function createRuntimeStateBridge(params) {
|
|
|
8517
8595
|
getStatus
|
|
8518
8596
|
};
|
|
8519
8597
|
}
|
|
8598
|
+
/** Reject after `ms`; always clears its own timer. */
|
|
8599
|
+
async function withTimeout(promise, ms, label) {
|
|
8600
|
+
let timer;
|
|
8601
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
8602
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`${label} timed out after ${String(ms)}ms`)), ms);
|
|
8603
|
+
});
|
|
8604
|
+
try {
|
|
8605
|
+
return await Promise.race([promise, timeout]);
|
|
8606
|
+
} finally {
|
|
8607
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
8608
|
+
}
|
|
8609
|
+
}
|
|
8610
|
+
/**
|
|
8611
|
+
* Start the reachability poll loop. Returns a handle whose `stop()` clears the
|
|
8612
|
+
* timer and prevents any further ticks. Start on device activation, stop on
|
|
8613
|
+
* device teardown (`removeDevice`) so no timer leaks.
|
|
8614
|
+
*/
|
|
8615
|
+
function startReachabilityPoll(options) {
|
|
8616
|
+
const intervalMs = options.intervalMs ?? 3e4;
|
|
8617
|
+
const failuresToOffline = options.failuresToOffline ?? 3;
|
|
8618
|
+
const probeTimeoutMs = options.probeTimeoutMs ?? 1e4;
|
|
8619
|
+
const runImmediately = options.runImmediately ?? true;
|
|
8620
|
+
let stopped = false;
|
|
8621
|
+
let running = false;
|
|
8622
|
+
let consecutiveFailures = 0;
|
|
8623
|
+
let timer;
|
|
8624
|
+
const tick = async () => {
|
|
8625
|
+
if (stopped) return;
|
|
8626
|
+
if (running) return;
|
|
8627
|
+
if (options.isEnabled && !options.isEnabled()) return;
|
|
8628
|
+
running = true;
|
|
8629
|
+
try {
|
|
8630
|
+
const reachable = await withTimeout(Promise.resolve().then(options.probe), probeTimeoutMs, "reachability probe");
|
|
8631
|
+
if (stopped) return;
|
|
8632
|
+
if (reachable) {
|
|
8633
|
+
consecutiveFailures = 0;
|
|
8634
|
+
options.setOnline(true);
|
|
8635
|
+
} else registerFailure("probe resolved unreachable");
|
|
8636
|
+
} catch (error) {
|
|
8637
|
+
if (stopped) return;
|
|
8638
|
+
registerFailure(error instanceof Error ? error.message : "probe threw");
|
|
8639
|
+
} finally {
|
|
8640
|
+
running = false;
|
|
8641
|
+
}
|
|
8642
|
+
};
|
|
8643
|
+
const registerFailure = (reason) => {
|
|
8644
|
+
consecutiveFailures += 1;
|
|
8645
|
+
options.logger?.debug("reachability probe failed", {
|
|
8646
|
+
reason,
|
|
8647
|
+
consecutiveFailures,
|
|
8648
|
+
failuresToOffline
|
|
8649
|
+
});
|
|
8650
|
+
if (consecutiveFailures >= failuresToOffline) options.setOnline(false);
|
|
8651
|
+
};
|
|
8652
|
+
timer = setInterval(() => {
|
|
8653
|
+
tick();
|
|
8654
|
+
}, intervalMs);
|
|
8655
|
+
if (typeof timer === "object" && timer !== null && "unref" in timer) timer.unref();
|
|
8656
|
+
if (runImmediately) tick();
|
|
8657
|
+
return { stop: () => {
|
|
8658
|
+
if (stopped) return;
|
|
8659
|
+
stopped = true;
|
|
8660
|
+
if (timer !== void 0) clearInterval(timer);
|
|
8661
|
+
timer = void 0;
|
|
8662
|
+
} };
|
|
8663
|
+
}
|
|
8520
8664
|
/**
|
|
8521
8665
|
* Generic device-level status snapshot. Auto-registered by `BaseDevice`
|
|
8522
8666
|
* for every device, regardless of provider — the kernel needs a uniform
|
|
@@ -9180,26 +9324,13 @@ onBrightnessChanged: { data: object({
|
|
|
9180
9324
|
*/
|
|
9181
9325
|
runtimeState: BrightnessStatusSchema
|
|
9182
9326
|
};
|
|
9327
|
+
/** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
|
|
9183
9328
|
var StreamFormatSchema = _enum([
|
|
9184
9329
|
"webrtc",
|
|
9185
9330
|
"hls",
|
|
9186
9331
|
"mjpeg",
|
|
9187
9332
|
"rtsp"
|
|
9188
9333
|
]);
|
|
9189
|
-
var StreamInfoSchema = object({
|
|
9190
|
-
streamId: string(),
|
|
9191
|
-
format: StreamFormatSchema,
|
|
9192
|
-
url: string().nullable(),
|
|
9193
|
-
active: boolean()
|
|
9194
|
-
});
|
|
9195
|
-
method(object({
|
|
9196
|
-
streamId: string(),
|
|
9197
|
-
sourceUrl: string(),
|
|
9198
|
-
codec: string().optional()
|
|
9199
|
-
}), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
|
|
9200
|
-
streamId: string(),
|
|
9201
|
-
format: StreamFormatSchema
|
|
9202
|
-
}), string().nullable()), method(_void(), array(StreamInfoSchema));
|
|
9203
9334
|
var RtspRestreamEntrySchema = object({
|
|
9204
9335
|
brokerId: string(),
|
|
9205
9336
|
url: string(),
|
|
@@ -10067,37 +10198,7 @@ var consumablesCapability = {
|
|
|
10067
10198
|
scope: "device",
|
|
10068
10199
|
deviceNative: true,
|
|
10069
10200
|
mode: "singleton",
|
|
10070
|
-
deviceTypes:
|
|
10071
|
-
DeviceType.Camera,
|
|
10072
|
-
DeviceType.Hub,
|
|
10073
|
-
DeviceType.Light,
|
|
10074
|
-
DeviceType.Siren,
|
|
10075
|
-
DeviceType.Switch,
|
|
10076
|
-
DeviceType.Sensor,
|
|
10077
|
-
DeviceType.Thermostat,
|
|
10078
|
-
DeviceType.Button,
|
|
10079
|
-
DeviceType.EventEmitter,
|
|
10080
|
-
DeviceType.Update,
|
|
10081
|
-
DeviceType.Generic,
|
|
10082
|
-
DeviceType.Notifier,
|
|
10083
|
-
DeviceType.Script,
|
|
10084
|
-
DeviceType.Automation,
|
|
10085
|
-
DeviceType.Lock,
|
|
10086
|
-
DeviceType.Cover,
|
|
10087
|
-
DeviceType.Valve,
|
|
10088
|
-
DeviceType.Humidifier,
|
|
10089
|
-
DeviceType.WaterHeater,
|
|
10090
|
-
DeviceType.Fan,
|
|
10091
|
-
DeviceType.MediaPlayer,
|
|
10092
|
-
DeviceType.AlarmPanel,
|
|
10093
|
-
DeviceType.Control,
|
|
10094
|
-
DeviceType.Presence,
|
|
10095
|
-
DeviceType.Weather,
|
|
10096
|
-
DeviceType.Vacuum,
|
|
10097
|
-
DeviceType.LawnMower,
|
|
10098
|
-
DeviceType.Container,
|
|
10099
|
-
DeviceType.Image
|
|
10100
|
-
],
|
|
10201
|
+
deviceTypes: Object.values(DeviceType),
|
|
10101
10202
|
deviceConfig: { ui: {
|
|
10102
10203
|
kind: "widget",
|
|
10103
10204
|
widgetId: "host/consumables-panel",
|
|
@@ -11690,7 +11791,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
|
|
|
11690
11791
|
enabled: boolean(),
|
|
11691
11792
|
modelId: string(),
|
|
11692
11793
|
children: array(PipelineDefaultStepSchema).readonly(),
|
|
11693
|
-
engine: PipelineEngineChoiceSchema.optional(),
|
|
11694
11794
|
group: string().optional(),
|
|
11695
11795
|
settings: record(string(), unknown()).optional()
|
|
11696
11796
|
}));
|
|
@@ -11715,7 +11815,9 @@ var PipelineModelOptionSchema = object({
|
|
|
11715
11815
|
formats: record(string(), object({
|
|
11716
11816
|
downloaded: boolean(),
|
|
11717
11817
|
sizeMB: number()
|
|
11718
|
-
}))
|
|
11818
|
+
})),
|
|
11819
|
+
group: ModelVariantGroupSchema.optional(),
|
|
11820
|
+
legacy: boolean().optional()
|
|
11719
11821
|
});
|
|
11720
11822
|
var ConfigFieldBridge = custom();
|
|
11721
11823
|
var PipelineAddonSchemaSchema = object({
|
|
@@ -11766,15 +11868,42 @@ var EngineProvisioningSchema = object({
|
|
|
11766
11868
|
]),
|
|
11767
11869
|
progress: number().optional(),
|
|
11768
11870
|
error: string().optional(),
|
|
11769
|
-
nextRetryAt: number().optional()
|
|
11871
|
+
nextRetryAt: number().optional(),
|
|
11872
|
+
/**
|
|
11873
|
+
* Gate A (config-correctness gate at engine change): human-readable
|
|
11874
|
+
* config issues surfaced EAGERLY when the node's engine changes — model
|
|
11875
|
+
* substitutions ("chose X, running Y") and zero-build steps ("no model
|
|
11876
|
+
* has a <format> build"). Additive/optional: informational only, never
|
|
11877
|
+
* enforced here — `assertEngineReady` (readiness) still gates inference.
|
|
11878
|
+
* Absent/empty when the node-default tree resolves cleanly.
|
|
11879
|
+
*/
|
|
11880
|
+
configIssues: array(string()).optional()
|
|
11770
11881
|
});
|
|
11771
11882
|
var PipelineStepInputSchema = lazy(() => object({
|
|
11772
11883
|
addonId: string(),
|
|
11773
|
-
modelId: string(),
|
|
11884
|
+
modelId: string().optional(),
|
|
11774
11885
|
enabled: boolean().default(true),
|
|
11775
11886
|
children: array(PipelineStepInputSchema).optional(),
|
|
11776
11887
|
settings: record(string(), unknown()).optional()
|
|
11777
11888
|
}));
|
|
11889
|
+
var ModelSubstitutionSchema = object({
|
|
11890
|
+
addonId: string(),
|
|
11891
|
+
chosen: string(),
|
|
11892
|
+
running: string(),
|
|
11893
|
+
format: string()
|
|
11894
|
+
});
|
|
11895
|
+
var PipelineValidationIssueSchema = object({
|
|
11896
|
+
addonId: string(),
|
|
11897
|
+
kind: _enum(["unknown-addon", "no-format-build"]),
|
|
11898
|
+
detail: string()
|
|
11899
|
+
});
|
|
11900
|
+
var PipelineValidationResultSchema = object({
|
|
11901
|
+
ok: boolean(),
|
|
11902
|
+
issues: array(PipelineValidationIssueSchema).readonly(),
|
|
11903
|
+
substitutions: array(ModelSubstitutionSchema).readonly(),
|
|
11904
|
+
/** The node's `currentEngine.format` this validation ran against. */
|
|
11905
|
+
format: string()
|
|
11906
|
+
});
|
|
11778
11907
|
var ReferenceImageEntrySchema = object({
|
|
11779
11908
|
filename: string(),
|
|
11780
11909
|
stepIds: array(string()).readonly().optional()
|
|
@@ -11845,7 +11974,13 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
|
|
|
11845
11974
|
})) }), object({ success: literal(true) }), {
|
|
11846
11975
|
kind: "mutation",
|
|
11847
11976
|
auth: "admin"
|
|
11848
|
-
}), method(
|
|
11977
|
+
}), method(object({ nodeId: string() }), object({
|
|
11978
|
+
success: literal(true),
|
|
11979
|
+
regeneratedModelId: string().nullable()
|
|
11980
|
+
}), {
|
|
11981
|
+
kind: "mutation",
|
|
11982
|
+
auth: "admin"
|
|
11983
|
+
}), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({ steps: array(PipelineStepInputSchema) }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
|
|
11849
11984
|
name: string(),
|
|
11850
11985
|
steps: array(PipelineTemplateStepSchema).readonly(),
|
|
11851
11986
|
engine: PipelineEngineChoiceSchema
|
|
@@ -12138,6 +12273,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
|
|
|
12138
12273
|
kind: literal("remote-restream"),
|
|
12139
12274
|
/** The camera's source-owner node (slice 1: always the hub). */
|
|
12140
12275
|
ownerNodeId: string(),
|
|
12276
|
+
/**
|
|
12277
|
+
* The owner's LAN-reachable host, resolved by the orchestrator from the
|
|
12278
|
+
* per-node `reachableHost` override (Cluster UI). When present the runner
|
|
12279
|
+
* dials THIS host for the owner's restream, in preference to the
|
|
12280
|
+
* `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
|
|
12281
|
+
*/
|
|
12282
|
+
ownerReachableHost: string().optional(),
|
|
12141
12283
|
/** Operator override for the owner host the runner dials. */
|
|
12142
12284
|
hubHostnameOverride: string().optional()
|
|
12143
12285
|
})]).describe("Per-camera frame-source mode for the runner (P2c)");
|
|
@@ -12146,13 +12288,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
|
|
|
12146
12288
|
* specific runner instance via `attachCamera`. Carries everything the
|
|
12147
12289
|
* runner needs to subscribe to the local broker and execute inference.
|
|
12148
12290
|
*
|
|
12149
|
-
* Stateless-pipeline model: the
|
|
12150
|
-
*
|
|
12151
|
-
*
|
|
12152
|
-
*
|
|
12153
|
-
*
|
|
12154
|
-
* `engine`/`steps`/`audio` are optional during the additive migration
|
|
12155
|
-
* window; once orchestrator + UI are migrated they become required.
|
|
12291
|
+
* Stateless-pipeline model: the pipeline content (`steps`, optional
|
|
12292
|
+
* `audio`) travels with the attach payload. The runner keeps it in RAM
|
|
12293
|
+
* for the lifetime of the attach — on rebalance, edit, or restart the
|
|
12294
|
+
* orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
|
|
12295
|
+
* node-local, resolved by the executing runner at dispatch time.
|
|
12156
12296
|
*/
|
|
12157
12297
|
var RunnerCameraConfigSchema = object({
|
|
12158
12298
|
deviceId: number(),
|
|
@@ -12203,14 +12343,11 @@ var RunnerCameraConfigSchema = object({
|
|
|
12203
12343
|
*/
|
|
12204
12344
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
12205
12345
|
pipelineEnabled: boolean().default(true),
|
|
12206
|
-
/** Engine choice for video steps (runtime+backend+format). */
|
|
12207
|
-
engine: PipelineEngineChoiceSchema.optional(),
|
|
12208
12346
|
/** Ordered tree of video steps. Absent → runner skips video detection. */
|
|
12209
12347
|
steps: array(PipelineStepInputSchema).readonly().optional(),
|
|
12210
12348
|
/** Audio classification branch. `enabled:false` disables, null skips. */
|
|
12211
12349
|
audio: object({
|
|
12212
|
-
|
|
12213
|
-
modelId: string(),
|
|
12350
|
+
modelId: string().optional(),
|
|
12214
12351
|
enabled: boolean()
|
|
12215
12352
|
}).nullable().optional(),
|
|
12216
12353
|
/**
|
|
@@ -18073,11 +18210,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
18073
18210
|
timestamp: number()
|
|
18074
18211
|
});
|
|
18075
18212
|
var CameraPipelineConfigSchema = object({
|
|
18076
|
-
engine: PipelineEngineChoiceSchema,
|
|
18213
|
+
engine: PipelineEngineChoiceSchema.optional(),
|
|
18077
18214
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
18078
18215
|
audio: object({
|
|
18079
|
-
engine: PipelineEngineChoiceSchema,
|
|
18080
|
-
modelId: string(),
|
|
18216
|
+
engine: PipelineEngineChoiceSchema.optional(),
|
|
18217
|
+
modelId: string().optional(),
|
|
18081
18218
|
enabled: boolean(),
|
|
18082
18219
|
settings: record(string(), unknown()).readonly().optional()
|
|
18083
18220
|
}).nullable().optional()
|
|
@@ -18092,7 +18229,7 @@ var PipelineTemplateSchema = object({
|
|
|
18092
18229
|
});
|
|
18093
18230
|
var AgentAddonConfigSchema = object({
|
|
18094
18231
|
enabled: boolean(),
|
|
18095
|
-
modelId: string(),
|
|
18232
|
+
modelId: string().optional(),
|
|
18096
18233
|
settings: record(string(), unknown()).readonly()
|
|
18097
18234
|
});
|
|
18098
18235
|
var AgentPipelineSettingsSchema = object({
|
|
@@ -18107,7 +18244,15 @@ var AgentPipelineSettingsSchema = object({
|
|
|
18107
18244
|
/** Node is eligible to run audio-analyzer sessions. */
|
|
18108
18245
|
audio: boolean().optional(),
|
|
18109
18246
|
/** Node is eligible to be the ingest / source-owner (serve the restream). */
|
|
18110
|
-
ingest: boolean().optional()
|
|
18247
|
+
ingest: boolean().optional(),
|
|
18248
|
+
/**
|
|
18249
|
+
* Operator override for the LAN host a cross-node decoder dials to reach
|
|
18250
|
+
* THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
|
|
18251
|
+
* falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
|
|
18252
|
+
* it already uses to reach the hub). Set this only when the auto-detected
|
|
18253
|
+
* address is wrong (multi-homed host, NAT, custom interface).
|
|
18254
|
+
*/
|
|
18255
|
+
reachableHost: string().optional()
|
|
18111
18256
|
});
|
|
18112
18257
|
var CameraPipelineForAgentSchema = object({
|
|
18113
18258
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -18213,6 +18358,15 @@ var GlobalMetricsSchema = object({
|
|
|
18213
18358
|
* capability providers.
|
|
18214
18359
|
*/
|
|
18215
18360
|
var CapabilityBindingsSchema = record(string(), string());
|
|
18361
|
+
/**
|
|
18362
|
+
* The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
|
|
18363
|
+
* its LAN-reachable host, if one is registered. See `getIngestOwner`.
|
|
18364
|
+
*/
|
|
18365
|
+
var IngestOwnerSchema = object({
|
|
18366
|
+
ownerNodeId: string(),
|
|
18367
|
+
reachableHost: string().optional(),
|
|
18368
|
+
configIssue: string().optional()
|
|
18369
|
+
});
|
|
18216
18370
|
/** Source block — always present; derives from the stream catalog. */
|
|
18217
18371
|
var CameraSourceStatusSchema = object({ streams: array(object({
|
|
18218
18372
|
camStreamId: string(),
|
|
@@ -18227,6 +18381,14 @@ var CameraAssignmentStatusSchema = object({
|
|
|
18227
18381
|
detectionNodeId: string().nullable(),
|
|
18228
18382
|
decoderNodeId: string().nullable(),
|
|
18229
18383
|
audioNodeId: string().nullable(),
|
|
18384
|
+
/**
|
|
18385
|
+
* The node that OWNS this camera's physical source pull (dials the RTSP and
|
|
18386
|
+
* hosts the broker/restream) — the cluster ingest owner today
|
|
18387
|
+
* (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
|
|
18388
|
+
* the UI show WHERE a camera is sourced without SSH/logs, and is the node the
|
|
18389
|
+
* broker block below was read from (pinned). Nullable only pre-wiring.
|
|
18390
|
+
*/
|
|
18391
|
+
sourceNodeId: string().nullable(),
|
|
18230
18392
|
pinned: object({
|
|
18231
18393
|
detection: boolean(),
|
|
18232
18394
|
decoder: boolean(),
|
|
@@ -18359,7 +18521,7 @@ method(object({
|
|
|
18359
18521
|
}), object({ success: literal(true) }), {
|
|
18360
18522
|
kind: "mutation",
|
|
18361
18523
|
auth: "admin"
|
|
18362
|
-
}), method(object({
|
|
18524
|
+
}), method(_void(), IngestOwnerSchema), method(object({
|
|
18363
18525
|
deviceId: number(),
|
|
18364
18526
|
nodeId: string()
|
|
18365
18527
|
}), _void(), {
|
|
@@ -18428,6 +18590,12 @@ method(object({
|
|
|
18428
18590
|
}), object({ success: literal(true) }), {
|
|
18429
18591
|
kind: "mutation",
|
|
18430
18592
|
auth: "admin"
|
|
18593
|
+
}), method(object({
|
|
18594
|
+
agentNodeId: string(),
|
|
18595
|
+
reachableHost: string().nullable()
|
|
18596
|
+
}), object({ success: literal(true) }), {
|
|
18597
|
+
kind: "mutation",
|
|
18598
|
+
auth: "admin"
|
|
18431
18599
|
}), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
|
|
18432
18600
|
deviceId: number(),
|
|
18433
18601
|
addonId: string(),
|
|
@@ -18472,22 +18640,6 @@ method(object({
|
|
|
18472
18640
|
kind: "mutation",
|
|
18473
18641
|
auth: "admin"
|
|
18474
18642
|
});
|
|
18475
|
-
var RegisteredStreamSchema = object({
|
|
18476
|
-
streamId: string(),
|
|
18477
|
-
label: string().optional(),
|
|
18478
|
-
codec: string(),
|
|
18479
|
-
type: _enum(["video", "audio"]),
|
|
18480
|
-
sourceUrl: string()
|
|
18481
|
-
});
|
|
18482
|
-
var ExposedResourceSchema = object({
|
|
18483
|
-
streamId: string(),
|
|
18484
|
-
format: string(),
|
|
18485
|
-
value: string()
|
|
18486
|
-
});
|
|
18487
|
-
method(object({
|
|
18488
|
-
deviceId: number(),
|
|
18489
|
-
streams: array(RegisteredStreamSchema).readonly()
|
|
18490
|
-
}), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(ExposedResourceSchema).readonly());
|
|
18491
18643
|
/**
|
|
18492
18644
|
* Query filter for settings-store collections.
|
|
18493
18645
|
*/
|
|
@@ -18640,9 +18792,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
|
|
|
18640
18792
|
/**
|
|
18641
18793
|
* A single device snapshot returned as base64 JPEG/PNG.
|
|
18642
18794
|
*
|
|
18643
|
-
*
|
|
18644
|
-
*
|
|
18645
|
-
*
|
|
18795
|
+
* The `SnapshotAddon` wrapper returns this shape whether the frame came from
|
|
18796
|
+
* the device-native provider (onboard capture) or from the stream-broker
|
|
18797
|
+
* prebuffer fallback.
|
|
18646
18798
|
*/
|
|
18647
18799
|
var SnapshotImageSchema = object({
|
|
18648
18800
|
base64: string(),
|
|
@@ -18717,10 +18869,6 @@ var snapshotCapability = {
|
|
|
18717
18869
|
kind: "poll"
|
|
18718
18870
|
}
|
|
18719
18871
|
};
|
|
18720
|
-
method(object({ deviceId: number() }), boolean()), method(object({
|
|
18721
|
-
deviceId: number(),
|
|
18722
|
-
streamId: string().optional()
|
|
18723
|
-
}), SnapshotImageSchema.nullable());
|
|
18724
18872
|
/**
|
|
18725
18873
|
* `sso-bridge` — internal hub-only cap that lets SSO-style auth
|
|
18726
18874
|
* providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
|
|
@@ -19081,9 +19229,10 @@ method(object({
|
|
|
19081
19229
|
auth: "admin"
|
|
19082
19230
|
});
|
|
19083
19231
|
/**
|
|
19084
|
-
* Optional client-side hints sent at session creation to help the
|
|
19085
|
-
*
|
|
19086
|
-
*
|
|
19232
|
+
* Optional client-side hints sent at session creation to help the provider
|
|
19233
|
+
* pick the best native source. All fields optional — a viewer that knows
|
|
19234
|
+
* nothing still gets a sane default. (Relocated from the retired `webrtc`
|
|
19235
|
+
* collection cap; this `webrtc-session` cap is the live signaling surface.)
|
|
19087
19236
|
*/
|
|
19088
19237
|
var webrtcClientHintsSchema = object({
|
|
19089
19238
|
viewportWidth: number().int().positive().optional(),
|
|
@@ -19094,22 +19243,6 @@ var webrtcClientHintsSchema = object({
|
|
|
19094
19243
|
/** Hard tier override; takes precedence over scoring when registered. */
|
|
19095
19244
|
prefersTier: string().optional()
|
|
19096
19245
|
}).partial();
|
|
19097
|
-
method(object({
|
|
19098
|
-
streamId: string(),
|
|
19099
|
-
sdpOffer: string()
|
|
19100
|
-
}), string(), { kind: "mutation" }), method(object({ streamId: string() }), boolean()), method(object({
|
|
19101
|
-
streamId: string(),
|
|
19102
|
-
codec: string()
|
|
19103
|
-
}), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
|
|
19104
|
-
streamId: string(),
|
|
19105
|
-
hints: webrtcClientHintsSchema.optional()
|
|
19106
|
-
}), object({
|
|
19107
|
-
sessionId: string(),
|
|
19108
|
-
sdpOffer: string()
|
|
19109
|
-
}), { kind: "mutation" }), method(object({
|
|
19110
|
-
sessionId: string(),
|
|
19111
|
-
sdpAnswer: string()
|
|
19112
|
-
}), _void(), { kind: "mutation" }), method(object({ sessionId: string() }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), boolean());
|
|
19113
19246
|
/**
|
|
19114
19247
|
* Discriminated target for a WebRTC session. The client sends this
|
|
19115
19248
|
* structured object instead of building / parsing brokerId strings;
|
|
@@ -24451,6 +24584,12 @@ Object.freeze({
|
|
|
24451
24584
|
addonId: null,
|
|
24452
24585
|
access: "create"
|
|
24453
24586
|
},
|
|
24587
|
+
"pipelineExecutor.resetToDefault": {
|
|
24588
|
+
capName: "pipeline-executor",
|
|
24589
|
+
capScope: "system",
|
|
24590
|
+
addonId: null,
|
|
24591
|
+
access: "delete"
|
|
24592
|
+
},
|
|
24454
24593
|
"pipelineExecutor.runAudioTest": {
|
|
24455
24594
|
capName: "pipeline-executor",
|
|
24456
24595
|
capScope: "system",
|
|
@@ -24499,6 +24638,12 @@ Object.freeze({
|
|
|
24499
24638
|
addonId: null,
|
|
24500
24639
|
access: "create"
|
|
24501
24640
|
},
|
|
24641
|
+
"pipelineExecutor.validatePipeline": {
|
|
24642
|
+
capName: "pipeline-executor",
|
|
24643
|
+
capScope: "system",
|
|
24644
|
+
addonId: null,
|
|
24645
|
+
access: "view"
|
|
24646
|
+
},
|
|
24502
24647
|
"pipelineOrchestrator.assignAudio": {
|
|
24503
24648
|
capName: "pipeline-orchestrator",
|
|
24504
24649
|
capScope: "system",
|
|
@@ -24607,6 +24752,12 @@ Object.freeze({
|
|
|
24607
24752
|
addonId: null,
|
|
24608
24753
|
access: "view"
|
|
24609
24754
|
},
|
|
24755
|
+
"pipelineOrchestrator.getIngestOwner": {
|
|
24756
|
+
capName: "pipeline-orchestrator",
|
|
24757
|
+
capScope: "system",
|
|
24758
|
+
addonId: null,
|
|
24759
|
+
access: "view"
|
|
24760
|
+
},
|
|
24610
24761
|
"pipelineOrchestrator.getPipelineAssignment": {
|
|
24611
24762
|
capName: "pipeline-orchestrator",
|
|
24612
24763
|
capScope: "system",
|
|
@@ -24679,6 +24830,12 @@ Object.freeze({
|
|
|
24679
24830
|
addonId: null,
|
|
24680
24831
|
access: "create"
|
|
24681
24832
|
},
|
|
24833
|
+
"pipelineOrchestrator.setAgentReachableHost": {
|
|
24834
|
+
capName: "pipeline-orchestrator",
|
|
24835
|
+
capScope: "system",
|
|
24836
|
+
addonId: null,
|
|
24837
|
+
access: "create"
|
|
24838
|
+
},
|
|
24682
24839
|
"pipelineOrchestrator.setCameraPipelineForAgent": {
|
|
24683
24840
|
capName: "pipeline-orchestrator",
|
|
24684
24841
|
capScope: "system",
|
|
@@ -25009,24 +25166,6 @@ Object.freeze({
|
|
|
25009
25166
|
addonId: null,
|
|
25010
25167
|
access: "create"
|
|
25011
25168
|
},
|
|
25012
|
-
"restreamer.getExposedResources": {
|
|
25013
|
-
capName: "restreamer",
|
|
25014
|
-
capScope: "system",
|
|
25015
|
-
addonId: null,
|
|
25016
|
-
access: "view"
|
|
25017
|
-
},
|
|
25018
|
-
"restreamer.registerDevice": {
|
|
25019
|
-
capName: "restreamer",
|
|
25020
|
-
capScope: "system",
|
|
25021
|
-
addonId: null,
|
|
25022
|
-
access: "create"
|
|
25023
|
-
},
|
|
25024
|
-
"restreamer.unregisterDevice": {
|
|
25025
|
-
capName: "restreamer",
|
|
25026
|
-
capScope: "system",
|
|
25027
|
-
addonId: null,
|
|
25028
|
-
access: "delete"
|
|
25029
|
-
},
|
|
25030
25169
|
"scriptRunner.run": {
|
|
25031
25170
|
capName: "script-runner",
|
|
25032
25171
|
capScope: "device",
|
|
@@ -25129,18 +25268,6 @@ Object.freeze({
|
|
|
25129
25268
|
addonId: null,
|
|
25130
25269
|
access: "create"
|
|
25131
25270
|
},
|
|
25132
|
-
"snapshotProvider.getSnapshot": {
|
|
25133
|
-
capName: "snapshot-provider",
|
|
25134
|
-
capScope: "system",
|
|
25135
|
-
addonId: null,
|
|
25136
|
-
access: "view"
|
|
25137
|
-
},
|
|
25138
|
-
"snapshotProvider.supportsDevice": {
|
|
25139
|
-
capName: "snapshot-provider",
|
|
25140
|
-
capScope: "system",
|
|
25141
|
-
addonId: null,
|
|
25142
|
-
access: "view"
|
|
25143
|
-
},
|
|
25144
25271
|
"ssoBridge.signBridgeToken": {
|
|
25145
25272
|
capName: "sso-bridge",
|
|
25146
25273
|
capScope: "system",
|
|
@@ -25567,30 +25694,6 @@ Object.freeze({
|
|
|
25567
25694
|
addonId: null,
|
|
25568
25695
|
access: "view"
|
|
25569
25696
|
},
|
|
25570
|
-
"streamingEngine.getStreamUrl": {
|
|
25571
|
-
capName: "streaming-engine",
|
|
25572
|
-
capScope: "system",
|
|
25573
|
-
addonId: null,
|
|
25574
|
-
access: "view"
|
|
25575
|
-
},
|
|
25576
|
-
"streamingEngine.listStreams": {
|
|
25577
|
-
capName: "streaming-engine",
|
|
25578
|
-
capScope: "system",
|
|
25579
|
-
addonId: null,
|
|
25580
|
-
access: "view"
|
|
25581
|
-
},
|
|
25582
|
-
"streamingEngine.registerStream": {
|
|
25583
|
-
capName: "streaming-engine",
|
|
25584
|
-
capScope: "system",
|
|
25585
|
-
addonId: null,
|
|
25586
|
-
access: "create"
|
|
25587
|
-
},
|
|
25588
|
-
"streamingEngine.unregisterStream": {
|
|
25589
|
-
capName: "streaming-engine",
|
|
25590
|
-
capScope: "system",
|
|
25591
|
-
addonId: null,
|
|
25592
|
-
access: "delete"
|
|
25593
|
-
},
|
|
25594
25697
|
"streamParams.getConfigSchema": {
|
|
25595
25698
|
capName: "stream-params",
|
|
25596
25699
|
capScope: "device",
|
|
@@ -25957,54 +26060,6 @@ Object.freeze({
|
|
|
25957
26060
|
addonId: null,
|
|
25958
26061
|
access: "create"
|
|
25959
26062
|
},
|
|
25960
|
-
"webrtc.closeSession": {
|
|
25961
|
-
capName: "webrtc",
|
|
25962
|
-
capScope: "system",
|
|
25963
|
-
addonId: null,
|
|
25964
|
-
access: "create"
|
|
25965
|
-
},
|
|
25966
|
-
"webrtc.createSession": {
|
|
25967
|
-
capName: "webrtc",
|
|
25968
|
-
capScope: "system",
|
|
25969
|
-
addonId: null,
|
|
25970
|
-
access: "create"
|
|
25971
|
-
},
|
|
25972
|
-
"webrtc.handleAnswer": {
|
|
25973
|
-
capName: "webrtc",
|
|
25974
|
-
capScope: "system",
|
|
25975
|
-
addonId: null,
|
|
25976
|
-
access: "create"
|
|
25977
|
-
},
|
|
25978
|
-
"webrtc.handleOffer": {
|
|
25979
|
-
capName: "webrtc",
|
|
25980
|
-
capScope: "system",
|
|
25981
|
-
addonId: null,
|
|
25982
|
-
access: "create"
|
|
25983
|
-
},
|
|
25984
|
-
"webrtc.hasAdaptiveBitrate": {
|
|
25985
|
-
capName: "webrtc",
|
|
25986
|
-
capScope: "system",
|
|
25987
|
-
addonId: null,
|
|
25988
|
-
access: "view"
|
|
25989
|
-
},
|
|
25990
|
-
"webrtc.registerStream": {
|
|
25991
|
-
capName: "webrtc",
|
|
25992
|
-
capScope: "system",
|
|
25993
|
-
addonId: null,
|
|
25994
|
-
access: "create"
|
|
25995
|
-
},
|
|
25996
|
-
"webrtc.supportsStream": {
|
|
25997
|
-
capName: "webrtc",
|
|
25998
|
-
capScope: "system",
|
|
25999
|
-
addonId: null,
|
|
26000
|
-
access: "view"
|
|
26001
|
-
},
|
|
26002
|
-
"webrtc.unregisterStream": {
|
|
26003
|
-
capName: "webrtc",
|
|
26004
|
-
capScope: "system",
|
|
26005
|
-
addonId: null,
|
|
26006
|
-
access: "delete"
|
|
26007
|
-
},
|
|
26008
26063
|
"webrtcSession.addIceCandidate": {
|
|
26009
26064
|
capName: "webrtc-session",
|
|
26010
26065
|
capScope: "device",
|
|
@@ -26756,6 +26811,10 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
26756
26811
|
motionActive = false;
|
|
26757
26812
|
/** Keepalive re-emit timer for a sustained motion window. */
|
|
26758
26813
|
motionKeepaliveTimer = null;
|
|
26814
|
+
/** Control-plane reachability poll — drives `device.online` from Dahua CGI
|
|
26815
|
+
* `getDeviceInfo` liveness, decoupled from stream-broker video health.
|
|
26816
|
+
* Started in `onActivate`, stopped in `removeDevice`. */
|
|
26817
|
+
reachabilityPoll = null;
|
|
26759
26818
|
/** Single-flight guard for the `stream-params` camera refresh. */
|
|
26760
26819
|
streamParamsRefreshInFlight = null;
|
|
26761
26820
|
/** Single-flight guard for the `motion-zones` camera refresh. */
|
|
@@ -26928,12 +26987,33 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
26928
26987
|
/** Phase 5 — device is live: open the onboard-motion event stream. */
|
|
26929
26988
|
async onActivate() {
|
|
26930
26989
|
this.ensureEventSubscription();
|
|
26990
|
+
this.startReachabilityPolling();
|
|
26991
|
+
}
|
|
26992
|
+
/** Start the control-plane reachability poll: a Dahua CGI `getDeviceInfo`
|
|
26993
|
+
* round-trip every 30s drives `device.online`, with hysteresis. Replaces
|
|
26994
|
+
* the old stream-health→online mirror so an on-demand (idle) but reachable
|
|
26995
|
+
* camera still reports ONLINE. Idempotent. */
|
|
26996
|
+
startReachabilityPolling() {
|
|
26997
|
+
if (this.reachabilityPoll) return;
|
|
26998
|
+
this.reachabilityPoll = startReachabilityPoll({
|
|
26999
|
+
probe: async () => {
|
|
27000
|
+
await this.ensureClient().getDeviceInfo();
|
|
27001
|
+
return true;
|
|
27002
|
+
},
|
|
27003
|
+
setOnline: (online) => {
|
|
27004
|
+
this.markOnline(online);
|
|
27005
|
+
},
|
|
27006
|
+
isEnabled: () => !this.disabled,
|
|
27007
|
+
logger: this.ctx.logger
|
|
27008
|
+
});
|
|
26931
27009
|
}
|
|
26932
27010
|
/** Teardown — stop the stream + motion timers, drop the client. */
|
|
26933
27011
|
async removeDevice() {
|
|
26934
27012
|
this.ctx.logger.info("Removing Amcrest camera", { tags: { deviceId: this.id } });
|
|
26935
27013
|
this.teardownEventSubscription();
|
|
26936
27014
|
this.clearPtzAutoStop();
|
|
27015
|
+
this.reachabilityPoll?.stop();
|
|
27016
|
+
this.reachabilityPoll = null;
|
|
26937
27017
|
this.client = null;
|
|
26938
27018
|
}
|
|
26939
27019
|
registerStreamCatalogProvider() {
|
|
@@ -27184,9 +27264,14 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
|
|
|
27184
27264
|
if (deviceId !== this.id) return;
|
|
27185
27265
|
const client = this.ensureClient();
|
|
27186
27266
|
const requested = Number.parseInt(presetId, 10);
|
|
27187
|
-
const
|
|
27188
|
-
let slot
|
|
27189
|
-
|
|
27267
|
+
const DAHUA_MAX_PRESETS = 25;
|
|
27268
|
+
let slot;
|
|
27269
|
+
if (Number.isInteger(requested) && requested >= 1 && requested <= DAHUA_MAX_PRESETS) slot = requested;
|
|
27270
|
+
else {
|
|
27271
|
+
const used = new Set((await client.getPtzPresets(this.channel)).map((p) => p.id));
|
|
27272
|
+
slot = 1;
|
|
27273
|
+
while (used.has(slot)) slot += 1;
|
|
27274
|
+
}
|
|
27190
27275
|
await client.ptzSetPreset(this.channel, slot);
|
|
27191
27276
|
},
|
|
27192
27277
|
deletePreset: async ({ deviceId, presetId }) => {
|
|
@@ -36949,21 +37034,7 @@ var AmcrestProviderAddon = class extends BaseDeviceProvider {
|
|
|
36949
37034
|
throw new Error(`Amcrest: probe on ${host || "(unknown host)"} resolved neither mac nor host address — cannot persist a stable row key. Verify network reachability + credentials, then retry.`);
|
|
36950
37035
|
}
|
|
36951
37036
|
async onInitialize() {
|
|
36952
|
-
|
|
36953
|
-
this.subscribe({ category: EventCategory.DeviceStateChanged }, (event) => {
|
|
36954
|
-
const data = event.data;
|
|
36955
|
-
if (data.capName !== "camera-streams") return;
|
|
36956
|
-
const deviceId = data.deviceId;
|
|
36957
|
-
if (typeof deviceId !== "number") return;
|
|
36958
|
-
const registry = this.ctx.kernel.deviceRegistry;
|
|
36959
|
-
if (!registry) return;
|
|
36960
|
-
if (registry.getAddonId(deviceId) !== this.addonId) return;
|
|
36961
|
-
const device = registry.getById(deviceId);
|
|
36962
|
-
if (!device) return;
|
|
36963
|
-
const online = data.slice?.online === true;
|
|
36964
|
-
if (device.online !== online) device.online = online;
|
|
36965
|
-
});
|
|
36966
|
-
return regs;
|
|
37037
|
+
return await super.onInitialize();
|
|
36967
37038
|
}
|
|
36968
37039
|
async supportsDiscovery() {
|
|
36969
37040
|
return true;
|