@camstack/addon-provider-homeassistant 1.1.27 → 1.1.29
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 +262 -24
- package/dist/addon.mjs +262 -24
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let node_crypto = require("node:crypto");
|
|
3
3
|
let node_zlib = require("node:zlib");
|
|
4
|
-
//#region ../types/dist/event-category-
|
|
4
|
+
//#region ../types/dist/event-category-H4AVePnn.mjs
|
|
5
5
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
6
6
|
EventCategory["SystemBoot"] = "system.boot";
|
|
7
7
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -151,6 +151,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
151
151
|
EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
|
|
152
152
|
EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
|
|
153
153
|
EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
|
|
154
|
+
/** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
|
|
155
|
+
* thumb is a scrub gap the recorder's keyframe backfill covers. */
|
|
156
|
+
EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
|
|
154
157
|
EventCategory["DetectionEvent"] = "detection.event";
|
|
155
158
|
EventCategory["SessionTrackNew"] = "session.track.new";
|
|
156
159
|
EventCategory["SessionTrackExpired"] = "session.track.expired";
|
|
@@ -7353,6 +7356,24 @@ var RecordingRetentionSchema = object({
|
|
|
7353
7356
|
maxSizeGb: number().min(0).optional()
|
|
7354
7357
|
});
|
|
7355
7358
|
/**
|
|
7359
|
+
* Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
|
|
7360
|
+
* sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
|
|
7361
|
+
* previews at. Five graduated steps; absent on a config = `standard` (the
|
|
7362
|
+
* shipped default, matching `sheet-geometry`/`sheet-composer`).
|
|
7363
|
+
*
|
|
7364
|
+
* Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
|
|
7365
|
+
* Each window's index sidecar carries its own tile dims, so a camera whose
|
|
7366
|
+
* preset changed over time renders every historical window at the dims it was
|
|
7367
|
+
* written with.
|
|
7368
|
+
*/
|
|
7369
|
+
var ScrubThumbnailPresetSchema = _enum([
|
|
7370
|
+
"minimal",
|
|
7371
|
+
"low",
|
|
7372
|
+
"standard",
|
|
7373
|
+
"high",
|
|
7374
|
+
"max"
|
|
7375
|
+
]);
|
|
7376
|
+
/**
|
|
7356
7377
|
* The full per-camera recording intent — the wire shape of a RecordingTarget.
|
|
7357
7378
|
*
|
|
7358
7379
|
* `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
|
|
@@ -7389,7 +7410,14 @@ var RecordingConfigSchema = object({
|
|
|
7389
7410
|
* derived into bands once via `migrateConfigToBands`.
|
|
7390
7411
|
*/
|
|
7391
7412
|
bands: array(RecordingBandSchema).optional(),
|
|
7392
|
-
retention: RecordingRetentionSchema.optional()
|
|
7413
|
+
retention: RecordingRetentionSchema.optional(),
|
|
7414
|
+
/**
|
|
7415
|
+
* Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
|
|
7416
|
+
* timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
|
|
7417
|
+
* windows only — existing sheets are immutable, and each window's index
|
|
7418
|
+
* carries its own tile dims so mixed-preset history renders correctly.
|
|
7419
|
+
*/
|
|
7420
|
+
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7393
7421
|
});
|
|
7394
7422
|
/**
|
|
7395
7423
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
@@ -11139,10 +11167,23 @@ var deviceDiscoveryCapability = {
|
|
|
11139
11167
|
}
|
|
11140
11168
|
};
|
|
11141
11169
|
/**
|
|
11142
|
-
* Doorbell button cap.
|
|
11143
|
-
*
|
|
11144
|
-
*
|
|
11145
|
-
*
|
|
11170
|
+
* Doorbell button cap. Two kinds of providers coexist behind this cap
|
|
11171
|
+
* name (same pattern as `snapshot`):
|
|
11172
|
+
*
|
|
11173
|
+
* - **Native** providers: registered per-device by device-driver
|
|
11174
|
+
* addons via `ctx.registerNativeCap` — either on a
|
|
11175
|
+
* `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
|
|
11176
|
+
* or directly on the camera (Reolink registers at camera level).
|
|
11177
|
+
* Emits an `onPressed` event every time the firmware pushes a
|
|
11178
|
+
* ring; status tracks the last press and a pressCount since start
|
|
11179
|
+
* (diagnostic).
|
|
11180
|
+
*
|
|
11181
|
+
* - **Wrapper** provider: the `virtual-doorbell` system builtin
|
|
11182
|
+
* (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
|
|
11183
|
+
* device (contact / switch / event-emitter …) into a doorbell for
|
|
11184
|
+
* a camera. `defaultActive: false` — the operator explicitly binds
|
|
11185
|
+
* it per camera in the device-bindings UI, then picks the source
|
|
11186
|
+
* device + trigger in the per-device settings.
|
|
11146
11187
|
*
|
|
11147
11188
|
* The DeviceEventPropagator re-emits `onPressed` on the camera parent
|
|
11148
11189
|
* — subscribers listening at the camera level receive ring events
|
|
@@ -11163,7 +11204,10 @@ var doorbellCapability = {
|
|
|
11163
11204
|
scope: "device",
|
|
11164
11205
|
deviceNative: true,
|
|
11165
11206
|
mode: "singleton",
|
|
11166
|
-
|
|
11207
|
+
kind: "wrapper",
|
|
11208
|
+
defaultActive: false,
|
|
11209
|
+
deviceTypes: [DeviceType.Button, DeviceType.Camera],
|
|
11210
|
+
exposesDeviceSettings: true,
|
|
11167
11211
|
methods: {},
|
|
11168
11212
|
events: {
|
|
11169
11213
|
/**
|
|
@@ -17600,6 +17644,14 @@ var ConfigEntrySchema = object({
|
|
|
17600
17644
|
value: unknown(),
|
|
17601
17645
|
description: string().optional()
|
|
17602
17646
|
});
|
|
17647
|
+
var DeviceLinkModeSchema = _enum(["auto", "manual"]);
|
|
17648
|
+
/** One resolved linked device — the compact projection consumers need. */
|
|
17649
|
+
var LinkedDeviceSchema = object({
|
|
17650
|
+
deviceId: number(),
|
|
17651
|
+
name: string(),
|
|
17652
|
+
location: string().nullable(),
|
|
17653
|
+
features: array(string())
|
|
17654
|
+
});
|
|
17603
17655
|
var SavedDeviceRowSchema = object({
|
|
17604
17656
|
/** Numeric id reserved at allocateDeviceId time. */
|
|
17605
17657
|
id: number(),
|
|
@@ -17821,7 +17873,10 @@ method(object({
|
|
|
17821
17873
|
}), _void(), {
|
|
17822
17874
|
kind: "mutation",
|
|
17823
17875
|
auth: "admin"
|
|
17824
|
-
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }),
|
|
17876
|
+
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
|
|
17877
|
+
mode: DeviceLinkModeSchema,
|
|
17878
|
+
devices: array(LinkedDeviceSchema)
|
|
17879
|
+
})), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
|
|
17825
17880
|
deviceId: number(),
|
|
17826
17881
|
values: record(string(), unknown())
|
|
17827
17882
|
}), object({ success: literal(true) }), {
|
|
@@ -19104,6 +19159,61 @@ var EventKindSchema = _enum([
|
|
|
19104
19159
|
"object",
|
|
19105
19160
|
"audio"
|
|
19106
19161
|
]);
|
|
19162
|
+
/**
|
|
19163
|
+
* Spatial filter for `listTracks` — the rect + polygon variants of the shared
|
|
19164
|
+
* MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
|
|
19165
|
+
* of the camera frame (top-left origin), matching the drawing-plane editor.
|
|
19166
|
+
*/
|
|
19167
|
+
var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
|
|
19168
|
+
/** Closed icon vocabulary so clients render a known glyph per kind. */
|
|
19169
|
+
var EventKindIconSchema = _enum([
|
|
19170
|
+
"motion",
|
|
19171
|
+
"audio",
|
|
19172
|
+
"person",
|
|
19173
|
+
"vehicle",
|
|
19174
|
+
"animal",
|
|
19175
|
+
"door",
|
|
19176
|
+
"pir",
|
|
19177
|
+
"smoke",
|
|
19178
|
+
"water",
|
|
19179
|
+
"button",
|
|
19180
|
+
"generic"
|
|
19181
|
+
]);
|
|
19182
|
+
var EventKindCategorySchema = _enum([
|
|
19183
|
+
"motion",
|
|
19184
|
+
"audio",
|
|
19185
|
+
"detection",
|
|
19186
|
+
"sensor",
|
|
19187
|
+
"custom"
|
|
19188
|
+
]);
|
|
19189
|
+
var EventKindDescriptorSchema = object({
|
|
19190
|
+
/** Stable kind id (e.g. 'motion', 'person', 'contact'). */
|
|
19191
|
+
kind: string(),
|
|
19192
|
+
label: string(),
|
|
19193
|
+
/** Hex color for timeline/legend rendering. */
|
|
19194
|
+
color: string(),
|
|
19195
|
+
icon: EventKindIconSchema,
|
|
19196
|
+
category: EventKindCategorySchema,
|
|
19197
|
+
/** Which cap + device contributes this kind. For built-ins the camera
|
|
19198
|
+
* itself; for sensor kinds the LINKED source device. */
|
|
19199
|
+
source: object({
|
|
19200
|
+
capName: string(),
|
|
19201
|
+
deviceId: number()
|
|
19202
|
+
})
|
|
19203
|
+
});
|
|
19204
|
+
var SensorEventSchema = object({
|
|
19205
|
+
id: string(),
|
|
19206
|
+
/** The CAMERA the event is attributed to (a sensor linked to N cameras
|
|
19207
|
+
* yields N rows, one per camera). */
|
|
19208
|
+
deviceId: number(),
|
|
19209
|
+
/** The linked sensor device whose state changed. */
|
|
19210
|
+
sourceDeviceId: number(),
|
|
19211
|
+
/** Event kind id — matches an `EventKindDescriptor.kind`. */
|
|
19212
|
+
kind: string(),
|
|
19213
|
+
/** Snapshot of the sensor cap's runtime-state slice at the change. */
|
|
19214
|
+
value: record(string(), unknown()).nullable(),
|
|
19215
|
+
timestamp: number()
|
|
19216
|
+
});
|
|
19107
19217
|
var TrackPositionSchema = object({
|
|
19108
19218
|
x: number(),
|
|
19109
19219
|
y: number(),
|
|
@@ -19117,6 +19227,30 @@ var TrackSnapshotSchema = object({
|
|
|
19117
19227
|
mediaKey: string()
|
|
19118
19228
|
});
|
|
19119
19229
|
/**
|
|
19230
|
+
* Normalized 0..1 trajectory envelope (min/max over every position bbox,
|
|
19231
|
+
* divided by the track's detection-frame dims), computed at persist time.
|
|
19232
|
+
* Absent when the frame dims were unknown when the track was persisted
|
|
19233
|
+
* (legacy rows / dims-less sources) and on active (in-RAM) tracks.
|
|
19234
|
+
*/
|
|
19235
|
+
var TrackEnvelopeSchema = object({
|
|
19236
|
+
minX: number(),
|
|
19237
|
+
minY: number(),
|
|
19238
|
+
maxX: number(),
|
|
19239
|
+
maxY: number()
|
|
19240
|
+
});
|
|
19241
|
+
/**
|
|
19242
|
+
* Row projection for track list queries. `full` (default) returns the
|
|
19243
|
+
* complete Track including the frame-rate `positions[]` history and the
|
|
19244
|
+
* `snapshots[]` references — megabytes across a page of tracks. `slim`
|
|
19245
|
+
* keeps every scalar the list surfaces actually render (ids, class(es),
|
|
19246
|
+
* label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
|
|
19247
|
+
* zonesVisited, bestEventId, envelope) and returns `positions` /
|
|
19248
|
+
* `snapshots` as EMPTY arrays — detail views re-fetch the full row via
|
|
19249
|
+
* `getTrack`. Mirrors the event-store `projection` convention
|
|
19250
|
+
* (`getObjectEvents` et al.).
|
|
19251
|
+
*/
|
|
19252
|
+
var TrackProjectionSchema = _enum(["full", "slim"]);
|
|
19253
|
+
/**
|
|
19120
19254
|
* One audio-classification label heard on the track's camera while the
|
|
19121
19255
|
* track was alive, aggregated per label. An "episode" is one persisted
|
|
19122
19256
|
* audio event (the confident-classification path: score ≥ the device's
|
|
@@ -19167,7 +19301,11 @@ var TrackSchema = object({
|
|
|
19167
19301
|
/** Audio-classification labels heard on the camera during the track's
|
|
19168
19302
|
* life (score ≥ device `classificationMinScore`), aggregated per label.
|
|
19169
19303
|
* Absent on legacy rows / tracks with no confident audio. */
|
|
19170
|
-
audioLabels: array(TrackAudioLabelSchema).readonly().optional()
|
|
19304
|
+
audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
|
|
19305
|
+
/** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
|
|
19306
|
+
* Populated from the persisted envelope columns on historical reads;
|
|
19307
|
+
* absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
|
|
19308
|
+
envelope: TrackEnvelopeSchema.optional()
|
|
19171
19309
|
});
|
|
19172
19310
|
var BaseEventFields = {
|
|
19173
19311
|
id: string(),
|
|
@@ -19277,11 +19415,13 @@ var MediaFileSchema = object({
|
|
|
19277
19415
|
sizeBytes: number(),
|
|
19278
19416
|
timestamp: number()
|
|
19279
19417
|
});
|
|
19418
|
+
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
19419
|
+
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
19280
19420
|
var DeviceEventQueryInput = object({
|
|
19281
19421
|
deviceId: number(),
|
|
19282
19422
|
since: number().optional(),
|
|
19283
19423
|
until: number().optional(),
|
|
19284
|
-
limit: number().int().min(1).max(
|
|
19424
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
|
|
19285
19425
|
/** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
|
|
19286
19426
|
* optional `mediaUrl` (populated by B5). `full` (default) keeps today's
|
|
19287
19427
|
* exact behaviour. Callers may omit this field — the store defaults to
|
|
@@ -19289,6 +19429,27 @@ var DeviceEventQueryInput = object({
|
|
|
19289
19429
|
projection: _enum(["full", "slim"]).optional()
|
|
19290
19430
|
});
|
|
19291
19431
|
var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
|
|
19432
|
+
var RecentTracksQueryInput = object({
|
|
19433
|
+
/** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
|
|
19434
|
+
deviceIds: array(number()),
|
|
19435
|
+
/** Window lower bound on `lastSeen` (inclusive). */
|
|
19436
|
+
since: number().optional(),
|
|
19437
|
+
/** Window upper bound on `lastSeen` (inclusive). */
|
|
19438
|
+
until: number().optional(),
|
|
19439
|
+
/** Page size. Default 200, max 1000. */
|
|
19440
|
+
limit: number().int().min(1).max(1e3).default(200),
|
|
19441
|
+
/** Opaque continuation cursor from a previous page's `nextCursor`.
|
|
19442
|
+
* Encodes the (lastSeen, trackId) sort position — treat as opaque. */
|
|
19443
|
+
cursor: string().optional(),
|
|
19444
|
+
/** See {@link TrackProjectionSchema}. Default `full`. */
|
|
19445
|
+
projection: TrackProjectionSchema.optional()
|
|
19446
|
+
});
|
|
19447
|
+
var RecentTracksPageSchema = object({
|
|
19448
|
+
/** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
|
|
19449
|
+
tracks: array(TrackSchema).readonly(),
|
|
19450
|
+
/** Cursor for the next page, or null when this page is the last. */
|
|
19451
|
+
nextCursor: string().nullable()
|
|
19452
|
+
});
|
|
19292
19453
|
var KeyEventQueryInput = object({
|
|
19293
19454
|
deviceId: number(),
|
|
19294
19455
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
@@ -19371,11 +19532,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19371
19532
|
deviceId: number(),
|
|
19372
19533
|
since: number().optional(),
|
|
19373
19534
|
until: number().optional(),
|
|
19374
|
-
limit: number().optional()
|
|
19375
|
-
|
|
19535
|
+
limit: number().optional(),
|
|
19536
|
+
/** Spatial filter — only tracks whose trajectory intersects the zone
|
|
19537
|
+
* (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
|
|
19538
|
+
* envelope columns, then precisely tested per position. Tracks with
|
|
19539
|
+
* an unknown envelope (no frame dims at persist time) always match. */
|
|
19540
|
+
zone: TrackZoneFilterSchema.optional(),
|
|
19541
|
+
/** See {@link TrackProjectionSchema}. Default `full` (backward
|
|
19542
|
+
* compatible — omitting the field keeps today's exact behaviour). */
|
|
19543
|
+
projection: TrackProjectionSchema.optional()
|
|
19544
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
19376
19545
|
kind: "mutation",
|
|
19377
19546
|
auth: "admin"
|
|
19378
|
-
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(
|
|
19547
|
+
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
|
|
19548
|
+
deviceId: number(),
|
|
19549
|
+
since: number().optional(),
|
|
19550
|
+
until: number().optional(),
|
|
19551
|
+
kinds: array(string()).optional(),
|
|
19552
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
19553
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
19379
19554
|
deviceId: number(),
|
|
19380
19555
|
since: number(),
|
|
19381
19556
|
until: number(),
|
|
@@ -22665,7 +22840,12 @@ var RecordingStorageUsageSchema = object({
|
|
|
22665
22840
|
/**
|
|
22666
22841
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
22667
22842
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
22668
|
-
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
22843
|
+
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
22844
|
+
* and the backward covered edge `prevEndMs` (exclusive end of the nearest
|
|
22845
|
+
* footage behind the epoch; `null` when none — optional so older providers
|
|
22846
|
+
* that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
|
|
22847
|
+
* small inter-segment cracks (durMs under-covers the span to the next
|
|
22848
|
+
* startMs by ~11-17 ms) instead of no-opping at a segment head.
|
|
22669
22849
|
*/
|
|
22670
22850
|
var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
22671
22851
|
kind: literal("segment"),
|
|
@@ -22674,7 +22854,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
|
22674
22854
|
bytes: number()
|
|
22675
22855
|
}), object({
|
|
22676
22856
|
kind: literal("gap"),
|
|
22677
|
-
nearestEdgeMs: number().nullable()
|
|
22857
|
+
nearestEdgeMs: number().nullable(),
|
|
22858
|
+
prevEndMs: number().nullable().optional()
|
|
22678
22859
|
})]);
|
|
22679
22860
|
/** Raw bytes of one finalized footage segment (read off disk on the recording node). */
|
|
22680
22861
|
var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
|
|
@@ -24243,6 +24424,12 @@ Object.freeze({
|
|
|
24243
24424
|
addonId: null,
|
|
24244
24425
|
access: "view"
|
|
24245
24426
|
},
|
|
24427
|
+
"deviceManager.getLinkedDevices": {
|
|
24428
|
+
capName: "device-manager",
|
|
24429
|
+
capScope: "system",
|
|
24430
|
+
addonId: null,
|
|
24431
|
+
access: "view"
|
|
24432
|
+
},
|
|
24246
24433
|
"deviceManager.getRoleDisplayDefaults": {
|
|
24247
24434
|
capName: "device-manager",
|
|
24248
24435
|
capScope: "system",
|
|
@@ -25797,6 +25984,12 @@ Object.freeze({
|
|
|
25797
25984
|
addonId: null,
|
|
25798
25985
|
access: "view"
|
|
25799
25986
|
},
|
|
25987
|
+
"pipelineAnalytics.getSensorEvents": {
|
|
25988
|
+
capName: "pipeline-analytics",
|
|
25989
|
+
capScope: "device",
|
|
25990
|
+
addonId: null,
|
|
25991
|
+
access: "view"
|
|
25992
|
+
},
|
|
25800
25993
|
"pipelineAnalytics.getTrack": {
|
|
25801
25994
|
capName: "pipeline-analytics",
|
|
25802
25995
|
capScope: "device",
|
|
@@ -25809,6 +26002,18 @@ Object.freeze({
|
|
|
25809
26002
|
addonId: null,
|
|
25810
26003
|
access: "view"
|
|
25811
26004
|
},
|
|
26005
|
+
"pipelineAnalytics.listEventKinds": {
|
|
26006
|
+
capName: "pipeline-analytics",
|
|
26007
|
+
capScope: "device",
|
|
26008
|
+
addonId: null,
|
|
26009
|
+
access: "view"
|
|
26010
|
+
},
|
|
26011
|
+
"pipelineAnalytics.listRecentTracks": {
|
|
26012
|
+
capName: "pipeline-analytics",
|
|
26013
|
+
capScope: "device",
|
|
26014
|
+
addonId: null,
|
|
26015
|
+
access: "view"
|
|
26016
|
+
},
|
|
25812
26017
|
"pipelineAnalytics.listTracks": {
|
|
25813
26018
|
capName: "pipeline-analytics",
|
|
25814
26019
|
capScope: "device",
|
|
@@ -32621,7 +32826,7 @@ var BUS_EVENT_TYPES = [
|
|
|
32621
32826
|
"deconz_event",
|
|
32622
32827
|
"xiaomi_aqara.click"
|
|
32623
32828
|
];
|
|
32624
|
-
var HaIntegrationManager = class {
|
|
32829
|
+
var HaIntegrationManager = class HaIntegrationManager {
|
|
32625
32830
|
opts;
|
|
32626
32831
|
wsClient = null;
|
|
32627
32832
|
connection = null;
|
|
@@ -32635,6 +32840,15 @@ var HaIntegrationManager = class {
|
|
|
32635
32840
|
lastCheckedAt = null;
|
|
32636
32841
|
lastError = null;
|
|
32637
32842
|
haInfo = {};
|
|
32843
|
+
/** Deadline timer for the INITIAL connect. `start()` no longer blocks on
|
|
32844
|
+
* the socket (see the comment there), so a broker whose host silently
|
|
32845
|
+
* drops packets would otherwise sit at `connecting` for the full OS TCP
|
|
32846
|
+
* timeout (tens of seconds) with no UI signal. This timer flips it to
|
|
32847
|
+
* `unreachable` promptly; a late successful connect still overrides it. */
|
|
32848
|
+
connectDeadlineTimer = null;
|
|
32849
|
+
/** Ceiling on the `connecting` state before the UI is shown `unreachable`.
|
|
32850
|
+
* Slightly above the throwaway `testSettings` probe timeout (8s). */
|
|
32851
|
+
static CONNECT_DEADLINE_MS = 1e4;
|
|
32638
32852
|
constructor(opts) {
|
|
32639
32853
|
this.opts = opts;
|
|
32640
32854
|
}
|
|
@@ -32648,6 +32862,8 @@ var HaIntegrationManager = class {
|
|
|
32648
32862
|
await this.refreshHaConfigInfo(connection);
|
|
32649
32863
|
await this.ensureBaselineSubscription();
|
|
32650
32864
|
await this.ensureBusEventSubscriptions();
|
|
32865
|
+
this.clearConnectDeadline();
|
|
32866
|
+
this.lastError = null;
|
|
32651
32867
|
this.setStatus("connected");
|
|
32652
32868
|
try {
|
|
32653
32869
|
this.opts.onConnected?.(this.opts.id);
|
|
@@ -32683,14 +32899,32 @@ var HaIntegrationManager = class {
|
|
|
32683
32899
|
}
|
|
32684
32900
|
}
|
|
32685
32901
|
});
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
} catch (err) {
|
|
32902
|
+
this.startConnectDeadline();
|
|
32903
|
+
this.wsClient.start().catch((err) => {
|
|
32689
32904
|
this.classifyAuthError(err);
|
|
32690
|
-
|
|
32905
|
+
});
|
|
32906
|
+
}
|
|
32907
|
+
/** Arm the initial-connect deadline. Idempotent — clears any prior timer. */
|
|
32908
|
+
startConnectDeadline() {
|
|
32909
|
+
this.clearConnectDeadline();
|
|
32910
|
+
const timer = setTimeout(() => {
|
|
32911
|
+
this.connectDeadlineTimer = null;
|
|
32912
|
+
if (this.status === "connecting") {
|
|
32913
|
+
if (this.lastError === null) this.lastError = "Connection timed out";
|
|
32914
|
+
this.setStatus("unreachable");
|
|
32915
|
+
}
|
|
32916
|
+
}, HaIntegrationManager.CONNECT_DEADLINE_MS);
|
|
32917
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
32918
|
+
this.connectDeadlineTimer = timer;
|
|
32919
|
+
}
|
|
32920
|
+
clearConnectDeadline() {
|
|
32921
|
+
if (this.connectDeadlineTimer) {
|
|
32922
|
+
clearTimeout(this.connectDeadlineTimer);
|
|
32923
|
+
this.connectDeadlineTimer = null;
|
|
32691
32924
|
}
|
|
32692
32925
|
}
|
|
32693
32926
|
async stop() {
|
|
32927
|
+
this.clearConnectDeadline();
|
|
32694
32928
|
if (this.upstreamSubscriptionUnsub) {
|
|
32695
32929
|
try {
|
|
32696
32930
|
await this.upstreamSubscriptionUnsub();
|
|
@@ -32905,15 +33139,19 @@ var HaIntegrationManager = class {
|
|
|
32905
33139
|
}));
|
|
32906
33140
|
}
|
|
32907
33141
|
classifyAuthError(err) {
|
|
33142
|
+
this.clearConnectDeadline();
|
|
32908
33143
|
if (err === 2) {
|
|
33144
|
+
this.lastError = "Authentication failed — check the access token";
|
|
32909
33145
|
this.setStatus("auth-failed");
|
|
32910
|
-
|
|
32911
|
-
|
|
33146
|
+
} else if (err === 1) {
|
|
33147
|
+
this.lastError = "Cannot connect — host unreachable or Home Assistant not responding";
|
|
33148
|
+
this.setStatus("unreachable");
|
|
33149
|
+
} else if (err === 4) {
|
|
33150
|
+
this.lastError = "Home Assistant host / URL is required";
|
|
32912
33151
|
this.setStatus("unreachable");
|
|
32913
|
-
this.lastError = errMsg(err);
|
|
32914
33152
|
} else {
|
|
32915
|
-
this.setStatus("error");
|
|
32916
33153
|
this.lastError = errMsg(err);
|
|
33154
|
+
this.setStatus("error");
|
|
32917
33155
|
}
|
|
32918
33156
|
}
|
|
32919
33157
|
requireConnection() {
|
package/dist/addon.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
|
|
3
|
-
//#region ../types/dist/event-category-
|
|
3
|
+
//#region ../types/dist/event-category-H4AVePnn.mjs
|
|
4
4
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
5
5
|
EventCategory["SystemBoot"] = "system.boot";
|
|
6
6
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -150,6 +150,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
150
150
|
EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
|
|
151
151
|
EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
|
|
152
152
|
EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
|
|
153
|
+
/** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
|
|
154
|
+
* thumb is a scrub gap the recorder's keyframe backfill covers. */
|
|
155
|
+
EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
|
|
153
156
|
EventCategory["DetectionEvent"] = "detection.event";
|
|
154
157
|
EventCategory["SessionTrackNew"] = "session.track.new";
|
|
155
158
|
EventCategory["SessionTrackExpired"] = "session.track.expired";
|
|
@@ -7352,6 +7355,24 @@ var RecordingRetentionSchema = object({
|
|
|
7352
7355
|
maxSizeGb: number().min(0).optional()
|
|
7353
7356
|
});
|
|
7354
7357
|
/**
|
|
7358
|
+
* Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
|
|
7359
|
+
* sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
|
|
7360
|
+
* previews at. Five graduated steps; absent on a config = `standard` (the
|
|
7361
|
+
* shipped default, matching `sheet-geometry`/`sheet-composer`).
|
|
7362
|
+
*
|
|
7363
|
+
* Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
|
|
7364
|
+
* Each window's index sidecar carries its own tile dims, so a camera whose
|
|
7365
|
+
* preset changed over time renders every historical window at the dims it was
|
|
7366
|
+
* written with.
|
|
7367
|
+
*/
|
|
7368
|
+
var ScrubThumbnailPresetSchema = _enum([
|
|
7369
|
+
"minimal",
|
|
7370
|
+
"low",
|
|
7371
|
+
"standard",
|
|
7372
|
+
"high",
|
|
7373
|
+
"max"
|
|
7374
|
+
]);
|
|
7375
|
+
/**
|
|
7355
7376
|
* The full per-camera recording intent — the wire shape of a RecordingTarget.
|
|
7356
7377
|
*
|
|
7357
7378
|
* `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
|
|
@@ -7388,7 +7409,14 @@ var RecordingConfigSchema = object({
|
|
|
7388
7409
|
* derived into bands once via `migrateConfigToBands`.
|
|
7389
7410
|
*/
|
|
7390
7411
|
bands: array(RecordingBandSchema).optional(),
|
|
7391
|
-
retention: RecordingRetentionSchema.optional()
|
|
7412
|
+
retention: RecordingRetentionSchema.optional(),
|
|
7413
|
+
/**
|
|
7414
|
+
* Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
|
|
7415
|
+
* timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
|
|
7416
|
+
* windows only — existing sheets are immutable, and each window's index
|
|
7417
|
+
* carries its own tile dims so mixed-preset history renders correctly.
|
|
7418
|
+
*/
|
|
7419
|
+
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7392
7420
|
});
|
|
7393
7421
|
/**
|
|
7394
7422
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
@@ -11138,10 +11166,23 @@ var deviceDiscoveryCapability = {
|
|
|
11138
11166
|
}
|
|
11139
11167
|
};
|
|
11140
11168
|
/**
|
|
11141
|
-
* Doorbell button cap.
|
|
11142
|
-
*
|
|
11143
|
-
*
|
|
11144
|
-
*
|
|
11169
|
+
* Doorbell button cap. Two kinds of providers coexist behind this cap
|
|
11170
|
+
* name (same pattern as `snapshot`):
|
|
11171
|
+
*
|
|
11172
|
+
* - **Native** providers: registered per-device by device-driver
|
|
11173
|
+
* addons via `ctx.registerNativeCap` — either on a
|
|
11174
|
+
* `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
|
|
11175
|
+
* or directly on the camera (Reolink registers at camera level).
|
|
11176
|
+
* Emits an `onPressed` event every time the firmware pushes a
|
|
11177
|
+
* ring; status tracks the last press and a pressCount since start
|
|
11178
|
+
* (diagnostic).
|
|
11179
|
+
*
|
|
11180
|
+
* - **Wrapper** provider: the `virtual-doorbell` system builtin
|
|
11181
|
+
* (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
|
|
11182
|
+
* device (contact / switch / event-emitter …) into a doorbell for
|
|
11183
|
+
* a camera. `defaultActive: false` — the operator explicitly binds
|
|
11184
|
+
* it per camera in the device-bindings UI, then picks the source
|
|
11185
|
+
* device + trigger in the per-device settings.
|
|
11145
11186
|
*
|
|
11146
11187
|
* The DeviceEventPropagator re-emits `onPressed` on the camera parent
|
|
11147
11188
|
* — subscribers listening at the camera level receive ring events
|
|
@@ -11162,7 +11203,10 @@ var doorbellCapability = {
|
|
|
11162
11203
|
scope: "device",
|
|
11163
11204
|
deviceNative: true,
|
|
11164
11205
|
mode: "singleton",
|
|
11165
|
-
|
|
11206
|
+
kind: "wrapper",
|
|
11207
|
+
defaultActive: false,
|
|
11208
|
+
deviceTypes: [DeviceType.Button, DeviceType.Camera],
|
|
11209
|
+
exposesDeviceSettings: true,
|
|
11166
11210
|
methods: {},
|
|
11167
11211
|
events: {
|
|
11168
11212
|
/**
|
|
@@ -17599,6 +17643,14 @@ var ConfigEntrySchema = object({
|
|
|
17599
17643
|
value: unknown(),
|
|
17600
17644
|
description: string().optional()
|
|
17601
17645
|
});
|
|
17646
|
+
var DeviceLinkModeSchema = _enum(["auto", "manual"]);
|
|
17647
|
+
/** One resolved linked device — the compact projection consumers need. */
|
|
17648
|
+
var LinkedDeviceSchema = object({
|
|
17649
|
+
deviceId: number(),
|
|
17650
|
+
name: string(),
|
|
17651
|
+
location: string().nullable(),
|
|
17652
|
+
features: array(string())
|
|
17653
|
+
});
|
|
17602
17654
|
var SavedDeviceRowSchema = object({
|
|
17603
17655
|
/** Numeric id reserved at allocateDeviceId time. */
|
|
17604
17656
|
id: number(),
|
|
@@ -17820,7 +17872,10 @@ method(object({
|
|
|
17820
17872
|
}), _void(), {
|
|
17821
17873
|
kind: "mutation",
|
|
17822
17874
|
auth: "admin"
|
|
17823
|
-
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }),
|
|
17875
|
+
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
|
|
17876
|
+
mode: DeviceLinkModeSchema,
|
|
17877
|
+
devices: array(LinkedDeviceSchema)
|
|
17878
|
+
})), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
|
|
17824
17879
|
deviceId: number(),
|
|
17825
17880
|
values: record(string(), unknown())
|
|
17826
17881
|
}), object({ success: literal(true) }), {
|
|
@@ -19103,6 +19158,61 @@ var EventKindSchema = _enum([
|
|
|
19103
19158
|
"object",
|
|
19104
19159
|
"audio"
|
|
19105
19160
|
]);
|
|
19161
|
+
/**
|
|
19162
|
+
* Spatial filter for `listTracks` — the rect + polygon variants of the shared
|
|
19163
|
+
* MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
|
|
19164
|
+
* of the camera frame (top-left origin), matching the drawing-plane editor.
|
|
19165
|
+
*/
|
|
19166
|
+
var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
|
|
19167
|
+
/** Closed icon vocabulary so clients render a known glyph per kind. */
|
|
19168
|
+
var EventKindIconSchema = _enum([
|
|
19169
|
+
"motion",
|
|
19170
|
+
"audio",
|
|
19171
|
+
"person",
|
|
19172
|
+
"vehicle",
|
|
19173
|
+
"animal",
|
|
19174
|
+
"door",
|
|
19175
|
+
"pir",
|
|
19176
|
+
"smoke",
|
|
19177
|
+
"water",
|
|
19178
|
+
"button",
|
|
19179
|
+
"generic"
|
|
19180
|
+
]);
|
|
19181
|
+
var EventKindCategorySchema = _enum([
|
|
19182
|
+
"motion",
|
|
19183
|
+
"audio",
|
|
19184
|
+
"detection",
|
|
19185
|
+
"sensor",
|
|
19186
|
+
"custom"
|
|
19187
|
+
]);
|
|
19188
|
+
var EventKindDescriptorSchema = object({
|
|
19189
|
+
/** Stable kind id (e.g. 'motion', 'person', 'contact'). */
|
|
19190
|
+
kind: string(),
|
|
19191
|
+
label: string(),
|
|
19192
|
+
/** Hex color for timeline/legend rendering. */
|
|
19193
|
+
color: string(),
|
|
19194
|
+
icon: EventKindIconSchema,
|
|
19195
|
+
category: EventKindCategorySchema,
|
|
19196
|
+
/** Which cap + device contributes this kind. For built-ins the camera
|
|
19197
|
+
* itself; for sensor kinds the LINKED source device. */
|
|
19198
|
+
source: object({
|
|
19199
|
+
capName: string(),
|
|
19200
|
+
deviceId: number()
|
|
19201
|
+
})
|
|
19202
|
+
});
|
|
19203
|
+
var SensorEventSchema = object({
|
|
19204
|
+
id: string(),
|
|
19205
|
+
/** The CAMERA the event is attributed to (a sensor linked to N cameras
|
|
19206
|
+
* yields N rows, one per camera). */
|
|
19207
|
+
deviceId: number(),
|
|
19208
|
+
/** The linked sensor device whose state changed. */
|
|
19209
|
+
sourceDeviceId: number(),
|
|
19210
|
+
/** Event kind id — matches an `EventKindDescriptor.kind`. */
|
|
19211
|
+
kind: string(),
|
|
19212
|
+
/** Snapshot of the sensor cap's runtime-state slice at the change. */
|
|
19213
|
+
value: record(string(), unknown()).nullable(),
|
|
19214
|
+
timestamp: number()
|
|
19215
|
+
});
|
|
19106
19216
|
var TrackPositionSchema = object({
|
|
19107
19217
|
x: number(),
|
|
19108
19218
|
y: number(),
|
|
@@ -19116,6 +19226,30 @@ var TrackSnapshotSchema = object({
|
|
|
19116
19226
|
mediaKey: string()
|
|
19117
19227
|
});
|
|
19118
19228
|
/**
|
|
19229
|
+
* Normalized 0..1 trajectory envelope (min/max over every position bbox,
|
|
19230
|
+
* divided by the track's detection-frame dims), computed at persist time.
|
|
19231
|
+
* Absent when the frame dims were unknown when the track was persisted
|
|
19232
|
+
* (legacy rows / dims-less sources) and on active (in-RAM) tracks.
|
|
19233
|
+
*/
|
|
19234
|
+
var TrackEnvelopeSchema = object({
|
|
19235
|
+
minX: number(),
|
|
19236
|
+
minY: number(),
|
|
19237
|
+
maxX: number(),
|
|
19238
|
+
maxY: number()
|
|
19239
|
+
});
|
|
19240
|
+
/**
|
|
19241
|
+
* Row projection for track list queries. `full` (default) returns the
|
|
19242
|
+
* complete Track including the frame-rate `positions[]` history and the
|
|
19243
|
+
* `snapshots[]` references — megabytes across a page of tracks. `slim`
|
|
19244
|
+
* keeps every scalar the list surfaces actually render (ids, class(es),
|
|
19245
|
+
* label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
|
|
19246
|
+
* zonesVisited, bestEventId, envelope) and returns `positions` /
|
|
19247
|
+
* `snapshots` as EMPTY arrays — detail views re-fetch the full row via
|
|
19248
|
+
* `getTrack`. Mirrors the event-store `projection` convention
|
|
19249
|
+
* (`getObjectEvents` et al.).
|
|
19250
|
+
*/
|
|
19251
|
+
var TrackProjectionSchema = _enum(["full", "slim"]);
|
|
19252
|
+
/**
|
|
19119
19253
|
* One audio-classification label heard on the track's camera while the
|
|
19120
19254
|
* track was alive, aggregated per label. An "episode" is one persisted
|
|
19121
19255
|
* audio event (the confident-classification path: score ≥ the device's
|
|
@@ -19166,7 +19300,11 @@ var TrackSchema = object({
|
|
|
19166
19300
|
/** Audio-classification labels heard on the camera during the track's
|
|
19167
19301
|
* life (score ≥ device `classificationMinScore`), aggregated per label.
|
|
19168
19302
|
* Absent on legacy rows / tracks with no confident audio. */
|
|
19169
|
-
audioLabels: array(TrackAudioLabelSchema).readonly().optional()
|
|
19303
|
+
audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
|
|
19304
|
+
/** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
|
|
19305
|
+
* Populated from the persisted envelope columns on historical reads;
|
|
19306
|
+
* absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
|
|
19307
|
+
envelope: TrackEnvelopeSchema.optional()
|
|
19170
19308
|
});
|
|
19171
19309
|
var BaseEventFields = {
|
|
19172
19310
|
id: string(),
|
|
@@ -19276,11 +19414,13 @@ var MediaFileSchema = object({
|
|
|
19276
19414
|
sizeBytes: number(),
|
|
19277
19415
|
timestamp: number()
|
|
19278
19416
|
});
|
|
19417
|
+
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
19418
|
+
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
19279
19419
|
var DeviceEventQueryInput = object({
|
|
19280
19420
|
deviceId: number(),
|
|
19281
19421
|
since: number().optional(),
|
|
19282
19422
|
until: number().optional(),
|
|
19283
|
-
limit: number().int().min(1).max(
|
|
19423
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
|
|
19284
19424
|
/** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
|
|
19285
19425
|
* optional `mediaUrl` (populated by B5). `full` (default) keeps today's
|
|
19286
19426
|
* exact behaviour. Callers may omit this field — the store defaults to
|
|
@@ -19288,6 +19428,27 @@ var DeviceEventQueryInput = object({
|
|
|
19288
19428
|
projection: _enum(["full", "slim"]).optional()
|
|
19289
19429
|
});
|
|
19290
19430
|
var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
|
|
19431
|
+
var RecentTracksQueryInput = object({
|
|
19432
|
+
/** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
|
|
19433
|
+
deviceIds: array(number()),
|
|
19434
|
+
/** Window lower bound on `lastSeen` (inclusive). */
|
|
19435
|
+
since: number().optional(),
|
|
19436
|
+
/** Window upper bound on `lastSeen` (inclusive). */
|
|
19437
|
+
until: number().optional(),
|
|
19438
|
+
/** Page size. Default 200, max 1000. */
|
|
19439
|
+
limit: number().int().min(1).max(1e3).default(200),
|
|
19440
|
+
/** Opaque continuation cursor from a previous page's `nextCursor`.
|
|
19441
|
+
* Encodes the (lastSeen, trackId) sort position — treat as opaque. */
|
|
19442
|
+
cursor: string().optional(),
|
|
19443
|
+
/** See {@link TrackProjectionSchema}. Default `full`. */
|
|
19444
|
+
projection: TrackProjectionSchema.optional()
|
|
19445
|
+
});
|
|
19446
|
+
var RecentTracksPageSchema = object({
|
|
19447
|
+
/** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
|
|
19448
|
+
tracks: array(TrackSchema).readonly(),
|
|
19449
|
+
/** Cursor for the next page, or null when this page is the last. */
|
|
19450
|
+
nextCursor: string().nullable()
|
|
19451
|
+
});
|
|
19291
19452
|
var KeyEventQueryInput = object({
|
|
19292
19453
|
deviceId: number(),
|
|
19293
19454
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
@@ -19370,11 +19531,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19370
19531
|
deviceId: number(),
|
|
19371
19532
|
since: number().optional(),
|
|
19372
19533
|
until: number().optional(),
|
|
19373
|
-
limit: number().optional()
|
|
19374
|
-
|
|
19534
|
+
limit: number().optional(),
|
|
19535
|
+
/** Spatial filter — only tracks whose trajectory intersects the zone
|
|
19536
|
+
* (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
|
|
19537
|
+
* envelope columns, then precisely tested per position. Tracks with
|
|
19538
|
+
* an unknown envelope (no frame dims at persist time) always match. */
|
|
19539
|
+
zone: TrackZoneFilterSchema.optional(),
|
|
19540
|
+
/** See {@link TrackProjectionSchema}. Default `full` (backward
|
|
19541
|
+
* compatible — omitting the field keeps today's exact behaviour). */
|
|
19542
|
+
projection: TrackProjectionSchema.optional()
|
|
19543
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
19375
19544
|
kind: "mutation",
|
|
19376
19545
|
auth: "admin"
|
|
19377
|
-
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(
|
|
19546
|
+
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
|
|
19547
|
+
deviceId: number(),
|
|
19548
|
+
since: number().optional(),
|
|
19549
|
+
until: number().optional(),
|
|
19550
|
+
kinds: array(string()).optional(),
|
|
19551
|
+
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
19552
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
19378
19553
|
deviceId: number(),
|
|
19379
19554
|
since: number(),
|
|
19380
19555
|
until: number(),
|
|
@@ -22664,7 +22839,12 @@ var RecordingStorageUsageSchema = object({
|
|
|
22664
22839
|
/**
|
|
22665
22840
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
22666
22841
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
22667
|
-
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
22842
|
+
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
22843
|
+
* and the backward covered edge `prevEndMs` (exclusive end of the nearest
|
|
22844
|
+
* footage behind the epoch; `null` when none — optional so older providers
|
|
22845
|
+
* that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
|
|
22846
|
+
* small inter-segment cracks (durMs under-covers the span to the next
|
|
22847
|
+
* startMs by ~11-17 ms) instead of no-opping at a segment head.
|
|
22668
22848
|
*/
|
|
22669
22849
|
var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
22670
22850
|
kind: literal("segment"),
|
|
@@ -22673,7 +22853,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
|
|
|
22673
22853
|
bytes: number()
|
|
22674
22854
|
}), object({
|
|
22675
22855
|
kind: literal("gap"),
|
|
22676
|
-
nearestEdgeMs: number().nullable()
|
|
22856
|
+
nearestEdgeMs: number().nullable(),
|
|
22857
|
+
prevEndMs: number().nullable().optional()
|
|
22677
22858
|
})]);
|
|
22678
22859
|
/** Raw bytes of one finalized footage segment (read off disk on the recording node). */
|
|
22679
22860
|
var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
|
|
@@ -24242,6 +24423,12 @@ Object.freeze({
|
|
|
24242
24423
|
addonId: null,
|
|
24243
24424
|
access: "view"
|
|
24244
24425
|
},
|
|
24426
|
+
"deviceManager.getLinkedDevices": {
|
|
24427
|
+
capName: "device-manager",
|
|
24428
|
+
capScope: "system",
|
|
24429
|
+
addonId: null,
|
|
24430
|
+
access: "view"
|
|
24431
|
+
},
|
|
24245
24432
|
"deviceManager.getRoleDisplayDefaults": {
|
|
24246
24433
|
capName: "device-manager",
|
|
24247
24434
|
capScope: "system",
|
|
@@ -25796,6 +25983,12 @@ Object.freeze({
|
|
|
25796
25983
|
addonId: null,
|
|
25797
25984
|
access: "view"
|
|
25798
25985
|
},
|
|
25986
|
+
"pipelineAnalytics.getSensorEvents": {
|
|
25987
|
+
capName: "pipeline-analytics",
|
|
25988
|
+
capScope: "device",
|
|
25989
|
+
addonId: null,
|
|
25990
|
+
access: "view"
|
|
25991
|
+
},
|
|
25799
25992
|
"pipelineAnalytics.getTrack": {
|
|
25800
25993
|
capName: "pipeline-analytics",
|
|
25801
25994
|
capScope: "device",
|
|
@@ -25808,6 +26001,18 @@ Object.freeze({
|
|
|
25808
26001
|
addonId: null,
|
|
25809
26002
|
access: "view"
|
|
25810
26003
|
},
|
|
26004
|
+
"pipelineAnalytics.listEventKinds": {
|
|
26005
|
+
capName: "pipeline-analytics",
|
|
26006
|
+
capScope: "device",
|
|
26007
|
+
addonId: null,
|
|
26008
|
+
access: "view"
|
|
26009
|
+
},
|
|
26010
|
+
"pipelineAnalytics.listRecentTracks": {
|
|
26011
|
+
capName: "pipeline-analytics",
|
|
26012
|
+
capScope: "device",
|
|
26013
|
+
addonId: null,
|
|
26014
|
+
access: "view"
|
|
26015
|
+
},
|
|
25811
26016
|
"pipelineAnalytics.listTracks": {
|
|
25812
26017
|
capName: "pipeline-analytics",
|
|
25813
26018
|
capScope: "device",
|
|
@@ -32620,7 +32825,7 @@ var BUS_EVENT_TYPES = [
|
|
|
32620
32825
|
"deconz_event",
|
|
32621
32826
|
"xiaomi_aqara.click"
|
|
32622
32827
|
];
|
|
32623
|
-
var HaIntegrationManager = class {
|
|
32828
|
+
var HaIntegrationManager = class HaIntegrationManager {
|
|
32624
32829
|
opts;
|
|
32625
32830
|
wsClient = null;
|
|
32626
32831
|
connection = null;
|
|
@@ -32634,6 +32839,15 @@ var HaIntegrationManager = class {
|
|
|
32634
32839
|
lastCheckedAt = null;
|
|
32635
32840
|
lastError = null;
|
|
32636
32841
|
haInfo = {};
|
|
32842
|
+
/** Deadline timer for the INITIAL connect. `start()` no longer blocks on
|
|
32843
|
+
* the socket (see the comment there), so a broker whose host silently
|
|
32844
|
+
* drops packets would otherwise sit at `connecting` for the full OS TCP
|
|
32845
|
+
* timeout (tens of seconds) with no UI signal. This timer flips it to
|
|
32846
|
+
* `unreachable` promptly; a late successful connect still overrides it. */
|
|
32847
|
+
connectDeadlineTimer = null;
|
|
32848
|
+
/** Ceiling on the `connecting` state before the UI is shown `unreachable`.
|
|
32849
|
+
* Slightly above the throwaway `testSettings` probe timeout (8s). */
|
|
32850
|
+
static CONNECT_DEADLINE_MS = 1e4;
|
|
32637
32851
|
constructor(opts) {
|
|
32638
32852
|
this.opts = opts;
|
|
32639
32853
|
}
|
|
@@ -32647,6 +32861,8 @@ var HaIntegrationManager = class {
|
|
|
32647
32861
|
await this.refreshHaConfigInfo(connection);
|
|
32648
32862
|
await this.ensureBaselineSubscription();
|
|
32649
32863
|
await this.ensureBusEventSubscriptions();
|
|
32864
|
+
this.clearConnectDeadline();
|
|
32865
|
+
this.lastError = null;
|
|
32650
32866
|
this.setStatus("connected");
|
|
32651
32867
|
try {
|
|
32652
32868
|
this.opts.onConnected?.(this.opts.id);
|
|
@@ -32682,14 +32898,32 @@ var HaIntegrationManager = class {
|
|
|
32682
32898
|
}
|
|
32683
32899
|
}
|
|
32684
32900
|
});
|
|
32685
|
-
|
|
32686
|
-
|
|
32687
|
-
} catch (err) {
|
|
32901
|
+
this.startConnectDeadline();
|
|
32902
|
+
this.wsClient.start().catch((err) => {
|
|
32688
32903
|
this.classifyAuthError(err);
|
|
32689
|
-
|
|
32904
|
+
});
|
|
32905
|
+
}
|
|
32906
|
+
/** Arm the initial-connect deadline. Idempotent — clears any prior timer. */
|
|
32907
|
+
startConnectDeadline() {
|
|
32908
|
+
this.clearConnectDeadline();
|
|
32909
|
+
const timer = setTimeout(() => {
|
|
32910
|
+
this.connectDeadlineTimer = null;
|
|
32911
|
+
if (this.status === "connecting") {
|
|
32912
|
+
if (this.lastError === null) this.lastError = "Connection timed out";
|
|
32913
|
+
this.setStatus("unreachable");
|
|
32914
|
+
}
|
|
32915
|
+
}, HaIntegrationManager.CONNECT_DEADLINE_MS);
|
|
32916
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
32917
|
+
this.connectDeadlineTimer = timer;
|
|
32918
|
+
}
|
|
32919
|
+
clearConnectDeadline() {
|
|
32920
|
+
if (this.connectDeadlineTimer) {
|
|
32921
|
+
clearTimeout(this.connectDeadlineTimer);
|
|
32922
|
+
this.connectDeadlineTimer = null;
|
|
32690
32923
|
}
|
|
32691
32924
|
}
|
|
32692
32925
|
async stop() {
|
|
32926
|
+
this.clearConnectDeadline();
|
|
32693
32927
|
if (this.upstreamSubscriptionUnsub) {
|
|
32694
32928
|
try {
|
|
32695
32929
|
await this.upstreamSubscriptionUnsub();
|
|
@@ -32904,15 +33138,19 @@ var HaIntegrationManager = class {
|
|
|
32904
33138
|
}));
|
|
32905
33139
|
}
|
|
32906
33140
|
classifyAuthError(err) {
|
|
33141
|
+
this.clearConnectDeadline();
|
|
32907
33142
|
if (err === 2) {
|
|
33143
|
+
this.lastError = "Authentication failed — check the access token";
|
|
32908
33144
|
this.setStatus("auth-failed");
|
|
32909
|
-
|
|
32910
|
-
|
|
33145
|
+
} else if (err === 1) {
|
|
33146
|
+
this.lastError = "Cannot connect — host unreachable or Home Assistant not responding";
|
|
33147
|
+
this.setStatus("unreachable");
|
|
33148
|
+
} else if (err === 4) {
|
|
33149
|
+
this.lastError = "Home Assistant host / URL is required";
|
|
32911
33150
|
this.setStatus("unreachable");
|
|
32912
|
-
this.lastError = errMsg(err);
|
|
32913
33151
|
} else {
|
|
32914
|
-
this.setStatus("error");
|
|
32915
33152
|
this.lastError = errMsg(err);
|
|
33153
|
+
this.setStatus("error");
|
|
32916
33154
|
}
|
|
32917
33155
|
}
|
|
32918
33156
|
requireConnection() {
|