@camstack/addon-provider-hikvision 1.2.141 → 1.2.143

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 +257 -79
  2. package/dist/addon.mjs +257 -79
  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
@@ -14532,7 +14599,7 @@ DeviceType.Camera;
14532
14599
  * This matters here more than anywhere else in the repo, because every provider
14533
14600
  * that knows a device's host knows its PASSWORD too: `{ host, port, username,
14534
14601
  * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
14535
- * and `http://admin:hunter2@192.168.x.x/` is a URL a browser accepts. The
14602
+ * and `http://admin:hunter2@192.168.60.139/` is a URL a browser accepts. The
14536
14603
  * camera's own page will ask for its own login. That is correct, and pre-
14537
14604
  * filling it is the operator's business, not ours.
14538
14605
  *
@@ -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",
@@ -20340,8 +20362,9 @@ var NcRuleSchema = NcRuleInputSchema.extend({
20340
20362
  disabledTargetIds: array(string()).default([])
20341
20363
  });
20342
20364
  /**
20343
- * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
20344
- * still-true state. `backups-exports` and `alarm` are not sections — a completed
20365
+ * The state-summary SECTIONS (D647 / D698 / D708): the D637 kind groups that
20366
+ * report a still-true state. `addons` is the crash-looped addons only and
20367
+ * `addon-updates` the addons that are behind (D708 split them). `backups-exports` and `alarm` are not sections — a completed
20345
20368
  * job is not an open state, and the alarm stays instant.
20346
20369
  */
20347
20370
  var NcStateSummarySectionIdSchema = _enum([
@@ -20353,6 +20376,7 @@ var NcStateSummarySectionIdSchema = _enum([
20353
20376
  "detection-blind",
20354
20377
  "nodes",
20355
20378
  "addons",
20379
+ "addon-updates",
20356
20380
  "updates"
20357
20381
  ]);
20358
20382
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
@@ -20833,7 +20857,19 @@ var NcAlarmModeDelaysListSchema = array(object({
20833
20857
  * When this mode may be armed. Absent / null = always allowed (same as a
20834
20858
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
20835
20859
  */
20836
- schedule: NcScheduleSchema.optional()
20860
+ schedule: NcScheduleSchema.optional(),
20861
+ /**
20862
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
20863
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
20864
+ * only one, and the matches ride the triggered notification's mosaic (D705).
20865
+ */
20866
+ notifyDuringEntryDelay: boolean().optional(),
20867
+ /**
20868
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
20869
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
20870
+ * is the one that starts the entry delay — the entry-delay notification already names it.
20871
+ */
20872
+ notifySensorRewatched: boolean().optional()
20837
20873
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20838
20874
  var NcAlarmModeDelaysEditListSchema = array(object({
20839
20875
  mode: AlarmArmModeSchema,
@@ -20842,7 +20878,11 @@ var NcAlarmModeDelaysEditListSchema = array(object({
20842
20878
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
20843
20879
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
20844
20880
  /** Set a schedule, or `null` to clear (mode always allowed again). */
20845
- schedule: NcScheduleSchema.nullable().optional()
20881
+ schedule: NcScheduleSchema.nullable().optional(),
20882
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
20883
+ notifyDuringEntryDelay: boolean().nullable().optional(),
20884
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
20885
+ notifySensorRewatched: boolean().nullable().optional()
20846
20886
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20847
20887
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20848
20888
  var NcAlarmNonBlockingEditSchema = object({
@@ -23870,7 +23910,8 @@ var PipelineModelOptionSchema = object({
23870
23910
  group: ModelVariantGroupSchema.optional(),
23871
23911
  legacy: boolean().optional(),
23872
23912
  provider: ModelProviderIdSchema.optional(),
23873
- defaultConfidence: number().optional()
23913
+ defaultConfidence: number().optional(),
23914
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional()
23874
23915
  });
23875
23916
  var ConfigFieldBridge = custom();
23876
23917
  var PipelineAddonSchemaSchema = object({
@@ -24703,6 +24744,22 @@ var MotionSourceEnum = _enum([
24703
24744
  "device-activity"
24704
24745
  ]);
24705
24746
  /**
24747
+ * What opened an on-motion detection SESSION: the motion source that passed the
24748
+ * camera's `motionSources` gate, and what raised it (the camera itself, or a
24749
+ * sensor the operator attached — D672). `sensorName` is the attached sensor's
24750
+ * device name as the orchestrator's camera-sensors resolution last read it;
24751
+ * absent when unknown or when the camera itself triggered.
24752
+ *
24753
+ * Carried on the session attach ONLY so the runner's `DetectionPhaseTransition`
24754
+ * can say what started the session (D672 rule 11, amended 2026-10-06). The
24755
+ * runner never decides anything on it.
24756
+ */
24757
+ var SessionTriggerSchema = object({
24758
+ source: MotionSourceEnum,
24759
+ triggeredBy: MotionTriggeredBySchema,
24760
+ sensorName: string().optional()
24761
+ });
24762
+ /**
24706
24763
  * List of motion sources active on a camera. Empty array is valid:
24707
24764
  * "no source" — happens for battery cams without firmware motion when
24708
24765
  * the operator hasn't opted into the SW analyzer (would hold the
@@ -24954,6 +25011,14 @@ var RunnerCameraConfigSchema = object({
24954
25011
  */
24955
25012
  motionSources: MotionSourcesSchema.default(["analyzer"]),
24956
25013
  /**
25014
+ * What opened this attach, on a SESSION attach only (see
25015
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
25016
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
25017
+ * behind it, and every attach from an orchestrator older than the field — the
25018
+ * runner then names no source on the phase event.
25019
+ */
25020
+ sessionTrigger: SessionTriggerSchema.optional(),
25021
+ /**
24957
25022
  * WHICH root detector runs for this camera. The detection counterpart of
24958
25023
  * {@link motionSources}, same shape and same discipline: a per-camera list
24959
25024
  * of sources the system understands, vendor-agnostic, owned here.
@@ -32864,7 +32929,7 @@ var LocalInterfaceSchema = object({
32864
32929
  name: string(),
32865
32930
  family: _enum(["IPv4", "IPv6"]),
32866
32931
  address: string(),
32867
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
32932
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
32868
32933
  cidr: string(),
32869
32934
  /** OS netmask. Stable across calls so the SDK can hash on it. */
32870
32935
  netmask: string(),
@@ -32918,7 +32983,8 @@ var ConnectionEndpointSchema = object({
32918
32983
  "lan-ipv4",
32919
32984
  "lan-ipv6",
32920
32985
  "public",
32921
- "loopback"
32986
+ "loopback",
32987
+ "app-link"
32922
32988
  ]),
32923
32989
  /**
32924
32990
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -32987,15 +33053,27 @@ var GetConnectionEndpointsResultSchema = object({
32987
33053
  localPort: AdvertisedLocalPortSchema
32988
33054
  });
32989
33055
  /**
32990
- * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
32991
- * AUTO (resolved from the candidate ranking at send time); `resolved` reports
32992
- * what AUTO currently picks, so the UI can show the effective value either way.
33056
+ * The address flagged for notification links. `baseUrl: null` = AUTO, which
33057
+ * since D709 is the CamStack app link; `resolved` reports the effective value,
33058
+ * so the UI can show it either way.
32993
33059
  */
32994
33060
  var NotificationEndpointSchema = object({
32995
33061
  /** The operator's explicit choice, or null for AUTO. */
32996
33062
  baseUrl: string().nullable(),
32997
- /** What the ranking currently resolves to (null when nothing is reachable). */
32998
- resolved: string().nullable()
33063
+ /**
33064
+ * The effective flagged address: the explicit choice, else (AUTO) the
33065
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
33066
+ * answered the public tunnel / mesh here, or null.
33067
+ */
33068
+ resolved: string().nullable(),
33069
+ /**
33070
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
33071
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
33072
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
33073
+ * hub advertises neither; the app link is then minted without `hub=`.
33074
+ * Absent on a pre-D709 hub.
33075
+ */
33076
+ appLinkHub: string().nullable()
32999
33077
  });
33000
33078
  /**
33001
33079
  * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
@@ -33049,7 +33127,11 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
33049
33127
  * Pass `'https'` when the caller is itself loaded over HTTPS
33050
33128
  * to avoid mixed-content blocks in the browser. The public
33051
33129
  * tunnel always emits `https://` regardless. */
33052
- scheme: _enum(["http", "https"]).optional()
33130
+ scheme: _enum(["http", "https"]).optional(),
33131
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
33132
+ * an address notification links can be flagged with, not a hub
33133
+ * route. Default `false`: racers and rankers never see it. */
33134
+ includeAppLink: boolean().optional()
33053
33135
  }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
33054
33136
  kind: "mutation",
33055
33137
  auth: "admin"
@@ -33378,7 +33460,7 @@ var MeshPeerSchema = object({
33378
33460
  tags: array(string()).readonly(),
33379
33461
  /** True when the peer advertises itself as an exit-node candidate. */
33380
33462
  exitNodeOption: boolean(),
33381
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
33463
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
33382
33464
  advertisedRoutes: array(string()).readonly(),
33383
33465
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
33384
33466
  userLogin: string().nullable(),
@@ -53855,12 +53937,14 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53855
53937
  {
53856
53938
  id: "addons",
53857
53939
  family: "system",
53858
- label: "Addons",
53859
- kinds: [
53860
- "addon-crash-loop",
53861
- "addon-update-available",
53862
- "addon-updated"
53863
- ]
53940
+ label: "Addons stopped",
53941
+ kinds: ["addon-crash-loop"]
53942
+ },
53943
+ {
53944
+ id: "addon-updates",
53945
+ family: "system",
53946
+ label: "Addon updates",
53947
+ kinds: ["addon-update-available", "addon-updated"]
53864
53948
  },
53865
53949
  {
53866
53950
  id: "updates",
@@ -54020,13 +54104,15 @@ var STATE_SUMMARY_SECTION_VARS = [
54020
54104
  },
54021
54105
  {
54022
54106
  name: "summaryAddons",
54023
- label: "Summary: addons",
54024
- example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
54025
- kinds: [
54026
- "addon-crash-loop",
54027
- "addon-update-available",
54028
- "addon-updated"
54029
- ]
54107
+ label: "Summary: addons stopped",
54108
+ example: "hub — recorder stopped after repeated crashes",
54109
+ kinds: ["addon-crash-loop"]
54110
+ },
54111
+ {
54112
+ name: "summaryAddonUpdates",
54113
+ label: "Summary: addon updates",
54114
+ example: "hub:\n• @camstack/addon-recorder 1.2.3 → 1.2.4",
54115
+ kinds: ["addon-update-available", "addon-updated"]
54030
54116
  },
54031
54117
  {
54032
54118
  name: "summaryUpdates",
@@ -54110,6 +54196,7 @@ var STATE_SUMMARY_VARS = [
54110
54196
  [...RULE_BODY_ONLY],
54111
54197
  [...RULE_TEXT],
54112
54198
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54199
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
54113
54200
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54114
54201
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
54115
54202
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -59847,6 +59934,75 @@ function createAccessoryDevice(kind, ctx, parent) {
59847
59934
  default: throw new Error(`Hikvision provider does not implement accessory kind "${String(kind)}"`);
59848
59935
  }
59849
59936
  }
59937
+ var ALARM_INGRESS_MESSAGE = "hikvision: alarm event received";
59938
+ var countsRecord = (counts) => Object.fromEntries(counts);
59939
+ function createAlarmIngressLog(deps) {
59940
+ const intervalMs = deps.intervalMs ?? 6e4;
59941
+ const bursts = /* @__PURE__ */ new Map();
59942
+ const tags = { deviceId: deps.deviceId };
59943
+ const received = (ev) => {
59944
+ const existing = bursts.get(ev.camStreamId);
59945
+ const burst = existing ?? {
59946
+ startedAt: ev.at,
59947
+ lastLineAt: ev.at,
59948
+ counts: /* @__PURE__ */ new Map(),
59949
+ types: /* @__PURE__ */ new Set(),
59950
+ ticks: 0
59951
+ };
59952
+ if (!existing) bursts.set(ev.camStreamId, burst);
59953
+ burst.ticks += 1;
59954
+ if (!burst.types.has(ev.type)) {
59955
+ burst.types.add(ev.type);
59956
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59957
+ tags,
59958
+ meta: {
59959
+ edge: "start",
59960
+ type: ev.type,
59961
+ state: ev.state,
59962
+ camStreamId: ev.camStreamId,
59963
+ ...ev.aiClass === null ? {} : { aiClass: ev.aiClass },
59964
+ ...existing ? { burstAgeMs: ev.at - burst.startedAt } : {}
59965
+ }
59966
+ });
59967
+ burst.lastLineAt = Math.max(burst.lastLineAt, ev.at);
59968
+ return;
59969
+ }
59970
+ burst.counts.set(ev.type, (burst.counts.get(ev.type) ?? 0) + 1);
59971
+ if (ev.at - burst.lastLineAt < intervalMs) return;
59972
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59973
+ tags,
59974
+ meta: {
59975
+ edge: "ongoing",
59976
+ camStreamId: ev.camStreamId,
59977
+ counts: countsRecord(burst.counts),
59978
+ windowMs: ev.at - burst.lastLineAt,
59979
+ burstAgeMs: ev.at - burst.startedAt
59980
+ }
59981
+ });
59982
+ burst.counts.clear();
59983
+ burst.lastLineAt = ev.at;
59984
+ };
59985
+ const ended = (camStreamId, at) => {
59986
+ const burst = bursts.get(camStreamId);
59987
+ if (!burst) return;
59988
+ bursts.delete(camStreamId);
59989
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59990
+ tags,
59991
+ meta: {
59992
+ edge: "end",
59993
+ camStreamId,
59994
+ counts: countsRecord(burst.counts),
59995
+ ticks: burst.ticks,
59996
+ types: [...burst.types],
59997
+ durationMs: at - burst.startedAt
59998
+ }
59999
+ });
60000
+ };
60001
+ return {
60002
+ received,
60003
+ ended
60004
+ };
60005
+ }
59850
60006
  function createAlarmTypeTally(deps) {
59851
60007
  const intervalMs = deps.intervalMs ?? 3e5;
59852
60008
  const lastLoggedAt = /* @__PURE__ */ new Map();
@@ -64233,6 +64389,20 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
64233
64389
  });
64234
64390
  return this.unhandledAlarmsTally;
64235
64391
  }
64392
+ /**
64393
+ * Sampled info-level ingress line for the motion-bearing alarms (VMD and
64394
+ * smart events): one at each burst's start, one per minute while it lasts,
64395
+ * one at its end — never one per ~1 Hz tick. Lazy for the same reason as
64396
+ * `unhandledAlarms`.
64397
+ */
64398
+ motionIngressLog = null;
64399
+ get motionIngress() {
64400
+ this.motionIngressLog ??= createAlarmIngressLog({
64401
+ logger: this.ctx.logger,
64402
+ deviceId: this.id
64403
+ });
64404
+ return this.motionIngressLog;
64405
+ }
64236
64406
  /** Hysteresis: drop motion-active back to inactive after this many ms with
64237
64407
  * no fresh `VMD` event. Hikvision fires VMD every ~1s while motion is
64238
64408
  * active; missing 2 ticks signals the burst is over. */
@@ -66808,6 +66978,13 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66808
66978
  handleMotionEvent(ev, camStreamId) {
66809
66979
  const now = ev.observedAt;
66810
66980
  this.lastMotionAt.set(camStreamId, now);
66981
+ this.motionIngress.received({
66982
+ type: ev.type,
66983
+ state: ev.state,
66984
+ aiClass: ev.aiClass,
66985
+ camStreamId,
66986
+ at: now
66987
+ });
66811
66988
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66812
66989
  deviceId: this.id,
66813
66990
  detected: true,
@@ -66820,6 +66997,7 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66820
66997
  const clearedAt = Date.now();
66821
66998
  this.lastMotionAt.set(camStreamId, 0);
66822
66999
  this.motionInactiveTimers.delete(camStreamId);
67000
+ this.motionIngress.ended(camStreamId, clearedAt);
66823
67001
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66824
67002
  deviceId: this.id,
66825
67003
  detected: false,
@@ -66857,7 +67035,7 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66857
67035
  key: "host",
66858
67036
  label: "Camera IP",
66859
67037
  required: true,
66860
- placeholder: "192.168.x.x"
67038
+ placeholder: "192.168.10.100"
66861
67039
  },
66862
67040
  {
66863
67041
  type: "number",
@@ -76906,13 +77084,13 @@ function enumerateHosts(cidr) {
76906
77084
  for (let d = 1; d <= 254; d += 1) out.push(`${a}.${b}.${c}.${d}`);
76907
77085
  return out;
76908
77086
  }
76909
- /** Best-effort local /24 from the first non-internal IPv4 interface (e.g. `192.168.x.x/24`). */
77087
+ /** Best-effort local /24 from the first non-internal IPv4 interface (e.g. `192.168.10.0/24`). */
76910
77088
  function detectLocalCidr() {
76911
77089
  for (const addrs of Object.values((0, node_os.networkInterfaces)())) for (const addr of addrs ?? []) if (addr.family === "IPv4" && !addr.internal) {
76912
77090
  const [a, b, c] = addr.address.split(".");
76913
77091
  return `${a}.${b}.${c}.0/24`;
76914
77092
  }
76915
- return "192.168.x.x/24";
77093
+ return "192.168.10.0/24";
76916
77094
  }
76917
77095
  /** Flatten a host into a flat row-key slug — must match `generateStableId`'s host fallback so a
76918
77096
  * re-scan of a host-added camera is detected as already onboarded. */
@@ -76982,7 +77160,7 @@ function buildCreationFormSchema() {
76982
77160
  key: "host",
76983
77161
  label: "Camera IP",
76984
77162
  required: true,
76985
- placeholder: "192.168.x.x",
77163
+ placeholder: "192.168.10.100",
76986
77164
  inputType: "text"
76987
77165
  },
76988
77166
  {
@@ -77032,14 +77210,14 @@ function buildDiscoveryParamsFormSchema() {
77032
77210
  }, {
77033
77211
  id: "scan",
77034
77212
  title: "Scan options",
77035
- description: "The credentialed ISAPI subnet scan is the reliable path (multicast WS-Discovery is not received on a macvlan/br0 hub). IMPORTANT: leaving the network blank scans only the HUB’s own subnet — if your cameras are on a DIFFERENT subnet than the hub, you MUST enter their CIDR here (e.g. 192.168.x.x/24), otherwise the scan returns nothing.",
77213
+ description: "The credentialed ISAPI subnet scan is the reliable path (multicast WS-Discovery is not received on a macvlan/br0 hub). IMPORTANT: leaving the network blank scans only the HUB’s own subnet — if your cameras are on a DIFFERENT subnet than the hub, you MUST enter their CIDR here (e.g. 192.168.60.0/24), otherwise the scan returns nothing.",
77036
77214
  columns: 1,
77037
77215
  fields: [{
77038
77216
  type: "text",
77039
77217
  key: "networkCidr",
77040
77218
  label: "Network (CIDR) — required if cameras are on a different subnet than the hub",
77041
77219
  required: false,
77042
- placeholder: "192.168.x.x/24"
77220
+ placeholder: "192.168.60.0/24"
77043
77221
  }, {
77044
77222
  type: "number",
77045
77223
  key: "timeoutMs",
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
@@ -14511,7 +14578,7 @@ DeviceType.Camera;
14511
14578
  * This matters here more than anywhere else in the repo, because every provider
14512
14579
  * that knows a device's host knows its PASSWORD too: `{ host, port, username,
14513
14580
  * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
14514
- * and `http://admin:hunter2@192.168.x.x/` is a URL a browser accepts. The
14581
+ * and `http://admin:hunter2@192.168.60.139/` is a URL a browser accepts. The
14515
14582
  * camera's own page will ask for its own login. That is correct, and pre-
14516
14583
  * filling it is the operator's business, not ours.
14517
14584
  *
@@ -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",
@@ -20319,8 +20341,9 @@ var NcRuleSchema = NcRuleInputSchema.extend({
20319
20341
  disabledTargetIds: array(string()).default([])
20320
20342
  });
20321
20343
  /**
20322
- * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
20323
- * still-true state. `backups-exports` and `alarm` are not sections — a completed
20344
+ * The state-summary SECTIONS (D647 / D698 / D708): the D637 kind groups that
20345
+ * report a still-true state. `addons` is the crash-looped addons only and
20346
+ * `addon-updates` the addons that are behind (D708 split them). `backups-exports` and `alarm` are not sections — a completed
20324
20347
  * job is not an open state, and the alarm stays instant.
20325
20348
  */
20326
20349
  var NcStateSummarySectionIdSchema = _enum([
@@ -20332,6 +20355,7 @@ var NcStateSummarySectionIdSchema = _enum([
20332
20355
  "detection-blind",
20333
20356
  "nodes",
20334
20357
  "addons",
20358
+ "addon-updates",
20335
20359
  "updates"
20336
20360
  ]);
20337
20361
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
@@ -20812,7 +20836,19 @@ var NcAlarmModeDelaysListSchema = array(object({
20812
20836
  * When this mode may be armed. Absent / null = always allowed (same as a
20813
20837
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
20814
20838
  */
20815
- schedule: NcScheduleSchema.optional()
20839
+ schedule: NcScheduleSchema.optional(),
20840
+ /**
20841
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
20842
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
20843
+ * only one, and the matches ride the triggered notification's mosaic (D705).
20844
+ */
20845
+ notifyDuringEntryDelay: boolean().optional(),
20846
+ /**
20847
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
20848
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
20849
+ * is the one that starts the entry delay — the entry-delay notification already names it.
20850
+ */
20851
+ notifySensorRewatched: boolean().optional()
20816
20852
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20817
20853
  var NcAlarmModeDelaysEditListSchema = array(object({
20818
20854
  mode: AlarmArmModeSchema,
@@ -20821,7 +20857,11 @@ var NcAlarmModeDelaysEditListSchema = array(object({
20821
20857
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
20822
20858
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
20823
20859
  /** Set a schedule, or `null` to clear (mode always allowed again). */
20824
- schedule: NcScheduleSchema.nullable().optional()
20860
+ schedule: NcScheduleSchema.nullable().optional(),
20861
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
20862
+ notifyDuringEntryDelay: boolean().nullable().optional(),
20863
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
20864
+ notifySensorRewatched: boolean().nullable().optional()
20825
20865
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20826
20866
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20827
20867
  var NcAlarmNonBlockingEditSchema = object({
@@ -23849,7 +23889,8 @@ var PipelineModelOptionSchema = object({
23849
23889
  group: ModelVariantGroupSchema.optional(),
23850
23890
  legacy: boolean().optional(),
23851
23891
  provider: ModelProviderIdSchema.optional(),
23852
- defaultConfidence: number().optional()
23892
+ defaultConfidence: number().optional(),
23893
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional()
23853
23894
  });
23854
23895
  var ConfigFieldBridge = custom();
23855
23896
  var PipelineAddonSchemaSchema = object({
@@ -24682,6 +24723,22 @@ var MotionSourceEnum = _enum([
24682
24723
  "device-activity"
24683
24724
  ]);
24684
24725
  /**
24726
+ * What opened an on-motion detection SESSION: the motion source that passed the
24727
+ * camera's `motionSources` gate, and what raised it (the camera itself, or a
24728
+ * sensor the operator attached — D672). `sensorName` is the attached sensor's
24729
+ * device name as the orchestrator's camera-sensors resolution last read it;
24730
+ * absent when unknown or when the camera itself triggered.
24731
+ *
24732
+ * Carried on the session attach ONLY so the runner's `DetectionPhaseTransition`
24733
+ * can say what started the session (D672 rule 11, amended 2026-10-06). The
24734
+ * runner never decides anything on it.
24735
+ */
24736
+ var SessionTriggerSchema = object({
24737
+ source: MotionSourceEnum,
24738
+ triggeredBy: MotionTriggeredBySchema,
24739
+ sensorName: string().optional()
24740
+ });
24741
+ /**
24685
24742
  * List of motion sources active on a camera. Empty array is valid:
24686
24743
  * "no source" — happens for battery cams without firmware motion when
24687
24744
  * the operator hasn't opted into the SW analyzer (would hold the
@@ -24933,6 +24990,14 @@ var RunnerCameraConfigSchema = object({
24933
24990
  */
24934
24991
  motionSources: MotionSourcesSchema.default(["analyzer"]),
24935
24992
  /**
24993
+ * What opened this attach, on a SESSION attach only (see
24994
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
24995
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
24996
+ * behind it, and every attach from an orchestrator older than the field — the
24997
+ * runner then names no source on the phase event.
24998
+ */
24999
+ sessionTrigger: SessionTriggerSchema.optional(),
25000
+ /**
24936
25001
  * WHICH root detector runs for this camera. The detection counterpart of
24937
25002
  * {@link motionSources}, same shape and same discipline: a per-camera list
24938
25003
  * of sources the system understands, vendor-agnostic, owned here.
@@ -32843,7 +32908,7 @@ var LocalInterfaceSchema = object({
32843
32908
  name: string(),
32844
32909
  family: _enum(["IPv4", "IPv6"]),
32845
32910
  address: string(),
32846
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
32911
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
32847
32912
  cidr: string(),
32848
32913
  /** OS netmask. Stable across calls so the SDK can hash on it. */
32849
32914
  netmask: string(),
@@ -32897,7 +32962,8 @@ var ConnectionEndpointSchema = object({
32897
32962
  "lan-ipv4",
32898
32963
  "lan-ipv6",
32899
32964
  "public",
32900
- "loopback"
32965
+ "loopback",
32966
+ "app-link"
32901
32967
  ]),
32902
32968
  /**
32903
32969
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -32966,15 +33032,27 @@ var GetConnectionEndpointsResultSchema = object({
32966
33032
  localPort: AdvertisedLocalPortSchema
32967
33033
  });
32968
33034
  /**
32969
- * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
32970
- * AUTO (resolved from the candidate ranking at send time); `resolved` reports
32971
- * what AUTO currently picks, so the UI can show the effective value either way.
33035
+ * The address flagged for notification links. `baseUrl: null` = AUTO, which
33036
+ * since D709 is the CamStack app link; `resolved` reports the effective value,
33037
+ * so the UI can show it either way.
32972
33038
  */
32973
33039
  var NotificationEndpointSchema = object({
32974
33040
  /** The operator's explicit choice, or null for AUTO. */
32975
33041
  baseUrl: string().nullable(),
32976
- /** What the ranking currently resolves to (null when nothing is reachable). */
32977
- resolved: string().nullable()
33042
+ /**
33043
+ * The effective flagged address: the explicit choice, else (AUTO) the
33044
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
33045
+ * answered the public tunnel / mesh here, or null.
33046
+ */
33047
+ resolved: string().nullable(),
33048
+ /**
33049
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
33050
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
33051
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
33052
+ * hub advertises neither; the app link is then minted without `hub=`.
33053
+ * Absent on a pre-D709 hub.
33054
+ */
33055
+ appLinkHub: string().nullable()
32978
33056
  });
32979
33057
  /**
32980
33058
  * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
@@ -33028,7 +33106,11 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
33028
33106
  * Pass `'https'` when the caller is itself loaded over HTTPS
33029
33107
  * to avoid mixed-content blocks in the browser. The public
33030
33108
  * tunnel always emits `https://` regardless. */
33031
- scheme: _enum(["http", "https"]).optional()
33109
+ scheme: _enum(["http", "https"]).optional(),
33110
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
33111
+ * an address notification links can be flagged with, not a hub
33112
+ * route. Default `false`: racers and rankers never see it. */
33113
+ includeAppLink: boolean().optional()
33032
33114
  }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
33033
33115
  kind: "mutation",
33034
33116
  auth: "admin"
@@ -33357,7 +33439,7 @@ var MeshPeerSchema = object({
33357
33439
  tags: array(string()).readonly(),
33358
33440
  /** True when the peer advertises itself as an exit-node candidate. */
33359
33441
  exitNodeOption: boolean(),
33360
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
33442
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
33361
33443
  advertisedRoutes: array(string()).readonly(),
33362
33444
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
33363
33445
  userLogin: string().nullable(),
@@ -53834,12 +53916,14 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53834
53916
  {
53835
53917
  id: "addons",
53836
53918
  family: "system",
53837
- label: "Addons",
53838
- kinds: [
53839
- "addon-crash-loop",
53840
- "addon-update-available",
53841
- "addon-updated"
53842
- ]
53919
+ label: "Addons stopped",
53920
+ kinds: ["addon-crash-loop"]
53921
+ },
53922
+ {
53923
+ id: "addon-updates",
53924
+ family: "system",
53925
+ label: "Addon updates",
53926
+ kinds: ["addon-update-available", "addon-updated"]
53843
53927
  },
53844
53928
  {
53845
53929
  id: "updates",
@@ -53999,13 +54083,15 @@ var STATE_SUMMARY_SECTION_VARS = [
53999
54083
  },
54000
54084
  {
54001
54085
  name: "summaryAddons",
54002
- label: "Summary: addons",
54003
- example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
54004
- kinds: [
54005
- "addon-crash-loop",
54006
- "addon-update-available",
54007
- "addon-updated"
54008
- ]
54086
+ label: "Summary: addons stopped",
54087
+ example: "hub — recorder stopped after repeated crashes",
54088
+ kinds: ["addon-crash-loop"]
54089
+ },
54090
+ {
54091
+ name: "summaryAddonUpdates",
54092
+ label: "Summary: addon updates",
54093
+ example: "hub:\n• @camstack/addon-recorder 1.2.3 → 1.2.4",
54094
+ kinds: ["addon-update-available", "addon-updated"]
54009
54095
  },
54010
54096
  {
54011
54097
  name: "summaryUpdates",
@@ -54089,6 +54175,7 @@ var STATE_SUMMARY_VARS = [
54089
54175
  [...RULE_BODY_ONLY],
54090
54176
  [...RULE_TEXT],
54091
54177
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54178
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
54092
54179
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54093
54180
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
54094
54181
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -59826,6 +59913,75 @@ function createAccessoryDevice(kind, ctx, parent) {
59826
59913
  default: throw new Error(`Hikvision provider does not implement accessory kind "${String(kind)}"`);
59827
59914
  }
59828
59915
  }
59916
+ var ALARM_INGRESS_MESSAGE = "hikvision: alarm event received";
59917
+ var countsRecord = (counts) => Object.fromEntries(counts);
59918
+ function createAlarmIngressLog(deps) {
59919
+ const intervalMs = deps.intervalMs ?? 6e4;
59920
+ const bursts = /* @__PURE__ */ new Map();
59921
+ const tags = { deviceId: deps.deviceId };
59922
+ const received = (ev) => {
59923
+ const existing = bursts.get(ev.camStreamId);
59924
+ const burst = existing ?? {
59925
+ startedAt: ev.at,
59926
+ lastLineAt: ev.at,
59927
+ counts: /* @__PURE__ */ new Map(),
59928
+ types: /* @__PURE__ */ new Set(),
59929
+ ticks: 0
59930
+ };
59931
+ if (!existing) bursts.set(ev.camStreamId, burst);
59932
+ burst.ticks += 1;
59933
+ if (!burst.types.has(ev.type)) {
59934
+ burst.types.add(ev.type);
59935
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59936
+ tags,
59937
+ meta: {
59938
+ edge: "start",
59939
+ type: ev.type,
59940
+ state: ev.state,
59941
+ camStreamId: ev.camStreamId,
59942
+ ...ev.aiClass === null ? {} : { aiClass: ev.aiClass },
59943
+ ...existing ? { burstAgeMs: ev.at - burst.startedAt } : {}
59944
+ }
59945
+ });
59946
+ burst.lastLineAt = Math.max(burst.lastLineAt, ev.at);
59947
+ return;
59948
+ }
59949
+ burst.counts.set(ev.type, (burst.counts.get(ev.type) ?? 0) + 1);
59950
+ if (ev.at - burst.lastLineAt < intervalMs) return;
59951
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59952
+ tags,
59953
+ meta: {
59954
+ edge: "ongoing",
59955
+ camStreamId: ev.camStreamId,
59956
+ counts: countsRecord(burst.counts),
59957
+ windowMs: ev.at - burst.lastLineAt,
59958
+ burstAgeMs: ev.at - burst.startedAt
59959
+ }
59960
+ });
59961
+ burst.counts.clear();
59962
+ burst.lastLineAt = ev.at;
59963
+ };
59964
+ const ended = (camStreamId, at) => {
59965
+ const burst = bursts.get(camStreamId);
59966
+ if (!burst) return;
59967
+ bursts.delete(camStreamId);
59968
+ deps.logger.info(ALARM_INGRESS_MESSAGE, {
59969
+ tags,
59970
+ meta: {
59971
+ edge: "end",
59972
+ camStreamId,
59973
+ counts: countsRecord(burst.counts),
59974
+ ticks: burst.ticks,
59975
+ types: [...burst.types],
59976
+ durationMs: at - burst.startedAt
59977
+ }
59978
+ });
59979
+ };
59980
+ return {
59981
+ received,
59982
+ ended
59983
+ };
59984
+ }
59829
59985
  function createAlarmTypeTally(deps) {
59830
59986
  const intervalMs = deps.intervalMs ?? 3e5;
59831
59987
  const lastLoggedAt = /* @__PURE__ */ new Map();
@@ -64212,6 +64368,20 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
64212
64368
  });
64213
64369
  return this.unhandledAlarmsTally;
64214
64370
  }
64371
+ /**
64372
+ * Sampled info-level ingress line for the motion-bearing alarms (VMD and
64373
+ * smart events): one at each burst's start, one per minute while it lasts,
64374
+ * one at its end — never one per ~1 Hz tick. Lazy for the same reason as
64375
+ * `unhandledAlarms`.
64376
+ */
64377
+ motionIngressLog = null;
64378
+ get motionIngress() {
64379
+ this.motionIngressLog ??= createAlarmIngressLog({
64380
+ logger: this.ctx.logger,
64381
+ deviceId: this.id
64382
+ });
64383
+ return this.motionIngressLog;
64384
+ }
64215
64385
  /** Hysteresis: drop motion-active back to inactive after this many ms with
64216
64386
  * no fresh `VMD` event. Hikvision fires VMD every ~1s while motion is
64217
64387
  * active; missing 2 ticks signals the burst is over. */
@@ -66787,6 +66957,13 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66787
66957
  handleMotionEvent(ev, camStreamId) {
66788
66958
  const now = ev.observedAt;
66789
66959
  this.lastMotionAt.set(camStreamId, now);
66960
+ this.motionIngress.received({
66961
+ type: ev.type,
66962
+ state: ev.state,
66963
+ aiClass: ev.aiClass,
66964
+ camStreamId,
66965
+ at: now
66966
+ });
66790
66967
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66791
66968
  deviceId: this.id,
66792
66969
  detected: true,
@@ -66799,6 +66976,7 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66799
66976
  const clearedAt = Date.now();
66800
66977
  this.lastMotionAt.set(camStreamId, 0);
66801
66978
  this.motionInactiveTimers.delete(camStreamId);
66979
+ this.motionIngress.ended(camStreamId, clearedAt);
66802
66980
  this.ctx.eventBus.emit(providerMotionEvent(this.eventSource, {
66803
66981
  deviceId: this.id,
66804
66982
  detected: false,
@@ -66836,7 +67014,7 @@ var HikvisionCamera = class HikvisionCamera extends BaseDevice {
66836
67014
  key: "host",
66837
67015
  label: "Camera IP",
66838
67016
  required: true,
66839
- placeholder: "192.168.x.x"
67017
+ placeholder: "192.168.10.100"
66840
67018
  },
66841
67019
  {
66842
67020
  type: "number",
@@ -76885,13 +77063,13 @@ function enumerateHosts(cidr) {
76885
77063
  for (let d = 1; d <= 254; d += 1) out.push(`${a}.${b}.${c}.${d}`);
76886
77064
  return out;
76887
77065
  }
76888
- /** Best-effort local /24 from the first non-internal IPv4 interface (e.g. `192.168.x.x/24`). */
77066
+ /** Best-effort local /24 from the first non-internal IPv4 interface (e.g. `192.168.10.0/24`). */
76889
77067
  function detectLocalCidr() {
76890
77068
  for (const addrs of Object.values(networkInterfaces())) for (const addr of addrs ?? []) if (addr.family === "IPv4" && !addr.internal) {
76891
77069
  const [a, b, c] = addr.address.split(".");
76892
77070
  return `${a}.${b}.${c}.0/24`;
76893
77071
  }
76894
- return "192.168.x.x/24";
77072
+ return "192.168.10.0/24";
76895
77073
  }
76896
77074
  /** Flatten a host into a flat row-key slug — must match `generateStableId`'s host fallback so a
76897
77075
  * re-scan of a host-added camera is detected as already onboarded. */
@@ -76961,7 +77139,7 @@ function buildCreationFormSchema() {
76961
77139
  key: "host",
76962
77140
  label: "Camera IP",
76963
77141
  required: true,
76964
- placeholder: "192.168.x.x",
77142
+ placeholder: "192.168.10.100",
76965
77143
  inputType: "text"
76966
77144
  },
76967
77145
  {
@@ -77011,14 +77189,14 @@ function buildDiscoveryParamsFormSchema() {
77011
77189
  }, {
77012
77190
  id: "scan",
77013
77191
  title: "Scan options",
77014
- description: "The credentialed ISAPI subnet scan is the reliable path (multicast WS-Discovery is not received on a macvlan/br0 hub). IMPORTANT: leaving the network blank scans only the HUB’s own subnet — if your cameras are on a DIFFERENT subnet than the hub, you MUST enter their CIDR here (e.g. 192.168.x.x/24), otherwise the scan returns nothing.",
77192
+ description: "The credentialed ISAPI subnet scan is the reliable path (multicast WS-Discovery is not received on a macvlan/br0 hub). IMPORTANT: leaving the network blank scans only the HUB’s own subnet — if your cameras are on a DIFFERENT subnet than the hub, you MUST enter their CIDR here (e.g. 192.168.60.0/24), otherwise the scan returns nothing.",
77015
77193
  columns: 1,
77016
77194
  fields: [{
77017
77195
  type: "text",
77018
77196
  key: "networkCidr",
77019
77197
  label: "Network (CIDR) — required if cameras are on a different subnet than the hub",
77020
77198
  required: false,
77021
- placeholder: "192.168.x.x/24"
77199
+ placeholder: "192.168.60.0/24"
77022
77200
  }, {
77023
77201
  type: "number",
77024
77202
  key: "timeoutMs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.141",
3
+ "version": "1.2.143",
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",