@camstack/addon-provider-amcrest 0.2.129 → 0.2.131

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 +249 -140
  2. package/dist/addon.mjs +249 -140
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -10489,6 +10489,57 @@ var ClipSearchStrictnessSchema = _enum([
10489
10489
  "balanced",
10490
10490
  "strict"
10491
10491
  ]);
10492
+ new Set([
10493
+ {
10494
+ id: "person",
10495
+ name: "Person"
10496
+ },
10497
+ {
10498
+ id: "vehicle",
10499
+ name: "Vehicle"
10500
+ },
10501
+ {
10502
+ id: "animal",
10503
+ name: "Animal"
10504
+ },
10505
+ {
10506
+ id: "package",
10507
+ name: "Package"
10508
+ }
10509
+ ].map((l) => l.id));
10510
+ var COCO_TO_MACRO = {
10511
+ mapping: {
10512
+ person: "person",
10513
+ bicycle: "vehicle",
10514
+ car: "vehicle",
10515
+ motorcycle: "vehicle",
10516
+ airplane: "vehicle",
10517
+ bus: "vehicle",
10518
+ train: "vehicle",
10519
+ truck: "vehicle",
10520
+ boat: "vehicle",
10521
+ bird: "animal",
10522
+ cat: "animal",
10523
+ dog: "animal",
10524
+ horse: "animal",
10525
+ sheep: "animal",
10526
+ cow: "animal",
10527
+ elephant: "animal",
10528
+ bear: "animal",
10529
+ zebra: "animal",
10530
+ giraffe: "animal",
10531
+ suitcase: "package",
10532
+ backpack: "package",
10533
+ handbag: "package"
10534
+ },
10535
+ preserveOriginal: false
10536
+ };
10537
+ /** Steps a root detector may declare it fills. */
10538
+ var FILLABLE_STEP_IDS = [
10539
+ "animal-classifier",
10540
+ "vehicle-classifier",
10541
+ "package-detection"
10542
+ ];
10492
10543
  var MODEL_FORMATS = [
10493
10544
  "onnx",
10494
10545
  "coreml",
@@ -10558,11 +10609,11 @@ var ModelVariantGroupSchema = object({
10558
10609
  /** Quantization axis. Omit ⇒ the fp32 base build. */
10559
10610
  precision: _enum(["fp32", "int8"]).optional(),
10560
10611
  /**
10561
- * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
10562
- * latency-optimized export (e.g. ReLU-activation variant) — the slot the
10563
- * future performance variants plug into.
10612
+ * Training / latency-variant axis. Omit ⇒ the standard build. `test` marks
10613
+ * an alternate training of the same family (Scrypted `relu_test`); chip
10614
+ * label is "Test".
10564
10615
  */
10565
- optimization: _enum(["standard", "fast"]).optional(),
10616
+ optimization: _enum(["standard", "test"]).optional(),
10566
10617
  /**
10567
10618
  * Input-resolution axis (square input side, px). Omit ⇒ the family's native
10568
10619
  * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
@@ -10585,9 +10636,10 @@ var ModelProviderIdSchema = _enum([
10585
10636
  * are per upstream. A persisted custom-model row written when this field was a
10586
10637
  * string still parses — the string is lifted into `{ weights }`.
10587
10638
  *
10588
- * Every licence surface (the admin "Models and licenses" page,
10589
- * `THIRD_PARTY_MODELS.md`, the Hugging Face model table) is GENERATED from
10590
- * these records by `scripts/gen-model-licenses.ts` (D661).
10639
+ * Every machine licence surface (`THIRD_PARTY_MODELS.md`, the Hugging Face
10640
+ * model table) is GENERATED from these records by
10641
+ * `scripts/gen-model-licenses.ts` (D661). Operator-facing docs live in
10642
+ * `camstack/docs` → `/docs/detection/models`.
10591
10643
  */
10592
10644
  var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { weights: value } : value, object({
10593
10645
  /** SPDX id of the WEIGHTS' terms, a `LicenseRef-*` for non-SPDX terms, or `UNKNOWN`. */
@@ -10620,6 +10672,12 @@ var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { wei
10620
10672
  /** True when the weights or their data forbid commercial use. */
10621
10673
  nonCommercial: boolean().optional()
10622
10674
  }));
10675
+ /** One `fillsClassifierEffects` entry — see `ModelCatalogEntry.fillsClassifierEffects`. */
10676
+ var FillsClassifierEffectSchema = object({
10677
+ stepId: _enum(FILLABLE_STEP_IDS),
10678
+ labelToEffect: record(string(), string()),
10679
+ uncovered: array(string()).readonly()
10680
+ });
10623
10681
  var ModelCatalogEntrySchema = object({
10624
10682
  id: string(),
10625
10683
  name: string(),
@@ -10746,6 +10804,16 @@ var ModelCatalogEntrySchema = object({
10746
10804
  */
10747
10805
  classMap: DetectionCatalogClassMapSchema.optional(),
10748
10806
  /**
10807
+ * The pipeline steps this ROOT detector already does the work of (D715):
10808
+ * its labels name the effect the step exists to produce (`deer` for
10809
+ * `animal-classifier`, `package` for `package-detection`). Derived from the
10810
+ * labelmap by `deriveClassifierFills`, never hand-written; a COCO model
10811
+ * declares none. Whether a declaration is honoured is the step's own
10812
+ * `labelLink` policy (`resolveLabelLink`). `labelToEffect` values are in
10813
+ * `FILLABLE_STEP_VOCABULARIES` (guard `check-label-link-vocabulary`).
10814
+ */
10815
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional(),
10816
+ /**
10749
10817
  * The model's CLIP feature-space contract (text encoder, dimension, vector
10750
10818
  * index, token window, search floor) — see `clip-model.ts`. Present ONLY on a
10751
10819
  * CLIP image encoder; its absence is what "not a CLIP model" means to the
@@ -14218,7 +14286,7 @@ DeviceType.Camera;
14218
14286
  * This matters here more than anywhere else in the repo, because every provider
14219
14287
  * that knows a device's host knows its PASSWORD too: `{ host, port, username,
14220
14288
  * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
14221
- * and `http://admin:hunter2@192.168.x.x/` is a URL a browser accepts. The
14289
+ * and `http://admin:hunter2@192.168.60.139/` is a URL a browser accepts. The
14222
14290
  * camera's own page will ask for its own login. That is correct, and pre-
14223
14291
  * filling it is the operator's business, not ours.
14224
14292
  *
@@ -17503,7 +17571,7 @@ method(object({
17503
17571
  timeoutMs: 6e5
17504
17572
  });
17505
17573
  method(object({
17506
- nodeId: string(),
17574
+ targetNodeId: string(),
17507
17575
  modelId: string(),
17508
17576
  format: _enum(MODEL_FORMATS),
17509
17577
  entry: ModelCatalogEntrySchema
@@ -18069,51 +18137,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
18069
18137
  kind: "mutation",
18070
18138
  auth: "admin"
18071
18139
  });
18072
- new Set([
18073
- {
18074
- id: "person",
18075
- name: "Person"
18076
- },
18077
- {
18078
- id: "vehicle",
18079
- name: "Vehicle"
18080
- },
18081
- {
18082
- id: "animal",
18083
- name: "Animal"
18084
- },
18085
- {
18086
- id: "package",
18087
- name: "Package"
18088
- }
18089
- ].map((l) => l.id));
18090
- var COCO_TO_MACRO = {
18091
- mapping: {
18092
- person: "person",
18093
- bicycle: "vehicle",
18094
- car: "vehicle",
18095
- motorcycle: "vehicle",
18096
- airplane: "vehicle",
18097
- bus: "vehicle",
18098
- train: "vehicle",
18099
- truck: "vehicle",
18100
- boat: "vehicle",
18101
- bird: "animal",
18102
- cat: "animal",
18103
- dog: "animal",
18104
- horse: "animal",
18105
- sheep: "animal",
18106
- cow: "animal",
18107
- elephant: "animal",
18108
- bear: "animal",
18109
- zebra: "animal",
18110
- giraffe: "animal",
18111
- suitcase: "package",
18112
- backpack: "package",
18113
- handbag: "package"
18114
- },
18115
- preserveOriginal: false
18116
- };
18117
18140
  var AUDIO_MACRO_LABELS = [
18118
18141
  {
18119
18142
  id: "speech",
@@ -18909,6 +18932,8 @@ var NcSystemEventKindSchema = _enum([
18909
18932
  "stream-offline",
18910
18933
  "node-online",
18911
18934
  "node-offline",
18935
+ "node-restarting",
18936
+ "node-updating",
18912
18937
  "node-inference-unavailable",
18913
18938
  "detection-blind",
18914
18939
  "addon-crash-loop",
@@ -20007,8 +20032,9 @@ var NcRuleSchema = NcRuleInputSchema.extend({
20007
20032
  disabledTargetIds: array(string()).default([])
20008
20033
  });
20009
20034
  /**
20010
- * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
20011
- * still-true state. `backups-exports` and `alarm` are not sections — a completed
20035
+ * The state-summary SECTIONS (D647 / D698 / D708): the D637 kind groups that
20036
+ * report a still-true state. `addons` is the crash-looped addons only and
20037
+ * `addon-updates` the addons that are behind (D708 split them). `backups-exports` and `alarm` are not sections — a completed
20012
20038
  * job is not an open state, and the alarm stays instant.
20013
20039
  */
20014
20040
  var NcStateSummarySectionIdSchema = _enum([
@@ -20020,6 +20046,7 @@ var NcStateSummarySectionIdSchema = _enum([
20020
20046
  "detection-blind",
20021
20047
  "nodes",
20022
20048
  "addons",
20049
+ "addon-updates",
20023
20050
  "updates"
20024
20051
  ]);
20025
20052
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
@@ -20500,7 +20527,19 @@ var NcAlarmModeDelaysListSchema = array(object({
20500
20527
  * When this mode may be armed. Absent / null = always allowed (same as a
20501
20528
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
20502
20529
  */
20503
- schedule: NcScheduleSchema.optional()
20530
+ schedule: NcScheduleSchema.optional(),
20531
+ /**
20532
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
20533
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
20534
+ * only one, and the matches ride the triggered notification's mosaic (D705).
20535
+ */
20536
+ notifyDuringEntryDelay: boolean().optional(),
20537
+ /**
20538
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
20539
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
20540
+ * is the one that starts the entry delay — the entry-delay notification already names it.
20541
+ */
20542
+ notifySensorRewatched: boolean().optional()
20504
20543
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20505
20544
  var NcAlarmModeDelaysEditListSchema = array(object({
20506
20545
  mode: AlarmArmModeSchema,
@@ -20509,7 +20548,11 @@ var NcAlarmModeDelaysEditListSchema = array(object({
20509
20548
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
20510
20549
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
20511
20550
  /** Set a schedule, or `null` to clear (mode always allowed again). */
20512
- schedule: NcScheduleSchema.nullable().optional()
20551
+ schedule: NcScheduleSchema.nullable().optional(),
20552
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
20553
+ notifyDuringEntryDelay: boolean().nullable().optional(),
20554
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
20555
+ notifySensorRewatched: boolean().nullable().optional()
20513
20556
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20514
20557
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20515
20558
  var NcAlarmNonBlockingEditSchema = object({
@@ -21470,39 +21513,19 @@ Object.freeze(Object.fromEntries([{
21470
21513
  label: "Face recognition model",
21471
21514
  defaultModelId: "auraface-r100",
21472
21515
  indexName: "the enrolled face gallery",
21473
- options: [
21474
- {
21475
- id: "auraface-r100",
21476
- label: "AuraFace R100"
21477
- },
21478
- {
21479
- id: "arcface-r100",
21480
- label: "ArcFace (ResNet34)"
21481
- },
21482
- {
21483
- id: "inception-resnet-v1",
21484
- label: "Inception ResNet V1"
21485
- }
21486
- ]
21516
+ options: [{
21517
+ id: "auraface-r100",
21518
+ label: "AuraFace R100"
21519
+ }]
21487
21520
  }, {
21488
21521
  stepId: "clip-embedding",
21489
21522
  label: "Semantic search model",
21490
- defaultModelId: "mobileclip-s1",
21523
+ defaultModelId: "siglip2-b16-224",
21491
21524
  indexName: "the object semantic-search index",
21492
- options: [
21493
- {
21494
- id: "mobileclip-s1",
21495
- label: "MobileCLIP S1"
21496
- },
21497
- {
21498
- id: "mobileclip-s2",
21499
- label: "MobileCLIP S2"
21500
- },
21501
- {
21502
- id: "siglip2-b16-224",
21503
- label: "SigLIP2 B/16 (Apache-2.0)"
21504
- }
21505
- ]
21525
+ options: [{
21526
+ id: "siglip2-b16-224",
21527
+ label: "SigLIP2 B/16 (Apache-2.0)"
21528
+ }]
21506
21529
  }].map((s) => [s.stepId, s.defaultModelId])));
21507
21530
  var CLUSTER_STEP_SETTING_FIELDS = [{
21508
21531
  stepId: "face-embedding",
@@ -23557,7 +23580,8 @@ var PipelineModelOptionSchema = object({
23557
23580
  group: ModelVariantGroupSchema.optional(),
23558
23581
  legacy: boolean().optional(),
23559
23582
  provider: ModelProviderIdSchema.optional(),
23560
- defaultConfidence: number().optional()
23583
+ defaultConfidence: number().optional(),
23584
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional()
23561
23585
  });
23562
23586
  var ConfigFieldBridge = custom();
23563
23587
  var PipelineAddonSchemaSchema = object({
@@ -24390,6 +24414,22 @@ var MotionSourceEnum = _enum([
24390
24414
  "device-activity"
24391
24415
  ]);
24392
24416
  /**
24417
+ * What opened an on-motion detection SESSION: the motion source that passed the
24418
+ * camera's `motionSources` gate, and what raised it (the camera itself, or a
24419
+ * sensor the operator attached — D672). `sensorName` is the attached sensor's
24420
+ * device name as the orchestrator's camera-sensors resolution last read it;
24421
+ * absent when unknown or when the camera itself triggered.
24422
+ *
24423
+ * Carried on the session attach ONLY so the runner's `DetectionPhaseTransition`
24424
+ * can say what started the session (D672 rule 11, amended 2026-10-06). The
24425
+ * runner never decides anything on it.
24426
+ */
24427
+ var SessionTriggerSchema = object({
24428
+ source: MotionSourceEnum,
24429
+ triggeredBy: MotionTriggeredBySchema,
24430
+ sensorName: string().optional()
24431
+ });
24432
+ /**
24393
24433
  * List of motion sources active on a camera. Empty array is valid:
24394
24434
  * "no source" — happens for battery cams without firmware motion when
24395
24435
  * the operator hasn't opted into the SW analyzer (would hold the
@@ -24641,6 +24681,14 @@ var RunnerCameraConfigSchema = object({
24641
24681
  */
24642
24682
  motionSources: MotionSourcesSchema.default(["analyzer"]),
24643
24683
  /**
24684
+ * What opened this attach, on a SESSION attach only (see
24685
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
24686
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
24687
+ * behind it, and every attach from an orchestrator older than the field — the
24688
+ * runner then names no source on the phase event.
24689
+ */
24690
+ sessionTrigger: SessionTriggerSchema.optional(),
24691
+ /**
24644
24692
  * WHICH root detector runs for this camera. The detection counterpart of
24645
24693
  * {@link motionSources}, same shape and same discipline: a per-camera list
24646
24694
  * of sources the system understands, vendor-agnostic, owned here.
@@ -32542,7 +32590,7 @@ var LocalInterfaceSchema = object({
32542
32590
  name: string(),
32543
32591
  family: _enum(["IPv4", "IPv6"]),
32544
32592
  address: string(),
32545
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
32593
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
32546
32594
  cidr: string(),
32547
32595
  /** OS netmask. Stable across calls so the SDK can hash on it. */
32548
32596
  netmask: string(),
@@ -32596,7 +32644,8 @@ var ConnectionEndpointSchema = object({
32596
32644
  "lan-ipv4",
32597
32645
  "lan-ipv6",
32598
32646
  "public",
32599
- "loopback"
32647
+ "loopback",
32648
+ "app-link"
32600
32649
  ]),
32601
32650
  /**
32602
32651
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -32665,15 +32714,27 @@ var GetConnectionEndpointsResultSchema = object({
32665
32714
  localPort: AdvertisedLocalPortSchema
32666
32715
  });
32667
32716
  /**
32668
- * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
32669
- * AUTO (resolved from the candidate ranking at send time); `resolved` reports
32670
- * what AUTO currently picks, so the UI can show the effective value either way.
32717
+ * The address flagged for notification links. `baseUrl: null` = AUTO, which
32718
+ * since D709 is the CamStack app link; `resolved` reports the effective value,
32719
+ * so the UI can show it either way.
32671
32720
  */
32672
32721
  var NotificationEndpointSchema = object({
32673
32722
  /** The operator's explicit choice, or null for AUTO. */
32674
32723
  baseUrl: string().nullable(),
32675
- /** What the ranking currently resolves to (null when nothing is reachable). */
32676
- resolved: string().nullable()
32724
+ /**
32725
+ * The effective flagged address: the explicit choice, else (AUTO) the
32726
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
32727
+ * answered the public tunnel / mesh here, or null.
32728
+ */
32729
+ resolved: string().nullable(),
32730
+ /**
32731
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
32732
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
32733
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
32734
+ * hub advertises neither; the app link is then minted without `hub=`.
32735
+ * Absent on a pre-D709 hub.
32736
+ */
32737
+ appLinkHub: string().nullable()
32677
32738
  });
32678
32739
  /**
32679
32740
  * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
@@ -32727,7 +32788,11 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
32727
32788
  * Pass `'https'` when the caller is itself loaded over HTTPS
32728
32789
  * to avoid mixed-content blocks in the browser. The public
32729
32790
  * tunnel always emits `https://` regardless. */
32730
- scheme: _enum(["http", "https"]).optional()
32791
+ scheme: _enum(["http", "https"]).optional(),
32792
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
32793
+ * an address notification links can be flagged with, not a hub
32794
+ * route. Default `false`: racers and rankers never see it. */
32795
+ includeAppLink: boolean().optional()
32731
32796
  }), 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, {
32732
32797
  kind: "mutation",
32733
32798
  auth: "admin"
@@ -33056,7 +33121,7 @@ var MeshPeerSchema = object({
33056
33121
  tags: array(string()).readonly(),
33057
33122
  /** True when the peer advertises itself as an exit-node candidate. */
33058
33123
  exitNodeOption: boolean(),
33059
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
33124
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
33060
33125
  advertisedRoutes: array(string()).readonly(),
33061
33126
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
33062
33127
  userLogin: string().nullable(),
@@ -33186,7 +33251,8 @@ var MotionStatusSchema = object({
33186
33251
  * writes this slice (not even a boot seed), and neither does the runner's
33187
33252
  * phase machine: its phase is "a session is attached" for every on-motion
33188
33253
  * session, not motion, and it now has its own `DetectionPhaseTransition`.
33189
- * A provider's `isDetected` answers from the hub's slice.
33254
+ * A provider's `isDetected` and `getStatus` answer from the hub's slice
33255
+ * (`hubMotionNativeProvider`).
33190
33256
  * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
33191
33257
  * imports) — the provider pushes both edges itself, from the upstream
33192
33258
  * system's own state change. That is the sensor's own fact.
@@ -34384,7 +34450,13 @@ var OsdStatusSchema = object({ overlays: array(object({
34384
34450
  */
34385
34451
  availablePositions: array(OsdPositionEnum).readonly(),
34386
34452
  /** Firmware does not allow editing the `text` field (e.g. timestamp). */
34387
- readOnly: boolean().optional()
34453
+ readOnly: boolean().optional(),
34454
+ /**
34455
+ * Firmware text ceiling for this overlay. Absent = no known limit.
34456
+ * The OSD manager truncates here on every write — never stores its own
34457
+ * copy on the binding.
34458
+ */
34459
+ maxCharacters: number().int().positive().optional()
34388
34460
  })).readonly() });
34389
34461
  var OsdOverlayPatchSchema = object({
34390
34462
  enabled: boolean().optional(),
@@ -34481,7 +34553,9 @@ var OsdTemplateBindingSchema = object({
34481
34553
  valuePath: string().min(1).max(64),
34482
34554
  /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
34483
34555
  displayUnit: string().max(16).optional(),
34484
- maxDecimals: number().int().min(0).max(4).default(1)
34556
+ maxDecimals: number().int().min(0).max(4).default(1),
34557
+ /** Raw value → display text (same shape as slot binding). Empty string hides. */
34558
+ valueMap: record(string(), string()).optional()
34485
34559
  });
34486
34560
  /**
34487
34561
  * A named template: bindings (alias → leaf field) + a parser that
@@ -34531,18 +34605,11 @@ var OsdSlotBindingSchema = object({
34531
34605
  source: OsdSourceSchema,
34532
34606
  /** `${value}` and `${unit}` are substituted; every occurrence. */
34533
34607
  template: string().max(96).default("${value}"),
34534
- /** Truncate with an ellipsis past this length. Absent = no limit. */
34535
- maxCharacters: number().int().min(4).max(64).optional(),
34536
34608
  /**
34537
34609
  * Decimal places for a numeric value. `0` yields an integer — the
34538
34610
  * documented workaround for firmwares that reject `.` in overlay text.
34539
34611
  */
34540
34612
  maxDecimals: number().int().min(0).max(4).default(1),
34541
- /**
34542
- * Appended via `${unit}` when no unit conversion produces a label.
34543
- * Prefer `source.displayUnit` for converted device-state values.
34544
- */
34545
- unitLabel: string().max(8).optional(),
34546
34613
  /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
34547
34614
  valueMap: record(string(), string()).optional(),
34548
34615
  /** Time windows in which the slot is shown. Absent = always. */
@@ -34551,9 +34618,7 @@ var OsdSlotBindingSchema = object({
34551
34618
  * Display gate, in the notification centre's condition vocabulary.
34552
34619
  * Only the keys reported by `getConditionSupport` are accepted.
34553
34620
  */
34554
- conditions: NcConditionsSchema.optional(),
34555
- /** Rendered when the gate is closed or the value unreadable. Empty = hide. */
34556
- fallbackText: string().max(64).default("")
34621
+ conditions: NcConditionsSchema.optional()
34557
34622
  });
34558
34623
  /** One camera slot, as the operator sees it: firmware truth + our binding. */
34559
34624
  var OsdSlotViewSchema = object({
@@ -53325,6 +53390,8 @@ new Map(Object.entries({
53325
53390
  "detection-blind": "device-events",
53326
53391
  "node-online": "system",
53327
53392
  "node-offline": "system",
53393
+ "node-restarting": "system",
53394
+ "node-updating": "system",
53328
53395
  "node-inference-unavailable": "system",
53329
53396
  "addon-crash-loop": "system",
53330
53397
  "addon-update-available": "system",
@@ -53402,18 +53469,21 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53402
53469
  kinds: [
53403
53470
  "node-offline",
53404
53471
  "node-online",
53472
+ "node-restarting",
53405
53473
  "node-inference-unavailable"
53406
53474
  ]
53407
53475
  },
53408
53476
  {
53409
53477
  id: "addons",
53410
53478
  family: "system",
53411
- label: "Addons",
53412
- kinds: [
53413
- "addon-crash-loop",
53414
- "addon-update-available",
53415
- "addon-updated"
53416
- ]
53479
+ label: "Addons stopped",
53480
+ kinds: ["addon-crash-loop"]
53481
+ },
53482
+ {
53483
+ id: "addon-updates",
53484
+ family: "system",
53485
+ label: "Addon updates",
53486
+ kinds: ["addon-update-available", "addon-updated"]
53417
53487
  },
53418
53488
  {
53419
53489
  id: "updates",
@@ -53421,6 +53491,7 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
53421
53491
  label: "Server & container updates",
53422
53492
  kinds: [
53423
53493
  "server-update-available",
53494
+ "node-updating",
53424
53495
  "server-updated",
53425
53496
  "wrapper-update-available"
53426
53497
  ]
@@ -53568,18 +53639,21 @@ var STATE_SUMMARY_SECTION_VARS = [
53568
53639
  kinds: [
53569
53640
  "node-offline",
53570
53641
  "node-online",
53642
+ "node-restarting",
53571
53643
  "node-inference-unavailable"
53572
53644
  ]
53573
53645
  },
53574
53646
  {
53575
53647
  name: "summaryAddons",
53576
- label: "Summary: addons",
53577
- example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
53578
- kinds: [
53579
- "addon-crash-loop",
53580
- "addon-update-available",
53581
- "addon-updated"
53582
- ]
53648
+ label: "Summary: addons stopped",
53649
+ example: "hub — recorder stopped after repeated crashes",
53650
+ kinds: ["addon-crash-loop"]
53651
+ },
53652
+ {
53653
+ name: "summaryAddonUpdates",
53654
+ label: "Summary: addon updates",
53655
+ example: "hub:\n• @camstack/addon-recorder 1.2.3 → 1.2.4",
53656
+ kinds: ["addon-update-available", "addon-updated"]
53583
53657
  },
53584
53658
  {
53585
53659
  name: "summaryUpdates",
@@ -53587,6 +53661,7 @@ var STATE_SUMMARY_SECTION_VARS = [
53587
53661
  example: "server 1.2.377 → 1.2.378",
53588
53662
  kinds: [
53589
53663
  "server-update-available",
53664
+ "node-updating",
53590
53665
  "server-updated",
53591
53666
  "wrapper-update-available"
53592
53667
  ]
@@ -53663,6 +53738,7 @@ var STATE_SUMMARY_VARS = [
53663
53738
  [...RULE_BODY_ONLY],
53664
53739
  [...RULE_TEXT],
53665
53740
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
53741
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
53666
53742
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
53667
53743
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
53668
53744
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -53703,6 +53779,8 @@ var STATE_SUMMARY_VARS = [
53703
53779
  [...RULE_TEXT],
53704
53780
  [...RULE_TEXT],
53705
53781
  [...RULE_TEXT],
53782
+ [...RULE_TEXT],
53783
+ [...RULE_TEXT],
53706
53784
  ...STATE_SUMMARY_VARS
53707
53785
  ];
53708
53786
  new Set([
@@ -54039,10 +54117,9 @@ TimelapseRuleInputSchema.extend({
54039
54117
  createdAt: number(),
54040
54118
  updatedAt: number()
54041
54119
  });
54042
- async function readHubMotionDetected(deviceState, deviceId) {
54043
- let slice;
54120
+ async function readSlice(deviceState, deviceId) {
54044
54121
  try {
54045
- slice = await deviceState.getCapSlice.query({
54122
+ return await deviceState.getCapSlice.query({
54046
54123
  deviceId,
54047
54124
  capName: "motion"
54048
54125
  });
@@ -54050,10 +54127,44 @@ async function readHubMotionDetected(deviceState, deviceId) {
54050
54127
  const reason = err instanceof Error ? err.message : String(err);
54051
54128
  throw new Error(`motion state of device ${deviceId} is unreadable: ${reason}`, { cause: err });
54052
54129
  }
54053
- const detected = slice?.["detected"];
54130
+ }
54131
+ /**
54132
+ * The hub's `motion` slice as a `MotionStatus`, or `null` when nobody has
54133
+ * published it since the hub started. Throws when the hub does not answer or
54134
+ * the slice is malformed — never a made-up "no motion".
54135
+ */
54136
+ async function readHubMotionStatus(deviceState, deviceId) {
54137
+ const slice = await readSlice(deviceState, deviceId);
54138
+ if (slice === null) return null;
54139
+ const parsed = MotionStatusSchema.safeParse(slice);
54140
+ if (!parsed.success) throw new Error(`motion state of device ${deviceId} is malformed on the hub: ${parsed.error.message}`);
54141
+ return parsed.data;
54142
+ }
54143
+ async function readHubMotionDetected(deviceState, deviceId) {
54144
+ const detected = (await readSlice(deviceState, deviceId))?.["detected"];
54054
54145
  if (typeof detected !== "boolean") throw new Error(`motion state of device ${deviceId} is not published yet (unknown)`);
54055
54146
  return detected;
54056
54147
  }
54148
+ /**
54149
+ * The native `motion` provider of a camera whose slice the orchestrator writes
54150
+ * (D696): `isDetected` AND `getStatus`, both from the hub. `owner` names the
54151
+ * provider class in the deviceId-mismatch error.
54152
+ */
54153
+ function hubMotionNativeProvider(deviceState, deviceId, owner) {
54154
+ const ownDevice = (asked) => {
54155
+ if (asked !== deviceId) throw new Error(`${owner}: motion deviceId mismatch, expected ${deviceId}, got ${asked}`);
54156
+ };
54157
+ return {
54158
+ isDetected: async ({ deviceId: asked }) => {
54159
+ ownDevice(asked);
54160
+ return readHubMotionDetected(deviceState, deviceId);
54161
+ },
54162
+ getStatus: async ({ deviceId: asked }) => {
54163
+ ownDevice(asked);
54164
+ return readHubMotionStatus(deviceState, deviceId);
54165
+ }
54166
+ };
54167
+ }
54057
54168
  MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
54058
54169
  function providerMotionEvent(source, data) {
54059
54170
  return createEvent(EventCategory.ProviderMotion, source, data);
@@ -56427,14 +56538,12 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
56427
56538
  }
56428
56539
  /** Register the native `motion` cap. Its mere presence (a `kind:'native'`
56429
56540
  * `motion` binding) is what the orchestrator reads to default a
56430
- * freshly-adopted camera to `motionSources:['onboard']`. `isDetected`
56431
- * answers from the HUB's slice; an unreadable hub, or a slice nobody has
56432
- * published yet, fails the call — never `false` for unknown (D49, D393). */
56541
+ * freshly-adopted camera to `motionSources:['onboard']`. `isDetected` and
56542
+ * `getStatus` answer from the HUB's slice (`hubMotionNativeProvider`); an
56543
+ * unreadable hub fails both — never `false` for unknown (D49, D393). */
56433
56544
  registerMotionCap() {
56434
- this.ctx.registerNativeCap(motionCapability, { isDetected: async ({ deviceId }) => {
56435
- if (deviceId !== this.id) throw new Error(`AmcrestCamera: motion deviceId mismatch, expected ${this.id}, got ${deviceId}`);
56436
- return readHubMotionDetected(this.ctx.api.deviceState, this.id);
56437
- } });
56545
+ const provider = hubMotionNativeProvider(this.ctx.api.deviceState, this.id, "AmcrestCamera");
56546
+ this.ctx.registerNativeCap(motionCapability, provider);
56438
56547
  this.ctx.logger.info("amcrest: motion cap registered", { tags: { deviceId: this.id } });
56439
56548
  }
56440
56549
  /** Reusable event-source identity for every emit on this device. */
@@ -56645,7 +56754,7 @@ var AmcrestCamera = class AmcrestCamera extends BaseDevice {
56645
56754
  key: "host",
56646
56755
  label: "Camera IP",
56647
56756
  required: true,
56648
- placeholder: "192.168.x.x"
56757
+ placeholder: "192.168.10.100"
56649
56758
  },
56650
56759
  {
56651
56760
  type: "number",
@@ -70867,13 +70976,13 @@ function enumerateHosts(cidr) {
70867
70976
  for (let d = 1; d <= 254; d += 1) out.push(`${a}.${b}.${c}.${d}`);
70868
70977
  return out;
70869
70978
  }
70870
- /** Best-effort local /24 from the first non-internal IPv4 interface (e.g. `192.168.x.x/24`). */
70979
+ /** Best-effort local /24 from the first non-internal IPv4 interface (e.g. `192.168.10.0/24`). */
70871
70980
  function detectLocalCidr() {
70872
70981
  for (const addrs of Object.values(networkInterfaces())) for (const addr of addrs ?? []) if (addr.family === "IPv4" && !addr.internal) {
70873
70982
  const [a, b, c] = addr.address.split(".");
70874
70983
  return `${a}.${b}.${c}.0/24`;
70875
70984
  }
70876
- return "192.168.x.x/24";
70985
+ return "192.168.10.0/24";
70877
70986
  }
70878
70987
  /** Flatten a host into a flat row-key slug — must match `generateStableId`'s host fallback so a
70879
70988
  * re-scan of a host-added camera is detected as already onboarded. */
@@ -70942,7 +71051,7 @@ function buildCreationFormSchema() {
70942
71051
  key: "host",
70943
71052
  label: "Camera IP",
70944
71053
  required: true,
70945
- placeholder: "192.168.x.x",
71054
+ placeholder: "192.168.10.100",
70946
71055
  inputType: "text"
70947
71056
  },
70948
71057
  {
@@ -70993,14 +71102,14 @@ function buildDiscoveryParamsFormSchema() {
70993
71102
  }, {
70994
71103
  id: "scan",
70995
71104
  title: "Scan options",
70996
- description: "The credentialed CGI 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.",
71105
+ description: "The credentialed CGI 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.",
70997
71106
  columns: 1,
70998
71107
  fields: [{
70999
71108
  type: "text",
71000
71109
  key: "networkCidr",
71001
71110
  label: "Network (CIDR) — required if cameras are on a different subnet than the hub",
71002
71111
  required: false,
71003
- placeholder: "192.168.x.x/24"
71112
+ placeholder: "192.168.60.0/24"
71004
71113
  }, {
71005
71114
  type: "number",
71006
71115
  key: "timeoutMs",