@camstack/addon-provider-reolink 1.2.185 → 1.2.186

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.js CHANGED
@@ -5424,7 +5424,7 @@ var ZodIssueCode = {
5424
5424
  var ZodFirstPartyTypeKind;
5425
5425
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5426
5426
  //#endregion
5427
- //#region ../types/dist/sleep-BBF1iXUi.mjs
5427
+ //#region ../types/dist/sleep-DRqxtc0C.mjs
5428
5428
  /**
5429
5429
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5430
5430
  * window to float samples (D455).
@@ -8297,6 +8297,24 @@ function collectZodSecretPaths(schema, prefix = "") {
8297
8297
  }
8298
8298
  return [...new Set(paths)];
8299
8299
  }
8300
+ object({
8301
+ code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
8302
+ params: record(string(), union([string(), number()])).optional()
8303
+ });
8304
+ var CamstackError = class extends Error {
8305
+ i18nCode;
8306
+ i18nParams;
8307
+ constructor(code, params, englishMessage) {
8308
+ super(englishMessage);
8309
+ this.name = "CamstackError";
8310
+ this.i18nCode = code;
8311
+ this.i18nParams = params;
8312
+ }
8313
+ };
8314
+ /** `throw userError('device.not-found', { id }, 'Device 12 not found')` */
8315
+ function userError(code, params, englishFallback) {
8316
+ return new CamstackError(code, params, englishFallback);
8317
+ }
8300
8318
  /**
8301
8319
  * Promise-based timer helpers — used everywhere the codebase needs to
8302
8320
  * wait, back off, or schedule a retry. Before these helpers landed, each
@@ -8322,7 +8340,7 @@ function sleep$2(ms) {
8322
8340
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
8323
8341
  }
8324
8342
  //#endregion
8325
- //#region ../types/dist/err-msg-DX6i_MY4.mjs
8343
+ //#region ../types/dist/err-msg-DLerKiVO.mjs
8326
8344
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
8327
8345
  DeviceType["Camera"] = "camera";
8328
8346
  DeviceType["Hub"] = "hub";
@@ -8572,6 +8590,22 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8572
8590
  DeviceRole["Nightvision"] = "nightvision";
8573
8591
  DeviceRole["PrivacyMask"] = "privacy-mask";
8574
8592
  DeviceRole["Doorbell"] = "doorbell";
8593
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
8594
+ DeviceRole["CoverBlind"] = "cover-blind";
8595
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
8596
+ DeviceRole["CoverShutter"] = "cover-shutter";
8597
+ /** Exterior awning. HA `device_class: awning`. */
8598
+ DeviceRole["CoverAwning"] = "cover-awning";
8599
+ /** Curtain / drape. HA `device_class: curtain`. */
8600
+ DeviceRole["CoverCurtain"] = "cover-curtain";
8601
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
8602
+ DeviceRole["CoverGarage"] = "cover-garage";
8603
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
8604
+ DeviceRole["CoverGate"] = "cover-gate";
8605
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
8606
+ DeviceRole["CoverDoor"] = "cover-door";
8607
+ /** Window. HA `device_class: window`. */
8608
+ DeviceRole["CoverWindow"] = "cover-window";
8575
8609
  /** Virtual HA toggle (input_boolean.*) — distinguishable from a
8576
8610
  * real Switch device for UI rendering / export adapters. */
8577
8611
  DeviceRole["BinaryHelper"] = "binary-helper";
@@ -8642,7 +8676,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8642
8676
  return DeviceRole;
8643
8677
  }({});
8644
8678
  //#endregion
8645
- //#region ../types/dist/composition-C-WpVYTX.mjs
8679
+ //#region ../types/dist/composition-EQ9Ok0Ee.mjs
8646
8680
  /**
8647
8681
  * The ffmpeg build every CamStack node downloads, per platform and arch.
8648
8682
  *
@@ -12870,6 +12904,177 @@ var ConvertResultSchema = object({
12870
12904
  files: array(string()).readonly()
12871
12905
  })).readonly()
12872
12906
  });
12907
+ _enum(["new-session", "step-up"]);
12908
+ /** The languages the hub's app landing pages are written in (D858). */
12909
+ var AppPageLangSchema = _enum(["en", "it"]);
12910
+ /** How a native app starts one login method (D858), as `auth.listLoginMethods` reports it. */
12911
+ var AppLoginPathSchema = discriminatedUnion("kind", [object({ kind: literal("native") }), object({
12912
+ kind: literal("browser"),
12913
+ startPath: string().startsWith("/")
12914
+ })]);
12915
+ /**
12916
+ * Device certificates (D855) — the wire contract shared by the hub, the admin
12917
+ * UI and the native viewer.
12918
+ *
12919
+ * A device (a phone) generates its OWN P-256 key pair in hardware (Secure
12920
+ * Enclave / Android Keystore / a non-extractable WebCrypto key). The private
12921
+ * key never travels. The hub signs the PUBLIC key into an X.509 leaf under a
12922
+ * dedicated device CA; the device later proves possession of the key by
12923
+ * signing a hub-issued nonce, and the hub answers with the same session a
12924
+ * password login opens.
12925
+ *
12926
+ * Everything on the wire is a small string (a PEM, a base64url signature):
12927
+ * D613 is about bulk bytes, and a certificate is ~600 bytes.
12928
+ */
12929
+ /** Signature algorithm of every device credential: ECDSA, curve P-256, SHA-256. */
12930
+ var DEVICE_CERT_SIGNATURE = "ecdsa-p256-sha256";
12931
+ /** First line of every signed message; a change of format bumps the version. */
12932
+ var DEVICE_SIGNATURE_MESSAGE_VERSION = "camstack-device-cert-v1";
12933
+ _enum([
12934
+ "login",
12935
+ "renew",
12936
+ "revoke"
12937
+ ]);
12938
+ var DevicePlatformSchema = _enum([
12939
+ "ios",
12940
+ "android",
12941
+ "web",
12942
+ "other"
12943
+ ]);
12944
+ var Base64UrlSchema = string().min(1).max(4096).regex(/^[A-Za-z0-9_-]+$/);
12945
+ var PemSchema = string().min(1).max(16384);
12946
+ /**
12947
+ * Why a device-certificate endpoint said no. Distinct from a transport / 5xx
12948
+ * failure: those carry no reason and mean "try again", never "the credential is
12949
+ * dead". The viewer switches on this exhaustively.
12950
+ */
12951
+ var DeviceRefusalReasonSchema = _enum([
12952
+ "revoked",
12953
+ "unknown-credential",
12954
+ "expired",
12955
+ "bad-signature",
12956
+ "nonce-invalid",
12957
+ "rate-limited",
12958
+ "invalid-certificate",
12959
+ "superseded",
12960
+ "user-gone",
12961
+ "unavailable",
12962
+ "code-invalid",
12963
+ "self-enrollment-disabled",
12964
+ "step-up-required",
12965
+ "bad-public-key"
12966
+ ]);
12967
+ var DeviceRefusalSchema = object({
12968
+ ok: literal(false),
12969
+ reason: DeviceRefusalReasonSchema,
12970
+ definitive: boolean(),
12971
+ retryAfterSeconds: number().optional()
12972
+ });
12973
+ object({
12974
+ code: string().min(1).max(256),
12975
+ /** SubjectPublicKeyInfo DER of the device's P-256 public key, base64url. */
12976
+ publicKeySpki: Base64UrlSchema,
12977
+ deviceName: string().min(1).max(80).optional(),
12978
+ platform: DevicePlatformSchema
12979
+ });
12980
+ discriminatedUnion("ok", [object({
12981
+ credentialId: string(),
12982
+ certPem: PemSchema,
12983
+ /** The device CA certificate the leaf chains to. */
12984
+ chainPem: PemSchema,
12985
+ notAfter: number(),
12986
+ user: object({
12987
+ id: string(),
12988
+ username: string()
12989
+ })
12990
+ }).extend({ ok: literal(true) }), DeviceRefusalSchema]);
12991
+ object({
12992
+ /** The single-use step-up proof (`auth.deviceEnrollProofFrom…`). */
12993
+ proof: string().min(1).max(512),
12994
+ publicKeySpki: Base64UrlSchema,
12995
+ deviceName: string().min(1).max(80),
12996
+ platform: DevicePlatformSchema
12997
+ });
12998
+ object({
12999
+ proof: string(),
13000
+ expiresAt: number()
13001
+ });
13002
+ discriminatedUnion("ok", [object({
13003
+ nonce: string(),
13004
+ /** The origin to put in the signed message. The app compares it to the hub it dialled. */
13005
+ origin: string(),
13006
+ expiresAt: number(),
13007
+ signature: literal(DEVICE_CERT_SIGNATURE)
13008
+ }).extend({ ok: literal(true) }), DeviceRefusalSchema]);
13009
+ object({
13010
+ certPem: PemSchema,
13011
+ nonce: string().min(1).max(256),
13012
+ /** ECDSA P-256 SHA-256 over {@link buildDeviceSignatureMessage}; raw r||s or DER, base64url. */
13013
+ signature: Base64UrlSchema
13014
+ });
13015
+ discriminatedUnion("ok", [object({
13016
+ credentialId: string(),
13017
+ certPem: PemSchema,
13018
+ chainPem: PemSchema,
13019
+ notAfter: number()
13020
+ }).extend({ ok: literal(true) }), DeviceRefusalSchema]);
13021
+ discriminatedUnion("ok", [object({
13022
+ ok: literal(true),
13023
+ credentialId: string(),
13024
+ sessionsEnded: number().int()
13025
+ }), DeviceRefusalSchema]);
13026
+ var DeviceCredentialStatusSchema = _enum([
13027
+ "active",
13028
+ "expired",
13029
+ "revoked"
13030
+ ]);
13031
+ var DeviceCredentialSchema = object({
13032
+ id: string(),
13033
+ userId: string(),
13034
+ username: string().nullable(),
13035
+ deviceName: string(),
13036
+ platform: DevicePlatformSchema,
13037
+ enrolledVia: _enum(["qr", "self"]),
13038
+ /** SHA-256 of the leaf certificate DER, colon-hex. */
13039
+ fingerprint: string(),
13040
+ issuedAt: number(),
13041
+ expiresAt: number(),
13042
+ lastUsedAt: number().nullable(),
13043
+ lastUsedFrom: string().nullable(),
13044
+ revokedAt: number().nullable(),
13045
+ /** `admin:<userId>` | `user:<userId>` | `device` | `user-deleted`. */
13046
+ revokedBy: string().nullable(),
13047
+ status: DeviceCredentialStatusSchema
13048
+ });
13049
+ var StartEnrollmentOutputSchema = object({
13050
+ /** Shown once; only its hash is kept. */
13051
+ code: string(),
13052
+ expiresAt: number(),
13053
+ /** `camstack://enroll?hub=…&code=…` — what the QR encodes. */
13054
+ enrollUrl: string(),
13055
+ /** The hub origin in the link, or `null` when the hub could not name one (the link then has no `hub`). */
13056
+ hubOrigin: string().nullable(),
13057
+ /** Every origin the apps may try (role `appConnections`). */
13058
+ hubOrigins: array(string())
13059
+ });
13060
+ object({
13061
+ kind: literal("device-certificate"),
13062
+ id: literal("device-certificate"),
13063
+ label: string(),
13064
+ stage: literal("primary"),
13065
+ /** tRPC paths under the hub's `auth` router, so a client names no literal. */
13066
+ challengePath: literal("auth.getDeviceChallenge"),
13067
+ revokePath: literal("auth.revokeOwnDeviceCertificate"),
13068
+ enrollSelfPath: literal("auth.enrollSelf"),
13069
+ loginPath: literal("auth.loginWithDeviceCertificate"),
13070
+ renewPath: literal("auth.renewDeviceCertificate"),
13071
+ signature: literal(DEVICE_CERT_SIGNATURE),
13072
+ messageVersion: literal(DEVICE_SIGNATURE_MESSAGE_VERSION),
13073
+ /** Which clients can execute it: a native app silently, a browser with a non-extractable WebCrypto key. */
13074
+ clients: array(_enum(["native", "web"])),
13075
+ /** The app executes it itself, silently (D858). */
13076
+ app: AppLoginPathSchema.nullable()
13077
+ });
12873
13078
  var ExpressionBindingSourceSchema = union([
12874
13079
  object({
12875
13080
  kind: literal("field").optional(),
@@ -13198,7 +13403,9 @@ var ConfigTabDeclarationSchema = object({
13198
13403
  var ConfigSectionWithValuesSchema = object({
13199
13404
  id: string(),
13200
13405
  title: string(),
13406
+ titleKey: string().optional(),
13201
13407
  description: string().optional(),
13408
+ descriptionKey: string().optional(),
13202
13409
  style: _enum(["card", "accordion"]).optional(),
13203
13410
  defaultCollapsed: boolean().optional(),
13204
13411
  columns: union([
@@ -15694,7 +15901,11 @@ var cameraStreamsCapability = {
15694
15901
  * See `RuntimeStateDurability`. Enforced by
15695
15902
  * `scripts/check-runtime-state-durability.ts`.
15696
15903
  */
15697
- durability: "session"
15904
+ durability: "session",
15905
+ /**
15906
+ * History (D850): none — `online` duplicates device-status.online (one authority); slot data is nested enum/string.
15907
+ */
15908
+ history: "none"
15698
15909
  };
15699
15910
  EventCategory.DoorbellOnPressed;
15700
15911
  var CompositionEventPlanSchema = object({
@@ -16526,7 +16737,345 @@ method(object({
16526
16737
  }), method(object({ refs: RefListSchema }), KeychainResolveResultSchema, { auth: "admin" }), method(object({ refs: RefListSchema }), object({ deleted: number().int() }), {
16527
16738
  kind: "mutation",
16528
16739
  auth: "admin"
16740
+ }), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
16741
+ kind: "mutation",
16742
+ auth: "admin"
16529
16743
  }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
16744
+ /**
16745
+ * device-history — the ONE mechanism for numeric history (D850).
16746
+ *
16747
+ * A device value is recorded because its capability says so (the `history`
16748
+ * block of a `*.cap.ts`, captured hub-side from `DeviceStateChanged`); any
16749
+ * other series is DECLARED by the addon that produces it and fed with points
16750
+ * (`record`) or closed five-minute buckets (`recordBuckets`). Rollups 5m / 1h /
16751
+ * 1d are computed here and nowhere else. Storage is `history.db` (D851).
16752
+ *
16753
+ * Who the PRODUCER is: `declareSeries`, `record` and `recordBuckets` are owner
16754
+ * checked against the `callerAddonId` the transport stamps into a call that
16755
+ * crossed a process (never in an input schema, so a session cannot supply it).
16756
+ * A call with no stamp is refused `not-owner`. The methods are `admin` so a
16757
+ * non-admin session never reaches them; an addon's `ctx.api` call bypasses the
16758
+ * tier by design.
16759
+ */
16760
+ /** Whose history: a real device, or a cluster node (load, memory). */
16761
+ var DeviceHistorySubjectSchema = discriminatedUnion("kind", [object({
16762
+ kind: literal("device"),
16763
+ deviceId: number().int()
16764
+ }), object({
16765
+ kind: literal("node"),
16766
+ nodeId: string().min(1)
16767
+ })]);
16768
+ var DeviceHistoryKindSchema = _enum([
16769
+ "gauge",
16770
+ "counter",
16771
+ "boolean"
16772
+ ]);
16773
+ var DeviceHistoryResolutionSchema = _enum([
16774
+ "raw",
16775
+ "5m",
16776
+ "1h",
16777
+ "1d"
16778
+ ]);
16779
+ /** Why a producer's point or bucket was not stored. Counted, never silent (D391). */
16780
+ var DeviceHistoryRefusalReasonSchema = _enum([
16781
+ "undeclared",
16782
+ "not-owner",
16783
+ "opted-out",
16784
+ "not-a-real-device",
16785
+ "rate",
16786
+ "ingest-mismatch",
16787
+ "out-of-range"
16788
+ ]);
16789
+ var RefusalSchema = object({
16790
+ key: string(),
16791
+ reason: DeviceHistoryRefusalReasonSchema,
16792
+ count: number().int()
16793
+ });
16794
+ var IngestResultSchema = object({
16795
+ accepted: number().int(),
16796
+ refused: array(RefusalSchema).readonly()
16797
+ });
16798
+ var SeriesInfoSchema = object({
16799
+ key: string(),
16800
+ scope: string(),
16801
+ kind: DeviceHistoryKindSchema,
16802
+ /** `null` is unknown (a `unitFrom` series whose device does not state a unit right now), never a guess. */
16803
+ unit: string().nullable(),
16804
+ displayName: string(),
16805
+ source: _enum(["cap", "addon"]),
16806
+ firstTs: number(),
16807
+ lastTs: number()
16808
+ });
16809
+ var QueryPointSchema = object({
16810
+ /** Raw: the point's time. Rollup: the bucket start. */
16811
+ t: number(),
16812
+ samples: number().int(),
16813
+ /** `sum / samples`. */
16814
+ mean: number().nullable(),
16815
+ /** Time-weighted mean (`tw_sum / tw_ms`); `null` for bucket-ingested series. For a boolean: the fraction of time true. */
16816
+ twMean: number().nullable(),
16817
+ min: number(),
16818
+ max: number(),
16819
+ /** `null` is unknown, never zero (D393). */
16820
+ last: number().nullable()
16821
+ });
16822
+ var QuerySeriesSchema = object({
16823
+ scope: string(),
16824
+ /** The last point STRICTLY BEFORE `from` in the chosen tier, so a step chart can start at the held value. */
16825
+ before: object({
16826
+ t: number(),
16827
+ value: number().nullable()
16828
+ }).nullable(),
16829
+ points: array(QueryPointSchema).readonly()
16830
+ });
16831
+ /** The window part of a query, shared by the device and node forms. */
16832
+ var QueryWindowShape = {
16833
+ key: string(),
16834
+ scope: string().optional(),
16835
+ from: number(),
16836
+ to: number(),
16837
+ resolution: union([DeviceHistoryResolutionSchema, literal("auto")]).default("auto"),
16838
+ maxPoints: number().int().positive().max(5e3).default(1e3)
16839
+ };
16840
+ var QueryResultSchema = object({
16841
+ resolution: DeviceHistoryResolutionSchema,
16842
+ series: array(QuerySeriesSchema).readonly(),
16843
+ truncated: boolean()
16844
+ });
16845
+ var TierStatusSchema = object({
16846
+ tier: DeviceHistoryResolutionSchema,
16847
+ /** `null` until the first background count answered. */
16848
+ rows: number().int().nullable(),
16849
+ /** `rows` times a fixed per-row estimate — approximate, labelled so. */
16850
+ approxBytes: number().nullable(),
16851
+ countedAt: number().nullable()
16852
+ });
16853
+ /** Operator/diagnostic view: rows per tier, last runs, refusals and drops since boot. */
16854
+ var DeviceHistoryStatusSchema = object({
16855
+ startedAt: number(),
16856
+ /** The store is declared, series loaded and the first reconcile has answered. */
16857
+ ready: boolean(),
16858
+ series: number().int(),
16859
+ metrics: number().int(),
16860
+ optedOutDevices: number().int(),
16861
+ tiers: array(TierStatusSchema).readonly(),
16862
+ capture: object({
16863
+ eventsSeen: number().int(),
16864
+ pointsEmitted: number().int(),
16865
+ pointsWritten: number().int(),
16866
+ pointsDropped: number().int(),
16867
+ bufferDepth: number().int(),
16868
+ trailingEmitted: number().int(),
16869
+ reconciledPoints: number().int(),
16870
+ reconcileRuns: number().int(),
16871
+ reconcileFailures: number().int(),
16872
+ lastReconcileAt: number().nullable(),
16873
+ lastFlushAt: number().nullable(),
16874
+ flushFailures: number().int(),
16875
+ skipped: record(string(), number().int())
16876
+ }),
16877
+ ingest: object({
16878
+ accepted: number().int(),
16879
+ refused: record(string(), number().int())
16880
+ }),
16881
+ rollup: object({
16882
+ last5mAt: number().nullable(),
16883
+ last1hAt: number().nullable(),
16884
+ last1dAt: number().nullable(),
16885
+ errors: number().int(),
16886
+ dirty5m: number().int(),
16887
+ dirty1h: number().int(),
16888
+ dirty1d: number().int()
16889
+ }),
16890
+ retention: object({
16891
+ lastRunAt: number().nullable(),
16892
+ deleted: record(string(), number().int()),
16893
+ purgedSeries: number().int()
16894
+ }),
16895
+ directory: object({
16896
+ devices: number().int(),
16897
+ lastRefreshAt: number().nullable()
16898
+ })
16899
+ });
16900
+ method(object({ deviceId: number().int() }), array(SeriesInfoSchema).readonly()), method(object({ nodeId: string().min(1) }), array(SeriesInfoSchema).readonly(), { auth: "admin" }), method(object({
16901
+ deviceId: number().int(),
16902
+ ...QueryWindowShape
16903
+ }), QueryResultSchema), method(object({
16904
+ nodeId: string().min(1),
16905
+ ...QueryWindowShape
16906
+ }), QueryResultSchema, { auth: "admin" }), method(object({
16907
+ name: string().regex(/^[a-z0-9-]+$/),
16908
+ subjectKind: _enum(["device", "node"]),
16909
+ kind: DeviceHistoryKindSchema,
16910
+ unit: string().optional(),
16911
+ displayName: string().min(1),
16912
+ ingest: _enum(["points", "buckets"])
16913
+ }), object({ key: string() }), {
16914
+ kind: "mutation",
16915
+ auth: "admin"
16916
+ }), method(object({ points: array(object({
16917
+ key: string(),
16918
+ subject: DeviceHistorySubjectSchema,
16919
+ scope: string().default(""),
16920
+ ts: number(),
16921
+ value: number()
16922
+ })).max(5e3) }), IngestResultSchema, {
16923
+ kind: "mutation",
16924
+ auth: "admin"
16925
+ }), method(object({ buckets: array(object({
16926
+ key: string(),
16927
+ subject: DeviceHistorySubjectSchema,
16928
+ scope: string().default(""),
16929
+ bucketStart: number(),
16930
+ samples: number().int().positive(),
16931
+ sum: number(),
16932
+ min: number(),
16933
+ max: number(),
16934
+ last: number().nullable(),
16935
+ lastTs: number().nullable()
16936
+ })).max(5e3) }), IngestResultSchema, {
16937
+ kind: "mutation",
16938
+ auth: "admin"
16939
+ }), method(object({ deviceId: number().int() }), object({ enabled: boolean() })), method(object({
16940
+ deviceId: number().int(),
16941
+ enabled: boolean()
16942
+ }), object({ enabled: boolean() }), {
16943
+ kind: "mutation",
16944
+ auth: "admin"
16945
+ }), method(object({}), DeviceHistoryStatusSchema, { auth: "admin" });
16946
+ var DeviceCaStatusSchema = discriminatedUnion("state", [
16947
+ object({ state: literal("absent") }),
16948
+ object({
16949
+ state: literal("ready"),
16950
+ subject: string(),
16951
+ fingerprintSha256: string(),
16952
+ validTo: string()
16953
+ }),
16954
+ object({
16955
+ state: literal("unavailable"),
16956
+ reason: string()
16957
+ })
16958
+ ]);
16959
+ var DeviceCertificateSettingsSchema = object({ selfEnrollment: _enum(["allowed", "disabled"]) });
16960
+ method(object({
16961
+ userId: string().min(1).optional(),
16962
+ includeRevoked: boolean().default(false)
16963
+ }), array(DeviceCredentialSchema).readonly(), { auth: "admin" }), method(object({
16964
+ userId: string().min(1),
16965
+ deviceName: string().min(1).max(80)
16966
+ }), StartEnrollmentOutputSchema, {
16967
+ kind: "mutation",
16968
+ auth: "admin",
16969
+ caller: "required"
16970
+ }), method(object({ id: string().min(1) }), object({
16971
+ revoked: boolean(),
16972
+ sessionsEnded: number().int(),
16973
+ status: DeviceCredentialStatusSchema
16974
+ }), {
16975
+ kind: "mutation",
16976
+ auth: "admin",
16977
+ caller: "required"
16978
+ }), method(object({}), DeviceCertificateSettingsSchema, { auth: "admin" }), method(DeviceCertificateSettingsSchema, DeviceCertificateSettingsSchema, {
16979
+ kind: "mutation",
16980
+ auth: "admin"
16981
+ }), method(object({ id: string().min(1) }), object({ result: _enum([
16982
+ "deleted",
16983
+ "live",
16984
+ "unknown"
16985
+ ]) }), {
16986
+ kind: "mutation",
16987
+ auth: "admin",
16988
+ caller: "required"
16989
+ }), method(object({}), DeviceCaStatusSchema, { auth: "admin" }), method(object({
16990
+ id: string().min(1),
16991
+ deviceName: string().min(1).max(80)
16992
+ }), object({ renamed: boolean() }), {
16993
+ kind: "mutation",
16994
+ auth: "admin"
16995
+ });
16996
+ /**
16997
+ * `node-certificates` - what a node holds and the two renewals the hub drives
16998
+ * (D856): the node's MESH leaf (mutual TLS to the hub) and its HTTPS leaf (issued
16999
+ * from the hub's local CA so a device that trusts the hub root trusts the node).
17000
+ *
17001
+ * Provided by the AGENT RUNTIME (the synthetic `agent-runtime` addon id, like
17002
+ * `server-management`): the hub reaches it through the ordinary node-pinned
17003
+ * capability call - no new Moleculer action. The hub's own certificates are
17004
+ * handled in-process by the hub (`certificates` router), so this cap has no hub
17005
+ * provider.
17006
+ *
17007
+ * No private key ever crosses the mesh: the node generates its key pair, sends a
17008
+ * CSR, and the hub returns only the signed certificate.
17009
+ */
17010
+ var MeshLeafInfoSchema = object({
17011
+ cn: string(),
17012
+ serial: string(),
17013
+ fingerprintSha256: string(),
17014
+ notBefore: number(),
17015
+ notAfter: number()
17016
+ });
17017
+ var HttpsLeafInfoSchema = object({
17018
+ fingerprintSha256: string(),
17019
+ issuer: string(),
17020
+ sans: array(string()),
17021
+ notBefore: number(),
17022
+ notAfter: number()
17023
+ });
17024
+ var NodeHttpsStateSchema = discriminatedUnion("state", [object({
17025
+ state: literal("served"),
17026
+ leaf: HttpsLeafInfoSchema
17027
+ }), object({
17028
+ state: literal("not-issued"),
17029
+ reason: string()
17030
+ })]);
17031
+ method(object({}), object({
17032
+ nodeId: string(),
17033
+ mesh: MeshLeafInfoSchema.nullable(),
17034
+ https: NodeHttpsStateSchema
17035
+ }), { auth: "admin" }), method(object({}), object({ csrPem: string() }), {
17036
+ kind: "mutation",
17037
+ auth: "admin"
17038
+ }), method(object({
17039
+ certPem: string().min(1),
17040
+ caCertPem: string().min(1),
17041
+ confirmWithinMs: number().int().min(1e4).max(6e5)
17042
+ }), object({
17043
+ fingerprintSha256: string(),
17044
+ serial: string()
17045
+ }), {
17046
+ kind: "mutation",
17047
+ auth: "admin"
17048
+ }), method(object({ fingerprintSha256: string() }), object({ confirmed: boolean() }), {
17049
+ kind: "mutation",
17050
+ auth: "admin"
17051
+ }), method(object({}), object({ rolledBack: boolean() }), {
17052
+ kind: "mutation",
17053
+ auth: "admin"
17054
+ }), method(object({}), object({
17055
+ csrPem: string(),
17056
+ hostnames: array(string()),
17057
+ addresses: array(string()),
17058
+ /** SHA-256 of the hub local CA this node pinned at its first install; `null` before one. */
17059
+ pinnedCaFingerprintSha256: string().nullable()
17060
+ }), {
17061
+ kind: "mutation",
17062
+ auth: "admin"
17063
+ }), method(object({}), object({ ok: literal(true) }), {
17064
+ kind: "mutation",
17065
+ auth: "admin"
17066
+ }), method(object({
17067
+ certPem: string().min(1),
17068
+ caCertPem: string().min(1),
17069
+ /**
17070
+ * The hub signals that its local CA was rotated (D227) and this leaf chains to the NEW
17071
+ * one. Only the hub, over the authenticated mesh, can say so; a leaf with a different CA
17072
+ * and no signal is refused.
17073
+ */
17074
+ caRotation: boolean().optional()
17075
+ }), HttpsLeafInfoSchema, {
17076
+ kind: "mutation",
17077
+ auth: "admin"
17078
+ });
16530
17079
  var KeychainKeySourceSchema = object({
16531
17080
  kind: _enum([
16532
17081
  "config-file",
@@ -17114,6 +17663,10 @@ var deviceDiscoveryCapability = {
17114
17663
  * `scripts/check-runtime-state-durability.ts`.
17115
17664
  */
17116
17665
  durability: "session",
17666
+ /**
17667
+ * History (D850): none — inventory.
17668
+ */
17669
+ history: "none",
17117
17670
  methods: {
17118
17671
  /**
17119
17672
  * Snapshot of the current `discovered` list. Returns the
@@ -37895,6 +38448,38 @@ var DeviceRegisterPayloadSchema = object({
37895
38448
  * omits it and the keyword backstop applies. */
37896
38449
  secretConfigPaths: array(string()).optional()
37897
38450
  });
38451
+ /** One deleted device, kept by device-manager so an addon that missed the
38452
+ * deletion can still learn of it (D865). Written by the delete path ONLY. */
38453
+ var DeviceTombstoneSchema = object({
38454
+ deviceId: number().int().positive(),
38455
+ /** Epoch ms the device was deleted. */
38456
+ deletedAt: number(),
38457
+ /** The addon that owned the device when it was deleted. */
38458
+ providerAddonId: string()
38459
+ });
38460
+ /** Data held for one device that device-manager no longer knows. */
38461
+ var OrphanDeviceDataSchema = object({
38462
+ deviceId: number().int().positive(),
38463
+ /** Rows/files per store (`<addonId>:<store>`). */
38464
+ stores: record(string(), number().int().nonnegative())
38465
+ });
38466
+ var CleanupDeletedDevicesResultSchema = object({
38467
+ /**
38468
+ * `skipped` — the absence check could not be trusted (a device-list read
38469
+ * failed or the two reads disagreed); nothing was listed or deleted.
38470
+ * `dry-run` — `devices` is what a confirmed run would delete.
38471
+ * `cleaned` — `devices` is what was deleted.
38472
+ */
38473
+ status: _enum([
38474
+ "skipped",
38475
+ "dry-run",
38476
+ "cleaned"
38477
+ ]),
38478
+ reason: string().nullable(),
38479
+ devices: array(OrphanDeviceDataSchema),
38480
+ /** Implementers that could not be asked (their stores are not in `devices`). */
38481
+ unreachable: array(string())
38482
+ });
37898
38483
  var DeviceRemovePayloadSchema = object({ deviceId: number() });
37899
38484
  var DevicePersistConfigPayloadSchema = object({
37900
38485
  deviceId: number(),
@@ -38133,6 +38718,9 @@ method(object({
38133
38718
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
38134
38719
  kind: "mutation",
38135
38720
  auth: "admin"
38721
+ }), method(object({ sinceMs: number() }), array(DeviceTombstoneSchema).readonly(), { auth: "admin" }), method(object({ confirm: boolean() }), CleanupDeletedDevicesResultSchema, {
38722
+ kind: "mutation",
38723
+ auth: "admin"
38136
38724
  }), method(object({ integrationId: string() }), object({ removed: number() }), {
38137
38725
  kind: "mutation",
38138
38726
  auth: "admin"
@@ -38595,6 +39183,72 @@ method(object({
38595
39183
  auth: "admin"
38596
39184
  });
38597
39185
  /**
39186
+ * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
39187
+ * so it can delete what it keeps for that device (D865).
39188
+ *
39189
+ * ## Why an event was not enough
39190
+ *
39191
+ * `DeviceUnregistered` is telemetry and may be dropped (D8, D11). An addon that
39192
+ * hung its per-device clean-up on it left durable garbage whenever the event was
39193
+ * lost, the addon was down, or it was restarting at that moment — footage,
39194
+ * analytics rows and settings of device 4483 outlived the device. Loss of this
39195
+ * signal corrupts durable state, so it travels by RPC with a recorded fact
39196
+ * behind it (D8: "if loss would corrupt durable state, use RPC plus a reconcile").
39197
+ *
39198
+ * ## The three pieces
39199
+ *
39200
+ * 1. device-manager records a TOMBSTONE per deleted device
39201
+ * (`deviceManager.listDeletedSince`). The tombstone is written by the delete
39202
+ * path only — a device that is unregistered, offline or whose provider is
39203
+ * restarting is NOT deleted and gets none.
39204
+ * 2. After a delete, device-manager calls `onDeviceRemoved` on EVERY provider of
39205
+ * this collection, one by one, with bounded retries, logged.
39206
+ * 3. An implementer that was down asks `deviceManager.listDeletedSince` at start
39207
+ * (and periodically) and runs its own `onDeviceRemoved` for each tombstone it
39208
+ * may have missed. Reconcile is driven by tombstones, never by "the device is
39209
+ * not in the current list": a failed or partial list read must not destroy data.
39210
+ *
39211
+ * ## Contract for an implementer
39212
+ *
39213
+ * - **Idempotent.** It is called again by a retry, by a reconcile and by the
39214
+ * admin cleanup of data that predates tombstones. A device with nothing left
39215
+ * answers zero counts.
39216
+ * - **One key per store** in `counts`, deleted rows/files per store, so the log
39217
+ * line and the admin dry run can say where the data was.
39218
+ * - **Training datasets are NOT device data** and are never deleted by a hook.
39219
+ * - **Per-device SETTINGS are not hooked.** Configuration lives in the standard
39220
+ * per-device store (`ctx.settings.*DeviceStore`), which device-manager clears
39221
+ * for every addon centrally at delete time. This hook is for device DATA
39222
+ * that is not settings: footage, event media, analytics rows, history.
39223
+ * - A provider that cannot tell is allowed to throw; the caller retries it.
39224
+ *
39225
+ * `listDeviceData` is the read side of the admin "Clean data of deleted devices"
39226
+ * action: which device ids this addon holds data for, and how much per store. It
39227
+ * never deletes. device-manager decides which of those ids are absent from the
39228
+ * fleet, from TWO consistent reads of the device list (D49).
39229
+ */
39230
+ /** What a hook is told about the deletion. */
39231
+ var DeviceRemovedInputSchema = object({
39232
+ deviceId: number().int().positive(),
39233
+ /** Epoch ms the device was deleted (the tombstone's `deletedAt`). */
39234
+ deletedAt: number(),
39235
+ /** The addon that owned the device, as device-manager last knew it. */
39236
+ providerAddonId: string()
39237
+ });
39238
+ /** Rows/files/refs deleted, per store this implementer owns. */
39239
+ var DeviceRemovedResultSchema = object({ counts: record(string(), number().int().nonnegative()) });
39240
+ /** One (store, device) pair an implementer holds data for. */
39241
+ var DeviceDataEntrySchema = object({
39242
+ store: string(),
39243
+ deviceId: number().int().positive(),
39244
+ /** Rows (or files) held for the device in the store. */
39245
+ count: number().int().nonnegative()
39246
+ });
39247
+ method(DeviceRemovedInputSchema, DeviceRemovedResultSchema, {
39248
+ kind: "mutation",
39249
+ auth: "admin"
39250
+ }), method(_void(), array(DeviceDataEntrySchema).readonly(), { auth: "admin" });
39251
+ /**
38598
39252
  * `failure-contribution` — the capability an addon reports its OWN losses
38599
39253
  * through, per camera, with the denominator attached. It stores nothing.
38600
39254
  *
@@ -39602,7 +40256,13 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
39602
40256
  * `beginExternalLink` offer exactly the redirect methods that set this,
39603
40257
  * whatever their id. Absent / false — a login button only.
39604
40258
  */
39605
- linkable: boolean().optional()
40259
+ linkable: boolean().optional(),
40260
+ /**
40261
+ * How a NATIVE app starts this method (D858): `{ kind: 'browser', startPath }`
40262
+ * when the route understands the app flow query (`app=1&code_challenge=…`),
40263
+ * `null`/absent when there is no app path - the app hides the method.
40264
+ */
40265
+ app: AppLoginPathSchema.nullable().optional()
39606
40266
  }),
39607
40267
  object({
39608
40268
  kind: literal("widget"),
@@ -39614,7 +40274,9 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
39614
40274
  bundle: string(),
39615
40275
  /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
39616
40276
  remote: WidgetRemoteSchema,
39617
- stage: LoginStageEnum
40277
+ stage: LoginStageEnum,
40278
+ /** Widgets are web ceremonies: no native app path (D858). Absent reads as `null`. */
40279
+ app: AppLoginPathSchema.nullable().optional()
39618
40280
  }),
39619
40281
  object({
39620
40282
  kind: literal("passkey"),
@@ -39626,7 +40288,9 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
39626
40288
  /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
39627
40289
  rpId: string(),
39628
40290
  /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
39629
- origin: string().nullable()
40291
+ origin: string().nullable(),
40292
+ /** Set by the hub for the primary arm; a second-factor passkey has no app path (D858). */
40293
+ app: AppLoginPathSchema.nullable().optional()
39630
40294
  })
39631
40295
  ]);
39632
40296
  method(_void(), array(LoginMethodContributionSchema).readonly());
@@ -40136,7 +40800,21 @@ var NetworkEndpointSchema = object({
40136
40800
  url: string(),
40137
40801
  hostname: string(),
40138
40802
  port: number(),
40139
- protocol: _enum(["http", "https"])
40803
+ protocol: _enum(["http", "https"]),
40804
+ /**
40805
+ * Whether this address survives a restart of its provider. `false` = it is
40806
+ * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
40807
+ * hub never selects such an address AUTOMATICALLY for a role (D860), though
40808
+ * it stays visible and an operator may still flag it. Absent = stable: every
40809
+ * provider except a Quick tunnel owns a name that does not change.
40810
+ */
40811
+ stable: boolean().optional()
40812
+ });
40813
+ object({
40814
+ url: string(),
40815
+ /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
40816
+ stable: boolean().optional(),
40817
+ updated: boolean().optional()
40140
40818
  });
40141
40819
  var NetworkAccessStatusSchema = object({
40142
40820
  connected: boolean(),
@@ -41233,6 +41911,10 @@ var alarmPanelCapability = {
41233
41911
  * `scripts/check-runtime-state-durability.ts`.
41234
41912
  */
41235
41913
  durability: "restored",
41914
+ /**
41915
+ * History (D850): none — enum state; arm/disarm history belongs in the events ledger.
41916
+ */
41917
+ history: "none",
41236
41918
  /** Clock fields: written, but excluded from the compare that decides
41237
41919
  * whether persisting is worth a SQLite commit. */
41238
41920
  volatileStateFields: ["lastChangedAt"]
@@ -47884,7 +48566,11 @@ var zonesCapability = {
47884
48566
  * See `RuntimeStateDurability`. Enforced by
47885
48567
  * `scripts/check-runtime-state-durability.ts`.
47886
48568
  */
47887
- durability: "restored"
48569
+ durability: "restored",
48570
+ /**
48571
+ * History (D850): none — configuration arrays.
48572
+ */
48573
+ history: "none"
47888
48574
  };
47889
48575
  /**
47890
48576
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
@@ -50341,7 +51027,22 @@ var SsoBridgeClaimsSchema = object({
50341
51027
  * request to present the matching cookie — a login bridge is useless in
50342
51028
  * any other browser.
50343
51029
  */
50344
- browserBinding: string().optional()
51030
+ browserBinding: string().optional(),
51031
+ /**
51032
+ * The native app that started this sign-in (D858): its PKCE challenge,
51033
+ * `state`, purpose and allow-listed return URL, copied from the server-side
51034
+ * pending OIDC state. A bridge carrying it is finished ONLY at
51035
+ * `/api/auth/sso/finish-app` (which mints a handoff code, never a web
51036
+ * session); `/api/auth/sso/finish` refuses it.
51037
+ */
51038
+ app: object({
51039
+ codeChallenge: string(),
51040
+ state: string(),
51041
+ purpose: _enum(["new-session", "step-up"]),
51042
+ redirect: string(),
51043
+ /** The page language the app asked for (D858); the confirm page speaks it. */
51044
+ lang: _enum(["en", "it"]).optional()
51045
+ }).optional()
50345
51046
  });
50346
51047
  method(object({
50347
51048
  claims: SsoBridgeClaimsSchema,
@@ -50728,6 +51429,10 @@ var streamSignalsCapability = {
50728
51429
  * `scripts/check-runtime-state-durability.ts`.
50729
51430
  */
50730
51431
  durability: "session",
51432
+ /**
51433
+ * History (D850): none — array state.
51434
+ */
51435
+ history: "none",
50731
51436
  methods: {
50732
51437
  /**
50733
51438
  * What this device can emit. Empty is a valid and common answer — most
@@ -52994,7 +53699,12 @@ var SearchResultSchema = object({
52994
53699
  publishedAt: string(),
52995
53700
  author: string(),
52996
53701
  installed: boolean(),
52997
- installedVersion: string().optional()
53702
+ installedVersion: string().optional(),
53703
+ /**
53704
+ * Capability names the package's addons declare (from its published
53705
+ * manifest). `null` = the registry lookup failed — unknown, never `[]` (D393).
53706
+ */
53707
+ capabilities: array(string()).readonly().nullable()
52998
53708
  });
52999
53709
  var AutoUpdateSettingsSchema = object({
53000
53710
  channel: ChannelSchema,
@@ -53156,7 +53866,11 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
53156
53866
  }), method(object({ packageName: string().optional() }), PackageInstallationsSchema, { auth: "admin" }), method(_void(), ReloadPackagesResultSchema, {
53157
53867
  kind: "mutation",
53158
53868
  auth: "admin"
53159
- }), method(object({ query: string().optional() }), array(SearchResultSchema)), method(object({ nodeId: string().optional() }), array(PackageUpdateSchema).readonly(), { auth: "admin" }), method(object({
53869
+ }), method(object({
53870
+ query: string().optional(),
53871
+ /** Only packages whose addons declare this capability (unknown ones excluded). */
53872
+ capability: string().optional()
53873
+ }), array(SearchResultSchema)), method(object({ nodeId: string().optional() }), array(PackageUpdateSchema).readonly(), { auth: "admin" }), method(object({
53160
53874
  name: string().min(1),
53161
53875
  version: string().optional(),
53162
53876
  nodeId: string().optional()
@@ -53292,6 +54006,36 @@ var airQualitySensorCapability = {
53292
54006
  * `scripts/check-runtime-state-durability.ts`.
53293
54007
  */
53294
54008
  durability: "restored",
54009
+ /**
54010
+ * History (D850): recorded series.
54011
+ */
54012
+ history: { fields: {
54013
+ co2Ppm: {
54014
+ kind: "gauge",
54015
+ unit: "ppm",
54016
+ deadband: 5
54017
+ },
54018
+ vocPpb: {
54019
+ kind: "gauge",
54020
+ unit: "ppb",
54021
+ deadband: 5
54022
+ },
54023
+ pm25: {
54024
+ kind: "gauge",
54025
+ unit: "ug/m3",
54026
+ deadband: 1
54027
+ },
54028
+ pm10: {
54029
+ kind: "gauge",
54030
+ unit: "ug/m3",
54031
+ deadband: 1
54032
+ },
54033
+ aqi: {
54034
+ kind: "gauge",
54035
+ unit: "count",
54036
+ deadband: 1
54037
+ }
54038
+ } },
53295
54039
  /** Clock fields: written, but excluded from the compare that decides
53296
54040
  * whether persisting is worth a SQLite commit. */
53297
54041
  volatileStateFields: ["lastFetchedAt"]
@@ -53334,6 +54078,15 @@ var ambientLightSensorCapability = {
53334
54078
  * `scripts/check-runtime-state-durability.ts`.
53335
54079
  */
53336
54080
  durability: "restored",
54081
+ /**
54082
+ * History (D850): recorded series. Wide range, so a RELATIVE deadband (10 %).
54083
+ */
54084
+ history: { fields: { lux: {
54085
+ kind: "gauge",
54086
+ unit: "lx",
54087
+ deadbandPct: 10,
54088
+ minIntervalMs: 6e4
54089
+ } } },
53337
54090
  /** Clock fields: written, but excluded from the compare that decides
53338
54091
  * whether persisting is worth a SQLite commit. */
53339
54092
  volatileStateFields: ["lastFetchedAt"]
@@ -53461,7 +54214,11 @@ var audioMetricsCapability = {
53461
54214
  * See `RuntimeStateDurability`. Enforced by
53462
54215
  * `scripts/check-runtime-state-durability.ts`.
53463
54216
  */
53464
- durability: "session"
54217
+ durability: "session",
54218
+ /**
54219
+ * History (D850): none — the level is the addon series `pipeline-analytics/audio-level` (producer `record`); the other numbers move every inference window and duplicate it.
54220
+ */
54221
+ history: "none"
53465
54222
  };
53466
54223
  /**
53467
54224
  * Automation-control cap. Models HA `automation.*` entities on
@@ -53530,7 +54287,11 @@ var automationControlCapability = {
53530
54287
  * See `RuntimeStateDurability`. Enforced by
53531
54288
  * `scripts/check-runtime-state-durability.ts`.
53532
54289
  */
53533
- durability: "session"
54290
+ durability: "session",
54291
+ /**
54292
+ * History (D850): none — configuration and transient run state.
54293
+ */
54294
+ history: "none"
53534
54295
  };
53535
54296
  /**
53536
54297
  * Battery status snapshot. Emitted by providers whose device is
@@ -53711,6 +54472,13 @@ onStatusChanged: { data: object({
53711
54472
  * `scripts/check-runtime-state-durability.ts`.
53712
54473
  */
53713
54474
  durability: "restored",
54475
+ /**
54476
+ * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
54477
+ */
54478
+ history: { fields: { percentage: {
54479
+ kind: "gauge",
54480
+ unit: "%"
54481
+ } } },
53714
54482
  /** Clock fields: written, but excluded from the compare that decides
53715
54483
  * whether persisting is worth a SQLite commit. */
53716
54484
  volatileStateFields: ["lastUpdated"]
@@ -53750,6 +54518,10 @@ var binaryCapability = {
53750
54518
  * `scripts/check-runtime-state-durability.ts`.
53751
54519
  */
53752
54520
  durability: "restored",
54521
+ /**
54522
+ * History (D850): recorded series.
54523
+ */
54524
+ history: { fields: { on: { kind: "boolean" } } },
53753
54525
  /** Clock fields: written, but excluded from the compare that decides
53754
54526
  * whether persisting is worth a SQLite commit. */
53755
54527
  volatileStateFields: ["lastChangedAt"]
@@ -53812,7 +54584,15 @@ onBrightnessChanged: { data: object({
53812
54584
  * See `RuntimeStateDurability`. Enforced by
53813
54585
  * `scripts/check-runtime-state-durability.ts`.
53814
54586
  */
53815
- durability: "session"
54587
+ durability: "session",
54588
+ /**
54589
+ * History (D850): recorded series. Actuator tier: level keeps every change.
54590
+ */
54591
+ history: { fields: { percentage: {
54592
+ kind: "gauge",
54593
+ unit: "%",
54594
+ minIntervalMs: 0
54595
+ } } }
53816
54596
  };
53817
54597
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
53818
54598
  kind: "mutation",
@@ -54146,6 +54926,10 @@ var carbonMonoxideCapability = {
54146
54926
  * `scripts/check-runtime-state-durability.ts`.
54147
54927
  */
54148
54928
  durability: "restored",
54929
+ /**
54930
+ * History (D850): recorded series.
54931
+ */
54932
+ history: { fields: { detected: { kind: "boolean" } } },
54149
54933
  /** Clock fields: written, but excluded from the compare that decides
54150
54934
  * whether persisting is worth a SQLite commit. */
54151
54935
  volatileStateFields: ["lastChangedAt"]
@@ -54315,7 +55099,37 @@ var climateControlCapability = {
54315
55099
  * See `RuntimeStateDurability`. Enforced by
54316
55100
  * `scripts/check-runtime-state-durability.ts`.
54317
55101
  */
54318
- durability: "session"
55102
+ durability: "session",
55103
+ /**
55104
+ * History (D850): recorded series. Celsius by contract; setpoints keep every change (`minIntervalMs: 0`).
55105
+ */
55106
+ history: { fields: {
55107
+ currentTemp: {
55108
+ kind: "gauge",
55109
+ unit: "°C",
55110
+ deadband: .1
55111
+ },
55112
+ currentHumidity: {
55113
+ kind: "gauge",
55114
+ unit: "%",
55115
+ deadband: .5
55116
+ },
55117
+ target: {
55118
+ kind: "gauge",
55119
+ unit: "°C",
55120
+ minIntervalMs: 0
55121
+ },
55122
+ targetHigh: {
55123
+ kind: "gauge",
55124
+ unit: "°C",
55125
+ minIntervalMs: 0
55126
+ },
55127
+ targetLow: {
55128
+ kind: "gauge",
55129
+ unit: "°C",
55130
+ minIntervalMs: 0
55131
+ }
55132
+ } }
54319
55133
  };
54320
55134
  /**
54321
55135
  * Color-light cap. Coexists with `switch` (on/off) and `brightness`
@@ -54432,7 +55246,11 @@ onColorChanged: { data: object({
54432
55246
  * See `RuntimeStateDurability`. Enforced by
54433
55247
  * `scripts/check-runtime-state-durability.ts`.
54434
55248
  */
54435
- durability: "session"
55249
+ durability: "session",
55250
+ /**
55251
+ * History (D850): none — nested and cosmetic.
55252
+ */
55253
+ history: "none"
54436
55254
  };
54437
55255
  var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
54438
55256
  object({
@@ -54498,6 +55316,10 @@ var connectivityCapability = {
54498
55316
  * `scripts/check-runtime-state-durability.ts`.
54499
55317
  */
54500
55318
  durability: "restored",
55319
+ /**
55320
+ * History (D850): none — duplicates device-status.online (one authority for availability).
55321
+ */
55322
+ history: "none",
54501
55323
  /** Clock fields: written, but excluded from the compare that decides
54502
55324
  * whether persisting is worth a SQLite commit. */
54503
55325
  volatileStateFields: ["lastChangedAt"]
@@ -54622,6 +55444,10 @@ reset: method(object({
54622
55444
  * `scripts/check-runtime-state-durability.ts`.
54623
55445
  */
54624
55446
  durability: "restored",
55447
+ /**
55448
+ * History (D850): none — `items` is an array keyed by runtime ids; a history path never enters it.
55449
+ */
55450
+ history: "none",
54625
55451
  /** Clocks, not content: a re-read that changed no item costs no commit. */
54626
55452
  volatileStateFields: ["lastChangedAt", "lastFetchedAt"]
54627
55453
  };
@@ -54662,6 +55488,10 @@ var contactCapability = {
54662
55488
  * `scripts/check-runtime-state-durability.ts`.
54663
55489
  */
54664
55490
  durability: "restored",
55491
+ /**
55492
+ * History (D850): recorded series.
55493
+ */
55494
+ history: { fields: { entryOpen: { kind: "boolean" } } },
54665
55495
  /** Clock fields: written, but excluded from the compare that decides
54666
55496
  * whether persisting is worth a SQLite commit. */
54667
55497
  volatileStateFields: ["lastChangedAt"]
@@ -54777,7 +55607,11 @@ var controlCapability = {
54777
55607
  * See `RuntimeStateDurability`. Enforced by
54778
55608
  * `scripts/check-runtime-state-durability.ts`.
54779
55609
  */
54780
- durability: "session"
55610
+ durability: "session",
55611
+ /**
55612
+ * History (D850): none — `value` is a number/string union; configuration entity.
55613
+ */
55614
+ history: "none"
54781
55615
  };
54782
55616
  var CoverStatusSchema = object({
54783
55617
  /** Lifecycle state of the cover. */
@@ -54846,6 +55680,14 @@ var coverCapability = {
54846
55680
  * `scripts/check-runtime-state-durability.ts`.
54847
55681
  */
54848
55682
  durability: "restored",
55683
+ /**
55684
+ * History (D850): recorded series. Actuator tier: position keeps every tick of a movement.
55685
+ */
55686
+ history: { fields: { position: {
55687
+ kind: "gauge",
55688
+ unit: "%",
55689
+ minIntervalMs: 0
55690
+ } } },
54849
55691
  /** Clock fields: written, but excluded from the compare that decides
54850
55692
  * whether persisting is worth a SQLite commit. */
54851
55693
  volatileStateFields: ["lastChangedAt"]
@@ -54950,6 +55792,10 @@ var dayNightCapability = {
54950
55792
  * `scripts/check-runtime-state-durability.ts`.
54951
55793
  */
54952
55794
  durability: "restored",
55795
+ /**
55796
+ * History (D850): none — firmware configuration, not a measurement.
55797
+ */
55798
+ history: "none",
54953
55799
  /** Clock fields: written, but excluded from the compare that decides
54954
55800
  * whether persisting is worth a SQLite commit. */
54955
55801
  volatileStateFields: ["lastFetchedAt"]
@@ -55007,6 +55853,10 @@ onStatusChanged: { data: object({
55007
55853
  * `scripts/check-runtime-state-durability.ts`.
55008
55854
  */
55009
55855
  durability: "restored",
55856
+ /**
55857
+ * History (D850): recorded series. The ONE availability series.
55858
+ */
55859
+ history: { fields: { online: { kind: "boolean" } } },
55010
55860
  /** Clock fields: written, but excluded from the compare that decides
55011
55861
  * whether persisting is worth a SQLite commit. */
55012
55862
  volatileStateFields: ["lastChangedAt"]
@@ -55073,7 +55923,11 @@ onPressed: {
55073
55923
  * See `RuntimeStateDurability`. Enforced by
55074
55924
  * `scripts/check-runtime-state-durability.ts`.
55075
55925
  */
55076
- durability: "restored"
55926
+ durability: "restored",
55927
+ /**
55928
+ * History (D850): none — `pressCountSinceStart` resets with the addon, so it is not a monotonic meter; presses live in the events ledger.
55929
+ */
55930
+ history: "none"
55077
55931
  };
55078
55932
  /**
55079
55933
  * Enum-state sensor — a string value picked from a finite option set.
@@ -55121,6 +55975,10 @@ var enumSensorCapability = {
55121
55975
  * `scripts/check-runtime-state-durability.ts`.
55122
55976
  */
55123
55977
  durability: "restored",
55978
+ /**
55979
+ * History (D850): none — text/enum state has no history (non-goal).
55980
+ */
55981
+ history: "none",
55124
55982
  /** Clock fields: written, but excluded from the compare that decides
55125
55983
  * whether persisting is worth a SQLite commit. */
55126
55984
  volatileStateFields: ["lastFetchedAt"]
@@ -55166,7 +56024,11 @@ var eventEmitterCapability = {
55166
56024
  * See `RuntimeStateDurability`. Enforced by
55167
56025
  * `scripts/check-runtime-state-durability.ts`.
55168
56026
  */
55169
- durability: "session"
56027
+ durability: "session",
56028
+ /**
56029
+ * History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
56030
+ */
56031
+ history: "none"
55170
56032
  };
55171
56033
  var IdentitySchema = object({
55172
56034
  id: string(),
@@ -55623,7 +56485,18 @@ var fanControlCapability = {
55623
56485
  * See `RuntimeStateDurability`. Enforced by
55624
56486
  * `scripts/check-runtime-state-durability.ts`.
55625
56487
  */
55626
- durability: "session"
56488
+ durability: "session",
56489
+ /**
56490
+ * History (D850): recorded series. Actuator tier: speed keeps every change.
56491
+ */
56492
+ history: { fields: {
56493
+ percentage: {
56494
+ kind: "gauge",
56495
+ unit: "%",
56496
+ minIntervalMs: 0
56497
+ },
56498
+ oscillating: { kind: "boolean" }
56499
+ } }
55627
56500
  };
55628
56501
  /**
55629
56502
  * Per-device feature/identity probe slice. Holds the runtime-resolved
@@ -55706,7 +56579,11 @@ onProbeChanged: { data: object({
55706
56579
  * See `RuntimeStateDurability`. Enforced by
55707
56580
  * `scripts/check-runtime-state-durability.ts`.
55708
56581
  */
55709
- durability: "session"
56582
+ durability: "session",
56583
+ /**
56584
+ * History (D850): none — static identity and capability probe.
56585
+ */
56586
+ history: "none"
55710
56587
  };
55711
56588
  /**
55712
56589
  * Water leak / moisture sensor. Boolean "is liquid currently
@@ -55741,6 +56618,10 @@ var floodCapability = {
55741
56618
  * `scripts/check-runtime-state-durability.ts`.
55742
56619
  */
55743
56620
  durability: "restored",
56621
+ /**
56622
+ * History (D850): recorded series.
56623
+ */
56624
+ history: { fields: { flooded: { kind: "boolean" } } },
55744
56625
  /** Clock fields: written, but excluded from the compare that decides
55745
56626
  * whether persisting is worth a SQLite commit. */
55746
56627
  volatileStateFields: ["lastChangedAt"]
@@ -55774,6 +56655,10 @@ var gasCapability = {
55774
56655
  * `scripts/check-runtime-state-durability.ts`.
55775
56656
  */
55776
56657
  durability: "restored",
56658
+ /**
56659
+ * History (D850): recorded series.
56660
+ */
56661
+ history: { fields: { detected: { kind: "boolean" } } },
55777
56662
  /** Clock fields: written, but excluded from the compare that decides
55778
56663
  * whether persisting is worth a SQLite commit. */
55779
56664
  volatileStateFields: ["lastChangedAt"]
@@ -55859,7 +56744,18 @@ var humidifierCapability = {
55859
56744
  * See `RuntimeStateDurability`. Enforced by
55860
56745
  * `scripts/check-runtime-state-durability.ts`.
55861
56746
  */
55862
- durability: "session"
56747
+ durability: "session",
56748
+ /**
56749
+ * History (D850): recorded series.
56750
+ */
56751
+ history: { fields: {
56752
+ currentHumidity: {
56753
+ kind: "gauge",
56754
+ unit: "%",
56755
+ deadband: .5
56756
+ },
56757
+ on: { kind: "boolean" }
56758
+ } }
55863
56759
  };
55864
56760
  /**
55865
56761
  * Single-metric humidity reading. Drives Home Assistant `sensor`
@@ -55903,6 +56799,14 @@ var humiditySensorCapability = {
55903
56799
  * `scripts/check-runtime-state-durability.ts`.
55904
56800
  */
55905
56801
  durability: "restored",
56802
+ /**
56803
+ * History (D850): recorded series.
56804
+ */
56805
+ history: { fields: { percent: {
56806
+ kind: "gauge",
56807
+ unit: "%",
56808
+ deadband: .5
56809
+ } } },
55906
56810
  /** Clock fields: written, but excluded from the compare that decides
55907
56811
  * whether persisting is worth a SQLite commit. */
55908
56812
  volatileStateFields: ["lastFetchedAt"]
@@ -55950,7 +56854,11 @@ var imageCapability = {
55950
56854
  * See `RuntimeStateDurability`. Enforced by
55951
56855
  * `scripts/check-runtime-state-durability.ts`.
55952
56856
  */
55953
- durability: "session"
56857
+ durability: "session",
56858
+ /**
56859
+ * History (D850): none — not numeric.
56860
+ */
56861
+ history: "none"
55954
56862
  };
55955
56863
  /**
55956
56864
  * Vendor-neutral image / picture-adjustment cap — the per-camera config
@@ -56107,6 +57015,10 @@ var imageSettingsCapability = {
56107
57015
  * `scripts/check-runtime-state-durability.ts`.
56108
57016
  */
56109
57017
  durability: "restored",
57018
+ /**
57019
+ * History (D850): none — camera image configuration (operator intent), not a trend.
57020
+ */
57021
+ history: "none",
56110
57022
  /** Clock fields: written, but excluded from the compare that decides
56111
57023
  * whether persisting is worth a SQLite commit. */
56112
57024
  volatileStateFields: ["lastFetchedAt"]
@@ -56492,7 +57404,11 @@ var intercomCapability = {
56492
57404
  * See `RuntimeStateDurability`. Enforced by
56493
57405
  * `scripts/check-runtime-state-durability.ts`.
56494
57406
  */
56495
- durability: "session"
57407
+ durability: "session",
57408
+ /**
57409
+ * History (D850): none — talk sessions are a ledger concern, not a trend.
57410
+ */
57411
+ history: "none"
56496
57412
  };
56497
57413
  /**
56498
57414
  * Robotic lawn-mower cap. Models HA `lawn_mower.*` entities — anything
@@ -56573,7 +57489,15 @@ var lawnMowerControlCapability = {
56573
57489
  * See `RuntimeStateDurability`. Enforced by
56574
57490
  * `scripts/check-runtime-state-durability.ts`.
56575
57491
  */
56576
- durability: "session"
57492
+ durability: "session",
57493
+ /**
57494
+ * History (D850): recorded series.
57495
+ */
57496
+ history: { fields: { batteryLevel: {
57497
+ kind: "gauge",
57498
+ unit: "%",
57499
+ deadband: 2
57500
+ } } }
56577
57501
  };
56578
57502
  /**
56579
57503
  * local-network — hub-only singleton.
@@ -56700,7 +57624,13 @@ var ConnectionEndpointSchema = object({
56700
57624
  * calls so the client can sort even if the cap returns a new
56701
57625
  * ordering between polls.
56702
57626
  */
56703
- priority: number()
57627
+ priority: number(),
57628
+ /**
57629
+ * `false` = the address changes on its own (a Quick tunnel, re-issued on
57630
+ * every respawn). Automatic role resolution skips it (D860); an explicit
57631
+ * operator flag still honours it. Absent = stable.
57632
+ */
57633
+ stable: boolean().optional()
56704
57634
  });
56705
57635
  /**
56706
57636
  * Where the advertised local port came from. Ordered most → least
@@ -56980,6 +57910,10 @@ var lockControlCapability = {
56980
57910
  * `scripts/check-runtime-state-durability.ts`.
56981
57911
  */
56982
57912
  durability: "restored",
57913
+ /**
57914
+ * History (D850): none — enum state; lock events belong in the events ledger.
57915
+ */
57916
+ history: "none",
56983
57917
  /** Clock fields: written, but excluded from the compare that decides
56984
57918
  * whether persisting is worth a SQLite commit. */
56985
57919
  volatileStateFields: ["lastChangedAt"]
@@ -57151,7 +58085,11 @@ var mediaPlayerCapability = {
57151
58085
  * See `RuntimeStateDurability`. Enforced by
57152
58086
  * `scripts/check-runtime-state-durability.ts`.
57153
58087
  */
57154
- durability: "session"
58088
+ durability: "session",
58089
+ /**
58090
+ * History (D850): none — `positionMs` moves continuously while playing; volume is transient.
58091
+ */
58092
+ history: "none"
57155
58093
  };
57156
58094
  /**
57157
58095
  * mesh-network — collection cap for mesh-VPN providers.
@@ -57460,7 +58398,14 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
57460
58398
  * See `RuntimeStateDurability`. Enforced by
57461
58399
  * `scripts/check-runtime-state-durability.ts`.
57462
58400
  */
57463
- durability: "session"
58401
+ durability: "session",
58402
+ /**
58403
+ * History (D850): recorded series. A PIR sensor is a series; a camera is not (its motion is an addon series and a ledger).
58404
+ */
58405
+ history: { fields: { detected: {
58406
+ kind: "boolean",
58407
+ excludeDeviceTypes: [DeviceType.Camera]
58408
+ } } }
57464
58409
  };
57465
58410
  /**
57466
58411
  * Motion-trigger toggle for accessory devices.
@@ -57532,7 +58477,11 @@ var motionTriggerCapability = {
57532
58477
  * See `RuntimeStateDurability`. Enforced by
57533
58478
  * `scripts/check-runtime-state-durability.ts`.
57534
58479
  */
57535
- durability: "session"
58480
+ durability: "session",
58481
+ /**
58482
+ * History (D850): none — configuration switch.
58483
+ */
58484
+ history: "none"
57536
58485
  };
57537
58486
  /**
57538
58487
  * Motion-zones share the same MaskShape vocabulary as privacy-mask — the
@@ -57607,6 +58556,10 @@ var motionZonesCapability = {
57607
58556
  * `scripts/check-runtime-state-durability.ts`.
57608
58557
  */
57609
58558
  durability: "restored",
58559
+ /**
58560
+ * History (D850): none — configuration.
58561
+ */
58562
+ history: "none",
57610
58563
  /** Clock fields: written, but excluded from the compare that decides
57611
58564
  * whether persisting is worth a SQLite commit. */
57612
58565
  volatileStateFields: ["lastFetchedAt"]
@@ -57736,6 +58689,10 @@ var nativeObjectDetectionCapability = {
57736
58689
  * `scripts/check-runtime-state-durability.ts`.
57737
58690
  */
57738
58691
  durability: "restored",
58692
+ /**
58693
+ * History (D850): none — event-like and nested per class; detections live in the objects ledger.
58694
+ */
58695
+ history: "none",
57739
58696
  /** Clock fields: written, but excluded from the compare that decides
57740
58697
  * whether persisting is worth a SQLite commit. */
57741
58698
  volatileStateFields: ["lastFetchedAt"]
@@ -58019,7 +58976,11 @@ var navigationCapability = {
58019
58976
  * See `RuntimeStateDurability`. Enforced by
58020
58977
  * `scripts/check-runtime-state-durability.ts`.
58021
58978
  */
58022
- durability: "session"
58979
+ durability: "session",
58980
+ /**
58981
+ * History (D850): none — control state of a robotic camera, configuration-like.
58982
+ */
58983
+ history: "none"
58023
58984
  };
58024
58985
  /**
58025
58986
  * Network-link snapshot. Same shape for every provider (a Reolink wifi
@@ -58119,6 +59080,23 @@ onStatusChanged: { data: object({
58119
59080
  * `scripts/check-runtime-state-durability.ts`.
58120
59081
  */
58121
59082
  durability: "restored",
59083
+ /**
59084
+ * History (D850): recorded series.
59085
+ */
59086
+ history: { fields: {
59087
+ signalPercent: {
59088
+ kind: "gauge",
59089
+ unit: "%",
59090
+ deadband: 3,
59091
+ minIntervalMs: 6e4
59092
+ },
59093
+ rssiDbm: {
59094
+ kind: "gauge",
59095
+ unit: "dBm",
59096
+ deadband: 2,
59097
+ minIntervalMs: 6e4
59098
+ }
59099
+ } },
58122
59100
  /** Clock fields: written, but excluded from the compare that decides
58123
59101
  * whether persisting is worth a SQLite commit. */
58124
59102
  volatileStateFields: ["lastUpdated"]
@@ -58507,7 +59485,11 @@ onSent: { data: object({
58507
59485
  * See `RuntimeStateDurability`. Enforced by
58508
59486
  * `scripts/check-runtime-state-durability.ts`.
58509
59487
  */
58510
- durability: "session"
59488
+ durability: "session",
59489
+ /**
59490
+ * History (D850): none — `queueDepth` is outbox operational state, not a device trend.
59491
+ */
59492
+ history: "none"
58511
59493
  };
58512
59494
  /**
58513
59495
  * Generic numeric sensor — last-resort fallback when no typed numeric
@@ -58558,6 +59540,14 @@ var numericSensorCapability = {
58558
59540
  * `scripts/check-runtime-state-durability.ts`.
58559
59541
  */
58560
59542
  durability: "restored",
59543
+ /**
59544
+ * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
59545
+ */
59546
+ history: { fields: { value: {
59547
+ kind: "gauge",
59548
+ unitFrom: "unit",
59549
+ minIntervalMs: 3e4
59550
+ } } },
58561
59551
  /** Clock fields: written, but excluded from the compare that decides
58562
59552
  * whether persisting is worth a SQLite commit. */
58563
59553
  volatileStateFields: ["lastFetchedAt"]
@@ -59034,7 +60024,24 @@ var petFeederCapability = {
59034
60024
  * See `RuntimeStateDurability`. Enforced by
59035
60025
  * `scripts/check-runtime-state-durability.ts`.
59036
60026
  */
59037
- durability: "session"
60027
+ durability: "session",
60028
+ /**
60029
+ * History (D850): recorded series.
60030
+ */
60031
+ history: { fields: {
60032
+ foodLevel: { kind: "gauge" },
60033
+ food1: { kind: "gauge" },
60034
+ food2: { kind: "gauge" },
60035
+ batteryPower: {
60036
+ kind: "gauge",
60037
+ unit: "%",
60038
+ deadband: 2
60039
+ },
60040
+ desiccantLeftDays: {
60041
+ kind: "gauge",
60042
+ unit: "days"
60043
+ }
60044
+ } }
59038
60045
  };
59039
60046
  var VehicleSchema = object({
59040
60047
  id: string(),
@@ -59412,6 +60419,47 @@ var powerMeterCapability = {
59412
60419
  * `scripts/check-runtime-state-durability.ts`.
59413
60420
  */
59414
60421
  durability: "restored",
60422
+ /**
60423
+ * History (D850): recorded series. Gauges are floored at 30 s; `kwhTotal` is the one monotonic meter (counter).
60424
+ */
60425
+ history: { fields: {
60426
+ watts: {
60427
+ kind: "gauge",
60428
+ unit: "W",
60429
+ deadband: 1,
60430
+ minIntervalMs: 3e4
60431
+ },
60432
+ kwhTotal: {
60433
+ kind: "counter",
60434
+ unit: "kWh",
60435
+ deadband: .01,
60436
+ minIntervalMs: 6e4
60437
+ },
60438
+ volts: {
60439
+ kind: "gauge",
60440
+ unit: "V",
60441
+ deadband: 1,
60442
+ minIntervalMs: 3e4
60443
+ },
60444
+ amps: {
60445
+ kind: "gauge",
60446
+ unit: "A",
60447
+ deadband: .05,
60448
+ minIntervalMs: 3e4
60449
+ },
60450
+ va: {
60451
+ kind: "gauge",
60452
+ unit: "VA",
60453
+ deadband: 1,
60454
+ minIntervalMs: 3e4
60455
+ },
60456
+ powerFactor: {
60457
+ kind: "gauge",
60458
+ unit: "ratio",
60459
+ deadband: .01,
60460
+ minIntervalMs: 6e4
60461
+ }
60462
+ } },
59415
60463
  /** Clock fields: written, but excluded from the compare that decides
59416
60464
  * whether persisting is worth a SQLite commit. */
59417
60465
  volatileStateFields: ["lastFetchedAt"]
@@ -59479,6 +60527,14 @@ var presenceCapability = {
59479
60527
  * `scripts/check-runtime-state-durability.ts`.
59480
60528
  */
59481
60529
  durability: "restored",
60530
+ /**
60531
+ * History (D850): recorded series.
60532
+ */
60533
+ history: { fields: { batteryPercent: {
60534
+ kind: "gauge",
60535
+ unit: "%",
60536
+ deadband: 1
60537
+ } } },
59482
60538
  /** Clock fields: written, but excluded from the compare that decides
59483
60539
  * whether persisting is worth a SQLite commit. */
59484
60540
  volatileStateFields: ["lastChangedAt"]
@@ -59525,6 +60581,14 @@ var pressureSensorCapability = {
59525
60581
  * `scripts/check-runtime-state-durability.ts`.
59526
60582
  */
59527
60583
  durability: "restored",
60584
+ /**
60585
+ * History (D850): recorded series.
60586
+ */
60587
+ history: { fields: { hpa: {
60588
+ kind: "gauge",
60589
+ unit: "hPa",
60590
+ deadband: .1
60591
+ } } },
59528
60592
  /** Clock fields: written, but excluded from the compare that decides
59529
60593
  * whether persisting is worth a SQLite commit. */
59530
60594
  volatileStateFields: ["lastFetchedAt"]
@@ -59684,6 +60748,10 @@ var privacyMaskCapability = {
59684
60748
  * `scripts/check-runtime-state-durability.ts`.
59685
60749
  */
59686
60750
  durability: "restored",
60751
+ /**
60752
+ * History (D850): none — configuration.
60753
+ */
60754
+ history: "none",
59687
60755
  /** Clock fields: written, but excluded from the compare that decides
59688
60756
  * whether persisting is worth a SQLite commit. */
59689
60757
  volatileStateFields: ["lastFetchedAt"]
@@ -60157,7 +61225,11 @@ var ptzAutotrackCapability = {
60157
61225
  * See `RuntimeStateDurability`. Enforced by
60158
61226
  * `scripts/check-runtime-state-durability.ts`.
60159
61227
  */
60160
- durability: "session"
61228
+ durability: "session",
61229
+ /**
61230
+ * History (D850): none — configuration and capability.
61231
+ */
61232
+ history: "none"
60161
61233
  };
60162
61234
  /**
60163
61235
  * reboot — device-scoped capability for "soft" device reboots (firmware
@@ -61612,6 +62684,10 @@ var recordingOnboardCapability = {
61612
62684
  * `scripts/check-runtime-state-durability.ts`.
61613
62685
  */
61614
62686
  durability: "restored",
62687
+ /**
62688
+ * History (D850): none — SD-card configuration and window inventory; arrays.
62689
+ */
62690
+ history: "none",
61615
62691
  /** Clock fields: written, but excluded from the compare that decides
61616
62692
  * whether persisting is worth a SQLite commit. */
61617
62693
  volatileStateFields: ["lastFetchedAt"]
@@ -61850,7 +62926,11 @@ getStatus: method(object({ deviceId: number() }), RecordingSignalStatusSchema) }
61850
62926
  * See `RuntimeStateDurability`. Enforced by
61851
62927
  * `scripts/check-runtime-state-durability.ts`.
61852
62928
  */
61853
- durability: "session"
62929
+ durability: "session",
62930
+ /**
62931
+ * History (D850): none — recording intent is already in the recordings ledger.
62932
+ */
62933
+ history: "none"
61854
62934
  };
61855
62935
  /**
61856
62936
  * scene-monitor — device-scoped reference-region state cap. An operator marks
@@ -62223,7 +63303,11 @@ var sceneMonitorCapability = {
62223
63303
  * See `RuntimeStateDurability`. Enforced by
62224
63304
  * `scripts/check-runtime-state-durability.ts`.
62225
63305
  */
62226
- durability: "session"
63306
+ durability: "session",
63307
+ /**
63308
+ * History (D850): none — nested configuration.
63309
+ */
63310
+ history: "none"
62227
63311
  };
62228
63312
  /**
62229
63313
  * Per-stage gating mode applied to the zones a rule references.
@@ -62374,7 +63458,11 @@ var scriptRunnerCapability = {
62374
63458
  * See `RuntimeStateDurability`. Enforced by
62375
63459
  * `scripts/check-runtime-state-durability.ts`.
62376
63460
  */
62377
- durability: "session"
63461
+ durability: "session",
63462
+ /**
63463
+ * History (D850): none — transient; run results belong in a ledger.
63464
+ */
63465
+ history: "none"
62378
63466
  };
62379
63467
  /**
62380
63468
  * Smoke alarm sensor — boolean "is smoke currently detected" with
@@ -62409,6 +63497,10 @@ var smokeCapability = {
62409
63497
  * `scripts/check-runtime-state-durability.ts`.
62410
63498
  */
62411
63499
  durability: "restored",
63500
+ /**
63501
+ * History (D850): recorded series.
63502
+ */
63503
+ history: { fields: { detected: { kind: "boolean" } } },
62412
63504
  /** Clock fields: written, but excluded from the compare that decides
62413
63505
  * whether persisting is worth a SQLite commit. */
62414
63506
  volatileStateFields: ["lastChangedAt"]
@@ -62508,6 +63600,10 @@ var streamCatalogCapability = {
62508
63600
  * `scripts/check-runtime-state-durability.ts`.
62509
63601
  */
62510
63602
  durability: "restored",
63603
+ /**
63604
+ * History (D850): none — stream inventory.
63605
+ */
63606
+ history: "none",
62511
63607
  /** Clock field: written, but excluded from the compare that decides whether
62512
63608
  * persisting is worth a SQLite commit — the descriptors are the value. */
62513
63609
  volatileStateFields: ["lastFetchedAt"]
@@ -62657,6 +63753,10 @@ var streamParamsCapability = {
62657
63753
  * `scripts/check-runtime-state-durability.ts`.
62658
63754
  */
62659
63755
  durability: "restored",
63756
+ /**
63757
+ * History (D850): none — encoder configuration.
63758
+ */
63759
+ history: "none",
62660
63760
  /** Clock fields: written, but excluded from the compare that decides
62661
63761
  * whether persisting is worth a SQLite commit. */
62662
63762
  volatileStateFields: ["lastFetchedAt"]
@@ -62910,6 +64010,10 @@ var switchCapability = {
62910
64010
  * `scripts/check-runtime-state-durability.ts`.
62911
64011
  */
62912
64012
  durability: "restored",
64013
+ /**
64014
+ * History (D850): recorded series.
64015
+ */
64016
+ history: { fields: { on: { kind: "boolean" } } },
62913
64017
  /** Clock fields: written, but excluded from the compare that decides
62914
64018
  * whether persisting is worth a SQLite commit. */
62915
64019
  volatileStateFields: ["lastChangedAt"],
@@ -62924,6 +64028,21 @@ var switchCapability = {
62924
64028
  }
62925
64029
  }] }
62926
64030
  };
64031
+ /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
64032
+ function hasControlCharacter(value) {
64033
+ for (const char of value) {
64034
+ const code = char.codePointAt(0) ?? 0;
64035
+ if (code <= 31 || code >= 127 && code <= 159) return true;
64036
+ }
64037
+ return false;
64038
+ }
64039
+ string().transform((value) => value.trim()).pipe(string().min(1, "The server name cannot be empty").max(48, `The server name is at most 48 characters`).refine((value) => !hasControlCharacter(value), { message: "The server name cannot contain control characters" }));
64040
+ /** What every reader gets: the name to show, and whether the hub chose it itself. */
64041
+ var ServerNameInfoSchema = object({
64042
+ name: string(),
64043
+ /** `true` when nobody named the hub: `name` is the machine hostname. */
64044
+ isDefault: boolean()
64045
+ });
62927
64046
  /**
62928
64047
  * system — system-scoped singleton capability exposing hub-level
62929
64048
  * health, feature flags, network interface enumeration, and the
@@ -62953,6 +64072,12 @@ var FeatureManifestSchema = object({
62953
64072
  backup: boolean(),
62954
64073
  repl: boolean()
62955
64074
  });
64075
+ /**
64076
+ * `system.info`: the feature flags plus the hub's human name (D861). The name is
64077
+ * `{ name, isDefault }` — `isDefault: true` means nobody named the hub and `name`
64078
+ * is the machine hostname.
64079
+ */
64080
+ var SystemInfoSchema = FeatureManifestSchema.extend({ serverName: ServerNameInfoSchema });
62956
64081
  var HealthStatusSchema = object({
62957
64082
  status: literal("ok"),
62958
64083
  uptime: number()
@@ -63425,7 +64550,7 @@ var LoggingSettingsStateSchema = object({
63425
64550
  opsLogRetentionDays: number().int().min(0),
63426
64551
  persisted: boolean()
63427
64552
  });
63428
- method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
64553
+ method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
63429
64554
  kind: "mutation",
63430
64555
  auth: "admin"
63431
64556
  }), method(_void(), _void(), {
@@ -63471,6 +64596,10 @@ var tamperCapability = {
63471
64596
  * `scripts/check-runtime-state-durability.ts`.
63472
64597
  */
63473
64598
  durability: "restored",
64599
+ /**
64600
+ * History (D850): recorded series.
64601
+ */
64602
+ history: { fields: { tampered: { kind: "boolean" } } },
63474
64603
  /** Clock fields: written, but excluded from the compare that decides
63475
64604
  * whether persisting is worth a SQLite commit. */
63476
64605
  volatileStateFields: ["lastChangedAt"]
@@ -63526,6 +64655,14 @@ var temperatureSensorCapability = {
63526
64655
  * `scripts/check-runtime-state-durability.ts`.
63527
64656
  */
63528
64657
  durability: "restored",
64658
+ /**
64659
+ * History (D850): recorded series.
64660
+ */
64661
+ history: { fields: { celsius: {
64662
+ kind: "gauge",
64663
+ unit: "°C",
64664
+ deadband: .1
64665
+ } } },
63529
64666
  /** Clock fields: written, but excluded from the compare that decides
63530
64667
  * whether persisting is worth a SQLite commit. */
63531
64668
  volatileStateFields: ["lastFetchedAt"]
@@ -63604,7 +64741,11 @@ var updateCapability = {
63604
64741
  * See `RuntimeStateDurability`. Enforced by
63605
64742
  * `scripts/check-runtime-state-durability.ts`.
63606
64743
  */
63607
- durability: "session"
64744
+ durability: "session",
64745
+ /**
64746
+ * History (D850): none — text and state.
64747
+ */
64748
+ history: "none"
63608
64749
  };
63609
64750
  var UserSummarySchema = object({
63610
64751
  id: string(),
@@ -63961,7 +65102,37 @@ var vacuumControlCapability = {
63961
65102
  * See `RuntimeStateDurability`. Enforced by
63962
65103
  * `scripts/check-runtime-state-durability.ts`.
63963
65104
  */
63964
- durability: "session"
65105
+ durability: "session",
65106
+ /**
65107
+ * History (D850): recorded series. Tank levels are fixed-object paths (`cleanWater.level`, ...).
65108
+ */
65109
+ history: { fields: {
65110
+ batteryLevel: {
65111
+ kind: "gauge",
65112
+ unit: "%",
65113
+ deadband: 2
65114
+ },
65115
+ "cleanWater.level": {
65116
+ kind: "gauge",
65117
+ unit: "%",
65118
+ deadband: 2
65119
+ },
65120
+ "dirtyWater.level": {
65121
+ kind: "gauge",
65122
+ unit: "%",
65123
+ deadband: 2
65124
+ },
65125
+ "detergent.level": {
65126
+ kind: "gauge",
65127
+ unit: "%",
65128
+ deadband: 2
65129
+ },
65130
+ "dustBin.level": {
65131
+ kind: "gauge",
65132
+ unit: "%",
65133
+ deadband: 2
65134
+ }
65135
+ } }
63965
65136
  };
63966
65137
  var ValveStatusSchema = object({
63967
65138
  /** Lifecycle state of the valve. */
@@ -64020,7 +65191,15 @@ var valveCapability = {
64020
65191
  * See `RuntimeStateDurability`. Enforced by
64021
65192
  * `scripts/check-runtime-state-durability.ts`.
64022
65193
  */
64023
- durability: "session"
65194
+ durability: "session",
65195
+ /**
65196
+ * History (D850): recorded series. Actuator tier: position keeps every change.
65197
+ */
65198
+ history: { fields: { position: {
65199
+ kind: "gauge",
65200
+ unit: "%",
65201
+ minIntervalMs: 0
65202
+ } } }
64024
65203
  };
64025
65204
  /**
64026
65205
  * Vibration / shake / impact sensor. Drives Home Assistant
@@ -64050,6 +65229,10 @@ var vibrationCapability = {
64050
65229
  * `scripts/check-runtime-state-durability.ts`.
64051
65230
  */
64052
65231
  durability: "restored",
65232
+ /**
65233
+ * History (D850): recorded series.
65234
+ */
65235
+ history: { fields: { detected: { kind: "boolean" } } },
64053
65236
  /** Clock fields: written, but excluded from the compare that decides
64054
65237
  * whether persisting is worth a SQLite commit. */
64055
65238
  volatileStateFields: ["lastChangedAt"]
@@ -64133,7 +65316,22 @@ var waterHeaterCapability = {
64133
65316
  * See `RuntimeStateDurability`. Enforced by
64134
65317
  * `scripts/check-runtime-state-durability.ts`.
64135
65318
  */
64136
- durability: "session"
65319
+ durability: "session",
65320
+ /**
65321
+ * History (D850): recorded series. The schema carries no unit; providers publish Celsius. Setpoint keeps every change.
65322
+ */
65323
+ history: { fields: {
65324
+ currentTemp: {
65325
+ kind: "gauge",
65326
+ unit: "°C",
65327
+ deadband: .5
65328
+ },
65329
+ targetTemp: {
65330
+ kind: "gauge",
65331
+ unit: "°C",
65332
+ minIntervalMs: 0
65333
+ }
65334
+ } }
64137
65335
  };
64138
65336
  /**
64139
65337
  * Weather provider cap. Models HA `weather.*` entities — a read-only
@@ -64199,7 +65397,32 @@ var weatherCapability = {
64199
65397
  * See `RuntimeStateDurability`. Enforced by
64200
65398
  * `scripts/check-runtime-state-durability.ts`.
64201
65399
  */
64202
- durability: "session"
65400
+ durability: "session",
65401
+ /**
65402
+ * History (D850): recorded series. Units follow the sibling `*Unit` fields.
65403
+ */
65404
+ history: { fields: {
65405
+ temperature: {
65406
+ kind: "gauge",
65407
+ unitFrom: "temperatureUnit",
65408
+ deadband: .2
65409
+ },
65410
+ humidity: {
65411
+ kind: "gauge",
65412
+ unit: "%",
65413
+ deadband: 1
65414
+ },
65415
+ pressure: {
65416
+ kind: "gauge",
65417
+ unitFrom: "pressureUnit",
65418
+ deadband: .5
65419
+ },
65420
+ windSpeed: {
65421
+ kind: "gauge",
65422
+ unitFrom: "windSpeedUnit",
65423
+ deadband: .5
65424
+ }
65425
+ } }
64203
65426
  };
64204
65427
  /**
64205
65428
  * Per-zone occupancy aggregation produced by the analytics frame
@@ -64441,7 +65664,11 @@ var zoneAnalyticsCapability = {
64441
65664
  * See `RuntimeStateDurability`. Enforced by
64442
65665
  * `scripts/check-runtime-state-durability.ts`.
64443
65666
  */
64444
- durability: "session"
65667
+ durability: "session",
65668
+ /**
65669
+ * History (D850): none — occupancy is the addon series `pipeline-analytics/occupancy`; the rest is per-zone arrays keyed by runtime ids.
65670
+ */
65671
+ history: "none"
64445
65672
  };
64446
65673
  /**
64447
65674
  * Stages a {@link ZoneRule} can apply to. Discriminator on the rules
@@ -64526,7 +65753,11 @@ var zoneRulesCapability = {
64526
65753
  * See `RuntimeStateDurability`. Enforced by
64527
65754
  * `scripts/check-runtime-state-durability.ts`.
64528
65755
  */
64529
- durability: "restored"
65756
+ durability: "restored",
65757
+ /**
65758
+ * History (D850): none — configuration arrays.
65759
+ */
65760
+ history: "none"
64530
65761
  };
64531
65762
  /**
64532
65763
  * `training-provider` — ONE cloud that can run the CamStack trainer.
@@ -68130,6 +69361,54 @@ Object.freeze({
68130
69361
  addonId: null,
68131
69362
  access: "create"
68132
69363
  },
69364
+ "deviceCredentials.deleteDeviceCredential": {
69365
+ capName: "device-credentials",
69366
+ capScope: "system",
69367
+ addonId: null,
69368
+ access: "delete"
69369
+ },
69370
+ "deviceCredentials.getDeviceCaStatus": {
69371
+ capName: "device-credentials",
69372
+ capScope: "system",
69373
+ addonId: null,
69374
+ access: "view"
69375
+ },
69376
+ "deviceCredentials.getDeviceCertificateSettings": {
69377
+ capName: "device-credentials",
69378
+ capScope: "system",
69379
+ addonId: null,
69380
+ access: "view"
69381
+ },
69382
+ "deviceCredentials.listDeviceCredentials": {
69383
+ capName: "device-credentials",
69384
+ capScope: "system",
69385
+ addonId: null,
69386
+ access: "view"
69387
+ },
69388
+ "deviceCredentials.renameDeviceCredential": {
69389
+ capName: "device-credentials",
69390
+ capScope: "system",
69391
+ addonId: null,
69392
+ access: "create"
69393
+ },
69394
+ "deviceCredentials.revokeDeviceCredential": {
69395
+ capName: "device-credentials",
69396
+ capScope: "system",
69397
+ addonId: null,
69398
+ access: "delete"
69399
+ },
69400
+ "deviceCredentials.setDeviceCertificateSettings": {
69401
+ capName: "device-credentials",
69402
+ capScope: "system",
69403
+ addonId: null,
69404
+ access: "create"
69405
+ },
69406
+ "deviceCredentials.startEnrollment": {
69407
+ capName: "device-credentials",
69408
+ capScope: "system",
69409
+ addonId: null,
69410
+ access: "create"
69411
+ },
68133
69412
  "deviceDiscovery.adoptDevice": {
68134
69413
  capName: "device-discovery",
68135
69414
  capScope: "device",
@@ -68232,6 +69511,66 @@ Object.freeze({
68232
69511
  addonId: null,
68233
69512
  access: "create"
68234
69513
  },
69514
+ "deviceHistory.declareSeries": {
69515
+ capName: "device-history",
69516
+ capScope: "system",
69517
+ addonId: null,
69518
+ access: "create"
69519
+ },
69520
+ "deviceHistory.getDeviceEnabled": {
69521
+ capName: "device-history",
69522
+ capScope: "system",
69523
+ addonId: null,
69524
+ access: "view"
69525
+ },
69526
+ "deviceHistory.getStatus": {
69527
+ capName: "device-history",
69528
+ capScope: "system",
69529
+ addonId: null,
69530
+ access: "view"
69531
+ },
69532
+ "deviceHistory.listNodeSeries": {
69533
+ capName: "device-history",
69534
+ capScope: "system",
69535
+ addonId: null,
69536
+ access: "view"
69537
+ },
69538
+ "deviceHistory.listSeries": {
69539
+ capName: "device-history",
69540
+ capScope: "system",
69541
+ addonId: null,
69542
+ access: "view"
69543
+ },
69544
+ "deviceHistory.query": {
69545
+ capName: "device-history",
69546
+ capScope: "system",
69547
+ addonId: null,
69548
+ access: "view"
69549
+ },
69550
+ "deviceHistory.queryNode": {
69551
+ capName: "device-history",
69552
+ capScope: "system",
69553
+ addonId: null,
69554
+ access: "view"
69555
+ },
69556
+ "deviceHistory.record": {
69557
+ capName: "device-history",
69558
+ capScope: "system",
69559
+ addonId: null,
69560
+ access: "create"
69561
+ },
69562
+ "deviceHistory.recordBuckets": {
69563
+ capName: "device-history",
69564
+ capScope: "system",
69565
+ addonId: null,
69566
+ access: "create"
69567
+ },
69568
+ "deviceHistory.setDeviceEnabled": {
69569
+ capName: "device-history",
69570
+ capScope: "system",
69571
+ addonId: null,
69572
+ access: "create"
69573
+ },
68235
69574
  "deviceManager.addLocation": {
68236
69575
  capName: "device-manager",
68237
69576
  capScope: "system",
@@ -68310,6 +69649,12 @@ Object.freeze({
68310
69649
  addonId: null,
68311
69650
  access: "create"
68312
69651
  },
69652
+ "deviceManager.cleanupDeletedDevicesData": {
69653
+ capName: "device-manager",
69654
+ capScope: "system",
69655
+ addonId: null,
69656
+ access: "create"
69657
+ },
68313
69658
  "deviceManager.createDevice": {
68314
69659
  capName: "device-manager",
68315
69660
  capScope: "system",
@@ -68490,6 +69835,12 @@ Object.freeze({
68490
69835
  addonId: null,
68491
69836
  access: "view"
68492
69837
  },
69838
+ "deviceManager.listDeletedSince": {
69839
+ capName: "device-manager",
69840
+ capScope: "system",
69841
+ addonId: null,
69842
+ access: "view"
69843
+ },
68493
69844
  "deviceManager.listDeviceIdsByCaps": {
68494
69845
  capName: "device-manager",
68495
69846
  capScope: "system",
@@ -68898,6 +70249,18 @@ Object.freeze({
68898
70249
  addonId: null,
68899
70250
  access: "create"
68900
70251
  },
70252
+ "deviceRemovalHook.listDeviceData": {
70253
+ capName: "device-removal-hook",
70254
+ capScope: "system",
70255
+ addonId: null,
70256
+ access: "view"
70257
+ },
70258
+ "deviceRemovalHook.onDeviceRemoved": {
70259
+ capName: "device-removal-hook",
70260
+ capScope: "system",
70261
+ addonId: null,
70262
+ access: "create"
70263
+ },
68901
70264
  "deviceState.claimFields": {
68902
70265
  capName: "device-state",
68903
70266
  capScope: "system",
@@ -69330,6 +70693,12 @@ Object.freeze({
69330
70693
  addonId: null,
69331
70694
  access: "delete"
69332
70695
  },
70696
+ "keychain.deleteDeviceScope": {
70697
+ capName: "keychain",
70698
+ capScope: "system",
70699
+ addonId: null,
70700
+ access: "delete"
70701
+ },
69333
70702
  "keychain.getState": {
69334
70703
  capName: "keychain",
69335
70704
  capScope: "system",
@@ -70290,6 +71659,54 @@ Object.freeze({
70290
71659
  addonId: null,
70291
71660
  access: "create"
70292
71661
  },
71662
+ "nodeCertificates.confirmMeshLeaf": {
71663
+ capName: "node-certificates",
71664
+ capScope: "system",
71665
+ addonId: null,
71666
+ access: "create"
71667
+ },
71668
+ "nodeCertificates.forgetHub": {
71669
+ capName: "node-certificates",
71670
+ capScope: "system",
71671
+ addonId: null,
71672
+ access: "delete"
71673
+ },
71674
+ "nodeCertificates.getCertificates": {
71675
+ capName: "node-certificates",
71676
+ capScope: "system",
71677
+ addonId: null,
71678
+ access: "view"
71679
+ },
71680
+ "nodeCertificates.installHttpsCertificate": {
71681
+ capName: "node-certificates",
71682
+ capScope: "system",
71683
+ addonId: null,
71684
+ access: "create"
71685
+ },
71686
+ "nodeCertificates.installMeshLeaf": {
71687
+ capName: "node-certificates",
71688
+ capScope: "system",
71689
+ addonId: null,
71690
+ access: "create"
71691
+ },
71692
+ "nodeCertificates.prepareHttpsCertificate": {
71693
+ capName: "node-certificates",
71694
+ capScope: "system",
71695
+ addonId: null,
71696
+ access: "create"
71697
+ },
71698
+ "nodeCertificates.prepareMeshRenewal": {
71699
+ capName: "node-certificates",
71700
+ capScope: "system",
71701
+ addonId: null,
71702
+ access: "create"
71703
+ },
71704
+ "nodeCertificates.rollbackMeshLeaf": {
71705
+ capName: "node-certificates",
71706
+ capScope: "system",
71707
+ addonId: null,
71708
+ access: "create"
71709
+ },
70293
71710
  "nodes.clusterAddonStatus": {
70294
71711
  capName: "nodes",
70295
71712
  capScope: "system",
@@ -75133,6 +76550,26 @@ Object.freeze({
75133
76550
  form: "single",
75134
76551
  optional: false
75135
76552
  }],
76553
+ "deviceHistory.getDeviceEnabled": [{
76554
+ name: "deviceId",
76555
+ form: "single",
76556
+ optional: false
76557
+ }],
76558
+ "deviceHistory.listSeries": [{
76559
+ name: "deviceId",
76560
+ form: "single",
76561
+ optional: false
76562
+ }],
76563
+ "deviceHistory.query": [{
76564
+ name: "deviceId",
76565
+ form: "single",
76566
+ optional: false
76567
+ }],
76568
+ "deviceHistory.setDeviceEnabled": [{
76569
+ name: "deviceId",
76570
+ form: "single",
76571
+ optional: false
76572
+ }],
75136
76573
  "deviceManager.adoptionRelease": [{
75137
76574
  name: "camDeviceId",
75138
76575
  form: "single",
@@ -75426,6 +76863,11 @@ Object.freeze({
75426
76863
  form: "single",
75427
76864
  optional: false
75428
76865
  }],
76866
+ "deviceRemovalHook.onDeviceRemoved": [{
76867
+ name: "deviceId",
76868
+ form: "single",
76869
+ optional: false
76870
+ }],
75429
76871
  "deviceState.claimFields": [{
75430
76872
  name: "deviceId",
75431
76873
  form: "single",
@@ -75615,6 +77057,11 @@ Object.freeze({
75615
77057
  form: "single",
75616
77058
  optional: false
75617
77059
  }],
77060
+ "keychain.deleteDeviceScope": [{
77061
+ name: "deviceId",
77062
+ form: "single",
77063
+ optional: false
77064
+ }],
75618
77065
  "lawnMowerControl.dock": [{
75619
77066
  name: "deviceId",
75620
77067
  form: "single",
@@ -77311,6 +78758,7 @@ Object.freeze({
77311
78758
  "network-access": "ingress",
77312
78759
  "smtp-provider": "email"
77313
78760
  });
78761
+ DeviceType.Camera;
77314
78762
  var G711_SCALE_CORRECTION_DB = {
77315
78763
  PCMU: 20 * Math.log10(4),
77316
78764
  PCMA: 20 * Math.log10(8)
@@ -77962,6 +79410,8 @@ new Set([
77962
79410
  "audio-tools",
77963
79411
  "audio-silence"
77964
79412
  ]);
79413
+ AppPageLangSchema.options;
79414
+ record(string().min(1), string());
77965
79415
  /**
77966
79416
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
77967
79417
  *
@@ -185281,71 +186731,88 @@ var intercomFailureReport = new IntercomFailureReport();
185281
186731
  var SNAPSHOT_GROUPS = [
185282
186732
  {
185283
186733
  key: "imageSnapshot",
185284
- label: "Image (getVideoInput)"
186734
+ label: "Image (getVideoInput)",
186735
+ labelKey: "provider-reolink:snapshot_groups.imageSnapshot.label"
185285
186736
  },
185286
186737
  {
185287
186738
  key: "motionSnapshot",
185288
- label: "Motion (getMotionAlarm)"
186739
+ label: "Motion (getMotionAlarm)",
186740
+ labelKey: "provider-reolink:snapshot_groups.motionSnapshot.label"
185289
186741
  },
185290
186742
  {
185291
186743
  key: "aiSensitivitySnapshot",
185292
- label: "AI sensitivity (getAiDetectionFull)"
186744
+ label: "AI sensitivity (getAiDetectionFull)",
186745
+ labelKey: "provider-reolink:snapshot_groups.aiSensitivitySnapshot.label"
185293
186746
  },
185294
186747
  {
185295
186748
  key: "encSnapshot",
185296
- label: "Encoder (getEnc)"
186749
+ label: "Encoder (getEnc)",
186750
+ labelKey: "provider-reolink:snapshot_groups.encSnapshot.label"
185297
186751
  },
185298
186752
  {
185299
186753
  key: "encOptionsSnapshot",
185300
- label: "Encoder options (getEncOptions)"
186754
+ label: "Encoder options (getEncOptions)",
186755
+ labelKey: "provider-reolink:snapshot_groups.encOptionsSnapshot.label"
185301
186756
  },
185302
186757
  {
185303
186758
  key: "maskSnapshot",
185304
- label: "Privacy mask (getMask)"
186759
+ label: "Privacy mask (getMask)",
186760
+ labelKey: "provider-reolink:snapshot_groups.maskSnapshot.label"
185305
186761
  },
185306
186762
  {
185307
186763
  key: "audioNoiseSnapshot",
185308
- label: "Audio noise (getAudioNoise)"
186764
+ label: "Audio noise (getAudioNoise)",
186765
+ labelKey: "provider-reolink:snapshot_groups.audioNoiseSnapshot.label"
185309
186766
  },
185310
186767
  {
185311
186768
  key: "autoFocusSnapshot",
185312
- label: "Auto-focus (getAutoFocus)"
186769
+ label: "Auto-focus (getAutoFocus)",
186770
+ labelKey: "provider-reolink:snapshot_groups.autoFocusSnapshot.label"
185313
186771
  },
185314
186772
  {
185315
186773
  key: "netPortSnapshot",
185316
- label: "Network ports (getNetPort)"
186774
+ label: "Network ports (getNetPort)",
186775
+ labelKey: "provider-reolink:snapshot_groups.netPortSnapshot.label"
185317
186776
  },
185318
186777
  {
185319
186778
  key: "ntpSnapshot",
185320
- label: "NTP (getNtp)"
186779
+ label: "NTP (getNtp)",
186780
+ labelKey: "provider-reolink:snapshot_groups.ntpSnapshot.label"
185321
186781
  },
185322
186782
  {
185323
186783
  key: "systemGeneralSnapshot",
185324
- label: "System general (getSystemGeneral)"
186784
+ label: "System general (getSystemGeneral)",
186785
+ labelKey: "provider-reolink:snapshot_groups.systemGeneralSnapshot.label"
185325
186786
  },
185326
186787
  {
185327
186788
  key: "osdSnapshot",
185328
- label: "OSD overlay (getOsd)"
186789
+ label: "OSD overlay (getOsd)",
186790
+ labelKey: "provider-reolink:snapshot_groups.osdSnapshot.label"
185329
186791
  },
185330
186792
  {
185331
186793
  key: "ledSnapshot",
185332
- label: "LEDs (getIrLights)"
186794
+ label: "LEDs (getIrLights)",
186795
+ labelKey: "provider-reolink:snapshot_groups.ledSnapshot.label"
185333
186796
  },
185334
186797
  {
185335
186798
  key: "pirSnapshot",
185336
- label: "PIR (getPirInfo)"
186799
+ label: "PIR (getPirInfo)",
186800
+ labelKey: "provider-reolink:snapshot_groups.pirSnapshot.label"
185337
186801
  },
185338
186802
  {
185339
186803
  key: "autoRebootSnapshot",
185340
- label: "Auto reboot (getAutoReboot)"
186804
+ label: "Auto reboot (getAutoReboot)",
186805
+ labelKey: "provider-reolink:snapshot_groups.autoRebootSnapshot.label"
185341
186806
  },
185342
186807
  {
185343
186808
  key: "emailConfigSnapshot",
185344
- label: "Email/SMTP (getEmail)"
186809
+ label: "Email/SMTP (getEmail)",
186810
+ labelKey: "provider-reolink:snapshot_groups.emailConfigSnapshot.label"
185345
186811
  },
185346
186812
  {
185347
186813
  key: "capOptionsSnapshot",
185348
- label: "Cap option probes (getOptions)"
186814
+ label: "Cap option probes (getOptions)",
186815
+ labelKey: "provider-reolink:snapshot_groups.capOptionsSnapshot.label"
185349
186816
  }
185350
186817
  ];
185351
186818
  /**
@@ -185429,12 +186896,15 @@ function buildSnapshotFreshnessSection(cache, opts) {
185429
186896
  id: "snapshotFreshness",
185430
186897
  tab: "advanced",
185431
186898
  title: "Snapshot freshness",
186899
+ titleKey: "provider-reolink:snapshotFreshnessSection.s_snapshotFreshness.title",
185432
186900
  description: "When each cached camera reading was last fetched. Groups without a stamp were persisted before per-snapshot tracking — their age is unknown, not fresh. Use \"Refresh from camera\" (General) to re-read an awake camera.",
186901
+ descriptionKey: "provider-reolink:snapshotFreshnessSection.s_snapshotFreshness.description",
185433
186902
  columns: 1,
185434
186903
  fields: [{
185435
186904
  type: "info",
185436
186905
  key: "snapshotFreshness",
185437
186906
  label: "Per-snapshot read times",
186907
+ labelKey: "provider-reolink:field.snapshotFreshness.label",
185438
186908
  content: `${opts.sleeping ? "Camera is asleep — no group can refresh until it wakes; a sleeping battery camera is never woken to read settings.\n" : ""}${header}\n${lines.join("\n")}`
185439
186909
  }]
185440
186910
  };
@@ -186630,7 +188100,7 @@ function capDayNightModeToReolink(mode) {
186630
188100
  case "auto": return "auto";
186631
188101
  case "day": return "color";
186632
188102
  case "night": return "blackAndWhite";
186633
- case "schedule": throw new Error("Reolink cameras do not support a scheduled day/night mode");
188103
+ case "schedule": throw userError("reolink.day-night-schedule-unsupported", void 0, "Reolink cameras do not support a scheduled day/night mode");
186634
188104
  }
186635
188105
  }
186636
188106
  //#endregion
@@ -186925,6 +188395,7 @@ function buildOsdStateBanner(snapshot, opts) {
186925
188395
  type: "info",
186926
188396
  key: "osdSnapshotState",
186927
188397
  label: "OSD state",
188398
+ labelKey: "provider-reolink:osdStateBanner.osdSnapshotState.label",
186928
188399
  variant: "warning",
186929
188400
  content: snapshot === void 0 ? `Camera is asleep and its OSD state has never been read — the toggles below are unknown until it wakes; ${NEVER_WOKEN_SUFFIX}` : snapshot.fetchedAt !== void 0 ? `Camera is asleep — showing the OSD state last read ${new Date(snapshot.fetchedAt).toLocaleString()}. It refreshes when the camera wakes; ${NEVER_WOKEN_SUFFIX}` : `Camera is asleep — showing the last known OSD state (age unknown). It refreshes when the camera wakes; ${NEVER_WOKEN_SUFFIX}`
186930
188401
  };
@@ -186932,6 +188403,7 @@ function buildOsdStateBanner(snapshot, opts) {
186932
188403
  type: "info",
186933
188404
  key: "osdSnapshotState",
186934
188405
  label: "OSD state",
188406
+ labelKey: "provider-reolink:osdStateBanner.osdSnapshotState.label",
186935
188407
  variant: "warning",
186936
188408
  content: "OSD state has not been read from this camera yet — unknown toggles are disabled until a read succeeds."
186937
188409
  };
@@ -186981,7 +188453,9 @@ function buildOsdSection(snapshot, values, opts) {
186981
188453
  id: "osd",
186982
188454
  tab: "image",
186983
188455
  title: "OSD overlay",
188456
+ titleKey: "provider-reolink:osdSection.s_osd.title",
186984
188457
  description: "On-screen overlays burned into the video by the camera. Read with `GetOsdDatetime` (cmd_id 44) and pushed with `SetOsdDatetime` (cmd_id 45). Positions are the four frame corners; an overlay the camera holds somewhere else keeps that placement unless a corner is chosen.",
188458
+ descriptionKey: "provider-reolink:osdSection.s_osd.description",
186985
188459
  columns: 2,
186986
188460
  fields: [
186987
188461
  ...banner ? [banner] : [],
@@ -186991,7 +188465,9 @@ function buildOsdSection(snapshot, values, opts) {
186991
188465
  type: "text",
186992
188466
  key: "osdChannelName",
186993
188467
  label: "Channel name",
188468
+ labelKey: "provider-reolink:fields.osdChannelName.label",
186994
188469
  description: "Text shown in the channel-name overlay.",
188470
+ descriptionKey: "provider-reolink:fields.osdChannelName.description",
186995
188471
  default: values.osdChannelName,
186996
188472
  placeholder: "Front door"
186997
188473
  },
@@ -187474,13 +188950,17 @@ var SirenAccessory = class extends BaseDevice {
187474
188950
  return hydrateSchema({ sections: [{
187475
188951
  id: "siren",
187476
188952
  title: "Siren",
188953
+ titleKey: "provider-reolink:schema.s_siren.title",
187477
188954
  description: "Tone duration when triggered. Set to 0 for \"play until turned off\"; positive values pulse for that many seconds.",
188955
+ descriptionKey: "provider-reolink:schema.s_siren.description",
187478
188956
  columns: 1,
187479
188957
  fields: [{
187480
188958
  type: "number",
187481
188959
  key: "sirenDurationS",
187482
188960
  label: "Default duration (seconds)",
188961
+ labelKey: "provider-reolink:schema.s_siren.sirenDurationS.label",
187483
188962
  description: "Forwarded to `setSiren(on=true, duration)` (cmd_id 30) when the operator flips the switch.",
188963
+ descriptionKey: "provider-reolink:schema.s_siren.sirenDurationS.description",
187484
188964
  default: 0,
187485
188965
  min: 0,
187486
188966
  max: 300,
@@ -187489,13 +188969,17 @@ var SirenAccessory = class extends BaseDevice {
187489
188969
  }, {
187490
188970
  id: "motion-siren",
187491
188971
  title: "Motion-triggered siren",
188972
+ titleKey: "provider-reolink:schema.s_motion-siren.title",
187492
188973
  description: "Detection classes that fire the siren when \"Trigger on motion\" is armed. Forwarded as a `typeScheduleList` to `setSirenOnMotion` (cmd_id 231). Saved values re-apply on every motion-trigger flip.",
188974
+ descriptionKey: "provider-reolink:schema.s_motion-siren.description",
187493
188975
  columns: 1,
187494
188976
  fields: [{
187495
188977
  type: "select",
187496
188978
  key: "motionSirenDetectType",
187497
188979
  label: "Detection classes",
188980
+ labelKey: "provider-reolink:schema.s_motion-siren.motionSirenDetectType.label",
187498
188981
  description: "Object/motion classes that arm the siren. `MD` = generic motion detection.",
188982
+ descriptionKey: "provider-reolink:schema.s_motion-siren.motionSirenDetectType.description",
187499
188983
  options: SIREN_DETECT_TYPE_CHOICES.map((v) => ({
187500
188984
  value: v,
187501
188985
  label: v.replace(/,/g, " + ").replace(/_/g, "+")
@@ -187958,13 +189442,17 @@ var FloodlightAccessory = class extends BaseDevice {
187958
189442
  return hydrateSchema({ sections: [{
187959
189443
  id: "motion-floodlight",
187960
189444
  title: "Motion-triggered floodlight",
189445
+ titleKey: "provider-reolink:schema.s_motion-floodlight.title",
187961
189446
  description: "Pushed to the camera (`FloodlightTask`) when the \"Trigger on motion\" toggle is armed. Saved values also re-apply on every motion-trigger flip so the firmware always honours your preference.",
189447
+ descriptionKey: "provider-reolink:schema.s_motion-floodlight.description",
187962
189448
  columns: 2,
187963
189449
  fields: [{
187964
189450
  type: "number",
187965
189451
  key: "motionLightDurationS",
187966
189452
  label: "Light duration (s)",
189453
+ labelKey: "provider-reolink:schema.s_motion-floodlight.motionLightDurationS.label",
187967
189454
  description: "Seconds the floodlight stays lit after a motion event. Reolink range 1..600s.",
189455
+ descriptionKey: "provider-reolink:schema.s_motion-floodlight.motionLightDurationS.description",
187968
189456
  min: 1,
187969
189457
  max: 600,
187970
189458
  step: 1,
@@ -187973,7 +189461,9 @@ var FloodlightAccessory = class extends BaseDevice {
187973
189461
  type: "select",
187974
189462
  key: "motionLightDetectType",
187975
189463
  label: "Detection classes",
189464
+ labelKey: "provider-reolink:schema.s_motion-floodlight.motionLightDetectType.label",
187976
189465
  description: "Object classes that turn the floodlight on. Composed values (e.g. \"people,vehicle\") fire when ANY listed class detects.",
189466
+ descriptionKey: "provider-reolink:schema.s_motion-floodlight.motionLightDetectType.description",
187977
189467
  options: FLOODLIGHT_DETECT_TYPE_CHOICES.map((v) => ({
187978
189468
  value: v,
187979
189469
  label: v.replace(/,/g, " + ").replace(/_/g, "+")
@@ -188212,14 +189702,18 @@ var PirAccessory = class extends BaseDevice {
188212
189702
  return hydrateSchema({ sections: [{
188213
189703
  id: "pir",
188214
189704
  title: "PIR sensor",
189705
+ titleKey: "provider-reolink:schema.s_pir.title",
188215
189706
  description: "Hardware passive-infrared trigger configuration. Pushed via cmd_id 153 (SetPirInfo).",
189707
+ descriptionKey: "provider-reolink:schema.s_pir.description",
188216
189708
  columns: 2,
188217
189709
  fields: [
188218
189710
  {
188219
189711
  type: "slider",
188220
189712
  key: "pirSensitive",
188221
189713
  label: "Sensitivity",
189714
+ labelKey: "provider-reolink:schema.s_pir.pirSensitive.label",
188222
189715
  description: "Higher value → triggers on smaller motion. Most cameras max at 100.",
189716
+ descriptionKey: "provider-reolink:schema.s_pir.pirSensitive.description",
188223
189717
  min: 1,
188224
189718
  max: 100,
188225
189719
  step: 1,
@@ -188230,6 +189724,7 @@ var PirAccessory = class extends BaseDevice {
188230
189724
  type: "slider",
188231
189725
  key: "pirIntervalS",
188232
189726
  label: "Cooldown (seconds)",
189727
+ labelKey: "provider-reolink:schema.s_pir.pirIntervalS.label",
188233
189728
  description: `Minimum delay between consecutive PIR triggers. Reolink app range 5..${intervalMax}s; firmware clamps on push.`,
188234
189729
  min: 5,
188235
189730
  max: intervalMax,
@@ -188242,7 +189737,9 @@ var PirAccessory = class extends BaseDevice {
188242
189737
  type: "boolean",
188243
189738
  key: "pirReduceAlarm",
188244
189739
  label: "Reduce false alarms",
189740
+ labelKey: "provider-reolink:schema.s_pir.pirReduceAlarm.label",
188245
189741
  description: "Trades detection range for fewer false positives. Maps to firmware `reduceAlarm` flag.",
189742
+ descriptionKey: "provider-reolink:schema.s_pir.pirReduceAlarm.description",
188246
189743
  ...typeof reduceAlarm === "boolean" ? { default: reduceAlarm } : {},
188247
189744
  style: "switch",
188248
189745
  span: 2
@@ -189770,6 +191267,7 @@ function buildSessionsTabSections(snap, opts) {
189770
191267
  type: "info",
189771
191268
  key: "sessionsError",
189772
191269
  label: "Refresh error",
191270
+ labelKey: "provider-reolink:errorBanner.sessionsError.label",
189773
191271
  content: snap.error,
189774
191272
  variant: "danger"
189775
191273
  }] : [];
@@ -189791,6 +191289,7 @@ function buildSessionsTabSections(snap, opts) {
189791
191289
  id: "sessions-status",
189792
191290
  title: opts.title ?? "Sessions",
189793
191291
  description: "Diagnostics for the Baichuan control channel. Auto-refreshes when older than 1 minute; click Refresh to force.",
191292
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.description",
189794
191293
  tab: opts.tabId,
189795
191294
  order: 0,
189796
191295
  columns: 1,
@@ -189800,38 +191299,46 @@ function buildSessionsTabSections(snap, opts) {
189800
191299
  type: "info",
189801
191300
  key: "sessionsLastRefreshed",
189802
191301
  label: "Last refreshed",
191302
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsLastRefreshed.label",
189803
191303
  content: ageInfo
189804
191304
  },
189805
191305
  {
189806
191306
  type: "object-array",
189807
191307
  key: "sessionsOnlineUsers",
189808
191308
  label: "Online users (firmware)",
191309
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.label",
189809
191310
  description: "Active user sessions reported by the device (cmd_id 145). Includes other admins, mobile app, NVR linkups.",
191311
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.description",
189810
191312
  columns: [
189811
191313
  {
189812
191314
  key: "userName",
189813
- label: "User"
191315
+ label: "User",
191316
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.userName.label"
189814
191317
  },
189815
191318
  {
189816
191319
  key: "ip",
189817
191320
  label: "IP",
191321
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.ip.label",
189818
191322
  kind: "monospace",
189819
191323
  width: "160px"
189820
191324
  },
189821
191325
  {
189822
191326
  key: "sessionId",
189823
191327
  label: "Session",
191328
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.sessionId.label",
189824
191329
  kind: "monospace",
189825
191330
  width: "90px"
189826
191331
  },
189827
191332
  {
189828
191333
  key: "level",
189829
191334
  label: "Level",
191335
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.level.label",
189830
191336
  width: "80px"
189831
191337
  },
189832
191338
  {
189833
191339
  key: "status",
189834
191340
  label: "State",
191341
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsOnlineUsers.status.label",
189835
191342
  kind: "status",
189836
191343
  width: "100px"
189837
191344
  }
@@ -189843,14 +191350,18 @@ function buildSessionsTabSections(snap, opts) {
189843
191350
  type: "object-array",
189844
191351
  key: "sessionsSocketPool",
189845
191352
  label: "Our socket pool",
191353
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.label",
189846
191354
  description: "Open Baichuan TCP/UDP sockets this process holds for the device. One row per pool tag.",
191355
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.description",
189847
191356
  columns: [{
189848
191357
  key: "tag",
189849
191358
  label: "Tag",
191359
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.tag.label",
189850
191360
  kind: "monospace"
189851
191361
  }, {
189852
191362
  key: "state",
189853
191363
  label: "State",
191364
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsSocketPool.state.label",
189854
191365
  kind: "status",
189855
191366
  width: "100px"
189856
191367
  }],
@@ -189861,10 +191372,13 @@ function buildSessionsTabSections(snap, opts) {
189861
191372
  type: "object-array",
189862
191373
  key: "sessionsDedicated",
189863
191374
  label: "Dedicated lib sessions",
191375
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsDedicated.label",
189864
191376
  description: "Internal lib-side dedicated sessions (streaming/talk channels). Older lib versions leave this list empty.",
191377
+ descriptionKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsDedicated.description",
189865
191378
  columns: [{
189866
191379
  key: "key",
189867
191380
  label: "Session key",
191381
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsDedicated.key.label",
189868
191382
  kind: "monospace"
189869
191383
  }],
189870
191384
  value: dedicatedRows,
@@ -189874,12 +191388,14 @@ function buildSessionsTabSections(snap, opts) {
189874
191388
  type: "info",
189875
191389
  key: "sessionsCooldown",
189876
191390
  label: "Reconnect cooldown",
191391
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status.sessionsCooldown.label",
189877
191392
  content: cooldownText
189878
191393
  },
189879
191394
  {
189880
191395
  type: "device-action-button",
189881
191396
  key: "_refreshSessions",
189882
191397
  label: "Refresh sessions",
191398
+ labelKey: "provider-reolink:sessionsTabSections.s_sessions-status._refreshSessions.label",
189883
191399
  buttonLabel: "Refresh now",
189884
191400
  action: "refresh-sessions",
189885
191401
  variant: "default"
@@ -191351,11 +192867,11 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
191351
192867
  });
191352
192868
  if (!this.isEmailPushEligible) {
191353
192869
  this.ctx.logger.warn("email-push: not eligible (hub-attached camera)", { tags: { deviceId: this.id } });
191354
- throw new Error("Email push is not available for hub-attached cameras");
192870
+ throw userError("reolink.email-push-hub-attached", void 0, "Email push is not available for hub-attached cameras");
191355
192871
  }
191356
192872
  if (!this.emailPushParamsResolver) {
191357
192873
  this.ctx.logger.warn("email-push: resolver not wired — restart the Reolink addon", { tags: { deviceId: this.id } });
191358
- throw new Error("Email Push Server is not wired — restart the Reolink addon");
192874
+ throw userError("reolink.email-push-not-wired", void 0, "Email Push Server is not wired — restart the Reolink addon");
191359
192875
  }
191360
192876
  const params = await this.emailPushParamsResolver();
191361
192877
  if (!params) {
@@ -191414,14 +192930,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
191414
192930
  this.ctx.logger.warn("baichuan-webhook: not eligible", { tags: { deviceId: this.id } });
191415
192931
  throw new Error("HaCfg webhook Auto-configure is only available on standalone battery cameras");
191416
192932
  }
191417
- if (!this.baichuanWebhookParamsResolver) throw new Error("HaCfg Webhook Server is not wired — restart the Reolink addon");
192933
+ if (!this.baichuanWebhookParamsResolver) throw userError("reolink.webhook-not-wired", void 0, "HaCfg Webhook Server is not wired — restart the Reolink addon");
191418
192934
  const params = await this.baichuanWebhookParamsResolver();
191419
192935
  if (!params) throw new Error("Enable the HaCfg Webhook Server in the Reolink addon settings, and ensure this host has a reachable LAN address.");
191420
192936
  await this.wakeIfSleeping();
191421
192937
  const api = await this.ensureApi();
191422
192938
  const supported = await api.probeBaichuanWebhookSupport();
191423
192939
  this.baichuanWebhookSupportedCached = supported;
191424
- if (!supported) throw new Error("Camera firmware does not support Baichuan HaCfg webhook (cmd 806)");
192940
+ if (!supported) throw userError("reolink.webhook-unsupported", void 0, "Camera firmware does not support Baichuan HaCfg webhook (cmd 806)");
191425
192941
  this.ctx.logger.info("baichuan-webhook: auto-configuring camera", {
191426
192942
  tags: { deviceId: this.id },
191427
192943
  meta: { url: params.url }
@@ -192100,26 +193616,32 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192100
193616
  id: "sleep-push",
192101
193617
  tab: "motion",
192102
193618
  title: "Battery motion (sleep push)",
193619
+ titleKey: "provider-reolink:emailPushSection.s_sleep-push.title",
192103
193620
  description: "Battery cameras sleep and can't be polled for motion. Prefer HaCfg webhook when the firmware supports it (cmd 806); otherwise use Email/SMTP. Enable the matching server under Integrations → Reolink, then Auto-configure. Only one path should be armed — the preference below gates which inbound source is accepted.",
193621
+ descriptionKey: "provider-reolink:emailPushSection.s_sleep-push.description",
192104
193622
  columns: 1,
192105
193623
  fields: [
192106
193624
  {
192107
193625
  type: "select",
192108
193626
  key: "batterySleepPushMethod",
192109
193627
  label: "Preferred sleep-push path",
193628
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.label",
192110
193629
  default: "auto",
192111
193630
  options: [
192112
193631
  {
192113
193632
  value: "auto",
192114
- label: "Auto (webhook if supported, else email)"
193633
+ label: "Auto (webhook if supported, else email)",
193634
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.o_auto.label"
192115
193635
  },
192116
193636
  {
192117
193637
  value: "webhook",
192118
- label: "HaCfg webhook only"
193638
+ label: "HaCfg webhook only",
193639
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.o_webhook.label"
192119
193640
  },
192120
193641
  {
192121
193642
  value: "email",
192122
- label: "Email/SMTP only"
193643
+ label: "Email/SMTP only",
193644
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push.batterySleepPushMethod.o_email.label"
192123
193645
  }
192124
193646
  ]
192125
193647
  },
@@ -192127,6 +193649,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192127
193649
  type: "device-action-button",
192128
193650
  key: "_autoConfigureBaichuanWebhook",
192129
193651
  label: "HaCfg webhook",
193652
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push._autoConfigureBaichuanWebhook.label",
192130
193653
  buttonLabel: "Auto-configure HaCfg webhook",
192131
193654
  action: "auto-configure-baichuan-webhook",
192132
193655
  variant: "default"
@@ -192135,6 +193658,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192135
193658
  type: "device-action-button",
192136
193659
  key: "_autoConfigureEmailPush",
192137
193660
  label: "Email push",
193661
+ labelKey: "provider-reolink:emailPushSection.s_sleep-push._autoConfigureEmailPush.label",
192138
193662
  buttonLabel: "Auto-configure email push",
192139
193663
  action: "auto-configure-email-push",
192140
193664
  variant: "default"
@@ -192158,19 +193682,23 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192158
193682
  id: "email-smtp",
192159
193683
  tab: "email",
192160
193684
  title: "SMTP server",
193685
+ titleKey: "provider-reolink:emailTabSections.s_email-smtp.title",
192161
193686
  description: "The camera's own outgoing-mail (SMTP) settings. Edit and save to write them to the camera, or use \"Auto-configure email push\" under Motion to point it at the built-in server. Leave the password blank to keep the current one.",
193687
+ descriptionKey: "provider-reolink:emailTabSections.s_email-smtp.description",
192162
193688
  columns: 2,
192163
193689
  fields: [
192164
193690
  {
192165
193691
  type: "text",
192166
193692
  key: "emailSmtpServer",
192167
193693
  label: "SMTP server",
193694
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSmtpServer.label",
192168
193695
  placeholder: "smtp.example.com"
192169
193696
  },
192170
193697
  {
192171
193698
  type: "number",
192172
193699
  key: "emailSmtpPort",
192173
193700
  label: "SMTP port",
193701
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSmtpPort.label",
192174
193702
  min: 1,
192175
193703
  max: 65535,
192176
193704
  step: 1
@@ -192179,12 +193707,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192179
193707
  type: "text",
192180
193708
  key: "emailUserName",
192181
193709
  label: "Username / sender",
193710
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailUserName.label",
192182
193711
  placeholder: "cam@example.com"
192183
193712
  },
192184
193713
  {
192185
193714
  type: "password",
192186
193715
  key: "emailPassword",
192187
193716
  label: "Password",
193717
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailPassword.label",
192188
193718
  showToggle: true,
192189
193719
  placeholder: "leave blank to keep"
192190
193720
  },
@@ -192192,12 +193722,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192192
193722
  type: "boolean",
192193
193723
  key: "emailSsl",
192194
193724
  label: "SSL/TLS",
193725
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSsl.label",
192195
193726
  style: "switch"
192196
193727
  },
192197
193728
  {
192198
193729
  type: "text",
192199
193730
  key: "emailSendNickname",
192200
- label: "Sender name"
193731
+ label: "Sender name",
193732
+ labelKey: "provider-reolink:emailTabSections.s_email-smtp.emailSendNickname.label"
192201
193733
  }
192202
193734
  ]
192203
193735
  },
@@ -192205,24 +193737,29 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192205
193737
  id: "email-recipients",
192206
193738
  tab: "email",
192207
193739
  title: "Recipients",
193740
+ titleKey: "provider-reolink:emailTabSections.s_email-recipients.title",
192208
193741
  description: "Up to three recipient addresses the camera sends motion mail to.",
193742
+ descriptionKey: "provider-reolink:emailTabSections.s_email-recipients.description",
192209
193743
  columns: 1,
192210
193744
  fields: [
192211
193745
  {
192212
193746
  type: "text",
192213
193747
  key: "emailRecipient1",
192214
193748
  label: "Recipient 1",
193749
+ labelKey: "provider-reolink:emailTabSections.s_email-recipients.emailRecipient1.label",
192215
193750
  placeholder: "alerts@example.com"
192216
193751
  },
192217
193752
  {
192218
193753
  type: "text",
192219
193754
  key: "emailRecipient2",
192220
- label: "Recipient 2"
193755
+ label: "Recipient 2",
193756
+ labelKey: "provider-reolink:emailTabSections.s_email-recipients.emailRecipient2.label"
192221
193757
  },
192222
193758
  {
192223
193759
  type: "text",
192224
193760
  key: "emailRecipient3",
192225
- label: "Recipient 3"
193761
+ label: "Recipient 3",
193762
+ labelKey: "provider-reolink:emailTabSections.s_email-recipients.emailRecipient3.label"
192226
193763
  }
192227
193764
  ]
192228
193765
  },
@@ -192230,12 +193767,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
192230
193767
  id: "email-schedule",
192231
193768
  tab: "email",
192232
193769
  title: "Alarm schedule",
193770
+ titleKey: "provider-reolink:emailTabSections.s_email-schedule.title",
192233
193771
  description: "Master enable for the camera's email alarm schedule (EmailTask).",
193772
+ descriptionKey: "provider-reolink:emailTabSections.s_email-schedule.description",
192234
193773
  columns: 1,
192235
193774
  fields: [{
192236
193775
  type: "boolean",
192237
193776
  key: "emailScheduleEnabled",
192238
193777
  label: "Email alarm schedule enabled",
193778
+ labelKey: "provider-reolink:emailTabSections.s_email-schedule.emailScheduleEnabled.label",
192239
193779
  style: "switch"
192240
193780
  }]
192241
193781
  }
@@ -194463,7 +196003,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194463
196003
  const api = await this.ensureApi();
194464
196004
  const channel = this.getChannel();
194465
196005
  const id = Number(presetId);
194466
- if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
196006
+ if (!Number.isFinite(id)) throw userError("reolink.preset-invalid", { presetId: String(presetId) }, `Invalid presetId: ${presetId}`);
194467
196007
  await api.moveToPtzPreset(channel, id);
194468
196008
  },
194469
196009
  savePreset: async ({ deviceId, presetId, name }) => {
@@ -194471,7 +196011,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194471
196011
  const api = await this.ensureApi();
194472
196012
  const channel = this.getChannel();
194473
196013
  const id = Number(presetId);
194474
- if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
196014
+ if (!Number.isFinite(id)) throw userError("reolink.preset-invalid", { presetId: String(presetId) }, `Invalid presetId: ${presetId}`);
194475
196015
  await api.setPtzPreset(channel, id, name);
194476
196016
  },
194477
196017
  deletePreset: async ({ deviceId, presetId }) => {
@@ -194479,7 +196019,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194479
196019
  const api = await this.ensureApi();
194480
196020
  const channel = this.getChannel();
194481
196021
  const id = Number(presetId);
194482
- if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
196022
+ if (!Number.isFinite(id)) throw userError("reolink.preset-invalid", { presetId: String(presetId) }, `Invalid presetId: ${presetId}`);
194483
196023
  await api.deletePtzPreset(channel, id);
194484
196024
  },
194485
196025
  getOptions: async ({ deviceId }) => {
@@ -194531,7 +196071,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
194531
196071
  }
194532
196072
  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");
194533
196073
  const id = Number(home.presetId);
194534
- if (!Number.isFinite(id)) throw new Error(`Configured home preset is not a Baichuan id: ${home.presetId}`);
196074
+ if (!Number.isFinite(id)) throw userError("reolink.home-preset-not-baichuan", { presetId: String(home.presetId) }, `Configured home preset is not a Baichuan id: ${home.presetId}`);
194535
196075
  const api = await this.ensureApi();
194536
196076
  const channel = this.getChannel();
194537
196077
  await api.moveToPtzPreset(channel, id);
@@ -196103,12 +197643,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196103
197643
  id: "driver-refresh",
196104
197644
  tab: "general",
196105
197645
  title: "Camera data",
197646
+ titleKey: "provider-reolink:schema.s_driver-refresh.title",
196106
197647
  description: "Go and ask the camera now. Every other read here is deliberately cheap about a battery camera — it serves what it already knows and refreshes quietly behind you, and never wakes a sleeping camera just because you opened this tab. This button is the exception: it wakes the camera, re-reads everything, and tells you what it got. Use it after changing settings from the Reolink app, or whenever you want to be sure the form is showing the camera and not a memory of it.",
197648
+ descriptionKey: "provider-reolink:schema.s_driver-refresh.description",
196107
197649
  columns: 1,
196108
197650
  fields: [{
196109
197651
  type: "device-action-button",
196110
197652
  key: "_refreshSettings",
196111
197653
  label: "Refresh from camera",
197654
+ labelKey: "provider-reolink:schema.s_driver-refresh._refreshSettings.label",
196112
197655
  buttonLabel: "Refresh now",
196113
197656
  action: "refresh-settings",
196114
197657
  variant: "default",
@@ -196119,13 +197662,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196119
197662
  id: "connection",
196120
197663
  tab: "general",
196121
197664
  title: "Connection",
197665
+ titleKey: "provider-reolink:schema.s_connection.title",
196122
197666
  description: "Baichuan protocol connection settings.",
197667
+ descriptionKey: "provider-reolink:schema.s_connection.description",
196123
197668
  columns: 2,
196124
197669
  fields: [
196125
197670
  {
196126
197671
  type: "text",
196127
197672
  key: "host",
196128
197673
  label: "Camera IP",
197674
+ labelKey: "provider-reolink:schema.s_connection.host.label",
196129
197675
  required: true,
196130
197676
  placeholder: "192.168.10.100"
196131
197677
  },
@@ -196133,6 +197679,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196133
197679
  type: "number",
196134
197680
  key: "port",
196135
197681
  label: "Port",
197682
+ labelKey: "provider-reolink:schema.s_connection.port.label",
196136
197683
  default: 9e3,
196137
197684
  min: 1,
196138
197685
  max: 65535,
@@ -196142,12 +197689,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196142
197689
  type: "text",
196143
197690
  key: "username",
196144
197691
  label: "Username",
197692
+ labelKey: "provider-reolink:schema.s_connection.username.label",
196145
197693
  default: "admin"
196146
197694
  },
196147
197695
  {
196148
197696
  type: "password",
196149
197697
  key: "password",
196150
197698
  label: "Password",
197699
+ labelKey: "provider-reolink:schema.s_connection.password.label",
196151
197700
  required: true,
196152
197701
  showToggle: true
196153
197702
  }
@@ -196157,13 +197706,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196157
197706
  id: "network-ports",
196158
197707
  tab: "network",
196159
197708
  title: "Service ports",
197709
+ titleKey: "provider-reolink:schema.s_network-ports.title",
196160
197710
  description: "Per-protocol enable toggle + port number, pushed via Baichuan `SetNetPort` (cmd_id 36). The Baichuan control port is the Connection → Port field. Changing a port number breaks existing URLs for that protocol until consumers are reconfigured.",
197711
+ descriptionKey: "provider-reolink:schema.s_network-ports.description",
196161
197712
  columns: 2,
196162
197713
  fields: [
196163
197714
  {
196164
197715
  type: "boolean",
196165
197716
  key: "netHttpEnable",
196166
197717
  label: "HTTP enabled",
197718
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpEnable.label",
196167
197719
  default: netSnap.http?.enable ?? true,
196168
197720
  style: "switch"
196169
197721
  },
@@ -196171,6 +197723,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196171
197723
  type: "number",
196172
197724
  key: "netHttpPort",
196173
197725
  label: "HTTP port",
197726
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpPort.label",
196174
197727
  default: netSnap.http?.port ?? 80,
196175
197728
  min: 1,
196176
197729
  max: 65535,
@@ -196180,6 +197733,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196180
197733
  type: "boolean",
196181
197734
  key: "netHttpsEnable",
196182
197735
  label: "HTTPS enabled",
197736
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpsEnable.label",
196183
197737
  default: netSnap.https?.enable ?? false,
196184
197738
  style: "switch"
196185
197739
  },
@@ -196187,6 +197741,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196187
197741
  type: "number",
196188
197742
  key: "netHttpsPort",
196189
197743
  label: "HTTPS port",
197744
+ labelKey: "provider-reolink:schema.s_network-ports.netHttpsPort.label",
196190
197745
  default: netSnap.https?.port ?? 443,
196191
197746
  min: 1,
196192
197747
  max: 65535,
@@ -196196,6 +197751,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196196
197751
  type: "boolean",
196197
197752
  key: "netRtspEnable",
196198
197753
  label: "RTSP enabled",
197754
+ labelKey: "provider-reolink:schema.s_network-ports.netRtspEnable.label",
196199
197755
  default: netSnap.rtsp?.enable ?? true,
196200
197756
  style: "switch"
196201
197757
  },
@@ -196203,6 +197759,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196203
197759
  type: "number",
196204
197760
  key: "netRtspPort",
196205
197761
  label: "RTSP port",
197762
+ labelKey: "provider-reolink:schema.s_network-ports.netRtspPort.label",
196206
197763
  default: netSnap.rtsp?.port ?? 554,
196207
197764
  min: 1,
196208
197765
  max: 65535,
@@ -196212,6 +197769,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196212
197769
  type: "boolean",
196213
197770
  key: "netRtmpEnable",
196214
197771
  label: "RTMP enabled",
197772
+ labelKey: "provider-reolink:schema.s_network-ports.netRtmpEnable.label",
196215
197773
  default: netSnap.rtmp?.enable ?? true,
196216
197774
  style: "switch"
196217
197775
  },
@@ -196219,6 +197777,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196219
197777
  type: "number",
196220
197778
  key: "netRtmpPort",
196221
197779
  label: "RTMP port",
197780
+ labelKey: "provider-reolink:schema.s_network-ports.netRtmpPort.label",
196222
197781
  default: netSnap.rtmp?.port ?? 1935,
196223
197782
  min: 1,
196224
197783
  max: 65535,
@@ -196228,6 +197787,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196228
197787
  type: "boolean",
196229
197788
  key: "netOnvifEnable",
196230
197789
  label: "ONVIF enabled",
197790
+ labelKey: "provider-reolink:schema.s_network-ports.netOnvifEnable.label",
196231
197791
  default: netSnap.onvif?.enable ?? true,
196232
197792
  style: "switch"
196233
197793
  },
@@ -196235,6 +197795,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196235
197795
  type: "number",
196236
197796
  key: "netOnvifPort",
196237
197797
  label: "ONVIF port",
197798
+ labelKey: "provider-reolink:schema.s_network-ports.netOnvifPort.label",
196238
197799
  default: netSnap.onvif?.port ?? 8e3,
196239
197800
  min: 1,
196240
197801
  max: 65535,
@@ -196246,14 +197807,18 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196246
197807
  id: "time-ntp",
196247
197808
  tab: "network",
196248
197809
  title: "Time / NTP",
197810
+ titleKey: "provider-reolink:schema.s_time-ntp.title",
196249
197811
  description: "Network time synchronisation. The camera-side push goes through the native Baichuan NTP commands; until those land in the driver lib the values persist to the driver config and apply on the next lib update.",
197812
+ descriptionKey: "provider-reolink:schema.s_time-ntp.description",
196250
197813
  columns: 2,
196251
197814
  fields: [
196252
197815
  {
196253
197816
  type: "timezone",
196254
197817
  key: "timeZoneId",
196255
197818
  label: "Timezone",
197819
+ labelKey: "provider-reolink:schema.s_time-ntp.timeZoneId.label",
196256
197820
  description: "Camera timezone. Selecting a zone pushes its standard UTC offset (Baichuan `SetSystemGeneral`, cmd 105) and DST rule (`SetDst`, cmd 107) to the camera — DST is handled automatically by the chosen zone, so summer/winter clocks track correctly without a separate toggle.",
197821
+ descriptionKey: "provider-reolink:schema.s_time-ntp.timeZoneId.description",
196257
197822
  default: this.config.get("timeZoneId") ?? reolinkTimeZoneToIana(this.config.get("deviceCache")?.systemGeneralSnapshot?.timeZone) ?? "",
196258
197823
  span: 2
196259
197824
  },
@@ -196261,6 +197826,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196261
197826
  type: "boolean",
196262
197827
  key: "ntpEnabled",
196263
197828
  label: "NTP sync",
197829
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpEnabled.label",
196264
197830
  default: ntpSnap.enabled ?? true,
196265
197831
  style: "switch",
196266
197832
  span: 2
@@ -196269,6 +197835,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196269
197835
  type: "text",
196270
197836
  key: "ntpServer",
196271
197837
  label: "NTP server",
197838
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpServer.label",
196272
197839
  default: ntpSnap.server ?? "pool.ntp.org",
196273
197840
  placeholder: "pool.ntp.org",
196274
197841
  span: 2
@@ -196277,6 +197844,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196277
197844
  type: "number",
196278
197845
  key: "ntpPort",
196279
197846
  label: "NTP port",
197847
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpPort.label",
196280
197848
  default: ntpSnap.port ?? 123,
196281
197849
  min: 1,
196282
197850
  max: 65535,
@@ -196286,6 +197854,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196286
197854
  type: "number",
196287
197855
  key: "ntpInterval",
196288
197856
  label: "Sync interval (min)",
197857
+ labelKey: "provider-reolink:schema.s_time-ntp.ntpInterval.label",
196289
197858
  default: ntpSnap.interval ?? 1440,
196290
197859
  min: 1,
196291
197860
  max: 10080,
@@ -196297,13 +197866,17 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196297
197866
  id: "motion",
196298
197867
  tab: "motion",
196299
197868
  title: "On-camera motion (MdAlarm)",
197869
+ titleKey: "provider-reolink:schema.s_motion.title",
196300
197870
  description: "Camera-side motion detection master switch + sensitivity. Pushed via Baichuan `SetMdAlarm` (cmd_id 47). Range 1..50 matches the Reolink mobile app + web UI; the driver inverts to the camera-side raw value (`51 - value`) before push.",
197871
+ descriptionKey: "provider-reolink:schema.s_motion.description",
196301
197872
  columns: 2,
196302
197873
  fields: [{
196303
197874
  type: "boolean",
196304
197875
  key: "motionEnabled",
196305
197876
  label: "Motion detection",
197877
+ labelKey: "provider-reolink:schema.s_motion.motionEnabled.label",
196306
197878
  description: "Master switch for the camera's on-board motion detector. Disabling stops the camera from emitting motion events on the alarm channel.",
197879
+ descriptionKey: "provider-reolink:schema.s_motion.motionEnabled.description",
196307
197880
  default: motionSnap.enabled ?? true,
196308
197881
  style: "switch",
196309
197882
  span: 2
@@ -196311,7 +197884,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196311
197884
  type: "slider",
196312
197885
  key: "motionSensitivity",
196313
197886
  label: "Sensitivity",
197887
+ labelKey: "provider-reolink:schema.s_motion.motionSensitivity.label",
196314
197888
  description: "Higher = triggers on smaller motion. Reolink user-facing range 1..50.",
197889
+ descriptionKey: "provider-reolink:schema.s_motion.motionSensitivity.description",
196315
197890
  min: 1,
196316
197891
  max: 50,
196317
197892
  step: 1,
@@ -196325,7 +197900,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196325
197900
  id: "ai-sensitivity",
196326
197901
  tab: "motion",
196327
197902
  title: "AI detection sensitivity",
197903
+ titleKey: "provider-reolink:schema.s_ai-sensitivity.title",
196328
197904
  description: "Per-class on-camera AI sensitivity, stay time (dwell) + min/max target-size gates. Pushed via `SetAiAlarm` (cmd_id 343) in one consolidated write. Sensitivity range 0..100; stay time is the seconds the camera keeps the alarm asserted after the object leaves; target-size gates are the fraction of the frame (0..1) an object must occupy to be considered. Each group appears only when the camera advertises the corresponding detector.",
197905
+ descriptionKey: "provider-reolink:schema.s_ai-sensitivity.description",
196329
197906
  columns: 2,
196330
197907
  fields: [
196331
197908
  [
@@ -196456,13 +198033,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196456
198033
  id: "image",
196457
198034
  tab: "image",
196458
198035
  title: "Image",
198036
+ titleKey: "provider-reolink:schema.s_image.title",
196459
198037
  description: "Core image settings pushed via Baichuan `SetImage` (cmd_id 25). Each slider rearms the camera on save; defaults seed from a `GetVideoInput` probe at boot (refreshed on every `applySettingsPatch`). Range 0..255 — most Reolink models clamp to 0..100 internally.",
198038
+ descriptionKey: "provider-reolink:schema.s_image.description",
196460
198039
  columns: 2,
196461
198040
  fields: [
196462
198041
  {
196463
198042
  type: "slider",
196464
198043
  key: "imgBright",
196465
198044
  label: "Brightness",
198045
+ labelKey: "provider-reolink:schema.s_image.imgBright.label",
196466
198046
  min: 0,
196467
198047
  max: 255,
196468
198048
  step: 1,
@@ -196474,6 +198054,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196474
198054
  type: "slider",
196475
198055
  key: "imgContrast",
196476
198056
  label: "Contrast",
198057
+ labelKey: "provider-reolink:schema.s_image.imgContrast.label",
196477
198058
  min: 0,
196478
198059
  max: 255,
196479
198060
  step: 1,
@@ -196485,6 +198066,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196485
198066
  type: "slider",
196486
198067
  key: "imgSaturation",
196487
198068
  label: "Saturation",
198069
+ labelKey: "provider-reolink:schema.s_image.imgSaturation.label",
196488
198070
  min: 0,
196489
198071
  max: 255,
196490
198072
  step: 1,
@@ -196496,6 +198078,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196496
198078
  type: "slider",
196497
198079
  key: "imgHue",
196498
198080
  label: "Hue",
198081
+ labelKey: "provider-reolink:schema.s_image.imgHue.label",
196499
198082
  min: 0,
196500
198083
  max: 255,
196501
198084
  step: 1,
@@ -196507,6 +198090,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196507
198090
  type: "slider",
196508
198091
  key: "imgSharpen",
196509
198092
  label: "Sharpness",
198093
+ labelKey: "provider-reolink:schema.s_image.imgSharpen.label",
196510
198094
  min: 0,
196511
198095
  max: 255,
196512
198096
  step: 1,
@@ -196521,19 +198105,25 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196521
198105
  id: "isp",
196522
198106
  tab: "image",
196523
198107
  title: "Advanced (ISP)",
198108
+ titleKey: "provider-reolink:schema.s_isp.title",
196524
198109
  description: "Day/Night + exposure + HDR. Pushed via `SetIsp` (cmd_id 25). The day/night threshold uses cmd_id 297 (DayNightThreshold).",
198110
+ descriptionKey: "provider-reolink:schema.s_isp.description",
196525
198111
  columns: 2,
196526
198112
  fields: [{
196527
198113
  type: "text",
196528
198114
  key: "ispDayNight",
196529
198115
  label: "Day/Night mode",
198116
+ labelKey: "provider-reolink:schema.s_isp.ispDayNight.label",
196530
198117
  description: "Camera-specific values — common: `auto`, `color`, `blackAndWhite`. The driver normalizes case before push.",
198118
+ descriptionKey: "provider-reolink:schema.s_isp.ispDayNight.description",
196531
198119
  default: imgSnap.dayNight ?? "auto"
196532
198120
  }, {
196533
198121
  type: "text",
196534
198122
  key: "ispExposure",
196535
198123
  label: "Exposure",
198124
+ labelKey: "provider-reolink:schema.s_isp.ispExposure.label",
196536
198125
  description: "Common values: `auto`, `manual`. Lowercased before push.",
198126
+ descriptionKey: "provider-reolink:schema.s_isp.ispExposure.description",
196537
198127
  default: "auto"
196538
198128
  }]
196539
198129
  }] : [],
@@ -196541,26 +198131,32 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196541
198131
  id: "ir-lights",
196542
198132
  tab: "image",
196543
198133
  title: "IR + status LED",
198134
+ titleKey: "provider-reolink:schema.s_ir-lights.title",
196544
198135
  description: "IR LED control + per-camera IR brightness. Pushed via `SetIrLights` (cmd_id 209). State `Auto` lets the camera switch IR on automatically at low light; `Off` forces colour-only mode.",
198136
+ descriptionKey: "provider-reolink:schema.s_ir-lights.description",
196545
198137
  columns: 2,
196546
198138
  fields: [
196547
198139
  {
196548
198140
  type: "select",
196549
198141
  key: "irLightsState",
196550
198142
  label: "IR LED",
198143
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.label",
196551
198144
  default: "Auto",
196552
198145
  options: [
196553
198146
  {
196554
198147
  value: "Auto",
196555
- label: "Auto (camera decides)"
198148
+ label: "Auto (camera decides)",
198149
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.o_Auto.label"
196556
198150
  },
196557
198151
  {
196558
198152
  value: "On",
196559
- label: "Always on"
198153
+ label: "Always on",
198154
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.o_On.label"
196560
198155
  },
196561
198156
  {
196562
198157
  value: "Off",
196563
- label: "Always off"
198158
+ label: "Always off",
198159
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsState.o_Off.label"
196564
198160
  }
196565
198161
  ]
196566
198162
  },
@@ -196568,7 +198164,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196568
198164
  type: "slider",
196569
198165
  key: "irLightsBrightness",
196570
198166
  label: "IR brightness",
198167
+ labelKey: "provider-reolink:schema.s_ir-lights.irLightsBrightness.label",
196571
198168
  description: "Only applied when the camera advertises programmable IR brightness. 0..255 raw range; many models clamp to 0..100.",
198169
+ descriptionKey: "provider-reolink:schema.s_ir-lights.irLightsBrightness.description",
196572
198170
  min: 0,
196573
198171
  max: 255,
196574
198172
  step: 5,
@@ -196580,7 +198178,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196580
198178
  type: "boolean",
196581
198179
  key: "statusLedEnabled",
196582
198180
  label: "Status LED",
198181
+ labelKey: "provider-reolink:schema.s_ir-lights.statusLedEnabled.label",
196583
198182
  description: "The visible status/power LED on the camera body. Pushed via `SetIrLights` (cmd_id 209) as `lightState`.",
198183
+ descriptionKey: "provider-reolink:schema.s_ir-lights.statusLedEnabled.description",
196584
198184
  default: cache?.ledSnapshot?.statusEnabled ?? true,
196585
198185
  style: "switch",
196586
198186
  span: cache?.ledSnapshot?.doorbellSupported === true ? 1 : 2
@@ -196589,7 +198189,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196589
198189
  type: "boolean",
196590
198190
  key: "doorbellLedEnabled",
196591
198191
  label: "Doorbell LED",
198192
+ labelKey: "provider-reolink:schema.s_ir-lights.doorbellLedEnabled.label",
196592
198193
  description: "The doorbell button ring LED. Pushed via `SetIrLights` (cmd_id 209) as `doorbellLightState`.",
198194
+ descriptionKey: "provider-reolink:schema.s_ir-lights.doorbellLedEnabled.description",
196593
198195
  default: cache?.ledSnapshot?.doorbellEnabled ?? true,
196594
198196
  style: "switch"
196595
198197
  }] : []
@@ -196603,12 +198205,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196603
198205
  id: "auto-focus",
196604
198206
  tab: "image",
196605
198207
  title: "Auto focus",
198208
+ titleKey: "provider-reolink:schema.s_auto-focus.title",
196606
198209
  description: "Toggles the lens auto-focus subsystem. Pushed via `SetAutoFocus` (cmd_id 225). Disabling locks the lens at its current focal position — useful for fixed-distance scenes where AF would hunt.",
198210
+ descriptionKey: "provider-reolink:schema.s_auto-focus.description",
196607
198211
  columns: 1,
196608
198212
  fields: [{
196609
198213
  type: "boolean",
196610
198214
  key: "autoFocusEnabled",
196611
198215
  label: "Auto focus",
198216
+ labelKey: "provider-reolink:schema.s_auto-focus.autoFocusEnabled.label",
196612
198217
  default: cache.autoFocusSnapshot?.enabled ?? true,
196613
198218
  style: "switch"
196614
198219
  }]
@@ -196617,13 +198222,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196617
198222
  id: "audio",
196618
198223
  tab: "audio",
196619
198224
  title: "Audio output",
198225
+ titleKey: "provider-reolink:schema.s_audio.title",
196620
198226
  description: "Speaker volumes pushed via `SetAudioCfg` (cmd_id 265). All ranges 0..100. Doorbell models expose the visitor / talkAndReply channels — non-doorbell cameras ignore those without erroring.",
198227
+ descriptionKey: "provider-reolink:schema.s_audio.description",
196621
198228
  columns: 2,
196622
198229
  fields: [
196623
198230
  {
196624
198231
  type: "slider",
196625
198232
  key: "audioVolume",
196626
198233
  label: "Speaker volume",
198234
+ labelKey: "provider-reolink:schema.s_audio.audioVolume.label",
196627
198235
  min: 0,
196628
198236
  max: 100,
196629
198237
  step: 5,
@@ -196635,7 +198243,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196635
198243
  type: "slider",
196636
198244
  key: "audioTalkAndReplyVolume",
196637
198245
  label: "Talk + reply volume",
198246
+ labelKey: "provider-reolink:schema.s_audio.audioTalkAndReplyVolume.label",
196638
198247
  description: "Two-way audio + canned reply playback. Doorbell-only on most firmwares.",
198248
+ descriptionKey: "provider-reolink:schema.s_audio.audioTalkAndReplyVolume.description",
196639
198249
  min: 0,
196640
198250
  max: 100,
196641
198251
  step: 5,
@@ -196647,7 +198257,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196647
198257
  type: "slider",
196648
198258
  key: "audioVisitorVolume",
196649
198259
  label: "Visitor volume",
198260
+ labelKey: "provider-reolink:schema.s_audio.audioVisitorVolume.label",
196650
198261
  description: "Visitor / chime playback channel. Doorbell-only on most firmwares.",
198262
+ descriptionKey: "provider-reolink:schema.s_audio.audioVisitorVolume.description",
196651
198263
  min: 0,
196652
198264
  max: 100,
196653
198265
  step: 5,
@@ -196662,13 +198274,17 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196662
198274
  id: "audio-noise",
196663
198275
  tab: "audio",
196664
198276
  title: "Audio noise reduction",
198277
+ titleKey: "provider-reolink:schema.s_audio-noise.title",
196665
198278
  description: "On-camera AI denoise applied to the encoded audio track. Pushed via `SetAudioNoise` (cmd_id 440). 0 disables; 1..3 maps to the Reolink mobile app levels (low / mid / high).",
198279
+ descriptionKey: "provider-reolink:schema.s_audio-noise.description",
196666
198280
  columns: 1,
196667
198281
  fields: [{
196668
198282
  type: "slider",
196669
198283
  key: "audioNoiseLevel",
196670
198284
  label: "Noise reduction level",
198285
+ labelKey: "provider-reolink:schema.s_audio-noise.audioNoiseLevel.label",
196671
198286
  description: "0 = off · 1 = low · 2 = medium · 3 = high.",
198287
+ descriptionKey: "provider-reolink:schema.s_audio-noise.audioNoiseLevel.description",
196672
198288
  min: 0,
196673
198289
  max: 3,
196674
198290
  step: 1,
@@ -196681,14 +198297,18 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196681
198297
  id: "intercom",
196682
198298
  tab: "audio",
196683
198299
  title: "Intercom",
198300
+ titleKey: "provider-reolink:schema.s_intercom.title",
196684
198301
  description: "Push-to-talk pipeline tuning. Settings take effect on the next talk session — restart any active session for changes to apply.",
198302
+ descriptionKey: "provider-reolink:schema.s_intercom.description",
196685
198303
  columns: 2,
196686
198304
  fields: [
196687
198305
  {
196688
198306
  type: "slider",
196689
198307
  key: "intercomBlocksPerPayload",
196690
198308
  label: "Blocks per payload",
198309
+ labelKey: "provider-reolink:schema.s_intercom.intercomBlocksPerPayload.label",
196691
198310
  description: "Lower = lower latency (more sendAudio calls/sec). Higher = fewer calls, less per-call socket overhead. Range 1..8. Default 1 (lowest latency).",
198311
+ descriptionKey: "provider-reolink:schema.s_intercom.intercomBlocksPerPayload.description",
196692
198312
  min: 1,
196693
198313
  max: 8,
196694
198314
  step: 1,
@@ -196700,7 +198320,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196700
198320
  type: "slider",
196701
198321
  key: "intercomMaxBacklogMs",
196702
198322
  label: "Max backlog (ms)",
198323
+ labelKey: "provider-reolink:schema.s_intercom.intercomMaxBacklogMs.label",
196703
198324
  description: "Maximum PCM backlog before dropping oldest samples to cap latency. Higher = more stable on slow systems but more latency. Range 20..5000. Default 120.",
198325
+ descriptionKey: "provider-reolink:schema.s_intercom.intercomMaxBacklogMs.description",
196704
198326
  min: 20,
196705
198327
  max: 5e3,
196706
198328
  step: 20,
@@ -196712,7 +198334,9 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196712
198334
  type: "slider",
196713
198335
  key: "intercomGain",
196714
198336
  label: "Output gain",
198337
+ labelKey: "provider-reolink:schema.s_intercom.intercomGain.label",
196715
198338
  description: "Multiplier applied to PCM samples before IMA-ADPCM encode. 1.0 = neutral, 2.0 ≈ +6dB, 0.5 ≈ -6dB. Hard-clipped at int16 bounds.",
198339
+ descriptionKey: "provider-reolink:schema.s_intercom.intercomGain.description",
196716
198340
  min: .1,
196717
198341
  max: 10,
196718
198342
  step: .1,
@@ -196727,12 +198351,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196727
198351
  id: "keyframe-gop",
196728
198352
  tab: "advanced",
196729
198353
  title: "Keyframe interval (GOP)",
198354
+ titleKey: "provider-reolink:schema.s_keyframe-gop.title",
196730
198355
  description: "Main-stream keyframe interval in seconds. Pushed via `SetEnc` (cmd_id 57) as `<gop><cur>`. Lower = more keyframes (faster seek/recovery, higher bitrate); higher = fewer keyframes (lower bitrate). The camera clamps to its supported range.",
198356
+ descriptionKey: "provider-reolink:schema.s_keyframe-gop.description",
196731
198357
  columns: 1,
196732
198358
  fields: [{
196733
198359
  type: "slider",
196734
198360
  key: "gop",
196735
198361
  label: "Keyframe interval (s)",
198362
+ labelKey: "provider-reolink:schema.s_keyframe-gop.gop.label",
196736
198363
  min: 1,
196737
198364
  max: 10,
196738
198365
  step: 1,
@@ -196745,12 +198372,15 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196745
198372
  id: "pir-reduce-alarm",
196746
198373
  tab: "advanced",
196747
198374
  title: "PIR detection",
198375
+ titleKey: "provider-reolink:schema.s_pir-reduce-alarm.title",
196748
198376
  description: "Passive-infrared false-alarm filter. Pushed via `SetPirInfo` (cmd_id 213) as `reduceAlarm`. Enabling makes the camera require a stronger/longer PIR signal before triggering, cutting false motion alerts from heat/light flicker.",
198377
+ descriptionKey: "provider-reolink:schema.s_pir-reduce-alarm.description",
196749
198378
  columns: 1,
196750
198379
  fields: [{
196751
198380
  type: "boolean",
196752
198381
  key: "pirReduceFalseAlarm",
196753
198382
  label: "Reduce false alarms",
198383
+ labelKey: "provider-reolink:schema.s_pir-reduce-alarm.pirReduceFalseAlarm.label",
196754
198384
  default: cache.pirSnapshot.reduceFalseAlarm ?? false,
196755
198385
  style: "switch"
196756
198386
  }]
@@ -196759,13 +198389,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196759
198389
  id: "auto-reboot",
196760
198390
  tab: "advanced",
196761
198391
  title: "Auto reboot",
198392
+ titleKey: "provider-reolink:schema.s_auto-reboot.title",
196762
198393
  description: "Scheduled automatic reboot. Pushed via `SetAutoReboot` (cmd_id 100). Useful for long-uptime stability. Choose a day (or every day) + time; the camera reboots once per matching window.",
198394
+ descriptionKey: "provider-reolink:schema.s_auto-reboot.description",
196763
198395
  columns: 2,
196764
198396
  fields: [
196765
198397
  {
196766
198398
  type: "boolean",
196767
198399
  key: "autoRebootEnabled",
196768
198400
  label: "Auto reboot",
198401
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootEnabled.label",
196769
198402
  default: cache.autoRebootSnapshot.enabled ?? false,
196770
198403
  style: "switch",
196771
198404
  span: 2
@@ -196774,40 +198407,49 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196774
198407
  type: "select",
196775
198408
  key: "autoRebootWeekDay",
196776
198409
  label: "Day",
198410
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.label",
196777
198411
  default: cache.autoRebootSnapshot.weekDay ?? "everyday",
196778
198412
  span: 2,
196779
198413
  options: [
196780
198414
  {
196781
198415
  value: "everyday",
196782
- label: "Every day"
198416
+ label: "Every day",
198417
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_everyday.label"
196783
198418
  },
196784
198419
  {
196785
198420
  value: "Sunday",
196786
- label: "Sunday"
198421
+ label: "Sunday",
198422
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Sunday.label"
196787
198423
  },
196788
198424
  {
196789
198425
  value: "Monday",
196790
- label: "Monday"
198426
+ label: "Monday",
198427
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Monday.label"
196791
198428
  },
196792
198429
  {
196793
198430
  value: "Tuesday",
196794
- label: "Tuesday"
198431
+ label: "Tuesday",
198432
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Tuesday.label"
196795
198433
  },
196796
198434
  {
196797
198435
  value: "Wednesday",
196798
- label: "Wednesday"
198436
+ label: "Wednesday",
198437
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Wednesday.label"
196799
198438
  },
196800
198439
  {
196801
198440
  value: "Thursday",
196802
- label: "Thursday"
198441
+ label: "Thursday",
198442
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Thursday.label"
196803
198443
  },
196804
198444
  {
196805
198445
  value: "Friday",
196806
- label: "Friday"
198446
+ label: "Friday",
198447
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Friday.label"
196807
198448
  },
196808
198449
  {
196809
198450
  value: "Saturday",
196810
- label: "Saturday"
198451
+ label: "Saturday",
198452
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootWeekDay.o_Saturday.label"
196811
198453
  }
196812
198454
  ]
196813
198455
  },
@@ -196815,6 +198457,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196815
198457
  type: "number",
196816
198458
  key: "autoRebootHour",
196817
198459
  label: "Hour (0-23)",
198460
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootHour.label",
196818
198461
  default: cache.autoRebootSnapshot.hour ?? 3,
196819
198462
  min: 0,
196820
198463
  max: 23,
@@ -196824,6 +198467,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196824
198467
  type: "number",
196825
198468
  key: "autoRebootMinute",
196826
198469
  label: "Minute (0-59)",
198470
+ labelKey: "provider-reolink:schema.s_auto-reboot.autoRebootMinute.label",
196827
198471
  default: cache.autoRebootSnapshot.minute ?? 0,
196828
198472
  min: 0,
196829
198473
  max: 59,
@@ -196835,41 +198479,52 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
196835
198479
  id: "debug",
196836
198480
  tab: "advanced",
196837
198481
  title: "Debug",
198482
+ titleKey: "provider-reolink:schema.s_debug.title",
196838
198483
  description: "Lib-side Baichuan tracing. Changing these resets the control-channel client (active streams keep running) so new debug options take effect on the next dial.",
198484
+ descriptionKey: "provider-reolink:schema.s_debug.description",
196839
198485
  columns: 1,
196840
198486
  fields: [{
196841
198487
  type: "boolean",
196842
198488
  key: "debugGeneral",
196843
198489
  label: "General debug logs",
198490
+ labelKey: "provider-reolink:schema.s_debug.debugGeneral.label",
196844
198491
  description: "Forwards `DebugOptions.general=true` — verbose lib protocol logs.",
198492
+ descriptionKey: "provider-reolink:schema.s_debug.debugGeneral.description",
196845
198493
  default: false,
196846
198494
  style: "switch"
196847
198495
  }, {
196848
198496
  type: "multiselect",
196849
198497
  key: "debugSocketLogs",
196850
198498
  label: "Socket trace categories",
198499
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.label",
196851
198500
  description: "Per-area lib tracing. Pick only what you need; each adds log volume.",
198501
+ descriptionKey: "provider-reolink:schema.s_debug.debugSocketLogs.description",
196852
198502
  default: [],
196853
198503
  options: [
196854
198504
  {
196855
198505
  value: "debugRtsp",
196856
- label: "RTSP (proxy/server traffic)"
198506
+ label: "RTSP (proxy/server traffic)",
198507
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_debugRtsp.label"
196857
198508
  },
196858
198509
  {
196859
198510
  value: "traceNativeStream",
196860
- label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)"
198511
+ label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)",
198512
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceNativeStream.label"
196861
198513
  },
196862
198514
  {
196863
198515
  value: "traceRecordings",
196864
- label: "Recordings (FileInfoList, findAlarmVideo, download)"
198516
+ label: "Recordings (FileInfoList, findAlarmVideo, download)",
198517
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceRecordings.label"
196865
198518
  },
196866
198519
  {
196867
198520
  value: "traceTalk",
196868
- label: "Talkback (cmd 10/11/201/202)"
198521
+ label: "Talkback (cmd 10/11/201/202)",
198522
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceTalk.label"
196869
198523
  },
196870
198524
  {
196871
198525
  value: "traceEvents",
196872
- label: "Events (cmd 33 — AlarmEventList push)"
198526
+ label: "Events (cmd 33 — AlarmEventList push)",
198527
+ labelKey: "provider-reolink:schema.s_debug.debugSocketLogs.o_traceEvents.label"
196873
198528
  }
196874
198529
  ]
196875
198530
  }]
@@ -198344,13 +199999,16 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198344
199999
  id: "connection",
198345
200000
  tab: "general",
198346
200001
  title: "Connection",
200002
+ titleKey: "provider-reolink:hub.s_connection.title",
198347
200003
  description: "Baichuan protocol connection settings for the hub. The same socket fronts every adopted channel — child cameras inherit these credentials, no per-channel login needed.",
200004
+ descriptionKey: "provider-reolink:hub.s_connection.description",
198348
200005
  columns: 2,
198349
200006
  fields: [
198350
200007
  {
198351
200008
  type: "text",
198352
200009
  key: "host",
198353
200010
  label: "Hub IP",
200011
+ labelKey: "provider-reolink:hub.s_connection.host.label",
198354
200012
  required: true,
198355
200013
  placeholder: "192.168.10.100"
198356
200014
  },
@@ -198358,6 +200016,7 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198358
200016
  type: "number",
198359
200017
  key: "port",
198360
200018
  label: "Port",
200019
+ labelKey: "provider-reolink:hub.s_connection.port.label",
198361
200020
  default: 9e3,
198362
200021
  min: 1,
198363
200022
  max: 65535,
@@ -198367,12 +200026,14 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198367
200026
  type: "text",
198368
200027
  key: "username",
198369
200028
  label: "Username",
200029
+ labelKey: "provider-reolink:hub.s_connection.username.label",
198370
200030
  default: "admin"
198371
200031
  },
198372
200032
  {
198373
200033
  type: "password",
198374
200034
  key: "password",
198375
200035
  label: "Password",
200036
+ labelKey: "provider-reolink:hub.s_connection.password.label",
198376
200037
  required: true,
198377
200038
  showToggle: true
198378
200039
  }
@@ -198382,18 +200043,22 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198382
200043
  id: "identity",
198383
200044
  tab: "general",
198384
200045
  title: "Hub identity",
200046
+ titleKey: "provider-reolink:hub.s_identity.title",
198385
200047
  description: "Read-only summary from the autodetect probe. Channel count and model are persisted at first contact so the panel renders without re-probing.",
200048
+ descriptionKey: "provider-reolink:hub.s_identity.description",
198386
200049
  columns: 2,
198387
200050
  fields: [{
198388
200051
  type: "text",
198389
200052
  key: "_model",
198390
200053
  label: "Model",
200054
+ labelKey: "provider-reolink:hub.s_identity._model.label",
198391
200055
  default: cache?.model ?? "unknown",
198392
200056
  disabled: true
198393
200057
  }, {
198394
200058
  type: "text",
198395
200059
  key: "_channelCount",
198396
200060
  label: "Total channels",
200061
+ labelKey: "provider-reolink:hub.s_identity._channelCount.label",
198397
200062
  default: String(cache?.channelCount ?? 0),
198398
200063
  disabled: true
198399
200064
  }]
@@ -198402,41 +200067,52 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
198402
200067
  id: "debug",
198403
200068
  tab: "advanced",
198404
200069
  title: "Debug",
200070
+ titleKey: "provider-reolink:hub.s_debug.title",
198405
200071
  description: "Lib-side Baichuan tracing. Changing these resets the control-channel client (active streams keep running) so new debug options take effect on the next dial.",
200072
+ descriptionKey: "provider-reolink:hub.s_debug.description",
198406
200073
  columns: 1,
198407
200074
  fields: [{
198408
200075
  type: "boolean",
198409
200076
  key: "debugGeneral",
198410
200077
  label: "General debug logs",
200078
+ labelKey: "provider-reolink:hub.s_debug.debugGeneral.label",
198411
200079
  description: "Forwards `DebugOptions.general=true` — verbose lib protocol logs.",
200080
+ descriptionKey: "provider-reolink:hub.s_debug.debugGeneral.description",
198412
200081
  default: false,
198413
200082
  style: "switch"
198414
200083
  }, {
198415
200084
  type: "multiselect",
198416
200085
  key: "debugSocketLogs",
198417
200086
  label: "Socket trace categories",
200087
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.label",
198418
200088
  description: "Per-area lib tracing. Pick only what you need; each adds log volume.",
200089
+ descriptionKey: "provider-reolink:hub.s_debug.debugSocketLogs.description",
198419
200090
  default: [],
198420
200091
  options: [
198421
200092
  {
198422
200093
  value: "debugRtsp",
198423
- label: "RTSP (proxy/server traffic)"
200094
+ label: "RTSP (proxy/server traffic)",
200095
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_debugRtsp.label"
198424
200096
  },
198425
200097
  {
198426
200098
  value: "traceNativeStream",
198427
- label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)"
200099
+ label: "Native stream (cmd 3/4 + H.264/H.265 + param sets)",
200100
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceNativeStream.label"
198428
200101
  },
198429
200102
  {
198430
200103
  value: "traceRecordings",
198431
- label: "Recordings (FileInfoList, findAlarmVideo, download)"
200104
+ label: "Recordings (FileInfoList, findAlarmVideo, download)",
200105
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceRecordings.label"
198432
200106
  },
198433
200107
  {
198434
200108
  value: "traceTalk",
198435
- label: "Talkback (cmd 10/11/201/202)"
200109
+ label: "Talkback (cmd 10/11/201/202)",
200110
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceTalk.label"
198436
200111
  },
198437
200112
  {
198438
200113
  value: "traceEvents",
198439
- label: "Events (cmd 33 — AlarmEventList push)"
200114
+ label: "Events (cmd 33 — AlarmEventList push)",
200115
+ labelKey: "provider-reolink:hub.s_debug.debugSocketLogs.o_traceEvents.label"
198440
200116
  }
198441
200117
  ]
198442
200118
  }]
@@ -199018,8 +200694,8 @@ var ReolinkHub = class ReolinkHub extends BaseDevice {
199018
200694
  this.api = null;
199019
200695
  }
199020
200696
  const cfg = this.config.values;
199021
- if (!cfg.host) throw new Error("Reolink Hub: host not configured");
199022
- if (!cfg.password) throw new Error("Reolink Hub: password not configured");
200697
+ if (!cfg.host) throw userError("reolink.hub-host-missing", void 0, "Reolink Hub: host not configured");
200698
+ if (!cfg.password) throw userError("reolink.hub-password-missing", void 0, "Reolink Hub: password not configured");
199023
200699
  const api = new ReolinkBaichuanApi({
199024
200700
  host: cfg.host,
199025
200701
  port: cfg.port,
@@ -199171,31 +200847,38 @@ function buildDiscoveryParamsFormSchema() {
199171
200847
  return { sections: [{
199172
200848
  id: "credentials",
199173
200849
  title: "Credentials",
200850
+ titleKey: "provider-reolink:discoveryParams.s_credentials.title",
199174
200851
  description: "Used to probe the cameras and pre-filled into each result so you can add it in one click. Assumes all cameras share these credentials.",
200852
+ descriptionKey: "provider-reolink:discoveryParams.s_credentials.description",
199175
200853
  columns: 2,
199176
200854
  fields: [{
199177
200855
  type: "text",
199178
200856
  key: "username",
199179
200857
  label: "Username",
200858
+ labelKey: "provider-reolink:discoveryParams.s_credentials.username.label",
199180
200859
  default: "admin",
199181
200860
  required: true
199182
200861
  }, {
199183
200862
  type: "password",
199184
200863
  key: "password",
199185
200864
  label: "Password",
200865
+ labelKey: "provider-reolink:discoveryParams.s_credentials.password.label",
199186
200866
  required: true,
199187
200867
  showToggle: true
199188
200868
  }]
199189
200869
  }, {
199190
200870
  id: "scan",
199191
200871
  title: "Scan options",
200872
+ titleKey: "provider-reolink:discoveryParams.s_scan.title",
199192
200873
  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).",
200874
+ descriptionKey: "provider-reolink:discoveryParams.s_scan.description",
199193
200875
  columns: 1,
199194
200876
  fields: [
199195
200877
  {
199196
200878
  type: "text",
199197
200879
  key: "networkCidr",
199198
200880
  label: "Network (CIDR)",
200881
+ labelKey: "provider-reolink:discoveryParams.s_scan.networkCidr.label",
199199
200882
  required: false,
199200
200883
  placeholder: "192.168.20.0/24"
199201
200884
  },
@@ -199203,12 +200886,14 @@ function buildDiscoveryParamsFormSchema() {
199203
200886
  type: "boolean",
199204
200887
  key: "enableOnvif",
199205
200888
  label: "Include ONVIF discovery",
200889
+ labelKey: "provider-reolink:discoveryParams.s_scan.enableOnvif.label",
199206
200890
  default: true
199207
200891
  },
199208
200892
  {
199209
200893
  type: "number",
199210
200894
  key: "timeoutMs",
199211
200895
  label: "Scan timeout (ms)",
200896
+ labelKey: "provider-reolink:discoveryParams.s_scan.timeoutMs.label",
199212
200897
  min: 1e3,
199213
200898
  max: 3e4,
199214
200899
  default: 5e3
@@ -199221,11 +200906,13 @@ function buildCreationFormSchema() {
199221
200906
  {
199222
200907
  id: "identity",
199223
200908
  title: "Camera",
200909
+ titleKey: "provider-reolink:creation.s_identity.title",
199224
200910
  columns: 1,
199225
200911
  fields: [{
199226
200912
  type: "text",
199227
200913
  key: "name",
199228
200914
  label: "Name",
200915
+ labelKey: "provider-reolink:creation.s_identity.name.label",
199229
200916
  required: true,
199230
200917
  placeholder: "Front Door"
199231
200918
  }]
@@ -199233,18 +200920,22 @@ function buildCreationFormSchema() {
199233
200920
  {
199234
200921
  id: "credentials",
199235
200922
  title: "Credentials",
200923
+ titleKey: "provider-reolink:creation.s_credentials.title",
199236
200924
  description: "Required for the autodetect probe — entered before the host so Test can run immediately.",
200925
+ descriptionKey: "provider-reolink:creation.s_credentials.description",
199237
200926
  columns: 2,
199238
200927
  fields: [{
199239
200928
  type: "text",
199240
200929
  key: "username",
199241
200930
  label: "Username",
200931
+ labelKey: "provider-reolink:creation.s_credentials.username.label",
199242
200932
  default: "admin",
199243
200933
  required: true
199244
200934
  }, {
199245
200935
  type: "password",
199246
200936
  key: "password",
199247
200937
  label: "Password",
200938
+ labelKey: "provider-reolink:creation.s_credentials.password.label",
199248
200939
  required: true,
199249
200940
  showToggle: true
199250
200941
  }]
@@ -199252,27 +200943,34 @@ function buildCreationFormSchema() {
199252
200943
  {
199253
200944
  id: "connection",
199254
200945
  title: "Connection",
200946
+ titleKey: "provider-reolink:creation.s_connection.title",
199255
200947
  description: "Click Test to auto-detect the device type (regular camera / NVR / battery / UDP-only). The detected type is then used at save time.",
200948
+ descriptionKey: "provider-reolink:creation.s_connection.description",
199256
200949
  columns: 2,
199257
200950
  fields: [
199258
200951
  {
199259
200952
  type: "select",
199260
200953
  key: "transport",
199261
200954
  label: "Connection mode",
200955
+ labelKey: "provider-reolink:creation.s_connection.transport.label",
199262
200956
  default: "auto",
199263
200957
  description: "Auto: try TCP first, fall back to UDP — best for unknown devices. UDP is required for battery cameras behind NAT.",
200958
+ descriptionKey: "provider-reolink:creation.s_connection.transport.description",
199264
200959
  options: [
199265
200960
  {
199266
200961
  value: "auto",
199267
- label: "Auto (recommended)"
200962
+ label: "Auto (recommended)",
200963
+ labelKey: "provider-reolink:creation.s_connection.transport.o_auto.label"
199268
200964
  },
199269
200965
  {
199270
200966
  value: "tcp",
199271
- label: "TCP only"
200967
+ label: "TCP only",
200968
+ labelKey: "provider-reolink:creation.s_connection.transport.o_tcp.label"
199272
200969
  },
199273
200970
  {
199274
200971
  value: "udp",
199275
- label: "UDP only (battery / cloud)"
200972
+ label: "UDP only (battery / cloud)",
200973
+ labelKey: "provider-reolink:creation.s_connection.transport.o_udp.label"
199276
200974
  }
199277
200975
  ]
199278
200976
  },
@@ -199280,20 +200978,24 @@ function buildCreationFormSchema() {
199280
200978
  type: "probe",
199281
200979
  key: "host",
199282
200980
  label: "Host (IP / hostname / UID)",
200981
+ labelKey: "provider-reolink:creation.s_connection.host.label",
199283
200982
  required: true,
199284
200983
  placeholder: "192.168.10.100",
199285
200984
  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.",
200985
+ descriptionKey: "provider-reolink:creation.s_connection.host.description",
199286
200986
  inputType: "text"
199287
200987
  },
199288
200988
  {
199289
200989
  type: "number",
199290
200990
  key: "port",
199291
200991
  label: "Port",
200992
+ labelKey: "provider-reolink:creation.s_connection.port.label",
199292
200993
  default: 9e3,
199293
200994
  min: 1,
199294
200995
  max: 65535,
199295
200996
  step: 1,
199296
200997
  description: "Baichuan port. Default 9000 — applies to TCP. Ignored for UDP transport.",
200998
+ descriptionKey: "provider-reolink:creation.s_connection.port.description",
199297
200999
  showWhen: {
199298
201000
  field: "transport",
199299
201001
  notEquals: "udp"
@@ -199303,8 +201005,10 @@ function buildCreationFormSchema() {
199303
201005
  type: "text",
199304
201006
  key: "uid",
199305
201007
  label: "Device UID",
201008
+ labelKey: "provider-reolink:creation.s_connection.uid.label",
199306
201009
  placeholder: "9527000XXXXXXXX",
199307
201010
  description: "Required for battery / UDP cameras when not on the same LAN. Optional for local-direct UDP discovery and ignored for pure TCP.",
201011
+ descriptionKey: "provider-reolink:creation.s_connection.uid.description",
199308
201012
  showWhen: {
199309
201013
  field: "transport",
199310
201014
  notEquals: "tcp"
@@ -199843,25 +201547,30 @@ function buildEmailPushSettingsSchema(recommendedHost) {
199843
201547
  return { sections: [{
199844
201548
  id: "email-push",
199845
201549
  title: "Battery-cam Motion (Email/SMTP Push)",
201550
+ titleKey: "provider-reolink:emailPushSettings.s_email-push.title",
199846
201551
  description: "Emulate an SMTP server that battery Reolink cameras push to on motion. Lets you capture motion from sleeping battery cams without polling them awake. Enable, then use \"Auto-configure email push\" on each battery camera.",
201552
+ descriptionKey: "provider-reolink:emailPushSettings.s_email-push.description",
199847
201553
  columns: 2,
199848
201554
  fields: [
199849
201555
  {
199850
201556
  type: "boolean",
199851
201557
  key: "emailPushEnabled",
199852
201558
  label: "Enable Email Push Server",
201559
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushEnabled.label",
199853
201560
  default: true
199854
201561
  },
199855
201562
  {
199856
201563
  type: "info",
199857
201564
  key: "emailPushRecommendedHost",
199858
201565
  label: "Camera-facing host",
201566
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushRecommendedHost.label",
199859
201567
  content: `Point cameras at this host: ${recommendedHost}`
199860
201568
  },
199861
201569
  {
199862
201570
  type: "number",
199863
201571
  key: "emailPushPort",
199864
201572
  label: "SMTP Port",
201573
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushPort.label",
199865
201574
  default: DEFAULT_EMAIL_PUSH_PORT,
199866
201575
  min: 1,
199867
201576
  max: 65535
@@ -199870,6 +201579,7 @@ function buildEmailPushSettingsSchema(recommendedHost) {
199870
201579
  type: "text",
199871
201580
  key: "emailPushDomain",
199872
201581
  label: "Virtual Domain",
201582
+ labelKey: "provider-reolink:emailPushSettings.s_email-push.emailPushDomain.label",
199873
201583
  default: DEFAULT_EMAIL_PUSH_DOMAIN,
199874
201584
  placeholder: DEFAULT_EMAIL_PUSH_DOMAIN
199875
201585
  }
@@ -199877,24 +201587,29 @@ function buildEmailPushSettingsSchema(recommendedHost) {
199877
201587
  }, {
199878
201588
  id: "email-push-auth",
199879
201589
  title: "Email Push Authentication",
201590
+ titleKey: "provider-reolink:emailPushSettings.s_email-push-auth.title",
199880
201591
  description: "AUTH credentials the camera must present. Reolink firmwares are more reliable with AUTH on. Random values are generated on first boot — re-run Auto-configure on cameras after changing them.",
201592
+ descriptionKey: "provider-reolink:emailPushSettings.s_email-push-auth.description",
199881
201593
  columns: 2,
199882
201594
  fields: [
199883
201595
  {
199884
201596
  type: "boolean",
199885
201597
  key: "emailPushRequireAuth",
199886
201598
  label: "Require AUTH",
201599
+ labelKey: "provider-reolink:emailPushSettings.s_email-push-auth.emailPushRequireAuth.label",
199887
201600
  default: true
199888
201601
  },
199889
201602
  {
199890
201603
  type: "text",
199891
201604
  key: "emailPushAuthUsername",
199892
- label: "Auth Username"
201605
+ label: "Auth Username",
201606
+ labelKey: "provider-reolink:emailPushSettings.s_email-push-auth.emailPushAuthUsername.label"
199893
201607
  },
199894
201608
  {
199895
201609
  type: "password",
199896
201610
  key: "emailPushAuthPassword",
199897
201611
  label: "Auth Password",
201612
+ labelKey: "provider-reolink:emailPushSettings.s_email-push-auth.emailPushAuthPassword.label",
199898
201613
  showToggle: true
199899
201614
  }
199900
201615
  ]
@@ -200081,25 +201796,30 @@ function buildBaichuanWebhookSettingsSchema(recommendedHost) {
200081
201796
  return { sections: [{
200082
201797
  id: "baichuan-webhook",
200083
201798
  title: "Battery-cam Motion (HaCfg Webhook)",
201799
+ titleKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.title",
200084
201800
  description: "HTTP intake for Baichuan HaCfg wake/sleep push (cmd 806/807). Prefer this over SMTP when the camera firmware supports it — lower latency, no AUTH dance. Enable, then use \"Auto-configure HaCfg webhook\" on each battery camera (or set preference to Auto).",
201801
+ descriptionKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.description",
200085
201802
  columns: 2,
200086
201803
  fields: [
200087
201804
  {
200088
201805
  type: "boolean",
200089
201806
  key: "baichuanWebhookEnabled",
200090
201807
  label: "Enable HaCfg Webhook Server",
201808
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookEnabled.label",
200091
201809
  default: true
200092
201810
  },
200093
201811
  {
200094
201812
  type: "info",
200095
201813
  key: "baichuanWebhookRecommendedHost",
200096
201814
  label: "Camera-facing host",
201815
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookRecommendedHost.label",
200097
201816
  content: `Point cameras at this host: ${recommendedHost}`
200098
201817
  },
200099
201818
  {
200100
201819
  type: "number",
200101
201820
  key: "baichuanWebhookPort",
200102
201821
  label: "HTTP Port",
201822
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookPort.label",
200103
201823
  default: DEFAULT_BAICHUAN_WEBHOOK_PORT,
200104
201824
  min: 1,
200105
201825
  max: 65535
@@ -200108,8 +201828,10 @@ function buildBaichuanWebhookSettingsSchema(recommendedHost) {
200108
201828
  type: "text",
200109
201829
  key: "baichuanWebhookBindHost",
200110
201830
  label: "Bind host",
201831
+ labelKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookBindHost.label",
200111
201832
  default: "0.0.0.0",
200112
- description: "0.0.0.0 accepts on every interface (cameras on LAN)."
201833
+ description: "0.0.0.0 accepts on every interface (cameras on LAN).",
201834
+ descriptionKey: "provider-reolink:baichuanWebhookSettings.s_baichuan-webhook.baichuanWebhookBindHost.description"
200113
201835
  }
200114
201836
  ]
200115
201837
  }] };
@@ -200665,7 +202387,7 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
200665
202387
  },
200666
202388
  wake: async (deviceId) => {
200667
202389
  const device = this.ctx.kernel.deviceRegistry?.getById(deviceId);
200668
- if (!(device instanceof ReolinkCamera)) throw new Error(`clip refresh: device ${String(deviceId)} is not a Reolink camera here`);
202390
+ if (!(device instanceof ReolinkCamera)) throw userError("reolink.clip-device-not-reolink", { deviceId: String(deviceId) }, `clip refresh: device ${String(deviceId)} is not a Reolink camera here`);
200669
202391
  await device.wakeForClipCatalogRefresh();
200670
202392
  },
200671
202393
  prefetchStills: async (deviceId) => service.prefetchThumbnails(deviceId),