@camstack/addon-pipeline 0.1.11 → 0.1.12
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/assets/icon.svg +6 -0
- package/dist/audio-analyzer/index.js +16 -16
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/audio-codec-nodeav/index.js +7 -7
- package/dist/audio-codec-nodeav/index.mjs +1 -1
- package/dist/decoder-nodeav/index.js +12 -12
- package/dist/decoder-nodeav/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +45 -45
- package/dist/detection-pipeline/index.js.map +1 -1
- package/dist/detection-pipeline/index.mjs +1 -1
- package/dist/index-BhOycEVH.js +13867 -0
- package/dist/index-BhOycEVH.js.map +1 -0
- package/dist/index-FxfFGsiL.mjs +13868 -0
- package/dist/index-FxfFGsiL.mjs.map +1 -0
- package/dist/motion-wasm/index.js +8 -8
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +76 -77
- package/dist/pipeline-runner/index.js.map +1 -1
- package/dist/pipeline-runner/index.mjs +50 -51
- package/dist/pipeline-runner/index.mjs.map +1 -1
- 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_ui_mf_2_library__loadShare__.mjs-BvJPhiY_.mjs +20 -0
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BuV9ar3i.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Czwg8GUO.mjs} +6 -6
- package/dist/stream-broker/{hostInit-CjVI5LuK.mjs → hostInit-fG6oFw4t.mjs} +6 -6
- package/dist/stream-broker/{index-BF5Qr03x.mjs → index-BOmtakNy.mjs} +66 -43
- package/dist/stream-broker/{index-DUJwOcGq.mjs → index-Bpv0NSqI.mjs} +1733 -1569
- package/dist/stream-broker/{index-DuBCn5us.mjs → index-l13fl8lu.mjs} +378 -373
- package/dist/stream-broker/index.js +100 -100
- package/dist/stream-broker/index.js.map +1 -1
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +24 -9
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-DbMNirr7.mjs +0 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../src/pipeline-runner/frame-queue.ts","../../src/pipeline-runner/semaphore.ts","../../src/pipeline-runner/timing-sampler.ts","../../src/pipeline-runner/runner.ts","../../src/pipeline-runner/bench-actions.ts","../../src/pipeline-runner/index.ts"],"sourcesContent":["import type { DecodedFrame } from '@camstack/types'\n\n/**\n * Latest-frame-only buffer. Keeps only the most recent frame, dropping all\n * older frames immediately. This ensures inference always runs on the\n * freshest available frame, never accumulating a backlog regardless of\n * inference latency.\n */\nexport class FrameQueue {\n private latest: DecodedFrame | null = null\n private _droppedFrames = 0\n\n constructor(readonly maxSize: number) {}\n\n enqueue(frame: DecodedFrame): void {\n if (this.latest !== null) {\n this._droppedFrames++\n }\n this.latest = frame\n }\n\n dequeue(): DecodedFrame | undefined {\n const frame = this.latest ?? undefined\n this.latest = null\n return frame\n }\n\n get size(): number {\n return this.latest !== null ? 1 : 0\n }\n\n get droppedFrames(): number {\n return this._droppedFrames\n }\n\n clear(): void {\n this.latest = null\n }\n}\n","/**\n * Counting semaphore with FIFO waiter queue. Used by the runner to bound\n * concurrent inference invocations across all attached cameras.\n *\n * The concurrency limit is **mutable** via `resize()` so the\n * pipeline-runner addon can hot-reload `maxConcurrentInferences` without\n * tearing down and restarting the runner. In-flight permits are\n * preserved: shrinking the limit just lowers the headroom until existing\n * releases catch up; growing it wakes pending waiters immediately.\n */\nexport class Semaphore {\n private _concurrency: number\n private _available: number\n private readonly waiters: Array<() => void> = []\n\n constructor(concurrency: number) {\n this._concurrency = concurrency\n this._available = concurrency\n }\n\n get concurrency(): number {\n return this._concurrency\n }\n\n get available(): number {\n return this._available\n }\n\n /**\n * Change the concurrency limit at runtime. Growing wakes as many\n * pending waiters as possible without exceeding the new headroom;\n * shrinking simply caps `_available` to `max(0, _available + delta)`.\n * In-flight permits are never revoked — the excess will drain\n * naturally as existing callers release.\n */\n resize(newConcurrency: number): void {\n if (newConcurrency < 1) throw new Error('Semaphore: concurrency must be >= 1')\n const delta = newConcurrency - this._concurrency\n this._concurrency = newConcurrency\n this._available = Math.max(0, this._available + delta)\n // If we just grew, wake as many waiters as new headroom allows.\n while (this._available > 0 && this.waiters.length > 0) {\n const next = this.waiters.shift()\n if (next) next()\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this._available > 0) {\n this._available--\n return () => this.release()\n }\n\n return new Promise<() => void>((resolve) => {\n this.waiters.push(() => {\n this._available--\n resolve(() => this.release())\n })\n })\n }\n\n private release(): void {\n this._available++\n const next = this.waiters.shift()\n if (next) next()\n }\n}\n","import type { IScopedLogger } from '@camstack/types'\n\nconst REPORT_INTERVAL_MS = 10_000\n\n/**\n * Audio sample shape consumed by the timing sampler. Pushed by the runner's\n * audio subscription handler so that the periodic timing log includes audio\n * classification stats alongside motion + detection stats.\n */\nexport interface AudioSample {\n readonly classifyMs: number\n readonly dbfs: number\n readonly topLabel: string | null\n readonly topScore: number\n}\n\n/**\n * Runtime info labels for the timing sampler — purely cosmetic, used to tag\n * the periodic timing log lines with the active engine + addon names.\n */\nexport interface RuntimeInfo {\n readonly pipelineRuntime?: string\n readonly pipelineModels?: readonly string[]\n readonly motionAddon?: string\n readonly audioEngine?: string\n}\n\ninterface PhaseSample {\n /** ms from frame.timestamp (camera capture) to enqueue */\n captureToEnqueue: number\n /** ms waiting in queue before being picked */\n queueWait: number\n /** ms waiting to acquire semaphore */\n semaphoreWait: number\n /** ms for the full processFrame call (IPC + inference) */\n inference: number\n /** ms from result ready to event emitted */\n resultToEmit: number\n /** total end-to-end: capture to event emitted */\n endToEnd: number\n /** number of detections returned */\n detections: number\n}\n\n/**\n * Periodic timing-stats sampler. Accumulates per-camera samples for the\n * detection pipeline (capture → enqueue → semaphore → inference → emit),\n * motion analysis, and audio classification, and logs aggregated stats\n * every REPORT_INTERVAL_MS.\n */\nexport class PipelineTimingSampler {\n private readonly detSamples = new Map<number, PhaseSample[]>()\n private readonly motSamples = new Map<number, number[]>()\n private readonly audioSamples = new Map<number, AudioSample[]>()\n private droppedFrames = 0\n private reportTimer: ReturnType<typeof setInterval> | null = null\n private log: IScopedLogger | null = null\n runtimeInfo: RuntimeInfo = {}\n\n setLogger(logger: IScopedLogger): void {\n this.log = logger\n }\n\n start(): void {\n if (this.reportTimer) return\n this.reportTimer = setInterval(() => this.report(), REPORT_INTERVAL_MS)\n }\n\n stop(): void {\n if (this.reportTimer) {\n clearInterval(this.reportTimer)\n this.reportTimer = null\n }\n }\n\n addSample(deviceId: number, s: PhaseSample): void {\n if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, [])\n this.detSamples.get(deviceId)!.push(s)\n }\n\n addMotionSample(deviceId: number, ms: number): void {\n if (!this.motSamples.has(deviceId)) this.motSamples.set(deviceId, [])\n this.motSamples.get(deviceId)!.push(ms)\n }\n\n addAudioSample(deviceId: number, s: AudioSample): void {\n if (!this.audioSamples.has(deviceId)) this.audioSamples.set(deviceId, [])\n this.audioSamples.get(deviceId)!.push(s)\n }\n\n addDrop(): void {\n this.droppedFrames++\n }\n\n private report(): void {\n if (!this.log) return\n\n const dropped = this.droppedFrames\n this.droppedFrames = 0\n\n const avg = (arr: number[]): number => arr.length > 0 ? Math.round(arr.reduce((a, b) => a + b, 0) / arr.length) : 0\n const max = (arr: number[]): number => arr.length > 0 ? Math.round(Math.max(...arr)) : 0\n const p95 = (arr: number[]): number => {\n if (arr.length === 0) return 0\n const sorted = [...arr].sort((a, b) => a - b)\n return Math.round(sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1]!)\n }\n\n const rt = this.runtimeInfo\n\n for (const [deviceId, det] of this.detSamples) {\n if (det.length === 0) continue\n const e2e = det.map((s) => s.endToEnd)\n const inf = det.map((s) => s.inference)\n const totalDet = det.reduce((s, d) => s + d.detections, 0)\n\n this.log.info(\n 'pipeline stats',\n {\n tags: { deviceId },\n meta: {\n frames: det.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n e2e: { avg: avg(e2e), p95: p95(e2e), max: max(e2e) },\n inference: { avg: avg(inf), p95: p95(inf) },\n detections: totalDet,\n dropped,\n pipelineRuntime: rt.pipelineRuntime ?? null,\n pipelineModels: rt.pipelineModels ?? null,\n },\n },\n )\n }\n this.detSamples.clear()\n\n for (const [deviceId, mot] of this.motSamples) {\n if (mot.length === 0) continue\n this.log.info(\n 'motion stats',\n {\n tags: { deviceId },\n meta: {\n frames: mot.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n avg: avg(mot),\n p95: p95(mot),\n max: max(mot),\n // motionAddon: rt.motionAddon ?? null,\n },\n },\n )\n }\n this.motSamples.clear()\n\n for (const [deviceId, aud] of this.audioSamples) {\n if (aud.length === 0) continue\n const classifyTimes = aud.filter((a) => a.classifyMs > 0).map((a) => a.classifyMs)\n const classified = aud.filter((a) => a.topLabel !== null)\n const topLabels = new Map<string, number>()\n for (const a of classified) {\n if (a.topLabel) topLabels.set(a.topLabel, (topLabels.get(a.topLabel) ?? 0) + 1)\n }\n const topSummary = [...topLabels.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([l, c]) => `${l}×${c}`).join(', ')\n const avgDbfs = avg(aud.map((a) => Math.round(a.dbfs)))\n\n this.log.info(\n 'audio stats',\n {\n tags: { deviceId },\n meta: {\n chunks: aud.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n classified: classified.length,\n classifyAvgMs: classifyTimes.length > 0 ? avg(classifyTimes) : 0,\n avgDbfs,\n topLabels: topSummary,\n audioEngine: rt.audioEngine ?? null,\n },\n },\n )\n }\n this.audioSamples.clear()\n }\n}\n","import type {\n FrameInput, FrameResult, OrchestratorMetrics, CameraMetrics,\n DecodedFrame, CameraPhase, IScopedLogger,\n MotionSource, MotionRegion,\n PipelinePhaseMode,\n} from '@camstack/types'\nimport { FrameQueue } from './frame-queue.js'\nimport { Semaphore } from './semaphore.js'\nimport { PipelineTimingSampler } from './timing-sampler.js'\n\nexport type StreamType = 'motion' | 'detection'\n\n/**\n * Metadata accompanying a phase transition emitted by the runner.\n * The addon wrapping the scheduler turns this into a `motion`\n * cap-state slice + an `onMotionChanged` bus event.\n */\nexport interface PhaseTransitionMeta {\n readonly source: MotionSource\n readonly regions?: ReadonlyArray<MotionRegion>\n readonly timestamp: number\n readonly cooldownMs: number\n}\n\nexport interface RunnerConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n /** Run inference on a detection frame (heavy, semaphore-limited). */\n readonly processFrame: (deviceId: number, frame: FrameInput) => Promise<FrameResult | null>\n /** Analyze a motion frame for activity (lightweight, no semaphore). */\n readonly analyzeMotion: (deviceId: number, frame: DecodedFrame) => Promise<void>\n /**\n * Fired on every phase transition (`watching ↔ active`). The\n * surrounding addon hooks this to (1) write the device's `motion`\n * cap-state slice via the unified `device-state.setCapSlice`\n * entrypoint and (2) emit `motion.onMotionChanged` on the bus.\n * Optional — test harnesses skip it.\n */\n readonly onPhaseChanged?: (\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ) => void\n /** Optional scoped logger; when provided, motion-gate transitions log here. */\n readonly logger?: IScopedLogger\n}\n\n/**\n * Per-camera registration. Mirrors the public RunnerCameraConfig but\n * narrowed to the fields the scheduler core actually needs (mode, fps,\n * cooldown). Broker subscription details are owned by the addon class\n * wrapping this scheduler — they don't reach the scheduler itself.\n */\nexport interface CameraRegistration {\n /** Operator-facing scheduling mode for the SW detection pipeline.\n * `'disabled'` — scheduler stays in `'idle'`, never processes frames.\n * `'always-on'` — initial phase `'active'`, no motion-gate fallback.\n * `'on-motion'` — initial phase `'watching'`, motion-gated. */\n readonly detectionMode: PipelinePhaseMode\n readonly fps: number\n readonly motionCooldownMs?: number\n}\n\nexport type ResultCallback = (\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n) => Promise<void> | void\n\nexport type DetectionStreamHandler = (\n deviceId: number,\n action: 'subscribe' | 'unsubscribe',\n) => void\n\ninterface CameraState {\n readonly registration: CameraRegistration\n readonly motionQueue: FrameQueue\n readonly detectionQueue: FrameQueue\n readonly inferenceTimes: number[]\n processedCount: number\n startTime: number\n phase: CameraPhase\n motionCooldownTimer: ReturnType<typeof setTimeout> | null\n /**\n * Source that armed the current cooldown timer. Carried into the\n * transition-to-watching event so consumers know which path was\n * the last positive signal before timeout.\n */\n lastArmedSource: MotionSource | null\n /** Regions from the most recent positive `analyzer` event — undefined for `onboard`. */\n lastArmedRegions: ReadonlyArray<MotionRegion> | undefined\n}\n\n// Fallback cooldown when the runner registration omits an explicit\n// `motionCooldownMs`. Aligned with the schema default in\n// `pipeline-runner.cap.ts` (motionCooldownMsField.default) so\n// always-on / synthetic registrations get the same window the\n// device-settings schema serves to operator-driven attaches.\nconst DEFAULT_MOTION_COOLDOWN_MS = 30_000\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/**\n * Pipeline runner scheduler — the per-node detection scheduler.\n *\n * Owns per-camera frame queues, the inference semaphore, the round-robin\n * scheduler, and the motion → detection phase machine. Inference and\n * motion analysis are delegated to caller-provided callbacks injected at\n * construction time so the scheduler stays decoupled from the surrounding\n * addon ecosystem (the addon class wrapping this scheduler injects\n * callbacks that resolve to the local pipeline-executor / motion-detection\n * caps when running in production).\n *\n * This class is intentionally framework-agnostic and side-effect-free\n * outside the callbacks — it can be unit tested with mock callbacks\n * without spinning up Moleculer or the addon framework.\n */\nexport class PipelineRunner {\n // Config is mutable (not `readonly`) because `updateLimits()` hot-reloads\n // the four tuning fields when the pipeline-runner addon's\n // `updateAddonSettings` is invoked via the new three-level settings API.\n // The callbacks (`processFrame`, `analyzeMotion`) are invariants captured\n // at construction and never changed.\n private config: RunnerConfig\n private readonly cameras = new Map<number, CameraState>()\n private readonly semaphore: Semaphore\n private readonly resultCallbacks: ResultCallback[] = []\n private readonly defaultRoundRobinKeys: number[] = []\n private defaultRoundRobinIndex = 0\n private intervalHandle: ReturnType<typeof setInterval> | null = null\n private detectionStreamHandler: DetectionStreamHandler | null = null\n private readonly logger: IScopedLogger | undefined\n readonly timingSampler = new PipelineTimingSampler()\n\n constructor(config: RunnerConfig) {\n this.config = config\n this.logger = config.logger\n this.semaphore = new Semaphore(config.maxConcurrentInferences)\n }\n\n /**\n * Hot-reload the four tuning fields without tearing down the runner.\n * - `maxConcurrentInferences`: resized on the live semaphore; in-flight\n * permits are preserved, new capacity is available immediately.\n * - `maxQueueDepth`: new `FrameQueue`s created from this point on use\n * the updated ceiling. Existing per-camera queues are not resized\n * (the FrameQueue implementation is latest-only and ignores maxSize\n * anyway — see `frame-queue.ts` — so the field is effectively a\n * metadata hint for observability).\n * - `targetLoadPercent` / `minThrottledFps`: stored for future\n * throttling logic (not yet consumed in the current runner body).\n *\n * Only keys present in the patch are overwritten; unspecified keys\n * retain their current value. Any illegal combination (e.g.\n * concurrency < 1) throws and leaves the runner unchanged.\n */\n updateLimits(patch: {\n readonly maxQueueDepth?: number\n readonly maxConcurrentInferences?: number\n readonly targetLoadPercent?: number\n readonly minThrottledFps?: number\n }): void {\n const next: RunnerConfig = {\n ...this.config,\n maxQueueDepth: patch.maxQueueDepth ?? this.config.maxQueueDepth,\n maxConcurrentInferences: patch.maxConcurrentInferences ?? this.config.maxConcurrentInferences,\n targetLoadPercent: patch.targetLoadPercent ?? this.config.targetLoadPercent,\n minThrottledFps: patch.minThrottledFps ?? this.config.minThrottledFps,\n }\n if (next.maxConcurrentInferences !== this.config.maxConcurrentInferences) {\n this.semaphore.resize(next.maxConcurrentInferences)\n }\n this.config = next\n }\n\n /** Read the current tuning fields for diagnostics / tests. */\n getLimits(): {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n } {\n return {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n }\n }\n\n /** Set a handler called when the runner needs to subscribe/unsubscribe the detection stream. */\n onDetectionStreamChange(handler: DetectionStreamHandler): void {\n this.detectionStreamHandler = handler\n }\n\n registerCamera(deviceId: number, registration: CameraRegistration): void {\n const motionQueue = new FrameQueue(this.config.maxQueueDepth)\n const detectionQueue = new FrameQueue(this.config.maxQueueDepth)\n // Map the operator-facing detectionMode to the scheduler's initial\n // phase. `'disabled'` short-circuits everything (no subscribe, no\n // round-robin slot, no detection queue activity).\n const initialPhase: CameraPhase = registration.detectionMode === 'disabled'\n ? 'idle'\n : registration.detectionMode === 'always-on'\n ? 'active'\n : 'watching'\n\n const state: CameraState = {\n registration,\n motionQueue,\n detectionQueue,\n inferenceTimes: [],\n processedCount: 0,\n startTime: Date.now(),\n phase: initialPhase,\n motionCooldownTimer: null,\n lastArmedSource: null,\n lastArmedRegions: undefined,\n }\n this.cameras.set(deviceId, state)\n\n // Round-robin scheduling applies only to `on-motion`. `always-on`\n // doesn't need a slot (always at the front of the queue).\n // `disabled` skips entirely.\n if (registration.detectionMode === 'on-motion') {\n this.defaultRoundRobinKeys.push(deviceId)\n }\n\n if (initialPhase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n // Always-on cameras start in `active` immediately — emit the\n // synthetic ON transition so consumers (motion cap-state +\n // bus event) see the same shape the gated path produces.\n const cooldownMs = registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source: 'analyzer',\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n // `idle` (detectionMode='disabled') intentionally never emits a\n // phase event or subscribes — the runner registered the camera\n // for accounting but nothing flows.\n }\n\n unregisterCamera(deviceId: number): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n }\n\n state.motionQueue.clear()\n state.detectionQueue.clear()\n this.cameras.delete(deviceId)\n\n const idx = this.defaultRoundRobinKeys.indexOf(deviceId)\n if (idx !== -1) {\n this.defaultRoundRobinKeys.splice(idx, 1)\n if (this.defaultRoundRobinIndex >= this.defaultRoundRobinKeys.length) {\n this.defaultRoundRobinIndex = 0\n }\n }\n }\n\n enqueueMotionFrame(deviceId: number, frame: DecodedFrame): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n state.motionQueue.enqueue(frame)\n }\n\n enqueueDetectionFrame(deviceId: number, frame: DecodedFrame): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n if (state.phase !== 'active') return\n ;(frame as { _enqueuedAt?: number })._enqueuedAt = Date.now()\n state.detectionQueue.enqueue(frame)\n }\n\n /**\n * Report a motion event for a camera. Drives the unified phase\n * machine for both motion sources (analyzer + onboard):\n *\n * - Every `detected: true` (any source) clears + rearms the\n * cooldown timer and transitions watching → active. The same\n * timer applies regardless of which source(s) are configured;\n * concurrent sources just keep refreshing the same window.\n * - `detected: false` is a no-op. Onboard sources never send an\n * explicit clear, and the analyzer's \"false\" pulses would\n * otherwise fight the cooldown when motion paused briefly\n * during a scene. The timer is the single closure path.\n * - Timer expiry transitions active → watching.\n *\n * Always-on cameras silently ignore reportMotion calls — they're\n * already in `active` and have no cooldown.\n *\n * `source` and `regions` propagate into the phase-transition event\n * so the wrapping addon can attach them to the cap-state slice +\n * bus event.\n */\n reportMotion(\n deviceId: number,\n detected: boolean,\n source: MotionSource = 'analyzer',\n regions: ReadonlyArray<MotionRegion> | undefined = undefined,\n ): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n // `always-on` ignores motion (already active); `disabled` ignores\n // motion (camera registered but never subscribed to detection).\n // Only `on-motion` flows through the cooldown path.\n if (state.registration.detectionMode !== 'on-motion') return\n if (!detected) return\n\n state.lastArmedSource = source\n state.lastArmedRegions = regions\n const cooldownMs = state.registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'watching') {\n this.transitionToActive(deviceId, state, source, regions, cooldownMs)\n }\n\n state.motionCooldownTimer = setTimeout(() => {\n state.motionCooldownTimer = null\n this.transitionToWatching(deviceId, state, cooldownMs)\n }, cooldownMs)\n }\n\n getPhase(deviceId: number): CameraPhase | undefined {\n return this.cameras.get(deviceId)?.phase\n }\n\n onResult(callback: ResultCallback): void {\n this.resultCallbacks.push(callback)\n }\n\n start(): void {\n if (this.intervalHandle !== null) return\n this.intervalHandle = setInterval(() => this.tick(), 10)\n this.timingSampler.start()\n }\n\n stop(): void {\n if (this.intervalHandle !== null) {\n clearInterval(this.intervalHandle)\n this.intervalHandle = null\n }\n this.timingSampler.stop()\n\n for (const state of this.cameras.values()) {\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n }\n }\n\n getMetrics(): OrchestratorMetrics {\n let totalQueueDepth = 0\n let totalInferenceTime = 0\n let totalInferenceCount = 0\n\n for (const state of this.cameras.values()) {\n totalQueueDepth += state.motionQueue.size + state.detectionQueue.size\n for (const t of state.inferenceTimes) {\n totalInferenceTime += t\n totalInferenceCount++\n }\n }\n\n return {\n activeCameras: this.cameras.size,\n throttledCameras: 0,\n avgInferenceTimeMs: totalInferenceCount > 0 ? totalInferenceTime / totalInferenceCount : 0,\n queueDepth: totalQueueDepth,\n }\n }\n\n getCameraMetrics(deviceId: number): CameraMetrics | undefined {\n const state = this.cameras.get(deviceId)\n if (!state) return undefined\n\n const elapsedMs = Date.now() - state.startTime\n const elapsedSec = elapsedMs / 1000\n const actualFps = elapsedSec > 0 ? state.processedCount / elapsedSec : 0\n\n const times = state.inferenceTimes\n const avgInference = times.length > 0 ? times.reduce((a, b) => a + b, 0) / times.length : 0\n\n return {\n detectionMode: state.registration.detectionMode,\n configuredFps: state.registration.fps,\n actualFps,\n queueDepth: state.motionQueue.size + state.detectionQueue.size,\n avgInferenceTimeMs: avgInference,\n droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames,\n phase: state.phase,\n }\n }\n\n getAllCameraMetrics(): Array<{ deviceId: number } & CameraMetrics> {\n const results: Array<{ deviceId: number } & CameraMetrics> = []\n for (const [deviceId] of this.cameras) {\n const metrics = this.getCameraMetrics(deviceId)\n if (metrics) {\n results.push({ deviceId, ...metrics })\n }\n }\n return results\n }\n\n getAttachedCameras(): readonly number[] {\n return [...this.cameras.keys()]\n }\n\n private transitionToActive(\n deviceId: number,\n state: CameraState,\n source: MotionSource,\n regions: ReadonlyArray<MotionRegion> | undefined,\n cooldownMs: number,\n ): void {\n state.phase = 'active'\n this.logger?.info('motion gate opened — phase=active', {\n tags: { deviceId },\n meta: { detectionMode: state.registration.detectionMode, source },\n })\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source,\n regions,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n\n private transitionToWatching(\n deviceId: number,\n state: CameraState,\n cooldownMs: number,\n ): void {\n state.phase = 'watching'\n state.detectionQueue.clear()\n this.logger?.info('motion gate closed — phase=watching', {\n tags: { deviceId },\n meta: { lastSource: state.lastArmedSource },\n })\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n // Last source that armed the timer carries the OFF event payload —\n // analyzer or onboard, whatever last fired before the silence\n // window expired. Regions are intentionally not propagated on\n // OFF: they describe a positive observation, not a closure.\n const source: MotionSource = state.lastArmedSource ?? 'analyzer'\n this.config.onPhaseChanged?.(deviceId, 'watching', {\n source,\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n state.lastArmedSource = null\n state.lastArmedRegions = undefined\n }\n\n private tick(): void {\n this.drainMotionQueues()\n\n if (this.semaphore.available <= 0) return\n\n const picked = this.pickNextDetectionFrame()\n if (!picked) return\n\n const { deviceId, frame, state } = picked\n const frameInput = toFrameInput(frame)\n\n void this.processWithSemaphore(deviceId, frame, frameInput, state, 'detection')\n }\n\n private drainMotionQueues(): void {\n for (const [deviceId, state] of this.cameras) {\n while (state.motionQueue.size > 0) {\n const frame = state.motionQueue.dequeue()\n if (frame) {\n void this.config.analyzeMotion(deviceId, frame)\n }\n }\n }\n }\n\n private async processWithSemaphore(\n deviceId: number,\n frame: DecodedFrame,\n frameInput: FrameInput,\n state: CameraState,\n streamType: StreamType,\n ): Promise<void> {\n const pickedAt = Date.now()\n const captureTs = frame.timestamp\n const enqueuedAt = (frame as { _enqueuedAt?: number })._enqueuedAt ?? captureTs\n\n const release = await this.semaphore.acquire()\n const semAcquiredAt = Date.now()\n try {\n const result = await this.config.processFrame(deviceId, frameInput)\n const inferDoneAt = Date.now()\n const inferenceMs = inferDoneAt - semAcquiredAt\n\n state.inferenceTimes.push(inferenceMs)\n if (state.inferenceTimes.length > 100) {\n state.inferenceTimes.shift()\n }\n state.processedCount++\n\n if (result) {\n await this.notifyCallbacks(deviceId, frame, result, streamType)\n const emittedAt = Date.now()\n\n this.timingSampler.addSample(deviceId, {\n captureToEnqueue: enqueuedAt - captureTs,\n queueWait: pickedAt - enqueuedAt,\n semaphoreWait: semAcquiredAt - pickedAt,\n inference: inferenceMs,\n resultToEmit: emittedAt - inferDoneAt,\n endToEnd: emittedAt - captureTs,\n detections: result.detections?.length ?? 0,\n })\n }\n } finally {\n release()\n }\n }\n\n private async notifyCallbacks(\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n ): Promise<void> {\n for (const callback of this.resultCallbacks) {\n try {\n await callback(deviceId, frame, result, streamType)\n } catch {\n // Error isolation: individual callback failures don't affect others\n }\n }\n }\n\n private pickNextDetectionFrame(): {\n deviceId: number\n frame: DecodedFrame\n state: CameraState\n } | null {\n for (const [deviceId, state] of this.cameras) {\n if (state.registration.detectionMode === 'always-on' && state.detectionQueue.size > 0) {\n const frame = state.detectionQueue.dequeue()!\n return { deviceId, frame, state }\n }\n }\n\n if (this.defaultRoundRobinKeys.length === 0) return null\n\n const startIndex = this.defaultRoundRobinIndex\n for (let i = 0; i < this.defaultRoundRobinKeys.length; i++) {\n const idx = (startIndex + i) % this.defaultRoundRobinKeys.length\n const deviceId = this.defaultRoundRobinKeys[idx]\n if (!deviceId) continue\n const state = this.cameras.get(deviceId)\n if (!state) continue\n\n if (state.phase === 'active' && state.detectionQueue.size > 0) {\n this.defaultRoundRobinIndex = (idx + 1) % this.defaultRoundRobinKeys.length\n const frame = state.detectionQueue.dequeue()\n if (!frame) continue\n return { deviceId, frame, state }\n }\n }\n\n return null\n }\n}\n","/**\n * Synthetic-bench custom actions for pipeline-runner.\n *\n * Lets the admin UI drive a production-realistic detection benchmark:\n * the runner — already co-located with detection-pipeline in the\n * `pipeline` group — invokes `api.pipelineExecutor.runPipeline` directly\n * (in-process via localProviderLink, no Moleculer hop), N workers\n * concurrent × M iterations. Returns aggregate stats.\n *\n * Why this exists: the older bench path (UI → benchmark addon → pipeline\n * addon) crosses TWO Moleculer process boundaries that the production\n * camera path does NOT have (broker / runner / detection-pipeline are\n * all in the same group). Running the loop from inside the runner gives\n * fps numbers that match what real cameras achieve.\n */\nimport { z } from 'zod'\nimport { customAction, defineCustomActions } from '@camstack/types'\n\n// Local mirror of pipeline-executor's engine-choice schema. Re-declared here\n// to avoid a cap-package coupling for one bench cap; shape is fixed by the\n// runtime contract (runtime + backend + format + optional device).\nconst BenchEngineChoiceSchema = z.object({\n runtime: z.enum(['node', 'python']),\n backend: z.string(),\n format: z.enum(['onnx', 'coreml', 'openvino', 'tflite', 'pt']),\n device: z.string().optional(),\n})\n\n// Recursive step shape — matches PipelineStepInput minimally for cap input.\ninterface BenchStepShape {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStepShape[]\n}\nconst BenchStepSchema: z.ZodType<BenchStepShape> = z.lazy(() => z.object({\n addonId: z.string(),\n modelId: z.string(),\n enabled: z.boolean(),\n children: z.array(BenchStepSchema).optional(),\n}))\n\ninterface CacheBenchFrameInputShape {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\ninterface CacheBenchFrameResultShape {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport const CacheBenchFrameInputSchema: z.ZodType<CacheBenchFrameInputShape> = z.object({\n imageBase64: z.string(),\n ttlSeconds: z.number().int().positive().optional(),\n})\nexport const CacheBenchFrameResultSchema: z.ZodType<CacheBenchFrameResultShape> = z.object({\n frameId: z.string(),\n width: z.number(),\n height: z.number(),\n expiresAt: z.number(),\n})\n\ninterface ReleaseBenchFrameInputShape { readonly frameId: string }\ninterface ReleaseBenchFrameResultShape { readonly released: boolean }\nexport const ReleaseBenchFrameInputSchema: z.ZodType<ReleaseBenchFrameInputShape> = z.object({\n frameId: z.string(),\n})\nexport const ReleaseBenchFrameResultSchema: z.ZodType<ReleaseBenchFrameResultShape> = z.object({\n released: z.boolean(),\n})\n\ninterface RunSyntheticBenchInputShape {\n readonly frameId: string\n readonly steps: readonly BenchStepShape[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n /** When true, forces the full runPipeline path (1.2MB pipe transfer per\n * call) instead of the cached inferCached fast path. Simulates the real\n * camera detection pipeline cost including IPC overhead. */\n readonly simulatePipeline?: boolean\n readonly engine?: { readonly runtime: 'node' | 'python'; readonly backend: string; readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'; readonly device?: string }\n}\nexport const RunSyntheticBenchInputSchema: z.ZodType<RunSyntheticBenchInputShape> = z.object({\n frameId: z.string(),\n steps: z.array(BenchStepSchema).min(1),\n parallel: z.number().int().min(1).max(32),\n iterations: z.number().int().min(1).max(10_000),\n warmup: z.number().int().min(0).max(100).optional(),\n sessionId: z.string().optional(),\n simulatePipeline: z.boolean().optional(),\n engine: BenchEngineChoiceSchema.optional(),\n})\n\ninterface TimingSplitShape {\n readonly mean: number\n readonly p50: number\n readonly p95: number\n readonly p99: number\n}\nconst TimingSplitSchema: z.ZodType<TimingSplitShape> = z.object({\n mean: z.number(),\n p50: z.number(),\n p95: z.number(),\n p99: z.number(),\n})\n\ninterface RunSyntheticBenchResultShape {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: TimingSplitShape\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\nexport const RunSyntheticBenchResultSchema: z.ZodType<RunSyntheticBenchResultShape> = z.object({\n runs: z.number(),\n wallSec: z.number(),\n fps: z.number(),\n detectionsPerSec: z.number(),\n avgDetections: z.number(),\n callMs: TimingSplitSchema,\n inferMs: z.number(),\n preprocessMs: z.number(),\n predictMs: z.number(),\n batchSizeMean: z.number(),\n batchSizeMax: z.number(),\n engine: z.object({ runtime: z.string(), backend: z.string(), device: z.string().optional() }).optional(),\n tuning: z.object({ batchMode: z.string(), windowMs: z.number(), maxBatchSize: z.number(), concurrency: z.number() }).optional(),\n path: z.string().optional(),\n})\n\nexport const pipelineRunnerBenchActions = defineCustomActions({\n cacheBenchFrame: customAction(\n CacheBenchFrameInputSchema,\n CacheBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n releaseBenchFrame: customAction(\n ReleaseBenchFrameInputSchema,\n ReleaseBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n runSyntheticBench: customAction(\n RunSyntheticBenchInputSchema,\n RunSyntheticBenchResultSchema,\n { kind: 'mutation' },\n ),\n})\n\nexport type PipelineRunnerBenchActions = typeof pipelineRunnerBenchActions\n\n// Explicit TypeScript types — z.infer<> on customAction's stored input/output\n// types loses precision through the spec wrapper. Caller code uses these.\nexport interface BenchStep {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStep[]\n}\nexport interface BenchEngineChoice {\n readonly runtime: 'node' | 'python'\n readonly backend: string\n readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'\n readonly device?: string\n}\nexport interface CacheBenchFrameInput {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\nexport interface CacheBenchFrameResult {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport interface ReleaseBenchFrameInput { readonly frameId: string }\nexport interface ReleaseBenchFrameResult { readonly released: boolean }\nexport interface RunSyntheticBenchInput {\n readonly frameId: string\n readonly steps: readonly BenchStep[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n readonly simulatePipeline?: boolean\n readonly engine?: BenchEngineChoice\n}\nexport interface RunSyntheticBenchResult {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: { readonly mean: number; readonly p50: number; readonly p95: number; readonly p99: number }\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\n","/**\n * addon-pipeline-runner — per-node detection scheduler.\n *\n * Lives on every \"vision node\" (hub if hub is one, every agent that runs\n * decoders/cameras). Receives camera assignments from the hub-side\n * `addon-pipeline-orchestrator` via cap calls, owns the per-camera frame\n * queues + semaphore + motion → detection phase machine, calls the local\n * `motion-detection` and `pipeline-executor` capabilities for the actual\n * work, and emits typed events on the bus\n * (`detection.motion-analysis`, `pipeline.inference-result`, `pipeline.trace`).\n *\n * Design notes:\n *\n * - All cap inputs/outputs are serializable so the runner is transparently\n * routable across the cluster: when the runner is co-located with the\n * broker/decoder/motion/detection (the default and recommended deploy),\n * capability lookups return live singletons and frame data never crosses\n * a process boundary. When a deploy splits the runner from the executor\n * (rare), the lookup returns null and the runner falls back to\n * `this.ctx.api` (broker proxy) at the cost of serialization.\n *\n * - The scheduler core (`PipelineRunner`) is callback-driven and framework\n * agnostic. The addon class injects the inference + motion callbacks at\n * construction time, capturing references to the relevant providers\n * lazily on each invocation.\n *\n * - Result post-processing (analysis pipeline, class filters, notifications)\n * is intentionally NOT done by the runner. The runner emits the raw\n * inference output as a `pipeline.inference-result` event on the bus;\n * a hub-side subscriber consumes the event and runs the post-processing\n * pipeline. This keeps the runner stateless w.r.t. detection-side\n * business logic and makes it possible to deploy the post-processor\n * independently from the inference workers.\n */\nimport type {\n IPipelineRunnerProvider, RunnerCameraConfig, RunnerLocalLoad, RunnerLocalMetrics,\n CameraMetrics, FrameInput, FrameResult, DecodedFrame,\n IStreamBroker,\n PipelineInferenceResultPayload, MotionAnalysisPayload, MotionZonesRawPayload, PhaseTransitionPayload,\n PipelineStepInput,\n CameraPhase, MotionSource, MotionRegion,\n} from '@camstack/types'\nimport { BaseAddon, EventCategory, createEvent, pipelineRunnerCapability, errMsg } from '@camstack/types'\nimport type { AddonInitResult } from '@camstack/types'\nimport { PipelineRunner, type PhaseTransitionMeta } from './runner.js'\nimport {\n pipelineRunnerBenchActions,\n type PipelineRunnerBenchActions,\n type CacheBenchFrameInput,\n type CacheBenchFrameResult,\n type ReleaseBenchFrameInput,\n type ReleaseBenchFrameResult,\n type RunSyntheticBenchInput,\n type RunSyntheticBenchResult,\n} from './bench-actions.js'\n\n/**\n * Subset of the stream-broker addon surface used by the runner. Mirrors\n * the local interface in `DetectionWiringService` — `getBroker()` is the\n * in-process extension that returns a live IStreamBroker reference (not\n * tRPC-serializable, available only via the kernel capability registry).\n */\n\ninterface RunnerAddonConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n}\n\nconst DEFAULT_CONFIG: RunnerAddonConfig = {\n maxQueueDepth: 30,\n // CoreML window accumulator coalesces concurrent calls into a single\n // model.predict([list]) — the more in-flight, the larger the batch and\n // the higher the per-frame throughput. With concurrency=2 the window\n // never fills past batch=2, capping the pool at ~50 fps single-node.\n // 16 matches the slider ceiling and lines up with bench numbers\n // (parallel=16 hits batch=7-8/8, sustaining ~140 fps full path).\n maxConcurrentInferences: 16,\n targetLoadPercent: 80,\n minThrottledFps: 1,\n}\n\n/**\n * Camera attachment record kept by the runner addon. Holds the original\n * RunnerCameraConfig for introspection via `getLocalCameras` plus the\n * live broker subscription handles needed to tear down on detach.\n */\ninterface AttachedCamera {\n readonly config: RunnerCameraConfig\n motionUnsubscribe: (() => void) | null\n detectionUnsubscribe: (() => void) | null\n}\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/** Interval for the per-camera step roster dump (ms). */\nconst STEP_LOG_INTERVAL_MS = 30_000\n\n/**\n * Interval for periodic metric snapshot emission. ~1 Hz strikes the\n * balance between UI freshness (overlay phase / fps numbers feel live)\n * and bus traffic (one snapshot per attached camera + one per node\n * load is well under 100 events/s even at 50 cameras).\n */\nconst METRICS_SNAPSHOT_INTERVAL_MS = 1_000\n/**\n * Force a metrics-snapshot emit at least every 30s even when the\n * payload hasn't changed — gives the UI's \"agent reachable\" chip a\n * heartbeat without the per-tick spam an unconditional emit\n * produces. Picked so a 5-minute idle window emits ~10 events\n * instead of ~300.\n */\nconst METRICS_HEARTBEAT_MS = 30_000\n\nexport default class PipelineRunnerAddon extends BaseAddon<RunnerAddonConfig> implements IPipelineRunnerProvider {\n private runner: PipelineRunner | null = null\n private readonly attached = new Map<number, AttachedCamera>()\n private nodeId = 'unknown'\n private stepLogTimer: ReturnType<typeof setInterval> | null = null\n private metricsSnapshotTimer: ReturnType<typeof setInterval> | null = null\n private unsubMotionEvents: (() => void) | null = null\n /** Last analyzer-detected state per device — gates the\n * `MotionOnMotionChanged` emit in `runMotionAnalysis` to transitions\n * only (otherwise we'd emit on every analyzer frame). */\n private readonly lastAnalyzerDetected = new Map<number, boolean>()\n /**\n * Last positive motion timestamp per device — preserved across the\n * OFF transition so the motion runtime-state slice keeps a stable\n * `lastDetectedAt` after the cooldown closes the phase. Cleared on\n * detach.\n */\n private readonly lastMotionAt = new Map<number, number>()\n\n /**\n * Snapshot-equality cache for metrics-snapshot defer. The runner\n * fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;\n * for an idle camera (no inference, queue empty, fps=0) every tick\n * carries an identical payload. We skip the bus emit when the\n * payload deep-equals the previous one so the events tab + remote\n * subscribers stop seeing 60 metrics-snapshots/min/camera that\n * convey nothing. A periodic heartbeat re-emits every\n * METRICS_HEARTBEAT_MS so consumers know the runner is still\n * alive.\n */\n private readonly lastEmittedCameraMetrics = new Map<number, { json: string; emittedAt: number }>()\n private lastEmittedRunnerLoad: { json: string; emittedAt: number } | null = null\n\n /**\n * In-memory bench-frame cache (decoded JPEG bytes). Populated by the\n * `cacheBenchFrame` custom action. Fed into the synthetic-bench loop\n * via the `frame: FrameInput` shape that mirrors what stream-broker\n * delivers to this very addon during real camera detection.\n */\n private readonly benchFrameCache = new Map<string, {\n readonly data: Uint8Array\n readonly width: number\n readonly height: number\n readonly format: 'rgb'\n readonly expiresAt: number\n }>()\n private benchFrameSweeper: ReturnType<typeof setInterval> | null = null\n\n constructor() { super({ ...DEFAULT_CONFIG }) }\n\n protected async onInitialize(): Promise<AddonInitResult<PipelineRunnerBenchActions>> {\n // Use the cluster node id (hub, dev-agent-0, etc.) for dispatch routing,\n // not the addon id (pipeline-runner). The orchestrator uses this to track\n // which cluster node a camera is assigned to.\n // When the runner lives in a group-runner subprocess the broker\n // nodeID is hierarchical (`hub/pipeline`); strip the suffix so\n // emitted events / cap calls refer to the cluster-visible parent\n // node. In-process boot leaves the value untouched.\n const raw = this.ctx.kernel.localNodeId ?? this.ctx.id\n this.nodeId = raw.includes('/') ? raw.split('/')[0]! : raw\n\n this.runner = new PipelineRunner({\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n processFrame: (deviceId, frame) => this.runInference(deviceId, frame),\n analyzeMotion: (deviceId, frame) => this.runMotionAnalysis(deviceId, frame),\n onPhaseChanged: (deviceId, phase, meta) => this.handlePhaseChanged(deviceId, phase, meta),\n logger: this.ctx.logger,\n })\n\n this.runner.timingSampler.setLogger(this.ctx.logger.child('timing'))\n\n // Hook the per-camera detection-stream subscribe/unsubscribe lifecycle\n // into the local broker. The runner's phase machine triggers this\n // handler when a camera transitions WATCHING ↔ ACTIVE.\n this.runner.onDetectionStreamChange((deviceId, action) => {\n this.handleDetectionStreamChange(deviceId, action)\n })\n\n // Emit raw inference results to the bus. Hub-side post-processors\n // (analysis pipeline, class filters, notifications) consume these\n // and emit the post-processed `detection.result` event.\n this.runner.onResult(async (deviceId, frame, result, _streamType) => {\n this.emitInferenceResult(deviceId, frame, result)\n })\n\n this.runner.start()\n this.ctx.logger.info(\n 'Pipeline runner started',\n {\n tags: { nodeId: this.nodeId },\n meta: {\n maxConcurrent: this.config.maxConcurrentInferences,\n queueDepth: this.config.maxQueueDepth,\n },\n },\n )\n\n // Unified motion event subscription. Sources (Reolink firmware push\n // events emitting `source: 'onboard'`, this runner's own analyzer\n // path emitting `source: 'analyzer'` from `runMotionAnalysis`) all\n // funnel through `MotionOnMotionChanged`. The phase machine reacts\n // via `reportMotion` regardless of source — `motionSources`\n // selector at the runner-config level still gates which sources\n // trigger detection downstream.\n if (this.ctx.eventBus) {\n this.unsubMotionEvents = this.ctx.eventBus.subscribe(\n { category: EventCategory.MotionOnMotionChanged },\n (event) => {\n const data = event.data\n const deviceId = data.deviceId\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n const source = data.source\n if (!attachment.config.motionSources.includes(source)) return\n this.runner?.reportMotion(\n deviceId,\n data.detected,\n source,\n data.regions ? [...data.regions] : undefined,\n )\n },\n )\n }\n\n // Periodic per-camera step roster dump. Logs once every\n // STEP_LOG_INTERVAL_MS for each attached camera so operators can\n // see at-a-glance which detection + audio steps are configured on\n // each device without correlating attach events. Skips when no\n // cameras are attached so quiet dev runs don't emit noise.\n this.stepLogTimer = setInterval(() => this.logAttachedSteps(), STEP_LOG_INTERVAL_MS)\n\n // Periodic metric-snapshot emission: one runner-load event +\n // one per-camera metrics event per attached camera per tick.\n // UI dashboards subscribe to these instead of polling the cap.\n this.metricsSnapshotTimer = setInterval(\n () => this.emitMetricsSnapshot(),\n METRICS_SNAPSHOT_INTERVAL_MS,\n )\n\n return {\n providers: [{ capability: pipelineRunnerCapability, provider: this }],\n customActions: pipelineRunnerBenchActions,\n actionHandlers: {\n cacheBenchFrame: async (input) => this.cacheBenchFrame(input),\n releaseBenchFrame: async (input) => this.releaseBenchFrame(input),\n runSyntheticBench: async (input) => this.runSyntheticBench(input),\n },\n }\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.metricsSnapshotTimer) {\n clearInterval(this.metricsSnapshotTimer)\n this.metricsSnapshotTimer = null\n }\n if (this.stepLogTimer) {\n clearInterval(this.stepLogTimer)\n this.stepLogTimer = null\n }\n if (this.benchFrameSweeper) {\n clearInterval(this.benchFrameSweeper)\n this.benchFrameSweeper = null\n }\n this.benchFrameCache.clear()\n if (this.unsubMotionEvents) {\n this.unsubMotionEvents()\n this.unsubMotionEvents = null\n }\n this.lastAnalyzerDetected.clear()\n if (this.runner) {\n this.runner.stop()\n this.runner = null\n }\n\n for (const attachment of this.attached.values()) {\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n }\n this.attached.clear()\n }\n\n // ── Synthetic bench (production-equivalent measurement) ───────────────\n\n private async cacheBenchFrame(input: CacheBenchFrameInput): Promise<CacheBenchFrameResult> {\n // Decode JPEG → raw RGB ONCE. The runner then feeds this raw buffer\n // to api.pipelineExecutor.runPipeline via the `frame` field — same\n // shape that stream-broker hands the runner during real camera\n // detection. In-process call; no Moleculer serialisation cost.\n const sharp = (await import('sharp')).default\n const jpeg = Buffer.from(input.imageBase64, 'base64')\n const { data, info } = await sharp(jpeg).raw().toBuffer({ resolveWithObject: true })\n if (info.channels !== 3) {\n throw new Error(`cacheBenchFrame: expected 3 channels (rgb), got ${info.channels}`)\n }\n const rgb = new Uint8Array(data)\n const ttlMs = Math.max(60_000, (input.ttlSeconds ?? 600) * 1000)\n const frameId = `runner-bench-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n const expiresAt = Date.now() + ttlMs\n this.benchFrameCache.set(frameId, { data: rgb, width: info.width, height: info.height, format: 'rgb', expiresAt })\n if (!this.benchFrameSweeper) {\n this.benchFrameSweeper = setInterval(() => this.sweepBenchFrameCache(), 60_000)\n this.benchFrameSweeper.unref?.()\n }\n this.ctx.logger.info('cached bench frame', {\n meta: { frameId, width: info.width, height: info.height, bytes: rgb.length, ttlMs },\n })\n return { frameId, width: info.width, height: info.height, expiresAt }\n }\n\n private async releaseBenchFrame(input: ReleaseBenchFrameInput): Promise<ReleaseBenchFrameResult> {\n return { released: this.benchFrameCache.delete(input.frameId) }\n }\n\n private sweepBenchFrameCache(): void {\n const now = Date.now()\n for (const [id, entry] of this.benchFrameCache) {\n if (entry.expiresAt < now) this.benchFrameCache.delete(id)\n }\n }\n\n private async runSyntheticBench(input: RunSyntheticBenchInput): Promise<RunSyntheticBenchResult> {\n const ctx = this.ctx\n const api = ctx.api\n if (!api) throw new Error('runSyntheticBench: ctx.api unavailable')\n ctx.logger.info('runSyntheticBench input', {\n meta: { frameId: input.frameId, parallel: input.parallel, iterations: input.iterations },\n })\n\n const cached = this.benchFrameCache.get(input.frameId)\n if (!cached) {\n throw new Error(`runSyntheticBench: frameId ${input.frameId} not cached (call cacheBenchFrame first)`)\n }\n\n const stepsToRun = input.steps.map(s => ({\n addonId: s.addonId,\n modelId: s.modelId,\n enabled: s.enabled,\n children: s.children ?? [],\n }))\n\n // Determine if we can use the fast cache path:\n // Single root step, no enabled children, and simulatePipeline not requested\n // → bypass runPipeline entirely, cache frame in Python pool once,\n // then send only 5-byte references.\n const enabledSteps = stepsToRun.filter(s => s.enabled)\n const isSingleStep = enabledSteps.length === 1\n && (!enabledSteps[0]!.children || enabledSteps[0]!.children.filter(c => c.enabled).length === 0)\n const useFastPath = isSingleStep && !input.simulatePipeline\n const rootStep = enabledSteps[0]\n\n // Ensure engine + model are loaded (same as runPipeline does)\n // Run one warmup call through the FULL path to trigger model load\n const sharedFrame: FrameInput = {\n data: cached.data,\n format: cached.format,\n width: cached.width,\n height: cached.height,\n timestamp: Date.now(),\n }\n\n let poolFrameId: number | null = null\n\n if (useFastPath && rootStep) {\n // ── Fast path: cache frame in Python, infer via 5-byte reference ──\n ctx.logger.info('synthetic bench: using Python cache path', {\n meta: { step: rootStep.addonId, model: rootStep.modelId },\n })\n\n // Cache the raw frame in the Python pool\n const cacheResult = await api.pipelineExecutor.cacheFrameInPool.mutate({\n data: new Uint8Array(cached.data.slice().buffer),\n width: cached.width,\n height: cached.height,\n format: cached.format,\n })\n poolFrameId = cacheResult.frameId\n\n // Warmup through full runPipeline path to ensure model is loaded.\n // Engine override is applied here — loads the model on the overridden\n // factory. However, inferCached bypasses the provider's override swap\n // (it goes directly to the current engineFactory). For same-engine\n // bench runs this is correct. TODO: for cross-engine override, expose\n // applyEngineOverride/restore on the cap so the cached path can use\n // the overridden factory.\n await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n // Additional warmup via cached path\n const warmupCount = input.warmup ?? 1\n for (let w = 0; w < warmupCount; w++) {\n await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId,\n })\n }\n\n // Timed run — each call sends only 5 bytes through the pipe\n const wallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n let _n = 0\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId!,\n })\n const wallMs = performance.now() - t0\n const r = result as Record<string, unknown>\n const inferMs = typeof r['inferenceMs'] === 'number' ? r['inferenceMs'] as number : wallMs\n const preMs = typeof r['preprocessMs'] === 'number' ? r['preprocessMs'] as number : 0\n const predMs = typeof r['predictMs'] === 'number' ? r['predictMs'] as number : 0\n const bs = typeof r['batchSize'] === 'number' ? r['batchSize'] as number : 1\n const dets = Array.isArray(r['detections']) ? (r['detections'] as unknown[]).length : 0\n wallTimings.push(wallMs)\n inferTimings.push(inferMs)\n preprocessTimings.push(preMs)\n predictTimings.push(predMs)\n batchSizes.push(bs)\n detCounts.push(dets)\n const n = ++_n\n if (n <= 20) {\n ctx.logger.info('bench call trace (cached)', {\n meta: { n, wallMs: Math.round(wallMs), inferMs: Math.round(inferMs), preMs: Math.round(preMs * 10) / 10, predMs: Math.round(predMs * 10) / 10, bs },\n })\n }\n // Emit live progress every `parallel` completions\n if (n % Math.max(1, input.parallel) === 0) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanCallMs = wallTimings.reduce((s, v) => s + v, 0) / wallTimings.length\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const avgDet = detCounts.length > 0 ? totalDet / detCounts.length : 0\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / batchSizes.length\n const msg = `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanCallMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`\n if (ctx.eventBus) {\n // Emit directly (bypassing createEvent's strict typing) so we\n // can attach structured bench stats as extra fields. The UI's\n // pipeline.progress subscription reads them from event.data.\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench', message: msg,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanCallMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / inferTimings.length) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / preprocessTimings.length) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / predictTimings.length) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: Math.round(avgDet * 100) / 100,\n },\n })\n } else {\n ctx.logger.warn('emitProgress: NO eventBus')\n }\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n // Cleanup\n await api.pipelineExecutor.uncacheFrame.mutate({ frameId: poolFrameId }).catch(() => {})\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'cached')\n }\n\n // ── Slow path: full runPipeline per call (multi-step trees) ──\n ctx.logger.info('synthetic bench: using full runPipeline path', {\n meta: { steps: enabledSteps.length, simulatePipeline: !!input.simulatePipeline },\n })\n\n let _callCount = 0\n const callOnce = async (): Promise<{ wallMs: number; result: FrameResult }> => {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n const wallMs = performance.now() - t0\n const n = ++_callCount\n if (n <= 20) {\n ctx.logger.info('bench call trace', {\n meta: {\n n,\n wallMs: Math.round(wallMs),\n totalInferenceMs: Math.round(result.debug?.totalInferenceMs ?? 0),\n predictMs: Math.round((result.debug?.predictMs ?? 0) * 10) / 10,\n preprocessMs: Math.round((result.debug?.preprocessMs ?? 0) * 10) / 10,\n batchSize: result.debug?.batchSize ?? 1,\n },\n })\n }\n return { wallMs, result }\n }\n\n // Warmup — sequential to ensure engine is hot before timing.\n const warmupCount = input.warmup ?? 1\n for (let i = 0; i < warmupCount; i++) {\n await callOnce()\n }\n\n const wallTimings: number[] = []\n const serverWallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const { wallMs, result } = await callOnce()\n wallTimings.push(wallMs)\n serverWallTimings.push(result.debug?.wallMs ?? 0)\n inferTimings.push(result.debug?.totalInferenceMs ?? 0)\n preprocessTimings.push(result.debug?.preprocessMs ?? 0)\n predictTimings.push(result.debug?.predictMs ?? 0)\n batchSizes.push(result.debug?.batchSize ?? 1)\n detCounts.push(result.detections?.length ?? 0)\n const n = wallTimings.length\n if (n % Math.max(1, input.parallel) === 0 && ctx.eventBus) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanMs = wallTimings.reduce((s, v) => s + v, 0) / n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / n\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench',\n message: `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: n > 0 ? Math.round((totalDet / n) * 100) / 100 : 0,\n },\n })\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'pipeline')\n }\n\n private async buildBenchResult(\n wallTimings: number[],\n inferTimings: number[],\n preprocessTimings: number[],\n predictTimings: number[],\n batchSizes: number[],\n detCounts: number[],\n wallSec: number,\n path: string,\n ): Promise<RunSyntheticBenchResult> {\n const meanOfArr = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n this.ctx.logger.info('synthetic bench summary', {\n meta: {\n runs: wallTimings.length,\n wallSec: Math.round(wallSec * 100) / 100,\n fps: Math.round((wallTimings.length / wallSec) * 100) / 100,\n callMeanMs: Math.round(meanOfArr(wallTimings)),\n inferMeanMs: Math.round(meanOfArr(inferTimings)),\n preprocessMeanMs: Math.round(meanOfArr(preprocessTimings)),\n predictMeanMs: Math.round(meanOfArr(predictTimings)),\n batchSizeMean: Math.round(meanOfArr(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n },\n })\n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const pick = (q: number): number =>\n sorted.length > 0\n ? sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))]!\n : 0\n const meanOf = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n\n const totalRuns = wallTimings.length\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n\n return {\n runs: totalRuns,\n wallSec: Math.round(wallSec * 1000) / 1000,\n fps: wallSec > 0 ? Math.round((totalRuns / wallSec) * 100) / 100 : 0,\n detectionsPerSec: wallSec > 0 ? Math.round((totalDet / wallSec) * 100) / 100 : 0,\n avgDetections: totalRuns > 0 ? Math.round((totalDet / totalRuns) * 100) / 100 : 0,\n callMs: {\n mean: Math.round(meanOf(wallTimings) * 100) / 100,\n p50: Math.round(pick(0.5) * 100) / 100,\n p95: Math.round(pick(0.95) * 100) / 100,\n p99: Math.round(pick(0.99) * 100) / 100,\n },\n inferMs: Math.round(meanOf(inferTimings) * 100) / 100,\n preprocessMs: Math.round(meanOf(preprocessTimings) * 100) / 100,\n predictMs: Math.round(meanOf(predictTimings) * 100) / 100,\n batchSizeMean: Math.round(meanOf(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n path,\n ...(await this.getEngineAndTuning()),\n }\n }\n\n private async getEngineAndTuning(): Promise<Pick<RunSyntheticBenchResult, 'engine' | 'tuning'>> {\n try {\n const api = this.ctx.api\n if (!api) return {}\n const [eng, tuning] = await Promise.all([\n api.pipelineExecutor.getSelectedEngine.query(),\n api.pipelineExecutor.getEffectiveTuning.query(),\n ])\n return {\n engine: eng ? { runtime: eng.runtime, backend: eng.backend, device: eng.device } : undefined,\n tuning: tuning ?? undefined,\n }\n } catch {\n return {}\n }\n }\n\n // ── IPipelineRunnerProvider implementation ────────────────────────────\n\n async attachCamera(config: RunnerCameraConfig): Promise<{ success: true }> {\n const runner = this.runner\n const ctx = this.ctx\n if (!runner || !ctx) {\n throw new Error('PipelineRunnerAddon: attachCamera called before initialize completed')\n }\n\n // Diagnostic: log the incoming config to understand why\n // subscribeMotionFrames isn't being called.\n this.ctx.logger.info('attachCamera received config', {\n tags: { deviceId: config.deviceId },\n meta: {\n motionSources: config.motionSources,\n motionSourcesType: Array.isArray(config.motionSources) ? `array(${config.motionSources.length})` : typeof config.motionSources,\n motionStreamId: config.motionStreamId,\n detectionStreamId: config.detectionStreamId,\n keys: Object.keys(config as unknown as Record<string, unknown>),\n },\n })\n\n if (this.attached.has(config.deviceId)) {\n // Idempotent: re-attach with new config replaces the existing one.\n this.detachInternal(config.deviceId)\n }\n\n runner.registerCamera(config.deviceId, {\n detectionMode: config.detectionMode,\n fps: config.detectionFps,\n motionCooldownMs: config.motionCooldownMs,\n })\n\n const attachment: AttachedCamera = {\n config,\n motionUnsubscribe: null,\n detectionUnsubscribe: null,\n }\n this.attached.set(config.deviceId, attachment)\n\n // Subscribe to the local motion stream broker only when 'analyzer'\n // is among the active motion sources — that's the source that\n // needs decoded frames flowing through the motion-detection cap.\n // 'onboard' alone arrives via the bus (ProviderMotion → orchestrator\n // → reportMotion) without needing broker frames.\n if (config.motionSources.includes('analyzer')) {\n attachment.motionUnsubscribe = await this.subscribeMotionFrames(config)\n }\n\n // Note: detection stream subscription is wired LAZILY by the\n // runner's phase machine via `handleDetectionStreamChange`, which\n // fires when the camera transitions to ACTIVE. Always-on cameras\n // transition immediately on registerCamera, so the subscribe call\n // already happened by the time we get here.\n\n const stepsCount = config.steps?.length ?? 0\n const dispatch = stepsCount > 0\n ? `runPipeline(${stepsCount}step${stepsCount === 1 ? '' : 's'})`\n : config.steps !== undefined\n ? 'skip(0steps)'\n : 'runFrame(legacy)'\n const engineLabel = config.engine ? `${config.engine.runtime}+${config.engine.backend}/${config.engine.format}` : 'default'\n this.ctx.logger.info(\n 'attachCamera',\n {\n tags: { deviceId: config.deviceId },\n meta: {\n detectionMode: config.detectionMode,\n audioMode: config.audioMode,\n motionFps: config.motionFps,\n detectionFps: config.detectionFps,\n motionSources: config.motionSources,\n dispatch,\n engine: engineLabel,\n },\n },\n )\n\n return { success: true }\n }\n\n async detachCamera(input: { deviceId: number }): Promise<{ success: true }> {\n this.detachInternal(input.deviceId)\n return { success: true }\n }\n\n async reportMotion(input: {\n deviceId: number\n detected: boolean\n source?: MotionSource\n regions?: ReadonlyArray<MotionRegion>\n }): Promise<{ success: true }> {\n this.runner?.reportMotion(input.deviceId, input.detected, input.source, input.regions)\n return { success: true }\n }\n\n /**\n * Periodic per-camera step roster dump. Once every\n * STEP_LOG_INTERVAL_MS (30s) emits one log line per attached camera\n * with the configured detection step tree + audio classifier branch\n * so an operator looking at the agent log can quickly see what each\n * camera is currently running without crossing tRPC. Skips when no\n * cameras are attached so quiet dev runs stay silent.\n */\n private logAttachedSteps(): void {\n if (this.attached.size === 0) return\n for (const [deviceId, attachment] of this.attached) {\n const cfg = attachment.config\n const detectionSteps = cfg.steps && cfg.steps.length > 0\n ? this.flattenSteps(cfg.steps).filter((s) => s.enabled)\n : []\n const detectionLabel = detectionSteps.length > 0\n ? detectionSteps.map((s) => `${s.addonId}/${s.modelId}`).join(' → ')\n : '<none>'\n const audioLabel = cfg.audio && cfg.audio.enabled\n ? `${cfg.audio.engine.runtime}/${cfg.audio.engine.backend}/${cfg.audio.modelId}`\n : '<off>'\n const engineLabel = cfg.engine\n ? `${cfg.engine.runtime}/${cfg.engine.backend}${cfg.engine.device ? `/${cfg.engine.device}` : ''}`\n : '<unset>'\n this.ctx.logger.info('Camera pipeline roster', {\n tags: { deviceId },\n meta: {\n phase: 'roster',\n intervalSec: STEP_LOG_INTERVAL_MS / 1000,\n pipelineEnabled: cfg.pipelineEnabled,\n motionSources: cfg.motionSources,\n motionFps: cfg.motionFps,\n detectionFps: cfg.detectionFps,\n engine: engineLabel,\n videoSteps: detectionLabel,\n videoStepCount: detectionSteps.length,\n audio: audioLabel,\n },\n })\n }\n }\n\n /** Recursively flatten the step tree → ordered list of every node. */\n private flattenSteps(steps: readonly PipelineStepInput[]): readonly PipelineStepInput[] {\n const out: PipelineStepInput[] = []\n const walk = (s: PipelineStepInput): void => {\n out.push(s)\n if (s.children) {\n for (const c of s.children) walk(c)\n }\n }\n for (const s of steps) walk(s)\n return out\n }\n\n private detachInternal(deviceId: number): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n this.attached.delete(deviceId)\n this.lastMotionAt.delete(deviceId)\n this.lastEmittedCameraMetrics.delete(deviceId)\n\n this.runner?.unregisterCamera(deviceId)\n this.ctx?.logger.info('detachCamera', { tags: { deviceId } })\n }\n\n async getLocalLoad(): Promise<RunnerLocalLoad> {\n const metrics = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? []\n\n let activeCameras = 0\n let totalActualFps = 0\n for (const cm of allCameraMetrics) {\n if (cm.phase === 'active') activeCameras++\n totalActualFps += cm.actualFps\n }\n\n // Phase 4: hardware probe moved off the runner.\n //\n // In the stateless-pipeline model each camera declares its own\n // engine. L3 hardware-aware balancing now lives on the orchestrator,\n // which knows `pipelines[deviceId].engine` and can match against\n // `nodes.topology.availableEngines` directly (spec Phase 3 extends\n // topology; consumed by the balancer once Phase 8 lands). The runner\n // no longer calls `pipelineExecutor.getSelectedEngine` — that cap is\n // being removed and its legacy \"one engine per node\" assumption\n // doesn't fit multi-engine nodes.\n return {\n nodeId: this.nodeId,\n attachedCameras: this.attached.size,\n activeCameras,\n avgInferenceFps: totalActualFps,\n avgInferenceTimeMs: metrics.avgInferenceTimeMs,\n queueDepthTotal: metrics.queueDepth,\n hardware: {\n hasGpu: false,\n inferenceBackend: undefined,\n },\n }\n }\n\n async getLocalMetrics(): Promise<RunnerLocalMetrics> {\n const m = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n return { nodeId: this.nodeId, ...m }\n }\n\n async getCameraMetrics(input: { deviceId: number }): Promise<CameraMetrics | null> {\n return this.runner?.getCameraMetrics(input.deviceId) ?? null\n }\n\n getAllCameraMetrics(): ReadonlyArray<{ deviceId: number } & CameraMetrics> {\n return this.runner?.getAllCameraMetrics() ?? []\n }\n\n getLocalCameras(): readonly number[] {\n return [...this.attached.keys()]\n }\n\n // ── Internal: broker subscription wiring ─────────────────────────────\n\n private async subscribeMotionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeMotionFrames: this.ctx.api not available')\n return null\n }\n\n const motionBrokerId = `${config.deviceId}/${config.motionStreamId}`\n const motionBroker: IStreamBroker | null = await api.streamBroker.getBroker.query({ brokerId: motionBrokerId })\n if (!motionBroker) {\n log.warn('subscribeMotionFrames: no broker found', { meta: { brokerId: motionBrokerId } })\n return null\n }\n return motionBroker.onDecodedFrame(\n (frame: DecodedFrame) => {\n runner.enqueueMotionFrame(config.deviceId, frame)\n },\n { maxFps: config.motionFps, format: 'gray', tag: 'motion' },\n )\n }\n\n private handleDetectionStreamChange(deviceId: number, action: 'subscribe' | 'unsubscribe'): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n if (action === 'subscribe') {\n void this.subscribeDetectionFrames(attachment.config).then(unsub => {\n attachment.detectionUnsubscribe = unsub\n })\n } else {\n attachment.detectionUnsubscribe?.()\n attachment.detectionUnsubscribe = null\n }\n }\n\n /**\n * Bridge runner phase transitions to the device's `motion` runtime\n * state + the bus. Single ownership point — every motion source\n * (analyzer, onboard, future variants) funnels through the runner's\n * phase machine and lands here.\n *\n * - Cap-state via the unified `device-state.setCapSlice` API.\n * `autoClearAfterMs = cooldownMs` on ON, `null` on OFF.\n * `lastDetectedAt` is preserved across OFF using `lastMotionAt`.\n * - Bus event `MotionOnMotionChanged` fires alongside for consumers\n * that prefer event-driven over runtime-state polling.\n */\n private handlePhaseChanged(\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ): void {\n const detected = phase === 'active'\n if (detected) this.lastMotionAt.set(deviceId, meta.timestamp)\n const lastDetectedAt = this.lastMotionAt.get(deviceId) ?? null\n const slice = {\n detected,\n lastDetectedAt,\n autoClearAfterMs: detected ? meta.cooldownMs : null,\n }\n void (async () => {\n const dev = await this.ctx.fetchDevice(deviceId)\n await dev.deviceState.setCapSlice({ capName: 'motion', slice })\n })().catch((err: unknown) => {\n this.ctx.logger.debug('motion cap-state write failed', {\n tags: { deviceId },\n meta: { error: errMsg(err) },\n })\n })\n // Emit `DetectionPhaseTransition` so observers (UI events panel,\n // analytics, alert center) see WATCHING ↔ ACTIVE flips on the bus.\n // Sources (Reolink firmware, this addon's analyzer path) still own\n // `MotionOnMotionChanged` — that channel carries the raw motion\n // signal. This event carries the post-phase-machine truth (after\n // cooldown debouncing). Different consumers, different cadences.\n if (this.ctx.eventBus) {\n const from: 'watching' | 'active' = detected ? 'watching' : 'active'\n const to: 'watching' | 'active' = detected ? 'active' : 'watching'\n const reason: 'motion_detected' | 'cooldown_expired' = detected ? 'motion_detected' : 'cooldown_expired'\n const payload: PhaseTransitionPayload = {\n deviceId,\n from,\n to,\n reason,\n source: meta.source,\n cooldownMs: meta.cooldownMs,\n timestamp: meta.timestamp,\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DetectionPhaseTransition,\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n }\n\n private async subscribeDetectionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeDetectionFrames: this.ctx.api not available')\n return null\n }\n\n const detectionBrokerId = `${config.deviceId}/${config.detectionStreamId}`\n const detectionBroker: IStreamBroker | null = await api.streamBroker.getBroker.query({ brokerId: detectionBrokerId })\n if (!detectionBroker) {\n log.warn('subscribeDetectionFrames: no broker found', { meta: { brokerId: detectionBrokerId } })\n return null\n }\n\n return detectionBroker.onDecodedFrame(\n (frame: DecodedFrame) => {\n runner.enqueueDetectionFrame(config.deviceId, frame)\n },\n // `format: 'rgb'` is the Phase 4 hot-path switch: detection now\n // requests raw RGB24 from the broker so the decoder skips the\n // sharp JPEG encode and the Python pool skips PIL JPEG decode.\n // When WebRTC also subscribes (jpeg), the broker derives JPEG\n // once per frame via its conversion cache — no double work.\n { maxFps: config.detectionFps, format: 'rgb', tag: 'detection' },\n )\n }\n\n // ── Internal: inference + motion callbacks ───────────────────────────\n\n private async runInference(deviceId: number, frame: FrameInput): Promise<FrameResult | null> {\n const ctx = this.ctx\n if (!ctx) return null\n\n const log = this.ctx.logger.withTags({ deviceId })\n const api = this.ctx.api\n if (!api) {\n log.error('runInference: this.ctx.api not available')\n return null\n }\n\n const attachment = this.attached.get(deviceId)\n const camConfig = attachment?.config\n const steps = camConfig?.steps\n const engine = camConfig?.engine\n\n // Stateless-pipeline dispatch (Phase 4): the orchestrator hydrates\n // every attach with `steps` (from `pipelines[deviceId]` or the\n // cluster default). Absent steps means the camera was attached\n // before the orchestrator migrated — skip the frame rather than\n // falling back to the removed legacy `runFrame` path.\n if (!steps) {\n log.warn('runInference: no steps in attach config — skipping frame (legacy attach?)')\n return null\n }\n if (steps.length === 0) {\n return null\n }\n\n try {\n return await api.pipelineExecutor.runPipeline.mutate({\n // tRPC input is a mutable array; the attach payload holds it\n // as readonly. One spread copy at the cap boundary is cheap\n // (pipeline step trees are tiny) and keeps the type surface\n // clean without casting.\n steps: [...steps],\n frame,\n deviceId,\n ...(engine ? { engine } : {}),\n })\n } catch (err: unknown) {\n const msg = errMsg(err)\n log.error('runInference failed', { meta: { error: msg } })\n return null\n }\n }\n\n private async runMotionAnalysis(deviceId: number, frame: DecodedFrame): Promise<void> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return\n\n const log = this.ctx.logger.withTags({ deviceId })\n const motionStart = Date.now()\n try {\n const api = this.ctx.api\n if (!api) {\n log.warn('runMotionAnalysis: this.ctx.api not available')\n return\n }\n const result = await api.motionDetection.analyze.mutate({ deviceId, frame: toFrameInput(frame) })\n if (!result) return\n\n const detected = result.regions.length > 0\n\n // Emit `MotionOnMotionChanged` ONLY on transitions (analyzer runs\n // every motion frame at `motionFps`; we don't want one event per\n // frame). The runner's bus subscriber receives this and drives\n // the phase machine — same path Reolink's firmware events take.\n // No in-process `reportMotion` call here: the unified subscription\n // is the single dispatch point so onboard and analyzer go through\n // identical logic.\n const prevDetected = this.lastAnalyzerDetected.get(deviceId) ?? false\n if (detected !== prevDetected) {\n this.lastAnalyzerDetected.set(deviceId, detected)\n if (this.ctx.eventBus) {\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionOnMotionChanged,\n // EventSource wrapper kept symmetric with the onboard\n // emit (Reolink/ONVIF/etc.) so consumers grouping by\n // addonId / deviceId see consistent provenance. `nodeId`\n // identifies which cluster node ran the analyzer.\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n {\n deviceId,\n detected,\n timestamp: frame.timestamp,\n source: 'analyzer',\n ...(detected ? { regions: result.regions } : {}),\n },\n ))\n }\n }\n\n if (this.ctx.eventBus) {\n const motionPayload: MotionAnalysisPayload = {\n detected,\n regionCount: result.regions.length,\n regions: result.regions.map((r) => ({\n bbox: { x: r.bbox.x, y: r.bbox.y, w: r.bbox.w, h: r.bbox.h },\n pixelCount: r.pixelCount,\n intensity: r.intensity,\n })),\n frameWidth: frame.width,\n frameHeight: frame.height,\n analysisMs: result.analysisMs,\n }\n const analyzerSource = { type: 'device' as const, id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionAnalysis,\n analyzerSource,\n motionPayload,\n ))\n\n const zonesPayload: MotionZonesRawPayload = {\n deviceId,\n timestamp: frame.timestamp,\n zones: result.rawRegions.map((r) => ({\n bbox: [r.bbox.x, r.bbox.y, r.bbox.x + r.bbox.w, r.bbox.y + r.bbox.h] as const,\n pixelCount: r.pixelCount,\n changeScore: r.intensity / 255,\n })),\n frameSize: { width: frame.width, height: frame.height },\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionZonesRaw,\n analyzerSource,\n zonesPayload,\n ))\n }\n\n runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart)\n } catch (error: unknown) {\n const msg = errMsg(error)\n log.error('runMotionAnalysis failed', { meta: { error: msg } })\n }\n }\n\n private emitInferenceResult(deviceId: number, _frame: DecodedFrame, result: FrameResult): void {\n const ctx = this.ctx\n if (!ctx?.eventBus) return\n\n // The new FrameResult already carries width/height/timestamp at the\n // root + debug.stepTimings / debug.totalInferenceMs. No frameMetadata\n // wrapper needed — the hub subscribes and reads `payload.frame`\n // directly.\n const payload: PipelineInferenceResultPayload = {\n deviceId,\n frame: result,\n nodeId: this.nodeId,\n }\n\n this.ctx.eventBus.emit(createEvent(\n EventCategory.PipelineInferenceResult,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n\n /**\n * Emit periodic metric snapshots: one runner-load event for the\n * node + one camera-metrics event per attached camera. Subscribed\n * by admin-ui dashboards (LiveLoadPanel, NodeDetailHeader,\n * CameraStreamPanel) to drive live overlays without polling.\n *\n * Skipped when there are no cameras attached so quiet dev runs\n * don't emit needless bus traffic. The runner-load event is still\n * emitted in that case because the dashboards rely on it to see\n * \"agent reachable, idle\".\n */\n private emitMetricsSnapshot(): void {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx?.eventBus || !runner) return\n const timestamp = Date.now()\n\n // Runner-load snapshot. Skip emit when the load payload is\n // bytewise-identical to the previous one (idle node — same\n // running-cap counts, same queue depths, same memory). Heartbeat\n // re-emits every METRICS_HEARTBEAT_MS so the \"agent reachable\"\n // chip in the UI doesn't go stale.\n void this.getLocalLoad().then((load) => {\n if (!ctx.eventBus) return\n const json = JSON.stringify(load)\n const prev = this.lastEmittedRunnerLoad\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) return\n this.lastEmittedRunnerLoad = { json, emittedAt: timestamp }\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineRunnerLoadSnapshot,\n { type: 'node', id: this.nodeId, nodeId: this.nodeId },\n { nodeId: this.nodeId, load, timestamp },\n ))\n }).catch(() => {\n // Skip this tick on transient failure — next interval will\n // reconcile. No need to log: the snapshot is a soft observability\n // path, not a correctness boundary.\n })\n\n // Per-camera metrics snapshot — same defer pattern.\n if (this.attached.size === 0) return\n for (const deviceId of this.attached.keys()) {\n const metrics = runner.getCameraMetrics(deviceId)\n if (!metrics) continue\n const json = JSON.stringify(metrics)\n const prev = this.lastEmittedCameraMetrics.get(deviceId)\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) continue\n this.lastEmittedCameraMetrics.set(deviceId, { json, emittedAt: timestamp })\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineCameraMetricsSnapshot,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n { deviceId, nodeId: this.nodeId, metrics, timestamp },\n ))\n }\n }\n\n // ── Standard ICamstackAddon — three-level settings API (Phase 3) ─────\n //\n // The runner is a per-node addon with only ADDON-LEVEL settings (no\n // per-device overrides, no cluster-wide tunables). All four tuning\n // fields live in `getAddonSettings()`. When the UI surface moves in\n // Phase 9 these will be rendered under Pipeline -> node -> Settings.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'pipeline-runner-tuning',\n title: 'Pipeline Runner',\n tab: 'scheduler',\n description: 'Per-node detection scheduler tuning. Change only if you understand the pipeline internals.',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'maxConcurrentInferences',\n label: 'Scheduler concurrency',\n description:\n 'Max parallel inferences the runner scheduler allows across all cameras on this node. ' +\n 'Distinct from the detection-pipeline inference-pool worker count (Pipeline tab → \"Worker concurrency\"), ' +\n 'which controls Python-side thread pool sizing inside a single inference job.',\n min: 1,\n max: 16,\n step: 1,\n default: DEFAULT_CONFIG.maxConcurrentInferences,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'maxQueueDepth',\n label: 'Max queue depth',\n description: 'Maximum frames held per camera before dropping.',\n min: 5,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.maxQueueDepth,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'targetLoadPercent',\n label: 'Target load',\n description: 'Percentage of inference capacity to target before throttling FPS.',\n min: 50,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.targetLoadPercent,\n unit: '%',\n showValue: true,\n },\n {\n type: 'slider',\n key: 'minThrottledFps',\n label: 'Min throttled FPS',\n description: 'Lowest FPS the runner will allow when load-shedding.',\n min: 1,\n max: 10,\n step: 1,\n default: DEFAULT_CONFIG.minThrottledFps,\n showValue: true,\n },\n ],\n },\n ],\n })\n }\n\n protected async onConfigChanged(): Promise<void> {\n this.runner?.updateLimits(this.config)\n this.ctx.logger.info(\n 'pipeline-runner tuning updated',\n {\n meta: {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n },\n },\n )\n }\n}\n\n// Re-export for tests / consumers that need direct class access.\nexport { PipelineRunner } from './runner.js'\nexport { FrameQueue } from './frame-queue.js'\nexport { Semaphore } from './semaphore.js'\nexport { PipelineTimingSampler } from './timing-sampler.js'\n\n/**\n * Static `customActions` catalog — read by the addon registry at boot time\n * (before instantiating the addon class) so cross-process Moleculer routes\n * can be wired for `addons.custom.<action>` dispatch. Mirrors the pattern\n * in addon-benchmark/index.ts.\n */\nexport { pipelineRunnerBenchActions as customActions } from './bench-actions.js'\n"],"names":["toFrameInput","warmupCount","wallTimings","inferTimings","preprocessTimings","predictTimings","batchSizes","detCounts","sessionId","totalRuns","wallStart","worker","wallSec"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/pipeline-runner/frame-queue.ts","../../src/pipeline-runner/semaphore.ts","../../src/pipeline-runner/timing-sampler.ts","../../src/pipeline-runner/runner.ts","../../src/pipeline-runner/bench-actions.ts","../../src/pipeline-runner/index.ts"],"sourcesContent":["import type { DecodedFrame } from '@camstack/types'\n\n/**\n * Latest-frame-only buffer. Keeps only the most recent frame, dropping all\n * older frames immediately. This ensures inference always runs on the\n * freshest available frame, never accumulating a backlog regardless of\n * inference latency.\n */\nexport class FrameQueue {\n private latest: DecodedFrame | null = null\n private _droppedFrames = 0\n\n constructor(readonly maxSize: number) {}\n\n enqueue(frame: DecodedFrame): void {\n if (this.latest !== null) {\n this._droppedFrames++\n }\n this.latest = frame\n }\n\n dequeue(): DecodedFrame | undefined {\n const frame = this.latest ?? undefined\n this.latest = null\n return frame\n }\n\n get size(): number {\n return this.latest !== null ? 1 : 0\n }\n\n get droppedFrames(): number {\n return this._droppedFrames\n }\n\n clear(): void {\n this.latest = null\n }\n}\n","/**\n * Counting semaphore with FIFO waiter queue. Used by the runner to bound\n * concurrent inference invocations across all attached cameras.\n *\n * The concurrency limit is **mutable** via `resize()` so the\n * pipeline-runner addon can hot-reload `maxConcurrentInferences` without\n * tearing down and restarting the runner. In-flight permits are\n * preserved: shrinking the limit just lowers the headroom until existing\n * releases catch up; growing it wakes pending waiters immediately.\n */\nexport class Semaphore {\n private _concurrency: number\n private _available: number\n private readonly waiters: Array<() => void> = []\n\n constructor(concurrency: number) {\n this._concurrency = concurrency\n this._available = concurrency\n }\n\n get concurrency(): number {\n return this._concurrency\n }\n\n get available(): number {\n return this._available\n }\n\n /**\n * Change the concurrency limit at runtime. Growing wakes as many\n * pending waiters as possible without exceeding the new headroom;\n * shrinking simply caps `_available` to `max(0, _available + delta)`.\n * In-flight permits are never revoked — the excess will drain\n * naturally as existing callers release.\n */\n resize(newConcurrency: number): void {\n if (newConcurrency < 1) throw new Error('Semaphore: concurrency must be >= 1')\n const delta = newConcurrency - this._concurrency\n this._concurrency = newConcurrency\n this._available = Math.max(0, this._available + delta)\n // If we just grew, wake as many waiters as new headroom allows.\n while (this._available > 0 && this.waiters.length > 0) {\n const next = this.waiters.shift()\n if (next) next()\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this._available > 0) {\n this._available--\n return () => this.release()\n }\n\n return new Promise<() => void>((resolve) => {\n this.waiters.push(() => {\n this._available--\n resolve(() => this.release())\n })\n })\n }\n\n private release(): void {\n this._available++\n const next = this.waiters.shift()\n if (next) next()\n }\n}\n","import type { IScopedLogger } from '@camstack/types'\n\nconst REPORT_INTERVAL_MS = 10_000\n\n/**\n * Audio sample shape consumed by the timing sampler. Pushed by the runner's\n * audio subscription handler so that the periodic timing log includes audio\n * classification stats alongside motion + detection stats.\n */\nexport interface AudioSample {\n readonly classifyMs: number\n readonly dbfs: number\n readonly topLabel: string | null\n readonly topScore: number\n}\n\n/**\n * Runtime info labels for the timing sampler — purely cosmetic, used to tag\n * the periodic timing log lines with the active engine + addon names.\n */\nexport interface RuntimeInfo {\n readonly pipelineRuntime?: string\n readonly pipelineModels?: readonly string[]\n readonly motionAddon?: string\n readonly audioEngine?: string\n}\n\ninterface PhaseSample {\n /** ms from frame.timestamp (camera capture) to enqueue */\n captureToEnqueue: number\n /** ms waiting in queue before being picked */\n queueWait: number\n /** ms waiting to acquire semaphore */\n semaphoreWait: number\n /** ms for the full processFrame call (IPC + inference) */\n inference: number\n /** ms from result ready to event emitted */\n resultToEmit: number\n /** total end-to-end: capture to event emitted */\n endToEnd: number\n /** number of detections returned */\n detections: number\n}\n\n/**\n * Periodic timing-stats sampler. Accumulates per-camera samples for the\n * detection pipeline (capture → enqueue → semaphore → inference → emit),\n * motion analysis, and audio classification, and logs aggregated stats\n * every REPORT_INTERVAL_MS.\n */\nexport class PipelineTimingSampler {\n private readonly detSamples = new Map<number, PhaseSample[]>()\n private readonly motSamples = new Map<number, number[]>()\n private readonly audioSamples = new Map<number, AudioSample[]>()\n private droppedFrames = 0\n private reportTimer: ReturnType<typeof setInterval> | null = null\n private log: IScopedLogger | null = null\n runtimeInfo: RuntimeInfo = {}\n\n setLogger(logger: IScopedLogger): void {\n this.log = logger\n }\n\n start(): void {\n if (this.reportTimer) return\n this.reportTimer = setInterval(() => this.report(), REPORT_INTERVAL_MS)\n }\n\n stop(): void {\n if (this.reportTimer) {\n clearInterval(this.reportTimer)\n this.reportTimer = null\n }\n }\n\n addSample(deviceId: number, s: PhaseSample): void {\n if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, [])\n this.detSamples.get(deviceId)!.push(s)\n }\n\n addMotionSample(deviceId: number, ms: number): void {\n if (!this.motSamples.has(deviceId)) this.motSamples.set(deviceId, [])\n this.motSamples.get(deviceId)!.push(ms)\n }\n\n addAudioSample(deviceId: number, s: AudioSample): void {\n if (!this.audioSamples.has(deviceId)) this.audioSamples.set(deviceId, [])\n this.audioSamples.get(deviceId)!.push(s)\n }\n\n addDrop(): void {\n this.droppedFrames++\n }\n\n private report(): void {\n if (!this.log) return\n\n const dropped = this.droppedFrames\n this.droppedFrames = 0\n\n const avg = (arr: number[]): number => arr.length > 0 ? Math.round(arr.reduce((a, b) => a + b, 0) / arr.length) : 0\n const max = (arr: number[]): number => arr.length > 0 ? Math.round(Math.max(...arr)) : 0\n const p95 = (arr: number[]): number => {\n if (arr.length === 0) return 0\n const sorted = [...arr].sort((a, b) => a - b)\n return Math.round(sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1]!)\n }\n\n const rt = this.runtimeInfo\n\n for (const [deviceId, det] of this.detSamples) {\n if (det.length === 0) continue\n const e2e = det.map((s) => s.endToEnd)\n const inf = det.map((s) => s.inference)\n const totalDet = det.reduce((s, d) => s + d.detections, 0)\n\n this.log.info(\n 'pipeline stats',\n {\n tags: { deviceId },\n meta: {\n frames: det.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n e2e: { avg: avg(e2e), p95: p95(e2e), max: max(e2e) },\n inference: { avg: avg(inf), p95: p95(inf) },\n detections: totalDet,\n dropped,\n pipelineRuntime: rt.pipelineRuntime ?? null,\n pipelineModels: rt.pipelineModels ?? null,\n },\n },\n )\n }\n this.detSamples.clear()\n\n for (const [deviceId, mot] of this.motSamples) {\n if (mot.length === 0) continue\n this.log.info(\n 'motion stats',\n {\n tags: { deviceId },\n meta: {\n frames: mot.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n avg: avg(mot),\n p95: p95(mot),\n max: max(mot),\n // motionAddon: rt.motionAddon ?? null,\n },\n },\n )\n }\n this.motSamples.clear()\n\n for (const [deviceId, aud] of this.audioSamples) {\n if (aud.length === 0) continue\n const classifyTimes = aud.filter((a) => a.classifyMs > 0).map((a) => a.classifyMs)\n const classified = aud.filter((a) => a.topLabel !== null)\n const topLabels = new Map<string, number>()\n for (const a of classified) {\n if (a.topLabel) topLabels.set(a.topLabel, (topLabels.get(a.topLabel) ?? 0) + 1)\n }\n const topSummary = [...topLabels.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([l, c]) => `${l}×${c}`).join(', ')\n const avgDbfs = avg(aud.map((a) => Math.round(a.dbfs)))\n\n this.log.info(\n 'audio stats',\n {\n tags: { deviceId },\n meta: {\n chunks: aud.length,\n intervalSec: REPORT_INTERVAL_MS / 1000,\n classified: classified.length,\n classifyAvgMs: classifyTimes.length > 0 ? avg(classifyTimes) : 0,\n avgDbfs,\n topLabels: topSummary,\n audioEngine: rt.audioEngine ?? null,\n },\n },\n )\n }\n this.audioSamples.clear()\n }\n}\n","import type {\n FrameInput, FrameResult, OrchestratorMetrics, CameraMetrics,\n DecodedFrame, CameraPhase, IScopedLogger,\n MotionSource, MotionRegion,\n PipelinePhaseMode,\n} from '@camstack/types'\nimport { FrameQueue } from './frame-queue.js'\nimport { Semaphore } from './semaphore.js'\nimport { PipelineTimingSampler } from './timing-sampler.js'\n\nexport type StreamType = 'motion' | 'detection'\n\n/**\n * Metadata accompanying a phase transition emitted by the runner.\n * The addon wrapping the scheduler turns this into a `motion`\n * cap-state slice + an `onMotionChanged` bus event.\n */\nexport interface PhaseTransitionMeta {\n readonly source: MotionSource\n readonly regions?: ReadonlyArray<MotionRegion>\n readonly timestamp: number\n readonly cooldownMs: number\n}\n\nexport interface RunnerConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n /** Run inference on a detection frame (heavy, semaphore-limited). */\n readonly processFrame: (deviceId: number, frame: FrameInput) => Promise<FrameResult | null>\n /** Analyze a motion frame for activity (lightweight, no semaphore). */\n readonly analyzeMotion: (deviceId: number, frame: DecodedFrame) => Promise<void>\n /**\n * Fired on every phase transition (`watching ↔ active`). The\n * surrounding addon hooks this to (1) write the device's `motion`\n * cap-state slice via the unified `device-state.setCapSlice`\n * entrypoint and (2) emit `motion.onMotionChanged` on the bus.\n * Optional — test harnesses skip it.\n */\n readonly onPhaseChanged?: (\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ) => void\n /** Optional scoped logger; when provided, motion-gate transitions log here. */\n readonly logger?: IScopedLogger\n}\n\n/**\n * Per-camera registration. Mirrors the public RunnerCameraConfig but\n * narrowed to the fields the scheduler core actually needs (mode, fps,\n * cooldown). Broker subscription details are owned by the addon class\n * wrapping this scheduler — they don't reach the scheduler itself.\n */\nexport interface CameraRegistration {\n /** Operator-facing scheduling mode for the SW detection pipeline.\n * `'disabled'` — scheduler stays in `'idle'`, never processes frames.\n * `'always-on'` — initial phase `'active'`, no motion-gate fallback.\n * `'on-motion'` — initial phase `'watching'`, motion-gated. */\n readonly detectionMode: PipelinePhaseMode\n readonly fps: number\n readonly motionCooldownMs?: number\n}\n\nexport type ResultCallback = (\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n) => Promise<void> | void\n\nexport type DetectionStreamHandler = (\n deviceId: number,\n action: 'subscribe' | 'unsubscribe',\n) => void\n\ninterface CameraState {\n readonly registration: CameraRegistration\n readonly motionQueue: FrameQueue\n readonly detectionQueue: FrameQueue\n readonly inferenceTimes: number[]\n processedCount: number\n startTime: number\n phase: CameraPhase\n motionCooldownTimer: ReturnType<typeof setTimeout> | null\n /**\n * Source that armed the current cooldown timer. Carried into the\n * transition-to-watching event so consumers know which path was\n * the last positive signal before timeout.\n */\n lastArmedSource: MotionSource | null\n /** Regions from the most recent positive `analyzer` event — undefined for `onboard`. */\n lastArmedRegions: ReadonlyArray<MotionRegion> | undefined\n}\n\n// Fallback cooldown when the runner registration omits an explicit\n// `motionCooldownMs`. Aligned with the schema default in\n// `pipeline-runner.cap.ts` (motionCooldownMsField.default) so\n// always-on / synthetic registrations get the same window the\n// device-settings schema serves to operator-driven attaches.\nconst DEFAULT_MOTION_COOLDOWN_MS = 30_000\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/**\n * Pipeline runner scheduler — the per-node detection scheduler.\n *\n * Owns per-camera frame queues, the inference semaphore, the round-robin\n * scheduler, and the motion → detection phase machine. Inference and\n * motion analysis are delegated to caller-provided callbacks injected at\n * construction time so the scheduler stays decoupled from the surrounding\n * addon ecosystem (the addon class wrapping this scheduler injects\n * callbacks that resolve to the local pipeline-executor / motion-detection\n * caps when running in production).\n *\n * This class is intentionally framework-agnostic and side-effect-free\n * outside the callbacks — it can be unit tested with mock callbacks\n * without spinning up Moleculer or the addon framework.\n */\nexport class PipelineRunner {\n // Config is mutable (not `readonly`) because `updateLimits()` hot-reloads\n // the four tuning fields when the pipeline-runner addon's\n // `updateAddonSettings` is invoked via the new three-level settings API.\n // The callbacks (`processFrame`, `analyzeMotion`) are invariants captured\n // at construction and never changed.\n private config: RunnerConfig\n private readonly cameras = new Map<number, CameraState>()\n private readonly semaphore: Semaphore\n private readonly resultCallbacks: ResultCallback[] = []\n private readonly defaultRoundRobinKeys: number[] = []\n private defaultRoundRobinIndex = 0\n private intervalHandle: ReturnType<typeof setInterval> | null = null\n private detectionStreamHandler: DetectionStreamHandler | null = null\n private readonly logger: IScopedLogger | undefined\n readonly timingSampler = new PipelineTimingSampler()\n\n constructor(config: RunnerConfig) {\n this.config = config\n this.logger = config.logger\n this.semaphore = new Semaphore(config.maxConcurrentInferences)\n }\n\n /**\n * Hot-reload the four tuning fields without tearing down the runner.\n * - `maxConcurrentInferences`: resized on the live semaphore; in-flight\n * permits are preserved, new capacity is available immediately.\n * - `maxQueueDepth`: new `FrameQueue`s created from this point on use\n * the updated ceiling. Existing per-camera queues are not resized\n * (the FrameQueue implementation is latest-only and ignores maxSize\n * anyway — see `frame-queue.ts` — so the field is effectively a\n * metadata hint for observability).\n * - `targetLoadPercent` / `minThrottledFps`: stored for future\n * throttling logic (not yet consumed in the current runner body).\n *\n * Only keys present in the patch are overwritten; unspecified keys\n * retain their current value. Any illegal combination (e.g.\n * concurrency < 1) throws and leaves the runner unchanged.\n */\n updateLimits(patch: {\n readonly maxQueueDepth?: number\n readonly maxConcurrentInferences?: number\n readonly targetLoadPercent?: number\n readonly minThrottledFps?: number\n }): void {\n const next: RunnerConfig = {\n ...this.config,\n maxQueueDepth: patch.maxQueueDepth ?? this.config.maxQueueDepth,\n maxConcurrentInferences: patch.maxConcurrentInferences ?? this.config.maxConcurrentInferences,\n targetLoadPercent: patch.targetLoadPercent ?? this.config.targetLoadPercent,\n minThrottledFps: patch.minThrottledFps ?? this.config.minThrottledFps,\n }\n if (next.maxConcurrentInferences !== this.config.maxConcurrentInferences) {\n this.semaphore.resize(next.maxConcurrentInferences)\n }\n this.config = next\n }\n\n /** Read the current tuning fields for diagnostics / tests. */\n getLimits(): {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n } {\n return {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n }\n }\n\n /** Set a handler called when the runner needs to subscribe/unsubscribe the detection stream. */\n onDetectionStreamChange(handler: DetectionStreamHandler): void {\n this.detectionStreamHandler = handler\n }\n\n registerCamera(deviceId: number, registration: CameraRegistration): void {\n const motionQueue = new FrameQueue(this.config.maxQueueDepth)\n const detectionQueue = new FrameQueue(this.config.maxQueueDepth)\n // Map the operator-facing detectionMode to the scheduler's initial\n // phase. `'disabled'` short-circuits everything (no subscribe, no\n // round-robin slot, no detection queue activity).\n const initialPhase: CameraPhase = registration.detectionMode === 'disabled'\n ? 'idle'\n : registration.detectionMode === 'always-on'\n ? 'active'\n : 'watching'\n\n const state: CameraState = {\n registration,\n motionQueue,\n detectionQueue,\n inferenceTimes: [],\n processedCount: 0,\n startTime: Date.now(),\n phase: initialPhase,\n motionCooldownTimer: null,\n lastArmedSource: null,\n lastArmedRegions: undefined,\n }\n this.cameras.set(deviceId, state)\n\n // Round-robin scheduling applies only to `on-motion`. `always-on`\n // doesn't need a slot (always at the front of the queue).\n // `disabled` skips entirely.\n if (registration.detectionMode === 'on-motion') {\n this.defaultRoundRobinKeys.push(deviceId)\n }\n\n if (initialPhase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n // Always-on cameras start in `active` immediately — emit the\n // synthetic ON transition so consumers (motion cap-state +\n // bus event) see the same shape the gated path produces.\n const cooldownMs = registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source: 'analyzer',\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n // `idle` (detectionMode='disabled') intentionally never emits a\n // phase event or subscribes — the runner registered the camera\n // for accounting but nothing flows.\n }\n\n unregisterCamera(deviceId: number): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'active') {\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n }\n\n state.motionQueue.clear()\n state.detectionQueue.clear()\n this.cameras.delete(deviceId)\n\n const idx = this.defaultRoundRobinKeys.indexOf(deviceId)\n if (idx !== -1) {\n this.defaultRoundRobinKeys.splice(idx, 1)\n if (this.defaultRoundRobinIndex >= this.defaultRoundRobinKeys.length) {\n this.defaultRoundRobinIndex = 0\n }\n }\n }\n\n enqueueMotionFrame(deviceId: number, frame: DecodedFrame): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n state.motionQueue.enqueue(frame)\n }\n\n enqueueDetectionFrame(deviceId: number, frame: DecodedFrame): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n if (state.phase !== 'active') return\n ;(frame as { _enqueuedAt?: number })._enqueuedAt = Date.now()\n state.detectionQueue.enqueue(frame)\n }\n\n /**\n * Report a motion event for a camera. Drives the unified phase\n * machine for both motion sources (analyzer + onboard):\n *\n * - Every `detected: true` (any source) clears + rearms the\n * cooldown timer and transitions watching → active. The same\n * timer applies regardless of which source(s) are configured;\n * concurrent sources just keep refreshing the same window.\n * - `detected: false` is a no-op. Onboard sources never send an\n * explicit clear, and the analyzer's \"false\" pulses would\n * otherwise fight the cooldown when motion paused briefly\n * during a scene. The timer is the single closure path.\n * - Timer expiry transitions active → watching.\n *\n * Always-on cameras silently ignore reportMotion calls — they're\n * already in `active` and have no cooldown.\n *\n * `source` and `regions` propagate into the phase-transition event\n * so the wrapping addon can attach them to the cap-state slice +\n * bus event.\n */\n reportMotion(\n deviceId: number,\n detected: boolean,\n source: MotionSource = 'analyzer',\n regions: ReadonlyArray<MotionRegion> | undefined = undefined,\n ): void {\n const state = this.cameras.get(deviceId)\n if (!state) return\n\n // `always-on` ignores motion (already active); `disabled` ignores\n // motion (camera registered but never subscribed to detection).\n // Only `on-motion` flows through the cooldown path.\n if (state.registration.detectionMode !== 'on-motion') return\n if (!detected) return\n\n state.lastArmedSource = source\n state.lastArmedRegions = regions\n const cooldownMs = state.registration.motionCooldownMs ?? DEFAULT_MOTION_COOLDOWN_MS\n\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n\n if (state.phase === 'watching') {\n this.transitionToActive(deviceId, state, source, regions, cooldownMs)\n }\n\n state.motionCooldownTimer = setTimeout(() => {\n state.motionCooldownTimer = null\n this.transitionToWatching(deviceId, state, cooldownMs)\n }, cooldownMs)\n }\n\n getPhase(deviceId: number): CameraPhase | undefined {\n return this.cameras.get(deviceId)?.phase\n }\n\n onResult(callback: ResultCallback): void {\n this.resultCallbacks.push(callback)\n }\n\n start(): void {\n if (this.intervalHandle !== null) return\n this.intervalHandle = setInterval(() => this.tick(), 10)\n this.timingSampler.start()\n }\n\n stop(): void {\n if (this.intervalHandle !== null) {\n clearInterval(this.intervalHandle)\n this.intervalHandle = null\n }\n this.timingSampler.stop()\n\n for (const state of this.cameras.values()) {\n if (state.motionCooldownTimer !== null) {\n clearTimeout(state.motionCooldownTimer)\n state.motionCooldownTimer = null\n }\n }\n }\n\n getMetrics(): OrchestratorMetrics {\n let totalQueueDepth = 0\n let totalInferenceTime = 0\n let totalInferenceCount = 0\n\n for (const state of this.cameras.values()) {\n totalQueueDepth += state.motionQueue.size + state.detectionQueue.size\n for (const t of state.inferenceTimes) {\n totalInferenceTime += t\n totalInferenceCount++\n }\n }\n\n return {\n activeCameras: this.cameras.size,\n throttledCameras: 0,\n avgInferenceTimeMs: totalInferenceCount > 0 ? totalInferenceTime / totalInferenceCount : 0,\n queueDepth: totalQueueDepth,\n }\n }\n\n getCameraMetrics(deviceId: number): CameraMetrics | undefined {\n const state = this.cameras.get(deviceId)\n if (!state) return undefined\n\n const elapsedMs = Date.now() - state.startTime\n const elapsedSec = elapsedMs / 1000\n const actualFps = elapsedSec > 0 ? state.processedCount / elapsedSec : 0\n\n const times = state.inferenceTimes\n const avgInference = times.length > 0 ? times.reduce((a, b) => a + b, 0) / times.length : 0\n\n return {\n detectionMode: state.registration.detectionMode,\n configuredFps: state.registration.fps,\n actualFps,\n queueDepth: state.motionQueue.size + state.detectionQueue.size,\n avgInferenceTimeMs: avgInference,\n droppedFrames: state.motionQueue.droppedFrames + state.detectionQueue.droppedFrames,\n phase: state.phase,\n }\n }\n\n getAllCameraMetrics(): Array<{ deviceId: number } & CameraMetrics> {\n const results: Array<{ deviceId: number } & CameraMetrics> = []\n for (const [deviceId] of this.cameras) {\n const metrics = this.getCameraMetrics(deviceId)\n if (metrics) {\n results.push({ deviceId, ...metrics })\n }\n }\n return results\n }\n\n getAttachedCameras(): readonly number[] {\n return [...this.cameras.keys()]\n }\n\n private transitionToActive(\n deviceId: number,\n state: CameraState,\n source: MotionSource,\n regions: ReadonlyArray<MotionRegion> | undefined,\n cooldownMs: number,\n ): void {\n state.phase = 'active'\n this.logger?.info('motion gate opened — phase=active', {\n tags: { deviceId },\n meta: { detectionMode: state.registration.detectionMode, source },\n })\n this.detectionStreamHandler?.(deviceId, 'subscribe')\n this.config.onPhaseChanged?.(deviceId, 'active', {\n source,\n regions,\n timestamp: Date.now(),\n cooldownMs,\n })\n }\n\n private transitionToWatching(\n deviceId: number,\n state: CameraState,\n cooldownMs: number,\n ): void {\n state.phase = 'watching'\n state.detectionQueue.clear()\n this.logger?.info('motion gate closed — phase=watching', {\n tags: { deviceId },\n meta: { lastSource: state.lastArmedSource },\n })\n this.detectionStreamHandler?.(deviceId, 'unsubscribe')\n // Last source that armed the timer carries the OFF event payload —\n // analyzer or onboard, whatever last fired before the silence\n // window expired. Regions are intentionally not propagated on\n // OFF: they describe a positive observation, not a closure.\n const source: MotionSource = state.lastArmedSource ?? 'analyzer'\n this.config.onPhaseChanged?.(deviceId, 'watching', {\n source,\n regions: undefined,\n timestamp: Date.now(),\n cooldownMs,\n })\n state.lastArmedSource = null\n state.lastArmedRegions = undefined\n }\n\n private tick(): void {\n this.drainMotionQueues()\n\n if (this.semaphore.available <= 0) return\n\n const picked = this.pickNextDetectionFrame()\n if (!picked) return\n\n const { deviceId, frame, state } = picked\n const frameInput = toFrameInput(frame)\n\n void this.processWithSemaphore(deviceId, frame, frameInput, state, 'detection')\n }\n\n private drainMotionQueues(): void {\n for (const [deviceId, state] of this.cameras) {\n while (state.motionQueue.size > 0) {\n const frame = state.motionQueue.dequeue()\n if (frame) {\n void this.config.analyzeMotion(deviceId, frame)\n }\n }\n }\n }\n\n private async processWithSemaphore(\n deviceId: number,\n frame: DecodedFrame,\n frameInput: FrameInput,\n state: CameraState,\n streamType: StreamType,\n ): Promise<void> {\n const pickedAt = Date.now()\n const captureTs = frame.timestamp\n const enqueuedAt = (frame as { _enqueuedAt?: number })._enqueuedAt ?? captureTs\n\n const release = await this.semaphore.acquire()\n const semAcquiredAt = Date.now()\n try {\n const result = await this.config.processFrame(deviceId, frameInput)\n const inferDoneAt = Date.now()\n const inferenceMs = inferDoneAt - semAcquiredAt\n\n state.inferenceTimes.push(inferenceMs)\n if (state.inferenceTimes.length > 100) {\n state.inferenceTimes.shift()\n }\n state.processedCount++\n\n if (result) {\n await this.notifyCallbacks(deviceId, frame, result, streamType)\n const emittedAt = Date.now()\n\n this.timingSampler.addSample(deviceId, {\n captureToEnqueue: enqueuedAt - captureTs,\n queueWait: pickedAt - enqueuedAt,\n semaphoreWait: semAcquiredAt - pickedAt,\n inference: inferenceMs,\n resultToEmit: emittedAt - inferDoneAt,\n endToEnd: emittedAt - captureTs,\n detections: result.detections?.length ?? 0,\n })\n }\n } finally {\n release()\n }\n }\n\n private async notifyCallbacks(\n deviceId: number,\n frame: DecodedFrame,\n result: FrameResult,\n streamType: StreamType,\n ): Promise<void> {\n for (const callback of this.resultCallbacks) {\n try {\n await callback(deviceId, frame, result, streamType)\n } catch {\n // Error isolation: individual callback failures don't affect others\n }\n }\n }\n\n private pickNextDetectionFrame(): {\n deviceId: number\n frame: DecodedFrame\n state: CameraState\n } | null {\n for (const [deviceId, state] of this.cameras) {\n if (state.registration.detectionMode === 'always-on' && state.detectionQueue.size > 0) {\n const frame = state.detectionQueue.dequeue()!\n return { deviceId, frame, state }\n }\n }\n\n if (this.defaultRoundRobinKeys.length === 0) return null\n\n const startIndex = this.defaultRoundRobinIndex\n for (let i = 0; i < this.defaultRoundRobinKeys.length; i++) {\n const idx = (startIndex + i) % this.defaultRoundRobinKeys.length\n const deviceId = this.defaultRoundRobinKeys[idx]\n if (!deviceId) continue\n const state = this.cameras.get(deviceId)\n if (!state) continue\n\n if (state.phase === 'active' && state.detectionQueue.size > 0) {\n this.defaultRoundRobinIndex = (idx + 1) % this.defaultRoundRobinKeys.length\n const frame = state.detectionQueue.dequeue()\n if (!frame) continue\n return { deviceId, frame, state }\n }\n }\n\n return null\n }\n}\n","/**\n * Synthetic-bench custom actions for pipeline-runner.\n *\n * Lets the admin UI drive a production-realistic detection benchmark:\n * the runner — already co-located with detection-pipeline in the\n * `pipeline` group — invokes `api.pipelineExecutor.runPipeline` directly\n * (in-process via localProviderLink, no Moleculer hop), N workers\n * concurrent × M iterations. Returns aggregate stats.\n *\n * Why this exists: the older bench path (UI → benchmark addon → pipeline\n * addon) crosses TWO Moleculer process boundaries that the production\n * camera path does NOT have (broker / runner / detection-pipeline are\n * all in the same group). Running the loop from inside the runner gives\n * fps numbers that match what real cameras achieve.\n */\nimport { z } from 'zod'\nimport { customAction, defineCustomActions } from '@camstack/types'\n\n// Local mirror of pipeline-executor's engine-choice schema. Re-declared here\n// to avoid a cap-package coupling for one bench cap; shape is fixed by the\n// runtime contract (runtime + backend + format + optional device).\nconst BenchEngineChoiceSchema = z.object({\n runtime: z.enum(['node', 'python']),\n backend: z.string(),\n format: z.enum(['onnx', 'coreml', 'openvino', 'tflite', 'pt']),\n device: z.string().optional(),\n})\n\n// Recursive step shape — matches PipelineStepInput minimally for cap input.\ninterface BenchStepShape {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStepShape[]\n}\nconst BenchStepSchema: z.ZodType<BenchStepShape> = z.lazy(() => z.object({\n addonId: z.string(),\n modelId: z.string(),\n enabled: z.boolean(),\n children: z.array(BenchStepSchema).optional(),\n}))\n\ninterface CacheBenchFrameInputShape {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\ninterface CacheBenchFrameResultShape {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport const CacheBenchFrameInputSchema: z.ZodType<CacheBenchFrameInputShape> = z.object({\n imageBase64: z.string(),\n ttlSeconds: z.number().int().positive().optional(),\n})\nexport const CacheBenchFrameResultSchema: z.ZodType<CacheBenchFrameResultShape> = z.object({\n frameId: z.string(),\n width: z.number(),\n height: z.number(),\n expiresAt: z.number(),\n})\n\ninterface ReleaseBenchFrameInputShape { readonly frameId: string }\ninterface ReleaseBenchFrameResultShape { readonly released: boolean }\nexport const ReleaseBenchFrameInputSchema: z.ZodType<ReleaseBenchFrameInputShape> = z.object({\n frameId: z.string(),\n})\nexport const ReleaseBenchFrameResultSchema: z.ZodType<ReleaseBenchFrameResultShape> = z.object({\n released: z.boolean(),\n})\n\ninterface RunSyntheticBenchInputShape {\n readonly frameId: string\n readonly steps: readonly BenchStepShape[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n /** When true, forces the full runPipeline path (1.2MB pipe transfer per\n * call) instead of the cached inferCached fast path. Simulates the real\n * camera detection pipeline cost including IPC overhead. */\n readonly simulatePipeline?: boolean\n readonly engine?: { readonly runtime: 'node' | 'python'; readonly backend: string; readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'; readonly device?: string }\n}\nexport const RunSyntheticBenchInputSchema: z.ZodType<RunSyntheticBenchInputShape> = z.object({\n frameId: z.string(),\n steps: z.array(BenchStepSchema).min(1),\n parallel: z.number().int().min(1).max(32),\n iterations: z.number().int().min(1).max(10_000),\n warmup: z.number().int().min(0).max(100).optional(),\n sessionId: z.string().optional(),\n simulatePipeline: z.boolean().optional(),\n engine: BenchEngineChoiceSchema.optional(),\n})\n\ninterface TimingSplitShape {\n readonly mean: number\n readonly p50: number\n readonly p95: number\n readonly p99: number\n}\nconst TimingSplitSchema: z.ZodType<TimingSplitShape> = z.object({\n mean: z.number(),\n p50: z.number(),\n p95: z.number(),\n p99: z.number(),\n})\n\ninterface RunSyntheticBenchResultShape {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: TimingSplitShape\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\nexport const RunSyntheticBenchResultSchema: z.ZodType<RunSyntheticBenchResultShape> = z.object({\n runs: z.number(),\n wallSec: z.number(),\n fps: z.number(),\n detectionsPerSec: z.number(),\n avgDetections: z.number(),\n callMs: TimingSplitSchema,\n inferMs: z.number(),\n preprocessMs: z.number(),\n predictMs: z.number(),\n batchSizeMean: z.number(),\n batchSizeMax: z.number(),\n engine: z.object({ runtime: z.string(), backend: z.string(), device: z.string().optional() }).optional(),\n tuning: z.object({ batchMode: z.string(), windowMs: z.number(), maxBatchSize: z.number(), concurrency: z.number() }).optional(),\n path: z.string().optional(),\n})\n\nexport const pipelineRunnerBenchActions = defineCustomActions({\n cacheBenchFrame: customAction(\n CacheBenchFrameInputSchema,\n CacheBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n releaseBenchFrame: customAction(\n ReleaseBenchFrameInputSchema,\n ReleaseBenchFrameResultSchema,\n { kind: 'mutation' },\n ),\n runSyntheticBench: customAction(\n RunSyntheticBenchInputSchema,\n RunSyntheticBenchResultSchema,\n { kind: 'mutation' },\n ),\n})\n\nexport type PipelineRunnerBenchActions = typeof pipelineRunnerBenchActions\n\n// Explicit TypeScript types — z.infer<> on customAction's stored input/output\n// types loses precision through the spec wrapper. Caller code uses these.\nexport interface BenchStep {\n readonly addonId: string\n readonly modelId: string\n readonly enabled: boolean\n readonly children?: readonly BenchStep[]\n}\nexport interface BenchEngineChoice {\n readonly runtime: 'node' | 'python'\n readonly backend: string\n readonly format: 'onnx' | 'coreml' | 'openvino' | 'tflite' | 'pt'\n readonly device?: string\n}\nexport interface CacheBenchFrameInput {\n readonly imageBase64: string\n readonly ttlSeconds?: number\n}\nexport interface CacheBenchFrameResult {\n readonly frameId: string\n readonly width: number\n readonly height: number\n readonly expiresAt: number\n}\nexport interface ReleaseBenchFrameInput { readonly frameId: string }\nexport interface ReleaseBenchFrameResult { readonly released: boolean }\nexport interface RunSyntheticBenchInput {\n readonly frameId: string\n readonly steps: readonly BenchStep[]\n readonly parallel: number\n readonly iterations: number\n readonly warmup?: number\n readonly sessionId?: string\n readonly simulatePipeline?: boolean\n readonly engine?: BenchEngineChoice\n}\nexport interface RunSyntheticBenchResult {\n readonly runs: number\n readonly wallSec: number\n readonly fps: number\n readonly detectionsPerSec: number\n readonly avgDetections: number\n readonly callMs: { readonly mean: number; readonly p50: number; readonly p95: number; readonly p99: number }\n readonly inferMs: number\n readonly preprocessMs: number\n readonly predictMs: number\n readonly batchSizeMean: number\n readonly batchSizeMax: number\n readonly engine?: { readonly runtime: string; readonly backend: string; readonly device?: string }\n readonly tuning?: { readonly batchMode: string; readonly windowMs: number; readonly maxBatchSize: number; readonly concurrency: number }\n readonly path?: string\n}\n","/**\n * addon-pipeline-runner — per-node detection scheduler.\n *\n * Lives on every \"vision node\" (hub if hub is one, every agent that runs\n * decoders/cameras). Receives camera assignments from the hub-side\n * `addon-pipeline-orchestrator` via cap calls, owns the per-camera frame\n * queues + semaphore + motion → detection phase machine, calls the local\n * `motion-detection` and `pipeline-executor` capabilities for the actual\n * work, and emits typed events on the bus\n * (`detection.motion-analysis`, `pipeline.inference-result`, `pipeline.trace`).\n *\n * Design notes:\n *\n * - All cap inputs/outputs are serializable so the runner is transparently\n * routable across the cluster: when the runner is co-located with the\n * broker/decoder/motion/detection (the default and recommended deploy),\n * capability lookups return live singletons and frame data never crosses\n * a process boundary. When a deploy splits the runner from the executor\n * (rare), the lookup returns null and the runner falls back to\n * `this.ctx.api` (broker proxy) at the cost of serialization.\n *\n * - The scheduler core (`PipelineRunner`) is callback-driven and framework\n * agnostic. The addon class injects the inference + motion callbacks at\n * construction time, capturing references to the relevant providers\n * lazily on each invocation.\n *\n * - Result post-processing (analysis pipeline, class filters, notifications)\n * is intentionally NOT done by the runner. The runner emits the raw\n * inference output as a `pipeline.inference-result` event on the bus;\n * a hub-side subscriber consumes the event and runs the post-processing\n * pipeline. This keeps the runner stateless w.r.t. detection-side\n * business logic and makes it possible to deploy the post-processor\n * independently from the inference workers.\n */\nimport type {\n IPipelineRunnerProvider, RunnerCameraConfig, RunnerLocalLoad, RunnerLocalMetrics,\n CameraMetrics, FrameInput, FrameResult, DecodedFrame,\n IStreamBroker,\n PipelineInferenceResultPayload, MotionAnalysisPayload, MotionZonesRawPayload, PhaseTransitionPayload,\n PipelineStepInput,\n CameraPhase, MotionSource, MotionRegion,\n} from '@camstack/types'\nimport { BaseAddon, EventCategory, createEvent, pipelineRunnerCapability, errMsg } from '@camstack/types'\nimport type { AddonInitResult } from '@camstack/types'\nimport { PipelineRunner, type PhaseTransitionMeta } from './runner.js'\nimport {\n pipelineRunnerBenchActions,\n type PipelineRunnerBenchActions,\n type CacheBenchFrameInput,\n type CacheBenchFrameResult,\n type ReleaseBenchFrameInput,\n type ReleaseBenchFrameResult,\n type RunSyntheticBenchInput,\n type RunSyntheticBenchResult,\n} from './bench-actions.js'\n\n/**\n * Subset of the stream-broker addon surface used by the runner. Mirrors\n * the local interface in `DetectionWiringService` — `getBroker()` is the\n * in-process extension that returns a live IStreamBroker reference (not\n * tRPC-serializable, available only via the kernel capability registry).\n */\n\ninterface RunnerAddonConfig {\n readonly maxQueueDepth: number\n readonly maxConcurrentInferences: number\n readonly targetLoadPercent: number\n readonly minThrottledFps: number\n}\n\nconst DEFAULT_CONFIG: RunnerAddonConfig = {\n maxQueueDepth: 30,\n // CoreML window accumulator coalesces concurrent calls into a single\n // model.predict([list]) — the more in-flight, the larger the batch and\n // the higher the per-frame throughput. With concurrency=2 the window\n // never fills past batch=2, capping the pool at ~50 fps single-node.\n // 16 matches the slider ceiling and lines up with bench numbers\n // (parallel=16 hits batch=7-8/8, sustaining ~140 fps full path).\n maxConcurrentInferences: 16,\n targetLoadPercent: 80,\n minThrottledFps: 1,\n}\n\n/**\n * Camera attachment record kept by the runner addon. Holds the original\n * RunnerCameraConfig for introspection via `getLocalCameras` plus the\n * live broker subscription handles needed to tear down on detach.\n */\ninterface AttachedCamera {\n readonly config: RunnerCameraConfig\n motionUnsubscribe: (() => void) | null\n detectionUnsubscribe: (() => void) | null\n}\n\nfunction toFrameInput(frame: DecodedFrame): FrameInput {\n return {\n data: frame.data,\n width: frame.width,\n height: frame.height,\n format: frame.format,\n timestamp: frame.timestamp,\n }\n}\n\n/** Interval for the per-camera step roster dump (ms). */\nconst STEP_LOG_INTERVAL_MS = 30_000\n\n/**\n * Interval for periodic metric snapshot emission. ~1 Hz strikes the\n * balance between UI freshness (overlay phase / fps numbers feel live)\n * and bus traffic (one snapshot per attached camera + one per node\n * load is well under 100 events/s even at 50 cameras).\n */\nconst METRICS_SNAPSHOT_INTERVAL_MS = 1_000\n/**\n * Force a metrics-snapshot emit at least every 30s even when the\n * payload hasn't changed — gives the UI's \"agent reachable\" chip a\n * heartbeat without the per-tick spam an unconditional emit\n * produces. Picked so a 5-minute idle window emits ~10 events\n * instead of ~300.\n */\nconst METRICS_HEARTBEAT_MS = 30_000\n\nexport default class PipelineRunnerAddon extends BaseAddon<RunnerAddonConfig> implements IPipelineRunnerProvider {\n private runner: PipelineRunner | null = null\n private readonly attached = new Map<number, AttachedCamera>()\n private nodeId = 'unknown'\n private stepLogTimer: ReturnType<typeof setInterval> | null = null\n private metricsSnapshotTimer: ReturnType<typeof setInterval> | null = null\n private unsubMotionEvents: (() => void) | null = null\n /** Last analyzer-detected state per device — gates the\n * `MotionOnMotionChanged` emit in `runMotionAnalysis` to transitions\n * only (otherwise we'd emit on every analyzer frame). */\n private readonly lastAnalyzerDetected = new Map<number, boolean>()\n /**\n * Last positive motion timestamp per device — preserved across the\n * OFF transition so the motion runtime-state slice keeps a stable\n * `lastDetectedAt` after the cooldown closes the phase. Cleared on\n * detach.\n */\n private readonly lastMotionAt = new Map<number, number>()\n\n /**\n * Snapshot-equality cache for metrics-snapshot defer. The runner\n * fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;\n * for an idle camera (no inference, queue empty, fps=0) every tick\n * carries an identical payload. We skip the bus emit when the\n * payload deep-equals the previous one so the events tab + remote\n * subscribers stop seeing 60 metrics-snapshots/min/camera that\n * convey nothing. A periodic heartbeat re-emits every\n * METRICS_HEARTBEAT_MS so consumers know the runner is still\n * alive.\n */\n private readonly lastEmittedCameraMetrics = new Map<number, { json: string; emittedAt: number }>()\n private lastEmittedRunnerLoad: { json: string; emittedAt: number } | null = null\n\n /**\n * In-memory bench-frame cache (decoded JPEG bytes). Populated by the\n * `cacheBenchFrame` custom action. Fed into the synthetic-bench loop\n * via the `frame: FrameInput` shape that mirrors what stream-broker\n * delivers to this very addon during real camera detection.\n */\n private readonly benchFrameCache = new Map<string, {\n readonly data: Uint8Array\n readonly width: number\n readonly height: number\n readonly format: 'rgb'\n readonly expiresAt: number\n }>()\n private benchFrameSweeper: ReturnType<typeof setInterval> | null = null\n\n constructor() { super({ ...DEFAULT_CONFIG }) }\n\n protected async onInitialize(): Promise<AddonInitResult<PipelineRunnerBenchActions>> {\n // Use the cluster node id (hub, dev-agent-0, etc.) for dispatch routing,\n // not the addon id (pipeline-runner). The orchestrator uses this to track\n // which cluster node a camera is assigned to.\n // When the runner lives in a group-runner subprocess the broker\n // nodeID is hierarchical (`hub/pipeline`); strip the suffix so\n // emitted events / cap calls refer to the cluster-visible parent\n // node. In-process boot leaves the value untouched.\n const raw = this.ctx.kernel.localNodeId ?? this.ctx.id\n this.nodeId = raw.includes('/') ? raw.split('/')[0]! : raw\n\n this.runner = new PipelineRunner({\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n processFrame: (deviceId, frame) => this.runInference(deviceId, frame),\n analyzeMotion: (deviceId, frame) => this.runMotionAnalysis(deviceId, frame),\n onPhaseChanged: (deviceId, phase, meta) => this.handlePhaseChanged(deviceId, phase, meta),\n logger: this.ctx.logger,\n })\n\n this.runner.timingSampler.setLogger(this.ctx.logger.child('timing'))\n\n // Hook the per-camera detection-stream subscribe/unsubscribe lifecycle\n // into the local broker. The runner's phase machine triggers this\n // handler when a camera transitions WATCHING ↔ ACTIVE.\n this.runner.onDetectionStreamChange((deviceId, action) => {\n this.handleDetectionStreamChange(deviceId, action)\n })\n\n // Emit raw inference results to the bus. Hub-side post-processors\n // (analysis pipeline, class filters, notifications) consume these\n // and emit the post-processed `detection.result` event.\n this.runner.onResult(async (deviceId, frame, result, _streamType) => {\n this.emitInferenceResult(deviceId, frame, result)\n })\n\n this.runner.start()\n this.ctx.logger.info(\n 'Pipeline runner started',\n {\n tags: { nodeId: this.nodeId },\n meta: {\n maxConcurrent: this.config.maxConcurrentInferences,\n queueDepth: this.config.maxQueueDepth,\n },\n },\n )\n\n // Unified motion event subscription. Sources (Reolink firmware push\n // events emitting `source: 'onboard'`, this runner's own analyzer\n // path emitting `source: 'analyzer'` from `runMotionAnalysis`) all\n // funnel through `MotionOnMotionChanged`. The phase machine reacts\n // via `reportMotion` regardless of source — `motionSources`\n // selector at the runner-config level still gates which sources\n // trigger detection downstream.\n if (this.ctx.eventBus) {\n this.unsubMotionEvents = this.ctx.eventBus.subscribe(\n { category: EventCategory.MotionOnMotionChanged },\n (event) => {\n const data = event.data\n const deviceId = data.deviceId\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n const source = data.source\n if (!attachment.config.motionSources.includes(source)) return\n this.runner?.reportMotion(\n deviceId,\n data.detected,\n source,\n data.regions ? [...data.regions] : undefined,\n )\n },\n )\n }\n\n // Periodic per-camera step roster dump. Logs once every\n // STEP_LOG_INTERVAL_MS for each attached camera so operators can\n // see at-a-glance which detection + audio steps are configured on\n // each device without correlating attach events. Skips when no\n // cameras are attached so quiet dev runs don't emit noise.\n this.stepLogTimer = setInterval(() => this.logAttachedSteps(), STEP_LOG_INTERVAL_MS)\n\n // Periodic metric-snapshot emission: one runner-load event +\n // one per-camera metrics event per attached camera per tick.\n // UI dashboards subscribe to these instead of polling the cap.\n this.metricsSnapshotTimer = setInterval(\n () => this.emitMetricsSnapshot(),\n METRICS_SNAPSHOT_INTERVAL_MS,\n )\n\n return {\n providers: [{ capability: pipelineRunnerCapability, provider: this }],\n customActions: pipelineRunnerBenchActions,\n actionHandlers: {\n cacheBenchFrame: async (input) => this.cacheBenchFrame(input),\n releaseBenchFrame: async (input) => this.releaseBenchFrame(input),\n runSyntheticBench: async (input) => this.runSyntheticBench(input),\n },\n }\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.metricsSnapshotTimer) {\n clearInterval(this.metricsSnapshotTimer)\n this.metricsSnapshotTimer = null\n }\n if (this.stepLogTimer) {\n clearInterval(this.stepLogTimer)\n this.stepLogTimer = null\n }\n if (this.benchFrameSweeper) {\n clearInterval(this.benchFrameSweeper)\n this.benchFrameSweeper = null\n }\n this.benchFrameCache.clear()\n if (this.unsubMotionEvents) {\n this.unsubMotionEvents()\n this.unsubMotionEvents = null\n }\n this.lastAnalyzerDetected.clear()\n if (this.runner) {\n this.runner.stop()\n this.runner = null\n }\n\n for (const attachment of this.attached.values()) {\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n }\n this.attached.clear()\n }\n\n // ── Synthetic bench (production-equivalent measurement) ───────────────\n\n private async cacheBenchFrame(input: CacheBenchFrameInput): Promise<CacheBenchFrameResult> {\n // Decode JPEG → raw RGB ONCE. The runner then feeds this raw buffer\n // to api.pipelineExecutor.runPipeline via the `frame` field — same\n // shape that stream-broker hands the runner during real camera\n // detection. In-process call; no Moleculer serialisation cost.\n const sharp = (await import('sharp')).default\n const jpeg = Buffer.from(input.imageBase64, 'base64')\n const { data, info } = await sharp(jpeg).raw().toBuffer({ resolveWithObject: true })\n if (info.channels !== 3) {\n throw new Error(`cacheBenchFrame: expected 3 channels (rgb), got ${info.channels}`)\n }\n const rgb = new Uint8Array(data)\n const ttlMs = Math.max(60_000, (input.ttlSeconds ?? 600) * 1000)\n const frameId = `runner-bench-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n const expiresAt = Date.now() + ttlMs\n this.benchFrameCache.set(frameId, { data: rgb, width: info.width, height: info.height, format: 'rgb', expiresAt })\n if (!this.benchFrameSweeper) {\n this.benchFrameSweeper = setInterval(() => this.sweepBenchFrameCache(), 60_000)\n this.benchFrameSweeper.unref?.()\n }\n this.ctx.logger.info('cached bench frame', {\n meta: { frameId, width: info.width, height: info.height, bytes: rgb.length, ttlMs },\n })\n return { frameId, width: info.width, height: info.height, expiresAt }\n }\n\n private async releaseBenchFrame(input: ReleaseBenchFrameInput): Promise<ReleaseBenchFrameResult> {\n return { released: this.benchFrameCache.delete(input.frameId) }\n }\n\n private sweepBenchFrameCache(): void {\n const now = Date.now()\n for (const [id, entry] of this.benchFrameCache) {\n if (entry.expiresAt < now) this.benchFrameCache.delete(id)\n }\n }\n\n private async runSyntheticBench(input: RunSyntheticBenchInput): Promise<RunSyntheticBenchResult> {\n const ctx = this.ctx\n const api = ctx.api\n if (!api) throw new Error('runSyntheticBench: ctx.api unavailable')\n ctx.logger.info('runSyntheticBench input', {\n meta: { frameId: input.frameId, parallel: input.parallel, iterations: input.iterations },\n })\n\n const cached = this.benchFrameCache.get(input.frameId)\n if (!cached) {\n throw new Error(`runSyntheticBench: frameId ${input.frameId} not cached (call cacheBenchFrame first)`)\n }\n\n const stepsToRun = input.steps.map(s => ({\n addonId: s.addonId,\n modelId: s.modelId,\n enabled: s.enabled,\n children: s.children ?? [],\n }))\n\n // Determine if we can use the fast cache path:\n // Single root step, no enabled children, and simulatePipeline not requested\n // → bypass runPipeline entirely, cache frame in Python pool once,\n // then send only 5-byte references.\n const enabledSteps = stepsToRun.filter(s => s.enabled)\n const isSingleStep = enabledSteps.length === 1\n && (!enabledSteps[0]!.children || enabledSteps[0]!.children.filter(c => c.enabled).length === 0)\n const useFastPath = isSingleStep && !input.simulatePipeline\n const rootStep = enabledSteps[0]\n\n // Ensure engine + model are loaded (same as runPipeline does)\n // Run one warmup call through the FULL path to trigger model load\n const sharedFrame: FrameInput = {\n data: cached.data,\n format: cached.format,\n width: cached.width,\n height: cached.height,\n timestamp: Date.now(),\n }\n\n let poolFrameId: number | null = null\n\n if (useFastPath && rootStep) {\n // ── Fast path: cache frame in Python, infer via 5-byte reference ──\n ctx.logger.info('synthetic bench: using Python cache path', {\n meta: { step: rootStep.addonId, model: rootStep.modelId },\n })\n\n // Cache the raw frame in the Python pool\n const cacheResult = await api.pipelineExecutor.cacheFrameInPool.mutate({\n data: new Uint8Array(cached.data.slice().buffer),\n width: cached.width,\n height: cached.height,\n format: cached.format,\n })\n poolFrameId = cacheResult.frameId\n\n // Warmup through full runPipeline path to ensure model is loaded.\n // Engine override is applied here — loads the model on the overridden\n // factory. However, inferCached bypasses the provider's override swap\n // (it goes directly to the current engineFactory). For same-engine\n // bench runs this is correct. TODO: for cross-engine override, expose\n // applyEngineOverride/restore on the cap so the cached path can use\n // the overridden factory.\n await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n // Additional warmup via cached path\n const warmupCount = input.warmup ?? 1\n for (let w = 0; w < warmupCount; w++) {\n await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId,\n })\n }\n\n // Timed run — each call sends only 5 bytes through the pipe\n const wallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n let _n = 0\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.inferCached.mutate({\n stepId: rootStep.addonId,\n frameId: poolFrameId!,\n })\n const wallMs = performance.now() - t0\n const r = result as Record<string, unknown>\n const inferMs = typeof r['inferenceMs'] === 'number' ? r['inferenceMs'] as number : wallMs\n const preMs = typeof r['preprocessMs'] === 'number' ? r['preprocessMs'] as number : 0\n const predMs = typeof r['predictMs'] === 'number' ? r['predictMs'] as number : 0\n const bs = typeof r['batchSize'] === 'number' ? r['batchSize'] as number : 1\n const dets = Array.isArray(r['detections']) ? (r['detections'] as unknown[]).length : 0\n wallTimings.push(wallMs)\n inferTimings.push(inferMs)\n preprocessTimings.push(preMs)\n predictTimings.push(predMs)\n batchSizes.push(bs)\n detCounts.push(dets)\n const n = ++_n\n if (n <= 20) {\n ctx.logger.info('bench call trace (cached)', {\n meta: { n, wallMs: Math.round(wallMs), inferMs: Math.round(inferMs), preMs: Math.round(preMs * 10) / 10, predMs: Math.round(predMs * 10) / 10, bs },\n })\n }\n // Emit live progress every `parallel` completions\n if (n % Math.max(1, input.parallel) === 0) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanCallMs = wallTimings.reduce((s, v) => s + v, 0) / wallTimings.length\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const avgDet = detCounts.length > 0 ? totalDet / detCounts.length : 0\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / batchSizes.length\n const msg = `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanCallMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`\n if (ctx.eventBus) {\n // Emit directly (bypassing createEvent's strict typing) so we\n // can attach structured bench stats as extra fields. The UI's\n // pipeline.progress subscription reads them from event.data.\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench', message: msg,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanCallMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / inferTimings.length) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / preprocessTimings.length) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / predictTimings.length) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: Math.round(avgDet * 100) / 100,\n },\n })\n } else {\n ctx.logger.warn('emitProgress: NO eventBus')\n }\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n // Cleanup\n await api.pipelineExecutor.uncacheFrame.mutate({ frameId: poolFrameId }).catch(() => {})\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'cached')\n }\n\n // ── Slow path: full runPipeline per call (multi-step trees) ──\n ctx.logger.info('synthetic bench: using full runPipeline path', {\n meta: { steps: enabledSteps.length, simulatePipeline: !!input.simulatePipeline },\n })\n\n let _callCount = 0\n const callOnce = async (): Promise<{ wallMs: number; result: FrameResult }> => {\n const t0 = performance.now()\n const result = await api.pipelineExecutor.runPipeline.mutate({\n steps: stepsToRun,\n frame: sharedFrame,\n ...(input.engine ? { engine: input.engine } : {}),\n })\n const wallMs = performance.now() - t0\n const n = ++_callCount\n if (n <= 20) {\n ctx.logger.info('bench call trace', {\n meta: {\n n,\n wallMs: Math.round(wallMs),\n totalInferenceMs: Math.round(result.debug?.totalInferenceMs ?? 0),\n predictMs: Math.round((result.debug?.predictMs ?? 0) * 10) / 10,\n preprocessMs: Math.round((result.debug?.preprocessMs ?? 0) * 10) / 10,\n batchSize: result.debug?.batchSize ?? 1,\n },\n })\n }\n return { wallMs, result }\n }\n\n // Warmup — sequential to ensure engine is hot before timing.\n const warmupCount = input.warmup ?? 1\n for (let i = 0; i < warmupCount; i++) {\n await callOnce()\n }\n\n const wallTimings: number[] = []\n const serverWallTimings: number[] = []\n const inferTimings: number[] = []\n const preprocessTimings: number[] = []\n const predictTimings: number[] = []\n const batchSizes: number[] = []\n const detCounts: number[] = []\n const sessionId = input.sessionId ?? `synth-${Date.now().toString(36)}`\n const totalRuns = input.parallel * input.iterations\n\n const wallStart = performance.now()\n const worker = async (): Promise<void> => {\n for (let i = 0; i < input.iterations; i++) {\n const { wallMs, result } = await callOnce()\n wallTimings.push(wallMs)\n serverWallTimings.push(result.debug?.wallMs ?? 0)\n inferTimings.push(result.debug?.totalInferenceMs ?? 0)\n preprocessTimings.push(result.debug?.preprocessMs ?? 0)\n predictTimings.push(result.debug?.predictMs ?? 0)\n batchSizes.push(result.debug?.batchSize ?? 1)\n detCounts.push(result.detections?.length ?? 0)\n const n = wallTimings.length\n if (n % Math.max(1, input.parallel) === 0 && ctx.eventBus) {\n const elapsed = (performance.now() - wallStart) / 1000\n const fps = elapsed > 0 ? n / elapsed : 0\n const meanMs = wallTimings.reduce((s, v) => s + v, 0) / n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const p95 = sorted[Math.min(sorted.length - 1, Math.floor(0.95 * sorted.length))] ?? 0\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n const bsMean = batchSizes.reduce((s, v) => s + v, 0) / n\n ctx.eventBus.emit({\n id: `bench-${n}`,\n timestamp: new Date(),\n source: { type: 'pipeline' as const, id: 'synthetic-bench' },\n category: EventCategory.PipelineProgress,\n data: {\n nodeId: 'hub', sessionId, step: 'synthetic-bench',\n message: `runs ${n}/${totalRuns} \\u00b7 ${fps.toFixed(1)} fps \\u00b7 call ${meanMs.toFixed(1)}ms \\u00b7 batch ${bsMean.toFixed(1)}`,\n benchProgress: true, runs: n, totalRuns,\n fps: Math.round(fps * 100) / 100,\n meanMs: Math.round(meanMs * 100) / 100,\n p95Ms: Math.round(p95 * 100) / 100,\n inferMeanMs: Math.round((inferTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n preprocessMeanMs: Math.round((preprocessTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n predictMeanMs: Math.round((predictTimings.reduce((s, v) => s + v, 0) / n) * 100) / 100,\n batchSizeMean: Math.round(bsMean * 100) / 100,\n detPerSec: elapsed > 0 ? Math.round((totalDet / elapsed) * 100) / 100 : 0,\n avgDetections: n > 0 ? Math.round((totalDet / n) * 100) / 100 : 0,\n },\n })\n }\n }\n }\n\n await Promise.all(Array.from({ length: input.parallel }, () => worker()))\n const wallSec = (performance.now() - wallStart) / 1000\n\n return this.buildBenchResult(wallTimings, inferTimings, preprocessTimings, predictTimings, batchSizes, detCounts, wallSec, 'pipeline')\n }\n\n private async buildBenchResult(\n wallTimings: number[],\n inferTimings: number[],\n preprocessTimings: number[],\n predictTimings: number[],\n batchSizes: number[],\n detCounts: number[],\n wallSec: number,\n path: string,\n ): Promise<RunSyntheticBenchResult> {\n const meanOfArr = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n this.ctx.logger.info('synthetic bench summary', {\n meta: {\n runs: wallTimings.length,\n wallSec: Math.round(wallSec * 100) / 100,\n fps: Math.round((wallTimings.length / wallSec) * 100) / 100,\n callMeanMs: Math.round(meanOfArr(wallTimings)),\n inferMeanMs: Math.round(meanOfArr(inferTimings)),\n preprocessMeanMs: Math.round(meanOfArr(preprocessTimings)),\n predictMeanMs: Math.round(meanOfArr(predictTimings)),\n batchSizeMean: Math.round(meanOfArr(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n },\n })\n\n const sorted = [...wallTimings].sort((a, b) => a - b)\n const pick = (q: number): number =>\n sorted.length > 0\n ? sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))]!\n : 0\n const meanOf = (xs: readonly number[]): number =>\n xs.length > 0 ? xs.reduce((s, v) => s + v, 0) / xs.length : 0\n\n const totalRuns = wallTimings.length\n const totalDet = detCounts.reduce((s, v) => s + v, 0)\n\n return {\n runs: totalRuns,\n wallSec: Math.round(wallSec * 1000) / 1000,\n fps: wallSec > 0 ? Math.round((totalRuns / wallSec) * 100) / 100 : 0,\n detectionsPerSec: wallSec > 0 ? Math.round((totalDet / wallSec) * 100) / 100 : 0,\n avgDetections: totalRuns > 0 ? Math.round((totalDet / totalRuns) * 100) / 100 : 0,\n callMs: {\n mean: Math.round(meanOf(wallTimings) * 100) / 100,\n p50: Math.round(pick(0.5) * 100) / 100,\n p95: Math.round(pick(0.95) * 100) / 100,\n p99: Math.round(pick(0.99) * 100) / 100,\n },\n inferMs: Math.round(meanOf(inferTimings) * 100) / 100,\n preprocessMs: Math.round(meanOf(preprocessTimings) * 100) / 100,\n predictMs: Math.round(meanOf(predictTimings) * 100) / 100,\n batchSizeMean: Math.round(meanOf(batchSizes) * 100) / 100,\n batchSizeMax: batchSizes.length > 0 ? Math.max(...batchSizes) : 0,\n path,\n ...(await this.getEngineAndTuning()),\n }\n }\n\n private async getEngineAndTuning(): Promise<Pick<RunSyntheticBenchResult, 'engine' | 'tuning'>> {\n try {\n const api = this.ctx.api\n if (!api) return {}\n const [eng, tuning] = await Promise.all([\n api.pipelineExecutor.getSelectedEngine.query(),\n api.pipelineExecutor.getEffectiveTuning.query(),\n ])\n return {\n engine: eng ? { runtime: eng.runtime, backend: eng.backend, device: eng.device } : undefined,\n tuning: tuning ?? undefined,\n }\n } catch {\n return {}\n }\n }\n\n // ── IPipelineRunnerProvider implementation ────────────────────────────\n\n async attachCamera(config: RunnerCameraConfig): Promise<{ success: true }> {\n const runner = this.runner\n const ctx = this.ctx\n if (!runner || !ctx) {\n throw new Error('PipelineRunnerAddon: attachCamera called before initialize completed')\n }\n\n // Diagnostic: log the incoming config to understand why\n // subscribeMotionFrames isn't being called.\n this.ctx.logger.info('attachCamera received config', {\n tags: { deviceId: config.deviceId },\n meta: {\n motionSources: config.motionSources,\n motionSourcesType: Array.isArray(config.motionSources) ? `array(${config.motionSources.length})` : typeof config.motionSources,\n motionStreamId: config.motionStreamId,\n detectionStreamId: config.detectionStreamId,\n keys: Object.keys(config as unknown as Record<string, unknown>),\n },\n })\n\n if (this.attached.has(config.deviceId)) {\n // Idempotent: re-attach with new config replaces the existing one.\n this.detachInternal(config.deviceId)\n }\n\n runner.registerCamera(config.deviceId, {\n detectionMode: config.detectionMode,\n fps: config.detectionFps,\n motionCooldownMs: config.motionCooldownMs,\n })\n\n const attachment: AttachedCamera = {\n config,\n motionUnsubscribe: null,\n detectionUnsubscribe: null,\n }\n this.attached.set(config.deviceId, attachment)\n\n // Subscribe to the local motion stream broker only when 'analyzer'\n // is among the active motion sources — that's the source that\n // needs decoded frames flowing through the motion-detection cap.\n // 'onboard' alone arrives via the bus (ProviderMotion → orchestrator\n // → reportMotion) without needing broker frames.\n if (config.motionSources.includes('analyzer')) {\n attachment.motionUnsubscribe = await this.subscribeMotionFrames(config)\n }\n\n // Note: detection stream subscription is wired LAZILY by the\n // runner's phase machine via `handleDetectionStreamChange`, which\n // fires when the camera transitions to ACTIVE. Always-on cameras\n // transition immediately on registerCamera, so the subscribe call\n // already happened by the time we get here.\n\n const stepsCount = config.steps?.length ?? 0\n const dispatch = stepsCount > 0\n ? `runPipeline(${stepsCount}step${stepsCount === 1 ? '' : 's'})`\n : config.steps !== undefined\n ? 'skip(0steps)'\n : 'runFrame(legacy)'\n const engineLabel = config.engine ? `${config.engine.runtime}+${config.engine.backend}/${config.engine.format}` : 'default'\n this.ctx.logger.info(\n 'attachCamera',\n {\n tags: { deviceId: config.deviceId },\n meta: {\n detectionMode: config.detectionMode,\n audioMode: config.audioMode,\n motionFps: config.motionFps,\n detectionFps: config.detectionFps,\n motionSources: config.motionSources,\n dispatch,\n engine: engineLabel,\n },\n },\n )\n\n return { success: true }\n }\n\n async detachCamera(input: { deviceId: number }): Promise<{ success: true }> {\n this.detachInternal(input.deviceId)\n return { success: true }\n }\n\n async reportMotion(input: {\n deviceId: number\n detected: boolean\n source?: MotionSource\n regions?: ReadonlyArray<MotionRegion>\n }): Promise<{ success: true }> {\n this.runner?.reportMotion(input.deviceId, input.detected, input.source, input.regions)\n return { success: true }\n }\n\n /**\n * Periodic per-camera step roster dump. Once every\n * STEP_LOG_INTERVAL_MS (30s) emits one log line per attached camera\n * with the configured detection step tree + audio classifier branch\n * so an operator looking at the agent log can quickly see what each\n * camera is currently running without crossing tRPC. Skips when no\n * cameras are attached so quiet dev runs stay silent.\n */\n private logAttachedSteps(): void {\n if (this.attached.size === 0) return\n for (const [deviceId, attachment] of this.attached) {\n const cfg = attachment.config\n const detectionSteps = cfg.steps && cfg.steps.length > 0\n ? this.flattenSteps(cfg.steps).filter((s) => s.enabled)\n : []\n const detectionLabel = detectionSteps.length > 0\n ? detectionSteps.map((s) => `${s.addonId}/${s.modelId}`).join(' → ')\n : '<none>'\n const audioLabel = cfg.audio && cfg.audio.enabled\n ? `${cfg.audio.engine.runtime}/${cfg.audio.engine.backend}/${cfg.audio.modelId}`\n : '<off>'\n const engineLabel = cfg.engine\n ? `${cfg.engine.runtime}/${cfg.engine.backend}${cfg.engine.device ? `/${cfg.engine.device}` : ''}`\n : '<unset>'\n this.ctx.logger.info('Camera pipeline roster', {\n tags: { deviceId },\n meta: {\n phase: 'roster',\n intervalSec: STEP_LOG_INTERVAL_MS / 1000,\n pipelineEnabled: cfg.pipelineEnabled,\n motionSources: cfg.motionSources,\n motionFps: cfg.motionFps,\n detectionFps: cfg.detectionFps,\n engine: engineLabel,\n videoSteps: detectionLabel,\n videoStepCount: detectionSteps.length,\n audio: audioLabel,\n },\n })\n }\n }\n\n /** Recursively flatten the step tree → ordered list of every node. */\n private flattenSteps(steps: readonly PipelineStepInput[]): readonly PipelineStepInput[] {\n const out: PipelineStepInput[] = []\n const walk = (s: PipelineStepInput): void => {\n out.push(s)\n if (s.children) {\n for (const c of s.children) walk(c)\n }\n }\n for (const s of steps) walk(s)\n return out\n }\n\n private detachInternal(deviceId: number): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n attachment.motionUnsubscribe?.()\n attachment.detectionUnsubscribe?.()\n this.attached.delete(deviceId)\n this.lastMotionAt.delete(deviceId)\n this.lastEmittedCameraMetrics.delete(deviceId)\n\n this.runner?.unregisterCamera(deviceId)\n this.ctx?.logger.info('detachCamera', { tags: { deviceId } })\n }\n\n async getLocalLoad(): Promise<RunnerLocalLoad> {\n const metrics = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? []\n\n let activeCameras = 0\n let totalActualFps = 0\n for (const cm of allCameraMetrics) {\n if (cm.phase === 'active') activeCameras++\n totalActualFps += cm.actualFps\n }\n\n // Phase 4: hardware probe moved off the runner.\n //\n // In the stateless-pipeline model each camera declares its own\n // engine. L3 hardware-aware balancing now lives on the orchestrator,\n // which knows `pipelines[deviceId].engine` and can match against\n // `nodes.topology.availableEngines` directly (spec Phase 3 extends\n // topology; consumed by the balancer once Phase 8 lands). The runner\n // no longer calls `pipelineExecutor.getSelectedEngine` — that cap is\n // being removed and its legacy \"one engine per node\" assumption\n // doesn't fit multi-engine nodes.\n return {\n nodeId: this.nodeId,\n attachedCameras: this.attached.size,\n activeCameras,\n avgInferenceFps: totalActualFps,\n avgInferenceTimeMs: metrics.avgInferenceTimeMs,\n queueDepthTotal: metrics.queueDepth,\n hardware: {\n hasGpu: false,\n inferenceBackend: undefined,\n },\n }\n }\n\n async getLocalMetrics(): Promise<RunnerLocalMetrics> {\n const m = this.runner?.getMetrics() ?? { activeCameras: 0, throttledCameras: 0, avgInferenceTimeMs: 0, queueDepth: 0 }\n return { nodeId: this.nodeId, ...m }\n }\n\n async getCameraMetrics(input: { deviceId: number }): Promise<CameraMetrics | null> {\n return this.runner?.getCameraMetrics(input.deviceId) ?? null\n }\n\n getAllCameraMetrics(): ReadonlyArray<{ deviceId: number } & CameraMetrics> {\n return this.runner?.getAllCameraMetrics() ?? []\n }\n\n getLocalCameras(): readonly number[] {\n return [...this.attached.keys()]\n }\n\n // ── Internal: broker subscription wiring ─────────────────────────────\n\n private async subscribeMotionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeMotionFrames: this.ctx.api not available')\n return null\n }\n\n const motionBrokerId = `${config.deviceId}/${config.motionStreamId}`\n const motionBroker: IStreamBroker | null = await api.streamBroker.getBroker.query({ brokerId: motionBrokerId })\n if (!motionBroker) {\n log.warn('subscribeMotionFrames: no broker found', { meta: { brokerId: motionBrokerId } })\n return null\n }\n return motionBroker.onDecodedFrame(\n (frame: DecodedFrame) => {\n runner.enqueueMotionFrame(config.deviceId, frame)\n },\n { maxFps: config.motionFps, format: 'gray', tag: 'motion' },\n )\n }\n\n private handleDetectionStreamChange(deviceId: number, action: 'subscribe' | 'unsubscribe'): void {\n const attachment = this.attached.get(deviceId)\n if (!attachment) return\n\n if (action === 'subscribe') {\n void this.subscribeDetectionFrames(attachment.config).then(unsub => {\n attachment.detectionUnsubscribe = unsub\n })\n } else {\n attachment.detectionUnsubscribe?.()\n attachment.detectionUnsubscribe = null\n }\n }\n\n /**\n * Bridge runner phase transitions to the device's `motion` runtime\n * state + the bus. Single ownership point — every motion source\n * (analyzer, onboard, future variants) funnels through the runner's\n * phase machine and lands here.\n *\n * - Cap-state via the unified `device-state.setCapSlice` API.\n * `autoClearAfterMs = cooldownMs` on ON, `null` on OFF.\n * `lastDetectedAt` is preserved across OFF using `lastMotionAt`.\n * - Bus event `MotionOnMotionChanged` fires alongside for consumers\n * that prefer event-driven over runtime-state polling.\n */\n private handlePhaseChanged(\n deviceId: number,\n phase: CameraPhase,\n meta: PhaseTransitionMeta,\n ): void {\n const detected = phase === 'active'\n if (detected) this.lastMotionAt.set(deviceId, meta.timestamp)\n const lastDetectedAt = this.lastMotionAt.get(deviceId) ?? null\n const slice = {\n detected,\n lastDetectedAt,\n autoClearAfterMs: detected ? meta.cooldownMs : null,\n }\n void (async () => {\n const dev = await this.ctx.fetchDevice(deviceId)\n await dev.deviceState.setCapSlice({ capName: 'motion', slice })\n })().catch((err: unknown) => {\n this.ctx.logger.debug('motion cap-state write failed', {\n tags: { deviceId },\n meta: { error: errMsg(err) },\n })\n })\n // Emit `DetectionPhaseTransition` so observers (UI events panel,\n // analytics, alert center) see WATCHING ↔ ACTIVE flips on the bus.\n // Sources (Reolink firmware, this addon's analyzer path) still own\n // `MotionOnMotionChanged` — that channel carries the raw motion\n // signal. This event carries the post-phase-machine truth (after\n // cooldown debouncing). Different consumers, different cadences.\n if (this.ctx.eventBus) {\n const from: 'watching' | 'active' = detected ? 'watching' : 'active'\n const to: 'watching' | 'active' = detected ? 'active' : 'watching'\n const reason: 'motion_detected' | 'cooldown_expired' = detected ? 'motion_detected' : 'cooldown_expired'\n const payload: PhaseTransitionPayload = {\n deviceId,\n from,\n to,\n reason,\n source: meta.source,\n cooldownMs: meta.cooldownMs,\n timestamp: meta.timestamp,\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DetectionPhaseTransition,\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n }\n\n private async subscribeDetectionFrames(config: RunnerCameraConfig): Promise<(() => void) | null> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return null\n\n const log = this.ctx.logger.withTags({ deviceId: config.deviceId })\n const api = this.ctx.api\n if (!api) {\n log.warn('subscribeDetectionFrames: this.ctx.api not available')\n return null\n }\n\n const detectionBrokerId = `${config.deviceId}/${config.detectionStreamId}`\n const detectionBroker: IStreamBroker | null = await api.streamBroker.getBroker.query({ brokerId: detectionBrokerId })\n if (!detectionBroker) {\n log.warn('subscribeDetectionFrames: no broker found', { meta: { brokerId: detectionBrokerId } })\n return null\n }\n\n return detectionBroker.onDecodedFrame(\n (frame: DecodedFrame) => {\n runner.enqueueDetectionFrame(config.deviceId, frame)\n },\n // `format: 'rgb'` is the Phase 4 hot-path switch: detection now\n // requests raw RGB24 from the broker so the decoder skips the\n // sharp JPEG encode and the Python pool skips PIL JPEG decode.\n // When WebRTC also subscribes (jpeg), the broker derives JPEG\n // once per frame via its conversion cache — no double work.\n { maxFps: config.detectionFps, format: 'rgb', tag: 'detection' },\n )\n }\n\n // ── Internal: inference + motion callbacks ───────────────────────────\n\n private async runInference(deviceId: number, frame: FrameInput): Promise<FrameResult | null> {\n const ctx = this.ctx\n if (!ctx) return null\n\n const log = this.ctx.logger.withTags({ deviceId })\n const api = this.ctx.api\n if (!api) {\n log.error('runInference: this.ctx.api not available')\n return null\n }\n\n const attachment = this.attached.get(deviceId)\n const camConfig = attachment?.config\n const steps = camConfig?.steps\n const engine = camConfig?.engine\n\n // Stateless-pipeline dispatch (Phase 4): the orchestrator hydrates\n // every attach with `steps` (from `pipelines[deviceId]` or the\n // cluster default). Absent steps means the camera was attached\n // before the orchestrator migrated — skip the frame rather than\n // falling back to the removed legacy `runFrame` path.\n if (!steps) {\n log.warn('runInference: no steps in attach config — skipping frame (legacy attach?)')\n return null\n }\n if (steps.length === 0) {\n return null\n }\n\n try {\n return await api.pipelineExecutor.runPipeline.mutate({\n // tRPC input is a mutable array; the attach payload holds it\n // as readonly. One spread copy at the cap boundary is cheap\n // (pipeline step trees are tiny) and keeps the type surface\n // clean without casting.\n steps: [...steps],\n frame,\n deviceId,\n ...(engine ? { engine } : {}),\n })\n } catch (err: unknown) {\n const msg = errMsg(err)\n log.error('runInference failed', { meta: { error: msg } })\n return null\n }\n }\n\n private async runMotionAnalysis(deviceId: number, frame: DecodedFrame): Promise<void> {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx || !runner) return\n\n const log = this.ctx.logger.withTags({ deviceId })\n const motionStart = Date.now()\n try {\n const api = this.ctx.api\n if (!api) {\n log.warn('runMotionAnalysis: this.ctx.api not available')\n return\n }\n const result = await api.motionDetection.analyze.mutate({ deviceId, frame: toFrameInput(frame) })\n if (!result) return\n\n const detected = result.regions.length > 0\n\n // Emit `MotionOnMotionChanged` ONLY on transitions (analyzer runs\n // every motion frame at `motionFps`; we don't want one event per\n // frame). The runner's bus subscriber receives this and drives\n // the phase machine — same path Reolink's firmware events take.\n // No in-process `reportMotion` call here: the unified subscription\n // is the single dispatch point so onboard and analyzer go through\n // identical logic.\n const prevDetected = this.lastAnalyzerDetected.get(deviceId) ?? false\n if (detected !== prevDetected) {\n this.lastAnalyzerDetected.set(deviceId, detected)\n if (this.ctx.eventBus) {\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionOnMotionChanged,\n // EventSource wrapper kept symmetric with the onboard\n // emit (Reolink/ONVIF/etc.) so consumers grouping by\n // addonId / deviceId see consistent provenance. `nodeId`\n // identifies which cluster node ran the analyzer.\n { type: 'device', id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId },\n {\n deviceId,\n detected,\n timestamp: frame.timestamp,\n source: 'analyzer',\n ...(detected ? { regions: result.regions } : {}),\n },\n ))\n }\n }\n\n if (this.ctx.eventBus) {\n const motionPayload: MotionAnalysisPayload = {\n detected,\n regionCount: result.regions.length,\n regions: result.regions.map((r) => ({\n bbox: { x: r.bbox.x, y: r.bbox.y, w: r.bbox.w, h: r.bbox.h },\n pixelCount: r.pixelCount,\n intensity: r.intensity,\n })),\n frameWidth: frame.width,\n frameHeight: frame.height,\n analysisMs: result.analysisMs,\n }\n const analyzerSource = { type: 'device' as const, id: deviceId, addonId: this.ctx.id, deviceId, nodeId: this.nodeId }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionAnalysis,\n analyzerSource,\n motionPayload,\n ))\n\n const zonesPayload: MotionZonesRawPayload = {\n deviceId,\n timestamp: frame.timestamp,\n zones: result.rawRegions.map((r) => ({\n bbox: [r.bbox.x, r.bbox.y, r.bbox.x + r.bbox.w, r.bbox.y + r.bbox.h] as const,\n pixelCount: r.pixelCount,\n changeScore: r.intensity / 255,\n })),\n frameSize: { width: frame.width, height: frame.height },\n }\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionZonesRaw,\n analyzerSource,\n zonesPayload,\n ))\n }\n\n runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart)\n } catch (error: unknown) {\n const msg = errMsg(error)\n log.error('runMotionAnalysis failed', { meta: { error: msg } })\n }\n }\n\n private emitInferenceResult(deviceId: number, _frame: DecodedFrame, result: FrameResult): void {\n const ctx = this.ctx\n if (!ctx?.eventBus) return\n\n // The new FrameResult already carries width/height/timestamp at the\n // root + debug.stepTimings / debug.totalInferenceMs. No frameMetadata\n // wrapper needed — the hub subscribes and reads `payload.frame`\n // directly.\n const payload: PipelineInferenceResultPayload = {\n deviceId,\n frame: result,\n nodeId: this.nodeId,\n }\n\n this.ctx.eventBus.emit(createEvent(\n EventCategory.PipelineInferenceResult,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n payload,\n ))\n }\n\n /**\n * Emit periodic metric snapshots: one runner-load event for the\n * node + one camera-metrics event per attached camera. Subscribed\n * by admin-ui dashboards (LiveLoadPanel, NodeDetailHeader,\n * CameraStreamPanel) to drive live overlays without polling.\n *\n * Skipped when there are no cameras attached so quiet dev runs\n * don't emit needless bus traffic. The runner-load event is still\n * emitted in that case because the dashboards rely on it to see\n * \"agent reachable, idle\".\n */\n private emitMetricsSnapshot(): void {\n const ctx = this.ctx\n const runner = this.runner\n if (!ctx?.eventBus || !runner) return\n const timestamp = Date.now()\n\n // Runner-load snapshot. Skip emit when the load payload is\n // bytewise-identical to the previous one (idle node — same\n // running-cap counts, same queue depths, same memory). Heartbeat\n // re-emits every METRICS_HEARTBEAT_MS so the \"agent reachable\"\n // chip in the UI doesn't go stale.\n void this.getLocalLoad().then((load) => {\n if (!ctx.eventBus) return\n const json = JSON.stringify(load)\n const prev = this.lastEmittedRunnerLoad\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) return\n this.lastEmittedRunnerLoad = { json, emittedAt: timestamp }\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineRunnerLoadSnapshot,\n { type: 'node', id: this.nodeId, nodeId: this.nodeId },\n { nodeId: this.nodeId, load, timestamp },\n ))\n }).catch(() => {\n // Skip this tick on transient failure — next interval will\n // reconcile. No need to log: the snapshot is a soft observability\n // path, not a correctness boundary.\n })\n\n // Per-camera metrics snapshot — same defer pattern.\n if (this.attached.size === 0) return\n for (const deviceId of this.attached.keys()) {\n const metrics = runner.getCameraMetrics(deviceId)\n if (!metrics) continue\n const json = JSON.stringify(metrics)\n const prev = this.lastEmittedCameraMetrics.get(deviceId)\n const heartbeatDue = !prev || timestamp - prev.emittedAt >= METRICS_HEARTBEAT_MS\n if (prev && prev.json === json && !heartbeatDue) continue\n this.lastEmittedCameraMetrics.set(deviceId, { json, emittedAt: timestamp })\n ctx.eventBus.emit(createEvent(\n EventCategory.PipelineCameraMetricsSnapshot,\n { type: 'device', id: deviceId, nodeId: this.nodeId },\n { deviceId, nodeId: this.nodeId, metrics, timestamp },\n ))\n }\n }\n\n // ── Standard ICamstackAddon — three-level settings API (Phase 3) ─────\n //\n // The runner is a per-node addon with only ADDON-LEVEL settings (no\n // per-device overrides, no cluster-wide tunables). All four tuning\n // fields live in `getAddonSettings()`. When the UI surface moves in\n // Phase 9 these will be rendered under Pipeline -> node -> Settings.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'pipeline-runner-tuning',\n title: 'Pipeline Runner',\n tab: 'scheduler',\n description: 'Per-node detection scheduler tuning. Change only if you understand the pipeline internals.',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'maxConcurrentInferences',\n label: 'Scheduler concurrency',\n description:\n 'Max parallel inferences the runner scheduler allows across all cameras on this node. ' +\n 'Distinct from the detection-pipeline inference-pool worker count (Pipeline tab → \"Worker concurrency\"), ' +\n 'which controls Python-side thread pool sizing inside a single inference job.',\n min: 1,\n max: 16,\n step: 1,\n default: DEFAULT_CONFIG.maxConcurrentInferences,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'maxQueueDepth',\n label: 'Max queue depth',\n description: 'Maximum frames held per camera before dropping.',\n min: 5,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.maxQueueDepth,\n showValue: true,\n },\n {\n type: 'slider',\n key: 'targetLoadPercent',\n label: 'Target load',\n description: 'Percentage of inference capacity to target before throttling FPS.',\n min: 50,\n max: 100,\n step: 5,\n default: DEFAULT_CONFIG.targetLoadPercent,\n unit: '%',\n showValue: true,\n },\n {\n type: 'slider',\n key: 'minThrottledFps',\n label: 'Min throttled FPS',\n description: 'Lowest FPS the runner will allow when load-shedding.',\n min: 1,\n max: 10,\n step: 1,\n default: DEFAULT_CONFIG.minThrottledFps,\n showValue: true,\n },\n ],\n },\n ],\n })\n }\n\n protected async onConfigChanged(): Promise<void> {\n this.runner?.updateLimits(this.config)\n this.ctx.logger.info(\n 'pipeline-runner tuning updated',\n {\n meta: {\n maxQueueDepth: this.config.maxQueueDepth,\n maxConcurrentInferences: this.config.maxConcurrentInferences,\n targetLoadPercent: this.config.targetLoadPercent,\n minThrottledFps: this.config.minThrottledFps,\n },\n },\n )\n }\n}\n\n// Re-export for tests / consumers that need direct class access.\nexport { PipelineRunner } from './runner.js'\nexport { FrameQueue } from './frame-queue.js'\nexport { Semaphore } from './semaphore.js'\nexport { PipelineTimingSampler } from './timing-sampler.js'\n\n/**\n * Static `customActions` catalog — read by the addon registry at boot time\n * (before instantiating the addon class) so cross-process Moleculer routes\n * can be wired for `addons.custom.<action>` dispatch. Mirrors the pattern\n * in addon-benchmark/index.ts.\n */\nexport { pipelineRunnerBenchActions as customActions } from './bench-actions.js'\n"],"names":["toFrameInput","z.object","z.enum","z.string","z.lazy","z.boolean","z.array","z.number","warmupCount","wallTimings","inferTimings","preprocessTimings","predictTimings","batchSizes","detCounts","sessionId","totalRuns","wallStart","worker","wallSec"],"mappings":"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"}
|