@camstack/addon-pipeline 1.2.288 → 1.2.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-DTvsx_iX.mjs → default-detection-model-DcTy-nJl.mjs} +1 -1
- package/dist/{default-detection-model-D5kMByfZ.js → default-detection-model-jjdmKKr3.js} +1 -1
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-mEtwupOP.mjs → dist-Bn6BQW8q.mjs} +1692 -920
- package/dist/{dist-CegiJ2Pw.js → dist-D7FonEUB.js} +1709 -919
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-B8p-rV9H.mjs → node-BN0Na3HN.mjs} +1 -1
- package/dist/{node-C8jhiPW2.js → node-D3C1krEi.js} +1 -1
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-C5bRH8-t.mjs → process-memory-CeZ8ms80.mjs} +1 -1
- package/dist/{process-memory-lPbJ1ta2.js → process-memory-DI4jrCtX.js} +1 -1
- package/dist/recorder/index.js +7432 -7264
- package/dist/recorder/index.mjs +7432 -7264
- package/dist/{segment-demux-js-BP5vqRPY.mjs → segment-demux-js-CNPzlcyO.mjs} +1 -1
- package/dist/{segment-demux-js-Dv1EPPdH.js → segment-demux-js-COooJ-t0.js} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BqjkSRCu.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BrD7TK3W.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-pSFdAaR0.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DhImOdbA.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-C4EuFQ_w.mjs → hostInit-CbSGtrCj.mjs} +2 -2
- package/dist/stream-broker/index.js +1465 -133
- package/dist/stream-broker/index.mjs +1465 -133
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +5 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DqcT4fRG.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DCbVs3IC.mjs +0 -26
|
@@ -5360,7 +5360,7 @@ var ZodIssueCode = {
|
|
|
5360
5360
|
var ZodFirstPartyTypeKind;
|
|
5361
5361
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5362
5362
|
//#endregion
|
|
5363
|
-
//#region ../types/dist/sleep-
|
|
5363
|
+
//#region ../types/dist/sleep-D_4W7xuQ.mjs
|
|
5364
5364
|
/**
|
|
5365
5365
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5366
5366
|
* window to float samples (D455).
|
|
@@ -6544,6 +6544,24 @@ function normalizeAddonInitResult(result) {
|
|
|
6544
6544
|
if (Array.isArray(result)) return { providers: result };
|
|
6545
6545
|
return result;
|
|
6546
6546
|
}
|
|
6547
|
+
/** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
|
|
6548
|
+
var PeerBytesTicketSchema = object({
|
|
6549
|
+
/** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
|
|
6550
|
+
url: string().min(1),
|
|
6551
|
+
/**
|
|
6552
|
+
* The HOST node this URL means something on — the hub or a named agent,
|
|
6553
|
+
* never a runner. {@link AddonPeerBytes.open} compares it to its own and
|
|
6554
|
+
* refuses `cross-node` by name when they differ, without dialling.
|
|
6555
|
+
*/
|
|
6556
|
+
hostNodeId: string().min(1),
|
|
6557
|
+
expiresAtMs: number().int().nonnegative(),
|
|
6558
|
+
/**
|
|
6559
|
+
* What the producer DECLARED the body to be, when it knows — `null` when it
|
|
6560
|
+
* does not. Never `0` for unknown (D393): a consumer sizing a bound off this
|
|
6561
|
+
* must be able to tell "the producer did not say" from "the body is empty".
|
|
6562
|
+
*/
|
|
6563
|
+
declaredBytes: number().int().nonnegative().nullable()
|
|
6564
|
+
});
|
|
6547
6565
|
/** Shared Zod schemas used across streaming capabilities. */
|
|
6548
6566
|
var CamProfileSchema = _enum([
|
|
6549
6567
|
"high",
|
|
@@ -7516,11 +7534,17 @@ var RawStateResultSchema = object({
|
|
|
7516
7534
|
method(object({ deviceId: number() }), array(StreamSourceEntrySchema)), method(object({ deviceId: number() }), array(ConfigEntrySchema$1)), method(object({
|
|
7517
7535
|
deviceId: number(),
|
|
7518
7536
|
values: record(string(), unknown())
|
|
7519
|
-
}), _void(), {
|
|
7537
|
+
}), _void(), {
|
|
7538
|
+
kind: "mutation",
|
|
7539
|
+
auth: "admin"
|
|
7540
|
+
}), method(object({
|
|
7520
7541
|
deviceId: number(),
|
|
7521
7542
|
action: string().min(1),
|
|
7522
7543
|
input: unknown()
|
|
7523
|
-
}), unknown(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), {
|
|
7544
|
+
}), unknown(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), {
|
|
7545
|
+
kind: "mutation",
|
|
7546
|
+
auth: "admin"
|
|
7547
|
+
}), method(object({ deviceId: number() }), unknown().nullable()), method(object({ deviceId: number() }), RawStateResultSchema.nullable(), { auth: "protected" });
|
|
7524
7548
|
/**
|
|
7525
7549
|
* The ONE retry schedule for work that can fail simply because the node was
|
|
7526
7550
|
* still booting.
|
|
@@ -8740,24 +8764,6 @@ object({
|
|
|
8740
8764
|
unreachable: number()
|
|
8741
8765
|
})
|
|
8742
8766
|
});
|
|
8743
|
-
/** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
|
|
8744
|
-
var PeerBytesTicketSchema = object({
|
|
8745
|
-
/** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
|
|
8746
|
-
url: string().min(1),
|
|
8747
|
-
/**
|
|
8748
|
-
* The HOST node this URL means something on — the hub or a named agent,
|
|
8749
|
-
* never a runner. {@link AddonPeerBytes.open} compares it to its own and
|
|
8750
|
-
* refuses `cross-node` by name when they differ, without dialling.
|
|
8751
|
-
*/
|
|
8752
|
-
hostNodeId: string().min(1),
|
|
8753
|
-
expiresAtMs: number().int().nonnegative(),
|
|
8754
|
-
/**
|
|
8755
|
-
* What the producer DECLARED the body to be, when it knows — `null` when it
|
|
8756
|
-
* does not. Never `0` for unknown (D393): a consumer sizing a bound off this
|
|
8757
|
-
* must be able to tell "the producer did not say" from "the body is empty".
|
|
8758
|
-
*/
|
|
8759
|
-
declaredBytes: number().int().nonnegative().nullable()
|
|
8760
|
-
});
|
|
8761
8767
|
/**
|
|
8762
8768
|
* Adoption job — the background form of `device-adoption.adopt`.
|
|
8763
8769
|
*
|
|
@@ -8869,7 +8875,7 @@ var AdoptionJobSchema = object({
|
|
|
8869
8875
|
* component's original options — detection to the detection-pipeline wrapper
|
|
8870
8876
|
* binding, audio analysis to its own, recording to `RecordingConfig.enabled`
|
|
8871
8877
|
* (which was always first-class; the switch was a veneer over
|
|
8872
|
-
* `
|
|
8878
|
+
* `recordingArchive.setDeviceConfig`), notifications to a notification-center
|
|
8873
8879
|
* per-device setting, the two camera planes to their own components.
|
|
8874
8880
|
*
|
|
8875
8881
|
* What survives is {@link composeSwitchedOff}: `CameraStatus.switchedOff`, the
|
|
@@ -8895,7 +8901,7 @@ var AdoptionJobSchema = object({
|
|
|
8895
8901
|
* | `stream-broker` | `deviceManager.setDisabled` | `StreamBrokerManager.reconcileAllCatalogs` releases the brokers; `ensureBroker` refuses re-creation |
|
|
8896
8902
|
* | `object-detection` | `deviceManager.setWrapperActive('detection-pipeline')` | `PipelineSettingsStore.resolvePipelineForDevice` returns `{ steps: [], audio: null }` |
|
|
8897
8903
|
* | `audio-analysis` | `deviceManager.setWrapperActive('audio-analysis')` | `AudioSubscriptionController.subscribeAudioStream` returns `null` before opening the stream |
|
|
8898
|
-
* | `recording` | `
|
|
8904
|
+
* | `recording` | `recordingArchive.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
|
|
8899
8905
|
* | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
|
|
8900
8906
|
* | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
|
|
8901
8907
|
* | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
|
|
@@ -13608,60 +13614,8 @@ method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, Broke
|
|
|
13608
13614
|
}), method(UnsubscribeInputSchema, _void(), {
|
|
13609
13615
|
kind: "mutation",
|
|
13610
13616
|
auth: "admin"
|
|
13611
|
-
}), method(GetStateInputSchema, unknown().nullable()), method(_void(), RegistryStatusSchema);
|
|
13617
|
+
}), method(GetStateInputSchema, unknown().nullable(), { auth: "admin" }), method(_void(), RegistryStatusSchema);
|
|
13612
13618
|
DeviceType.Camera;
|
|
13613
|
-
/**
|
|
13614
|
-
* The signals a device can emit to WAKE its own stream.
|
|
13615
|
-
*
|
|
13616
|
-
* A camera whose stream is built on demand sleeps until something asks for it,
|
|
13617
|
-
* and "something" cannot be a consumer that is merely attached — a Frigate-style
|
|
13618
|
-
* puller holds a session open for ever, and treating that as demand would keep
|
|
13619
|
-
* a battery camera awake for ever, which is the whole thing the battery is for
|
|
13620
|
-
* (D173). So the wake has to come from the CAMERA: an event it noticed by
|
|
13621
|
-
* itself, with no stream running.
|
|
13622
|
-
*
|
|
13623
|
-
* ## The vocabulary is the PROVIDER'S, not ours
|
|
13624
|
-
*
|
|
13625
|
-
* Like `consumables`, this cap declares no vocabulary of its own. A provider
|
|
13626
|
-
* names each signal with a `code` it chooses and a `label` an operator reads.
|
|
13627
|
-
* Reolink offers motion and camera-native detection; another provider may offer
|
|
13628
|
-
* a tamper, a doorbell press, a PIR, or something no camera in this fleet has
|
|
13629
|
-
* yet. A fixed enum here would mean every new signal is a framework release.
|
|
13630
|
-
*
|
|
13631
|
-
* It is deliberately NOT derived from the caps a device already binds. Whether
|
|
13632
|
-
* a camera CAN push firmware motion is expressed by `motionSources` containing
|
|
13633
|
-
* `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
|
|
13634
|
-
* binding — but both answer "what drives the detection pipeline", which is a
|
|
13635
|
-
* different question from "what may wake a sleeping stream". A camera can do
|
|
13636
|
-
* the first and not be trusted with the second, and the operator picks per
|
|
13637
|
-
* camera. Two questions, two authorities.
|
|
13638
|
-
*
|
|
13639
|
-
* ## Availability is not permission
|
|
13640
|
-
*
|
|
13641
|
-
* `listSignals` says what the device CAN emit. Whether a given signal actually
|
|
13642
|
-
* wakes the stream is the operator's per-camera choice, held by the broker
|
|
13643
|
-
* alongside the cooldown — see the stream-broker cap's wake settings. A
|
|
13644
|
-
* provider declaring a signal is not a provider enabling it.
|
|
13645
|
-
*/
|
|
13646
|
-
/** One signal a device can emit. */
|
|
13647
|
-
var StreamSignalSchema = object({
|
|
13648
|
-
/** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
|
|
13649
|
-
code: string().min(1),
|
|
13650
|
-
/** What an operator reads in the picker. The provider's own wording. */
|
|
13651
|
-
label: string().min(1),
|
|
13652
|
-
/**
|
|
13653
|
-
* Whether the provider recommends this signal ON when a camera is first set
|
|
13654
|
-
* up. A provider knows which of its signals are cheap and reliable; an
|
|
13655
|
-
* operator should not have to discover that by trial. Reolink recommends
|
|
13656
|
-
* both of its own.
|
|
13657
|
-
*/
|
|
13658
|
-
recommended: boolean()
|
|
13659
|
-
});
|
|
13660
|
-
object({
|
|
13661
|
-
signals: array(StreamSignalSchema),
|
|
13662
|
-
lastFetchedAt: number()
|
|
13663
|
-
});
|
|
13664
|
-
Object.values(DeviceType), method(_void(), array(StreamSignalSchema).readonly());
|
|
13665
13619
|
/** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
|
|
13666
13620
|
var StreamFormatSchema = _enum([
|
|
13667
13621
|
"webrtc",
|
|
@@ -15095,17 +15049,20 @@ method(object({
|
|
|
15095
15049
|
namespace: string().optional(),
|
|
15096
15050
|
collection: string(),
|
|
15097
15051
|
key: string()
|
|
15098
|
-
}), unknown()), method(object({
|
|
15052
|
+
}), unknown(), { auth: "admin" }), method(object({
|
|
15099
15053
|
namespace: string().optional(),
|
|
15100
15054
|
collection: string(),
|
|
15101
15055
|
key: string(),
|
|
15102
15056
|
value: unknown()
|
|
15103
|
-
}), _void(), {
|
|
15057
|
+
}), _void(), {
|
|
15058
|
+
kind: "mutation",
|
|
15059
|
+
auth: "admin"
|
|
15060
|
+
}), method(object({
|
|
15104
15061
|
namespace: string().optional(),
|
|
15105
15062
|
collection: string(),
|
|
15106
15063
|
filter: QueryFilterSchema.optional(),
|
|
15107
15064
|
columns: array(string()).readonly().optional()
|
|
15108
|
-
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
15065
|
+
}), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
|
|
15109
15066
|
namespace: string().optional(),
|
|
15110
15067
|
collection: string(),
|
|
15111
15068
|
/** Declared columns identifying the slot; must be covered by a UNIQUE
|
|
@@ -15115,7 +15072,10 @@ method(object({
|
|
|
15115
15072
|
}), object({
|
|
15116
15073
|
/** The id the row HAS: assigned by SQLite on an insert, or the existing
|
|
15117
15074
|
* row's own id on an update. */
|
|
15118
|
-
id: union([string(), number()]) }), {
|
|
15075
|
+
id: union([string(), number()]) }), {
|
|
15076
|
+
kind: "mutation",
|
|
15077
|
+
auth: "admin"
|
|
15078
|
+
}), method(object({
|
|
15119
15079
|
namespace: string().optional(),
|
|
15120
15080
|
collection: string(),
|
|
15121
15081
|
record: object({
|
|
@@ -15128,38 +15088,56 @@ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
|
|
|
15128
15088
|
* for an absent one, or the ROWID SQLite assigned on an `INTEGER`
|
|
15129
15089
|
* primary key — which is the only place an auto key is knowable.
|
|
15130
15090
|
*/
|
|
15131
|
-
id: union([string(), number()]) }), {
|
|
15091
|
+
id: union([string(), number()]) }), {
|
|
15092
|
+
kind: "mutation",
|
|
15093
|
+
auth: "admin"
|
|
15094
|
+
}), method(object({
|
|
15132
15095
|
namespace: string().optional(),
|
|
15133
15096
|
collection: string(),
|
|
15134
15097
|
records: array(BulkRecordSchema).readonly()
|
|
15135
|
-
}), object({ inserted: number().int() }), {
|
|
15098
|
+
}), object({ inserted: number().int() }), {
|
|
15099
|
+
kind: "mutation",
|
|
15100
|
+
auth: "admin"
|
|
15101
|
+
}), method(object({
|
|
15136
15102
|
namespace: string().optional(),
|
|
15137
15103
|
collection: string(),
|
|
15138
15104
|
id: string(),
|
|
15139
15105
|
data: record(string(), unknown())
|
|
15140
|
-
}), _void(), {
|
|
15106
|
+
}), _void(), {
|
|
15107
|
+
kind: "mutation",
|
|
15108
|
+
auth: "admin"
|
|
15109
|
+
}), method(object({
|
|
15141
15110
|
namespace: string().optional(),
|
|
15142
15111
|
collection: string(),
|
|
15143
15112
|
key: string()
|
|
15144
|
-
}), _void(), {
|
|
15113
|
+
}), _void(), {
|
|
15114
|
+
kind: "mutation",
|
|
15115
|
+
auth: "admin"
|
|
15116
|
+
}), method(object({
|
|
15145
15117
|
namespace: string().optional(),
|
|
15146
15118
|
collection: string(),
|
|
15147
15119
|
filter: MutationFilterSchema
|
|
15148
|
-
}), object({ deleted: number().int() }), {
|
|
15120
|
+
}), object({ deleted: number().int() }), {
|
|
15121
|
+
kind: "mutation",
|
|
15122
|
+
auth: "admin"
|
|
15123
|
+
}), method(object({
|
|
15149
15124
|
namespace: string().optional(),
|
|
15150
15125
|
collection: string(),
|
|
15151
15126
|
filter: MutationFilterSchema,
|
|
15152
15127
|
data: record(string(), unknown())
|
|
15153
|
-
}), object({ updated: number().int() }), {
|
|
15128
|
+
}), object({ updated: number().int() }), {
|
|
15129
|
+
kind: "mutation",
|
|
15130
|
+
auth: "admin"
|
|
15131
|
+
}), method(object({
|
|
15154
15132
|
namespace: string().optional(),
|
|
15155
15133
|
collection: string(),
|
|
15156
15134
|
filter: QueryFilterSchema.optional()
|
|
15157
|
-
}), number()), method(object({
|
|
15135
|
+
}), number(), { auth: "admin" }), method(object({
|
|
15158
15136
|
namespace: string().optional(),
|
|
15159
15137
|
collection: string(),
|
|
15160
15138
|
fields: array(AggregateFieldSchema).readonly(),
|
|
15161
15139
|
filter: QueryFilterSchema.optional()
|
|
15162
|
-
}), AggregateResultSchema), method(object({
|
|
15140
|
+
}), AggregateResultSchema, { auth: "admin" }), method(object({
|
|
15163
15141
|
namespace: string().optional(),
|
|
15164
15142
|
collection: string(),
|
|
15165
15143
|
field: string(),
|
|
@@ -15169,15 +15147,18 @@ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
|
|
|
15169
15147
|
}), array(object({
|
|
15170
15148
|
bucket: number().int(),
|
|
15171
15149
|
count: number().int()
|
|
15172
|
-
})).readonly()), method(object({
|
|
15150
|
+
})).readonly(), { auth: "admin" }), method(object({
|
|
15173
15151
|
namespace: string().optional(),
|
|
15174
15152
|
collection: string()
|
|
15175
|
-
}), boolean()), method(object({
|
|
15153
|
+
}), boolean(), { auth: "admin" }), method(object({
|
|
15176
15154
|
namespace: string().optional(),
|
|
15177
15155
|
collection: string(),
|
|
15178
15156
|
columns: array(CollectionColumnSchema).readonly(),
|
|
15179
15157
|
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
15180
|
-
}), _void(), {
|
|
15158
|
+
}), _void(), {
|
|
15159
|
+
kind: "mutation",
|
|
15160
|
+
auth: "admin"
|
|
15161
|
+
});
|
|
15181
15162
|
/**
|
|
15182
15163
|
* What one engine says about itself. The orchestrator uses `kind` to pick
|
|
15183
15164
|
* a registrant for a collection; `engineId` is what a log line names when
|
|
@@ -15410,6 +15391,97 @@ var detectionPipelineCapability = {
|
|
|
15410
15391
|
methods: {}
|
|
15411
15392
|
};
|
|
15412
15393
|
/**
|
|
15394
|
+
* device-admin-link — "this device has a management page of its own, and here
|
|
15395
|
+
* is its address".
|
|
15396
|
+
*
|
|
15397
|
+
* ## Why this is not a `deviceConfig` cap
|
|
15398
|
+
*
|
|
15399
|
+
* There is nothing to edit. A `deviceConfig` cap (D14) exists so the framework
|
|
15400
|
+
* can DERIVE a settings form from `getOptions` + `getStatus` and route a flat
|
|
15401
|
+
* patch back through a setter; it costs a `builderId` reducer in
|
|
15402
|
+
* `device-config-contribution.ts` and a `*-config-schema.ts` beside it, and it
|
|
15403
|
+
* renders a form section. This cap answers ONE question with ONE read and
|
|
15404
|
+
* renders a button. Nothing about it is a form, so it carries no `deviceConfig`
|
|
15405
|
+
* block, no `settings`, no `runtimeState` and no reducer — exactly like
|
|
15406
|
+
* `reboot`, the other pure-RPC device-native cap.
|
|
15407
|
+
*
|
|
15408
|
+
* ## Absent, and the difference between "no page" and "we cannot say"
|
|
15409
|
+
*
|
|
15410
|
+
* The two are answered at DIFFERENT layers, on purpose:
|
|
15411
|
+
*
|
|
15412
|
+
* - **"We cannot say"** → the provider never registers the cap for that
|
|
15413
|
+
* device. A VeSync humidifier, a Petkit feeder, a Dreame vacuum and a Dreo
|
|
15414
|
+
* fan are reached only through a vendor cloud; there is no address to hand
|
|
15415
|
+
* out and no page to open. A Tuya plug, a Wyze camera and a Gree air
|
|
15416
|
+
* conditioner DO have a LAN IP, and still have no HTTP management page
|
|
15417
|
+
* behind it. None of them register, so `deviceManager.getBindings` never
|
|
15418
|
+
* lists the cap and no surface asks.
|
|
15419
|
+
* - **"This device has no page, and I know that"** → the provider registers
|
|
15420
|
+
* and `getAdminLink` returns `null`. This is the answer for a device whose
|
|
15421
|
+
* sibling DOES have a page: a Reolink battery camera reached over UDP by
|
|
15422
|
+
* `uid` with a blank `host`, an Ecowitt gateway configured in `listener`
|
|
15423
|
+
* transport, a Home Assistant broker authenticated by supervisor token
|
|
15424
|
+
* (which carries no `baseUrl` at all).
|
|
15425
|
+
*
|
|
15426
|
+
* Both draw NOTHING. A button that opens a browser error is worse than no
|
|
15427
|
+
* button, and D62 is the same rule from the other side: an off switch is
|
|
15428
|
+
* reported off, never made to look broken. There is no third state where the
|
|
15429
|
+
* UI renders a disabled button "because the device might have a page".
|
|
15430
|
+
*
|
|
15431
|
+
* ## The URL never carries credentials
|
|
15432
|
+
*
|
|
15433
|
+
* Not in userinfo, not in a query string. Every provider builds through
|
|
15434
|
+
* `buildDeviceAdminUrl` (`device-admin-link-url.ts`), which takes host, port,
|
|
15435
|
+
* scheme and path as separate arguments — there is no parameter a secret could
|
|
15436
|
+
* arrive in — and re-checks its own output for the `scheme://user:pass@` shape
|
|
15437
|
+
* that `scripts/check-no-credential-urls-in-fixtures.ts` bans from recorded
|
|
15438
|
+
* output. `scripts/check-admin-link-builder-is-the-only-url-source.ts` is what
|
|
15439
|
+
* keeps providers from hand-rolling one anyway.
|
|
15440
|
+
*
|
|
15441
|
+
* This matters here more than anywhere else in the repo, because every provider
|
|
15442
|
+
* that knows a device's host knows its PASSWORD too: `{ host, port, username,
|
|
15443
|
+
* password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
|
|
15444
|
+
* and `http://admin:hunter2@192.168.50.139/` is a URL a browser accepts. The
|
|
15445
|
+
* camera's own page will ask for its own login. That is correct, and pre-
|
|
15446
|
+
* filling it is the operator's business, not ours.
|
|
15447
|
+
*
|
|
15448
|
+
* ## It is a LAN fact
|
|
15449
|
+
*
|
|
15450
|
+
* The URL addresses the device where the NODE can see it. It is not proxied,
|
|
15451
|
+
* not made reachable from outside, and not sent anywhere. A surface renders it
|
|
15452
|
+
* as a link the operator's own browser follows, on the operator's own network,
|
|
15453
|
+
* or renders nothing.
|
|
15454
|
+
*/
|
|
15455
|
+
/**
|
|
15456
|
+
* Whose page is it. The distinction is for the OPERATOR, who needs to know
|
|
15457
|
+
* before clicking whether he is about to land on a camera's own web server or
|
|
15458
|
+
* inside Home Assistant.
|
|
15459
|
+
*/
|
|
15460
|
+
var AdminLinkTargetEnum = _enum(["device", "integration"]);
|
|
15461
|
+
var DeviceAdminLinkSchema = object({
|
|
15462
|
+
/**
|
|
15463
|
+
* Absolute `http(s)://` URL. Built by `buildDeviceAdminUrl` and therefore
|
|
15464
|
+
* free of userinfo and of any credential-shaped query key.
|
|
15465
|
+
*/
|
|
15466
|
+
url: string(),
|
|
15467
|
+
/**
|
|
15468
|
+
* What the surface calls it — "Web UI", "Home Assistant", "UniFi controller".
|
|
15469
|
+
* The PROVIDER names it, because only the provider knows what the page is;
|
|
15470
|
+
* a UI that invented the label from the addon id would call the Home
|
|
15471
|
+
* Assistant device page "Provider Homeassistant".
|
|
15472
|
+
*/
|
|
15473
|
+
label: string(),
|
|
15474
|
+
target: AdminLinkTargetEnum,
|
|
15475
|
+
/**
|
|
15476
|
+
* Host the URL points at, without scheme, port or path — for the tooltip, so
|
|
15477
|
+
* an operator can see WHERE the button goes before he follows it. Redundant
|
|
15478
|
+
* with `url` by construction; carried separately so no surface has to parse
|
|
15479
|
+
* a URL to show it.
|
|
15480
|
+
*/
|
|
15481
|
+
host: string()
|
|
15482
|
+
});
|
|
15483
|
+
method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
|
|
15484
|
+
/**
|
|
15413
15485
|
* Identity envelope for a device's upstream-system metadata.
|
|
15414
15486
|
*
|
|
15415
15487
|
* Two jobs:
|
|
@@ -15737,97 +15809,6 @@ method(object({ integrationId: string() }), object({ filters: array(AdoptionFilt
|
|
|
15737
15809
|
auth: "admin"
|
|
15738
15810
|
});
|
|
15739
15811
|
/**
|
|
15740
|
-
* device-admin-link — "this device has a management page of its own, and here
|
|
15741
|
-
* is its address".
|
|
15742
|
-
*
|
|
15743
|
-
* ## Why this is not a `deviceConfig` cap
|
|
15744
|
-
*
|
|
15745
|
-
* There is nothing to edit. A `deviceConfig` cap (D14) exists so the framework
|
|
15746
|
-
* can DERIVE a settings form from `getOptions` + `getStatus` and route a flat
|
|
15747
|
-
* patch back through a setter; it costs a `builderId` reducer in
|
|
15748
|
-
* `device-config-contribution.ts` and a `*-config-schema.ts` beside it, and it
|
|
15749
|
-
* renders a form section. This cap answers ONE question with ONE read and
|
|
15750
|
-
* renders a button. Nothing about it is a form, so it carries no `deviceConfig`
|
|
15751
|
-
* block, no `settings`, no `runtimeState` and no reducer — exactly like
|
|
15752
|
-
* `reboot`, the other pure-RPC device-native cap.
|
|
15753
|
-
*
|
|
15754
|
-
* ## Absent, and the difference between "no page" and "we cannot say"
|
|
15755
|
-
*
|
|
15756
|
-
* The two are answered at DIFFERENT layers, on purpose:
|
|
15757
|
-
*
|
|
15758
|
-
* - **"We cannot say"** → the provider never registers the cap for that
|
|
15759
|
-
* device. A VeSync humidifier, a Petkit feeder, a Dreame vacuum and a Dreo
|
|
15760
|
-
* fan are reached only through a vendor cloud; there is no address to hand
|
|
15761
|
-
* out and no page to open. A Tuya plug, a Wyze camera and a Gree air
|
|
15762
|
-
* conditioner DO have a LAN IP, and still have no HTTP management page
|
|
15763
|
-
* behind it. None of them register, so `deviceManager.getBindings` never
|
|
15764
|
-
* lists the cap and no surface asks.
|
|
15765
|
-
* - **"This device has no page, and I know that"** → the provider registers
|
|
15766
|
-
* and `getAdminLink` returns `null`. This is the answer for a device whose
|
|
15767
|
-
* sibling DOES have a page: a Reolink battery camera reached over UDP by
|
|
15768
|
-
* `uid` with a blank `host`, an Ecowitt gateway configured in `listener`
|
|
15769
|
-
* transport, a Home Assistant broker authenticated by supervisor token
|
|
15770
|
-
* (which carries no `baseUrl` at all).
|
|
15771
|
-
*
|
|
15772
|
-
* Both draw NOTHING. A button that opens a browser error is worse than no
|
|
15773
|
-
* button, and D62 is the same rule from the other side: an off switch is
|
|
15774
|
-
* reported off, never made to look broken. There is no third state where the
|
|
15775
|
-
* UI renders a disabled button "because the device might have a page".
|
|
15776
|
-
*
|
|
15777
|
-
* ## The URL never carries credentials
|
|
15778
|
-
*
|
|
15779
|
-
* Not in userinfo, not in a query string. Every provider builds through
|
|
15780
|
-
* `buildDeviceAdminUrl` (`device-admin-link-url.ts`), which takes host, port,
|
|
15781
|
-
* scheme and path as separate arguments — there is no parameter a secret could
|
|
15782
|
-
* arrive in — and re-checks its own output for the `scheme://user:pass@` shape
|
|
15783
|
-
* that `scripts/check-no-credential-urls-in-fixtures.ts` bans from recorded
|
|
15784
|
-
* output. `scripts/check-admin-link-builder-is-the-only-url-source.ts` is what
|
|
15785
|
-
* keeps providers from hand-rolling one anyway.
|
|
15786
|
-
*
|
|
15787
|
-
* This matters here more than anywhere else in the repo, because every provider
|
|
15788
|
-
* that knows a device's host knows its PASSWORD too: `{ host, port, username,
|
|
15789
|
-
* password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
|
|
15790
|
-
* and `http://admin:hunter2@192.168.50.139/` is a URL a browser accepts. The
|
|
15791
|
-
* camera's own page will ask for its own login. That is correct, and pre-
|
|
15792
|
-
* filling it is the operator's business, not ours.
|
|
15793
|
-
*
|
|
15794
|
-
* ## It is a LAN fact
|
|
15795
|
-
*
|
|
15796
|
-
* The URL addresses the device where the NODE can see it. It is not proxied,
|
|
15797
|
-
* not made reachable from outside, and not sent anywhere. A surface renders it
|
|
15798
|
-
* as a link the operator's own browser follows, on the operator's own network,
|
|
15799
|
-
* or renders nothing.
|
|
15800
|
-
*/
|
|
15801
|
-
/**
|
|
15802
|
-
* Whose page is it. The distinction is for the OPERATOR, who needs to know
|
|
15803
|
-
* before clicking whether he is about to land on a camera's own web server or
|
|
15804
|
-
* inside Home Assistant.
|
|
15805
|
-
*/
|
|
15806
|
-
var AdminLinkTargetEnum = _enum(["device", "integration"]);
|
|
15807
|
-
var DeviceAdminLinkSchema = object({
|
|
15808
|
-
/**
|
|
15809
|
-
* Absolute `http(s)://` URL. Built by `buildDeviceAdminUrl` and therefore
|
|
15810
|
-
* free of userinfo and of any credential-shaped query key.
|
|
15811
|
-
*/
|
|
15812
|
-
url: string(),
|
|
15813
|
-
/**
|
|
15814
|
-
* What the surface calls it — "Web UI", "Home Assistant", "UniFi controller".
|
|
15815
|
-
* The PROVIDER names it, because only the provider knows what the page is;
|
|
15816
|
-
* a UI that invented the label from the addon id would call the Home
|
|
15817
|
-
* Assistant device page "Provider Homeassistant".
|
|
15818
|
-
*/
|
|
15819
|
-
label: string(),
|
|
15820
|
-
target: AdminLinkTargetEnum,
|
|
15821
|
-
/**
|
|
15822
|
-
* Host the URL points at, without scheme, port or path — for the tooltip, so
|
|
15823
|
-
* an operator can see WHERE the button goes before he follows it. Redundant
|
|
15824
|
-
* with `url` by construction; carried separately so no surface has to parse
|
|
15825
|
-
* a URL to show it.
|
|
15826
|
-
*/
|
|
15827
|
-
host: string()
|
|
15828
|
-
});
|
|
15829
|
-
method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
|
|
15830
|
-
/**
|
|
15831
15812
|
* `device-export` — collection cap for addons that export camstack
|
|
15832
15813
|
* devices to external ecosystems (HomeAssistant via MQTT discovery,
|
|
15833
15814
|
* HomeKit/HAP, Alexa Smart Home, …).
|
|
@@ -15892,7 +15873,13 @@ var ExposeInputSchema = object({
|
|
|
15892
15873
|
capabilities: array(string()).optional()
|
|
15893
15874
|
});
|
|
15894
15875
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
15895
|
-
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
15876
|
+
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
15877
|
+
kind: "mutation",
|
|
15878
|
+
auth: "admin"
|
|
15879
|
+
}), method(UnexposeInputSchema, _void(), {
|
|
15880
|
+
kind: "mutation",
|
|
15881
|
+
auth: "admin"
|
|
15882
|
+
});
|
|
15896
15883
|
/** Wire/storage schema for one icon id. */
|
|
15897
15884
|
var LocationIconIdSchema = _enum([
|
|
15898
15885
|
"map-pin",
|
|
@@ -16396,7 +16383,10 @@ method(object({
|
|
|
16396
16383
|
kind: "mutation",
|
|
16397
16384
|
auth: "admin",
|
|
16398
16385
|
timeoutMs: 12 * 6e4
|
|
16399
|
-
}), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), {
|
|
16386
|
+
}), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), {
|
|
16387
|
+
kind: "mutation",
|
|
16388
|
+
auth: "admin"
|
|
16389
|
+
}), method(object({ deviceId: number() }), record(string(), unknown()), { auth: "admin" }), method(object({ deviceId: number() }), record(string(), unknown()), { auth: "admin" }), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
|
|
16400
16390
|
deviceId: number(),
|
|
16401
16391
|
name: string()
|
|
16402
16392
|
}), _void(), {
|
|
@@ -16601,7 +16591,7 @@ method(object({
|
|
|
16601
16591
|
}), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
|
|
16602
16592
|
kind: "mutation",
|
|
16603
16593
|
auth: "admin"
|
|
16604
|
-
}), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
|
|
16594
|
+
}), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({ capNames: array(string().min(1)).min(1).max(16) }), array(number())), method(object({
|
|
16605
16595
|
deviceId: number(),
|
|
16606
16596
|
capName: string(),
|
|
16607
16597
|
wrapperAddonId: string(),
|
|
@@ -18219,7 +18209,22 @@ var StatusSchema = object({
|
|
|
18219
18209
|
brokerCount: number(),
|
|
18220
18210
|
embeddedRunning: boolean()
|
|
18221
18211
|
});
|
|
18222
|
-
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema
|
|
18212
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
18213
|
+
kind: "mutation",
|
|
18214
|
+
auth: "admin"
|
|
18215
|
+
}), method(IdInputSchema, _void(), {
|
|
18216
|
+
kind: "mutation",
|
|
18217
|
+
auth: "admin"
|
|
18218
|
+
}), method(IdInputSchema, TestResultSchema$1, {
|
|
18219
|
+
kind: "mutation",
|
|
18220
|
+
auth: "admin"
|
|
18221
|
+
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
18222
|
+
kind: "mutation",
|
|
18223
|
+
auth: "admin"
|
|
18224
|
+
}), method(IdInputSchema, _void(), {
|
|
18225
|
+
kind: "mutation",
|
|
18226
|
+
auth: "admin"
|
|
18227
|
+
}), method(_void(), StatusSchema);
|
|
18223
18228
|
var NetworkEndpointSchema = object({
|
|
18224
18229
|
url: string(),
|
|
18225
18230
|
hostname: string(),
|
|
@@ -18514,6 +18519,18 @@ var TargetKindSchema = object({
|
|
|
18514
18519
|
*/
|
|
18515
18520
|
iconMediaType: string().optional(),
|
|
18516
18521
|
configSchema: ConfigSchemaPassthrough,
|
|
18522
|
+
/**
|
|
18523
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
18524
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
18525
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
18526
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
18527
|
+
* on every edit.
|
|
18528
|
+
*
|
|
18529
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
18530
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
18531
|
+
* it absent must assume ANY edit may need the secret.
|
|
18532
|
+
*/
|
|
18533
|
+
destinationKeys: array(string()).optional(),
|
|
18517
18534
|
supportsDiscovery: boolean(),
|
|
18518
18535
|
caps: TargetKindCapsSchema
|
|
18519
18536
|
});
|
|
@@ -18552,19 +18569,34 @@ var SendResultSchema = object({
|
|
|
18552
18569
|
});
|
|
18553
18570
|
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
18554
18571
|
var TestResultSchema = SendResultSchema;
|
|
18555
|
-
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
|
|
18572
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({
|
|
18556
18573
|
kind: string(),
|
|
18557
18574
|
config: record(string(), unknown()).optional()
|
|
18558
|
-
}), array(DiscoveredTargetSchema)), method(object({
|
|
18575
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
18559
18576
|
targetId: string(),
|
|
18560
18577
|
notification: NotificationSchema
|
|
18561
|
-
}), SendResultSchema, {
|
|
18578
|
+
}), SendResultSchema, {
|
|
18579
|
+
kind: "mutation",
|
|
18580
|
+
auth: "admin"
|
|
18581
|
+
}), method(object({
|
|
18562
18582
|
targetId: string(),
|
|
18563
18583
|
sample: NotificationSchema.optional()
|
|
18564
|
-
}), TestResultSchema, {
|
|
18584
|
+
}), TestResultSchema, {
|
|
18585
|
+
kind: "mutation",
|
|
18586
|
+
auth: "admin"
|
|
18587
|
+
}), method(object({ target: TargetSchema }), TargetSchema, {
|
|
18588
|
+
kind: "mutation",
|
|
18589
|
+
auth: "admin"
|
|
18590
|
+
}), method(object({ targetId: string() }), _void(), {
|
|
18591
|
+
kind: "mutation",
|
|
18592
|
+
auth: "admin"
|
|
18593
|
+
}), method(object({
|
|
18565
18594
|
targetId: string(),
|
|
18566
18595
|
enabled: boolean()
|
|
18567
|
-
}), _void(), {
|
|
18596
|
+
}), _void(), {
|
|
18597
|
+
kind: "mutation",
|
|
18598
|
+
auth: "admin"
|
|
18599
|
+
});
|
|
18568
18600
|
var COCO_80_LABELS = [
|
|
18569
18601
|
{
|
|
18570
18602
|
id: "person",
|
|
@@ -19411,6 +19443,7 @@ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
|
|
|
19411
19443
|
sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
|
|
19412
19444
|
sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
|
|
19413
19445
|
sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
|
|
19446
|
+
sub("cover", "control", "control", "#0ea5e9", "door", "Cover");
|
|
19414
19447
|
sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
|
|
19415
19448
|
sub("siren", "control", "control", "#dc2626", "siren", "Siren");
|
|
19416
19449
|
sub("button", "control", "control", "#10b981", "button", "Button");
|
|
@@ -19691,6 +19724,10 @@ var NcSystemEventKindSchema = _enum([
|
|
|
19691
19724
|
"alarm-disarmed",
|
|
19692
19725
|
"alarm-arming",
|
|
19693
19726
|
"alarm-arm-refused",
|
|
19727
|
+
"alarm-pending",
|
|
19728
|
+
"alarm-rearmed",
|
|
19729
|
+
"alarm-sensor-bypass",
|
|
19730
|
+
"alarm-check-failed",
|
|
19694
19731
|
"addon-updated",
|
|
19695
19732
|
"server-updated",
|
|
19696
19733
|
"export-completed",
|
|
@@ -19728,6 +19765,19 @@ var NcSystemEventConditionSchema = object({
|
|
|
19728
19765
|
* does not carry) matches no `deviceTypes` list.
|
|
19729
19766
|
*/
|
|
19730
19767
|
deviceTypes: array(string().min(1)).min(1).optional(),
|
|
19768
|
+
/**
|
|
19769
|
+
* Devices this rule NEVER speaks about, whatever else matched. Scoped to the
|
|
19770
|
+
* same kind family as `deviceIds` (`systemEventFilterApplies(kind,
|
|
19771
|
+
* 'deviceIds')`) and applied AFTER it: include first, then exclude. A
|
|
19772
|
+
* consumable subject (`consumable:<id>:<key>`) is excluded by its device id.
|
|
19773
|
+
*
|
|
19774
|
+
* It never fails closed: a subject that carries no device id is not in the
|
|
19775
|
+
* list, so it is not excluded. "Every battery except the one thermometer that
|
|
19776
|
+
* reads 0 % forever" is the question this answers — expressed as an include
|
|
19777
|
+
* list it would have to name every other battery device, and every device
|
|
19778
|
+
* adopted later would be silently left out (D636).
|
|
19779
|
+
*/
|
|
19780
|
+
excludeDeviceIds: array(number().int()).min(1).optional(),
|
|
19731
19781
|
nodeIds: array(string().min(1)).min(1).optional(),
|
|
19732
19782
|
packageNames: array(string().min(1)).min(1).optional()
|
|
19733
19783
|
});
|
|
@@ -20286,6 +20336,19 @@ var NcMediaFrameSchema = _enum([
|
|
|
20286
20336
|
"full",
|
|
20287
20337
|
"boxed"
|
|
20288
20338
|
]);
|
|
20339
|
+
/**
|
|
20340
|
+
* How a SENSOR notification shows the cameras that link the sensor.
|
|
20341
|
+
*
|
|
20342
|
+
* `mosaic` — one image composed of every linked camera's photograph, taken at
|
|
20343
|
+
* the trigger. `each` — one image per camera where the target takes several
|
|
20344
|
+
* attachments; a target that takes one keeps the first camera's (the degrade
|
|
20345
|
+
* engine drops the rest by order). Either way ONE notification per event.
|
|
20346
|
+
* Absent = `mosaic`, applied by the Notification Center and deliberately NOT
|
|
20347
|
+
* a Zod default (see NcRulePatchSchema: a default materialises on every
|
|
20348
|
+
* partial patch). Meaningless for a rule whose subject is not a sensor, and a
|
|
20349
|
+
* sensor linked to one camera ships that camera's plain snapshot either way.
|
|
20350
|
+
*/
|
|
20351
|
+
var NcLinkedCameraModeSchema = _enum(["mosaic", "each"]);
|
|
20289
20352
|
var NcMediaPolicySchema = object({
|
|
20290
20353
|
attach: _enum([
|
|
20291
20354
|
"best",
|
|
@@ -20331,7 +20394,9 @@ var NcMediaPolicySchema = object({
|
|
|
20331
20394
|
* A profile that is not assigned falls back to the cheapest, and the render
|
|
20332
20395
|
* reports which one actually ran.
|
|
20333
20396
|
*/
|
|
20334
|
-
profile: CamProfileSchema.optional()
|
|
20397
|
+
profile: CamProfileSchema.optional(),
|
|
20398
|
+
/** See {@link NcLinkedCameraModeSchema}. */
|
|
20399
|
+
linkedCameras: NcLinkedCameraModeSchema.optional()
|
|
20335
20400
|
});
|
|
20336
20401
|
/**
|
|
20337
20402
|
* Cooldown GRANULARITY over the subject's class — how much a fired
|
|
@@ -20451,7 +20516,7 @@ var NcRuleInputSchema = object({
|
|
|
20451
20516
|
cooldownSec: 60,
|
|
20452
20517
|
scope: "rule-device"
|
|
20453
20518
|
}),
|
|
20454
|
-
/** `{{var}}`
|
|
20519
|
+
/** `{{var}}` title/body. The variables are declared in `NC_TEMPLATE_VARS` (@camstack/types) and depend on the rule kind — see `templateVarsFor`. */
|
|
20455
20520
|
template: object({
|
|
20456
20521
|
title: string().max(500).optional(),
|
|
20457
20522
|
body: string().max(2e3).optional()
|
|
@@ -20631,6 +20696,22 @@ var NcRulePatchSchema = NcRuleInputSchema.partial().extend({
|
|
|
20631
20696
|
throttle: NcThrottleSchema.optional(),
|
|
20632
20697
|
priority: number().int().min(1).max(5).optional()
|
|
20633
20698
|
});
|
|
20699
|
+
var NcRuleClearableKeySchema = _enum([
|
|
20700
|
+
"template",
|
|
20701
|
+
"schedule",
|
|
20702
|
+
"targetUsers",
|
|
20703
|
+
"snoozeOptions",
|
|
20704
|
+
"snoozeAllowGlobal",
|
|
20705
|
+
"waitForEnhancement",
|
|
20706
|
+
"actions",
|
|
20707
|
+
"confirm",
|
|
20708
|
+
"groupIdleSec"
|
|
20709
|
+
]);
|
|
20710
|
+
var NcRuleUpdateInputSchema = object({
|
|
20711
|
+
ruleId: string(),
|
|
20712
|
+
patch: NcRulePatchSchema,
|
|
20713
|
+
clear: array(NcRuleClearableKeySchema).optional()
|
|
20714
|
+
});
|
|
20634
20715
|
/** A persisted rule. */
|
|
20635
20716
|
var NcRuleSchema = NcRuleInputSchema.extend({
|
|
20636
20717
|
id: string(),
|
|
@@ -20662,6 +20743,85 @@ var NcTestResultSchema = object({
|
|
|
20662
20743
|
className: string().optional(),
|
|
20663
20744
|
label: string().optional()
|
|
20664
20745
|
});
|
|
20746
|
+
/** The five rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts`
|
|
20747
|
+
* is the same union; the template-vars spec pins the two together. */
|
|
20748
|
+
var NcRuleKindSchema = _enum([
|
|
20749
|
+
"detection",
|
|
20750
|
+
"sensor",
|
|
20751
|
+
"occupancy",
|
|
20752
|
+
"sound",
|
|
20753
|
+
"system"
|
|
20754
|
+
]);
|
|
20755
|
+
/** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
|
|
20756
|
+
var NcTemplateFamilySchema = _enum([
|
|
20757
|
+
"rule",
|
|
20758
|
+
"timelapse",
|
|
20759
|
+
"summary"
|
|
20760
|
+
]);
|
|
20761
|
+
/** Which text field of that producer. `previewText` is the timelapse frame
|
|
20762
|
+
* caption, `captionText` the digest mosaic caption — both see fewer vars. */
|
|
20763
|
+
var NcTemplateFieldSchema = _enum([
|
|
20764
|
+
"title",
|
|
20765
|
+
"body",
|
|
20766
|
+
"previewText",
|
|
20767
|
+
"captionText"
|
|
20768
|
+
]);
|
|
20769
|
+
var NcTemplateVarGroupSchema = _enum([
|
|
20770
|
+
"subject",
|
|
20771
|
+
"place",
|
|
20772
|
+
"time",
|
|
20773
|
+
"rule",
|
|
20774
|
+
"occupancy",
|
|
20775
|
+
"sound",
|
|
20776
|
+
"sensor",
|
|
20777
|
+
"system",
|
|
20778
|
+
"digest",
|
|
20779
|
+
"ai"
|
|
20780
|
+
]);
|
|
20781
|
+
/**
|
|
20782
|
+
* ONE `{{var}}` a notification template may name.
|
|
20783
|
+
*
|
|
20784
|
+
* `families` / `fields` / `kinds` / `deliveries` / `systemEventKinds` say WHERE
|
|
20785
|
+
* it has a value; absent = no restriction on that axis. `systemEventKinds` is
|
|
20786
|
+
* read only for a `system` rule. `dynamic` declares a FAMILY of names
|
|
20787
|
+
* (`count_<class>`): the descriptor's `name` is the example member.
|
|
20788
|
+
*/
|
|
20789
|
+
var NcTemplateVarDescriptorSchema = object({
|
|
20790
|
+
name: string().regex(/^\w+$/),
|
|
20791
|
+
label: string(),
|
|
20792
|
+
description: string().optional(),
|
|
20793
|
+
example: string(),
|
|
20794
|
+
group: NcTemplateVarGroupSchema,
|
|
20795
|
+
families: array(NcTemplateFamilySchema).min(1),
|
|
20796
|
+
fields: array(NcTemplateFieldSchema).optional(),
|
|
20797
|
+
kinds: array(NcRuleKindSchema).optional(),
|
|
20798
|
+
deliveries: array(NcDeliverySchema).optional(),
|
|
20799
|
+
systemEventKinds: array(NcSystemEventKindSchema).optional(),
|
|
20800
|
+
dynamic: object({
|
|
20801
|
+
prefix: literal("count_"),
|
|
20802
|
+
from: literal("classes")
|
|
20803
|
+
}).optional()
|
|
20804
|
+
});
|
|
20805
|
+
/** Input to `previewTemplate` — the editor's own context, plus the draft text. */
|
|
20806
|
+
var NcTemplatePreviewInputSchema = object({
|
|
20807
|
+
context: object({
|
|
20808
|
+
family: NcTemplateFamilySchema,
|
|
20809
|
+
field: NcTemplateFieldSchema,
|
|
20810
|
+
kind: NcRuleKindSchema.optional(),
|
|
20811
|
+
delivery: NcDeliverySchema.optional(),
|
|
20812
|
+
systemEventKinds: array(NcSystemEventKindSchema).optional()
|
|
20813
|
+
}),
|
|
20814
|
+
template: object({
|
|
20815
|
+
title: string().max(500).optional(),
|
|
20816
|
+
body: string().max(2e3).optional()
|
|
20817
|
+
})
|
|
20818
|
+
});
|
|
20819
|
+
var NcTemplatePreviewSchema = object({
|
|
20820
|
+
title: string().nullable(),
|
|
20821
|
+
body: string().nullable(),
|
|
20822
|
+
/** Names the template uses that this context never fills — they render empty. */
|
|
20823
|
+
unknown: array(string())
|
|
20824
|
+
});
|
|
20665
20825
|
var NcConditionDescriptorSchema = object({
|
|
20666
20826
|
/** Field id inside `NcConditions` (or `'schedule'` for the rule-level group). */
|
|
20667
20827
|
id: string(),
|
|
@@ -20927,8 +21087,17 @@ object({
|
|
|
20927
21087
|
lastAt: number()
|
|
20928
21088
|
});
|
|
20929
21089
|
/**
|
|
20930
|
-
* The
|
|
20931
|
-
*
|
|
21090
|
+
* The per-mode lists, at most one per mode. Two lists for one mode have no
|
|
21091
|
+
* meaning a reader could agree on (union? the last one?), so they are refused
|
|
21092
|
+
* at the boundary rather than interpreted.
|
|
21093
|
+
*/
|
|
21094
|
+
var NcAlarmNonBlockingListSchema = array(object({
|
|
21095
|
+
mode: AlarmArmModeSchema,
|
|
21096
|
+
deviceIds: array(number().int()).max(200)
|
|
21097
|
+
})).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
|
|
21098
|
+
/**
|
|
21099
|
+
* The three durations the panel's state machine runs on, who hears about its
|
|
21100
|
+
* transitions, and which openings each mode tolerates.
|
|
20932
21101
|
*
|
|
20933
21102
|
* They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
|
|
20934
21103
|
* `alarm-panel` is `deviceNative` and its other provider mirrors somebody
|
|
@@ -20947,21 +21116,39 @@ var NcAlarmSettingsSchema = object({
|
|
|
20947
21116
|
* existed, and therefore what an untouched install keeps doing.
|
|
20948
21117
|
*/
|
|
20949
21118
|
triggeredDurationSec: number().int().min(0).max(3600),
|
|
20950
|
-
/** Send a notification when a mode takes effect. */
|
|
20951
|
-
announceArm: boolean(),
|
|
20952
21119
|
/**
|
|
20953
|
-
*
|
|
21120
|
+
* Deprecated by D630 — every transition is announced. Parsed, never read;
|
|
21121
|
+
* remove after one release. Optional so an older admin's patch (which still
|
|
21122
|
+
* sends it) parses rather than failing the whole save.
|
|
21123
|
+
*/
|
|
21124
|
+
announceArm: boolean().optional(),
|
|
21125
|
+
/**
|
|
21126
|
+
* Target ids from `notification-output`: every alarm transition goes here.
|
|
20954
21127
|
*
|
|
20955
|
-
* Explicit rather than "everyone": an
|
|
21128
|
+
* Explicit rather than "everyone": an alarm announcement is a household
|
|
20956
21129
|
* message, and broadcasting it to every configured endpoint (including a
|
|
20957
21130
|
* webhook wired to something else) is not a default anybody would choose.
|
|
20958
|
-
* Empty
|
|
20959
|
-
*
|
|
21131
|
+
* Empty sends nothing, and the server logs that — silence must be
|
|
21132
|
+
* attributable.
|
|
21133
|
+
*/
|
|
21134
|
+
announceTargets: array(string().min(1)).max(16),
|
|
21135
|
+
/**
|
|
21136
|
+
* Per arm mode, the covered devices whose being OPEN does not refuse the
|
|
21137
|
+
* arm (a window left ajar for the cat under `home`, say). At most one entry
|
|
21138
|
+
* per mode is meaningful. Defaulted to `[]` so a blob stored before D630
|
|
21139
|
+
* parses as "every opening blocks" — the behaviour it was written under.
|
|
20960
21140
|
*/
|
|
20961
|
-
|
|
21141
|
+
nonBlocking: NcAlarmNonBlockingListSchema.default([])
|
|
20962
21142
|
});
|
|
20963
|
-
/**
|
|
20964
|
-
|
|
21143
|
+
/**
|
|
21144
|
+
* Every field optional — a tab edits one control at a time.
|
|
21145
|
+
*
|
|
21146
|
+
* `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
|
|
21147
|
+
* remove an inner `.default()`, so a patch that never named the field would
|
|
21148
|
+
* parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
|
|
21149
|
+
* (the same trap {@link NcRulePatchSchema} documents).
|
|
21150
|
+
*/
|
|
21151
|
+
var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
|
|
20965
21152
|
/**
|
|
20966
21153
|
* What one arm mode actually arms, DERIVED from the enabled rules gated on it.
|
|
20967
21154
|
* Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
|
|
@@ -21006,6 +21193,36 @@ var NcAlarmModeCoverageSchema = object({
|
|
|
21006
21193
|
*/
|
|
21007
21194
|
skippedDevices: array(NcAlarmSkippedDeviceSchema).default([])
|
|
21008
21195
|
});
|
|
21196
|
+
/** One covered device that reads open, and whether that refuses the arm. */
|
|
21197
|
+
var NcAlarmOpeningSchema = object({
|
|
21198
|
+
deviceId: number().int(),
|
|
21199
|
+
name: string(),
|
|
21200
|
+
/** The device's state word as read (`open`, `unlocked`, …). */
|
|
21201
|
+
state: string(),
|
|
21202
|
+
/** False when the mode's non-blocking list names this device. */
|
|
21203
|
+
blocking: boolean()
|
|
21204
|
+
});
|
|
21205
|
+
/** A device the panel is currently ignoring, and which side of it it is on. */
|
|
21206
|
+
var NcAlarmExclusionViewSchema = object({
|
|
21207
|
+
deviceId: number().int(),
|
|
21208
|
+
name: string(),
|
|
21209
|
+
phase: _enum(["open", "closed"])
|
|
21210
|
+
});
|
|
21211
|
+
/** One mode's openings: devices read open, plus those whose state is unknown. */
|
|
21212
|
+
var NcAlarmModeOpeningsSchema = object({
|
|
21213
|
+
mode: AlarmArmModeSchema,
|
|
21214
|
+
devices: array(NcAlarmOpeningSchema),
|
|
21215
|
+
/** Covered devices whose state could not be read (D49): neither open nor closed. */
|
|
21216
|
+
unknown: array(number().int())
|
|
21217
|
+
});
|
|
21218
|
+
var NcAlarmLiveSchema = object({
|
|
21219
|
+
state: AlarmStateSchema,
|
|
21220
|
+
/** The mode being armed into (exit delay) or held; null when disarmed. */
|
|
21221
|
+
targetMode: AlarmArmModeSchema.nullable(),
|
|
21222
|
+
availableModes: array(AlarmArmModeSchema),
|
|
21223
|
+
openings: array(NcAlarmModeOpeningsSchema),
|
|
21224
|
+
exclusions: array(NcAlarmExclusionViewSchema)
|
|
21225
|
+
});
|
|
21009
21226
|
var NcAlarmConfigSchema = object({
|
|
21010
21227
|
/**
|
|
21011
21228
|
* The panel's device id, or null when this install has no panel (the ensure
|
|
@@ -21015,7 +21232,13 @@ var NcAlarmConfigSchema = object({
|
|
|
21015
21232
|
*/
|
|
21016
21233
|
deviceId: number().int().nullable(),
|
|
21017
21234
|
settings: NcAlarmSettingsSchema,
|
|
21018
|
-
coverage: array(NcAlarmModeCoverageSchema)
|
|
21235
|
+
coverage: array(NcAlarmModeCoverageSchema),
|
|
21236
|
+
/**
|
|
21237
|
+
* The panel as it is NOW — state, what each mode would find open, what is
|
|
21238
|
+
* excluded. Optional: a panel-less hub answers without it, and a client
|
|
21239
|
+
* must not read its absence as "nothing is open".
|
|
21240
|
+
*/
|
|
21241
|
+
live: NcAlarmLiveSchema.optional()
|
|
21019
21242
|
});
|
|
21020
21243
|
/**
|
|
21021
21244
|
* ONE rule's demand on ONE camera's clip ring.
|
|
@@ -21063,10 +21286,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
21063
21286
|
kind: "mutation",
|
|
21064
21287
|
auth: "admin",
|
|
21065
21288
|
caller: "required"
|
|
21066
|
-
}), method(object({
|
|
21067
|
-
ruleId: string(),
|
|
21068
|
-
patch: NcRulePatchSchema
|
|
21069
|
-
}), object({ rule: NcRuleSchema }), {
|
|
21289
|
+
}), method(NcRuleUpdateInputSchema, object({ rule: NcRuleSchema }), {
|
|
21070
21290
|
kind: "mutation",
|
|
21071
21291
|
auth: "admin",
|
|
21072
21292
|
caller: "required"
|
|
@@ -21094,7 +21314,10 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
21094
21314
|
}), method(object({}), object({
|
|
21095
21315
|
catalog: array(NcConditionDescriptorSchema),
|
|
21096
21316
|
taxonomy: NcTaxonomySchema.optional()
|
|
21097
|
-
})), method(object({
|
|
21317
|
+
})), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
|
|
21318
|
+
kind: "mutation",
|
|
21319
|
+
access: "view"
|
|
21320
|
+
}), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
21098
21321
|
kind: "mutation",
|
|
21099
21322
|
caller: "required"
|
|
21100
21323
|
}), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
|
|
@@ -25903,6 +26126,7 @@ var CAP_TO_KIND = {
|
|
|
25903
26126
|
"enum-sensor": "enum-sensor",
|
|
25904
26127
|
"event-emitter": "device-event",
|
|
25905
26128
|
"lock-control": "lock",
|
|
26129
|
+
cover: "cover",
|
|
25906
26130
|
switch: "switch",
|
|
25907
26131
|
button: "button",
|
|
25908
26132
|
doorbell: "doorbell"
|
|
@@ -26463,41 +26687,65 @@ var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object(
|
|
|
26463
26687
|
method(object({
|
|
26464
26688
|
location: StorageLocationRefSchema,
|
|
26465
26689
|
relativePath: string()
|
|
26466
|
-
}), string()), method(object({
|
|
26690
|
+
}), string(), { auth: "admin" }), method(object({
|
|
26467
26691
|
location: StorageLocationRefSchema,
|
|
26468
26692
|
relativePath: string(),
|
|
26469
26693
|
data: _instanceof(Uint8Array)
|
|
26470
|
-
}), _void(), {
|
|
26694
|
+
}), _void(), {
|
|
26695
|
+
kind: "mutation",
|
|
26696
|
+
auth: "admin"
|
|
26697
|
+
}), method(object({
|
|
26471
26698
|
location: StorageLocationRefSchema,
|
|
26472
26699
|
relativePath: string()
|
|
26473
|
-
}), _instanceof(Uint8Array)), method(object({
|
|
26700
|
+
}), _instanceof(Uint8Array), { auth: "admin" }), method(object({
|
|
26474
26701
|
location: StorageLocationRefSchema,
|
|
26475
26702
|
relativePath: string()
|
|
26476
|
-
}), boolean()), method(object({
|
|
26703
|
+
}), boolean(), { auth: "admin" }), method(object({
|
|
26477
26704
|
location: StorageLocationRefSchema,
|
|
26478
26705
|
prefix: string().optional()
|
|
26479
|
-
}), array(string()).readonly()), method(object({
|
|
26706
|
+
}), array(string()).readonly(), { auth: "admin" }), method(object({
|
|
26480
26707
|
location: StorageLocationRefSchema,
|
|
26481
26708
|
relativePath: string()
|
|
26482
|
-
}), _void(), {
|
|
26709
|
+
}), _void(), {
|
|
26710
|
+
kind: "mutation",
|
|
26711
|
+
auth: "admin"
|
|
26712
|
+
}), method(object({ location: StorageLocationRefSchema }), number().nullable()), method(object({
|
|
26483
26713
|
location: StorageLocationRefSchema,
|
|
26484
26714
|
relativePath: string(),
|
|
26485
26715
|
sizeBytes: number().optional()
|
|
26486
|
-
}), object({ uploadId: string() }), {
|
|
26716
|
+
}), object({ uploadId: string() }), {
|
|
26717
|
+
kind: "mutation",
|
|
26718
|
+
auth: "admin"
|
|
26719
|
+
}), method(object({
|
|
26487
26720
|
uploadId: string(),
|
|
26488
26721
|
offset: number(),
|
|
26489
26722
|
data: _instanceof(Uint8Array)
|
|
26490
|
-
}), _void(), {
|
|
26723
|
+
}), _void(), {
|
|
26724
|
+
kind: "mutation",
|
|
26725
|
+
auth: "admin"
|
|
26726
|
+
}), method(object({ uploadId: string() }), _void(), {
|
|
26727
|
+
kind: "mutation",
|
|
26728
|
+
auth: "admin"
|
|
26729
|
+
}), method(object({ uploadId: string() }), _void(), {
|
|
26730
|
+
kind: "mutation",
|
|
26731
|
+
auth: "admin"
|
|
26732
|
+
}), method(object({
|
|
26491
26733
|
location: StorageLocationRefSchema,
|
|
26492
26734
|
relativePath: string()
|
|
26493
26735
|
}), object({
|
|
26494
26736
|
downloadId: string(),
|
|
26495
26737
|
sizeBytes: number()
|
|
26496
|
-
}), {
|
|
26738
|
+
}), {
|
|
26739
|
+
kind: "mutation",
|
|
26740
|
+
auth: "admin"
|
|
26741
|
+
}), method(object({
|
|
26497
26742
|
downloadId: string(),
|
|
26498
26743
|
offset: number(),
|
|
26499
26744
|
length: number()
|
|
26500
|
-
}), _instanceof(Uint8Array)
|
|
26745
|
+
}), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
|
|
26746
|
+
kind: "mutation",
|
|
26747
|
+
auth: "admin"
|
|
26748
|
+
}), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
|
|
26501
26749
|
createdAt: true,
|
|
26502
26750
|
updatedAt: true
|
|
26503
26751
|
}), StorageLocationSchema, {
|
|
@@ -26762,6 +27010,58 @@ method(_void(), ProviderInfoSchema, { auth: "admin" }), method(object({ config:
|
|
|
26762
27010
|
kind: "mutation",
|
|
26763
27011
|
auth: "admin"
|
|
26764
27012
|
});
|
|
27013
|
+
/**
|
|
27014
|
+
* The signals a device can emit to WAKE its own stream.
|
|
27015
|
+
*
|
|
27016
|
+
* A camera whose stream is built on demand sleeps until something asks for it,
|
|
27017
|
+
* and "something" cannot be a consumer that is merely attached — a Frigate-style
|
|
27018
|
+
* puller holds a session open for ever, and treating that as demand would keep
|
|
27019
|
+
* a battery camera awake for ever, which is the whole thing the battery is for
|
|
27020
|
+
* (D173). So the wake has to come from the CAMERA: an event it noticed by
|
|
27021
|
+
* itself, with no stream running.
|
|
27022
|
+
*
|
|
27023
|
+
* ## The vocabulary is the PROVIDER'S, not ours
|
|
27024
|
+
*
|
|
27025
|
+
* Like `consumables`, this cap declares no vocabulary of its own. A provider
|
|
27026
|
+
* names each signal with a `code` it chooses and a `label` an operator reads.
|
|
27027
|
+
* Reolink offers motion and camera-native detection; another provider may offer
|
|
27028
|
+
* a tamper, a doorbell press, a PIR, or something no camera in this fleet has
|
|
27029
|
+
* yet. A fixed enum here would mean every new signal is a framework release.
|
|
27030
|
+
*
|
|
27031
|
+
* It is deliberately NOT derived from the caps a device already binds. Whether
|
|
27032
|
+
* a camera CAN push firmware motion is expressed by `motionSources` containing
|
|
27033
|
+
* `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
|
|
27034
|
+
* binding — but both answer "what drives the detection pipeline", which is a
|
|
27035
|
+
* different question from "what may wake a sleeping stream". A camera can do
|
|
27036
|
+
* the first and not be trusted with the second, and the operator picks per
|
|
27037
|
+
* camera. Two questions, two authorities.
|
|
27038
|
+
*
|
|
27039
|
+
* ## Availability is not permission
|
|
27040
|
+
*
|
|
27041
|
+
* `listSignals` says what the device CAN emit. Whether a given signal actually
|
|
27042
|
+
* wakes the stream is the operator's per-camera choice, held by the broker
|
|
27043
|
+
* alongside the cooldown — see the stream-broker cap's wake settings. A
|
|
27044
|
+
* provider declaring a signal is not a provider enabling it.
|
|
27045
|
+
*/
|
|
27046
|
+
/** One signal a device can emit. */
|
|
27047
|
+
var StreamSignalSchema = object({
|
|
27048
|
+
/** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
|
|
27049
|
+
code: string().min(1),
|
|
27050
|
+
/** What an operator reads in the picker. The provider's own wording. */
|
|
27051
|
+
label: string().min(1),
|
|
27052
|
+
/**
|
|
27053
|
+
* Whether the provider recommends this signal ON when a camera is first set
|
|
27054
|
+
* up. A provider knows which of its signals are cheap and reliable; an
|
|
27055
|
+
* operator should not have to discover that by trial. Reolink recommends
|
|
27056
|
+
* both of its own.
|
|
27057
|
+
*/
|
|
27058
|
+
recommended: boolean()
|
|
27059
|
+
});
|
|
27060
|
+
object({
|
|
27061
|
+
signals: array(StreamSignalSchema),
|
|
27062
|
+
lastFetchedAt: number()
|
|
27063
|
+
});
|
|
27064
|
+
Object.values(DeviceType), method(_void(), array(StreamSignalSchema).readonly());
|
|
26765
27065
|
/** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
|
|
26766
27066
|
var ProfileSettingsSchemaBridge = unknown().nullable();
|
|
26767
27067
|
var ProfileSettingsBagSchema = record(string(), unknown());
|
|
@@ -27745,25 +28045,44 @@ var ClipStreamDialSchema = object({
|
|
|
27745
28045
|
audioReason: ClipStreamAudioReasonSchema.optional()
|
|
27746
28046
|
});
|
|
27747
28047
|
/**
|
|
27748
|
-
* The playback rates
|
|
28048
|
+
* The playback rates the broker can pace over media it HOLDS, ascending,
|
|
28049
|
+
* always with `1` — and the WIDEST ladder there is. It is the domain the
|
|
28050
|
+
* broker's `clampPlaybackRate` is derived from, at both ends.
|
|
27749
28051
|
*
|
|
27750
28052
|
* These are the BROKER's: it re-paces frames it has already demuxed — the
|
|
27751
28053
|
* `MonotonicClock` divides source elapsed time by the factor and the pacer
|
|
27752
|
-
* pushes that much faster
|
|
27753
|
-
*
|
|
27754
|
-
*
|
|
27755
|
-
*
|
|
27756
|
-
*
|
|
27757
|
-
* `<playSpeed>`
|
|
27758
|
-
*
|
|
27759
|
-
*
|
|
27760
|
-
*
|
|
27761
|
-
*
|
|
27762
|
-
*
|
|
27763
|
-
*
|
|
27764
|
-
*
|
|
27765
|
-
*
|
|
27766
|
-
*
|
|
28054
|
+
* pushes that much faster. `1.5` is in it because a re-pacer has no reason to
|
|
28055
|
+
* refuse it.
|
|
28056
|
+
*
|
|
28057
|
+
* **`8` is here since D621, and it is the pacer's, not a camera's.** D620
|
|
28058
|
+
* measured the thing that was assumed for a year and was never true: a
|
|
28059
|
+
* Reolink `<playSpeed>` does not time-compress anything. At 1, 2, 4 and 8 the
|
|
28060
|
+
* transfer is byte-identical — same 947 472 bytes, same 594 access units,
|
|
28061
|
+
* same 39.855 s presentation span, same 624 AAC frames — and only the wall
|
|
28062
|
+
* clock moves. It buys SUPPLY, never speed. So there was never a second
|
|
28063
|
+
* mechanism to wire for 8×: there is one, this one, and what it needs is
|
|
28064
|
+
* media in hand and a clamp that does not move the number.
|
|
28065
|
+
*
|
|
28066
|
+
* **`16` is deliberately NOT here, and will not join by widening this array.**
|
|
28067
|
+
* The camera's `playSpeed 16` is not a rate at all but an I-frame-only MODE —
|
|
28068
|
+
* measured on 592: 10 access units for a whole 36.3 s sub clip, 20 for the
|
|
28069
|
+
* main twin at a 2 025 ms median spacing, and no audio track. That is
|
|
28070
|
+
* different CONTENT, a stream of its own, and the honest broker-side twin of
|
|
28071
|
+
* it would be a pacer that DROPS whole GOPs rather than one that pushes 16×
|
|
28072
|
+
* the bitrate at a live WebRTC track. Neither exists. A `16` in this array
|
|
28073
|
+
* today would be a rate accepted and delivered at 8 — D612's defect, which
|
|
28074
|
+
* this list exists to end, one number further along.
|
|
28075
|
+
*
|
|
28076
|
+
* Every entry must survive the broker's `clampPlaybackRate` unchanged. Since
|
|
28077
|
+
* D621 that is structural rather than a discipline: the clamp reads this
|
|
28078
|
+
* array's own ends. What still has to be proved behaviourally — and is, in
|
|
28079
|
+
* the broker's `clip-stream-feeder.spec` — is that the PACER really empties a
|
|
28080
|
+
* clip `rate` times faster, because a clamp agreeing with a ladder is a
|
|
28081
|
+
* constant compared against itself.
|
|
28082
|
+
*
|
|
28083
|
+
* Narrower ladders exist for transports whose SUPPLY is bounded; they are
|
|
28084
|
+
* subsets of this one ({@link CLIP_STREAM_SOURCE_RATES},
|
|
28085
|
+
* {@link CLIP_REALTIME_SOURCE_RATES}).
|
|
27767
28086
|
*/
|
|
27768
28087
|
var CLIP_BROKER_PACED_RATES = [
|
|
27769
28088
|
.25,
|
|
@@ -27771,7 +28090,8 @@ var CLIP_BROKER_PACED_RATES = [
|
|
|
27771
28090
|
1,
|
|
27772
28091
|
1.5,
|
|
27773
28092
|
2,
|
|
27774
|
-
4
|
|
28093
|
+
4,
|
|
28094
|
+
8
|
|
27775
28095
|
];
|
|
27776
28096
|
/**
|
|
27777
28097
|
* What a surface may DRAW for this provider's clips — the answer to
|
|
@@ -28297,7 +28617,67 @@ var WebrtcLadderDecisionSchema = object({
|
|
|
28297
28617
|
serverSending: boolean().nullable(),
|
|
28298
28618
|
/** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
|
|
28299
28619
|
* must not read like one from this second. */
|
|
28300
|
-
at: number()
|
|
28620
|
+
at: number(),
|
|
28621
|
+
/** The broker's own cause when it is finer than `cause` can carry, e.g.
|
|
28622
|
+
* `trial-revert` (reported as `stall`/`loss` for older consumers). Optional:
|
|
28623
|
+
* an older server strips it instead of rejecting the answer. */
|
|
28624
|
+
causeDetail: string().optional()
|
|
28625
|
+
});
|
|
28626
|
+
var WebrtcClimbHoldSchema = object({
|
|
28627
|
+
/** `stall-building` — the client receives ≪ what is sent; `delivery` — the
|
|
28628
|
+
* current rung is not landing whole; `no-evidence` — no receiver feedback;
|
|
28629
|
+
* `warming` — healthy, accumulating the streak; `cooldown` — too soon after
|
|
28630
|
+
* a change; `capacity` — a MEASURED probe below the next rung's bar;
|
|
28631
|
+
* `try-unclean` — no capacity evidence, and the path is not clean enough to
|
|
28632
|
+
* try; `try-backoff` — a try reverted, waiting out its backoff; `trial` —
|
|
28633
|
+
* this rung IS a try, reverting if it does not land; `suspended` —
|
|
28634
|
+
* recorded playback on the track. */
|
|
28635
|
+
code: string(),
|
|
28636
|
+
/** The rung a climb would go to. */
|
|
28637
|
+
nextTier: CamProfileSchema,
|
|
28638
|
+
/** Bytes received ÷ bytes sent over the delivery window (≥ one GOP). */
|
|
28639
|
+
deliveryRatio: number().nullable(),
|
|
28640
|
+
deliveryWindowMs: number().nullable(),
|
|
28641
|
+
/** The ratio a climb requires. */
|
|
28642
|
+
climbDeliveryRatio: number(),
|
|
28643
|
+
/** The receiver's bandwidth estimate (REMB), kbps. Capped by what is sent. */
|
|
28644
|
+
capacityKbps: number().nullable(),
|
|
28645
|
+
/** The viewer's downlink probe for this session, kbps. */
|
|
28646
|
+
probeKbps: number().nullable(),
|
|
28647
|
+
/** The next rung's measured bitrate, kbps. */
|
|
28648
|
+
nextTierKbps: number().nullable(),
|
|
28649
|
+
capacityMargin: number(),
|
|
28650
|
+
/** The capacity a climb to `nextTier` needs, kbps (bitrate × margin, or the
|
|
28651
|
+
* client ladder threshold when the rung has measured nothing). */
|
|
28652
|
+
neededKbps: number().nullable(),
|
|
28653
|
+
/** For `try-unclean`: which clean-path condition failed (a `WebrtcTryBlocker`,
|
|
28654
|
+
* carried as a string for the same reason as `code`). */
|
|
28655
|
+
tryBlockedBy: string().nullable(),
|
|
28656
|
+
/** The delivery ratio and the loss ratio a try requires / must stay under.
|
|
28657
|
+
* Optional, like every field added to this schema after it first shipped:
|
|
28658
|
+
* the router validates output, so a REQUIRED new field makes a newer server
|
|
28659
|
+
* reject an older broker's whole answer (review m1). */
|
|
28660
|
+
tryDeliveryRatio: number().optional(),
|
|
28661
|
+
tryMaxLossRatio: number().optional(),
|
|
28662
|
+
/** For `try-backoff`: time until the next try, ms. */
|
|
28663
|
+
retryInMs: number().nullable(),
|
|
28664
|
+
/** The last try that reverted in this viewer session. */
|
|
28665
|
+
lastRevert: object({
|
|
28666
|
+
tier: CamProfileSchema,
|
|
28667
|
+
deliveryRatio: number().nullable(),
|
|
28668
|
+
stalled: boolean(),
|
|
28669
|
+
at: number()
|
|
28670
|
+
}).nullable(),
|
|
28671
|
+
/** For `trial`: how long this rung has been on trial, and for how long it must hold. */
|
|
28672
|
+
trialForMs: number().nullable(),
|
|
28673
|
+
trialMs: number().nullable(),
|
|
28674
|
+
healthyForMs: number().nullable(),
|
|
28675
|
+
requiredHealthyMs: number().nullable(),
|
|
28676
|
+
/** Epoch ms this hold's code began. */
|
|
28677
|
+
since: number(),
|
|
28678
|
+
/** The same one-line account the broker logs, e.g.
|
|
28679
|
+
* `held: delivery 0.97 over 4.0s ok, REMB 480 kbps < mid 1800 kbps × 1.2`. */
|
|
28680
|
+
summary: string()
|
|
28301
28681
|
});
|
|
28302
28682
|
/** Per-tier count of MEASURED downgrades this viewer session has suffered. */
|
|
28303
28683
|
var WebrtcTierFailureSchema = object({
|
|
@@ -28361,7 +28741,12 @@ var WebrtcSessionDebugSchema = object({
|
|
|
28361
28741
|
tierFailures: array(WebrtcTierFailureSchema),
|
|
28362
28742
|
/** Successful promotions in this viewer session (drives the progressive
|
|
28363
28743
|
* healthy timer). Null when no ladder is running. */
|
|
28364
|
-
upgradeCount: number().nullable()
|
|
28744
|
+
upgradeCount: number().nullable(),
|
|
28745
|
+
/** Why the ladder is not climbing right now. Null when no ladder runs, it
|
|
28746
|
+
* is at the top rung, or it just moved. Optional because this output is
|
|
28747
|
+
* validated by the router: a server carrying this schema must still accept
|
|
28748
|
+
* the answer of a stream-broker deployed before the field existed. */
|
|
28749
|
+
climbHold: WebrtcClimbHoldSchema.nullable().optional()
|
|
28365
28750
|
});
|
|
28366
28751
|
/**
|
|
28367
28752
|
* WebRTC session — device-scoped facade over the system `webrtc` cap.
|
|
@@ -29442,6 +29827,143 @@ DeviceType.Camera, method(object({ deviceId: number() }), CameraCredentialsSchem
|
|
|
29442
29827
|
kind: "query",
|
|
29443
29828
|
auth: "admin"
|
|
29444
29829
|
});
|
|
29830
|
+
/**
|
|
29831
|
+
* camera-grid-layout — the geometry of a COMPOSITE camera, on the camera's own
|
|
29832
|
+
* page.
|
|
29833
|
+
*
|
|
29834
|
+
* ## Why this is a capability and not an addon settings schema
|
|
29835
|
+
*
|
|
29836
|
+
* It was one, and it did not render. The addon declared the editor as a
|
|
29837
|
+
* `type: 'widget'` field inside its own `deviceSettingsSchema()`; the hub
|
|
29838
|
+
* returned that section correctly and `ConfigFormField` renders `type:'widget'`
|
|
29839
|
+
* perfectly well — and nothing ever asked camera-grid for it. The Cluster →
|
|
29840
|
+
* Pipeline → Device Overrides page interrogates a HAND-WRITTEN list of four
|
|
29841
|
+
* addons (`PIPELINE_CLUSTER_DEVICE_ADDONS`), whose own comment says an addon
|
|
29842
|
+
* not on it "falls off silently".
|
|
29843
|
+
*
|
|
29844
|
+
* Adding a fifth name to that list would have been the wrong fix twice over:
|
|
29845
|
+
* that page is per-camera DETECTION tuning, and a grid's geometry belongs
|
|
29846
|
+
* beside PTZ and motion zones on the camera itself. The device page is
|
|
29847
|
+
* BINDING-driven (D12), so the way in is a capability bound to the device —
|
|
29848
|
+
* and this cap carries its section the way `recording` does, by RETURNING it
|
|
29849
|
+
* from `getDeviceSettingsContribution`.
|
|
29850
|
+
*
|
|
29851
|
+
* Seven other widgets are still declared the other way, through a
|
|
29852
|
+
* `deviceConfig.ui` block the framework derives a section from. That route
|
|
29853
|
+
* gives the addon no say in where its own panel lands and no way to decline
|
|
29854
|
+
* for a device the panel does not suit, which is why this one does not use it.
|
|
29855
|
+
*
|
|
29856
|
+
* ## Why one addon may implement it
|
|
29857
|
+
*
|
|
29858
|
+
* It is a device-scoped NATIVE cap, registered by the grid camera device
|
|
29859
|
+
* itself. Nothing else declares a composite camera, so nothing else has a
|
|
29860
|
+
* layout — and the device-scoped route means the widget asks THE camera, not
|
|
29861
|
+
* "the camera-grid addon", which is what let the old custom-action pair be
|
|
29862
|
+
* reached only by a caller that already knew the addon id.
|
|
29863
|
+
*
|
|
29864
|
+
* ## The tab
|
|
29865
|
+
*
|
|
29866
|
+
* `streaming`, not a top-tab of its own. A grid's geometry IS what its stream
|
|
29867
|
+
* is, so the Streaming tab is where it belongs; a `grid` top-tab would need an
|
|
29868
|
+
* entry in `WELL_KNOWN_TAB_MAP` or the device page renders the raw id as the
|
|
29869
|
+
* label (measured on the robot camera, 2026-09-06 — a tab called "navigation"
|
|
29870
|
+
* next to "PTZ").
|
|
29871
|
+
*/
|
|
29872
|
+
/** A rectangle in normalized [0,1] coordinates of whatever contains it. */
|
|
29873
|
+
var GridNormalizedRectSchema = object({
|
|
29874
|
+
x: number().min(0).max(1),
|
|
29875
|
+
y: number().min(0).max(1),
|
|
29876
|
+
width: number().gt(0).max(1),
|
|
29877
|
+
height: number().gt(0).max(1)
|
|
29878
|
+
});
|
|
29879
|
+
/**
|
|
29880
|
+
* One source camera, the part of its picture taken, and where that part lands.
|
|
29881
|
+
*
|
|
29882
|
+
* Both rectangles are NORMALIZED (D519): a source camera can change resolution
|
|
29883
|
+
* — a profile switch, a firmware update, a substream that comes back different
|
|
29884
|
+
* — and a stored PIXEL rectangle would quietly start cutting the wrong region,
|
|
29885
|
+
* which is the class of bug nobody files.
|
|
29886
|
+
*/
|
|
29887
|
+
var GridLayoutCellSchema = object({
|
|
29888
|
+
deviceId: number().int().positive(),
|
|
29889
|
+
/** The part of the SOURCE taken, normalized against the source. */
|
|
29890
|
+
source: GridNormalizedRectSchema,
|
|
29891
|
+
/** Where it lands, normalized against the CANVAS. */
|
|
29892
|
+
cell: GridNormalizedRectSchema
|
|
29893
|
+
});
|
|
29894
|
+
/**
|
|
29895
|
+
* Which profiles this grid can actually compose, and why not.
|
|
29896
|
+
*
|
|
29897
|
+
* A grid's `high` composes its sources' `high` and its `low` their `low`, so a
|
|
29898
|
+
* profile is on offer only when EVERY source can serve it. The refusal NAMES
|
|
29899
|
+
* the sources, because "this grid has no low" is not a finding — "615 has no
|
|
29900
|
+
* low" is, and it is the one an operator can act on.
|
|
29901
|
+
*/
|
|
29902
|
+
var GridProfileOfferSchema = object({
|
|
29903
|
+
profile: _enum([
|
|
29904
|
+
"high",
|
|
29905
|
+
"mid",
|
|
29906
|
+
"low"
|
|
29907
|
+
]),
|
|
29908
|
+
offered: boolean(),
|
|
29909
|
+
/** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
|
|
29910
|
+
missingSources: array(number().int().positive()),
|
|
29911
|
+
/**
|
|
29912
|
+
* The canvas this profile composes onto, `WxH`, or empty when it is not
|
|
29913
|
+
* offered. DERIVED from the cells and the sources' own size at this profile —
|
|
29914
|
+
* it is reported because nothing else in the system would ever say what the
|
|
29915
|
+
* grid came out as, and because it is the number an operator would otherwise
|
|
29916
|
+
* expect to type.
|
|
29917
|
+
*/
|
|
29918
|
+
canvas: string(),
|
|
29919
|
+
/**
|
|
29920
|
+
* Whether this profile is PUBLISHED, of the ones the grid could serve.
|
|
29921
|
+
*
|
|
29922
|
+
* A grid's `high` is composed of its sources' `high`, so on a 4K fleet it is
|
|
29923
|
+
* a 4K canvas built from 4K decodes — something to opt into, not something a
|
|
29924
|
+
* viewer's adaptive should be handed by climbing to the top rung it can see.
|
|
29925
|
+
* Default is `mid` + `low`.
|
|
29926
|
+
*/
|
|
29927
|
+
published: boolean()
|
|
29928
|
+
});
|
|
29929
|
+
var GridLayoutViewSchema = object({
|
|
29930
|
+
/** The persisted grid row this camera was declared from. */
|
|
29931
|
+
instanceId: string(),
|
|
29932
|
+
deviceId: number().int().nonnegative(),
|
|
29933
|
+
name: string(),
|
|
29934
|
+
/**
|
|
29935
|
+
* NO canvas size. A grid's resolution is not authored: each profile derives
|
|
29936
|
+
* its own from the cells and its sources' dimensions. The two numbers that
|
|
29937
|
+
* used to be here were a text field that silently decided both how much the
|
|
29938
|
+
* composite cost and how sharp it was — see `profiles[].canvas` for what it
|
|
29939
|
+
* came out as.
|
|
29940
|
+
*/
|
|
29941
|
+
fps: number().int(),
|
|
29942
|
+
cells: array(GridLayoutCellSchema),
|
|
29943
|
+
/** What the catalog will publish, and what it refuses to. Read-only. */
|
|
29944
|
+
profiles: array(GridProfileOfferSchema)
|
|
29945
|
+
});
|
|
29946
|
+
var GridLayoutPatchSchema = object({
|
|
29947
|
+
deviceId: number().int().nonnegative(),
|
|
29948
|
+
name: string().min(1).max(160).optional(),
|
|
29949
|
+
fps: number().int().min(1).max(60).optional(),
|
|
29950
|
+
/** Which profiles to publish. See `GridProfileOffer.published`. */
|
|
29951
|
+
publishedProfiles: array(_enum([
|
|
29952
|
+
"high",
|
|
29953
|
+
"mid",
|
|
29954
|
+
"low"
|
|
29955
|
+
])).max(3).optional(),
|
|
29956
|
+
/**
|
|
29957
|
+
* The whole cell list at once. A per-cell patch would need an ordering the
|
|
29958
|
+
* editor does not have, and a half-applied layout is a picture nobody asked
|
|
29959
|
+
* for.
|
|
29960
|
+
*/
|
|
29961
|
+
cells: array(GridLayoutCellSchema).max(16)
|
|
29962
|
+
});
|
|
29963
|
+
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), GridLayoutViewSchema.nullable(), { auth: "admin" }), method(GridLayoutPatchSchema, GridLayoutViewSchema, {
|
|
29964
|
+
kind: "mutation",
|
|
29965
|
+
auth: "admin"
|
|
29966
|
+
});
|
|
29445
29967
|
object({
|
|
29446
29968
|
detected: boolean(),
|
|
29447
29969
|
/** Ms epoch of the last transition. 0 if never observed. */
|
|
@@ -29915,307 +30437,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), DayNightOptionsSchema)
|
|
|
29915
30437
|
auth: "admin"
|
|
29916
30438
|
});
|
|
29917
30439
|
/**
|
|
29918
|
-
* Vendor-neutral **onboard** recording + storage cap — what the CAMERA
|
|
29919
|
-
* writes to the CAMERA's own card, on the camera's own schedule.
|
|
29920
|
-
*
|
|
29921
|
-
* This is NOT `recording.cap.ts`. That one is CamStack's recorder: our
|
|
29922
|
-
* footage ledger, our storage locations, our retention. This one has a
|
|
29923
|
-
* different authority — the camera's firmware — and per D62 it stores
|
|
29924
|
-
* nothing of its own. Every value here is read from the camera and every
|
|
29925
|
-
* write goes back to the camera; there is no CamStack-side mirror that
|
|
29926
|
-
* could disagree with the device.
|
|
29927
|
-
*
|
|
29928
|
-
* ## One shape, two firmwares
|
|
29929
|
-
*
|
|
29930
|
-
* Measured 2026-09-22 against the live fleet:
|
|
29931
|
-
*
|
|
29932
|
-
* | fact | Hikvision (ISAPI) | Reolink (Baichuan) |
|
|
29933
|
-
* | --- | --- | --- |
|
|
29934
|
-
* | storage | `ContentMgmt/Storage` `<hdd>` rows: status, capacity, freeSpace (MB) | `getHddInfoList` (cmd 102): `mount`, `format`, `capacity` GB + `capacityM` MB remainder |
|
|
29935
|
-
* | tracks | several (101 **and** 103 on both 1436 and 3833), each with its own schedule | one per channel |
|
|
29936
|
-
* | schedule | per track, 7 `ScheduleAction` blocks: DayOfWeek + TimeOfDay range + ONE `ActionRecordingMode` | per trigger type, a 168-char weekly HOUR mask |
|
|
29937
|
-
* | triggers | `CMR`, `MOTION` | `Normal`, `MD`, `people`, `vehicle`, `dog_cat`, `crossline`, `intrude`, `loitering` |
|
|
29938
|
-
* | pre-record | `PreRecordTimeSeconds` | `preRecordTime` |
|
|
29939
|
-
* | post-record | `PostRecordTimeSeconds` | `recordDelayTime` |
|
|
29940
|
-
* | overwrite | per track `LoopEnable` | `cycle`, with `cycleList` enumerating the accepted values |
|
|
29941
|
-
* | segment length | not exposed on V5.7.1 | `packageTime` (minutes) |
|
|
29942
|
-
*
|
|
29943
|
-
* The two schedule models look different and are the same thing in
|
|
29944
|
-
* different coordinates: both answer "for this trigger, during which
|
|
29945
|
-
* weekly windows does the camera record". {@link RecordWindow} is that
|
|
29946
|
-
* question in one shape — Hikvision's ranges map straight onto it,
|
|
29947
|
-
* Reolink's mask expands into hour-aligned windows.
|
|
29948
|
-
*
|
|
29949
|
-
* ## Union, not intersection
|
|
29950
|
-
*
|
|
29951
|
-
* **The same fields exist on every camera.** What differs per device is
|
|
29952
|
-
* which VALUES that device accepts, and that is what {@link
|
|
29953
|
-
* RecordingOnboardOptions} reports — a `{ readable, writable, reason }`
|
|
29954
|
-
* per field plus the schedule's own limits. A control a camera cannot
|
|
29955
|
-
* honour is rendered DISABLED WITH ITS REASON, never missing and never
|
|
29956
|
-
* dead: disabled must not look like broken.
|
|
29957
|
-
*
|
|
29958
|
-
* ## Refusal by name
|
|
29959
|
-
*
|
|
29960
|
-
* A write a camera cannot honour is refused with a sentence the operator
|
|
29961
|
-
* can read — never accepted and dropped. Both providers refuse through
|
|
29962
|
-
* {@link describeOnboardRefusal}, so the vocabulary is one function and
|
|
29963
|
-
* one test, not two hand-written vendor opinions.
|
|
29964
|
-
*
|
|
29965
|
-
* Follows the D14 `deviceConfig` archetype (see `stream-params.cap.ts`):
|
|
29966
|
-
* `getOptions` advertises per-camera availability, `getStatus` (auto-
|
|
29967
|
-
* injected from `status`) reports the live values, and a single
|
|
29968
|
-
* `setSettings` mutation applies a partial change. No hand-written
|
|
29969
|
-
* settings-contribution methods.
|
|
29970
|
-
*/
|
|
29971
|
-
/**
|
|
29972
|
-
* What makes the camera start recording during a window.
|
|
29973
|
-
*
|
|
29974
|
-
* The union of both vendors' vocabularies. `continuous` is Hikvision's
|
|
29975
|
-
* `CMR` and Reolink's `Normal`; `motion` is `MOTION` / `MD`. The
|
|
29976
|
-
* object-class triggers are Reolink-only today and the smart-event ones
|
|
29977
|
-
* (`lineCrossing`, `intrusion`, `loitering`) are Reolink-only on the
|
|
29978
|
-
* firmwares measured — a camera that cannot record on a trigger simply
|
|
29979
|
-
* does not list it in `options.schedule.triggers`, and a window naming
|
|
29980
|
-
* it is REFUSED, not dropped.
|
|
29981
|
-
*/
|
|
29982
|
-
var RecordTriggerSchema = _enum([
|
|
29983
|
-
"continuous",
|
|
29984
|
-
"motion",
|
|
29985
|
-
"person",
|
|
29986
|
-
"vehicle",
|
|
29987
|
-
"animal",
|
|
29988
|
-
"lineCrossing",
|
|
29989
|
-
"intrusion",
|
|
29990
|
-
"loitering",
|
|
29991
|
-
"alarmInput"
|
|
29992
|
-
]);
|
|
29993
|
-
/**
|
|
29994
|
-
* One weekly recording window: "on `day`, from `startMinute` to
|
|
29995
|
-
* `endMinute`, record on `trigger`".
|
|
29996
|
-
*
|
|
29997
|
-
* `day` is 0 = Monday … 6 = Sunday (ISO order, which is also the order
|
|
29998
|
-
* both firmwares enumerate). Minutes are local camera time since
|
|
29999
|
-
* midnight; `endMinute` may be 1440, meaning end of day — that is
|
|
30000
|
-
* Hikvision's literal `24:00` and Reolink's 24th mask slot, and
|
|
30001
|
-
* collapsing it to 0 would turn a whole-day window into an empty one.
|
|
30002
|
-
*/
|
|
30003
|
-
var RecordWindowSchema = object({
|
|
30004
|
-
trigger: RecordTriggerSchema,
|
|
30005
|
-
day: number().int().min(0).max(6),
|
|
30006
|
-
startMinute: number().int().min(0).max(1439),
|
|
30007
|
-
endMinute: number().int().min(1).max(1440)
|
|
30008
|
-
});
|
|
30009
|
-
/** Status of one physical volume, as the camera itself describes it. */
|
|
30010
|
-
var OnboardStorageVolumeSchema = object({
|
|
30011
|
-
/** The camera's own id for the volume (`hdd/id`, Reolink `HddInfo/number`). */
|
|
30012
|
-
id: string(),
|
|
30013
|
-
/** The camera's own name for it, when it gives one (`hddName`). */
|
|
30014
|
-
label: string().optional(),
|
|
30015
|
-
status: _enum([
|
|
30016
|
-
"ok",
|
|
30017
|
-
"unformatted",
|
|
30018
|
-
"error",
|
|
30019
|
-
"offline",
|
|
30020
|
-
"unknown"
|
|
30021
|
-
]),
|
|
30022
|
-
/**
|
|
30023
|
-
* Total size in MB, or **null when the camera did not say**.
|
|
30024
|
-
*
|
|
30025
|
-
* Never 0 for an unreadable value: a measurement that failed is not a
|
|
30026
|
-
* measurement (D393), and a card whose size is unknown must not be
|
|
30027
|
-
* rendered as a card of size zero.
|
|
30028
|
-
*/
|
|
30029
|
-
capacityMb: number().nullable(),
|
|
30030
|
-
/**
|
|
30031
|
-
* Free space in MB, or null when unknown.
|
|
30032
|
-
*
|
|
30033
|
-
* **Not a proxy for "has footage".** Measured 2026-09-22: 1436 and
|
|
30034
|
-
* 1439 both report exactly 11776 MB free — the fixed reserve a looping
|
|
30035
|
-
* card converges on once it has wrapped. At loop steady state the
|
|
30036
|
-
* number is identical whether the camera recorded yesterday or stopped
|
|
30037
|
-
* a month ago.
|
|
30038
|
-
*/
|
|
30039
|
-
freeMb: number().nullable(),
|
|
30040
|
-
/** True when the camera reports the volume writable (`property` RW). */
|
|
30041
|
-
writable: boolean().optional()
|
|
30042
|
-
});
|
|
30043
|
-
object({
|
|
30044
|
-
storage: discriminatedUnion("kind", [
|
|
30045
|
-
object({
|
|
30046
|
-
kind: literal("present"),
|
|
30047
|
-
volumes: array(OnboardStorageVolumeSchema)
|
|
30048
|
-
}),
|
|
30049
|
-
object({
|
|
30050
|
-
kind: literal("absent"),
|
|
30051
|
-
reason: string()
|
|
30052
|
-
}),
|
|
30053
|
-
object({
|
|
30054
|
-
kind: literal("unknown"),
|
|
30055
|
-
reason: string()
|
|
30056
|
-
})
|
|
30057
|
-
]),
|
|
30058
|
-
tracks: array(object({
|
|
30059
|
-
id: string(),
|
|
30060
|
-
enabled: boolean(),
|
|
30061
|
-
isVideo: boolean(),
|
|
30062
|
-
/** From the camera's own track description. Null when it does not say. */
|
|
30063
|
-
codec: string().nullable(),
|
|
30064
|
-
resolution: string().nullable(),
|
|
30065
|
-
/** Per-track overwrite flag, where the firmware keeps it per track. */
|
|
30066
|
-
overwriteWhenFull: boolean().nullable()
|
|
30067
|
-
})),
|
|
30068
|
-
/**
|
|
30069
|
-
* The track the write path targets — the enabled VIDEO one. Null when
|
|
30070
|
-
* no track could be identified, which is itself a refusal reason.
|
|
30071
|
-
*/
|
|
30072
|
-
primaryTrackId: string().nullable(),
|
|
30073
|
-
/** Master "record to the card at all" switch. */
|
|
30074
|
-
enabled: boolean().nullable(),
|
|
30075
|
-
overwriteWhenFull: boolean().nullable(),
|
|
30076
|
-
preRecordSec: number().nullable(),
|
|
30077
|
-
postRecordSec: number().nullable(),
|
|
30078
|
-
/** Length of one recorded file, in minutes. */
|
|
30079
|
-
segmentMinutes: number().nullable(),
|
|
30080
|
-
/** The primary track's weekly windows, flattened. */
|
|
30081
|
-
windows: array(RecordWindowSchema),
|
|
30082
|
-
/**
|
|
30083
|
-
* How many windows the camera described that CamStack could NOT read —
|
|
30084
|
-
* an unrecognised trigger, an unparseable clock, a weekday it does not
|
|
30085
|
-
* name.
|
|
30086
|
-
*
|
|
30087
|
-
* A dropped window is work the reader threw away, and a schedule that
|
|
30088
|
-
* silently shows fewer rows than the camera holds is how an operator
|
|
30089
|
-
* saves back a schedule shorter than the one they were looking at
|
|
30090
|
-
* (D391). Non-zero means the window list is INCOMPLETE and a write
|
|
30091
|
-
* that replaces it would delete what was not shown — which is why a
|
|
30092
|
-
* provider reporting a non-zero count also reports the schedule as not
|
|
30093
|
-
* writable.
|
|
30094
|
-
*/
|
|
30095
|
-
unreadableWindows: number(),
|
|
30096
|
-
/**
|
|
30097
|
-
* The camera is scheduled to record and has NO usable storage.
|
|
30098
|
-
*
|
|
30099
|
-
* A first-class fact because it is the fleet's most common silent
|
|
30100
|
-
* defect: measured 2026-09-22, 1441 and 3831 are both motion-recording
|
|
30101
|
-
* to a card that is not there. Neither the schedule nor the storage
|
|
30102
|
-
* read says anything wrong on its own; only the pair does.
|
|
30103
|
-
*/
|
|
30104
|
-
recordingToNowhere: boolean(),
|
|
30105
|
-
lastFetchedAt: number()
|
|
30106
|
-
});
|
|
30107
|
-
/** Numeric range descriptor — `{ min, max, step }` per the getOptions convention. */
|
|
30108
|
-
var RangeSchema = object({
|
|
30109
|
-
min: number(),
|
|
30110
|
-
max: number(),
|
|
30111
|
-
step: number()
|
|
30112
|
-
});
|
|
30113
|
-
/**
|
|
30114
|
-
* The values a camera actually takes for a numeric field, when they are a SET
|
|
30115
|
-
* rather than a range.
|
|
30116
|
-
*
|
|
30117
|
-
* `{min,max,step}` cannot say what these two firmwares do. Measured on 1436
|
|
30118
|
-
* (I91DN) on 2026-09-22 by writing each value and reading it back:
|
|
30119
|
-
*
|
|
30120
|
-
* - pre-record: `0, 5, 10, 15, 20, 25, 30` and `2147483647` (INT32_MAX, the
|
|
30121
|
-
* camera's "no limit" — `-1` and `4294967295` both land on it);
|
|
30122
|
-
* - post-record: `5, 10, 30, 60, 120, 300, 600`.
|
|
30123
|
-
*
|
|
30124
|
-
* Neither is expressible as a step: the first has a sentinel two billion away
|
|
30125
|
-
* from its neighbours, the second doubles and then jumps. A range that tried
|
|
30126
|
-
* would forbid values the camera takes AND permit values it silently replaces
|
|
30127
|
-
* with 5 — wrong in both directions at once.
|
|
30128
|
-
*
|
|
30129
|
-
* `sentinel` names the member that is not a duration, so a surface can render
|
|
30130
|
-
* "no limit" instead of `2147483647` seconds.
|
|
30131
|
-
*/
|
|
30132
|
-
var AllowedValuesSchema = object({
|
|
30133
|
-
values: array(number()).min(1),
|
|
30134
|
-
sentinel: object({
|
|
30135
|
-
value: number(),
|
|
30136
|
-
meaning: _enum(["no-limit", "disabled"])
|
|
30137
|
-
}).optional()
|
|
30138
|
-
});
|
|
30139
|
-
/**
|
|
30140
|
-
* Per-field availability on ONE camera.
|
|
30141
|
-
*
|
|
30142
|
-
* The field exists on every camera — this says whether this one can be
|
|
30143
|
-
* read and whether it can be written, and `reason` says why not when
|
|
30144
|
-
* either is false. The UI renders the control DISABLED with the reason
|
|
30145
|
-
* rather than hiding it, so a limitation is legible instead of looking
|
|
30146
|
-
* like a missing feature.
|
|
30147
|
-
*/
|
|
30148
|
-
var OnboardFieldSupportSchema = object({
|
|
30149
|
-
readable: boolean(),
|
|
30150
|
-
writable: boolean(),
|
|
30151
|
-
/** Required whenever `readable` or `writable` is false. */
|
|
30152
|
-
reason: string().optional()
|
|
30153
|
-
});
|
|
30154
|
-
/** What this camera's schedule model can express. */
|
|
30155
|
-
var OnboardScheduleSupportSchema = object({
|
|
30156
|
-
support: OnboardFieldSupportSchema,
|
|
30157
|
-
/**
|
|
30158
|
-
* The smallest time step the camera can express, in minutes.
|
|
30159
|
-
*
|
|
30160
|
-
* Hikvision takes arbitrary minutes (`00:05:00`–`23:57:00` observed on
|
|
30161
|
-
* 1436's track 103). Reolink's schedule is a 7×24 HOUR mask, so 60. A
|
|
30162
|
-
* window whose edges are not a multiple of this is REFUSED rather than
|
|
30163
|
-
* quietly rounded — rounding is how an operator's 06:30 becomes 06:00
|
|
30164
|
-
* and nothing says so.
|
|
30165
|
-
*/
|
|
30166
|
-
granularityMinutes: number(),
|
|
30167
|
-
/** Triggers this camera can record on. A window naming another is refused. */
|
|
30168
|
-
triggers: array(RecordTriggerSchema),
|
|
30169
|
-
/**
|
|
30170
|
-
* False when the camera stores ONE trigger per time range, so two
|
|
30171
|
-
* windows overlapping on the same day cannot carry different triggers.
|
|
30172
|
-
* True on Reolink, whose mask is per-trigger and independent.
|
|
30173
|
-
*/
|
|
30174
|
-
supportsOverlappingTriggers: boolean()
|
|
30175
|
-
});
|
|
30176
|
-
var RecordingOnboardOptionsSchema = object({
|
|
30177
|
-
enabled: OnboardFieldSupportSchema,
|
|
30178
|
-
overwriteWhenFull: OnboardFieldSupportSchema,
|
|
30179
|
-
preRecordSec: OnboardFieldSupportSchema,
|
|
30180
|
-
preRecordSecRange: RangeSchema.optional(),
|
|
30181
|
-
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
30182
|
-
preRecordSecAllowed: AllowedValuesSchema.optional(),
|
|
30183
|
-
postRecordSec: OnboardFieldSupportSchema,
|
|
30184
|
-
postRecordSecRange: RangeSchema.optional(),
|
|
30185
|
-
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
30186
|
-
postRecordSecAllowed: AllowedValuesSchema.optional(),
|
|
30187
|
-
segmentMinutes: OnboardFieldSupportSchema,
|
|
30188
|
-
segmentMinutesRange: RangeSchema.optional(),
|
|
30189
|
-
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
30190
|
-
segmentMinutesAllowed: AllowedValuesSchema.optional(),
|
|
30191
|
-
schedule: OnboardScheduleSupportSchema
|
|
30192
|
-
});
|
|
30193
|
-
/**
|
|
30194
|
-
* A partial change. Every field optional.
|
|
30195
|
-
*
|
|
30196
|
-
* Unlike the other `deviceConfig` caps, a provider here does **NOT**
|
|
30197
|
-
* silently ignore a field it cannot support — it refuses, by name,
|
|
30198
|
-
* through {@link describeOnboardRefusal}. Silence on a recording setting
|
|
30199
|
-
* is the failure D62 exists to prevent: the operator believes the camera
|
|
30200
|
-
* is recording the way the form says, and it is not.
|
|
30201
|
-
*/
|
|
30202
|
-
var RecordingOnboardPatchSchema = object({
|
|
30203
|
-
enabled: boolean().optional(),
|
|
30204
|
-
overwriteWhenFull: boolean().optional(),
|
|
30205
|
-
preRecordSec: number().optional(),
|
|
30206
|
-
postRecordSec: number().optional(),
|
|
30207
|
-
segmentMinutes: number().optional(),
|
|
30208
|
-
/** The complete new window set for the primary track — not a delta. */
|
|
30209
|
-
windows: array(RecordWindowSchema).optional()
|
|
30210
|
-
});
|
|
30211
|
-
DeviceType.Camera, method(object({ deviceId: number() }), RecordingOnboardOptionsSchema), method(object({
|
|
30212
|
-
deviceId: number(),
|
|
30213
|
-
settings: RecordingOnboardPatchSchema
|
|
30214
|
-
}), _void(), {
|
|
30215
|
-
kind: "mutation",
|
|
30216
|
-
auth: "admin"
|
|
30217
|
-
});
|
|
30218
|
-
/**
|
|
30219
30440
|
* Generic device-level status snapshot. Auto-registered by `BaseDevice`
|
|
30220
30441
|
* for every device, regardless of provider — the kernel needs a uniform
|
|
30221
30442
|
* cap-keyed slice for the basic device flags every consumer expects to
|
|
@@ -30309,30 +30530,6 @@ object({
|
|
|
30309
30530
|
eventCountSinceStart: number()
|
|
30310
30531
|
});
|
|
30311
30532
|
DeviceType.EventEmitter;
|
|
30312
|
-
var EventItemSchema = object({
|
|
30313
|
-
id: string(),
|
|
30314
|
-
type: string(),
|
|
30315
|
-
timestamp: number(),
|
|
30316
|
-
label: string().optional(),
|
|
30317
|
-
thumbnailUrl: string().optional(),
|
|
30318
|
-
clipUrl: string().optional(),
|
|
30319
|
-
metadata: record(string(), unknown()).optional()
|
|
30320
|
-
});
|
|
30321
|
-
DeviceType.Camera, method(object({
|
|
30322
|
-
deviceId: number(),
|
|
30323
|
-
from: number().optional(),
|
|
30324
|
-
to: number().optional(),
|
|
30325
|
-
limit: number().optional()
|
|
30326
|
-
}), array(EventItemSchema)), method(object({
|
|
30327
|
-
deviceId: number(),
|
|
30328
|
-
eventId: string()
|
|
30329
|
-
}), object({
|
|
30330
|
-
base64: string(),
|
|
30331
|
-
contentType: string()
|
|
30332
|
-
}).nullable()), method(object({
|
|
30333
|
-
deviceId: number(),
|
|
30334
|
-
eventId: string()
|
|
30335
|
-
}), string().nullable());
|
|
30336
30533
|
var IdentitySchema = object({
|
|
30337
30534
|
id: string(),
|
|
30338
30535
|
name: string(),
|
|
@@ -32029,143 +32226,6 @@ DeviceType.Light, DeviceType.Siren, DeviceType.Switch, method(object({
|
|
|
32029
32226
|
lastChangedAt: number()
|
|
32030
32227
|
});
|
|
32031
32228
|
/**
|
|
32032
|
-
* camera-grid-layout — the geometry of a COMPOSITE camera, on the camera's own
|
|
32033
|
-
* page.
|
|
32034
|
-
*
|
|
32035
|
-
* ## Why this is a capability and not an addon settings schema
|
|
32036
|
-
*
|
|
32037
|
-
* It was one, and it did not render. The addon declared the editor as a
|
|
32038
|
-
* `type: 'widget'` field inside its own `deviceSettingsSchema()`; the hub
|
|
32039
|
-
* returned that section correctly and `ConfigFormField` renders `type:'widget'`
|
|
32040
|
-
* perfectly well — and nothing ever asked camera-grid for it. The Cluster →
|
|
32041
|
-
* Pipeline → Device Overrides page interrogates a HAND-WRITTEN list of four
|
|
32042
|
-
* addons (`PIPELINE_CLUSTER_DEVICE_ADDONS`), whose own comment says an addon
|
|
32043
|
-
* not on it "falls off silently".
|
|
32044
|
-
*
|
|
32045
|
-
* Adding a fifth name to that list would have been the wrong fix twice over:
|
|
32046
|
-
* that page is per-camera DETECTION tuning, and a grid's geometry belongs
|
|
32047
|
-
* beside PTZ and motion zones on the camera itself. The device page is
|
|
32048
|
-
* BINDING-driven (D12), so the way in is a capability bound to the device —
|
|
32049
|
-
* and this cap carries its section the way `recording` does, by RETURNING it
|
|
32050
|
-
* from `getDeviceSettingsContribution`.
|
|
32051
|
-
*
|
|
32052
|
-
* Seven other widgets are still declared the other way, through a
|
|
32053
|
-
* `deviceConfig.ui` block the framework derives a section from. That route
|
|
32054
|
-
* gives the addon no say in where its own panel lands and no way to decline
|
|
32055
|
-
* for a device the panel does not suit, which is why this one does not use it.
|
|
32056
|
-
*
|
|
32057
|
-
* ## Why one addon may implement it
|
|
32058
|
-
*
|
|
32059
|
-
* It is a device-scoped NATIVE cap, registered by the grid camera device
|
|
32060
|
-
* itself. Nothing else declares a composite camera, so nothing else has a
|
|
32061
|
-
* layout — and the device-scoped route means the widget asks THE camera, not
|
|
32062
|
-
* "the camera-grid addon", which is what let the old custom-action pair be
|
|
32063
|
-
* reached only by a caller that already knew the addon id.
|
|
32064
|
-
*
|
|
32065
|
-
* ## The tab
|
|
32066
|
-
*
|
|
32067
|
-
* `streaming`, not a top-tab of its own. A grid's geometry IS what its stream
|
|
32068
|
-
* is, so the Streaming tab is where it belongs; a `grid` top-tab would need an
|
|
32069
|
-
* entry in `WELL_KNOWN_TAB_MAP` or the device page renders the raw id as the
|
|
32070
|
-
* label (measured on the robot camera, 2026-09-06 — a tab called "navigation"
|
|
32071
|
-
* next to "PTZ").
|
|
32072
|
-
*/
|
|
32073
|
-
/** A rectangle in normalized [0,1] coordinates of whatever contains it. */
|
|
32074
|
-
var GridNormalizedRectSchema = object({
|
|
32075
|
-
x: number().min(0).max(1),
|
|
32076
|
-
y: number().min(0).max(1),
|
|
32077
|
-
width: number().gt(0).max(1),
|
|
32078
|
-
height: number().gt(0).max(1)
|
|
32079
|
-
});
|
|
32080
|
-
/**
|
|
32081
|
-
* One source camera, the part of its picture taken, and where that part lands.
|
|
32082
|
-
*
|
|
32083
|
-
* Both rectangles are NORMALIZED (D519): a source camera can change resolution
|
|
32084
|
-
* — a profile switch, a firmware update, a substream that comes back different
|
|
32085
|
-
* — and a stored PIXEL rectangle would quietly start cutting the wrong region,
|
|
32086
|
-
* which is the class of bug nobody files.
|
|
32087
|
-
*/
|
|
32088
|
-
var GridLayoutCellSchema = object({
|
|
32089
|
-
deviceId: number().int().positive(),
|
|
32090
|
-
/** The part of the SOURCE taken, normalized against the source. */
|
|
32091
|
-
source: GridNormalizedRectSchema,
|
|
32092
|
-
/** Where it lands, normalized against the CANVAS. */
|
|
32093
|
-
cell: GridNormalizedRectSchema
|
|
32094
|
-
});
|
|
32095
|
-
/**
|
|
32096
|
-
* Which profiles this grid can actually compose, and why not.
|
|
32097
|
-
*
|
|
32098
|
-
* A grid's `high` composes its sources' `high` and its `low` their `low`, so a
|
|
32099
|
-
* profile is on offer only when EVERY source can serve it. The refusal NAMES
|
|
32100
|
-
* the sources, because "this grid has no low" is not a finding — "615 has no
|
|
32101
|
-
* low" is, and it is the one an operator can act on.
|
|
32102
|
-
*/
|
|
32103
|
-
var GridProfileOfferSchema = object({
|
|
32104
|
-
profile: _enum([
|
|
32105
|
-
"high",
|
|
32106
|
-
"mid",
|
|
32107
|
-
"low"
|
|
32108
|
-
]),
|
|
32109
|
-
offered: boolean(),
|
|
32110
|
-
/** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
|
|
32111
|
-
missingSources: array(number().int().positive()),
|
|
32112
|
-
/**
|
|
32113
|
-
* The canvas this profile composes onto, `WxH`, or empty when it is not
|
|
32114
|
-
* offered. DERIVED from the cells and the sources' own size at this profile —
|
|
32115
|
-
* it is reported because nothing else in the system would ever say what the
|
|
32116
|
-
* grid came out as, and because it is the number an operator would otherwise
|
|
32117
|
-
* expect to type.
|
|
32118
|
-
*/
|
|
32119
|
-
canvas: string(),
|
|
32120
|
-
/**
|
|
32121
|
-
* Whether this profile is PUBLISHED, of the ones the grid could serve.
|
|
32122
|
-
*
|
|
32123
|
-
* A grid's `high` is composed of its sources' `high`, so on a 4K fleet it is
|
|
32124
|
-
* a 4K canvas built from 4K decodes — something to opt into, not something a
|
|
32125
|
-
* viewer's adaptive should be handed by climbing to the top rung it can see.
|
|
32126
|
-
* Default is `mid` + `low`.
|
|
32127
|
-
*/
|
|
32128
|
-
published: boolean()
|
|
32129
|
-
});
|
|
32130
|
-
var GridLayoutViewSchema = object({
|
|
32131
|
-
/** The persisted grid row this camera was declared from. */
|
|
32132
|
-
instanceId: string(),
|
|
32133
|
-
deviceId: number().int().nonnegative(),
|
|
32134
|
-
name: string(),
|
|
32135
|
-
/**
|
|
32136
|
-
* NO canvas size. A grid's resolution is not authored: each profile derives
|
|
32137
|
-
* its own from the cells and its sources' dimensions. The two numbers that
|
|
32138
|
-
* used to be here were a text field that silently decided both how much the
|
|
32139
|
-
* composite cost and how sharp it was — see `profiles[].canvas` for what it
|
|
32140
|
-
* came out as.
|
|
32141
|
-
*/
|
|
32142
|
-
fps: number().int(),
|
|
32143
|
-
cells: array(GridLayoutCellSchema),
|
|
32144
|
-
/** What the catalog will publish, and what it refuses to. Read-only. */
|
|
32145
|
-
profiles: array(GridProfileOfferSchema)
|
|
32146
|
-
});
|
|
32147
|
-
var GridLayoutPatchSchema = object({
|
|
32148
|
-
deviceId: number().int().nonnegative(),
|
|
32149
|
-
name: string().min(1).max(160).optional(),
|
|
32150
|
-
fps: number().int().min(1).max(60).optional(),
|
|
32151
|
-
/** Which profiles to publish. See `GridProfileOffer.published`. */
|
|
32152
|
-
publishedProfiles: array(_enum([
|
|
32153
|
-
"high",
|
|
32154
|
-
"mid",
|
|
32155
|
-
"low"
|
|
32156
|
-
])).max(3).optional(),
|
|
32157
|
-
/**
|
|
32158
|
-
* The whole cell list at once. A per-cell patch would need an ordering the
|
|
32159
|
-
* editor does not have, and a half-applied layout is a picture nobody asked
|
|
32160
|
-
* for.
|
|
32161
|
-
*/
|
|
32162
|
-
cells: array(GridLayoutCellSchema).max(16)
|
|
32163
|
-
});
|
|
32164
|
-
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), GridLayoutViewSchema.nullable(), { auth: "admin" }), method(GridLayoutPatchSchema, GridLayoutViewSchema, {
|
|
32165
|
-
kind: "mutation",
|
|
32166
|
-
auth: "admin"
|
|
32167
|
-
});
|
|
32168
|
-
/**
|
|
32169
32229
|
* Motion-zones share the same MaskShape vocabulary as privacy-mask — the
|
|
32170
32230
|
* on-camera motion-detection mask is a single `grid` region (a row-major
|
|
32171
32231
|
* boolean cell lattice the camera's onboard VMD evaluates). Composing it as
|
|
@@ -32567,6 +32627,11 @@ var ClientNetworkStatsSchema = object({
|
|
|
32567
32627
|
jitterMs: number(),
|
|
32568
32628
|
estimatedBandwidthKbps: number(),
|
|
32569
32629
|
packetLossPercent: number().min(0).max(100),
|
|
32630
|
+
/** The viewer's latest `/adaptive-probe` reading, Mbps — whatever it read
|
|
32631
|
+
* (D635 I4). Absent when it never probed or the probe failed (unknown). */
|
|
32632
|
+
downlinkMbps: number().optional(),
|
|
32633
|
+
/** Age of that reading when this report was sent, ms. */
|
|
32634
|
+
downlinkAgeMs: number().optional(),
|
|
32570
32635
|
lastUpdated: number()
|
|
32571
32636
|
});
|
|
32572
32637
|
var DeviceNetworkStatsSchema = object({
|
|
@@ -32579,7 +32644,11 @@ method(object({ deviceId: number() }), DeviceNetworkStatsSchema.nullable()), met
|
|
|
32579
32644
|
rttMs: number().min(0).max(6e4),
|
|
32580
32645
|
jitterMs: number().min(0).max(1e4),
|
|
32581
32646
|
estimatedBandwidthKbps: number().min(0).max(1e6),
|
|
32582
|
-
packetLossPercent: number().min(0).max(100)
|
|
32647
|
+
packetLossPercent: number().min(0).max(100),
|
|
32648
|
+
/** Latest downlink probe, Mbps, whatever it read; absent = never / failed. */
|
|
32649
|
+
downlinkMbps: number().min(0).max(1e5).optional(),
|
|
32650
|
+
/** Age of that probe when this report is sent, ms. */
|
|
32651
|
+
downlinkAgeMs: number().min(0).max(864e5).optional()
|
|
32583
32652
|
}), _void(), { kind: "mutation" });
|
|
32584
32653
|
/**
|
|
32585
32654
|
* nodes — system-scoped singleton capability for cluster topology
|
|
@@ -33978,36 +34047,95 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceI
|
|
|
33978
34047
|
auth: "admin"
|
|
33979
34048
|
});
|
|
33980
34049
|
/**
|
|
33981
|
-
* `recording`
|
|
33982
|
-
*
|
|
33983
|
-
*
|
|
33984
|
-
*
|
|
33985
|
-
*
|
|
33986
|
-
*
|
|
33987
|
-
*
|
|
33988
|
-
*
|
|
34050
|
+
* `recording` — a camera's recorded COVERAGE and PLAYBACK, from whichever
|
|
34051
|
+
* source the operator picked.
|
|
34052
|
+
*
|
|
34053
|
+
* A device-scoped wrapper COLLECTION since D625, and the five lines that make
|
|
34054
|
+
* it one (`scope`/`mode`/`kind`/`defaultActive`/`deviceTypes`) are copied from
|
|
34055
|
+
* `videoclips.cap.ts` deliberately: `scope: 'device'` + `mode: 'collection'` is
|
|
34056
|
+
* what makes `resolveCapMount` keep the PER-DEVICE mount while the collection
|
|
34057
|
+
* dispatcher fans out, and a device's sources are then its BINDINGS
|
|
34058
|
+
* (`deviceManager.getBindings`, D554 amended) rather than "every addon that
|
|
34059
|
+
* registered the cap somewhere".
|
|
34060
|
+
*
|
|
34061
|
+
* ## What is here and what is not
|
|
34062
|
+
*
|
|
34063
|
+
* FIVE of them — `listSources`, `getAvailability`, `getDaysWithRecordings`,
|
|
34064
|
+
* `getPlayback`, `getPlaybackOptions`. Every source must be able to answer
|
|
34065
|
+
* these or it is not a source. Everything else that used to live on this cap —
|
|
34066
|
+
* storage locations, retention, relocation, rebalance, the ops log, the
|
|
34067
|
+
* placement table, the `mfra` byte plane, `getDeviceConfig`/`setDeviceConfig`
|
|
34068
|
+
* and the two batch reads — is on `recording-archive`, a system singleton, and
|
|
34069
|
+
* the reasons are argued there.
|
|
34070
|
+
*
|
|
34071
|
+
* ## The default is OURS, and there is nowhere to say otherwise
|
|
34072
|
+
*
|
|
34073
|
+
* {@link RECORDING_SOURCE_CAMSTACK} is RESERVED. The absence of an operator
|
|
34074
|
+
* choice MEANS that value; it is not a row that happened to sort first. Clips
|
|
34075
|
+
* end their resolver with `?? sources[0]` and that is right for clips — a
|
|
34076
|
+
* camera full of onboard clips that says "no clips" is the defect that work
|
|
34077
|
+
* exists to end. For a TIMELINE it is wrong and the measurement says why: on
|
|
34078
|
+
* 592, ours covers 85 362 s of the day and the card 21 388 s, and
|
|
34079
|
+
* `videoclips.listSources` answers the ONBOARD row first. A silent fall to the
|
|
34080
|
+
* first row would swap a 98.8 % bar for a 24.8 % one with nothing saying so.
|
|
34081
|
+
*
|
|
34082
|
+
* So a surface that cannot find our row says `ours-absent` and offers the
|
|
34083
|
+
* others as a CHOICE; it never takes one on the operator's behalf. The
|
|
34084
|
+
* reservation is enforced in the collection dispatcher — a `listSources` row
|
|
34085
|
+
* carrying this id from any addon but the recorder is refused by name — and by
|
|
34086
|
+
* `scripts/check-reserved-collection-sources.ts` in CI.
|
|
34087
|
+
*
|
|
34088
|
+
* NOTE on events (source of truth, R5/C3): this cap carries NO event surface.
|
|
34089
|
+
* Timeline events (motion/object/audio) come from `pipelineAnalytics` (durable
|
|
34090
|
+
* SQLite rows) and are the ONLY event surface — the recorder has none.
|
|
34091
|
+
* Event<->footage joins are by time, padded with the shared `EVENT_PAD_MS`
|
|
34092
|
+
* (`interfaces/recording-config.ts`).
|
|
33989
34093
|
*/
|
|
33990
|
-
|
|
33991
|
-
|
|
33992
|
-
|
|
33993
|
-
|
|
33994
|
-
|
|
33995
|
-
|
|
33996
|
-
|
|
33997
|
-
|
|
33998
|
-
|
|
33999
|
-
|
|
34094
|
+
/**
|
|
34095
|
+
* OUR recorder's source id. RESERVED: no other provider may return it from
|
|
34096
|
+
* `listSources`, and the absence of an operator choice MEANS this value.
|
|
34097
|
+
*
|
|
34098
|
+
* Reserved is ENFORCED, not requested — `device-collection-dispatch.ts` drops a
|
|
34099
|
+
* row spelling it from any addon but {@link RECORDING_SOURCE_CAMSTACK_ADDON},
|
|
34100
|
+
* naming the impostor, and `scripts/check-reserved-collection-sources.ts`
|
|
34101
|
+
* fails CI on a provider package that spells it at all. A rule nothing checks
|
|
34102
|
+
* has already been broken somewhere.
|
|
34103
|
+
*/
|
|
34104
|
+
var RECORDING_SOURCE_CAMSTACK = "camstack";
|
|
34105
|
+
/** The one addon allowed to serve {@link RECORDING_SOURCE_CAMSTACK}: the
|
|
34106
|
+
* recorder, which is the addon that writes the footage (D560). */
|
|
34107
|
+
var RECORDING_SOURCE_CAMSTACK_ADDON = "recorder";
|
|
34000
34108
|
var RecordingRangeSchema = object({
|
|
34001
34109
|
profile: string(),
|
|
34002
34110
|
startMs: number(),
|
|
34003
34111
|
endMs: number()
|
|
34004
34112
|
});
|
|
34113
|
+
/**
|
|
34114
|
+
* How a source ANSWERED, on every singular read of this cap.
|
|
34115
|
+
*
|
|
34116
|
+
* `'read'` — it looked. `ranges: []` / `days: []` is then a real claim: this
|
|
34117
|
+
* source has no coverage in the window. `'unreadable'` — nobody could look
|
|
34118
|
+
* (the camera was unreachable, the calendar rung threw, the location is
|
|
34119
|
+
* unmounted, the node is still on the old build), and the emptiness beside it
|
|
34120
|
+
* means NOTHING.
|
|
34121
|
+
*
|
|
34122
|
+
* The batch rows have carried this since the grid existed; the SINGULAR
|
|
34123
|
+
* answers gained it with the collection (D625 §10.4), because they are the
|
|
34124
|
+
* ones the single-camera picker uses and because a half-converted fleet makes
|
|
34125
|
+
* "nobody looked" common for the length of a deploy. Without it the timeline
|
|
34126
|
+
* has no vocabulary for it, and `(data ?? [])` in a viewer turns a rollout into
|
|
34127
|
+
* a fleet of cameras that appear to have lost their recordings (D315, D393).
|
|
34128
|
+
*/
|
|
34129
|
+
var RecordingReadSchema = _enum(["read", "unreadable"]);
|
|
34005
34130
|
var RecordingAvailabilitySchema = object({
|
|
34006
34131
|
deviceId: number(),
|
|
34132
|
+
/** See {@link RecordingReadSchema}. An `'unreadable'` answer carries an empty
|
|
34133
|
+
* `ranges` that means nothing — never draw it as "no footage". */
|
|
34134
|
+
read: RecordingReadSchema,
|
|
34007
34135
|
ranges: array(RecordingRangeSchema),
|
|
34008
34136
|
/**
|
|
34009
|
-
* Every profile this camera has footage in — not only the one
|
|
34010
|
-
* describes (D433).
|
|
34137
|
+
* Every profile this camera has footage in AT THIS SOURCE — not only the one
|
|
34138
|
+
* `ranges` describes (D433).
|
|
34011
34139
|
*
|
|
34012
34140
|
* `ranges` answers for ONE profile by design: the timeline is a single bar,
|
|
34013
34141
|
* and enumerating all of them triples the directory reads for a bar that
|
|
@@ -34024,15 +34152,344 @@ var RecordingAvailabilitySchema = object({
|
|
|
34024
34152
|
});
|
|
34025
34153
|
var RecordingDaysSchema = object({
|
|
34026
34154
|
deviceId: number(),
|
|
34155
|
+
/** See {@link RecordingReadSchema}. `days: []` on an `'unreadable'` answer is
|
|
34156
|
+
* "nobody could look", and the date-picker must not spell it the same as
|
|
34157
|
+
* "no footage this month". */
|
|
34158
|
+
read: RecordingReadSchema,
|
|
34027
34159
|
/** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
|
|
34028
34160
|
days: array(number())
|
|
34029
34161
|
});
|
|
34162
|
+
var RecordingManifestSchema = object({
|
|
34163
|
+
deviceId: number(),
|
|
34164
|
+
/** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
|
|
34165
|
+
localMasterPath: string().nullable(),
|
|
34166
|
+
/** HTTP(S) URL to the master playlist on the recording node's playback server
|
|
34167
|
+
* (the PRIMARY candidate); null when no recording / server. Carries the
|
|
34168
|
+
* scoped playback token in its path. */
|
|
34169
|
+
playbackUrl: string().nullable(),
|
|
34170
|
+
/**
|
|
34171
|
+
* Candidate master-playlist URLs the client tries in order (LAN first, then
|
|
34172
|
+
* remote — Tailscale/Cloudflare if the operator configured extra hosts), each
|
|
34173
|
+
* carrying the same scoped token. `playbackUrl` is the first entry. Empty when
|
|
34174
|
+
* there is no recording / server.
|
|
34175
|
+
*/
|
|
34176
|
+
playbackEndpoints: array(string())
|
|
34177
|
+
});
|
|
34178
|
+
var RecordingSourceAvailabilitySchema = object({
|
|
34179
|
+
state: _enum([
|
|
34180
|
+
"ok",
|
|
34181
|
+
"sleeping",
|
|
34182
|
+
"unreachable",
|
|
34183
|
+
"no-storage",
|
|
34184
|
+
"index-empty"
|
|
34185
|
+
]),
|
|
34186
|
+
/** Free text, shown verbatim. Names the camera's own refusal when there is one. */
|
|
34187
|
+
reason: string().optional(),
|
|
34188
|
+
/** When this source's coverage was last CONFIRMED. A cached answer is never
|
|
34189
|
+
* drawn as current: the surface shows the age whenever it is older than the
|
|
34190
|
+
* refresh interval. The clip catalog's `catalogAsOf`, under the name the
|
|
34191
|
+
* timeline uses for it. */
|
|
34192
|
+
coverageAsOf: number().optional()
|
|
34193
|
+
});
|
|
34194
|
+
/**
|
|
34195
|
+
* One SOURCE of recorded coverage for a camera — a row of the picker.
|
|
34196
|
+
*
|
|
34197
|
+
* A provider lists the sources IT serves for that device, and answers for each
|
|
34198
|
+
* of them whether it can answer at all. A provider with nothing to offer on a
|
|
34199
|
+
* camera returns `[]` — it is not that camera's business. The five availability
|
|
34200
|
+
* states are `ClipSourceAvailability`'s verbatim: they mean exactly the same
|
|
34201
|
+
* things about a coverage index as about a clip catalog, and `sleeping` in
|
|
34202
|
+
* particular is what stops a battery camera being woken to paint a bar.
|
|
34203
|
+
*/
|
|
34204
|
+
var RecordingSourceSchema = object({
|
|
34205
|
+
/** The source id. {@link RECORDING_SOURCE_CAMSTACK} for ours (RESERVED), a
|
|
34206
|
+
* vendor namespace (`native:reolink:onboard`, …) for a camera's own store. */
|
|
34207
|
+
source: string(),
|
|
34208
|
+
/** Operator-facing name of the source ("CamStack recordings", "SD card"). */
|
|
34209
|
+
label: string(),
|
|
34210
|
+
/**
|
|
34211
|
+
* The addon that SERVES this row, and the value a later call passes as
|
|
34212
|
+
* `provider`.
|
|
34213
|
+
*
|
|
34214
|
+
* Optional for version skew only. The collection dispatcher stamps it from
|
|
34215
|
+
* the registry, so a row that travelled through the fan-out carries the
|
|
34216
|
+
* authoritative id whatever the provider filled in (D557 §4).
|
|
34217
|
+
*/
|
|
34218
|
+
addonId: string().optional(),
|
|
34219
|
+
availability: RecordingSourceAvailabilitySchema
|
|
34220
|
+
});
|
|
34221
|
+
/**
|
|
34222
|
+
* What a surface may DRAW for this (camera, source) — D612's rule applied to a
|
|
34223
|
+
* timeline: **the source declares what it can do, and the surface draws what
|
|
34224
|
+
* was declared. It never assumes, and never offers a gesture it will then
|
|
34225
|
+
* refuse.** D612 exists because `8` and `16` were offered as clip rates, the
|
|
34226
|
+
* broker clamped them to `4`, and no line anywhere said so.
|
|
34227
|
+
*
|
|
34228
|
+
* Asked once per (camera, source) before anything is drawn — never replaced by
|
|
34229
|
+
* a constant the surface keeps, which is the second authority D612 ends.
|
|
34230
|
+
*/
|
|
34231
|
+
var RecordingSourceOptionsSchema = object({
|
|
34232
|
+
/** How this source's media reaches the player.
|
|
34233
|
+
* `archive` = our own indexed segment tree; `stream` = the provider's
|
|
34234
|
+
* forward-only fMP4 (D597); `realtime` = a replay bound to wall clock. */
|
|
34235
|
+
transport: _enum([
|
|
34236
|
+
"archive",
|
|
34237
|
+
"stream",
|
|
34238
|
+
"realtime"
|
|
34239
|
+
]),
|
|
34240
|
+
/** What the BAR means. `continuous` = gaps are holes in a recording;
|
|
34241
|
+
* `sparse` = gaps are the absence of one, and must be drawn as such.
|
|
34242
|
+
*
|
|
34243
|
+
* Not an onboard-only concession: measured 2026-09-24, OUR bar covers 98.8 %
|
|
34244
|
+
* of 592's day and 1.2 % of 1436's. It is a fact about a (source, camera)
|
|
34245
|
+
* pair, and ours answers it per camera from `deriveRecordingMode`. */
|
|
34246
|
+
coverage: _enum(["continuous", "sparse"]),
|
|
34247
|
+
/** Where the playhead may be put.
|
|
34248
|
+
* `free` — anywhere, to the frame.
|
|
34249
|
+
* `forward` — only ahead of the current position.
|
|
34250
|
+
* `segment` — a position SNAPS to the head of the covering segment; a finer
|
|
34251
|
+
* ask is accepted by the camera and SILENTLY IGNORED. Measured
|
|
34252
|
+
* on 1436 (Hikvision V5.7.1, 2026-09-23): a window-narrowed
|
|
34253
|
+
* `ContentMgmt/search` returns a row and a `playbackURI`, the
|
|
34254
|
+
* replay opens 200 and delivers media — and the burned-in OSD of
|
|
34255
|
+
* the first frame reads the SEGMENT HEAD every time. Calling
|
|
34256
|
+
* that `forward` would tell the surface it may move the playhead
|
|
34257
|
+
* ahead within a loaded segment, which it may not. */
|
|
34258
|
+
seek: _enum([
|
|
34259
|
+
"free",
|
|
34260
|
+
"forward",
|
|
34261
|
+
"segment"
|
|
34262
|
+
]),
|
|
34263
|
+
/** Frame-step BACKWARD is meaningful. */
|
|
34264
|
+
stepBack: boolean(),
|
|
34265
|
+
/** Whether the drag-scrub gesture is served, as opposed to refused by name. */
|
|
34266
|
+
scrub: boolean(),
|
|
34267
|
+
/** Deliverable rates, ascending, always containing `1`. The surface draws its
|
|
34268
|
+
* picker from this and from NOTHING else (D612, D620, D621). `0` is not a
|
|
34269
|
+
* member: pause is the absence of a rate. */
|
|
34270
|
+
rates: array(number().positive()).min(1).readonly(),
|
|
34271
|
+
/** TRUE when a read of this source HOLDS the camera's only playback session.
|
|
34272
|
+
* A surface with this set makes at most ONE read at a time and draws no
|
|
34273
|
+
* scrub-thumbnail strip, no hover preview, no prefetch and no background
|
|
34274
|
+
* refresh. The precedent is exact and expensive: filling one screen of
|
|
34275
|
+
* Hikvision thumbnails at 1.01× realtime consumed fifteen minutes of that
|
|
34276
|
+
* camera's only playback session (1.2.126, reported within minutes), and a
|
|
34277
|
+
* timeline is a screenful of reads by construction. */
|
|
34278
|
+
exclusive: boolean()
|
|
34279
|
+
});
|
|
34280
|
+
/**
|
|
34281
|
+
* How to PLAY the instant that was asked for, from the chosen source.
|
|
34282
|
+
*
|
|
34283
|
+
* No new media transport is built for onboard sources: the `clip` arm is a
|
|
34284
|
+
* DELEGATION to the `videoclips` transport that vendor already has (D597 /
|
|
34285
|
+
* D616 / D617). The onboard half of this collection is a PROJECTION of
|
|
34286
|
+
* `videoclips` for coverage and a delegation to it for bytes.
|
|
34287
|
+
*/
|
|
34288
|
+
var RecordingPlaybackSchema = discriminatedUnion("kind", [
|
|
34289
|
+
object({
|
|
34290
|
+
kind: literal("hls"),
|
|
34291
|
+
manifest: RecordingManifestSchema
|
|
34292
|
+
}),
|
|
34293
|
+
object({
|
|
34294
|
+
kind: literal("clip"),
|
|
34295
|
+
/** The `videoclips` source namespace this clip id belongs to. */
|
|
34296
|
+
source: string(),
|
|
34297
|
+
clipId: string(),
|
|
34298
|
+
/** Where this clip actually STARTS. On a `seek: 'segment'` source the
|
|
34299
|
+
* playhead lands here, not at the requested instant — the surface must be
|
|
34300
|
+
* TOLD, not left to discover it from a burned-in OSD. */
|
|
34301
|
+
startsAtMs: number()
|
|
34302
|
+
}),
|
|
34303
|
+
object({
|
|
34304
|
+
kind: literal("none"),
|
|
34305
|
+
reason: string()
|
|
34306
|
+
})
|
|
34307
|
+
]);
|
|
34308
|
+
var recordingCapability = {
|
|
34309
|
+
name: "recording",
|
|
34310
|
+
scope: "device",
|
|
34311
|
+
/** Several sources per camera, listed beside each other. The mount stays
|
|
34312
|
+
* `device-scoped` — see `resolveCapMount`'s ordering and D554: per-device
|
|
34313
|
+
* wins over the global collection fan-out. */
|
|
34314
|
+
mode: "collection",
|
|
34315
|
+
kind: "wrapper",
|
|
34316
|
+
defaultActive: true,
|
|
34317
|
+
/** Recorded coverage is a property of a camera — the cap is meaningless on a
|
|
34318
|
+
* sensor, a button or an event emitter, and the `defaultActive` auto-bind
|
|
34319
|
+
* reads this to decide which devices it may claim. */
|
|
34320
|
+
deviceTypes: [DeviceType.Camera],
|
|
34321
|
+
methods: {
|
|
34322
|
+
/**
|
|
34323
|
+
* The sources this camera has, WITH the reason any of them cannot answer.
|
|
34324
|
+
*
|
|
34325
|
+
* Asked separately from `getAvailability` because an empty bar is
|
|
34326
|
+
* ambiguous and this is the only place the ambiguity is resolved: every
|
|
34327
|
+
* bound provider contributes its own rows, and a provider that could not be
|
|
34328
|
+
* reached at all still produces one row saying so. A surface that draws "no
|
|
34329
|
+
* recordings" without reading this is drawing a guess.
|
|
34330
|
+
*
|
|
34331
|
+
* The ONLY method here without a `provider` — it is the call that tells the
|
|
34332
|
+
* caller what to put there.
|
|
34333
|
+
*/
|
|
34334
|
+
listSources: method(object({ deviceId: number() }), array(RecordingSourceSchema).readonly(), {
|
|
34335
|
+
kind: "query",
|
|
34336
|
+
auth: "protected"
|
|
34337
|
+
}),
|
|
34338
|
+
/**
|
|
34339
|
+
* Recorded coverage of `[fromMs, toMs)` at ONE source — the timeline bar.
|
|
34340
|
+
*
|
|
34341
|
+
* `protected`, not `admin`: a per-camera read is exactly what a camera
|
|
34342
|
+
* viewer is FOR, and the device-scoped mount routes through
|
|
34343
|
+
* `getProviderForDevice`, so a camera outside the caller's scope is refused
|
|
34344
|
+
* before a provider is reached.
|
|
34345
|
+
*/
|
|
34346
|
+
getAvailability: method(object({
|
|
34347
|
+
deviceId: number(),
|
|
34348
|
+
/**
|
|
34349
|
+
* WHICH provider to ask — the `addonId` a {@link RecordingSourceSchema}
|
|
34350
|
+
* row carries, never a source id and never a list. **REQUIRED**, in the
|
|
34351
|
+
* schema, where the generated types make it unomittable rather than
|
|
34352
|
+
* merely discouraged (D554 amended).
|
|
34353
|
+
*
|
|
34354
|
+
* It was learned the expensive way on `videoclips.listClips`: measured
|
|
34355
|
+
* on the live hub 2026-09-20, device 592 bound to `recorder` AND
|
|
34356
|
+
* `provider-reolink`, a bare call with `limit: 3` answered SIX rows,
|
|
34357
|
+
* three from each source, merged — `device-collection-dispatch.ts`
|
|
34358
|
+
* leaves an unpinned fan-out un-narrowed, so absence buys the union the
|
|
34359
|
+
* method exists not to be. An un-narrowed `getAvailability` would do
|
|
34360
|
+
* that to a TIMELINE: our ranges and the card's clips unioned into one
|
|
34361
|
+
* bar, which is "two sources are never drawn together" broken in the
|
|
34362
|
+
* one place it matters most.
|
|
34363
|
+
*
|
|
34364
|
+
* A provider the device is not bound to is refused BY NAME (D552's
|
|
34365
|
+
* `rejectUnresolvedAddonPin`), never answered by another one.
|
|
34366
|
+
*/
|
|
34367
|
+
provider: string().min(1),
|
|
34368
|
+
fromMs: number(),
|
|
34369
|
+
toMs: number(),
|
|
34370
|
+
/**
|
|
34371
|
+
* Answer for THIS profile instead of the source's preferred one (D433).
|
|
34372
|
+
* Absent keeps the timeline's behaviour — one bar, one profile, one set
|
|
34373
|
+
* of reads. `profilesWithFootage` on the answer says what may be asked
|
|
34374
|
+
* for.
|
|
34375
|
+
*/
|
|
34376
|
+
profile: string().optional()
|
|
34377
|
+
}), RecordingAvailabilitySchema, {
|
|
34378
|
+
kind: "query",
|
|
34379
|
+
auth: "protected"
|
|
34380
|
+
}),
|
|
34381
|
+
/** Which calendar days in [fromMs,toMs) this source has ≥1 recording in,
|
|
34382
|
+
* bucketed by the client's local day (`tzOffsetMinutes` = minutes to add
|
|
34383
|
+
* to UTC). Drives the theater date-picker's day dots. `provider` is
|
|
34384
|
+
* REQUIRED for the reason `getAvailability` states. */
|
|
34385
|
+
getDaysWithRecordings: method(object({
|
|
34386
|
+
deviceId: number(),
|
|
34387
|
+
provider: string().min(1),
|
|
34388
|
+
fromMs: number(),
|
|
34389
|
+
toMs: number(),
|
|
34390
|
+
tzOffsetMinutes: number()
|
|
34391
|
+
}), RecordingDaysSchema, {
|
|
34392
|
+
kind: "query",
|
|
34393
|
+
auth: "protected"
|
|
34394
|
+
}),
|
|
34395
|
+
/**
|
|
34396
|
+
* How to PLAY `[fromMs, toMs)` at this source.
|
|
34397
|
+
*
|
|
34398
|
+
* It was `getPlaybackManifest`, and the rename is not cosmetic: a
|
|
34399
|
+
* "manifest" is an HLS master playlist, which is a property of OUR recorder
|
|
34400
|
+
* and of nothing else. Keeping the name would make every onboard
|
|
34401
|
+
* implementation a lie in its signature. The old shape survives verbatim
|
|
34402
|
+
* inside the union's `hls` arm, so the recorder's implementation is
|
|
34403
|
+
* unchanged behind it.
|
|
34404
|
+
*/
|
|
34405
|
+
getPlayback: method(object({
|
|
34406
|
+
deviceId: number(),
|
|
34407
|
+
provider: string().min(1),
|
|
34408
|
+
fromMs: number(),
|
|
34409
|
+
toMs: number(),
|
|
34410
|
+
profile: CamProfileSchema.optional()
|
|
34411
|
+
}), RecordingPlaybackSchema, {
|
|
34412
|
+
kind: "query",
|
|
34413
|
+
auth: "protected"
|
|
34414
|
+
}),
|
|
34415
|
+
/**
|
|
34416
|
+
* What this (camera, source) can actually DO — asked before anything is
|
|
34417
|
+
* drawn. See {@link RecordingSourceOptionsSchema}; a constant the surface
|
|
34418
|
+
* keeps instead is the second authority D612 exists to end.
|
|
34419
|
+
*/
|
|
34420
|
+
getPlaybackOptions: method(object({
|
|
34421
|
+
deviceId: number(),
|
|
34422
|
+
provider: string().min(1)
|
|
34423
|
+
}), RecordingSourceOptionsSchema, {
|
|
34424
|
+
kind: "query",
|
|
34425
|
+
auth: "protected"
|
|
34426
|
+
})
|
|
34427
|
+
}
|
|
34428
|
+
};
|
|
34429
|
+
/**
|
|
34430
|
+
* `recording-archive` — OUR archive, and the intent that fills it.
|
|
34431
|
+
*
|
|
34432
|
+
* The system-singleton half of the 2026-09-24 cut (D625). `recording` used to
|
|
34433
|
+
* be one 33-method system singleton holding two unrelated subjects: three
|
|
34434
|
+
* per-camera READS about coverage and playback, and everything else — storage
|
|
34435
|
+
* locations, retention, relocation, rebalance, the ops log, the placement
|
|
34436
|
+
* table and the byte-plane primitives our scrub and export are built on.
|
|
34437
|
+
*
|
|
34438
|
+
* The reads became a device-scoped COLLECTION, so a camera's own card can be a
|
|
34439
|
+
* source beside ours (`recording.cap.ts`). Everything that is about OUR store,
|
|
34440
|
+
* or unimplementable by a camera, stayed here.
|
|
34441
|
+
*
|
|
34442
|
+
* ## On the name
|
|
34443
|
+
*
|
|
34444
|
+
* `recording-storage` was the obvious choice and is wrong: this cap also holds
|
|
34445
|
+
* `getDeviceConfig`/`setDeviceConfig`, which are recording INTENT — bands,
|
|
34446
|
+
* retention, the D62 switch authority — and a name that says "storage" invites
|
|
34447
|
+
* the next reader to move them out again. An archive is a thing we keep, and
|
|
34448
|
+
* what we keep it under is a policy; the name covers both halves honestly and
|
|
34449
|
+
* sits in the existing family (`recording-onboard`, `recording-export`,
|
|
34450
|
+
* `recording-signal`).
|
|
34451
|
+
*
|
|
34452
|
+
* ## What must NOT happen to it
|
|
34453
|
+
*
|
|
34454
|
+
* It stays a SINGLETON. It is registered by `recorder`, which is
|
|
34455
|
+
* `placement: 'any-node'` and runs on every recording node; the hub dispatches
|
|
34456
|
+
* to one of them. Putting the ledger, the placement table or the relocation
|
|
34457
|
+
* jobs behind a fan-out is the one genuinely dangerous move in this cut.
|
|
34458
|
+
*
|
|
34459
|
+
* `getDeviceConfig` / `setDeviceConfig` in particular are the D62 recording
|
|
34460
|
+
* authority (`CameraSwitch.authority`). If a write reached a different provider
|
|
34461
|
+
* than the read — which a collection fan-out permits — two authorities would
|
|
34462
|
+
* decide when one camera records, and the symptom (recording silently off, or
|
|
34463
|
+
* a `bands` array clobbered by a partial write) is durable and silent. Keeping
|
|
34464
|
+
* them here means the worst case during a rollout is a 412: the switch refuses
|
|
34465
|
+
* to flip and SAYS so. **Do not move them into the collection, at any point,
|
|
34466
|
+
* for any reason.**
|
|
34467
|
+
*
|
|
34468
|
+
* ## The two batch reads
|
|
34469
|
+
*
|
|
34470
|
+
* `getAvailabilityBatch` / `getDaysWithRecordingsBatch` take `deviceIds:
|
|
34471
|
+
* number[]` with no single `deviceId`, and a device-scoped mount routes
|
|
34472
|
+
* through `getProviderForDevice(deviceId)` — there is nothing for it to route
|
|
34473
|
+
* on. They stay here, and on this cap the batch is explicitly OURS: a grid has
|
|
34474
|
+
* no per-camera picker, and a caller that wants another source's coverage asks
|
|
34475
|
+
* `recording.getAvailability` per device with that source's `provider`.
|
|
34476
|
+
*/
|
|
34477
|
+
var RecordingStatusSchema = object({
|
|
34478
|
+
deviceId: number(),
|
|
34479
|
+
enabled: boolean(),
|
|
34480
|
+
/** THE derived storage mode, from the one definition
|
|
34481
|
+
* (`deriveRecordingMode`) — never a second enum. A duplicated list is how
|
|
34482
|
+
* `on-device-decision` could have reached the recorder and not the status. */
|
|
34483
|
+
activeMode: RecordingStorageModeSchema,
|
|
34484
|
+
nodeId: string(),
|
|
34485
|
+
storageBytes: number()
|
|
34486
|
+
});
|
|
34030
34487
|
/**
|
|
34031
34488
|
* One camera's row in a `getAvailabilityBatch` answer.
|
|
34032
34489
|
*
|
|
34033
|
-
* `ranges` is EXACTLY what `getAvailability` returns for that camera
|
|
34034
|
-
* batch collapses the transport, not the work — plus the
|
|
34035
|
-
*
|
|
34490
|
+
* `ranges` is EXACTLY what `recording.getAvailability` returns for that camera
|
|
34491
|
+
* at OUR source — the batch collapses the transport, not the work — plus the
|
|
34492
|
+
* `read` mark the singular answer now carries too (D625):
|
|
34036
34493
|
*
|
|
34037
34494
|
* - `read: 'read'` — answered. `ranges: []` means "read, and this camera has
|
|
34038
34495
|
* no footage in the window", which is a real claim.
|
|
@@ -34064,22 +34521,6 @@ var RecordingDaysForDeviceSchema = object({
|
|
|
34064
34521
|
/** Local-midnight epochs (UTC ms) of days that have ≥1 recorded segment. */
|
|
34065
34522
|
days: array(number()).readonly()
|
|
34066
34523
|
});
|
|
34067
|
-
var RecordingManifestSchema = object({
|
|
34068
|
-
deviceId: number(),
|
|
34069
|
-
/** Local filesystem path to the master playlist; null when no recording exists for the requested range. */
|
|
34070
|
-
localMasterPath: string().nullable(),
|
|
34071
|
-
/** HTTP(S) URL to the master playlist on the recording node's playback server
|
|
34072
|
-
* (the PRIMARY candidate); null when no recording / server. Carries the
|
|
34073
|
-
* scoped playback token in its path. */
|
|
34074
|
-
playbackUrl: string().nullable(),
|
|
34075
|
-
/**
|
|
34076
|
-
* Candidate master-playlist URLs the client tries in order (LAN first, then
|
|
34077
|
-
* remote — Tailscale/Cloudflare if the operator configured extra hosts), each
|
|
34078
|
-
* carrying the same scoped token. `playbackUrl` is the first entry. Empty when
|
|
34079
|
-
* there is no recording / server.
|
|
34080
|
-
*/
|
|
34081
|
-
playbackEndpoints: array(string())
|
|
34082
|
-
});
|
|
34083
34524
|
/**
|
|
34084
34525
|
* Recording storage usage for one camera — what the ARCHIVE holds for it,
|
|
34085
34526
|
* across every profile and every resolvable location on this node.
|
|
@@ -34337,33 +34778,22 @@ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
|
|
|
34337
34778
|
* not a retry hint (retrying the same window would refuse again). */
|
|
34338
34779
|
segmentEndMs: number()
|
|
34339
34780
|
})]);
|
|
34340
|
-
var
|
|
34341
|
-
name: "recording",
|
|
34781
|
+
var recordingArchiveCapability = {
|
|
34782
|
+
name: "recording-archive",
|
|
34342
34783
|
scope: "system",
|
|
34343
34784
|
mode: "singleton",
|
|
34785
|
+
/** Moves here with the config pair — the derived Recording settings
|
|
34786
|
+
* section is a view over `getDeviceConfig`/`setDeviceConfig` (D14), and
|
|
34787
|
+
* those did not move (D625). */
|
|
34344
34788
|
exposesDeviceSettings: true,
|
|
34345
34789
|
status: {
|
|
34346
34790
|
schema: RecordingStatusSchema,
|
|
34347
34791
|
kind: "command-driven"
|
|
34348
34792
|
},
|
|
34349
34793
|
methods: {
|
|
34350
|
-
getAvailability: method(object({
|
|
34351
|
-
deviceId: number(),
|
|
34352
|
-
fromMs: number(),
|
|
34353
|
-
toMs: number(),
|
|
34354
|
-
/**
|
|
34355
|
-
* Answer for THIS profile instead of the preferred one (D433). Absent
|
|
34356
|
-
* keeps the timeline's behaviour — one bar, one profile, one set of
|
|
34357
|
-
* reads. `profilesWithFootage` on the answer says what may be asked
|
|
34358
|
-
* for.
|
|
34359
|
-
*/
|
|
34360
|
-
profile: string().optional()
|
|
34361
|
-
}), RecordingAvailabilitySchema, {
|
|
34362
|
-
kind: "query",
|
|
34363
|
-
auth: "protected"
|
|
34364
|
-
}),
|
|
34365
34794
|
/**
|
|
34366
|
-
* `getAvailability` for a SET of cameras, in one round trip
|
|
34795
|
+
* `recording.getAvailability` for a SET of cameras, in one round trip, at
|
|
34796
|
+
* OUR source.
|
|
34367
34797
|
*
|
|
34368
34798
|
* A multi-camera timeline re-asks availability for every camera in the grid
|
|
34369
34799
|
* on every day change; fanned out that is one request per camera for N
|
|
@@ -34371,6 +34801,13 @@ var recordingCapability = {
|
|
|
34371
34801
|
* `availabilityProfileFor` + `rangesIn`, run concurrently inside the
|
|
34372
34802
|
* recorder) — only the transport collapses.
|
|
34373
34803
|
*
|
|
34804
|
+
* It lives on the ARCHIVE, not on the `recording` collection, because it
|
|
34805
|
+
* takes `deviceIds` with no single `deviceId` and a device-scoped mount has
|
|
34806
|
+
* nothing to route on (D625 §10.2). The consequence is stated rather than
|
|
34807
|
+
* hidden: the batch answers for OURS. A caller that needs another source's
|
|
34808
|
+
* coverage asks `recording.getAvailability` per device, naming that
|
|
34809
|
+
* source's provider.
|
|
34810
|
+
*
|
|
34374
34811
|
* `protected` for the same reason the singular method is: every id in
|
|
34375
34812
|
* `deviceIds` is a device reference, so the F1 #3 gate refuses any camera
|
|
34376
34813
|
* outside the caller's scope — one id out of scope refuses the CALL, it
|
|
@@ -34388,20 +34825,10 @@ var recordingCapability = {
|
|
|
34388
34825
|
kind: "query",
|
|
34389
34826
|
auth: "protected"
|
|
34390
34827
|
}),
|
|
34391
|
-
/** Which calendar days in [fromMs,toMs) have ≥1 recorded segment, bucketed by
|
|
34392
|
-
* the client's local day (`tzOffsetMinutes` = minutes to add to UTC). Drives
|
|
34393
|
-
* the theater date-picker's day dots. */
|
|
34394
|
-
getDaysWithRecordings: method(object({
|
|
34395
|
-
deviceId: number(),
|
|
34396
|
-
fromMs: number(),
|
|
34397
|
-
toMs: number(),
|
|
34398
|
-
tzOffsetMinutes: number()
|
|
34399
|
-
}), RecordingDaysSchema, {
|
|
34400
|
-
kind: "query",
|
|
34401
|
-
auth: "protected"
|
|
34402
|
-
}),
|
|
34403
34828
|
/**
|
|
34404
|
-
* `getDaysWithRecordings` for a SET of cameras, in one round
|
|
34829
|
+
* `recording.getDaysWithRecordings` for a SET of cameras, in one round
|
|
34830
|
+
* trip, at OUR source. Same placement argument as
|
|
34831
|
+
* {@link getAvailabilityBatch}.
|
|
34405
34832
|
*
|
|
34406
34833
|
* The cheapest question in the product, asked once per camera per month
|
|
34407
34834
|
* change. One directory read per day per camera at the owner, unchanged;
|
|
@@ -34420,14 +34847,6 @@ var recordingCapability = {
|
|
|
34420
34847
|
kind: "query",
|
|
34421
34848
|
auth: "protected"
|
|
34422
34849
|
}),
|
|
34423
|
-
getPlaybackManifest: method(object({
|
|
34424
|
-
deviceId: number(),
|
|
34425
|
-
fromMs: number(),
|
|
34426
|
-
toMs: number()
|
|
34427
|
-
}), RecordingManifestSchema, {
|
|
34428
|
-
kind: "query",
|
|
34429
|
-
auth: "protected"
|
|
34430
|
-
}),
|
|
34431
34850
|
getStorageUsage: method(object({}), RecordingStorageUsageSchema, {
|
|
34432
34851
|
kind: "query",
|
|
34433
34852
|
auth: "admin"
|
|
@@ -34443,6 +34862,10 @@ var recordingCapability = {
|
|
|
34443
34862
|
* value (D315, D393, D590). It carries schedules and retention, no secret,
|
|
34444
34863
|
* and the per-device gate in `scope-access.ts` still applies; `setDeviceConfig`
|
|
34445
34864
|
* stays `admin`.
|
|
34865
|
+
*
|
|
34866
|
+
* It is a SINGLETON method and stays one (D625 §16.2): it is the D62
|
|
34867
|
+
* recording authority, and an authority that several providers could answer
|
|
34868
|
+
* is the "two knobs over one decision" D62 forbids.
|
|
34446
34869
|
*/
|
|
34447
34870
|
getDeviceConfig: method(object({ deviceId: number() }), RecordingConfigSchema, {
|
|
34448
34871
|
kind: "query",
|
|
@@ -34450,7 +34873,13 @@ var recordingCapability = {
|
|
|
34450
34873
|
}),
|
|
34451
34874
|
/** Locate footage at a wall-clock instant: the covering segment's window,
|
|
34452
34875
|
* or a gap with the forward nearest covered edge. Used by a feeder running
|
|
34453
|
-
* in another addon process to seek recorded footage over tRPC.
|
|
34876
|
+
* in another addon process to seek recorded footage over tRPC.
|
|
34877
|
+
*
|
|
34878
|
+
* BYTE PLANE: defined by `mfra` byte ranges over OUR own MP4 segment tree.
|
|
34879
|
+
* There is no vendor-neutral statement of it, its only callers are feeders
|
|
34880
|
+
* in other addon processes (the stream broker, the replay-clip source),
|
|
34881
|
+
* and an onboard source that ever needs bytes delegates to the
|
|
34882
|
+
* `videoclips` transport its vendor already has (D625 §9.3, §10.5). */
|
|
34454
34883
|
locateSegment: method(object({
|
|
34455
34884
|
deviceId: number(),
|
|
34456
34885
|
profile: string(),
|
|
@@ -35183,6 +35612,307 @@ var recordingExportCapability = {
|
|
|
35183
35612
|
}
|
|
35184
35613
|
};
|
|
35185
35614
|
/**
|
|
35615
|
+
* Vendor-neutral **onboard** recording + storage cap — what the CAMERA
|
|
35616
|
+
* writes to the CAMERA's own card, on the camera's own schedule.
|
|
35617
|
+
*
|
|
35618
|
+
* This is NOT `recording.cap.ts`. That one is CamStack's recorder: our
|
|
35619
|
+
* footage ledger, our storage locations, our retention. This one has a
|
|
35620
|
+
* different authority — the camera's firmware — and per D62 it stores
|
|
35621
|
+
* nothing of its own. Every value here is read from the camera and every
|
|
35622
|
+
* write goes back to the camera; there is no CamStack-side mirror that
|
|
35623
|
+
* could disagree with the device.
|
|
35624
|
+
*
|
|
35625
|
+
* ## One shape, two firmwares
|
|
35626
|
+
*
|
|
35627
|
+
* Measured 2026-09-22 against the live fleet:
|
|
35628
|
+
*
|
|
35629
|
+
* | fact | Hikvision (ISAPI) | Reolink (Baichuan) |
|
|
35630
|
+
* | --- | --- | --- |
|
|
35631
|
+
* | storage | `ContentMgmt/Storage` `<hdd>` rows: status, capacity, freeSpace (MB) | `getHddInfoList` (cmd 102): `mount`, `format`, `capacity` GB + `capacityM` MB remainder |
|
|
35632
|
+
* | tracks | several (101 **and** 103 on both 1436 and 3833), each with its own schedule | one per channel |
|
|
35633
|
+
* | schedule | per track, 7 `ScheduleAction` blocks: DayOfWeek + TimeOfDay range + ONE `ActionRecordingMode` | per trigger type, a 168-char weekly HOUR mask |
|
|
35634
|
+
* | triggers | `CMR`, `MOTION` | `Normal`, `MD`, `people`, `vehicle`, `dog_cat`, `crossline`, `intrude`, `loitering` |
|
|
35635
|
+
* | pre-record | `PreRecordTimeSeconds` | `preRecordTime` |
|
|
35636
|
+
* | post-record | `PostRecordTimeSeconds` | `recordDelayTime` |
|
|
35637
|
+
* | overwrite | per track `LoopEnable` | `cycle`, with `cycleList` enumerating the accepted values |
|
|
35638
|
+
* | segment length | not exposed on V5.7.1 | `packageTime` (minutes) |
|
|
35639
|
+
*
|
|
35640
|
+
* The two schedule models look different and are the same thing in
|
|
35641
|
+
* different coordinates: both answer "for this trigger, during which
|
|
35642
|
+
* weekly windows does the camera record". {@link RecordWindow} is that
|
|
35643
|
+
* question in one shape — Hikvision's ranges map straight onto it,
|
|
35644
|
+
* Reolink's mask expands into hour-aligned windows.
|
|
35645
|
+
*
|
|
35646
|
+
* ## Union, not intersection
|
|
35647
|
+
*
|
|
35648
|
+
* **The same fields exist on every camera.** What differs per device is
|
|
35649
|
+
* which VALUES that device accepts, and that is what {@link
|
|
35650
|
+
* RecordingOnboardOptions} reports — a `{ readable, writable, reason }`
|
|
35651
|
+
* per field plus the schedule's own limits. A control a camera cannot
|
|
35652
|
+
* honour is rendered DISABLED WITH ITS REASON, never missing and never
|
|
35653
|
+
* dead: disabled must not look like broken.
|
|
35654
|
+
*
|
|
35655
|
+
* ## Refusal by name
|
|
35656
|
+
*
|
|
35657
|
+
* A write a camera cannot honour is refused with a sentence the operator
|
|
35658
|
+
* can read — never accepted and dropped. Both providers refuse through
|
|
35659
|
+
* {@link describeOnboardRefusal}, so the vocabulary is one function and
|
|
35660
|
+
* one test, not two hand-written vendor opinions.
|
|
35661
|
+
*
|
|
35662
|
+
* Follows the D14 `deviceConfig` archetype (see `stream-params.cap.ts`):
|
|
35663
|
+
* `getOptions` advertises per-camera availability, `getStatus` (auto-
|
|
35664
|
+
* injected from `status`) reports the live values, and a single
|
|
35665
|
+
* `setSettings` mutation applies a partial change. No hand-written
|
|
35666
|
+
* settings-contribution methods.
|
|
35667
|
+
*/
|
|
35668
|
+
/**
|
|
35669
|
+
* What makes the camera start recording during a window.
|
|
35670
|
+
*
|
|
35671
|
+
* The union of both vendors' vocabularies. `continuous` is Hikvision's
|
|
35672
|
+
* `CMR` and Reolink's `Normal`; `motion` is `MOTION` / `MD`. The
|
|
35673
|
+
* object-class triggers are Reolink-only today and the smart-event ones
|
|
35674
|
+
* (`lineCrossing`, `intrusion`, `loitering`) are Reolink-only on the
|
|
35675
|
+
* firmwares measured — a camera that cannot record on a trigger simply
|
|
35676
|
+
* does not list it in `options.schedule.triggers`, and a window naming
|
|
35677
|
+
* it is REFUSED, not dropped.
|
|
35678
|
+
*/
|
|
35679
|
+
var RecordTriggerSchema = _enum([
|
|
35680
|
+
"continuous",
|
|
35681
|
+
"motion",
|
|
35682
|
+
"person",
|
|
35683
|
+
"vehicle",
|
|
35684
|
+
"animal",
|
|
35685
|
+
"lineCrossing",
|
|
35686
|
+
"intrusion",
|
|
35687
|
+
"loitering",
|
|
35688
|
+
"alarmInput"
|
|
35689
|
+
]);
|
|
35690
|
+
/**
|
|
35691
|
+
* One weekly recording window: "on `day`, from `startMinute` to
|
|
35692
|
+
* `endMinute`, record on `trigger`".
|
|
35693
|
+
*
|
|
35694
|
+
* `day` is 0 = Monday … 6 = Sunday (ISO order, which is also the order
|
|
35695
|
+
* both firmwares enumerate). Minutes are local camera time since
|
|
35696
|
+
* midnight; `endMinute` may be 1440, meaning end of day — that is
|
|
35697
|
+
* Hikvision's literal `24:00` and Reolink's 24th mask slot, and
|
|
35698
|
+
* collapsing it to 0 would turn a whole-day window into an empty one.
|
|
35699
|
+
*/
|
|
35700
|
+
var RecordWindowSchema = object({
|
|
35701
|
+
trigger: RecordTriggerSchema,
|
|
35702
|
+
day: number().int().min(0).max(6),
|
|
35703
|
+
startMinute: number().int().min(0).max(1439),
|
|
35704
|
+
endMinute: number().int().min(1).max(1440)
|
|
35705
|
+
});
|
|
35706
|
+
/** Status of one physical volume, as the camera itself describes it. */
|
|
35707
|
+
var OnboardStorageVolumeSchema = object({
|
|
35708
|
+
/** The camera's own id for the volume (`hdd/id`, Reolink `HddInfo/number`). */
|
|
35709
|
+
id: string(),
|
|
35710
|
+
/** The camera's own name for it, when it gives one (`hddName`). */
|
|
35711
|
+
label: string().optional(),
|
|
35712
|
+
status: _enum([
|
|
35713
|
+
"ok",
|
|
35714
|
+
"unformatted",
|
|
35715
|
+
"error",
|
|
35716
|
+
"offline",
|
|
35717
|
+
"unknown"
|
|
35718
|
+
]),
|
|
35719
|
+
/**
|
|
35720
|
+
* Total size in MB, or **null when the camera did not say**.
|
|
35721
|
+
*
|
|
35722
|
+
* Never 0 for an unreadable value: a measurement that failed is not a
|
|
35723
|
+
* measurement (D393), and a card whose size is unknown must not be
|
|
35724
|
+
* rendered as a card of size zero.
|
|
35725
|
+
*/
|
|
35726
|
+
capacityMb: number().nullable(),
|
|
35727
|
+
/**
|
|
35728
|
+
* Free space in MB, or null when unknown.
|
|
35729
|
+
*
|
|
35730
|
+
* **Not a proxy for "has footage".** Measured 2026-09-22: 1436 and
|
|
35731
|
+
* 1439 both report exactly 11776 MB free — the fixed reserve a looping
|
|
35732
|
+
* card converges on once it has wrapped. At loop steady state the
|
|
35733
|
+
* number is identical whether the camera recorded yesterday or stopped
|
|
35734
|
+
* a month ago.
|
|
35735
|
+
*/
|
|
35736
|
+
freeMb: number().nullable(),
|
|
35737
|
+
/** True when the camera reports the volume writable (`property` RW). */
|
|
35738
|
+
writable: boolean().optional()
|
|
35739
|
+
});
|
|
35740
|
+
object({
|
|
35741
|
+
storage: discriminatedUnion("kind", [
|
|
35742
|
+
object({
|
|
35743
|
+
kind: literal("present"),
|
|
35744
|
+
volumes: array(OnboardStorageVolumeSchema)
|
|
35745
|
+
}),
|
|
35746
|
+
object({
|
|
35747
|
+
kind: literal("absent"),
|
|
35748
|
+
reason: string()
|
|
35749
|
+
}),
|
|
35750
|
+
object({
|
|
35751
|
+
kind: literal("unknown"),
|
|
35752
|
+
reason: string()
|
|
35753
|
+
})
|
|
35754
|
+
]),
|
|
35755
|
+
tracks: array(object({
|
|
35756
|
+
id: string(),
|
|
35757
|
+
enabled: boolean(),
|
|
35758
|
+
isVideo: boolean(),
|
|
35759
|
+
/** From the camera's own track description. Null when it does not say. */
|
|
35760
|
+
codec: string().nullable(),
|
|
35761
|
+
resolution: string().nullable(),
|
|
35762
|
+
/** Per-track overwrite flag, where the firmware keeps it per track. */
|
|
35763
|
+
overwriteWhenFull: boolean().nullable()
|
|
35764
|
+
})),
|
|
35765
|
+
/**
|
|
35766
|
+
* The track the write path targets — the enabled VIDEO one. Null when
|
|
35767
|
+
* no track could be identified, which is itself a refusal reason.
|
|
35768
|
+
*/
|
|
35769
|
+
primaryTrackId: string().nullable(),
|
|
35770
|
+
/** Master "record to the card at all" switch. */
|
|
35771
|
+
enabled: boolean().nullable(),
|
|
35772
|
+
overwriteWhenFull: boolean().nullable(),
|
|
35773
|
+
preRecordSec: number().nullable(),
|
|
35774
|
+
postRecordSec: number().nullable(),
|
|
35775
|
+
/** Length of one recorded file, in minutes. */
|
|
35776
|
+
segmentMinutes: number().nullable(),
|
|
35777
|
+
/** The primary track's weekly windows, flattened. */
|
|
35778
|
+
windows: array(RecordWindowSchema),
|
|
35779
|
+
/**
|
|
35780
|
+
* How many windows the camera described that CamStack could NOT read —
|
|
35781
|
+
* an unrecognised trigger, an unparseable clock, a weekday it does not
|
|
35782
|
+
* name.
|
|
35783
|
+
*
|
|
35784
|
+
* A dropped window is work the reader threw away, and a schedule that
|
|
35785
|
+
* silently shows fewer rows than the camera holds is how an operator
|
|
35786
|
+
* saves back a schedule shorter than the one they were looking at
|
|
35787
|
+
* (D391). Non-zero means the window list is INCOMPLETE and a write
|
|
35788
|
+
* that replaces it would delete what was not shown — which is why a
|
|
35789
|
+
* provider reporting a non-zero count also reports the schedule as not
|
|
35790
|
+
* writable.
|
|
35791
|
+
*/
|
|
35792
|
+
unreadableWindows: number(),
|
|
35793
|
+
/**
|
|
35794
|
+
* The camera is scheduled to record and has NO usable storage.
|
|
35795
|
+
*
|
|
35796
|
+
* A first-class fact because it is the fleet's most common silent
|
|
35797
|
+
* defect: measured 2026-09-22, 1441 and 3831 are both motion-recording
|
|
35798
|
+
* to a card that is not there. Neither the schedule nor the storage
|
|
35799
|
+
* read says anything wrong on its own; only the pair does.
|
|
35800
|
+
*/
|
|
35801
|
+
recordingToNowhere: boolean(),
|
|
35802
|
+
lastFetchedAt: number()
|
|
35803
|
+
});
|
|
35804
|
+
/** Numeric range descriptor — `{ min, max, step }` per the getOptions convention. */
|
|
35805
|
+
var RangeSchema = object({
|
|
35806
|
+
min: number(),
|
|
35807
|
+
max: number(),
|
|
35808
|
+
step: number()
|
|
35809
|
+
});
|
|
35810
|
+
/**
|
|
35811
|
+
* The values a camera actually takes for a numeric field, when they are a SET
|
|
35812
|
+
* rather than a range.
|
|
35813
|
+
*
|
|
35814
|
+
* `{min,max,step}` cannot say what these two firmwares do. Measured on 1436
|
|
35815
|
+
* (I91DN) on 2026-09-22 by writing each value and reading it back:
|
|
35816
|
+
*
|
|
35817
|
+
* - pre-record: `0, 5, 10, 15, 20, 25, 30` and `2147483647` (INT32_MAX, the
|
|
35818
|
+
* camera's "no limit" — `-1` and `4294967295` both land on it);
|
|
35819
|
+
* - post-record: `5, 10, 30, 60, 120, 300, 600`.
|
|
35820
|
+
*
|
|
35821
|
+
* Neither is expressible as a step: the first has a sentinel two billion away
|
|
35822
|
+
* from its neighbours, the second doubles and then jumps. A range that tried
|
|
35823
|
+
* would forbid values the camera takes AND permit values it silently replaces
|
|
35824
|
+
* with 5 — wrong in both directions at once.
|
|
35825
|
+
*
|
|
35826
|
+
* `sentinel` names the member that is not a duration, so a surface can render
|
|
35827
|
+
* "no limit" instead of `2147483647` seconds.
|
|
35828
|
+
*/
|
|
35829
|
+
var AllowedValuesSchema = object({
|
|
35830
|
+
values: array(number()).min(1),
|
|
35831
|
+
sentinel: object({
|
|
35832
|
+
value: number(),
|
|
35833
|
+
meaning: _enum(["no-limit", "disabled"])
|
|
35834
|
+
}).optional()
|
|
35835
|
+
});
|
|
35836
|
+
/**
|
|
35837
|
+
* Per-field availability on ONE camera.
|
|
35838
|
+
*
|
|
35839
|
+
* The field exists on every camera — this says whether this one can be
|
|
35840
|
+
* read and whether it can be written, and `reason` says why not when
|
|
35841
|
+
* either is false. The UI renders the control DISABLED with the reason
|
|
35842
|
+
* rather than hiding it, so a limitation is legible instead of looking
|
|
35843
|
+
* like a missing feature.
|
|
35844
|
+
*/
|
|
35845
|
+
var OnboardFieldSupportSchema = object({
|
|
35846
|
+
readable: boolean(),
|
|
35847
|
+
writable: boolean(),
|
|
35848
|
+
/** Required whenever `readable` or `writable` is false. */
|
|
35849
|
+
reason: string().optional()
|
|
35850
|
+
});
|
|
35851
|
+
/** What this camera's schedule model can express. */
|
|
35852
|
+
var OnboardScheduleSupportSchema = object({
|
|
35853
|
+
support: OnboardFieldSupportSchema,
|
|
35854
|
+
/**
|
|
35855
|
+
* The smallest time step the camera can express, in minutes.
|
|
35856
|
+
*
|
|
35857
|
+
* Hikvision takes arbitrary minutes (`00:05:00`–`23:57:00` observed on
|
|
35858
|
+
* 1436's track 103). Reolink's schedule is a 7×24 HOUR mask, so 60. A
|
|
35859
|
+
* window whose edges are not a multiple of this is REFUSED rather than
|
|
35860
|
+
* quietly rounded — rounding is how an operator's 06:30 becomes 06:00
|
|
35861
|
+
* and nothing says so.
|
|
35862
|
+
*/
|
|
35863
|
+
granularityMinutes: number(),
|
|
35864
|
+
/** Triggers this camera can record on. A window naming another is refused. */
|
|
35865
|
+
triggers: array(RecordTriggerSchema),
|
|
35866
|
+
/**
|
|
35867
|
+
* False when the camera stores ONE trigger per time range, so two
|
|
35868
|
+
* windows overlapping on the same day cannot carry different triggers.
|
|
35869
|
+
* True on Reolink, whose mask is per-trigger and independent.
|
|
35870
|
+
*/
|
|
35871
|
+
supportsOverlappingTriggers: boolean()
|
|
35872
|
+
});
|
|
35873
|
+
var RecordingOnboardOptionsSchema = object({
|
|
35874
|
+
enabled: OnboardFieldSupportSchema,
|
|
35875
|
+
overwriteWhenFull: OnboardFieldSupportSchema,
|
|
35876
|
+
preRecordSec: OnboardFieldSupportSchema,
|
|
35877
|
+
preRecordSecRange: RangeSchema.optional(),
|
|
35878
|
+
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
35879
|
+
preRecordSecAllowed: AllowedValuesSchema.optional(),
|
|
35880
|
+
postRecordSec: OnboardFieldSupportSchema,
|
|
35881
|
+
postRecordSecRange: RangeSchema.optional(),
|
|
35882
|
+
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
35883
|
+
postRecordSecAllowed: AllowedValuesSchema.optional(),
|
|
35884
|
+
segmentMinutes: OnboardFieldSupportSchema,
|
|
35885
|
+
segmentMinutesRange: RangeSchema.optional(),
|
|
35886
|
+
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
35887
|
+
segmentMinutesAllowed: AllowedValuesSchema.optional(),
|
|
35888
|
+
schedule: OnboardScheduleSupportSchema
|
|
35889
|
+
});
|
|
35890
|
+
/**
|
|
35891
|
+
* A partial change. Every field optional.
|
|
35892
|
+
*
|
|
35893
|
+
* Unlike the other `deviceConfig` caps, a provider here does **NOT**
|
|
35894
|
+
* silently ignore a field it cannot support — it refuses, by name,
|
|
35895
|
+
* through {@link describeOnboardRefusal}. Silence on a recording setting
|
|
35896
|
+
* is the failure D62 exists to prevent: the operator believes the camera
|
|
35897
|
+
* is recording the way the form says, and it is not.
|
|
35898
|
+
*/
|
|
35899
|
+
var RecordingOnboardPatchSchema = object({
|
|
35900
|
+
enabled: boolean().optional(),
|
|
35901
|
+
overwriteWhenFull: boolean().optional(),
|
|
35902
|
+
preRecordSec: number().optional(),
|
|
35903
|
+
postRecordSec: number().optional(),
|
|
35904
|
+
segmentMinutes: number().optional(),
|
|
35905
|
+
/** The complete new window set for the primary track — not a delta. */
|
|
35906
|
+
windows: array(RecordWindowSchema).optional()
|
|
35907
|
+
});
|
|
35908
|
+
DeviceType.Camera, method(object({ deviceId: number() }), RecordingOnboardOptionsSchema), method(object({
|
|
35909
|
+
deviceId: number(),
|
|
35910
|
+
settings: RecordingOnboardPatchSchema
|
|
35911
|
+
}), _void(), {
|
|
35912
|
+
kind: "mutation",
|
|
35913
|
+
auth: "admin"
|
|
35914
|
+
});
|
|
35915
|
+
/**
|
|
35186
35916
|
* A camera's own "record me NOW" LEVEL — a signal the device raises while
|
|
35187
35917
|
* something it knows about is happening (a robot vacuum cleaning, a machine
|
|
35188
35918
|
* running, a gate open) and lowers when it stops.
|
|
@@ -40179,6 +40909,12 @@ Object.freeze({
|
|
|
40179
40909
|
addonId: null,
|
|
40180
40910
|
access: "view"
|
|
40181
40911
|
},
|
|
40912
|
+
"deviceManager.listDeviceIdsByCaps": {
|
|
40913
|
+
capName: "device-manager",
|
|
40914
|
+
capScope: "system",
|
|
40915
|
+
addonId: null,
|
|
40916
|
+
access: "view"
|
|
40917
|
+
},
|
|
40182
40918
|
"deviceManager.listLocationDescriptions": {
|
|
40183
40919
|
capName: "device-manager",
|
|
40184
40920
|
capScope: "system",
|
|
@@ -40665,24 +41401,6 @@ Object.freeze({
|
|
|
40665
41401
|
addonId: null,
|
|
40666
41402
|
access: "view"
|
|
40667
41403
|
},
|
|
40668
|
-
"events.getEventClipUrl": {
|
|
40669
|
-
capName: "events",
|
|
40670
|
-
capScope: "device",
|
|
40671
|
-
addonId: null,
|
|
40672
|
-
access: "view"
|
|
40673
|
-
},
|
|
40674
|
-
"events.getEvents": {
|
|
40675
|
-
capName: "events",
|
|
40676
|
-
capScope: "device",
|
|
40677
|
-
addonId: null,
|
|
40678
|
-
access: "view"
|
|
40679
|
-
},
|
|
40680
|
-
"events.getEventThumbnail": {
|
|
40681
|
-
capName: "events",
|
|
40682
|
-
capScope: "device",
|
|
40683
|
-
addonId: null,
|
|
40684
|
-
access: "view"
|
|
40685
|
-
},
|
|
40686
41404
|
"faceGallery.assignFace": {
|
|
40687
41405
|
capName: "face-gallery",
|
|
40688
41406
|
capScope: "system",
|
|
@@ -42003,6 +42721,12 @@ Object.freeze({
|
|
|
42003
42721
|
addonId: null,
|
|
42004
42722
|
access: "view"
|
|
42005
42723
|
},
|
|
42724
|
+
"notificationRules.getTemplateCatalog": {
|
|
42725
|
+
capName: "notification-rules",
|
|
42726
|
+
capScope: "system",
|
|
42727
|
+
addonId: null,
|
|
42728
|
+
access: "view"
|
|
42729
|
+
},
|
|
42006
42730
|
"notificationRules.listDeviceMutes": {
|
|
42007
42731
|
capName: "notification-rules",
|
|
42008
42732
|
capScope: "system",
|
|
@@ -42021,6 +42745,12 @@ Object.freeze({
|
|
|
42021
42745
|
addonId: null,
|
|
42022
42746
|
access: "view"
|
|
42023
42747
|
},
|
|
42748
|
+
"notificationRules.previewTemplate": {
|
|
42749
|
+
capName: "notification-rules",
|
|
42750
|
+
capScope: "system",
|
|
42751
|
+
addonId: null,
|
|
42752
|
+
access: "view"
|
|
42753
|
+
},
|
|
42024
42754
|
"notificationRules.resolveArtifactUrl": {
|
|
42025
42755
|
capName: "notification-rules",
|
|
42026
42756
|
capScope: "system",
|
|
@@ -43557,224 +44287,236 @@ Object.freeze({
|
|
|
43557
44287
|
addonId: null,
|
|
43558
44288
|
access: "create"
|
|
43559
44289
|
},
|
|
43560
|
-
"recording.
|
|
44290
|
+
"recording.getAvailability": {
|
|
43561
44291
|
capName: "recording",
|
|
43562
|
-
capScope: "
|
|
44292
|
+
capScope: "device",
|
|
43563
44293
|
addonId: null,
|
|
43564
|
-
access: "
|
|
44294
|
+
access: "view"
|
|
43565
44295
|
},
|
|
43566
|
-
"recording.
|
|
44296
|
+
"recording.getDaysWithRecordings": {
|
|
43567
44297
|
capName: "recording",
|
|
43568
|
-
capScope: "
|
|
44298
|
+
capScope: "device",
|
|
43569
44299
|
addonId: null,
|
|
43570
|
-
access: "
|
|
44300
|
+
access: "view"
|
|
43571
44301
|
},
|
|
43572
|
-
"recording.
|
|
44302
|
+
"recording.getPlayback": {
|
|
43573
44303
|
capName: "recording",
|
|
43574
|
-
capScope: "
|
|
44304
|
+
capScope: "device",
|
|
43575
44305
|
addonId: null,
|
|
43576
|
-
access: "
|
|
44306
|
+
access: "view"
|
|
43577
44307
|
},
|
|
43578
|
-
"recording.
|
|
44308
|
+
"recording.getPlaybackOptions": {
|
|
43579
44309
|
capName: "recording",
|
|
43580
|
-
capScope: "
|
|
44310
|
+
capScope: "device",
|
|
43581
44311
|
addonId: null,
|
|
43582
|
-
access: "
|
|
44312
|
+
access: "view"
|
|
43583
44313
|
},
|
|
43584
|
-
"recording.
|
|
44314
|
+
"recording.listSources": {
|
|
43585
44315
|
capName: "recording",
|
|
43586
|
-
capScope: "
|
|
44316
|
+
capScope: "device",
|
|
43587
44317
|
addonId: null,
|
|
43588
44318
|
access: "view"
|
|
43589
44319
|
},
|
|
43590
|
-
"
|
|
43591
|
-
capName: "recording",
|
|
44320
|
+
"recordingArchive.applyDeviceSettingsPatch": {
|
|
44321
|
+
capName: "recording-archive",
|
|
43592
44322
|
capScope: "system",
|
|
43593
44323
|
addonId: null,
|
|
43594
|
-
access: "
|
|
44324
|
+
access: "create"
|
|
43595
44325
|
},
|
|
43596
|
-
"
|
|
43597
|
-
capName: "recording",
|
|
44326
|
+
"recordingArchive.cancelRelocateJob": {
|
|
44327
|
+
capName: "recording-archive",
|
|
43598
44328
|
capScope: "system",
|
|
43599
44329
|
addonId: null,
|
|
43600
|
-
access: "
|
|
44330
|
+
access: "create"
|
|
43601
44331
|
},
|
|
43602
|
-
"
|
|
43603
|
-
capName: "recording",
|
|
44332
|
+
"recordingArchive.cancelStorageMigrationMove": {
|
|
44333
|
+
capName: "recording-archive",
|
|
44334
|
+
capScope: "system",
|
|
44335
|
+
addonId: null,
|
|
44336
|
+
access: "create"
|
|
44337
|
+
},
|
|
44338
|
+
"recordingArchive.deleteFootprint": {
|
|
44339
|
+
capName: "recording-archive",
|
|
44340
|
+
capScope: "system",
|
|
44341
|
+
addonId: null,
|
|
44342
|
+
access: "delete"
|
|
44343
|
+
},
|
|
44344
|
+
"recordingArchive.getAvailabilityBatch": {
|
|
44345
|
+
capName: "recording-archive",
|
|
43604
44346
|
capScope: "system",
|
|
43605
44347
|
addonId: null,
|
|
43606
44348
|
access: "view"
|
|
43607
44349
|
},
|
|
43608
|
-
"
|
|
43609
|
-
capName: "recording",
|
|
44350
|
+
"recordingArchive.getDaysWithRecordingsBatch": {
|
|
44351
|
+
capName: "recording-archive",
|
|
43610
44352
|
capScope: "system",
|
|
43611
44353
|
addonId: null,
|
|
43612
44354
|
access: "view"
|
|
43613
44355
|
},
|
|
43614
|
-
"
|
|
43615
|
-
capName: "recording",
|
|
44356
|
+
"recordingArchive.getDeviceConfig": {
|
|
44357
|
+
capName: "recording-archive",
|
|
43616
44358
|
capScope: "system",
|
|
43617
44359
|
addonId: null,
|
|
43618
44360
|
access: "view"
|
|
43619
44361
|
},
|
|
43620
|
-
"
|
|
43621
|
-
capName: "recording",
|
|
44362
|
+
"recordingArchive.getDeviceLiveContribution": {
|
|
44363
|
+
capName: "recording-archive",
|
|
43622
44364
|
capScope: "system",
|
|
43623
44365
|
addonId: null,
|
|
43624
44366
|
access: "view"
|
|
43625
44367
|
},
|
|
43626
|
-
"
|
|
43627
|
-
capName: "recording",
|
|
44368
|
+
"recordingArchive.getDeviceSettingsContribution": {
|
|
44369
|
+
capName: "recording-archive",
|
|
43628
44370
|
capScope: "system",
|
|
43629
44371
|
addonId: null,
|
|
43630
44372
|
access: "view"
|
|
43631
44373
|
},
|
|
43632
|
-
"
|
|
43633
|
-
capName: "recording",
|
|
44374
|
+
"recordingArchive.getPlacement": {
|
|
44375
|
+
capName: "recording-archive",
|
|
43634
44376
|
capScope: "system",
|
|
43635
44377
|
addonId: null,
|
|
43636
44378
|
access: "view"
|
|
43637
44379
|
},
|
|
43638
|
-
"
|
|
43639
|
-
capName: "recording",
|
|
44380
|
+
"recordingArchive.getRelocateResidue": {
|
|
44381
|
+
capName: "recording-archive",
|
|
43640
44382
|
capScope: "system",
|
|
43641
44383
|
addonId: null,
|
|
43642
44384
|
access: "view"
|
|
43643
44385
|
},
|
|
43644
|
-
"
|
|
43645
|
-
capName: "recording",
|
|
44386
|
+
"recordingArchive.getStatus": {
|
|
44387
|
+
capName: "recording-archive",
|
|
43646
44388
|
capScope: "system",
|
|
43647
44389
|
addonId: null,
|
|
43648
44390
|
access: "view"
|
|
43649
44391
|
},
|
|
43650
|
-
"
|
|
43651
|
-
capName: "recording",
|
|
44392
|
+
"recordingArchive.getStorageMigrationMoveStatus": {
|
|
44393
|
+
capName: "recording-archive",
|
|
43652
44394
|
capScope: "system",
|
|
43653
44395
|
addonId: null,
|
|
43654
44396
|
access: "view"
|
|
43655
44397
|
},
|
|
43656
|
-
"
|
|
43657
|
-
capName: "recording",
|
|
44398
|
+
"recordingArchive.getStorageUsage": {
|
|
44399
|
+
capName: "recording-archive",
|
|
43658
44400
|
capScope: "system",
|
|
43659
44401
|
addonId: null,
|
|
43660
44402
|
access: "view"
|
|
43661
44403
|
},
|
|
43662
|
-
"
|
|
43663
|
-
capName: "recording",
|
|
44404
|
+
"recordingArchive.listOpsLog": {
|
|
44405
|
+
capName: "recording-archive",
|
|
43664
44406
|
capScope: "system",
|
|
43665
44407
|
addonId: null,
|
|
43666
44408
|
access: "view"
|
|
43667
44409
|
},
|
|
43668
|
-
"
|
|
43669
|
-
capName: "recording",
|
|
44410
|
+
"recordingArchive.listRelocateJobs": {
|
|
44411
|
+
capName: "recording-archive",
|
|
43670
44412
|
capScope: "system",
|
|
43671
44413
|
addonId: null,
|
|
43672
44414
|
access: "view"
|
|
43673
44415
|
},
|
|
43674
|
-
"
|
|
43675
|
-
capName: "recording",
|
|
44416
|
+
"recordingArchive.locateSegment": {
|
|
44417
|
+
capName: "recording-archive",
|
|
43676
44418
|
capScope: "system",
|
|
43677
44419
|
addonId: null,
|
|
43678
44420
|
access: "view"
|
|
43679
44421
|
},
|
|
43680
|
-
"
|
|
43681
|
-
capName: "recording",
|
|
44422
|
+
"recordingArchive.pauseForStorageMigration": {
|
|
44423
|
+
capName: "recording-archive",
|
|
43682
44424
|
capScope: "system",
|
|
43683
44425
|
addonId: null,
|
|
43684
44426
|
access: "create"
|
|
43685
44427
|
},
|
|
43686
|
-
"
|
|
43687
|
-
capName: "recording",
|
|
44428
|
+
"recordingArchive.planStorageRebalance": {
|
|
44429
|
+
capName: "recording-archive",
|
|
43688
44430
|
capScope: "system",
|
|
43689
44431
|
addonId: null,
|
|
43690
44432
|
access: "view"
|
|
43691
44433
|
},
|
|
43692
|
-
"
|
|
43693
|
-
capName: "recording",
|
|
44434
|
+
"recordingArchive.pruneFootage": {
|
|
44435
|
+
capName: "recording-archive",
|
|
43694
44436
|
capScope: "system",
|
|
43695
44437
|
addonId: null,
|
|
43696
44438
|
access: "create"
|
|
43697
44439
|
},
|
|
43698
|
-
"
|
|
43699
|
-
capName: "recording",
|
|
44440
|
+
"recordingArchive.readGopBytes": {
|
|
44441
|
+
capName: "recording-archive",
|
|
43700
44442
|
capScope: "system",
|
|
43701
44443
|
addonId: null,
|
|
43702
44444
|
access: "view"
|
|
43703
44445
|
},
|
|
43704
|
-
"
|
|
43705
|
-
capName: "recording",
|
|
44446
|
+
"recordingArchive.readSegmentBytes": {
|
|
44447
|
+
capName: "recording-archive",
|
|
43706
44448
|
capScope: "system",
|
|
43707
44449
|
addonId: null,
|
|
43708
44450
|
access: "view"
|
|
43709
44451
|
},
|
|
43710
|
-
"
|
|
43711
|
-
capName: "recording",
|
|
44452
|
+
"recordingArchive.readWindowBytes": {
|
|
44453
|
+
capName: "recording-archive",
|
|
43712
44454
|
capScope: "system",
|
|
43713
44455
|
addonId: null,
|
|
43714
44456
|
access: "view"
|
|
43715
44457
|
},
|
|
43716
|
-
"
|
|
43717
|
-
capName: "recording",
|
|
44458
|
+
"recordingArchive.reconcileLedgerAgainstDisk": {
|
|
44459
|
+
capName: "recording-archive",
|
|
43718
44460
|
capScope: "system",
|
|
43719
44461
|
addonId: null,
|
|
43720
44462
|
access: "create"
|
|
43721
44463
|
},
|
|
43722
|
-
"
|
|
43723
|
-
capName: "recording",
|
|
44464
|
+
"recordingArchive.refreshStorageLocationsForMigration": {
|
|
44465
|
+
capName: "recording-archive",
|
|
43724
44466
|
capScope: "system",
|
|
43725
44467
|
addonId: null,
|
|
43726
44468
|
access: "create"
|
|
43727
44469
|
},
|
|
43728
|
-
"
|
|
43729
|
-
capName: "recording",
|
|
44470
|
+
"recordingArchive.relocateFootage": {
|
|
44471
|
+
capName: "recording-archive",
|
|
43730
44472
|
capScope: "system",
|
|
43731
44473
|
addonId: null,
|
|
43732
44474
|
access: "create"
|
|
43733
44475
|
},
|
|
43734
|
-
"
|
|
43735
|
-
capName: "recording",
|
|
44476
|
+
"recordingArchive.renderClip": {
|
|
44477
|
+
capName: "recording-archive",
|
|
43736
44478
|
capScope: "system",
|
|
43737
44479
|
addonId: null,
|
|
43738
44480
|
access: "create"
|
|
43739
44481
|
},
|
|
43740
|
-
"
|
|
43741
|
-
capName: "recording",
|
|
44482
|
+
"recordingArchive.renderGif": {
|
|
44483
|
+
capName: "recording-archive",
|
|
43742
44484
|
capScope: "system",
|
|
43743
44485
|
addonId: null,
|
|
43744
44486
|
access: "create"
|
|
43745
44487
|
},
|
|
43746
|
-
"
|
|
43747
|
-
capName: "recording",
|
|
44488
|
+
"recordingArchive.rescanStorage": {
|
|
44489
|
+
capName: "recording-archive",
|
|
43748
44490
|
capScope: "system",
|
|
43749
44491
|
addonId: null,
|
|
43750
44492
|
access: "create"
|
|
43751
44493
|
},
|
|
43752
|
-
"
|
|
43753
|
-
capName: "recording",
|
|
44494
|
+
"recordingArchive.resumeForStorageMigration": {
|
|
44495
|
+
capName: "recording-archive",
|
|
43754
44496
|
capScope: "system",
|
|
43755
44497
|
addonId: null,
|
|
43756
44498
|
access: "create"
|
|
43757
44499
|
},
|
|
43758
|
-
"
|
|
43759
|
-
capName: "recording",
|
|
44500
|
+
"recordingArchive.setDeviceConfig": {
|
|
44501
|
+
capName: "recording-archive",
|
|
43760
44502
|
capScope: "system",
|
|
43761
44503
|
addonId: null,
|
|
43762
44504
|
access: "create"
|
|
43763
44505
|
},
|
|
43764
|
-
"
|
|
43765
|
-
capName: "recording",
|
|
44506
|
+
"recordingArchive.setDevicePlacement": {
|
|
44507
|
+
capName: "recording-archive",
|
|
43766
44508
|
capScope: "system",
|
|
43767
44509
|
addonId: null,
|
|
43768
44510
|
access: "create"
|
|
43769
44511
|
},
|
|
43770
|
-
"
|
|
43771
|
-
capName: "recording",
|
|
44512
|
+
"recordingArchive.startStorageMigrationMove": {
|
|
44513
|
+
capName: "recording-archive",
|
|
43772
44514
|
capScope: "system",
|
|
43773
44515
|
addonId: null,
|
|
43774
44516
|
access: "create"
|
|
43775
44517
|
},
|
|
43776
|
-
"
|
|
43777
|
-
capName: "recording",
|
|
44518
|
+
"recordingArchive.startStorageRebalance": {
|
|
44519
|
+
capName: "recording-archive",
|
|
43778
44520
|
capScope: "system",
|
|
43779
44521
|
addonId: null,
|
|
43780
44522
|
access: "create"
|
|
@@ -46079,21 +46821,6 @@ Object.freeze({
|
|
|
46079
46821
|
form: "single",
|
|
46080
46822
|
optional: false
|
|
46081
46823
|
}],
|
|
46082
|
-
"events.getEventClipUrl": [{
|
|
46083
|
-
name: "deviceId",
|
|
46084
|
-
form: "single",
|
|
46085
|
-
optional: false
|
|
46086
|
-
}],
|
|
46087
|
-
"events.getEvents": [{
|
|
46088
|
-
name: "deviceId",
|
|
46089
|
-
form: "single",
|
|
46090
|
-
optional: false
|
|
46091
|
-
}],
|
|
46092
|
-
"events.getEventThumbnail": [{
|
|
46093
|
-
name: "deviceId",
|
|
46094
|
-
form: "single",
|
|
46095
|
-
optional: false
|
|
46096
|
-
}],
|
|
46097
46824
|
"faceGallery.getFaceByTrack": [{
|
|
46098
46825
|
name: "deviceId",
|
|
46099
46826
|
form: "single",
|
|
@@ -47012,107 +47739,117 @@ Object.freeze({
|
|
|
47012
47739
|
form: "single",
|
|
47013
47740
|
optional: false
|
|
47014
47741
|
}],
|
|
47015
|
-
"recording.
|
|
47742
|
+
"recording.getAvailability": [{
|
|
47016
47743
|
name: "deviceId",
|
|
47017
47744
|
form: "single",
|
|
47018
47745
|
optional: false
|
|
47019
47746
|
}],
|
|
47020
|
-
"recording.
|
|
47747
|
+
"recording.getDaysWithRecordings": [{
|
|
47021
47748
|
name: "deviceId",
|
|
47022
47749
|
form: "single",
|
|
47023
47750
|
optional: false
|
|
47024
47751
|
}],
|
|
47025
|
-
"recording.
|
|
47026
|
-
name: "
|
|
47027
|
-
form: "
|
|
47752
|
+
"recording.getPlayback": [{
|
|
47753
|
+
name: "deviceId",
|
|
47754
|
+
form: "single",
|
|
47028
47755
|
optional: false
|
|
47029
47756
|
}],
|
|
47030
|
-
"recording.
|
|
47757
|
+
"recording.getPlaybackOptions": [{
|
|
47031
47758
|
name: "deviceId",
|
|
47032
47759
|
form: "single",
|
|
47033
47760
|
optional: false
|
|
47034
47761
|
}],
|
|
47035
|
-
"recording.
|
|
47036
|
-
name: "
|
|
47037
|
-
form: "
|
|
47762
|
+
"recording.listSources": [{
|
|
47763
|
+
name: "deviceId",
|
|
47764
|
+
form: "single",
|
|
47038
47765
|
optional: false
|
|
47039
47766
|
}],
|
|
47040
|
-
"
|
|
47767
|
+
"recordingArchive.deleteFootprint": [{
|
|
47041
47768
|
name: "deviceId",
|
|
47042
47769
|
form: "single",
|
|
47043
47770
|
optional: false
|
|
47044
47771
|
}],
|
|
47045
|
-
"
|
|
47772
|
+
"recordingArchive.getAvailabilityBatch": [{
|
|
47773
|
+
name: "deviceIds",
|
|
47774
|
+
form: "array",
|
|
47775
|
+
optional: false
|
|
47776
|
+
}],
|
|
47777
|
+
"recordingArchive.getDaysWithRecordingsBatch": [{
|
|
47778
|
+
name: "deviceIds",
|
|
47779
|
+
form: "array",
|
|
47780
|
+
optional: false
|
|
47781
|
+
}],
|
|
47782
|
+
"recordingArchive.getDeviceConfig": [{
|
|
47046
47783
|
name: "deviceId",
|
|
47047
47784
|
form: "single",
|
|
47048
47785
|
optional: false
|
|
47049
47786
|
}],
|
|
47050
|
-
"
|
|
47787
|
+
"recordingArchive.listOpsLog": [{
|
|
47051
47788
|
name: "deviceId",
|
|
47052
47789
|
form: "single",
|
|
47053
47790
|
optional: true
|
|
47054
47791
|
}],
|
|
47055
|
-
"
|
|
47792
|
+
"recordingArchive.locateSegment": [{
|
|
47056
47793
|
name: "deviceId",
|
|
47057
47794
|
form: "single",
|
|
47058
47795
|
optional: false
|
|
47059
47796
|
}],
|
|
47060
|
-
"
|
|
47797
|
+
"recordingArchive.pruneFootage": [{
|
|
47061
47798
|
name: "deviceId",
|
|
47062
47799
|
form: "single",
|
|
47063
47800
|
optional: false
|
|
47064
47801
|
}],
|
|
47065
|
-
"
|
|
47802
|
+
"recordingArchive.readGopBytes": [{
|
|
47066
47803
|
name: "deviceId",
|
|
47067
47804
|
form: "single",
|
|
47068
47805
|
optional: false
|
|
47069
47806
|
}],
|
|
47070
|
-
"
|
|
47807
|
+
"recordingArchive.readSegmentBytes": [{
|
|
47071
47808
|
name: "deviceId",
|
|
47072
47809
|
form: "single",
|
|
47073
47810
|
optional: false
|
|
47074
47811
|
}],
|
|
47075
|
-
"
|
|
47812
|
+
"recordingArchive.readWindowBytes": [{
|
|
47076
47813
|
name: "deviceId",
|
|
47077
47814
|
form: "single",
|
|
47078
47815
|
optional: false
|
|
47079
47816
|
}],
|
|
47080
|
-
"
|
|
47817
|
+
"recordingArchive.reconcileLedgerAgainstDisk": [{
|
|
47081
47818
|
name: "deviceId",
|
|
47082
47819
|
form: "single",
|
|
47083
47820
|
optional: true
|
|
47084
47821
|
}],
|
|
47085
|
-
"
|
|
47822
|
+
"recordingArchive.relocateFootage": [{
|
|
47086
47823
|
name: "deviceId",
|
|
47087
47824
|
form: "single",
|
|
47088
47825
|
optional: true
|
|
47089
47826
|
}],
|
|
47090
|
-
"
|
|
47827
|
+
"recordingArchive.renderClip": [{
|
|
47091
47828
|
name: "deviceId",
|
|
47092
47829
|
form: "single",
|
|
47093
47830
|
optional: false
|
|
47094
47831
|
}],
|
|
47095
|
-
"
|
|
47832
|
+
"recordingArchive.renderGif": [{
|
|
47096
47833
|
name: "deviceId",
|
|
47097
47834
|
form: "single",
|
|
47098
47835
|
optional: false
|
|
47099
47836
|
}],
|
|
47100
|
-
"
|
|
47837
|
+
"recordingArchive.rescanStorage": [{
|
|
47101
47838
|
name: "deviceId",
|
|
47102
47839
|
form: "single",
|
|
47103
47840
|
optional: false
|
|
47104
47841
|
}],
|
|
47105
|
-
"
|
|
47842
|
+
"recordingArchive.setDeviceConfig": [{
|
|
47106
47843
|
name: "deviceId",
|
|
47107
47844
|
form: "single",
|
|
47108
47845
|
optional: false
|
|
47109
47846
|
}],
|
|
47110
|
-
"
|
|
47847
|
+
"recordingArchive.setDevicePlacement": [{
|
|
47111
47848
|
name: "deviceId",
|
|
47112
47849
|
form: "single",
|
|
47113
47850
|
optional: false
|
|
47114
47851
|
}],
|
|
47115
|
-
"
|
|
47852
|
+
"recordingArchive.startStorageMigrationMove": [{
|
|
47116
47853
|
name: "deviceId",
|
|
47117
47854
|
form: "single",
|
|
47118
47855
|
optional: true
|
|
@@ -47631,6 +48368,41 @@ var NC_AUDIO_DEFAULTS = {
|
|
|
47631
48368
|
};
|
|
47632
48369
|
new Set(["devices", "classes"]);
|
|
47633
48370
|
NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
48371
|
+
var RULE_BODY_ONLY = ["body"];
|
|
48372
|
+
var RULE_TEXT = ["title", "body"];
|
|
48373
|
+
var BATTERY = ["device-battery-low", "device-battery-normal"];
|
|
48374
|
+
var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
|
|
48375
|
+
var UPDATES = [
|
|
48376
|
+
"addon-update-available",
|
|
48377
|
+
"server-update-available",
|
|
48378
|
+
"wrapper-update-available"
|
|
48379
|
+
];
|
|
48380
|
+
var PACKAGE_UPDATES = ["addon-update-available", "server-update-available"];
|
|
48381
|
+
var ALARM = [
|
|
48382
|
+
"alarm-triggered",
|
|
48383
|
+
"alarm-armed",
|
|
48384
|
+
"alarm-disarmed",
|
|
48385
|
+
"alarm-arming",
|
|
48386
|
+
"alarm-arm-refused",
|
|
48387
|
+
"alarm-pending",
|
|
48388
|
+
"alarm-rearmed",
|
|
48389
|
+
"alarm-sensor-bypass",
|
|
48390
|
+
"alarm-check-failed"
|
|
48391
|
+
];
|
|
48392
|
+
var COMBINED_ALARM = ["alarm-triggered"];
|
|
48393
|
+
var DEVICE_EVENTS = [
|
|
48394
|
+
"device-online",
|
|
48395
|
+
"device-offline",
|
|
48396
|
+
"device-disabled",
|
|
48397
|
+
"device-enabled",
|
|
48398
|
+
...BATTERY,
|
|
48399
|
+
...CONSUMABLE,
|
|
48400
|
+
"stream-online",
|
|
48401
|
+
"stream-offline",
|
|
48402
|
+
"detection-blind",
|
|
48403
|
+
...ALARM
|
|
48404
|
+
];
|
|
48405
|
+
[...DEVICE_EVENTS], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_BODY_ONLY], [...RULE_TEXT], [...RULE_TEXT], [...DEVICE_EVENTS], [...RULE_TEXT], [...DEVICE_EVENTS], [...RULE_TEXT], [...BATTERY, ...CONSUMABLE], [...RULE_TEXT], [...CONSUMABLE], [...RULE_TEXT], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT];
|
|
47634
48406
|
/**
|
|
47635
48407
|
* TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
|
|
47636
48408
|
*
|
|
@@ -49157,4 +49929,4 @@ function enumerateInferenceDevices(hw) {
|
|
|
49157
49929
|
return out;
|
|
49158
49930
|
}
|
|
49159
49931
|
//#endregion
|
|
49160
|
-
export {
|
|
49932
|
+
export { defaultDeviceFor as $, errMsg as $t, PoolMemoryWatchdog as A, literal as An, pickDetailCropConvention as At, VISIT_MERGE_GAP_MS as B, resolveCreateExportSubject as Bt, FailureCounters as C, selectAssignedProfileSlots as Cn, migrateLegacyDeviceSignalConfig as Ct, NativeLeaseAdmissionSchema as D, boolean as Dn, parseProcStatus as Dt, MOTION_CLOSE_AFTER_MS as E, array as En, overlayClusterStepSettings as Et, RECORDING_SOURCE_CAMSTACK_ADDON as F, string as Fn, recordingArchiveCapability as Ft, audioKindId as G, runtimeDevices as Gt, addonWidgetsSourceCapability as H, resolveLocationMode as Ht, RecordingConfigSchema as I, union as In, recordingCapability as It, clipExportFailure as J, streamBrokerCapability as Jt, batteryCapability as K, storageEvictableCapability as Kt, RecordingSignalStatusSchema as L, EventCategory as Ln, recordingExportCapability as Lt, RATE_CONTROL_TIGHT as M, object as Mn, pipelineExecutorCapability as Mt, RECORDING_EXPORT_MAX_READ_BYTES as N, preprocess as Nn, pipelineRunnerCapability as Nt, NativeLeaseSettingsSchema as O, discriminatedUnion as On, pickClusterStepModels as Ot, RECORDING_SOURCE_CAMSTACK as P, record as Pn, rankStreams as Pt, declareLogChannel as Q, webrtcSessionCapability as Qt, RingBuffer as R, recordingSignalCapability as Rt, ExportRecordSchema as S, parseProfileBrokerId as Sn, mayWriteToLocation as St, HF_BASE_URL as T, _enum as Tn, motionDetectionCapability as Tt, audioAnalysisCapability as U, resolvePoolMemoryPolicy as Ut, YAMNET_TO_MACRO as V, resolveEgressDecodeHwAccel as Vt, audioAnalyzerCapability as W, resolveRecordingProfiles as Wt, createHwAccelCache as X, supportedRuntimes as Xt, clipExportFailureOf as Y, stripRetiredBandPreBufferSec as Yt, createLogChannelsProvider as Z, videoclipsCapability as Zt, DEFAULT_NATIVE_LEASE_SETTINGS as _, isEvent as _n, loadContributionCapability as _t, CLIP_BROKER_PACED_RATES as a, BOOT_RECOVERY_BACKOFF_MS as an, egressTransportFromRequest as at, EVENT_PAD_MS as b, nodePin as bn, maskUrlCredentials as bt, CLIP_STREAM_TRANSPORT_HEADER as c, CamProfileSchema as cn, evaluateZoneRules as ct, DEFAULT_AUDIO_ANALYZER_CONFIG as d, audioChunkBytesPerSample as dn, exportSubjectOf as dt, AUDIO_PRESETS as en, deriveBatteryPresence as et, DEFAULT_CLUSTER_STEP_MODELS as f, createEvent as fn, failureContributionCapability as ft, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as g, hydrateSchema as gn, isFirstLevelMacroClass as gt, DEFAULT_EVENTS_BAND_BUFFER_SEC as h, expandAudioChunkToF32le as hn, inferModelProvider as ht, BatteryStatusSchema as i, isSoftwareDecode as in, egressTranscodeSharingKey as it, RATE_CONTROL_RELAXED as j, number as jn, pickNativeLeaseOverride as jt, OpsLogEntrySchema as k, lazy as kn, pickClusterStepSettings as kt, COCO_80_LABELS as l, DeviceFeature as ln, evictionPolicyForMode as lt, DEFAULT_DETAIL_CROP_CONVENTION as m, defineCustomActions as mn, hfModelUrl as mt, AUDIO_BACKEND_CHOICES as n, buildFfmpegArgs as nn, deriveRecordingMode as nt, CLIP_STILL_REASON_CODE_HEADER as o, BaseAddon as on, enumerateInferenceDevices as ot, DEFAULT_CLUSTER_STEP_SETTINGS as p, customAction as pn, gbToBytes as pt, cameraStreamsCapability as q, storageOccupancyCapability as qt, AUDIO_MACRO_LABELS as r, invocationFromEncodeProfile as rn, detectionPipelineCapability as rt, CLIP_STILL_REASON_HEADER as s, CAM_PROFILE_ORDER as sn, evaluateSensorEdge as st, APPLE_SA_TO_MACRO as t, Fmp4BoxSplitter as tn, deriveDetailCropRect as tt, COCO_TO_MACRO as u, DeviceType as un, evictionPolicyOfLocation as ut, DEVICE_BACKEND_TO_FORMAT as v, makeProfileBrokerId as vn, logChannelsCapability as vt, G711_SCALE_CORRECTION_DB as w, sleep as wn, modeMayWrite as wt, EncodeProfileSchema as x, parseJsonUnknown as xn, mayReadLocation as xt, EVENT_OWNER_TYPES as y, makeSourceBrokerId as yn, mapAudioLabelToMacro as yt, VIDEOCLIPS_MAX_OFFER_BYTES as z, resolveClusterStepModelId as zt };
|