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