@camstack/addon-pipeline 0.1.18 → 0.1.20
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/audio-analyzer/index.js +12 -4
- package/dist/audio-analyzer/index.js.map +1 -1
- package/dist/audio-analyzer/index.mjs +12 -4
- package/dist/audio-analyzer/index.mjs.map +1 -1
- package/dist/audio-codec-nodeav/index.js +1 -1
- package/dist/audio-codec-nodeav/index.mjs +1 -1
- package/dist/decoder-nodeav/index.js +2 -2
- package/dist/decoder-nodeav/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +47 -44
- package/dist/detection-pipeline/index.js.map +1 -1
- package/dist/detection-pipeline/index.mjs +47 -44
- package/dist/detection-pipeline/index.mjs.map +1 -1
- package/dist/{index-asZs8U_s.mjs → index-5aYef068.mjs} +4020 -820
- package/dist/index-5aYef068.mjs.map +1 -0
- package/dist/{index-DLHaHm6u.js → index-B36NMAdu.js} +3996 -796
- package/dist/index-B36NMAdu.js.map +1 -0
- package/dist/{index-D_cl0Qqb.js → index-CMcx_k6Y.js} +48 -48
- package/dist/{index-D_cl0Qqb.js.map → index-CMcx_k6Y.js.map} +1 -1
- package/dist/{index-UbcdLS7a.mjs → index-CYb7cFrv.mjs} +46 -46
- package/dist/{index-UbcdLS7a.mjs.map → index-CYb7cFrv.mjs.map} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +205 -90
- package/dist/pipeline-runner/index.js.map +1 -1
- package/dist/pipeline-runner/index.mjs +206 -91
- package/dist/pipeline-runner/index.mjs.map +1 -1
- package/dist/recorder/index.js +2209 -0
- package/dist/recorder/index.js.map +1 -0
- package/dist/recorder/index.mjs +2209 -0
- package/dist/recorder/index.mjs.map +1 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/FfmpegParamsField.d.ts +41 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/GeometryBuilder.d.ts +54 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/format-ua.d.ts +13 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/index.d.ts +2 -0
- package/dist/stream-broker/@mf-types.zip +0 -0
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-lantnv8e.mjs} +1 -1
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-DJ3UNg7O.mjs +30 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-CYXy_bhS.mjs +21 -0
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DAssX3h0.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-CaDEYBIU.mjs} +9 -7
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-DFoJJhpt.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-D6EROtlA.mjs} +1 -1
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-gBEZsQrp.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-x6pP3Ghk.mjs} +2 -2
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-x7XMEeuJ.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CcnN6sbA.mjs} +1 -1
- package/dist/stream-broker/_stub.js +963 -333
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-3TxRVJ5L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CL9DR49k.mjs} +6 -6
- package/dist/stream-broker/{client-CZXrddDR.mjs → client-BvTmMOQu.mjs} +2 -2
- package/dist/stream-broker/{hostInit-De6APW25.mjs → hostInit-ChmiMPS0.mjs} +12 -12
- package/dist/stream-broker/{index-cYW01SNH.mjs → index-BxsFuFmE.mjs} +24 -24
- package/dist/stream-broker/{index-KtR7Pp0O.mjs → index-C-248uOU.mjs} +2 -2
- package/dist/stream-broker/{index-C0BzaWmB.mjs → index-C05B6jqp.mjs} +1 -1
- package/dist/stream-broker/index-DOJoSShD.mjs +67784 -0
- package/dist/stream-broker/index-DtOI1aTU.mjs +18504 -0
- package/dist/stream-broker/{index-BvV3RVTZ.mjs → index-oMq6ilgR.mjs} +254 -268
- package/dist/stream-broker/{index-CZNxa0ad.mjs → index-vIWZQBIL.mjs} +1 -1
- package/dist/stream-broker/index.js +4666 -756
- package/dist/stream-broker/index.js.map +1 -1
- package/dist/stream-broker/index.mjs +4668 -756
- package/dist/stream-broker/index.mjs.map +1 -1
- package/dist/stream-broker/{jsx-runtime-B_evVsXl.mjs → jsx-runtime-BRT_HL0A.mjs} +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/stream-broker/{schemas-ChN4Ih0h.mjs → schemas-B7L0qZtq.mjs} +530 -515
- package/package.json +51 -3
- package/dist/index-DLHaHm6u.js.map +0 -1
- package/dist/index-asZs8U_s.mjs.map +0 -1
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +0 -19
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-B4l8Nb2y.mjs +0 -20
- package/dist/stream-broker/index-CUXiTSWS.mjs +0 -13883
- package/dist/stream-broker/index-Kb4xa8FX.mjs +0 -36403
|
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
25
|
-
const index = require("../index-
|
|
25
|
+
const index = require("../index-B36NMAdu.js");
|
|
26
26
|
const fs = require("node:fs");
|
|
27
27
|
const path = require("node:path");
|
|
28
28
|
const DEFAULT_CONFIG$1 = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { j as evaluateZoneRules, B as BaseAddon, v as motionDetectionCapability, r as hydrateSchema, D as DeviceType } from "../index-5aYef068.mjs";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
const DEFAULT_CONFIG$1 = {
|
|
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
25
|
-
const index = require("../index-
|
|
25
|
+
const index = require("../index-B36NMAdu.js");
|
|
26
26
|
const shmRing = require("@camstack/shm-ring");
|
|
27
27
|
class FrameQueue {
|
|
28
28
|
/**
|
|
@@ -137,9 +137,9 @@ class PipelineTimingSampler {
|
|
|
137
137
|
if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, []);
|
|
138
138
|
this.detSamples.get(deviceId).push(s);
|
|
139
139
|
}
|
|
140
|
-
addMotionSample(deviceId, ms) {
|
|
140
|
+
addMotionSample(deviceId, ms, frameAge = -1) {
|
|
141
141
|
if (!this.motSamples.has(deviceId)) this.motSamples.set(deviceId, []);
|
|
142
|
-
this.motSamples.get(deviceId).push(ms);
|
|
142
|
+
this.motSamples.get(deviceId).push({ ms, frameAge });
|
|
143
143
|
}
|
|
144
144
|
addAudioSample(deviceId, s) {
|
|
145
145
|
if (!this.audioSamples.has(deviceId)) this.audioSamples.set(deviceId, []);
|
|
@@ -164,6 +164,7 @@ class PipelineTimingSampler {
|
|
|
164
164
|
if (det.length === 0) continue;
|
|
165
165
|
const e2e = det.map((s) => s.endToEnd);
|
|
166
166
|
const inf = det.map((s) => s.inference);
|
|
167
|
+
const frameAge = det.map((s) => s.frameAge).filter((v) => v >= 0);
|
|
167
168
|
const totalDet = det.reduce((s, d) => s + d.detections, 0);
|
|
168
169
|
this.log.info(
|
|
169
170
|
"pipeline stats",
|
|
@@ -172,7 +173,20 @@ class PipelineTimingSampler {
|
|
|
172
173
|
meta: {
|
|
173
174
|
frames: det.length,
|
|
174
175
|
intervalSec: REPORT_INTERVAL_MS / 1e3,
|
|
176
|
+
// enqueue → emit, in ms. Stage breakdown (avg) exposes WHERE the
|
|
177
|
+
// latency sits: queue backlog vs semaphore contention vs inference
|
|
178
|
+
// vs result-emit. inference is usually the floor (model chain).
|
|
175
179
|
e2e: { avg: avg(e2e), p95: p95(e2e), max: max(e2e) },
|
|
180
|
+
// Frame age (capture→inference-pick): if large, the analyzed frame is
|
|
181
|
+
// already stale (decoder/ring behind), which delays the overlay
|
|
182
|
+
// regardless of how fast the result is delivered.
|
|
183
|
+
frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) },
|
|
184
|
+
stagesMs: {
|
|
185
|
+
queueWait: avg(det.map((s) => s.queueWait)),
|
|
186
|
+
semaphoreWait: avg(det.map((s) => s.semaphoreWait)),
|
|
187
|
+
inference: avg(inf),
|
|
188
|
+
resultToEmit: avg(det.map((s) => s.resultToEmit))
|
|
189
|
+
},
|
|
176
190
|
inference: { avg: avg(inf), p95: p95(inf) },
|
|
177
191
|
detections: totalDet,
|
|
178
192
|
dropped,
|
|
@@ -185,6 +199,8 @@ class PipelineTimingSampler {
|
|
|
185
199
|
this.detSamples.clear();
|
|
186
200
|
for (const [deviceId, mot] of this.motSamples) {
|
|
187
201
|
if (mot.length === 0) continue;
|
|
202
|
+
const ms = mot.map((s) => s.ms);
|
|
203
|
+
const frameAge = mot.map((s) => s.frameAge).filter((v) => v >= 0);
|
|
188
204
|
this.log.info(
|
|
189
205
|
"motion stats",
|
|
190
206
|
{
|
|
@@ -192,10 +208,12 @@ class PipelineTimingSampler {
|
|
|
192
208
|
meta: {
|
|
193
209
|
frames: mot.length,
|
|
194
210
|
intervalSec: REPORT_INTERVAL_MS / 1e3,
|
|
195
|
-
avg: avg(
|
|
196
|
-
p95: p95(
|
|
197
|
-
max: max(
|
|
198
|
-
//
|
|
211
|
+
avg: avg(ms),
|
|
212
|
+
p95: p95(ms),
|
|
213
|
+
max: max(ms),
|
|
214
|
+
// Frame age at motion analysis (capture→analysis). Large = stale
|
|
215
|
+
// input frame (decoder/ring behind) → motion box lags real movement.
|
|
216
|
+
frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) }
|
|
199
217
|
}
|
|
200
218
|
}
|
|
201
219
|
);
|
|
@@ -541,8 +559,7 @@ class PipelineRunner {
|
|
|
541
559
|
async processWithSemaphore(deviceId, entry, frameInput, state, streamType) {
|
|
542
560
|
const pickedAt = Date.now();
|
|
543
561
|
const { frame, handle } = entry;
|
|
544
|
-
const
|
|
545
|
-
const enqueuedAt = frame._enqueuedAt ?? captureTs;
|
|
562
|
+
const enqueuedAt = frame._enqueuedAt ?? pickedAt;
|
|
546
563
|
const release = await this.semaphore.acquire();
|
|
547
564
|
const semAcquiredAt = Date.now();
|
|
548
565
|
try {
|
|
@@ -557,13 +574,19 @@ class PipelineRunner {
|
|
|
557
574
|
if (result) {
|
|
558
575
|
await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
|
|
559
576
|
const emittedAt = Date.now();
|
|
577
|
+
const capturedAt = frame.capturedAt;
|
|
560
578
|
this.timingSampler.addSample(deviceId, {
|
|
561
|
-
captureToEnqueue: enqueuedAt - captureTs,
|
|
562
579
|
queueWait: pickedAt - enqueuedAt,
|
|
563
580
|
semaphoreWait: semAcquiredAt - pickedAt,
|
|
564
581
|
inference: inferenceMs,
|
|
565
582
|
resultToEmit: emittedAt - inferDoneAt,
|
|
566
|
-
|
|
583
|
+
// capturedAt is the shm-ring commit wall-clock; pickedAt − capturedAt
|
|
584
|
+
// is how stale the frame was when inference started. <0/absent → unknown.
|
|
585
|
+
frameAge: typeof capturedAt === "number" && capturedAt > 0 ? pickedAt - capturedAt : -1,
|
|
586
|
+
// Wall-clock pipeline latency: enqueue → result emitted. (Capture →
|
|
587
|
+
// enqueue, i.e. RTSP decode + shm-ring write, is not measured here —
|
|
588
|
+
// the shm frame carries only a PTS, no wall-clock capture stamp.)
|
|
589
|
+
endToEnd: emittedAt - enqueuedAt,
|
|
567
590
|
detections: result.detections?.length ?? 0
|
|
568
591
|
});
|
|
569
592
|
}
|
|
@@ -607,36 +630,87 @@ class PipelineRunner {
|
|
|
607
630
|
const PULL_MAX_COUNT = 4;
|
|
608
631
|
const MIN_POLL_INTERVAL_MS = 20;
|
|
609
632
|
const FALLBACK_POLL_INTERVAL_MS = 200;
|
|
633
|
+
const INITIAL_SUBSCRIBE_RETRY_BACKOFF_MS = 250;
|
|
634
|
+
const MAX_SUBSCRIBE_RETRY_BACKOFF_MS = 5e3;
|
|
610
635
|
async function startFrameHandlePoller(options) {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
636
|
+
const lifecycle = {
|
|
637
|
+
stopped: false,
|
|
638
|
+
retryTimer: void 0,
|
|
639
|
+
activeTeardown: null
|
|
640
|
+
};
|
|
641
|
+
const teardown = () => {
|
|
642
|
+
if (lifecycle.stopped) return;
|
|
643
|
+
lifecycle.stopped = true;
|
|
644
|
+
if (lifecycle.retryTimer) {
|
|
645
|
+
clearTimeout(lifecycle.retryTimer);
|
|
646
|
+
lifecycle.retryTimer = void 0;
|
|
647
|
+
}
|
|
648
|
+
lifecycle.activeTeardown?.();
|
|
649
|
+
};
|
|
650
|
+
void subscribeWithRetry(options, lifecycle);
|
|
651
|
+
return teardown;
|
|
652
|
+
}
|
|
653
|
+
async function subscribeWithRetry(options, lifecycle) {
|
|
654
|
+
const { api, brokerId, format, maxFps, tag, logger } = options;
|
|
655
|
+
let backoffMs = INITIAL_SUBSCRIBE_RETRY_BACKOFF_MS;
|
|
656
|
+
let attempt = 0;
|
|
657
|
+
while (!lifecycle.stopped) {
|
|
658
|
+
attempt += 1;
|
|
659
|
+
try {
|
|
660
|
+
const result = await api.streamBroker.subscribeFrames.mutate({
|
|
661
|
+
brokerId,
|
|
662
|
+
format,
|
|
663
|
+
maxFps,
|
|
664
|
+
tag
|
|
665
|
+
});
|
|
666
|
+
if (lifecycle.stopped) {
|
|
667
|
+
await api.streamBroker.unsubscribeFrames.mutate({ subscriptionId: result.subscriptionId }).catch((err) => {
|
|
668
|
+
logger.warn("frame-handle poller: late unsubscribe failed", {
|
|
669
|
+
meta: { brokerId, subscriptionId: result.subscriptionId, error: index.errMsg(err) }
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
lifecycle.activeTeardown = startPolling(options, result.subscriptionId, result.maxFps, lifecycle);
|
|
675
|
+
return;
|
|
676
|
+
} catch (err) {
|
|
677
|
+
if (lifecycle.stopped) return;
|
|
678
|
+
if (attempt === 1) {
|
|
679
|
+
logger.warn("frame-handle poller: subscribeFrames failed, retrying", {
|
|
680
|
+
meta: { brokerId, format, tag, error: index.errMsg(err), nextRetryInMs: backoffMs }
|
|
681
|
+
});
|
|
682
|
+
} else {
|
|
683
|
+
logger.debug("frame-handle poller: subscribeFrames still failing", {
|
|
684
|
+
meta: { brokerId, format, tag, attempt, error: index.errMsg(err), nextRetryInMs: backoffMs }
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
await sleep(backoffMs, lifecycle);
|
|
688
|
+
backoffMs = Math.min(MAX_SUBSCRIBE_RETRY_BACKOFF_MS, backoffMs * 2);
|
|
689
|
+
}
|
|
625
690
|
}
|
|
626
|
-
|
|
691
|
+
}
|
|
692
|
+
function sleep(ms, lifecycle) {
|
|
693
|
+
return new Promise((resolve) => {
|
|
694
|
+
lifecycle.retryTimer = setTimeout(() => {
|
|
695
|
+
lifecycle.retryTimer = void 0;
|
|
696
|
+
resolve();
|
|
697
|
+
}, ms);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
function startPolling(options, subscriptionId, resolvedMaxFps, lifecycle) {
|
|
701
|
+
const { api, brokerId, onFrame, logger } = options;
|
|
627
702
|
const readers = new shmRing.FrameRingReaderCache(logger);
|
|
628
|
-
const pollIntervalMs =
|
|
629
|
-
let stopped = false;
|
|
703
|
+
const pollIntervalMs = resolvedMaxFps > 0 ? Math.max(MIN_POLL_INTERVAL_MS, Math.round(1e3 / resolvedMaxFps)) : FALLBACK_POLL_INTERVAL_MS;
|
|
630
704
|
let timer;
|
|
631
705
|
const tick = async () => {
|
|
632
|
-
if (stopped) return;
|
|
706
|
+
if (lifecycle.stopped) return;
|
|
633
707
|
try {
|
|
634
708
|
const handles = await api.streamBroker.pullFrameHandles.query({
|
|
635
709
|
subscriptionId,
|
|
636
710
|
maxCount: PULL_MAX_COUNT
|
|
637
711
|
});
|
|
638
712
|
for (const handle of handles) {
|
|
639
|
-
if (stopped) break;
|
|
713
|
+
if (lifecycle.stopped) break;
|
|
640
714
|
const frame = readers.read(handle);
|
|
641
715
|
if (frame) onFrame(frame, handle);
|
|
642
716
|
}
|
|
@@ -645,14 +719,12 @@ async function startFrameHandlePoller(options) {
|
|
|
645
719
|
meta: { brokerId, subscriptionId, error: index.errMsg(err) }
|
|
646
720
|
});
|
|
647
721
|
}
|
|
648
|
-
if (!stopped) {
|
|
722
|
+
if (!lifecycle.stopped) {
|
|
649
723
|
timer = setTimeout(() => void tick(), pollIntervalMs);
|
|
650
724
|
}
|
|
651
725
|
};
|
|
652
726
|
void tick();
|
|
653
727
|
return () => {
|
|
654
|
-
if (stopped) return;
|
|
655
|
-
stopped = true;
|
|
656
728
|
if (timer) {
|
|
657
729
|
clearTimeout(timer);
|
|
658
730
|
timer = void 0;
|
|
@@ -754,6 +826,11 @@ const DEFAULT_CONFIG = {
|
|
|
754
826
|
targetLoadPercent: 80,
|
|
755
827
|
minThrottledFps: 1
|
|
756
828
|
};
|
|
829
|
+
function shouldStartOnboardAnalyzer(config) {
|
|
830
|
+
if (!config.onboardMotionDrivesAnalyzer) return false;
|
|
831
|
+
if (config.motionSources.includes("analyzer")) return false;
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
757
834
|
function toFrameInput(frame) {
|
|
758
835
|
return {
|
|
759
836
|
data: frame.data,
|
|
@@ -763,14 +840,12 @@ function toFrameInput(frame) {
|
|
|
763
840
|
timestamp: frame.timestamp
|
|
764
841
|
};
|
|
765
842
|
}
|
|
766
|
-
const STEP_LOG_INTERVAL_MS = 3e4;
|
|
767
843
|
const METRICS_SNAPSHOT_INTERVAL_MS = 1e3;
|
|
768
844
|
const METRICS_HEARTBEAT_MS = 3e4;
|
|
769
845
|
class PipelineRunnerAddon extends index.BaseAddon {
|
|
770
846
|
runner = null;
|
|
771
847
|
attached = /* @__PURE__ */ new Map();
|
|
772
848
|
nodeId = "unknown";
|
|
773
|
-
stepLogTimer = null;
|
|
774
849
|
metricsSnapshotTimer = null;
|
|
775
850
|
unsubMotionEvents = null;
|
|
776
851
|
/** Last analyzer-detected state per device — gates the
|
|
@@ -784,6 +859,20 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
784
859
|
* detach.
|
|
785
860
|
*/
|
|
786
861
|
lastMotionAt = /* @__PURE__ */ new Map();
|
|
862
|
+
/**
|
|
863
|
+
* Dynamic analyzer subscriptions opened on `MotionOnMotionChanged
|
|
864
|
+
* source:'onboard'` when `onboardMotionDrivesAnalyzer === true`. Each
|
|
865
|
+
* entry is the unsubscribe handle returned by `subscribeMotionFrames`.
|
|
866
|
+
* Cleared on teardown timer fire, detach, and shutdown.
|
|
867
|
+
*/
|
|
868
|
+
onboardAnalyzerSubs = /* @__PURE__ */ new Map();
|
|
869
|
+
/**
|
|
870
|
+
* Teardown timers that close the dynamic analyzer subscription after
|
|
871
|
+
* `motionCooldownMs` without a new motion event. Re-armed on every
|
|
872
|
+
* `MotionOnMotionChanged source:'onboard'` call so the sub stays open
|
|
873
|
+
* while motion persists.
|
|
874
|
+
*/
|
|
875
|
+
onboardAnalyzerTeardownTimers = /* @__PURE__ */ new Map();
|
|
787
876
|
/**
|
|
788
877
|
* Snapshot-equality cache for metrics-snapshot defer. The runner
|
|
789
878
|
* fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;
|
|
@@ -848,6 +937,9 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
848
937
|
const attachment = this.attached.get(deviceId);
|
|
849
938
|
if (!attachment) return;
|
|
850
939
|
const source = data.source;
|
|
940
|
+
if (source === "onboard") {
|
|
941
|
+
void this.handleOnboardMotionAnalyzer(deviceId, data.detected);
|
|
942
|
+
}
|
|
851
943
|
if (!attachment.config.motionSources.includes(source)) return;
|
|
852
944
|
this.runner?.reportMotion(
|
|
853
945
|
deviceId,
|
|
@@ -858,7 +950,6 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
858
950
|
}
|
|
859
951
|
);
|
|
860
952
|
}
|
|
861
|
-
this.stepLogTimer = setInterval(() => this.logAttachedSteps(), STEP_LOG_INTERVAL_MS);
|
|
862
953
|
this.metricsSnapshotTimer = setInterval(
|
|
863
954
|
() => this.emitMetricsSnapshot(),
|
|
864
955
|
METRICS_SNAPSHOT_INTERVAL_MS
|
|
@@ -878,10 +969,6 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
878
969
|
clearInterval(this.metricsSnapshotTimer);
|
|
879
970
|
this.metricsSnapshotTimer = null;
|
|
880
971
|
}
|
|
881
|
-
if (this.stepLogTimer) {
|
|
882
|
-
clearInterval(this.stepLogTimer);
|
|
883
|
-
this.stepLogTimer = null;
|
|
884
|
-
}
|
|
885
972
|
if (this.benchFrameSweeper) {
|
|
886
973
|
clearInterval(this.benchFrameSweeper);
|
|
887
974
|
this.benchFrameSweeper = null;
|
|
@@ -892,6 +979,9 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
892
979
|
this.unsubMotionEvents = null;
|
|
893
980
|
}
|
|
894
981
|
this.lastAnalyzerDetected.clear();
|
|
982
|
+
for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) {
|
|
983
|
+
this.clearOnboardAnalyzer(deviceId);
|
|
984
|
+
}
|
|
895
985
|
if (this.runner) {
|
|
896
986
|
this.runner.stop();
|
|
897
987
|
this.runner = null;
|
|
@@ -1278,54 +1368,10 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
1278
1368
|
this.runner?.reportMotion(input.deviceId, input.detected, input.source, input.regions);
|
|
1279
1369
|
return { success: true };
|
|
1280
1370
|
}
|
|
1281
|
-
/**
|
|
1282
|
-
* Periodic per-camera step roster dump. Once every
|
|
1283
|
-
* STEP_LOG_INTERVAL_MS (30s) emits one log line per attached camera
|
|
1284
|
-
* with the configured detection step tree + audio classifier branch
|
|
1285
|
-
* so an operator looking at the agent log can quickly see what each
|
|
1286
|
-
* camera is currently running without crossing tRPC. Skips when no
|
|
1287
|
-
* cameras are attached so quiet dev runs stay silent.
|
|
1288
|
-
*/
|
|
1289
|
-
logAttachedSteps() {
|
|
1290
|
-
if (this.attached.size === 0) return;
|
|
1291
|
-
for (const [deviceId, attachment] of this.attached) {
|
|
1292
|
-
const cfg = attachment.config;
|
|
1293
|
-
const detectionSteps = cfg.steps && cfg.steps.length > 0 ? this.flattenSteps(cfg.steps).filter((s) => s.enabled) : [];
|
|
1294
|
-
const detectionLabel = detectionSteps.length > 0 ? detectionSteps.map((s) => `${s.addonId}/${s.modelId}`).join(" → ") : "<none>";
|
|
1295
|
-
const audioLabel = cfg.audio && cfg.audio.enabled ? `${cfg.audio.engine.runtime}/${cfg.audio.engine.backend}/${cfg.audio.modelId}` : "<off>";
|
|
1296
|
-
const engineLabel = cfg.engine ? `${cfg.engine.runtime}/${cfg.engine.backend}${cfg.engine.device ? `/${cfg.engine.device}` : ""}` : "<unset>";
|
|
1297
|
-
this.ctx.logger.info("Camera pipeline roster", {
|
|
1298
|
-
tags: { deviceId },
|
|
1299
|
-
meta: {
|
|
1300
|
-
phase: "roster",
|
|
1301
|
-
intervalSec: STEP_LOG_INTERVAL_MS / 1e3,
|
|
1302
|
-
pipelineEnabled: cfg.pipelineEnabled,
|
|
1303
|
-
motionSources: cfg.motionSources,
|
|
1304
|
-
motionFps: cfg.motionFps,
|
|
1305
|
-
detectionFps: cfg.detectionFps,
|
|
1306
|
-
engine: engineLabel,
|
|
1307
|
-
videoSteps: detectionLabel,
|
|
1308
|
-
videoStepCount: detectionSteps.length,
|
|
1309
|
-
audio: audioLabel
|
|
1310
|
-
}
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
/** Recursively flatten the step tree → ordered list of every node. */
|
|
1315
|
-
flattenSteps(steps) {
|
|
1316
|
-
const out = [];
|
|
1317
|
-
const walk = (s) => {
|
|
1318
|
-
out.push(s);
|
|
1319
|
-
if (s.children) {
|
|
1320
|
-
for (const c of s.children) walk(c);
|
|
1321
|
-
}
|
|
1322
|
-
};
|
|
1323
|
-
for (const s of steps) walk(s);
|
|
1324
|
-
return out;
|
|
1325
|
-
}
|
|
1326
1371
|
detachInternal(deviceId) {
|
|
1327
1372
|
const attachment = this.attached.get(deviceId);
|
|
1328
1373
|
if (!attachment) return;
|
|
1374
|
+
this.clearOnboardAnalyzer(deviceId);
|
|
1329
1375
|
attachment.motionUnsubscribe?.();
|
|
1330
1376
|
attachment.detectionUnsubscribe?.();
|
|
1331
1377
|
this.attached.delete(deviceId);
|
|
@@ -1334,6 +1380,70 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
1334
1380
|
this.runner?.unregisterCamera(deviceId);
|
|
1335
1381
|
this.ctx?.logger.info("detachCamera", { tags: { deviceId } });
|
|
1336
1382
|
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Synchronously cancel the teardown timer and call the unsubscribe
|
|
1385
|
+
* handle for the dynamic onboard analyzer, if one is open. Safe to
|
|
1386
|
+
* call when no subscription exists.
|
|
1387
|
+
*/
|
|
1388
|
+
clearOnboardAnalyzer(deviceId) {
|
|
1389
|
+
const timer = this.onboardAnalyzerTeardownTimers.get(deviceId);
|
|
1390
|
+
if (timer !== void 0) {
|
|
1391
|
+
clearTimeout(timer);
|
|
1392
|
+
this.onboardAnalyzerTeardownTimers.delete(deviceId);
|
|
1393
|
+
}
|
|
1394
|
+
const unsub = this.onboardAnalyzerSubs.get(deviceId);
|
|
1395
|
+
if (unsub !== void 0) {
|
|
1396
|
+
try {
|
|
1397
|
+
unsub();
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
this.onboardAnalyzerSubs.delete(deviceId);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Dynamic analyzer gate for onboard-motion cameras.
|
|
1405
|
+
*
|
|
1406
|
+
* Called from the `MotionOnMotionChanged` subscriber whenever
|
|
1407
|
+
* `source === 'onboard'`. Opens a `subscribeMotionFrames` subscription
|
|
1408
|
+
* the first time motion is detected (idempotent — a second `detected:true`
|
|
1409
|
+
* while the sub is already open is a no-op). Always re-arms the teardown
|
|
1410
|
+
* timer so the subscription stays open as long as motion events keep
|
|
1411
|
+
* arriving and tears down `motionCooldownMs` after the last event.
|
|
1412
|
+
*
|
|
1413
|
+
* No-op when:
|
|
1414
|
+
* - The camera is not currently attached.
|
|
1415
|
+
* - `shouldStartOnboardAnalyzer(config)` returns false (flag off or
|
|
1416
|
+
* `motionSources` already includes `'analyzer'`).
|
|
1417
|
+
*/
|
|
1418
|
+
async handleOnboardMotionAnalyzer(deviceId, detected) {
|
|
1419
|
+
const attachment = this.attached.get(deviceId);
|
|
1420
|
+
if (!attachment) return;
|
|
1421
|
+
const config = attachment.config;
|
|
1422
|
+
if (!shouldStartOnboardAnalyzer(config)) return;
|
|
1423
|
+
const log = this.ctx.logger.withTags({ deviceId });
|
|
1424
|
+
if (detected && !this.onboardAnalyzerSubs.has(deviceId)) {
|
|
1425
|
+
const unsub = await this.subscribeMotionFrames(config);
|
|
1426
|
+
if (unsub) {
|
|
1427
|
+
this.onboardAnalyzerSubs.set(deviceId, unsub);
|
|
1428
|
+
log.debug("onboard-analyzer: opened motion-frame subscription");
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
const existing = this.onboardAnalyzerTeardownTimers.get(deviceId);
|
|
1432
|
+
if (existing !== void 0) clearTimeout(existing);
|
|
1433
|
+
const cooldownMs = config.motionCooldownMs;
|
|
1434
|
+
const timer = setTimeout(() => {
|
|
1435
|
+
this.onboardAnalyzerTeardownTimers.delete(deviceId);
|
|
1436
|
+
const unsub = this.onboardAnalyzerSubs.get(deviceId);
|
|
1437
|
+
if (!unsub) return;
|
|
1438
|
+
try {
|
|
1439
|
+
unsub();
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
this.onboardAnalyzerSubs.delete(deviceId);
|
|
1443
|
+
log.debug("onboard-analyzer: closed after motion cooldown", { meta: { cooldownMs } });
|
|
1444
|
+
}, cooldownMs);
|
|
1445
|
+
this.onboardAnalyzerTeardownTimers.set(deviceId, timer);
|
|
1446
|
+
}
|
|
1337
1447
|
async getLocalLoad() {
|
|
1338
1448
|
const metrics = this.runner?.getMetrics() ?? { avgInferenceTimeMs: 0, queueDepth: 0 };
|
|
1339
1449
|
const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? [];
|
|
@@ -1382,7 +1492,7 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
1382
1492
|
}
|
|
1383
1493
|
return startFrameHandlePoller({
|
|
1384
1494
|
api,
|
|
1385
|
-
brokerId:
|
|
1495
|
+
brokerId: index.makeSourceBrokerId(config.deviceId, config.motionStreamId),
|
|
1386
1496
|
format: "gray",
|
|
1387
1497
|
maxFps: config.motionFps,
|
|
1388
1498
|
tag: "motion",
|
|
@@ -1468,7 +1578,7 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
1468
1578
|
}
|
|
1469
1579
|
return startFrameHandlePoller({
|
|
1470
1580
|
api,
|
|
1471
|
-
brokerId:
|
|
1581
|
+
brokerId: index.makeSourceBrokerId(config.deviceId, config.detectionStreamId),
|
|
1472
1582
|
format: "rgb",
|
|
1473
1583
|
maxFps: config.detectionFps,
|
|
1474
1584
|
tag: "detection",
|
|
@@ -1590,20 +1700,24 @@ class PipelineRunnerAddon extends index.BaseAddon {
|
|
|
1590
1700
|
zonesPayload
|
|
1591
1701
|
));
|
|
1592
1702
|
}
|
|
1593
|
-
|
|
1703
|
+
const capturedAt = frame.capturedAt;
|
|
1704
|
+
const motionFrameAge = typeof capturedAt === "number" && capturedAt > 0 ? motionStart - capturedAt : -1;
|
|
1705
|
+
runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart, motionFrameAge);
|
|
1594
1706
|
} catch (error) {
|
|
1595
1707
|
const msg = index.errMsg(error);
|
|
1596
1708
|
log.error("runMotionAnalysis failed", { meta: { error: msg } });
|
|
1597
1709
|
}
|
|
1598
1710
|
}
|
|
1599
|
-
emitInferenceResult(deviceId,
|
|
1711
|
+
emitInferenceResult(deviceId, frame, result, handle) {
|
|
1600
1712
|
const ctx = this.ctx;
|
|
1601
1713
|
if (!ctx?.eventBus) return;
|
|
1714
|
+
const capturedAt = frame.capturedAt;
|
|
1602
1715
|
const payload = {
|
|
1603
1716
|
deviceId,
|
|
1604
1717
|
frame: result,
|
|
1605
1718
|
nodeId: this.nodeId,
|
|
1606
|
-
frameHandle: handle
|
|
1719
|
+
frameHandle: handle,
|
|
1720
|
+
...typeof capturedAt === "number" && capturedAt > 0 ? { capturedAt } : {}
|
|
1607
1721
|
};
|
|
1608
1722
|
this.ctx.eventBus.emit(index.createEvent(
|
|
1609
1723
|
index.EventCategory.PipelineInferenceResult,
|
|
@@ -1744,4 +1858,5 @@ exports.PipelineTimingSampler = PipelineTimingSampler;
|
|
|
1744
1858
|
exports.Semaphore = Semaphore;
|
|
1745
1859
|
exports.customActions = pipelineRunnerBenchActions;
|
|
1746
1860
|
exports.default = PipelineRunnerAddon;
|
|
1861
|
+
exports.shouldStartOnboardAnalyzer = shouldStartOnboardAnalyzer;
|
|
1747
1862
|
//# sourceMappingURL=index.js.map
|