@camstack/addon-matter-broker 0.1.20 → 0.1.22

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 +220 -15
  2. package/dist/addon.mjs +220 -15
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13,7 +13,7 @@ let node_stream_promises = require("node:stream/promises");
13
13
  let node_net = require("node:net");
14
14
  let node_dgram = require("node:dgram");
15
15
  node_dgram = require_esm.__toESM(node_dgram, 1);
16
- //#region ../types/dist/event-category-CFZs3jI4.mjs
16
+ //#region ../types/dist/event-category-H4AVePnn.mjs
17
17
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
18
18
  EventCategory["SystemBoot"] = "system.boot";
19
19
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -163,6 +163,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
163
163
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
164
164
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
165
165
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
166
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
167
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
168
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
166
169
  EventCategory["DetectionEvent"] = "detection.event";
167
170
  EventCategory["SessionTrackNew"] = "session.track.new";
168
171
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7262,6 +7265,24 @@ var RecordingRetentionSchema = object({
7262
7265
  maxSizeGb: number().min(0).optional()
7263
7266
  });
7264
7267
  /**
7268
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7269
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7270
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7271
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7272
+ *
7273
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7274
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7275
+ * preset changed over time renders every historical window at the dims it was
7276
+ * written with.
7277
+ */
7278
+ var ScrubThumbnailPresetSchema = _enum([
7279
+ "minimal",
7280
+ "low",
7281
+ "standard",
7282
+ "high",
7283
+ "max"
7284
+ ]);
7285
+ /**
7265
7286
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7266
7287
  *
7267
7288
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7298,7 +7319,14 @@ var RecordingConfigSchema = object({
7298
7319
  * derived into bands once via `migrateConfigToBands`.
7299
7320
  */
7300
7321
  bands: array(RecordingBandSchema).optional(),
7301
- retention: RecordingRetentionSchema.optional()
7322
+ retention: RecordingRetentionSchema.optional(),
7323
+ /**
7324
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7325
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7326
+ * windows only — existing sheets are immutable, and each window's index
7327
+ * carries its own tile dims so mixed-preset history renders correctly.
7328
+ */
7329
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7302
7330
  });
7303
7331
  /**
7304
7332
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10810,10 +10838,23 @@ var deviceDiscoveryCapability = {
10810
10838
  }
10811
10839
  };
10812
10840
  /**
10813
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10814
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10815
- * time the firmware pushes a ring; status tracks the last press and
10816
- * a pressCount since start (diagnostic).
10841
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10842
+ * name (same pattern as `snapshot`):
10843
+ *
10844
+ * - **Native** providers: registered per-device by device-driver
10845
+ * addons via `ctx.registerNativeCap` — either on a
10846
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10847
+ * or directly on the camera (Reolink registers at camera level).
10848
+ * Emits an `onPressed` event every time the firmware pushes a
10849
+ * ring; status tracks the last press and a pressCount since start
10850
+ * (diagnostic).
10851
+ *
10852
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10853
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10854
+ * device (contact / switch / event-emitter …) into a doorbell for
10855
+ * a camera. `defaultActive: false` — the operator explicitly binds
10856
+ * it per camera in the device-bindings UI, then picks the source
10857
+ * device + trigger in the per-device settings.
10817
10858
  *
10818
10859
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10819
10860
  * — subscribers listening at the camera level receive ring events
@@ -10834,7 +10875,10 @@ var doorbellCapability = {
10834
10875
  scope: "device",
10835
10876
  deviceNative: true,
10836
10877
  mode: "singleton",
10837
- deviceTypes: [DeviceType.Button],
10878
+ kind: "wrapper",
10879
+ defaultActive: false,
10880
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10881
+ exposesDeviceSettings: true,
10838
10882
  methods: {},
10839
10883
  events: {
10840
10884
  /**
@@ -17252,6 +17296,14 @@ var ConfigEntrySchema = object({
17252
17296
  value: unknown(),
17253
17297
  description: string$2().optional()
17254
17298
  });
17299
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17300
+ /** One resolved linked device — the compact projection consumers need. */
17301
+ var LinkedDeviceSchema = object({
17302
+ deviceId: number(),
17303
+ name: string$2(),
17304
+ location: string$2().nullable(),
17305
+ features: array(string$2())
17306
+ });
17255
17307
  var SavedDeviceRowSchema = object({
17256
17308
  /** Numeric id reserved at allocateDeviceId time. */
17257
17309
  id: number(),
@@ -17473,7 +17525,10 @@ method(object({
17473
17525
  }), _void(), {
17474
17526
  kind: "mutation",
17475
17527
  auth: "admin"
17476
- }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().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({
17528
+ }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
17529
+ mode: DeviceLinkModeSchema,
17530
+ devices: array(LinkedDeviceSchema)
17531
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17477
17532
  deviceId: number(),
17478
17533
  values: record(string$2(), unknown())
17479
17534
  }), object({ success: literal(true) }), {
@@ -18742,6 +18797,61 @@ var EventKindSchema = _enum([
18742
18797
  "object",
18743
18798
  "audio"
18744
18799
  ]);
18800
+ /**
18801
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18802
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18803
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18804
+ */
18805
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18806
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18807
+ var EventKindIconSchema = _enum([
18808
+ "motion",
18809
+ "audio",
18810
+ "person",
18811
+ "vehicle",
18812
+ "animal",
18813
+ "door",
18814
+ "pir",
18815
+ "smoke",
18816
+ "water",
18817
+ "button",
18818
+ "generic"
18819
+ ]);
18820
+ var EventKindCategorySchema = _enum([
18821
+ "motion",
18822
+ "audio",
18823
+ "detection",
18824
+ "sensor",
18825
+ "custom"
18826
+ ]);
18827
+ var EventKindDescriptorSchema = object({
18828
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18829
+ kind: string$2(),
18830
+ label: string$2(),
18831
+ /** Hex color for timeline/legend rendering. */
18832
+ color: string$2(),
18833
+ icon: EventKindIconSchema,
18834
+ category: EventKindCategorySchema,
18835
+ /** Which cap + device contributes this kind. For built-ins the camera
18836
+ * itself; for sensor kinds the LINKED source device. */
18837
+ source: object({
18838
+ capName: string$2(),
18839
+ deviceId: number()
18840
+ })
18841
+ });
18842
+ var SensorEventSchema = object({
18843
+ id: string$2(),
18844
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18845
+ * yields N rows, one per camera). */
18846
+ deviceId: number(),
18847
+ /** The linked sensor device whose state changed. */
18848
+ sourceDeviceId: number(),
18849
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18850
+ kind: string$2(),
18851
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18852
+ value: record(string$2(), unknown()).nullable(),
18853
+ timestamp: number()
18854
+ });
18745
18855
  var TrackPositionSchema = object({
18746
18856
  x: number(),
18747
18857
  y: number(),
@@ -18755,6 +18865,30 @@ var TrackSnapshotSchema = object({
18755
18865
  mediaKey: string$2()
18756
18866
  });
18757
18867
  /**
18868
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18869
+ * divided by the track's detection-frame dims), computed at persist time.
18870
+ * Absent when the frame dims were unknown when the track was persisted
18871
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18872
+ */
18873
+ var TrackEnvelopeSchema = object({
18874
+ minX: number(),
18875
+ minY: number(),
18876
+ maxX: number(),
18877
+ maxY: number()
18878
+ });
18879
+ /**
18880
+ * Row projection for track list queries. `full` (default) returns the
18881
+ * complete Track including the frame-rate `positions[]` history and the
18882
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18883
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18884
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18885
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18886
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18887
+ * `getTrack`. Mirrors the event-store `projection` convention
18888
+ * (`getObjectEvents` et al.).
18889
+ */
18890
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18891
+ /**
18758
18892
  * One audio-classification label heard on the track's camera while the
18759
18893
  * track was alive, aggregated per label. An "episode" is one persisted
18760
18894
  * audio event (the confident-classification path: score ≥ the device's
@@ -18805,7 +18939,11 @@ var TrackSchema = object({
18805
18939
  /** Audio-classification labels heard on the camera during the track's
18806
18940
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18807
18941
  * Absent on legacy rows / tracks with no confident audio. */
18808
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18942
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18943
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18944
+ * Populated from the persisted envelope columns on historical reads;
18945
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18946
+ envelope: TrackEnvelopeSchema.optional()
18809
18947
  });
18810
18948
  var BaseEventFields = {
18811
18949
  id: string$2(),
@@ -18915,11 +19053,13 @@ var MediaFileSchema = object({
18915
19053
  sizeBytes: number(),
18916
19054
  timestamp: number()
18917
19055
  });
19056
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19057
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18918
19058
  var DeviceEventQueryInput = object({
18919
19059
  deviceId: number(),
18920
19060
  since: number().optional(),
18921
19061
  until: number().optional(),
18922
- limit: number().int().min(1).max(5e3).default(1e3),
19062
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18923
19063
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18924
19064
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18925
19065
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18927,6 +19067,27 @@ var DeviceEventQueryInput = object({
18927
19067
  projection: _enum(["full", "slim"]).optional()
18928
19068
  });
18929
19069
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string$2().optional() });
19070
+ var RecentTracksQueryInput = object({
19071
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19072
+ deviceIds: array(number()),
19073
+ /** Window lower bound on `lastSeen` (inclusive). */
19074
+ since: number().optional(),
19075
+ /** Window upper bound on `lastSeen` (inclusive). */
19076
+ until: number().optional(),
19077
+ /** Page size. Default 200, max 1000. */
19078
+ limit: number().int().min(1).max(1e3).default(200),
19079
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19080
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19081
+ cursor: string$2().optional(),
19082
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19083
+ projection: TrackProjectionSchema.optional()
19084
+ });
19085
+ var RecentTracksPageSchema = object({
19086
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19087
+ tracks: array(TrackSchema).readonly(),
19088
+ /** Cursor for the next page, or null when this page is the last. */
19089
+ nextCursor: string$2().nullable()
19090
+ });
18930
19091
  var KeyEventQueryInput = object({
18931
19092
  deviceId: number(),
18932
19093
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19009,11 +19170,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19009
19170
  deviceId: number(),
19010
19171
  since: number().optional(),
19011
19172
  until: number().optional(),
19012
- limit: number().optional()
19013
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19173
+ limit: number().optional(),
19174
+ /** Spatial filter only tracks whose trajectory intersects the zone
19175
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19176
+ * envelope columns, then precisely tested per position. Tracks with
19177
+ * an unknown envelope (no frame dims at persist time) always match. */
19178
+ zone: TrackZoneFilterSchema.optional(),
19179
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19180
+ * compatible — omitting the field keeps today's exact behaviour). */
19181
+ projection: TrackProjectionSchema.optional()
19182
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19014
19183
  kind: "mutation",
19015
19184
  auth: "admin"
19016
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19185
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19186
+ deviceId: number(),
19187
+ since: number().optional(),
19188
+ until: number().optional(),
19189
+ kinds: array(string$2()).optional(),
19190
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19191
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19017
19192
  deviceId: number(),
19018
19193
  since: number(),
19019
19194
  until: number(),
@@ -22231,7 +22406,12 @@ var RecordingStorageUsageSchema = object({
22231
22406
  /**
22232
22407
  * Result of locating footage at a wall-clock instant for one device/profile.
22233
22408
  * `segment` carries the covering segment's window; `gap` reports the forward
22234
- * nearest covered edge (`null` past the end of footage / when none exists).
22409
+ * nearest covered edge (`null` past the end of footage / when none exists)
22410
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22411
+ * footage behind the epoch; `null` when none — optional so older providers
22412
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22413
+ * small inter-segment cracks (durMs under-covers the span to the next
22414
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22235
22415
  */
22236
22416
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22237
22417
  kind: literal("segment"),
@@ -22240,7 +22420,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22240
22420
  bytes: number()
22241
22421
  }), object({
22242
22422
  kind: literal("gap"),
22243
- nearestEdgeMs: number().nullable()
22423
+ nearestEdgeMs: number().nullable(),
22424
+ prevEndMs: number().nullable().optional()
22244
22425
  })]);
22245
22426
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22246
22427
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23809,6 +23990,12 @@ Object.freeze({
23809
23990
  addonId: null,
23810
23991
  access: "view"
23811
23992
  },
23993
+ "deviceManager.getLinkedDevices": {
23994
+ capName: "device-manager",
23995
+ capScope: "system",
23996
+ addonId: null,
23997
+ access: "view"
23998
+ },
23812
23999
  "deviceManager.getRoleDisplayDefaults": {
23813
24000
  capName: "device-manager",
23814
24001
  capScope: "system",
@@ -25363,6 +25550,12 @@ Object.freeze({
25363
25550
  addonId: null,
25364
25551
  access: "view"
25365
25552
  },
25553
+ "pipelineAnalytics.getSensorEvents": {
25554
+ capName: "pipeline-analytics",
25555
+ capScope: "device",
25556
+ addonId: null,
25557
+ access: "view"
25558
+ },
25366
25559
  "pipelineAnalytics.getTrack": {
25367
25560
  capName: "pipeline-analytics",
25368
25561
  capScope: "device",
@@ -25375,6 +25568,18 @@ Object.freeze({
25375
25568
  addonId: null,
25376
25569
  access: "view"
25377
25570
  },
25571
+ "pipelineAnalytics.listEventKinds": {
25572
+ capName: "pipeline-analytics",
25573
+ capScope: "device",
25574
+ addonId: null,
25575
+ access: "view"
25576
+ },
25577
+ "pipelineAnalytics.listRecentTracks": {
25578
+ capName: "pipeline-analytics",
25579
+ capScope: "device",
25580
+ addonId: null,
25581
+ access: "view"
25582
+ },
25378
25583
  "pipelineAnalytics.listTracks": {
25379
25584
  capName: "pipeline-analytics",
25380
25585
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -11,7 +11,7 @@ import { networkInterfaces, tmpdir, uptime } from "node:os";
11
11
  import { finished } from "node:stream/promises";
12
12
  import { createConnection, createServer as createServer$2 } from "node:net";
13
13
  import * as dgram from "node:dgram";
14
- //#region ../types/dist/event-category-CFZs3jI4.mjs
14
+ //#region ../types/dist/event-category-H4AVePnn.mjs
15
15
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
16
16
  EventCategory["SystemBoot"] = "system.boot";
17
17
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -161,6 +161,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
161
161
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
162
162
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
163
163
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
164
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
165
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
166
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
164
167
  EventCategory["DetectionEvent"] = "detection.event";
165
168
  EventCategory["SessionTrackNew"] = "session.track.new";
166
169
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7260,6 +7263,24 @@ var RecordingRetentionSchema = object({
7260
7263
  maxSizeGb: number().min(0).optional()
7261
7264
  });
7262
7265
  /**
7266
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7267
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7268
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7269
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7270
+ *
7271
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7272
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7273
+ * preset changed over time renders every historical window at the dims it was
7274
+ * written with.
7275
+ */
7276
+ var ScrubThumbnailPresetSchema = _enum([
7277
+ "minimal",
7278
+ "low",
7279
+ "standard",
7280
+ "high",
7281
+ "max"
7282
+ ]);
7283
+ /**
7263
7284
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7264
7285
  *
7265
7286
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7296,7 +7317,14 @@ var RecordingConfigSchema = object({
7296
7317
  * derived into bands once via `migrateConfigToBands`.
7297
7318
  */
7298
7319
  bands: array(RecordingBandSchema).optional(),
7299
- retention: RecordingRetentionSchema.optional()
7320
+ retention: RecordingRetentionSchema.optional(),
7321
+ /**
7322
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7323
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7324
+ * windows only — existing sheets are immutable, and each window's index
7325
+ * carries its own tile dims so mixed-preset history renders correctly.
7326
+ */
7327
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7300
7328
  });
7301
7329
  /**
7302
7330
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10808,10 +10836,23 @@ var deviceDiscoveryCapability = {
10808
10836
  }
10809
10837
  };
10810
10838
  /**
10811
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10812
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10813
- * time the firmware pushes a ring; status tracks the last press and
10814
- * a pressCount since start (diagnostic).
10839
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10840
+ * name (same pattern as `snapshot`):
10841
+ *
10842
+ * - **Native** providers: registered per-device by device-driver
10843
+ * addons via `ctx.registerNativeCap` — either on a
10844
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10845
+ * or directly on the camera (Reolink registers at camera level).
10846
+ * Emits an `onPressed` event every time the firmware pushes a
10847
+ * ring; status tracks the last press and a pressCount since start
10848
+ * (diagnostic).
10849
+ *
10850
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10851
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10852
+ * device (contact / switch / event-emitter …) into a doorbell for
10853
+ * a camera. `defaultActive: false` — the operator explicitly binds
10854
+ * it per camera in the device-bindings UI, then picks the source
10855
+ * device + trigger in the per-device settings.
10815
10856
  *
10816
10857
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10817
10858
  * — subscribers listening at the camera level receive ring events
@@ -10832,7 +10873,10 @@ var doorbellCapability = {
10832
10873
  scope: "device",
10833
10874
  deviceNative: true,
10834
10875
  mode: "singleton",
10835
- deviceTypes: [DeviceType.Button],
10876
+ kind: "wrapper",
10877
+ defaultActive: false,
10878
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10879
+ exposesDeviceSettings: true,
10836
10880
  methods: {},
10837
10881
  events: {
10838
10882
  /**
@@ -17250,6 +17294,14 @@ var ConfigEntrySchema = object({
17250
17294
  value: unknown(),
17251
17295
  description: string$2().optional()
17252
17296
  });
17297
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17298
+ /** One resolved linked device — the compact projection consumers need. */
17299
+ var LinkedDeviceSchema = object({
17300
+ deviceId: number(),
17301
+ name: string$2(),
17302
+ location: string$2().nullable(),
17303
+ features: array(string$2())
17304
+ });
17253
17305
  var SavedDeviceRowSchema = object({
17254
17306
  /** Numeric id reserved at allocateDeviceId time. */
17255
17307
  id: number(),
@@ -17471,7 +17523,10 @@ method(object({
17471
17523
  }), _void(), {
17472
17524
  kind: "mutation",
17473
17525
  auth: "admin"
17474
- }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().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({
17526
+ }), method(object({ addonId: string$2() }), array(SavedDeviceRowSchema)), method(object({ addonId: string$2().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
17527
+ mode: DeviceLinkModeSchema,
17528
+ devices: array(LinkedDeviceSchema)
17529
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17475
17530
  deviceId: number(),
17476
17531
  values: record(string$2(), unknown())
17477
17532
  }), object({ success: literal(true) }), {
@@ -18740,6 +18795,61 @@ var EventKindSchema = _enum([
18740
18795
  "object",
18741
18796
  "audio"
18742
18797
  ]);
18798
+ /**
18799
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18800
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18801
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18802
+ */
18803
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18804
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18805
+ var EventKindIconSchema = _enum([
18806
+ "motion",
18807
+ "audio",
18808
+ "person",
18809
+ "vehicle",
18810
+ "animal",
18811
+ "door",
18812
+ "pir",
18813
+ "smoke",
18814
+ "water",
18815
+ "button",
18816
+ "generic"
18817
+ ]);
18818
+ var EventKindCategorySchema = _enum([
18819
+ "motion",
18820
+ "audio",
18821
+ "detection",
18822
+ "sensor",
18823
+ "custom"
18824
+ ]);
18825
+ var EventKindDescriptorSchema = object({
18826
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18827
+ kind: string$2(),
18828
+ label: string$2(),
18829
+ /** Hex color for timeline/legend rendering. */
18830
+ color: string$2(),
18831
+ icon: EventKindIconSchema,
18832
+ category: EventKindCategorySchema,
18833
+ /** Which cap + device contributes this kind. For built-ins the camera
18834
+ * itself; for sensor kinds the LINKED source device. */
18835
+ source: object({
18836
+ capName: string$2(),
18837
+ deviceId: number()
18838
+ })
18839
+ });
18840
+ var SensorEventSchema = object({
18841
+ id: string$2(),
18842
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18843
+ * yields N rows, one per camera). */
18844
+ deviceId: number(),
18845
+ /** The linked sensor device whose state changed. */
18846
+ sourceDeviceId: number(),
18847
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18848
+ kind: string$2(),
18849
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18850
+ value: record(string$2(), unknown()).nullable(),
18851
+ timestamp: number()
18852
+ });
18743
18853
  var TrackPositionSchema = object({
18744
18854
  x: number(),
18745
18855
  y: number(),
@@ -18753,6 +18863,30 @@ var TrackSnapshotSchema = object({
18753
18863
  mediaKey: string$2()
18754
18864
  });
18755
18865
  /**
18866
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18867
+ * divided by the track's detection-frame dims), computed at persist time.
18868
+ * Absent when the frame dims were unknown when the track was persisted
18869
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18870
+ */
18871
+ var TrackEnvelopeSchema = object({
18872
+ minX: number(),
18873
+ minY: number(),
18874
+ maxX: number(),
18875
+ maxY: number()
18876
+ });
18877
+ /**
18878
+ * Row projection for track list queries. `full` (default) returns the
18879
+ * complete Track including the frame-rate `positions[]` history and the
18880
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18881
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18882
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18883
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18884
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18885
+ * `getTrack`. Mirrors the event-store `projection` convention
18886
+ * (`getObjectEvents` et al.).
18887
+ */
18888
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18889
+ /**
18756
18890
  * One audio-classification label heard on the track's camera while the
18757
18891
  * track was alive, aggregated per label. An "episode" is one persisted
18758
18892
  * audio event (the confident-classification path: score ≥ the device's
@@ -18803,7 +18937,11 @@ var TrackSchema = object({
18803
18937
  /** Audio-classification labels heard on the camera during the track's
18804
18938
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18805
18939
  * Absent on legacy rows / tracks with no confident audio. */
18806
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18940
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18941
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18942
+ * Populated from the persisted envelope columns on historical reads;
18943
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18944
+ envelope: TrackEnvelopeSchema.optional()
18807
18945
  });
18808
18946
  var BaseEventFields = {
18809
18947
  id: string$2(),
@@ -18913,11 +19051,13 @@ var MediaFileSchema = object({
18913
19051
  sizeBytes: number(),
18914
19052
  timestamp: number()
18915
19053
  });
19054
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19055
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18916
19056
  var DeviceEventQueryInput = object({
18917
19057
  deviceId: number(),
18918
19058
  since: number().optional(),
18919
19059
  until: number().optional(),
18920
- limit: number().int().min(1).max(5e3).default(1e3),
19060
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18921
19061
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18922
19062
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18923
19063
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18925,6 +19065,27 @@ var DeviceEventQueryInput = object({
18925
19065
  projection: _enum(["full", "slim"]).optional()
18926
19066
  });
18927
19067
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string$2().optional() });
19068
+ var RecentTracksQueryInput = object({
19069
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19070
+ deviceIds: array(number()),
19071
+ /** Window lower bound on `lastSeen` (inclusive). */
19072
+ since: number().optional(),
19073
+ /** Window upper bound on `lastSeen` (inclusive). */
19074
+ until: number().optional(),
19075
+ /** Page size. Default 200, max 1000. */
19076
+ limit: number().int().min(1).max(1e3).default(200),
19077
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19078
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19079
+ cursor: string$2().optional(),
19080
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19081
+ projection: TrackProjectionSchema.optional()
19082
+ });
19083
+ var RecentTracksPageSchema = object({
19084
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19085
+ tracks: array(TrackSchema).readonly(),
19086
+ /** Cursor for the next page, or null when this page is the last. */
19087
+ nextCursor: string$2().nullable()
19088
+ });
18928
19089
  var KeyEventQueryInput = object({
18929
19090
  deviceId: number(),
18930
19091
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19007,11 +19168,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19007
19168
  deviceId: number(),
19008
19169
  since: number().optional(),
19009
19170
  until: number().optional(),
19010
- limit: number().optional()
19011
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19171
+ limit: number().optional(),
19172
+ /** Spatial filter only tracks whose trajectory intersects the zone
19173
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19174
+ * envelope columns, then precisely tested per position. Tracks with
19175
+ * an unknown envelope (no frame dims at persist time) always match. */
19176
+ zone: TrackZoneFilterSchema.optional(),
19177
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19178
+ * compatible — omitting the field keeps today's exact behaviour). */
19179
+ projection: TrackProjectionSchema.optional()
19180
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19012
19181
  kind: "mutation",
19013
19182
  auth: "admin"
19014
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19183
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19184
+ deviceId: number(),
19185
+ since: number().optional(),
19186
+ until: number().optional(),
19187
+ kinds: array(string$2()).optional(),
19188
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19189
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19015
19190
  deviceId: number(),
19016
19191
  since: number(),
19017
19192
  until: number(),
@@ -22229,7 +22404,12 @@ var RecordingStorageUsageSchema = object({
22229
22404
  /**
22230
22405
  * Result of locating footage at a wall-clock instant for one device/profile.
22231
22406
  * `segment` carries the covering segment's window; `gap` reports the forward
22232
- * nearest covered edge (`null` past the end of footage / when none exists).
22407
+ * nearest covered edge (`null` past the end of footage / when none exists)
22408
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22409
+ * footage behind the epoch; `null` when none — optional so older providers
22410
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22411
+ * small inter-segment cracks (durMs under-covers the span to the next
22412
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22233
22413
  */
22234
22414
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22235
22415
  kind: literal("segment"),
@@ -22238,7 +22418,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22238
22418
  bytes: number()
22239
22419
  }), object({
22240
22420
  kind: literal("gap"),
22241
- nearestEdgeMs: number().nullable()
22421
+ nearestEdgeMs: number().nullable(),
22422
+ prevEndMs: number().nullable().optional()
22242
22423
  })]);
22243
22424
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22244
22425
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23807,6 +23988,12 @@ Object.freeze({
23807
23988
  addonId: null,
23808
23989
  access: "view"
23809
23990
  },
23991
+ "deviceManager.getLinkedDevices": {
23992
+ capName: "device-manager",
23993
+ capScope: "system",
23994
+ addonId: null,
23995
+ access: "view"
23996
+ },
23810
23997
  "deviceManager.getRoleDisplayDefaults": {
23811
23998
  capName: "device-manager",
23812
23999
  capScope: "system",
@@ -25361,6 +25548,12 @@ Object.freeze({
25361
25548
  addonId: null,
25362
25549
  access: "view"
25363
25550
  },
25551
+ "pipelineAnalytics.getSensorEvents": {
25552
+ capName: "pipeline-analytics",
25553
+ capScope: "device",
25554
+ addonId: null,
25555
+ access: "view"
25556
+ },
25364
25557
  "pipelineAnalytics.getTrack": {
25365
25558
  capName: "pipeline-analytics",
25366
25559
  capScope: "device",
@@ -25373,6 +25566,18 @@ Object.freeze({
25373
25566
  addonId: null,
25374
25567
  access: "view"
25375
25568
  },
25569
+ "pipelineAnalytics.listEventKinds": {
25570
+ capName: "pipeline-analytics",
25571
+ capScope: "device",
25572
+ addonId: null,
25573
+ access: "view"
25574
+ },
25575
+ "pipelineAnalytics.listRecentTracks": {
25576
+ capName: "pipeline-analytics",
25577
+ capScope: "device",
25578
+ addonId: null,
25579
+ access: "view"
25580
+ },
25376
25581
  "pipelineAnalytics.listTracks": {
25377
25582
  capName: "pipeline-analytics",
25378
25583
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",