@camstack/addon-provider-homeassistant 1.2.30 → 1.2.31
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/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/{dist-XWveotlj.js → dist-ByKdDxGp.js} +415 -22
- package/dist/{dist-CzZXxFrl.mjs → dist-CyjeVkIa.mjs} +415 -22
- package/dist/ha-export/ha-export.addon.js +15 -6
- package/dist/ha-export/ha-export.addon.mjs +15 -6
- package/package.json +1 -1
|
@@ -11103,6 +11103,8 @@ var QueryFilterSchema = object({
|
|
|
11103
11103
|
where: record(string(), unknown()).optional(),
|
|
11104
11104
|
whereIn: record(string(), array(unknown())).optional(),
|
|
11105
11105
|
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
11106
|
+
/** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
|
|
11107
|
+
whereNot: record(string(), unknown()).optional(),
|
|
11106
11108
|
orderBy: object({
|
|
11107
11109
|
field: string(),
|
|
11108
11110
|
direction: _enum(["asc", "desc"])
|
|
@@ -11122,7 +11124,8 @@ var QueryFilterSchema = object({
|
|
|
11122
11124
|
var MutationFilterSchema = object({
|
|
11123
11125
|
where: record(string(), unknown()).optional(),
|
|
11124
11126
|
whereIn: record(string(), array(unknown())).optional(),
|
|
11125
|
-
whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
|
|
11127
|
+
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
11128
|
+
whereNot: record(string(), unknown()).optional()
|
|
11126
11129
|
});
|
|
11127
11130
|
/** A single stored record: `{ id, data }`. */
|
|
11128
11131
|
var SettingsRecordSchema = object({
|
|
@@ -12787,6 +12790,18 @@ var LlmGenerateBaseInputSchema = object({
|
|
|
12787
12790
|
* Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
|
|
12788
12791
|
* watchdog — operator decision #3).
|
|
12789
12792
|
*/
|
|
12793
|
+
/**
|
|
12794
|
+
* A companion artifact that MUST land beside the main GGUF: the `mmproj`
|
|
12795
|
+
* projector of a vision model, or shards 2..N of a split GGUF. Carried on the
|
|
12796
|
+
* REF rather than looked up at install time, so what the operator approved in
|
|
12797
|
+
* the preview is exactly what the node downloads.
|
|
12798
|
+
*/
|
|
12799
|
+
var ManagedModelExtraFileSchema = object({
|
|
12800
|
+
url: string(),
|
|
12801
|
+
filename: string(),
|
|
12802
|
+
sizeBytes: number(),
|
|
12803
|
+
sha256: string().optional()
|
|
12804
|
+
});
|
|
12790
12805
|
var ManagedModelRefSchema = discriminatedUnion("kind", [
|
|
12791
12806
|
object({
|
|
12792
12807
|
kind: literal("catalog"),
|
|
@@ -12795,7 +12810,11 @@ var ManagedModelRefSchema = discriminatedUnion("kind", [
|
|
|
12795
12810
|
object({
|
|
12796
12811
|
kind: literal("url"),
|
|
12797
12812
|
url: string(),
|
|
12798
|
-
sha256: string().optional()
|
|
12813
|
+
sha256: string().optional(),
|
|
12814
|
+
/** Picker/status label; the file basename when absent. */
|
|
12815
|
+
label: string().optional(),
|
|
12816
|
+
sizeBytes: number().optional(),
|
|
12817
|
+
extraFiles: array(ManagedModelExtraFileSchema).optional()
|
|
12799
12818
|
}),
|
|
12800
12819
|
object({
|
|
12801
12820
|
kind: literal("path"),
|
|
@@ -12856,11 +12875,39 @@ var ManagedRuntimeConfigSchema = object({
|
|
|
12856
12875
|
"q4_1",
|
|
12857
12876
|
"q4_0"
|
|
12858
12877
|
]).optional(),
|
|
12878
|
+
/**
|
|
12879
|
+
* Escape hatch for llama-server flags this schema does NOT model — `--jinja`
|
|
12880
|
+
* (which most vision chat templates need and some language-only models
|
|
12881
|
+
* dislike), `--cont-batching`, `--rope-scaling`, …
|
|
12882
|
+
*
|
|
12883
|
+
* It is NOT a second place to set the flags above. A token that collides
|
|
12884
|
+
* with a typed field is REJECTED at start, naming the field that owns it
|
|
12885
|
+
* (`assertNoOwnedFlags`), because two knobs writing the same argv is exactly
|
|
12886
|
+
* the "two switches that disagree" failure this repo has already shipped
|
|
12887
|
+
* twice (D62).
|
|
12888
|
+
*/
|
|
12889
|
+
extraArgs: array(string()).default([]),
|
|
12859
12890
|
/** Else lazy: first generate boots it. */
|
|
12860
12891
|
autoStart: boolean().default(false),
|
|
12861
12892
|
/** 0 = never; frees RAM after quiet periods. */
|
|
12862
12893
|
idleStopMinutes: number().int().default(30)
|
|
12863
12894
|
});
|
|
12895
|
+
/**
|
|
12896
|
+
* Where a multi-GB install currently is. A single 0..1 fraction cannot answer
|
|
12897
|
+
* "is it stuck?" for an install that is three files (shards + mmproj) followed
|
|
12898
|
+
* by a sha256 pass over 22 GB — during which the fraction sat at 1.0 and the
|
|
12899
|
+
* node looked hung. Phase + file + bytes is the smallest shape that does.
|
|
12900
|
+
*/
|
|
12901
|
+
var LlmDownloadProgressSchema = object({
|
|
12902
|
+
phase: _enum(["downloading", "verifying"]),
|
|
12903
|
+
/** The artifact currently moving, e.g. `mmproj-F16.gguf`. */
|
|
12904
|
+
file: string(),
|
|
12905
|
+
fileIndex: number().int(),
|
|
12906
|
+
fileCount: number().int(),
|
|
12907
|
+
/** Across the WHOLE install, not the current file. */
|
|
12908
|
+
downloadedBytes: number(),
|
|
12909
|
+
totalBytes: number().optional()
|
|
12910
|
+
});
|
|
12864
12911
|
var LlmRuntimeStatusSchema = object({
|
|
12865
12912
|
/** Status is ALWAYS node-qualified. */
|
|
12866
12913
|
nodeId: string(),
|
|
@@ -12877,6 +12924,8 @@ var LlmRuntimeStatusSchema = object({
|
|
|
12877
12924
|
modelPath: string().optional(),
|
|
12878
12925
|
modelId: string().optional(),
|
|
12879
12926
|
downloadProgress: number().min(0).max(1).optional(),
|
|
12927
|
+
/** Detail behind `downloadProgress`; present for the same lifetime. */
|
|
12928
|
+
download: LlmDownloadProgressSchema.optional(),
|
|
12880
12929
|
lastError: string().optional(),
|
|
12881
12930
|
crashesInWindow: number(),
|
|
12882
12931
|
/** Child RSS (sampled best-effort). */
|
|
@@ -12887,7 +12936,14 @@ var LlmNodeModelSchema = object({
|
|
|
12887
12936
|
file: string(),
|
|
12888
12937
|
sizeBytes: number(),
|
|
12889
12938
|
catalogId: string().optional(),
|
|
12890
|
-
installedAt: number().optional()
|
|
12939
|
+
installedAt: number().optional(),
|
|
12940
|
+
/**
|
|
12941
|
+
* Absolute path on the node. Present so a file that is on disk but matches
|
|
12942
|
+
* no catalog entry — a custom Hugging Face install, or a GGUF the operator
|
|
12943
|
+
* copied in by hand — is still SELECTABLE, as a `{kind:'path'}` ref. Without
|
|
12944
|
+
* it the picker could list such a file and do nothing with it.
|
|
12945
|
+
*/
|
|
12946
|
+
path: string().optional()
|
|
12891
12947
|
});
|
|
12892
12948
|
var LlmRuntimeDiskUsageSchema = object({
|
|
12893
12949
|
nodeId: string(),
|
|
@@ -12965,9 +13021,12 @@ var LlmProfileSchema = object({
|
|
|
12965
13021
|
systemPrompt: string().optional(),
|
|
12966
13022
|
/** Total generation bound — the only one a unary call has. */
|
|
12967
13023
|
timeoutMs: number().int().positive().default(6e4),
|
|
12968
|
-
/**
|
|
13024
|
+
/** The TCP handshake only — "is the port even open". NOT the wait for
|
|
13025
|
+
* response headers: on the LM Studio / llama-server wire those are written
|
|
13026
|
+
* once the model has finished loading, so they belong to the bound below. */
|
|
12969
13027
|
connectTimeoutMs: number().int().positive().default(1e4),
|
|
12970
|
-
/** Accepted, but no output yet —
|
|
13028
|
+
/** Accepted, but no output yet — response headers included, because a cold
|
|
13029
|
+
* GPU load is exactly what happens before them. */
|
|
12971
13030
|
firstTokenTimeoutMs: number().int().positive().default(12e4),
|
|
12972
13031
|
/** Output started then stopped. */
|
|
12973
13032
|
idleTimeoutMs: number().int().positive().default(6e4),
|
|
@@ -13030,6 +13089,36 @@ var ManagedModelCatalogEntrySchema = object({
|
|
|
13030
13089
|
/** Vision models: companion projector file. */
|
|
13031
13090
|
mmprojUrl: string().optional()
|
|
13032
13091
|
});
|
|
13092
|
+
/**
|
|
13093
|
+
* The outcome of turning one operator-typed Hugging Face reference into a
|
|
13094
|
+
* download plan. A RESULT, never a throw: "this repo has 24 quantizations and
|
|
13095
|
+
* I will not pick for you" is a normal answer the UI has to render, not an
|
|
13096
|
+
* exception.
|
|
13097
|
+
*
|
|
13098
|
+
* `candidates` is the whole reason the refusal is usable — every string in it
|
|
13099
|
+
* is a tag that resolves when pasted back as `<org>/<repo>:<TAG>`.
|
|
13100
|
+
*/
|
|
13101
|
+
var HfModelResolutionSchema = discriminatedUnion("ok", [object({
|
|
13102
|
+
ok: literal(true),
|
|
13103
|
+
/** Ready to hand to `installModel` unchanged. */
|
|
13104
|
+
model: ManagedModelRefSchema,
|
|
13105
|
+
label: string(),
|
|
13106
|
+
repo: string(),
|
|
13107
|
+
quantization: string(),
|
|
13108
|
+
purpose: _enum(["text", "vision"]),
|
|
13109
|
+
totalBytes: number(),
|
|
13110
|
+
/** mmproj + shards, for the preview: an operator approving 23 GB should
|
|
13111
|
+
* see that 0.9 GB of it is a projector they did not name. */
|
|
13112
|
+
extraFilenames: array(string())
|
|
13113
|
+
}), object({
|
|
13114
|
+
ok: literal(false),
|
|
13115
|
+
code: string(),
|
|
13116
|
+
message: string(),
|
|
13117
|
+
candidates: array(string()).optional(),
|
|
13118
|
+
/** Set when the refusal was only the ceiling: re-calling with
|
|
13119
|
+
* `maxBytes: requiredBytes` is the operator's explicit override. */
|
|
13120
|
+
requiredBytes: number().optional()
|
|
13121
|
+
})]);
|
|
13033
13122
|
var LlmRuntimeNodeSchema = object({
|
|
13034
13123
|
nodeId: string(),
|
|
13035
13124
|
reachable: boolean(),
|
|
@@ -13066,6 +13155,15 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
|
|
|
13066
13155
|
consumer: string().optional(),
|
|
13067
13156
|
profileId: string().optional()
|
|
13068
13157
|
}), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
|
|
13158
|
+
/** `https://huggingface.co/<org>/<repo>/resolve/main/<f>.gguf`,
|
|
13159
|
+
* `<org>/<repo>/<f>.gguf`, `<org>/<repo>` or `<org>/<repo>:<QUANT>`. */
|
|
13160
|
+
ref: string(),
|
|
13161
|
+
/** Explicit ceiling override, in bytes. Absent = the built-in ceiling. */
|
|
13162
|
+
maxBytes: number().positive().optional()
|
|
13163
|
+
}), HfModelResolutionSchema, {
|
|
13164
|
+
kind: "mutation",
|
|
13165
|
+
auth: "admin"
|
|
13166
|
+
}), method(object({
|
|
13069
13167
|
nodeId: string(),
|
|
13070
13168
|
model: ManagedModelRefSchema
|
|
13071
13169
|
}), _void(), {
|
|
@@ -15014,13 +15112,81 @@ var NcRuleActionsSchema = object({
|
|
|
15014
15112
|
*/
|
|
15015
15113
|
buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
|
|
15016
15114
|
});
|
|
15115
|
+
/**
|
|
15116
|
+
* "This rule applies only while `deviceId` is in one of `states`."
|
|
15117
|
+
*
|
|
15118
|
+
* The states are the DEVICE's own vocabulary — `AlarmState` for a panel,
|
|
15119
|
+
* `on`/`off` for a switch — not a normalised set, because normalising would
|
|
15120
|
+
* make the condition lie about devices whose states have no equivalent.
|
|
15121
|
+
*
|
|
15122
|
+
* An unreadable state does NOT match: see the engine's fail-closed gate. A
|
|
15123
|
+
* condition that fired on "I could not read it" would be worse than no gate.
|
|
15124
|
+
*/
|
|
15125
|
+
var NcDeviceStateConditionSchema = object({
|
|
15126
|
+
deviceId: number().int(),
|
|
15127
|
+
/** Any of these matches. */
|
|
15128
|
+
states: array(string().min(1)).min(1)
|
|
15129
|
+
});
|
|
15130
|
+
/**
|
|
15131
|
+
* "This rule applies only while scene `sceneId` is `matched` / `diverged`."
|
|
15132
|
+
*
|
|
15133
|
+
* A GATE, not a trigger. `occupancy` and `audio` each DISCRIMINATE their rule —
|
|
15134
|
+
* carrying one makes the rule fire on that subject and nothing else. Scene is
|
|
15135
|
+
* the other shape entirely, the `deviceState` shape: it narrows a rule that
|
|
15136
|
+
* already has a trigger ("tell me about a person at the front door, but only
|
|
15137
|
+
* while the bin is still out"). That is why it composes with every delivery
|
|
15138
|
+
* instead of owning one, and why no new `NcDelivery` member and no new subject
|
|
15139
|
+
* kind exist for it — see D159.
|
|
15140
|
+
*
|
|
15141
|
+
* ── Identity ───────────────────────────────────────────────────────────────
|
|
15142
|
+
* `sceneId` is `SceneMonitor.id`, a `randomUUID()` minted by `createScene` —
|
|
15143
|
+
* globally unique, so it needs no device to disambiguate it. `deviceId` is
|
|
15144
|
+
* carried as a HINT for the editor and for the log line, never as part of the
|
|
15145
|
+
* lookup key: a rule whose hint drifted must still gate correctly.
|
|
15146
|
+
*
|
|
15147
|
+
* ── Which boolean ──────────────────────────────────────────────────────────
|
|
15148
|
+
* `latched` ABSENT means "whatever the scene itself says" — `SceneMonitor.emit`
|
|
15149
|
+
* already declares which boolean drives notification rules, and a second knob
|
|
15150
|
+
* that could disagree with it is exactly the D62 failure. Set it only to
|
|
15151
|
+
* override one rule against the scene's own default.
|
|
15152
|
+
*
|
|
15153
|
+
* - LIVE reading (`emit`/`latched` resolve to live): passes iff
|
|
15154
|
+
* `verdict === requiredState`. `unknown` — no reference for this light, view
|
|
15155
|
+
* shifted, no snapshot — passes NEITHER. A scene that cannot judge is not
|
|
15156
|
+
* evidence, in either direction.
|
|
15157
|
+
* - LATCHED reading: passes iff `latched === (requiredState === 'diverged')`.
|
|
15158
|
+
* The latch is a durable fact about the past ("it has diverged since I armed
|
|
15159
|
+
* it"), so a camera that has gone dark does not clear it — that is the whole
|
|
15160
|
+
* reason the operator asked for a latch.
|
|
15161
|
+
*
|
|
15162
|
+
* The gate reads an in-memory mirror (`NcSceneStateCache`) refreshed OFF the
|
|
15163
|
+
* event path, never the cap: D49. A mirror that has never loaded, or a scene it
|
|
15164
|
+
* does not carry, reads absent and the rule does NOT fire — fail closed, and
|
|
15165
|
+
* said out loud in the log rather than dropped in silence.
|
|
15166
|
+
*/
|
|
15167
|
+
var NcSceneConditionSchema = object({
|
|
15168
|
+
/** `SceneMonitor.id` — the uuid the cap mints. The whole lookup key. */
|
|
15169
|
+
sceneId: string().min(1),
|
|
15170
|
+
/** The camera the scene lives on. A hint for the editor and the log line. */
|
|
15171
|
+
deviceId: number().int().optional(),
|
|
15172
|
+
/** The state the scene must be in for the rule to fire. */
|
|
15173
|
+
requiredState: _enum(["matched", "diverged"]),
|
|
15174
|
+
/**
|
|
15175
|
+
* Read the LATCH (`true`) or the LIVE verdict (`false`). Absent = follow the
|
|
15176
|
+
* scene's own `emit` field, which is the only place that decision belongs.
|
|
15177
|
+
*/
|
|
15178
|
+
latched: boolean().optional()
|
|
15179
|
+
});
|
|
15017
15180
|
var NcConditionsSchema = object({
|
|
15018
15181
|
/** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
|
|
15019
|
-
deviceState:
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15182
|
+
deviceState: NcDeviceStateConditionSchema.optional(),
|
|
15183
|
+
/**
|
|
15184
|
+
* Gate on a SCENE's state — "only while the bin is still out". Composes with
|
|
15185
|
+
* every trigger (detection, occupancy, audio, sensor, package, track-end);
|
|
15186
|
+
* unlike `occupancy`/`audio` it discriminates nothing. See
|
|
15187
|
+
* {@link NcSceneCondition} and D159.
|
|
15188
|
+
*/
|
|
15189
|
+
scene: NcSceneConditionSchema.optional(),
|
|
15024
15190
|
/** Device scope — absent = all devices. */
|
|
15025
15191
|
devices: array(number()).optional(),
|
|
15026
15192
|
/** Detector class names (any overlap with the record's class set). */
|
|
@@ -15658,6 +15824,7 @@ var NcConditionDescriptorSchema = object({
|
|
|
15658
15824
|
"occupancy",
|
|
15659
15825
|
"audio",
|
|
15660
15826
|
"deviceState",
|
|
15827
|
+
"scene",
|
|
15661
15828
|
"systemEvent"
|
|
15662
15829
|
]),
|
|
15663
15830
|
operator: _enum([
|
|
@@ -17141,7 +17308,10 @@ var RecentTracksQueryInput = object({
|
|
|
17141
17308
|
* Encodes the (lastSeen, trackId) sort position — treat as opaque. */
|
|
17142
17309
|
cursor: string().optional(),
|
|
17143
17310
|
/** See {@link TrackProjectionSchema}. Default `full`. */
|
|
17144
|
-
projection: TrackProjectionSchema.optional()
|
|
17311
|
+
projection: TrackProjectionSchema.optional(),
|
|
17312
|
+
/** Include stationary-promoted rows (parked objects). Default false: the
|
|
17313
|
+
* feed lists passages; parking records live on the stationary registry. */
|
|
17314
|
+
includeStationary: boolean().optional()
|
|
17145
17315
|
});
|
|
17146
17316
|
var RecentTracksPageSchema = object({
|
|
17147
17317
|
/** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
|
|
@@ -17359,7 +17529,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
17359
17529
|
zone: TrackZoneFilterSchema.optional(),
|
|
17360
17530
|
/** See {@link TrackProjectionSchema}. Default `full` (backward
|
|
17361
17531
|
* compatible — omitting the field keeps today's exact behaviour). */
|
|
17362
|
-
projection: TrackProjectionSchema.optional()
|
|
17532
|
+
projection: TrackProjectionSchema.optional(),
|
|
17533
|
+
/** Include stationary-promoted rows (parked objects handed to the
|
|
17534
|
+
* stationary registry). Default false: the timeline lists passages,
|
|
17535
|
+
* not parking records (operator decision, 2026-08-15). */
|
|
17536
|
+
includeStationary: boolean().optional()
|
|
17363
17537
|
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
17364
17538
|
kind: "mutation",
|
|
17365
17539
|
auth: "admin"
|
|
@@ -17523,11 +17697,16 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
17523
17697
|
auth: "admin"
|
|
17524
17698
|
}), method(object({
|
|
17525
17699
|
eventId: string(),
|
|
17526
|
-
kind: MediaFileKindEnum.optional()
|
|
17700
|
+
kind: MediaFileKindEnum.optional(),
|
|
17701
|
+
deviceId: number()
|
|
17527
17702
|
}), array(MediaFileSchema).readonly()), method(object({
|
|
17528
17703
|
trackId: string(),
|
|
17529
|
-
kinds: array(MediaFileKindEnum).optional()
|
|
17530
|
-
|
|
17704
|
+
kinds: array(MediaFileKindEnum).optional(),
|
|
17705
|
+
deviceId: number()
|
|
17706
|
+
}), array(MediaFileSchema).readonly()), method(object({
|
|
17707
|
+
trackId: string(),
|
|
17708
|
+
deviceId: number()
|
|
17709
|
+
}), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
|
|
17531
17710
|
kind: "mutation",
|
|
17532
17711
|
auth: "admin"
|
|
17533
17712
|
}), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
|
|
@@ -18227,6 +18406,17 @@ var maxSessionHoldMsField = {
|
|
|
18227
18406
|
default: 12e4,
|
|
18228
18407
|
step: 5e3
|
|
18229
18408
|
};
|
|
18409
|
+
/**
|
|
18410
|
+
* Quiet period that closes an `audioMode: 'on-motion'` audio window. Floor of
|
|
18411
|
+
* 5s so a rearm can never degenerate into per-event stream churn; default 90s
|
|
18412
|
+
* comfortably outlives the gap between two PIR wakes on a battery camera.
|
|
18413
|
+
*/
|
|
18414
|
+
var audioMotionWindowMsField = {
|
|
18415
|
+
min: 5e3,
|
|
18416
|
+
max: 6e5,
|
|
18417
|
+
default: 9e4,
|
|
18418
|
+
step: 5e3
|
|
18419
|
+
};
|
|
18230
18420
|
var motionFpsField = {
|
|
18231
18421
|
min: 1,
|
|
18232
18422
|
max: 30,
|
|
@@ -18403,6 +18593,27 @@ var RunnerCameraConfigSchema = object({
|
|
|
18403
18593
|
* resolved `CameraDetectionConfig`.
|
|
18404
18594
|
*/
|
|
18405
18595
|
maxSessionHoldMs: number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
|
|
18596
|
+
/**
|
|
18597
|
+
* Orchestrator-side quiet period (ms) that closes an `audioMode:
|
|
18598
|
+
* 'on-motion'` audio window, measured from the LAST motion event.
|
|
18599
|
+
*
|
|
18600
|
+
* This exists because the falling edge cannot be relied on. Camera-native
|
|
18601
|
+
* providers emit motion as a RISING EDGE ONLY (Reolink's Baichuan push and
|
|
18602
|
+
* its email-push SMTP path both emit `detected: true` and never the
|
|
18603
|
+
* counterpart); only the frame-diff analyzer emits falls. So on an
|
|
18604
|
+
* onboard-only camera a window that closed only on `detected: false` never
|
|
18605
|
+
* closed at all, and `on-motion` silently behaved as `always-on` — on a
|
|
18606
|
+
* battery camera, the one failure mode the mode exists to prevent.
|
|
18607
|
+
*
|
|
18608
|
+
* Every motion event rearms this timer WITHOUT restarting the stream, so a
|
|
18609
|
+
* burst of re-fires costs nothing. A falling edge, when one does arrive,
|
|
18610
|
+
* still closes earlier via `motionCooldownMs` — whichever comes first wins.
|
|
18611
|
+
*
|
|
18612
|
+
* Not consumed by the runner: carried here so it shares the per-camera
|
|
18613
|
+
* device-settings surface with `motionCooldownMs`, exactly like
|
|
18614
|
+
* `maxSessionHoldMs`.
|
|
18615
|
+
*/
|
|
18616
|
+
audioMotionWindowMs: number().min(audioMotionWindowMsField.min).max(audioMotionWindowMsField.max).optional(),
|
|
18406
18617
|
motionFps: number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
|
|
18407
18618
|
detectionFps: number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
|
|
18408
18619
|
motionStreamId: string(),
|
|
@@ -18498,7 +18709,7 @@ var RunnerCameraConfigSchema = object({
|
|
|
18498
18709
|
*/
|
|
18499
18710
|
inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
|
|
18500
18711
|
});
|
|
18501
|
-
motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
|
|
18712
|
+
motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
|
|
18502
18713
|
/**
|
|
18503
18714
|
* Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
|
|
18504
18715
|
* load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
|
|
@@ -19514,7 +19725,16 @@ targets: array(object({
|
|
|
19514
19725
|
/** A sleeping battery camera: the frame is deliberately stale and will
|
|
19515
19726
|
* NOT refresh in the background. A surface should say so rather than
|
|
19516
19727
|
* present it as current. */
|
|
19517
|
-
sleeping: boolean()
|
|
19728
|
+
sleeping: boolean(),
|
|
19729
|
+
/** Current device state rendered over the cached frame. State images
|
|
19730
|
+
* remain authoritative even when their photographic background is
|
|
19731
|
+
* old; null means the link must carry a current camera frame. */
|
|
19732
|
+
stateReason: _enum([
|
|
19733
|
+
"disabled",
|
|
19734
|
+
"sleeping",
|
|
19735
|
+
"unreachable",
|
|
19736
|
+
"waking"
|
|
19737
|
+
]).nullable()
|
|
19518
19738
|
})));
|
|
19519
19739
|
/**
|
|
19520
19740
|
* `sso-bridge` — internal hub-only cap that lets SSO-style auth
|
|
@@ -21240,6 +21460,25 @@ var BatteryStatusSchema = object({
|
|
|
21240
21460
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
21241
21461
|
lastUpdated: number(),
|
|
21242
21462
|
/**
|
|
21463
|
+
* Ms epoch of the last time the device PROVED it was reachable — a
|
|
21464
|
+
* completed firmware round-trip, an observed wake, or an inbound push
|
|
21465
|
+
* (firmware event, email). `0`/absent = never since this slice was born.
|
|
21466
|
+
*
|
|
21467
|
+
* This is the ONLY input that separates "asleep" from "gone", and it is
|
|
21468
|
+
* fed exclusively by PASSIVE signals: nothing may write it by reaching
|
|
21469
|
+
* for the radio, because a poll that confirms reachability is the same
|
|
21470
|
+
* poll that drains the battery. See {@link deriveBatteryPresence} — the
|
|
21471
|
+
* single derivation every consumer must use; no surface computes its own.
|
|
21472
|
+
*
|
|
21473
|
+
* It is deliberately NOT a clock in the
|
|
21474
|
+
* `scripts/check-runtime-state-durability.ts` sense: it is the
|
|
21475
|
+
* observation itself, and it is the only thing a 30-hour silence is
|
|
21476
|
+
* visible in. Writers quantise it (see `CONTACT_WRITE_QUANTUM_MS` in the
|
|
21477
|
+
* Reolink provider) so a value that means "recently" cannot cost a
|
|
21478
|
+
* SQLite commit per round-trip.
|
|
21479
|
+
*/
|
|
21480
|
+
lastContactAt: number().optional(),
|
|
21481
|
+
/**
|
|
21243
21482
|
* True when the source is a BINARY low-battery indicator (HA
|
|
21244
21483
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
21245
21484
|
* charge level — `percentage` is then a coarse stand-in (100 = normal,
|
|
@@ -26681,6 +26920,33 @@ method(object({
|
|
|
26681
26920
|
* as `unknown`, never guessed. A day reference scored against an IR frame
|
|
26682
26921
|
* collapses the cosine and would latch a false alarm every single night. */
|
|
26683
26922
|
var SceneConditionSchema = string();
|
|
26923
|
+
/**
|
|
26924
|
+
* What a scene does when the CURRENT light has no reference of its own.
|
|
26925
|
+
*
|
|
26926
|
+
* The lighting variants are not equally likely to exist. Almost every operator
|
|
26927
|
+
* captures daylight and then never stands outside at 22:00 to capture IR, and a
|
|
26928
|
+
* scene that is only ever going to be asked about a daytime question ("is the
|
|
26929
|
+
* bin still on the kerb at 08:00") does not need a night reference at all. The
|
|
26930
|
+
* night half must therefore be OPTIONAL, and optional means the scene keeps
|
|
26931
|
+
* working without it rather than degrading into a permanent complaint.
|
|
26932
|
+
*
|
|
26933
|
+
* - `skip` (default) — the check in that light is not made. Not a verdict, not
|
|
26934
|
+
* an alarm, not even an `unknown`: the live state simply stays whatever the
|
|
26935
|
+
* last covered light left it at, the latch is untouched, and the hysteresis
|
|
26936
|
+
* run is neither spent nor cleared. The scene resumes by itself at first
|
|
26937
|
+
* light. This is the only behaviour under which "I never captured IR" is a
|
|
26938
|
+
* configuration choice instead of a nightly fault.
|
|
26939
|
+
* - `judge-anyway` — score against the OTHER conditions' references. Available
|
|
26940
|
+
* for cameras whose IR frame is close enough to daylight (a floodlit
|
|
26941
|
+
* driveway, an always-white-light doorbell), and wrong for everything else:
|
|
26942
|
+
* cross-condition cosines are not comparable, so a day reference against a
|
|
26943
|
+
* true IR frame collapses and the scene reports a theft at 21:40.
|
|
26944
|
+
*
|
|
26945
|
+
* Never applies when the scene has NO comparable reference at all — that is
|
|
26946
|
+
* "not armed yet", it is reported as `no-reference-for-condition`, and silence
|
|
26947
|
+
* there would hide a scene the operator never finished setting up.
|
|
26948
|
+
*/
|
|
26949
|
+
var SceneUncoveredPolicySchema = _enum(["skip", "judge-anyway"]);
|
|
26684
26950
|
/** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
|
|
26685
26951
|
* `unknown` = we cannot judge (no reference for this condition, encoder model
|
|
26686
26952
|
* changed, view shifted, no snapshot). `unknown` is a real value, not a null,
|
|
@@ -26736,6 +27002,9 @@ var SceneCheckSchema = discriminatedUnion("mode", [object({
|
|
|
26736
27002
|
hysteresisCount: number().int().positive()
|
|
26737
27003
|
})]);
|
|
26738
27004
|
var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
|
|
27005
|
+
/** Night is OPTIONAL. A scene with only a daylight reference sits the IR hours
|
|
27006
|
+
* out in silence rather than reporting a fault every night. */
|
|
27007
|
+
var SCENE_DEFAULT_UNCOVERED_POLICY = "skip";
|
|
26739
27008
|
/**
|
|
26740
27009
|
* Vision-model adjudication of a candidate flip. Field names deliberately
|
|
26741
27010
|
* mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
|
|
@@ -26802,6 +27071,21 @@ var SceneMonitorSchema = object({
|
|
|
26802
27071
|
* automation can react to the bin coming back without the operator's own
|
|
26803
27072
|
* alarm silently clearing itself. */
|
|
26804
27073
|
autoRestore: boolean().default(false),
|
|
27074
|
+
/** What to do when the current light has no reference of its own. See
|
|
27075
|
+
* {@link SceneUncoveredPolicySchema} — the default makes night OPTIONAL. */
|
|
27076
|
+
onUncoveredCondition: SceneUncoveredPolicySchema.default(SCENE_DEFAULT_UNCOVERED_POLICY),
|
|
27077
|
+
/**
|
|
27078
|
+
* The light whose checks are currently being SAT OUT under
|
|
27079
|
+
* `onUncoveredCondition: 'skip'` — `null` when the scene is checking normally.
|
|
27080
|
+
*
|
|
27081
|
+
* Engine-reported and advisory only: it moves no verdict, no latch and no
|
|
27082
|
+
* hysteresis. It exists so the card can say *"night (IR) — checks paused,
|
|
27083
|
+
* nothing captured in this light"* in the same calm voice as the coverage
|
|
27084
|
+
* line, because the alternative is a scene that silently stops answering
|
|
27085
|
+
* after sunset with nothing anywhere saying why. A skipped check must never
|
|
27086
|
+
* read as a broken one.
|
|
27087
|
+
*/
|
|
27088
|
+
suspendedCondition: SceneConditionSchema.nullable().default(null),
|
|
26805
27089
|
/** Named cause when `verdict === 'unknown'`. */
|
|
26806
27090
|
unavailable: SceneUnavailableSchema.nullable(),
|
|
26807
27091
|
/** Conditions that have at least one comparable reference — the coverage line
|
|
@@ -26855,6 +27139,7 @@ var sceneMonitorCapability = {
|
|
|
26855
27139
|
minObservationSpacingSec: number().int().min(0).max(3600).optional(),
|
|
26856
27140
|
anchorThreshold: number().min(0).max(1).optional(),
|
|
26857
27141
|
autoRestore: boolean().optional(),
|
|
27142
|
+
onUncoveredCondition: SceneUncoveredPolicySchema.optional(),
|
|
26858
27143
|
/** `null` clears the vision-model adjudicator. */
|
|
26859
27144
|
confirm: SceneConfirmSchema.nullable().optional()
|
|
26860
27145
|
})
|
|
@@ -27153,7 +27438,70 @@ var CamStreamDescriptorSchema = object({
|
|
|
27153
27438
|
/** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
|
|
27154
27439
|
metadata: record(string(), unknown()).optional()
|
|
27155
27440
|
});
|
|
27156
|
-
|
|
27441
|
+
/**
|
|
27442
|
+
* `stream-catalog` — device-scoped, provider-implemented. The pull counterpart
|
|
27443
|
+
* of the removed `publishCameraStream` push: a camera provider returns the full
|
|
27444
|
+
* set of stream descriptors it can offer for the device, synchronously, so the
|
|
27445
|
+
* broker can reconcile its registry against the authoritative provider state.
|
|
27446
|
+
*/
|
|
27447
|
+
/**
|
|
27448
|
+
* The catalog as a DURABLE fact rather than a live answer.
|
|
27449
|
+
*
|
|
27450
|
+
* A battery camera's descriptors are profile-stable — they change when the
|
|
27451
|
+
* operator rewrites an encoder profile, not minute to minute — but building
|
|
27452
|
+
* them costs a Baichuan login, which on a sleeping Argus IS a wake. So the
|
|
27453
|
+
* provider is allowed to build them exactly once per profile and must serve
|
|
27454
|
+
* every later pull from a cache.
|
|
27455
|
+
*
|
|
27456
|
+
* Holding that cache only in RAM is what turned a restart into an outage. The
|
|
27457
|
+
* runner comes back with the camera asleep, `buildStreamCatalogUncached`
|
|
27458
|
+
* correctly refuses to wake it, the pull answers `[]`, the broker has no
|
|
27459
|
+
* cam-stream entry to build a broker from, and `webrtcSession.handleOffer`
|
|
27460
|
+
* fails with a flat "No broker for stream" — for as long as the camera sleeps,
|
|
27461
|
+
* which on a battery cam is most of the day. The camera was fine. The stream
|
|
27462
|
+
* was unreachable because the process had forgotten what the camera offers.
|
|
27463
|
+
*
|
|
27464
|
+
* Declaring it here puts it in `device-runtime-state`, the kernel's canonical
|
|
27465
|
+
* declared collection, with the same `restored` durability `battery` uses for
|
|
27466
|
+
* the same reason: the last known value is the only value there is while the
|
|
27467
|
+
* device is asleep. The broker's brokers are therefore always DEFINABLE — it
|
|
27468
|
+
* is the DIAL that wakes a camera, never the catalog (D173).
|
|
27469
|
+
*/
|
|
27470
|
+
var StreamCatalogStateSchema = object({
|
|
27471
|
+
/** The descriptors as last built from a real camera response. Never a guess:
|
|
27472
|
+
* a failed or refused build writes NOTHING, so a restored catalog is always
|
|
27473
|
+
* one the camera itself once produced. */
|
|
27474
|
+
descriptors: array(CamStreamDescriptorSchema),
|
|
27475
|
+
/** Ms epoch of the build that produced {@link descriptors}. Lets the wake
|
|
27476
|
+
* path decide whether the camera's own awake window is worth spending on a
|
|
27477
|
+
* re-read. */
|
|
27478
|
+
lastFetchedAt: number()
|
|
27479
|
+
});
|
|
27480
|
+
var streamCatalogCapability = {
|
|
27481
|
+
name: "stream-catalog",
|
|
27482
|
+
scope: "device",
|
|
27483
|
+
deviceNative: true,
|
|
27484
|
+
mode: "singleton",
|
|
27485
|
+
deviceTypes: [DeviceType.Camera],
|
|
27486
|
+
methods: { getCatalog: method(object({ deviceId: number().int().nonnegative() }), array(CamStreamDescriptorSchema).readonly()) },
|
|
27487
|
+
runtimeState: StreamCatalogStateSchema,
|
|
27488
|
+
/**
|
|
27489
|
+
* Runtime-state durability: **restored** — see the schema doc. A cold
|
|
27490
|
+
* catalog on a sleeping battery camera is not a slow first frame, it is a
|
|
27491
|
+
* camera that cannot be watched at all until it happens to wake.
|
|
27492
|
+
*
|
|
27493
|
+
* Churn is nil by construction: the slice is written only by a SUCCESSFUL
|
|
27494
|
+
* build, and a build only runs when there is no cached copy (or the copy is
|
|
27495
|
+
* a day old and the camera is awake anyway).
|
|
27496
|
+
*
|
|
27497
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
27498
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
27499
|
+
*/
|
|
27500
|
+
durability: "restored",
|
|
27501
|
+
/** Clock field: written, but excluded from the compare that decides whether
|
|
27502
|
+
* persisting is worth a SQLite commit — the descriptors are the value. */
|
|
27503
|
+
volatileStateFields: ["lastFetchedAt"]
|
|
27504
|
+
};
|
|
27157
27505
|
/** One of the camera's stream profiles. */
|
|
27158
27506
|
var StreamProfileSchema = _enum([
|
|
27159
27507
|
"main",
|
|
@@ -28803,6 +29151,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
28803
29151
|
sceneMonitor: sceneMonitorCapability,
|
|
28804
29152
|
scriptRunner: scriptRunnerCapability,
|
|
28805
29153
|
smoke: smokeCapability,
|
|
29154
|
+
streamCatalog: streamCatalogCapability,
|
|
28806
29155
|
streamParams: streamParamsCapability,
|
|
28807
29156
|
switch: switchCapability,
|
|
28808
29157
|
tamper: tamperCapability,
|
|
@@ -29456,6 +29805,15 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
29456
29805
|
labels: ["probe not implemented"]
|
|
29457
29806
|
};
|
|
29458
29807
|
}
|
|
29808
|
+
/**
|
|
29809
|
+
* Top-level devices restored at once in {@link onRestoreDevices}.
|
|
29810
|
+
*
|
|
29811
|
+
* Four covers the fleets this ships to without turning a boot into a burst a
|
|
29812
|
+
* camera NVR answers with a refusal. A provider whose upstream is a single
|
|
29813
|
+
* session with a serial command channel (a Baichuan hub, an NVR that
|
|
29814
|
+
* serialises ISAPI) should lower it; nothing needs to raise it.
|
|
29815
|
+
*/
|
|
29816
|
+
restoreConcurrency = 4;
|
|
29459
29817
|
async restoreDevices(savedDevices) {
|
|
29460
29818
|
await this.onRestoreDevices(savedDevices);
|
|
29461
29819
|
if (savedDevices.length > 0) this.ctx.logger.info(`Restored ${savedDevices.length} ${this.providerName} device(s)`);
|
|
@@ -29487,15 +29845,15 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
29487
29845
|
*/
|
|
29488
29846
|
async onRestoreDevices(savedDevices) {
|
|
29489
29847
|
const restored = /* @__PURE__ */ new Set();
|
|
29490
|
-
|
|
29491
|
-
|
|
29848
|
+
const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
|
|
29849
|
+
const restoreOne = async (saved) => {
|
|
29492
29850
|
const Class = this.deviceClasses[saved.type];
|
|
29493
29851
|
if (!Class) {
|
|
29494
29852
|
this.ctx.logger.warn("No device class registered for restored type — skipping", {
|
|
29495
29853
|
tags: { stableId: saved.stableId },
|
|
29496
29854
|
meta: { type: saved.type }
|
|
29497
29855
|
});
|
|
29498
|
-
|
|
29856
|
+
return;
|
|
29499
29857
|
}
|
|
29500
29858
|
try {
|
|
29501
29859
|
await this.ctx.kernel.devices.create(saved.stableId, Class, {});
|
|
@@ -29509,7 +29867,15 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
29509
29867
|
}
|
|
29510
29868
|
});
|
|
29511
29869
|
}
|
|
29512
|
-
}
|
|
29870
|
+
};
|
|
29871
|
+
let nextTopLevel = 0;
|
|
29872
|
+
await Promise.all(Array.from({ length: Math.min(Math.max(1, this.restoreConcurrency), topLevel.length) }, async () => {
|
|
29873
|
+
for (;;) {
|
|
29874
|
+
const saved = topLevel[nextTopLevel++];
|
|
29875
|
+
if (saved === void 0) return;
|
|
29876
|
+
await restoreOne(saved);
|
|
29877
|
+
}
|
|
29878
|
+
}));
|
|
29513
29879
|
const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
|
|
29514
29880
|
for (const saved of childRows) {
|
|
29515
29881
|
const Class = this.deviceClasses[saved.type];
|
|
@@ -31690,6 +32056,12 @@ Object.freeze({
|
|
|
31690
32056
|
addonId: null,
|
|
31691
32057
|
access: "view"
|
|
31692
32058
|
},
|
|
32059
|
+
"llm.resolveModelRef": {
|
|
32060
|
+
capName: "llm",
|
|
32061
|
+
capScope: "system",
|
|
32062
|
+
addonId: null,
|
|
32063
|
+
access: "create"
|
|
32064
|
+
},
|
|
31693
32065
|
"llm.setDefault": {
|
|
31694
32066
|
capName: "llm",
|
|
31695
32067
|
capScope: "system",
|
|
@@ -36082,6 +36454,11 @@ Object.freeze({
|
|
|
36082
36454
|
form: "single",
|
|
36083
36455
|
optional: false
|
|
36084
36456
|
}],
|
|
36457
|
+
"pipelineAnalytics.getEventMedia": [{
|
|
36458
|
+
name: "deviceId",
|
|
36459
|
+
form: "single",
|
|
36460
|
+
optional: false
|
|
36461
|
+
}],
|
|
36085
36462
|
"pipelineAnalytics.getKeyEvents": [{
|
|
36086
36463
|
name: "deviceId",
|
|
36087
36464
|
form: "single",
|
|
@@ -36112,6 +36489,11 @@ Object.freeze({
|
|
|
36112
36489
|
form: "single",
|
|
36113
36490
|
optional: false
|
|
36114
36491
|
}],
|
|
36492
|
+
"pipelineAnalytics.getTrackMedia": [{
|
|
36493
|
+
name: "deviceId",
|
|
36494
|
+
form: "single",
|
|
36495
|
+
optional: false
|
|
36496
|
+
}],
|
|
36115
36497
|
"pipelineAnalytics.getTrainingExportSummary": [{
|
|
36116
36498
|
name: "deviceIds",
|
|
36117
36499
|
form: "array",
|
|
@@ -36147,6 +36529,11 @@ Object.freeze({
|
|
|
36147
36529
|
form: "array",
|
|
36148
36530
|
optional: true
|
|
36149
36531
|
}],
|
|
36532
|
+
"pipelineAnalytics.listTrackMedia": [{
|
|
36533
|
+
name: "deviceId",
|
|
36534
|
+
form: "single",
|
|
36535
|
+
optional: false
|
|
36536
|
+
}],
|
|
36150
36537
|
"pipelineAnalytics.listTracks": [{
|
|
36151
36538
|
name: "deviceId",
|
|
36152
36539
|
form: "single",
|
|
@@ -36587,6 +36974,12 @@ Object.freeze({
|
|
|
36587
36974
|
form: "single",
|
|
36588
36975
|
optional: false
|
|
36589
36976
|
}],
|
|
36977
|
+
"snapshot.getSnapshotLinks": [{
|
|
36978
|
+
name: "targets",
|
|
36979
|
+
form: "object-array",
|
|
36980
|
+
optional: false,
|
|
36981
|
+
itemField: "deviceId"
|
|
36982
|
+
}],
|
|
36590
36983
|
"snapshot.getSnapshotOverview": [{
|
|
36591
36984
|
name: "deviceIds",
|
|
36592
36985
|
form: "array",
|