@camstack/addon-provider-rtsp 1.1.23 → 1.1.25

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
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let node_net = require("node:net");
25
25
  node_net = __toESM(node_net);
26
- //#region ../types/dist/event-category-CFZs3jI4.mjs
26
+ //#region ../types/dist/event-category-H4AVePnn.mjs
27
27
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
28
28
  EventCategory["SystemBoot"] = "system.boot";
29
29
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -173,6 +173,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
173
173
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
174
174
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
175
175
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
176
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
177
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
178
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
176
179
  EventCategory["DetectionEvent"] = "detection.event";
177
180
  EventCategory["SessionTrackNew"] = "session.track.new";
178
181
  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
@@ -10906,10 +10934,23 @@ var deviceDiscoveryCapability = {
10906
10934
  }
10907
10935
  };
10908
10936
  /**
10909
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10910
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10911
- * time the firmware pushes a ring; status tracks the last press and
10912
- * a pressCount since start (diagnostic).
10937
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10938
+ * name (same pattern as `snapshot`):
10939
+ *
10940
+ * - **Native** providers: registered per-device by device-driver
10941
+ * addons via `ctx.registerNativeCap` — either on a
10942
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10943
+ * or directly on the camera (Reolink registers at camera level).
10944
+ * Emits an `onPressed` event every time the firmware pushes a
10945
+ * ring; status tracks the last press and a pressCount since start
10946
+ * (diagnostic).
10947
+ *
10948
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10949
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10950
+ * device (contact / switch / event-emitter …) into a doorbell for
10951
+ * a camera. `defaultActive: false` — the operator explicitly binds
10952
+ * it per camera in the device-bindings UI, then picks the source
10953
+ * device + trigger in the per-device settings.
10913
10954
  *
10914
10955
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10915
10956
  * — subscribers listening at the camera level receive ring events
@@ -10930,7 +10971,10 @@ var doorbellCapability = {
10930
10971
  scope: "device",
10931
10972
  deviceNative: true,
10932
10973
  mode: "singleton",
10933
- deviceTypes: [DeviceType.Button],
10974
+ kind: "wrapper",
10975
+ defaultActive: false,
10976
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10977
+ exposesDeviceSettings: true,
10934
10978
  methods: {},
10935
10979
  events: {
10936
10980
  /**
@@ -17282,6 +17326,14 @@ var ConfigEntrySchema = object({
17282
17326
  value: unknown(),
17283
17327
  description: string().optional()
17284
17328
  });
17329
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17330
+ /** One resolved linked device — the compact projection consumers need. */
17331
+ var LinkedDeviceSchema = object({
17332
+ deviceId: number(),
17333
+ name: string(),
17334
+ location: string().nullable(),
17335
+ features: array(string())
17336
+ });
17285
17337
  var SavedDeviceRowSchema = object({
17286
17338
  /** Numeric id reserved at allocateDeviceId time. */
17287
17339
  id: number(),
@@ -17503,7 +17555,10 @@ method(object({
17503
17555
  }), _void(), {
17504
17556
  kind: "mutation",
17505
17557
  auth: "admin"
17506
- }), 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({
17558
+ }), 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({
17559
+ mode: DeviceLinkModeSchema,
17560
+ devices: array(LinkedDeviceSchema)
17561
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17507
17562
  deviceId: number(),
17508
17563
  values: record(string(), unknown())
17509
17564
  }), object({ success: literal(true) }), {
@@ -18772,6 +18827,61 @@ var EventKindSchema = _enum([
18772
18827
  "object",
18773
18828
  "audio"
18774
18829
  ]);
18830
+ /**
18831
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18832
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18833
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18834
+ */
18835
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18836
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18837
+ var EventKindIconSchema = _enum([
18838
+ "motion",
18839
+ "audio",
18840
+ "person",
18841
+ "vehicle",
18842
+ "animal",
18843
+ "door",
18844
+ "pir",
18845
+ "smoke",
18846
+ "water",
18847
+ "button",
18848
+ "generic"
18849
+ ]);
18850
+ var EventKindCategorySchema = _enum([
18851
+ "motion",
18852
+ "audio",
18853
+ "detection",
18854
+ "sensor",
18855
+ "custom"
18856
+ ]);
18857
+ var EventKindDescriptorSchema = object({
18858
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18859
+ kind: string(),
18860
+ label: string(),
18861
+ /** Hex color for timeline/legend rendering. */
18862
+ color: string(),
18863
+ icon: EventKindIconSchema,
18864
+ category: EventKindCategorySchema,
18865
+ /** Which cap + device contributes this kind. For built-ins the camera
18866
+ * itself; for sensor kinds the LINKED source device. */
18867
+ source: object({
18868
+ capName: string(),
18869
+ deviceId: number()
18870
+ })
18871
+ });
18872
+ var SensorEventSchema = object({
18873
+ id: string(),
18874
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18875
+ * yields N rows, one per camera). */
18876
+ deviceId: number(),
18877
+ /** The linked sensor device whose state changed. */
18878
+ sourceDeviceId: number(),
18879
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18880
+ kind: string(),
18881
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18882
+ value: record(string(), unknown()).nullable(),
18883
+ timestamp: number()
18884
+ });
18775
18885
  var TrackPositionSchema = object({
18776
18886
  x: number(),
18777
18887
  y: number(),
@@ -18785,6 +18895,30 @@ var TrackSnapshotSchema = object({
18785
18895
  mediaKey: string()
18786
18896
  });
18787
18897
  /**
18898
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18899
+ * divided by the track's detection-frame dims), computed at persist time.
18900
+ * Absent when the frame dims were unknown when the track was persisted
18901
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18902
+ */
18903
+ var TrackEnvelopeSchema = object({
18904
+ minX: number(),
18905
+ minY: number(),
18906
+ maxX: number(),
18907
+ maxY: number()
18908
+ });
18909
+ /**
18910
+ * Row projection for track list queries. `full` (default) returns the
18911
+ * complete Track including the frame-rate `positions[]` history and the
18912
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18913
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18914
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18915
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18916
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18917
+ * `getTrack`. Mirrors the event-store `projection` convention
18918
+ * (`getObjectEvents` et al.).
18919
+ */
18920
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18921
+ /**
18788
18922
  * One audio-classification label heard on the track's camera while the
18789
18923
  * track was alive, aggregated per label. An "episode" is one persisted
18790
18924
  * audio event (the confident-classification path: score ≥ the device's
@@ -18835,7 +18969,11 @@ var TrackSchema = object({
18835
18969
  /** Audio-classification labels heard on the camera during the track's
18836
18970
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18837
18971
  * Absent on legacy rows / tracks with no confident audio. */
18838
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18972
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18973
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18974
+ * Populated from the persisted envelope columns on historical reads;
18975
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18976
+ envelope: TrackEnvelopeSchema.optional()
18839
18977
  });
18840
18978
  var BaseEventFields = {
18841
18979
  id: string(),
@@ -18945,11 +19083,13 @@ var MediaFileSchema = object({
18945
19083
  sizeBytes: number(),
18946
19084
  timestamp: number()
18947
19085
  });
19086
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19087
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18948
19088
  var DeviceEventQueryInput = object({
18949
19089
  deviceId: number(),
18950
19090
  since: number().optional(),
18951
19091
  until: number().optional(),
18952
- limit: number().int().min(1).max(5e3).default(1e3),
19092
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18953
19093
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18954
19094
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18955
19095
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18957,6 +19097,27 @@ var DeviceEventQueryInput = object({
18957
19097
  projection: _enum(["full", "slim"]).optional()
18958
19098
  });
18959
19099
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19100
+ var RecentTracksQueryInput = object({
19101
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19102
+ deviceIds: array(number()),
19103
+ /** Window lower bound on `lastSeen` (inclusive). */
19104
+ since: number().optional(),
19105
+ /** Window upper bound on `lastSeen` (inclusive). */
19106
+ until: number().optional(),
19107
+ /** Page size. Default 200, max 1000. */
19108
+ limit: number().int().min(1).max(1e3).default(200),
19109
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19110
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19111
+ cursor: string().optional(),
19112
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19113
+ projection: TrackProjectionSchema.optional()
19114
+ });
19115
+ var RecentTracksPageSchema = object({
19116
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19117
+ tracks: array(TrackSchema).readonly(),
19118
+ /** Cursor for the next page, or null when this page is the last. */
19119
+ nextCursor: string().nullable()
19120
+ });
18960
19121
  var KeyEventQueryInput = object({
18961
19122
  deviceId: number(),
18962
19123
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19039,11 +19200,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19039
19200
  deviceId: number(),
19040
19201
  since: number().optional(),
19041
19202
  until: number().optional(),
19042
- limit: number().optional()
19043
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19203
+ limit: number().optional(),
19204
+ /** Spatial filter only tracks whose trajectory intersects the zone
19205
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19206
+ * envelope columns, then precisely tested per position. Tracks with
19207
+ * an unknown envelope (no frame dims at persist time) always match. */
19208
+ zone: TrackZoneFilterSchema.optional(),
19209
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19210
+ * compatible — omitting the field keeps today's exact behaviour). */
19211
+ projection: TrackProjectionSchema.optional()
19212
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19044
19213
  kind: "mutation",
19045
19214
  auth: "admin"
19046
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19215
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19216
+ deviceId: number(),
19217
+ since: number().optional(),
19218
+ until: number().optional(),
19219
+ kinds: array(string()).optional(),
19220
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19221
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19047
19222
  deviceId: number(),
19048
19223
  since: number(),
19049
19224
  until: number(),
@@ -22295,7 +22470,12 @@ var RecordingStorageUsageSchema = object({
22295
22470
  /**
22296
22471
  * Result of locating footage at a wall-clock instant for one device/profile.
22297
22472
  * `segment` carries the covering segment's window; `gap` reports the forward
22298
- * nearest covered edge (`null` past the end of footage / when none exists).
22473
+ * nearest covered edge (`null` past the end of footage / when none exists)
22474
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22475
+ * footage behind the epoch; `null` when none — optional so older providers
22476
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22477
+ * small inter-segment cracks (durMs under-covers the span to the next
22478
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22299
22479
  */
22300
22480
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22301
22481
  kind: literal("segment"),
@@ -22304,7 +22484,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22304
22484
  bytes: number()
22305
22485
  }), object({
22306
22486
  kind: literal("gap"),
22307
- nearestEdgeMs: number().nullable()
22487
+ nearestEdgeMs: number().nullable(),
22488
+ prevEndMs: number().nullable().optional()
22308
22489
  })]);
22309
22490
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22310
22491
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23886,6 +24067,12 @@ Object.freeze({
23886
24067
  addonId: null,
23887
24068
  access: "view"
23888
24069
  },
24070
+ "deviceManager.getLinkedDevices": {
24071
+ capName: "device-manager",
24072
+ capScope: "system",
24073
+ addonId: null,
24074
+ access: "view"
24075
+ },
23889
24076
  "deviceManager.getRoleDisplayDefaults": {
23890
24077
  capName: "device-manager",
23891
24078
  capScope: "system",
@@ -25440,6 +25627,12 @@ Object.freeze({
25440
25627
  addonId: null,
25441
25628
  access: "view"
25442
25629
  },
25630
+ "pipelineAnalytics.getSensorEvents": {
25631
+ capName: "pipeline-analytics",
25632
+ capScope: "device",
25633
+ addonId: null,
25634
+ access: "view"
25635
+ },
25443
25636
  "pipelineAnalytics.getTrack": {
25444
25637
  capName: "pipeline-analytics",
25445
25638
  capScope: "device",
@@ -25452,6 +25645,18 @@ Object.freeze({
25452
25645
  addonId: null,
25453
25646
  access: "view"
25454
25647
  },
25648
+ "pipelineAnalytics.listEventKinds": {
25649
+ capName: "pipeline-analytics",
25650
+ capScope: "device",
25651
+ addonId: null,
25652
+ access: "view"
25653
+ },
25654
+ "pipelineAnalytics.listRecentTracks": {
25655
+ capName: "pipeline-analytics",
25656
+ capScope: "device",
25657
+ addonId: null,
25658
+ access: "view"
25659
+ },
25455
25660
  "pipelineAnalytics.listTracks": {
25456
25661
  capName: "pipeline-analytics",
25457
25662
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import net from "node:net";
2
- //#region ../types/dist/event-category-CFZs3jI4.mjs
2
+ //#region ../types/dist/event-category-H4AVePnn.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -149,6 +149,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
149
149
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
150
150
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
151
151
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
152
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
153
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
154
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
152
155
  EventCategory["DetectionEvent"] = "detection.event";
153
156
  EventCategory["SessionTrackNew"] = "session.track.new";
154
157
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7236,6 +7239,24 @@ var RecordingRetentionSchema = object({
7236
7239
  maxSizeGb: number().min(0).optional()
7237
7240
  });
7238
7241
  /**
7242
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7243
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7244
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7245
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7246
+ *
7247
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7248
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7249
+ * preset changed over time renders every historical window at the dims it was
7250
+ * written with.
7251
+ */
7252
+ var ScrubThumbnailPresetSchema = _enum([
7253
+ "minimal",
7254
+ "low",
7255
+ "standard",
7256
+ "high",
7257
+ "max"
7258
+ ]);
7259
+ /**
7239
7260
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7240
7261
  *
7241
7262
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7272,7 +7293,14 @@ var RecordingConfigSchema = object({
7272
7293
  * derived into bands once via `migrateConfigToBands`.
7273
7294
  */
7274
7295
  bands: array(RecordingBandSchema).optional(),
7275
- retention: RecordingRetentionSchema.optional()
7296
+ retention: RecordingRetentionSchema.optional(),
7297
+ /**
7298
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7299
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7300
+ * windows only — existing sheets are immutable, and each window's index
7301
+ * carries its own tile dims so mixed-preset history renders correctly.
7302
+ */
7303
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7276
7304
  });
7277
7305
  /**
7278
7306
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10882,10 +10910,23 @@ var deviceDiscoveryCapability = {
10882
10910
  }
10883
10911
  };
10884
10912
  /**
10885
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10886
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10887
- * time the firmware pushes a ring; status tracks the last press and
10888
- * a pressCount since start (diagnostic).
10913
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10914
+ * name (same pattern as `snapshot`):
10915
+ *
10916
+ * - **Native** providers: registered per-device by device-driver
10917
+ * addons via `ctx.registerNativeCap` — either on a
10918
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10919
+ * or directly on the camera (Reolink registers at camera level).
10920
+ * Emits an `onPressed` event every time the firmware pushes a
10921
+ * ring; status tracks the last press and a pressCount since start
10922
+ * (diagnostic).
10923
+ *
10924
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10925
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10926
+ * device (contact / switch / event-emitter …) into a doorbell for
10927
+ * a camera. `defaultActive: false` — the operator explicitly binds
10928
+ * it per camera in the device-bindings UI, then picks the source
10929
+ * device + trigger in the per-device settings.
10889
10930
  *
10890
10931
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10891
10932
  * — subscribers listening at the camera level receive ring events
@@ -10906,7 +10947,10 @@ var doorbellCapability = {
10906
10947
  scope: "device",
10907
10948
  deviceNative: true,
10908
10949
  mode: "singleton",
10909
- deviceTypes: [DeviceType.Button],
10950
+ kind: "wrapper",
10951
+ defaultActive: false,
10952
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10953
+ exposesDeviceSettings: true,
10910
10954
  methods: {},
10911
10955
  events: {
10912
10956
  /**
@@ -17258,6 +17302,14 @@ var ConfigEntrySchema = object({
17258
17302
  value: unknown(),
17259
17303
  description: string().optional()
17260
17304
  });
17305
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17306
+ /** One resolved linked device — the compact projection consumers need. */
17307
+ var LinkedDeviceSchema = object({
17308
+ deviceId: number(),
17309
+ name: string(),
17310
+ location: string().nullable(),
17311
+ features: array(string())
17312
+ });
17261
17313
  var SavedDeviceRowSchema = object({
17262
17314
  /** Numeric id reserved at allocateDeviceId time. */
17263
17315
  id: number(),
@@ -17479,7 +17531,10 @@ method(object({
17479
17531
  }), _void(), {
17480
17532
  kind: "mutation",
17481
17533
  auth: "admin"
17482
- }), 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({
17534
+ }), 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({
17535
+ mode: DeviceLinkModeSchema,
17536
+ devices: array(LinkedDeviceSchema)
17537
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17483
17538
  deviceId: number(),
17484
17539
  values: record(string(), unknown())
17485
17540
  }), object({ success: literal(true) }), {
@@ -18748,6 +18803,61 @@ var EventKindSchema = _enum([
18748
18803
  "object",
18749
18804
  "audio"
18750
18805
  ]);
18806
+ /**
18807
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18808
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18809
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18810
+ */
18811
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18812
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18813
+ var EventKindIconSchema = _enum([
18814
+ "motion",
18815
+ "audio",
18816
+ "person",
18817
+ "vehicle",
18818
+ "animal",
18819
+ "door",
18820
+ "pir",
18821
+ "smoke",
18822
+ "water",
18823
+ "button",
18824
+ "generic"
18825
+ ]);
18826
+ var EventKindCategorySchema = _enum([
18827
+ "motion",
18828
+ "audio",
18829
+ "detection",
18830
+ "sensor",
18831
+ "custom"
18832
+ ]);
18833
+ var EventKindDescriptorSchema = object({
18834
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18835
+ kind: string(),
18836
+ label: string(),
18837
+ /** Hex color for timeline/legend rendering. */
18838
+ color: string(),
18839
+ icon: EventKindIconSchema,
18840
+ category: EventKindCategorySchema,
18841
+ /** Which cap + device contributes this kind. For built-ins the camera
18842
+ * itself; for sensor kinds the LINKED source device. */
18843
+ source: object({
18844
+ capName: string(),
18845
+ deviceId: number()
18846
+ })
18847
+ });
18848
+ var SensorEventSchema = object({
18849
+ id: string(),
18850
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18851
+ * yields N rows, one per camera). */
18852
+ deviceId: number(),
18853
+ /** The linked sensor device whose state changed. */
18854
+ sourceDeviceId: number(),
18855
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18856
+ kind: string(),
18857
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18858
+ value: record(string(), unknown()).nullable(),
18859
+ timestamp: number()
18860
+ });
18751
18861
  var TrackPositionSchema = object({
18752
18862
  x: number(),
18753
18863
  y: number(),
@@ -18761,6 +18871,30 @@ var TrackSnapshotSchema = object({
18761
18871
  mediaKey: string()
18762
18872
  });
18763
18873
  /**
18874
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18875
+ * divided by the track's detection-frame dims), computed at persist time.
18876
+ * Absent when the frame dims were unknown when the track was persisted
18877
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18878
+ */
18879
+ var TrackEnvelopeSchema = object({
18880
+ minX: number(),
18881
+ minY: number(),
18882
+ maxX: number(),
18883
+ maxY: number()
18884
+ });
18885
+ /**
18886
+ * Row projection for track list queries. `full` (default) returns the
18887
+ * complete Track including the frame-rate `positions[]` history and the
18888
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18889
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18890
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18891
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18892
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18893
+ * `getTrack`. Mirrors the event-store `projection` convention
18894
+ * (`getObjectEvents` et al.).
18895
+ */
18896
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18897
+ /**
18764
18898
  * One audio-classification label heard on the track's camera while the
18765
18899
  * track was alive, aggregated per label. An "episode" is one persisted
18766
18900
  * audio event (the confident-classification path: score ≥ the device's
@@ -18811,7 +18945,11 @@ var TrackSchema = object({
18811
18945
  /** Audio-classification labels heard on the camera during the track's
18812
18946
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18813
18947
  * Absent on legacy rows / tracks with no confident audio. */
18814
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18948
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18949
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18950
+ * Populated from the persisted envelope columns on historical reads;
18951
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18952
+ envelope: TrackEnvelopeSchema.optional()
18815
18953
  });
18816
18954
  var BaseEventFields = {
18817
18955
  id: string(),
@@ -18921,11 +19059,13 @@ var MediaFileSchema = object({
18921
19059
  sizeBytes: number(),
18922
19060
  timestamp: number()
18923
19061
  });
19062
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19063
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18924
19064
  var DeviceEventQueryInput = object({
18925
19065
  deviceId: number(),
18926
19066
  since: number().optional(),
18927
19067
  until: number().optional(),
18928
- limit: number().int().min(1).max(5e3).default(1e3),
19068
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18929
19069
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18930
19070
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18931
19071
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18933,6 +19073,27 @@ var DeviceEventQueryInput = object({
18933
19073
  projection: _enum(["full", "slim"]).optional()
18934
19074
  });
18935
19075
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19076
+ var RecentTracksQueryInput = object({
19077
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19078
+ deviceIds: array(number()),
19079
+ /** Window lower bound on `lastSeen` (inclusive). */
19080
+ since: number().optional(),
19081
+ /** Window upper bound on `lastSeen` (inclusive). */
19082
+ until: number().optional(),
19083
+ /** Page size. Default 200, max 1000. */
19084
+ limit: number().int().min(1).max(1e3).default(200),
19085
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19086
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19087
+ cursor: string().optional(),
19088
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19089
+ projection: TrackProjectionSchema.optional()
19090
+ });
19091
+ var RecentTracksPageSchema = object({
19092
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19093
+ tracks: array(TrackSchema).readonly(),
19094
+ /** Cursor for the next page, or null when this page is the last. */
19095
+ nextCursor: string().nullable()
19096
+ });
18936
19097
  var KeyEventQueryInput = object({
18937
19098
  deviceId: number(),
18938
19099
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19015,11 +19176,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19015
19176
  deviceId: number(),
19016
19177
  since: number().optional(),
19017
19178
  until: number().optional(),
19018
- limit: number().optional()
19019
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19179
+ limit: number().optional(),
19180
+ /** Spatial filter only tracks whose trajectory intersects the zone
19181
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19182
+ * envelope columns, then precisely tested per position. Tracks with
19183
+ * an unknown envelope (no frame dims at persist time) always match. */
19184
+ zone: TrackZoneFilterSchema.optional(),
19185
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19186
+ * compatible — omitting the field keeps today's exact behaviour). */
19187
+ projection: TrackProjectionSchema.optional()
19188
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19020
19189
  kind: "mutation",
19021
19190
  auth: "admin"
19022
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19191
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19192
+ deviceId: number(),
19193
+ since: number().optional(),
19194
+ until: number().optional(),
19195
+ kinds: array(string()).optional(),
19196
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19197
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19023
19198
  deviceId: number(),
19024
19199
  since: number(),
19025
19200
  until: number(),
@@ -22271,7 +22446,12 @@ var RecordingStorageUsageSchema = object({
22271
22446
  /**
22272
22447
  * Result of locating footage at a wall-clock instant for one device/profile.
22273
22448
  * `segment` carries the covering segment's window; `gap` reports the forward
22274
- * nearest covered edge (`null` past the end of footage / when none exists).
22449
+ * nearest covered edge (`null` past the end of footage / when none exists)
22450
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22451
+ * footage behind the epoch; `null` when none — optional so older providers
22452
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22453
+ * small inter-segment cracks (durMs under-covers the span to the next
22454
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22275
22455
  */
22276
22456
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22277
22457
  kind: literal("segment"),
@@ -22280,7 +22460,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22280
22460
  bytes: number()
22281
22461
  }), object({
22282
22462
  kind: literal("gap"),
22283
- nearestEdgeMs: number().nullable()
22463
+ nearestEdgeMs: number().nullable(),
22464
+ prevEndMs: number().nullable().optional()
22284
22465
  })]);
22285
22466
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22286
22467
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23862,6 +24043,12 @@ Object.freeze({
23862
24043
  addonId: null,
23863
24044
  access: "view"
23864
24045
  },
24046
+ "deviceManager.getLinkedDevices": {
24047
+ capName: "device-manager",
24048
+ capScope: "system",
24049
+ addonId: null,
24050
+ access: "view"
24051
+ },
23865
24052
  "deviceManager.getRoleDisplayDefaults": {
23866
24053
  capName: "device-manager",
23867
24054
  capScope: "system",
@@ -25416,6 +25603,12 @@ Object.freeze({
25416
25603
  addonId: null,
25417
25604
  access: "view"
25418
25605
  },
25606
+ "pipelineAnalytics.getSensorEvents": {
25607
+ capName: "pipeline-analytics",
25608
+ capScope: "device",
25609
+ addonId: null,
25610
+ access: "view"
25611
+ },
25419
25612
  "pipelineAnalytics.getTrack": {
25420
25613
  capName: "pipeline-analytics",
25421
25614
  capScope: "device",
@@ -25428,6 +25621,18 @@ Object.freeze({
25428
25621
  addonId: null,
25429
25622
  access: "view"
25430
25623
  },
25624
+ "pipelineAnalytics.listEventKinds": {
25625
+ capName: "pipeline-analytics",
25626
+ capScope: "device",
25627
+ addonId: null,
25628
+ access: "view"
25629
+ },
25630
+ "pipelineAnalytics.listRecentTracks": {
25631
+ capName: "pipeline-analytics",
25632
+ capScope: "device",
25633
+ addonId: null,
25634
+ access: "view"
25635
+ },
25431
25636
  "pipelineAnalytics.listTracks": {
25432
25637
  capName: "pipeline-analytics",
25433
25638
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",