@camstack/addon-provider-homeassistant 1.2.7 → 1.2.9

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
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let node_crypto = require("node:crypto");
3
3
  let node_zlib = require("node:zlib");
4
- //#region ../types/dist/event-category-BLcNejAE.mjs
4
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
5
5
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
6
6
  EventCategory["SystemBoot"] = "system.boot";
7
7
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -272,6 +272,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
272
272
  */
273
273
  EventCategory["DeviceStateChanged"] = "device.state-changed";
274
274
  /**
275
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
276
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
277
+ *
278
+ * Emitted only on a change, so a steady scene is silent. It exists so a
279
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
280
+ * one live badge with no push signal at all, and it cost a request every
281
+ * four seconds per visible camera.
282
+ *
283
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
284
+ * keeps a slow reconcile rather than trusting it alone.
285
+ */
286
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
287
+ /**
275
288
  * Cap event fired by every device that registers the `battery`
276
289
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
277
290
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -9637,6 +9650,29 @@ var NcOccupancyConditionSchema = object({
9637
9650
  count: number().int().min(0).default(1),
9638
9651
  sustainSeconds: number().int().min(0).max(3600).default(15)
9639
9652
  });
9653
+ /**
9654
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9655
+ *
9656
+ * The values are not symmetric, and deliberately so — the absent value has to
9657
+ * mean exactly what every rule authored before this condition existed already
9658
+ * does:
9659
+ * - `enter` — entries and every NON-crossing record (movement state,
9660
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9661
+ * an operator who never asked for exits must not start receiving them.
9662
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9663
+ * fails closed, because "the car left the drive" is a question about a
9664
+ * boundary, not about a detection.
9665
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9666
+ *
9667
+ * A rule asking for a direction should normally also scope `zones`, which the
9668
+ * engine evaluates against the crossed zone as well as the current membership
9669
+ * (an exit's membership no longer contains the zone it just left).
9670
+ */
9671
+ var NcCrossingSchema = _enum([
9672
+ "enter",
9673
+ "exit",
9674
+ "any"
9675
+ ]);
9640
9676
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9641
9677
  var NcZoneConditionSchema = object({
9642
9678
  ids: array(string().min(1)).min(1),
@@ -9661,6 +9697,13 @@ var NcConditionsSchema = object({
9661
9697
  /** Veto zones — any hit fails the rule. */
9662
9698
  zonesExclude: array(string().min(1)).optional(),
9663
9699
  /**
9700
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9701
+ * and a closed track carries none, so a `track-end` rule asking for one
9702
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9703
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9704
+ */
9705
+ crossing: NcCrossingSchema.optional(),
9706
+ /**
9664
9707
  * Exact (case-insensitive) match on the record's collapsed `label`
9665
9708
  * (identity name / plate text / subclass).
9666
9709
  */
@@ -9849,11 +9892,31 @@ var NcMediaPolicySchema = object({
9849
9892
  */
9850
9893
  profile: CamProfileSchema.optional()
9851
9894
  });
9895
+ /**
9896
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9897
+ * notification suppresses.
9898
+ * - `shared` (default, and the absent value) — one window for the whole
9899
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9900
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9901
+ * at once and cat→cat still waits.
9902
+ *
9903
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9904
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9905
+ * see `cooldownKey` in the rule engine).
9906
+ */
9907
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9852
9908
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9853
9909
  var NcThrottleSchema = object({
9854
9910
  cooldownSec: number().int().min(0).max(86400).default(60),
9855
9911
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9856
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9912
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9913
+ /**
9914
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9915
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9916
+ * rule authored before this field simply carries none — and the engine
9917
+ * reads absent as `shared`, the pre-existing behaviour.
9918
+ */
9919
+ granularity: NcThrottleGranularitySchema.optional()
9857
9920
  });
9858
9921
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9859
9922
  var NcRuleInputSchema = object({
@@ -9961,6 +10024,7 @@ var NcConditionDescriptorSchema = object({
9961
10024
  "schedule",
9962
10025
  "plateMatcher",
9963
10026
  "packagePhase",
10027
+ "crossingSelect",
9964
10028
  "polygonDraw",
9965
10029
  "occupancy"
9966
10030
  ]),
@@ -14296,6 +14360,18 @@ var motionCapability = {
14296
14360
  name: "motion",
14297
14361
  scope: "device",
14298
14362
  mode: "singleton",
14363
+ /**
14364
+ * Providers register per-device natives via `ctx.registerNativeCap`
14365
+ * (Hikvision/Reolink/Amcrest/Wyze/HA/Homematic/Alexa/Matter) — there is
14366
+ * NO system singleton provider. Without this flag `resolveCapMount`
14367
+ * derived `{ kind: 'singleton' }`, so `motion.getStatus`/`isDetected`
14368
+ * resolved via `registry.getSingleton('motion')` (always null) and every
14369
+ * call 412'd "provider not available" while bindings listed a live
14370
+ * `motion` native (2026-08-02). The flag routes the router through
14371
+ * `requireDeviceScoped` → `getProviderForDevice`, like `motion-trigger`,
14372
+ * `snapshot` and every other per-device native cap.
14373
+ */
14374
+ deviceNative: true,
14299
14375
  deviceTypes: [DeviceType.Camera, DeviceType.Sensor],
14300
14376
  methods: {
14301
14377
  /**
@@ -19313,7 +19389,10 @@ method(object({
19313
19389
  }), method(object({
19314
19390
  deviceId: number(),
19315
19391
  caps: array(string()).readonly().optional()
19316
- }), record(string(), unknown().nullable()));
19392
+ }), record(string(), unknown().nullable())), method(object({
19393
+ deviceIds: array(number()).readonly(),
19394
+ caps: array(string()).readonly().optional()
19395
+ }), record(string(), record(string(), unknown().nullable())));
19317
19396
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
19318
19397
  deviceId: number(),
19319
19398
  capName: string()
@@ -20735,6 +20814,29 @@ var MotionEventSchema = object({
20735
20814
  * Absent on legacy rows ⇒ treat as `pipeline`.
20736
20815
  */
20737
20816
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
20817
+ /**
20818
+ * The confirmed zone crossing that produced an object event. Present ONLY on
20819
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
20820
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
20821
+ * appearance event carry none, so a rule asking for a direction fails closed
20822
+ * on them.
20823
+ *
20824
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
20825
+ * into its own event, so a frame in which a track enters A while leaving B
20826
+ * produces two events with two directions — never one ambiguous row.
20827
+ *
20828
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
20829
+ * membership the box has NOW, and by definition it no longer contains the zone
20830
+ * that was just left. Without the id here, a zone-scoped rule could never match
20831
+ * the exit it asked for.
20832
+ */
20833
+ var ZoneCrossingSchema = object({
20834
+ direction: _enum(["enter", "exit"]),
20835
+ /** Admin zone id crossed. */
20836
+ zoneId: string(),
20837
+ /** Zone display name at crossing time (falls back to the id). */
20838
+ zoneName: string().optional()
20839
+ });
20738
20840
  var ObjectEventSchema = object({
20739
20841
  ...BaseEventFields,
20740
20842
  kind: literal("object"),
@@ -20761,6 +20863,12 @@ var ObjectEventSchema = object({
20761
20863
  zones: array(string()).readonly().optional(),
20762
20864
  /** Omitted in slim projection. */
20763
20865
  state: TrackStateSchema.optional(),
20866
+ /**
20867
+ * The zone crossing this event IS, when it is one. Absent on every other
20868
+ * event kind (movement state, appearance, package) — see
20869
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
20870
+ */
20871
+ zoneCrossing: ZoneCrossingSchema.optional(),
20764
20872
  /** Detection-frame dimensions in pixels — let consumers normalize the
20765
20873
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20766
20874
  frameWidth: number().optional(),
@@ -26174,6 +26282,12 @@ Object.freeze({
26174
26282
  addonId: null,
26175
26283
  access: "view"
26176
26284
  },
26285
+ "deviceManager.getDeviceStatusAggregateBatch": {
26286
+ capName: "device-manager",
26287
+ capScope: "system",
26288
+ addonId: null,
26289
+ access: "view"
26290
+ },
26177
26291
  "deviceManager.getLinkedDevices": {
26178
26292
  capName: "device-manager",
26179
26293
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
3
- //#region ../types/dist/event-category-BLcNejAE.mjs
3
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -271,6 +271,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
271
271
  */
272
272
  EventCategory["DeviceStateChanged"] = "device.state-changed";
273
273
  /**
274
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
275
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
276
+ *
277
+ * Emitted only on a change, so a steady scene is silent. It exists so a
278
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
279
+ * one live badge with no push signal at all, and it cost a request every
280
+ * four seconds per visible camera.
281
+ *
282
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
283
+ * keeps a slow reconcile rather than trusting it alone.
284
+ */
285
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
286
+ /**
274
287
  * Cap event fired by every device that registers the `battery`
275
288
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
276
289
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -9636,6 +9649,29 @@ var NcOccupancyConditionSchema = object({
9636
9649
  count: number().int().min(0).default(1),
9637
9650
  sustainSeconds: number().int().min(0).max(3600).default(15)
9638
9651
  });
9652
+ /**
9653
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9654
+ *
9655
+ * The values are not symmetric, and deliberately so — the absent value has to
9656
+ * mean exactly what every rule authored before this condition existed already
9657
+ * does:
9658
+ * - `enter` — entries and every NON-crossing record (movement state,
9659
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9660
+ * an operator who never asked for exits must not start receiving them.
9661
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9662
+ * fails closed, because "the car left the drive" is a question about a
9663
+ * boundary, not about a detection.
9664
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9665
+ *
9666
+ * A rule asking for a direction should normally also scope `zones`, which the
9667
+ * engine evaluates against the crossed zone as well as the current membership
9668
+ * (an exit's membership no longer contains the zone it just left).
9669
+ */
9670
+ var NcCrossingSchema = _enum([
9671
+ "enter",
9672
+ "exit",
9673
+ "any"
9674
+ ]);
9639
9675
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9640
9676
  var NcZoneConditionSchema = object({
9641
9677
  ids: array(string().min(1)).min(1),
@@ -9660,6 +9696,13 @@ var NcConditionsSchema = object({
9660
9696
  /** Veto zones — any hit fails the rule. */
9661
9697
  zonesExclude: array(string().min(1)).optional(),
9662
9698
  /**
9699
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9700
+ * and a closed track carries none, so a `track-end` rule asking for one
9701
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9702
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9703
+ */
9704
+ crossing: NcCrossingSchema.optional(),
9705
+ /**
9663
9706
  * Exact (case-insensitive) match on the record's collapsed `label`
9664
9707
  * (identity name / plate text / subclass).
9665
9708
  */
@@ -9848,11 +9891,31 @@ var NcMediaPolicySchema = object({
9848
9891
  */
9849
9892
  profile: CamProfileSchema.optional()
9850
9893
  });
9894
+ /**
9895
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9896
+ * notification suppresses.
9897
+ * - `shared` (default, and the absent value) — one window for the whole
9898
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9899
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9900
+ * at once and cat→cat still waits.
9901
+ *
9902
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9903
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9904
+ * see `cooldownKey` in the rule engine).
9905
+ */
9906
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9851
9907
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9852
9908
  var NcThrottleSchema = object({
9853
9909
  cooldownSec: number().int().min(0).max(86400).default(60),
9854
9910
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9855
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9911
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9912
+ /**
9913
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9914
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9915
+ * rule authored before this field simply carries none — and the engine
9916
+ * reads absent as `shared`, the pre-existing behaviour.
9917
+ */
9918
+ granularity: NcThrottleGranularitySchema.optional()
9856
9919
  });
9857
9920
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9858
9921
  var NcRuleInputSchema = object({
@@ -9960,6 +10023,7 @@ var NcConditionDescriptorSchema = object({
9960
10023
  "schedule",
9961
10024
  "plateMatcher",
9962
10025
  "packagePhase",
10026
+ "crossingSelect",
9963
10027
  "polygonDraw",
9964
10028
  "occupancy"
9965
10029
  ]),
@@ -14295,6 +14359,18 @@ var motionCapability = {
14295
14359
  name: "motion",
14296
14360
  scope: "device",
14297
14361
  mode: "singleton",
14362
+ /**
14363
+ * Providers register per-device natives via `ctx.registerNativeCap`
14364
+ * (Hikvision/Reolink/Amcrest/Wyze/HA/Homematic/Alexa/Matter) — there is
14365
+ * NO system singleton provider. Without this flag `resolveCapMount`
14366
+ * derived `{ kind: 'singleton' }`, so `motion.getStatus`/`isDetected`
14367
+ * resolved via `registry.getSingleton('motion')` (always null) and every
14368
+ * call 412'd "provider not available" while bindings listed a live
14369
+ * `motion` native (2026-08-02). The flag routes the router through
14370
+ * `requireDeviceScoped` → `getProviderForDevice`, like `motion-trigger`,
14371
+ * `snapshot` and every other per-device native cap.
14372
+ */
14373
+ deviceNative: true,
14298
14374
  deviceTypes: [DeviceType.Camera, DeviceType.Sensor],
14299
14375
  methods: {
14300
14376
  /**
@@ -19312,7 +19388,10 @@ method(object({
19312
19388
  }), method(object({
19313
19389
  deviceId: number(),
19314
19390
  caps: array(string()).readonly().optional()
19315
- }), record(string(), unknown().nullable()));
19391
+ }), record(string(), unknown().nullable())), method(object({
19392
+ deviceIds: array(number()).readonly(),
19393
+ caps: array(string()).readonly().optional()
19394
+ }), record(string(), record(string(), unknown().nullable())));
19316
19395
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
19317
19396
  deviceId: number(),
19318
19397
  capName: string()
@@ -20734,6 +20813,29 @@ var MotionEventSchema = object({
20734
20813
  * Absent on legacy rows ⇒ treat as `pipeline`.
20735
20814
  */
20736
20815
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
20816
+ /**
20817
+ * The confirmed zone crossing that produced an object event. Present ONLY on
20818
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
20819
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
20820
+ * appearance event carry none, so a rule asking for a direction fails closed
20821
+ * on them.
20822
+ *
20823
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
20824
+ * into its own event, so a frame in which a track enters A while leaving B
20825
+ * produces two events with two directions — never one ambiguous row.
20826
+ *
20827
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
20828
+ * membership the box has NOW, and by definition it no longer contains the zone
20829
+ * that was just left. Without the id here, a zone-scoped rule could never match
20830
+ * the exit it asked for.
20831
+ */
20832
+ var ZoneCrossingSchema = object({
20833
+ direction: _enum(["enter", "exit"]),
20834
+ /** Admin zone id crossed. */
20835
+ zoneId: string(),
20836
+ /** Zone display name at crossing time (falls back to the id). */
20837
+ zoneName: string().optional()
20838
+ });
20737
20839
  var ObjectEventSchema = object({
20738
20840
  ...BaseEventFields,
20739
20841
  kind: literal("object"),
@@ -20760,6 +20862,12 @@ var ObjectEventSchema = object({
20760
20862
  zones: array(string()).readonly().optional(),
20761
20863
  /** Omitted in slim projection. */
20762
20864
  state: TrackStateSchema.optional(),
20865
+ /**
20866
+ * The zone crossing this event IS, when it is one. Absent on every other
20867
+ * event kind (movement state, appearance, package) — see
20868
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
20869
+ */
20870
+ zoneCrossing: ZoneCrossingSchema.optional(),
20763
20871
  /** Detection-frame dimensions in pixels — let consumers normalize the
20764
20872
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20765
20873
  frameWidth: number().optional(),
@@ -26173,6 +26281,12 @@ Object.freeze({
26173
26281
  addonId: null,
26174
26282
  access: "view"
26175
26283
  },
26284
+ "deviceManager.getDeviceStatusAggregateBatch": {
26285
+ capName: "device-manager",
26286
+ capScope: "system",
26287
+ addonId: null,
26288
+ access: "view"
26289
+ },
26176
26290
  "deviceManager.getLinkedDevices": {
26177
26291
  capName: "device-manager",
26178
26292
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",