@camstack/addon-provider-hikvision 1.2.142 → 1.2.144

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 +213 -58
  2. package/dist/addon.mjs +213 -58
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -10802,6 +10802,57 @@ var ClipSearchStrictnessSchema = _enum([
10802
10802
  "balanced",
10803
10803
  "strict"
10804
10804
  ]);
10805
+ new Set([
10806
+ {
10807
+ id: "person",
10808
+ name: "Person"
10809
+ },
10810
+ {
10811
+ id: "vehicle",
10812
+ name: "Vehicle"
10813
+ },
10814
+ {
10815
+ id: "animal",
10816
+ name: "Animal"
10817
+ },
10818
+ {
10819
+ id: "package",
10820
+ name: "Package"
10821
+ }
10822
+ ].map((l) => l.id));
10823
+ var COCO_TO_MACRO = {
10824
+ mapping: {
10825
+ person: "person",
10826
+ bicycle: "vehicle",
10827
+ car: "vehicle",
10828
+ motorcycle: "vehicle",
10829
+ airplane: "vehicle",
10830
+ bus: "vehicle",
10831
+ train: "vehicle",
10832
+ truck: "vehicle",
10833
+ boat: "vehicle",
10834
+ bird: "animal",
10835
+ cat: "animal",
10836
+ dog: "animal",
10837
+ horse: "animal",
10838
+ sheep: "animal",
10839
+ cow: "animal",
10840
+ elephant: "animal",
10841
+ bear: "animal",
10842
+ zebra: "animal",
10843
+ giraffe: "animal",
10844
+ suitcase: "package",
10845
+ backpack: "package",
10846
+ handbag: "package"
10847
+ },
10848
+ preserveOriginal: false
10849
+ };
10850
+ /** Steps a root detector may declare it fills. */
10851
+ var FILLABLE_STEP_IDS = [
10852
+ "animal-classifier",
10853
+ "vehicle-classifier",
10854
+ "package-detection"
10855
+ ];
10805
10856
  var MODEL_FORMATS = [
10806
10857
  "onnx",
10807
10858
  "coreml",
@@ -10934,6 +10985,12 @@ var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { wei
10934
10985
  /** True when the weights or their data forbid commercial use. */
10935
10986
  nonCommercial: boolean().optional()
10936
10987
  }));
10988
+ /** One `fillsClassifierEffects` entry — see `ModelCatalogEntry.fillsClassifierEffects`. */
10989
+ var FillsClassifierEffectSchema = object({
10990
+ stepId: _enum(FILLABLE_STEP_IDS),
10991
+ labelToEffect: record(string(), string()),
10992
+ uncovered: array(string()).readonly()
10993
+ });
10937
10994
  var ModelCatalogEntrySchema = object({
10938
10995
  id: string(),
10939
10996
  name: string(),
@@ -11060,6 +11117,16 @@ var ModelCatalogEntrySchema = object({
11060
11117
  */
11061
11118
  classMap: DetectionCatalogClassMapSchema.optional(),
11062
11119
  /**
11120
+ * The pipeline steps this ROOT detector already does the work of (D715):
11121
+ * its labels name the effect the step exists to produce (`deer` for
11122
+ * `animal-classifier`, `package` for `package-detection`). Derived from the
11123
+ * labelmap by `deriveClassifierFills`, never hand-written; a COCO model
11124
+ * declares none. Whether a declaration is honoured is the step's own
11125
+ * `labelLink` policy (`resolveLabelLink`). `labelToEffect` values are in
11126
+ * `FILLABLE_STEP_VOCABULARIES` (guard `check-label-link-vocabulary`).
11127
+ */
11128
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional(),
11129
+ /**
11063
11130
  * The model's CLIP feature-space contract (text encoder, dimension, vector
11064
11131
  * index, token window, search floor) — see `clip-model.ts`. Present ONLY on a
11065
11132
  * CLIP image encoder; its absence is what "not a CLIP model" means to the
@@ -18402,51 +18469,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
18402
18469
  kind: "mutation",
18403
18470
  auth: "admin"
18404
18471
  });
18405
- new Set([
18406
- {
18407
- id: "person",
18408
- name: "Person"
18409
- },
18410
- {
18411
- id: "vehicle",
18412
- name: "Vehicle"
18413
- },
18414
- {
18415
- id: "animal",
18416
- name: "Animal"
18417
- },
18418
- {
18419
- id: "package",
18420
- name: "Package"
18421
- }
18422
- ].map((l) => l.id));
18423
- var COCO_TO_MACRO = {
18424
- mapping: {
18425
- person: "person",
18426
- bicycle: "vehicle",
18427
- car: "vehicle",
18428
- motorcycle: "vehicle",
18429
- airplane: "vehicle",
18430
- bus: "vehicle",
18431
- train: "vehicle",
18432
- truck: "vehicle",
18433
- boat: "vehicle",
18434
- bird: "animal",
18435
- cat: "animal",
18436
- dog: "animal",
18437
- horse: "animal",
18438
- sheep: "animal",
18439
- cow: "animal",
18440
- elephant: "animal",
18441
- bear: "animal",
18442
- zebra: "animal",
18443
- giraffe: "animal",
18444
- suitcase: "package",
18445
- backpack: "package",
18446
- handbag: "package"
18447
- },
18448
- preserveOriginal: false
18449
- };
18450
18472
  var AUDIO_MACRO_LABELS = [
18451
18473
  {
18452
18474
  id: "speech",
@@ -19242,6 +19264,8 @@ var NcSystemEventKindSchema = _enum([
19242
19264
  "stream-offline",
19243
19265
  "node-online",
19244
19266
  "node-offline",
19267
+ "node-restarting",
19268
+ "node-updating",
19245
19269
  "node-inference-unavailable",
19246
19270
  "detection-blind",
19247
19271
  "addon-crash-loop",
@@ -23888,7 +23912,8 @@ var PipelineModelOptionSchema = object({
23888
23912
  group: ModelVariantGroupSchema.optional(),
23889
23913
  legacy: boolean().optional(),
23890
23914
  provider: ModelProviderIdSchema.optional(),
23891
- defaultConfidence: number().optional()
23915
+ defaultConfidence: number().optional(),
23916
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional()
23892
23917
  });
23893
23918
  var ConfigFieldBridge = custom();
23894
23919
  var PipelineAddonSchemaSchema = object({
@@ -33567,7 +33592,8 @@ var MotionStatusSchema = object({
33567
33592
  * writes this slice (not even a boot seed), and neither does the runner's
33568
33593
  * phase machine: its phase is "a session is attached" for every on-motion
33569
33594
  * session, not motion, and it now has its own `DetectionPhaseTransition`.
33570
- * A provider's `isDetected` answers from the hub's slice.
33595
+ * A provider's `isDetected` and `getStatus` answer from the hub's slice
33596
+ * (`hubMotionNativeProvider`).
33571
33597
  * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
33572
33598
  * imports) — the provider pushes both edges itself, from the upstream
33573
33599
  * system's own state change. That is the sensor's own fact.
@@ -53831,6 +53857,8 @@ new Map(Object.entries({
53831
53857
  "detection-blind": "device-events",
53832
53858
  "node-online": "system",
53833
53859
  "node-offline": "system",
53860
+ "node-restarting": "system",
53861
+ "node-updating": "system",
53834
53862
  "node-inference-unavailable": "system",
53835
53863
  "addon-crash-loop": "system",
53836
53864
  "addon-update-available": "system",
@@ -53908,6 +53936,7 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53908
53936
  kinds: [
53909
53937
  "node-offline",
53910
53938
  "node-online",
53939
+ "node-restarting",
53911
53940
  "node-inference-unavailable"
53912
53941
  ]
53913
53942
  },
@@ -53929,6 +53958,7 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53929
53958
  label: "Server & container updates",
53930
53959
  kinds: [
53931
53960
  "server-update-available",
53961
+ "node-updating",
53932
53962
  "server-updated",
53933
53963
  "wrapper-update-available"
53934
53964
  ]
@@ -54076,6 +54106,7 @@ var STATE_SUMMARY_SECTION_VARS = [
54076
54106
  kinds: [
54077
54107
  "node-offline",
54078
54108
  "node-online",
54109
+ "node-restarting",
54079
54110
  "node-inference-unavailable"
54080
54111
  ]
54081
54112
  },
@@ -54097,6 +54128,7 @@ var STATE_SUMMARY_SECTION_VARS = [
54097
54128
  example: "server 1.2.377 → 1.2.378",
54098
54129
  kinds: [
54099
54130
  "server-update-available",
54131
+ "node-updating",
54100
54132
  "server-updated",
54101
54133
  "wrapper-update-available"
54102
54134
  ]
@@ -54552,10 +54584,9 @@ TimelapseRuleInputSchema.extend({
54552
54584
  createdAt: number(),
54553
54585
  updatedAt: number()
54554
54586
  });
54555
- async function readHubMotionDetected(deviceState, deviceId) {
54556
- let slice;
54587
+ async function readSlice(deviceState, deviceId) {
54557
54588
  try {
54558
- slice = await deviceState.getCapSlice.query({
54589
+ return await deviceState.getCapSlice.query({
54559
54590
  deviceId,
54560
54591
  capName: "motion"
54561
54592
  });
@@ -54563,10 +54594,44 @@ async function readHubMotionDetected(deviceState, deviceId) {
54563
54594
  const reason = err instanceof Error ? err.message : String(err);
54564
54595
  throw new Error(`motion state of device ${deviceId} is unreadable: ${reason}`, { cause: err });
54565
54596
  }
54566
- const detected = slice?.["detected"];
54597
+ }
54598
+ /**
54599
+ * The hub's `motion` slice as a `MotionStatus`, or `null` when nobody has
54600
+ * published it since the hub started. Throws when the hub does not answer or
54601
+ * the slice is malformed — never a made-up "no motion".
54602
+ */
54603
+ async function readHubMotionStatus(deviceState, deviceId) {
54604
+ const slice = await readSlice(deviceState, deviceId);
54605
+ if (slice === null) return null;
54606
+ const parsed = MotionStatusSchema.safeParse(slice);
54607
+ if (!parsed.success) throw new Error(`motion state of device ${deviceId} is malformed on the hub: ${parsed.error.message}`);
54608
+ return parsed.data;
54609
+ }
54610
+ async function readHubMotionDetected(deviceState, deviceId) {
54611
+ const detected = (await readSlice(deviceState, deviceId))?.["detected"];
54567
54612
  if (typeof detected !== "boolean") throw new Error(`motion state of device ${deviceId} is not published yet (unknown)`);
54568
54613
  return detected;
54569
54614
  }
54615
+ /**
54616
+ * The native `motion` provider of a camera whose slice the orchestrator writes
54617
+ * (D696): `isDetected` AND `getStatus`, both from the hub. `owner` names the
54618
+ * provider class in the deviceId-mismatch error.
54619
+ */
54620
+ function hubMotionNativeProvider(deviceState, deviceId, owner) {
54621
+ const ownDevice = (asked) => {
54622
+ if (asked !== deviceId) throw new Error(`${owner}: motion deviceId mismatch, expected ${deviceId}, got ${asked}`);
54623
+ };
54624
+ return {
54625
+ isDetected: async ({ deviceId: asked }) => {
54626
+ ownDevice(asked);
54627
+ return readHubMotionDetected(deviceState, deviceId);
54628
+ },
54629
+ getStatus: async ({ deviceId: asked }) => {
54630
+ ownDevice(asked);
54631
+ return readHubMotionStatus(deviceState, deviceId);
54632
+ }
54633
+ };
54634
+ }
54570
54635
  MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
54571
54636
  function providerMotionEvent(source, data) {
54572
54637
  return createEvent(EventCategory.ProviderMotion, source, data);
@@ -59911,6 +59976,75 @@ function createAccessoryDevice(kind, ctx, parent) {
59911
59976
  default: throw new Error(`Hikvision provider does not implement accessory kind "${String(kind)}"`);
59912
59977
  }
59913
59978
  }
59979
+ var ALARM_INGRESS_MESSAGE = "hikvision: alarm event received";
59980
+ var countsRecord = (counts) => Object.fromEntries(counts);
59981
+ function createAlarmIngressLog(deps) {
59982
+ const intervalMs = deps.intervalMs ?? 6e4;
59983
+ const bursts = /* @__PURE__ */ new Map();
59984
+ const tags = { deviceId: deps.deviceId };
59985
+ const received = (ev) => {
59986
+ const existing = bursts.get(ev.camStreamId);
59987
+ const burst = existing ?? {
59988
+ startedAt: ev.at,
59989
+ lastLineAt: ev.at,
59990
+ counts: /* @__PURE__ */ new Map(),
59991
+ types: /* @__PURE__ */ new Set(),
59992
+ ticks: 0
59993
+ };
59994
+ if (!existing) bursts.set(ev.camStreamId, burst);
59995
+ burst.ticks += 1;
59996
+ if (!burst.types.has(ev.type)) {
59997
+ burst.types.add(ev.type);
59998
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59999
+ tags,
60000
+ meta: {
60001
+ edge: "start",
60002
+ type: ev.type,
60003
+ state: ev.state,
60004
+ camStreamId: ev.camStreamId,
60005
+ ...ev.aiClass === null ? {} : { aiClass: ev.aiClass },
60006
+ ...existing ? { burstAgeMs: ev.at - burst.startedAt } : {}
60007
+ }
60008
+ });
60009
+ burst.lastLineAt = Math.max(burst.lastLineAt, ev.at);
60010
+ return;
60011
+ }
60012
+ burst.counts.set(ev.type, (burst.counts.get(ev.type) ?? 0) + 1);
60013
+ if (ev.at - burst.lastLineAt < intervalMs) return;
60014
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
60015
+ tags,
60016
+ meta: {
60017
+ edge: "ongoing",
60018
+ camStreamId: ev.camStreamId,
60019
+ counts: countsRecord(burst.counts),
60020
+ windowMs: ev.at - burst.lastLineAt,
60021
+ burstAgeMs: ev.at - burst.startedAt
60022
+ }
60023
+ });
60024
+ burst.counts.clear();
60025
+ burst.lastLineAt = ev.at;
60026
+ };
60027
+ const ended = (camStreamId, at) => {
60028
+ const burst = bursts.get(camStreamId);
60029
+ if (!burst) return;
60030
+ bursts.delete(camStreamId);
60031
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
60032
+ tags,
60033
+ meta: {
60034
+ edge: "end",
60035
+ camStreamId,
60036
+ counts: countsRecord(burst.counts),
60037
+ ticks: burst.ticks,
60038
+ types: [...burst.types],
60039
+ durationMs: at - burst.startedAt
60040
+ }
60041
+ });
60042
+ };
60043
+ return {
60044
+ received,
60045
+ ended
60046
+ };
60047
+ }
59914
60048
  function createAlarmTypeTally(deps) {
59915
60049
  const intervalMs = deps.intervalMs ?? 3e5;
59916
60050
  const lastLoggedAt = /* @__PURE__ */ new Map();
@@ -64297,6 +64431,20 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
64297
64431
  });
64298
64432
  return this.unhandledAlarmsTally;
64299
64433
  }
64434
+ /**
64435
+ * Sampled info-level ingress line for the motion-bearing alarms (VMD and
64436
+ * smart events): one at each burst's start, one per minute while it lasts,
64437
+ * one at its end — never one per ~1 Hz tick. Lazy for the same reason as
64438
+ * `unhandledAlarms`.
64439
+ */
64440
+ motionIngressLog = null;
64441
+ get motionIngress() {
64442
+ this.motionIngressLog ??= createAlarmIngressLog({
64443
+ logger: this.ctx.logger,
64444
+ deviceId: this.id
64445
+ });
64446
+ return this.motionIngressLog;
64447
+ }
64300
64448
  /** Hysteresis: drop motion-active back to inactive after this many ms with
64301
64449
  * no fresh `VMD` event. Hikvision fires VMD every ~1s while motion is
64302
64450
  * active; missing 2 ticks signals the burst is over. */
@@ -66859,19 +67007,25 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66859
67007
  * The native `motion` cap. Its presence is what defaults the camera to
66860
67008
  * `motionSources: ['onboard']`. This provider writes NO `motion` slice (D696):
66861
67009
  * the orchestrator is its only writer, with the EFFECTIVE motion, on the hub.
66862
- * `isDetected` therefore answers from the hub's slice; an unreadable hub, or
66863
- * a slice nobody has published since the hub started, makes the call fail.
66864
- * It never answers `false` for "unknown" (D49, D393).
67010
+ * `isDetected` AND `getStatus` therefore answer from the hub's slice
67011
+ * (`hubMotionNativeProvider`; without its own `getStatus` the framework's
67012
+ * default reads this process's empty local state and answers `null`). An
67013
+ * unreadable hub fails both; never `false` for "unknown" (D49, D393).
66865
67014
  */
66866
67015
  registerMotionCap() {
66867
- this.ctx.registerNativeCap(motionCapability, { isDetected: async ({ deviceId }) => {
66868
- if (deviceId !== this.id) throw new Error(`HikvisionCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
66869
- return readHubMotionDetected(this.ctx.api.deviceState, this.id);
66870
- } });
67016
+ const motionProvider = hubMotionNativeProvider(this.ctx.api.deviceState, this.id, "HikvisionCamera");
67017
+ this.ctx.registerNativeCap(motionCapability, motionProvider);
66871
67018
  }
66872
67019
  handleMotionEvent(ev, camStreamId) {
66873
67020
  const now = ev.observedAt;
66874
67021
  this.lastMotionAt.set(camStreamId, now);
67022
+ this.motionIngress.received({
67023
+ type: ev.type,
67024
+ state: ev.state,
67025
+ aiClass: ev.aiClass,
67026
+ camStreamId,
67027
+ at: now
67028
+ });
66875
67029
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66876
67030
  deviceId: this.id,
66877
67031
  detected: true,
@@ -66884,6 +67038,7 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66884
67038
  const clearedAt = Date.now();
66885
67039
  this.lastMotionAt.set(camStreamId, 0);
66886
67040
  this.motionInactiveTimers.delete(camStreamId);
67041
+ this.motionIngress.ended(camStreamId, clearedAt);
66887
67042
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66888
67043
  deviceId: this.id,
66889
67044
  detected: false,
package/dist/addon.mjs CHANGED
@@ -10781,6 +10781,57 @@ var ClipSearchStrictnessSchema = _enum([
10781
10781
  "balanced",
10782
10782
  "strict"
10783
10783
  ]);
10784
+ new Set([
10785
+ {
10786
+ id: "person",
10787
+ name: "Person"
10788
+ },
10789
+ {
10790
+ id: "vehicle",
10791
+ name: "Vehicle"
10792
+ },
10793
+ {
10794
+ id: "animal",
10795
+ name: "Animal"
10796
+ },
10797
+ {
10798
+ id: "package",
10799
+ name: "Package"
10800
+ }
10801
+ ].map((l) => l.id));
10802
+ var COCO_TO_MACRO = {
10803
+ mapping: {
10804
+ person: "person",
10805
+ bicycle: "vehicle",
10806
+ car: "vehicle",
10807
+ motorcycle: "vehicle",
10808
+ airplane: "vehicle",
10809
+ bus: "vehicle",
10810
+ train: "vehicle",
10811
+ truck: "vehicle",
10812
+ boat: "vehicle",
10813
+ bird: "animal",
10814
+ cat: "animal",
10815
+ dog: "animal",
10816
+ horse: "animal",
10817
+ sheep: "animal",
10818
+ cow: "animal",
10819
+ elephant: "animal",
10820
+ bear: "animal",
10821
+ zebra: "animal",
10822
+ giraffe: "animal",
10823
+ suitcase: "package",
10824
+ backpack: "package",
10825
+ handbag: "package"
10826
+ },
10827
+ preserveOriginal: false
10828
+ };
10829
+ /** Steps a root detector may declare it fills. */
10830
+ var FILLABLE_STEP_IDS = [
10831
+ "animal-classifier",
10832
+ "vehicle-classifier",
10833
+ "package-detection"
10834
+ ];
10784
10835
  var MODEL_FORMATS = [
10785
10836
  "onnx",
10786
10837
  "coreml",
@@ -10913,6 +10964,12 @@ var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { wei
10913
10964
  /** True when the weights or their data forbid commercial use. */
10914
10965
  nonCommercial: boolean().optional()
10915
10966
  }));
10967
+ /** One `fillsClassifierEffects` entry — see `ModelCatalogEntry.fillsClassifierEffects`. */
10968
+ var FillsClassifierEffectSchema = object({
10969
+ stepId: _enum(FILLABLE_STEP_IDS),
10970
+ labelToEffect: record(string(), string()),
10971
+ uncovered: array(string()).readonly()
10972
+ });
10916
10973
  var ModelCatalogEntrySchema = object({
10917
10974
  id: string(),
10918
10975
  name: string(),
@@ -11039,6 +11096,16 @@ var ModelCatalogEntrySchema = object({
11039
11096
  */
11040
11097
  classMap: DetectionCatalogClassMapSchema.optional(),
11041
11098
  /**
11099
+ * The pipeline steps this ROOT detector already does the work of (D715):
11100
+ * its labels name the effect the step exists to produce (`deer` for
11101
+ * `animal-classifier`, `package` for `package-detection`). Derived from the
11102
+ * labelmap by `deriveClassifierFills`, never hand-written; a COCO model
11103
+ * declares none. Whether a declaration is honoured is the step's own
11104
+ * `labelLink` policy (`resolveLabelLink`). `labelToEffect` values are in
11105
+ * `FILLABLE_STEP_VOCABULARIES` (guard `check-label-link-vocabulary`).
11106
+ */
11107
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional(),
11108
+ /**
11042
11109
  * The model's CLIP feature-space contract (text encoder, dimension, vector
11043
11110
  * index, token window, search floor) — see `clip-model.ts`. Present ONLY on a
11044
11111
  * CLIP image encoder; its absence is what "not a CLIP model" means to the
@@ -18381,51 +18448,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
18381
18448
  kind: "mutation",
18382
18449
  auth: "admin"
18383
18450
  });
18384
- new Set([
18385
- {
18386
- id: "person",
18387
- name: "Person"
18388
- },
18389
- {
18390
- id: "vehicle",
18391
- name: "Vehicle"
18392
- },
18393
- {
18394
- id: "animal",
18395
- name: "Animal"
18396
- },
18397
- {
18398
- id: "package",
18399
- name: "Package"
18400
- }
18401
- ].map((l) => l.id));
18402
- var COCO_TO_MACRO = {
18403
- mapping: {
18404
- person: "person",
18405
- bicycle: "vehicle",
18406
- car: "vehicle",
18407
- motorcycle: "vehicle",
18408
- airplane: "vehicle",
18409
- bus: "vehicle",
18410
- train: "vehicle",
18411
- truck: "vehicle",
18412
- boat: "vehicle",
18413
- bird: "animal",
18414
- cat: "animal",
18415
- dog: "animal",
18416
- horse: "animal",
18417
- sheep: "animal",
18418
- cow: "animal",
18419
- elephant: "animal",
18420
- bear: "animal",
18421
- zebra: "animal",
18422
- giraffe: "animal",
18423
- suitcase: "package",
18424
- backpack: "package",
18425
- handbag: "package"
18426
- },
18427
- preserveOriginal: false
18428
- };
18429
18451
  var AUDIO_MACRO_LABELS = [
18430
18452
  {
18431
18453
  id: "speech",
@@ -19221,6 +19243,8 @@ var NcSystemEventKindSchema = _enum([
19221
19243
  "stream-offline",
19222
19244
  "node-online",
19223
19245
  "node-offline",
19246
+ "node-restarting",
19247
+ "node-updating",
19224
19248
  "node-inference-unavailable",
19225
19249
  "detection-blind",
19226
19250
  "addon-crash-loop",
@@ -23867,7 +23891,8 @@ var PipelineModelOptionSchema = object({
23867
23891
  group: ModelVariantGroupSchema.optional(),
23868
23892
  legacy: boolean().optional(),
23869
23893
  provider: ModelProviderIdSchema.optional(),
23870
- defaultConfidence: number().optional()
23894
+ defaultConfidence: number().optional(),
23895
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional()
23871
23896
  });
23872
23897
  var ConfigFieldBridge = custom();
23873
23898
  var PipelineAddonSchemaSchema = object({
@@ -33546,7 +33571,8 @@ var MotionStatusSchema = object({
33546
33571
  * writes this slice (not even a boot seed), and neither does the runner's
33547
33572
  * phase machine: its phase is "a session is attached" for every on-motion
33548
33573
  * session, not motion, and it now has its own `DetectionPhaseTransition`.
33549
- * A provider's `isDetected` answers from the hub's slice.
33574
+ * A provider's `isDetected` and `getStatus` answer from the hub's slice
33575
+ * (`hubMotionNativeProvider`).
33550
33576
  * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
33551
33577
  * imports) — the provider pushes both edges itself, from the upstream
33552
33578
  * system's own state change. That is the sensor's own fact.
@@ -53810,6 +53836,8 @@ new Map(Object.entries({
53810
53836
  "detection-blind": "device-events",
53811
53837
  "node-online": "system",
53812
53838
  "node-offline": "system",
53839
+ "node-restarting": "system",
53840
+ "node-updating": "system",
53813
53841
  "node-inference-unavailable": "system",
53814
53842
  "addon-crash-loop": "system",
53815
53843
  "addon-update-available": "system",
@@ -53887,6 +53915,7 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53887
53915
  kinds: [
53888
53916
  "node-offline",
53889
53917
  "node-online",
53918
+ "node-restarting",
53890
53919
  "node-inference-unavailable"
53891
53920
  ]
53892
53921
  },
@@ -53908,6 +53937,7 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53908
53937
  label: "Server & container updates",
53909
53938
  kinds: [
53910
53939
  "server-update-available",
53940
+ "node-updating",
53911
53941
  "server-updated",
53912
53942
  "wrapper-update-available"
53913
53943
  ]
@@ -54055,6 +54085,7 @@ var STATE_SUMMARY_SECTION_VARS = [
54055
54085
  kinds: [
54056
54086
  "node-offline",
54057
54087
  "node-online",
54088
+ "node-restarting",
54058
54089
  "node-inference-unavailable"
54059
54090
  ]
54060
54091
  },
@@ -54076,6 +54107,7 @@ var STATE_SUMMARY_SECTION_VARS = [
54076
54107
  example: "server 1.2.377 → 1.2.378",
54077
54108
  kinds: [
54078
54109
  "server-update-available",
54110
+ "node-updating",
54079
54111
  "server-updated",
54080
54112
  "wrapper-update-available"
54081
54113
  ]
@@ -54531,10 +54563,9 @@ TimelapseRuleInputSchema.extend({
54531
54563
  createdAt: number(),
54532
54564
  updatedAt: number()
54533
54565
  });
54534
- async function readHubMotionDetected(deviceState, deviceId) {
54535
- let slice;
54566
+ async function readSlice(deviceState, deviceId) {
54536
54567
  try {
54537
- slice = await deviceState.getCapSlice.query({
54568
+ return await deviceState.getCapSlice.query({
54538
54569
  deviceId,
54539
54570
  capName: "motion"
54540
54571
  });
@@ -54542,10 +54573,44 @@ async function readHubMotionDetected(deviceState, deviceId) {
54542
54573
  const reason = err instanceof Error ? err.message : String(err);
54543
54574
  throw new Error(`motion state of device ${deviceId} is unreadable: ${reason}`, { cause: err });
54544
54575
  }
54545
- const detected = slice?.["detected"];
54576
+ }
54577
+ /**
54578
+ * The hub's `motion` slice as a `MotionStatus`, or `null` when nobody has
54579
+ * published it since the hub started. Throws when the hub does not answer or
54580
+ * the slice is malformed — never a made-up "no motion".
54581
+ */
54582
+ async function readHubMotionStatus(deviceState, deviceId) {
54583
+ const slice = await readSlice(deviceState, deviceId);
54584
+ if (slice === null) return null;
54585
+ const parsed = MotionStatusSchema.safeParse(slice);
54586
+ if (!parsed.success) throw new Error(`motion state of device ${deviceId} is malformed on the hub: ${parsed.error.message}`);
54587
+ return parsed.data;
54588
+ }
54589
+ async function readHubMotionDetected(deviceState, deviceId) {
54590
+ const detected = (await readSlice(deviceState, deviceId))?.["detected"];
54546
54591
  if (typeof detected !== "boolean") throw new Error(`motion state of device ${deviceId} is not published yet (unknown)`);
54547
54592
  return detected;
54548
54593
  }
54594
+ /**
54595
+ * The native `motion` provider of a camera whose slice the orchestrator writes
54596
+ * (D696): `isDetected` AND `getStatus`, both from the hub. `owner` names the
54597
+ * provider class in the deviceId-mismatch error.
54598
+ */
54599
+ function hubMotionNativeProvider(deviceState, deviceId, owner) {
54600
+ const ownDevice = (asked) => {
54601
+ if (asked !== deviceId) throw new Error(`${owner}: motion deviceId mismatch, expected ${deviceId}, got ${asked}`);
54602
+ };
54603
+ return {
54604
+ isDetected: async ({ deviceId: asked }) => {
54605
+ ownDevice(asked);
54606
+ return readHubMotionDetected(deviceState, deviceId);
54607
+ },
54608
+ getStatus: async ({ deviceId: asked }) => {
54609
+ ownDevice(asked);
54610
+ return readHubMotionStatus(deviceState, deviceId);
54611
+ }
54612
+ };
54613
+ }
54549
54614
  MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
54550
54615
  function providerMotionEvent(source, data) {
54551
54616
  return createEvent(EventCategory.ProviderMotion, source, data);
@@ -59890,6 +59955,75 @@ function createAccessoryDevice(kind, ctx, parent) {
59890
59955
  default: throw new Error(`Hikvision provider does not implement accessory kind "${String(kind)}"`);
59891
59956
  }
59892
59957
  }
59958
+ var ALARM_INGRESS_MESSAGE = "hikvision: alarm event received";
59959
+ var countsRecord = (counts) => Object.fromEntries(counts);
59960
+ function createAlarmIngressLog(deps) {
59961
+ const intervalMs = deps.intervalMs ?? 6e4;
59962
+ const bursts = /* @__PURE__ */ new Map();
59963
+ const tags = { deviceId: deps.deviceId };
59964
+ const received = (ev) => {
59965
+ const existing = bursts.get(ev.camStreamId);
59966
+ const burst = existing ?? {
59967
+ startedAt: ev.at,
59968
+ lastLineAt: ev.at,
59969
+ counts: /* @__PURE__ */ new Map(),
59970
+ types: /* @__PURE__ */ new Set(),
59971
+ ticks: 0
59972
+ };
59973
+ if (!existing) bursts.set(ev.camStreamId, burst);
59974
+ burst.ticks += 1;
59975
+ if (!burst.types.has(ev.type)) {
59976
+ burst.types.add(ev.type);
59977
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59978
+ tags,
59979
+ meta: {
59980
+ edge: "start",
59981
+ type: ev.type,
59982
+ state: ev.state,
59983
+ camStreamId: ev.camStreamId,
59984
+ ...ev.aiClass === null ? {} : { aiClass: ev.aiClass },
59985
+ ...existing ? { burstAgeMs: ev.at - burst.startedAt } : {}
59986
+ }
59987
+ });
59988
+ burst.lastLineAt = Math.max(burst.lastLineAt, ev.at);
59989
+ return;
59990
+ }
59991
+ burst.counts.set(ev.type, (burst.counts.get(ev.type) ?? 0) + 1);
59992
+ if (ev.at - burst.lastLineAt < intervalMs) return;
59993
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59994
+ tags,
59995
+ meta: {
59996
+ edge: "ongoing",
59997
+ camStreamId: ev.camStreamId,
59998
+ counts: countsRecord(burst.counts),
59999
+ windowMs: ev.at - burst.lastLineAt,
60000
+ burstAgeMs: ev.at - burst.startedAt
60001
+ }
60002
+ });
60003
+ burst.counts.clear();
60004
+ burst.lastLineAt = ev.at;
60005
+ };
60006
+ const ended = (camStreamId, at) => {
60007
+ const burst = bursts.get(camStreamId);
60008
+ if (!burst) return;
60009
+ bursts.delete(camStreamId);
60010
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
60011
+ tags,
60012
+ meta: {
60013
+ edge: "end",
60014
+ camStreamId,
60015
+ counts: countsRecord(burst.counts),
60016
+ ticks: burst.ticks,
60017
+ types: [...burst.types],
60018
+ durationMs: at - burst.startedAt
60019
+ }
60020
+ });
60021
+ };
60022
+ return {
60023
+ received,
60024
+ ended
60025
+ };
60026
+ }
59893
60027
  function createAlarmTypeTally(deps) {
59894
60028
  const intervalMs = deps.intervalMs ?? 3e5;
59895
60029
  const lastLoggedAt = /* @__PURE__ */ new Map();
@@ -64276,6 +64410,20 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
64276
64410
  });
64277
64411
  return this.unhandledAlarmsTally;
64278
64412
  }
64413
+ /**
64414
+ * Sampled info-level ingress line for the motion-bearing alarms (VMD and
64415
+ * smart events): one at each burst's start, one per minute while it lasts,
64416
+ * one at its end — never one per ~1 Hz tick. Lazy for the same reason as
64417
+ * `unhandledAlarms`.
64418
+ */
64419
+ motionIngressLog = null;
64420
+ get motionIngress() {
64421
+ this.motionIngressLog ??= createAlarmIngressLog({
64422
+ logger: this.ctx.logger,
64423
+ deviceId: this.id
64424
+ });
64425
+ return this.motionIngressLog;
64426
+ }
64279
64427
  /** Hysteresis: drop motion-active back to inactive after this many ms with
64280
64428
  * no fresh `VMD` event. Hikvision fires VMD every ~1s while motion is
64281
64429
  * active; missing 2 ticks signals the burst is over. */
@@ -66838,19 +66986,25 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66838
66986
  * The native `motion` cap. Its presence is what defaults the camera to
66839
66987
  * `motionSources: ['onboard']`. This provider writes NO `motion` slice (D696):
66840
66988
  * the orchestrator is its only writer, with the EFFECTIVE motion, on the hub.
66841
- * `isDetected` therefore answers from the hub's slice; an unreadable hub, or
66842
- * a slice nobody has published since the hub started, makes the call fail.
66843
- * It never answers `false` for "unknown" (D49, D393).
66989
+ * `isDetected` AND `getStatus` therefore answer from the hub's slice
66990
+ * (`hubMotionNativeProvider`; without its own `getStatus` the framework's
66991
+ * default reads this process's empty local state and answers `null`). An
66992
+ * unreadable hub fails both; never `false` for "unknown" (D49, D393).
66844
66993
  */
66845
66994
  registerMotionCap() {
66846
- this.ctx.registerNativeCap(motionCapability, { isDetected: async ({ deviceId }) => {
66847
- if (deviceId !== this.id) throw new Error(`HikvisionCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
66848
- return readHubMotionDetected(this.ctx.api.deviceState, this.id);
66849
- } });
66995
+ const motionProvider = hubMotionNativeProvider(this.ctx.api.deviceState, this.id, "HikvisionCamera");
66996
+ this.ctx.registerNativeCap(motionCapability, motionProvider);
66850
66997
  }
66851
66998
  handleMotionEvent(ev, camStreamId) {
66852
66999
  const now = ev.observedAt;
66853
67000
  this.lastMotionAt.set(camStreamId, now);
67001
+ this.motionIngress.received({
67002
+ type: ev.type,
67003
+ state: ev.state,
67004
+ aiClass: ev.aiClass,
67005
+ camStreamId,
67006
+ at: now
67007
+ });
66854
67008
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66855
67009
  deviceId: this.id,
66856
67010
  detected: true,
@@ -66863,6 +67017,7 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66863
67017
  const clearedAt = Date.now();
66864
67018
  this.lastMotionAt.set(camStreamId, 0);
66865
67019
  this.motionInactiveTimers.delete(camStreamId);
67020
+ this.motionIngress.ended(camStreamId, clearedAt);
66866
67021
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66867
67022
  deviceId: this.id,
66868
67023
  detected: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.142",
3
+ "version": "1.2.144",
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",