@camstack/addon-provider-hikvision 1.2.8 → 1.2.10

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.
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-BLcNejAE.mjs
9
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
10
10
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
11
11
  EventCategory["SystemBoot"] = "system.boot";
12
12
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -277,6 +277,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
277
277
  */
278
278
  EventCategory["DeviceStateChanged"] = "device.state-changed";
279
279
  /**
280
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
281
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
282
+ *
283
+ * Emitted only on a change, so a steady scene is silent. It exists so a
284
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
285
+ * one live badge with no push signal at all, and it cost a request every
286
+ * four seconds per visible camera.
287
+ *
288
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
289
+ * keeps a slow reconcile rather than trusting it alone.
290
+ */
291
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
292
+ /**
280
293
  * Cap event fired by every device that registers the `battery`
281
294
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
282
295
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -9589,6 +9602,29 @@ var NcOccupancyConditionSchema = object({
9589
9602
  count: number().int().min(0).default(1),
9590
9603
  sustainSeconds: number().int().min(0).max(3600).default(15)
9591
9604
  });
9605
+ /**
9606
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9607
+ *
9608
+ * The values are not symmetric, and deliberately so — the absent value has to
9609
+ * mean exactly what every rule authored before this condition existed already
9610
+ * does:
9611
+ * - `enter` — entries and every NON-crossing record (movement state,
9612
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9613
+ * an operator who never asked for exits must not start receiving them.
9614
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9615
+ * fails closed, because "the car left the drive" is a question about a
9616
+ * boundary, not about a detection.
9617
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9618
+ *
9619
+ * A rule asking for a direction should normally also scope `zones`, which the
9620
+ * engine evaluates against the crossed zone as well as the current membership
9621
+ * (an exit's membership no longer contains the zone it just left).
9622
+ */
9623
+ var NcCrossingSchema = _enum([
9624
+ "enter",
9625
+ "exit",
9626
+ "any"
9627
+ ]);
9592
9628
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9593
9629
  var NcZoneConditionSchema = object({
9594
9630
  ids: array(string().min(1)).min(1),
@@ -9613,6 +9649,13 @@ var NcConditionsSchema = object({
9613
9649
  /** Veto zones — any hit fails the rule. */
9614
9650
  zonesExclude: array(string().min(1)).optional(),
9615
9651
  /**
9652
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9653
+ * and a closed track carries none, so a `track-end` rule asking for one
9654
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9655
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9656
+ */
9657
+ crossing: NcCrossingSchema.optional(),
9658
+ /**
9616
9659
  * Exact (case-insensitive) match on the record's collapsed `label`
9617
9660
  * (identity name / plate text / subclass).
9618
9661
  */
@@ -9801,11 +9844,31 @@ var NcMediaPolicySchema = object({
9801
9844
  */
9802
9845
  profile: CamProfileSchema.optional()
9803
9846
  });
9847
+ /**
9848
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9849
+ * notification suppresses.
9850
+ * - `shared` (default, and the absent value) — one window for the whole
9851
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9852
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9853
+ * at once and cat→cat still waits.
9854
+ *
9855
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9856
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9857
+ * see `cooldownKey` in the rule engine).
9858
+ */
9859
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9804
9860
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9805
9861
  var NcThrottleSchema = object({
9806
9862
  cooldownSec: number().int().min(0).max(86400).default(60),
9807
9863
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9808
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9864
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9865
+ /**
9866
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9867
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9868
+ * rule authored before this field simply carries none — and the engine
9869
+ * reads absent as `shared`, the pre-existing behaviour.
9870
+ */
9871
+ granularity: NcThrottleGranularitySchema.optional()
9809
9872
  });
9810
9873
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9811
9874
  var NcRuleInputSchema = object({
@@ -9913,6 +9976,7 @@ var NcConditionDescriptorSchema = object({
9913
9976
  "schedule",
9914
9977
  "plateMatcher",
9915
9978
  "packagePhase",
9979
+ "crossingSelect",
9916
9980
  "polygonDraw",
9917
9981
  "occupancy"
9918
9982
  ]),
@@ -14203,6 +14267,18 @@ var motionCapability = {
14203
14267
  name: "motion",
14204
14268
  scope: "device",
14205
14269
  mode: "singleton",
14270
+ /**
14271
+ * Providers register per-device natives via `ctx.registerNativeCap`
14272
+ * (Hikvision/Reolink/Amcrest/Wyze/HA/Homematic/Alexa/Matter) — there is
14273
+ * NO system singleton provider. Without this flag `resolveCapMount`
14274
+ * derived `{ kind: 'singleton' }`, so `motion.getStatus`/`isDetected`
14275
+ * resolved via `registry.getSingleton('motion')` (always null) and every
14276
+ * call 412'd "provider not available" while bindings listed a live
14277
+ * `motion` native (2026-08-02). The flag routes the router through
14278
+ * `requireDeviceScoped` → `getProviderForDevice`, like `motion-trigger`,
14279
+ * `snapshot` and every other per-device native cap.
14280
+ */
14281
+ deviceNative: true,
14206
14282
  deviceTypes: [DeviceType.Camera, DeviceType.Sensor],
14207
14283
  methods: {
14208
14284
  /**
@@ -19147,7 +19223,10 @@ method(object({
19147
19223
  }), method(object({
19148
19224
  deviceId: number(),
19149
19225
  caps: array(string()).readonly().optional()
19150
- }), record(string(), unknown().nullable()));
19226
+ }), record(string(), unknown().nullable())), method(object({
19227
+ deviceIds: array(number()).readonly(),
19228
+ caps: array(string()).readonly().optional()
19229
+ }), record(string(), record(string(), unknown().nullable())));
19151
19230
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
19152
19231
  deviceId: number(),
19153
19232
  capName: string()
@@ -20555,6 +20634,29 @@ var MotionEventSchema = object({
20555
20634
  * Absent on legacy rows ⇒ treat as `pipeline`.
20556
20635
  */
20557
20636
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
20637
+ /**
20638
+ * The confirmed zone crossing that produced an object event. Present ONLY on
20639
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
20640
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
20641
+ * appearance event carry none, so a rule asking for a direction fails closed
20642
+ * on them.
20643
+ *
20644
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
20645
+ * into its own event, so a frame in which a track enters A while leaving B
20646
+ * produces two events with two directions — never one ambiguous row.
20647
+ *
20648
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
20649
+ * membership the box has NOW, and by definition it no longer contains the zone
20650
+ * that was just left. Without the id here, a zone-scoped rule could never match
20651
+ * the exit it asked for.
20652
+ */
20653
+ var ZoneCrossingSchema = object({
20654
+ direction: _enum(["enter", "exit"]),
20655
+ /** Admin zone id crossed. */
20656
+ zoneId: string(),
20657
+ /** Zone display name at crossing time (falls back to the id). */
20658
+ zoneName: string().optional()
20659
+ });
20558
20660
  var ObjectEventSchema = object({
20559
20661
  ...BaseEventFields,
20560
20662
  kind: literal("object"),
@@ -20581,6 +20683,12 @@ var ObjectEventSchema = object({
20581
20683
  zones: array(string()).readonly().optional(),
20582
20684
  /** Omitted in slim projection. */
20583
20685
  state: TrackStateSchema.optional(),
20686
+ /**
20687
+ * The zone crossing this event IS, when it is one. Absent on every other
20688
+ * event kind (movement state, appearance, package) — see
20689
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
20690
+ */
20691
+ zoneCrossing: ZoneCrossingSchema.optional(),
20584
20692
  /** Detection-frame dimensions in pixels — let consumers normalize the
20585
20693
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20586
20694
  frameWidth: number().optional(),
@@ -26331,6 +26439,12 @@ Object.freeze({
26331
26439
  addonId: null,
26332
26440
  access: "view"
26333
26441
  },
26442
+ "deviceManager.getDeviceStatusAggregateBatch": {
26443
+ capName: "device-manager",
26444
+ capScope: "system",
26445
+ addonId: null,
26446
+ access: "view"
26447
+ },
26334
26448
  "deviceManager.getLinkedDevices": {
26335
26449
  capName: "device-manager",
26336
26450
  capScope: "system",
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-BLcNejAE.mjs
10
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
11
11
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
12
12
  EventCategory["SystemBoot"] = "system.boot";
13
13
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -278,6 +278,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
278
278
  */
279
279
  EventCategory["DeviceStateChanged"] = "device.state-changed";
280
280
  /**
281
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
282
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
283
+ *
284
+ * Emitted only on a change, so a steady scene is silent. It exists so a
285
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
286
+ * one live badge with no push signal at all, and it cost a request every
287
+ * four seconds per visible camera.
288
+ *
289
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
290
+ * keeps a slow reconcile rather than trusting it alone.
291
+ */
292
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
293
+ /**
281
294
  * Cap event fired by every device that registers the `battery`
282
295
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
283
296
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -9590,6 +9603,29 @@ var NcOccupancyConditionSchema = object({
9590
9603
  count: number().int().min(0).default(1),
9591
9604
  sustainSeconds: number().int().min(0).max(3600).default(15)
9592
9605
  });
9606
+ /**
9607
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9608
+ *
9609
+ * The values are not symmetric, and deliberately so — the absent value has to
9610
+ * mean exactly what every rule authored before this condition existed already
9611
+ * does:
9612
+ * - `enter` — entries and every NON-crossing record (movement state,
9613
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9614
+ * an operator who never asked for exits must not start receiving them.
9615
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9616
+ * fails closed, because "the car left the drive" is a question about a
9617
+ * boundary, not about a detection.
9618
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9619
+ *
9620
+ * A rule asking for a direction should normally also scope `zones`, which the
9621
+ * engine evaluates against the crossed zone as well as the current membership
9622
+ * (an exit's membership no longer contains the zone it just left).
9623
+ */
9624
+ var NcCrossingSchema = _enum([
9625
+ "enter",
9626
+ "exit",
9627
+ "any"
9628
+ ]);
9593
9629
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9594
9630
  var NcZoneConditionSchema = object({
9595
9631
  ids: array(string().min(1)).min(1),
@@ -9614,6 +9650,13 @@ var NcConditionsSchema = object({
9614
9650
  /** Veto zones — any hit fails the rule. */
9615
9651
  zonesExclude: array(string().min(1)).optional(),
9616
9652
  /**
9653
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9654
+ * and a closed track carries none, so a `track-end` rule asking for one
9655
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9656
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9657
+ */
9658
+ crossing: NcCrossingSchema.optional(),
9659
+ /**
9617
9660
  * Exact (case-insensitive) match on the record's collapsed `label`
9618
9661
  * (identity name / plate text / subclass).
9619
9662
  */
@@ -9802,11 +9845,31 @@ var NcMediaPolicySchema = object({
9802
9845
  */
9803
9846
  profile: CamProfileSchema.optional()
9804
9847
  });
9848
+ /**
9849
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9850
+ * notification suppresses.
9851
+ * - `shared` (default, and the absent value) — one window for the whole
9852
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9853
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9854
+ * at once and cat→cat still waits.
9855
+ *
9856
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9857
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9858
+ * see `cooldownKey` in the rule engine).
9859
+ */
9860
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9805
9861
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9806
9862
  var NcThrottleSchema = object({
9807
9863
  cooldownSec: number().int().min(0).max(86400).default(60),
9808
9864
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9809
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9865
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9866
+ /**
9867
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9868
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9869
+ * rule authored before this field simply carries none — and the engine
9870
+ * reads absent as `shared`, the pre-existing behaviour.
9871
+ */
9872
+ granularity: NcThrottleGranularitySchema.optional()
9810
9873
  });
9811
9874
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9812
9875
  var NcRuleInputSchema = object({
@@ -9914,6 +9977,7 @@ var NcConditionDescriptorSchema = object({
9914
9977
  "schedule",
9915
9978
  "plateMatcher",
9916
9979
  "packagePhase",
9980
+ "crossingSelect",
9917
9981
  "polygonDraw",
9918
9982
  "occupancy"
9919
9983
  ]),
@@ -14204,6 +14268,18 @@ var motionCapability = {
14204
14268
  name: "motion",
14205
14269
  scope: "device",
14206
14270
  mode: "singleton",
14271
+ /**
14272
+ * Providers register per-device natives via `ctx.registerNativeCap`
14273
+ * (Hikvision/Reolink/Amcrest/Wyze/HA/Homematic/Alexa/Matter) — there is
14274
+ * NO system singleton provider. Without this flag `resolveCapMount`
14275
+ * derived `{ kind: 'singleton' }`, so `motion.getStatus`/`isDetected`
14276
+ * resolved via `registry.getSingleton('motion')` (always null) and every
14277
+ * call 412'd "provider not available" while bindings listed a live
14278
+ * `motion` native (2026-08-02). The flag routes the router through
14279
+ * `requireDeviceScoped` → `getProviderForDevice`, like `motion-trigger`,
14280
+ * `snapshot` and every other per-device native cap.
14281
+ */
14282
+ deviceNative: true,
14207
14283
  deviceTypes: [DeviceType.Camera, DeviceType.Sensor],
14208
14284
  methods: {
14209
14285
  /**
@@ -19148,7 +19224,10 @@ method(object({
19148
19224
  }), method(object({
19149
19225
  deviceId: number(),
19150
19226
  caps: array(string()).readonly().optional()
19151
- }), record(string(), unknown().nullable()));
19227
+ }), record(string(), unknown().nullable())), method(object({
19228
+ deviceIds: array(number()).readonly(),
19229
+ caps: array(string()).readonly().optional()
19230
+ }), record(string(), record(string(), unknown().nullable())));
19152
19231
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
19153
19232
  deviceId: number(),
19154
19233
  capName: string()
@@ -20556,6 +20635,29 @@ var MotionEventSchema = object({
20556
20635
  * Absent on legacy rows ⇒ treat as `pipeline`.
20557
20636
  */
20558
20637
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
20638
+ /**
20639
+ * The confirmed zone crossing that produced an object event. Present ONLY on
20640
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
20641
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
20642
+ * appearance event carry none, so a rule asking for a direction fails closed
20643
+ * on them.
20644
+ *
20645
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
20646
+ * into its own event, so a frame in which a track enters A while leaving B
20647
+ * produces two events with two directions — never one ambiguous row.
20648
+ *
20649
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
20650
+ * membership the box has NOW, and by definition it no longer contains the zone
20651
+ * that was just left. Without the id here, a zone-scoped rule could never match
20652
+ * the exit it asked for.
20653
+ */
20654
+ var ZoneCrossingSchema = object({
20655
+ direction: _enum(["enter", "exit"]),
20656
+ /** Admin zone id crossed. */
20657
+ zoneId: string(),
20658
+ /** Zone display name at crossing time (falls back to the id). */
20659
+ zoneName: string().optional()
20660
+ });
20559
20661
  var ObjectEventSchema = object({
20560
20662
  ...BaseEventFields,
20561
20663
  kind: literal("object"),
@@ -20582,6 +20684,12 @@ var ObjectEventSchema = object({
20582
20684
  zones: array(string()).readonly().optional(),
20583
20685
  /** Omitted in slim projection. */
20584
20686
  state: TrackStateSchema.optional(),
20687
+ /**
20688
+ * The zone crossing this event IS, when it is one. Absent on every other
20689
+ * event kind (movement state, appearance, package) — see
20690
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
20691
+ */
20692
+ zoneCrossing: ZoneCrossingSchema.optional(),
20585
20693
  /** Detection-frame dimensions in pixels — let consumers normalize the
20586
20694
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20587
20695
  frameWidth: number().optional(),
@@ -26332,6 +26440,12 @@ Object.freeze({
26332
26440
  addonId: null,
26333
26441
  access: "view"
26334
26442
  },
26443
+ "deviceManager.getDeviceStatusAggregateBatch": {
26444
+ capName: "device-manager",
26445
+ capScope: "system",
26446
+ addonId: null,
26447
+ access: "view"
26448
+ },
26335
26449
  "deviceManager.getLinkedDevices": {
26336
26450
  capName: "device-manager",
26337
26451
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
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",