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