@camstack/addon-provider-reolink 1.2.138 → 1.2.140

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-CNf5ZN-e.mjs";
2
- import { A as splitAnnexBToNalPayloads2, C as extractVpsFromAnnexB, D as normalizeDebugOptions, E as md5StrModern, M as traceLog, O as recordingsTraceLog, S as extractSpsFromAnnexB, T as isH265Irap, _ as convertToAnnexB2, a as BC_MAGIC, b as eventTraceLog, c as BaichuanVideoStream, d as aesDecrypt, f as aesEncrypt, g as convertToAnnexB, h as bcHeaderHasPayloadOffset, i as BC_CLASS_MODERN_24, j as talkTraceLog, k as splitAnnexBToNalPayloads, l as BcMediaAnnexBDecoder, m as bcEncrypt, n as BC_CLASS_LEGACY, o as BC_MAGIC_REV, p as bcDecrypt, r as BC_CLASS_MODERN_20, t as BC_CLASS_FILE_DOWNLOAD, u as __require$1, v as debugLog, w as getH265NalType, x as extractPpsFromAnnexB, y as deriveAesKey } from "./chunk-KWWUCNIY-Ccq-OAGJ.mjs";
2
+ import { A as splitAnnexBToNalPayloads2, C as extractVpsFromAnnexB, D as normalizeDebugOptions, E as md5StrModern, M as traceLog, O as recordingsTraceLog, S as extractSpsFromAnnexB, T as isH265Irap, _ as convertToAnnexB2, a as BC_MAGIC, b as eventTraceLog, c as BaichuanVideoStream, d as aesDecrypt, f as aesEncrypt, g as convertToAnnexB, h as bcHeaderHasPayloadOffset, i as BC_CLASS_MODERN_24, j as talkTraceLog, k as splitAnnexBToNalPayloads, l as BcMediaAnnexBDecoder, m as bcEncrypt, n as BC_CLASS_LEGACY, o as BC_MAGIC_REV, p as bcDecrypt, r as BC_CLASS_MODERN_20, t as BC_CLASS_FILE_DOWNLOAD, u as __require$1, v as debugLog, w as getH265NalType, x as extractPpsFromAnnexB, y as deriveAesKey } from "./chunk-ZQFQRCLQ-BNqCZtSS.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { EventEmitter } from "events";
5
5
  import * as fs2 from "fs";
@@ -5381,7 +5381,7 @@ var ZodIssueCode = {
5381
5381
  var ZodFirstPartyTypeKind;
5382
5382
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5383
5383
  //#endregion
5384
- //#region ../types/dist/sleep-BDR76Ykr.mjs
5384
+ //#region ../types/dist/sleep-vl6nBE8d.mjs
5385
5385
  /**
5386
5386
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5387
5387
  * window to float samples (D455).
@@ -31652,14 +31652,81 @@ function summarisePrivacyAudio(profiles) {
31652
31652
  if (profiles.length === 0) return null;
31653
31653
  return profiles.some((p) => p.audioEnabled);
31654
31654
  }
31655
+ /**
31656
+ * What a preset id DOES on this camera.
31657
+ *
31658
+ * On several PTZ firmwares the preset namespace is shared: low ids are stored
31659
+ * positions, a reserved band triggers camera FUNCTIONS. The dispensa camera
31660
+ * (Hikvision) returns its own reserved band in the preset list, named by the
31661
+ * firmware:
31662
+ *
31663
+ * 34 Back to origin · 39 Day mode · 40 Night mode · 46 Day/Night Auto Mode
31664
+ * 92 Set manual limits · 93 Save manual limits · 94 Remote reboot
31665
+ * 95 Call OSD menu
31666
+ *
31667
+ * We used to hand all of these to the UI as ordinary presets. Saving over one
31668
+ * is what produced `PUT /ISAPI/PTZCtrl/channels/1/presets/34 -> HTTP 500`: the
31669
+ * camera refusing to let "Back to origin" be overwritten, surfaced to the
31670
+ * operator as a transport failure.
31671
+ *
31672
+ * A function preset stays fully usable as a DESTINATION -- `goToPreset('34')`
31673
+ * is the single most useful thing that camera can do. Only `savePreset` and
31674
+ * `deletePreset` are gated.
31675
+ */
31676
+ var PtzPresetFunctionSchema = _enum([
31677
+ "home",
31678
+ "day-mode",
31679
+ "night-mode",
31680
+ "day-night-auto",
31681
+ "set-limits",
31682
+ "save-limits",
31683
+ "reboot",
31684
+ "osd-menu"
31685
+ ]);
31655
31686
  var PtzPresetSchema = object({
31656
31687
  id: string(),
31657
- name: string()
31688
+ name: string(),
31689
+ /**
31690
+ * The firmware function this id performs, when the provider KNOWS the id is
31691
+ * reserved. `null` = an ordinary stored position, OR a reserved id this
31692
+ * provider does not recognise -- the two are told apart by `writable`.
31693
+ */
31694
+ fn: PtzPresetFunctionSchema.nullable(),
31695
+ /**
31696
+ * Whether `savePreset` / `deletePreset` may target this id. A provider sets
31697
+ * it false only for a band it KNOWS is reserved; where the firmware is not
31698
+ * documented it stays true and a refusal comes from the camera, named.
31699
+ */
31700
+ writable: boolean()
31658
31701
  });
31702
+ /**
31703
+ * Where the head is pointing -- per axis, and `null` when the driver cannot
31704
+ * read it.
31705
+ *
31706
+ * It used to be three REQUIRED numbers, which left a provider that cannot read
31707
+ * position no way to say so. All four said `0`:
31708
+ *
31709
+ * reolink `return { pan: 0, tilt: 0, zoom: 0 }` "until a position-query
31710
+ * path lands upstream"
31711
+ * hikvision stub, "firmware-dependent and frequently absent"
31712
+ * amcrest stub, "no generic absolute-position read for this model family"
31713
+ * onvif throws
31714
+ *
31715
+ * So every consumer asking where a camera points was told "perfectly centred"
31716
+ * by four cameras that had never looked. That is D393 -- a measurement that
31717
+ * FAILED is `null`, never `0`, and the type says so all the way to the
31718
+ * decision -- the same shape as the unreadable `statfs` folded into "0 bytes of
31719
+ * headroom", which evacuated a healthy disk.
31720
+ *
31721
+ * D393 also says to check for a narrower structural TWIN of the result type.
31722
+ * There is one: `PtzStatusSchema` extends this, so the lie had already
31723
+ * propagated into `getStatus`, which is the surface `getPosition`'s own comment
31724
+ * tells callers to migrate to.
31725
+ */
31659
31726
  var PtzPositionSchema = object({
31660
- pan: number(),
31661
- tilt: number(),
31662
- zoom: number()
31727
+ pan: number().nullable(),
31728
+ tilt: number().nullable(),
31729
+ zoom: number().nullable()
31663
31730
  });
31664
31731
  var PtzMoveCommandSchema = object({
31665
31732
  pan: number().optional(),
@@ -31681,7 +31748,72 @@ var PtzOptionsSchema = object({
31681
31748
  maxPresets: number().optional(),
31682
31749
  /** Whether the camera exposes a controllable autofocus toggle
31683
31750
  * (boolean `hasX` per the getOptions availability convention). */
31684
- hasAutofocus: boolean()
31751
+ hasAutofocus: boolean(),
31752
+ /**
31753
+ * How many distinct speeds this camera's NATIVE scale offers.
31754
+ *
31755
+ * The cap's `speed` is normalized 0..1 and each provider maps it down --
31756
+ * Baichuan 1..63, Dahua 1..8, ISAPI's percentage -100..100. The normalized
31757
+ * value hides how coarse that really is: asking an amcrest for `0.37` is
31758
+ * meaningless, because it has eight steps and three of them round to the
31759
+ * same one. A loop that tunes its own gain has to know the granularity of
31760
+ * the knob it is turning.
31761
+ *
31762
+ * `null` = the driver does not know, or the scale is continuous (ONVIF takes
31763
+ * a float). Never a made-up number.
31764
+ */
31765
+ speedSteps: number().int().positive().nullable(),
31766
+ /**
31767
+ * How long ONE `move` pulse runs on this driver, ms.
31768
+ *
31769
+ * `move` is a self-terminating burst everywhere, but every provider hardcoded
31770
+ * its own duration in private -- 200 reolink, 350 amcrest, 500 hikvision,
31771
+ * 1000 onvif -- so no caller could read it. It is the DENOMINATOR of "how far
31772
+ * did the head travel per pulse": without it a measured displacement has no
31773
+ * gain to be divided into.
31774
+ *
31775
+ * `null` = the driver cannot say.
31776
+ */
31777
+ moveImpulseMs: number().int().positive().nullable()
31778
+ });
31779
+ /**
31780
+ * Which preset `goHome` goes to, and WHO decided.
31781
+ *
31782
+ * `goHome` used to be three vendor "conventions" written in three comments --
31783
+ * Reolink preset 0, Hikvision preset '1', Dahua preset 1 -- and on this fleet
31784
+ * not one of the three cameras honours its own:
31785
+ *
31786
+ * 592 Videocamera camera Daniel (reolink) preset 0 holds "stanza"
31787
+ * 1438 Videocamera dispensa (hikvision) preset 1 holds "Credenza"
31788
+ * 3836 Videocamera studio (amcrest) preset 1 holds "Preset1"
31789
+ *
31790
+ * So `goHome` meant "go wherever the operator happened to save in slot 0 or 1",
31791
+ * and two of the three swallowed the failure in a bare `catch`. For autotrack
31792
+ * this is the HOTTEST path -- it runs every time a subject is released -- so it
31793
+ * needs an answer that is true per camera and audible when it is missing.
31794
+ *
31795
+ * `source` is what makes it honest:
31796
+ * - `operator` -- picked in the UI, stored by the provider.
31797
+ * - `firmware` -- the camera itself named a preset `fn: 'home'` (Hikvision's
31798
+ * "Back to origin"), adopted as the default with nothing to configure.
31799
+ * - `native` -- the driver homes WITHOUT a preset at all. ONVIF does:
31800
+ * `goHome` is `ptzAbsoluteMove({x:0, y:0, zoom:0})`. `presetId` is null and
31801
+ * that is not a failure -- there is nothing to pick, and the UI must offer
31802
+ * no picker.
31803
+ * - `none` -- nothing is configured and the camera names nothing.
31804
+ * `goHome` REFUSES rather than moving the head somewhere arbitrary.
31805
+ *
31806
+ * `source === 'none'` is the refusal condition, NOT `presetId === null`: the
31807
+ * native case has no preset and homes perfectly well.
31808
+ */
31809
+ var PtzHomePresetSchema = object({
31810
+ presetId: string().nullable(),
31811
+ source: _enum([
31812
+ "operator",
31813
+ "firmware",
31814
+ "native",
31815
+ "none"
31816
+ ])
31685
31817
  });
31686
31818
  var ptzCapability = {
31687
31819
  name: "ptz",
@@ -31722,7 +31854,66 @@ var ptzCapability = {
31722
31854
  auth: "admin"
31723
31855
  }),
31724
31856
  getOptions: method(object({ deviceId: number() }), PtzOptionsSchema),
31857
+ /**
31858
+ * Move to the camera's configured home preset. THROWS when
31859
+ * `getHomePreset()` resolves to `none` -- a head that did not move must
31860
+ * never look like a head that went home.
31861
+ */
31725
31862
  goHome: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
31863
+ /** Which preset `goHome` targets, and who decided it. */
31864
+ getHomePreset: method(object({ deviceId: number() }), PtzHomePresetSchema),
31865
+ /**
31866
+ * Pin the home preset for this camera. `null` clears the operator's choice
31867
+ * and falls back to whatever the firmware names, or to `none`.
31868
+ */
31869
+ setHomePreset: method(object({
31870
+ deviceId: number(),
31871
+ presetId: string().nullable()
31872
+ }), _void(), {
31873
+ kind: "mutation",
31874
+ auth: "admin"
31875
+ }),
31876
+ /**
31877
+ * Make WHERE THE HEAD IS POINTING RIGHT NOW this camera's home.
31878
+ *
31879
+ * `setHomePreset` takes a preset id, and a preset id is the wrong shape for
31880
+ * the most natural thing an operator does: aim the camera, then say "here".
31881
+ * It is also the only shape a camera whose home is NOT a preset can accept
31882
+ * -- Reolink's home is a guard point, a stored position with no id at all,
31883
+ * and hiding the control for that case (as the UI briefly did) left the one
31884
+ * vendor with a native home unable to set it.
31885
+ *
31886
+ * Each provider does it its own way:
31887
+ * reolink `setGrd` with `needSetPos`, pinning the guard point here
31888
+ * hikvision / amcrest save a preset, then pin it as home
31889
+ * onvif REFUSES -- its home is the PTZ origin and cannot be moved
31890
+ *
31891
+ * A provider that cannot honour it throws rather than silently doing
31892
+ * nothing: an operator who pressed "set home here" and saw no error would
31893
+ * believe it took.
31894
+ */
31895
+ captureHomeHere: method(object({ deviceId: number() }), _void(), {
31896
+ kind: "mutation",
31897
+ auth: "admin"
31898
+ }),
31899
+ /**
31900
+ * How long the camera waits, idle, before returning home on its own --
31901
+ * `null` when it has no such behaviour.
31902
+ *
31903
+ * Reolink's guard point carries one (`timeout`, freely settable in
31904
+ * seconds). It is a real function of the camera that we did not expose at
31905
+ * all, and it belongs next to the home it governs rather than in a vendor
31906
+ * settings page nobody associates with Home.
31907
+ */
31908
+ getHomeReturnSeconds: method(object({ deviceId: number() }), number().int().nullable()),
31909
+ /** Set the idle-return delay. Refused by a camera that has no such timer. */
31910
+ setHomeReturnSeconds: method(object({
31911
+ deviceId: number(),
31912
+ seconds: number().int().min(0).max(3600)
31913
+ }), _void(), {
31914
+ kind: "mutation",
31915
+ auth: "admin"
31916
+ }),
31726
31917
  /**
31727
31918
  * Pull the current PTZ position. Redundant with the auto-injected
31728
31919
  * `getStatus` method (see `status` below); kept for callers that
@@ -41381,6 +41572,12 @@ Object.freeze({
41381
41572
  addonId: null,
41382
41573
  access: "create"
41383
41574
  },
41575
+ "ptz.captureHomeHere": {
41576
+ capName: "ptz",
41577
+ capScope: "device",
41578
+ addonId: null,
41579
+ access: "create"
41580
+ },
41384
41581
  "ptz.continuousMove": {
41385
41582
  capName: "ptz",
41386
41583
  capScope: "device",
@@ -41393,6 +41590,18 @@ Object.freeze({
41393
41590
  addonId: null,
41394
41591
  access: "delete"
41395
41592
  },
41593
+ "ptz.getHomePreset": {
41594
+ capName: "ptz",
41595
+ capScope: "device",
41596
+ addonId: null,
41597
+ access: "view"
41598
+ },
41599
+ "ptz.getHomeReturnSeconds": {
41600
+ capName: "ptz",
41601
+ capScope: "device",
41602
+ addonId: null,
41603
+ access: "view"
41604
+ },
41396
41605
  "ptz.getOptions": {
41397
41606
  capName: "ptz",
41398
41607
  capScope: "device",
@@ -41441,6 +41650,18 @@ Object.freeze({
41441
41650
  addonId: null,
41442
41651
  access: "create"
41443
41652
  },
41653
+ "ptz.setHomePreset": {
41654
+ capName: "ptz",
41655
+ capScope: "device",
41656
+ addonId: null,
41657
+ access: "create"
41658
+ },
41659
+ "ptz.setHomeReturnSeconds": {
41660
+ capName: "ptz",
41661
+ capScope: "device",
41662
+ addonId: null,
41663
+ access: "create"
41664
+ },
41444
41665
  "ptz.stop": {
41445
41666
  capName: "ptz",
41446
41667
  capScope: "device",
@@ -44604,6 +44825,11 @@ Object.freeze({
44604
44825
  form: "single",
44605
44826
  optional: false
44606
44827
  }],
44828
+ "ptz.captureHomeHere": [{
44829
+ name: "deviceId",
44830
+ form: "single",
44831
+ optional: false
44832
+ }],
44607
44833
  "ptz.continuousMove": [{
44608
44834
  name: "deviceId",
44609
44835
  form: "single",
@@ -44614,6 +44840,16 @@ Object.freeze({
44614
44840
  form: "single",
44615
44841
  optional: false
44616
44842
  }],
44843
+ "ptz.getHomePreset": [{
44844
+ name: "deviceId",
44845
+ form: "single",
44846
+ optional: false
44847
+ }],
44848
+ "ptz.getHomeReturnSeconds": [{
44849
+ name: "deviceId",
44850
+ form: "single",
44851
+ optional: false
44852
+ }],
44617
44853
  "ptz.getOptions": [{
44618
44854
  name: "deviceId",
44619
44855
  form: "single",
@@ -44654,6 +44890,16 @@ Object.freeze({
44654
44890
  form: "single",
44655
44891
  optional: false
44656
44892
  }],
44893
+ "ptz.setHomePreset": [{
44894
+ name: "deviceId",
44895
+ form: "single",
44896
+ optional: false
44897
+ }],
44898
+ "ptz.setHomeReturnSeconds": [{
44899
+ name: "deviceId",
44900
+ form: "single",
44901
+ optional: false
44902
+ }],
44657
44903
  "ptz.stop": [{
44658
44904
  name: "deviceId",
44659
44905
  form: "single",
@@ -68338,7 +68584,7 @@ var require_yazl = /* @__PURE__ */ __commonJSMin(((exports) => {
68338
68584
  }
68339
68585
  }));
68340
68586
  //#endregion
68341
- //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-HL6TWIAL.js
68587
+ //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-KWQVN7H5.js
68342
68588
  var import_undici = require_undici();
68343
68589
  var import_yazl = /* @__PURE__ */ __toESM(require_yazl(), 1);
68344
68590
  var FLAGS_CAM_V2 = {
@@ -74755,7 +75001,7 @@ var require_lz4 = /* @__PURE__ */ __commonJSMin(((exports) => {
74755
75001
  };
74756
75002
  }));
74757
75003
  //#endregion
74758
- //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-3SEJZ2U5.js
75004
+ //#region node_modules/@apocaliss92/nodelink-js/dist/chunk-CE77DTET.js
74759
75005
  var import_fxp = require_fxp();
74760
75006
  var import_lz4 = /* @__PURE__ */ __toESM(require_lz4(), 1);
74761
75007
  function encodeHeader(h) {
@@ -75074,8 +75320,8 @@ function buildC2mQ(params) {
75074
75320
  const os = params.os ?? "MAC";
75075
75321
  return buildP2pXml(`<C2M_Q><uid>${xmlEscape(params.uid)}</uid><p>${xmlEscape(os)}</p></C2M_Q>`);
75076
75322
  }
75077
- function parseIpPortBlock(tag, body) {
75078
- const m = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, "i").exec(body);
75323
+ function parseIpPortBlock(tag2, body) {
75324
+ const m = new RegExp(`<${tag2}>([\\s\\S]*?)</${tag2}>`, "i").exec(body);
75079
75325
  if (!m) return void 0;
75080
75326
  const block = m[1] ?? "";
75081
75327
  const ip = /<ip>([^<]+)<\/ip>/i.exec(block)?.[1];
@@ -75120,8 +75366,8 @@ function parseR2cCr(xml) {
75120
75366
  if (!Number.isFinite(rsp)) return void 0;
75121
75367
  const sidStr = /<sid>(-?\d+)<\/sid>/i.exec(body)?.[1];
75122
75368
  const sid = sidStr != null ? Number(sidStr) : void 0;
75123
- const parseCustom = (tag) => {
75124
- const mm = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, "i").exec(body);
75369
+ const parseCustom = (tag2) => {
75370
+ const mm = new RegExp(`<${tag2}>([\\s\\S]*?)</${tag2}>`, "i").exec(body);
75125
75371
  if (!mm) return void 0;
75126
75372
  const block = mm[1] ?? "";
75127
75373
  const ip = /<ip>([^<]+)<\/ip>/i.exec(block)?.[1];
@@ -76681,12 +76927,12 @@ function asLogger(logger) {
76681
76927
  error: resolveMethod(base, "error"),
76682
76928
  debug: resolveMethod(base, "debug")
76683
76929
  };
76684
- out.child = (tag) => createTaggedLogger(out, tag);
76930
+ out.child = (tag2) => createTaggedLogger(out, tag2);
76685
76931
  return out;
76686
76932
  }
76687
- function createTaggedLogger(base, tag) {
76933
+ function createTaggedLogger(base, tag2) {
76688
76934
  const b = base.log ? base : asLogger(base);
76689
- const prefix = `[${tag}] `;
76935
+ const prefix = `[${tag2}] `;
76690
76936
  const wrap = (fn) => {
76691
76937
  return (message, ...optionalParams) => {
76692
76938
  if (typeof message === "string") fn(`${prefix}${message}`, ...optionalParams);
@@ -76713,7 +76959,7 @@ function createDebugGateLogger(base, enabled = false) {
76713
76959
  error: b.error,
76714
76960
  debug: enabled ? b.debug : noop
76715
76961
  };
76716
- out.child = (tag) => createDebugGateLogger(createTaggedLogger(out, tag), enabled);
76962
+ out.child = (tag2) => createDebugGateLogger(createTaggedLogger(out, tag2), enabled);
76717
76963
  return out;
76718
76964
  }
76719
76965
  function isTalkCmd(cmdId) {
@@ -82407,29 +82653,29 @@ function parseSupportXml(xml) {
82407
82653
  if (!match) return void 0;
82408
82654
  const supportXml = match[1] ?? "";
82409
82655
  const ptzMode = supportXml.match(/<ptzMode>([^<]*)<\/ptzMode>/i)?.[1];
82410
- const topLevelInt = (tag) => {
82411
- return toNumberOrUndefined(supportXml.match(new RegExp(`<${tag}>([^<]*)<\\/${tag}>`, "i"))?.[1]);
82656
+ const topLevelInt = (tag2) => {
82657
+ return toNumberOrUndefined(supportXml.match(new RegExp(`<${tag2}>([^<]*)<\\/${tag2}>`, "i"))?.[1]);
82412
82658
  };
82413
- const topLevelString = (tag) => {
82414
- return supportXml.match(new RegExp(`<${tag}>([^<]*)<\\/${tag}>`, "i"))?.[1];
82659
+ const topLevelString = (tag2) => {
82660
+ return supportXml.match(new RegExp(`<${tag2}>([^<]*)<\\/${tag2}>`, "i"))?.[1];
82415
82661
  };
82416
82662
  const items = [];
82417
82663
  for (const itemMatch of supportXml.matchAll(/<item[^>]*>([\s\S]*?)<\/item>/gi)) {
82418
82664
  const itemXml = itemMatch[1] ?? "";
82419
82665
  const item = { chnID: toNumberOrUndefined(itemXml.match(/<chnID>([^<]*)<\/chnID>/i)?.[1]) ?? 0 };
82420
82666
  for (const tagMatch of itemXml.matchAll(/<([A-Za-z0-9_]+)>([^<]*)<\/\1>/g)) {
82421
- const tag = tagMatch[1];
82667
+ const tag2 = tagMatch[1];
82422
82668
  const value = tagMatch[2];
82423
- if (!tag) continue;
82424
- if (tag === "chnID") continue;
82425
- item[tag] = toNumberOrUndefined(value) ?? value;
82669
+ if (!tag2) continue;
82670
+ if (tag2 === "chnID") continue;
82671
+ item[tag2] = toNumberOrUndefined(value) ?? value;
82426
82672
  }
82427
82673
  items.push(item);
82428
82674
  }
82429
82675
  const support = { items };
82430
82676
  if (ptzMode !== void 0) support.ptzMode = ptzMode;
82431
- const assignInt = (key, tag) => {
82432
- const v = topLevelInt(tag);
82677
+ const assignInt = (key, tag2) => {
82678
+ const v = topLevelInt(tag2);
82433
82679
  if (v !== void 0) support[key] = v;
82434
82680
  };
82435
82681
  assignInt("IOInputPortNum", "IOInputPortNum");
@@ -82656,15 +82902,15 @@ function extractCanvasFromShelterXml(xml) {
82656
82902
  const yMatch = xml.match(/<topLeftY>(\d+)<\/topLeftY>/g) ?? [];
82657
82903
  let canvasX = 0;
82658
82904
  let canvasY = 0;
82659
- for (const tag of xMatch) {
82660
- const v = Number(tag.replace(/<\/?topLeftX>/g, "")) | 0;
82905
+ for (const tag2 of xMatch) {
82906
+ const v = Number(tag2.replace(/<\/?topLeftX>/g, "")) | 0;
82661
82907
  if (v > 0) {
82662
82908
  canvasX = v & 65535;
82663
82909
  break;
82664
82910
  }
82665
82911
  }
82666
- for (const tag of yMatch) {
82667
- const v = Number(tag.replace(/<\/?topLeftY>/g, "")) | 0;
82912
+ for (const tag2 of yMatch) {
82913
+ const v = Number(tag2.replace(/<\/?topLeftY>/g, "")) | 0;
82668
82914
  if (v > 0) {
82669
82915
  canvasY = v & 65535;
82670
82916
  break;
@@ -82677,9 +82923,9 @@ function extractCanvasFromShelterXml(xml) {
82677
82923
  }
82678
82924
  function parseRectTags(block) {
82679
82925
  const out = {};
82680
- const grab = (tag) => {
82681
- const m = block.match(new RegExp(`<${tag}>([^<]*)<\\/${tag}>`));
82682
- if (m && m[1] !== void 0) out[tag] = m[1];
82926
+ const grab = (tag2) => {
82927
+ const m = block.match(new RegExp(`<${tag2}>([^<]*)<\\/${tag2}>`));
82928
+ if (m && m[1] !== void 0) out[tag2] = m[1];
82683
82929
  };
82684
82930
  grab("id");
82685
82931
  grab("name");
@@ -82924,9 +83170,9 @@ var buildDetectionClasses = (parsed, recordType) => {
82924
83170
  };
82925
83171
  var getXmlTexts = (xml, tags) => {
82926
83172
  const out = {};
82927
- for (const tag of tags) {
82928
- const v = getXmlText(xml, tag);
82929
- if (v !== void 0) out[tag] = v;
83173
+ for (const tag2 of tags) {
83174
+ const v = getXmlText(xml, tag2);
83175
+ if (v !== void 0) out[tag2] = v;
82930
83176
  }
82931
83177
  return out;
82932
83178
  };
@@ -83188,11 +83434,11 @@ function patchBlock(xml, block, fields) {
83188
83434
  const end = xml.indexOf(`</${block}>`, start);
83189
83435
  if (end < 0) return xml;
83190
83436
  let body = xml.slice(start, end);
83191
- for (const [tag, value] of Object.entries(fields)) {
83437
+ for (const [tag2, value] of Object.entries(fields)) {
83192
83438
  if (value === void 0) continue;
83193
83439
  const escaped = escapeXmlText(typeof value === "boolean" ? value ? "1" : "0" : String(value));
83194
- if (body.includes(`<${tag}>`)) body = body.replace(new RegExp(`<${tag}>[^<]*<\\/${tag}>`), `<${tag}>${escaped}</${tag}>`);
83195
- else body += `<${tag}>${escaped}</${tag}>`;
83440
+ if (body.includes(`<${tag2}>`)) body = body.replace(new RegExp(`<${tag2}>[^<]*<\\/${tag2}>`), `<${tag2}>${escaped}</${tag2}>`);
83441
+ else body += `<${tag2}>${escaped}</${tag2}>`;
83196
83442
  }
83197
83443
  return xml.slice(0, start) + body + xml.slice(end);
83198
83444
  }
@@ -83260,6 +83506,66 @@ var OSD_CORNER_LABELS = {
83260
83506
  "bottom-left": "Bottom left",
83261
83507
  "bottom-right": "Bottom right"
83262
83508
  };
83509
+ function wireFloat(value) {
83510
+ return value.toExponential(6).replace(/e([+-])(\d)$/, "e$10$2");
83511
+ }
83512
+ function buildPtzGuardExtensionXml(channelId, direction) {
83513
+ return `<?xml version="1.0" encoding="UTF-8" ?>
83514
+ <Extension version="1.1">
83515
+ <channelId>${channelId}</channelId>${direction === "read" ? `
83516
+ <chnType>0</chnType>` : ""}
83517
+ </Extension>
83518
+ `;
83519
+ }
83520
+ function guardBody(channelId, command, fields) {
83521
+ return [
83522
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>",
83523
+ "<body>",
83524
+ "<PtzGuard version=\"1.1\">",
83525
+ `<channelId>${channelId}</channelId>`,
83526
+ ...fields,
83527
+ `<command>${command}</command>`,
83528
+ "<imageName></imageName>",
83529
+ `<xpos>${wireFloat(0)}</xpos>`,
83530
+ `<ypos>${wireFloat(0)}</ypos>`,
83531
+ `<height>${wireFloat(0)}</height>`,
83532
+ `<width>${wireFloat(0)}</width>`,
83533
+ "<mode>global</mode>",
83534
+ "</PtzGuard>",
83535
+ "</body>",
83536
+ ""
83537
+ ].join("\n");
83538
+ }
83539
+ function buildPtzGuardGoXml(channelId) {
83540
+ return guardBody(channelId, "toGrd", ["<benable>0</benable>", "<timeout>68</timeout>"]);
83541
+ }
83542
+ function buildPtzGuardSetXml(input) {
83543
+ return guardBody(input.channelId, "setGrd", [
83544
+ `<benable>${input.enabled ? 1 : 0}</benable>`,
83545
+ `<timeout>${Math.round(input.timeoutSeconds)}</timeout>`,
83546
+ ...input.setPosition ? ["<needSetPos>1</needSetPos>"] : []
83547
+ ]);
83548
+ }
83549
+ function tag(xml, name) {
83550
+ const m = new RegExp(`<${name}>([\\s\\S]*?)</${name}>`).exec(xml);
83551
+ return m === null ? null : (m[1] ?? "").trim();
83552
+ }
83553
+ function parsePtzGuardXml(xml) {
83554
+ if (!xml.includes("<PtzGuard")) return null;
83555
+ const enabled = tag(xml, "benable");
83556
+ const valid = tag(xml, "bvalid");
83557
+ const timeout = tag(xml, "timeout");
83558
+ if (enabled === null || valid === null || timeout === null) return null;
83559
+ const timeoutSeconds = Number.parseInt(timeout, 10);
83560
+ if (!Number.isFinite(timeoutSeconds)) return null;
83561
+ return {
83562
+ enabled: enabled === "1",
83563
+ valid: valid === "1",
83564
+ timeoutSeconds,
83565
+ mode: tag(xml, "mode") ?? "global",
83566
+ imageName: tag(xml, "imageName") ?? ""
83567
+ };
83568
+ }
83263
83569
  var emitter = new EventEmitter();
83264
83570
  var lastEventByCamera = /* @__PURE__ */ new Map();
83265
83571
  var MAX_GLOBAL_EVENTS = 300;
@@ -84472,8 +84778,8 @@ var buildFileInfoListStopXml = (params) => {
84472
84778
  </body>`;
84473
84779
  };
84474
84780
  var sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
84475
- var xmlDateTimePayload = (tag, d) => {
84476
- return `<${tag}><year>${d.getFullYear()}</year><month>${d.getMonth() + 1}</month><day>${d.getDate()}</day><hour>${d.getHours()}</hour><minute>${d.getMinutes()}</minute><second>${d.getSeconds()}</second></${tag}>`;
84781
+ var xmlDateTimePayload = (tag2, d) => {
84782
+ return `<${tag2}><year>${d.getFullYear()}</year><month>${d.getMonth() + 1}</month><day>${d.getDate()}</day><hour>${d.getHours()}</hour><minute>${d.getMinutes()}</minute><second>${d.getSeconds()}</second></${tag2}>`;
84477
84783
  };
84478
84784
  var buildFileInfoListOpenXml = (params) => {
84479
84785
  return `<?xml version="1.0" encoding="UTF-8" ?>
@@ -84661,10 +84967,10 @@ var isPlausibleStream = (s) => {
84661
84967
  return s.width > 0 && s.height > 0 && (s.frameRate > 0 || s.bitRate > 0);
84662
84968
  };
84663
84969
  var logDebugStreamBlock = (params) => {
84664
- const { logger, traceNativeStream, channel, tag, blockXml } = params;
84970
+ const { logger, traceNativeStream, channel, tag: tag2, blockXml } = params;
84665
84971
  if (!traceNativeStream) return;
84666
84972
  if (!blockXml) {
84667
- (logger.warn ?? logger.log).call(logger, `[ReolinkBaichuanApi] getStreamMetadata(traceNativeStream): channel=${channel} tag=<${tag}> missing`);
84973
+ (logger.warn ?? logger.log).call(logger, `[ReolinkBaichuanApi] getStreamMetadata(traceNativeStream): channel=${channel} tag=<${tag2}> missing`);
84668
84974
  return;
84669
84975
  }
84670
84976
  const raw = blockXml;
@@ -84691,7 +84997,7 @@ var logDebugStreamBlock = (params) => {
84691
84997
  const previewMax = 1400;
84692
84998
  const xmlPreview = raw.length <= previewMax ? raw : raw.slice(0, previewMax) + `
84693
84999
  ...truncated (+${raw.length - previewMax} chars)`;
84694
- (logger.warn ?? logger.log).call(logger, `[ReolinkBaichuanApi] getStreamMetadata(traceNativeStream): channel=${channel} tag=<${tag}> enabled=${isEnabled} plausible=${plausible} width=${width} height=${height} frame=${frameRate} bitRate=${bitRate} audio=${audio} videoEncType=${videoEncTypeText ?? "?"} rawFields=${JSON.stringify({
85000
+ (logger.warn ?? logger.log).call(logger, `[ReolinkBaichuanApi] getStreamMetadata(traceNativeStream): channel=${channel} tag=<${tag2}> enabled=${isEnabled} plausible=${plausible} width=${width} height=${height} frame=${frameRate} bitRate=${bitRate} audio=${audio} videoEncType=${videoEncTypeText ?? "?"} rawFields=${JSON.stringify({
84695
85001
  widthText,
84696
85002
  heightText,
84697
85003
  frameText,
@@ -84784,20 +85090,20 @@ var parseChannelStreamMetadataFromGetEncXml = (params) => {
84784
85090
  audioEnabled = audioEnabled && s.audio === 1;
84785
85091
  }
84786
85092
  }
84787
- for (const tag of [
85093
+ for (const tag2 of [
84788
85094
  "extStream",
84789
85095
  "thirdStream",
84790
85096
  "externStream",
84791
85097
  "extraStream"
84792
85098
  ]) {
84793
- const extMatch = xml.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`));
85099
+ const extMatch = xml.match(new RegExp(`<${tag2}[^>]*>([\\s\\S]*?)<\\/${tag2}>`));
84794
85100
  if (!extMatch) continue;
84795
85101
  const extXml = extMatch[1] ?? "";
84796
85102
  logDebugStreamBlock({
84797
85103
  logger,
84798
85104
  traceNativeStream,
84799
85105
  channel,
84800
- tag,
85106
+ tag: tag2,
84801
85107
  blockXml: extXml
84802
85108
  });
84803
85109
  const s = buildStream({
@@ -86390,7 +86696,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86390
86696
  * @param logger - Optional logger for debug output
86391
86697
  * @returns The socket and a release function
86392
86698
  */
86393
- async acquirePooledSocket(tag, logger) {
86699
+ async acquirePooledSocket(tag2, logger) {
86394
86700
  const log = logger ?? this.logger;
86395
86701
  const now = Date.now();
86396
86702
  const cooldownEntry = this.socketPoolCooldowns.get(this.host);
@@ -86401,12 +86707,12 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86401
86707
  } else if (now < cooldownEntry.cooldownUntil) {
86402
86708
  const remainingMs = cooldownEntry.cooldownUntil - now;
86403
86709
  const reason = this.lastD2cDiscAtMs > 0 && now - this.lastD2cDiscAtMs < 12e4 ? "D2C_DISC (camera sleeping)" : "repeated login failures";
86404
- const error = /* @__PURE__ */ new Error(`[SocketPool] Host ${this.host} is in cooldown for ${Math.ceil(remainingMs / 1e3)}s due to ${reason}. tag=${tag}`);
86710
+ const error = /* @__PURE__ */ new Error(`[SocketPool] Host ${this.host} is in cooldown for ${Math.ceil(remainingMs / 1e3)}s due to ${reason}. tag=${tag2}`);
86405
86711
  log?.debug?.(error.message);
86406
86712
  throw error;
86407
86713
  }
86408
86714
  }
86409
- const existing = this.socketPool.get(tag);
86715
+ const existing = this.socketPool.get(tag2);
86410
86716
  if (existing) {
86411
86717
  if (existing.idleCloseTimer) {
86412
86718
  clearTimeout(existing.idleCloseTimer);
@@ -86416,35 +86722,35 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86416
86722
  const client2 = await existing.pendingPromise;
86417
86723
  existing.refCount++;
86418
86724
  existing.lastUsedAt = Date.now();
86419
- log?.debug?.(`[SocketPool] Waited for pending socket creation for tag=${tag} (refCount=${existing.refCount})`);
86725
+ log?.debug?.(`[SocketPool] Waited for pending socket creation for tag=${tag2} (refCount=${existing.refCount})`);
86420
86726
  return {
86421
86727
  client: client2,
86422
- release: () => this.releasePooledSocket(tag, logger)
86728
+ release: () => this.releasePooledSocket(tag2, logger)
86423
86729
  };
86424
86730
  }
86425
86731
  if (existing.refCount === 0) {
86426
86732
  existing.refCount = 1;
86427
86733
  existing.lastUsedAt = Date.now();
86428
- log?.debug?.(`[SocketPool] Reusing idle socket for tag=${tag}`);
86734
+ log?.debug?.(`[SocketPool] Reusing idle socket for tag=${tag2}`);
86429
86735
  try {
86430
86736
  if (!existing.client.loggedIn) await existing.client.login();
86431
86737
  } catch {}
86432
86738
  if (existing.client.loggedIn) return {
86433
86739
  client: existing.client,
86434
- release: () => this.releasePooledSocket(tag, logger)
86740
+ release: () => this.releasePooledSocket(tag2, logger)
86435
86741
  };
86436
- } else if (tag.startsWith("replay:")) log?.debug?.(`[SocketPool] Preempting active replay socket for tag=${tag}`);
86742
+ } else if (tag2.startsWith("replay:")) log?.debug?.(`[SocketPool] Preempting active replay socket for tag=${tag2}`);
86437
86743
  else {
86438
86744
  existing.refCount++;
86439
86745
  existing.lastUsedAt = Date.now();
86440
- log?.debug?.(`[SocketPool] Reusing active socket for tag=${tag} (refCount=${existing.refCount})`);
86746
+ log?.debug?.(`[SocketPool] Reusing active socket for tag=${tag2} (refCount=${existing.refCount})`);
86441
86747
  return {
86442
86748
  client: existing.client,
86443
- release: () => this.releasePooledSocket(tag, logger)
86749
+ release: () => this.releasePooledSocket(tag2, logger)
86444
86750
  };
86445
86751
  }
86446
- log?.debug?.(`[SocketPool] Closing existing socket for tag=${tag} (recreating)`);
86447
- this.socketPool.delete(tag);
86752
+ log?.debug?.(`[SocketPool] Closing existing socket for tag=${tag2} (recreating)`);
86753
+ this.socketPool.delete(tag2);
86448
86754
  if (existing.generalPermitRelease) {
86449
86755
  try {
86450
86756
  existing.generalPermitRelease();
@@ -86457,10 +86763,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86457
86763
  skipLogout: true
86458
86764
  });
86459
86765
  } catch (e) {
86460
- log?.warn?.(`[SocketPool] Error closing old socket for tag=${tag}: ${e}`);
86766
+ log?.warn?.(`[SocketPool] Error closing old socket for tag=${tag2}: ${e}`);
86461
86767
  }
86462
86768
  }
86463
- log?.log?.(`[SocketPool] Creating new socket for tag=${tag}`);
86769
+ log?.log?.(`[SocketPool] Creating new socket for tag=${tag2}`);
86464
86770
  const entry = {
86465
86771
  client: void 0,
86466
86772
  refCount: 0,
@@ -86477,7 +86783,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86477
86783
  } : this.clientOptions);
86478
86784
  this.attachD2cDiscListener(newClient);
86479
86785
  newClient.on("error", (err) => {
86480
- log?.debug?.(`[SocketPool] tag=${tag} client error: ${err?.message ?? err}`);
86786
+ log?.debug?.(`[SocketPool] tag=${tag2} client error: ${err?.message ?? err}`);
86481
86787
  });
86482
86788
  await newClient.login();
86483
86789
  const existingCooldown = this.socketPoolCooldowns.get(this.host);
@@ -86489,10 +86795,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86489
86795
  entry.refCount = 1;
86490
86796
  entry.lastUsedAt = Date.now();
86491
86797
  delete entry.pendingPromise;
86492
- log?.log?.(`[SocketPool] Socket connected for tag=${tag}`);
86493
- if (tag !== "general") try {
86798
+ log?.log?.(`[SocketPool] Socket connected for tag=${tag2}`);
86799
+ if (tag2 !== "general") try {
86494
86800
  const generalEntry = this.socketPool.get("general");
86495
- if (generalEntry?.client) entry.generalPermitRelease = generalEntry.client.acquirePermit(0, `streaming-peer:${tag}`);
86801
+ if (generalEntry?.client) entry.generalPermitRelease = generalEntry.client.acquirePermit(0, `streaming-peer:${tag2}`);
86496
86802
  } catch {}
86497
86803
  if (this.sessionGuardEnabled) this.maybeRebootOnTooManySessions();
86498
86804
  return newClient;
@@ -86503,21 +86809,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86503
86809
  if (failureCount >= _ReolinkBaichuanApi.SOCKET_POOL_FAILURE_THRESHOLD) {
86504
86810
  const backoffMs = Math.min(_ReolinkBaichuanApi.SOCKET_POOL_BASE_COOLDOWN_MS * Math.pow(2, failureCount - _ReolinkBaichuanApi.SOCKET_POOL_FAILURE_THRESHOLD), _ReolinkBaichuanApi.SOCKET_POOL_MAX_COOLDOWN_MS);
86505
86811
  cooldownUntil = now2 + backoffMs;
86506
- log?.warn?.(`[SocketPool] Login failed for host=${this.host} (failure #${failureCount}). Entering cooldown for ${Math.ceil(backoffMs / 1e3)}s. tag=${tag}`);
86507
- } else log?.warn?.(`[SocketPool] Login failed for host=${this.host} (failure #${failureCount}/${_ReolinkBaichuanApi.SOCKET_POOL_FAILURE_THRESHOLD} before cooldown). tag=${tag}`);
86812
+ log?.warn?.(`[SocketPool] Login failed for host=${this.host} (failure #${failureCount}). Entering cooldown for ${Math.ceil(backoffMs / 1e3)}s. tag=${tag2}`);
86813
+ } else log?.warn?.(`[SocketPool] Login failed for host=${this.host} (failure #${failureCount}/${_ReolinkBaichuanApi.SOCKET_POOL_FAILURE_THRESHOLD} before cooldown). tag=${tag2}`);
86508
86814
  this.socketPoolCooldowns.set(this.host, {
86509
86815
  failureCount,
86510
86816
  lastFailureAt: now2,
86511
86817
  cooldownUntil
86512
86818
  });
86513
- this.socketPool.delete(tag);
86819
+ this.socketPool.delete(tag2);
86514
86820
  throw loginError;
86515
86821
  }
86516
86822
  })();
86517
- this.socketPool.set(tag, entry);
86823
+ this.socketPool.set(tag2, entry);
86518
86824
  return {
86519
86825
  client: await entry.pendingPromise,
86520
- release: () => this.releasePooledSocket(tag, logger)
86826
+ release: () => this.releasePooledSocket(tag2, logger)
86521
86827
  };
86522
86828
  }
86523
86829
  /**
@@ -86525,31 +86831,31 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86525
86831
  * For shared sockets (general, streaming), just decrements refCount.
86526
86832
  * For replay sockets, schedules idle close.
86527
86833
  */
86528
- async releasePooledSocket(tag, logger) {
86834
+ async releasePooledSocket(tag2, logger) {
86529
86835
  const log = logger ?? this.logger;
86530
- const entry = this.socketPool.get(tag);
86836
+ const entry = this.socketPool.get(tag2);
86531
86837
  if (!entry) return;
86532
86838
  entry.refCount = Math.max(0, entry.refCount - 1);
86533
86839
  entry.lastUsedAt = Date.now();
86534
- log?.debug?.(`[SocketPool] Released socket for tag=${tag} (refCount=${entry.refCount})`);
86840
+ log?.debug?.(`[SocketPool] Released socket for tag=${tag2} (refCount=${entry.refCount})`);
86535
86841
  if (entry.refCount > 0) return;
86536
- const isReplayTag = tag.startsWith("replay:");
86537
- const isStreamingTag = tag.startsWith("streaming:");
86538
- if (tag === "general") return;
86842
+ const isReplayTag = tag2.startsWith("replay:");
86843
+ const isStreamingTag = tag2.startsWith("streaming:");
86844
+ if (tag2 === "general") return;
86539
86845
  if (isStreamingTag) {
86540
86846
  if (entry.idleCloseTimer) return;
86541
86847
  entry.idleCloseTimer = setTimeout(async () => {
86542
- const current = this.socketPool.get(tag);
86848
+ const current = this.socketPool.get(tag2);
86543
86849
  if (!current) return;
86544
86850
  if (current.refCount > 0) return;
86545
- this.socketPool.delete(tag);
86851
+ this.socketPool.delete(tag2);
86546
86852
  if (current.generalPermitRelease) {
86547
86853
  try {
86548
86854
  current.generalPermitRelease();
86549
86855
  } catch {}
86550
86856
  current.generalPermitRelease = void 0;
86551
86857
  }
86552
- log?.log?.(`[SocketPool] Closing idle streaming socket for tag=${tag}`);
86858
+ log?.log?.(`[SocketPool] Closing idle streaming socket for tag=${tag2}`);
86553
86859
  try {
86554
86860
  await current.client.close({
86555
86861
  reason: "streaming idle close",
@@ -86562,11 +86868,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86562
86868
  if (isReplayTag) {
86563
86869
  if (entry.idleCloseTimer) return;
86564
86870
  entry.idleCloseTimer = setTimeout(async () => {
86565
- const current = this.socketPool.get(tag);
86871
+ const current = this.socketPool.get(tag2);
86566
86872
  if (!current) return;
86567
86873
  if (current.refCount > 0) return;
86568
- this.socketPool.delete(tag);
86569
- log?.debug?.(`[SocketPool] Closing idle replay socket for tag=${tag} (keepalive expired)`);
86874
+ this.socketPool.delete(tag2);
86875
+ log?.debug?.(`[SocketPool] Closing idle replay socket for tag=${tag2} (keepalive expired)`);
86570
86876
  try {
86571
86877
  await current.client.close({
86572
86878
  reason: "replay idle keepalive expired",
@@ -86576,24 +86882,24 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86576
86882
  }, _ReolinkBaichuanApi.SOCKET_POOL_KEEPALIVE_MS);
86577
86883
  return;
86578
86884
  }
86579
- this.socketPool.delete(tag);
86885
+ this.socketPool.delete(tag2);
86580
86886
  try {
86581
86887
  await entry.client.close({
86582
86888
  reason: "socket pool release",
86583
86889
  skipLogout: true
86584
86890
  });
86585
- log?.log?.(`[SocketPool] Closed socket for tag=${tag}`);
86891
+ log?.log?.(`[SocketPool] Closed socket for tag=${tag2}`);
86586
86892
  } catch (e) {
86587
- log?.warn?.(`[SocketPool] Error closing socket for tag=${tag}: ${e}`);
86893
+ log?.warn?.(`[SocketPool] Error closing socket for tag=${tag2}: ${e}`);
86588
86894
  }
86589
86895
  }
86590
86896
  /**
86591
86897
  * Force-close a socket by tag.
86592
86898
  * Used to preempt existing connections before acquiring a new one.
86593
86899
  */
86594
- async forceClosePooledSocket(tag, logger) {
86900
+ async forceClosePooledSocket(tag2, logger) {
86595
86901
  const log = logger ?? this.logger;
86596
- const entry = this.socketPool.get(tag);
86902
+ const entry = this.socketPool.get(tag2);
86597
86903
  if (!entry) return false;
86598
86904
  if (entry.idleCloseTimer) {
86599
86905
  clearTimeout(entry.idleCloseTimer);
@@ -86605,16 +86911,16 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86605
86911
  } catch {}
86606
86912
  entry.generalPermitRelease = void 0;
86607
86913
  }
86608
- log?.debug?.(`[SocketPool] Force-closing socket for tag=${tag}`);
86609
- this.socketPool.delete(tag);
86914
+ log?.debug?.(`[SocketPool] Force-closing socket for tag=${tag2}`);
86915
+ this.socketPool.delete(tag2);
86610
86916
  try {
86611
86917
  await entry.client.close({
86612
86918
  reason: "force closed",
86613
86919
  skipLogout: true
86614
86920
  });
86615
- log?.log?.(`[SocketPool] Force-closed socket for tag=${tag}`);
86921
+ log?.log?.(`[SocketPool] Force-closed socket for tag=${tag2}`);
86616
86922
  } catch (e) {
86617
- log?.warn?.(`[SocketPool] Error during force-close for tag=${tag}: ${e}`);
86923
+ log?.warn?.(`[SocketPool] Error during force-close for tag=${tag2}: ${e}`);
86618
86924
  }
86619
86925
  return true;
86620
86926
  }
@@ -86624,7 +86930,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86624
86930
  async cleanupSocketPool() {
86625
86931
  const entries = Array.from(this.socketPool.entries());
86626
86932
  this.socketPool.clear();
86627
- await Promise.allSettled(entries.map(async ([tag, entry]) => {
86933
+ await Promise.allSettled(entries.map(async ([tag2, entry]) => {
86628
86934
  try {
86629
86935
  if (entry.idleCloseTimer) clearTimeout(entry.idleCloseTimer);
86630
86936
  if (entry.generalPermitRelease) {
@@ -86633,7 +86939,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86633
86939
  } catch {}
86634
86940
  entry.generalPermitRelease = void 0;
86635
86941
  }
86636
- this.logger?.debug?.(`[SocketPool] Cleanup: closing tag=${tag}`);
86942
+ this.logger?.debug?.(`[SocketPool] Cleanup: closing tag=${tag2}`);
86637
86943
  await entry.client.close({
86638
86944
  reason: "API cleanup",
86639
86945
  skipLogout: true
@@ -86666,17 +86972,17 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86666
86972
  * ```
86667
86973
  */
86668
86974
  async createDedicatedSession(sessionKey, logger) {
86669
- const tag = this.resolveSocketTag(sessionKey);
86670
- (logger ?? this.logger)?.debug?.(`[SocketPool] createDedicatedSession sessionKey=${sessionKey} \u2192 tag=${tag}`);
86671
- return await this.acquirePooledSocket(tag, logger);
86975
+ const tag2 = this.resolveSocketTag(sessionKey);
86976
+ (logger ?? this.logger)?.debug?.(`[SocketPool] createDedicatedSession sessionKey=${sessionKey} \u2192 tag=${tag2}`);
86977
+ return await this.acquirePooledSocket(tag2, logger);
86672
86978
  }
86673
86979
  /**
86674
86980
  * @deprecated Use forceClosePooledSocket via createDedicatedSession instead.
86675
86981
  * Force-close a dedicated client if it exists.
86676
86982
  */
86677
86983
  async forceCloseDedicatedClient(sessionKey, logger) {
86678
- const tag = this.resolveSocketTag(sessionKey);
86679
- return await this.forceClosePooledSocket(tag, logger);
86984
+ const tag2 = this.resolveSocketTag(sessionKey);
86985
+ return await this.forceClosePooledSocket(tag2, logger);
86680
86986
  }
86681
86987
  /**
86682
86988
  * @deprecated Cleanup handled by cleanupSocketPool now.
@@ -86691,8 +86997,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
86691
86997
  const debugCfg = this.client.getDebugConfig?.();
86692
86998
  if (debugCfg) {
86693
86999
  const sid = this.client.getSocketSessionId?.();
86694
- const tag = sid ? `ReolinkSimpleEvent sid=${sid}` : "ReolinkSimpleEvent";
86695
- eventTraceLog(debugCfg, this.logger, tag, `dispatch type=${evt.type} channel=${evt.channel} timestamp=${evt.timestamp}`);
87000
+ const tag2 = sid ? `ReolinkSimpleEvent sid=${sid}` : "ReolinkSimpleEvent";
87001
+ eventTraceLog(debugCfg, this.logger, tag2, `dispatch type=${evt.type} channel=${evt.channel} timestamp=${evt.timestamp}`);
86696
87002
  }
86697
87003
  for (const cb of this.simpleEventListeners) try {
86698
87004
  Promise.resolve(cb(evt)).catch((e) => {
@@ -87290,7 +87596,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
87290
87596
  return;
87291
87597
  }
87292
87598
  entry.startInFlight = (async () => {
87293
- const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-PBJL3EB5-D-etWPiW.mjs");
87599
+ const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-2M4UO7MB-CB-tIHMA.mjs");
87294
87600
  const sessionKey = `live:object-detections:ch${entry.channel}:${entry.profile}`;
87295
87601
  const dedicated = await this.createDedicatedSession(sessionKey);
87296
87602
  const stream = new BaichuanVideoStream2({
@@ -87857,8 +88163,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
87857
88163
  const idleTimeoutMs = options?.idleTimeoutMs ?? 3e4;
87858
88164
  const sessionKey = `talk:${options?.deviceId ?? "unknown"}:ch${channel}:${Date.now()}`;
87859
88165
  logger?.info?.(`[DedicatedTalk] Creating session: ${sessionKey} (idleTimeout=${idleTimeoutMs}ms)`);
87860
- const tag = this.resolveSocketTag(sessionKey);
87861
- const { client: dedicatedClient, release } = await this.acquirePooledSocket(tag, logger);
88166
+ const tag2 = this.resolveSocketTag(sessionKey);
88167
+ const { client: dedicatedClient, release } = await this.acquirePooledSocket(tag2, logger);
87862
88168
  const summary = this.getSocketPoolSummary();
87863
88169
  logger?.info?.(`[DedicatedTalk] Session created [sessions: ${summary.count} active${summary.count > 0 ? ` (${summary.tags.join(", ")})` : ""}]`);
87864
88170
  try {
@@ -87971,8 +88277,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
87971
88277
  }
87972
88278
  /** SetNetPort via Baichuan: cmd_id 36 (enable/disable rtsp/rtmp/onvif/http/https) */
87973
88279
  async setPortEnabled(params) {
87974
- const tag = `${params.port[0].toUpperCase()}${params.port.slice(1)}Port`;
87975
- const xml = `<?xml version="1.0" encoding="UTF-8" ?><body><${tag} version="1.1"><enable>${params.enable ? 1 : 0}</enable></${tag}></body>`;
88280
+ const tag2 = `${params.port[0].toUpperCase()}${params.port.slice(1)}Port`;
88281
+ const xml = `<?xml version="1.0" encoding="UTF-8" ?><body><${tag2} version="1.1"><enable>${params.enable ? 1 : 0}</enable></${tag2}></body>`;
87976
88282
  await this.sendXml({
87977
88283
  cmdId: 36,
87978
88284
  payloadXml: xml
@@ -88003,13 +88309,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
88003
88309
  */
88004
88310
  async setPortConfig(patch) {
88005
88311
  const blocks = [];
88006
- const append = (tag, portField, cfg) => {
88312
+ const append = (tag2, portField, cfg) => {
88007
88313
  if (!cfg) return;
88008
88314
  if (cfg.port === void 0 && cfg.enable === void 0) return;
88009
88315
  const inner = [];
88010
88316
  if (cfg.port !== void 0) inner.push(`<${portField}>${cfg.port}</${portField}>`);
88011
88317
  if (cfg.enable !== void 0) inner.push(`<enable>${cfg.enable ? 1 : 0}</enable>`);
88012
- blocks.push(`<${tag} version="1.1">${inner.join("")}</${tag}>`);
88318
+ blocks.push(`<${tag2} version="1.1">${inner.join("")}</${tag2}>`);
88013
88319
  };
88014
88320
  append("ServerPort", "serverPort", patch.server);
88015
88321
  append("HttpPort", "httpPort", patch.http);
@@ -88523,8 +88829,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
88523
88829
  ];
88524
88830
  const current = await this.getEncXml(ch);
88525
88831
  let updated = null;
88526
- for (const tag of candidateTags) {
88527
- const sectionRe = new RegExp(`(<${tag}[^>]*>[\\s\\S]*?<videoEncType>)(\\d+)(</videoEncType>)`);
88832
+ for (const tag2 of candidateTags) {
88833
+ const sectionRe = new RegExp(`(<${tag2}[^>]*>[\\s\\S]*?<videoEncType>)(\\d+)(</videoEncType>)`);
88528
88834
  if (!sectionRe.test(current)) continue;
88529
88835
  const next = current.replace(sectionRe, `$1${desired}$3`);
88530
88836
  if (next !== current) {
@@ -89054,9 +89360,9 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
89054
89360
  const streamType = params.streamType;
89055
89361
  const logger = params.logger ?? this.logger;
89056
89362
  const sessionKey = params.deviceId ? `replay:${params.deviceId}:ch${channel}` : `replay:standalone:ch${channel}:${Date.now()}`;
89057
- const tag = this.resolveSocketTag(sessionKey);
89058
- logger?.debug?.(`[startRecordingReplayStreamStandalone] sessionKey=${sessionKey} -> tag=${tag}`);
89059
- const { client: dedicatedClient, release: releaseDedicatedClient } = await this.acquirePooledSocket(tag, logger);
89363
+ const tag2 = this.resolveSocketTag(sessionKey);
89364
+ logger?.debug?.(`[startRecordingReplayStreamStandalone] sessionKey=${sessionKey} -> tag=${tag2}`);
89365
+ const { client: dedicatedClient, release: releaseDedicatedClient } = await this.acquirePooledSocket(tag2, logger);
89060
89366
  const uid = await this.ensureUidForRecordings(channel, void 0);
89061
89367
  const payloadXml = buildFileInfoListReplayByNameXml({
89062
89368
  channel,
@@ -89159,8 +89465,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
89159
89465
  const streamType = params.streamType;
89160
89466
  const logger = params.logger ?? this.logger;
89161
89467
  const sessionKey = params.deviceId ? `replay:${params.deviceId}:ch${channel}` : `replay:nvr:ch${channel}:${Date.now()}`;
89162
- const tag = this.resolveSocketTag(sessionKey);
89163
- const { client: dedicatedClient, release: releaseDedicatedClient } = await this.acquirePooledSocket(tag, logger);
89468
+ const tag2 = this.resolveSocketTag(sessionKey);
89469
+ const { client: dedicatedClient, release: releaseDedicatedClient } = await this.acquirePooledSocket(tag2, logger);
89164
89470
  let uid;
89165
89471
  try {
89166
89472
  uid = await this.ensureUidForRecordings(channel, void 0);
@@ -91387,6 +91693,73 @@ ${stderr}`));
91387
91693
  return result;
91388
91694
  }
91389
91695
  /**
91696
+ * Read the PTZ guard point ("monitoring point" in the app).
91697
+ *
91698
+ * cmd_id: 332
91699
+ *
91700
+ * Returns `null` when the camera has no readable guard configuration — never
91701
+ * a fabricated "none", because that answer is what a consumer uses to decide
91702
+ * whether to move the head.
91703
+ */
91704
+ async getPtzGuard(channel) {
91705
+ const ch = this.normalizeChannel(channel);
91706
+ return parsePtzGuardXml(await this.sendXml({
91707
+ cmdId: 332,
91708
+ channel: ch,
91709
+ channelIdOverride: ch,
91710
+ extensionXml: buildPtzGuardExtensionXml(ch, "read"),
91711
+ messageClass: BC_CLASS_MODERN_24,
91712
+ streamType: 0
91713
+ }));
91714
+ }
91715
+ /**
91716
+ * Configure the PTZ guard point.
91717
+ *
91718
+ * cmd_id: 331 with `<command>setGrd</command>`
91719
+ *
91720
+ * `setPosition` pins the guard point to the head's CURRENT position. Leave it
91721
+ * false when only changing the timeout or the enable flag: re-sending it
91722
+ * would silently move a point the operator had already placed.
91723
+ */
91724
+ async setPtzGuard(options, channel) {
91725
+ const ch = this.normalizeChannel(channel);
91726
+ await this.sendXml({
91727
+ cmdId: 331,
91728
+ channel: ch,
91729
+ channelIdOverride: ch,
91730
+ extensionXml: buildPtzGuardExtensionXml(ch, "write"),
91731
+ payloadXml: buildPtzGuardSetXml({
91732
+ channelId: ch,
91733
+ enabled: options.enabled,
91734
+ timeoutSeconds: options.timeoutSeconds,
91735
+ setPosition: options.setPosition === true
91736
+ }),
91737
+ messageClass: BC_CLASS_MODERN_24,
91738
+ streamType: 0
91739
+ });
91740
+ }
91741
+ /**
91742
+ * Send the head to the guard point NOW.
91743
+ *
91744
+ * cmd_id: 331 with `<command>toGrd</command>`
91745
+ *
91746
+ * This is the closest thing Baichuan has to a "go home": the guard point is
91747
+ * an operator-placed position, unlike preset slot 0 which merely happens to
91748
+ * be first.
91749
+ */
91750
+ async goToPtzGuard(channel) {
91751
+ const ch = this.normalizeChannel(channel);
91752
+ await this.sendXml({
91753
+ cmdId: 331,
91754
+ channel: ch,
91755
+ channelIdOverride: ch,
91756
+ extensionXml: buildPtzGuardExtensionXml(ch, "write"),
91757
+ payloadXml: buildPtzGuardGoXml(ch),
91758
+ messageClass: BC_CLASS_MODERN_24,
91759
+ streamType: 0
91760
+ });
91761
+ }
91762
+ /**
91390
91763
  * Read zoom/focus min/max/current positions.
91391
91764
  * cmd_id: 294 (MSG_ID_GET_ZOOM_FOCUS)
91392
91765
  */
@@ -91479,10 +91852,10 @@ ${stderr}`));
91479
91852
  notifyD2cDisc() {
91480
91853
  const now = Date.now();
91481
91854
  this.lastD2cDiscAtMs = now;
91482
- const streamingTags = Array.from(this.socketPool.keys()).filter((tag) => tag.startsWith("streaming:"));
91855
+ const streamingTags = Array.from(this.socketPool.keys()).filter((tag2) => tag2.startsWith("streaming:"));
91483
91856
  if (streamingTags.length > 0) {
91484
91857
  this.logger?.log?.(`[D2C_DISC] Force-closing ${streamingTags.length} streaming socket(s): ${streamingTags.join(", ")}`);
91485
- for (const tag of streamingTags) this.forceClosePooledSocket(tag, this.logger).catch(() => {});
91858
+ for (const tag2 of streamingTags) this.forceClosePooledSocket(tag2, this.logger).catch(() => {});
91486
91859
  }
91487
91860
  const immediateCooldownUntil = now + _ReolinkBaichuanApi.D2C_DISC_IMMEDIATE_COOLDOWN_MS;
91488
91861
  const existing = this.socketPoolCooldowns.get(this.host);
@@ -91515,15 +91888,15 @@ ${stderr}`));
91515
91888
  * Returns undefined if the client is not in the pool (e.g. it's the general socket used directly).
91516
91889
  */
91517
91890
  findSocketTagForClient(client) {
91518
- for (const [tag, entry] of this.socketPool) if (entry.client === client) return tag;
91891
+ for (const [tag2, entry] of this.socketPool) if (entry.client === client) return tag2;
91519
91892
  }
91520
91893
  /**
91521
91894
  * Reset the consecutive stream-start timeout counter for a streaming socket.
91522
91895
  * Called on successful stream start.
91523
91896
  */
91524
91897
  resetStreamTimeoutCounter(client) {
91525
- const tag = this.findSocketTagForClient(client);
91526
- if (tag) this.consecutiveStreamTimeouts.delete(tag);
91898
+ const tag2 = this.findSocketTagForClient(client);
91899
+ if (tag2) this.consecutiveStreamTimeouts.delete(tag2);
91527
91900
  }
91528
91901
  /**
91529
91902
  * Track a stream-start timeout on a streaming socket.
@@ -91531,14 +91904,14 @@ ${stderr}`));
91531
91904
  * socket so the next attempt creates a fresh connection.
91532
91905
  */
91533
91906
  trackStreamTimeout(client) {
91534
- const tag = this.findSocketTagForClient(client);
91535
- if (!tag || !tag.startsWith("streaming:")) return;
91536
- const count = (this.consecutiveStreamTimeouts.get(tag) ?? 0) + 1;
91537
- this.consecutiveStreamTimeouts.set(tag, count);
91907
+ const tag2 = this.findSocketTagForClient(client);
91908
+ if (!tag2 || !tag2.startsWith("streaming:")) return;
91909
+ const count = (this.consecutiveStreamTimeouts.get(tag2) ?? 0) + 1;
91910
+ this.consecutiveStreamTimeouts.set(tag2, count);
91538
91911
  if (count >= _ReolinkBaichuanApi.MAX_CONSECUTIVE_STREAM_TIMEOUTS) {
91539
- this.logger?.warn?.(`[SocketPool] ${count} consecutive stream timeouts on tag=${tag}, force-closing socket`);
91540
- this.consecutiveStreamTimeouts.delete(tag);
91541
- this.forceClosePooledSocket(tag, this.logger).catch(() => {});
91912
+ this.logger?.warn?.(`[SocketPool] ${count} consecutive stream timeouts on tag=${tag2}, force-closing socket`);
91913
+ this.consecutiveStreamTimeouts.delete(tag2);
91914
+ this.forceClosePooledSocket(tag2, this.logger).catch(() => {});
91542
91915
  }
91543
91916
  }
91544
91917
  /**
@@ -92405,8 +92778,20 @@ ${xml}`);
92405
92778
  const support = supportResult.status === "fulfilled" ? supportResult.value : void 0;
92406
92779
  const abilities = abilitiesResult.status === "fulfilled" ? abilitiesResult.value : void 0;
92407
92780
  const supportItem = getSupportItemForChannel(support, ch);
92781
+ let model;
92782
+ try {
92783
+ const info = await this.getInfo(void 0, { timeoutMs: 5e3 });
92784
+ model = typeof info?.type === "string" ? info.type : void 0;
92785
+ } catch (e) {
92786
+ this.logger.debug("[ReolinkBaichuanApi] getDeviceCapabilities: getInfo(type) failed", {
92787
+ host: this.host,
92788
+ channel: ch,
92789
+ err: e instanceof Error ? e.message : String(e)
92790
+ });
92791
+ }
92408
92792
  const capabilities = computeDeviceCapabilities({
92409
92793
  channel: ch,
92794
+ ...model != null && { model },
92410
92795
  ...support != null && { support },
92411
92796
  ...abilities != null && { abilities }
92412
92797
  });
@@ -93175,7 +93560,7 @@ ${xml}`);
93175
93560
  * @returns Test results for all stream types and profiles
93176
93561
  */
93177
93562
  async testChannelStreams(channel, logger) {
93178
- const { testChannelStreams } = await import("./DiagnosticsTools-TID5M22B-9NV95vRN.mjs");
93563
+ const { testChannelStreams } = await import("./DiagnosticsTools-GOFDNGAY-9NV95vRN.mjs");
93179
93564
  return await testChannelStreams({
93180
93565
  api: this,
93181
93566
  channel: this.normalizeChannel(channel),
@@ -93191,7 +93576,7 @@ ${xml}`);
93191
93576
  * @returns Complete diagnostics for all channels and streams
93192
93577
  */
93193
93578
  async collectMultifocalDiagnostics(logger) {
93194
- const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-TID5M22B-9NV95vRN.mjs");
93579
+ const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-GOFDNGAY-9NV95vRN.mjs");
93195
93580
  return await collectMultifocalDiagnostics({
93196
93581
  api: this,
93197
93582
  logger
@@ -148318,6 +148703,17 @@ var ReolinkStreamProfileOptionsSchema = object({
148318
148703
  }).optional()
148319
148704
  });
148320
148705
  var reolinkCameraSchema = object({
148706
+ /**
148707
+ * Which preset `ptz.goHome` targets on THIS camera.
148708
+ *
148709
+ * Empty = nothing configured, and `goHome` then REFUSES. It used to go to a
148710
+ * hardcoded slot on a vendor "convention" this fleet contradicts: reolink
148711
+ * went to preset 0, which on device 592 holds "stanza"; amcrest went to
148712
+ * preset 1, which on device 3836 holds "Preset1". Both swallowed the failure
148713
+ * in a bare catch, so a head that never moved was indistinguishable from a
148714
+ * head that went home.
148715
+ */
148716
+ homePresetId: string().default("").describe("Preset id ptz.goHome targets (empty = goHome refuses)"),
148321
148717
  host: string().describe("Camera IP or hostname"),
148322
148718
  port: number().default(9e3).describe("Baichuan port (default 9000)"),
148323
148719
  username: string().default("admin").describe("Username"),
@@ -149608,6 +150004,20 @@ function coerceNumber(value) {
149608
150004
  return null;
149609
150005
  }
149610
150006
  /**
150007
+ * Baichuan's native PTZ speed scale is 1..63; `runPtz` maps the cap's
150008
+ * normalized 0..1 onto it. Named because the autotrack loop has to know the
150009
+ * GRANULARITY of the knob it turns -- a normalized 0.37 means something very
150010
+ * different on 63 steps than on amcrest's 8.
150011
+ */
150012
+ var BAICHUAN_SPEED_STEPS = 63;
150013
+ /**
150014
+ * How long one `ptz.move` pulse runs, ms -- the `autoStopMs` handed to
150015
+ * Baichuan. It is the DENOMINATOR of "how far did the head travel per pulse",
150016
+ * so it is exported through `getOptions` rather than left a literal no caller
150017
+ * could read.
150018
+ */
150019
+ var PTZ_MOVE_IMPULSE_MS = 200;
150020
+ /**
149611
150021
  * Per-device transient diagnostics blob populated from the lib's
149612
150022
  * `getOnlineUserSessionsForUi` + `getSocketPoolSummary` +
149613
150023
  * `getSocketPoolCooldownStatus` calls. NOT persisted — recomputed on
@@ -149949,6 +150359,48 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
149949
150359
  * during `adoptDevice`). Standalone cameras default to 0 so the
149950
150360
  * pre-Hub behavior is preserved verbatim.
149951
150361
  */
150362
+ /**
150363
+ * Which preset `goHome` targets, and who decided it.
150364
+ *
150365
+ * Baichuan names no home of its own, so the operator's pick is the only
150366
+ * answer there can be. With none, `none` -- and `goHome` refuses rather than
150367
+ * moving the head to whatever happens to sit in slot 0.
150368
+ */
150369
+ async resolveHomePreset() {
150370
+ const chosen = this.config.get("homePresetId") ?? "";
150371
+ if (chosen !== "") return {
150372
+ presetId: chosen,
150373
+ source: "operator"
150374
+ };
150375
+ if ((await this.readGuardStatus())?.valid === true) return {
150376
+ presetId: null,
150377
+ source: "native"
150378
+ };
150379
+ return {
150380
+ presetId: null,
150381
+ source: "none"
150382
+ };
150383
+ }
150384
+ /**
150385
+ * The camera's guard-point configuration, or `null` when it cannot be read.
150386
+ *
150387
+ * Never a fabricated "no guard configured": that answer decides whether
150388
+ * `goHome` moves the head, and an unreadable camera is not evidence that no
150389
+ * guard point exists (D393).
150390
+ */
150391
+ async readGuardStatus() {
150392
+ try {
150393
+ const api = await this.ensureApi();
150394
+ const channel = this.getChannel();
150395
+ return await api.getPtzGuard(channel);
150396
+ } catch (err) {
150397
+ this.ctx.logger.warn("reolink guard point unreadable — goHome will fall back", {
150398
+ tags: { deviceId: this.id },
150399
+ meta: { error: err instanceof Error ? err.message : String(err) }
150400
+ });
150401
+ return null;
150402
+ }
150403
+ }
149952
150404
  getChannel() {
149953
150405
  const ch = this.config.get("channel");
149954
150406
  if (typeof ch === "number" && ch >= 0) return ch;
@@ -153326,7 +153778,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
153326
153778
  const channel = this.getChannel();
153327
153779
  return (await api.getPtzPresets(channel)).map((p) => ({
153328
153780
  id: String(p.id),
153329
- name: p.name
153781
+ name: p.name,
153782
+ fn: null,
153783
+ writable: true
153330
153784
  }));
153331
153785
  } catch {
153332
153786
  return [];
@@ -153362,7 +153816,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
153362
153816
  hasTilt: false,
153363
153817
  hasZoom: false,
153364
153818
  supportsPresets: false,
153365
- hasAutofocus: false
153819
+ hasAutofocus: false,
153820
+ speedSteps: BAICHUAN_SPEED_STEPS,
153821
+ moveImpulseMs: PTZ_MOVE_IMPULSE_MS
153366
153822
  };
153367
153823
  const hasAutofocus = this.config.get("deviceCache")?.autoFocusSnapshot?.supported === true;
153368
153824
  return this.resolveCapOptions({
@@ -153375,7 +153831,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
153375
153831
  hasTilt: capabilities.hasTilt,
153376
153832
  hasZoom: capabilities.hasZoom,
153377
153833
  supportsPresets: capabilities.hasPresets,
153378
- hasAutofocus
153834
+ hasAutofocus,
153835
+ speedSteps: BAICHUAN_SPEED_STEPS,
153836
+ moveImpulseMs: PTZ_MOVE_IMPULSE_MS
153379
153837
  };
153380
153838
  },
153381
153839
  fallback: () => {
@@ -153385,42 +153843,89 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
153385
153843
  hasTilt: hasPtz,
153386
153844
  hasZoom: hasPtz,
153387
153845
  supportsPresets: hasPtz,
153388
- hasAutofocus
153846
+ hasAutofocus,
153847
+ speedSteps: BAICHUAN_SPEED_STEPS,
153848
+ moveImpulseMs: PTZ_MOVE_IMPULSE_MS
153389
153849
  };
153390
153850
  }
153391
153851
  });
153392
153852
  },
153393
153853
  goHome: async ({ deviceId }) => {
153394
153854
  if (deviceId !== this.id) return;
153395
- try {
153396
- const api = await this.ensureApi();
153397
- const channel = this.getChannel();
153398
- await api.moveToPtzPreset(channel, 0);
153399
- } catch {}
153855
+ const home = await this.resolveHomePreset();
153856
+ if (home.source === "native") {
153857
+ await (await this.ensureApi()).goToPtzGuard(this.getChannel());
153858
+ return;
153859
+ }
153860
+ if (home.source === "none" || home.presetId === null) throw new Error("No home preset configured for this camera, and it has no guard point set — set one in the PTZ settings, or place a guard point on the camera");
153861
+ const id = Number(home.presetId);
153862
+ if (!Number.isFinite(id)) throw new Error(`Configured home preset is not a Baichuan id: ${home.presetId}`);
153863
+ const api = await this.ensureApi();
153864
+ const channel = this.getChannel();
153865
+ await api.moveToPtzPreset(channel, id);
153866
+ },
153867
+ getHomePreset: async ({ deviceId }) => {
153868
+ if (deviceId !== this.id) return {
153869
+ presetId: null,
153870
+ source: "none"
153871
+ };
153872
+ return this.resolveHomePreset();
153873
+ },
153874
+ captureHomeHere: async ({ deviceId }) => {
153875
+ if (deviceId !== this.id) return;
153876
+ const current = await this.readGuardStatus();
153877
+ const api = await this.ensureApi();
153878
+ const channel = this.getChannel();
153879
+ await api.setPtzGuard({
153880
+ enabled: current?.enabled ?? false,
153881
+ timeoutSeconds: current?.timeoutSeconds ?? 60,
153882
+ setPosition: true
153883
+ }, channel);
153884
+ this.ctx.logger.info("reolink: guard point pinned to the current position", { tags: { deviceId: this.id } });
153885
+ },
153886
+ getHomeReturnSeconds: async ({ deviceId }) => {
153887
+ if (deviceId !== this.id) return null;
153888
+ const guard = await this.readGuardStatus();
153889
+ return guard === null ? null : guard.timeoutSeconds;
153890
+ },
153891
+ setHomeReturnSeconds: async ({ deviceId, seconds }) => {
153892
+ if (deviceId !== this.id) return;
153893
+ const current = await this.readGuardStatus();
153894
+ const api = await this.ensureApi();
153895
+ const channel = this.getChannel();
153896
+ await api.setPtzGuard({
153897
+ enabled: current?.enabled ?? true,
153898
+ timeoutSeconds: seconds,
153899
+ setPosition: false
153900
+ }, channel);
153901
+ },
153902
+ setHomePreset: async ({ deviceId, presetId }) => {
153903
+ if (deviceId !== this.id) return;
153904
+ await this.config.setAll({ homePresetId: presetId ?? "" });
153400
153905
  },
153401
153906
  getPosition: async ({ deviceId }) => {
153402
153907
  if (deviceId !== this.id) return {
153403
- pan: 0,
153404
- tilt: 0,
153405
- zoom: 0
153908
+ pan: null,
153909
+ tilt: null,
153910
+ zoom: null
153406
153911
  };
153407
153912
  return {
153408
- pan: 0,
153409
- tilt: 0,
153410
- zoom: 0
153913
+ pan: null,
153914
+ tilt: null,
153915
+ zoom: null
153411
153916
  };
153412
153917
  },
153413
153918
  getStatus: async ({ deviceId }) => {
153414
153919
  if (deviceId !== this.id) return {
153415
- pan: 0,
153416
- tilt: 0,
153417
- zoom: 0,
153920
+ pan: null,
153921
+ tilt: null,
153922
+ zoom: null,
153418
153923
  autofocus: false
153419
153924
  };
153420
153925
  return {
153421
- pan: 0,
153422
- tilt: 0,
153423
- zoom: 0,
153926
+ pan: null,
153927
+ tilt: null,
153928
+ zoom: null,
153424
153929
  autofocus: (this.config.get("deviceCache")?.autoFocusSnapshot)?.enabled === true
153425
153930
  };
153426
153931
  },
@@ -153471,13 +153976,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
153471
153976
  if (tilt !== void 0 && Math.abs(tilt) > .01) cmds.push(tilt > 0 ? "Up" : "Down");
153472
153977
  if (zoom !== void 0 && Math.abs(zoom) > .01) await this.runZoom(api, channel, zoom);
153473
153978
  if (cmds.length === 0) return;
153474
- const baichuanSpeed = speed === void 0 ? 32 : Math.max(1, Math.min(63, Math.round(speed * 63)));
153979
+ const baichuanSpeed = speed === void 0 ? Math.round(BAICHUAN_SPEED_STEPS / 2) : Math.max(1, Math.min(BAICHUAN_SPEED_STEPS, Math.round(speed * BAICHUAN_SPEED_STEPS)));
153475
153980
  for (const command of cmds) try {
153476
153981
  await api.ptz(channel, {
153477
153982
  action: "start",
153478
153983
  command,
153479
153984
  speed: baichuanSpeed,
153480
- ...continuous ? {} : { autoStopMs: 200 }
153985
+ ...continuous ? {} : { autoStopMs: PTZ_MOVE_IMPULSE_MS }
153481
153986
  });
153482
153987
  } catch (err) {
153483
153988
  this.ctx.logger.warn("Baichuan ptz command failed", {