@camstack/addon-provider-rademacher 0.1.9 → 0.1.11

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
@@ -984,7 +984,7 @@ var Rademacher = class {
984
984
  }
985
985
  };
986
986
  //#endregion
987
- //#region ../types/dist/event-category-CFZs3jI4.mjs
987
+ //#region ../types/dist/event-category-H4AVePnn.mjs
988
988
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
989
989
  EventCategory["SystemBoot"] = "system.boot";
990
990
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1134,6 +1134,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1134
1134
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
1135
1135
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
1136
1136
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
1137
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
1138
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
1139
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
1137
1140
  EventCategory["DetectionEvent"] = "detection.event";
1138
1141
  EventCategory["SessionTrackNew"] = "session.track.new";
1139
1142
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -8233,6 +8236,24 @@ var RecordingRetentionSchema = object({
8233
8236
  maxSizeGb: number().min(0).optional()
8234
8237
  });
8235
8238
  /**
8239
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
8240
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
8241
+ * previews at. Five graduated steps; absent on a config = `standard` (the
8242
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
8243
+ *
8244
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
8245
+ * Each window's index sidecar carries its own tile dims, so a camera whose
8246
+ * preset changed over time renders every historical window at the dims it was
8247
+ * written with.
8248
+ */
8249
+ var ScrubThumbnailPresetSchema = _enum([
8250
+ "minimal",
8251
+ "low",
8252
+ "standard",
8253
+ "high",
8254
+ "max"
8255
+ ]);
8256
+ /**
8236
8257
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
8237
8258
  *
8238
8259
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -8269,7 +8290,14 @@ var RecordingConfigSchema = object({
8269
8290
  * derived into bands once via `migrateConfigToBands`.
8270
8291
  */
8271
8292
  bands: array(RecordingBandSchema).optional(),
8272
- retention: RecordingRetentionSchema.optional()
8293
+ retention: RecordingRetentionSchema.optional(),
8294
+ /**
8295
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
8296
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
8297
+ * windows only — existing sheets are immutable, and each window's index
8298
+ * carries its own tile dims so mixed-preset history renders correctly.
8299
+ */
8300
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
8273
8301
  });
8274
8302
  /**
8275
8303
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -11781,10 +11809,23 @@ var deviceDiscoveryCapability = {
11781
11809
  }
11782
11810
  };
11783
11811
  /**
11784
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11785
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11786
- * time the firmware pushes a ring; status tracks the last press and
11787
- * a pressCount since start (diagnostic).
11812
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11813
+ * name (same pattern as `snapshot`):
11814
+ *
11815
+ * - **Native** providers: registered per-device by device-driver
11816
+ * addons via `ctx.registerNativeCap` — either on a
11817
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11818
+ * or directly on the camera (Reolink registers at camera level).
11819
+ * Emits an `onPressed` event every time the firmware pushes a
11820
+ * ring; status tracks the last press and a pressCount since start
11821
+ * (diagnostic).
11822
+ *
11823
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11824
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11825
+ * device (contact / switch / event-emitter …) into a doorbell for
11826
+ * a camera. `defaultActive: false` — the operator explicitly binds
11827
+ * it per camera in the device-bindings UI, then picks the source
11828
+ * device + trigger in the per-device settings.
11788
11829
  *
11789
11830
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11790
11831
  * — subscribers listening at the camera level receive ring events
@@ -11805,7 +11846,10 @@ var doorbellCapability = {
11805
11846
  scope: "device",
11806
11847
  deviceNative: true,
11807
11848
  mode: "singleton",
11808
- deviceTypes: [DeviceType.Button],
11849
+ kind: "wrapper",
11850
+ defaultActive: false,
11851
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11852
+ exposesDeviceSettings: true,
11809
11853
  methods: {},
11810
11854
  events: {
11811
11855
  /**
@@ -18157,6 +18201,14 @@ var ConfigEntrySchema = object({
18157
18201
  value: unknown(),
18158
18202
  description: string().optional()
18159
18203
  });
18204
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
18205
+ /** One resolved linked device — the compact projection consumers need. */
18206
+ var LinkedDeviceSchema = object({
18207
+ deviceId: number(),
18208
+ name: string(),
18209
+ location: string().nullable(),
18210
+ features: array(string())
18211
+ });
18160
18212
  var SavedDeviceRowSchema = object({
18161
18213
  /** Numeric id reserved at allocateDeviceId time. */
18162
18214
  id: number(),
@@ -18378,7 +18430,10 @@ method(object({
18378
18430
  }), _void(), {
18379
18431
  kind: "mutation",
18380
18432
  auth: "admin"
18381
- }), 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({
18433
+ }), 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({
18434
+ mode: DeviceLinkModeSchema,
18435
+ devices: array(LinkedDeviceSchema)
18436
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
18382
18437
  deviceId: number(),
18383
18438
  values: record(string(), unknown())
18384
18439
  }), object({ success: literal(true) }), {
@@ -19647,6 +19702,61 @@ var EventKindSchema = _enum([
19647
19702
  "object",
19648
19703
  "audio"
19649
19704
  ]);
19705
+ /**
19706
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
19707
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
19708
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
19709
+ */
19710
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
19711
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
19712
+ var EventKindIconSchema = _enum([
19713
+ "motion",
19714
+ "audio",
19715
+ "person",
19716
+ "vehicle",
19717
+ "animal",
19718
+ "door",
19719
+ "pir",
19720
+ "smoke",
19721
+ "water",
19722
+ "button",
19723
+ "generic"
19724
+ ]);
19725
+ var EventKindCategorySchema = _enum([
19726
+ "motion",
19727
+ "audio",
19728
+ "detection",
19729
+ "sensor",
19730
+ "custom"
19731
+ ]);
19732
+ var EventKindDescriptorSchema = object({
19733
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19734
+ kind: string(),
19735
+ label: string(),
19736
+ /** Hex color for timeline/legend rendering. */
19737
+ color: string(),
19738
+ icon: EventKindIconSchema,
19739
+ category: EventKindCategorySchema,
19740
+ /** Which cap + device contributes this kind. For built-ins the camera
19741
+ * itself; for sensor kinds the LINKED source device. */
19742
+ source: object({
19743
+ capName: string(),
19744
+ deviceId: number()
19745
+ })
19746
+ });
19747
+ var SensorEventSchema = object({
19748
+ id: string(),
19749
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19750
+ * yields N rows, one per camera). */
19751
+ deviceId: number(),
19752
+ /** The linked sensor device whose state changed. */
19753
+ sourceDeviceId: number(),
19754
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19755
+ kind: string(),
19756
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19757
+ value: record(string(), unknown()).nullable(),
19758
+ timestamp: number()
19759
+ });
19650
19760
  var TrackPositionSchema = object({
19651
19761
  x: number(),
19652
19762
  y: number(),
@@ -19660,6 +19770,30 @@ var TrackSnapshotSchema = object({
19660
19770
  mediaKey: string()
19661
19771
  });
19662
19772
  /**
19773
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19774
+ * divided by the track's detection-frame dims), computed at persist time.
19775
+ * Absent when the frame dims were unknown when the track was persisted
19776
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19777
+ */
19778
+ var TrackEnvelopeSchema = object({
19779
+ minX: number(),
19780
+ minY: number(),
19781
+ maxX: number(),
19782
+ maxY: number()
19783
+ });
19784
+ /**
19785
+ * Row projection for track list queries. `full` (default) returns the
19786
+ * complete Track including the frame-rate `positions[]` history and the
19787
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19788
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19789
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19790
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19791
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19792
+ * `getTrack`. Mirrors the event-store `projection` convention
19793
+ * (`getObjectEvents` et al.).
19794
+ */
19795
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19796
+ /**
19663
19797
  * One audio-classification label heard on the track's camera while the
19664
19798
  * track was alive, aggregated per label. An "episode" is one persisted
19665
19799
  * audio event (the confident-classification path: score ≥ the device's
@@ -19710,7 +19844,11 @@ var TrackSchema = object({
19710
19844
  /** Audio-classification labels heard on the camera during the track's
19711
19845
  * life (score ≥ device `classificationMinScore`), aggregated per label.
19712
19846
  * Absent on legacy rows / tracks with no confident audio. */
19713
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19847
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19848
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19849
+ * Populated from the persisted envelope columns on historical reads;
19850
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19851
+ envelope: TrackEnvelopeSchema.optional()
19714
19852
  });
19715
19853
  var BaseEventFields = {
19716
19854
  id: string(),
@@ -19820,11 +19958,13 @@ var MediaFileSchema = object({
19820
19958
  sizeBytes: number(),
19821
19959
  timestamp: number()
19822
19960
  });
19961
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19962
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19823
19963
  var DeviceEventQueryInput = object({
19824
19964
  deviceId: number(),
19825
19965
  since: number().optional(),
19826
19966
  until: number().optional(),
19827
- limit: number().int().min(1).max(5e3).default(1e3),
19967
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19828
19968
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19829
19969
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19830
19970
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19832,6 +19972,27 @@ var DeviceEventQueryInput = object({
19832
19972
  projection: _enum(["full", "slim"]).optional()
19833
19973
  });
19834
19974
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19975
+ var RecentTracksQueryInput = object({
19976
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19977
+ deviceIds: array(number()),
19978
+ /** Window lower bound on `lastSeen` (inclusive). */
19979
+ since: number().optional(),
19980
+ /** Window upper bound on `lastSeen` (inclusive). */
19981
+ until: number().optional(),
19982
+ /** Page size. Default 200, max 1000. */
19983
+ limit: number().int().min(1).max(1e3).default(200),
19984
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19985
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19986
+ cursor: string().optional(),
19987
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19988
+ projection: TrackProjectionSchema.optional()
19989
+ });
19990
+ var RecentTracksPageSchema = object({
19991
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19992
+ tracks: array(TrackSchema).readonly(),
19993
+ /** Cursor for the next page, or null when this page is the last. */
19994
+ nextCursor: string().nullable()
19995
+ });
19835
19996
  var KeyEventQueryInput = object({
19836
19997
  deviceId: number(),
19837
19998
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19914,11 +20075,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19914
20075
  deviceId: number(),
19915
20076
  since: number().optional(),
19916
20077
  until: number().optional(),
19917
- limit: number().optional()
19918
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
20078
+ limit: number().optional(),
20079
+ /** Spatial filter only tracks whose trajectory intersects the zone
20080
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
20081
+ * envelope columns, then precisely tested per position. Tracks with
20082
+ * an unknown envelope (no frame dims at persist time) always match. */
20083
+ zone: TrackZoneFilterSchema.optional(),
20084
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
20085
+ * compatible — omitting the field keeps today's exact behaviour). */
20086
+ projection: TrackProjectionSchema.optional()
20087
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19919
20088
  kind: "mutation",
19920
20089
  auth: "admin"
19921
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20090
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
20091
+ deviceId: number(),
20092
+ since: number().optional(),
20093
+ until: number().optional(),
20094
+ kinds: array(string()).optional(),
20095
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20096
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19922
20097
  deviceId: number(),
19923
20098
  since: number(),
19924
20099
  until: number(),
@@ -23119,7 +23294,12 @@ var RecordingStorageUsageSchema = object({
23119
23294
  /**
23120
23295
  * Result of locating footage at a wall-clock instant for one device/profile.
23121
23296
  * `segment` carries the covering segment's window; `gap` reports the forward
23122
- * nearest covered edge (`null` past the end of footage / when none exists).
23297
+ * nearest covered edge (`null` past the end of footage / when none exists)
23298
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
23299
+ * footage behind the epoch; `null` when none — optional so older providers
23300
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
23301
+ * small inter-segment cracks (durMs under-covers the span to the next
23302
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
23123
23303
  */
23124
23304
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
23125
23305
  kind: literal("segment"),
@@ -23128,7 +23308,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
23128
23308
  bytes: number()
23129
23309
  }), object({
23130
23310
  kind: literal("gap"),
23131
- nearestEdgeMs: number().nullable()
23311
+ nearestEdgeMs: number().nullable(),
23312
+ prevEndMs: number().nullable().optional()
23132
23313
  })]);
23133
23314
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
23134
23315
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24697,6 +24878,12 @@ Object.freeze({
24697
24878
  addonId: null,
24698
24879
  access: "view"
24699
24880
  },
24881
+ "deviceManager.getLinkedDevices": {
24882
+ capName: "device-manager",
24883
+ capScope: "system",
24884
+ addonId: null,
24885
+ access: "view"
24886
+ },
24700
24887
  "deviceManager.getRoleDisplayDefaults": {
24701
24888
  capName: "device-manager",
24702
24889
  capScope: "system",
@@ -26251,6 +26438,12 @@ Object.freeze({
26251
26438
  addonId: null,
26252
26439
  access: "view"
26253
26440
  },
26441
+ "pipelineAnalytics.getSensorEvents": {
26442
+ capName: "pipeline-analytics",
26443
+ capScope: "device",
26444
+ addonId: null,
26445
+ access: "view"
26446
+ },
26254
26447
  "pipelineAnalytics.getTrack": {
26255
26448
  capName: "pipeline-analytics",
26256
26449
  capScope: "device",
@@ -26263,6 +26456,18 @@ Object.freeze({
26263
26456
  addonId: null,
26264
26457
  access: "view"
26265
26458
  },
26459
+ "pipelineAnalytics.listEventKinds": {
26460
+ capName: "pipeline-analytics",
26461
+ capScope: "device",
26462
+ addonId: null,
26463
+ access: "view"
26464
+ },
26465
+ "pipelineAnalytics.listRecentTracks": {
26466
+ capName: "pipeline-analytics",
26467
+ capScope: "device",
26468
+ addonId: null,
26469
+ access: "view"
26470
+ },
26266
26471
  "pipelineAnalytics.listTracks": {
26267
26472
  capName: "pipeline-analytics",
26268
26473
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -983,7 +983,7 @@ var Rademacher = class {
983
983
  }
984
984
  };
985
985
  //#endregion
986
- //#region ../types/dist/event-category-CFZs3jI4.mjs
986
+ //#region ../types/dist/event-category-H4AVePnn.mjs
987
987
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
988
988
  EventCategory["SystemBoot"] = "system.boot";
989
989
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1133,6 +1133,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1133
1133
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
1134
1134
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
1135
1135
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
1136
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
1137
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
1138
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
1136
1139
  EventCategory["DetectionEvent"] = "detection.event";
1137
1140
  EventCategory["SessionTrackNew"] = "session.track.new";
1138
1141
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -8232,6 +8235,24 @@ var RecordingRetentionSchema = object({
8232
8235
  maxSizeGb: number().min(0).optional()
8233
8236
  });
8234
8237
  /**
8238
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
8239
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
8240
+ * previews at. Five graduated steps; absent on a config = `standard` (the
8241
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
8242
+ *
8243
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
8244
+ * Each window's index sidecar carries its own tile dims, so a camera whose
8245
+ * preset changed over time renders every historical window at the dims it was
8246
+ * written with.
8247
+ */
8248
+ var ScrubThumbnailPresetSchema = _enum([
8249
+ "minimal",
8250
+ "low",
8251
+ "standard",
8252
+ "high",
8253
+ "max"
8254
+ ]);
8255
+ /**
8235
8256
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
8236
8257
  *
8237
8258
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -8268,7 +8289,14 @@ var RecordingConfigSchema = object({
8268
8289
  * derived into bands once via `migrateConfigToBands`.
8269
8290
  */
8270
8291
  bands: array(RecordingBandSchema).optional(),
8271
- retention: RecordingRetentionSchema.optional()
8292
+ retention: RecordingRetentionSchema.optional(),
8293
+ /**
8294
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
8295
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
8296
+ * windows only — existing sheets are immutable, and each window's index
8297
+ * carries its own tile dims so mixed-preset history renders correctly.
8298
+ */
8299
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
8272
8300
  });
8273
8301
  /**
8274
8302
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -11780,10 +11808,23 @@ var deviceDiscoveryCapability = {
11780
11808
  }
11781
11809
  };
11782
11810
  /**
11783
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11784
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11785
- * time the firmware pushes a ring; status tracks the last press and
11786
- * a pressCount since start (diagnostic).
11811
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11812
+ * name (same pattern as `snapshot`):
11813
+ *
11814
+ * - **Native** providers: registered per-device by device-driver
11815
+ * addons via `ctx.registerNativeCap` — either on a
11816
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11817
+ * or directly on the camera (Reolink registers at camera level).
11818
+ * Emits an `onPressed` event every time the firmware pushes a
11819
+ * ring; status tracks the last press and a pressCount since start
11820
+ * (diagnostic).
11821
+ *
11822
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11823
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11824
+ * device (contact / switch / event-emitter …) into a doorbell for
11825
+ * a camera. `defaultActive: false` — the operator explicitly binds
11826
+ * it per camera in the device-bindings UI, then picks the source
11827
+ * device + trigger in the per-device settings.
11787
11828
  *
11788
11829
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11789
11830
  * — subscribers listening at the camera level receive ring events
@@ -11804,7 +11845,10 @@ var doorbellCapability = {
11804
11845
  scope: "device",
11805
11846
  deviceNative: true,
11806
11847
  mode: "singleton",
11807
- deviceTypes: [DeviceType.Button],
11848
+ kind: "wrapper",
11849
+ defaultActive: false,
11850
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11851
+ exposesDeviceSettings: true,
11808
11852
  methods: {},
11809
11853
  events: {
11810
11854
  /**
@@ -18156,6 +18200,14 @@ var ConfigEntrySchema = object({
18156
18200
  value: unknown(),
18157
18201
  description: string().optional()
18158
18202
  });
18203
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
18204
+ /** One resolved linked device — the compact projection consumers need. */
18205
+ var LinkedDeviceSchema = object({
18206
+ deviceId: number(),
18207
+ name: string(),
18208
+ location: string().nullable(),
18209
+ features: array(string())
18210
+ });
18159
18211
  var SavedDeviceRowSchema = object({
18160
18212
  /** Numeric id reserved at allocateDeviceId time. */
18161
18213
  id: number(),
@@ -18377,7 +18429,10 @@ method(object({
18377
18429
  }), _void(), {
18378
18430
  kind: "mutation",
18379
18431
  auth: "admin"
18380
- }), 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({
18432
+ }), 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({
18433
+ mode: DeviceLinkModeSchema,
18434
+ devices: array(LinkedDeviceSchema)
18435
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
18381
18436
  deviceId: number(),
18382
18437
  values: record(string(), unknown())
18383
18438
  }), object({ success: literal(true) }), {
@@ -19646,6 +19701,61 @@ var EventKindSchema = _enum([
19646
19701
  "object",
19647
19702
  "audio"
19648
19703
  ]);
19704
+ /**
19705
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
19706
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
19707
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
19708
+ */
19709
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
19710
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
19711
+ var EventKindIconSchema = _enum([
19712
+ "motion",
19713
+ "audio",
19714
+ "person",
19715
+ "vehicle",
19716
+ "animal",
19717
+ "door",
19718
+ "pir",
19719
+ "smoke",
19720
+ "water",
19721
+ "button",
19722
+ "generic"
19723
+ ]);
19724
+ var EventKindCategorySchema = _enum([
19725
+ "motion",
19726
+ "audio",
19727
+ "detection",
19728
+ "sensor",
19729
+ "custom"
19730
+ ]);
19731
+ var EventKindDescriptorSchema = object({
19732
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19733
+ kind: string(),
19734
+ label: string(),
19735
+ /** Hex color for timeline/legend rendering. */
19736
+ color: string(),
19737
+ icon: EventKindIconSchema,
19738
+ category: EventKindCategorySchema,
19739
+ /** Which cap + device contributes this kind. For built-ins the camera
19740
+ * itself; for sensor kinds the LINKED source device. */
19741
+ source: object({
19742
+ capName: string(),
19743
+ deviceId: number()
19744
+ })
19745
+ });
19746
+ var SensorEventSchema = object({
19747
+ id: string(),
19748
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19749
+ * yields N rows, one per camera). */
19750
+ deviceId: number(),
19751
+ /** The linked sensor device whose state changed. */
19752
+ sourceDeviceId: number(),
19753
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19754
+ kind: string(),
19755
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19756
+ value: record(string(), unknown()).nullable(),
19757
+ timestamp: number()
19758
+ });
19649
19759
  var TrackPositionSchema = object({
19650
19760
  x: number(),
19651
19761
  y: number(),
@@ -19659,6 +19769,30 @@ var TrackSnapshotSchema = object({
19659
19769
  mediaKey: string()
19660
19770
  });
19661
19771
  /**
19772
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19773
+ * divided by the track's detection-frame dims), computed at persist time.
19774
+ * Absent when the frame dims were unknown when the track was persisted
19775
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19776
+ */
19777
+ var TrackEnvelopeSchema = object({
19778
+ minX: number(),
19779
+ minY: number(),
19780
+ maxX: number(),
19781
+ maxY: number()
19782
+ });
19783
+ /**
19784
+ * Row projection for track list queries. `full` (default) returns the
19785
+ * complete Track including the frame-rate `positions[]` history and the
19786
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19787
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19788
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19789
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19790
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19791
+ * `getTrack`. Mirrors the event-store `projection` convention
19792
+ * (`getObjectEvents` et al.).
19793
+ */
19794
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19795
+ /**
19662
19796
  * One audio-classification label heard on the track's camera while the
19663
19797
  * track was alive, aggregated per label. An "episode" is one persisted
19664
19798
  * audio event (the confident-classification path: score ≥ the device's
@@ -19709,7 +19843,11 @@ var TrackSchema = object({
19709
19843
  /** Audio-classification labels heard on the camera during the track's
19710
19844
  * life (score ≥ device `classificationMinScore`), aggregated per label.
19711
19845
  * Absent on legacy rows / tracks with no confident audio. */
19712
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19846
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19847
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19848
+ * Populated from the persisted envelope columns on historical reads;
19849
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19850
+ envelope: TrackEnvelopeSchema.optional()
19713
19851
  });
19714
19852
  var BaseEventFields = {
19715
19853
  id: string(),
@@ -19819,11 +19957,13 @@ var MediaFileSchema = object({
19819
19957
  sizeBytes: number(),
19820
19958
  timestamp: number()
19821
19959
  });
19960
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19961
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19822
19962
  var DeviceEventQueryInput = object({
19823
19963
  deviceId: number(),
19824
19964
  since: number().optional(),
19825
19965
  until: number().optional(),
19826
- limit: number().int().min(1).max(5e3).default(1e3),
19966
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19827
19967
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19828
19968
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19829
19969
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19831,6 +19971,27 @@ var DeviceEventQueryInput = object({
19831
19971
  projection: _enum(["full", "slim"]).optional()
19832
19972
  });
19833
19973
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19974
+ var RecentTracksQueryInput = object({
19975
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19976
+ deviceIds: array(number()),
19977
+ /** Window lower bound on `lastSeen` (inclusive). */
19978
+ since: number().optional(),
19979
+ /** Window upper bound on `lastSeen` (inclusive). */
19980
+ until: number().optional(),
19981
+ /** Page size. Default 200, max 1000. */
19982
+ limit: number().int().min(1).max(1e3).default(200),
19983
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19984
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19985
+ cursor: string().optional(),
19986
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19987
+ projection: TrackProjectionSchema.optional()
19988
+ });
19989
+ var RecentTracksPageSchema = object({
19990
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19991
+ tracks: array(TrackSchema).readonly(),
19992
+ /** Cursor for the next page, or null when this page is the last. */
19993
+ nextCursor: string().nullable()
19994
+ });
19834
19995
  var KeyEventQueryInput = object({
19835
19996
  deviceId: number(),
19836
19997
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19913,11 +20074,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19913
20074
  deviceId: number(),
19914
20075
  since: number().optional(),
19915
20076
  until: number().optional(),
19916
- limit: number().optional()
19917
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
20077
+ limit: number().optional(),
20078
+ /** Spatial filter only tracks whose trajectory intersects the zone
20079
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
20080
+ * envelope columns, then precisely tested per position. Tracks with
20081
+ * an unknown envelope (no frame dims at persist time) always match. */
20082
+ zone: TrackZoneFilterSchema.optional(),
20083
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
20084
+ * compatible — omitting the field keeps today's exact behaviour). */
20085
+ projection: TrackProjectionSchema.optional()
20086
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19918
20087
  kind: "mutation",
19919
20088
  auth: "admin"
19920
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20089
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
20090
+ deviceId: number(),
20091
+ since: number().optional(),
20092
+ until: number().optional(),
20093
+ kinds: array(string()).optional(),
20094
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20095
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19921
20096
  deviceId: number(),
19922
20097
  since: number(),
19923
20098
  until: number(),
@@ -23118,7 +23293,12 @@ var RecordingStorageUsageSchema = object({
23118
23293
  /**
23119
23294
  * Result of locating footage at a wall-clock instant for one device/profile.
23120
23295
  * `segment` carries the covering segment's window; `gap` reports the forward
23121
- * nearest covered edge (`null` past the end of footage / when none exists).
23296
+ * nearest covered edge (`null` past the end of footage / when none exists)
23297
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
23298
+ * footage behind the epoch; `null` when none — optional so older providers
23299
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
23300
+ * small inter-segment cracks (durMs under-covers the span to the next
23301
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
23122
23302
  */
23123
23303
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
23124
23304
  kind: literal("segment"),
@@ -23127,7 +23307,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
23127
23307
  bytes: number()
23128
23308
  }), object({
23129
23309
  kind: literal("gap"),
23130
- nearestEdgeMs: number().nullable()
23310
+ nearestEdgeMs: number().nullable(),
23311
+ prevEndMs: number().nullable().optional()
23131
23312
  })]);
23132
23313
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
23133
23314
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24696,6 +24877,12 @@ Object.freeze({
24696
24877
  addonId: null,
24697
24878
  access: "view"
24698
24879
  },
24880
+ "deviceManager.getLinkedDevices": {
24881
+ capName: "device-manager",
24882
+ capScope: "system",
24883
+ addonId: null,
24884
+ access: "view"
24885
+ },
24699
24886
  "deviceManager.getRoleDisplayDefaults": {
24700
24887
  capName: "device-manager",
24701
24888
  capScope: "system",
@@ -26250,6 +26437,12 @@ Object.freeze({
26250
26437
  addonId: null,
26251
26438
  access: "view"
26252
26439
  },
26440
+ "pipelineAnalytics.getSensorEvents": {
26441
+ capName: "pipeline-analytics",
26442
+ capScope: "device",
26443
+ addonId: null,
26444
+ access: "view"
26445
+ },
26253
26446
  "pipelineAnalytics.getTrack": {
26254
26447
  capName: "pipeline-analytics",
26255
26448
  capScope: "device",
@@ -26262,6 +26455,18 @@ Object.freeze({
26262
26455
  addonId: null,
26263
26456
  access: "view"
26264
26457
  },
26458
+ "pipelineAnalytics.listEventKinds": {
26459
+ capName: "pipeline-analytics",
26460
+ capScope: "device",
26461
+ addonId: null,
26462
+ access: "view"
26463
+ },
26464
+ "pipelineAnalytics.listRecentTracks": {
26465
+ capName: "pipeline-analytics",
26466
+ capScope: "device",
26467
+ addonId: null,
26468
+ access: "view"
26469
+ },
26265
26470
  "pipelineAnalytics.listTracks": {
26266
26471
  capName: "pipeline-analytics",
26267
26472
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rademacher",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Rademacher HomePilot device-provider addon for CamStack — wraps the @apocaliss92/noderademacher local-hub client (roller shutters over the cover cap)",
5
5
  "keywords": [
6
6
  "camstack",