@camstack/addon-pipeline-orchestrator 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@mf-types/compiled-types/widgets/index.d.ts +0 -2
- package/dist/@mf-types/compiled-types/widgets/index.d.ts.map +1 -1
- package/dist/@mf-types.zip +0 -0
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-C4HmLg0z.mjs +20 -0
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-DEuqbomC.mjs +34 -0
- package/dist/_stub.js +568 -776
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-GASHflbS.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DRIzngbd.mjs} +6 -6
- package/dist/{hostInit-s8NZVmrk.mjs → hostInit-B0ePO-AC.mjs} +6 -6
- package/dist/{index-BP1Nti7b.mjs → index-BCEx31Mh.mjs} +3767 -3350
- package/dist/{index-BIlr4dIX.mjs → index-BmY66bNn.mjs} +1 -1
- package/dist/{index-CMke0KpS.mjs → index-BuYTzV_S.mjs} +6594 -6053
- package/dist/index.js +325 -91
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +325 -91
- package/dist/index.mjs.map +1 -1
- package/dist/remoteEntry.js +1 -1
- package/package.json +3 -3
- package/dist/@mf-types/compiled-types/widgets/MotionZonesEditor.d.ts +0 -19
- package/dist/@mf-types/compiled-types/widgets/MotionZonesEditor.d.ts.map +0 -1
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionGridCanvas.d.ts +0 -10
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionGridCanvas.d.ts.map +0 -1
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesTab.d.ts +0 -5
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesTab.d.ts.map +0 -1
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-UNj4rttw.mjs +0 -20
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-DPoup41Y.mjs +0 -34
package/dist/index.mjs
CHANGED
|
@@ -5067,6 +5067,7 @@ const WELL_KNOWN_TABS = [
|
|
|
5067
5067
|
{ id: "osd", label: "OSD", icon: "type", order: 18 },
|
|
5068
5068
|
{ id: "stream-broker", label: "Stream Broker", icon: "radio", order: 20 },
|
|
5069
5069
|
{ id: "streaming", label: "Streaming", icon: "video", order: 35 },
|
|
5070
|
+
{ id: "ptz", label: "PTZ", icon: "move", order: 40 },
|
|
5070
5071
|
{ id: "pipeline", label: "Detection Pipeline", icon: "cpu", order: 39 },
|
|
5071
5072
|
{ id: "zones", label: "Detection", icon: "shapes", order: 38 },
|
|
5072
5073
|
{ id: "live-stats", label: "Live Stats", icon: "activity", order: 39 },
|
|
@@ -5223,7 +5224,19 @@ const DecoderSessionConfigSchema = object({
|
|
|
5223
5224
|
* on every line so `grep tag=broker:5/high` filters one camera
|
|
5224
5225
|
* profile cleanly.
|
|
5225
5226
|
*/
|
|
5226
|
-
tag: string().optional()
|
|
5227
|
+
tag: string().optional(),
|
|
5228
|
+
/**
|
|
5229
|
+
* Where the session delivers decoded frames (Phase 5 / D9):
|
|
5230
|
+
*
|
|
5231
|
+
* - `'callback'` (default) — the legacy pixel path: decoded frames are
|
|
5232
|
+
* buffered as `DecodedFrame`s and drained via `pullFrames`.
|
|
5233
|
+
* - `'shm'` — the shared-memory frame plane: decoded frames are written
|
|
5234
|
+
* into an OS shared-memory ring and drained as zero-pixel
|
|
5235
|
+
* `FrameHandle`s via `pullHandles`. A session is one mode or the
|
|
5236
|
+
* other — `pullFrames` returns nothing for an `'shm'` session and
|
|
5237
|
+
* `pullHandles` returns nothing for a `'callback'` session.
|
|
5238
|
+
*/
|
|
5239
|
+
frameSink: _enum(["callback", "shm"]).default("callback")
|
|
5227
5240
|
});
|
|
5228
5241
|
function errMsg(err) {
|
|
5229
5242
|
if (err instanceof Error) return err.message;
|
|
@@ -5981,6 +5994,53 @@ const DecodedFrameSchema = object({
|
|
|
5981
5994
|
format: _enum(["jpeg", "rgb", "bgr", "yuv420", "gray"]),
|
|
5982
5995
|
timestamp: number()
|
|
5983
5996
|
});
|
|
5997
|
+
const FrameHandleSchema = object({
|
|
5998
|
+
shmId: string(),
|
|
5999
|
+
slot: number().int().nonnegative(),
|
|
6000
|
+
seq: number().int().nonnegative(),
|
|
6001
|
+
width: number().int().positive(),
|
|
6002
|
+
height: number().int().positive(),
|
|
6003
|
+
format: _enum(["jpeg", "rgb", "bgr", "yuv420", "gray"]),
|
|
6004
|
+
pts: number(),
|
|
6005
|
+
byteLength: number().int().nonnegative(),
|
|
6006
|
+
nodeId: string(),
|
|
6007
|
+
slotCount: number().int().positive()
|
|
6008
|
+
});
|
|
6009
|
+
const FrameHandleFormatSchema = _enum(["rgb", "bgr", "yuv420", "gray"]);
|
|
6010
|
+
const SubscribeFramesInputSchema = object({
|
|
6011
|
+
brokerId: string(),
|
|
6012
|
+
format: FrameHandleFormatSchema,
|
|
6013
|
+
/**
|
|
6014
|
+
* Optional reader-side cadence hint in frames per second. The broker does
|
|
6015
|
+
* NOT throttle — latest-wins ring reads drop frames implicitly for a slow
|
|
6016
|
+
* consumer. The value is echoed back in `SubscribeFramesResult.maxFps` so
|
|
6017
|
+
* the consumer can pace its own `pullFrameHandles` polling.
|
|
6018
|
+
*/
|
|
6019
|
+
maxFps: number().positive().optional(),
|
|
6020
|
+
/** Short caller-identity tag (`motion`, `detection`, …) for diagnostics. */
|
|
6021
|
+
tag: string().optional()
|
|
6022
|
+
});
|
|
6023
|
+
const SubscribeFramesResultSchema = object({
|
|
6024
|
+
/** Opaque id the consumer passes to `pullFrameHandles` / `unsubscribeFrames`. */
|
|
6025
|
+
subscriptionId: string(),
|
|
6026
|
+
/** Reader-side cadence hint (frames/s) — echoes `SubscribeFramesInput.maxFps`. */
|
|
6027
|
+
maxFps: number().nonnegative()
|
|
6028
|
+
});
|
|
6029
|
+
const DecodedAudioChunkSchema = object({
|
|
6030
|
+
data: _instanceof(Uint8Array),
|
|
6031
|
+
sampleRate: number().int().positive(),
|
|
6032
|
+
channels: number().int().positive(),
|
|
6033
|
+
timestamp: number()
|
|
6034
|
+
});
|
|
6035
|
+
const SubscribeAudioChunksInputSchema = object({
|
|
6036
|
+
brokerId: string(),
|
|
6037
|
+
/** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
|
|
6038
|
+
tag: string().optional()
|
|
6039
|
+
});
|
|
6040
|
+
const SubscribeAudioChunksResultSchema = object({
|
|
6041
|
+
/** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
|
|
6042
|
+
subscriptionId: string()
|
|
6043
|
+
});
|
|
5984
6044
|
const BrokerStatusSchema$1 = _enum(["idle", "connecting", "streaming", "error", "stopped"]);
|
|
5985
6045
|
const BrokerStatsSchema = object({
|
|
5986
6046
|
status: BrokerStatusSchema$1,
|
|
@@ -6202,9 +6262,76 @@ const RtpSourceSchema = object({
|
|
|
6202
6262
|
object({ released: boolean(), refcount: number().int().nonnegative() }),
|
|
6203
6263
|
{ kind: "mutation", auth: "admin" }
|
|
6204
6264
|
),
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6265
|
+
/**
|
|
6266
|
+
* ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
|
|
6267
|
+
*
|
|
6268
|
+
* The serialisable replacement for the live-object `IStreamBroker.
|
|
6269
|
+
* onDecodedAudioChunk` callback path. Unlike the video frame plane,
|
|
6270
|
+
* audio chunks are tiny (a ~500ms PCM window is a few KB) so they ship
|
|
6271
|
+
* their bytes INLINE over tRPC — no shared-memory ring. A consumer:
|
|
6272
|
+
*
|
|
6273
|
+
* 1. `subscribeAudioChunks({ brokerId, tag })` — the broker registers
|
|
6274
|
+
* a per-subscription bounded FIFO queue and returns a
|
|
6275
|
+
* `subscriptionId`.
|
|
6276
|
+
* 2. polls `pullAudioChunks({ subscriptionId, maxCount })` — drains
|
|
6277
|
+
* `DecodedAudioChunk[]` in arrival order (FIFO, no latest-wins
|
|
6278
|
+
* drop: an audio gap is audible / breaks an analysis window). The
|
|
6279
|
+
* queue is generously sized; it only drops its oldest chunk if a
|
|
6280
|
+
* truly stalled consumer lets it overflow.
|
|
6281
|
+
* 3. `unsubscribeAudioChunks({ subscriptionId })` on teardown.
|
|
6282
|
+
*/
|
|
6283
|
+
subscribeAudioChunks: method(
|
|
6284
|
+
SubscribeAudioChunksInputSchema,
|
|
6285
|
+
SubscribeAudioChunksResultSchema,
|
|
6286
|
+
{ kind: "mutation" }
|
|
6287
|
+
),
|
|
6288
|
+
pullAudioChunks: method(
|
|
6289
|
+
object({
|
|
6290
|
+
subscriptionId: string(),
|
|
6291
|
+
maxCount: number().int().positive().default(8)
|
|
6292
|
+
}),
|
|
6293
|
+
array(DecodedAudioChunkSchema).readonly()
|
|
6294
|
+
),
|
|
6295
|
+
unsubscribeAudioChunks: method(
|
|
6296
|
+
object({ subscriptionId: string() }),
|
|
6297
|
+
object({ released: boolean() }),
|
|
6298
|
+
{ kind: "mutation" }
|
|
6299
|
+
),
|
|
6300
|
+
/**
|
|
6301
|
+
* ── Shared-memory frame plane (Phase 5 / D9) ──────────────────────
|
|
6302
|
+
*
|
|
6303
|
+
* The handle-based replacement for the live-object `IStreamBroker.
|
|
6304
|
+
* onDecodedFrame` callback path. A consumer:
|
|
6305
|
+
*
|
|
6306
|
+
* 1. `subscribeFrames({ brokerId, format })` — the broker spins up
|
|
6307
|
+
* (or reuses) a `frameSink: 'shm'` decoder session producing that
|
|
6308
|
+
* `format` and returns a `subscriptionId`.
|
|
6309
|
+
* 2. polls `pullFrameHandles({ subscriptionId, maxCount })` — drains
|
|
6310
|
+
* zero-pixel `FrameHandle[]`; each handle is fed to a
|
|
6311
|
+
* `FrameRingReader` that opens the named shm segment and reads the
|
|
6312
|
+
* pixels back zero-copy.
|
|
6313
|
+
* 3. `unsubscribeFrames({ subscriptionId })` on teardown.
|
|
6314
|
+
*
|
|
6315
|
+
* The broker keeps one shm ring per `(brokerId, format)` actually
|
|
6316
|
+
* requested — no broker-side `sharp` conversion. fps throttling is
|
|
6317
|
+
* implicit (latest-wins ring reads drop frames for a slow consumer).
|
|
6318
|
+
*/
|
|
6319
|
+
subscribeFrames: method(
|
|
6320
|
+
SubscribeFramesInputSchema,
|
|
6321
|
+
SubscribeFramesResultSchema,
|
|
6322
|
+
{ kind: "mutation" }
|
|
6323
|
+
),
|
|
6324
|
+
pullFrameHandles: method(
|
|
6325
|
+
object({
|
|
6326
|
+
subscriptionId: string(),
|
|
6327
|
+
maxCount: number().int().positive().default(4)
|
|
6328
|
+
}),
|
|
6329
|
+
array(FrameHandleSchema).readonly()
|
|
6330
|
+
),
|
|
6331
|
+
unsubscribeFrames: method(
|
|
6332
|
+
object({ subscriptionId: string() }),
|
|
6333
|
+
object({ released: boolean() }),
|
|
6334
|
+
{ kind: "mutation" }
|
|
6208
6335
|
),
|
|
6209
6336
|
setPreBufferDuration: method(
|
|
6210
6337
|
object({ brokerId: string(), seconds: number().min(0).max(30) }),
|
|
@@ -7530,6 +7657,29 @@ const StreamProfilePatchSchema = object({
|
|
|
7530
7657
|
}),
|
|
7531
7658
|
_void(),
|
|
7532
7659
|
{ kind: "mutation", auth: "admin" }
|
|
7660
|
+
),
|
|
7661
|
+
/**
|
|
7662
|
+
* Build the `ConfigUISchema` (admin-ui `ConfigFormBuilder` input
|
|
7663
|
+
* shape) for this camera's stream-encoder settings — one section per
|
|
7664
|
+
* profile (main / sub / ext) with the resolution / codec / framerate
|
|
7665
|
+
* / bitrate / bitrate-mode / encoder-profile / GOP controls the
|
|
7666
|
+
* firmware actually exposes.
|
|
7667
|
+
*
|
|
7668
|
+
* Driven by `getOptions` (camera-probed availability) + `getStatus`
|
|
7669
|
+
* (current per-profile config); each field's `default` is seeded
|
|
7670
|
+
* from the live config so the form renders the camera state in one
|
|
7671
|
+
* pass. Returns `null` when the camera exposes no configurable
|
|
7672
|
+
* stream property — the renderer then shows the unsupported message.
|
|
7673
|
+
*
|
|
7674
|
+
* Output is `z.unknown().nullable()` — the same convention every
|
|
7675
|
+
* other `ConfigUISchema`-returning cap method uses (`device-ops`,
|
|
7676
|
+
* `device-manager`); `ConfigUISchema` is a TS-only type with no
|
|
7677
|
+
* companion Zod schema, and a concrete object would collapse
|
|
7678
|
+
* unrelated AppRouter branches to `unknown` during codegen.
|
|
7679
|
+
*/
|
|
7680
|
+
getConfigSchema: method(
|
|
7681
|
+
object({ deviceId: number() }),
|
|
7682
|
+
unknown().nullable()
|
|
7533
7683
|
)
|
|
7534
7684
|
}
|
|
7535
7685
|
});
|
|
@@ -8600,7 +8750,7 @@ const OauthIntegrationDescriptorSchema = object({
|
|
|
8600
8750
|
displayName: string(),
|
|
8601
8751
|
/** Scopes baked into every token issued for this integration. */
|
|
8602
8752
|
requestedScopes: array(TokenScopeSchema),
|
|
8603
|
-
/** Allowed redirect_uri prefixes. /oauth2/authorize rejects any
|
|
8753
|
+
/** Allowed redirect_uri prefixes. /api/oauth2/authorize rejects any
|
|
8604
8754
|
* redirect_uri that does not start with one of these. Required —
|
|
8605
8755
|
* an empty list means the integration can never complete linking. */
|
|
8606
8756
|
allowedRedirectPrefixes: array(string()).min(1)
|
|
@@ -8890,21 +9040,30 @@ const AddonPageDeclarationSchema = object({
|
|
|
8890
9040
|
});
|
|
8891
9041
|
const WidgetHostEnum = _enum(["device-tab", "dashboard", "integration-detail"]);
|
|
8892
9042
|
const WidgetSizeEnum = _enum(["xs", "sm", "md", "lg", "xl"]);
|
|
9043
|
+
const WidgetRemoteSchema = object({
|
|
9044
|
+
remoteName: string(),
|
|
9045
|
+
exposedModule: string(),
|
|
9046
|
+
componentKey: string().optional()
|
|
9047
|
+
});
|
|
8893
9048
|
const WidgetMetadataSchema = object({
|
|
8894
|
-
|
|
8895
|
-
|
|
9049
|
+
// ── UiContribution core (kind:'remote') ──────────────────────────
|
|
9050
|
+
/** Primary host tab — `'dashboard'`, `'device-tab'`, or a device-detail tab id. */
|
|
9051
|
+
tab: string(),
|
|
9052
|
+
/** Optional sub-tab within `tab`. */
|
|
9053
|
+
subTab: string().optional(),
|
|
8896
9054
|
/** Operator-facing label. */
|
|
8897
9055
|
label: string(),
|
|
9056
|
+
/** Ordering within `(tab, subTab)`, ascending. */
|
|
9057
|
+
order: number().optional(),
|
|
9058
|
+
/** Always `'remote'` — a widget is a Module Federation remote. */
|
|
9059
|
+
kind: literal("remote"),
|
|
9060
|
+
/** MF remote descriptor. */
|
|
9061
|
+
remote: WidgetRemoteSchema,
|
|
9062
|
+
// ── Widget-only metadata ─────────────────────────────────────────
|
|
9063
|
+
/** Stable id within the addon — kebab-case. Equals `remote.componentKey`. */
|
|
9064
|
+
stableId: string(),
|
|
8898
9065
|
description: string().optional(),
|
|
8899
9066
|
icon: string().optional(),
|
|
8900
|
-
/**
|
|
8901
|
-
* Module Federation remote name — must match the `name` field on the
|
|
8902
|
-
* widget addon's `federation()` plugin config. Used by the host's
|
|
8903
|
-
* `<WidgetRegistryProvider>` to call `loadRemote('<remoteName>/widgets')`.
|
|
8904
|
-
* Conventionally `addon_<addonid>_widgets` (snake_case; MF names
|
|
8905
|
-
* cannot contain hyphens).
|
|
8906
|
-
*/
|
|
8907
|
-
remoteName: string(),
|
|
8908
9067
|
/**
|
|
8909
9068
|
* Bundle filename inside the addon's `dist/` dir served at
|
|
8910
9069
|
* `/api/addon-widgets/<addonId>/<bundle>`. With Module Federation
|
|
@@ -8913,9 +9072,9 @@ const WidgetMetadataSchema = object({
|
|
|
8913
9072
|
* cache-buster URL without a separate filesystem stat.
|
|
8914
9073
|
*/
|
|
8915
9074
|
bundle: string(),
|
|
8916
|
-
/**
|
|
9075
|
+
/** Every host the widget supports. The picker filters on this set. */
|
|
8917
9076
|
hosts: array(WidgetHostEnum).readonly(),
|
|
8918
|
-
/** Required props the host must supply. Validated at
|
|
9077
|
+
/** Required props the host must supply. Validated at `<WidgetSlot>` mount. */
|
|
8919
9078
|
requires: object({
|
|
8920
9079
|
deviceContext: boolean().default(false),
|
|
8921
9080
|
integrationContext: boolean().default(false)
|
|
@@ -8990,6 +9149,16 @@ const InvokeReplyEnvelopeSchema = object({
|
|
|
8990
9149
|
invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
|
|
8991
9150
|
}
|
|
8992
9151
|
});
|
|
9152
|
+
const ShmRingStatsSchema = object({
|
|
9153
|
+
sessionId: string(),
|
|
9154
|
+
slotCount: number().int(),
|
|
9155
|
+
slotByteLength: number().int(),
|
|
9156
|
+
segmentBytes: number().int(),
|
|
9157
|
+
budgetMb: number().int(),
|
|
9158
|
+
framesWritten: number().int(),
|
|
9159
|
+
getFrameHits: number().int(),
|
|
9160
|
+
getFrameMisses: number().int()
|
|
9161
|
+
});
|
|
8993
9162
|
({
|
|
8994
9163
|
methods: {
|
|
8995
9164
|
// ── Discovery ─────────────────────────────────────────────────
|
|
@@ -9017,10 +9186,27 @@ const InvokeReplyEnvelopeSchema = object({
|
|
|
9017
9186
|
url: string()
|
|
9018
9187
|
}), _void()),
|
|
9019
9188
|
// ── Output — polling-based frame retrieval ────────────────────
|
|
9189
|
+
// `pullFrames` drains the pixel `DecodedFrame[]` of a `frameSink:
|
|
9190
|
+
// 'callback'` session. `pullHandles` (Phase 5 / D9) drains the
|
|
9191
|
+
// zero-pixel `FrameHandle[]` of a `frameSink: 'shm'` session — the
|
|
9192
|
+
// broker hands each handle to a `FrameRingReader` that opens the
|
|
9193
|
+
// named segment and reads the pixels back zero-copy. A session is
|
|
9194
|
+
// one mode or the other; the unmatched method returns an empty
|
|
9195
|
+
// array.
|
|
9020
9196
|
pullFrames: method(object({
|
|
9021
9197
|
sessionId: string(),
|
|
9022
9198
|
maxCount: number().default(1)
|
|
9023
9199
|
}), array(DecodedFrameSchema)),
|
|
9200
|
+
pullHandles: method(object({
|
|
9201
|
+
sessionId: string(),
|
|
9202
|
+
maxCount: number().default(1)
|
|
9203
|
+
}), array(FrameHandleSchema)),
|
|
9204
|
+
// ── Frame fetch (Phase 5 / D9 downstream access) ──────────────
|
|
9205
|
+
// Read the pixels a FrameHandle refers to from this node's shm ring.
|
|
9206
|
+
// Returns null when the slot was already recycled (latest-wins).
|
|
9207
|
+
getFrame: method(object({ handle: FrameHandleSchema }), DecodedFrameSchema.nullable()),
|
|
9208
|
+
// shm ring usage stats for a session.
|
|
9209
|
+
getShmStats: method(object({ sessionId: string() }), ShmRingStatsSchema.nullable()),
|
|
9024
9210
|
// ── Control ───────────────────────────────────────────────────
|
|
9025
9211
|
updateConfig: method(object({
|
|
9026
9212
|
sessionId: string(),
|
|
@@ -10192,8 +10378,8 @@ const DevicePersistConfigPayloadSchema = object({
|
|
|
10192
10378
|
/**
|
|
10193
10379
|
* Return the addon ids that declared a wrapper provider for `capName`.
|
|
10194
10380
|
* Backs the device-bindings UI's wrapper-picker dropdown. Entries are
|
|
10195
|
-
* sourced from `CapabilityRegistry.wrapperProviders`, populated
|
|
10196
|
-
* `
|
|
10381
|
+
* sourced from `CapabilityRegistry.wrapperProviders`, populated when
|
|
10382
|
+
* the cap definition declares `kind: 'wrapper'`.
|
|
10197
10383
|
*/
|
|
10198
10384
|
listWrappersForCap: method(
|
|
10199
10385
|
object({ capName: string() }),
|
|
@@ -11283,13 +11469,17 @@ const PtzMoveCommandSchema = object({
|
|
|
11283
11469
|
zoom: number().optional(),
|
|
11284
11470
|
speed: number().optional()
|
|
11285
11471
|
});
|
|
11472
|
+
PtzPositionSchema.extend({ autofocus: boolean() });
|
|
11286
11473
|
const PtzOptionsSchema = object({
|
|
11287
11474
|
hasPan: boolean(),
|
|
11288
11475
|
hasTilt: boolean(),
|
|
11289
11476
|
hasZoom: boolean(),
|
|
11290
11477
|
supportsPresets: boolean(),
|
|
11291
11478
|
/** Max number of named presets the camera supports, when known. */
|
|
11292
|
-
maxPresets: number().optional()
|
|
11479
|
+
maxPresets: number().optional(),
|
|
11480
|
+
/** Whether the camera exposes a controllable autofocus toggle
|
|
11481
|
+
* (boolean `hasX` per the getOptions availability convention). */
|
|
11482
|
+
hasAutofocus: boolean()
|
|
11293
11483
|
});
|
|
11294
11484
|
({
|
|
11295
11485
|
deviceTypes: [DeviceType.Camera],
|
|
@@ -11346,6 +11536,13 @@ const PtzOptionsSchema = object({
|
|
|
11346
11536
|
getPosition: method(
|
|
11347
11537
|
object({ deviceId: number() }),
|
|
11348
11538
|
PtzPositionSchema
|
|
11539
|
+
),
|
|
11540
|
+
/** Toggle the camera's autofocus. Only meaningful when
|
|
11541
|
+
* `getOptions().hasAutofocus` is true. */
|
|
11542
|
+
setAutofocus: method(
|
|
11543
|
+
object({ deviceId: number(), enabled: boolean() }),
|
|
11544
|
+
_void(),
|
|
11545
|
+
{ kind: "mutation" }
|
|
11349
11546
|
)
|
|
11350
11547
|
}
|
|
11351
11548
|
});
|
|
@@ -12714,6 +12911,19 @@ const RenameNodeResultSchema = object({
|
|
|
12714
12911
|
record(string(), ClusterAddonStatusEntrySchema),
|
|
12715
12912
|
{ auth: "admin" }
|
|
12716
12913
|
),
|
|
12914
|
+
getCapUsageGraph: method(
|
|
12915
|
+
object({
|
|
12916
|
+
windowSeconds: number().int().positive().max(300).default(60)
|
|
12917
|
+
}),
|
|
12918
|
+
array(object({
|
|
12919
|
+
callerAddonId: string(),
|
|
12920
|
+
providerAddonId: string(),
|
|
12921
|
+
capName: string(),
|
|
12922
|
+
callsPerMin: number(),
|
|
12923
|
+
lastCallAtMs: number()
|
|
12924
|
+
})).readonly(),
|
|
12925
|
+
{ auth: "admin" }
|
|
12926
|
+
),
|
|
12717
12927
|
/**
|
|
12718
12928
|
* Direct per-node addon listing — calls `$agent.status` on the target
|
|
12719
12929
|
* node (or returns the hub registry for `nodeId === 'hub'`) and surfaces
|
|
@@ -14519,6 +14729,60 @@ function balanceAudio(input) {
|
|
|
14519
14729
|
const best = input.nodes.slice().sort((a, b) => a.deviceCount - b.deviceCount)[0];
|
|
14520
14730
|
return { nodeId: best.nodeId, reason: "capacity" };
|
|
14521
14731
|
}
|
|
14732
|
+
const POLL_INTERVAL_MS = 200;
|
|
14733
|
+
const PULL_MAX_COUNT = 8;
|
|
14734
|
+
async function startAudioChunkPoller(options) {
|
|
14735
|
+
const { api, brokerId, tag, onChunk, logger } = options;
|
|
14736
|
+
let subscriptionId;
|
|
14737
|
+
try {
|
|
14738
|
+
const result = await api.streamBroker.subscribeAudioChunks.mutate({
|
|
14739
|
+
brokerId,
|
|
14740
|
+
tag
|
|
14741
|
+
});
|
|
14742
|
+
subscriptionId = result.subscriptionId;
|
|
14743
|
+
} catch (err) {
|
|
14744
|
+
logger.warn("audio-chunk poller: subscribeAudioChunks failed", {
|
|
14745
|
+
meta: { brokerId, tag, error: errMsg(err) }
|
|
14746
|
+
});
|
|
14747
|
+
return null;
|
|
14748
|
+
}
|
|
14749
|
+
let stopped = false;
|
|
14750
|
+
let timer;
|
|
14751
|
+
const tick = async () => {
|
|
14752
|
+
if (stopped) return;
|
|
14753
|
+
try {
|
|
14754
|
+
const chunks = await api.streamBroker.pullAudioChunks.query({
|
|
14755
|
+
subscriptionId,
|
|
14756
|
+
maxCount: PULL_MAX_COUNT
|
|
14757
|
+
});
|
|
14758
|
+
for (const chunk of chunks) {
|
|
14759
|
+
if (stopped) break;
|
|
14760
|
+
await onChunk(chunk);
|
|
14761
|
+
}
|
|
14762
|
+
} catch (err) {
|
|
14763
|
+
logger.warn("audio-chunk poller: pullAudioChunks failed", {
|
|
14764
|
+
meta: { brokerId, subscriptionId, error: errMsg(err) }
|
|
14765
|
+
});
|
|
14766
|
+
}
|
|
14767
|
+
if (!stopped) {
|
|
14768
|
+
timer = setTimeout(() => void tick(), POLL_INTERVAL_MS);
|
|
14769
|
+
}
|
|
14770
|
+
};
|
|
14771
|
+
void tick();
|
|
14772
|
+
return () => {
|
|
14773
|
+
if (stopped) return;
|
|
14774
|
+
stopped = true;
|
|
14775
|
+
if (timer) {
|
|
14776
|
+
clearTimeout(timer);
|
|
14777
|
+
timer = void 0;
|
|
14778
|
+
}
|
|
14779
|
+
api.streamBroker.unsubscribeAudioChunks.mutate({ subscriptionId }).catch((err) => {
|
|
14780
|
+
logger.warn("audio-chunk poller: unsubscribeAudioChunks failed", {
|
|
14781
|
+
meta: { brokerId, subscriptionId, error: errMsg(err) }
|
|
14782
|
+
});
|
|
14783
|
+
});
|
|
14784
|
+
};
|
|
14785
|
+
}
|
|
14522
14786
|
const PHASE_MODE_VALUES = /* @__PURE__ */ new Set([
|
|
14523
14787
|
"disabled",
|
|
14524
14788
|
"always-on",
|
|
@@ -14974,11 +15238,17 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
14974
15238
|
const widgetsProvider = {
|
|
14975
15239
|
listWidgets: async () => [
|
|
14976
15240
|
{
|
|
14977
|
-
|
|
15241
|
+
tab: "device-tab",
|
|
14978
15242
|
label: "Pipeline Quick Stats",
|
|
15243
|
+
kind: "remote",
|
|
15244
|
+
remote: {
|
|
15245
|
+
remoteName: "addon_pipeline_orchestrator_widgets",
|
|
15246
|
+
exposedModule: "./widgets",
|
|
15247
|
+
componentKey: "pipeline-quick-stats"
|
|
15248
|
+
},
|
|
15249
|
+
stableId: "pipeline-quick-stats",
|
|
14979
15250
|
description: "Phase / Detection FPS / Inference / Active Tracks tile row.",
|
|
14980
15251
|
icon: "activity",
|
|
14981
|
-
remoteName: "addon_pipeline_orchestrator_widgets",
|
|
14982
15252
|
bundle: "remoteEntry.js",
|
|
14983
15253
|
hosts: ["device-tab", "dashboard"],
|
|
14984
15254
|
requires: { deviceContext: true, integrationContext: false },
|
|
@@ -14988,11 +15258,17 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
14988
15258
|
defaultRows: 1
|
|
14989
15259
|
},
|
|
14990
15260
|
{
|
|
14991
|
-
|
|
15261
|
+
tab: "device-tab",
|
|
14992
15262
|
label: "Zone Editor",
|
|
15263
|
+
kind: "remote",
|
|
15264
|
+
remote: {
|
|
15265
|
+
remoteName: "addon_pipeline_orchestrator_widgets",
|
|
15266
|
+
exposedModule: "./widgets",
|
|
15267
|
+
componentKey: "zone-editor"
|
|
15268
|
+
},
|
|
15269
|
+
stableId: "zone-editor",
|
|
14993
15270
|
description: "Polygon / tripwire CRUD + per-stage rule editor.",
|
|
14994
15271
|
icon: "shapes",
|
|
14995
|
-
remoteName: "addon_pipeline_orchestrator_widgets",
|
|
14996
15272
|
bundle: "remoteEntry.js",
|
|
14997
15273
|
hosts: ["device-tab"],
|
|
14998
15274
|
requires: { deviceContext: true, integrationContext: false },
|
|
@@ -15000,24 +15276,6 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
15000
15276
|
allowedSizes: ["lg", "xl"],
|
|
15001
15277
|
defaultColumns: 12,
|
|
15002
15278
|
defaultRows: 4
|
|
15003
|
-
},
|
|
15004
|
-
{
|
|
15005
|
-
// On-camera motion-detection grid editor. The `motion-zones`
|
|
15006
|
-
// cap is owned by the camera provider addons; the widget only
|
|
15007
|
-
// talks to it over tRPC, so hosting the React surface here
|
|
15008
|
-
// (one bundle) avoids duplicating it per provider.
|
|
15009
|
-
stableId: "motion-zones-editor",
|
|
15010
|
-
label: "Motion Zones",
|
|
15011
|
-
description: "On-camera motion-detection grid editor (live-frame overlay).",
|
|
15012
|
-
icon: "grid-3x3",
|
|
15013
|
-
remoteName: "addon_pipeline_orchestrator_widgets",
|
|
15014
|
-
bundle: "remoteEntry.js",
|
|
15015
|
-
hosts: ["device-tab"],
|
|
15016
|
-
requires: { deviceContext: true, integrationContext: false },
|
|
15017
|
-
defaultSize: "lg",
|
|
15018
|
-
allowedSizes: ["md", "lg"],
|
|
15019
|
-
defaultColumns: 12,
|
|
15020
|
-
defaultRows: 1
|
|
15021
15279
|
}
|
|
15022
15280
|
]
|
|
15023
15281
|
};
|
|
@@ -17166,53 +17424,27 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
17166
17424
|
title: "Detection Zones",
|
|
17167
17425
|
tab: "zones",
|
|
17168
17426
|
location: "top-tab",
|
|
17169
|
-
// Single full-width column — the
|
|
17170
|
-
//
|
|
17427
|
+
// Single full-width column — the zone-editor widget paints the
|
|
17428
|
+
// polygon canvas + side panel + rules + occupancy across the
|
|
17171
17429
|
// entire viewport. Default 2-column grid would clip everything to
|
|
17172
17430
|
// 50% width.
|
|
17173
17431
|
columns: 1,
|
|
17174
17432
|
order: 0,
|
|
17175
17433
|
fields: [
|
|
17176
17434
|
{
|
|
17177
|
-
type: "
|
|
17435
|
+
type: "widget",
|
|
17178
17436
|
key: "zones",
|
|
17179
17437
|
label: "Detection Zones",
|
|
17180
|
-
|
|
17438
|
+
widgetId: "pipeline-orchestrator/zone-editor",
|
|
17181
17439
|
// Span across the (single) column so even if a layout engine
|
|
17182
17440
|
// were to compute multiple columns, the field still pulls full
|
|
17183
17441
|
// width.
|
|
17184
|
-
span: 1,
|
|
17185
|
-
// Hydrated form requires a value field; the `zone-editor`
|
|
17186
|
-
// renderer reads zones from the `dev.state.zones` slice via
|
|
17187
|
-
// DeviceProxy and ignores this value entirely.
|
|
17188
|
-
value: void 0
|
|
17189
|
-
}
|
|
17190
|
-
]
|
|
17191
|
-
};
|
|
17192
|
-
const motionZonesSection = {
|
|
17193
|
-
id: "motion-zones",
|
|
17194
|
-
title: "Motion Zones",
|
|
17195
|
-
tab: "motion",
|
|
17196
|
-
location: "settings",
|
|
17197
|
-
columns: 1,
|
|
17198
|
-
order: 0,
|
|
17199
|
-
fields: [
|
|
17200
|
-
{
|
|
17201
|
-
// Widget fields manage their own state via DeviceProxy —
|
|
17202
|
-
// `ConfigWidgetField` carries no `value` (unlike the legacy
|
|
17203
|
-
// `zone-editor` field type which still does). No `label` —
|
|
17204
|
-
// the section title already reads "Motion Zones"; a field
|
|
17205
|
-
// label here would duplicate it.
|
|
17206
|
-
type: "widget",
|
|
17207
|
-
key: "motion-zones",
|
|
17208
|
-
label: "",
|
|
17209
|
-
widgetId: "pipeline-orchestrator/motion-zones-editor",
|
|
17210
17442
|
span: 1
|
|
17211
17443
|
}
|
|
17212
17444
|
]
|
|
17213
17445
|
};
|
|
17214
17446
|
return {
|
|
17215
|
-
sections: [...baseSections, zonesSection
|
|
17447
|
+
sections: [...baseSections, zonesSection]
|
|
17216
17448
|
};
|
|
17217
17449
|
}
|
|
17218
17450
|
// `getCameraPipelineWithFallback` was a thin wrapper over
|
|
@@ -18065,14 +18297,16 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
18065
18297
|
async handleInferenceResult(payload) {
|
|
18066
18298
|
const ctx = this.ctx;
|
|
18067
18299
|
if (!ctx) return;
|
|
18068
|
-
const { deviceId, frame } = payload;
|
|
18300
|
+
const { deviceId, frame, frameHandle } = payload;
|
|
18069
18301
|
if (frame.detections.length === 0) return;
|
|
18070
18302
|
this.ctx.eventBus.emit({
|
|
18071
18303
|
id: `detection-${deviceId}-${Date.now()}`,
|
|
18072
18304
|
category: EventCategory.DetectionResult,
|
|
18073
18305
|
source: { type: "device", id: deviceId, nodeId: "hub", addonId: "pipeline-orchestrator", deviceId },
|
|
18074
18306
|
timestamp: /* @__PURE__ */ new Date(),
|
|
18075
|
-
|
|
18307
|
+
// Forward the upstream shm-ring `frameHandle` so post-analysis
|
|
18308
|
+
// consumers (Task 8) can resolve the original frame zero-copy.
|
|
18309
|
+
data: { frame, analysisResults: [], frameHandle }
|
|
18076
18310
|
});
|
|
18077
18311
|
}
|
|
18078
18312
|
/**
|
|
@@ -18163,16 +18397,6 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
18163
18397
|
}
|
|
18164
18398
|
const audioStream = config2.audioStreamId ?? config2.motionStreamId;
|
|
18165
18399
|
const audioBrokerId = `${deviceId}/${audioStream}`;
|
|
18166
|
-
const broker = await api.streamBroker.getBroker.query({
|
|
18167
|
-
brokerId: audioBrokerId
|
|
18168
|
-
});
|
|
18169
|
-
if (!broker) {
|
|
18170
|
-
this.ctx.logger.warn("No broker found for audio subscription", {
|
|
18171
|
-
tags: { deviceId },
|
|
18172
|
-
meta: { brokerId: audioBrokerId }
|
|
18173
|
-
});
|
|
18174
|
-
return null;
|
|
18175
|
-
}
|
|
18176
18400
|
const settings = await api.audioAnalysis.resolveDeviceSettings.query({
|
|
18177
18401
|
deviceId
|
|
18178
18402
|
});
|
|
@@ -18190,8 +18414,12 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
18190
18414
|
meta: { audioNodeId, isRemote: isRemoteAudio }
|
|
18191
18415
|
});
|
|
18192
18416
|
let loggedAnalyzerStatus = false;
|
|
18193
|
-
const
|
|
18194
|
-
|
|
18417
|
+
const teardown = await startAudioChunkPoller({
|
|
18418
|
+
api,
|
|
18419
|
+
brokerId: audioBrokerId,
|
|
18420
|
+
tag: "audio-analyzer",
|
|
18421
|
+
logger: this.ctx.logger,
|
|
18422
|
+
onChunk: async (chunk) => {
|
|
18195
18423
|
if (!loggedAnalyzerStatus) {
|
|
18196
18424
|
loggedAnalyzerStatus = true;
|
|
18197
18425
|
this.ctx.logger.info("audio status", {
|
|
@@ -18280,12 +18508,18 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
18280
18508
|
meta: { error: msg }
|
|
18281
18509
|
});
|
|
18282
18510
|
}
|
|
18283
|
-
}
|
|
18284
|
-
|
|
18285
|
-
)
|
|
18511
|
+
}
|
|
18512
|
+
});
|
|
18513
|
+
if (!teardown) {
|
|
18514
|
+
this.ctx.logger.warn("audio subscription failed — no broker", {
|
|
18515
|
+
tags: { deviceId },
|
|
18516
|
+
meta: { brokerId: audioBrokerId }
|
|
18517
|
+
});
|
|
18518
|
+
return null;
|
|
18519
|
+
}
|
|
18286
18520
|
this.ctx.logger.info("Audio stream subscribed", { tags: { deviceId } });
|
|
18287
18521
|
return () => {
|
|
18288
|
-
|
|
18522
|
+
teardown();
|
|
18289
18523
|
};
|
|
18290
18524
|
}
|
|
18291
18525
|
}
|