@camstack/addon-post-analysis 1.2.48 → 1.2.50
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/{dist-4NhN1aPx.mjs → dist-BhadCxSQ.mjs} +800 -38
- package/dist/{dist-DKhwOB5L.js → dist-Bm5GpbGn.js} +811 -37
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-Bjc_xs4R.mjs → node-Co0YpEFr.mjs} +1 -0
- package/dist/{node-XPzbKGXi.js → node-HEkeoPPw.js} +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CD55gKkr.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B0B7ZGiM.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BfiNBOyJ.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CJ3AVT6B.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-DnhLbxcA.mjs → hostInit-BahNk1yZ.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +1562 -508
- package/dist/pipeline-analytics/index.mjs +1561 -507
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -6460,7 +6460,20 @@ var BrokerStatsSchema = object({
|
|
|
6460
6460
|
sampleRate: number(),
|
|
6461
6461
|
channels: number(),
|
|
6462
6462
|
supported: boolean()
|
|
6463
|
-
}).nullable().optional()
|
|
6463
|
+
}).nullable().optional(),
|
|
6464
|
+
/**
|
|
6465
|
+
* BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
|
|
6466
|
+
* distributing none of the device's audio, on live or recording.
|
|
6467
|
+
*
|
|
6468
|
+
* Present so a silent camera can be told apart from a broken one on the
|
|
6469
|
+
* stream panel itself, without cross-referencing the switch group: a
|
|
6470
|
+
* broker holding an `audio` track descriptor while `audioMuted` is true is
|
|
6471
|
+
* working exactly as asked. `audioMutedDropped` counts the audio units
|
|
6472
|
+
* thrown away since the current dial — it is how you confirm from stats
|
|
6473
|
+
* alone that the mute is on the packet path and not merely persisted.
|
|
6474
|
+
*/
|
|
6475
|
+
audioMuted: boolean().optional(),
|
|
6476
|
+
audioMutedDropped: number().optional()
|
|
6464
6477
|
});
|
|
6465
6478
|
/**
|
|
6466
6479
|
* Exporter-facing "profile restream" entry. Returned by
|
|
@@ -6509,9 +6522,38 @@ var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
|
6509
6522
|
/**
|
|
6510
6523
|
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
6511
6524
|
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
6525
|
+
*
|
|
6526
|
+
* ## The id is normalised here, and it has to be
|
|
6527
|
+
*
|
|
6528
|
+
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
6529
|
+
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
6530
|
+
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
6531
|
+
* call fails with `no provider registered for cap "…"`. The local-first
|
|
6532
|
+
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
6533
|
+
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
6534
|
+
* the thing you reach for when you specifically need THIS node — silently
|
|
6535
|
+
* addressed a node that does not exist.
|
|
6536
|
+
*
|
|
6537
|
+
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
6538
|
+
* own node to read the host's hardware-decode backend. It never once answered,
|
|
6539
|
+
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
6540
|
+
* while D67's whole premise was that the decoder addon is the authority on
|
|
6541
|
+
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
6542
|
+
* hardware", which was false.
|
|
6543
|
+
*
|
|
6544
|
+
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
6545
|
+
* it is here and not at the call sites.
|
|
6512
6546
|
*/
|
|
6513
6547
|
function nodePin(nodeId) {
|
|
6514
|
-
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: nodeId } };
|
|
6548
|
+
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
6549
|
+
}
|
|
6550
|
+
/**
|
|
6551
|
+
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
6552
|
+
* is idempotent, so passing an already-clean id costs nothing.
|
|
6553
|
+
*/
|
|
6554
|
+
function toNodeId(idOrRunnerId) {
|
|
6555
|
+
const head = idOrRunnerId.split("/")[0];
|
|
6556
|
+
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
6515
6557
|
}
|
|
6516
6558
|
/**
|
|
6517
6559
|
* Output schema shared by the contribution + live methods.
|
|
@@ -7249,7 +7291,7 @@ object({
|
|
|
7249
7291
|
* ## This file adds no state
|
|
7250
7292
|
*
|
|
7251
7293
|
* Every switch here is a VIEW onto an authority that already existed
|
|
7252
|
-
* ([
|
|
7294
|
+
* ([D62](../../../../docs/decisions/adr-0062.md)). The whole point of the
|
|
7253
7295
|
* group is that there is exactly one place each function is turned off, and
|
|
7254
7296
|
* the group routes to it:
|
|
7255
7297
|
*
|
|
@@ -7260,6 +7302,53 @@ object({
|
|
|
7260
7302
|
* | `audio-analysis` | `deviceManager.setWrapperActive('audio-analysis')` | `AudioSubscriptionController.subscribeAudioStream` returns `null` before opening the stream |
|
|
7261
7303
|
* | `recording` | `recording.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
|
|
7262
7304
|
* | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
|
|
7305
|
+
* | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
|
|
7306
|
+
* | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
|
|
7307
|
+
* | `broker-audio` | `streamBroker.setDeviceAudioMute` → `DeviceOverride.audioMuted` | `StreamBroker.setAudioMuted` drops the audio plane at the source: no `type:'audio'` packet leaves `fanOutEncoded`, no RTP reaches the restreamer, and the restreamer serves the video-only SDP |
|
|
7308
|
+
*
|
|
7309
|
+
* ## `device-audio` and `broker-audio` are two functions, not two knobs
|
|
7310
|
+
*
|
|
7311
|
+
* They look adjacent and they are not the same control ([D83](../../../../docs/decisions/adr-0083.md)):
|
|
7312
|
+
* `device-audio` writes the CAMERA, so it is hardware privacy — the microphone
|
|
7313
|
+
* genuinely stops, it survives CamStack entirely, and it costs a multi-second
|
|
7314
|
+
* encoder restart on every flip. `broker-audio` writes THIS server, so it is
|
|
7315
|
+
* instant, vendor-independent and reversible without touching the camera, and
|
|
7316
|
+
* a camera that ignores or lacks the ISAPI/Reolink control is still silenced.
|
|
7317
|
+
* D62 forbids a second switch that *disagrees* with the first; these two
|
|
7318
|
+
* cannot disagree, because neither reads the other's store — the camera holds
|
|
7319
|
+
* one, the broker holds the other, and each reports its own fact.
|
|
7320
|
+
*
|
|
7321
|
+
* ## The two switches whose authority is not on this server
|
|
7322
|
+
*
|
|
7323
|
+
* `privacy-mask` and `device-audio` write the CAMERA. That is not a loophole
|
|
7324
|
+
* in "the group stores nothing" — it is the purest form of it: the camera
|
|
7325
|
+
* holds the fact, every read is a read-through, and there is no server-side
|
|
7326
|
+
* copy that could drift. Their availability therefore cannot come from
|
|
7327
|
+
* `listBindableCapsForDeviceType` (a device-NATIVE cap carries no wrappers and
|
|
7328
|
+
* is filtered out there); it comes from the cap's own camera-probed
|
|
7329
|
+
* `privacyMask.getOptions()`, which is strictly more honest — it answers for
|
|
7330
|
+
* THIS camera rather than for the device type
|
|
7331
|
+
* ([D74](../../../../docs/decisions/adr-0074.md)).
|
|
7332
|
+
*
|
|
7333
|
+
* ## `privacy-mask` is the one row whose ON is not "the function is working"
|
|
7334
|
+
*
|
|
7335
|
+
* Every other switch means *this camera's function is doing its job*, so
|
|
7336
|
+
* `enabled: false` is a thing an operator took away. `privacy-mask` means **the
|
|
7337
|
+
* MASK is active** — `enabled: true` is video deliberately obscured. The
|
|
7338
|
+
* polarity is not a choice made here: `addon-export-hap`'s privacy `Switch`
|
|
7339
|
+
* (`builders/privacy-switch.ts`) already mirrors `patch.enabled` verbatim, and
|
|
7340
|
+
* a HomeKit toggle that disagreed with the app's toggle for the same camera is
|
|
7341
|
+
* worse than either surface not having one.
|
|
7342
|
+
*
|
|
7343
|
+
* Two consequences follow and both are load-bearing:
|
|
7344
|
+
*
|
|
7345
|
+
* - **It never counts as `switchedOff`.** `countsAsSwitchedOff` is `false` for
|
|
7346
|
+
* exactly this row. With the polarity above, every camera that has NOT drawn
|
|
7347
|
+
* a privacy mask would otherwise report `switchedOff: ['privacy-mask']` — the
|
|
7348
|
+
* normal, healthy state of most cameras rendered as an operator disablement.
|
|
7349
|
+
* - **Its cost line names BOTH directions.** `costWhenOff` is rendered
|
|
7350
|
+
* unconditionally by both clients, so for this row it has to read correctly
|
|
7351
|
+
* whichever way the switch is sitting.
|
|
7263
7352
|
*
|
|
7264
7353
|
* The wrapper-binding pair is not a new idea: `legacy-migrations.ts` already
|
|
7265
7354
|
* migrated the legacy `audioEnabled` / `pipelineEnabled` /
|
|
@@ -7278,14 +7367,18 @@ object({
|
|
|
7278
7367
|
* `CameraStatus.switchedOff`.
|
|
7279
7368
|
*/
|
|
7280
7369
|
/**
|
|
7281
|
-
* The
|
|
7282
|
-
*
|
|
7370
|
+
* The functions the operator named — five on 2026-08-05, plus the camera's own
|
|
7371
|
+
* microphone on 2026-08-07. Deliberately NOT one id per pipeline step: face
|
|
7372
|
+
* recognition and plate/LPR are per-step toggles on
|
|
7283
7373
|
* `pipelineOrchestrator.setCameraStepToggle` and belong in the pipeline
|
|
7284
|
-
* editor, not in a
|
|
7374
|
+
* editor, not in a safety group.
|
|
7285
7375
|
*/
|
|
7286
7376
|
var CameraSwitchIdSchema = _enum([
|
|
7287
7377
|
"stream-broker",
|
|
7288
7378
|
"object-detection",
|
|
7379
|
+
"privacy-mask",
|
|
7380
|
+
"device-audio",
|
|
7381
|
+
"broker-audio",
|
|
7289
7382
|
"audio-analysis",
|
|
7290
7383
|
"recording",
|
|
7291
7384
|
"notifications"
|
|
@@ -7303,14 +7396,27 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7303
7396
|
capName: string()
|
|
7304
7397
|
}),
|
|
7305
7398
|
object({ kind: literal("recording-config") }),
|
|
7306
|
-
object({ kind: literal("notification-mute") })
|
|
7399
|
+
object({ kind: literal("notification-mute") }),
|
|
7400
|
+
object({
|
|
7401
|
+
kind: literal("camera-audio"),
|
|
7402
|
+
capName: string()
|
|
7403
|
+
}),
|
|
7404
|
+
object({
|
|
7405
|
+
kind: literal("camera-mask"),
|
|
7406
|
+
capName: string()
|
|
7407
|
+
}),
|
|
7408
|
+
object({ kind: literal("broker-audio-mute") })
|
|
7307
7409
|
]);
|
|
7308
7410
|
/**
|
|
7309
7411
|
* Why a switch is not offered for this camera. Rendered instead of the
|
|
7310
7412
|
* control, never as a dead control — an absent function and a broken one must
|
|
7311
7413
|
* not look the same.
|
|
7312
7414
|
*/
|
|
7313
|
-
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7415
|
+
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7416
|
+
"no-provider",
|
|
7417
|
+
"source-unreachable",
|
|
7418
|
+
"not-configured"
|
|
7419
|
+
]);
|
|
7314
7420
|
/**
|
|
7315
7421
|
* One switch, resolved for one camera.
|
|
7316
7422
|
*
|
|
@@ -9303,6 +9409,26 @@ var EgressTranscodeRequestSchema = object({
|
|
|
9303
9409
|
"h264_mp4toannexb",
|
|
9304
9410
|
"hevc_mp4toannexb"
|
|
9305
9411
|
]).optional(),
|
|
9412
|
+
/**
|
|
9413
|
+
* Publish the transcode as a LOCAL push cam stream, instead of leaving the
|
|
9414
|
+
* consumer to dial the returned url. The broker picks the id and returns it
|
|
9415
|
+
* as `camStreamId` — a caller-supplied one would be circular, since the
|
|
9416
|
+
* sharing key is computed FROM this request.
|
|
9417
|
+
*
|
|
9418
|
+
* The url is still returned and still the contract for a transcode pinned to
|
|
9419
|
+
* another node. But dialling it locally costs an RTSP round trip that changes
|
|
9420
|
+
* the transport underneath the consumer: a dialled stream is an RTP source,
|
|
9421
|
+
* so `isRtpSource()` is true and the session takes the RTP-passthrough +
|
|
9422
|
+
* repacketizer branch. The push branch — the one the derived mechanism has
|
|
9423
|
+
* live hours on — is never reached. Measured on Alexa: broker registered, RTP
|
|
9424
|
+
* arriving, key frame arriving, black screen, on a chain healthy at every
|
|
9425
|
+
* other point.
|
|
9426
|
+
*
|
|
9427
|
+
* Same idea the transport already applies to CALLS, where `classifyCapRoute`
|
|
9428
|
+
* gives priority to `hub-in-process` so a local call never leaves the node.
|
|
9429
|
+
* This is that rule for media.
|
|
9430
|
+
*/
|
|
9431
|
+
publishLocally: boolean().optional(),
|
|
9306
9432
|
pixelFormat: _enum(["yuv420p", "nv12"]).optional(),
|
|
9307
9433
|
/**
|
|
9308
9434
|
* Operator/consumer override for decode hardware. ABSENT is the normal case
|
|
@@ -9347,7 +9473,13 @@ var EgressTranscodeSchema = object({
|
|
|
9347
9473
|
* Returned rather than assumed: a consumer that asked for hardware and got
|
|
9348
9474
|
* software needs to be able to see that without reading the broker's logs.
|
|
9349
9475
|
*/
|
|
9350
|
-
decodeHwAccel: string().nullable()
|
|
9476
|
+
decodeHwAccel: string().nullable(),
|
|
9477
|
+
/**
|
|
9478
|
+
* Set when `publishLocally` was honoured: attach to THIS instead of dialling
|
|
9479
|
+
* `url`, and the session takes the push/deframe transport rather than the
|
|
9480
|
+
* RTP-passthrough one. `null` means the consumer must dial.
|
|
9481
|
+
*/
|
|
9482
|
+
camStreamId: string().nullable()
|
|
9351
9483
|
});
|
|
9352
9484
|
method(object({
|
|
9353
9485
|
deviceId: number().int().nonnegative(),
|
|
@@ -9495,7 +9627,25 @@ method(object({
|
|
|
9495
9627
|
}), _void(), {
|
|
9496
9628
|
kind: "mutation",
|
|
9497
9629
|
auth: "admin"
|
|
9498
|
-
}), method(object({ brokerId: string() }), boolean()), object({
|
|
9630
|
+
}), method(object({ brokerId: string() }), boolean()), method(object({ deviceId: number().int() }), object({
|
|
9631
|
+
muted: boolean(),
|
|
9632
|
+
/**
|
|
9633
|
+
* How many live non-derived brokers currently hold the mute. Purely
|
|
9634
|
+
* diagnostic: `muted` is the policy and is authoritative on its own
|
|
9635
|
+
* (it applies to brokers that do not exist yet), while this says
|
|
9636
|
+
* whether anything is presently being silenced.
|
|
9637
|
+
*/
|
|
9638
|
+
appliedBrokers: number().int().nonnegative()
|
|
9639
|
+
})), method(object({
|
|
9640
|
+
deviceId: number().int(),
|
|
9641
|
+
muted: boolean()
|
|
9642
|
+
}), object({
|
|
9643
|
+
muted: boolean(),
|
|
9644
|
+
appliedBrokers: number().int().nonnegative()
|
|
9645
|
+
}), {
|
|
9646
|
+
kind: "mutation",
|
|
9647
|
+
auth: "admin"
|
|
9648
|
+
}), object({
|
|
9499
9649
|
deviceId: number().int().nonnegative(),
|
|
9500
9650
|
camStreamId: string(),
|
|
9501
9651
|
profile: CamProfileSchema
|
|
@@ -15451,6 +15601,30 @@ var TrackSourceSchema = _enum([
|
|
|
15451
15601
|
"audio"
|
|
15452
15602
|
]);
|
|
15453
15603
|
/**
|
|
15604
|
+
* Where a track sits in the RETRAIN lifecycle (D81).
|
|
15605
|
+
*
|
|
15606
|
+
* - `none` — never marked, or un-marked. Evictable.
|
|
15607
|
+
* - `staging` — the operator wants this track as training material and has not
|
|
15608
|
+
* finished with it. **This is the only state retention holds**: the track and
|
|
15609
|
+
* everything it owns (object events, crops, keyframes, CLIP vector) survive
|
|
15610
|
+
* the device's age window.
|
|
15611
|
+
* - `trained` — the retrain page has taken what it needed. The frames it chose
|
|
15612
|
+
* were COPIED into the retrain dataset at selection time, so the dataset no
|
|
15613
|
+
* longer depends on the track's media and the track becomes EVICTABLE again.
|
|
15614
|
+
* Terminal for the plain `markForTrain` toggle: returning it to `staging` is
|
|
15615
|
+
* a deliberate action of the retrain page, not a side effect of a checkbox.
|
|
15616
|
+
*
|
|
15617
|
+
* There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
|
|
15618
|
+
* the store's filter language has only positive equality and `whereIn` — no
|
|
15619
|
+
* negation, no IS NULL — so a NULL would be unselectable by ANY predicate and
|
|
15620
|
+
* would make the entire pre-column history immortal in one deploy.
|
|
15621
|
+
*/
|
|
15622
|
+
var RetrainStatusSchema = _enum([
|
|
15623
|
+
"none",
|
|
15624
|
+
"staging",
|
|
15625
|
+
"trained"
|
|
15626
|
+
]);
|
|
15627
|
+
/**
|
|
15454
15628
|
* Per-track OPERATOR flags — set by hand from the admin UI or the viewer, never
|
|
15455
15629
|
* by the pipeline. Spread into `TrackSchema` and `KeyEventSchema` from one place
|
|
15456
15630
|
* so the two surfaces cannot drift.
|
|
@@ -15460,18 +15634,31 @@ var TrackSourceSchema = _enum([
|
|
|
15460
15634
|
* columns existed read as absent, and a consumer that needs a boolean should say
|
|
15461
15635
|
* `flag === true`, not `flag !== false`.
|
|
15462
15636
|
*
|
|
15463
|
-
*
|
|
15464
|
-
*
|
|
15465
|
-
*
|
|
15466
|
-
* `
|
|
15637
|
+
* `markForTrain` is the WIRE FACE of {@link RetrainStatusSchema}, not a column:
|
|
15638
|
+
* it is exactly `retrainStatus === 'staging'`, in both directions. Writing
|
|
15639
|
+
* `true` moves `none → staging`, writing `false` moves `staging → none`, and a
|
|
15640
|
+
* `trained` track reports `false` while refusing both writes. The boolean is
|
|
15641
|
+
* kept because three surfaces drive a toggle off it; anything that needs to tell
|
|
15642
|
+
* "never marked" from "already trained" must read `retrainStatus`.
|
|
15643
|
+
*
|
|
15644
|
+
* `debug` does NOT pin; it is attention, not durability.
|
|
15467
15645
|
*/
|
|
15468
15646
|
var TrackFlagFields = {
|
|
15469
|
-
/** Operator marked this track as training material.
|
|
15647
|
+
/** Operator marked this track as training material — i.e. `retrainStatus` is
|
|
15648
|
+
* `'staging'`. */
|
|
15470
15649
|
markForTrain: boolean().optional(),
|
|
15471
15650
|
/** Operator marked this track for diagnostic attention. */
|
|
15472
15651
|
debug: boolean().optional()
|
|
15473
15652
|
};
|
|
15474
15653
|
/**
|
|
15654
|
+
* The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
|
|
15655
|
+
* Deliberately NOT part of {@link TrackFlagFields}: that group also builds the
|
|
15656
|
+
* write patch, and the status is not something the toggle sets — it is what the
|
|
15657
|
+
* toggle's boolean is derived from. Absent on an in-RAM track never touched;
|
|
15658
|
+
* always present on a persisted row (the column default materialises `'none'`).
|
|
15659
|
+
*/
|
|
15660
|
+
var TrackRetrainFields = { retrainStatus: RetrainStatusSchema.optional() };
|
|
15661
|
+
/**
|
|
15475
15662
|
* The write half: a PARTIAL patch. An omitted key is left untouched, so setting
|
|
15476
15663
|
* one flag can never clear the other — the toggles are independent and are
|
|
15477
15664
|
* driven from three surfaces that do not know about each other.
|
|
@@ -15485,7 +15672,32 @@ var TrackFlagsPatchSchema = object(TrackFlagFields);
|
|
|
15485
15672
|
var TrackFlagsSchema = object({
|
|
15486
15673
|
trackId: string(),
|
|
15487
15674
|
markForTrain: boolean(),
|
|
15488
|
-
debug: boolean()
|
|
15675
|
+
debug: boolean(),
|
|
15676
|
+
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
15677
|
+
* a track row) because this shape is only ever produced by the write body,
|
|
15678
|
+
* which always knows it — and a surface that has just written needs to render
|
|
15679
|
+
* `trained` without a re-fetch. */
|
|
15680
|
+
retrainStatus: RetrainStatusSchema
|
|
15681
|
+
});
|
|
15682
|
+
/** Per-camera slice of a training-export estimate. */
|
|
15683
|
+
var TrainingExportDeviceTotalsSchema = object({
|
|
15684
|
+
deviceId: number(),
|
|
15685
|
+
tracks: number().int(),
|
|
15686
|
+
files: number().int(),
|
|
15687
|
+
bytes: number().int()
|
|
15688
|
+
});
|
|
15689
|
+
/**
|
|
15690
|
+
* What a training export WOULD contain. Computed from media index rows only —
|
|
15691
|
+
* no blob is read to produce this.
|
|
15692
|
+
*/
|
|
15693
|
+
var TrainingExportSummarySchema = object({
|
|
15694
|
+
generatedAt: number(),
|
|
15695
|
+
trackCount: number().int(),
|
|
15696
|
+
fileCount: number().int(),
|
|
15697
|
+
byteCount: number().int(),
|
|
15698
|
+
/** More marked tracks exist than a single pass carries. */
|
|
15699
|
+
truncated: boolean(),
|
|
15700
|
+
devices: array(TrainingExportDeviceTotalsSchema).readonly()
|
|
15489
15701
|
});
|
|
15490
15702
|
var TrackSchema = object({
|
|
15491
15703
|
trackId: string(),
|
|
@@ -15530,7 +15742,8 @@ var TrackSchema = object({
|
|
|
15530
15742
|
* Populated from the persisted envelope columns on historical reads;
|
|
15531
15743
|
* absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
|
|
15532
15744
|
envelope: TrackEnvelopeSchema.optional(),
|
|
15533
|
-
...TrackFlagFields
|
|
15745
|
+
...TrackFlagFields,
|
|
15746
|
+
...TrackRetrainFields
|
|
15534
15747
|
});
|
|
15535
15748
|
var BaseEventFields = {
|
|
15536
15749
|
id: string(),
|
|
@@ -15744,7 +15957,8 @@ var KeyEventSchema = object({
|
|
|
15744
15957
|
bestEventId: string(),
|
|
15745
15958
|
/** Track lifetime in ms (lastSeen - firstSeen). */
|
|
15746
15959
|
windowMs: number().optional(),
|
|
15747
|
-
...TrackFlagFields
|
|
15960
|
+
...TrackFlagFields,
|
|
15961
|
+
...TrackRetrainFields
|
|
15748
15962
|
});
|
|
15749
15963
|
object({
|
|
15750
15964
|
trackId: string(),
|
|
@@ -16106,11 +16320,29 @@ var pipelineAnalyticsCapability = {
|
|
|
16106
16320
|
*
|
|
16107
16321
|
* `auth: 'protected'` (the default), NOT `admin`: the viewer is an
|
|
16108
16322
|
* authenticated non-admin surface and two of the three call sites are
|
|
16109
|
-
* there.
|
|
16110
|
-
*
|
|
16323
|
+
* there. The note that used to sit here said to revisit this the day a flag
|
|
16324
|
+
* gained an effect that costs storage, and D81 is that day — `markForTrain`
|
|
16325
|
+
* now pins. It STAYS protected, and the reason is that the alternative
|
|
16326
|
+
* makes the feature pointless: marking a track is something you do while
|
|
16327
|
+
* looking at it, on the surface you were already looking at it on, and that
|
|
16328
|
+
* surface is the viewer. What the storage cost gets instead is a BOUND — a
|
|
16329
|
+
* per-device pin budget enforced in the body, refusing a new pin past the
|
|
16330
|
+
* limit while always allowing un-marking. `deleteTracks` next door is still
|
|
16331
|
+
* admin, because destroying evidence and preserving it are not symmetric.
|
|
16332
|
+
*
|
|
16333
|
+
* `markForTrain` writes the retrain LIFECYCLE, not a boolean column: `true`
|
|
16334
|
+
* is `none → staging`, `false` is `staging → none`. A track already
|
|
16335
|
+
* `trained` refuses BOTH — its frames are copies inside the retrain dataset
|
|
16336
|
+
* and re-staging it from a generic toggle is how the same material gets
|
|
16337
|
+
* annotated twice under two ground truths. Returning a trained track to
|
|
16338
|
+
* staging is a deliberate action of the retrain page, which is also the only
|
|
16339
|
+
* thing that produces `trained` in the first place.
|
|
16111
16340
|
*
|
|
16112
|
-
* Returns the RESOLVED state of both flags (absent → `false`)
|
|
16113
|
-
*
|
|
16341
|
+
* Returns the RESOLVED state of both flags (absent → `false`) plus the
|
|
16342
|
+
* `retrainStatus` they were derived from, so a caller can drive its toggle
|
|
16343
|
+
* — and render a `trained` badge — without a re-fetch. Rejects an unknown
|
|
16344
|
+
* track, a new staging mark on a device already holding its full budget, and
|
|
16345
|
+
* any `markForTrain` write against a trained track.
|
|
16114
16346
|
*/
|
|
16115
16347
|
setTrackFlags: method(object({
|
|
16116
16348
|
/** Log/audit scope only — the trackId is globally unique on its own. */
|
|
@@ -16174,6 +16406,42 @@ var pipelineAnalyticsCapability = {
|
|
|
16174
16406
|
kind: "query",
|
|
16175
16407
|
auth: "admin"
|
|
16176
16408
|
}),
|
|
16409
|
+
/**
|
|
16410
|
+
* The CHEAP QUESTION, asked before any media moves: how big is the dataset
|
|
16411
|
+
* the marked (`markForTrain`) tracks would produce?
|
|
16412
|
+
*
|
|
16413
|
+
* Answered from media INDEX rows only — key, kind, size, timestamp — so it
|
|
16414
|
+
* costs ~2 KB of reads per track and no blob reads at all. The measured harm
|
|
16415
|
+
* behind D56 was a bulk pass that read and base64'd every blob a track owned
|
|
16416
|
+
* before deciding anything, taking hub-main to 82 s busy out of 120; an
|
|
16417
|
+
* export is that same I/O shape, so it inherits the same discipline: know
|
|
16418
|
+
* the size, then decide.
|
|
16419
|
+
*
|
|
16420
|
+
* `truncated` reports that more marked tracks exist than one pass carries.
|
|
16421
|
+
* Empty `deviceIds` ⇒ every device that has marked tracks.
|
|
16422
|
+
*/
|
|
16423
|
+
getTrainingExportSummary: method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
|
|
16424
|
+
kind: "query",
|
|
16425
|
+
auth: "admin"
|
|
16426
|
+
}),
|
|
16427
|
+
/**
|
|
16428
|
+
* Where to download the dataset archive.
|
|
16429
|
+
*
|
|
16430
|
+
* The BYTES do not come back through this cap — they come from the returned
|
|
16431
|
+
* data-plane URL, which streams a tar built entry by entry. A multi-gigabyte
|
|
16432
|
+
* archive base64'd through a unary RPC envelope would be held whole in
|
|
16433
|
+
* memory twice on a hub this repo has already OOM'd once (D9/D18 are the
|
|
16434
|
+
* same lesson about frames). `getDownloadUrl` on `recordingExport` is the
|
|
16435
|
+
* precedent, and this follows it deliberately.
|
|
16436
|
+
*
|
|
16437
|
+
* The archive contains a `manifest.json` FIRST, then the stored media
|
|
16438
|
+
* VERBATIM under `tracks/<deviceId>/<trackId>/…`. No crop is derived and no
|
|
16439
|
+
* model is run: a training set's pixels must be the pixels the pipeline saw.
|
|
16440
|
+
*/
|
|
16441
|
+
getTrainingExportUrl: method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
|
|
16442
|
+
kind: "query",
|
|
16443
|
+
auth: "admin"
|
|
16444
|
+
}),
|
|
16177
16445
|
getEventMedia: method(object({
|
|
16178
16446
|
eventId: string(),
|
|
16179
16447
|
kind: MediaFileKindEnum.optional()
|
|
@@ -18106,9 +18374,15 @@ DeviceType.Camera, method(object({
|
|
|
18106
18374
|
* Bypass the cache freshness check and fetch directly from the
|
|
18107
18375
|
* native (or stream-broker fallback). Triggered by the UI's
|
|
18108
18376
|
* "refresh" button so an operator can force a fresh frame
|
|
18109
|
-
* even when the cache is well within
|
|
18110
|
-
*
|
|
18111
|
-
*
|
|
18377
|
+
* even when the cache is well within the device's
|
|
18378
|
+
* `snapshotMaxAgeS` window.
|
|
18379
|
+
*
|
|
18380
|
+
* **`force` is an OPERATOR signal, not a freshness preference.** On a
|
|
18381
|
+
* battery camera it is the one thing that walks past the wrapper's
|
|
18382
|
+
* sleep gate and wakes the camera, so a background caller — a poller,
|
|
18383
|
+
* an event handler, a thumbnail — must NEVER set it. Every such caller
|
|
18384
|
+
* gets the cached frame, which on a sleeping battery camera is the
|
|
18385
|
+
* correct answer: stale but honest beats woken.
|
|
18112
18386
|
*/
|
|
18113
18387
|
force: boolean().optional()
|
|
18114
18388
|
}), SnapshotImageSchema.nullable()), method(object({ deviceId: number() }), _void(), {
|
|
@@ -23000,6 +23274,173 @@ DeviceType.Camera, method(object({
|
|
|
23000
23274
|
status: OsdStatusSchema
|
|
23001
23275
|
});
|
|
23002
23276
|
/**
|
|
23277
|
+
* `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
|
|
23278
|
+
*
|
|
23279
|
+
* The `osd` cap is the firmware contract: it probes a camera's overlay
|
|
23280
|
+
* SLOTS and writes literal text into one. It has no idea WHERE that text
|
|
23281
|
+
* comes from, and it must not — a driver that grew a "show the temperature
|
|
23282
|
+
* here" feature would grow it once per vendor.
|
|
23283
|
+
*
|
|
23284
|
+
* This cap owns the other half: a per-(camera, slot) BINDING that says
|
|
23285
|
+
* which value feeds the slot, how it is formatted, and under which
|
|
23286
|
+
* conditions it is shown at all. One addon renders every binding on every
|
|
23287
|
+
* camera, so a new source costs zero driver code.
|
|
23288
|
+
*
|
|
23289
|
+
* Three deliberate choices, each with a rejected alternative:
|
|
23290
|
+
*
|
|
23291
|
+
* 1. A source is `(capName, valuePath)` over the kernel's device
|
|
23292
|
+
* runtime-state mirror — NOT a closed enum of source kinds. Every
|
|
23293
|
+
* cap-keyed slice a device publishes is bindable the day the cap
|
|
23294
|
+
* ships. The rejected alternative (one enum member per source, with
|
|
23295
|
+
* a resolver branch each) is what makes "add the humidity too" a
|
|
23296
|
+
* code change.
|
|
23297
|
+
* 2. The display gate reuses `NcConditionsSchema` verbatim — the
|
|
23298
|
+
* notification centre's condition vocabulary — rather than a parallel
|
|
23299
|
+
* model. An operator who has learned one condition editor has learned
|
|
23300
|
+
* both.
|
|
23301
|
+
* 3. Because the renderer's facts are device STATE and not a detection
|
|
23302
|
+
* record, only a SUBSET of that vocabulary can be answered here.
|
|
23303
|
+
* `setSlotBinding` REJECTS the rest at write time (see
|
|
23304
|
+
* `getConditionSupport`). It does not accept-then-fail-closed: a
|
|
23305
|
+
* condition that can never be true renders a permanently blank
|
|
23306
|
+
* overlay, and a blank overlay looks exactly like a broken camera.
|
|
23307
|
+
*/
|
|
23308
|
+
/** Where a slot's value comes from. */
|
|
23309
|
+
var OsdSourceSchema = discriminatedUnion("kind", [
|
|
23310
|
+
object({
|
|
23311
|
+
kind: literal("static"),
|
|
23312
|
+
text: string().max(64)
|
|
23313
|
+
}),
|
|
23314
|
+
object({
|
|
23315
|
+
kind: literal("clock"),
|
|
23316
|
+
/** Token pattern: `YYYY MM DD HH mm ss`. Everything else is literal. */
|
|
23317
|
+
pattern: string().min(1).max(32).default("HH:mm"),
|
|
23318
|
+
/** IANA zone. Omitted = the server's zone. */
|
|
23319
|
+
timezone: string().min(1).max(64).optional()
|
|
23320
|
+
}),
|
|
23321
|
+
object({
|
|
23322
|
+
kind: literal("device-state"),
|
|
23323
|
+
deviceId: number().int().optional(),
|
|
23324
|
+
capName: string().min(1).max(64),
|
|
23325
|
+
/** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
|
|
23326
|
+
valuePath: string().min(1).max(64)
|
|
23327
|
+
})
|
|
23328
|
+
]);
|
|
23329
|
+
var OsdSlotBindingSchema = object({
|
|
23330
|
+
/** Off = the manager stops driving this slot. It does NOT clear it. */
|
|
23331
|
+
enabled: boolean().default(true),
|
|
23332
|
+
source: OsdSourceSchema,
|
|
23333
|
+
/** `${value}` and `${unit}` are substituted; every occurrence. */
|
|
23334
|
+
template: string().max(96).default("${value}"),
|
|
23335
|
+
/** Truncate with an ellipsis past this length. Absent = no limit. */
|
|
23336
|
+
maxCharacters: number().int().min(4).max(64).optional(),
|
|
23337
|
+
/**
|
|
23338
|
+
* Decimal places for a numeric value. `0` yields an integer — the
|
|
23339
|
+
* documented workaround for firmwares that reject `.` in overlay text.
|
|
23340
|
+
*/
|
|
23341
|
+
maxDecimals: number().int().min(0).max(4).default(1),
|
|
23342
|
+
/** Appended via `${unit}`. The state mirror does not carry units. */
|
|
23343
|
+
unitLabel: string().max(8).optional(),
|
|
23344
|
+
/** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
|
|
23345
|
+
valueMap: record(string(), string()).optional(),
|
|
23346
|
+
/** Time windows in which the slot is shown. Absent = always. */
|
|
23347
|
+
schedule: NcScheduleSchema.optional(),
|
|
23348
|
+
/**
|
|
23349
|
+
* Display gate, in the notification centre's condition vocabulary.
|
|
23350
|
+
* Only the keys reported by `getConditionSupport` are accepted.
|
|
23351
|
+
*/
|
|
23352
|
+
conditions: NcConditionsSchema.optional(),
|
|
23353
|
+
/** Rendered when the gate is closed or the value unreadable. Empty = hide. */
|
|
23354
|
+
fallbackText: string().max(64).default("")
|
|
23355
|
+
});
|
|
23356
|
+
/** One camera slot, as the operator sees it: firmware truth + our binding. */
|
|
23357
|
+
var OsdSlotViewSchema = object({
|
|
23358
|
+
slotId: string(),
|
|
23359
|
+
kind: OsdOverlayKindEnum,
|
|
23360
|
+
/** Firmware refuses text edits (a timestamp, the channel name). */
|
|
23361
|
+
readOnly: boolean(),
|
|
23362
|
+
cameraEnabled: boolean(),
|
|
23363
|
+
cameraText: string().optional(),
|
|
23364
|
+
binding: OsdSlotBindingSchema.nullable()
|
|
23365
|
+
});
|
|
23366
|
+
/**
|
|
23367
|
+
* What happened to one slot on one render pass. `unchanged` exists so the
|
|
23368
|
+
* operator can tell "we are driving this and the value is steady" from
|
|
23369
|
+
* "we never got there" — and so the loop can prove it is not rewriting
|
|
23370
|
+
* identical text to the camera every tick.
|
|
23371
|
+
*/
|
|
23372
|
+
var OsdRenderOutcomeEnum = _enum([
|
|
23373
|
+
"written",
|
|
23374
|
+
"unchanged",
|
|
23375
|
+
"gated",
|
|
23376
|
+
"unreadable",
|
|
23377
|
+
"disabled",
|
|
23378
|
+
"unbound",
|
|
23379
|
+
"failed"
|
|
23380
|
+
]);
|
|
23381
|
+
var OsdRenderResultSchema = object({
|
|
23382
|
+
slotId: string(),
|
|
23383
|
+
outcome: OsdRenderOutcomeEnum,
|
|
23384
|
+
/** The text the slot should carry. Empty = the slot is switched off. */
|
|
23385
|
+
text: string(),
|
|
23386
|
+
/** Why, whenever the outcome is not a plain write. Never silent. */
|
|
23387
|
+
reason: string().optional()
|
|
23388
|
+
});
|
|
23389
|
+
var OsdSourceValueTypeEnum = _enum([
|
|
23390
|
+
"number",
|
|
23391
|
+
"boolean",
|
|
23392
|
+
"string",
|
|
23393
|
+
"enum"
|
|
23394
|
+
]);
|
|
23395
|
+
/**
|
|
23396
|
+
* One bindable value, derived from a cap's `runtimeState` schema — never
|
|
23397
|
+
* hand-listed. The editor renders from this, so a cap that ships a new
|
|
23398
|
+
* state field becomes bindable with no UI change.
|
|
23399
|
+
*/
|
|
23400
|
+
var OsdSourceOptionSchema = object({
|
|
23401
|
+
deviceId: number().int(),
|
|
23402
|
+
deviceName: string(),
|
|
23403
|
+
capName: string(),
|
|
23404
|
+
valuePath: string(),
|
|
23405
|
+
label: string(),
|
|
23406
|
+
valueType: OsdSourceValueTypeEnum,
|
|
23407
|
+
/** Present for `enum`; the editor offers these as `valueMap` keys. */
|
|
23408
|
+
enumValues: array(string()).readonly().optional()
|
|
23409
|
+
});
|
|
23410
|
+
method(object({ deviceId: number().int() }), object({
|
|
23411
|
+
supported: boolean(),
|
|
23412
|
+
slots: array(OsdSlotViewSchema)
|
|
23413
|
+
}), { auth: "admin" }), method(object({ deviceId: number().int() }), object({ sources: array(OsdSourceOptionSchema) }), { auth: "admin" }), method(object({}), object({
|
|
23414
|
+
supported: array(string()),
|
|
23415
|
+
catalog: array(NcConditionDescriptorSchema)
|
|
23416
|
+
}), { auth: "admin" }), method(object({
|
|
23417
|
+
deviceId: number().int(),
|
|
23418
|
+
slotId: string().min(1),
|
|
23419
|
+
binding: OsdSlotBindingSchema
|
|
23420
|
+
}), object({
|
|
23421
|
+
slot: OsdSlotViewSchema,
|
|
23422
|
+
render: OsdRenderResultSchema
|
|
23423
|
+
}), {
|
|
23424
|
+
kind: "mutation",
|
|
23425
|
+
auth: "admin"
|
|
23426
|
+
}), method(object({
|
|
23427
|
+
deviceId: number().int(),
|
|
23428
|
+
slotId: string().min(1)
|
|
23429
|
+
}), object({ success: literal(true) }), {
|
|
23430
|
+
kind: "mutation",
|
|
23431
|
+
auth: "admin"
|
|
23432
|
+
}), method(object({
|
|
23433
|
+
deviceId: number().int(),
|
|
23434
|
+
slotId: string().min(1),
|
|
23435
|
+
binding: OsdSlotBindingSchema.optional()
|
|
23436
|
+
}), OsdRenderResultSchema, {
|
|
23437
|
+
kind: "mutation",
|
|
23438
|
+
auth: "admin"
|
|
23439
|
+
}), method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
|
|
23440
|
+
kind: "mutation",
|
|
23441
|
+
auth: "admin"
|
|
23442
|
+
});
|
|
23443
|
+
/**
|
|
23003
23444
|
* Feeder connectivity / power status — mirrors the HA petkit device-status
|
|
23004
23445
|
* enum: `normal` (online, mains), `offline` (not reaching PetKit cloud),
|
|
23005
23446
|
* `on_batteries` (running on battery backup). `null` until first reported.
|
|
@@ -23590,12 +24031,30 @@ var pressureSensorCapability = {
|
|
|
23590
24031
|
runtimeState: PressureSensorStatusSchema
|
|
23591
24032
|
};
|
|
23592
24033
|
/**
|
|
23593
|
-
*
|
|
23594
|
-
*
|
|
23595
|
-
*
|
|
23596
|
-
*
|
|
23597
|
-
*
|
|
23598
|
-
*
|
|
24034
|
+
* PRIVACY — what the camera deliberately does not capture. Two planes:
|
|
24035
|
+
*
|
|
24036
|
+
* - **video**: up to `maxRegions` SHAPES the camera blanks out (NOT a cell
|
|
24037
|
+
* grid). Reolink `<shelterList>` zones are rectangles; Hikvision ISAPI
|
|
24038
|
+
* `<RegionCoordinatesList>` zones are free polygons (this camera: exactly
|
|
24039
|
+
* 4 vertices, not necessarily axis-aligned). The cap composes the shared
|
|
24040
|
+
* rect|polygon subset of the MaskShape vocabulary. All coords are
|
|
24041
|
+
* normalized 0..1 (top-left origin).
|
|
24042
|
+
* - **audio**: the camera's microphone. `setAudioEnabled(false)` stops the
|
|
24043
|
+
* camera encoding an audio track at all, so EVERY consumer — live view,
|
|
24044
|
+
* recording, the audio analyzer, an export — sees silent video. There is
|
|
24045
|
+
* no server-side copy of this fact; the camera is the store and every read
|
|
24046
|
+
* is a read-through, which is why a switch over it cannot drift
|
|
24047
|
+
* ([D62](../../../../docs/decisions/adr-0062.md)).
|
|
24048
|
+
*
|
|
24049
|
+
* Both belong here for one reason: they are the two things an operator turns
|
|
24050
|
+
* off when the answer to "what is this camera allowed to record" changes, and
|
|
24051
|
+
* both are applied ON the device, before anything leaves it.
|
|
24052
|
+
*
|
|
24053
|
+
* **The audio flag has exactly one writer.** `stream-params` used to carry a
|
|
24054
|
+
* per-profile `audio` in its patch schema — reachable from no UI and honoured
|
|
24055
|
+
* by one provider — and it was removed when this landed. A second writer onto
|
|
24056
|
+
* one device register is the shape of every knob this repo has shipped that
|
|
24057
|
+
* disagreed with the one the reader read.
|
|
23599
24058
|
*/
|
|
23600
24059
|
/** A privacy-mask region's geometry — rectangle or free polygon. */
|
|
23601
24060
|
var PrivacyMaskShapeSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
|
|
@@ -23607,21 +24066,45 @@ var PrivacyMaskRegionSchema = object({
|
|
|
23607
24066
|
enabled: boolean(),
|
|
23608
24067
|
shape: PrivacyMaskShapeSchema
|
|
23609
24068
|
});
|
|
23610
|
-
/** Current on-camera privacy
|
|
24069
|
+
/** Current on-camera privacy state — mask master enable + zones + microphone. */
|
|
23611
24070
|
var PrivacyMaskStatusSchema = object({
|
|
23612
24071
|
enabled: boolean(),
|
|
23613
24072
|
/** Active zones (normalized 0..1). Length ≤ maxRegions. */
|
|
23614
24073
|
regions: array(PrivacyMaskRegionSchema),
|
|
24074
|
+
/**
|
|
24075
|
+
* Is the camera capturing sound right now? Read from the camera, never from
|
|
24076
|
+
* a server-side mirror.
|
|
24077
|
+
*
|
|
24078
|
+
* `null` means "no answer" — either this camera exposes no controllable
|
|
24079
|
+
* microphone (`getOptions().supportsAudioMute === false`) or the read
|
|
24080
|
+
* failed. A consumer must render `null` as UNKNOWN and never as `false`:
|
|
24081
|
+
* "the microphone is off" and "we could not ask" look identical to an
|
|
24082
|
+
* operator only until one of them is wrong.
|
|
24083
|
+
*
|
|
24084
|
+
* On a camera whose profiles carry the flag independently (Reolink writes
|
|
24085
|
+
* it per stream), `true` means AT LEAST ONE profile still carries audio —
|
|
24086
|
+
* privacy is only satisfied when every one of them is silent.
|
|
24087
|
+
*/
|
|
24088
|
+
audioEnabled: boolean().nullable(),
|
|
23615
24089
|
lastFetchedAt: number()
|
|
23616
24090
|
});
|
|
23617
|
-
/** Per-camera availability. */
|
|
24091
|
+
/** Per-camera availability. Probed, never assumed from the model name. */
|
|
23618
24092
|
var PrivacyMaskOptionsSchema = object({
|
|
23619
24093
|
/** Maximum number of supported zones. */
|
|
23620
24094
|
maxRegions: number(),
|
|
23621
24095
|
/** Shape kinds this camera accepts — Reolink: ['rect']; Hikvision: ['rect','polygon']. */
|
|
23622
24096
|
supportedShapes: array(MaskShapeKindSchema),
|
|
23623
24097
|
/** Polygon vertex bounds when 'polygon' is supported (Hikvision: {min:4,max:4}). */
|
|
23624
|
-
polygonVertices: MaskPolygonVerticesSchema.optional()
|
|
24098
|
+
polygonVertices: MaskPolygonVerticesSchema.optional(),
|
|
24099
|
+
/**
|
|
24100
|
+
* Does this camera expose a microphone switch we can actually write?
|
|
24101
|
+
*
|
|
24102
|
+
* Camera-probed: `true` only when the firmware answered with an audio flag
|
|
24103
|
+
* we know how to patch. A camera that never answered is `false` — a control
|
|
24104
|
+
* the operator can press that changes nothing is worse than no control, and
|
|
24105
|
+
* the switch group renders "not available" instead.
|
|
24106
|
+
*/
|
|
24107
|
+
supportsAudioMute: boolean()
|
|
23625
24108
|
});
|
|
23626
24109
|
/** Partial change — every field optional. */
|
|
23627
24110
|
var PrivacyMaskPatchSchema = object({
|
|
@@ -23648,6 +24131,27 @@ var privacyMaskCapability = {
|
|
|
23648
24131
|
}), _void(), {
|
|
23649
24132
|
kind: "mutation",
|
|
23650
24133
|
auth: "admin"
|
|
24134
|
+
}),
|
|
24135
|
+
/**
|
|
24136
|
+
* Turn the camera's microphone on or off, at the camera.
|
|
24137
|
+
*
|
|
24138
|
+
* Deliberately its OWN mutation rather than a field on
|
|
24139
|
+
* {@link PrivacyMaskPatchSchema}: `patch.enabled` already means "the video
|
|
24140
|
+
* mask master switch", and overloading it would make one boolean mean two
|
|
24141
|
+
* unrelated things on the same call. It is also the only method here whose
|
|
24142
|
+
* write leaves the device in a state a later `getStatus` reads back
|
|
24143
|
+
* verbatim, which is what makes it safe as a switch authority.
|
|
24144
|
+
*
|
|
24145
|
+
* A camera whose `getOptions().supportsAudioMute` is false must REJECT
|
|
24146
|
+
* this rather than silently accept it — a write nothing applies is exactly
|
|
24147
|
+
* what the switch group exists to remove.
|
|
24148
|
+
*/
|
|
24149
|
+
setAudioEnabled: method(object({
|
|
24150
|
+
deviceId: number(),
|
|
24151
|
+
enabled: boolean()
|
|
24152
|
+
}), _void(), {
|
|
24153
|
+
kind: "mutation",
|
|
24154
|
+
auth: "admin"
|
|
23651
24155
|
})
|
|
23652
24156
|
},
|
|
23653
24157
|
status: {
|
|
@@ -23930,6 +24434,21 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
|
23930
24434
|
})]);
|
|
23931
24435
|
/** Raw bytes of one finalized footage segment (read off disk on the recording node). */
|
|
23932
24436
|
var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
|
|
24437
|
+
/**
|
|
24438
|
+
* One GOP of a finalized segment, cut by byte range through the segment's own
|
|
24439
|
+
* `mfra` (D31 on the D42 feeder path). `data` is the `ftyp`+`moov` head plus
|
|
24440
|
+
* the single `moof`+`mdat` covering the requested instant — standalone-
|
|
24441
|
+
* demuxable, never the whole file. When the segment's index cannot be parsed
|
|
24442
|
+
* the provider degrades INSIDE the mechanism to the whole segment (still one
|
|
24443
|
+
* `data`, `gopStartMs` = the segment start) — a worse read, not another path.
|
|
24444
|
+
*/
|
|
24445
|
+
var ReadGopBytesResultSchema = object({
|
|
24446
|
+
data: _instanceof(Uint8Array),
|
|
24447
|
+
/** Absolute epoch ms of the returned fragment's first sample. */
|
|
24448
|
+
gopStartMs: number(),
|
|
24449
|
+
/** Media ms the returned fragment covers. */
|
|
24450
|
+
gopDurMs: number()
|
|
24451
|
+
});
|
|
23933
24452
|
method(object({
|
|
23934
24453
|
deviceId: number(),
|
|
23935
24454
|
fromMs: number(),
|
|
@@ -23972,6 +24491,14 @@ method(object({
|
|
|
23972
24491
|
}), ReadSegmentBytesResultSchema, {
|
|
23973
24492
|
kind: "query",
|
|
23974
24493
|
auth: "admin"
|
|
24494
|
+
}), method(object({
|
|
24495
|
+
deviceId: number(),
|
|
24496
|
+
profile: string(),
|
|
24497
|
+
startMs: number(),
|
|
24498
|
+
epochMs: number()
|
|
24499
|
+
}), ReadGopBytesResultSchema, {
|
|
24500
|
+
kind: "query",
|
|
24501
|
+
auth: "admin"
|
|
23975
24502
|
}), method(object({
|
|
23976
24503
|
deviceId: number(),
|
|
23977
24504
|
config: RecordingConfigSchema
|
|
@@ -24531,6 +25058,16 @@ var StreamProfileConfigSchema = object({
|
|
|
24531
25058
|
"baseline"
|
|
24532
25059
|
]).optional(),
|
|
24533
25060
|
gop: number().optional(),
|
|
25061
|
+
/**
|
|
25062
|
+
* Whether THIS profile currently carries an audio track. READ-ONLY here.
|
|
25063
|
+
*
|
|
25064
|
+
* There is no matching field on {@link StreamProfilePatchSchema}: the
|
|
25065
|
+
* camera's microphone is owned by `privacy-mask` (`setAudioEnabled`), which
|
|
25066
|
+
* writes every profile at once so "audio off" means silent everywhere. A
|
|
25067
|
+
* per-profile writer beside it would let a camera be half-muted and would be
|
|
25068
|
+
* a second knob onto one device register — the failure D62 exists to
|
|
25069
|
+
* prevent. Absent when the firmware does not report the flag.
|
|
25070
|
+
*/
|
|
24534
25071
|
audio: boolean().optional()
|
|
24535
25072
|
});
|
|
24536
25073
|
var StreamParamsStatusSchema = object({
|
|
@@ -24571,7 +25108,13 @@ var StreamParamsOptionsSchema = object({
|
|
|
24571
25108
|
ext: StreamProfileOptionsSchema.optional()
|
|
24572
25109
|
});
|
|
24573
25110
|
/** A partial change to one profile — every field optional; a provider
|
|
24574
|
-
* ignores fields it doesn't support.
|
|
25111
|
+
* ignores fields it doesn't support.
|
|
25112
|
+
*
|
|
25113
|
+
* There is deliberately NO `audio` here. It existed until 2026-08-07,
|
|
25114
|
+
* reachable from no form and honoured by exactly one provider, while the
|
|
25115
|
+
* camera's microphone is a whole-device fact. It now has one writer,
|
|
25116
|
+
* `privacyMask.setAudioEnabled`, which writes every profile — see
|
|
25117
|
+
* `privacy-mask.cap.ts`. */
|
|
24575
25118
|
var StreamProfilePatchSchema = object({
|
|
24576
25119
|
width: number().optional(),
|
|
24577
25120
|
height: number().optional(),
|
|
@@ -24584,8 +25127,7 @@ var StreamProfilePatchSchema = object({
|
|
|
24584
25127
|
"main",
|
|
24585
25128
|
"baseline"
|
|
24586
25129
|
]).optional(),
|
|
24587
|
-
gop: number().optional()
|
|
24588
|
-
audio: boolean().optional()
|
|
25130
|
+
gop: number().optional()
|
|
24589
25131
|
});
|
|
24590
25132
|
var streamParamsCapability = {
|
|
24591
25133
|
name: "stream-params",
|
|
@@ -29399,6 +29941,48 @@ Object.freeze({
|
|
|
29399
29941
|
addonId: null,
|
|
29400
29942
|
access: "create"
|
|
29401
29943
|
},
|
|
29944
|
+
"osdManager.clearSlotBinding": {
|
|
29945
|
+
capName: "osd-manager",
|
|
29946
|
+
capScope: "system",
|
|
29947
|
+
addonId: null,
|
|
29948
|
+
access: "delete"
|
|
29949
|
+
},
|
|
29950
|
+
"osdManager.getConditionSupport": {
|
|
29951
|
+
capName: "osd-manager",
|
|
29952
|
+
capScope: "system",
|
|
29953
|
+
addonId: null,
|
|
29954
|
+
access: "view"
|
|
29955
|
+
},
|
|
29956
|
+
"osdManager.getDeviceOsd": {
|
|
29957
|
+
capName: "osd-manager",
|
|
29958
|
+
capScope: "system",
|
|
29959
|
+
addonId: null,
|
|
29960
|
+
access: "view"
|
|
29961
|
+
},
|
|
29962
|
+
"osdManager.getSourceCatalog": {
|
|
29963
|
+
capName: "osd-manager",
|
|
29964
|
+
capScope: "system",
|
|
29965
|
+
addonId: null,
|
|
29966
|
+
access: "view"
|
|
29967
|
+
},
|
|
29968
|
+
"osdManager.previewSlot": {
|
|
29969
|
+
capName: "osd-manager",
|
|
29970
|
+
capScope: "system",
|
|
29971
|
+
addonId: null,
|
|
29972
|
+
access: "create"
|
|
29973
|
+
},
|
|
29974
|
+
"osdManager.renderDevice": {
|
|
29975
|
+
capName: "osd-manager",
|
|
29976
|
+
capScope: "system",
|
|
29977
|
+
addonId: null,
|
|
29978
|
+
access: "create"
|
|
29979
|
+
},
|
|
29980
|
+
"osdManager.setSlotBinding": {
|
|
29981
|
+
capName: "osd-manager",
|
|
29982
|
+
capScope: "system",
|
|
29983
|
+
addonId: null,
|
|
29984
|
+
access: "create"
|
|
29985
|
+
},
|
|
29402
29986
|
"petFeeder.callPet": {
|
|
29403
29987
|
capName: "pet-feeder",
|
|
29404
29988
|
capScope: "device",
|
|
@@ -29561,6 +30145,18 @@ Object.freeze({
|
|
|
29561
30145
|
addonId: null,
|
|
29562
30146
|
access: "view"
|
|
29563
30147
|
},
|
|
30148
|
+
"pipelineAnalytics.getTrainingExportSummary": {
|
|
30149
|
+
capName: "pipeline-analytics",
|
|
30150
|
+
capScope: "device",
|
|
30151
|
+
addonId: null,
|
|
30152
|
+
access: "view"
|
|
30153
|
+
},
|
|
30154
|
+
"pipelineAnalytics.getTrainingExportUrl": {
|
|
30155
|
+
capName: "pipeline-analytics",
|
|
30156
|
+
capScope: "device",
|
|
30157
|
+
addonId: null,
|
|
30158
|
+
access: "view"
|
|
30159
|
+
},
|
|
29564
30160
|
"pipelineAnalytics.listEventKinds": {
|
|
29565
30161
|
capName: "pipeline-analytics",
|
|
29566
30162
|
capScope: "device",
|
|
@@ -30317,6 +30913,12 @@ Object.freeze({
|
|
|
30317
30913
|
addonId: null,
|
|
30318
30914
|
access: "view"
|
|
30319
30915
|
},
|
|
30916
|
+
"privacyMask.setAudioEnabled": {
|
|
30917
|
+
capName: "privacy-mask",
|
|
30918
|
+
capScope: "device",
|
|
30919
|
+
addonId: null,
|
|
30920
|
+
access: "create"
|
|
30921
|
+
},
|
|
30320
30922
|
"privacyMask.setMask": {
|
|
30321
30923
|
capName: "privacy-mask",
|
|
30322
30924
|
capScope: "device",
|
|
@@ -30485,6 +31087,12 @@ Object.freeze({
|
|
|
30485
31087
|
addonId: null,
|
|
30486
31088
|
access: "create"
|
|
30487
31089
|
},
|
|
31090
|
+
"recording.readGopBytes": {
|
|
31091
|
+
capName: "recording",
|
|
31092
|
+
capScope: "system",
|
|
31093
|
+
addonId: null,
|
|
31094
|
+
access: "view"
|
|
31095
|
+
},
|
|
30488
31096
|
"recording.readSegmentBytes": {
|
|
30489
31097
|
capName: "recording",
|
|
30490
31098
|
capScope: "system",
|
|
@@ -31025,6 +31633,12 @@ Object.freeze({
|
|
|
31025
31633
|
addonId: null,
|
|
31026
31634
|
access: "view"
|
|
31027
31635
|
},
|
|
31636
|
+
"streamBroker.getDeviceAudioMute": {
|
|
31637
|
+
capName: "stream-broker",
|
|
31638
|
+
capScope: "system",
|
|
31639
|
+
addonId: null,
|
|
31640
|
+
access: "view"
|
|
31641
|
+
},
|
|
31028
31642
|
"streamBroker.getPreBufferInfo": {
|
|
31029
31643
|
capName: "stream-broker",
|
|
31030
31644
|
capScope: "system",
|
|
@@ -31145,6 +31759,12 @@ Object.freeze({
|
|
|
31145
31759
|
addonId: null,
|
|
31146
31760
|
access: "create"
|
|
31147
31761
|
},
|
|
31762
|
+
"streamBroker.setDeviceAudioMute": {
|
|
31763
|
+
capName: "stream-broker",
|
|
31764
|
+
capScope: "system",
|
|
31765
|
+
addonId: null,
|
|
31766
|
+
access: "create"
|
|
31767
|
+
},
|
|
31148
31768
|
"streamBroker.setPreBufferDuration": {
|
|
31149
31769
|
capName: "stream-broker",
|
|
31150
31770
|
capScope: "system",
|
|
@@ -31792,6 +32412,66 @@ Object.freeze({
|
|
|
31792
32412
|
"smtp-provider": "email"
|
|
31793
32413
|
});
|
|
31794
32414
|
new Set(["devices", "classes"]);
|
|
32415
|
+
var WEEKDAY_TO_DAY = {
|
|
32416
|
+
Sun: 0,
|
|
32417
|
+
Mon: 1,
|
|
32418
|
+
Tue: 2,
|
|
32419
|
+
Wed: 3,
|
|
32420
|
+
Thu: 4,
|
|
32421
|
+
Fri: 5,
|
|
32422
|
+
Sat: 6
|
|
32423
|
+
};
|
|
32424
|
+
/** Resolve (weekday, minute-of-day) of `atMs` in the schedule's timezone.
|
|
32425
|
+
* An invalid/unknown IANA name falls back to the host timezone. */
|
|
32426
|
+
function localDayMinute(atMs, timezone) {
|
|
32427
|
+
const d = new Date(atMs);
|
|
32428
|
+
if (timezone !== void 0) try {
|
|
32429
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
32430
|
+
timeZone: timezone,
|
|
32431
|
+
weekday: "short",
|
|
32432
|
+
hour: "numeric",
|
|
32433
|
+
minute: "numeric",
|
|
32434
|
+
hourCycle: "h23"
|
|
32435
|
+
}).formatToParts(d);
|
|
32436
|
+
let weekday;
|
|
32437
|
+
let hour;
|
|
32438
|
+
let minute;
|
|
32439
|
+
for (const p of parts) if (p.type === "weekday") weekday = p.value;
|
|
32440
|
+
else if (p.type === "hour") hour = Number(p.value);
|
|
32441
|
+
else if (p.type === "minute") minute = Number(p.value);
|
|
32442
|
+
const day = weekday !== void 0 ? WEEKDAY_TO_DAY[weekday] : void 0;
|
|
32443
|
+
if (day !== void 0 && hour !== void 0 && minute !== void 0) return {
|
|
32444
|
+
day,
|
|
32445
|
+
minute: hour * 60 + minute
|
|
32446
|
+
};
|
|
32447
|
+
} catch {}
|
|
32448
|
+
return {
|
|
32449
|
+
day: d.getDay(),
|
|
32450
|
+
minute: d.getHours() * 60 + d.getMinutes()
|
|
32451
|
+
};
|
|
32452
|
+
}
|
|
32453
|
+
/**
|
|
32454
|
+
* Is the schedule active at `atMs`? No schedule = always active. Windows
|
|
32455
|
+
* are OR'd; a window with `startMinute > endMinute` crosses midnight (it
|
|
32456
|
+
* starts on a listed day and spills into the next). `invert` flips the
|
|
32457
|
+
* result (active OUTSIDE the windows).
|
|
32458
|
+
*/
|
|
32459
|
+
function isScheduleActive(schedule, atMs) {
|
|
32460
|
+
if (schedule === void 0) return true;
|
|
32461
|
+
const { day, minute } = localDayMinute(atMs, schedule.timezone);
|
|
32462
|
+
const prevDay = (day + 6) % 7;
|
|
32463
|
+
let inside = false;
|
|
32464
|
+
for (const w of schedule.windows) if (w.startMinute <= w.endMinute) {
|
|
32465
|
+
if (w.days.includes(day) && minute >= w.startMinute && minute < w.endMinute) {
|
|
32466
|
+
inside = true;
|
|
32467
|
+
break;
|
|
32468
|
+
}
|
|
32469
|
+
} else if (w.days.includes(day) && minute >= w.startMinute || w.days.includes(prevDay) && minute < w.endMinute) {
|
|
32470
|
+
inside = true;
|
|
32471
|
+
break;
|
|
32472
|
+
}
|
|
32473
|
+
return schedule.invert === true ? !inside : inside;
|
|
32474
|
+
}
|
|
31795
32475
|
/**
|
|
31796
32476
|
* TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
|
|
31797
32477
|
*
|
|
@@ -31951,5 +32631,87 @@ object({
|
|
|
31951
32631
|
paddingRatio: .15,
|
|
31952
32632
|
square: false
|
|
31953
32633
|
}).paddingRatio;
|
|
32634
|
+
/**
|
|
32635
|
+
* WHICH delivered frames the decode worker retains a native copy of.
|
|
32636
|
+
*
|
|
32637
|
+
* - `all` — every frame the worker delivered to the runner. The shipped
|
|
32638
|
+
* behaviour, and the only correct one if something can ask for a crop of a
|
|
32639
|
+
* frame the runner never sent to inference.
|
|
32640
|
+
* - `inferred` — only the frames the runner ADMITTED to its detection queue.
|
|
32641
|
+
* A native-crop request always names a `frameId` that rode an inference
|
|
32642
|
+
* result, so that is the only set a request can name. How much it drops is
|
|
32643
|
+
* the two-plane governor's admit ratio and nothing else: measured at ~50% on
|
|
32644
|
+
* this cluster, not the ~80% the design sketch assumed, because the governor
|
|
32645
|
+
* was not throttling as hard as the sketch supposed. Read
|
|
32646
|
+
* `leaseAdmitted`/`leaseOffered` off the metrics line for the camera in front
|
|
32647
|
+
* of you rather than quoting a number from here. The newest delivered frame is
|
|
32648
|
+
* croppable regardless — it is still the worker's reserved slot, not a lease —
|
|
32649
|
+
* which covers the one-frame race between a mark and the supersede that
|
|
32650
|
+
* consumes it.
|
|
32651
|
+
*/
|
|
32652
|
+
var NativeLeaseAdmissionSchema = _enum(["all", "inferred"]);
|
|
32653
|
+
object({
|
|
32654
|
+
/**
|
|
32655
|
+
* How long a retained native frame is served before it counts as a miss.
|
|
32656
|
+
*
|
|
32657
|
+
* Must cover the FULL late-crop horizon: detection inference + the
|
|
32658
|
+
* cross-process inference-result hop to hub post-analysis + tracking + the
|
|
32659
|
+
* tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
|
|
32660
|
+
* outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
|
|
32661
|
+
* RAM per busy camera grows linearly with no measured hit-rate gain.
|
|
32662
|
+
*/
|
|
32663
|
+
ttlMs: number().int().min(250).max(1e4),
|
|
32664
|
+
/**
|
|
32665
|
+
* Hard per-decode-worker RAM ceiling for retained native frames, in MB.
|
|
32666
|
+
*
|
|
32667
|
+
* Intended as a SAFETY ceiling with the TTL as the effective cap — but check
|
|
32668
|
+
* which one is actually binding before reasoning from that. At the shipped
|
|
32669
|
+
* 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
|
|
32670
|
+
* at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
|
|
32671
|
+
* `leaseFrames` on the metrics line say which. When the ceiling binds, a
|
|
32672
|
+
* change that admits fewer frames buys retention WINDOW at constant RAM
|
|
32673
|
+
* rather than giving RAM back — lower this knob if RAM is what you wanted.
|
|
32674
|
+
* `0` DISABLES the lease entirely and falls the worker back to the tiny
|
|
32675
|
+
* leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
|
|
32676
|
+
* to replace).
|
|
32677
|
+
*/
|
|
32678
|
+
budgetMb: number().int().min(0).max(4096),
|
|
32679
|
+
/**
|
|
32680
|
+
* Demand window: eager per-frame native retention runs only within this many
|
|
32681
|
+
* ms of the last native-crop request (or of the dial starting).
|
|
32682
|
+
*
|
|
32683
|
+
* `0` means ALWAYS ON — it disables the gate, it does not disable retention.
|
|
32684
|
+
* That is the legacy behaviour that saturated an N100 (24 native-4K downloads
|
|
32685
|
+
* per second on a camera with zero crop demand), so leave it non-zero unless
|
|
32686
|
+
* you are reproducing that.
|
|
32687
|
+
*/
|
|
32688
|
+
activityMs: number().int().min(0).max(12e4),
|
|
32689
|
+
/**
|
|
32690
|
+
* Which delivered frames are retained at all — see
|
|
32691
|
+
* {@link NativeLeaseAdmissionSchema}. This is the only knob of the four that
|
|
32692
|
+
* changes WHAT is kept rather than for how long, so it is also the only one
|
|
32693
|
+
* that can turn a crop that used to hit into a miss. The worker counts every
|
|
32694
|
+
* crop request naming a frame it did NOT see marked
|
|
32695
|
+
* (`leaseUnmarkedCrops` on the session-decode metrics line): a non-zero value
|
|
32696
|
+
* there is the signal that some caller names frames outside the inference set
|
|
32697
|
+
* and that this must go back to `all`.
|
|
32698
|
+
*/
|
|
32699
|
+
admission: NativeLeaseAdmissionSchema
|
|
32700
|
+
});
|
|
32701
|
+
/**
|
|
32702
|
+
* The values in force when the operator has set nothing — byte-for-byte the
|
|
32703
|
+
* constants the decode worker shipped with as env-var defaults, so making these
|
|
32704
|
+
* settings changed no behaviour on the day it landed.
|
|
32705
|
+
*/
|
|
32706
|
+
var DEFAULT_NATIVE_LEASE_SETTINGS = {
|
|
32707
|
+
ttlMs: 1200,
|
|
32708
|
+
budgetMb: 1024,
|
|
32709
|
+
activityMs: 15e3,
|
|
32710
|
+
admission: "inferred"
|
|
32711
|
+
};
|
|
32712
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
|
|
32713
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
|
|
32714
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
32715
|
+
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
31954
32716
|
//#endregion
|
|
31955
|
-
export {
|
|
32717
|
+
export { literal as $, faceGalleryCapability as A, videoclipsCapability as B, audioMetricsCapability as C, defineCustomActions as D, customAction as E, pipelineAnalyticsCapability as F, createEvent as G, errMsg as H, plateGalleryCapability as I, nodePin as J, hydrateSchema as K, readDeviceStateFrom as L, isScheduleActive as M, kebabToCamel as N, embeddingEncoderCapability as O, notificationRulesCapability as P, boolean as Q, subKindsOf as R, alarmPanelCapability as S, cosineSimilarity as T, BaseAddon as U, zoneAnalyticsCapability as V, DeviceType as W, _enum as X, sleep as Y, array as Z, RetrainStatusSchema as _, MACRO_LABELS as a, EventCategory as at, TrackSourceSchema as b, NcConditionDescriptorSchema as c, NcRuleSchema as d, number as et, NcSnoozeInputSchema as f, OpsLogEntrySchema as g, NcTaxonomySchema as h, EVENT_PAD_MS as i, unknown as it, hfModelUrl as j, encodeVectorBase64 as k, NcRuleInputSchema as l, NcSnoozeSuppressedSchema as m, DEFAULT_EVENT_COLOR as n, record as nt, NC_CONDITION_CATALOG as o, NcSnoozeSchema as p, isDeviceScopedCap as q, EVENT_KIND_BY_CAP as r, string as rt, NC_TAXONOMY as s, BaseDevice as t, object as tt, NcRulePatchSchema as u, TimelapseRuleInputSchema as v, buildEventKindDescriptor as w, addonWidgetsSourceCapability as x, TimelapseRuleSchema as y, vectorDimFromBase64 as z };
|