@camstack/addon-provider-reolink 1.2.130 → 1.2.132
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 +332 -82
- package/dist/addon.mjs +332 -82
- package/package.json +2 -2
package/dist/addon.js
CHANGED
|
@@ -5386,7 +5386,7 @@ var ZodIssueCode = {
|
|
|
5386
5386
|
var ZodFirstPartyTypeKind;
|
|
5387
5387
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5388
5388
|
//#endregion
|
|
5389
|
-
//#region ../types/dist/sleep-
|
|
5389
|
+
//#region ../types/dist/sleep-BDR76Ykr.mjs
|
|
5390
5390
|
/**
|
|
5391
5391
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5392
5392
|
* window to float samples (D455).
|
|
@@ -7271,6 +7271,40 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
7271
7271
|
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
7272
7272
|
return DeviceRole;
|
|
7273
7273
|
}({});
|
|
7274
|
+
/**
|
|
7275
|
+
* Identity — preserves literal types for downstream inference.
|
|
7276
|
+
*
|
|
7277
|
+
* The constraint is `Record<string, unknown>` (not `CustomActionsSpec`) so
|
|
7278
|
+
* TypeScript does not widen each entry's literal `kind`/`auth` fields to
|
|
7279
|
+
* the broader unions declared on `CustomActionSpec`'s default generics.
|
|
7280
|
+
* Shape validity is enforced separately by the `customAction(...)` helper
|
|
7281
|
+
* whose return type is already a `CustomActionSpec<...>`.
|
|
7282
|
+
*/
|
|
7283
|
+
function defineCustomActions(spec) {
|
|
7284
|
+
return spec;
|
|
7285
|
+
}
|
|
7286
|
+
function customAction(input, output, options) {
|
|
7287
|
+
return {
|
|
7288
|
+
input,
|
|
7289
|
+
output,
|
|
7290
|
+
kind: options?.kind ?? "query",
|
|
7291
|
+
auth: options?.auth ?? "protected",
|
|
7292
|
+
scope: options?.scope ?? { kind: "system" },
|
|
7293
|
+
...options?.caller ? { caller: "required" } : {}
|
|
7294
|
+
};
|
|
7295
|
+
}
|
|
7296
|
+
function deviceCustomAction(input, output, options) {
|
|
7297
|
+
return {
|
|
7298
|
+
input,
|
|
7299
|
+
output,
|
|
7300
|
+
kind: options?.kind ?? "query",
|
|
7301
|
+
auth: options?.auth ?? "protected",
|
|
7302
|
+
scope: {
|
|
7303
|
+
kind: "device",
|
|
7304
|
+
...options?.deviceTypes ? { deviceTypes: options.deviceTypes } : {}
|
|
7305
|
+
}
|
|
7306
|
+
};
|
|
7307
|
+
}
|
|
7274
7308
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
7275
7309
|
var VersionOutputSchema = object({ version: string() });
|
|
7276
7310
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -12132,6 +12166,21 @@ var streamSignalsCapability = {
|
|
|
12132
12166
|
mode: "singleton",
|
|
12133
12167
|
deviceTypes: Object.values(DeviceType),
|
|
12134
12168
|
runtimeState: StreamSignalsStatusSchema,
|
|
12169
|
+
/**
|
|
12170
|
+
* Runtime-state durability: **session** — mirrored in RAM, never written.
|
|
12171
|
+
*
|
|
12172
|
+
* The slice holds what the DEVICE says it can emit. That is a probed fact,
|
|
12173
|
+
* not an operator choice: the provider re-declares it on every registration,
|
|
12174
|
+
* so losing it loses nothing and persisting it would freeze an answer the
|
|
12175
|
+
* camera is entitled to change. Measured the same day on the sibling case —
|
|
12176
|
+
* `native-object-detection.supportedClasses` was persisted, and a firmware
|
|
12177
|
+
* class the camera really detected stayed missing for the life of the row
|
|
12178
|
+
* because the fix could not reach it.
|
|
12179
|
+
*
|
|
12180
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
12181
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
12182
|
+
*/
|
|
12183
|
+
durability: "session",
|
|
12135
12184
|
methods: {
|
|
12136
12185
|
/**
|
|
12137
12186
|
* What this device can emit. Empty is a valid and common answer — most
|
|
@@ -13106,40 +13155,6 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
13106
13155
|
content: string()
|
|
13107
13156
|
})) }), { auth: "admin" });
|
|
13108
13157
|
/**
|
|
13109
|
-
* Identity — preserves literal types for downstream inference.
|
|
13110
|
-
*
|
|
13111
|
-
* The constraint is `Record<string, unknown>` (not `CustomActionsSpec`) so
|
|
13112
|
-
* TypeScript does not widen each entry's literal `kind`/`auth` fields to
|
|
13113
|
-
* the broader unions declared on `CustomActionSpec`'s default generics.
|
|
13114
|
-
* Shape validity is enforced separately by the `customAction(...)` helper
|
|
13115
|
-
* whose return type is already a `CustomActionSpec<...>`.
|
|
13116
|
-
*/
|
|
13117
|
-
function defineCustomActions(spec) {
|
|
13118
|
-
return spec;
|
|
13119
|
-
}
|
|
13120
|
-
function customAction(input, output, options) {
|
|
13121
|
-
return {
|
|
13122
|
-
input,
|
|
13123
|
-
output,
|
|
13124
|
-
kind: options?.kind ?? "query",
|
|
13125
|
-
auth: options?.auth ?? "protected",
|
|
13126
|
-
scope: options?.scope ?? { kind: "system" },
|
|
13127
|
-
...options?.caller ? { caller: "required" } : {}
|
|
13128
|
-
};
|
|
13129
|
-
}
|
|
13130
|
-
function deviceCustomAction(input, output, options) {
|
|
13131
|
-
return {
|
|
13132
|
-
input,
|
|
13133
|
-
output,
|
|
13134
|
-
kind: options?.kind ?? "query",
|
|
13135
|
-
auth: options?.auth ?? "protected",
|
|
13136
|
-
scope: {
|
|
13137
|
-
kind: "device",
|
|
13138
|
-
...options?.deviceTypes ? { deviceTypes: options.deviceTypes } : {}
|
|
13139
|
-
}
|
|
13140
|
-
};
|
|
13141
|
-
}
|
|
13142
|
-
/**
|
|
13143
13158
|
* `custom-model-registry` — collection cap exposing operator-registered
|
|
13144
13159
|
* custom detection models. Each provider (today: `addon-model-studio`)
|
|
13145
13160
|
* contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
|
|
@@ -18820,6 +18835,48 @@ var NcAlarmConfigSchema = object({
|
|
|
18820
18835
|
settings: NcAlarmSettingsSchema,
|
|
18821
18836
|
coverage: array(NcAlarmModeCoverageSchema)
|
|
18822
18837
|
});
|
|
18838
|
+
/**
|
|
18839
|
+
* ONE rule's demand on ONE camera's clip ring.
|
|
18840
|
+
*
|
|
18841
|
+
* A rule, not a camera: the broker takes the MAX over the asks that reach a
|
|
18842
|
+
* camera, so the answer stays a faithful description of the rules and the
|
|
18843
|
+
* bounding (the byte budget, the broker's own ceiling) stays where the cost
|
|
18844
|
+
* lives. A camera that appears in no ask is being told **nothing** — which is
|
|
18845
|
+
* a different thing from never having been told, and only the envelope
|
|
18846
|
+
* (`rulesLoaded`) can tell those apart.
|
|
18847
|
+
*/
|
|
18848
|
+
var NcClipRetentionAskSchema = object({
|
|
18849
|
+
/**
|
|
18850
|
+
* The camera this ask is about. **Absent = every camera**, which is what a
|
|
18851
|
+
* rule with no `conditions.devices` means: it can fire anywhere, so it is
|
|
18852
|
+
* asking everywhere.
|
|
18853
|
+
*/
|
|
18854
|
+
deviceId: number().int().nonnegative().optional(),
|
|
18855
|
+
/** Seconds of history the rule needs held for it. Never zero — a rule that
|
|
18856
|
+
* needs nothing produces no ask at all. */
|
|
18857
|
+
seconds: number().min(0).max(60),
|
|
18858
|
+
/** The profile the rule cuts from, when it named one. Absent = the cheapest
|
|
18859
|
+
* assigned, which is what the cut defaults to. */
|
|
18860
|
+
profile: CamProfileSchema.optional(),
|
|
18861
|
+
/** Who is asking — so a retention the operator did not expect names a rule. */
|
|
18862
|
+
ruleId: string(),
|
|
18863
|
+
ruleName: string(),
|
|
18864
|
+
/** Why this many seconds: the rule's own window, or the occupancy ceiling. */
|
|
18865
|
+
reason: _enum(["window", "occupancy"])
|
|
18866
|
+
});
|
|
18867
|
+
/**
|
|
18868
|
+
* The whole answer, with the one bit that keeps absence from reading as zero.
|
|
18869
|
+
*
|
|
18870
|
+
* `rulesLoaded` is false until the rule ledger has completed a load at least
|
|
18871
|
+
* once. A centre that is up but has not read its rules yet answers with an
|
|
18872
|
+
* empty ask list that means nothing at all — applying it would drop every
|
|
18873
|
+
* camera's ring to zero for the first events after a restart, which is the
|
|
18874
|
+
* footage this mechanism exists to keep.
|
|
18875
|
+
*/
|
|
18876
|
+
var NcClipRetentionPlanSchema = object({
|
|
18877
|
+
rulesLoaded: boolean(),
|
|
18878
|
+
asks: array(NcClipRetentionAskSchema).readonly()
|
|
18879
|
+
});
|
|
18823
18880
|
method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), method(object({ ruleId: string() }), object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }), method(object({ rule: NcRuleInputSchema }), object({ rule: NcRuleSchema }), {
|
|
18824
18881
|
kind: "mutation",
|
|
18825
18882
|
auth: "admin",
|
|
@@ -18840,7 +18897,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
18840
18897
|
}), object({ success: literal(true) }), {
|
|
18841
18898
|
kind: "mutation",
|
|
18842
18899
|
auth: "admin"
|
|
18843
|
-
}), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
|
|
18900
|
+
}), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
|
|
18844
18901
|
deviceId: number().int(),
|
|
18845
18902
|
muted: boolean()
|
|
18846
18903
|
}), object({ success: literal(true) }), {
|
|
@@ -20025,13 +20082,26 @@ var TrackAudioLabelSchema = object({
|
|
|
20025
20082
|
* - `audio` — an audio event on the camera itself that was anomalous for
|
|
20026
20083
|
* THAT camera, loud, and heard while nothing visual was happening (D62).
|
|
20027
20084
|
*
|
|
20085
|
+
* - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
|
|
20086
|
+
* that frame's pixel space. A real root detector and a SPATIAL one: its
|
|
20087
|
+
* boxes are the same kind of fact `pipeline`'s are, produced by a different
|
|
20088
|
+
* detector.
|
|
20089
|
+
*
|
|
20090
|
+
* `onboard` was missing here while `DetectionSourceSchema` already had it, so
|
|
20091
|
+
* a persisted onboard track failed `safeParse` on read and came back with NO
|
|
20092
|
+
* source at all — which `isSpatialTrack` then admitted by accident, through the
|
|
20093
|
+
* `undefined` arm rather than by anybody's decision, and which `excludeSources`
|
|
20094
|
+
* could not name.
|
|
20095
|
+
*
|
|
20028
20096
|
* The spatial subsystems (tracker association, occupancy count, re-id /
|
|
20029
20097
|
* embedding, resurrection) MUST skip every synthetic source. Test for that
|
|
20030
|
-
* with `isSpatialTrack`, which allow-lists
|
|
20031
|
-
* check silently readmits every source added after it was
|
|
20098
|
+
* with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
|
|
20099
|
+
* `!== 'sensor'` check silently readmits every source added after it was
|
|
20100
|
+
* written.
|
|
20032
20101
|
*/
|
|
20033
20102
|
var TrackSourceSchema = _enum([
|
|
20034
20103
|
"pipeline",
|
|
20104
|
+
"onboard",
|
|
20035
20105
|
"sensor",
|
|
20036
20106
|
"audio"
|
|
20037
20107
|
]);
|
|
@@ -21414,6 +21484,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
|
|
|
21414
21484
|
* solid is this track", cheaper than re-deriving it from a trajectory. */
|
|
21415
21485
|
framesMatched: number().int()
|
|
21416
21486
|
})).readonly() });
|
|
21487
|
+
/**
|
|
21488
|
+
* The long-term series a camera produces. A closed union so a typo cannot
|
|
21489
|
+
* invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
|
|
21490
|
+
* is an alias of this, not a second list.
|
|
21491
|
+
*/
|
|
21492
|
+
var AnalyticsLtsSeriesSchema = _enum([
|
|
21493
|
+
"motion",
|
|
21494
|
+
"audio-dbfs",
|
|
21495
|
+
"battery",
|
|
21496
|
+
"occupancy"
|
|
21497
|
+
]);
|
|
21498
|
+
/**
|
|
21499
|
+
* One closed 5-minute bucket of a long-term series.
|
|
21500
|
+
*
|
|
21501
|
+
* `samples` is how many readings the bucket folded — it is the ACTIVITY for a
|
|
21502
|
+
* series like `motion`, and the confidence for one like `occupancy`. A bucket
|
|
21503
|
+
* that exists at all was measured; a bucket that is absent was not, which is a
|
|
21504
|
+
* different claim from zero and the reason this is a sparse series.
|
|
21505
|
+
*/
|
|
21506
|
+
var LtsBucketSchema = object({
|
|
21507
|
+
scope: string(),
|
|
21508
|
+
bucketStart: number().int(),
|
|
21509
|
+
samples: number().int().nonnegative(),
|
|
21510
|
+
sum: number(),
|
|
21511
|
+
min: number(),
|
|
21512
|
+
max: number()
|
|
21513
|
+
});
|
|
21417
21514
|
DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
|
|
21418
21515
|
deviceId: number(),
|
|
21419
21516
|
trackId: string()
|
|
@@ -21460,6 +21557,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
21460
21557
|
kinds: array(string()).optional(),
|
|
21461
21558
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
21462
21559
|
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
21560
|
+
deviceId: number(),
|
|
21561
|
+
series: AnalyticsLtsSeriesSchema,
|
|
21562
|
+
/** Omit for EVERY scope of this series in the range. */
|
|
21563
|
+
scope: string().optional(),
|
|
21564
|
+
from: number(),
|
|
21565
|
+
to: number(),
|
|
21566
|
+
limit: number().int().positive().optional()
|
|
21567
|
+
}), array(LtsBucketSchema).readonly()), method(object({
|
|
21463
21568
|
deviceId: number(),
|
|
21464
21569
|
since: number(),
|
|
21465
21570
|
until: number(),
|
|
@@ -22078,13 +22183,39 @@ var MotionSourceEnum = _enum([
|
|
|
22078
22183
|
*/
|
|
22079
22184
|
var MotionSourcesSchema = array(MotionSourceEnum);
|
|
22080
22185
|
/**
|
|
22081
|
-
* Which root
|
|
22082
|
-
*
|
|
22083
|
-
*
|
|
22084
|
-
*
|
|
22085
|
-
*
|
|
22186
|
+
* Which root detector a camera runs — EXACTLY ONE.
|
|
22187
|
+
*
|
|
22188
|
+
* Deliberately the SAME vocabulary post-analysis already tags every detection
|
|
22189
|
+
* with (`DetectionSource`) rather than a second spelling of the same two
|
|
22190
|
+
* ideas: the value an operator picks here is the value that comes back on the
|
|
22191
|
+
* track, the overlay and the debug row.
|
|
22192
|
+
*
|
|
22193
|
+
* ## Why one, and why it is still an array
|
|
22194
|
+
*
|
|
22195
|
+
* Two roots on one camera is a capability nobody asked for and the operator has
|
|
22196
|
+
* since ruled out. It was never free: both planes feed the SAME per-device
|
|
22197
|
+
* stationary registry, both can promote the same parked object, and the two
|
|
22198
|
+
* detectors disagree about the same box by construction — which is the
|
|
22199
|
+
* disagreement D505 had to arbitrate. Removing the case removes the arbitration.
|
|
22200
|
+
*
|
|
22201
|
+
* The ARRAY shape survives because the stored settings and the attach payload
|
|
22202
|
+
* already speak it on every camera of every fleet, and a cap input that
|
|
22203
|
+
* suddenly refuses a stored value makes the camera un-attachable — a fail-closed
|
|
22204
|
+
* in the one direction that costs an operator their cameras. So the wire stays
|
|
22205
|
+
* tolerant and the TYPE is exact: a longer list is truncated to its first
|
|
22206
|
+
* element rather than refused, and `max(1)` is what every consumer can then
|
|
22207
|
+
* rely on.
|
|
22208
|
+
*
|
|
22209
|
+
* AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
|
|
22210
|
+
* a camera an operator left with no root detector — and `planDetectionSources`
|
|
22211
|
+
* has always said so: inventing a root for them is how a default acts without
|
|
22212
|
+
* anybody choosing it. Tightening this to `length(1)` broke that, and the
|
|
22213
|
+
* suite caught it.
|
|
22214
|
+
*
|
|
22215
|
+
* Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
|
|
22216
|
+
* `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
|
|
22086
22217
|
*/
|
|
22087
|
-
var DetectionSourcesSchema = array(DetectionSourceSchema);
|
|
22218
|
+
var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
|
|
22088
22219
|
/**
|
|
22089
22220
|
* Input shape for `pipeline-runner.reportMotion` cap method. Exported
|
|
22090
22221
|
* so cap-side consumers (the orchestrator forward, the runner addon's
|
|
@@ -22446,7 +22577,47 @@ var RunnerLocalMetricsSchema = object({
|
|
|
22446
22577
|
queueDepth: number(),
|
|
22447
22578
|
frameLazy: FrameLazyMetricsSchema.optional()
|
|
22448
22579
|
});
|
|
22580
|
+
/**
|
|
22581
|
+
* Pipeline Runner capability — runtime detection workhorse.
|
|
22582
|
+
*
|
|
22583
|
+
* One instance per node. Receives camera assignments from
|
|
22584
|
+
* `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
|
|
22585
|
+
* decoded frames, drains motion + detection queues, calls the local
|
|
22586
|
+
* `motion-detection` and `pipeline-executor` capabilities, and emits typed
|
|
22587
|
+
* `pipeline.inference-result` and `detection.motion-analysis` events on
|
|
22588
|
+
* the bus.
|
|
22589
|
+
*
|
|
22590
|
+
* Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
|
|
22591
|
+
* runner has zero knowledge of other agents, no global state, and never
|
|
22592
|
+
* makes assignment decisions itself.
|
|
22593
|
+
*/
|
|
22594
|
+
/**
|
|
22595
|
+
* Why a runner declined to look at a camera out of band. Named rather than a
|
|
22596
|
+
* bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
|
|
22597
|
+
* already mid-session and one already bursting are four different facts, and a
|
|
22598
|
+
* caller told only "no" cannot tell a protection from a bug.
|
|
22599
|
+
*/
|
|
22600
|
+
var OccupancyBurstRefusalSchema = _enum([
|
|
22601
|
+
"not-attached",
|
|
22602
|
+
"battery-asleep",
|
|
22603
|
+
"not-watching",
|
|
22604
|
+
"already-bursting"
|
|
22605
|
+
]);
|
|
22449
22606
|
method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
|
|
22607
|
+
deviceId: number(),
|
|
22608
|
+
/** How many frames to collect. The caller sizes this against what its
|
|
22609
|
+
* own confirmation needs; the runner clamps it to the field's range. */
|
|
22610
|
+
frames: number().int().positive().optional(),
|
|
22611
|
+
/** Why, for the log. A burst nobody can attribute is a cost nobody can
|
|
22612
|
+
* defend — the periodic one at least has a timer to point at. */
|
|
22613
|
+
reason: string()
|
|
22614
|
+
}), object({
|
|
22615
|
+
started: boolean(),
|
|
22616
|
+
refusedBecause: OccupancyBurstRefusalSchema.optional()
|
|
22617
|
+
}), {
|
|
22618
|
+
auth: "admin",
|
|
22619
|
+
kind: "mutation"
|
|
22620
|
+
}), method(object({
|
|
22450
22621
|
handle: FrameHandleSchema,
|
|
22451
22622
|
bbox: NativeCropBboxSchema,
|
|
22452
22623
|
maxWidth: number().int().positive().optional(),
|
|
@@ -34598,7 +34769,28 @@ var PerScopeBreakdownSchema = object({
|
|
|
34598
34769
|
/** Total tracked objects in this scope (frame / zone / unzoned). */
|
|
34599
34770
|
totalObjects: number().int().nonnegative(),
|
|
34600
34771
|
/** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
|
|
34601
|
-
byClass: record(string(), number().int().nonnegative())
|
|
34772
|
+
byClass: record(string(), number().int().nonnegative()),
|
|
34773
|
+
/**
|
|
34774
|
+
* Of `totalObjects`, how many are STANDING — objects the census holds,
|
|
34775
|
+
* present but not going anywhere (a parked car, a bin, a statue).
|
|
34776
|
+
*
|
|
34777
|
+
* The operator's question about a scene has two halves and they answer
|
|
34778
|
+
* different things: "what is parked here" is stable for hours and must not
|
|
34779
|
+
* flap, while "who is walking through" is the thing an alert is about. One
|
|
34780
|
+
* number could only serve one of them, and `totalObjects` served the first
|
|
34781
|
+
* badly — a passer-by moved it and every occupancy rule saw a change.
|
|
34782
|
+
*
|
|
34783
|
+
* `standing + transient === totalObjects`, always. The total keeps its old
|
|
34784
|
+
* meaning exactly, so no rule written against it changes behaviour.
|
|
34785
|
+
*
|
|
34786
|
+
* ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
|
|
34787
|
+
* this field cannot say, and a reader that folds absence in as 0 reports "no
|
|
34788
|
+
* parked objects here" about a camera whose lot is full.
|
|
34789
|
+
*/
|
|
34790
|
+
standingObjects: number().int().nonnegative().optional(),
|
|
34791
|
+
/** Of `totalObjects`, how many are passing through. See `standingObjects` —
|
|
34792
|
+
* same absence rule. */
|
|
34793
|
+
transientObjects: number().int().nonnegative().optional()
|
|
34602
34794
|
});
|
|
34603
34795
|
var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
|
|
34604
34796
|
zoneId: string(),
|
|
@@ -34721,6 +34913,23 @@ var HistoryPointSchema = object({
|
|
|
34721
34913
|
* triple covers the three spatial scopes (per-zone, frame-wide,
|
|
34722
34914
|
* outside-any-zone). Crossing them with `className?` gives the full
|
|
34723
34915
|
* combinatorial coverage the operator UI requested.
|
|
34916
|
+
*
|
|
34917
|
+
* ## ⚠️ The three history methods are DEPRECATED
|
|
34918
|
+
*
|
|
34919
|
+
* They are served from a 60-MINUTE in-memory ring. It dies with the process,
|
|
34920
|
+
* so after a restart they report nothing about a night that did happen, and any
|
|
34921
|
+
* window past an hour is silently clamped — a caller cannot tell a clamp from
|
|
34922
|
+
* an empty scene. The presets in the occupancy chart existed to hide that
|
|
34923
|
+
* horizon rather than to answer a question.
|
|
34924
|
+
*
|
|
34925
|
+
* Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
|
|
34926
|
+
* instead. Those rows are already written for every camera, ~60 bytes per
|
|
34927
|
+
* (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
|
|
34928
|
+
* Spell the scope with `occupancyScope(zoneId?, className?)` — the same
|
|
34929
|
+
* function the writer uses, so the two cannot drift.
|
|
34930
|
+
*
|
|
34931
|
+
* The ring stays until these three go, and it is the only thing left reading
|
|
34932
|
+
* it. Do not add a fourth caller.
|
|
34724
34933
|
*/
|
|
34725
34934
|
var zoneAnalyticsCapability = {
|
|
34726
34935
|
name: "zone-analytics",
|
|
@@ -39580,6 +39789,12 @@ Object.freeze({
|
|
|
39580
39789
|
addonId: null,
|
|
39581
39790
|
access: "view"
|
|
39582
39791
|
},
|
|
39792
|
+
"notificationRules.getClipRetentionAsks": {
|
|
39793
|
+
capName: "notification-rules",
|
|
39794
|
+
capScope: "system",
|
|
39795
|
+
addonId: null,
|
|
39796
|
+
access: "view"
|
|
39797
|
+
},
|
|
39583
39798
|
"notificationRules.getConditionCatalog": {
|
|
39584
39799
|
capName: "notification-rules",
|
|
39585
39800
|
capScope: "system",
|
|
@@ -40090,6 +40305,12 @@ Object.freeze({
|
|
|
40090
40305
|
addonId: null,
|
|
40091
40306
|
access: "create"
|
|
40092
40307
|
},
|
|
40308
|
+
"pipelineAnalytics.readLongTermSeries": {
|
|
40309
|
+
capName: "pipeline-analytics",
|
|
40310
|
+
capScope: "device",
|
|
40311
|
+
addonId: null,
|
|
40312
|
+
access: "view"
|
|
40313
|
+
},
|
|
40093
40314
|
"pipelineAnalytics.rebuildObjectEmbeddings": {
|
|
40094
40315
|
capName: "pipeline-analytics",
|
|
40095
40316
|
capScope: "device",
|
|
@@ -40762,6 +40983,12 @@ Object.freeze({
|
|
|
40762
40983
|
addonId: null,
|
|
40763
40984
|
access: "create"
|
|
40764
40985
|
},
|
|
40986
|
+
"pipelineRunner.requestOccupancyBurst": {
|
|
40987
|
+
capName: "pipeline-runner",
|
|
40988
|
+
capScope: "system",
|
|
40989
|
+
addonId: null,
|
|
40990
|
+
access: "create"
|
|
40991
|
+
},
|
|
40765
40992
|
"pipelineRunner.runDetailSubtree": {
|
|
40766
40993
|
capName: "pipeline-runner",
|
|
40767
40994
|
capScope: "system",
|
|
@@ -43906,6 +44133,11 @@ Object.freeze({
|
|
|
43906
44133
|
form: "single",
|
|
43907
44134
|
optional: false
|
|
43908
44135
|
}],
|
|
44136
|
+
"pipelineAnalytics.readLongTermSeries": [{
|
|
44137
|
+
name: "deviceId",
|
|
44138
|
+
form: "single",
|
|
44139
|
+
optional: false
|
|
44140
|
+
}],
|
|
43909
44141
|
"pipelineAnalytics.rebuildObjectEmbeddings": [{
|
|
43910
44142
|
name: "deviceId",
|
|
43911
44143
|
form: "single",
|
|
@@ -44096,6 +44328,11 @@ Object.freeze({
|
|
|
44096
44328
|
form: "single",
|
|
44097
44329
|
optional: false
|
|
44098
44330
|
}],
|
|
44331
|
+
"pipelineRunner.requestOccupancyBurst": [{
|
|
44332
|
+
name: "deviceId",
|
|
44333
|
+
form: "single",
|
|
44334
|
+
optional: false
|
|
44335
|
+
}],
|
|
44099
44336
|
"pipelineRunner.runDetailSubtree": [{
|
|
44100
44337
|
name: "deviceId",
|
|
44101
44338
|
form: "single",
|
|
@@ -145323,6 +145560,29 @@ function buildOsdSection(snapshot, values, opts) {
|
|
|
145323
145560
|
};
|
|
145324
145561
|
}
|
|
145325
145562
|
//#endregion
|
|
145563
|
+
//#region src/snapshot-wake-gate.ts
|
|
145564
|
+
/**
|
|
145565
|
+
* Decide whether a snapshot read may reach the camera's socket.
|
|
145566
|
+
*
|
|
145567
|
+
* Pure, and deliberately total: three inputs, no clock, no cooldown, no
|
|
145568
|
+
* fallible read (D49 — a gate that can be talked out of its answer by a
|
|
145569
|
+
* stale timestamp is not a gate).
|
|
145570
|
+
*/
|
|
145571
|
+
function decideSnapshotCameraAccess(input) {
|
|
145572
|
+
if (!input.isBattery || !input.sleeping) return {
|
|
145573
|
+
kind: "proceed",
|
|
145574
|
+
stampCooldown: false
|
|
145575
|
+
};
|
|
145576
|
+
if (!input.force) return {
|
|
145577
|
+
kind: "refuse",
|
|
145578
|
+
reason: "sleeping"
|
|
145579
|
+
};
|
|
145580
|
+
return {
|
|
145581
|
+
kind: "proceed",
|
|
145582
|
+
stampCooldown: true
|
|
145583
|
+
};
|
|
145584
|
+
}
|
|
145585
|
+
//#endregion
|
|
145326
145586
|
//#region src/raw-state.ts
|
|
145327
145587
|
/**
|
|
145328
145588
|
* Source tag for every raw-state blob this provider emits.
|
|
@@ -150117,9 +150377,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150117
150377
|
}
|
|
150118
150378
|
registerNativeCapabilities() {
|
|
150119
150379
|
this.ctx.registerNativeCap(snapshotCapability, {
|
|
150120
|
-
getSnapshot: async ({ deviceId }) => {
|
|
150380
|
+
getSnapshot: async ({ deviceId, force }) => {
|
|
150121
150381
|
if (deviceId !== this.id) throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
|
|
150122
|
-
return this.fetchSnapshotWithSingleFlight();
|
|
150382
|
+
return this.fetchSnapshotWithSingleFlight(force === true);
|
|
150123
150383
|
},
|
|
150124
150384
|
invalidateCache: async () => {}
|
|
150125
150385
|
});
|
|
@@ -150562,33 +150822,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150562
150822
|
status: this.state.battery
|
|
150563
150823
|
}));
|
|
150564
150824
|
}
|
|
150565
|
-
/**
|
|
150566
|
-
* Gate for a PROACTIVE camera read, checked BEFORE `ensureApi()`.
|
|
150567
|
-
*
|
|
150568
|
-
* The login itself is the wake. On a sleeping UDP/battery camera the
|
|
150569
|
-
* lib's discovery + handshake nudges the firmware awake (same reason
|
|
150570
|
-
* `refreshParentSettingsSnapshot` refuses to call `ensureApi` while
|
|
150571
|
-
* asleep), so gating only the explicit `wakeUp()` call is useless —
|
|
150572
|
-
* by the time we reach it the camera is already up. Production logs
|
|
150573
|
-
* showed exactly that: a background read produced a full
|
|
150574
|
-
* `Connecting to Reolink` → BCUDP discovery → `battery sleep state
|
|
150575
|
-
* committed` (awake) → whole refresh cascade, on a camera that had
|
|
150576
|
-
* been asleep for six minutes.
|
|
150577
|
-
*
|
|
150578
|
-
* Returns `false` when the caller must serve cache / bail out without
|
|
150579
|
-
* touching the socket. Stamps the cooldown when it does let a wake
|
|
150580
|
-
* through, so "at most one proactive wake per
|
|
150581
|
-
* `PROACTIVE_WAKE_COOLDOWN_MS`" holds across ALL proactive callers
|
|
150582
|
-
* rather than per-caller.
|
|
150583
|
-
*
|
|
150584
|
-
* Demand-driven paths never call this — see `canProactivelyWake`.
|
|
150585
|
-
*/
|
|
150586
|
-
allowProactiveCameraAccess(reason) {
|
|
150587
|
-
if (!this.isBattery || !this.sleeping) return true;
|
|
150588
|
-
if (!this.canProactivelyWake(reason)) return false;
|
|
150589
|
-
this.markWakeIssued();
|
|
150590
|
-
return true;
|
|
150591
|
-
}
|
|
150592
150825
|
updateBatteryCache(info) {
|
|
150593
150826
|
const mapped = this.mapBatteryInfo(info);
|
|
150594
150827
|
const now = Date.now();
|
|
@@ -150868,12 +151101,27 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150868
151101
|
}
|
|
150869
151102
|
await this.refreshCameraEmailConfig().catch(() => {});
|
|
150870
151103
|
}
|
|
150871
|
-
|
|
151104
|
+
/**
|
|
151105
|
+
* @param force - the caller's `snapshot.getSnapshot({ force })`. THE gate:
|
|
151106
|
+
* without it a sleeping battery camera is never touched, whatever the
|
|
151107
|
+
* cache holds. See `snapshot-wake-gate.ts` for why the flag — and not a
|
|
151108
|
+
* cooldown — is what decides.
|
|
151109
|
+
*/
|
|
151110
|
+
async fetchSnapshotWithSingleFlight(force) {
|
|
150872
151111
|
if (this.snapshotInFlight) return this.snapshotInFlight;
|
|
150873
|
-
|
|
150874
|
-
|
|
151112
|
+
const access = decideSnapshotCameraAccess({
|
|
151113
|
+
isBattery: this.isBattery,
|
|
151114
|
+
sleeping: this.sleeping,
|
|
151115
|
+
force
|
|
151116
|
+
});
|
|
151117
|
+
if (access.kind === "refuse") {
|
|
151118
|
+
this.ctx.logger.info("snapshot: refused — battery cam asleep and no operator force", {
|
|
151119
|
+
tags: { deviceId: this.id },
|
|
151120
|
+
meta: { reason: access.reason }
|
|
151121
|
+
});
|
|
150875
151122
|
return null;
|
|
150876
151123
|
}
|
|
151124
|
+
if (access.stampCooldown) this.markWakeIssued();
|
|
150877
151125
|
const promise = (async () => {
|
|
150878
151126
|
let api;
|
|
150879
151127
|
try {
|
|
@@ -152596,11 +152844,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152596
152844
|
};
|
|
152597
152845
|
this.ctx.registerNativeCap(nativeObjectDetectionCapability, provider);
|
|
152598
152846
|
this.registerStreamSignalsCap();
|
|
152599
|
-
if (this.runtimeState.getCapState(CAP_NAME) === void 0)
|
|
152600
|
-
|
|
152601
|
-
|
|
152602
|
-
|
|
152603
|
-
|
|
152847
|
+
if (this.runtimeState.getCapState(CAP_NAME) === void 0) {
|
|
152848
|
+
this.runtimeState.setCapState(CAP_NAME, {
|
|
152849
|
+
...buildEmptyState(),
|
|
152850
|
+
lastFetchedAt: Date.now()
|
|
152851
|
+
});
|
|
152852
|
+
this.ctx.logger.info("Reolink onboard AI: no restored state — cold-start default (OFF)", { tags: { deviceId: this.id } });
|
|
152853
|
+
} else this.runtimeState.patchCapState(CAP_NAME, { supportedClasses: buildSupportedClasses() });
|
|
152604
152854
|
this.ctx.logger.info("Reolink native-object-detection cap registered", { tags: { deviceId: this.id } });
|
|
152605
152855
|
}
|
|
152606
152856
|
/**
|
package/dist/addon.mjs
CHANGED
|
@@ -5381,7 +5381,7 @@ var ZodIssueCode = {
|
|
|
5381
5381
|
var ZodFirstPartyTypeKind;
|
|
5382
5382
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5383
5383
|
//#endregion
|
|
5384
|
-
//#region ../types/dist/sleep-
|
|
5384
|
+
//#region ../types/dist/sleep-BDR76Ykr.mjs
|
|
5385
5385
|
/**
|
|
5386
5386
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5387
5387
|
* window to float samples (D455).
|
|
@@ -7266,6 +7266,40 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
7266
7266
|
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
7267
7267
|
return DeviceRole;
|
|
7268
7268
|
}({});
|
|
7269
|
+
/**
|
|
7270
|
+
* Identity — preserves literal types for downstream inference.
|
|
7271
|
+
*
|
|
7272
|
+
* The constraint is `Record<string, unknown>` (not `CustomActionsSpec`) so
|
|
7273
|
+
* TypeScript does not widen each entry's literal `kind`/`auth` fields to
|
|
7274
|
+
* the broader unions declared on `CustomActionSpec`'s default generics.
|
|
7275
|
+
* Shape validity is enforced separately by the `customAction(...)` helper
|
|
7276
|
+
* whose return type is already a `CustomActionSpec<...>`.
|
|
7277
|
+
*/
|
|
7278
|
+
function defineCustomActions(spec) {
|
|
7279
|
+
return spec;
|
|
7280
|
+
}
|
|
7281
|
+
function customAction(input, output, options) {
|
|
7282
|
+
return {
|
|
7283
|
+
input,
|
|
7284
|
+
output,
|
|
7285
|
+
kind: options?.kind ?? "query",
|
|
7286
|
+
auth: options?.auth ?? "protected",
|
|
7287
|
+
scope: options?.scope ?? { kind: "system" },
|
|
7288
|
+
...options?.caller ? { caller: "required" } : {}
|
|
7289
|
+
};
|
|
7290
|
+
}
|
|
7291
|
+
function deviceCustomAction(input, output, options) {
|
|
7292
|
+
return {
|
|
7293
|
+
input,
|
|
7294
|
+
output,
|
|
7295
|
+
kind: options?.kind ?? "query",
|
|
7296
|
+
auth: options?.auth ?? "protected",
|
|
7297
|
+
scope: {
|
|
7298
|
+
kind: "device",
|
|
7299
|
+
...options?.deviceTypes ? { deviceTypes: options.deviceTypes } : {}
|
|
7300
|
+
}
|
|
7301
|
+
};
|
|
7302
|
+
}
|
|
7269
7303
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
7270
7304
|
var VersionOutputSchema = object({ version: string() });
|
|
7271
7305
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -12127,6 +12161,21 @@ var streamSignalsCapability = {
|
|
|
12127
12161
|
mode: "singleton",
|
|
12128
12162
|
deviceTypes: Object.values(DeviceType),
|
|
12129
12163
|
runtimeState: StreamSignalsStatusSchema,
|
|
12164
|
+
/**
|
|
12165
|
+
* Runtime-state durability: **session** — mirrored in RAM, never written.
|
|
12166
|
+
*
|
|
12167
|
+
* The slice holds what the DEVICE says it can emit. That is a probed fact,
|
|
12168
|
+
* not an operator choice: the provider re-declares it on every registration,
|
|
12169
|
+
* so losing it loses nothing and persisting it would freeze an answer the
|
|
12170
|
+
* camera is entitled to change. Measured the same day on the sibling case —
|
|
12171
|
+
* `native-object-detection.supportedClasses` was persisted, and a firmware
|
|
12172
|
+
* class the camera really detected stayed missing for the life of the row
|
|
12173
|
+
* because the fix could not reach it.
|
|
12174
|
+
*
|
|
12175
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
12176
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
12177
|
+
*/
|
|
12178
|
+
durability: "session",
|
|
12130
12179
|
methods: {
|
|
12131
12180
|
/**
|
|
12132
12181
|
* What this device can emit. Empty is a valid and common answer — most
|
|
@@ -13101,40 +13150,6 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
13101
13150
|
content: string()
|
|
13102
13151
|
})) }), { auth: "admin" });
|
|
13103
13152
|
/**
|
|
13104
|
-
* Identity — preserves literal types for downstream inference.
|
|
13105
|
-
*
|
|
13106
|
-
* The constraint is `Record<string, unknown>` (not `CustomActionsSpec`) so
|
|
13107
|
-
* TypeScript does not widen each entry's literal `kind`/`auth` fields to
|
|
13108
|
-
* the broader unions declared on `CustomActionSpec`'s default generics.
|
|
13109
|
-
* Shape validity is enforced separately by the `customAction(...)` helper
|
|
13110
|
-
* whose return type is already a `CustomActionSpec<...>`.
|
|
13111
|
-
*/
|
|
13112
|
-
function defineCustomActions(spec) {
|
|
13113
|
-
return spec;
|
|
13114
|
-
}
|
|
13115
|
-
function customAction(input, output, options) {
|
|
13116
|
-
return {
|
|
13117
|
-
input,
|
|
13118
|
-
output,
|
|
13119
|
-
kind: options?.kind ?? "query",
|
|
13120
|
-
auth: options?.auth ?? "protected",
|
|
13121
|
-
scope: options?.scope ?? { kind: "system" },
|
|
13122
|
-
...options?.caller ? { caller: "required" } : {}
|
|
13123
|
-
};
|
|
13124
|
-
}
|
|
13125
|
-
function deviceCustomAction(input, output, options) {
|
|
13126
|
-
return {
|
|
13127
|
-
input,
|
|
13128
|
-
output,
|
|
13129
|
-
kind: options?.kind ?? "query",
|
|
13130
|
-
auth: options?.auth ?? "protected",
|
|
13131
|
-
scope: {
|
|
13132
|
-
kind: "device",
|
|
13133
|
-
...options?.deviceTypes ? { deviceTypes: options.deviceTypes } : {}
|
|
13134
|
-
}
|
|
13135
|
-
};
|
|
13136
|
-
}
|
|
13137
|
-
/**
|
|
13138
13153
|
* `custom-model-registry` — collection cap exposing operator-registered
|
|
13139
13154
|
* custom detection models. Each provider (today: `addon-model-studio`)
|
|
13140
13155
|
* contributes a list of `CustomModelDescriptor`s; the hub auto-concatenates
|
|
@@ -18815,6 +18830,48 @@ var NcAlarmConfigSchema = object({
|
|
|
18815
18830
|
settings: NcAlarmSettingsSchema,
|
|
18816
18831
|
coverage: array(NcAlarmModeCoverageSchema)
|
|
18817
18832
|
});
|
|
18833
|
+
/**
|
|
18834
|
+
* ONE rule's demand on ONE camera's clip ring.
|
|
18835
|
+
*
|
|
18836
|
+
* A rule, not a camera: the broker takes the MAX over the asks that reach a
|
|
18837
|
+
* camera, so the answer stays a faithful description of the rules and the
|
|
18838
|
+
* bounding (the byte budget, the broker's own ceiling) stays where the cost
|
|
18839
|
+
* lives. A camera that appears in no ask is being told **nothing** — which is
|
|
18840
|
+
* a different thing from never having been told, and only the envelope
|
|
18841
|
+
* (`rulesLoaded`) can tell those apart.
|
|
18842
|
+
*/
|
|
18843
|
+
var NcClipRetentionAskSchema = object({
|
|
18844
|
+
/**
|
|
18845
|
+
* The camera this ask is about. **Absent = every camera**, which is what a
|
|
18846
|
+
* rule with no `conditions.devices` means: it can fire anywhere, so it is
|
|
18847
|
+
* asking everywhere.
|
|
18848
|
+
*/
|
|
18849
|
+
deviceId: number().int().nonnegative().optional(),
|
|
18850
|
+
/** Seconds of history the rule needs held for it. Never zero — a rule that
|
|
18851
|
+
* needs nothing produces no ask at all. */
|
|
18852
|
+
seconds: number().min(0).max(60),
|
|
18853
|
+
/** The profile the rule cuts from, when it named one. Absent = the cheapest
|
|
18854
|
+
* assigned, which is what the cut defaults to. */
|
|
18855
|
+
profile: CamProfileSchema.optional(),
|
|
18856
|
+
/** Who is asking — so a retention the operator did not expect names a rule. */
|
|
18857
|
+
ruleId: string(),
|
|
18858
|
+
ruleName: string(),
|
|
18859
|
+
/** Why this many seconds: the rule's own window, or the occupancy ceiling. */
|
|
18860
|
+
reason: _enum(["window", "occupancy"])
|
|
18861
|
+
});
|
|
18862
|
+
/**
|
|
18863
|
+
* The whole answer, with the one bit that keeps absence from reading as zero.
|
|
18864
|
+
*
|
|
18865
|
+
* `rulesLoaded` is false until the rule ledger has completed a load at least
|
|
18866
|
+
* once. A centre that is up but has not read its rules yet answers with an
|
|
18867
|
+
* empty ask list that means nothing at all — applying it would drop every
|
|
18868
|
+
* camera's ring to zero for the first events after a restart, which is the
|
|
18869
|
+
* footage this mechanism exists to keep.
|
|
18870
|
+
*/
|
|
18871
|
+
var NcClipRetentionPlanSchema = object({
|
|
18872
|
+
rulesLoaded: boolean(),
|
|
18873
|
+
asks: array(NcClipRetentionAskSchema).readonly()
|
|
18874
|
+
});
|
|
18818
18875
|
method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), method(object({ ruleId: string() }), object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }), method(object({ rule: NcRuleInputSchema }), object({ rule: NcRuleSchema }), {
|
|
18819
18876
|
kind: "mutation",
|
|
18820
18877
|
auth: "admin",
|
|
@@ -18835,7 +18892,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
18835
18892
|
}), object({ success: literal(true) }), {
|
|
18836
18893
|
kind: "mutation",
|
|
18837
18894
|
auth: "admin"
|
|
18838
|
-
}), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
|
|
18895
|
+
}), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
|
|
18839
18896
|
deviceId: number().int(),
|
|
18840
18897
|
muted: boolean()
|
|
18841
18898
|
}), object({ success: literal(true) }), {
|
|
@@ -20020,13 +20077,26 @@ var TrackAudioLabelSchema = object({
|
|
|
20020
20077
|
* - `audio` — an audio event on the camera itself that was anomalous for
|
|
20021
20078
|
* THAT camera, loud, and heard while nothing visual was happening (D62).
|
|
20022
20079
|
*
|
|
20080
|
+
* - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
|
|
20081
|
+
* that frame's pixel space. A real root detector and a SPATIAL one: its
|
|
20082
|
+
* boxes are the same kind of fact `pipeline`'s are, produced by a different
|
|
20083
|
+
* detector.
|
|
20084
|
+
*
|
|
20085
|
+
* `onboard` was missing here while `DetectionSourceSchema` already had it, so
|
|
20086
|
+
* a persisted onboard track failed `safeParse` on read and came back with NO
|
|
20087
|
+
* source at all — which `isSpatialTrack` then admitted by accident, through the
|
|
20088
|
+
* `undefined` arm rather than by anybody's decision, and which `excludeSources`
|
|
20089
|
+
* could not name.
|
|
20090
|
+
*
|
|
20023
20091
|
* The spatial subsystems (tracker association, occupancy count, re-id /
|
|
20024
20092
|
* embedding, resurrection) MUST skip every synthetic source. Test for that
|
|
20025
|
-
* with `isSpatialTrack`, which allow-lists
|
|
20026
|
-
* check silently readmits every source added after it was
|
|
20093
|
+
* with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
|
|
20094
|
+
* `!== 'sensor'` check silently readmits every source added after it was
|
|
20095
|
+
* written.
|
|
20027
20096
|
*/
|
|
20028
20097
|
var TrackSourceSchema = _enum([
|
|
20029
20098
|
"pipeline",
|
|
20099
|
+
"onboard",
|
|
20030
20100
|
"sensor",
|
|
20031
20101
|
"audio"
|
|
20032
20102
|
]);
|
|
@@ -21409,6 +21479,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
|
|
|
21409
21479
|
* solid is this track", cheaper than re-deriving it from a trajectory. */
|
|
21410
21480
|
framesMatched: number().int()
|
|
21411
21481
|
})).readonly() });
|
|
21482
|
+
/**
|
|
21483
|
+
* The long-term series a camera produces. A closed union so a typo cannot
|
|
21484
|
+
* invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
|
|
21485
|
+
* is an alias of this, not a second list.
|
|
21486
|
+
*/
|
|
21487
|
+
var AnalyticsLtsSeriesSchema = _enum([
|
|
21488
|
+
"motion",
|
|
21489
|
+
"audio-dbfs",
|
|
21490
|
+
"battery",
|
|
21491
|
+
"occupancy"
|
|
21492
|
+
]);
|
|
21493
|
+
/**
|
|
21494
|
+
* One closed 5-minute bucket of a long-term series.
|
|
21495
|
+
*
|
|
21496
|
+
* `samples` is how many readings the bucket folded — it is the ACTIVITY for a
|
|
21497
|
+
* series like `motion`, and the confidence for one like `occupancy`. A bucket
|
|
21498
|
+
* that exists at all was measured; a bucket that is absent was not, which is a
|
|
21499
|
+
* different claim from zero and the reason this is a sparse series.
|
|
21500
|
+
*/
|
|
21501
|
+
var LtsBucketSchema = object({
|
|
21502
|
+
scope: string(),
|
|
21503
|
+
bucketStart: number().int(),
|
|
21504
|
+
samples: number().int().nonnegative(),
|
|
21505
|
+
sum: number(),
|
|
21506
|
+
min: number(),
|
|
21507
|
+
max: number()
|
|
21508
|
+
});
|
|
21412
21509
|
DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
|
|
21413
21510
|
deviceId: number(),
|
|
21414
21511
|
trackId: string()
|
|
@@ -21455,6 +21552,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
21455
21552
|
kinds: array(string()).optional(),
|
|
21456
21553
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
21457
21554
|
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
21555
|
+
deviceId: number(),
|
|
21556
|
+
series: AnalyticsLtsSeriesSchema,
|
|
21557
|
+
/** Omit for EVERY scope of this series in the range. */
|
|
21558
|
+
scope: string().optional(),
|
|
21559
|
+
from: number(),
|
|
21560
|
+
to: number(),
|
|
21561
|
+
limit: number().int().positive().optional()
|
|
21562
|
+
}), array(LtsBucketSchema).readonly()), method(object({
|
|
21458
21563
|
deviceId: number(),
|
|
21459
21564
|
since: number(),
|
|
21460
21565
|
until: number(),
|
|
@@ -22073,13 +22178,39 @@ var MotionSourceEnum = _enum([
|
|
|
22073
22178
|
*/
|
|
22074
22179
|
var MotionSourcesSchema = array(MotionSourceEnum);
|
|
22075
22180
|
/**
|
|
22076
|
-
* Which root
|
|
22077
|
-
*
|
|
22078
|
-
*
|
|
22079
|
-
*
|
|
22080
|
-
*
|
|
22181
|
+
* Which root detector a camera runs — EXACTLY ONE.
|
|
22182
|
+
*
|
|
22183
|
+
* Deliberately the SAME vocabulary post-analysis already tags every detection
|
|
22184
|
+
* with (`DetectionSource`) rather than a second spelling of the same two
|
|
22185
|
+
* ideas: the value an operator picks here is the value that comes back on the
|
|
22186
|
+
* track, the overlay and the debug row.
|
|
22187
|
+
*
|
|
22188
|
+
* ## Why one, and why it is still an array
|
|
22189
|
+
*
|
|
22190
|
+
* Two roots on one camera is a capability nobody asked for and the operator has
|
|
22191
|
+
* since ruled out. It was never free: both planes feed the SAME per-device
|
|
22192
|
+
* stationary registry, both can promote the same parked object, and the two
|
|
22193
|
+
* detectors disagree about the same box by construction — which is the
|
|
22194
|
+
* disagreement D505 had to arbitrate. Removing the case removes the arbitration.
|
|
22195
|
+
*
|
|
22196
|
+
* The ARRAY shape survives because the stored settings and the attach payload
|
|
22197
|
+
* already speak it on every camera of every fleet, and a cap input that
|
|
22198
|
+
* suddenly refuses a stored value makes the camera un-attachable — a fail-closed
|
|
22199
|
+
* in the one direction that costs an operator their cameras. So the wire stays
|
|
22200
|
+
* tolerant and the TYPE is exact: a longer list is truncated to its first
|
|
22201
|
+
* element rather than refused, and `max(1)` is what every consumer can then
|
|
22202
|
+
* rely on.
|
|
22203
|
+
*
|
|
22204
|
+
* AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
|
|
22205
|
+
* a camera an operator left with no root detector — and `planDetectionSources`
|
|
22206
|
+
* has always said so: inventing a root for them is how a default acts without
|
|
22207
|
+
* anybody choosing it. Tightening this to `length(1)` broke that, and the
|
|
22208
|
+
* suite caught it.
|
|
22209
|
+
*
|
|
22210
|
+
* Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
|
|
22211
|
+
* `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
|
|
22081
22212
|
*/
|
|
22082
|
-
var DetectionSourcesSchema = array(DetectionSourceSchema);
|
|
22213
|
+
var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
|
|
22083
22214
|
/**
|
|
22084
22215
|
* Input shape for `pipeline-runner.reportMotion` cap method. Exported
|
|
22085
22216
|
* so cap-side consumers (the orchestrator forward, the runner addon's
|
|
@@ -22441,7 +22572,47 @@ var RunnerLocalMetricsSchema = object({
|
|
|
22441
22572
|
queueDepth: number(),
|
|
22442
22573
|
frameLazy: FrameLazyMetricsSchema.optional()
|
|
22443
22574
|
});
|
|
22575
|
+
/**
|
|
22576
|
+
* Pipeline Runner capability — runtime detection workhorse.
|
|
22577
|
+
*
|
|
22578
|
+
* One instance per node. Receives camera assignments from
|
|
22579
|
+
* `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
|
|
22580
|
+
* decoded frames, drains motion + detection queues, calls the local
|
|
22581
|
+
* `motion-detection` and `pipeline-executor` capabilities, and emits typed
|
|
22582
|
+
* `pipeline.inference-result` and `detection.motion-analysis` events on
|
|
22583
|
+
* the bus.
|
|
22584
|
+
*
|
|
22585
|
+
* Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
|
|
22586
|
+
* runner has zero knowledge of other agents, no global state, and never
|
|
22587
|
+
* makes assignment decisions itself.
|
|
22588
|
+
*/
|
|
22589
|
+
/**
|
|
22590
|
+
* Why a runner declined to look at a camera out of band. Named rather than a
|
|
22591
|
+
* bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
|
|
22592
|
+
* already mid-session and one already bursting are four different facts, and a
|
|
22593
|
+
* caller told only "no" cannot tell a protection from a bug.
|
|
22594
|
+
*/
|
|
22595
|
+
var OccupancyBurstRefusalSchema = _enum([
|
|
22596
|
+
"not-attached",
|
|
22597
|
+
"battery-asleep",
|
|
22598
|
+
"not-watching",
|
|
22599
|
+
"already-bursting"
|
|
22600
|
+
]);
|
|
22444
22601
|
method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
|
|
22602
|
+
deviceId: number(),
|
|
22603
|
+
/** How many frames to collect. The caller sizes this against what its
|
|
22604
|
+
* own confirmation needs; the runner clamps it to the field's range. */
|
|
22605
|
+
frames: number().int().positive().optional(),
|
|
22606
|
+
/** Why, for the log. A burst nobody can attribute is a cost nobody can
|
|
22607
|
+
* defend — the periodic one at least has a timer to point at. */
|
|
22608
|
+
reason: string()
|
|
22609
|
+
}), object({
|
|
22610
|
+
started: boolean(),
|
|
22611
|
+
refusedBecause: OccupancyBurstRefusalSchema.optional()
|
|
22612
|
+
}), {
|
|
22613
|
+
auth: "admin",
|
|
22614
|
+
kind: "mutation"
|
|
22615
|
+
}), method(object({
|
|
22445
22616
|
handle: FrameHandleSchema,
|
|
22446
22617
|
bbox: NativeCropBboxSchema,
|
|
22447
22618
|
maxWidth: number().int().positive().optional(),
|
|
@@ -34593,7 +34764,28 @@ var PerScopeBreakdownSchema = object({
|
|
|
34593
34764
|
/** Total tracked objects in this scope (frame / zone / unzoned). */
|
|
34594
34765
|
totalObjects: number().int().nonnegative(),
|
|
34595
34766
|
/** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
|
|
34596
|
-
byClass: record(string(), number().int().nonnegative())
|
|
34767
|
+
byClass: record(string(), number().int().nonnegative()),
|
|
34768
|
+
/**
|
|
34769
|
+
* Of `totalObjects`, how many are STANDING — objects the census holds,
|
|
34770
|
+
* present but not going anywhere (a parked car, a bin, a statue).
|
|
34771
|
+
*
|
|
34772
|
+
* The operator's question about a scene has two halves and they answer
|
|
34773
|
+
* different things: "what is parked here" is stable for hours and must not
|
|
34774
|
+
* flap, while "who is walking through" is the thing an alert is about. One
|
|
34775
|
+
* number could only serve one of them, and `totalObjects` served the first
|
|
34776
|
+
* badly — a passer-by moved it and every occupancy rule saw a change.
|
|
34777
|
+
*
|
|
34778
|
+
* `standing + transient === totalObjects`, always. The total keeps its old
|
|
34779
|
+
* meaning exactly, so no rule written against it changes behaviour.
|
|
34780
|
+
*
|
|
34781
|
+
* ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
|
|
34782
|
+
* this field cannot say, and a reader that folds absence in as 0 reports "no
|
|
34783
|
+
* parked objects here" about a camera whose lot is full.
|
|
34784
|
+
*/
|
|
34785
|
+
standingObjects: number().int().nonnegative().optional(),
|
|
34786
|
+
/** Of `totalObjects`, how many are passing through. See `standingObjects` —
|
|
34787
|
+
* same absence rule. */
|
|
34788
|
+
transientObjects: number().int().nonnegative().optional()
|
|
34597
34789
|
});
|
|
34598
34790
|
var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
|
|
34599
34791
|
zoneId: string(),
|
|
@@ -34716,6 +34908,23 @@ var HistoryPointSchema = object({
|
|
|
34716
34908
|
* triple covers the three spatial scopes (per-zone, frame-wide,
|
|
34717
34909
|
* outside-any-zone). Crossing them with `className?` gives the full
|
|
34718
34910
|
* combinatorial coverage the operator UI requested.
|
|
34911
|
+
*
|
|
34912
|
+
* ## ⚠️ The three history methods are DEPRECATED
|
|
34913
|
+
*
|
|
34914
|
+
* They are served from a 60-MINUTE in-memory ring. It dies with the process,
|
|
34915
|
+
* so after a restart they report nothing about a night that did happen, and any
|
|
34916
|
+
* window past an hour is silently clamped — a caller cannot tell a clamp from
|
|
34917
|
+
* an empty scene. The presets in the occupancy chart existed to hide that
|
|
34918
|
+
* horizon rather than to answer a question.
|
|
34919
|
+
*
|
|
34920
|
+
* Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
|
|
34921
|
+
* instead. Those rows are already written for every camera, ~60 bytes per
|
|
34922
|
+
* (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
|
|
34923
|
+
* Spell the scope with `occupancyScope(zoneId?, className?)` — the same
|
|
34924
|
+
* function the writer uses, so the two cannot drift.
|
|
34925
|
+
*
|
|
34926
|
+
* The ring stays until these three go, and it is the only thing left reading
|
|
34927
|
+
* it. Do not add a fourth caller.
|
|
34719
34928
|
*/
|
|
34720
34929
|
var zoneAnalyticsCapability = {
|
|
34721
34930
|
name: "zone-analytics",
|
|
@@ -39575,6 +39784,12 @@ Object.freeze({
|
|
|
39575
39784
|
addonId: null,
|
|
39576
39785
|
access: "view"
|
|
39577
39786
|
},
|
|
39787
|
+
"notificationRules.getClipRetentionAsks": {
|
|
39788
|
+
capName: "notification-rules",
|
|
39789
|
+
capScope: "system",
|
|
39790
|
+
addonId: null,
|
|
39791
|
+
access: "view"
|
|
39792
|
+
},
|
|
39578
39793
|
"notificationRules.getConditionCatalog": {
|
|
39579
39794
|
capName: "notification-rules",
|
|
39580
39795
|
capScope: "system",
|
|
@@ -40085,6 +40300,12 @@ Object.freeze({
|
|
|
40085
40300
|
addonId: null,
|
|
40086
40301
|
access: "create"
|
|
40087
40302
|
},
|
|
40303
|
+
"pipelineAnalytics.readLongTermSeries": {
|
|
40304
|
+
capName: "pipeline-analytics",
|
|
40305
|
+
capScope: "device",
|
|
40306
|
+
addonId: null,
|
|
40307
|
+
access: "view"
|
|
40308
|
+
},
|
|
40088
40309
|
"pipelineAnalytics.rebuildObjectEmbeddings": {
|
|
40089
40310
|
capName: "pipeline-analytics",
|
|
40090
40311
|
capScope: "device",
|
|
@@ -40757,6 +40978,12 @@ Object.freeze({
|
|
|
40757
40978
|
addonId: null,
|
|
40758
40979
|
access: "create"
|
|
40759
40980
|
},
|
|
40981
|
+
"pipelineRunner.requestOccupancyBurst": {
|
|
40982
|
+
capName: "pipeline-runner",
|
|
40983
|
+
capScope: "system",
|
|
40984
|
+
addonId: null,
|
|
40985
|
+
access: "create"
|
|
40986
|
+
},
|
|
40760
40987
|
"pipelineRunner.runDetailSubtree": {
|
|
40761
40988
|
capName: "pipeline-runner",
|
|
40762
40989
|
capScope: "system",
|
|
@@ -43901,6 +44128,11 @@ Object.freeze({
|
|
|
43901
44128
|
form: "single",
|
|
43902
44129
|
optional: false
|
|
43903
44130
|
}],
|
|
44131
|
+
"pipelineAnalytics.readLongTermSeries": [{
|
|
44132
|
+
name: "deviceId",
|
|
44133
|
+
form: "single",
|
|
44134
|
+
optional: false
|
|
44135
|
+
}],
|
|
43904
44136
|
"pipelineAnalytics.rebuildObjectEmbeddings": [{
|
|
43905
44137
|
name: "deviceId",
|
|
43906
44138
|
form: "single",
|
|
@@ -44091,6 +44323,11 @@ Object.freeze({
|
|
|
44091
44323
|
form: "single",
|
|
44092
44324
|
optional: false
|
|
44093
44325
|
}],
|
|
44326
|
+
"pipelineRunner.requestOccupancyBurst": [{
|
|
44327
|
+
name: "deviceId",
|
|
44328
|
+
form: "single",
|
|
44329
|
+
optional: false
|
|
44330
|
+
}],
|
|
44094
44331
|
"pipelineRunner.runDetailSubtree": [{
|
|
44095
44332
|
name: "deviceId",
|
|
44096
44333
|
form: "single",
|
|
@@ -145318,6 +145555,29 @@ function buildOsdSection(snapshot, values, opts) {
|
|
|
145318
145555
|
};
|
|
145319
145556
|
}
|
|
145320
145557
|
//#endregion
|
|
145558
|
+
//#region src/snapshot-wake-gate.ts
|
|
145559
|
+
/**
|
|
145560
|
+
* Decide whether a snapshot read may reach the camera's socket.
|
|
145561
|
+
*
|
|
145562
|
+
* Pure, and deliberately total: three inputs, no clock, no cooldown, no
|
|
145563
|
+
* fallible read (D49 — a gate that can be talked out of its answer by a
|
|
145564
|
+
* stale timestamp is not a gate).
|
|
145565
|
+
*/
|
|
145566
|
+
function decideSnapshotCameraAccess(input) {
|
|
145567
|
+
if (!input.isBattery || !input.sleeping) return {
|
|
145568
|
+
kind: "proceed",
|
|
145569
|
+
stampCooldown: false
|
|
145570
|
+
};
|
|
145571
|
+
if (!input.force) return {
|
|
145572
|
+
kind: "refuse",
|
|
145573
|
+
reason: "sleeping"
|
|
145574
|
+
};
|
|
145575
|
+
return {
|
|
145576
|
+
kind: "proceed",
|
|
145577
|
+
stampCooldown: true
|
|
145578
|
+
};
|
|
145579
|
+
}
|
|
145580
|
+
//#endregion
|
|
145321
145581
|
//#region src/raw-state.ts
|
|
145322
145582
|
/**
|
|
145323
145583
|
* Source tag for every raw-state blob this provider emits.
|
|
@@ -150112,9 +150372,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150112
150372
|
}
|
|
150113
150373
|
registerNativeCapabilities() {
|
|
150114
150374
|
this.ctx.registerNativeCap(snapshotCapability, {
|
|
150115
|
-
getSnapshot: async ({ deviceId }) => {
|
|
150375
|
+
getSnapshot: async ({ deviceId, force }) => {
|
|
150116
150376
|
if (deviceId !== this.id) throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
|
|
150117
|
-
return this.fetchSnapshotWithSingleFlight();
|
|
150377
|
+
return this.fetchSnapshotWithSingleFlight(force === true);
|
|
150118
150378
|
},
|
|
150119
150379
|
invalidateCache: async () => {}
|
|
150120
150380
|
});
|
|
@@ -150557,33 +150817,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150557
150817
|
status: this.state.battery
|
|
150558
150818
|
}));
|
|
150559
150819
|
}
|
|
150560
|
-
/**
|
|
150561
|
-
* Gate for a PROACTIVE camera read, checked BEFORE `ensureApi()`.
|
|
150562
|
-
*
|
|
150563
|
-
* The login itself is the wake. On a sleeping UDP/battery camera the
|
|
150564
|
-
* lib's discovery + handshake nudges the firmware awake (same reason
|
|
150565
|
-
* `refreshParentSettingsSnapshot` refuses to call `ensureApi` while
|
|
150566
|
-
* asleep), so gating only the explicit `wakeUp()` call is useless —
|
|
150567
|
-
* by the time we reach it the camera is already up. Production logs
|
|
150568
|
-
* showed exactly that: a background read produced a full
|
|
150569
|
-
* `Connecting to Reolink` → BCUDP discovery → `battery sleep state
|
|
150570
|
-
* committed` (awake) → whole refresh cascade, on a camera that had
|
|
150571
|
-
* been asleep for six minutes.
|
|
150572
|
-
*
|
|
150573
|
-
* Returns `false` when the caller must serve cache / bail out without
|
|
150574
|
-
* touching the socket. Stamps the cooldown when it does let a wake
|
|
150575
|
-
* through, so "at most one proactive wake per
|
|
150576
|
-
* `PROACTIVE_WAKE_COOLDOWN_MS`" holds across ALL proactive callers
|
|
150577
|
-
* rather than per-caller.
|
|
150578
|
-
*
|
|
150579
|
-
* Demand-driven paths never call this — see `canProactivelyWake`.
|
|
150580
|
-
*/
|
|
150581
|
-
allowProactiveCameraAccess(reason) {
|
|
150582
|
-
if (!this.isBattery || !this.sleeping) return true;
|
|
150583
|
-
if (!this.canProactivelyWake(reason)) return false;
|
|
150584
|
-
this.markWakeIssued();
|
|
150585
|
-
return true;
|
|
150586
|
-
}
|
|
150587
150820
|
updateBatteryCache(info) {
|
|
150588
150821
|
const mapped = this.mapBatteryInfo(info);
|
|
150589
150822
|
const now = Date.now();
|
|
@@ -150863,12 +151096,27 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150863
151096
|
}
|
|
150864
151097
|
await this.refreshCameraEmailConfig().catch(() => {});
|
|
150865
151098
|
}
|
|
150866
|
-
|
|
151099
|
+
/**
|
|
151100
|
+
* @param force - the caller's `snapshot.getSnapshot({ force })`. THE gate:
|
|
151101
|
+
* without it a sleeping battery camera is never touched, whatever the
|
|
151102
|
+
* cache holds. See `snapshot-wake-gate.ts` for why the flag — and not a
|
|
151103
|
+
* cooldown — is what decides.
|
|
151104
|
+
*/
|
|
151105
|
+
async fetchSnapshotWithSingleFlight(force) {
|
|
150867
151106
|
if (this.snapshotInFlight) return this.snapshotInFlight;
|
|
150868
|
-
|
|
150869
|
-
|
|
151107
|
+
const access = decideSnapshotCameraAccess({
|
|
151108
|
+
isBattery: this.isBattery,
|
|
151109
|
+
sleeping: this.sleeping,
|
|
151110
|
+
force
|
|
151111
|
+
});
|
|
151112
|
+
if (access.kind === "refuse") {
|
|
151113
|
+
this.ctx.logger.info("snapshot: refused — battery cam asleep and no operator force", {
|
|
151114
|
+
tags: { deviceId: this.id },
|
|
151115
|
+
meta: { reason: access.reason }
|
|
151116
|
+
});
|
|
150870
151117
|
return null;
|
|
150871
151118
|
}
|
|
151119
|
+
if (access.stampCooldown) this.markWakeIssued();
|
|
150872
151120
|
const promise = (async () => {
|
|
150873
151121
|
let api;
|
|
150874
151122
|
try {
|
|
@@ -152591,11 +152839,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152591
152839
|
};
|
|
152592
152840
|
this.ctx.registerNativeCap(nativeObjectDetectionCapability, provider);
|
|
152593
152841
|
this.registerStreamSignalsCap();
|
|
152594
|
-
if (this.runtimeState.getCapState(CAP_NAME) === void 0)
|
|
152595
|
-
|
|
152596
|
-
|
|
152597
|
-
|
|
152598
|
-
|
|
152842
|
+
if (this.runtimeState.getCapState(CAP_NAME) === void 0) {
|
|
152843
|
+
this.runtimeState.setCapState(CAP_NAME, {
|
|
152844
|
+
...buildEmptyState(),
|
|
152845
|
+
lastFetchedAt: Date.now()
|
|
152846
|
+
});
|
|
152847
|
+
this.ctx.logger.info("Reolink onboard AI: no restored state — cold-start default (OFF)", { tags: { deviceId: this.id } });
|
|
152848
|
+
} else this.runtimeState.patchCapState(CAP_NAME, { supportedClasses: buildSupportedClasses() });
|
|
152599
152849
|
this.ctx.logger.info("Reolink native-object-detection cap registered", { tags: { deviceId: this.id } });
|
|
152600
152850
|
}
|
|
152601
152851
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-reolink",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.132",
|
|
4
4
|
"description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"build": "vite build",
|
|
86
86
|
"dev": "vite build --watch",
|
|
87
87
|
"typecheck": "tsc --noEmit",
|
|
88
|
-
"publish": "npm publish --access public"
|
|
88
|
+
"publish:npm": "npm publish --access public"
|
|
89
89
|
},
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"@apocaliss92/nodelink-js": "^0.7.0"
|