@camstack/addon-provider-dreo 0.1.18 → 0.1.20

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
@@ -35,7 +35,7 @@ var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["modu
35
35
  //#endregion
36
36
  let crypto$1 = require("crypto");
37
37
  let events = require("events");
38
- //#region ../types/dist/event-category-CFZs3jI4.mjs
38
+ //#region ../types/dist/event-category-H4AVePnn.mjs
39
39
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
40
40
  EventCategory["SystemBoot"] = "system.boot";
41
41
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -185,6 +185,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
185
185
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
186
186
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
187
187
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
188
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
189
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
190
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
188
191
  EventCategory["DetectionEvent"] = "detection.event";
189
192
  EventCategory["SessionTrackNew"] = "session.track.new";
190
193
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7270,6 +7273,24 @@ var RecordingRetentionSchema = object({
7270
7273
  maxSizeGb: number().min(0).optional()
7271
7274
  });
7272
7275
  /**
7276
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7277
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7278
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7279
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7280
+ *
7281
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7282
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7283
+ * preset changed over time renders every historical window at the dims it was
7284
+ * written with.
7285
+ */
7286
+ var ScrubThumbnailPresetSchema = _enum([
7287
+ "minimal",
7288
+ "low",
7289
+ "standard",
7290
+ "high",
7291
+ "max"
7292
+ ]);
7293
+ /**
7273
7294
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7274
7295
  *
7275
7296
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7306,7 +7327,14 @@ var RecordingConfigSchema = object({
7306
7327
  * derived into bands once via `migrateConfigToBands`.
7307
7328
  */
7308
7329
  bands: array(RecordingBandSchema).optional(),
7309
- retention: RecordingRetentionSchema.optional()
7330
+ retention: RecordingRetentionSchema.optional(),
7331
+ /**
7332
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7333
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7334
+ * windows only — existing sheets are immutable, and each window's index
7335
+ * carries its own tile dims so mixed-preset history renders correctly.
7336
+ */
7337
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7310
7338
  });
7311
7339
  /**
7312
7340
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10818,10 +10846,23 @@ var deviceDiscoveryCapability = {
10818
10846
  }
10819
10847
  };
10820
10848
  /**
10821
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10822
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10823
- * time the firmware pushes a ring; status tracks the last press and
10824
- * a pressCount since start (diagnostic).
10849
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10850
+ * name (same pattern as `snapshot`):
10851
+ *
10852
+ * - **Native** providers: registered per-device by device-driver
10853
+ * addons via `ctx.registerNativeCap` — either on a
10854
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10855
+ * or directly on the camera (Reolink registers at camera level).
10856
+ * Emits an `onPressed` event every time the firmware pushes a
10857
+ * ring; status tracks the last press and a pressCount since start
10858
+ * (diagnostic).
10859
+ *
10860
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10861
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10862
+ * device (contact / switch / event-emitter …) into a doorbell for
10863
+ * a camera. `defaultActive: false` — the operator explicitly binds
10864
+ * it per camera in the device-bindings UI, then picks the source
10865
+ * device + trigger in the per-device settings.
10825
10866
  *
10826
10867
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10827
10868
  * — subscribers listening at the camera level receive ring events
@@ -10842,7 +10883,10 @@ var doorbellCapability = {
10842
10883
  scope: "device",
10843
10884
  deviceNative: true,
10844
10885
  mode: "singleton",
10845
- deviceTypes: [DeviceType.Button],
10886
+ kind: "wrapper",
10887
+ defaultActive: false,
10888
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10889
+ exposesDeviceSettings: true,
10846
10890
  methods: {},
10847
10891
  events: {
10848
10892
  /**
@@ -17211,6 +17255,14 @@ var ConfigEntrySchema = object({
17211
17255
  value: unknown(),
17212
17256
  description: string().optional()
17213
17257
  });
17258
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17259
+ /** One resolved linked device — the compact projection consumers need. */
17260
+ var LinkedDeviceSchema = object({
17261
+ deviceId: number(),
17262
+ name: string(),
17263
+ location: string().nullable(),
17264
+ features: array(string())
17265
+ });
17214
17266
  var SavedDeviceRowSchema = object({
17215
17267
  /** Numeric id reserved at allocateDeviceId time. */
17216
17268
  id: number(),
@@ -17432,7 +17484,10 @@ method(object({
17432
17484
  }), _void(), {
17433
17485
  kind: "mutation",
17434
17486
  auth: "admin"
17435
- }), 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({
17487
+ }), 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({
17488
+ mode: DeviceLinkModeSchema,
17489
+ devices: array(LinkedDeviceSchema)
17490
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17436
17491
  deviceId: number(),
17437
17492
  values: record(string(), unknown())
17438
17493
  }), object({ success: literal(true) }), {
@@ -18701,6 +18756,61 @@ var EventKindSchema = _enum([
18701
18756
  "object",
18702
18757
  "audio"
18703
18758
  ]);
18759
+ /**
18760
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18761
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18762
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18763
+ */
18764
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18765
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18766
+ var EventKindIconSchema = _enum([
18767
+ "motion",
18768
+ "audio",
18769
+ "person",
18770
+ "vehicle",
18771
+ "animal",
18772
+ "door",
18773
+ "pir",
18774
+ "smoke",
18775
+ "water",
18776
+ "button",
18777
+ "generic"
18778
+ ]);
18779
+ var EventKindCategorySchema = _enum([
18780
+ "motion",
18781
+ "audio",
18782
+ "detection",
18783
+ "sensor",
18784
+ "custom"
18785
+ ]);
18786
+ var EventKindDescriptorSchema = object({
18787
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18788
+ kind: string(),
18789
+ label: string(),
18790
+ /** Hex color for timeline/legend rendering. */
18791
+ color: string(),
18792
+ icon: EventKindIconSchema,
18793
+ category: EventKindCategorySchema,
18794
+ /** Which cap + device contributes this kind. For built-ins the camera
18795
+ * itself; for sensor kinds the LINKED source device. */
18796
+ source: object({
18797
+ capName: string(),
18798
+ deviceId: number()
18799
+ })
18800
+ });
18801
+ var SensorEventSchema = object({
18802
+ id: string(),
18803
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18804
+ * yields N rows, one per camera). */
18805
+ deviceId: number(),
18806
+ /** The linked sensor device whose state changed. */
18807
+ sourceDeviceId: number(),
18808
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18809
+ kind: string(),
18810
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18811
+ value: record(string(), unknown()).nullable(),
18812
+ timestamp: number()
18813
+ });
18704
18814
  var TrackPositionSchema = object({
18705
18815
  x: number(),
18706
18816
  y: number(),
@@ -18714,6 +18824,30 @@ var TrackSnapshotSchema = object({
18714
18824
  mediaKey: string()
18715
18825
  });
18716
18826
  /**
18827
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18828
+ * divided by the track's detection-frame dims), computed at persist time.
18829
+ * Absent when the frame dims were unknown when the track was persisted
18830
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18831
+ */
18832
+ var TrackEnvelopeSchema = object({
18833
+ minX: number(),
18834
+ minY: number(),
18835
+ maxX: number(),
18836
+ maxY: number()
18837
+ });
18838
+ /**
18839
+ * Row projection for track list queries. `full` (default) returns the
18840
+ * complete Track including the frame-rate `positions[]` history and the
18841
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18842
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18843
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18844
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18845
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18846
+ * `getTrack`. Mirrors the event-store `projection` convention
18847
+ * (`getObjectEvents` et al.).
18848
+ */
18849
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18850
+ /**
18717
18851
  * One audio-classification label heard on the track's camera while the
18718
18852
  * track was alive, aggregated per label. An "episode" is one persisted
18719
18853
  * audio event (the confident-classification path: score ≥ the device's
@@ -18764,7 +18898,11 @@ var TrackSchema = object({
18764
18898
  /** Audio-classification labels heard on the camera during the track's
18765
18899
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18766
18900
  * Absent on legacy rows / tracks with no confident audio. */
18767
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18901
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18902
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18903
+ * Populated from the persisted envelope columns on historical reads;
18904
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18905
+ envelope: TrackEnvelopeSchema.optional()
18768
18906
  });
18769
18907
  var BaseEventFields = {
18770
18908
  id: string(),
@@ -18874,11 +19012,13 @@ var MediaFileSchema = object({
18874
19012
  sizeBytes: number(),
18875
19013
  timestamp: number()
18876
19014
  });
19015
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19016
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18877
19017
  var DeviceEventQueryInput = object({
18878
19018
  deviceId: number(),
18879
19019
  since: number().optional(),
18880
19020
  until: number().optional(),
18881
- limit: number().int().min(1).max(5e3).default(1e3),
19021
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18882
19022
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18883
19023
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18884
19024
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18886,6 +19026,27 @@ var DeviceEventQueryInput = object({
18886
19026
  projection: _enum(["full", "slim"]).optional()
18887
19027
  });
18888
19028
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19029
+ var RecentTracksQueryInput = object({
19030
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19031
+ deviceIds: array(number()),
19032
+ /** Window lower bound on `lastSeen` (inclusive). */
19033
+ since: number().optional(),
19034
+ /** Window upper bound on `lastSeen` (inclusive). */
19035
+ until: number().optional(),
19036
+ /** Page size. Default 200, max 1000. */
19037
+ limit: number().int().min(1).max(1e3).default(200),
19038
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19039
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19040
+ cursor: string().optional(),
19041
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19042
+ projection: TrackProjectionSchema.optional()
19043
+ });
19044
+ var RecentTracksPageSchema = object({
19045
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19046
+ tracks: array(TrackSchema).readonly(),
19047
+ /** Cursor for the next page, or null when this page is the last. */
19048
+ nextCursor: string().nullable()
19049
+ });
18889
19050
  var KeyEventQueryInput = object({
18890
19051
  deviceId: number(),
18891
19052
  /** Window lower bound (track firstSeen ≥ since). */
@@ -18968,11 +19129,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18968
19129
  deviceId: number(),
18969
19130
  since: number().optional(),
18970
19131
  until: number().optional(),
18971
- limit: number().optional()
18972
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19132
+ limit: number().optional(),
19133
+ /** Spatial filter only tracks whose trajectory intersects the zone
19134
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19135
+ * envelope columns, then precisely tested per position. Tracks with
19136
+ * an unknown envelope (no frame dims at persist time) always match. */
19137
+ zone: TrackZoneFilterSchema.optional(),
19138
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19139
+ * compatible — omitting the field keeps today's exact behaviour). */
19140
+ projection: TrackProjectionSchema.optional()
19141
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
18973
19142
  kind: "mutation",
18974
19143
  auth: "admin"
18975
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19144
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19145
+ deviceId: number(),
19146
+ since: number().optional(),
19147
+ until: number().optional(),
19148
+ kinds: array(string()).optional(),
19149
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19150
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
18976
19151
  deviceId: number(),
18977
19152
  since: number(),
18978
19153
  until: number(),
@@ -22173,7 +22348,12 @@ var RecordingStorageUsageSchema = object({
22173
22348
  /**
22174
22349
  * Result of locating footage at a wall-clock instant for one device/profile.
22175
22350
  * `segment` carries the covering segment's window; `gap` reports the forward
22176
- * nearest covered edge (`null` past the end of footage / when none exists).
22351
+ * nearest covered edge (`null` past the end of footage / when none exists)
22352
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22353
+ * footage behind the epoch; `null` when none — optional so older providers
22354
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22355
+ * small inter-segment cracks (durMs under-covers the span to the next
22356
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22177
22357
  */
22178
22358
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22179
22359
  kind: literal("segment"),
@@ -22182,7 +22362,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22182
22362
  bytes: number()
22183
22363
  }), object({
22184
22364
  kind: literal("gap"),
22185
- nearestEdgeMs: number().nullable()
22365
+ nearestEdgeMs: number().nullable(),
22366
+ prevEndMs: number().nullable().optional()
22186
22367
  })]);
22187
22368
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22188
22369
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23751,6 +23932,12 @@ Object.freeze({
23751
23932
  addonId: null,
23752
23933
  access: "view"
23753
23934
  },
23935
+ "deviceManager.getLinkedDevices": {
23936
+ capName: "device-manager",
23937
+ capScope: "system",
23938
+ addonId: null,
23939
+ access: "view"
23940
+ },
23754
23941
  "deviceManager.getRoleDisplayDefaults": {
23755
23942
  capName: "device-manager",
23756
23943
  capScope: "system",
@@ -25305,6 +25492,12 @@ Object.freeze({
25305
25492
  addonId: null,
25306
25493
  access: "view"
25307
25494
  },
25495
+ "pipelineAnalytics.getSensorEvents": {
25496
+ capName: "pipeline-analytics",
25497
+ capScope: "device",
25498
+ addonId: null,
25499
+ access: "view"
25500
+ },
25308
25501
  "pipelineAnalytics.getTrack": {
25309
25502
  capName: "pipeline-analytics",
25310
25503
  capScope: "device",
@@ -25317,6 +25510,18 @@ Object.freeze({
25317
25510
  addonId: null,
25318
25511
  access: "view"
25319
25512
  },
25513
+ "pipelineAnalytics.listEventKinds": {
25514
+ capName: "pipeline-analytics",
25515
+ capScope: "device",
25516
+ addonId: null,
25517
+ access: "view"
25518
+ },
25519
+ "pipelineAnalytics.listRecentTracks": {
25520
+ capName: "pipeline-analytics",
25521
+ capScope: "device",
25522
+ addonId: null,
25523
+ access: "view"
25524
+ },
25320
25525
  "pipelineAnalytics.listTracks": {
25321
25526
  capName: "pipeline-analytics",
25322
25527
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
37
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
38
38
  //#endregion
39
- //#region ../types/dist/event-category-CFZs3jI4.mjs
39
+ //#region ../types/dist/event-category-H4AVePnn.mjs
40
40
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
41
41
  EventCategory["SystemBoot"] = "system.boot";
42
42
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -186,6 +186,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
186
186
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
187
187
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
188
188
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
189
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
190
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
191
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
189
192
  EventCategory["DetectionEvent"] = "detection.event";
190
193
  EventCategory["SessionTrackNew"] = "session.track.new";
191
194
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7271,6 +7274,24 @@ var RecordingRetentionSchema = object({
7271
7274
  maxSizeGb: number().min(0).optional()
7272
7275
  });
7273
7276
  /**
7277
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7278
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7279
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7280
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7281
+ *
7282
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7283
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7284
+ * preset changed over time renders every historical window at the dims it was
7285
+ * written with.
7286
+ */
7287
+ var ScrubThumbnailPresetSchema = _enum([
7288
+ "minimal",
7289
+ "low",
7290
+ "standard",
7291
+ "high",
7292
+ "max"
7293
+ ]);
7294
+ /**
7274
7295
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7275
7296
  *
7276
7297
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7307,7 +7328,14 @@ var RecordingConfigSchema = object({
7307
7328
  * derived into bands once via `migrateConfigToBands`.
7308
7329
  */
7309
7330
  bands: array(RecordingBandSchema).optional(),
7310
- retention: RecordingRetentionSchema.optional()
7331
+ retention: RecordingRetentionSchema.optional(),
7332
+ /**
7333
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7334
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7335
+ * windows only — existing sheets are immutable, and each window's index
7336
+ * carries its own tile dims so mixed-preset history renders correctly.
7337
+ */
7338
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7311
7339
  });
7312
7340
  /**
7313
7341
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10819,10 +10847,23 @@ var deviceDiscoveryCapability = {
10819
10847
  }
10820
10848
  };
10821
10849
  /**
10822
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10823
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10824
- * time the firmware pushes a ring; status tracks the last press and
10825
- * a pressCount since start (diagnostic).
10850
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10851
+ * name (same pattern as `snapshot`):
10852
+ *
10853
+ * - **Native** providers: registered per-device by device-driver
10854
+ * addons via `ctx.registerNativeCap` — either on a
10855
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10856
+ * or directly on the camera (Reolink registers at camera level).
10857
+ * Emits an `onPressed` event every time the firmware pushes a
10858
+ * ring; status tracks the last press and a pressCount since start
10859
+ * (diagnostic).
10860
+ *
10861
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10862
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10863
+ * device (contact / switch / event-emitter …) into a doorbell for
10864
+ * a camera. `defaultActive: false` — the operator explicitly binds
10865
+ * it per camera in the device-bindings UI, then picks the source
10866
+ * device + trigger in the per-device settings.
10826
10867
  *
10827
10868
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10828
10869
  * — subscribers listening at the camera level receive ring events
@@ -10843,7 +10884,10 @@ var doorbellCapability = {
10843
10884
  scope: "device",
10844
10885
  deviceNative: true,
10845
10886
  mode: "singleton",
10846
- deviceTypes: [DeviceType.Button],
10887
+ kind: "wrapper",
10888
+ defaultActive: false,
10889
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10890
+ exposesDeviceSettings: true,
10847
10891
  methods: {},
10848
10892
  events: {
10849
10893
  /**
@@ -17212,6 +17256,14 @@ var ConfigEntrySchema = object({
17212
17256
  value: unknown(),
17213
17257
  description: string().optional()
17214
17258
  });
17259
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17260
+ /** One resolved linked device — the compact projection consumers need. */
17261
+ var LinkedDeviceSchema = object({
17262
+ deviceId: number(),
17263
+ name: string(),
17264
+ location: string().nullable(),
17265
+ features: array(string())
17266
+ });
17215
17267
  var SavedDeviceRowSchema = object({
17216
17268
  /** Numeric id reserved at allocateDeviceId time. */
17217
17269
  id: number(),
@@ -17433,7 +17485,10 @@ method(object({
17433
17485
  }), _void(), {
17434
17486
  kind: "mutation",
17435
17487
  auth: "admin"
17436
- }), 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({
17488
+ }), 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({
17489
+ mode: DeviceLinkModeSchema,
17490
+ devices: array(LinkedDeviceSchema)
17491
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17437
17492
  deviceId: number(),
17438
17493
  values: record(string(), unknown())
17439
17494
  }), object({ success: literal(true) }), {
@@ -18702,6 +18757,61 @@ var EventKindSchema = _enum([
18702
18757
  "object",
18703
18758
  "audio"
18704
18759
  ]);
18760
+ /**
18761
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18762
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18763
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18764
+ */
18765
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18766
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18767
+ var EventKindIconSchema = _enum([
18768
+ "motion",
18769
+ "audio",
18770
+ "person",
18771
+ "vehicle",
18772
+ "animal",
18773
+ "door",
18774
+ "pir",
18775
+ "smoke",
18776
+ "water",
18777
+ "button",
18778
+ "generic"
18779
+ ]);
18780
+ var EventKindCategorySchema = _enum([
18781
+ "motion",
18782
+ "audio",
18783
+ "detection",
18784
+ "sensor",
18785
+ "custom"
18786
+ ]);
18787
+ var EventKindDescriptorSchema = object({
18788
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18789
+ kind: string(),
18790
+ label: string(),
18791
+ /** Hex color for timeline/legend rendering. */
18792
+ color: string(),
18793
+ icon: EventKindIconSchema,
18794
+ category: EventKindCategorySchema,
18795
+ /** Which cap + device contributes this kind. For built-ins the camera
18796
+ * itself; for sensor kinds the LINKED source device. */
18797
+ source: object({
18798
+ capName: string(),
18799
+ deviceId: number()
18800
+ })
18801
+ });
18802
+ var SensorEventSchema = object({
18803
+ id: string(),
18804
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18805
+ * yields N rows, one per camera). */
18806
+ deviceId: number(),
18807
+ /** The linked sensor device whose state changed. */
18808
+ sourceDeviceId: number(),
18809
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18810
+ kind: string(),
18811
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18812
+ value: record(string(), unknown()).nullable(),
18813
+ timestamp: number()
18814
+ });
18705
18815
  var TrackPositionSchema = object({
18706
18816
  x: number(),
18707
18817
  y: number(),
@@ -18715,6 +18825,30 @@ var TrackSnapshotSchema = object({
18715
18825
  mediaKey: string()
18716
18826
  });
18717
18827
  /**
18828
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18829
+ * divided by the track's detection-frame dims), computed at persist time.
18830
+ * Absent when the frame dims were unknown when the track was persisted
18831
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18832
+ */
18833
+ var TrackEnvelopeSchema = object({
18834
+ minX: number(),
18835
+ minY: number(),
18836
+ maxX: number(),
18837
+ maxY: number()
18838
+ });
18839
+ /**
18840
+ * Row projection for track list queries. `full` (default) returns the
18841
+ * complete Track including the frame-rate `positions[]` history and the
18842
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18843
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18844
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18845
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18846
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18847
+ * `getTrack`. Mirrors the event-store `projection` convention
18848
+ * (`getObjectEvents` et al.).
18849
+ */
18850
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18851
+ /**
18718
18852
  * One audio-classification label heard on the track's camera while the
18719
18853
  * track was alive, aggregated per label. An "episode" is one persisted
18720
18854
  * audio event (the confident-classification path: score ≥ the device's
@@ -18765,7 +18899,11 @@ var TrackSchema = object({
18765
18899
  /** Audio-classification labels heard on the camera during the track's
18766
18900
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18767
18901
  * Absent on legacy rows / tracks with no confident audio. */
18768
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18902
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18903
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18904
+ * Populated from the persisted envelope columns on historical reads;
18905
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18906
+ envelope: TrackEnvelopeSchema.optional()
18769
18907
  });
18770
18908
  var BaseEventFields = {
18771
18909
  id: string(),
@@ -18875,11 +19013,13 @@ var MediaFileSchema = object({
18875
19013
  sizeBytes: number(),
18876
19014
  timestamp: number()
18877
19015
  });
19016
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19017
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18878
19018
  var DeviceEventQueryInput = object({
18879
19019
  deviceId: number(),
18880
19020
  since: number().optional(),
18881
19021
  until: number().optional(),
18882
- limit: number().int().min(1).max(5e3).default(1e3),
19022
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18883
19023
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18884
19024
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18885
19025
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18887,6 +19027,27 @@ var DeviceEventQueryInput = object({
18887
19027
  projection: _enum(["full", "slim"]).optional()
18888
19028
  });
18889
19029
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19030
+ var RecentTracksQueryInput = object({
19031
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19032
+ deviceIds: array(number()),
19033
+ /** Window lower bound on `lastSeen` (inclusive). */
19034
+ since: number().optional(),
19035
+ /** Window upper bound on `lastSeen` (inclusive). */
19036
+ until: number().optional(),
19037
+ /** Page size. Default 200, max 1000. */
19038
+ limit: number().int().min(1).max(1e3).default(200),
19039
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19040
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19041
+ cursor: string().optional(),
19042
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19043
+ projection: TrackProjectionSchema.optional()
19044
+ });
19045
+ var RecentTracksPageSchema = object({
19046
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19047
+ tracks: array(TrackSchema).readonly(),
19048
+ /** Cursor for the next page, or null when this page is the last. */
19049
+ nextCursor: string().nullable()
19050
+ });
18890
19051
  var KeyEventQueryInput = object({
18891
19052
  deviceId: number(),
18892
19053
  /** Window lower bound (track firstSeen ≥ since). */
@@ -18969,11 +19130,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18969
19130
  deviceId: number(),
18970
19131
  since: number().optional(),
18971
19132
  until: number().optional(),
18972
- limit: number().optional()
18973
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19133
+ limit: number().optional(),
19134
+ /** Spatial filter only tracks whose trajectory intersects the zone
19135
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19136
+ * envelope columns, then precisely tested per position. Tracks with
19137
+ * an unknown envelope (no frame dims at persist time) always match. */
19138
+ zone: TrackZoneFilterSchema.optional(),
19139
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19140
+ * compatible — omitting the field keeps today's exact behaviour). */
19141
+ projection: TrackProjectionSchema.optional()
19142
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
18974
19143
  kind: "mutation",
18975
19144
  auth: "admin"
18976
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19145
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19146
+ deviceId: number(),
19147
+ since: number().optional(),
19148
+ until: number().optional(),
19149
+ kinds: array(string()).optional(),
19150
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19151
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
18977
19152
  deviceId: number(),
18978
19153
  since: number(),
18979
19154
  until: number(),
@@ -22174,7 +22349,12 @@ var RecordingStorageUsageSchema = object({
22174
22349
  /**
22175
22350
  * Result of locating footage at a wall-clock instant for one device/profile.
22176
22351
  * `segment` carries the covering segment's window; `gap` reports the forward
22177
- * nearest covered edge (`null` past the end of footage / when none exists).
22352
+ * nearest covered edge (`null` past the end of footage / when none exists)
22353
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22354
+ * footage behind the epoch; `null` when none — optional so older providers
22355
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22356
+ * small inter-segment cracks (durMs under-covers the span to the next
22357
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22178
22358
  */
22179
22359
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22180
22360
  kind: literal("segment"),
@@ -22183,7 +22363,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22183
22363
  bytes: number()
22184
22364
  }), object({
22185
22365
  kind: literal("gap"),
22186
- nearestEdgeMs: number().nullable()
22366
+ nearestEdgeMs: number().nullable(),
22367
+ prevEndMs: number().nullable().optional()
22187
22368
  })]);
22188
22369
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22189
22370
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23752,6 +23933,12 @@ Object.freeze({
23752
23933
  addonId: null,
23753
23934
  access: "view"
23754
23935
  },
23936
+ "deviceManager.getLinkedDevices": {
23937
+ capName: "device-manager",
23938
+ capScope: "system",
23939
+ addonId: null,
23940
+ access: "view"
23941
+ },
23755
23942
  "deviceManager.getRoleDisplayDefaults": {
23756
23943
  capName: "device-manager",
23757
23944
  capScope: "system",
@@ -25306,6 +25493,12 @@ Object.freeze({
25306
25493
  addonId: null,
25307
25494
  access: "view"
25308
25495
  },
25496
+ "pipelineAnalytics.getSensorEvents": {
25497
+ capName: "pipeline-analytics",
25498
+ capScope: "device",
25499
+ addonId: null,
25500
+ access: "view"
25501
+ },
25309
25502
  "pipelineAnalytics.getTrack": {
25310
25503
  capName: "pipeline-analytics",
25311
25504
  capScope: "device",
@@ -25318,6 +25511,18 @@ Object.freeze({
25318
25511
  addonId: null,
25319
25512
  access: "view"
25320
25513
  },
25514
+ "pipelineAnalytics.listEventKinds": {
25515
+ capName: "pipeline-analytics",
25516
+ capScope: "device",
25517
+ addonId: null,
25518
+ access: "view"
25519
+ },
25520
+ "pipelineAnalytics.listRecentTracks": {
25521
+ capName: "pipeline-analytics",
25522
+ capScope: "device",
25523
+ addonId: null,
25524
+ access: "view"
25525
+ },
25321
25526
  "pipelineAnalytics.listTracks": {
25322
25527
  capName: "pipeline-analytics",
25323
25528
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreo",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
5
5
  "keywords": [
6
6
  "camstack",