@camstack/addon-provider-homematic 1.1.22 → 1.1.24

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
@@ -8,7 +8,7 @@ let http = require("http");
8
8
  let crypto$1 = require("crypto");
9
9
  let fs_promises = require("fs/promises");
10
10
  let path = require("path");
11
- //#region ../types/dist/event-category-CFZs3jI4.mjs
11
+ //#region ../types/dist/event-category-H4AVePnn.mjs
12
12
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
13
13
  EventCategory["SystemBoot"] = "system.boot";
14
14
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -158,6 +158,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
158
158
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
159
159
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
160
160
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
161
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
162
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
163
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
161
164
  EventCategory["DetectionEvent"] = "detection.event";
162
165
  EventCategory["SessionTrackNew"] = "session.track.new";
163
166
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7257,6 +7260,24 @@ var RecordingRetentionSchema = object({
7257
7260
  maxSizeGb: number().min(0).optional()
7258
7261
  });
7259
7262
  /**
7263
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7264
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7265
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7266
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7267
+ *
7268
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7269
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7270
+ * preset changed over time renders every historical window at the dims it was
7271
+ * written with.
7272
+ */
7273
+ var ScrubThumbnailPresetSchema = _enum([
7274
+ "minimal",
7275
+ "low",
7276
+ "standard",
7277
+ "high",
7278
+ "max"
7279
+ ]);
7280
+ /**
7260
7281
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7261
7282
  *
7262
7283
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7293,7 +7314,14 @@ var RecordingConfigSchema = object({
7293
7314
  * derived into bands once via `migrateConfigToBands`.
7294
7315
  */
7295
7316
  bands: array(RecordingBandSchema).optional(),
7296
- retention: RecordingRetentionSchema.optional()
7317
+ retention: RecordingRetentionSchema.optional(),
7318
+ /**
7319
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7320
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7321
+ * windows only — existing sheets are immutable, and each window's index
7322
+ * carries its own tile dims so mixed-preset history renders correctly.
7323
+ */
7324
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7297
7325
  });
7298
7326
  /**
7299
7327
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10805,10 +10833,23 @@ var deviceDiscoveryCapability = {
10805
10833
  }
10806
10834
  };
10807
10835
  /**
10808
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10809
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10810
- * time the firmware pushes a ring; status tracks the last press and
10811
- * a pressCount since start (diagnostic).
10836
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10837
+ * name (same pattern as `snapshot`):
10838
+ *
10839
+ * - **Native** providers: registered per-device by device-driver
10840
+ * addons via `ctx.registerNativeCap` — either on a
10841
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10842
+ * or directly on the camera (Reolink registers at camera level).
10843
+ * Emits an `onPressed` event every time the firmware pushes a
10844
+ * ring; status tracks the last press and a pressCount since start
10845
+ * (diagnostic).
10846
+ *
10847
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10848
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10849
+ * device (contact / switch / event-emitter …) into a doorbell for
10850
+ * a camera. `defaultActive: false` — the operator explicitly binds
10851
+ * it per camera in the device-bindings UI, then picks the source
10852
+ * device + trigger in the per-device settings.
10812
10853
  *
10813
10854
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10814
10855
  * — subscribers listening at the camera level receive ring events
@@ -10829,7 +10870,10 @@ var doorbellCapability = {
10829
10870
  scope: "device",
10830
10871
  deviceNative: true,
10831
10872
  mode: "singleton",
10832
- deviceTypes: [DeviceType.Button],
10873
+ kind: "wrapper",
10874
+ defaultActive: false,
10875
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10876
+ exposesDeviceSettings: true,
10833
10877
  methods: {},
10834
10878
  events: {
10835
10879
  /**
@@ -17247,6 +17291,14 @@ var ConfigEntrySchema = object({
17247
17291
  value: unknown(),
17248
17292
  description: string().optional()
17249
17293
  });
17294
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17295
+ /** One resolved linked device — the compact projection consumers need. */
17296
+ var LinkedDeviceSchema = object({
17297
+ deviceId: number(),
17298
+ name: string(),
17299
+ location: string().nullable(),
17300
+ features: array(string())
17301
+ });
17250
17302
  var SavedDeviceRowSchema = object({
17251
17303
  /** Numeric id reserved at allocateDeviceId time. */
17252
17304
  id: number(),
@@ -17468,7 +17520,10 @@ method(object({
17468
17520
  }), _void(), {
17469
17521
  kind: "mutation",
17470
17522
  auth: "admin"
17471
- }), 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({
17523
+ }), 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({
17524
+ mode: DeviceLinkModeSchema,
17525
+ devices: array(LinkedDeviceSchema)
17526
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17472
17527
  deviceId: number(),
17473
17528
  values: record(string(), unknown())
17474
17529
  }), object({ success: literal(true) }), {
@@ -18737,6 +18792,61 @@ var EventKindSchema = _enum([
18737
18792
  "object",
18738
18793
  "audio"
18739
18794
  ]);
18795
+ /**
18796
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18797
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18798
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18799
+ */
18800
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18801
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18802
+ var EventKindIconSchema = _enum([
18803
+ "motion",
18804
+ "audio",
18805
+ "person",
18806
+ "vehicle",
18807
+ "animal",
18808
+ "door",
18809
+ "pir",
18810
+ "smoke",
18811
+ "water",
18812
+ "button",
18813
+ "generic"
18814
+ ]);
18815
+ var EventKindCategorySchema = _enum([
18816
+ "motion",
18817
+ "audio",
18818
+ "detection",
18819
+ "sensor",
18820
+ "custom"
18821
+ ]);
18822
+ var EventKindDescriptorSchema = object({
18823
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18824
+ kind: string(),
18825
+ label: string(),
18826
+ /** Hex color for timeline/legend rendering. */
18827
+ color: string(),
18828
+ icon: EventKindIconSchema,
18829
+ category: EventKindCategorySchema,
18830
+ /** Which cap + device contributes this kind. For built-ins the camera
18831
+ * itself; for sensor kinds the LINKED source device. */
18832
+ source: object({
18833
+ capName: string(),
18834
+ deviceId: number()
18835
+ })
18836
+ });
18837
+ var SensorEventSchema = object({
18838
+ id: string(),
18839
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18840
+ * yields N rows, one per camera). */
18841
+ deviceId: number(),
18842
+ /** The linked sensor device whose state changed. */
18843
+ sourceDeviceId: number(),
18844
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18845
+ kind: string(),
18846
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18847
+ value: record(string(), unknown()).nullable(),
18848
+ timestamp: number()
18849
+ });
18740
18850
  var TrackPositionSchema = object({
18741
18851
  x: number(),
18742
18852
  y: number(),
@@ -18750,6 +18860,30 @@ var TrackSnapshotSchema = object({
18750
18860
  mediaKey: string()
18751
18861
  });
18752
18862
  /**
18863
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18864
+ * divided by the track's detection-frame dims), computed at persist time.
18865
+ * Absent when the frame dims were unknown when the track was persisted
18866
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18867
+ */
18868
+ var TrackEnvelopeSchema = object({
18869
+ minX: number(),
18870
+ minY: number(),
18871
+ maxX: number(),
18872
+ maxY: number()
18873
+ });
18874
+ /**
18875
+ * Row projection for track list queries. `full` (default) returns the
18876
+ * complete Track including the frame-rate `positions[]` history and the
18877
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18878
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18879
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18880
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18881
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18882
+ * `getTrack`. Mirrors the event-store `projection` convention
18883
+ * (`getObjectEvents` et al.).
18884
+ */
18885
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18886
+ /**
18753
18887
  * One audio-classification label heard on the track's camera while the
18754
18888
  * track was alive, aggregated per label. An "episode" is one persisted
18755
18889
  * audio event (the confident-classification path: score ≥ the device's
@@ -18800,7 +18934,11 @@ var TrackSchema = object({
18800
18934
  /** Audio-classification labels heard on the camera during the track's
18801
18935
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18802
18936
  * Absent on legacy rows / tracks with no confident audio. */
18803
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18937
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18938
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18939
+ * Populated from the persisted envelope columns on historical reads;
18940
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18941
+ envelope: TrackEnvelopeSchema.optional()
18804
18942
  });
18805
18943
  var BaseEventFields = {
18806
18944
  id: string(),
@@ -18910,11 +19048,13 @@ var MediaFileSchema = object({
18910
19048
  sizeBytes: number(),
18911
19049
  timestamp: number()
18912
19050
  });
19051
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19052
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18913
19053
  var DeviceEventQueryInput = object({
18914
19054
  deviceId: number(),
18915
19055
  since: number().optional(),
18916
19056
  until: number().optional(),
18917
- limit: number().int().min(1).max(5e3).default(1e3),
19057
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18918
19058
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18919
19059
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18920
19060
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18922,6 +19062,27 @@ var DeviceEventQueryInput = object({
18922
19062
  projection: _enum(["full", "slim"]).optional()
18923
19063
  });
18924
19064
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19065
+ var RecentTracksQueryInput = object({
19066
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19067
+ deviceIds: array(number()),
19068
+ /** Window lower bound on `lastSeen` (inclusive). */
19069
+ since: number().optional(),
19070
+ /** Window upper bound on `lastSeen` (inclusive). */
19071
+ until: number().optional(),
19072
+ /** Page size. Default 200, max 1000. */
19073
+ limit: number().int().min(1).max(1e3).default(200),
19074
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19075
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19076
+ cursor: string().optional(),
19077
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19078
+ projection: TrackProjectionSchema.optional()
19079
+ });
19080
+ var RecentTracksPageSchema = object({
19081
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19082
+ tracks: array(TrackSchema).readonly(),
19083
+ /** Cursor for the next page, or null when this page is the last. */
19084
+ nextCursor: string().nullable()
19085
+ });
18925
19086
  var KeyEventQueryInput = object({
18926
19087
  deviceId: number(),
18927
19088
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19004,11 +19165,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19004
19165
  deviceId: number(),
19005
19166
  since: number().optional(),
19006
19167
  until: number().optional(),
19007
- limit: number().optional()
19008
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19168
+ limit: number().optional(),
19169
+ /** Spatial filter — only tracks whose trajectory intersects the zone
19170
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19171
+ * envelope columns, then precisely tested per position. Tracks with
19172
+ * an unknown envelope (no frame dims at persist time) always match. */
19173
+ zone: TrackZoneFilterSchema.optional(),
19174
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19175
+ * compatible — omitting the field keeps today's exact behaviour). */
19176
+ projection: TrackProjectionSchema.optional()
19177
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19009
19178
  kind: "mutation",
19010
19179
  auth: "admin"
19011
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19180
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19181
+ deviceId: number(),
19182
+ since: number().optional(),
19183
+ until: number().optional(),
19184
+ kinds: array(string()).optional(),
19185
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19186
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19012
19187
  deviceId: number(),
19013
19188
  since: number(),
19014
19189
  until: number(),
@@ -22209,7 +22384,12 @@ var RecordingStorageUsageSchema = object({
22209
22384
  /**
22210
22385
  * Result of locating footage at a wall-clock instant for one device/profile.
22211
22386
  * `segment` carries the covering segment's window; `gap` reports the forward
22212
- * nearest covered edge (`null` past the end of footage / when none exists).
22387
+ * nearest covered edge (`null` past the end of footage / when none exists)
22388
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22389
+ * footage behind the epoch; `null` when none — optional so older providers
22390
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22391
+ * small inter-segment cracks (durMs under-covers the span to the next
22392
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22213
22393
  */
22214
22394
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22215
22395
  kind: literal("segment"),
@@ -22218,7 +22398,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22218
22398
  bytes: number()
22219
22399
  }), object({
22220
22400
  kind: literal("gap"),
22221
- nearestEdgeMs: number().nullable()
22401
+ nearestEdgeMs: number().nullable(),
22402
+ prevEndMs: number().nullable().optional()
22222
22403
  })]);
22223
22404
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22224
22405
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23787,6 +23968,12 @@ Object.freeze({
23787
23968
  addonId: null,
23788
23969
  access: "view"
23789
23970
  },
23971
+ "deviceManager.getLinkedDevices": {
23972
+ capName: "device-manager",
23973
+ capScope: "system",
23974
+ addonId: null,
23975
+ access: "view"
23976
+ },
23790
23977
  "deviceManager.getRoleDisplayDefaults": {
23791
23978
  capName: "device-manager",
23792
23979
  capScope: "system",
@@ -25341,6 +25528,12 @@ Object.freeze({
25341
25528
  addonId: null,
25342
25529
  access: "view"
25343
25530
  },
25531
+ "pipelineAnalytics.getSensorEvents": {
25532
+ capName: "pipeline-analytics",
25533
+ capScope: "device",
25534
+ addonId: null,
25535
+ access: "view"
25536
+ },
25344
25537
  "pipelineAnalytics.getTrack": {
25345
25538
  capName: "pipeline-analytics",
25346
25539
  capScope: "device",
@@ -25353,6 +25546,18 @@ Object.freeze({
25353
25546
  addonId: null,
25354
25547
  access: "view"
25355
25548
  },
25549
+ "pipelineAnalytics.listEventKinds": {
25550
+ capName: "pipeline-analytics",
25551
+ capScope: "device",
25552
+ addonId: null,
25553
+ access: "view"
25554
+ },
25555
+ "pipelineAnalytics.listRecentTracks": {
25556
+ capName: "pipeline-analytics",
25557
+ capScope: "device",
25558
+ addonId: null,
25559
+ access: "view"
25560
+ },
25356
25561
  "pipelineAnalytics.listTracks": {
25357
25562
  capName: "pipeline-analytics",
25358
25563
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -9,7 +9,7 @@ import { join } from "path";
9
9
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
10
10
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
11
11
  //#endregion
12
- //#region ../types/dist/event-category-CFZs3jI4.mjs
12
+ //#region ../types/dist/event-category-H4AVePnn.mjs
13
13
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
14
14
  EventCategory["SystemBoot"] = "system.boot";
15
15
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -159,6 +159,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
159
159
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
160
160
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
161
161
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
162
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
163
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
164
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
162
165
  EventCategory["DetectionEvent"] = "detection.event";
163
166
  EventCategory["SessionTrackNew"] = "session.track.new";
164
167
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -7258,6 +7261,24 @@ var RecordingRetentionSchema = object({
7258
7261
  maxSizeGb: number().min(0).optional()
7259
7262
  });
7260
7263
  /**
7264
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7265
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7266
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7267
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7268
+ *
7269
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7270
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7271
+ * preset changed over time renders every historical window at the dims it was
7272
+ * written with.
7273
+ */
7274
+ var ScrubThumbnailPresetSchema = _enum([
7275
+ "minimal",
7276
+ "low",
7277
+ "standard",
7278
+ "high",
7279
+ "max"
7280
+ ]);
7281
+ /**
7261
7282
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7262
7283
  *
7263
7284
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7294,7 +7315,14 @@ var RecordingConfigSchema = object({
7294
7315
  * derived into bands once via `migrateConfigToBands`.
7295
7316
  */
7296
7317
  bands: array(RecordingBandSchema).optional(),
7297
- retention: RecordingRetentionSchema.optional()
7318
+ retention: RecordingRetentionSchema.optional(),
7319
+ /**
7320
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7321
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7322
+ * windows only — existing sheets are immutable, and each window's index
7323
+ * carries its own tile dims so mixed-preset history renders correctly.
7324
+ */
7325
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7298
7326
  });
7299
7327
  /**
7300
7328
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -10806,10 +10834,23 @@ var deviceDiscoveryCapability = {
10806
10834
  }
10807
10835
  };
10808
10836
  /**
10809
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
10810
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
10811
- * time the firmware pushes a ring; status tracks the last press and
10812
- * a pressCount since start (diagnostic).
10837
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10838
+ * name (same pattern as `snapshot`):
10839
+ *
10840
+ * - **Native** providers: registered per-device by device-driver
10841
+ * addons via `ctx.registerNativeCap` — either on a
10842
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10843
+ * or directly on the camera (Reolink registers at camera level).
10844
+ * Emits an `onPressed` event every time the firmware pushes a
10845
+ * ring; status tracks the last press and a pressCount since start
10846
+ * (diagnostic).
10847
+ *
10848
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10849
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10850
+ * device (contact / switch / event-emitter …) into a doorbell for
10851
+ * a camera. `defaultActive: false` — the operator explicitly binds
10852
+ * it per camera in the device-bindings UI, then picks the source
10853
+ * device + trigger in the per-device settings.
10813
10854
  *
10814
10855
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10815
10856
  * — subscribers listening at the camera level receive ring events
@@ -10830,7 +10871,10 @@ var doorbellCapability = {
10830
10871
  scope: "device",
10831
10872
  deviceNative: true,
10832
10873
  mode: "singleton",
10833
- deviceTypes: [DeviceType.Button],
10874
+ kind: "wrapper",
10875
+ defaultActive: false,
10876
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10877
+ exposesDeviceSettings: true,
10834
10878
  methods: {},
10835
10879
  events: {
10836
10880
  /**
@@ -17248,6 +17292,14 @@ var ConfigEntrySchema = object({
17248
17292
  value: unknown(),
17249
17293
  description: string().optional()
17250
17294
  });
17295
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17296
+ /** One resolved linked device — the compact projection consumers need. */
17297
+ var LinkedDeviceSchema = object({
17298
+ deviceId: number(),
17299
+ name: string(),
17300
+ location: string().nullable(),
17301
+ features: array(string())
17302
+ });
17251
17303
  var SavedDeviceRowSchema = object({
17252
17304
  /** Numeric id reserved at allocateDeviceId time. */
17253
17305
  id: number(),
@@ -17469,7 +17521,10 @@ method(object({
17469
17521
  }), _void(), {
17470
17522
  kind: "mutation",
17471
17523
  auth: "admin"
17472
- }), 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({
17524
+ }), 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({
17525
+ mode: DeviceLinkModeSchema,
17526
+ devices: array(LinkedDeviceSchema)
17527
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17473
17528
  deviceId: number(),
17474
17529
  values: record(string(), unknown())
17475
17530
  }), object({ success: literal(true) }), {
@@ -18738,6 +18793,61 @@ var EventKindSchema = _enum([
18738
18793
  "object",
18739
18794
  "audio"
18740
18795
  ]);
18796
+ /**
18797
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18798
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18799
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18800
+ */
18801
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18802
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18803
+ var EventKindIconSchema = _enum([
18804
+ "motion",
18805
+ "audio",
18806
+ "person",
18807
+ "vehicle",
18808
+ "animal",
18809
+ "door",
18810
+ "pir",
18811
+ "smoke",
18812
+ "water",
18813
+ "button",
18814
+ "generic"
18815
+ ]);
18816
+ var EventKindCategorySchema = _enum([
18817
+ "motion",
18818
+ "audio",
18819
+ "detection",
18820
+ "sensor",
18821
+ "custom"
18822
+ ]);
18823
+ var EventKindDescriptorSchema = object({
18824
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18825
+ kind: string(),
18826
+ label: string(),
18827
+ /** Hex color for timeline/legend rendering. */
18828
+ color: string(),
18829
+ icon: EventKindIconSchema,
18830
+ category: EventKindCategorySchema,
18831
+ /** Which cap + device contributes this kind. For built-ins the camera
18832
+ * itself; for sensor kinds the LINKED source device. */
18833
+ source: object({
18834
+ capName: string(),
18835
+ deviceId: number()
18836
+ })
18837
+ });
18838
+ var SensorEventSchema = object({
18839
+ id: string(),
18840
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18841
+ * yields N rows, one per camera). */
18842
+ deviceId: number(),
18843
+ /** The linked sensor device whose state changed. */
18844
+ sourceDeviceId: number(),
18845
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18846
+ kind: string(),
18847
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18848
+ value: record(string(), unknown()).nullable(),
18849
+ timestamp: number()
18850
+ });
18741
18851
  var TrackPositionSchema = object({
18742
18852
  x: number(),
18743
18853
  y: number(),
@@ -18751,6 +18861,30 @@ var TrackSnapshotSchema = object({
18751
18861
  mediaKey: string()
18752
18862
  });
18753
18863
  /**
18864
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18865
+ * divided by the track's detection-frame dims), computed at persist time.
18866
+ * Absent when the frame dims were unknown when the track was persisted
18867
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18868
+ */
18869
+ var TrackEnvelopeSchema = object({
18870
+ minX: number(),
18871
+ minY: number(),
18872
+ maxX: number(),
18873
+ maxY: number()
18874
+ });
18875
+ /**
18876
+ * Row projection for track list queries. `full` (default) returns the
18877
+ * complete Track including the frame-rate `positions[]` history and the
18878
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18879
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18880
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18881
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18882
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18883
+ * `getTrack`. Mirrors the event-store `projection` convention
18884
+ * (`getObjectEvents` et al.).
18885
+ */
18886
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18887
+ /**
18754
18888
  * One audio-classification label heard on the track's camera while the
18755
18889
  * track was alive, aggregated per label. An "episode" is one persisted
18756
18890
  * audio event (the confident-classification path: score ≥ the device's
@@ -18801,7 +18935,11 @@ var TrackSchema = object({
18801
18935
  /** Audio-classification labels heard on the camera during the track's
18802
18936
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18803
18937
  * Absent on legacy rows / tracks with no confident audio. */
18804
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18938
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18939
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18940
+ * Populated from the persisted envelope columns on historical reads;
18941
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18942
+ envelope: TrackEnvelopeSchema.optional()
18805
18943
  });
18806
18944
  var BaseEventFields = {
18807
18945
  id: string(),
@@ -18911,11 +19049,13 @@ var MediaFileSchema = object({
18911
19049
  sizeBytes: number(),
18912
19050
  timestamp: number()
18913
19051
  });
19052
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19053
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18914
19054
  var DeviceEventQueryInput = object({
18915
19055
  deviceId: number(),
18916
19056
  since: number().optional(),
18917
19057
  until: number().optional(),
18918
- limit: number().int().min(1).max(5e3).default(1e3),
19058
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18919
19059
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18920
19060
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18921
19061
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18923,6 +19063,27 @@ var DeviceEventQueryInput = object({
18923
19063
  projection: _enum(["full", "slim"]).optional()
18924
19064
  });
18925
19065
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19066
+ var RecentTracksQueryInput = object({
19067
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19068
+ deviceIds: array(number()),
19069
+ /** Window lower bound on `lastSeen` (inclusive). */
19070
+ since: number().optional(),
19071
+ /** Window upper bound on `lastSeen` (inclusive). */
19072
+ until: number().optional(),
19073
+ /** Page size. Default 200, max 1000. */
19074
+ limit: number().int().min(1).max(1e3).default(200),
19075
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19076
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19077
+ cursor: string().optional(),
19078
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19079
+ projection: TrackProjectionSchema.optional()
19080
+ });
19081
+ var RecentTracksPageSchema = object({
19082
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19083
+ tracks: array(TrackSchema).readonly(),
19084
+ /** Cursor for the next page, or null when this page is the last. */
19085
+ nextCursor: string().nullable()
19086
+ });
18926
19087
  var KeyEventQueryInput = object({
18927
19088
  deviceId: number(),
18928
19089
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19005,11 +19166,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19005
19166
  deviceId: number(),
19006
19167
  since: number().optional(),
19007
19168
  until: number().optional(),
19008
- limit: number().optional()
19009
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19169
+ limit: number().optional(),
19170
+ /** Spatial filter — only tracks whose trajectory intersects the zone
19171
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19172
+ * envelope columns, then precisely tested per position. Tracks with
19173
+ * an unknown envelope (no frame dims at persist time) always match. */
19174
+ zone: TrackZoneFilterSchema.optional(),
19175
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19176
+ * compatible — omitting the field keeps today's exact behaviour). */
19177
+ projection: TrackProjectionSchema.optional()
19178
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19010
19179
  kind: "mutation",
19011
19180
  auth: "admin"
19012
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19181
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19182
+ deviceId: number(),
19183
+ since: number().optional(),
19184
+ until: number().optional(),
19185
+ kinds: array(string()).optional(),
19186
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19187
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19013
19188
  deviceId: number(),
19014
19189
  since: number(),
19015
19190
  until: number(),
@@ -22210,7 +22385,12 @@ var RecordingStorageUsageSchema = object({
22210
22385
  /**
22211
22386
  * Result of locating footage at a wall-clock instant for one device/profile.
22212
22387
  * `segment` carries the covering segment's window; `gap` reports the forward
22213
- * nearest covered edge (`null` past the end of footage / when none exists).
22388
+ * nearest covered edge (`null` past the end of footage / when none exists)
22389
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22390
+ * footage behind the epoch; `null` when none — optional so older providers
22391
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22392
+ * small inter-segment cracks (durMs under-covers the span to the next
22393
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22214
22394
  */
22215
22395
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22216
22396
  kind: literal("segment"),
@@ -22219,7 +22399,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22219
22399
  bytes: number()
22220
22400
  }), object({
22221
22401
  kind: literal("gap"),
22222
- nearestEdgeMs: number().nullable()
22402
+ nearestEdgeMs: number().nullable(),
22403
+ prevEndMs: number().nullable().optional()
22223
22404
  })]);
22224
22405
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22225
22406
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23788,6 +23969,12 @@ Object.freeze({
23788
23969
  addonId: null,
23789
23970
  access: "view"
23790
23971
  },
23972
+ "deviceManager.getLinkedDevices": {
23973
+ capName: "device-manager",
23974
+ capScope: "system",
23975
+ addonId: null,
23976
+ access: "view"
23977
+ },
23791
23978
  "deviceManager.getRoleDisplayDefaults": {
23792
23979
  capName: "device-manager",
23793
23980
  capScope: "system",
@@ -25342,6 +25529,12 @@ Object.freeze({
25342
25529
  addonId: null,
25343
25530
  access: "view"
25344
25531
  },
25532
+ "pipelineAnalytics.getSensorEvents": {
25533
+ capName: "pipeline-analytics",
25534
+ capScope: "device",
25535
+ addonId: null,
25536
+ access: "view"
25537
+ },
25345
25538
  "pipelineAnalytics.getTrack": {
25346
25539
  capName: "pipeline-analytics",
25347
25540
  capScope: "device",
@@ -25354,6 +25547,18 @@ Object.freeze({
25354
25547
  addonId: null,
25355
25548
  access: "view"
25356
25549
  },
25550
+ "pipelineAnalytics.listEventKinds": {
25551
+ capName: "pipeline-analytics",
25552
+ capScope: "device",
25553
+ addonId: null,
25554
+ access: "view"
25555
+ },
25556
+ "pipelineAnalytics.listRecentTracks": {
25557
+ capName: "pipeline-analytics",
25558
+ capScope: "device",
25559
+ addonId: null,
25560
+ access: "view"
25561
+ },
25357
25562
  "pipelineAnalytics.listTracks": {
25358
25563
  capName: "pipeline-analytics",
25359
25564
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homematic",
3
- "version": "1.1.22",
3
+ "version": "1.1.24",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",