@camstack/addon-provider-homematic 1.1.22 → 1.1.23

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 +194 -14
  2. package/dist/addon.mjs +194 -14
  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";
@@ -10805,10 +10808,23 @@ var deviceDiscoveryCapability = {
10805
10808
  }
10806
10809
  };
10807
10810
  /**
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).
10811
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10812
+ * name (same pattern as `snapshot`):
10813
+ *
10814
+ * - **Native** providers: registered per-device by device-driver
10815
+ * addons via `ctx.registerNativeCap` — either on a
10816
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10817
+ * or directly on the camera (Reolink registers at camera level).
10818
+ * Emits an `onPressed` event every time the firmware pushes a
10819
+ * ring; status tracks the last press and a pressCount since start
10820
+ * (diagnostic).
10821
+ *
10822
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10823
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10824
+ * device (contact / switch / event-emitter …) into a doorbell for
10825
+ * a camera. `defaultActive: false` — the operator explicitly binds
10826
+ * it per camera in the device-bindings UI, then picks the source
10827
+ * device + trigger in the per-device settings.
10812
10828
  *
10813
10829
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10814
10830
  * — subscribers listening at the camera level receive ring events
@@ -10829,7 +10845,10 @@ var doorbellCapability = {
10829
10845
  scope: "device",
10830
10846
  deviceNative: true,
10831
10847
  mode: "singleton",
10832
- deviceTypes: [DeviceType.Button],
10848
+ kind: "wrapper",
10849
+ defaultActive: false,
10850
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10851
+ exposesDeviceSettings: true,
10833
10852
  methods: {},
10834
10853
  events: {
10835
10854
  /**
@@ -17247,6 +17266,14 @@ var ConfigEntrySchema = object({
17247
17266
  value: unknown(),
17248
17267
  description: string().optional()
17249
17268
  });
17269
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17270
+ /** One resolved linked device — the compact projection consumers need. */
17271
+ var LinkedDeviceSchema = object({
17272
+ deviceId: number(),
17273
+ name: string(),
17274
+ location: string().nullable(),
17275
+ features: array(string())
17276
+ });
17250
17277
  var SavedDeviceRowSchema = object({
17251
17278
  /** Numeric id reserved at allocateDeviceId time. */
17252
17279
  id: number(),
@@ -17468,7 +17495,10 @@ method(object({
17468
17495
  }), _void(), {
17469
17496
  kind: "mutation",
17470
17497
  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({
17498
+ }), 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({
17499
+ mode: DeviceLinkModeSchema,
17500
+ devices: array(LinkedDeviceSchema)
17501
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17472
17502
  deviceId: number(),
17473
17503
  values: record(string(), unknown())
17474
17504
  }), object({ success: literal(true) }), {
@@ -18737,6 +18767,61 @@ var EventKindSchema = _enum([
18737
18767
  "object",
18738
18768
  "audio"
18739
18769
  ]);
18770
+ /**
18771
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18772
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18773
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18774
+ */
18775
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18776
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18777
+ var EventKindIconSchema = _enum([
18778
+ "motion",
18779
+ "audio",
18780
+ "person",
18781
+ "vehicle",
18782
+ "animal",
18783
+ "door",
18784
+ "pir",
18785
+ "smoke",
18786
+ "water",
18787
+ "button",
18788
+ "generic"
18789
+ ]);
18790
+ var EventKindCategorySchema = _enum([
18791
+ "motion",
18792
+ "audio",
18793
+ "detection",
18794
+ "sensor",
18795
+ "custom"
18796
+ ]);
18797
+ var EventKindDescriptorSchema = object({
18798
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18799
+ kind: string(),
18800
+ label: string(),
18801
+ /** Hex color for timeline/legend rendering. */
18802
+ color: string(),
18803
+ icon: EventKindIconSchema,
18804
+ category: EventKindCategorySchema,
18805
+ /** Which cap + device contributes this kind. For built-ins the camera
18806
+ * itself; for sensor kinds the LINKED source device. */
18807
+ source: object({
18808
+ capName: string(),
18809
+ deviceId: number()
18810
+ })
18811
+ });
18812
+ var SensorEventSchema = object({
18813
+ id: string(),
18814
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18815
+ * yields N rows, one per camera). */
18816
+ deviceId: number(),
18817
+ /** The linked sensor device whose state changed. */
18818
+ sourceDeviceId: number(),
18819
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18820
+ kind: string(),
18821
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18822
+ value: record(string(), unknown()).nullable(),
18823
+ timestamp: number()
18824
+ });
18740
18825
  var TrackPositionSchema = object({
18741
18826
  x: number(),
18742
18827
  y: number(),
@@ -18750,6 +18835,30 @@ var TrackSnapshotSchema = object({
18750
18835
  mediaKey: string()
18751
18836
  });
18752
18837
  /**
18838
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18839
+ * divided by the track's detection-frame dims), computed at persist time.
18840
+ * Absent when the frame dims were unknown when the track was persisted
18841
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18842
+ */
18843
+ var TrackEnvelopeSchema = object({
18844
+ minX: number(),
18845
+ minY: number(),
18846
+ maxX: number(),
18847
+ maxY: number()
18848
+ });
18849
+ /**
18850
+ * Row projection for track list queries. `full` (default) returns the
18851
+ * complete Track including the frame-rate `positions[]` history and the
18852
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18853
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18854
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18855
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18856
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18857
+ * `getTrack`. Mirrors the event-store `projection` convention
18858
+ * (`getObjectEvents` et al.).
18859
+ */
18860
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18861
+ /**
18753
18862
  * One audio-classification label heard on the track's camera while the
18754
18863
  * track was alive, aggregated per label. An "episode" is one persisted
18755
18864
  * audio event (the confident-classification path: score ≥ the device's
@@ -18800,7 +18909,11 @@ var TrackSchema = object({
18800
18909
  /** Audio-classification labels heard on the camera during the track's
18801
18910
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18802
18911
  * Absent on legacy rows / tracks with no confident audio. */
18803
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18912
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18913
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18914
+ * Populated from the persisted envelope columns on historical reads;
18915
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18916
+ envelope: TrackEnvelopeSchema.optional()
18804
18917
  });
18805
18918
  var BaseEventFields = {
18806
18919
  id: string(),
@@ -18910,11 +19023,13 @@ var MediaFileSchema = object({
18910
19023
  sizeBytes: number(),
18911
19024
  timestamp: number()
18912
19025
  });
19026
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19027
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18913
19028
  var DeviceEventQueryInput = object({
18914
19029
  deviceId: number(),
18915
19030
  since: number().optional(),
18916
19031
  until: number().optional(),
18917
- limit: number().int().min(1).max(5e3).default(1e3),
19032
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18918
19033
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18919
19034
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18920
19035
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18922,6 +19037,27 @@ var DeviceEventQueryInput = object({
18922
19037
  projection: _enum(["full", "slim"]).optional()
18923
19038
  });
18924
19039
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19040
+ var RecentTracksQueryInput = object({
19041
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19042
+ deviceIds: array(number()),
19043
+ /** Window lower bound on `lastSeen` (inclusive). */
19044
+ since: number().optional(),
19045
+ /** Window upper bound on `lastSeen` (inclusive). */
19046
+ until: number().optional(),
19047
+ /** Page size. Default 200, max 1000. */
19048
+ limit: number().int().min(1).max(1e3).default(200),
19049
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19050
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19051
+ cursor: string().optional(),
19052
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19053
+ projection: TrackProjectionSchema.optional()
19054
+ });
19055
+ var RecentTracksPageSchema = object({
19056
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19057
+ tracks: array(TrackSchema).readonly(),
19058
+ /** Cursor for the next page, or null when this page is the last. */
19059
+ nextCursor: string().nullable()
19060
+ });
18925
19061
  var KeyEventQueryInput = object({
18926
19062
  deviceId: number(),
18927
19063
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19004,11 +19140,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19004
19140
  deviceId: number(),
19005
19141
  since: number().optional(),
19006
19142
  until: number().optional(),
19007
- limit: number().optional()
19008
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19143
+ limit: number().optional(),
19144
+ /** Spatial filter — only tracks whose trajectory intersects the zone
19145
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19146
+ * envelope columns, then precisely tested per position. Tracks with
19147
+ * an unknown envelope (no frame dims at persist time) always match. */
19148
+ zone: TrackZoneFilterSchema.optional(),
19149
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19150
+ * compatible — omitting the field keeps today's exact behaviour). */
19151
+ projection: TrackProjectionSchema.optional()
19152
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19009
19153
  kind: "mutation",
19010
19154
  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({
19155
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19156
+ deviceId: number(),
19157
+ since: number().optional(),
19158
+ until: number().optional(),
19159
+ kinds: array(string()).optional(),
19160
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19161
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19012
19162
  deviceId: number(),
19013
19163
  since: number(),
19014
19164
  until: number(),
@@ -22209,7 +22359,12 @@ var RecordingStorageUsageSchema = object({
22209
22359
  /**
22210
22360
  * Result of locating footage at a wall-clock instant for one device/profile.
22211
22361
  * `segment` carries the covering segment's window; `gap` reports the forward
22212
- * nearest covered edge (`null` past the end of footage / when none exists).
22362
+ * nearest covered edge (`null` past the end of footage / when none exists)
22363
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22364
+ * footage behind the epoch; `null` when none — optional so older providers
22365
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22366
+ * small inter-segment cracks (durMs under-covers the span to the next
22367
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22213
22368
  */
22214
22369
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22215
22370
  kind: literal("segment"),
@@ -22218,7 +22373,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22218
22373
  bytes: number()
22219
22374
  }), object({
22220
22375
  kind: literal("gap"),
22221
- nearestEdgeMs: number().nullable()
22376
+ nearestEdgeMs: number().nullable(),
22377
+ prevEndMs: number().nullable().optional()
22222
22378
  })]);
22223
22379
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22224
22380
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23787,6 +23943,12 @@ Object.freeze({
23787
23943
  addonId: null,
23788
23944
  access: "view"
23789
23945
  },
23946
+ "deviceManager.getLinkedDevices": {
23947
+ capName: "device-manager",
23948
+ capScope: "system",
23949
+ addonId: null,
23950
+ access: "view"
23951
+ },
23790
23952
  "deviceManager.getRoleDisplayDefaults": {
23791
23953
  capName: "device-manager",
23792
23954
  capScope: "system",
@@ -25341,6 +25503,12 @@ Object.freeze({
25341
25503
  addonId: null,
25342
25504
  access: "view"
25343
25505
  },
25506
+ "pipelineAnalytics.getSensorEvents": {
25507
+ capName: "pipeline-analytics",
25508
+ capScope: "device",
25509
+ addonId: null,
25510
+ access: "view"
25511
+ },
25344
25512
  "pipelineAnalytics.getTrack": {
25345
25513
  capName: "pipeline-analytics",
25346
25514
  capScope: "device",
@@ -25353,6 +25521,18 @@ Object.freeze({
25353
25521
  addonId: null,
25354
25522
  access: "view"
25355
25523
  },
25524
+ "pipelineAnalytics.listEventKinds": {
25525
+ capName: "pipeline-analytics",
25526
+ capScope: "device",
25527
+ addonId: null,
25528
+ access: "view"
25529
+ },
25530
+ "pipelineAnalytics.listRecentTracks": {
25531
+ capName: "pipeline-analytics",
25532
+ capScope: "device",
25533
+ addonId: null,
25534
+ access: "view"
25535
+ },
25356
25536
  "pipelineAnalytics.listTracks": {
25357
25537
  capName: "pipeline-analytics",
25358
25538
  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";
@@ -10806,10 +10809,23 @@ var deviceDiscoveryCapability = {
10806
10809
  }
10807
10810
  };
10808
10811
  /**
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).
10812
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
10813
+ * name (same pattern as `snapshot`):
10814
+ *
10815
+ * - **Native** providers: registered per-device by device-driver
10816
+ * addons via `ctx.registerNativeCap` — either on a
10817
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
10818
+ * or directly on the camera (Reolink registers at camera level).
10819
+ * Emits an `onPressed` event every time the firmware pushes a
10820
+ * ring; status tracks the last press and a pressCount since start
10821
+ * (diagnostic).
10822
+ *
10823
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
10824
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
10825
+ * device (contact / switch / event-emitter …) into a doorbell for
10826
+ * a camera. `defaultActive: false` — the operator explicitly binds
10827
+ * it per camera in the device-bindings UI, then picks the source
10828
+ * device + trigger in the per-device settings.
10813
10829
  *
10814
10830
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
10815
10831
  * — subscribers listening at the camera level receive ring events
@@ -10830,7 +10846,10 @@ var doorbellCapability = {
10830
10846
  scope: "device",
10831
10847
  deviceNative: true,
10832
10848
  mode: "singleton",
10833
- deviceTypes: [DeviceType.Button],
10849
+ kind: "wrapper",
10850
+ defaultActive: false,
10851
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
10852
+ exposesDeviceSettings: true,
10834
10853
  methods: {},
10835
10854
  events: {
10836
10855
  /**
@@ -17248,6 +17267,14 @@ var ConfigEntrySchema = object({
17248
17267
  value: unknown(),
17249
17268
  description: string().optional()
17250
17269
  });
17270
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17271
+ /** One resolved linked device — the compact projection consumers need. */
17272
+ var LinkedDeviceSchema = object({
17273
+ deviceId: number(),
17274
+ name: string(),
17275
+ location: string().nullable(),
17276
+ features: array(string())
17277
+ });
17251
17278
  var SavedDeviceRowSchema = object({
17252
17279
  /** Numeric id reserved at allocateDeviceId time. */
17253
17280
  id: number(),
@@ -17469,7 +17496,10 @@ method(object({
17469
17496
  }), _void(), {
17470
17497
  kind: "mutation",
17471
17498
  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({
17499
+ }), 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({
17500
+ mode: DeviceLinkModeSchema,
17501
+ devices: array(LinkedDeviceSchema)
17502
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17473
17503
  deviceId: number(),
17474
17504
  values: record(string(), unknown())
17475
17505
  }), object({ success: literal(true) }), {
@@ -18738,6 +18768,61 @@ var EventKindSchema = _enum([
18738
18768
  "object",
18739
18769
  "audio"
18740
18770
  ]);
18771
+ /**
18772
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18773
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18774
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18775
+ */
18776
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18777
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18778
+ var EventKindIconSchema = _enum([
18779
+ "motion",
18780
+ "audio",
18781
+ "person",
18782
+ "vehicle",
18783
+ "animal",
18784
+ "door",
18785
+ "pir",
18786
+ "smoke",
18787
+ "water",
18788
+ "button",
18789
+ "generic"
18790
+ ]);
18791
+ var EventKindCategorySchema = _enum([
18792
+ "motion",
18793
+ "audio",
18794
+ "detection",
18795
+ "sensor",
18796
+ "custom"
18797
+ ]);
18798
+ var EventKindDescriptorSchema = object({
18799
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18800
+ kind: string(),
18801
+ label: string(),
18802
+ /** Hex color for timeline/legend rendering. */
18803
+ color: string(),
18804
+ icon: EventKindIconSchema,
18805
+ category: EventKindCategorySchema,
18806
+ /** Which cap + device contributes this kind. For built-ins the camera
18807
+ * itself; for sensor kinds the LINKED source device. */
18808
+ source: object({
18809
+ capName: string(),
18810
+ deviceId: number()
18811
+ })
18812
+ });
18813
+ var SensorEventSchema = object({
18814
+ id: string(),
18815
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
18816
+ * yields N rows, one per camera). */
18817
+ deviceId: number(),
18818
+ /** The linked sensor device whose state changed. */
18819
+ sourceDeviceId: number(),
18820
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
18821
+ kind: string(),
18822
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
18823
+ value: record(string(), unknown()).nullable(),
18824
+ timestamp: number()
18825
+ });
18741
18826
  var TrackPositionSchema = object({
18742
18827
  x: number(),
18743
18828
  y: number(),
@@ -18751,6 +18836,30 @@ var TrackSnapshotSchema = object({
18751
18836
  mediaKey: string()
18752
18837
  });
18753
18838
  /**
18839
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
18840
+ * divided by the track's detection-frame dims), computed at persist time.
18841
+ * Absent when the frame dims were unknown when the track was persisted
18842
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
18843
+ */
18844
+ var TrackEnvelopeSchema = object({
18845
+ minX: number(),
18846
+ minY: number(),
18847
+ maxX: number(),
18848
+ maxY: number()
18849
+ });
18850
+ /**
18851
+ * Row projection for track list queries. `full` (default) returns the
18852
+ * complete Track including the frame-rate `positions[]` history and the
18853
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
18854
+ * keeps every scalar the list surfaces actually render (ids, class(es),
18855
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
18856
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
18857
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
18858
+ * `getTrack`. Mirrors the event-store `projection` convention
18859
+ * (`getObjectEvents` et al.).
18860
+ */
18861
+ var TrackProjectionSchema = _enum(["full", "slim"]);
18862
+ /**
18754
18863
  * One audio-classification label heard on the track's camera while the
18755
18864
  * track was alive, aggregated per label. An "episode" is one persisted
18756
18865
  * audio event (the confident-classification path: score ≥ the device's
@@ -18801,7 +18910,11 @@ var TrackSchema = object({
18801
18910
  /** Audio-classification labels heard on the camera during the track's
18802
18911
  * life (score ≥ device `classificationMinScore`), aggregated per label.
18803
18912
  * Absent on legacy rows / tracks with no confident audio. */
18804
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
18913
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
18914
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
18915
+ * Populated from the persisted envelope columns on historical reads;
18916
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
18917
+ envelope: TrackEnvelopeSchema.optional()
18805
18918
  });
18806
18919
  var BaseEventFields = {
18807
18920
  id: string(),
@@ -18911,11 +19024,13 @@ var MediaFileSchema = object({
18911
19024
  sizeBytes: number(),
18912
19025
  timestamp: number()
18913
19026
  });
19027
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19028
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
18914
19029
  var DeviceEventQueryInput = object({
18915
19030
  deviceId: number(),
18916
19031
  since: number().optional(),
18917
19032
  until: number().optional(),
18918
- limit: number().int().min(1).max(5e3).default(1e3),
19033
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
18919
19034
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
18920
19035
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
18921
19036
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -18923,6 +19038,27 @@ var DeviceEventQueryInput = object({
18923
19038
  projection: _enum(["full", "slim"]).optional()
18924
19039
  });
18925
19040
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19041
+ var RecentTracksQueryInput = object({
19042
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19043
+ deviceIds: array(number()),
19044
+ /** Window lower bound on `lastSeen` (inclusive). */
19045
+ since: number().optional(),
19046
+ /** Window upper bound on `lastSeen` (inclusive). */
19047
+ until: number().optional(),
19048
+ /** Page size. Default 200, max 1000. */
19049
+ limit: number().int().min(1).max(1e3).default(200),
19050
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19051
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19052
+ cursor: string().optional(),
19053
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19054
+ projection: TrackProjectionSchema.optional()
19055
+ });
19056
+ var RecentTracksPageSchema = object({
19057
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19058
+ tracks: array(TrackSchema).readonly(),
19059
+ /** Cursor for the next page, or null when this page is the last. */
19060
+ nextCursor: string().nullable()
19061
+ });
18926
19062
  var KeyEventQueryInput = object({
18927
19063
  deviceId: number(),
18928
19064
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19005,11 +19141,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19005
19141
  deviceId: number(),
19006
19142
  since: number().optional(),
19007
19143
  until: number().optional(),
19008
- limit: number().optional()
19009
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19144
+ limit: number().optional(),
19145
+ /** Spatial filter — only tracks whose trajectory intersects the zone
19146
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19147
+ * envelope columns, then precisely tested per position. Tracks with
19148
+ * an unknown envelope (no frame dims at persist time) always match. */
19149
+ zone: TrackZoneFilterSchema.optional(),
19150
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19151
+ * compatible — omitting the field keeps today's exact behaviour). */
19152
+ projection: TrackProjectionSchema.optional()
19153
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19010
19154
  kind: "mutation",
19011
19155
  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({
19156
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19157
+ deviceId: number(),
19158
+ since: number().optional(),
19159
+ until: number().optional(),
19160
+ kinds: array(string()).optional(),
19161
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19162
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19013
19163
  deviceId: number(),
19014
19164
  since: number(),
19015
19165
  until: number(),
@@ -22210,7 +22360,12 @@ var RecordingStorageUsageSchema = object({
22210
22360
  /**
22211
22361
  * Result of locating footage at a wall-clock instant for one device/profile.
22212
22362
  * `segment` carries the covering segment's window; `gap` reports the forward
22213
- * nearest covered edge (`null` past the end of footage / when none exists).
22363
+ * nearest covered edge (`null` past the end of footage / when none exists)
22364
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22365
+ * footage behind the epoch; `null` when none — optional so older providers
22366
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22367
+ * small inter-segment cracks (durMs under-covers the span to the next
22368
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22214
22369
  */
22215
22370
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22216
22371
  kind: literal("segment"),
@@ -22219,7 +22374,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22219
22374
  bytes: number()
22220
22375
  }), object({
22221
22376
  kind: literal("gap"),
22222
- nearestEdgeMs: number().nullable()
22377
+ nearestEdgeMs: number().nullable(),
22378
+ prevEndMs: number().nullable().optional()
22223
22379
  })]);
22224
22380
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22225
22381
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -23788,6 +23944,12 @@ Object.freeze({
23788
23944
  addonId: null,
23789
23945
  access: "view"
23790
23946
  },
23947
+ "deviceManager.getLinkedDevices": {
23948
+ capName: "device-manager",
23949
+ capScope: "system",
23950
+ addonId: null,
23951
+ access: "view"
23952
+ },
23791
23953
  "deviceManager.getRoleDisplayDefaults": {
23792
23954
  capName: "device-manager",
23793
23955
  capScope: "system",
@@ -25342,6 +25504,12 @@ Object.freeze({
25342
25504
  addonId: null,
25343
25505
  access: "view"
25344
25506
  },
25507
+ "pipelineAnalytics.getSensorEvents": {
25508
+ capName: "pipeline-analytics",
25509
+ capScope: "device",
25510
+ addonId: null,
25511
+ access: "view"
25512
+ },
25345
25513
  "pipelineAnalytics.getTrack": {
25346
25514
  capName: "pipeline-analytics",
25347
25515
  capScope: "device",
@@ -25354,6 +25522,18 @@ Object.freeze({
25354
25522
  addonId: null,
25355
25523
  access: "view"
25356
25524
  },
25525
+ "pipelineAnalytics.listEventKinds": {
25526
+ capName: "pipeline-analytics",
25527
+ capScope: "device",
25528
+ addonId: null,
25529
+ access: "view"
25530
+ },
25531
+ "pipelineAnalytics.listRecentTracks": {
25532
+ capName: "pipeline-analytics",
25533
+ capScope: "device",
25534
+ addonId: null,
25535
+ access: "view"
25536
+ },
25357
25537
  "pipelineAnalytics.listTracks": {
25358
25538
  capName: "pipeline-analytics",
25359
25539
  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.23",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",