@camstack/addon-provider-dreame 0.2.90 → 0.2.93
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 +287 -24
- package/dist/addon.mjs +287 -24
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -52672,7 +52672,7 @@ var DreameCameraStream = class extends TypedEmitter {
|
|
|
52672
52672
|
}
|
|
52673
52673
|
};
|
|
52674
52674
|
//#endregion
|
|
52675
|
-
//#region ../types/dist/event-category-
|
|
52675
|
+
//#region ../types/dist/event-category-DAXzJeTX.mjs
|
|
52676
52676
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
52677
52677
|
EventCategory["SystemBoot"] = "system.boot";
|
|
52678
52678
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -53264,7 +53264,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
53264
53264
|
* issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
|
|
53265
53265
|
*/
|
|
53266
53266
|
EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
|
|
53267
|
-
EventCategory["FrigateLiveEvent"] = "frigate.live-event";
|
|
53268
53267
|
EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
|
|
53269
53268
|
/**
|
|
53270
53269
|
* Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
|
|
@@ -65298,7 +65297,7 @@ DeviceType.Camera;
|
|
|
65298
65297
|
var SourceInfoSchema = object({
|
|
65299
65298
|
/** Live dispatch key — mutable when the source system allows rename. */
|
|
65300
65299
|
id: string(),
|
|
65301
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | '
|
|
65300
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
65302
65301
|
system: string(),
|
|
65303
65302
|
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
65304
65303
|
* Used to detect rename when `id` changes. */
|
|
@@ -65311,7 +65310,7 @@ var SourceInfoSchema = object({
|
|
|
65311
65310
|
/**
|
|
65312
65311
|
* Build the synthetic SourceInfo every existing provider falls back to
|
|
65313
65312
|
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
65314
|
-
* (Reolink / Hikvision / ONVIF /
|
|
65313
|
+
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
65315
65314
|
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
65316
65315
|
* id. Real upstream identity replaces this once a provider migrates and
|
|
65317
65316
|
* calls `updateSourceInfo()` with concrete values.
|
|
@@ -65990,7 +65989,7 @@ var deviceProviderCapability = {
|
|
|
65990
65989
|
* - `device-management.router.ts` (deleted in Phase 2)
|
|
65991
65990
|
* - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
|
|
65992
65991
|
*
|
|
65993
|
-
* All device provider addons (rtsp, onvif,
|
|
65992
|
+
* All device provider addons (rtsp, onvif, …) are hub-local: they may
|
|
65994
65993
|
* fork into separate processes but never run on remote cluster agents. Therefore:
|
|
65995
65994
|
* - No nodeId routing needed — this is a pure hub singleton.
|
|
65996
65995
|
* - The hub's DeviceRegistry is the single source of truth for all live devices.
|
|
@@ -70038,7 +70037,7 @@ var NcRuleInputSchema = object({
|
|
|
70038
70037
|
* The measured seven-person arrival on device 590 spans 110 s with every
|
|
70039
70038
|
* internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
|
|
70040
70039
|
* idle cutoff holds it as one and ends it when the arrival actually ends.
|
|
70041
|
-
* 30 is
|
|
70040
|
+
* 30 s is a widely shipped value for the same decision.
|
|
70042
70041
|
*
|
|
70043
70042
|
* ### What it replaces
|
|
70044
70043
|
*
|
|
@@ -72120,6 +72119,13 @@ var ZoneCrossingSchema = object({
|
|
|
72120
72119
|
/** Zone display name at crossing time (falls back to the id). */
|
|
72121
72120
|
zoneName: string().optional()
|
|
72122
72121
|
});
|
|
72122
|
+
/** One zone a box was stamped with, and how much of the BOX fell inside it. */
|
|
72123
|
+
var ZoneOverlapSchema = object({
|
|
72124
|
+
zoneId: string(),
|
|
72125
|
+
zoneName: string().optional(),
|
|
72126
|
+
/** Percentage of the BOX area inside the zone, 0–100. */
|
|
72127
|
+
overlapPct: number()
|
|
72128
|
+
});
|
|
72123
72129
|
var ObjectEventSchema = object({
|
|
72124
72130
|
...BaseEventFields,
|
|
72125
72131
|
kind: literal("object"),
|
|
@@ -72144,6 +72150,35 @@ var ObjectEventSchema = object({
|
|
|
72144
72150
|
bbox: BoundingBoxSchema.optional(),
|
|
72145
72151
|
/** Heavy JSON — omitted in slim projection. */
|
|
72146
72152
|
zones: array(string()).readonly().optional(),
|
|
72153
|
+
/**
|
|
72154
|
+
* How far inside each stamped zone the box actually was, as a percentage of
|
|
72155
|
+
* the BOX area.
|
|
72156
|
+
*
|
|
72157
|
+
* Membership alone cannot express "at least a third of it inside", which is
|
|
72158
|
+
* the bar an operator argues with — and the number already existed: the
|
|
72159
|
+
* engine computed it, the pipeline logged it, and then discarded it. It
|
|
72160
|
+
* matters most on the events that carry no track, where a rule has nothing
|
|
72161
|
+
* else to judge by.
|
|
72162
|
+
*/
|
|
72163
|
+
zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
|
|
72164
|
+
/**
|
|
72165
|
+
* Present when this event describes a detection the TRACKER refused, so it
|
|
72166
|
+
* has no track and never will.
|
|
72167
|
+
*
|
|
72168
|
+
* A zone-scoped immediate rule asks "is there an animal in the flowerbed
|
|
72169
|
+
* NOW?"; tracking answers a different question ("is this the same subject as
|
|
72170
|
+
* before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
|
|
72171
|
+
* dog at 0.7729, 100% inside a watched zone, was refused three times in six
|
|
72172
|
+
* seconds with three different candidate ids — seven analysed frames in the
|
|
72173
|
+
* window, the subject in a handful of them, metres apart — and produced no
|
|
72174
|
+
* event at all.
|
|
72175
|
+
*
|
|
72176
|
+
* It is NAMED rather than merely implied by a missing `trackId`: an event
|
|
72177
|
+
* with no track and no reason is indistinguishable from a track whose media
|
|
72178
|
+
* went missing, and a consumer that asks this one for a best shot or a
|
|
72179
|
+
* history is asking for something that never existed.
|
|
72180
|
+
*/
|
|
72181
|
+
spawnRefused: string().optional(),
|
|
72147
72182
|
/** Omitted in slim projection. */
|
|
72148
72183
|
state: TrackStateSchema.optional(),
|
|
72149
72184
|
/**
|
|
@@ -73217,6 +73252,60 @@ var NativeCropResultSchema = object({
|
|
|
73217
73252
|
*/
|
|
73218
73253
|
tier: _enum(["native", "ram-fullframe"]).optional()
|
|
73219
73254
|
});
|
|
73255
|
+
/**
|
|
73256
|
+
* What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
|
|
73257
|
+
* kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
|
|
73258
|
+
*
|
|
73259
|
+
* - `keyFrame` — the track's clean best FULL frame at native width (capped by
|
|
73260
|
+
* the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
|
|
73261
|
+
* from, and what the detail plane's full-frame rung cuts from.
|
|
73262
|
+
* - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
|
|
73263
|
+
* window, cut by the runner at uncapped native resolution.
|
|
73264
|
+
*
|
|
73265
|
+
* The runner never interprets `trackId`; it is an opaque key the hub owns.
|
|
73266
|
+
*/
|
|
73267
|
+
var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
|
|
73268
|
+
/**
|
|
73269
|
+
* Why a park did NOT happen. Every one is logged on the runner with
|
|
73270
|
+
* `tags: { deviceId }` and the handle's measured age, because a handle already
|
|
73271
|
+
* gone at park time IS the defect the 150–500 ms window exists to prevent —
|
|
73272
|
+
* never a reason to retry in a loop.
|
|
73273
|
+
*
|
|
73274
|
+
* - `handle-gone` — the native surface no longer resolves this handle (never
|
|
73275
|
+
* registered, past the lease bound, or the worker released it). `detail`
|
|
73276
|
+
* carries the runner's own miss reason.
|
|
73277
|
+
* - `encode-failed` — pixels resolved and the JPEG encode threw.
|
|
73278
|
+
*/
|
|
73279
|
+
var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
|
|
73280
|
+
/** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
|
|
73281
|
+
var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
|
|
73282
|
+
parked: literal(true),
|
|
73283
|
+
/** The node holding the parcel — what a later retrieval must be pinned to. */
|
|
73284
|
+
nodeId: string(),
|
|
73285
|
+
width: number().int().positive(),
|
|
73286
|
+
height: number().int().positive(),
|
|
73287
|
+
/** JPEG bytes the parcel costs on the node. */
|
|
73288
|
+
bytes: number().int().nonnegative(),
|
|
73289
|
+
/** Whether this park replaced a parcel already held for the key. */
|
|
73290
|
+
replaced: boolean()
|
|
73291
|
+
}), object({
|
|
73292
|
+
parked: literal(false),
|
|
73293
|
+
nodeId: string(),
|
|
73294
|
+
reason: ParkRefusalSchema,
|
|
73295
|
+
/** Age of the handle at the park, on the runner's clock; null if unknown. */
|
|
73296
|
+
handleAgeMs: number().nullable(),
|
|
73297
|
+
detail: string().optional()
|
|
73298
|
+
})]);
|
|
73299
|
+
/** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
|
|
73300
|
+
var ParkedTrackFrameSchema = object({
|
|
73301
|
+
jpeg: string(),
|
|
73302
|
+
width: number().int().positive(),
|
|
73303
|
+
height: number().int().positive(),
|
|
73304
|
+
/** The frame instant the parcel shows (the caller's clock, echoed back). */
|
|
73305
|
+
timestamp: number(),
|
|
73306
|
+
/** When the node stored it (the node's clock). */
|
|
73307
|
+
parkedAt: number()
|
|
73308
|
+
});
|
|
73220
73309
|
/** Parent detection context passed to `runDetailSubtree` — the crop's
|
|
73221
73310
|
* originating detection, in FRAME-space coordinates. Reuses
|
|
73222
73311
|
* `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
|
|
@@ -73606,7 +73695,7 @@ var RunnerCameraConfigSchema = object({
|
|
|
73606
73695
|
* - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
|
|
73607
73696
|
* analyzer providers) running on decoded frames in this runner.
|
|
73608
73697
|
* - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
|
|
73609
|
-
* analytics
|
|
73698
|
+
* analytics). The provider emits ProviderMotion
|
|
73610
73699
|
* events; the orchestrator forwards to `reportMotion`.
|
|
73611
73700
|
*/
|
|
73612
73701
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
@@ -73848,7 +73937,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
|
|
|
73848
73937
|
className: string(),
|
|
73849
73938
|
/** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
|
|
73850
73939
|
sourceDeviceId: number()
|
|
73851
|
-
}), RunStatelessStepResultSchema, { kind: "mutation" })
|
|
73940
|
+
}), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
|
|
73941
|
+
deviceId: number(),
|
|
73942
|
+
/** Opaque to the runner — the hub's track id. */
|
|
73943
|
+
trackId: string(),
|
|
73944
|
+
kind: ParkedFrameKindSchema,
|
|
73945
|
+
handle: FrameHandleSchema,
|
|
73946
|
+
/** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
|
|
73947
|
+
bbox: NativeCropBboxSchema,
|
|
73948
|
+
/** Width cap for a full-frame park (the `keyFrame` native cap). */
|
|
73949
|
+
maxWidth: number().int().positive().optional(),
|
|
73950
|
+
/** The frame instant (caller's clock) — orders parks for one key. */
|
|
73951
|
+
timestamp: number()
|
|
73952
|
+
}), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
|
|
73953
|
+
deviceId: number(),
|
|
73954
|
+
trackId: string(),
|
|
73955
|
+
kind: ParkedFrameKindSchema
|
|
73956
|
+
}), ParkedTrackFrameSchema.nullable()), method(object({
|
|
73957
|
+
deviceId: number(),
|
|
73958
|
+
trackId: string()
|
|
73959
|
+
}), object({ released: number().int().nonnegative() }), { kind: "mutation" });
|
|
73852
73960
|
var CameraPipelineConfigSchema = object({
|
|
73853
73961
|
engine: PipelineEngineChoiceSchema.optional(),
|
|
73854
73962
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -76918,8 +77026,8 @@ onStatusChanged: { data: object({
|
|
|
76918
77026
|
/**
|
|
76919
77027
|
* Runtime-state slice — every provider that registers this cap
|
|
76920
77028
|
* stores the same shape under `device.runtimeState[battery]`.
|
|
76921
|
-
* Cross-provider uniformity: a Reolink Argus, a
|
|
76922
|
-
*
|
|
77029
|
+
* Cross-provider uniformity: a Reolink Argus, a sensor proxy,
|
|
77030
|
+
* an ONVIF battery cam all read/write the same keys.
|
|
76923
77031
|
* Consumers (BatteryBadge, snapshot wrapper sleep gate) read once
|
|
76924
77032
|
* via `device.runtimeState.getCapState('battery')` regardless of
|
|
76925
77033
|
* the underlying driver.
|
|
@@ -77069,7 +77177,7 @@ var buttonCapability = {
|
|
|
77069
77177
|
*
|
|
77070
77178
|
* Implementations:
|
|
77071
77179
|
* - Each camera-providing addon registers this cap natively per
|
|
77072
|
-
* device (Reolink, Hikvision, RTSP,
|
|
77180
|
+
* device (Reolink, Hikvision, RTSP, ONVIF self).
|
|
77073
77181
|
* - Returns a snapshot from the persisted device config blob — no
|
|
77074
77182
|
* extra round-trip to the camera, no probing.
|
|
77075
77183
|
* - The cap is admin-only (`auth: 'admin'`) — credentials must not
|
|
@@ -79169,7 +79277,7 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
79169
79277
|
* Which integration-marker cap the addon declared, so the wizard can
|
|
79170
79278
|
* branch on CAP — never on addon name. `device-adoption` integrations
|
|
79171
79279
|
* (Home Assistant, …) route through the broker step (Approach A);
|
|
79172
|
-
* `device-provider` integrations (Reolink/
|
|
79280
|
+
* `device-provider` integrations (Reolink/ONVIF) keep the
|
|
79173
79281
|
* legacy config → discovery flow.
|
|
79174
79282
|
*/
|
|
79175
79283
|
kind: _enum(["device-adoption", "device-provider"]),
|
|
@@ -82741,13 +82849,31 @@ var RecordingManifestSchema = object({
|
|
|
82741
82849
|
*/
|
|
82742
82850
|
playbackEndpoints: array(string())
|
|
82743
82851
|
});
|
|
82744
|
-
/**
|
|
82745
|
-
*
|
|
82852
|
+
/**
|
|
82853
|
+
* Recording storage usage for one camera — what the ARCHIVE holds for it,
|
|
82854
|
+
* across every profile and every resolvable location on this node.
|
|
82855
|
+
*
|
|
82856
|
+
* ## Archive vs residency, which is the whole point of this shape (D418)
|
|
82857
|
+
*
|
|
82858
|
+
* `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
|
|
82859
|
+
* the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
|
|
82860
|
+
* this camera writes to, so the two can be added up and compared.
|
|
82861
|
+
*
|
|
82862
|
+
* They were not always. Until D418 this row came from the recorder's in-RAM
|
|
82863
|
+
* `RecordingIndex`, which since D287 holds only the live tail plus the hours
|
|
82864
|
+
* somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
|
|
82865
|
+
* 60.5 GB and "oldest: three hours ago" on the same page as the volume row
|
|
82866
|
+
* that said otherwise, and two people read it as retention eating recent
|
|
82867
|
+
* footage. Residency is still reported, under `residentBytes` /
|
|
82868
|
+
* `residentOldestMs`, which answer a different question and say so.
|
|
82869
|
+
*/
|
|
82746
82870
|
var RecordingDeviceUsageSchema = object({
|
|
82747
82871
|
deviceId: number(),
|
|
82872
|
+
/** Bytes the ARCHIVE holds for this camera on this node's resolvable
|
|
82873
|
+
* recordings locations. */
|
|
82748
82874
|
usedBytes: number(),
|
|
82749
82875
|
/**
|
|
82750
|
-
* Start of this camera's OLDEST
|
|
82876
|
+
* Start of this camera's OLDEST ARCHIVED segment, across every profile and
|
|
82751
82877
|
* location — the "Oldest footage" column in Recordings → Storage, and the
|
|
82752
82878
|
* only honest answer to "is retention actually holding?" per camera.
|
|
82753
82879
|
*
|
|
@@ -82757,7 +82883,24 @@ var RecordingDeviceUsageSchema = object({
|
|
|
82757
82883
|
* (types) and the addon ship on different trains, and the addon is usually
|
|
82758
82884
|
* the later of the two.
|
|
82759
82885
|
*/
|
|
82760
|
-
oldestMs: number().nullable().optional()
|
|
82886
|
+
oldestMs: number().nullable().optional(),
|
|
82887
|
+
/** Segments the archive holds for this camera — the "M" beside `usedBytes`.
|
|
82888
|
+
* OPTIONAL for the same train-skew reason as `oldestMs`. */
|
|
82889
|
+
segments: number().optional(),
|
|
82890
|
+
/**
|
|
82891
|
+
* RESIDENCY, not the archive: bytes the recorder process currently holds in
|
|
82892
|
+
* its RAM index — the live tail plus the hours a reader hydrated.
|
|
82893
|
+
*
|
|
82894
|
+
* It answers "what can be served without touching the disk", never "how much
|
|
82895
|
+
* footage this camera has". A UI that renders it must label it as such, and
|
|
82896
|
+
* must never total it as storage. OPTIONAL: a recorder predating D418 omits
|
|
82897
|
+
* it, and so does a node whose ledger could not answer.
|
|
82898
|
+
*/
|
|
82899
|
+
residentBytes: number().optional(),
|
|
82900
|
+
/** The residency counterpart of `oldestMs` — the oldest segment currently in
|
|
82901
|
+
* RAM, which on a healthy recorder is minutes ago and means nothing about
|
|
82902
|
+
* retention. `null` = nothing resident. */
|
|
82903
|
+
residentOldestMs: number().nullable().optional()
|
|
82761
82904
|
});
|
|
82762
82905
|
/** Recording storage usage + capacity for one storage location. */
|
|
82763
82906
|
var RecordingLocationUsageSchema = object({
|
|
@@ -82795,6 +82938,17 @@ var RecordingStorageUsageSchema = object({
|
|
|
82795
82938
|
totalUsedBytes: number(),
|
|
82796
82939
|
devices: array(RecordingDeviceUsageSchema),
|
|
82797
82940
|
/**
|
|
82941
|
+
* WHERE `devices[]` came from, so an empty table is never mistaken for empty
|
|
82942
|
+
* disks (D418).
|
|
82943
|
+
*
|
|
82944
|
+
* `archive` = the durable hour ledger answered. `unavailable` = this node
|
|
82945
|
+
* declared no ledger and the recorder REFUSED rather than answering the
|
|
82946
|
+
* question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
|
|
82947
|
+
* zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
|
|
82948
|
+
* and its rows are residency.
|
|
82949
|
+
*/
|
|
82950
|
+
devicesRead: _enum(["archive", "unavailable"]).optional(),
|
|
82951
|
+
/**
|
|
82798
82952
|
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
82799
82953
|
* of the payload without it: the hub serves this method through a BAKED
|
|
82800
82954
|
* router, so a hub whose framework train predates a change to this array
|
|
@@ -86674,7 +86828,7 @@ function toDeviceSummary(device, addonId) {
|
|
|
86674
86828
|
};
|
|
86675
86829
|
}
|
|
86676
86830
|
/**
|
|
86677
|
-
* Base class for device-provider addons (rtsp, onvif
|
|
86831
|
+
* Base class for device-provider addons (rtsp, onvif).
|
|
86678
86832
|
*
|
|
86679
86833
|
* Provides default implementations for the common device-provider cap
|
|
86680
86834
|
* methods (`start`, `stop`, `getStatus`, `getDevices`, `supportsDiscovery`,
|
|
@@ -91330,6 +91484,24 @@ Object.freeze({
|
|
|
91330
91484
|
addonId: null,
|
|
91331
91485
|
access: "view"
|
|
91332
91486
|
},
|
|
91487
|
+
"pipelineRunner.getParkedTrackFrame": {
|
|
91488
|
+
capName: "pipeline-runner",
|
|
91489
|
+
capScope: "system",
|
|
91490
|
+
addonId: null,
|
|
91491
|
+
access: "view"
|
|
91492
|
+
},
|
|
91493
|
+
"pipelineRunner.parkTrackFrame": {
|
|
91494
|
+
capName: "pipeline-runner",
|
|
91495
|
+
capScope: "system",
|
|
91496
|
+
addonId: null,
|
|
91497
|
+
access: "create"
|
|
91498
|
+
},
|
|
91499
|
+
"pipelineRunner.releaseParkedTrackFrames": {
|
|
91500
|
+
capName: "pipeline-runner",
|
|
91501
|
+
capScope: "system",
|
|
91502
|
+
addonId: null,
|
|
91503
|
+
access: "create"
|
|
91504
|
+
},
|
|
91333
91505
|
"pipelineRunner.reportMotion": {
|
|
91334
91506
|
capName: "pipeline-runner",
|
|
91335
91507
|
capScope: "system",
|
|
@@ -94607,6 +94779,21 @@ Object.freeze({
|
|
|
94607
94779
|
form: "single",
|
|
94608
94780
|
optional: false
|
|
94609
94781
|
}],
|
|
94782
|
+
"pipelineRunner.getParkedTrackFrame": [{
|
|
94783
|
+
name: "deviceId",
|
|
94784
|
+
form: "single",
|
|
94785
|
+
optional: false
|
|
94786
|
+
}],
|
|
94787
|
+
"pipelineRunner.parkTrackFrame": [{
|
|
94788
|
+
name: "deviceId",
|
|
94789
|
+
form: "single",
|
|
94790
|
+
optional: false
|
|
94791
|
+
}],
|
|
94792
|
+
"pipelineRunner.releaseParkedTrackFrames": [{
|
|
94793
|
+
name: "deviceId",
|
|
94794
|
+
form: "single",
|
|
94795
|
+
optional: false
|
|
94796
|
+
}],
|
|
94610
94797
|
"pipelineRunner.reportMotion": [{
|
|
94611
94798
|
name: "deviceId",
|
|
94612
94799
|
form: "single",
|
|
@@ -95298,7 +95485,7 @@ var PreviewTextField = string().max(200);
|
|
|
95298
95485
|
* Whether the notification's preview is a STILL or a short animation.
|
|
95299
95486
|
*
|
|
95300
95487
|
* The operator's ask, verbatim: *"inviato come gif o video (come per le altre
|
|
95301
|
-
* rule)"* —
|
|
95488
|
+
* rule)"* — the other rules already offer an animated preview, and a ten-hour
|
|
95302
95489
|
* night reads better as three seconds of motion than as one frame of it. Both
|
|
95303
95490
|
* modes get the SAME treatment (blurred frame, large centred title); `'gif'`
|
|
95304
95491
|
* simply applies it to a dozen frames sampled across the render and assembles
|
|
@@ -143943,9 +144130,9 @@ var { mulaw, alaw } = resolveAlawmulaw();
|
|
|
143943
144130
|
//#endregion
|
|
143944
144131
|
//#region src/devices/dreame-rfc4571-server.ts
|
|
143945
144132
|
/**
|
|
143946
|
-
* RFC 4571 (framed RTP over TCP) output for the robot camera — the shape
|
|
143947
|
-
*
|
|
143948
|
-
*
|
|
144133
|
+
* RFC 4571 (framed RTP over TCP) output for the robot camera — the shape
|
|
144134
|
+
* already proven on this robot: nodedreame's `DreameCameraStream` speaks the
|
|
144135
|
+
* LinkVisual relay
|
|
143949
144136
|
* and emits H.264 access units + AAC (ADTS); nodelink's `Rfc4571Muxer` frames
|
|
143950
144137
|
* them as RTP on a local TCP server the stream-broker dials as a
|
|
143951
144138
|
* `pull-rfc4571` source, exactly like a Reolink Baichuan camera.
|
|
@@ -143954,9 +144141,28 @@ var { mulaw, alaw } = resolveAlawmulaw();
|
|
|
143954
144141
|
* name, and WE own the TCP socket — when the broker's reader detaches we know
|
|
143955
144142
|
* the viewer is gone and release the monitor at once, instead of polling the
|
|
143956
144143
|
* broker for consumers or waiting on a duration cap.
|
|
144144
|
+
*
|
|
144145
|
+
* And we own the FEED: the relay dies in three ways nodedreame reports and one
|
|
144146
|
+
* it cannot — the socket errors (`error`; with no listener an EventEmitter
|
|
144147
|
+
* throws, and on 2026-09-09 a `read ETIMEDOUT` took the whole provider down),
|
|
144148
|
+
* the relay closes and the stream stops itself (`stopped`), or the socket goes
|
|
144149
|
+
* silent for minutes before the kernel gives up (nothing at all: 16 min of a
|
|
144150
|
+
* broker dialling a port that accepted and sent nothing). The server reports
|
|
144151
|
+
* every one of them ONCE through `onFeedLost`, and never its own close, so the
|
|
144152
|
+
* device can put the lazy sentinel back and let the next dial be a wake (D424).
|
|
143957
144153
|
*/
|
|
143958
144154
|
var VIDEO_PT = 96;
|
|
143959
144155
|
var AUDIO_PT = 97;
|
|
144156
|
+
/**
|
|
144157
|
+
* How long the feed may go without a video access unit before the relay is
|
|
144158
|
+
* declared dead. The broker gives a source 8 s (`RTP_ACTIVITY_TIMEOUT_MS`);
|
|
144159
|
+
* this sits just past it so a stall is judged by the side that can act on it.
|
|
144160
|
+
*/
|
|
144161
|
+
var FEED_STALL_MS = 1e4;
|
|
144162
|
+
var FEED_STALL_SWEEP_MS = 1e3;
|
|
144163
|
+
function describeLoss(loss) {
|
|
144164
|
+
return loss.error === null ? loss.reason : `${loss.reason}: ${loss.error.message}`;
|
|
144165
|
+
}
|
|
143960
144166
|
function deferred() {
|
|
143961
144167
|
let resolve = () => void 0;
|
|
143962
144168
|
return {
|
|
@@ -143989,11 +144195,33 @@ function consoleInto(logger) {
|
|
|
143989
144195
|
* server calls `onConsumerDisconnect` when the last reader detaches.
|
|
143990
144196
|
*/
|
|
143991
144197
|
async function startRfc4571Server(params) {
|
|
143992
|
-
const { stream, logger, onConsumerDisconnect, bindHost = "0.0.0.0", keyframeTimeoutMs = 2e4, audioGraceMs = 1500, idleGraceMs = 4e3 } = params;
|
|
144198
|
+
const { stream, logger, onConsumerDisconnect, onFeedLost, bindHost = "0.0.0.0", keyframeTimeoutMs = 2e4, audioGraceMs = 1500, idleGraceMs = 4e3, stallTimeoutMs = FEED_STALL_MS } = params;
|
|
143993
144199
|
const videoRef = { current: null };
|
|
143994
144200
|
const audioRef = { current: null };
|
|
143995
144201
|
const firstKeyframe = deferred();
|
|
143996
144202
|
const firstAudio = deferred();
|
|
144203
|
+
const firstLoss = deferred();
|
|
144204
|
+
let pendingLoss = null;
|
|
144205
|
+
let toreDown = false;
|
|
144206
|
+
let serving = false;
|
|
144207
|
+
let lastVideoAt = 0;
|
|
144208
|
+
const reportLoss = (loss) => {
|
|
144209
|
+
if (toreDown || pendingLoss !== null) return;
|
|
144210
|
+
pendingLoss = loss;
|
|
144211
|
+
firstLoss.resolve(loss);
|
|
144212
|
+
if (serving) onFeedLost(loss);
|
|
144213
|
+
};
|
|
144214
|
+
stream.on("error", (err) => reportLoss({
|
|
144215
|
+
reason: "error",
|
|
144216
|
+
error: err
|
|
144217
|
+
}));
|
|
144218
|
+
stream.on("stopped", () => reportLoss({
|
|
144219
|
+
reason: "stopped",
|
|
144220
|
+
error: null
|
|
144221
|
+
}));
|
|
144222
|
+
stream.on("videoAccessUnit", () => {
|
|
144223
|
+
lastVideoAt = Date.now();
|
|
144224
|
+
});
|
|
143997
144225
|
stream.on("videoAccessUnit", (e) => {
|
|
143998
144226
|
if (videoRef.current !== null) return;
|
|
143999
144227
|
const p = extractH264ParamSetsFromAccessUnit(e.data);
|
|
@@ -144019,8 +144247,11 @@ async function startRfc4571Server(params) {
|
|
|
144019
144247
|
}
|
|
144020
144248
|
});
|
|
144021
144249
|
await stream.start();
|
|
144250
|
+
const lostBeforeServing = firstLoss.promise.then((loss) => {
|
|
144251
|
+
throw new Error(`relay lost before the first keyframe (${describeLoss(loss)})`);
|
|
144252
|
+
});
|
|
144022
144253
|
try {
|
|
144023
|
-
await withTimeout(firstKeyframe.promise, keyframeTimeoutMs, "timed out waiting for the first keyframe");
|
|
144254
|
+
await Promise.race([withTimeout(firstKeyframe.promise, keyframeTimeoutMs, "timed out waiting for the first keyframe"), lostBeforeServing]);
|
|
144024
144255
|
} catch (err) {
|
|
144025
144256
|
await stream.stop().catch(() => void 0);
|
|
144026
144257
|
throw err;
|
|
@@ -144028,6 +144259,10 @@ async function startRfc4571Server(params) {
|
|
|
144028
144259
|
await Promise.race([firstAudio.promise, new Promise((resolve) => {
|
|
144029
144260
|
setTimeout(resolve, audioGraceMs).unref?.();
|
|
144030
144261
|
})]);
|
|
144262
|
+
if (pendingLoss !== null) {
|
|
144263
|
+
await stream.stop().catch(() => void 0);
|
|
144264
|
+
throw new Error(`relay lost before the server was up (${describeLoss(pendingLoss)})`);
|
|
144265
|
+
}
|
|
144031
144266
|
const paramSets = videoRef.current;
|
|
144032
144267
|
if (paramSets === null) throw new Error("no parameter sets after the first keyframe");
|
|
144033
144268
|
const audioFacts = audioRef.current;
|
|
@@ -144047,7 +144282,6 @@ async function startRfc4571Server(params) {
|
|
|
144047
144282
|
if (audioFacts !== null) stream.on("audioFrame", (adts) => muxer.sendAudioAdtsFrame(adts));
|
|
144048
144283
|
let liveClients = 0;
|
|
144049
144284
|
let idleTimer = null;
|
|
144050
|
-
let toreDown = false;
|
|
144051
144285
|
const server = node_net.default.createServer((socket) => {
|
|
144052
144286
|
liveClients += 1;
|
|
144053
144287
|
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
@@ -144074,6 +144308,15 @@ async function startRfc4571Server(params) {
|
|
|
144074
144308
|
});
|
|
144075
144309
|
const addr = server.address();
|
|
144076
144310
|
if (addr === null || typeof addr === "string") throw new Error("failed to bind the RFC4571 server");
|
|
144311
|
+
const stallSweep = setInterval(() => {
|
|
144312
|
+
if (toreDown || Date.now() - lastVideoAt < stallTimeoutMs) return;
|
|
144313
|
+
reportLoss({
|
|
144314
|
+
reason: "stalled",
|
|
144315
|
+
error: null
|
|
144316
|
+
});
|
|
144317
|
+
}, Math.max(5, Math.min(FEED_STALL_SWEEP_MS, Math.floor(stallTimeoutMs / 2))));
|
|
144318
|
+
stallSweep.unref?.();
|
|
144319
|
+
serving = true;
|
|
144077
144320
|
return {
|
|
144078
144321
|
host: bindHost,
|
|
144079
144322
|
port: addr.port,
|
|
@@ -144085,6 +144328,7 @@ async function startRfc4571Server(params) {
|
|
|
144085
144328
|
} : null,
|
|
144086
144329
|
close: async () => {
|
|
144087
144330
|
toreDown = true;
|
|
144331
|
+
clearInterval(stallSweep);
|
|
144088
144332
|
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
144089
144333
|
try {
|
|
144090
144334
|
muxer.close();
|
|
@@ -144364,6 +144608,9 @@ var DreameCameraDevice = class extends DreameChildDevice {
|
|
|
144364
144608
|
keyframeTimeoutMs: WAKE_TIMEOUT_MS,
|
|
144365
144609
|
onConsumerDisconnect: () => {
|
|
144366
144610
|
this.closeStream("idle");
|
|
144611
|
+
},
|
|
144612
|
+
onFeedLost: (loss) => {
|
|
144613
|
+
this.onFeedLost(loss);
|
|
144367
144614
|
}
|
|
144368
144615
|
});
|
|
144369
144616
|
this.live = {
|
|
@@ -144403,6 +144650,22 @@ var DreameCameraDevice = class extends DreameChildDevice {
|
|
|
144403
144650
|
});
|
|
144404
144651
|
return this.opening;
|
|
144405
144652
|
}
|
|
144653
|
+
/**
|
|
144654
|
+
* The feed died under an open monitor. The session is closed — the lazy
|
|
144655
|
+
* sentinel goes back on the broker — so the broker's next dial asks for a
|
|
144656
|
+
* materialize, which is the wake. Logged: this branch drops a live stream.
|
|
144657
|
+
*/
|
|
144658
|
+
onFeedLost(loss) {
|
|
144659
|
+
this.ctx.logger.warn("robot camera: relay feed lost — closing the monitor; the next dial re-opens it", {
|
|
144660
|
+
tags: { deviceId: this.id },
|
|
144661
|
+
meta: {
|
|
144662
|
+
reason: loss.reason,
|
|
144663
|
+
error: loss.error === null ? null : errMsg(loss.error),
|
|
144664
|
+
openMs: Date.now() - this.sessionOpenedAt
|
|
144665
|
+
}
|
|
144666
|
+
});
|
|
144667
|
+
this.closeStream("feed-lost");
|
|
144668
|
+
}
|
|
144406
144669
|
armMaxDuration() {
|
|
144407
144670
|
if (this.maxTimer !== null) clearTimeout(this.maxTimer);
|
|
144408
144671
|
this.maxTimer = setTimeout(() => {
|
package/dist/addon.mjs
CHANGED
|
@@ -52669,7 +52669,7 @@ var DreameCameraStream = class extends TypedEmitter {
|
|
|
52669
52669
|
}
|
|
52670
52670
|
};
|
|
52671
52671
|
//#endregion
|
|
52672
|
-
//#region ../types/dist/event-category-
|
|
52672
|
+
//#region ../types/dist/event-category-DAXzJeTX.mjs
|
|
52673
52673
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
52674
52674
|
EventCategory["SystemBoot"] = "system.boot";
|
|
52675
52675
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -53261,7 +53261,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
53261
53261
|
* issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
|
|
53262
53262
|
*/
|
|
53263
53263
|
EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
|
|
53264
|
-
EventCategory["FrigateLiveEvent"] = "frigate.live-event";
|
|
53265
53264
|
EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
|
|
53266
53265
|
/**
|
|
53267
53266
|
* Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
|
|
@@ -65295,7 +65294,7 @@ DeviceType.Camera;
|
|
|
65295
65294
|
var SourceInfoSchema = object({
|
|
65296
65295
|
/** Live dispatch key — mutable when the source system allows rename. */
|
|
65297
65296
|
id: string(),
|
|
65298
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | '
|
|
65297
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
65299
65298
|
system: string(),
|
|
65300
65299
|
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
65301
65300
|
* Used to detect rename when `id` changes. */
|
|
@@ -65308,7 +65307,7 @@ var SourceInfoSchema = object({
|
|
|
65308
65307
|
/**
|
|
65309
65308
|
* Build the synthetic SourceInfo every existing provider falls back to
|
|
65310
65309
|
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
65311
|
-
* (Reolink / Hikvision / ONVIF /
|
|
65310
|
+
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
65312
65311
|
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
65313
65312
|
* id. Real upstream identity replaces this once a provider migrates and
|
|
65314
65313
|
* calls `updateSourceInfo()` with concrete values.
|
|
@@ -65987,7 +65986,7 @@ var deviceProviderCapability = {
|
|
|
65987
65986
|
* - `device-management.router.ts` (deleted in Phase 2)
|
|
65988
65987
|
* - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
|
|
65989
65988
|
*
|
|
65990
|
-
* All device provider addons (rtsp, onvif,
|
|
65989
|
+
* All device provider addons (rtsp, onvif, …) are hub-local: they may
|
|
65991
65990
|
* fork into separate processes but never run on remote cluster agents. Therefore:
|
|
65992
65991
|
* - No nodeId routing needed — this is a pure hub singleton.
|
|
65993
65992
|
* - The hub's DeviceRegistry is the single source of truth for all live devices.
|
|
@@ -70035,7 +70034,7 @@ var NcRuleInputSchema = object({
|
|
|
70035
70034
|
* The measured seven-person arrival on device 590 spans 110 s with every
|
|
70036
70035
|
* internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
|
|
70037
70036
|
* idle cutoff holds it as one and ends it when the arrival actually ends.
|
|
70038
|
-
* 30 is
|
|
70037
|
+
* 30 s is a widely shipped value for the same decision.
|
|
70039
70038
|
*
|
|
70040
70039
|
* ### What it replaces
|
|
70041
70040
|
*
|
|
@@ -72117,6 +72116,13 @@ var ZoneCrossingSchema = object({
|
|
|
72117
72116
|
/** Zone display name at crossing time (falls back to the id). */
|
|
72118
72117
|
zoneName: string().optional()
|
|
72119
72118
|
});
|
|
72119
|
+
/** One zone a box was stamped with, and how much of the BOX fell inside it. */
|
|
72120
|
+
var ZoneOverlapSchema = object({
|
|
72121
|
+
zoneId: string(),
|
|
72122
|
+
zoneName: string().optional(),
|
|
72123
|
+
/** Percentage of the BOX area inside the zone, 0–100. */
|
|
72124
|
+
overlapPct: number()
|
|
72125
|
+
});
|
|
72120
72126
|
var ObjectEventSchema = object({
|
|
72121
72127
|
...BaseEventFields,
|
|
72122
72128
|
kind: literal("object"),
|
|
@@ -72141,6 +72147,35 @@ var ObjectEventSchema = object({
|
|
|
72141
72147
|
bbox: BoundingBoxSchema.optional(),
|
|
72142
72148
|
/** Heavy JSON — omitted in slim projection. */
|
|
72143
72149
|
zones: array(string()).readonly().optional(),
|
|
72150
|
+
/**
|
|
72151
|
+
* How far inside each stamped zone the box actually was, as a percentage of
|
|
72152
|
+
* the BOX area.
|
|
72153
|
+
*
|
|
72154
|
+
* Membership alone cannot express "at least a third of it inside", which is
|
|
72155
|
+
* the bar an operator argues with — and the number already existed: the
|
|
72156
|
+
* engine computed it, the pipeline logged it, and then discarded it. It
|
|
72157
|
+
* matters most on the events that carry no track, where a rule has nothing
|
|
72158
|
+
* else to judge by.
|
|
72159
|
+
*/
|
|
72160
|
+
zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
|
|
72161
|
+
/**
|
|
72162
|
+
* Present when this event describes a detection the TRACKER refused, so it
|
|
72163
|
+
* has no track and never will.
|
|
72164
|
+
*
|
|
72165
|
+
* A zone-scoped immediate rule asks "is there an animal in the flowerbed
|
|
72166
|
+
* NOW?"; tracking answers a different question ("is this the same subject as
|
|
72167
|
+
* before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
|
|
72168
|
+
* dog at 0.7729, 100% inside a watched zone, was refused three times in six
|
|
72169
|
+
* seconds with three different candidate ids — seven analysed frames in the
|
|
72170
|
+
* window, the subject in a handful of them, metres apart — and produced no
|
|
72171
|
+
* event at all.
|
|
72172
|
+
*
|
|
72173
|
+
* It is NAMED rather than merely implied by a missing `trackId`: an event
|
|
72174
|
+
* with no track and no reason is indistinguishable from a track whose media
|
|
72175
|
+
* went missing, and a consumer that asks this one for a best shot or a
|
|
72176
|
+
* history is asking for something that never existed.
|
|
72177
|
+
*/
|
|
72178
|
+
spawnRefused: string().optional(),
|
|
72144
72179
|
/** Omitted in slim projection. */
|
|
72145
72180
|
state: TrackStateSchema.optional(),
|
|
72146
72181
|
/**
|
|
@@ -73214,6 +73249,60 @@ var NativeCropResultSchema = object({
|
|
|
73214
73249
|
*/
|
|
73215
73250
|
tier: _enum(["native", "ram-fullframe"]).optional()
|
|
73216
73251
|
});
|
|
73252
|
+
/**
|
|
73253
|
+
* What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
|
|
73254
|
+
* kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
|
|
73255
|
+
*
|
|
73256
|
+
* - `keyFrame` — the track's clean best FULL frame at native width (capped by
|
|
73257
|
+
* the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
|
|
73258
|
+
* from, and what the detail plane's full-frame rung cuts from.
|
|
73259
|
+
* - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
|
|
73260
|
+
* window, cut by the runner at uncapped native resolution.
|
|
73261
|
+
*
|
|
73262
|
+
* The runner never interprets `trackId`; it is an opaque key the hub owns.
|
|
73263
|
+
*/
|
|
73264
|
+
var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
|
|
73265
|
+
/**
|
|
73266
|
+
* Why a park did NOT happen. Every one is logged on the runner with
|
|
73267
|
+
* `tags: { deviceId }` and the handle's measured age, because a handle already
|
|
73268
|
+
* gone at park time IS the defect the 150–500 ms window exists to prevent —
|
|
73269
|
+
* never a reason to retry in a loop.
|
|
73270
|
+
*
|
|
73271
|
+
* - `handle-gone` — the native surface no longer resolves this handle (never
|
|
73272
|
+
* registered, past the lease bound, or the worker released it). `detail`
|
|
73273
|
+
* carries the runner's own miss reason.
|
|
73274
|
+
* - `encode-failed` — pixels resolved and the JPEG encode threw.
|
|
73275
|
+
*/
|
|
73276
|
+
var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
|
|
73277
|
+
/** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
|
|
73278
|
+
var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
|
|
73279
|
+
parked: literal(true),
|
|
73280
|
+
/** The node holding the parcel — what a later retrieval must be pinned to. */
|
|
73281
|
+
nodeId: string(),
|
|
73282
|
+
width: number().int().positive(),
|
|
73283
|
+
height: number().int().positive(),
|
|
73284
|
+
/** JPEG bytes the parcel costs on the node. */
|
|
73285
|
+
bytes: number().int().nonnegative(),
|
|
73286
|
+
/** Whether this park replaced a parcel already held for the key. */
|
|
73287
|
+
replaced: boolean()
|
|
73288
|
+
}), object({
|
|
73289
|
+
parked: literal(false),
|
|
73290
|
+
nodeId: string(),
|
|
73291
|
+
reason: ParkRefusalSchema,
|
|
73292
|
+
/** Age of the handle at the park, on the runner's clock; null if unknown. */
|
|
73293
|
+
handleAgeMs: number().nullable(),
|
|
73294
|
+
detail: string().optional()
|
|
73295
|
+
})]);
|
|
73296
|
+
/** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
|
|
73297
|
+
var ParkedTrackFrameSchema = object({
|
|
73298
|
+
jpeg: string(),
|
|
73299
|
+
width: number().int().positive(),
|
|
73300
|
+
height: number().int().positive(),
|
|
73301
|
+
/** The frame instant the parcel shows (the caller's clock, echoed back). */
|
|
73302
|
+
timestamp: number(),
|
|
73303
|
+
/** When the node stored it (the node's clock). */
|
|
73304
|
+
parkedAt: number()
|
|
73305
|
+
});
|
|
73217
73306
|
/** Parent detection context passed to `runDetailSubtree` — the crop's
|
|
73218
73307
|
* originating detection, in FRAME-space coordinates. Reuses
|
|
73219
73308
|
* `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
|
|
@@ -73603,7 +73692,7 @@ var RunnerCameraConfigSchema = object({
|
|
|
73603
73692
|
* - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
|
|
73604
73693
|
* analyzer providers) running on decoded frames in this runner.
|
|
73605
73694
|
* - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
|
|
73606
|
-
* analytics
|
|
73695
|
+
* analytics). The provider emits ProviderMotion
|
|
73607
73696
|
* events; the orchestrator forwards to `reportMotion`.
|
|
73608
73697
|
*/
|
|
73609
73698
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
@@ -73845,7 +73934,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
|
|
|
73845
73934
|
className: string(),
|
|
73846
73935
|
/** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
|
|
73847
73936
|
sourceDeviceId: number()
|
|
73848
|
-
}), RunStatelessStepResultSchema, { kind: "mutation" })
|
|
73937
|
+
}), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
|
|
73938
|
+
deviceId: number(),
|
|
73939
|
+
/** Opaque to the runner — the hub's track id. */
|
|
73940
|
+
trackId: string(),
|
|
73941
|
+
kind: ParkedFrameKindSchema,
|
|
73942
|
+
handle: FrameHandleSchema,
|
|
73943
|
+
/** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
|
|
73944
|
+
bbox: NativeCropBboxSchema,
|
|
73945
|
+
/** Width cap for a full-frame park (the `keyFrame` native cap). */
|
|
73946
|
+
maxWidth: number().int().positive().optional(),
|
|
73947
|
+
/** The frame instant (caller's clock) — orders parks for one key. */
|
|
73948
|
+
timestamp: number()
|
|
73949
|
+
}), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
|
|
73950
|
+
deviceId: number(),
|
|
73951
|
+
trackId: string(),
|
|
73952
|
+
kind: ParkedFrameKindSchema
|
|
73953
|
+
}), ParkedTrackFrameSchema.nullable()), method(object({
|
|
73954
|
+
deviceId: number(),
|
|
73955
|
+
trackId: string()
|
|
73956
|
+
}), object({ released: number().int().nonnegative() }), { kind: "mutation" });
|
|
73849
73957
|
var CameraPipelineConfigSchema = object({
|
|
73850
73958
|
engine: PipelineEngineChoiceSchema.optional(),
|
|
73851
73959
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -76915,8 +77023,8 @@ onStatusChanged: { data: object({
|
|
|
76915
77023
|
/**
|
|
76916
77024
|
* Runtime-state slice — every provider that registers this cap
|
|
76917
77025
|
* stores the same shape under `device.runtimeState[battery]`.
|
|
76918
|
-
* Cross-provider uniformity: a Reolink Argus, a
|
|
76919
|
-
*
|
|
77026
|
+
* Cross-provider uniformity: a Reolink Argus, a sensor proxy,
|
|
77027
|
+
* an ONVIF battery cam all read/write the same keys.
|
|
76920
77028
|
* Consumers (BatteryBadge, snapshot wrapper sleep gate) read once
|
|
76921
77029
|
* via `device.runtimeState.getCapState('battery')` regardless of
|
|
76922
77030
|
* the underlying driver.
|
|
@@ -77066,7 +77174,7 @@ var buttonCapability = {
|
|
|
77066
77174
|
*
|
|
77067
77175
|
* Implementations:
|
|
77068
77176
|
* - Each camera-providing addon registers this cap natively per
|
|
77069
|
-
* device (Reolink, Hikvision, RTSP,
|
|
77177
|
+
* device (Reolink, Hikvision, RTSP, ONVIF self).
|
|
77070
77178
|
* - Returns a snapshot from the persisted device config blob — no
|
|
77071
77179
|
* extra round-trip to the camera, no probing.
|
|
77072
77180
|
* - The cap is admin-only (`auth: 'admin'`) — credentials must not
|
|
@@ -79166,7 +79274,7 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
79166
79274
|
* Which integration-marker cap the addon declared, so the wizard can
|
|
79167
79275
|
* branch on CAP — never on addon name. `device-adoption` integrations
|
|
79168
79276
|
* (Home Assistant, …) route through the broker step (Approach A);
|
|
79169
|
-
* `device-provider` integrations (Reolink/
|
|
79277
|
+
* `device-provider` integrations (Reolink/ONVIF) keep the
|
|
79170
79278
|
* legacy config → discovery flow.
|
|
79171
79279
|
*/
|
|
79172
79280
|
kind: _enum(["device-adoption", "device-provider"]),
|
|
@@ -82738,13 +82846,31 @@ var RecordingManifestSchema = object({
|
|
|
82738
82846
|
*/
|
|
82739
82847
|
playbackEndpoints: array(string())
|
|
82740
82848
|
});
|
|
82741
|
-
/**
|
|
82742
|
-
*
|
|
82849
|
+
/**
|
|
82850
|
+
* Recording storage usage for one camera — what the ARCHIVE holds for it,
|
|
82851
|
+
* across every profile and every resolvable location on this node.
|
|
82852
|
+
*
|
|
82853
|
+
* ## Archive vs residency, which is the whole point of this shape (D418)
|
|
82854
|
+
*
|
|
82855
|
+
* `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
|
|
82856
|
+
* the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
|
|
82857
|
+
* this camera writes to, so the two can be added up and compared.
|
|
82858
|
+
*
|
|
82859
|
+
* They were not always. Until D418 this row came from the recorder's in-RAM
|
|
82860
|
+
* `RecordingIndex`, which since D287 holds only the live tail plus the hours
|
|
82861
|
+
* somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
|
|
82862
|
+
* 60.5 GB and "oldest: three hours ago" on the same page as the volume row
|
|
82863
|
+
* that said otherwise, and two people read it as retention eating recent
|
|
82864
|
+
* footage. Residency is still reported, under `residentBytes` /
|
|
82865
|
+
* `residentOldestMs`, which answer a different question and say so.
|
|
82866
|
+
*/
|
|
82743
82867
|
var RecordingDeviceUsageSchema = object({
|
|
82744
82868
|
deviceId: number(),
|
|
82869
|
+
/** Bytes the ARCHIVE holds for this camera on this node's resolvable
|
|
82870
|
+
* recordings locations. */
|
|
82745
82871
|
usedBytes: number(),
|
|
82746
82872
|
/**
|
|
82747
|
-
* Start of this camera's OLDEST
|
|
82873
|
+
* Start of this camera's OLDEST ARCHIVED segment, across every profile and
|
|
82748
82874
|
* location — the "Oldest footage" column in Recordings → Storage, and the
|
|
82749
82875
|
* only honest answer to "is retention actually holding?" per camera.
|
|
82750
82876
|
*
|
|
@@ -82754,7 +82880,24 @@ var RecordingDeviceUsageSchema = object({
|
|
|
82754
82880
|
* (types) and the addon ship on different trains, and the addon is usually
|
|
82755
82881
|
* the later of the two.
|
|
82756
82882
|
*/
|
|
82757
|
-
oldestMs: number().nullable().optional()
|
|
82883
|
+
oldestMs: number().nullable().optional(),
|
|
82884
|
+
/** Segments the archive holds for this camera — the "M" beside `usedBytes`.
|
|
82885
|
+
* OPTIONAL for the same train-skew reason as `oldestMs`. */
|
|
82886
|
+
segments: number().optional(),
|
|
82887
|
+
/**
|
|
82888
|
+
* RESIDENCY, not the archive: bytes the recorder process currently holds in
|
|
82889
|
+
* its RAM index — the live tail plus the hours a reader hydrated.
|
|
82890
|
+
*
|
|
82891
|
+
* It answers "what can be served without touching the disk", never "how much
|
|
82892
|
+
* footage this camera has". A UI that renders it must label it as such, and
|
|
82893
|
+
* must never total it as storage. OPTIONAL: a recorder predating D418 omits
|
|
82894
|
+
* it, and so does a node whose ledger could not answer.
|
|
82895
|
+
*/
|
|
82896
|
+
residentBytes: number().optional(),
|
|
82897
|
+
/** The residency counterpart of `oldestMs` — the oldest segment currently in
|
|
82898
|
+
* RAM, which on a healthy recorder is minutes ago and means nothing about
|
|
82899
|
+
* retention. `null` = nothing resident. */
|
|
82900
|
+
residentOldestMs: number().nullable().optional()
|
|
82758
82901
|
});
|
|
82759
82902
|
/** Recording storage usage + capacity for one storage location. */
|
|
82760
82903
|
var RecordingLocationUsageSchema = object({
|
|
@@ -82792,6 +82935,17 @@ var RecordingStorageUsageSchema = object({
|
|
|
82792
82935
|
totalUsedBytes: number(),
|
|
82793
82936
|
devices: array(RecordingDeviceUsageSchema),
|
|
82794
82937
|
/**
|
|
82938
|
+
* WHERE `devices[]` came from, so an empty table is never mistaken for empty
|
|
82939
|
+
* disks (D418).
|
|
82940
|
+
*
|
|
82941
|
+
* `archive` = the durable hour ledger answered. `unavailable` = this node
|
|
82942
|
+
* declared no ledger and the recorder REFUSED rather than answering the
|
|
82943
|
+
* question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
|
|
82944
|
+
* zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
|
|
82945
|
+
* and its rows are residency.
|
|
82946
|
+
*/
|
|
82947
|
+
devicesRead: _enum(["archive", "unavailable"]).optional(),
|
|
82948
|
+
/**
|
|
82795
82949
|
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
82796
82950
|
* of the payload without it: the hub serves this method through a BAKED
|
|
82797
82951
|
* router, so a hub whose framework train predates a change to this array
|
|
@@ -86671,7 +86825,7 @@ function toDeviceSummary(device, addonId) {
|
|
|
86671
86825
|
};
|
|
86672
86826
|
}
|
|
86673
86827
|
/**
|
|
86674
|
-
* Base class for device-provider addons (rtsp, onvif
|
|
86828
|
+
* Base class for device-provider addons (rtsp, onvif).
|
|
86675
86829
|
*
|
|
86676
86830
|
* Provides default implementations for the common device-provider cap
|
|
86677
86831
|
* methods (`start`, `stop`, `getStatus`, `getDevices`, `supportsDiscovery`,
|
|
@@ -91327,6 +91481,24 @@ Object.freeze({
|
|
|
91327
91481
|
addonId: null,
|
|
91328
91482
|
access: "view"
|
|
91329
91483
|
},
|
|
91484
|
+
"pipelineRunner.getParkedTrackFrame": {
|
|
91485
|
+
capName: "pipeline-runner",
|
|
91486
|
+
capScope: "system",
|
|
91487
|
+
addonId: null,
|
|
91488
|
+
access: "view"
|
|
91489
|
+
},
|
|
91490
|
+
"pipelineRunner.parkTrackFrame": {
|
|
91491
|
+
capName: "pipeline-runner",
|
|
91492
|
+
capScope: "system",
|
|
91493
|
+
addonId: null,
|
|
91494
|
+
access: "create"
|
|
91495
|
+
},
|
|
91496
|
+
"pipelineRunner.releaseParkedTrackFrames": {
|
|
91497
|
+
capName: "pipeline-runner",
|
|
91498
|
+
capScope: "system",
|
|
91499
|
+
addonId: null,
|
|
91500
|
+
access: "create"
|
|
91501
|
+
},
|
|
91330
91502
|
"pipelineRunner.reportMotion": {
|
|
91331
91503
|
capName: "pipeline-runner",
|
|
91332
91504
|
capScope: "system",
|
|
@@ -94604,6 +94776,21 @@ Object.freeze({
|
|
|
94604
94776
|
form: "single",
|
|
94605
94777
|
optional: false
|
|
94606
94778
|
}],
|
|
94779
|
+
"pipelineRunner.getParkedTrackFrame": [{
|
|
94780
|
+
name: "deviceId",
|
|
94781
|
+
form: "single",
|
|
94782
|
+
optional: false
|
|
94783
|
+
}],
|
|
94784
|
+
"pipelineRunner.parkTrackFrame": [{
|
|
94785
|
+
name: "deviceId",
|
|
94786
|
+
form: "single",
|
|
94787
|
+
optional: false
|
|
94788
|
+
}],
|
|
94789
|
+
"pipelineRunner.releaseParkedTrackFrames": [{
|
|
94790
|
+
name: "deviceId",
|
|
94791
|
+
form: "single",
|
|
94792
|
+
optional: false
|
|
94793
|
+
}],
|
|
94607
94794
|
"pipelineRunner.reportMotion": [{
|
|
94608
94795
|
name: "deviceId",
|
|
94609
94796
|
form: "single",
|
|
@@ -95295,7 +95482,7 @@ var PreviewTextField = string().max(200);
|
|
|
95295
95482
|
* Whether the notification's preview is a STILL or a short animation.
|
|
95296
95483
|
*
|
|
95297
95484
|
* The operator's ask, verbatim: *"inviato come gif o video (come per le altre
|
|
95298
|
-
* rule)"* —
|
|
95485
|
+
* rule)"* — the other rules already offer an animated preview, and a ten-hour
|
|
95299
95486
|
* night reads better as three seconds of motion than as one frame of it. Both
|
|
95300
95487
|
* modes get the SAME treatment (blurred frame, large centred title); `'gif'`
|
|
95301
95488
|
* simply applies it to a dozen frames sampled across the render and assembles
|
|
@@ -143940,9 +144127,9 @@ var { mulaw, alaw } = resolveAlawmulaw();
|
|
|
143940
144127
|
//#endregion
|
|
143941
144128
|
//#region src/devices/dreame-rfc4571-server.ts
|
|
143942
144129
|
/**
|
|
143943
|
-
* RFC 4571 (framed RTP over TCP) output for the robot camera — the shape
|
|
143944
|
-
*
|
|
143945
|
-
*
|
|
144130
|
+
* RFC 4571 (framed RTP over TCP) output for the robot camera — the shape
|
|
144131
|
+
* already proven on this robot: nodedreame's `DreameCameraStream` speaks the
|
|
144132
|
+
* LinkVisual relay
|
|
143946
144133
|
* and emits H.264 access units + AAC (ADTS); nodelink's `Rfc4571Muxer` frames
|
|
143947
144134
|
* them as RTP on a local TCP server the stream-broker dials as a
|
|
143948
144135
|
* `pull-rfc4571` source, exactly like a Reolink Baichuan camera.
|
|
@@ -143951,9 +144138,28 @@ var { mulaw, alaw } = resolveAlawmulaw();
|
|
|
143951
144138
|
* name, and WE own the TCP socket — when the broker's reader detaches we know
|
|
143952
144139
|
* the viewer is gone and release the monitor at once, instead of polling the
|
|
143953
144140
|
* broker for consumers or waiting on a duration cap.
|
|
144141
|
+
*
|
|
144142
|
+
* And we own the FEED: the relay dies in three ways nodedreame reports and one
|
|
144143
|
+
* it cannot — the socket errors (`error`; with no listener an EventEmitter
|
|
144144
|
+
* throws, and on 2026-09-09 a `read ETIMEDOUT` took the whole provider down),
|
|
144145
|
+
* the relay closes and the stream stops itself (`stopped`), or the socket goes
|
|
144146
|
+
* silent for minutes before the kernel gives up (nothing at all: 16 min of a
|
|
144147
|
+
* broker dialling a port that accepted and sent nothing). The server reports
|
|
144148
|
+
* every one of them ONCE through `onFeedLost`, and never its own close, so the
|
|
144149
|
+
* device can put the lazy sentinel back and let the next dial be a wake (D424).
|
|
143954
144150
|
*/
|
|
143955
144151
|
var VIDEO_PT = 96;
|
|
143956
144152
|
var AUDIO_PT = 97;
|
|
144153
|
+
/**
|
|
144154
|
+
* How long the feed may go without a video access unit before the relay is
|
|
144155
|
+
* declared dead. The broker gives a source 8 s (`RTP_ACTIVITY_TIMEOUT_MS`);
|
|
144156
|
+
* this sits just past it so a stall is judged by the side that can act on it.
|
|
144157
|
+
*/
|
|
144158
|
+
var FEED_STALL_MS = 1e4;
|
|
144159
|
+
var FEED_STALL_SWEEP_MS = 1e3;
|
|
144160
|
+
function describeLoss(loss) {
|
|
144161
|
+
return loss.error === null ? loss.reason : `${loss.reason}: ${loss.error.message}`;
|
|
144162
|
+
}
|
|
143957
144163
|
function deferred() {
|
|
143958
144164
|
let resolve = () => void 0;
|
|
143959
144165
|
return {
|
|
@@ -143986,11 +144192,33 @@ function consoleInto(logger) {
|
|
|
143986
144192
|
* server calls `onConsumerDisconnect` when the last reader detaches.
|
|
143987
144193
|
*/
|
|
143988
144194
|
async function startRfc4571Server(params) {
|
|
143989
|
-
const { stream, logger, onConsumerDisconnect, bindHost = "0.0.0.0", keyframeTimeoutMs = 2e4, audioGraceMs = 1500, idleGraceMs = 4e3 } = params;
|
|
144195
|
+
const { stream, logger, onConsumerDisconnect, onFeedLost, bindHost = "0.0.0.0", keyframeTimeoutMs = 2e4, audioGraceMs = 1500, idleGraceMs = 4e3, stallTimeoutMs = FEED_STALL_MS } = params;
|
|
143990
144196
|
const videoRef = { current: null };
|
|
143991
144197
|
const audioRef = { current: null };
|
|
143992
144198
|
const firstKeyframe = deferred();
|
|
143993
144199
|
const firstAudio = deferred();
|
|
144200
|
+
const firstLoss = deferred();
|
|
144201
|
+
let pendingLoss = null;
|
|
144202
|
+
let toreDown = false;
|
|
144203
|
+
let serving = false;
|
|
144204
|
+
let lastVideoAt = 0;
|
|
144205
|
+
const reportLoss = (loss) => {
|
|
144206
|
+
if (toreDown || pendingLoss !== null) return;
|
|
144207
|
+
pendingLoss = loss;
|
|
144208
|
+
firstLoss.resolve(loss);
|
|
144209
|
+
if (serving) onFeedLost(loss);
|
|
144210
|
+
};
|
|
144211
|
+
stream.on("error", (err) => reportLoss({
|
|
144212
|
+
reason: "error",
|
|
144213
|
+
error: err
|
|
144214
|
+
}));
|
|
144215
|
+
stream.on("stopped", () => reportLoss({
|
|
144216
|
+
reason: "stopped",
|
|
144217
|
+
error: null
|
|
144218
|
+
}));
|
|
144219
|
+
stream.on("videoAccessUnit", () => {
|
|
144220
|
+
lastVideoAt = Date.now();
|
|
144221
|
+
});
|
|
143994
144222
|
stream.on("videoAccessUnit", (e) => {
|
|
143995
144223
|
if (videoRef.current !== null) return;
|
|
143996
144224
|
const p = extractH264ParamSetsFromAccessUnit(e.data);
|
|
@@ -144016,8 +144244,11 @@ async function startRfc4571Server(params) {
|
|
|
144016
144244
|
}
|
|
144017
144245
|
});
|
|
144018
144246
|
await stream.start();
|
|
144247
|
+
const lostBeforeServing = firstLoss.promise.then((loss) => {
|
|
144248
|
+
throw new Error(`relay lost before the first keyframe (${describeLoss(loss)})`);
|
|
144249
|
+
});
|
|
144019
144250
|
try {
|
|
144020
|
-
await withTimeout(firstKeyframe.promise, keyframeTimeoutMs, "timed out waiting for the first keyframe");
|
|
144251
|
+
await Promise.race([withTimeout(firstKeyframe.promise, keyframeTimeoutMs, "timed out waiting for the first keyframe"), lostBeforeServing]);
|
|
144021
144252
|
} catch (err) {
|
|
144022
144253
|
await stream.stop().catch(() => void 0);
|
|
144023
144254
|
throw err;
|
|
@@ -144025,6 +144256,10 @@ async function startRfc4571Server(params) {
|
|
|
144025
144256
|
await Promise.race([firstAudio.promise, new Promise((resolve) => {
|
|
144026
144257
|
setTimeout(resolve, audioGraceMs).unref?.();
|
|
144027
144258
|
})]);
|
|
144259
|
+
if (pendingLoss !== null) {
|
|
144260
|
+
await stream.stop().catch(() => void 0);
|
|
144261
|
+
throw new Error(`relay lost before the server was up (${describeLoss(pendingLoss)})`);
|
|
144262
|
+
}
|
|
144028
144263
|
const paramSets = videoRef.current;
|
|
144029
144264
|
if (paramSets === null) throw new Error("no parameter sets after the first keyframe");
|
|
144030
144265
|
const audioFacts = audioRef.current;
|
|
@@ -144044,7 +144279,6 @@ async function startRfc4571Server(params) {
|
|
|
144044
144279
|
if (audioFacts !== null) stream.on("audioFrame", (adts) => muxer.sendAudioAdtsFrame(adts));
|
|
144045
144280
|
let liveClients = 0;
|
|
144046
144281
|
let idleTimer = null;
|
|
144047
|
-
let toreDown = false;
|
|
144048
144282
|
const server = net.createServer((socket) => {
|
|
144049
144283
|
liveClients += 1;
|
|
144050
144284
|
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
@@ -144071,6 +144305,15 @@ async function startRfc4571Server(params) {
|
|
|
144071
144305
|
});
|
|
144072
144306
|
const addr = server.address();
|
|
144073
144307
|
if (addr === null || typeof addr === "string") throw new Error("failed to bind the RFC4571 server");
|
|
144308
|
+
const stallSweep = setInterval(() => {
|
|
144309
|
+
if (toreDown || Date.now() - lastVideoAt < stallTimeoutMs) return;
|
|
144310
|
+
reportLoss({
|
|
144311
|
+
reason: "stalled",
|
|
144312
|
+
error: null
|
|
144313
|
+
});
|
|
144314
|
+
}, Math.max(5, Math.min(FEED_STALL_SWEEP_MS, Math.floor(stallTimeoutMs / 2))));
|
|
144315
|
+
stallSweep.unref?.();
|
|
144316
|
+
serving = true;
|
|
144074
144317
|
return {
|
|
144075
144318
|
host: bindHost,
|
|
144076
144319
|
port: addr.port,
|
|
@@ -144082,6 +144325,7 @@ async function startRfc4571Server(params) {
|
|
|
144082
144325
|
} : null,
|
|
144083
144326
|
close: async () => {
|
|
144084
144327
|
toreDown = true;
|
|
144328
|
+
clearInterval(stallSweep);
|
|
144085
144329
|
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
144086
144330
|
try {
|
|
144087
144331
|
muxer.close();
|
|
@@ -144361,6 +144605,9 @@ var DreameCameraDevice = class extends DreameChildDevice {
|
|
|
144361
144605
|
keyframeTimeoutMs: WAKE_TIMEOUT_MS,
|
|
144362
144606
|
onConsumerDisconnect: () => {
|
|
144363
144607
|
this.closeStream("idle");
|
|
144608
|
+
},
|
|
144609
|
+
onFeedLost: (loss) => {
|
|
144610
|
+
this.onFeedLost(loss);
|
|
144364
144611
|
}
|
|
144365
144612
|
});
|
|
144366
144613
|
this.live = {
|
|
@@ -144400,6 +144647,22 @@ var DreameCameraDevice = class extends DreameChildDevice {
|
|
|
144400
144647
|
});
|
|
144401
144648
|
return this.opening;
|
|
144402
144649
|
}
|
|
144650
|
+
/**
|
|
144651
|
+
* The feed died under an open monitor. The session is closed — the lazy
|
|
144652
|
+
* sentinel goes back on the broker — so the broker's next dial asks for a
|
|
144653
|
+
* materialize, which is the wake. Logged: this branch drops a live stream.
|
|
144654
|
+
*/
|
|
144655
|
+
onFeedLost(loss) {
|
|
144656
|
+
this.ctx.logger.warn("robot camera: relay feed lost — closing the monitor; the next dial re-opens it", {
|
|
144657
|
+
tags: { deviceId: this.id },
|
|
144658
|
+
meta: {
|
|
144659
|
+
reason: loss.reason,
|
|
144660
|
+
error: loss.error === null ? null : errMsg(loss.error),
|
|
144661
|
+
openMs: Date.now() - this.sessionOpenedAt
|
|
144662
|
+
}
|
|
144663
|
+
});
|
|
144664
|
+
this.closeStream("feed-lost");
|
|
144665
|
+
}
|
|
144403
144666
|
armMaxDuration() {
|
|
144404
144667
|
if (this.maxTimer !== null) clearTimeout(this.maxTimer);
|
|
144405
144668
|
this.maxTimer = setTimeout(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-dreame",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.93",
|
|
4
4
|
"description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|