@camstack/addon-provider-homeassistant 1.1.27 → 1.1.28

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.
Files changed (3) hide show
  1. package/dist/addon.js +194 -14
  2. package/dist/addon.mjs +194 -14
  3. 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-CFZs3jI4.mjs
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";
@@ -11139,10 +11142,23 @@ var deviceDiscoveryCapability = {
11139
11142
  }
11140
11143
  };
11141
11144
  /**
11142
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11143
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11144
- * time the firmware pushes a ring; status tracks the last press and
11145
- * a pressCount since start (diagnostic).
11145
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11146
+ * name (same pattern as `snapshot`):
11147
+ *
11148
+ * - **Native** providers: registered per-device by device-driver
11149
+ * addons via `ctx.registerNativeCap` — either on a
11150
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11151
+ * or directly on the camera (Reolink registers at camera level).
11152
+ * Emits an `onPressed` event every time the firmware pushes a
11153
+ * ring; status tracks the last press and a pressCount since start
11154
+ * (diagnostic).
11155
+ *
11156
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11157
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11158
+ * device (contact / switch / event-emitter …) into a doorbell for
11159
+ * a camera. `defaultActive: false` — the operator explicitly binds
11160
+ * it per camera in the device-bindings UI, then picks the source
11161
+ * device + trigger in the per-device settings.
11146
11162
  *
11147
11163
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11148
11164
  * — subscribers listening at the camera level receive ring events
@@ -11163,7 +11179,10 @@ var doorbellCapability = {
11163
11179
  scope: "device",
11164
11180
  deviceNative: true,
11165
11181
  mode: "singleton",
11166
- deviceTypes: [DeviceType.Button],
11182
+ kind: "wrapper",
11183
+ defaultActive: false,
11184
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11185
+ exposesDeviceSettings: true,
11167
11186
  methods: {},
11168
11187
  events: {
11169
11188
  /**
@@ -17600,6 +17619,14 @@ var ConfigEntrySchema = object({
17600
17619
  value: unknown(),
17601
17620
  description: string().optional()
17602
17621
  });
17622
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17623
+ /** One resolved linked device — the compact projection consumers need. */
17624
+ var LinkedDeviceSchema = object({
17625
+ deviceId: number(),
17626
+ name: string(),
17627
+ location: string().nullable(),
17628
+ features: array(string())
17629
+ });
17603
17630
  var SavedDeviceRowSchema = object({
17604
17631
  /** Numeric id reserved at allocateDeviceId time. */
17605
17632
  id: number(),
@@ -17821,7 +17848,10 @@ method(object({
17821
17848
  }), _void(), {
17822
17849
  kind: "mutation",
17823
17850
  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() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17851
+ }), 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({
17852
+ mode: DeviceLinkModeSchema,
17853
+ devices: array(LinkedDeviceSchema)
17854
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17825
17855
  deviceId: number(),
17826
17856
  values: record(string(), unknown())
17827
17857
  }), object({ success: literal(true) }), {
@@ -19104,6 +19134,61 @@ var EventKindSchema = _enum([
19104
19134
  "object",
19105
19135
  "audio"
19106
19136
  ]);
19137
+ /**
19138
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
19139
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
19140
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
19141
+ */
19142
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
19143
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
19144
+ var EventKindIconSchema = _enum([
19145
+ "motion",
19146
+ "audio",
19147
+ "person",
19148
+ "vehicle",
19149
+ "animal",
19150
+ "door",
19151
+ "pir",
19152
+ "smoke",
19153
+ "water",
19154
+ "button",
19155
+ "generic"
19156
+ ]);
19157
+ var EventKindCategorySchema = _enum([
19158
+ "motion",
19159
+ "audio",
19160
+ "detection",
19161
+ "sensor",
19162
+ "custom"
19163
+ ]);
19164
+ var EventKindDescriptorSchema = object({
19165
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19166
+ kind: string(),
19167
+ label: string(),
19168
+ /** Hex color for timeline/legend rendering. */
19169
+ color: string(),
19170
+ icon: EventKindIconSchema,
19171
+ category: EventKindCategorySchema,
19172
+ /** Which cap + device contributes this kind. For built-ins the camera
19173
+ * itself; for sensor kinds the LINKED source device. */
19174
+ source: object({
19175
+ capName: string(),
19176
+ deviceId: number()
19177
+ })
19178
+ });
19179
+ var SensorEventSchema = object({
19180
+ id: string(),
19181
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19182
+ * yields N rows, one per camera). */
19183
+ deviceId: number(),
19184
+ /** The linked sensor device whose state changed. */
19185
+ sourceDeviceId: number(),
19186
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19187
+ kind: string(),
19188
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19189
+ value: record(string(), unknown()).nullable(),
19190
+ timestamp: number()
19191
+ });
19107
19192
  var TrackPositionSchema = object({
19108
19193
  x: number(),
19109
19194
  y: number(),
@@ -19117,6 +19202,30 @@ var TrackSnapshotSchema = object({
19117
19202
  mediaKey: string()
19118
19203
  });
19119
19204
  /**
19205
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19206
+ * divided by the track's detection-frame dims), computed at persist time.
19207
+ * Absent when the frame dims were unknown when the track was persisted
19208
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19209
+ */
19210
+ var TrackEnvelopeSchema = object({
19211
+ minX: number(),
19212
+ minY: number(),
19213
+ maxX: number(),
19214
+ maxY: number()
19215
+ });
19216
+ /**
19217
+ * Row projection for track list queries. `full` (default) returns the
19218
+ * complete Track including the frame-rate `positions[]` history and the
19219
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19220
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19221
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19222
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19223
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19224
+ * `getTrack`. Mirrors the event-store `projection` convention
19225
+ * (`getObjectEvents` et al.).
19226
+ */
19227
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19228
+ /**
19120
19229
  * One audio-classification label heard on the track's camera while the
19121
19230
  * track was alive, aggregated per label. An "episode" is one persisted
19122
19231
  * audio event (the confident-classification path: score ≥ the device's
@@ -19167,7 +19276,11 @@ var TrackSchema = object({
19167
19276
  /** Audio-classification labels heard on the camera during the track's
19168
19277
  * life (score ≥ device `classificationMinScore`), aggregated per label.
19169
19278
  * Absent on legacy rows / tracks with no confident audio. */
19170
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19279
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19280
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19281
+ * Populated from the persisted envelope columns on historical reads;
19282
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19283
+ envelope: TrackEnvelopeSchema.optional()
19171
19284
  });
19172
19285
  var BaseEventFields = {
19173
19286
  id: string(),
@@ -19277,11 +19390,13 @@ var MediaFileSchema = object({
19277
19390
  sizeBytes: number(),
19278
19391
  timestamp: number()
19279
19392
  });
19393
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19394
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19280
19395
  var DeviceEventQueryInput = object({
19281
19396
  deviceId: number(),
19282
19397
  since: number().optional(),
19283
19398
  until: number().optional(),
19284
- limit: number().int().min(1).max(5e3).default(1e3),
19399
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19285
19400
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19286
19401
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19287
19402
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19289,6 +19404,27 @@ var DeviceEventQueryInput = object({
19289
19404
  projection: _enum(["full", "slim"]).optional()
19290
19405
  });
19291
19406
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19407
+ var RecentTracksQueryInput = object({
19408
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19409
+ deviceIds: array(number()),
19410
+ /** Window lower bound on `lastSeen` (inclusive). */
19411
+ since: number().optional(),
19412
+ /** Window upper bound on `lastSeen` (inclusive). */
19413
+ until: number().optional(),
19414
+ /** Page size. Default 200, max 1000. */
19415
+ limit: number().int().min(1).max(1e3).default(200),
19416
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19417
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19418
+ cursor: string().optional(),
19419
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19420
+ projection: TrackProjectionSchema.optional()
19421
+ });
19422
+ var RecentTracksPageSchema = object({
19423
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19424
+ tracks: array(TrackSchema).readonly(),
19425
+ /** Cursor for the next page, or null when this page is the last. */
19426
+ nextCursor: string().nullable()
19427
+ });
19292
19428
  var KeyEventQueryInput = object({
19293
19429
  deviceId: number(),
19294
19430
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19371,11 +19507,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19371
19507
  deviceId: number(),
19372
19508
  since: number().optional(),
19373
19509
  until: number().optional(),
19374
- limit: number().optional()
19375
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19510
+ limit: number().optional(),
19511
+ /** Spatial filter only tracks whose trajectory intersects the zone
19512
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19513
+ * envelope columns, then precisely tested per position. Tracks with
19514
+ * an unknown envelope (no frame dims at persist time) always match. */
19515
+ zone: TrackZoneFilterSchema.optional(),
19516
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19517
+ * compatible — omitting the field keeps today's exact behaviour). */
19518
+ projection: TrackProjectionSchema.optional()
19519
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19376
19520
  kind: "mutation",
19377
19521
  auth: "admin"
19378
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19522
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19523
+ deviceId: number(),
19524
+ since: number().optional(),
19525
+ until: number().optional(),
19526
+ kinds: array(string()).optional(),
19527
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19528
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19379
19529
  deviceId: number(),
19380
19530
  since: number(),
19381
19531
  until: number(),
@@ -22665,7 +22815,12 @@ var RecordingStorageUsageSchema = object({
22665
22815
  /**
22666
22816
  * Result of locating footage at a wall-clock instant for one device/profile.
22667
22817
  * `segment` carries the covering segment's window; `gap` reports the forward
22668
- * nearest covered edge (`null` past the end of footage / when none exists).
22818
+ * nearest covered edge (`null` past the end of footage / when none exists)
22819
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22820
+ * footage behind the epoch; `null` when none — optional so older providers
22821
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22822
+ * small inter-segment cracks (durMs under-covers the span to the next
22823
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22669
22824
  */
22670
22825
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22671
22826
  kind: literal("segment"),
@@ -22674,7 +22829,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22674
22829
  bytes: number()
22675
22830
  }), object({
22676
22831
  kind: literal("gap"),
22677
- nearestEdgeMs: number().nullable()
22832
+ nearestEdgeMs: number().nullable(),
22833
+ prevEndMs: number().nullable().optional()
22678
22834
  })]);
22679
22835
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22680
22836
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24243,6 +24399,12 @@ Object.freeze({
24243
24399
  addonId: null,
24244
24400
  access: "view"
24245
24401
  },
24402
+ "deviceManager.getLinkedDevices": {
24403
+ capName: "device-manager",
24404
+ capScope: "system",
24405
+ addonId: null,
24406
+ access: "view"
24407
+ },
24246
24408
  "deviceManager.getRoleDisplayDefaults": {
24247
24409
  capName: "device-manager",
24248
24410
  capScope: "system",
@@ -25797,6 +25959,12 @@ Object.freeze({
25797
25959
  addonId: null,
25798
25960
  access: "view"
25799
25961
  },
25962
+ "pipelineAnalytics.getSensorEvents": {
25963
+ capName: "pipeline-analytics",
25964
+ capScope: "device",
25965
+ addonId: null,
25966
+ access: "view"
25967
+ },
25800
25968
  "pipelineAnalytics.getTrack": {
25801
25969
  capName: "pipeline-analytics",
25802
25970
  capScope: "device",
@@ -25809,6 +25977,18 @@ Object.freeze({
25809
25977
  addonId: null,
25810
25978
  access: "view"
25811
25979
  },
25980
+ "pipelineAnalytics.listEventKinds": {
25981
+ capName: "pipeline-analytics",
25982
+ capScope: "device",
25983
+ addonId: null,
25984
+ access: "view"
25985
+ },
25986
+ "pipelineAnalytics.listRecentTracks": {
25987
+ capName: "pipeline-analytics",
25988
+ capScope: "device",
25989
+ addonId: null,
25990
+ access: "view"
25991
+ },
25812
25992
  "pipelineAnalytics.listTracks": {
25813
25993
  capName: "pipeline-analytics",
25814
25994
  capScope: "device",
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-CFZs3jI4.mjs
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";
@@ -11138,10 +11141,23 @@ var deviceDiscoveryCapability = {
11138
11141
  }
11139
11142
  };
11140
11143
  /**
11141
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11142
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11143
- * time the firmware pushes a ring; status tracks the last press and
11144
- * a pressCount since start (diagnostic).
11144
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11145
+ * name (same pattern as `snapshot`):
11146
+ *
11147
+ * - **Native** providers: registered per-device by device-driver
11148
+ * addons via `ctx.registerNativeCap` — either on a
11149
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11150
+ * or directly on the camera (Reolink registers at camera level).
11151
+ * Emits an `onPressed` event every time the firmware pushes a
11152
+ * ring; status tracks the last press and a pressCount since start
11153
+ * (diagnostic).
11154
+ *
11155
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11156
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11157
+ * device (contact / switch / event-emitter …) into a doorbell for
11158
+ * a camera. `defaultActive: false` — the operator explicitly binds
11159
+ * it per camera in the device-bindings UI, then picks the source
11160
+ * device + trigger in the per-device settings.
11145
11161
  *
11146
11162
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11147
11163
  * — subscribers listening at the camera level receive ring events
@@ -11162,7 +11178,10 @@ var doorbellCapability = {
11162
11178
  scope: "device",
11163
11179
  deviceNative: true,
11164
11180
  mode: "singleton",
11165
- deviceTypes: [DeviceType.Button],
11181
+ kind: "wrapper",
11182
+ defaultActive: false,
11183
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11184
+ exposesDeviceSettings: true,
11166
11185
  methods: {},
11167
11186
  events: {
11168
11187
  /**
@@ -17599,6 +17618,14 @@ var ConfigEntrySchema = object({
17599
17618
  value: unknown(),
17600
17619
  description: string().optional()
17601
17620
  });
17621
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17622
+ /** One resolved linked device — the compact projection consumers need. */
17623
+ var LinkedDeviceSchema = object({
17624
+ deviceId: number(),
17625
+ name: string(),
17626
+ location: string().nullable(),
17627
+ features: array(string())
17628
+ });
17602
17629
  var SavedDeviceRowSchema = object({
17603
17630
  /** Numeric id reserved at allocateDeviceId time. */
17604
17631
  id: number(),
@@ -17820,7 +17847,10 @@ method(object({
17820
17847
  }), _void(), {
17821
17848
  kind: "mutation",
17822
17849
  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() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17850
+ }), 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({
17851
+ mode: DeviceLinkModeSchema,
17852
+ devices: array(LinkedDeviceSchema)
17853
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17824
17854
  deviceId: number(),
17825
17855
  values: record(string(), unknown())
17826
17856
  }), object({ success: literal(true) }), {
@@ -19103,6 +19133,61 @@ var EventKindSchema = _enum([
19103
19133
  "object",
19104
19134
  "audio"
19105
19135
  ]);
19136
+ /**
19137
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
19138
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
19139
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
19140
+ */
19141
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
19142
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
19143
+ var EventKindIconSchema = _enum([
19144
+ "motion",
19145
+ "audio",
19146
+ "person",
19147
+ "vehicle",
19148
+ "animal",
19149
+ "door",
19150
+ "pir",
19151
+ "smoke",
19152
+ "water",
19153
+ "button",
19154
+ "generic"
19155
+ ]);
19156
+ var EventKindCategorySchema = _enum([
19157
+ "motion",
19158
+ "audio",
19159
+ "detection",
19160
+ "sensor",
19161
+ "custom"
19162
+ ]);
19163
+ var EventKindDescriptorSchema = object({
19164
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19165
+ kind: string(),
19166
+ label: string(),
19167
+ /** Hex color for timeline/legend rendering. */
19168
+ color: string(),
19169
+ icon: EventKindIconSchema,
19170
+ category: EventKindCategorySchema,
19171
+ /** Which cap + device contributes this kind. For built-ins the camera
19172
+ * itself; for sensor kinds the LINKED source device. */
19173
+ source: object({
19174
+ capName: string(),
19175
+ deviceId: number()
19176
+ })
19177
+ });
19178
+ var SensorEventSchema = object({
19179
+ id: string(),
19180
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19181
+ * yields N rows, one per camera). */
19182
+ deviceId: number(),
19183
+ /** The linked sensor device whose state changed. */
19184
+ sourceDeviceId: number(),
19185
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19186
+ kind: string(),
19187
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19188
+ value: record(string(), unknown()).nullable(),
19189
+ timestamp: number()
19190
+ });
19106
19191
  var TrackPositionSchema = object({
19107
19192
  x: number(),
19108
19193
  y: number(),
@@ -19116,6 +19201,30 @@ var TrackSnapshotSchema = object({
19116
19201
  mediaKey: string()
19117
19202
  });
19118
19203
  /**
19204
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19205
+ * divided by the track's detection-frame dims), computed at persist time.
19206
+ * Absent when the frame dims were unknown when the track was persisted
19207
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19208
+ */
19209
+ var TrackEnvelopeSchema = object({
19210
+ minX: number(),
19211
+ minY: number(),
19212
+ maxX: number(),
19213
+ maxY: number()
19214
+ });
19215
+ /**
19216
+ * Row projection for track list queries. `full` (default) returns the
19217
+ * complete Track including the frame-rate `positions[]` history and the
19218
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19219
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19220
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19221
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19222
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19223
+ * `getTrack`. Mirrors the event-store `projection` convention
19224
+ * (`getObjectEvents` et al.).
19225
+ */
19226
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19227
+ /**
19119
19228
  * One audio-classification label heard on the track's camera while the
19120
19229
  * track was alive, aggregated per label. An "episode" is one persisted
19121
19230
  * audio event (the confident-classification path: score ≥ the device's
@@ -19166,7 +19275,11 @@ var TrackSchema = object({
19166
19275
  /** Audio-classification labels heard on the camera during the track's
19167
19276
  * life (score ≥ device `classificationMinScore`), aggregated per label.
19168
19277
  * Absent on legacy rows / tracks with no confident audio. */
19169
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19278
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19279
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19280
+ * Populated from the persisted envelope columns on historical reads;
19281
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19282
+ envelope: TrackEnvelopeSchema.optional()
19170
19283
  });
19171
19284
  var BaseEventFields = {
19172
19285
  id: string(),
@@ -19276,11 +19389,13 @@ var MediaFileSchema = object({
19276
19389
  sizeBytes: number(),
19277
19390
  timestamp: number()
19278
19391
  });
19392
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19393
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19279
19394
  var DeviceEventQueryInput = object({
19280
19395
  deviceId: number(),
19281
19396
  since: number().optional(),
19282
19397
  until: number().optional(),
19283
- limit: number().int().min(1).max(5e3).default(1e3),
19398
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19284
19399
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19285
19400
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19286
19401
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19288,6 +19403,27 @@ var DeviceEventQueryInput = object({
19288
19403
  projection: _enum(["full", "slim"]).optional()
19289
19404
  });
19290
19405
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19406
+ var RecentTracksQueryInput = object({
19407
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19408
+ deviceIds: array(number()),
19409
+ /** Window lower bound on `lastSeen` (inclusive). */
19410
+ since: number().optional(),
19411
+ /** Window upper bound on `lastSeen` (inclusive). */
19412
+ until: number().optional(),
19413
+ /** Page size. Default 200, max 1000. */
19414
+ limit: number().int().min(1).max(1e3).default(200),
19415
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19416
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19417
+ cursor: string().optional(),
19418
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19419
+ projection: TrackProjectionSchema.optional()
19420
+ });
19421
+ var RecentTracksPageSchema = object({
19422
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19423
+ tracks: array(TrackSchema).readonly(),
19424
+ /** Cursor for the next page, or null when this page is the last. */
19425
+ nextCursor: string().nullable()
19426
+ });
19291
19427
  var KeyEventQueryInput = object({
19292
19428
  deviceId: number(),
19293
19429
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19370,11 +19506,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19370
19506
  deviceId: number(),
19371
19507
  since: number().optional(),
19372
19508
  until: number().optional(),
19373
- limit: number().optional()
19374
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19509
+ limit: number().optional(),
19510
+ /** Spatial filter only tracks whose trajectory intersects the zone
19511
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19512
+ * envelope columns, then precisely tested per position. Tracks with
19513
+ * an unknown envelope (no frame dims at persist time) always match. */
19514
+ zone: TrackZoneFilterSchema.optional(),
19515
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19516
+ * compatible — omitting the field keeps today's exact behaviour). */
19517
+ projection: TrackProjectionSchema.optional()
19518
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19375
19519
  kind: "mutation",
19376
19520
  auth: "admin"
19377
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19521
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19522
+ deviceId: number(),
19523
+ since: number().optional(),
19524
+ until: number().optional(),
19525
+ kinds: array(string()).optional(),
19526
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19527
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19378
19528
  deviceId: number(),
19379
19529
  since: number(),
19380
19530
  until: number(),
@@ -22664,7 +22814,12 @@ var RecordingStorageUsageSchema = object({
22664
22814
  /**
22665
22815
  * Result of locating footage at a wall-clock instant for one device/profile.
22666
22816
  * `segment` carries the covering segment's window; `gap` reports the forward
22667
- * nearest covered edge (`null` past the end of footage / when none exists).
22817
+ * nearest covered edge (`null` past the end of footage / when none exists)
22818
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22819
+ * footage behind the epoch; `null` when none — optional so older providers
22820
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22821
+ * small inter-segment cracks (durMs under-covers the span to the next
22822
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22668
22823
  */
22669
22824
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22670
22825
  kind: literal("segment"),
@@ -22673,7 +22828,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22673
22828
  bytes: number()
22674
22829
  }), object({
22675
22830
  kind: literal("gap"),
22676
- nearestEdgeMs: number().nullable()
22831
+ nearestEdgeMs: number().nullable(),
22832
+ prevEndMs: number().nullable().optional()
22677
22833
  })]);
22678
22834
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22679
22835
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24242,6 +24398,12 @@ Object.freeze({
24242
24398
  addonId: null,
24243
24399
  access: "view"
24244
24400
  },
24401
+ "deviceManager.getLinkedDevices": {
24402
+ capName: "device-manager",
24403
+ capScope: "system",
24404
+ addonId: null,
24405
+ access: "view"
24406
+ },
24245
24407
  "deviceManager.getRoleDisplayDefaults": {
24246
24408
  capName: "device-manager",
24247
24409
  capScope: "system",
@@ -25796,6 +25958,12 @@ Object.freeze({
25796
25958
  addonId: null,
25797
25959
  access: "view"
25798
25960
  },
25961
+ "pipelineAnalytics.getSensorEvents": {
25962
+ capName: "pipeline-analytics",
25963
+ capScope: "device",
25964
+ addonId: null,
25965
+ access: "view"
25966
+ },
25799
25967
  "pipelineAnalytics.getTrack": {
25800
25968
  capName: "pipeline-analytics",
25801
25969
  capScope: "device",
@@ -25808,6 +25976,18 @@ Object.freeze({
25808
25976
  addonId: null,
25809
25977
  access: "view"
25810
25978
  },
25979
+ "pipelineAnalytics.listEventKinds": {
25980
+ capName: "pipeline-analytics",
25981
+ capScope: "device",
25982
+ addonId: null,
25983
+ access: "view"
25984
+ },
25985
+ "pipelineAnalytics.listRecentTracks": {
25986
+ capName: "pipeline-analytics",
25987
+ capScope: "device",
25988
+ addonId: null,
25989
+ access: "view"
25990
+ },
25811
25991
  "pipelineAnalytics.listTracks": {
25812
25992
  capName: "pipeline-analytics",
25813
25993
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.1.27",
3
+ "version": "1.1.28",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",