@camstack/addon-provider-reolink 1.2.174 → 1.2.176

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 +232 -81
  2. package/dist/addon.mjs +232 -81
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -11101,6 +11101,57 @@ var ClipSearchStrictnessSchema = _enum([
11101
11101
  "balanced",
11102
11102
  "strict"
11103
11103
  ]);
11104
+ new Set([
11105
+ {
11106
+ id: "person",
11107
+ name: "Person"
11108
+ },
11109
+ {
11110
+ id: "vehicle",
11111
+ name: "Vehicle"
11112
+ },
11113
+ {
11114
+ id: "animal",
11115
+ name: "Animal"
11116
+ },
11117
+ {
11118
+ id: "package",
11119
+ name: "Package"
11120
+ }
11121
+ ].map((l) => l.id));
11122
+ var COCO_TO_MACRO = {
11123
+ mapping: {
11124
+ person: "person",
11125
+ bicycle: "vehicle",
11126
+ car: "vehicle",
11127
+ motorcycle: "vehicle",
11128
+ airplane: "vehicle",
11129
+ bus: "vehicle",
11130
+ train: "vehicle",
11131
+ truck: "vehicle",
11132
+ boat: "vehicle",
11133
+ bird: "animal",
11134
+ cat: "animal",
11135
+ dog: "animal",
11136
+ horse: "animal",
11137
+ sheep: "animal",
11138
+ cow: "animal",
11139
+ elephant: "animal",
11140
+ bear: "animal",
11141
+ zebra: "animal",
11142
+ giraffe: "animal",
11143
+ suitcase: "package",
11144
+ backpack: "package",
11145
+ handbag: "package"
11146
+ },
11147
+ preserveOriginal: false
11148
+ };
11149
+ /** Steps a root detector may declare it fills. */
11150
+ var FILLABLE_STEP_IDS = [
11151
+ "animal-classifier",
11152
+ "vehicle-classifier",
11153
+ "package-detection"
11154
+ ];
11104
11155
  var MODEL_FORMATS = [
11105
11156
  "onnx",
11106
11157
  "coreml",
@@ -11233,6 +11284,12 @@ var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { wei
11233
11284
  /** True when the weights or their data forbid commercial use. */
11234
11285
  nonCommercial: boolean().optional()
11235
11286
  }));
11287
+ /** One `fillsClassifierEffects` entry — see `ModelCatalogEntry.fillsClassifierEffects`. */
11288
+ var FillsClassifierEffectSchema = object({
11289
+ stepId: _enum(FILLABLE_STEP_IDS),
11290
+ labelToEffect: record(string(), string()),
11291
+ uncovered: array(string()).readonly()
11292
+ });
11236
11293
  var ModelCatalogEntrySchema = object({
11237
11294
  id: string(),
11238
11295
  name: string(),
@@ -11359,6 +11416,16 @@ var ModelCatalogEntrySchema = object({
11359
11416
  */
11360
11417
  classMap: DetectionCatalogClassMapSchema.optional(),
11361
11418
  /**
11419
+ * The pipeline steps this ROOT detector already does the work of (D715):
11420
+ * its labels name the effect the step exists to produce (`deer` for
11421
+ * `animal-classifier`, `package` for `package-detection`). Derived from the
11422
+ * labelmap by `deriveClassifierFills`, never hand-written; a COCO model
11423
+ * declares none. Whether a declaration is honoured is the step's own
11424
+ * `labelLink` policy (`resolveLabelLink`). `labelToEffect` values are in
11425
+ * `FILLABLE_STEP_VOCABULARIES` (guard `check-label-link-vocabulary`).
11426
+ */
11427
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional(),
11428
+ /**
11362
11429
  * The model's CLIP feature-space contract (text encoder, dimension, vector
11363
11430
  * index, token window, search floor) — see `clip-model.ts`. Present ONLY on a
11364
11431
  * CLIP image encoder; its absence is what "not a CLIP model" means to the
@@ -14831,7 +14898,7 @@ DeviceType.Camera;
14831
14898
  * This matters here more than anywhere else in the repo, because every provider
14832
14899
  * that knows a device's host knows its PASSWORD too: `{ host, port, username,
14833
14900
  * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
14834
- * and `http://admin:hunter2@192.168.x.x/` is a URL a browser accepts. The
14901
+ * and `http://admin:hunter2@192.168.60.139/` is a URL a browser accepts. The
14835
14902
  * camera's own page will ask for its own login. That is correct, and pre-
14836
14903
  * filling it is the operator's business, not ours.
14837
14904
  *
@@ -18722,51 +18789,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
18722
18789
  kind: "mutation",
18723
18790
  auth: "admin"
18724
18791
  });
18725
- new Set([
18726
- {
18727
- id: "person",
18728
- name: "Person"
18729
- },
18730
- {
18731
- id: "vehicle",
18732
- name: "Vehicle"
18733
- },
18734
- {
18735
- id: "animal",
18736
- name: "Animal"
18737
- },
18738
- {
18739
- id: "package",
18740
- name: "Package"
18741
- }
18742
- ].map((l) => l.id));
18743
- var COCO_TO_MACRO = {
18744
- mapping: {
18745
- person: "person",
18746
- bicycle: "vehicle",
18747
- car: "vehicle",
18748
- motorcycle: "vehicle",
18749
- airplane: "vehicle",
18750
- bus: "vehicle",
18751
- train: "vehicle",
18752
- truck: "vehicle",
18753
- boat: "vehicle",
18754
- bird: "animal",
18755
- cat: "animal",
18756
- dog: "animal",
18757
- horse: "animal",
18758
- sheep: "animal",
18759
- cow: "animal",
18760
- elephant: "animal",
18761
- bear: "animal",
18762
- zebra: "animal",
18763
- giraffe: "animal",
18764
- suitcase: "package",
18765
- backpack: "package",
18766
- handbag: "package"
18767
- },
18768
- preserveOriginal: false
18769
- };
18770
18792
  var AUDIO_MACRO_LABELS = [
18771
18793
  {
18772
18794
  id: "speech",
@@ -20660,8 +20682,9 @@ var NcRuleSchema = NcRuleInputSchema.extend({
20660
20682
  disabledTargetIds: array(string()).default([])
20661
20683
  });
20662
20684
  /**
20663
- * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
20664
- * still-true state. `backups-exports` and `alarm` are not sections — a completed
20685
+ * The state-summary SECTIONS (D647 / D698 / D708): the D637 kind groups that
20686
+ * report a still-true state. `addons` is the crash-looped addons only and
20687
+ * `addon-updates` the addons that are behind (D708 split them). `backups-exports` and `alarm` are not sections — a completed
20665
20688
  * job is not an open state, and the alarm stays instant.
20666
20689
  */
20667
20690
  var NcStateSummarySectionIdSchema = _enum([
@@ -20673,6 +20696,7 @@ var NcStateSummarySectionIdSchema = _enum([
20673
20696
  "detection-blind",
20674
20697
  "nodes",
20675
20698
  "addons",
20699
+ "addon-updates",
20676
20700
  "updates"
20677
20701
  ]);
20678
20702
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
@@ -21153,7 +21177,19 @@ var NcAlarmModeDelaysListSchema = array(object({
21153
21177
  * When this mode may be armed. Absent / null = always allowed (same as a
21154
21178
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
21155
21179
  */
21156
- schedule: NcScheduleSchema.optional()
21180
+ schedule: NcScheduleSchema.optional(),
21181
+ /**
21182
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
21183
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
21184
+ * only one, and the matches ride the triggered notification's mosaic (D705).
21185
+ */
21186
+ notifyDuringEntryDelay: boolean().optional(),
21187
+ /**
21188
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
21189
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
21190
+ * is the one that starts the entry delay — the entry-delay notification already names it.
21191
+ */
21192
+ notifySensorRewatched: boolean().optional()
21157
21193
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
21158
21194
  var NcAlarmModeDelaysEditListSchema = array(object({
21159
21195
  mode: AlarmArmModeSchema,
@@ -21162,7 +21198,11 @@ var NcAlarmModeDelaysEditListSchema = array(object({
21162
21198
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
21163
21199
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
21164
21200
  /** Set a schedule, or `null` to clear (mode always allowed again). */
21165
- schedule: NcScheduleSchema.nullable().optional()
21201
+ schedule: NcScheduleSchema.nullable().optional(),
21202
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
21203
+ notifyDuringEntryDelay: boolean().nullable().optional(),
21204
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
21205
+ notifySensorRewatched: boolean().nullable().optional()
21166
21206
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
21167
21207
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
21168
21208
  var NcAlarmNonBlockingEditSchema = object({
@@ -24190,7 +24230,8 @@ var PipelineModelOptionSchema = object({
24190
24230
  group: ModelVariantGroupSchema.optional(),
24191
24231
  legacy: boolean().optional(),
24192
24232
  provider: ModelProviderIdSchema.optional(),
24193
- defaultConfidence: number().optional()
24233
+ defaultConfidence: number().optional(),
24234
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional()
24194
24235
  });
24195
24236
  var ConfigFieldBridge = custom();
24196
24237
  var PipelineAddonSchemaSchema = object({
@@ -25023,6 +25064,22 @@ var MotionSourceEnum = _enum([
25023
25064
  "device-activity"
25024
25065
  ]);
25025
25066
  /**
25067
+ * What opened an on-motion detection SESSION: the motion source that passed the
25068
+ * camera's `motionSources` gate, and what raised it (the camera itself, or a
25069
+ * sensor the operator attached — D672). `sensorName` is the attached sensor's
25070
+ * device name as the orchestrator's camera-sensors resolution last read it;
25071
+ * absent when unknown or when the camera itself triggered.
25072
+ *
25073
+ * Carried on the session attach ONLY so the runner's `DetectionPhaseTransition`
25074
+ * can say what started the session (D672 rule 11, amended 2026-10-06). The
25075
+ * runner never decides anything on it.
25076
+ */
25077
+ var SessionTriggerSchema = object({
25078
+ source: MotionSourceEnum,
25079
+ triggeredBy: MotionTriggeredBySchema,
25080
+ sensorName: string().optional()
25081
+ });
25082
+ /**
25026
25083
  * List of motion sources active on a camera. Empty array is valid:
25027
25084
  * "no source" — happens for battery cams without firmware motion when
25028
25085
  * the operator hasn't opted into the SW analyzer (would hold the
@@ -25274,6 +25331,14 @@ var RunnerCameraConfigSchema = object({
25274
25331
  */
25275
25332
  motionSources: MotionSourcesSchema.default(["analyzer"]),
25276
25333
  /**
25334
+ * What opened this attach, on a SESSION attach only (see
25335
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
25336
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
25337
+ * behind it, and every attach from an orchestrator older than the field — the
25338
+ * runner then names no source on the phase event.
25339
+ */
25340
+ sessionTrigger: SessionTriggerSchema.optional(),
25341
+ /**
25277
25342
  * WHICH root detector runs for this camera. The detection counterpart of
25278
25343
  * {@link motionSources}, same shape and same discipline: a per-camera list
25279
25344
  * of sources the system understands, vendor-agnostic, owned here.
@@ -33184,7 +33249,7 @@ var LocalInterfaceSchema = object({
33184
33249
  name: string(),
33185
33250
  family: _enum(["IPv4", "IPv6"]),
33186
33251
  address: string(),
33187
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
33252
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
33188
33253
  cidr: string(),
33189
33254
  /** OS netmask. Stable across calls so the SDK can hash on it. */
33190
33255
  netmask: string(),
@@ -33238,7 +33303,8 @@ var ConnectionEndpointSchema = object({
33238
33303
  "lan-ipv4",
33239
33304
  "lan-ipv6",
33240
33305
  "public",
33241
- "loopback"
33306
+ "loopback",
33307
+ "app-link"
33242
33308
  ]),
33243
33309
  /**
33244
33310
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -33307,15 +33373,27 @@ var GetConnectionEndpointsResultSchema = object({
33307
33373
  localPort: AdvertisedLocalPortSchema
33308
33374
  });
33309
33375
  /**
33310
- * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
33311
- * AUTO (resolved from the candidate ranking at send time); `resolved` reports
33312
- * what AUTO currently picks, so the UI can show the effective value either way.
33376
+ * The address flagged for notification links. `baseUrl: null` = AUTO, which
33377
+ * since D709 is the CamStack app link; `resolved` reports the effective value,
33378
+ * so the UI can show it either way.
33313
33379
  */
33314
33380
  var NotificationEndpointSchema = object({
33315
33381
  /** The operator's explicit choice, or null for AUTO. */
33316
33382
  baseUrl: string().nullable(),
33317
- /** What the ranking currently resolves to (null when nothing is reachable). */
33318
- resolved: string().nullable()
33383
+ /**
33384
+ * The effective flagged address: the explicit choice, else (AUTO) the
33385
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
33386
+ * answered the public tunnel / mesh here, or null.
33387
+ */
33388
+ resolved: string().nullable(),
33389
+ /**
33390
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
33391
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
33392
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
33393
+ * hub advertises neither; the app link is then minted without `hub=`.
33394
+ * Absent on a pre-D709 hub.
33395
+ */
33396
+ appLinkHub: string().nullable()
33319
33397
  });
33320
33398
  /**
33321
33399
  * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
@@ -33369,7 +33447,11 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
33369
33447
  * Pass `'https'` when the caller is itself loaded over HTTPS
33370
33448
  * to avoid mixed-content blocks in the browser. The public
33371
33449
  * tunnel always emits `https://` regardless. */
33372
- scheme: _enum(["http", "https"]).optional()
33450
+ scheme: _enum(["http", "https"]).optional(),
33451
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
33452
+ * an address notification links can be flagged with, not a hub
33453
+ * route. Default `false`: racers and rankers never see it. */
33454
+ includeAppLink: boolean().optional()
33373
33455
  }), 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, {
33374
33456
  kind: "mutation",
33375
33457
  auth: "admin"
@@ -33698,7 +33780,7 @@ var MeshPeerSchema = object({
33698
33780
  tags: array(string()).readonly(),
33699
33781
  /** True when the peer advertises itself as an exit-node candidate. */
33700
33782
  exitNodeOption: boolean(),
33701
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
33783
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
33702
33784
  advertisedRoutes: array(string()).readonly(),
33703
33785
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
33704
33786
  userLogin: string().nullable(),
@@ -54129,12 +54211,14 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
54129
54211
  {
54130
54212
  id: "addons",
54131
54213
  family: "system",
54132
- label: "Addons",
54133
- kinds: [
54134
- "addon-crash-loop",
54135
- "addon-update-available",
54136
- "addon-updated"
54137
- ]
54214
+ label: "Addons stopped",
54215
+ kinds: ["addon-crash-loop"]
54216
+ },
54217
+ {
54218
+ id: "addon-updates",
54219
+ family: "system",
54220
+ label: "Addon updates",
54221
+ kinds: ["addon-update-available", "addon-updated"]
54138
54222
  },
54139
54223
  {
54140
54224
  id: "updates",
@@ -54294,13 +54378,15 @@ var STATE_SUMMARY_SECTION_VARS = [
54294
54378
  },
54295
54379
  {
54296
54380
  name: "summaryAddons",
54297
- label: "Summary: addons",
54298
- example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
54299
- kinds: [
54300
- "addon-crash-loop",
54301
- "addon-update-available",
54302
- "addon-updated"
54303
- ]
54381
+ label: "Summary: addons stopped",
54382
+ example: "hub — recorder stopped after repeated crashes",
54383
+ kinds: ["addon-crash-loop"]
54384
+ },
54385
+ {
54386
+ name: "summaryAddonUpdates",
54387
+ label: "Summary: addon updates",
54388
+ example: "hub:\n• @camstack/addon-recorder 1.2.3 → 1.2.4",
54389
+ kinds: ["addon-update-available", "addon-updated"]
54304
54390
  },
54305
54391
  {
54306
54392
  name: "summaryUpdates",
@@ -54384,6 +54470,7 @@ var STATE_SUMMARY_VARS = [
54384
54470
  [...RULE_BODY_ONLY],
54385
54471
  [...RULE_TEXT],
54386
54472
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54473
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
54387
54474
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54388
54475
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
54389
54476
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -166246,6 +166333,37 @@ function buildSyntheticRfc4571Sdp(input) {
166246
166333
  function buildLazyRfc4571Url(camStreamId) {
166247
166334
  return `lazy:rfc4571:${encodeURIComponent(camStreamId)}`;
166248
166335
  }
166336
+ function createMotionSensorTracker(now = Date.now) {
166337
+ const latest = /* @__PURE__ */ new Map();
166338
+ return {
166339
+ observe(event) {
166340
+ if (event.type !== "motion") return;
166341
+ const sensor = event.motion.source;
166342
+ if (sensor === void 0) return;
166343
+ latest.set(event.channel, {
166344
+ sensor,
166345
+ at: now()
166346
+ });
166347
+ },
166348
+ take(channel) {
166349
+ const entry = latest.get(channel);
166350
+ if (!entry) return null;
166351
+ latest.delete(channel);
166352
+ return now() - entry.at <= 2e3 ? entry.sensor : null;
166353
+ }
166354
+ };
166355
+ }
166356
+ var isMotionSensor = (value) => value === "md" || value === "pir" || value === "unknown";
166357
+ /**
166358
+ * The sensor a simple event states itself, for a library version that forwards
166359
+ * `motion.source` on the simple event; `null` for the versions that do not.
166360
+ */
166361
+ function motionSensorOnSimpleEvent(event) {
166362
+ if (!("motion" in event)) return null;
166363
+ const motion = event.motion;
166364
+ if (typeof motion !== "object" || motion === null || !("source" in motion)) return null;
166365
+ return isMotionSensor(motion.source) ? motion.source : null;
166366
+ }
166249
166367
  //#endregion
166250
166368
  //#region src/reolink-camera.ts
166251
166369
  /**
@@ -166552,6 +166670,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
166552
166670
  this.handleSimpleEvent(event);
166553
166671
  };
166554
166672
  /**
166673
+ * MD vs PIR of this camera's own motion pushes. The lib's simple event drops
166674
+ * it, so the raw client `event` is observed — PREPENDED, so the sensor is
166675
+ * recorded before the API maps the same emit into the simple event
166676
+ * (`motion-sensor-source.ts`). Hub children get theirs from the hub.
166677
+ */
166678
+ motionSensors = createMotionSensorTracker();
166679
+ observeRawEventBound = (event) => {
166680
+ this.motionSensors.observe(event);
166681
+ };
166682
+ /**
166555
166683
  * Stable bound handler for `api.onObjectDetections` — created ONCE so
166556
166684
  * every `onObjectDetections` / `offObjectDetections` call uses the
166557
166685
  * SAME function reference (same listener-Set dedup rationale as
@@ -171953,6 +172081,11 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
171953
172081
  this.ctx.logger.debug("Reolink event watchdog re-subscribe failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
171954
172082
  }
171955
172083
  }
172084
+ /** Off-then-prepend, so a re-attached api never carries two observers. */
172085
+ observeMotionSensors(api) {
172086
+ api.client.off("event", this.observeRawEventBound);
172087
+ api.client.prependListener("event", this.observeRawEventBound);
172088
+ }
171956
172089
  /**
171957
172090
  * Subscribe (or re-subscribe) `handleSimpleEventBound` against
171958
172091
  * the Baichuan API. Single-source-of-truth for the listener
@@ -172288,7 +172421,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
172288
172421
  key: "host",
172289
172422
  label: "Camera IP",
172290
172423
  required: true,
172291
- placeholder: "192.168.x.x"
172424
+ placeholder: "192.168.10.100"
172292
172425
  },
172293
172426
  {
172294
172427
  type: "number",
@@ -173547,6 +173680,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173547
173680
  adoptApi(api) {
173548
173681
  this.api = api;
173549
173682
  this.reconnectAttempts = 0;
173683
+ this.observeMotionSensors(api);
173550
173684
  api.client.on("close", () => this.scheduleReconnect("close"));
173551
173685
  api.client.on("error", (err) => {
173552
173686
  this.ctx.logger.warn("Baichuan client error — scheduling reconnect", {
@@ -173694,6 +173828,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173694
173828
  meta: { error: err instanceof Error ? err.message : String(err) }
173695
173829
  });
173696
173830
  });
173831
+ this.observeMotionSensors(api);
173697
173832
  api.client.on("close", () => this.scheduleReconnect("close"));
173698
173833
  api.client.on("error", (err) => {
173699
173834
  this.ctx.logger.warn("Baichuan client error — scheduling reconnect", {
@@ -173763,8 +173898,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173763
173898
  * when the camera lives under a Hub and inherits the parent's
173764
173899
  * single subscription.
173765
173900
  */
173766
- handleSimpleEvent(event) {
173901
+ handleSimpleEvent(event, routedMotionSensor = null) {
173767
173902
  this.lastEventAt = Date.now();
173903
+ const motionSensor = event.type === "motion" ? motionSensorOnSimpleEvent(event) ?? routedMotionSensor ?? this.motionSensors.take(event.channel) : null;
173768
173904
  this.consecutiveStaleHealthChecks = 0;
173769
173905
  this.nextEventHealthCheckAt = 0;
173770
173906
  this.markPassiveContact();
@@ -173783,7 +173919,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173783
173919
  type: event.type,
173784
173920
  channel: event.channel,
173785
173921
  timestamp: event.timestamp,
173786
- source: event.source
173922
+ source: event.source,
173923
+ ...event.type === "motion" ? { motionSensor } : {}
173787
173924
  } });
173788
173925
  if (event.type === "motion") {
173789
173926
  const now = Date.now();
@@ -174342,6 +174479,17 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
174342
174479
  handleSimpleEventBound = (ev) => {
174343
174480
  this.routeSimpleEvent(ev);
174344
174481
  };
174482
+ /** MD vs PIR per channel, read off the raw client event the lib's simple
174483
+ * event drops (`motion-sensor-source.ts`) and handed to the child. */
174484
+ motionSensors = createMotionSensorTracker();
174485
+ observeRawEventBound = (ev) => {
174486
+ this.motionSensors.observe(ev);
174487
+ };
174488
+ /** Off-then-prepend: recorded before the API maps the same emit. */
174489
+ observeMotionSensors(api) {
174490
+ api.client.off("event", this.observeRawEventBound);
174491
+ api.client.prependListener("event", this.observeRawEventBound);
174492
+ }
174345
174493
  constructor(ctx) {
174346
174494
  super(ctx, reolinkHubSchema, { type: DeviceType.Hub });
174347
174495
  this.registerDeviceDiscovery();
@@ -174494,7 +174642,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
174494
174642
  key: "host",
174495
174643
  label: "Hub IP",
174496
174644
  required: true,
174497
- placeholder: "192.168.x.x"
174645
+ placeholder: "192.168.10.100"
174498
174646
  },
174499
174647
  {
174500
174648
  type: "number",
@@ -175105,6 +175253,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175105
175253
  routeSimpleEvent(ev) {
175106
175254
  const channel = ev?.channel;
175107
175255
  if (typeof channel !== "number") return;
175256
+ const motionSensor = ev.type === "motion" ? this.motionSensors.take(channel) : null;
175108
175257
  const deviceId = this.channelToDeviceId.get(channel);
175109
175258
  if (deviceId === void 0) {
175110
175259
  const last = this.unroutedChannelLoggedAt.get(channel) ?? 0;
@@ -175134,7 +175283,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175134
175283
  deviceId: child.id,
175135
175284
  name: child.name
175136
175285
  } });
175137
- child.handleSimpleEvent(ev);
175286
+ child.handleSimpleEvent(ev, motionSensor);
175138
175287
  }).catch((err) => {
175139
175288
  this.ctx.logger.debug("Hub simpleEvent dispatch failed", { meta: {
175140
175289
  channel,
@@ -175178,6 +175327,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175178
175327
  try {
175179
175328
  await api.offSimpleEvent(this.handleSimpleEventBound);
175180
175329
  } catch {}
175330
+ this.observeMotionSensors(api);
175181
175331
  await api.onSimpleEvent(this.handleSimpleEventBound);
175182
175332
  api.client.on("close", () => this.scheduleReconnect("socket-close"));
175183
175333
  api.client.on("error", (err) => {
@@ -175236,6 +175386,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175236
175386
  this.api = api;
175237
175387
  this.reconnectAttempts = 0;
175238
175388
  this.markOnline(true);
175389
+ this.observeMotionSensors(api);
175239
175390
  api.client.on("close", () => this.scheduleReconnect("socket-close"));
175240
175391
  api.client.on("error", (err) => {
175241
175392
  this.ctx.logger.warn("Reolink Hub adopted socket error", { meta: { error: err instanceof Error ? err.message : String(err) } });
@@ -175328,7 +175479,7 @@ function buildDiscoveryParamsFormSchema() {
175328
175479
  }, {
175329
175480
  id: "scan",
175330
175481
  title: "Scan options",
175331
- description: "Leave the network blank to scan the local subnet. To find cameras on a different subnet, enter its CIDR (e.g. 192.168.x.x/24).",
175482
+ description: "Leave the network blank to scan the local subnet. To find cameras on a different subnet, enter its CIDR (e.g. 192.168.20.0/24).",
175332
175483
  columns: 1,
175333
175484
  fields: [
175334
175485
  {
@@ -175336,7 +175487,7 @@ function buildDiscoveryParamsFormSchema() {
175336
175487
  key: "networkCidr",
175337
175488
  label: "Network (CIDR)",
175338
175489
  required: false,
175339
- placeholder: "192.168.x.x/24"
175490
+ placeholder: "192.168.20.0/24"
175340
175491
  },
175341
175492
  {
175342
175493
  type: "boolean",
@@ -175420,7 +175571,7 @@ function buildCreationFormSchema() {
175420
175571
  key: "host",
175421
175572
  label: "Host (IP / hostname / UID)",
175422
175573
  required: true,
175423
- placeholder: "192.168.x.x",
175574
+ placeholder: "192.168.10.100",
175424
175575
  description: "For UDP-only mode you can leave the host blank and supply the camera UID below — the library will discover it on the network.",
175425
175576
  inputType: "text"
175426
175577
  },
package/dist/addon.mjs CHANGED
@@ -11093,6 +11093,57 @@ var ClipSearchStrictnessSchema = _enum([
11093
11093
  "balanced",
11094
11094
  "strict"
11095
11095
  ]);
11096
+ new Set([
11097
+ {
11098
+ id: "person",
11099
+ name: "Person"
11100
+ },
11101
+ {
11102
+ id: "vehicle",
11103
+ name: "Vehicle"
11104
+ },
11105
+ {
11106
+ id: "animal",
11107
+ name: "Animal"
11108
+ },
11109
+ {
11110
+ id: "package",
11111
+ name: "Package"
11112
+ }
11113
+ ].map((l) => l.id));
11114
+ var COCO_TO_MACRO = {
11115
+ mapping: {
11116
+ person: "person",
11117
+ bicycle: "vehicle",
11118
+ car: "vehicle",
11119
+ motorcycle: "vehicle",
11120
+ airplane: "vehicle",
11121
+ bus: "vehicle",
11122
+ train: "vehicle",
11123
+ truck: "vehicle",
11124
+ boat: "vehicle",
11125
+ bird: "animal",
11126
+ cat: "animal",
11127
+ dog: "animal",
11128
+ horse: "animal",
11129
+ sheep: "animal",
11130
+ cow: "animal",
11131
+ elephant: "animal",
11132
+ bear: "animal",
11133
+ zebra: "animal",
11134
+ giraffe: "animal",
11135
+ suitcase: "package",
11136
+ backpack: "package",
11137
+ handbag: "package"
11138
+ },
11139
+ preserveOriginal: false
11140
+ };
11141
+ /** Steps a root detector may declare it fills. */
11142
+ var FILLABLE_STEP_IDS = [
11143
+ "animal-classifier",
11144
+ "vehicle-classifier",
11145
+ "package-detection"
11146
+ ];
11096
11147
  var MODEL_FORMATS = [
11097
11148
  "onnx",
11098
11149
  "coreml",
@@ -11225,6 +11276,12 @@ var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { wei
11225
11276
  /** True when the weights or their data forbid commercial use. */
11226
11277
  nonCommercial: boolean().optional()
11227
11278
  }));
11279
+ /** One `fillsClassifierEffects` entry — see `ModelCatalogEntry.fillsClassifierEffects`. */
11280
+ var FillsClassifierEffectSchema = object({
11281
+ stepId: _enum(FILLABLE_STEP_IDS),
11282
+ labelToEffect: record(string(), string()),
11283
+ uncovered: array(string()).readonly()
11284
+ });
11228
11285
  var ModelCatalogEntrySchema = object({
11229
11286
  id: string(),
11230
11287
  name: string(),
@@ -11351,6 +11408,16 @@ var ModelCatalogEntrySchema = object({
11351
11408
  */
11352
11409
  classMap: DetectionCatalogClassMapSchema.optional(),
11353
11410
  /**
11411
+ * The pipeline steps this ROOT detector already does the work of (D715):
11412
+ * its labels name the effect the step exists to produce (`deer` for
11413
+ * `animal-classifier`, `package` for `package-detection`). Derived from the
11414
+ * labelmap by `deriveClassifierFills`, never hand-written; a COCO model
11415
+ * declares none. Whether a declaration is honoured is the step's own
11416
+ * `labelLink` policy (`resolveLabelLink`). `labelToEffect` values are in
11417
+ * `FILLABLE_STEP_VOCABULARIES` (guard `check-label-link-vocabulary`).
11418
+ */
11419
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional(),
11420
+ /**
11354
11421
  * The model's CLIP feature-space contract (text encoder, dimension, vector
11355
11422
  * index, token window, search floor) — see `clip-model.ts`. Present ONLY on a
11356
11423
  * CLIP image encoder; its absence is what "not a CLIP model" means to the
@@ -14823,7 +14890,7 @@ DeviceType.Camera;
14823
14890
  * This matters here more than anywhere else in the repo, because every provider
14824
14891
  * that knows a device's host knows its PASSWORD too: `{ host, port, username,
14825
14892
  * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
14826
- * and `http://admin:hunter2@192.168.x.x/` is a URL a browser accepts. The
14893
+ * and `http://admin:hunter2@192.168.60.139/` is a URL a browser accepts. The
14827
14894
  * camera's own page will ask for its own login. That is correct, and pre-
14828
14895
  * filling it is the operator's business, not ours.
14829
14896
  *
@@ -18714,51 +18781,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
18714
18781
  kind: "mutation",
18715
18782
  auth: "admin"
18716
18783
  });
18717
- new Set([
18718
- {
18719
- id: "person",
18720
- name: "Person"
18721
- },
18722
- {
18723
- id: "vehicle",
18724
- name: "Vehicle"
18725
- },
18726
- {
18727
- id: "animal",
18728
- name: "Animal"
18729
- },
18730
- {
18731
- id: "package",
18732
- name: "Package"
18733
- }
18734
- ].map((l) => l.id));
18735
- var COCO_TO_MACRO = {
18736
- mapping: {
18737
- person: "person",
18738
- bicycle: "vehicle",
18739
- car: "vehicle",
18740
- motorcycle: "vehicle",
18741
- airplane: "vehicle",
18742
- bus: "vehicle",
18743
- train: "vehicle",
18744
- truck: "vehicle",
18745
- boat: "vehicle",
18746
- bird: "animal",
18747
- cat: "animal",
18748
- dog: "animal",
18749
- horse: "animal",
18750
- sheep: "animal",
18751
- cow: "animal",
18752
- elephant: "animal",
18753
- bear: "animal",
18754
- zebra: "animal",
18755
- giraffe: "animal",
18756
- suitcase: "package",
18757
- backpack: "package",
18758
- handbag: "package"
18759
- },
18760
- preserveOriginal: false
18761
- };
18762
18784
  var AUDIO_MACRO_LABELS = [
18763
18785
  {
18764
18786
  id: "speech",
@@ -20652,8 +20674,9 @@ var NcRuleSchema = NcRuleInputSchema.extend({
20652
20674
  disabledTargetIds: array(string()).default([])
20653
20675
  });
20654
20676
  /**
20655
- * The state-summary SECTIONS (D647 / D698): the D637 kind groups that report a
20656
- * still-true state. `backups-exports` and `alarm` are not sections — a completed
20677
+ * The state-summary SECTIONS (D647 / D698 / D708): the D637 kind groups that
20678
+ * report a still-true state. `addons` is the crash-looped addons only and
20679
+ * `addon-updates` the addons that are behind (D708 split them). `backups-exports` and `alarm` are not sections — a completed
20657
20680
  * job is not an open state, and the alarm stays instant.
20658
20681
  */
20659
20682
  var NcStateSummarySectionIdSchema = _enum([
@@ -20665,6 +20688,7 @@ var NcStateSummarySectionIdSchema = _enum([
20665
20688
  "detection-blind",
20666
20689
  "nodes",
20667
20690
  "addons",
20691
+ "addon-updates",
20668
20692
  "updates"
20669
20693
  ]);
20670
20694
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
@@ -21145,7 +21169,19 @@ var NcAlarmModeDelaysListSchema = array(object({
21145
21169
  * When this mode may be armed. Absent / null = always allowed (same as a
21146
21170
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
21147
21171
  */
21148
- schedule: NcScheduleSchema.optional()
21172
+ schedule: NcScheduleSchema.optional(),
21173
+ /**
21174
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
21175
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
21176
+ * only one, and the matches ride the triggered notification's mosaic (D705).
21177
+ */
21178
+ notifyDuringEntryDelay: boolean().optional(),
21179
+ /**
21180
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
21181
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
21182
+ * is the one that starts the entry delay — the entry-delay notification already names it.
21183
+ */
21184
+ notifySensorRewatched: boolean().optional()
21149
21185
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
21150
21186
  var NcAlarmModeDelaysEditListSchema = array(object({
21151
21187
  mode: AlarmArmModeSchema,
@@ -21154,7 +21190,11 @@ var NcAlarmModeDelaysEditListSchema = array(object({
21154
21190
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
21155
21191
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
21156
21192
  /** Set a schedule, or `null` to clear (mode always allowed again). */
21157
- schedule: NcScheduleSchema.nullable().optional()
21193
+ schedule: NcScheduleSchema.nullable().optional(),
21194
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
21195
+ notifyDuringEntryDelay: boolean().nullable().optional(),
21196
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
21197
+ notifySensorRewatched: boolean().nullable().optional()
21158
21198
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
21159
21199
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
21160
21200
  var NcAlarmNonBlockingEditSchema = object({
@@ -24182,7 +24222,8 @@ var PipelineModelOptionSchema = object({
24182
24222
  group: ModelVariantGroupSchema.optional(),
24183
24223
  legacy: boolean().optional(),
24184
24224
  provider: ModelProviderIdSchema.optional(),
24185
- defaultConfidence: number().optional()
24225
+ defaultConfidence: number().optional(),
24226
+ fillsClassifierEffects: array(FillsClassifierEffectSchema).readonly().optional()
24186
24227
  });
24187
24228
  var ConfigFieldBridge = custom();
24188
24229
  var PipelineAddonSchemaSchema = object({
@@ -25015,6 +25056,22 @@ var MotionSourceEnum = _enum([
25015
25056
  "device-activity"
25016
25057
  ]);
25017
25058
  /**
25059
+ * What opened an on-motion detection SESSION: the motion source that passed the
25060
+ * camera's `motionSources` gate, and what raised it (the camera itself, or a
25061
+ * sensor the operator attached — D672). `sensorName` is the attached sensor's
25062
+ * device name as the orchestrator's camera-sensors resolution last read it;
25063
+ * absent when unknown or when the camera itself triggered.
25064
+ *
25065
+ * Carried on the session attach ONLY so the runner's `DetectionPhaseTransition`
25066
+ * can say what started the session (D672 rule 11, amended 2026-10-06). The
25067
+ * runner never decides anything on it.
25068
+ */
25069
+ var SessionTriggerSchema = object({
25070
+ source: MotionSourceEnum,
25071
+ triggeredBy: MotionTriggeredBySchema,
25072
+ sensorName: string().optional()
25073
+ });
25074
+ /**
25018
25075
  * List of motion sources active on a camera. Empty array is valid:
25019
25076
  * "no source" — happens for battery cams without firmware motion when
25020
25077
  * the operator hasn't opted into the SW analyzer (would hold the
@@ -25266,6 +25323,14 @@ var RunnerCameraConfigSchema = object({
25266
25323
  */
25267
25324
  motionSources: MotionSourcesSchema.default(["analyzer"]),
25268
25325
  /**
25326
+ * What opened this attach, on a SESSION attach only (see
25327
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
25328
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
25329
+ * behind it, and every attach from an orchestrator older than the field — the
25330
+ * runner then names no source on the phase event.
25331
+ */
25332
+ sessionTrigger: SessionTriggerSchema.optional(),
25333
+ /**
25269
25334
  * WHICH root detector runs for this camera. The detection counterpart of
25270
25335
  * {@link motionSources}, same shape and same discipline: a per-camera list
25271
25336
  * of sources the system understands, vendor-agnostic, owned here.
@@ -33176,7 +33241,7 @@ var LocalInterfaceSchema = object({
33176
33241
  name: string(),
33177
33242
  family: _enum(["IPv4", "IPv6"]),
33178
33243
  address: string(),
33179
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
33244
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
33180
33245
  cidr: string(),
33181
33246
  /** OS netmask. Stable across calls so the SDK can hash on it. */
33182
33247
  netmask: string(),
@@ -33230,7 +33295,8 @@ var ConnectionEndpointSchema = object({
33230
33295
  "lan-ipv4",
33231
33296
  "lan-ipv6",
33232
33297
  "public",
33233
- "loopback"
33298
+ "loopback",
33299
+ "app-link"
33234
33300
  ]),
33235
33301
  /**
33236
33302
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -33299,15 +33365,27 @@ var GetConnectionEndpointsResultSchema = object({
33299
33365
  localPort: AdvertisedLocalPortSchema
33300
33366
  });
33301
33367
  /**
33302
- * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
33303
- * AUTO (resolved from the candidate ranking at send time); `resolved` reports
33304
- * what AUTO currently picks, so the UI can show the effective value either way.
33368
+ * The address flagged for notification links. `baseUrl: null` = AUTO, which
33369
+ * since D709 is the CamStack app link; `resolved` reports the effective value,
33370
+ * so the UI can show it either way.
33305
33371
  */
33306
33372
  var NotificationEndpointSchema = object({
33307
33373
  /** The operator's explicit choice, or null for AUTO. */
33308
33374
  baseUrl: string().nullable(),
33309
- /** What the ranking currently resolves to (null when nothing is reachable). */
33310
- resolved: string().nullable()
33375
+ /**
33376
+ * The effective flagged address: the explicit choice, else (AUTO) the
33377
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
33378
+ * answered the public tunnel / mesh here, or null.
33379
+ */
33380
+ resolved: string().nullable(),
33381
+ /**
33382
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
33383
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
33384
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
33385
+ * hub advertises neither; the app link is then minted without `hub=`.
33386
+ * Absent on a pre-D709 hub.
33387
+ */
33388
+ appLinkHub: string().nullable()
33311
33389
  });
33312
33390
  /**
33313
33391
  * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
@@ -33361,7 +33439,11 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
33361
33439
  * Pass `'https'` when the caller is itself loaded over HTTPS
33362
33440
  * to avoid mixed-content blocks in the browser. The public
33363
33441
  * tunnel always emits `https://` regardless. */
33364
- scheme: _enum(["http", "https"]).optional()
33442
+ scheme: _enum(["http", "https"]).optional(),
33443
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
33444
+ * an address notification links can be flagged with, not a hub
33445
+ * route. Default `false`: racers and rankers never see it. */
33446
+ includeAppLink: boolean().optional()
33365
33447
  }), 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, {
33366
33448
  kind: "mutation",
33367
33449
  auth: "admin"
@@ -33690,7 +33772,7 @@ var MeshPeerSchema = object({
33690
33772
  tags: array(string()).readonly(),
33691
33773
  /** True when the peer advertises itself as an exit-node candidate. */
33692
33774
  exitNodeOption: boolean(),
33693
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
33775
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
33694
33776
  advertisedRoutes: array(string()).readonly(),
33695
33777
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
33696
33778
  userLogin: string().nullable(),
@@ -54121,12 +54203,14 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
54121
54203
  {
54122
54204
  id: "addons",
54123
54205
  family: "system",
54124
- label: "Addons",
54125
- kinds: [
54126
- "addon-crash-loop",
54127
- "addon-update-available",
54128
- "addon-updated"
54129
- ]
54206
+ label: "Addons stopped",
54207
+ kinds: ["addon-crash-loop"]
54208
+ },
54209
+ {
54210
+ id: "addon-updates",
54211
+ family: "system",
54212
+ label: "Addon updates",
54213
+ kinds: ["addon-update-available", "addon-updated"]
54130
54214
  },
54131
54215
  {
54132
54216
  id: "updates",
@@ -54286,13 +54370,15 @@ var STATE_SUMMARY_SECTION_VARS = [
54286
54370
  },
54287
54371
  {
54288
54372
  name: "summaryAddons",
54289
- label: "Summary: addons",
54290
- example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
54291
- kinds: [
54292
- "addon-crash-loop",
54293
- "addon-update-available",
54294
- "addon-updated"
54295
- ]
54373
+ label: "Summary: addons stopped",
54374
+ example: "hub — recorder stopped after repeated crashes",
54375
+ kinds: ["addon-crash-loop"]
54376
+ },
54377
+ {
54378
+ name: "summaryAddonUpdates",
54379
+ label: "Summary: addon updates",
54380
+ example: "hub:\n• @camstack/addon-recorder 1.2.3 → 1.2.4",
54381
+ kinds: ["addon-update-available", "addon-updated"]
54296
54382
  },
54297
54383
  {
54298
54384
  name: "summaryUpdates",
@@ -54376,6 +54462,7 @@ var STATE_SUMMARY_VARS = [
54376
54462
  [...RULE_BODY_ONLY],
54377
54463
  [...RULE_TEXT],
54378
54464
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54465
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
54379
54466
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
54380
54467
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
54381
54468
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -166238,6 +166325,37 @@ function buildSyntheticRfc4571Sdp(input) {
166238
166325
  function buildLazyRfc4571Url(camStreamId) {
166239
166326
  return `lazy:rfc4571:${encodeURIComponent(camStreamId)}`;
166240
166327
  }
166328
+ function createMotionSensorTracker(now = Date.now) {
166329
+ const latest = /* @__PURE__ */ new Map();
166330
+ return {
166331
+ observe(event) {
166332
+ if (event.type !== "motion") return;
166333
+ const sensor = event.motion.source;
166334
+ if (sensor === void 0) return;
166335
+ latest.set(event.channel, {
166336
+ sensor,
166337
+ at: now()
166338
+ });
166339
+ },
166340
+ take(channel) {
166341
+ const entry = latest.get(channel);
166342
+ if (!entry) return null;
166343
+ latest.delete(channel);
166344
+ return now() - entry.at <= 2e3 ? entry.sensor : null;
166345
+ }
166346
+ };
166347
+ }
166348
+ var isMotionSensor = (value) => value === "md" || value === "pir" || value === "unknown";
166349
+ /**
166350
+ * The sensor a simple event states itself, for a library version that forwards
166351
+ * `motion.source` on the simple event; `null` for the versions that do not.
166352
+ */
166353
+ function motionSensorOnSimpleEvent(event) {
166354
+ if (!("motion" in event)) return null;
166355
+ const motion = event.motion;
166356
+ if (typeof motion !== "object" || motion === null || !("source" in motion)) return null;
166357
+ return isMotionSensor(motion.source) ? motion.source : null;
166358
+ }
166241
166359
  //#endregion
166242
166360
  //#region src/reolink-camera.ts
166243
166361
  /**
@@ -166544,6 +166662,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
166544
166662
  this.handleSimpleEvent(event);
166545
166663
  };
166546
166664
  /**
166665
+ * MD vs PIR of this camera's own motion pushes. The lib's simple event drops
166666
+ * it, so the raw client `event` is observed — PREPENDED, so the sensor is
166667
+ * recorded before the API maps the same emit into the simple event
166668
+ * (`motion-sensor-source.ts`). Hub children get theirs from the hub.
166669
+ */
166670
+ motionSensors = createMotionSensorTracker();
166671
+ observeRawEventBound = (event) => {
166672
+ this.motionSensors.observe(event);
166673
+ };
166674
+ /**
166547
166675
  * Stable bound handler for `api.onObjectDetections` — created ONCE so
166548
166676
  * every `onObjectDetections` / `offObjectDetections` call uses the
166549
166677
  * SAME function reference (same listener-Set dedup rationale as
@@ -171945,6 +172073,11 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
171945
172073
  this.ctx.logger.debug("Reolink event watchdog re-subscribe failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
171946
172074
  }
171947
172075
  }
172076
+ /** Off-then-prepend, so a re-attached api never carries two observers. */
172077
+ observeMotionSensors(api) {
172078
+ api.client.off("event", this.observeRawEventBound);
172079
+ api.client.prependListener("event", this.observeRawEventBound);
172080
+ }
171948
172081
  /**
171949
172082
  * Subscribe (or re-subscribe) `handleSimpleEventBound` against
171950
172083
  * the Baichuan API. Single-source-of-truth for the listener
@@ -172280,7 +172413,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
172280
172413
  key: "host",
172281
172414
  label: "Camera IP",
172282
172415
  required: true,
172283
- placeholder: "192.168.x.x"
172416
+ placeholder: "192.168.10.100"
172284
172417
  },
172285
172418
  {
172286
172419
  type: "number",
@@ -173539,6 +173672,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173539
173672
  adoptApi(api) {
173540
173673
  this.api = api;
173541
173674
  this.reconnectAttempts = 0;
173675
+ this.observeMotionSensors(api);
173542
173676
  api.client.on("close", () => this.scheduleReconnect("close"));
173543
173677
  api.client.on("error", (err) => {
173544
173678
  this.ctx.logger.warn("Baichuan client error — scheduling reconnect", {
@@ -173686,6 +173820,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173686
173820
  meta: { error: err instanceof Error ? err.message : String(err) }
173687
173821
  });
173688
173822
  });
173823
+ this.observeMotionSensors(api);
173689
173824
  api.client.on("close", () => this.scheduleReconnect("close"));
173690
173825
  api.client.on("error", (err) => {
173691
173826
  this.ctx.logger.warn("Baichuan client error — scheduling reconnect", {
@@ -173755,8 +173890,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173755
173890
  * when the camera lives under a Hub and inherits the parent's
173756
173891
  * single subscription.
173757
173892
  */
173758
- handleSimpleEvent(event) {
173893
+ handleSimpleEvent(event, routedMotionSensor = null) {
173759
173894
  this.lastEventAt = Date.now();
173895
+ const motionSensor = event.type === "motion" ? motionSensorOnSimpleEvent(event) ?? routedMotionSensor ?? this.motionSensors.take(event.channel) : null;
173760
173896
  this.consecutiveStaleHealthChecks = 0;
173761
173897
  this.nextEventHealthCheckAt = 0;
173762
173898
  this.markPassiveContact();
@@ -173775,7 +173911,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
173775
173911
  type: event.type,
173776
173912
  channel: event.channel,
173777
173913
  timestamp: event.timestamp,
173778
- source: event.source
173914
+ source: event.source,
173915
+ ...event.type === "motion" ? { motionSensor } : {}
173779
173916
  } });
173780
173917
  if (event.type === "motion") {
173781
173918
  const now = Date.now();
@@ -174334,6 +174471,17 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
174334
174471
  handleSimpleEventBound = (ev) => {
174335
174472
  this.routeSimpleEvent(ev);
174336
174473
  };
174474
+ /** MD vs PIR per channel, read off the raw client event the lib's simple
174475
+ * event drops (`motion-sensor-source.ts`) and handed to the child. */
174476
+ motionSensors = createMotionSensorTracker();
174477
+ observeRawEventBound = (ev) => {
174478
+ this.motionSensors.observe(ev);
174479
+ };
174480
+ /** Off-then-prepend: recorded before the API maps the same emit. */
174481
+ observeMotionSensors(api) {
174482
+ api.client.off("event", this.observeRawEventBound);
174483
+ api.client.prependListener("event", this.observeRawEventBound);
174484
+ }
174337
174485
  constructor(ctx) {
174338
174486
  super(ctx, reolinkHubSchema, { type: DeviceType.Hub });
174339
174487
  this.registerDeviceDiscovery();
@@ -174486,7 +174634,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
174486
174634
  key: "host",
174487
174635
  label: "Hub IP",
174488
174636
  required: true,
174489
- placeholder: "192.168.x.x"
174637
+ placeholder: "192.168.10.100"
174490
174638
  },
174491
174639
  {
174492
174640
  type: "number",
@@ -175097,6 +175245,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175097
175245
  routeSimpleEvent(ev) {
175098
175246
  const channel = ev?.channel;
175099
175247
  if (typeof channel !== "number") return;
175248
+ const motionSensor = ev.type === "motion" ? this.motionSensors.take(channel) : null;
175100
175249
  const deviceId = this.channelToDeviceId.get(channel);
175101
175250
  if (deviceId === void 0) {
175102
175251
  const last = this.unroutedChannelLoggedAt.get(channel) ?? 0;
@@ -175126,7 +175275,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175126
175275
  deviceId: child.id,
175127
175276
  name: child.name
175128
175277
  } });
175129
- child.handleSimpleEvent(ev);
175278
+ child.handleSimpleEvent(ev, motionSensor);
175130
175279
  }).catch((err) => {
175131
175280
  this.ctx.logger.debug("Hub simpleEvent dispatch failed", { meta: {
175132
175281
  channel,
@@ -175170,6 +175319,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175170
175319
  try {
175171
175320
  await api.offSimpleEvent(this.handleSimpleEventBound);
175172
175321
  } catch {}
175322
+ this.observeMotionSensors(api);
175173
175323
  await api.onSimpleEvent(this.handleSimpleEventBound);
175174
175324
  api.client.on("close", () => this.scheduleReconnect("socket-close"));
175175
175325
  api.client.on("error", (err) => {
@@ -175228,6 +175378,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
175228
175378
  this.api = api;
175229
175379
  this.reconnectAttempts = 0;
175230
175380
  this.markOnline(true);
175381
+ this.observeMotionSensors(api);
175231
175382
  api.client.on("close", () => this.scheduleReconnect("socket-close"));
175232
175383
  api.client.on("error", (err) => {
175233
175384
  this.ctx.logger.warn("Reolink Hub adopted socket error", { meta: { error: err instanceof Error ? err.message : String(err) } });
@@ -175320,7 +175471,7 @@ function buildDiscoveryParamsFormSchema() {
175320
175471
  }, {
175321
175472
  id: "scan",
175322
175473
  title: "Scan options",
175323
- description: "Leave the network blank to scan the local subnet. To find cameras on a different subnet, enter its CIDR (e.g. 192.168.x.x/24).",
175474
+ description: "Leave the network blank to scan the local subnet. To find cameras on a different subnet, enter its CIDR (e.g. 192.168.20.0/24).",
175324
175475
  columns: 1,
175325
175476
  fields: [
175326
175477
  {
@@ -175328,7 +175479,7 @@ function buildDiscoveryParamsFormSchema() {
175328
175479
  key: "networkCidr",
175329
175480
  label: "Network (CIDR)",
175330
175481
  required: false,
175331
- placeholder: "192.168.x.x/24"
175482
+ placeholder: "192.168.20.0/24"
175332
175483
  },
175333
175484
  {
175334
175485
  type: "boolean",
@@ -175412,7 +175563,7 @@ function buildCreationFormSchema() {
175412
175563
  key: "host",
175413
175564
  label: "Host (IP / hostname / UID)",
175414
175565
  required: true,
175415
- placeholder: "192.168.x.x",
175566
+ placeholder: "192.168.10.100",
175416
175567
  description: "For UDP-only mode you can leave the host blank and supply the camera UID below — the library will discover it on the network.",
175417
175568
  inputType: "text"
175418
175569
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.174",
3
+ "version": "1.2.176",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",