@camstack/addon-provider-hikvision 1.1.24 → 1.1.25

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 +4 -7
package/dist/addon.js CHANGED
@@ -6,7 +6,7 @@ let node_http = require("node:http");
6
6
  let node_https = require("node:https");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_os = require("node:os");
9
- //#region ../types/dist/event-category-CFZs3jI4.mjs
9
+ //#region ../types/dist/event-category-H4AVePnn.mjs
10
10
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
11
11
  EventCategory["SystemBoot"] = "system.boot";
12
12
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -156,6 +156,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
156
156
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
157
157
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
158
158
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
159
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
160
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
161
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
159
162
  EventCategory["DetectionEvent"] = "detection.event";
160
163
  EventCategory["SessionTrackNew"] = "session.track.new";
161
164
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -11057,10 +11060,23 @@ var deviceDiscoveryCapability = {
11057
11060
  }
11058
11061
  };
11059
11062
  /**
11060
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11061
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11062
- * time the firmware pushes a ring; status tracks the last press and
11063
- * a pressCount since start (diagnostic).
11063
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11064
+ * name (same pattern as `snapshot`):
11065
+ *
11066
+ * - **Native** providers: registered per-device by device-driver
11067
+ * addons via `ctx.registerNativeCap` — either on a
11068
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11069
+ * or directly on the camera (Reolink registers at camera level).
11070
+ * Emits an `onPressed` event every time the firmware pushes a
11071
+ * ring; status tracks the last press and a pressCount since start
11072
+ * (diagnostic).
11073
+ *
11074
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11075
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11076
+ * device (contact / switch / event-emitter …) into a doorbell for
11077
+ * a camera. `defaultActive: false` — the operator explicitly binds
11078
+ * it per camera in the device-bindings UI, then picks the source
11079
+ * device + trigger in the per-device settings.
11064
11080
  *
11065
11081
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11066
11082
  * — subscribers listening at the camera level receive ring events
@@ -11081,7 +11097,10 @@ var doorbellCapability = {
11081
11097
  scope: "device",
11082
11098
  deviceNative: true,
11083
11099
  mode: "singleton",
11084
- deviceTypes: [DeviceType.Button],
11100
+ kind: "wrapper",
11101
+ defaultActive: false,
11102
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11103
+ exposesDeviceSettings: true,
11085
11104
  methods: {},
11086
11105
  events: {
11087
11106
  /**
@@ -17445,6 +17464,14 @@ var ConfigEntrySchema = object({
17445
17464
  value: unknown(),
17446
17465
  description: string().optional()
17447
17466
  });
17467
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17468
+ /** One resolved linked device — the compact projection consumers need. */
17469
+ var LinkedDeviceSchema = object({
17470
+ deviceId: number(),
17471
+ name: string(),
17472
+ location: string().nullable(),
17473
+ features: array(string())
17474
+ });
17448
17475
  var SavedDeviceRowSchema = object({
17449
17476
  /** Numeric id reserved at allocateDeviceId time. */
17450
17477
  id: number(),
@@ -17666,7 +17693,10 @@ method(object({
17666
17693
  }), _void(), {
17667
17694
  kind: "mutation",
17668
17695
  auth: "admin"
17669
- }), 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({
17696
+ }), 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({
17697
+ mode: DeviceLinkModeSchema,
17698
+ devices: array(LinkedDeviceSchema)
17699
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17670
17700
  deviceId: number(),
17671
17701
  values: record(string(), unknown())
17672
17702
  }), object({ success: literal(true) }), {
@@ -18935,6 +18965,61 @@ var EventKindSchema = _enum([
18935
18965
  "object",
18936
18966
  "audio"
18937
18967
  ]);
18968
+ /**
18969
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18970
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18971
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18972
+ */
18973
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18974
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18975
+ var EventKindIconSchema = _enum([
18976
+ "motion",
18977
+ "audio",
18978
+ "person",
18979
+ "vehicle",
18980
+ "animal",
18981
+ "door",
18982
+ "pir",
18983
+ "smoke",
18984
+ "water",
18985
+ "button",
18986
+ "generic"
18987
+ ]);
18988
+ var EventKindCategorySchema = _enum([
18989
+ "motion",
18990
+ "audio",
18991
+ "detection",
18992
+ "sensor",
18993
+ "custom"
18994
+ ]);
18995
+ var EventKindDescriptorSchema = object({
18996
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18997
+ kind: string(),
18998
+ label: string(),
18999
+ /** Hex color for timeline/legend rendering. */
19000
+ color: string(),
19001
+ icon: EventKindIconSchema,
19002
+ category: EventKindCategorySchema,
19003
+ /** Which cap + device contributes this kind. For built-ins the camera
19004
+ * itself; for sensor kinds the LINKED source device. */
19005
+ source: object({
19006
+ capName: string(),
19007
+ deviceId: number()
19008
+ })
19009
+ });
19010
+ var SensorEventSchema = object({
19011
+ id: string(),
19012
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19013
+ * yields N rows, one per camera). */
19014
+ deviceId: number(),
19015
+ /** The linked sensor device whose state changed. */
19016
+ sourceDeviceId: number(),
19017
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19018
+ kind: string(),
19019
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19020
+ value: record(string(), unknown()).nullable(),
19021
+ timestamp: number()
19022
+ });
18938
19023
  var TrackPositionSchema = object({
18939
19024
  x: number(),
18940
19025
  y: number(),
@@ -18948,6 +19033,30 @@ var TrackSnapshotSchema = object({
18948
19033
  mediaKey: string()
18949
19034
  });
18950
19035
  /**
19036
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19037
+ * divided by the track's detection-frame dims), computed at persist time.
19038
+ * Absent when the frame dims were unknown when the track was persisted
19039
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19040
+ */
19041
+ var TrackEnvelopeSchema = object({
19042
+ minX: number(),
19043
+ minY: number(),
19044
+ maxX: number(),
19045
+ maxY: number()
19046
+ });
19047
+ /**
19048
+ * Row projection for track list queries. `full` (default) returns the
19049
+ * complete Track including the frame-rate `positions[]` history and the
19050
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19051
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19052
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19053
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19054
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19055
+ * `getTrack`. Mirrors the event-store `projection` convention
19056
+ * (`getObjectEvents` et al.).
19057
+ */
19058
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19059
+ /**
18951
19060
  * One audio-classification label heard on the track's camera while the
18952
19061
  * track was alive, aggregated per label. An "episode" is one persisted
18953
19062
  * audio event (the confident-classification path: score ≥ the device's
@@ -18998,7 +19107,11 @@ var TrackSchema = object({
18998
19107
  /** Audio-classification labels heard on the camera during the track's
18999
19108
  * life (score ≥ device `classificationMinScore`), aggregated per label.
19000
19109
  * Absent on legacy rows / tracks with no confident audio. */
19001
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19110
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19111
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19112
+ * Populated from the persisted envelope columns on historical reads;
19113
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19114
+ envelope: TrackEnvelopeSchema.optional()
19002
19115
  });
19003
19116
  var BaseEventFields = {
19004
19117
  id: string(),
@@ -19108,11 +19221,13 @@ var MediaFileSchema = object({
19108
19221
  sizeBytes: number(),
19109
19222
  timestamp: number()
19110
19223
  });
19224
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19225
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19111
19226
  var DeviceEventQueryInput = object({
19112
19227
  deviceId: number(),
19113
19228
  since: number().optional(),
19114
19229
  until: number().optional(),
19115
- limit: number().int().min(1).max(5e3).default(1e3),
19230
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19116
19231
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19117
19232
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19118
19233
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19120,6 +19235,27 @@ var DeviceEventQueryInput = object({
19120
19235
  projection: _enum(["full", "slim"]).optional()
19121
19236
  });
19122
19237
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19238
+ var RecentTracksQueryInput = object({
19239
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19240
+ deviceIds: array(number()),
19241
+ /** Window lower bound on `lastSeen` (inclusive). */
19242
+ since: number().optional(),
19243
+ /** Window upper bound on `lastSeen` (inclusive). */
19244
+ until: number().optional(),
19245
+ /** Page size. Default 200, max 1000. */
19246
+ limit: number().int().min(1).max(1e3).default(200),
19247
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19248
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19249
+ cursor: string().optional(),
19250
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19251
+ projection: TrackProjectionSchema.optional()
19252
+ });
19253
+ var RecentTracksPageSchema = object({
19254
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19255
+ tracks: array(TrackSchema).readonly(),
19256
+ /** Cursor for the next page, or null when this page is the last. */
19257
+ nextCursor: string().nullable()
19258
+ });
19123
19259
  var KeyEventQueryInput = object({
19124
19260
  deviceId: number(),
19125
19261
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19202,11 +19338,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19202
19338
  deviceId: number(),
19203
19339
  since: number().optional(),
19204
19340
  until: number().optional(),
19205
- limit: number().optional()
19206
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19341
+ limit: number().optional(),
19342
+ /** Spatial filter only tracks whose trajectory intersects the zone
19343
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19344
+ * envelope columns, then precisely tested per position. Tracks with
19345
+ * an unknown envelope (no frame dims at persist time) always match. */
19346
+ zone: TrackZoneFilterSchema.optional(),
19347
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19348
+ * compatible — omitting the field keeps today's exact behaviour). */
19349
+ projection: TrackProjectionSchema.optional()
19350
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19207
19351
  kind: "mutation",
19208
19352
  auth: "admin"
19209
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19353
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19354
+ deviceId: number(),
19355
+ since: number().optional(),
19356
+ until: number().optional(),
19357
+ kinds: array(string()).optional(),
19358
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19359
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19210
19360
  deviceId: number(),
19211
19361
  since: number(),
19212
19362
  until: number(),
@@ -22622,7 +22772,12 @@ var RecordingStorageUsageSchema = object({
22622
22772
  /**
22623
22773
  * Result of locating footage at a wall-clock instant for one device/profile.
22624
22774
  * `segment` carries the covering segment's window; `gap` reports the forward
22625
- * nearest covered edge (`null` past the end of footage / when none exists).
22775
+ * nearest covered edge (`null` past the end of footage / when none exists)
22776
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22777
+ * footage behind the epoch; `null` when none — optional so older providers
22778
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22779
+ * small inter-segment cracks (durMs under-covers the span to the next
22780
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22626
22781
  */
22627
22782
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22628
22783
  kind: literal("segment"),
@@ -22631,7 +22786,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22631
22786
  bytes: number()
22632
22787
  }), object({
22633
22788
  kind: literal("gap"),
22634
- nearestEdgeMs: number().nullable()
22789
+ nearestEdgeMs: number().nullable(),
22790
+ prevEndMs: number().nullable().optional()
22635
22791
  })]);
22636
22792
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22637
22793
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24411,6 +24567,12 @@ Object.freeze({
24411
24567
  addonId: null,
24412
24568
  access: "view"
24413
24569
  },
24570
+ "deviceManager.getLinkedDevices": {
24571
+ capName: "device-manager",
24572
+ capScope: "system",
24573
+ addonId: null,
24574
+ access: "view"
24575
+ },
24414
24576
  "deviceManager.getRoleDisplayDefaults": {
24415
24577
  capName: "device-manager",
24416
24578
  capScope: "system",
@@ -25965,6 +26127,12 @@ Object.freeze({
25965
26127
  addonId: null,
25966
26128
  access: "view"
25967
26129
  },
26130
+ "pipelineAnalytics.getSensorEvents": {
26131
+ capName: "pipeline-analytics",
26132
+ capScope: "device",
26133
+ addonId: null,
26134
+ access: "view"
26135
+ },
25968
26136
  "pipelineAnalytics.getTrack": {
25969
26137
  capName: "pipeline-analytics",
25970
26138
  capScope: "device",
@@ -25977,6 +26145,18 @@ Object.freeze({
25977
26145
  addonId: null,
25978
26146
  access: "view"
25979
26147
  },
26148
+ "pipelineAnalytics.listEventKinds": {
26149
+ capName: "pipeline-analytics",
26150
+ capScope: "device",
26151
+ addonId: null,
26152
+ access: "view"
26153
+ },
26154
+ "pipelineAnalytics.listRecentTracks": {
26155
+ capName: "pipeline-analytics",
26156
+ capScope: "device",
26157
+ addonId: null,
26158
+ access: "view"
26159
+ },
25980
26160
  "pipelineAnalytics.listTracks": {
25981
26161
  capName: "pipeline-analytics",
25982
26162
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -7,7 +7,7 @@ import { networkInterfaces } from "node:os";
7
7
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
8
8
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
9
9
  //#endregion
10
- //#region ../types/dist/event-category-CFZs3jI4.mjs
10
+ //#region ../types/dist/event-category-H4AVePnn.mjs
11
11
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
12
12
  EventCategory["SystemBoot"] = "system.boot";
13
13
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -157,6 +157,9 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
157
157
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
158
158
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
159
159
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
160
+ /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
161
+ * thumb is a scrub gap the recorder's keyframe backfill covers. */
162
+ EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
160
163
  EventCategory["DetectionEvent"] = "detection.event";
161
164
  EventCategory["SessionTrackNew"] = "session.track.new";
162
165
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -11058,10 +11061,23 @@ var deviceDiscoveryCapability = {
11058
11061
  }
11059
11062
  };
11060
11063
  /**
11061
- * Doorbell button cap. Installed on a `DeviceType.Button` accessory
11062
- * with `role: DeviceRole.Doorbell`. Emits an `onPressed` event every
11063
- * time the firmware pushes a ring; status tracks the last press and
11064
- * a pressCount since start (diagnostic).
11064
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
11065
+ * name (same pattern as `snapshot`):
11066
+ *
11067
+ * - **Native** providers: registered per-device by device-driver
11068
+ * addons via `ctx.registerNativeCap` — either on a
11069
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
11070
+ * or directly on the camera (Reolink registers at camera level).
11071
+ * Emits an `onPressed` event every time the firmware pushes a
11072
+ * ring; status tracks the last press and a pressCount since start
11073
+ * (diagnostic).
11074
+ *
11075
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
11076
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
11077
+ * device (contact / switch / event-emitter …) into a doorbell for
11078
+ * a camera. `defaultActive: false` — the operator explicitly binds
11079
+ * it per camera in the device-bindings UI, then picks the source
11080
+ * device + trigger in the per-device settings.
11065
11081
  *
11066
11082
  * The DeviceEventPropagator re-emits `onPressed` on the camera parent
11067
11083
  * — subscribers listening at the camera level receive ring events
@@ -11082,7 +11098,10 @@ var doorbellCapability = {
11082
11098
  scope: "device",
11083
11099
  deviceNative: true,
11084
11100
  mode: "singleton",
11085
- deviceTypes: [DeviceType.Button],
11101
+ kind: "wrapper",
11102
+ defaultActive: false,
11103
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
11104
+ exposesDeviceSettings: true,
11086
11105
  methods: {},
11087
11106
  events: {
11088
11107
  /**
@@ -17446,6 +17465,14 @@ var ConfigEntrySchema = object({
17446
17465
  value: unknown(),
17447
17466
  description: string().optional()
17448
17467
  });
17468
+ var DeviceLinkModeSchema = _enum(["auto", "manual"]);
17469
+ /** One resolved linked device — the compact projection consumers need. */
17470
+ var LinkedDeviceSchema = object({
17471
+ deviceId: number(),
17472
+ name: string(),
17473
+ location: string().nullable(),
17474
+ features: array(string())
17475
+ });
17449
17476
  var SavedDeviceRowSchema = object({
17450
17477
  /** Numeric id reserved at allocateDeviceId time. */
17451
17478
  id: number(),
@@ -17667,7 +17694,10 @@ method(object({
17667
17694
  }), _void(), {
17668
17695
  kind: "mutation",
17669
17696
  auth: "admin"
17670
- }), 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({
17697
+ }), 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({
17698
+ mode: DeviceLinkModeSchema,
17699
+ devices: array(LinkedDeviceSchema)
17700
+ })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
17671
17701
  deviceId: number(),
17672
17702
  values: record(string(), unknown())
17673
17703
  }), object({ success: literal(true) }), {
@@ -18936,6 +18966,61 @@ var EventKindSchema = _enum([
18936
18966
  "object",
18937
18967
  "audio"
18938
18968
  ]);
18969
+ /**
18970
+ * Spatial filter for `listTracks` — the rect + polygon variants of the shared
18971
+ * MaskShape vocabulary (see `mask-shape.ts`). Coordinates are NORMALIZED 0..1
18972
+ * of the camera frame (top-left origin), matching the drawing-plane editor.
18973
+ */
18974
+ var TrackZoneFilterSchema = discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
18975
+ /** Closed icon vocabulary so clients render a known glyph per kind. */
18976
+ var EventKindIconSchema = _enum([
18977
+ "motion",
18978
+ "audio",
18979
+ "person",
18980
+ "vehicle",
18981
+ "animal",
18982
+ "door",
18983
+ "pir",
18984
+ "smoke",
18985
+ "water",
18986
+ "button",
18987
+ "generic"
18988
+ ]);
18989
+ var EventKindCategorySchema = _enum([
18990
+ "motion",
18991
+ "audio",
18992
+ "detection",
18993
+ "sensor",
18994
+ "custom"
18995
+ ]);
18996
+ var EventKindDescriptorSchema = object({
18997
+ /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
18998
+ kind: string(),
18999
+ label: string(),
19000
+ /** Hex color for timeline/legend rendering. */
19001
+ color: string(),
19002
+ icon: EventKindIconSchema,
19003
+ category: EventKindCategorySchema,
19004
+ /** Which cap + device contributes this kind. For built-ins the camera
19005
+ * itself; for sensor kinds the LINKED source device. */
19006
+ source: object({
19007
+ capName: string(),
19008
+ deviceId: number()
19009
+ })
19010
+ });
19011
+ var SensorEventSchema = object({
19012
+ id: string(),
19013
+ /** The CAMERA the event is attributed to (a sensor linked to N cameras
19014
+ * yields N rows, one per camera). */
19015
+ deviceId: number(),
19016
+ /** The linked sensor device whose state changed. */
19017
+ sourceDeviceId: number(),
19018
+ /** Event kind id — matches an `EventKindDescriptor.kind`. */
19019
+ kind: string(),
19020
+ /** Snapshot of the sensor cap's runtime-state slice at the change. */
19021
+ value: record(string(), unknown()).nullable(),
19022
+ timestamp: number()
19023
+ });
18939
19024
  var TrackPositionSchema = object({
18940
19025
  x: number(),
18941
19026
  y: number(),
@@ -18949,6 +19034,30 @@ var TrackSnapshotSchema = object({
18949
19034
  mediaKey: string()
18950
19035
  });
18951
19036
  /**
19037
+ * Normalized 0..1 trajectory envelope (min/max over every position bbox,
19038
+ * divided by the track's detection-frame dims), computed at persist time.
19039
+ * Absent when the frame dims were unknown when the track was persisted
19040
+ * (legacy rows / dims-less sources) and on active (in-RAM) tracks.
19041
+ */
19042
+ var TrackEnvelopeSchema = object({
19043
+ minX: number(),
19044
+ minY: number(),
19045
+ maxX: number(),
19046
+ maxY: number()
19047
+ });
19048
+ /**
19049
+ * Row projection for track list queries. `full` (default) returns the
19050
+ * complete Track including the frame-rate `positions[]` history and the
19051
+ * `snapshots[]` references — megabytes across a page of tracks. `slim`
19052
+ * keeps every scalar the list surfaces actually render (ids, class(es),
19053
+ * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
19054
+ * zonesVisited, bestEventId, envelope) and returns `positions` /
19055
+ * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
19056
+ * `getTrack`. Mirrors the event-store `projection` convention
19057
+ * (`getObjectEvents` et al.).
19058
+ */
19059
+ var TrackProjectionSchema = _enum(["full", "slim"]);
19060
+ /**
18952
19061
  * One audio-classification label heard on the track's camera while the
18953
19062
  * track was alive, aggregated per label. An "episode" is one persisted
18954
19063
  * audio event (the confident-classification path: score ≥ the device's
@@ -18999,7 +19108,11 @@ var TrackSchema = object({
18999
19108
  /** Audio-classification labels heard on the camera during the track's
19000
19109
  * life (score ≥ device `classificationMinScore`), aggregated per label.
19001
19110
  * Absent on legacy rows / tracks with no confident audio. */
19002
- audioLabels: array(TrackAudioLabelSchema).readonly().optional()
19111
+ audioLabels: array(TrackAudioLabelSchema).readonly().optional(),
19112
+ /** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
19113
+ * Populated from the persisted envelope columns on historical reads;
19114
+ * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
19115
+ envelope: TrackEnvelopeSchema.optional()
19003
19116
  });
19004
19117
  var BaseEventFields = {
19005
19118
  id: string(),
@@ -19109,11 +19222,13 @@ var MediaFileSchema = object({
19109
19222
  sizeBytes: number(),
19110
19223
  timestamp: number()
19111
19224
  });
19225
+ var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
19226
+ var MAX_EVENT_QUERY_LIMIT = 5e3;
19112
19227
  var DeviceEventQueryInput = object({
19113
19228
  deviceId: number(),
19114
19229
  since: number().optional(),
19115
19230
  until: number().optional(),
19116
- limit: number().int().min(1).max(5e3).default(1e3),
19231
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT),
19117
19232
  /** `slim` drops heavy JSON fields (regions/bbox/zones) and carries an
19118
19233
  * optional `mediaUrl` (populated by B5). `full` (default) keeps today's
19119
19234
  * exact behaviour. Callers may omit this field — the store defaults to
@@ -19121,6 +19236,27 @@ var DeviceEventQueryInput = object({
19121
19236
  projection: _enum(["full", "slim"]).optional()
19122
19237
  });
19123
19238
  var ObjectEventQueryInput = DeviceEventQueryInput.extend({ classFilter: string().optional() });
19239
+ var RecentTracksQueryInput = object({
19240
+ /** Devices to merge. An empty array yields `{ tracks: [], nextCursor: null }`. */
19241
+ deviceIds: array(number()),
19242
+ /** Window lower bound on `lastSeen` (inclusive). */
19243
+ since: number().optional(),
19244
+ /** Window upper bound on `lastSeen` (inclusive). */
19245
+ until: number().optional(),
19246
+ /** Page size. Default 200, max 1000. */
19247
+ limit: number().int().min(1).max(1e3).default(200),
19248
+ /** Opaque continuation cursor from a previous page's `nextCursor`.
19249
+ * Encodes the (lastSeen, trackId) sort position — treat as opaque. */
19250
+ cursor: string().optional(),
19251
+ /** See {@link TrackProjectionSchema}. Default `full`. */
19252
+ projection: TrackProjectionSchema.optional()
19253
+ });
19254
+ var RecentTracksPageSchema = object({
19255
+ /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
19256
+ tracks: array(TrackSchema).readonly(),
19257
+ /** Cursor for the next page, or null when this page is the last. */
19258
+ nextCursor: string().nullable()
19259
+ });
19124
19260
  var KeyEventQueryInput = object({
19125
19261
  deviceId: number(),
19126
19262
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19203,11 +19339,25 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19203
19339
  deviceId: number(),
19204
19340
  since: number().optional(),
19205
19341
  until: number().optional(),
19206
- limit: number().optional()
19207
- }), array(TrackSchema).readonly()), method(object({ deviceId: number() }), _void(), {
19342
+ limit: number().optional(),
19343
+ /** Spatial filter only tracks whose trajectory intersects the zone
19344
+ * (normalized 0..1 rect or polygon). SQL-prefiltered on the persisted
19345
+ * envelope columns, then precisely tested per position. Tracks with
19346
+ * an unknown envelope (no frame dims at persist time) always match. */
19347
+ zone: TrackZoneFilterSchema.optional(),
19348
+ /** See {@link TrackProjectionSchema}. Default `full` (backward
19349
+ * compatible — omitting the field keeps today's exact behaviour). */
19350
+ projection: TrackProjectionSchema.optional()
19351
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19208
19352
  kind: "mutation",
19209
19353
  auth: "admin"
19210
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19354
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
19355
+ deviceId: number(),
19356
+ since: number().optional(),
19357
+ until: number().optional(),
19358
+ kinds: array(string()).optional(),
19359
+ limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19360
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19211
19361
  deviceId: number(),
19212
19362
  since: number(),
19213
19363
  until: number(),
@@ -22623,7 +22773,12 @@ var RecordingStorageUsageSchema = object({
22623
22773
  /**
22624
22774
  * Result of locating footage at a wall-clock instant for one device/profile.
22625
22775
  * `segment` carries the covering segment's window; `gap` reports the forward
22626
- * nearest covered edge (`null` past the end of footage / when none exists).
22776
+ * nearest covered edge (`null` past the end of footage / when none exists)
22777
+ * and the backward covered edge `prevEndMs` (exclusive end of the nearest
22778
+ * footage behind the epoch; `null` when none — optional so older providers
22779
+ * that omit it stay valid). `prevEndMs` lets a backward frame-step hop the
22780
+ * small inter-segment cracks (durMs under-covers the span to the next
22781
+ * startMs by ~11-17 ms) instead of no-opping at a segment head.
22627
22782
  */
22628
22783
  var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22629
22784
  kind: literal("segment"),
@@ -22632,7 +22787,8 @@ var LocateSegmentResultSchema = discriminatedUnion("kind", [object({
22632
22787
  bytes: number()
22633
22788
  }), object({
22634
22789
  kind: literal("gap"),
22635
- nearestEdgeMs: number().nullable()
22790
+ nearestEdgeMs: number().nullable(),
22791
+ prevEndMs: number().nullable().optional()
22636
22792
  })]);
22637
22793
  /** Raw bytes of one finalized footage segment (read off disk on the recording node). */
22638
22794
  var ReadSegmentBytesResultSchema = object({ data: _instanceof(Uint8Array) });
@@ -24412,6 +24568,12 @@ Object.freeze({
24412
24568
  addonId: null,
24413
24569
  access: "view"
24414
24570
  },
24571
+ "deviceManager.getLinkedDevices": {
24572
+ capName: "device-manager",
24573
+ capScope: "system",
24574
+ addonId: null,
24575
+ access: "view"
24576
+ },
24415
24577
  "deviceManager.getRoleDisplayDefaults": {
24416
24578
  capName: "device-manager",
24417
24579
  capScope: "system",
@@ -25966,6 +26128,12 @@ Object.freeze({
25966
26128
  addonId: null,
25967
26129
  access: "view"
25968
26130
  },
26131
+ "pipelineAnalytics.getSensorEvents": {
26132
+ capName: "pipeline-analytics",
26133
+ capScope: "device",
26134
+ addonId: null,
26135
+ access: "view"
26136
+ },
25969
26137
  "pipelineAnalytics.getTrack": {
25970
26138
  capName: "pipeline-analytics",
25971
26139
  capScope: "device",
@@ -25978,6 +26146,18 @@ Object.freeze({
25978
26146
  addonId: null,
25979
26147
  access: "view"
25980
26148
  },
26149
+ "pipelineAnalytics.listEventKinds": {
26150
+ capName: "pipeline-analytics",
26151
+ capScope: "device",
26152
+ addonId: null,
26153
+ access: "view"
26154
+ },
26155
+ "pipelineAnalytics.listRecentTracks": {
26156
+ capName: "pipeline-analytics",
26157
+ capScope: "device",
26158
+ addonId: null,
26159
+ access: "view"
26160
+ },
25981
26161
  "pipelineAnalytics.listTracks": {
25982
26162
  capName: "pipeline-analytics",
25983
26163
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.1.24",
3
+ "version": "1.1.25",
4
4
  "description": "Hikvision camera device provider addon for CamStack — ISAPI over HTTP(S) with digest auth (snapshot, alarm stream, RTSP discovery)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -81,16 +81,13 @@
81
81
  "typecheck": "tsc --noEmit",
82
82
  "publish": "npm publish --access public"
83
83
  },
84
+ "dependencies": {
85
+ "werift": "^0.22.9"
86
+ },
84
87
  "peerDependencies": {
85
88
  "@camstack/types": "*",
86
- "werift": "^0.22.9",
87
89
  "zod": "^4.3.6"
88
90
  },
89
- "peerDependenciesMeta": {
90
- "werift": {
91
- "optional": true
92
- }
93
- },
94
91
  "devDependencies": {
95
92
  "@camstack/types": "*",
96
93
  "typescript": "~6.0.3",