@camstack/addon-provider-reolink 1.1.25 → 1.1.27

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
@@ -26,7 +26,7 @@ let fs_promises = require("fs/promises");
26
26
  fs_promises = require_chunk.__toESM(fs_promises, 1);
27
27
  let node_os = require("node:os");
28
28
  node_os = require_chunk.__toESM(node_os);
29
- //#region ../types/dist/event-category-CFZs3jI4.mjs
29
+ //#region ../types/dist/event-category-H4AVePnn.mjs
30
30
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
31
31
  EventCategory["SystemBoot"] = "system.boot";
32
32
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -176,6 +176,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
176
176
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
177
177
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
178
178
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
179
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
180
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
181
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
179
182
  EventCategory["DetectionEvent"] = "detection.event";
180
183
  EventCategory["SessionTrackNew"] = "session.track.new";
181
184
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7448,6 +7451,24 @@ var RecordingRetentionSchema = object({
7448
7451
  maxSizeGb: number().min(0).optional()
7449
7452
  });
7450
7453
  /**
7454
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7455
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7456
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7457
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7458
+ *
7459
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7460
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7461
+ * preset changed over time renders every historical window at the dims it was
7462
+ * written with.
7463
+ */
7464
+ var ScrubThumbnailPresetSchema = _enum([
7465
+ "minimal",
7466
+ "low",
7467
+ "standard",
7468
+ "high",
7469
+ "max"
7470
+ ]);
7471
+ /**
7451
7472
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7452
7473
  *
7453
7474
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7484,7 +7505,14 @@ var RecordingConfigSchema = object({
7484
7505
  * derived into bands once via `migrateConfigToBands`.
7485
7506
  */
7486
7507
  bands: array(RecordingBandSchema).optional(),
7487
- retention: RecordingRetentionSchema.optional()
7508
+ retention: RecordingRetentionSchema.optional(),
7509
+ /**
7510
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7511
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7512
+ * windows only — existing sheets are immutable, and each window's index
7513
+ * carries its own tile dims so mixed-preset history renders correctly.
7514
+ */
7515
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7488
7516
  });
7489
7517
  /**
7490
7518
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -11035,10 +11063,23 @@ var deviceDiscoveryCapability = {
11035
11063
  }
11036
11064
  };
11037
11065
  /**
11038
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11039
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11040
- * time the firmware pushes a ring; status tracks the last press and
11041
- * a pressCount since start (diagnostic).
11066
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11067
+ * name (same pattern as `snapshot`):
11068
+ *
11069
+ * - **Native** providers: registered per-device by device-driver
11070
+ * addons via `ctx.registerNativeCap` — either on a
11071
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11072
+ * or directly on the camera (Reolink registers at camera level).
11073
+ * Emits an `onPressed` event every time the firmware pushes a
11074
+ * ring; status tracks the last press and a pressCount since start
11075
+ * (diagnostic).
11076
+ *
11077
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11078
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11079
+ * device (contact / switch / event-emitter …) into a doorbell for
11080
+ * a camera. `defaultActive: false` — the operator explicitly binds
11081
+ * it per camera in the device-bindings UI, then picks the source
11082
+ * device + trigger in the per-device settings.
11042
11083
  *
11043
11084
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11044
11085
  * — subscribers listening at the camera level receive ring events
@@ -11059,7 +11100,10 @@ var doorbellCapability = {
11059
11100
  scope: "device",
11060
11101
  deviceNative: true,
11061
11102
  mode: "singleton",
11062
- deviceTypes: [DeviceType.Button],
11103
+ kind: "wrapper",
11104
+ defaultActive: false,
11105
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11106
+ exposesDeviceSettings: true,
11063
11107
  methods: {},
11064
11108
  events: {
11065
11109
  /**
@@ -17423,6 +17467,14 @@ var ConfigEntrySchema = object({
17423
17467
  value: unknown(),
17424
17468
  description: string().optional()
17425
17469
  });
17470
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17471
+ /** One resolved linked device — the compact projection consumers need. */
17472
+ var LinkedDeviceSchema = object({
17473
+ deviceId: number(),
17474
+ name: string(),
17475
+ location: string().nullable(),
17476
+ features: array(string())
17477
+ });
17426
17478
  var SavedDeviceRowSchema = object({
17427
17479
  /** Numeric id reserved at allocateDeviceId time. */
17428
17480
  id: number(),
@@ -17644,7 +17696,10 @@ method(object({
17644
17696
  }), _void(), {
17645
17697
  kind: "mutation",
17646
17698
  auth: "admin"
17647
- }), 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({
17699
+ }), 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({
17700
+ mode: DeviceLinkModeSchema,
17701
+ devices: array(LinkedDeviceSchema)
17702
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17648
17703
  deviceId: number(),
17649
17704
  values: record(string(), unknown())
17650
17705
  }), object({ success: literal(true) }), {
@@ -18913,6 +18968,61 @@ var EventKindSchema = _enum([
18913
18968
  "object",
18914
18969
  "audio"
18915
18970
  ]);
18971
+ /**
18972
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18973
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18974
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18975
+ */
18976
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18977
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18978
+ var EventKindIconSchema = _enum([
18979
+ "motion",
18980
+ "audio",
18981
+ "person",
18982
+ "vehicle",
18983
+ "animal",
18984
+ "door",
18985
+ "pir",
18986
+ "smoke",
18987
+ "water",
18988
+ "button",
18989
+ "generic"
18990
+ ]);
18991
+ var EventKindCategorySchema = _enum([
18992
+ "motion",
18993
+ "audio",
18994
+ "detection",
18995
+ "sensor",
18996
+ "custom"
18997
+ ]);
18998
+ var EventKindDescriptorSchema = object({
18999
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19000
+ kind: string(),
19001
+ label: string(),
19002
+ /** Hex color for timeline/legend rendering. */
19003
+ color: string(),
19004
+ icon: EventKindIconSchema,
19005
+ category: EventKindCategorySchema,
19006
+ /** Which cap + device contributes this kind. For built-ins the camera
19007
+ * itself; for sensor kinds the LINKED source device. */
19008
+ source: object({
19009
+ capName: string(),
19010
+ deviceId: number()
19011
+ })
19012
+ });
19013
+ var SensorEventSchema = object({
19014
+ id: string(),
19015
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19016
+ * yields N rows, one per camera). */
19017
+ deviceId: number(),
19018
+ /** The linked sensor device whose state changed. */
19019
+ sourceDeviceId: number(),
19020
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19021
+ kind: string(),
19022
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19023
+ value: record(string(), unknown()).nullable(),
19024
+ timestamp: number()
19025
+ });
18916
19026
  var TrackPositionSchema = object({
18917
19027
  x: number(),
18918
19028
  y: number(),
@@ -18926,6 +19036,30 @@ var TrackSnapshotSchema = object({
18926
19036
  mediaKey: string()
18927
19037
  });
18928
19038
  /**
19039
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19040
+ * divided by the track's detection-frame dims), computed at persist time.
19041
+ * Absent when the frame dims were unknown when the track was persisted
19042
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19043
+ */
19044
+ var TrackEnvelopeSchema = object({
19045
+ minX: number(),
19046
+ minY: number(),
19047
+ maxX: number(),
19048
+ maxY: number()
19049
+ });
19050
+ /**
19051
+ * Row projection for track list queries. `full` (default) returns the
19052
+ * complete Track including the frame-rate `positions[]` history and the
19053
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19054
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19055
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19056
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19057
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19058
+ * `getTrack`. Mirrors the event-store `projection` convention
19059
+ * (`getObjectEvents` et al.).
19060
+ */
19061
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19062
+ /**
18929
19063
  * One audio-classification label heard on the track's camera while the
18930
19064
  * track was alive, aggregated per label. An "episode" is one persisted
18931
19065
  * audio event (the confident-classification path: score ≥ the device's
@@ -18976,7 +19110,11 @@ var TrackSchema = object({
18976
19110
  /** Audio-classification labels heard on the camera during the track's
18977
19111
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18978
19112
  * Absent on legacy rows / tracks with no confident audio. */
18979
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19113
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19114
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19115
+ * Populated from the persisted envelope columns on historical reads;
19116
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19117
+ envelope: TrackEnvelopeSchema.optional()
18980
19118
  });
18981
19119
  var BaseEventFields = {
18982
19120
  id: string(),
@@ -19086,11 +19224,13 @@ var MediaFileSchema = object({
19086
19224
  sizeBytes: number(),
19087
19225
  timestamp: number()
19088
19226
  });
19227
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19228
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19089
19229
  var DeviceEventQueryInput = object({
19090
19230
  deviceId: number(),
19091
19231
  since: number().optional(),
19092
19232
  until: number().optional(),
19093
- limit: number().int().min(1).max(5e3).default(1e3),
19233
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19094
19234
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19095
19235
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19096
19236
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19098,6 +19238,27 @@ var DeviceEventQueryInput = object({
19098
19238
  projection: _enum(["full", "slim"]).optional()
19099
19239
  });
19100
19240
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19241
+ var RecentTracksQueryInput = object({
19242
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19243
+ deviceIds: array(number()),
19244
+ /** Window lower bound on `lastSeen` (inclusive). */
19245
+ since: number().optional(),
19246
+ /** Window upper bound on `lastSeen` (inclusive). */
19247
+ until: number().optional(),
19248
+ /** Page size. Default 200, max 1000. */
19249
+ limit: number().int().min(1).max(1e3).default(200),
19250
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19251
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19252
+ cursor: string().optional(),
19253
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19254
+ projection: TrackProjectionSchema.optional()
19255
+ });
19256
+ var RecentTracksPageSchema = object({
19257
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19258
+ tracks: array(TrackSchema).readonly(),
19259
+ /** Cursor for the next page, or null when this page is the last. */
19260
+ nextCursor: string().nullable()
19261
+ });
19101
19262
  var KeyEventQueryInput = object({
19102
19263
  deviceId: number(),
19103
19264
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19180,11 +19341,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19180
19341
  deviceId: number(),
19181
19342
  since: number().optional(),
19182
19343
  until: number().optional(),
19183
- limit: number().optional()
19184
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19344
+ limit: number().optional(),
19345
+ /** Spatial filter only tracks whose trajectory intersects the zone
19346
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19347
+ * envelope columns, then precisely tested per position. Tracks with
19348
+ * an unknown envelope (no frame dims at persist time) always match. */
19349
+ zone: TrackZoneFilterSchema.optional(),
19350
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19351
+ * compatible — omitting the field keeps today's exact behaviour). */
19352
+ projection: TrackProjectionSchema.optional()
19353
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19185
19354
  kind: "mutation",
19186
19355
  auth: "admin"
19187
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19356
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19357
+ deviceId: number(),
19358
+ since: number().optional(),
19359
+ until: number().optional(),
19360
+ kinds: array(string()).optional(),
19361
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19362
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19188
19363
  deviceId: number(),
19189
19364
  since: number(),
19190
19365
  until: number(),
@@ -22554,7 +22729,12 @@ var RecordingStorageUsageSchema = object({
22554
22729
  /**
22555
22730
  * Result of locating footage at a wall-clock instant for one device/profile.
22556
22731
  * `segment` carries the covering segment's window; `gap` reports the forward
22557
- * nearest covered edge (`null` past the end of footage / when none exists).
22732
+ * nearest covered edge (`null` past the end of footage / when none exists)
22733
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22734
+ * footage behind the epoch; `null` when none — optional so older providers
22735
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22736
+ * small inter-segment cracks (durMs under-covers the span to the next
22737
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22558
22738
  */
22559
22739
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22560
22740
  kind: literal("segment"),
@@ -22563,7 +22743,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22563
22743
  bytes: number()
22564
22744
  }), object({
22565
22745
  kind: literal("gap"),
22566
- nearestEdgeMs: number().nullable()
22746
+ nearestEdgeMs: number().nullable(),
22747
+ prevEndMs: number().nullable().optional()
22567
22748
  })]);
22568
22749
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22569
22750
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24343,6 +24524,12 @@ Object.freeze({
24343
24524
  addonId: null,
24344
24525
  access: "view"
24345
24526
  },
24527
+ "deviceManager.getLinkedDevices": {
24528
+ capName: "device-manager",
24529
+ capScope: "system",
24530
+ addonId: null,
24531
+ access: "view"
24532
+ },
24346
24533
  "deviceManager.getRoleDisplayDefaults": {
24347
24534
  capName: "device-manager",
24348
24535
  capScope: "system",
@@ -25897,6 +26084,12 @@ Object.freeze({
25897
26084
  addonId: null,
25898
26085
  access: "view"
25899
26086
  },
26087
+ "pipelineAnalytics.getSensorEvents": {
26088
+ capName: "pipeline-analytics",
26089
+ capScope: "device",
26090
+ addonId: null,
26091
+ access: "view"
26092
+ },
25900
26093
  "pipelineAnalytics.getTrack": {
25901
26094
  capName: "pipeline-analytics",
25902
26095
  capScope: "device",
@@ -25909,6 +26102,18 @@ Object.freeze({
25909
26102
  addonId: null,
25910
26103
  access: "view"
25911
26104
  },
26105
+ "pipelineAnalytics.listEventKinds": {
26106
+ capName: "pipeline-analytics",
26107
+ capScope: "device",
26108
+ addonId: null,
26109
+ access: "view"
26110
+ },
26111
+ "pipelineAnalytics.listRecentTracks": {
26112
+ capName: "pipeline-analytics",
26113
+ capScope: "device",
26114
+ addonId: null,
26115
+ access: "view"
26116
+ },
25912
26117
  "pipelineAnalytics.listTracks": {
25913
26118
  capName: "pipeline-analytics",
25914
26119
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -21,7 +21,7 @@ import netImpl from "net";
21
21
  import { fileURLToPath } from "url";
22
22
  import { mkdir } from "fs/promises";
23
23
  import os from "node:os";
24
- //#region ../types/dist/event-category-CFZs3jI4.mjs
24
+ //#region ../types/dist/event-category-H4AVePnn.mjs
25
25
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
26
26
  EventCategory["SystemBoot"] = "system.boot";
27
27
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -171,6 +171,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
171
171
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
172
172
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
173
173
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
174
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
175
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
176
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
174
177
  EventCategory["DetectionEvent"] = "detection.event";
175
178
  EventCategory["SessionTrackNew"] = "session.track.new";
176
179
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7443,6 +7446,24 @@ var RecordingRetentionSchema = object({
7443
7446
  maxSizeGb: number().min(0).optional()
7444
7447
  });
7445
7448
  /**
7449
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7450
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7451
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7452
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7453
+ *
7454
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7455
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7456
+ * preset changed over time renders every historical window at the dims it was
7457
+ * written with.
7458
+ */
7459
+ var ScrubThumbnailPresetSchema = _enum([
7460
+ "minimal",
7461
+ "low",
7462
+ "standard",
7463
+ "high",
7464
+ "max"
7465
+ ]);
7466
+ /**
7446
7467
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7447
7468
  *
7448
7469
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7479,7 +7500,14 @@ var RecordingConfigSchema = object({
7479
7500
  * derived into bands once via `migrateConfigToBands`.
7480
7501
  */
7481
7502
  bands: array(RecordingBandSchema).optional(),
7482
- retention: RecordingRetentionSchema.optional()
7503
+ retention: RecordingRetentionSchema.optional(),
7504
+ /**
7505
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7506
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7507
+ * windows only — existing sheets are immutable, and each window's index
7508
+ * carries its own tile dims so mixed-preset history renders correctly.
7509
+ */
7510
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7483
7511
  });
7484
7512
  /**
7485
7513
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -11030,10 +11058,23 @@ var deviceDiscoveryCapability = {
11030
11058
  }
11031
11059
  };
11032
11060
  /**
11033
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11034
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11035
- * time the firmware pushes a ring; status tracks the last press and
11036
- * a pressCount since start (diagnostic).
11061
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11062
+ * name (same pattern as `snapshot`):
11063
+ *
11064
+ * - **Native** providers: registered per-device by device-driver
11065
+ * addons via `ctx.registerNativeCap` — either on a
11066
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11067
+ * or directly on the camera (Reolink registers at camera level).
11068
+ * Emits an `onPressed` event every time the firmware pushes a
11069
+ * ring; status tracks the last press and a pressCount since start
11070
+ * (diagnostic).
11071
+ *
11072
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11073
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11074
+ * device (contact / switch / event-emitter …) into a doorbell for
11075
+ * a camera. `defaultActive: false` — the operator explicitly binds
11076
+ * it per camera in the device-bindings UI, then picks the source
11077
+ * device + trigger in the per-device settings.
11037
11078
  *
11038
11079
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11039
11080
  * — subscribers listening at the camera level receive ring events
@@ -11054,7 +11095,10 @@ var doorbellCapability = {
11054
11095
  scope: "device",
11055
11096
  deviceNative: true,
11056
11097
  mode: "singleton",
11057
- deviceTypes: [DeviceType.Button],
11098
+ kind: "wrapper",
11099
+ defaultActive: false,
11100
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11101
+ exposesDeviceSettings: true,
11058
11102
  methods: {},
11059
11103
  events: {
11060
11104
  /**
@@ -17418,6 +17462,14 @@ var ConfigEntrySchema = object({
17418
17462
  value: unknown(),
17419
17463
  description: string().optional()
17420
17464
  });
17465
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17466
+ /** One resolved linked device — the compact projection consumers need. */
17467
+ var LinkedDeviceSchema = object({
17468
+ deviceId: number(),
17469
+ name: string(),
17470
+ location: string().nullable(),
17471
+ features: array(string())
17472
+ });
17421
17473
  var SavedDeviceRowSchema = object({
17422
17474
  /** Numeric id reserved at allocateDeviceId time. */
17423
17475
  id: number(),
@@ -17639,7 +17691,10 @@ method(object({
17639
17691
  }), _void(), {
17640
17692
  kind: "mutation",
17641
17693
  auth: "admin"
17642
- }), 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({
17694
+ }), 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({
17695
+ mode: DeviceLinkModeSchema,
17696
+ devices: array(LinkedDeviceSchema)
17697
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17643
17698
  deviceId: number(),
17644
17699
  values: record(string(), unknown())
17645
17700
  }), object({ success: literal(true) }), {
@@ -18908,6 +18963,61 @@ var EventKindSchema = _enum([
18908
18963
  "object",
18909
18964
  "audio"
18910
18965
  ]);
18966
+ /**
18967
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18968
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18969
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18970
+ */
18971
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18972
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18973
+ var EventKindIconSchema = _enum([
18974
+ "motion",
18975
+ "audio",
18976
+ "person",
18977
+ "vehicle",
18978
+ "animal",
18979
+ "door",
18980
+ "pir",
18981
+ "smoke",
18982
+ "water",
18983
+ "button",
18984
+ "generic"
18985
+ ]);
18986
+ var EventKindCategorySchema = _enum([
18987
+ "motion",
18988
+ "audio",
18989
+ "detection",
18990
+ "sensor",
18991
+ "custom"
18992
+ ]);
18993
+ var EventKindDescriptorSchema = object({
18994
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18995
+ kind: string(),
18996
+ label: string(),
18997
+ /** Hex color for timeline/legend rendering. */
18998
+ color: string(),
18999
+ icon: EventKindIconSchema,
19000
+ category: EventKindCategorySchema,
19001
+ /** Which cap + device contributes this kind. For built-ins the camera
19002
+ * itself; for sensor kinds the LINKED source device. */
19003
+ source: object({
19004
+ capName: string(),
19005
+ deviceId: number()
19006
+ })
19007
+ });
19008
+ var SensorEventSchema = object({
19009
+ id: string(),
19010
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19011
+ * yields N rows, one per camera). */
19012
+ deviceId: number(),
19013
+ /** The linked sensor device whose state changed. */
19014
+ sourceDeviceId: number(),
19015
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19016
+ kind: string(),
19017
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19018
+ value: record(string(), unknown()).nullable(),
19019
+ timestamp: number()
19020
+ });
18911
19021
  var TrackPositionSchema = object({
18912
19022
  x: number(),
18913
19023
  y: number(),
@@ -18921,6 +19031,30 @@ var TrackSnapshotSchema = object({
18921
19031
  mediaKey: string()
18922
19032
  });
18923
19033
  /**
19034
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19035
+ * divided by the track's detection-frame dims), computed at persist time.
19036
+ * Absent when the frame dims were unknown when the track was persisted
19037
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19038
+ */
19039
+ var TrackEnvelopeSchema = object({
19040
+ minX: number(),
19041
+ minY: number(),
19042
+ maxX: number(),
19043
+ maxY: number()
19044
+ });
19045
+ /**
19046
+ * Row projection for track list queries. `full` (default) returns the
19047
+ * complete Track including the frame-rate `positions[]` history and the
19048
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19049
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19050
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19051
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19052
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19053
+ * `getTrack`. Mirrors the event-store `projection` convention
19054
+ * (`getObjectEvents` et al.).
19055
+ */
19056
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19057
+ /**
18924
19058
  * One audio-classification label heard on the track's camera while the
18925
19059
  * track was alive, aggregated per label. An "episode" is one persisted
18926
19060
  * audio event (the confident-classification path: score ≥ the device's
@@ -18971,7 +19105,11 @@ var TrackSchema = object({
18971
19105
  /** Audio-classification labels heard on the camera during the track's
18972
19106
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18973
19107
  * Absent on legacy rows / tracks with no confident audio. */
18974
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19108
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19109
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19110
+ * Populated from the persisted envelope columns on historical reads;
19111
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19112
+ envelope: TrackEnvelopeSchema.optional()
18975
19113
  });
18976
19114
  var BaseEventFields = {
18977
19115
  id: string(),
@@ -19081,11 +19219,13 @@ var MediaFileSchema = object({
19081
19219
  sizeBytes: number(),
19082
19220
  timestamp: number()
19083
19221
  });
19222
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19223
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19084
19224
  var DeviceEventQueryInput = object({
19085
19225
  deviceId: number(),
19086
19226
  since: number().optional(),
19087
19227
  until: number().optional(),
19088
- limit: number().int().min(1).max(5e3).default(1e3),
19228
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19089
19229
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19090
19230
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19091
19231
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19093,6 +19233,27 @@ var DeviceEventQueryInput = object({
19093
19233
  projection: _enum(["full", "slim"]).optional()
19094
19234
  });
19095
19235
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19236
+ var RecentTracksQueryInput = object({
19237
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19238
+ deviceIds: array(number()),
19239
+ /** Window lower bound on `lastSeen` (inclusive). */
19240
+ since: number().optional(),
19241
+ /** Window upper bound on `lastSeen` (inclusive). */
19242
+ until: number().optional(),
19243
+ /** Page size. Default 200, max 1000. */
19244
+ limit: number().int().min(1).max(1e3).default(200),
19245
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19246
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19247
+ cursor: string().optional(),
19248
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19249
+ projection: TrackProjectionSchema.optional()
19250
+ });
19251
+ var RecentTracksPageSchema = object({
19252
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19253
+ tracks: array(TrackSchema).readonly(),
19254
+ /** Cursor for the next page, or null when this page is the last. */
19255
+ nextCursor: string().nullable()
19256
+ });
19096
19257
  var KeyEventQueryInput = object({
19097
19258
  deviceId: number(),
19098
19259
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19175,11 +19336,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19175
19336
  deviceId: number(),
19176
19337
  since: number().optional(),
19177
19338
  until: number().optional(),
19178
- limit: number().optional()
19179
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19339
+ limit: number().optional(),
19340
+ /** Spatial filter only tracks whose trajectory intersects the zone
19341
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19342
+ * envelope columns, then precisely tested per position. Tracks with
19343
+ * an unknown envelope (no frame dims at persist time) always match. */
19344
+ zone: TrackZoneFilterSchema.optional(),
19345
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19346
+ * compatible — omitting the field keeps today's exact behaviour). */
19347
+ projection: TrackProjectionSchema.optional()
19348
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19180
19349
  kind: "mutation",
19181
19350
  auth: "admin"
19182
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19351
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19352
+ deviceId: number(),
19353
+ since: number().optional(),
19354
+ until: number().optional(),
19355
+ kinds: array(string()).optional(),
19356
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19357
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19183
19358
  deviceId: number(),
19184
19359
  since: number(),
19185
19360
  until: number(),
@@ -22549,7 +22724,12 @@ var RecordingStorageUsageSchema = object({
22549
22724
  /**
22550
22725
  * Result of locating footage at a wall-clock instant for one device/profile.
22551
22726
  * `segment` carries the covering segment's window; `gap` reports the forward
22552
- * nearest covered edge (`null` past the end of footage / when none exists).
22727
+ * nearest covered edge (`null` past the end of footage / when none exists)
22728
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22729
+ * footage behind the epoch; `null` when none — optional so older providers
22730
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22731
+ * small inter-segment cracks (durMs under-covers the span to the next
22732
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22553
22733
  */
22554
22734
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22555
22735
  kind: literal("segment"),
@@ -22558,7 +22738,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22558
22738
  bytes: number()
22559
22739
  }), object({
22560
22740
  kind: literal("gap"),
22561
- nearestEdgeMs: number().nullable()
22741
+ nearestEdgeMs: number().nullable(),
22742
+ prevEndMs: number().nullable().optional()
22562
22743
  })]);
22563
22744
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22564
22745
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24338,6 +24519,12 @@ Object.freeze({
24338
24519
  addonId: null,
24339
24520
  access: "view"
24340
24521
  },
24522
+ "deviceManager.getLinkedDevices": {
24523
+ capName: "device-manager",
24524
+ capScope: "system",
24525
+ addonId: null,
24526
+ access: "view"
24527
+ },
24341
24528
  "deviceManager.getRoleDisplayDefaults": {
24342
24529
  capName: "device-manager",
24343
24530
  capScope: "system",
@@ -25892,6 +26079,12 @@ Object.freeze({
25892
26079
  addonId: null,
25893
26080
  access: "view"
25894
26081
  },
26082
+ "pipelineAnalytics.getSensorEvents": {
26083
+ capName: "pipeline-analytics",
26084
+ capScope: "device",
26085
+ addonId: null,
26086
+ access: "view"
26087
+ },
25895
26088
  "pipelineAnalytics.getTrack": {
25896
26089
  capName: "pipeline-analytics",
25897
26090
  capScope: "device",
@@ -25904,6 +26097,18 @@ Object.freeze({
25904
26097
  addonId: null,
25905
26098
  access: "view"
25906
26099
  },
26100
+ "pipelineAnalytics.listEventKinds": {
26101
+ capName: "pipeline-analytics",
26102
+ capScope: "device",
26103
+ addonId: null,
26104
+ access: "view"
26105
+ },
26106
+ "pipelineAnalytics.listRecentTracks": {
26107
+ capName: "pipeline-analytics",
26108
+ capScope: "device",
26109
+ addonId: null,
26110
+ access: "view"
26111
+ },
25907
26112
  "pipelineAnalytics.listTracks": {
25908
26113
  capName: "pipeline-analytics",
25909
26114
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",