@camstack/addon-provider-reolink 1.2.143 → 1.2.144
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 +352 -68
- package/dist/addon.mjs +352 -68
- package/package.json +2 -2
package/dist/addon.mjs
CHANGED
|
@@ -5381,7 +5381,7 @@ var ZodIssueCode = {
|
|
|
5381
5381
|
var ZodFirstPartyTypeKind;
|
|
5382
5382
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5383
5383
|
//#endregion
|
|
5384
|
-
//#region ../types/dist/sleep-
|
|
5384
|
+
//#region ../types/dist/sleep-CiR4z7r9.mjs
|
|
5385
5385
|
/**
|
|
5386
5386
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5387
5387
|
* window to float samples (D455).
|
|
@@ -6523,6 +6523,17 @@ var CamProfileSchema = _enum([
|
|
|
6523
6523
|
"mid",
|
|
6524
6524
|
"low"
|
|
6525
6525
|
]);
|
|
6526
|
+
/**
|
|
6527
|
+
* Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
|
|
6528
|
+
*
|
|
6529
|
+
* `auto` is a REAL value, not an absent one — it means "resolve it by the rule
|
|
6530
|
+
* that consumer owns". It lives beside `CamProfile` because a rung is the only
|
|
6531
|
+
* operator-facing way to name a stream: a cam-stream id is broker-internal and
|
|
6532
|
+
* moves when a slot is reassigned, so a stored id silently stops meaning what
|
|
6533
|
+
* the operator picked. Today's one user is the audio analyser, whose rule is
|
|
6534
|
+
* "the lowest AVAILABLE native rung" (D537).
|
|
6535
|
+
*/
|
|
6536
|
+
var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
|
|
6526
6537
|
var CamStreamKindSchema = _enum([
|
|
6527
6538
|
"pull-rtsp",
|
|
6528
6539
|
"pull-rtmp",
|
|
@@ -7789,6 +7800,7 @@ var CameraSwitchIdSchema = _enum([
|
|
|
7789
7800
|
"device-audio",
|
|
7790
7801
|
"broker-audio",
|
|
7791
7802
|
"audio-analysis",
|
|
7803
|
+
"event-saving",
|
|
7792
7804
|
"recording",
|
|
7793
7805
|
"notifications"
|
|
7794
7806
|
]);
|
|
@@ -8263,11 +8275,31 @@ var RecordingBandTriggersSchema = RecordingTriggersSchema;
|
|
|
8263
8275
|
* band engine's `applies` rule). `start`/`end` are `HH:MM`; an `end <= start`
|
|
8264
8276
|
* span wraps past midnight (handled by the band engine).
|
|
8265
8277
|
*/
|
|
8278
|
+
/**
|
|
8279
|
+
* Whether a band's footage carries sound.
|
|
8280
|
+
*
|
|
8281
|
+
* The recorder has always attached with `audio: 'aac'`, so every band of every
|
|
8282
|
+
* camera recorded sound whether anyone wanted it or not. The broker already
|
|
8283
|
+
* serves the other answer — `audio: 'none'` takes the video-only `/muted`
|
|
8284
|
+
* restream — so this was a choice the product could always have offered.
|
|
8285
|
+
*
|
|
8286
|
+
* It belongs on the BAND rather than the camera because one camera can want
|
|
8287
|
+
* both: silent across the shop floor during opening hours, sound overnight.
|
|
8288
|
+
* And it applies to BOTH modes — an events band firing on motion has the same
|
|
8289
|
+
* question to answer as a continuous one.
|
|
8290
|
+
*/
|
|
8291
|
+
var RecordingBandAudioSchema = _enum(["on", "muted"]);
|
|
8266
8292
|
var RecordingBandSchema = object({
|
|
8267
8293
|
days: array(RecordingWeekdaySchema),
|
|
8268
8294
|
start: string().regex(HHMM),
|
|
8269
8295
|
end: string().regex(HHMM),
|
|
8270
8296
|
mode: RecordingBandModeSchema,
|
|
8297
|
+
/**
|
|
8298
|
+
* Sound, or silence. OPTIONAL and absent-means-`on`: every recording
|
|
8299
|
+
* written before this existed carried sound, and a band whose key is
|
|
8300
|
+
* missing must keep meaning what it meant. See {@link RecordingBandAudioSchema}.
|
|
8301
|
+
*/
|
|
8302
|
+
audio: RecordingBandAudioSchema.optional(),
|
|
8271
8303
|
triggers: RecordingBandTriggersSchema.optional(),
|
|
8272
8304
|
/**
|
|
8273
8305
|
* RETIRED (D381). A band no longer carries a pre-buffer of its own.
|
|
@@ -20889,6 +20921,7 @@ var MediaFileKindEnum = _enum([
|
|
|
20889
20921
|
"keyFrameSmall",
|
|
20890
20922
|
"thumbnailSmall",
|
|
20891
20923
|
"confirmationCrop",
|
|
20924
|
+
"debugCrop",
|
|
20892
20925
|
"sceneFrame"
|
|
20893
20926
|
]);
|
|
20894
20927
|
/**
|
|
@@ -22553,6 +22586,18 @@ var RunnerCameraConfigSchema = object({
|
|
|
22553
22586
|
motionStreamId: string(),
|
|
22554
22587
|
detectionStreamId: string(),
|
|
22555
22588
|
/**
|
|
22589
|
+
* Which rung this camera's audio analysis listens on — `auto` (the lowest
|
|
22590
|
+
* AVAILABLE native rung) or an explicit one.
|
|
22591
|
+
*
|
|
22592
|
+
* Not consumed by the runner: carried here so it travels with the camera's
|
|
22593
|
+
* config the way `audioMotionWindowMs` does, and so the orchestrator's
|
|
22594
|
+
* watchdog resolves the same stream the audio subscription did. OPTIONAL
|
|
22595
|
+
* with no default — absent means `auto`, which is what every camera written
|
|
22596
|
+
* before this field had, and an absent optional adds nothing to the
|
|
22597
|
+
* generated cap-input defaults (D537).
|
|
22598
|
+
*/
|
|
22599
|
+
audioStreamPick: AudioStreamPickSchema.optional(),
|
|
22600
|
+
/**
|
|
22556
22601
|
* Motion sources that can activate the pipeline. At least one must
|
|
22557
22602
|
* be present. Multiple sources OR-ed: any one firing arms the
|
|
22558
22603
|
* cooldown timer and transitions watching → active. The same
|
|
@@ -25148,6 +25193,104 @@ var WebrtcStreamChoiceSchema = object({
|
|
|
25148
25193
|
/** Bitrate in kbps (null for adaptive). */
|
|
25149
25194
|
bitrateKbps: number().nullable()
|
|
25150
25195
|
});
|
|
25196
|
+
/**
|
|
25197
|
+
* The last move the adaptive ladder made on this session, and the numbers it
|
|
25198
|
+
* rested on. Mirrors `LadderDecision` in the stream-broker's
|
|
25199
|
+
* `adaptive-controller.ts` — the log line's evidence, made readable.
|
|
25200
|
+
*
|
|
25201
|
+
* Nullability is load-bearing: `lossRatio: null` means the loss tracker had
|
|
25202
|
+
* no window to report, which is NOT a link with zero loss.
|
|
25203
|
+
*/
|
|
25204
|
+
var WebrtcLadderDecisionSchema = object({
|
|
25205
|
+
/** `loss` — RTCP / client-reported packet loss; `stall` — the picture
|
|
25206
|
+
* stopped moving; `healthy` — a promotion. */
|
|
25207
|
+
cause: _enum([
|
|
25208
|
+
"loss",
|
|
25209
|
+
"stall",
|
|
25210
|
+
"healthy"
|
|
25211
|
+
]),
|
|
25212
|
+
/** The rung the ladder moved TO. Null for a move that changed no rung
|
|
25213
|
+
* (an MTU reduction, a forced transcode-down at the bottom). */
|
|
25214
|
+
tier: CamProfileSchema.nullable(),
|
|
25215
|
+
/** Whether that move forced the egress transcode-down rung. */
|
|
25216
|
+
forcedTranscodeDown: boolean(),
|
|
25217
|
+
/** Loss ratio over the tracker's window at the moment of the decision. */
|
|
25218
|
+
lossRatio: number().nullable(),
|
|
25219
|
+
/** The client's reported downlink at that moment, in kbps. */
|
|
25220
|
+
clientKbps: number().nullable(),
|
|
25221
|
+
/** How long the stall detector had been seeing nothing arrive. */
|
|
25222
|
+
stalledForMs: number().nullable(),
|
|
25223
|
+
/** Whether the broker WAS sending while that was measured — the half that
|
|
25224
|
+
* separates "the camera stopped" from "the link stopped". */
|
|
25225
|
+
serverSending: boolean().nullable(),
|
|
25226
|
+
/** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
|
|
25227
|
+
* must not read like one from this second. */
|
|
25228
|
+
at: number()
|
|
25229
|
+
});
|
|
25230
|
+
/** Per-tier count of MEASURED downgrades this viewer session has suffered. */
|
|
25231
|
+
var WebrtcTierFailureSchema = object({
|
|
25232
|
+
tier: CamProfileSchema,
|
|
25233
|
+
failures: number().int().nonnegative()
|
|
25234
|
+
});
|
|
25235
|
+
/**
|
|
25236
|
+
* What the SERVER decided for one WebRTC session — the half the client cannot
|
|
25237
|
+
* measure. See `getSessionDebug`.
|
|
25238
|
+
*/
|
|
25239
|
+
var WebrtcSessionDebugSchema = object({
|
|
25240
|
+
sessionId: string(),
|
|
25241
|
+
/** Whether the session still exists here. Everything below is null when
|
|
25242
|
+
* it does not — a dead session has no measurements, not zero ones. */
|
|
25243
|
+
alive: boolean(),
|
|
25244
|
+
/** Epoch ms this snapshot was taken, so the overlay can show its age. */
|
|
25245
|
+
observedAt: number(),
|
|
25246
|
+
/** The broker actually feeding this session (`<deviceId>/<camStreamId>`). */
|
|
25247
|
+
brokerId: string().nullable(),
|
|
25248
|
+
camStreamId: string().nullable(),
|
|
25249
|
+
/** `derived` = this hub BUILT the rung being served (a transcode/scale of
|
|
25250
|
+
* another stream); `native` = the camera's own encode. */
|
|
25251
|
+
sourceKind: _enum([
|
|
25252
|
+
"native",
|
|
25253
|
+
"derived",
|
|
25254
|
+
"unknown"
|
|
25255
|
+
]),
|
|
25256
|
+
/** Whether the ladder is running at all (an `adaptive` target). */
|
|
25257
|
+
adaptive: boolean(),
|
|
25258
|
+
/** The rung `auto` ACTUALLY resolved to right now. Null when the broker
|
|
25259
|
+
* is bound to no profile slot (a manually-activated stream). */
|
|
25260
|
+
servedTier: CamProfileSchema.nullable(),
|
|
25261
|
+
/** Which node ingests the source. Null when unresolved. */
|
|
25262
|
+
ingestNodeId: string().nullable(),
|
|
25263
|
+
/** Whether that node is a remote one (the media crosses a node boundary
|
|
25264
|
+
* before it reaches this session). Null when unresolved. */
|
|
25265
|
+
ingestIsRemote: boolean().nullable(),
|
|
25266
|
+
/** Codec off the source, and the one negotiated with this client. */
|
|
25267
|
+
sourceCodec: string().nullable(),
|
|
25268
|
+
negotiatedCodec: string().nullable(),
|
|
25269
|
+
/** Whether the EGRESS leg re-encodes for this client or forwards the
|
|
25270
|
+
* source bytes untouched. */
|
|
25271
|
+
egressLeg: _enum([
|
|
25272
|
+
"copy",
|
|
25273
|
+
"transcode",
|
|
25274
|
+
"unknown"
|
|
25275
|
+
]),
|
|
25276
|
+
/** What the broker itself reports about the rung it is serving. */
|
|
25277
|
+
brokerStatus: string().nullable(),
|
|
25278
|
+
brokerCodec: string().nullable(),
|
|
25279
|
+
brokerBitrateKbps: number().nullable(),
|
|
25280
|
+
brokerInputFps: number().nullable(),
|
|
25281
|
+
brokerUptimeMs: number().nullable(),
|
|
25282
|
+
/** RTP packets this session has sent — the server's own "am I sending?". */
|
|
25283
|
+
rtpPacketsSent: number().nullable(),
|
|
25284
|
+
/** D42: the track may be carrying the recorded feeder, not live. The ladder
|
|
25285
|
+
* is suspended there, so a frozen picture in `recorded` mode means
|
|
25286
|
+
* something entirely different. */
|
|
25287
|
+
trackMode: _enum(["live", "recorded"]).nullable(),
|
|
25288
|
+
lastDecision: WebrtcLadderDecisionSchema.nullable(),
|
|
25289
|
+
tierFailures: array(WebrtcTierFailureSchema),
|
|
25290
|
+
/** Successful promotions in this viewer session (drives the progressive
|
|
25291
|
+
* healthy timer). Null when no ladder is running. */
|
|
25292
|
+
upgradeCount: number().nullable()
|
|
25293
|
+
});
|
|
25151
25294
|
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(WebrtcStreamChoiceSchema)), method(object({
|
|
25152
25295
|
deviceId: number().int().nonnegative(),
|
|
25153
25296
|
target: WebrtcStreamTargetSchema,
|
|
@@ -25312,7 +25455,10 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
|
|
|
25312
25455
|
* the same answer as "it ended": either way nothing is holding it up.
|
|
25313
25456
|
*/
|
|
25314
25457
|
alive: boolean()
|
|
25315
|
-
}))
|
|
25458
|
+
})), method(object({
|
|
25459
|
+
deviceId: number().int().nonnegative(),
|
|
25460
|
+
sessionId: string()
|
|
25461
|
+
}), WebrtcSessionDebugSchema, { kind: "query" });
|
|
25316
25462
|
object({
|
|
25317
25463
|
/** All accessory children of the parent. */
|
|
25318
25464
|
childDeviceIds: array(number()).readonly(),
|
|
@@ -29711,16 +29857,37 @@ var GridProfileOfferSchema = object({
|
|
|
29711
29857
|
]),
|
|
29712
29858
|
offered: boolean(),
|
|
29713
29859
|
/** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
|
|
29714
|
-
missingSources: array(number().int().positive())
|
|
29860
|
+
missingSources: array(number().int().positive()),
|
|
29861
|
+
/**
|
|
29862
|
+
* The canvas this profile composes onto, `WxH`, or empty when it is not
|
|
29863
|
+
* offered. DERIVED from the cells and the sources' own size at this profile —
|
|
29864
|
+
* it is reported because nothing else in the system would ever say what the
|
|
29865
|
+
* grid came out as, and because it is the number an operator would otherwise
|
|
29866
|
+
* expect to type.
|
|
29867
|
+
*/
|
|
29868
|
+
canvas: string(),
|
|
29869
|
+
/**
|
|
29870
|
+
* Whether this profile is PUBLISHED, of the ones the grid could serve.
|
|
29871
|
+
*
|
|
29872
|
+
* A grid's `high` is composed of its sources' `high`, so on a 4K fleet it is
|
|
29873
|
+
* a 4K canvas built from 4K decodes — something to opt into, not something a
|
|
29874
|
+
* viewer's adaptive should be handed by climbing to the top rung it can see.
|
|
29875
|
+
* Default is `mid` + `low`.
|
|
29876
|
+
*/
|
|
29877
|
+
published: boolean()
|
|
29715
29878
|
});
|
|
29716
29879
|
var GridLayoutViewSchema = object({
|
|
29717
29880
|
/** The persisted grid row this camera was declared from. */
|
|
29718
29881
|
instanceId: string(),
|
|
29719
29882
|
deviceId: number().int().nonnegative(),
|
|
29720
29883
|
name: string(),
|
|
29721
|
-
/**
|
|
29722
|
-
|
|
29723
|
-
|
|
29884
|
+
/**
|
|
29885
|
+
* NO canvas size. A grid's resolution is not authored: each profile derives
|
|
29886
|
+
* its own from the cells and its sources' dimensions. The two numbers that
|
|
29887
|
+
* used to be here were a text field that silently decided both how much the
|
|
29888
|
+
* composite cost and how sharp it was — see `profiles[].canvas` for what it
|
|
29889
|
+
* came out as.
|
|
29890
|
+
*/
|
|
29724
29891
|
fps: number().int(),
|
|
29725
29892
|
cells: array(GridLayoutCellSchema),
|
|
29726
29893
|
/** What the catalog will publish, and what it refuses to. Read-only. */
|
|
@@ -29729,9 +29896,13 @@ var GridLayoutViewSchema = object({
|
|
|
29729
29896
|
var GridLayoutPatchSchema = object({
|
|
29730
29897
|
deviceId: number().int().nonnegative(),
|
|
29731
29898
|
name: string().min(1).max(160).optional(),
|
|
29732
|
-
width: number().int().min(160).max(7680).optional(),
|
|
29733
|
-
height: number().int().min(120).max(4320).optional(),
|
|
29734
29899
|
fps: number().int().min(1).max(60).optional(),
|
|
29900
|
+
/** Which profiles to publish. See `GridProfileOffer.published`. */
|
|
29901
|
+
publishedProfiles: array(_enum([
|
|
29902
|
+
"high",
|
|
29903
|
+
"mid",
|
|
29904
|
+
"low"
|
|
29905
|
+
])).max(3).optional(),
|
|
29735
29906
|
/**
|
|
29736
29907
|
* The whole cell list at once. A per-cell patch would need an ordering the
|
|
29737
29908
|
* editor does not have, and a half-applied layout is a picture nobody asked
|
|
@@ -32146,6 +32317,25 @@ var ptzAutotrackCapability = {
|
|
|
32146
32317
|
scope: "device",
|
|
32147
32318
|
deviceNative: true,
|
|
32148
32319
|
mode: "singleton",
|
|
32320
|
+
/**
|
|
32321
|
+
* Two kinds of provider behind one name, the same shape `snapshot` and
|
|
32322
|
+
* `doorbell` use:
|
|
32323
|
+
*
|
|
32324
|
+
* - **Native**: the camera's own firmware autotrack, registered per device by
|
|
32325
|
+
* the driver when the probe says it exists (Reolink: yes; Hikvision:
|
|
32326
|
+
* model-dependent).
|
|
32327
|
+
* - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
|
|
32328
|
+
* which follows a subject with `ptz.move` pulses driven by the detection
|
|
32329
|
+
* pipeline. `defaultActive: false` — the operator binds it per camera in
|
|
32330
|
+
* the bindings UI, exactly as `virtual-doorbell` is bound.
|
|
32331
|
+
*
|
|
32332
|
+
* The binding chooses WHICH implementation answers; `setEnabled` stays the
|
|
32333
|
+
* one switch for the function either way. A camera never has both driving its
|
|
32334
|
+
* head, which is the whole reason this is not a second cap with a second
|
|
32335
|
+
* toggle (D62, D520).
|
|
32336
|
+
*/
|
|
32337
|
+
kind: "wrapper",
|
|
32338
|
+
defaultActive: false,
|
|
32149
32339
|
deviceTypes: [DeviceType.Camera],
|
|
32150
32340
|
deviceConfig: { ui: {
|
|
32151
32341
|
kind: "widget",
|
|
@@ -43517,6 +43707,12 @@ Object.freeze({
|
|
|
43517
43707
|
addonId: null,
|
|
43518
43708
|
access: "view"
|
|
43519
43709
|
},
|
|
43710
|
+
"webrtcSession.getSessionDebug": {
|
|
43711
|
+
capName: "webrtc-session",
|
|
43712
|
+
capScope: "device",
|
|
43713
|
+
addonId: null,
|
|
43714
|
+
access: "view"
|
|
43715
|
+
},
|
|
43520
43716
|
"webrtcSession.getSessionState": {
|
|
43521
43717
|
capName: "webrtc-session",
|
|
43522
43718
|
capScope: "device",
|
|
@@ -45500,6 +45696,11 @@ Object.freeze({
|
|
|
45500
45696
|
form: "single",
|
|
45501
45697
|
optional: false
|
|
45502
45698
|
}],
|
|
45699
|
+
"webrtcSession.getSessionDebug": [{
|
|
45700
|
+
name: "deviceId",
|
|
45701
|
+
form: "single",
|
|
45702
|
+
optional: false
|
|
45703
|
+
}],
|
|
45503
45704
|
"webrtcSession.getSessionState": [{
|
|
45504
45705
|
name: "deviceId",
|
|
45505
45706
|
form: "single",
|
|
@@ -75185,7 +75386,7 @@ var require_lz4 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
75185
75386
|
};
|
|
75186
75387
|
}));
|
|
75187
75388
|
//#endregion
|
|
75188
|
-
//#region node_modules/@apocaliss92/nodelink-js/dist/chunk-
|
|
75389
|
+
//#region node_modules/@apocaliss92/nodelink-js/dist/chunk-7JMYBUEB.js
|
|
75189
75390
|
var import_fxp = require_fxp();
|
|
75190
75391
|
var import_lz4 = /* @__PURE__ */ __toESM(require_lz4(), 1);
|
|
75191
75392
|
function encodeHeader(h) {
|
|
@@ -83645,10 +83846,14 @@ function applyOsdDatetimePatch(rawXml, patch) {
|
|
|
83645
83846
|
return xml;
|
|
83646
83847
|
}
|
|
83647
83848
|
var OSD_POSITION_MAX = 65536;
|
|
83849
|
+
var OSD_POSITION_START_WRITE = 1;
|
|
83850
|
+
var OSD_POSITION_CENTER = 65537;
|
|
83648
83851
|
var OSD_CORNERS = [
|
|
83649
83852
|
"top-left",
|
|
83853
|
+
"top-center",
|
|
83650
83854
|
"top-right",
|
|
83651
83855
|
"bottom-left",
|
|
83856
|
+
"bottom-center",
|
|
83652
83857
|
"bottom-right"
|
|
83653
83858
|
];
|
|
83654
83859
|
function isStartEdge(value) {
|
|
@@ -83660,7 +83865,7 @@ function isEndEdge(value) {
|
|
|
83660
83865
|
function readOsdPosition(x, y) {
|
|
83661
83866
|
if (typeof x !== "number" || typeof y !== "number") return { kind: "unknown" };
|
|
83662
83867
|
if (!Number.isFinite(x) || !Number.isFinite(y)) return { kind: "unknown" };
|
|
83663
|
-
const horizontal = isStartEdge(x) ? "left" : isEndEdge(x) ? "right" : null;
|
|
83868
|
+
const horizontal = x === OSD_POSITION_CENTER ? "center" : isStartEdge(x) ? "left" : isEndEdge(x) ? "right" : null;
|
|
83664
83869
|
const vertical = isStartEdge(y) ? "top" : isEndEdge(y) ? "bottom" : null;
|
|
83665
83870
|
if (horizontal === null || vertical === null) return {
|
|
83666
83871
|
kind: "custom",
|
|
@@ -83674,8 +83879,8 @@ function readOsdPosition(x, y) {
|
|
|
83674
83879
|
}
|
|
83675
83880
|
function coordsForOsdCorner(corner) {
|
|
83676
83881
|
return {
|
|
83677
|
-
x: corner.endsWith("-right") ? OSD_POSITION_MAX :
|
|
83678
|
-
y: corner.startsWith("bottom-") ? OSD_POSITION_MAX :
|
|
83882
|
+
x: corner.endsWith("-center") ? OSD_POSITION_CENTER : corner.endsWith("-right") ? OSD_POSITION_MAX : OSD_POSITION_START_WRITE,
|
|
83883
|
+
y: corner.startsWith("bottom-") ? OSD_POSITION_MAX : OSD_POSITION_START_WRITE
|
|
83679
83884
|
};
|
|
83680
83885
|
}
|
|
83681
83886
|
function isOsdCorner(value) {
|
|
@@ -83686,8 +83891,10 @@ function formatOsdCoords(x, y) {
|
|
|
83686
83891
|
}
|
|
83687
83892
|
var OSD_CORNER_LABELS = {
|
|
83688
83893
|
"top-left": "Top left",
|
|
83894
|
+
"top-center": "Top centre",
|
|
83689
83895
|
"top-right": "Top right",
|
|
83690
83896
|
"bottom-left": "Bottom left",
|
|
83897
|
+
"bottom-center": "Bottom centre",
|
|
83691
83898
|
"bottom-right": "Bottom right"
|
|
83692
83899
|
};
|
|
83693
83900
|
function wireFloat(value) {
|
|
@@ -88214,8 +88421,12 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
88214
88421
|
/** Generic Baichuan cmd_id call, returns XML (if any). */
|
|
88215
88422
|
async sendXml(params, retry = 3) {
|
|
88216
88423
|
if (!this.client.loggedIn) await this.client.login();
|
|
88217
|
-
const
|
|
88218
|
-
|
|
88424
|
+
const addressed = params.channel != null && params.extensionXml == null ? {
|
|
88425
|
+
...params,
|
|
88426
|
+
extensionXml: buildChannelExtensionXml(params.channel)
|
|
88427
|
+
} : params;
|
|
88428
|
+
const frame = await this.client.sendFrame(addressed);
|
|
88429
|
+
if (frame.header.responseCode === 400) return await this.handleSendXml400(addressed, frame, retry);
|
|
88219
88430
|
if (frame.body.length === 0) return "";
|
|
88220
88431
|
return this.client.tryDecryptXml(frame.body, frame.header.channelId, this.client.enc);
|
|
88221
88432
|
}
|
|
@@ -94516,6 +94727,7 @@ ${xml}`);
|
|
|
94516
94727
|
await this.sendXml({
|
|
94517
94728
|
cmdId: 45,
|
|
94518
94729
|
channel: ch,
|
|
94730
|
+
extensionXml: buildChannelExtensionXml(ch),
|
|
94519
94731
|
payloadXml: ensureXmlHeader(xml),
|
|
94520
94732
|
...timeoutOpts
|
|
94521
94733
|
});
|
|
@@ -145550,6 +145762,7 @@ var RAW_READ_CATALOG = {
|
|
|
145550
145762
|
kind: "deviceCache",
|
|
145551
145763
|
snapshotKey: "autoFocusSnapshot"
|
|
145552
145764
|
},
|
|
145765
|
+
needs: "ptz",
|
|
145553
145766
|
invoke: (api, channel) => api.getAutoFocus(channel, { timeoutMs: 1500 })
|
|
145554
145767
|
},
|
|
145555
145768
|
netPort: {
|
|
@@ -145746,6 +145959,7 @@ var RawReadResultSchema = discriminatedUnion("ok", [object({
|
|
|
145746
145959
|
slice: RawReadSliceSchema,
|
|
145747
145960
|
reason: _enum([
|
|
145748
145961
|
"sleeping",
|
|
145962
|
+
"unsupported",
|
|
145749
145963
|
"login-failed",
|
|
145750
145964
|
"read-failed"
|
|
145751
145965
|
]),
|
|
@@ -145789,6 +146003,20 @@ function toPlainJson(value) {
|
|
|
145789
146003
|
async function performRawRead(slice, deps) {
|
|
145790
146004
|
const entry = RAW_READ_CATALOG[slice];
|
|
145791
146005
|
const believed = believedState(deps, entry);
|
|
146006
|
+
if (entry.needs === "ptz" && !deps.hasPtz) {
|
|
146007
|
+
deps.logger.info("reolink raw read refused — the camera has no PTZ head", {
|
|
146008
|
+
tags: { deviceId: deps.deviceId },
|
|
146009
|
+
meta: { slice }
|
|
146010
|
+
});
|
|
146011
|
+
return {
|
|
146012
|
+
ok: false,
|
|
146013
|
+
deviceId: deps.deviceId,
|
|
146014
|
+
slice,
|
|
146015
|
+
reason: "unsupported",
|
|
146016
|
+
message: `This camera has no pan/tilt/zoom head, so it has no focus motor and no "${slice}" to read — the Reolink app does not ask it either. Nothing is wrong with the camera.`,
|
|
146017
|
+
believed
|
|
146018
|
+
};
|
|
146019
|
+
}
|
|
145792
146020
|
const servedWithoutTouchingCamera = deps.hubChild && entry.servedByParentHub === true;
|
|
145793
146021
|
if (deps.sleeping && !servedWithoutTouchingCamera) {
|
|
145794
146022
|
deps.logger.info("reolink raw read refused — battery cam is sleeping", {
|
|
@@ -149452,7 +149680,6 @@ var reolinkCameraSchema = object({
|
|
|
149452
149680
|
* read via 52). Toggles the `<Shelter><enable>` flag. Zone
|
|
149453
149681
|
* editing is a separate flow not exposed by this driver.
|
|
149454
149682
|
*/
|
|
149455
|
-
privacyMaskEnabled: boolean().optional(),
|
|
149456
149683
|
/**
|
|
149457
149684
|
* Audio noise reduction level — pushed via `setAudioNoise`
|
|
149458
149685
|
* (cmd_id 440, read via 439). 0 disables; 1..3 maps to
|
|
@@ -150286,7 +150513,6 @@ function slicesForPatch(patch) {
|
|
|
150286
150513
|
if (has("motionEnabled", "motionSensitivity")) out.add("motion");
|
|
150287
150514
|
if (Object.keys(patch).some((k) => /^ai(Person|Vehicle|Animal|Face|Package)/.test(k))) out.add("ai");
|
|
150288
150515
|
if (has("gop")) out.add("enc");
|
|
150289
|
-
if (has("privacyMaskEnabled")) out.add("mask");
|
|
150290
150516
|
if (has("audioNoiseLevel")) out.add("audioNoise");
|
|
150291
150517
|
if (has("autoFocusEnabled")) out.add("autofocus");
|
|
150292
150518
|
if (has("netHttpEnable", "netHttpPort", "netHttpsEnable", "netHttpsPort", "netRtspEnable", "netRtspPort", "netRtmpEnable", "netRtmpPort", "netOnvifEnable", "netOnvifPort")) out.add("netPort");
|
|
@@ -150298,6 +150524,20 @@ function slicesForPatch(patch) {
|
|
|
150298
150524
|
if (has("timeZoneId")) out.add("systemGeneral");
|
|
150299
150525
|
return out;
|
|
150300
150526
|
}
|
|
150527
|
+
/**
|
|
150528
|
+
* What the operator's "Refresh from camera" actually managed.
|
|
150529
|
+
*
|
|
150530
|
+
* Returned rather than logged, because the button is the one place a person
|
|
150531
|
+
* asks "is what I am looking at true right now" — and an answer of `void`
|
|
150532
|
+
* cannot distinguish a full re-read from a camera that never woke.
|
|
150533
|
+
*/
|
|
150534
|
+
var RefreshSettingsResultSchema = object({
|
|
150535
|
+
/** False when a battery camera did not come up — everything below is then stale. */
|
|
150536
|
+
awake: boolean(),
|
|
150537
|
+
snapshotRefreshed: boolean(),
|
|
150538
|
+
capsWarmed: number(),
|
|
150539
|
+
capsTotal: number()
|
|
150540
|
+
});
|
|
150301
150541
|
var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
150302
150542
|
type = DeviceType.Camera;
|
|
150303
150543
|
/**
|
|
@@ -150767,9 +151007,12 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150767
151007
|
* `setVideoInput`); motion is writable via `setMotionAlarm` so its
|
|
150768
151008
|
* snapshot also drives the dispatch's known-good baseline.
|
|
150769
151009
|
*/
|
|
150770
|
-
async refreshParentSettingsSnapshot(slices) {
|
|
150771
|
-
if (this.isBattery && this.sleeping) {
|
|
150772
|
-
this.ctx.logger.info("refreshParentSettingsSnapshot skipped — battery cam is sleeping", { tags: {
|
|
151010
|
+
async refreshParentSettingsSnapshot(slices, reason = "background") {
|
|
151011
|
+
if (this.isBattery && this.sleeping && reason !== "post-write") {
|
|
151012
|
+
this.ctx.logger.info("refreshParentSettingsSnapshot skipped — battery cam is sleeping", { tags: {
|
|
151013
|
+
deviceId: this.id,
|
|
151014
|
+
reason
|
|
151015
|
+
} });
|
|
150773
151016
|
return;
|
|
150774
151017
|
}
|
|
150775
151018
|
let api;
|
|
@@ -151014,6 +151257,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151014
151257
|
channel: this.getChannel(),
|
|
151015
151258
|
sleeping: this.isBattery && this.sleeping,
|
|
151016
151259
|
hubChild: this.isHubChild(),
|
|
151260
|
+
hasPtz: this.getProbeFlags().hasPtz === true,
|
|
151017
151261
|
getApi: () => this.ensureApi(),
|
|
151018
151262
|
cache: this.config.get("deviceCache"),
|
|
151019
151263
|
batteryState: this.runtimeState.getCapState("battery"),
|
|
@@ -151107,6 +151351,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151107
151351
|
});
|
|
151108
151352
|
await sleep$2(1500);
|
|
151109
151353
|
})(), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("wakeIfSleeping timeout")), timeoutMs))]);
|
|
151354
|
+
if (this.commitSleepState(false, "confirmed-wake")) {
|
|
151355
|
+
this.ctx.logger.info("proactive action: camera is up — committing the mirror", { tags: { deviceId: this.id } });
|
|
151356
|
+
this.onWakeTransition("confirmed-wake").catch(() => {});
|
|
151357
|
+
}
|
|
151110
151358
|
return true;
|
|
151111
151359
|
} catch (err) {
|
|
151112
151360
|
this.ctx.logger.warn("proactive action: wake did not complete in time", {
|
|
@@ -151278,8 +151526,26 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151278
151526
|
this.registerDeviceAction("auto-configure-email-push", deviceCustomAction(unknown(), _void(), { kind: "mutation" }), async () => {
|
|
151279
151527
|
await this.autoConfigureEmailPush();
|
|
151280
151528
|
});
|
|
151281
|
-
this.registerDeviceAction("refresh-settings", deviceCustomAction(unknown(),
|
|
151282
|
-
|
|
151529
|
+
this.registerDeviceAction("refresh-settings", deviceCustomAction(unknown(), RefreshSettingsResultSchema, { kind: "mutation" }), async () => {
|
|
151530
|
+
const awake = await this.wakeIfSleeping();
|
|
151531
|
+
const [snapshot, warm] = await Promise.allSettled([this.refreshParentSettingsSnapshot(void 0, "operator"), this.warmCapSlicesOnWake({ force: true })]);
|
|
151532
|
+
const warmed = warm.status === "fulfilled" ? warm.value.warmed : 0;
|
|
151533
|
+
const capsTotal = this.capWarmers.size;
|
|
151534
|
+
this.ctx.logger.info("refresh from camera: operator rehydrate", {
|
|
151535
|
+
tags: { deviceId: this.id },
|
|
151536
|
+
meta: {
|
|
151537
|
+
awake,
|
|
151538
|
+
snapshot: snapshot.status,
|
|
151539
|
+
warmed,
|
|
151540
|
+
capsTotal
|
|
151541
|
+
}
|
|
151542
|
+
});
|
|
151543
|
+
return {
|
|
151544
|
+
awake,
|
|
151545
|
+
snapshotRefreshed: snapshot.status === "fulfilled",
|
|
151546
|
+
capsWarmed: warmed,
|
|
151547
|
+
capsTotal
|
|
151548
|
+
};
|
|
151283
151549
|
});
|
|
151284
151550
|
this.registerDeviceAction("refresh-sessions", deviceCustomAction(unknown(), _void(), { kind: "mutation" }), async () => {
|
|
151285
151551
|
await this.refreshSessionsSnapshot();
|
|
@@ -151319,7 +151585,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151319
151585
|
* registration so the general "Refresh from camera" button can warm
|
|
151320
151586
|
* the `stream-params` runtime-state slice (the editable Stream
|
|
151321
151587
|
* section's current values read from it). */
|
|
151322
|
-
refreshStreamParamsFromCamera = null;
|
|
151323
151588
|
/** Single-flight guard for the motion-zones cap refresh. */
|
|
151324
151589
|
motionZonesRefreshInFlight = null;
|
|
151325
151590
|
/** Single-flight guard for the privacy-mask cap refresh. */
|
|
@@ -151453,6 +151718,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151453
151718
|
};
|
|
151454
151719
|
}
|
|
151455
151720
|
if (confirmed && !observedAwake && parent !== null) this.watchHubChildAwake(parent).catch(() => {});
|
|
151721
|
+
if (this.commitSleepState(false, "confirmed-wake")) {
|
|
151722
|
+
this.ctx.logger.info("battery wakeForStream: answering awake — committing the mirror", {
|
|
151723
|
+
tags: { deviceId: this.id },
|
|
151724
|
+
meta: {
|
|
151725
|
+
confirmedMs: Date.now() - startedAt,
|
|
151726
|
+
observedAwake
|
|
151727
|
+
}
|
|
151728
|
+
});
|
|
151729
|
+
this.onWakeTransition("confirmed-wake").catch(() => {});
|
|
151730
|
+
}
|
|
151456
151731
|
return {
|
|
151457
151732
|
awoke: true,
|
|
151458
151733
|
durationMs: Date.now() - startedAt
|
|
@@ -151578,7 +151853,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151578
151853
|
commitSleepState(next, source) {
|
|
151579
151854
|
if (!this.isBattery) return false;
|
|
151580
151855
|
if (this.sleeping === next) return false;
|
|
151581
|
-
if (source
|
|
151856
|
+
if (source === "confirmed-wake" && next === false) this.sleepStateChangedAt = Date.now();
|
|
151857
|
+
else if (source !== "hub-summary" && !this.acceptSleepingTransition(next)) return false;
|
|
151582
151858
|
if (source === "hub-summary") {
|
|
151583
151859
|
const sinceCommitMs = Date.now() - this.sleepStateChangedAt;
|
|
151584
151860
|
if (next === true && this.sleepStateChangedAt > 0 && sinceCommitMs < ReolinkCamera.HUB_SUMMARY_SLEEP_GRACE_MS) {
|
|
@@ -152714,14 +152990,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152714
152990
|
* swallowed: the window is short, partial progress accumulates across
|
|
152715
152991
|
* wakes because the slices persist.
|
|
152716
152992
|
*/
|
|
152717
|
-
async warmCapSlicesOnWake() {
|
|
152993
|
+
async warmCapSlicesOnWake(opts) {
|
|
152718
152994
|
const now = Date.now();
|
|
152719
152995
|
let warmed = 0;
|
|
152720
152996
|
let skipped = 0;
|
|
152721
152997
|
for (const [capName, warm] of this.capWarmers) {
|
|
152722
152998
|
const slice = this.runtimeState.getCapState(capName);
|
|
152723
152999
|
const fetchedAt = typeof slice?.lastFetchedAt === "number" ? slice.lastFetchedAt : 0;
|
|
152724
|
-
if (fetchedAt > 0 && now - fetchedAt < ReolinkCamera.WAKE_WARM_MAX_AGE_MS) {
|
|
153000
|
+
if (opts?.force !== true && fetchedAt > 0 && now - fetchedAt < ReolinkCamera.WAKE_WARM_MAX_AGE_MS) {
|
|
152725
153001
|
skipped += 1;
|
|
152726
153002
|
continue;
|
|
152727
153003
|
}
|
|
@@ -152746,6 +153022,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152746
153022
|
total: this.capWarmers.size
|
|
152747
153023
|
}
|
|
152748
153024
|
});
|
|
153025
|
+
return {
|
|
153026
|
+
warmed,
|
|
153027
|
+
skipped
|
|
153028
|
+
};
|
|
152749
153029
|
}
|
|
152750
153030
|
/**
|
|
152751
153031
|
* Ask the snapshot wrapper for a frame while the camera is up, so its
|
|
@@ -152811,7 +153091,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152811
153091
|
registerStreamParamsCap() {
|
|
152812
153092
|
const channel = this.getChannel();
|
|
152813
153093
|
const CAP_NAME = "stream-params";
|
|
152814
|
-
const STALE_MS = 1e4;
|
|
152815
153094
|
/** Round-trip `getEnc`, map into the cap status schema, write slice. */
|
|
152816
153095
|
const refreshFromCamera = async () => {
|
|
152817
153096
|
if (this.streamParamsRefreshInFlight) return this.streamParamsRefreshInFlight;
|
|
@@ -152844,13 +153123,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152844
153123
|
this.streamParamsRefreshInFlight = null;
|
|
152845
153124
|
}
|
|
152846
153125
|
};
|
|
152847
|
-
this.refreshStreamParamsFromCamera = refreshFromCamera;
|
|
152848
153126
|
const bridge = createRuntimeStateBridge({
|
|
152849
153127
|
runtimeState: this.runtimeState,
|
|
152850
153128
|
cap: streamParamsCapability,
|
|
152851
153129
|
ownDeviceId: this.id,
|
|
152852
153130
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
152853
|
-
staleMs:
|
|
153131
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153132
|
+
staleRead: "serve-and-revalidate",
|
|
152854
153133
|
empty: () => ({ lastFetchedAt: 0 })
|
|
152855
153134
|
});
|
|
152856
153135
|
const resolveOptions = async () => this.resolveCapOptions({
|
|
@@ -152935,7 +153214,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152935
153214
|
registerMotionZonesCap() {
|
|
152936
153215
|
const channel = this.getChannel();
|
|
152937
153216
|
const CAP_NAME = "motion-zones";
|
|
152938
|
-
const STALE_MS = 1e4;
|
|
152939
153217
|
/** Round-trip `getMotionAlarm`, map into the cap status schema, write slice. */
|
|
152940
153218
|
const refreshFromCamera = async () => {
|
|
152941
153219
|
if (this.motionZonesRefreshInFlight) return this.motionZonesRefreshInFlight;
|
|
@@ -152985,7 +153263,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152985
153263
|
cap: motionZonesCapability,
|
|
152986
153264
|
ownDeviceId: this.id,
|
|
152987
153265
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
152988
|
-
staleMs:
|
|
153266
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153267
|
+
staleRead: "serve-and-revalidate",
|
|
152989
153268
|
empty: () => ({
|
|
152990
153269
|
enabled: false,
|
|
152991
153270
|
sensitivity: 1,
|
|
@@ -153316,7 +153595,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153316
153595
|
registerDayNightCap() {
|
|
153317
153596
|
const channel = this.getChannel();
|
|
153318
153597
|
const CAP_NAME = "day-night";
|
|
153319
|
-
const STALE_MS = 1e4;
|
|
153320
153598
|
/** Round-trip `getVideoInput` + `getDayNightThreshold`, map into the
|
|
153321
153599
|
* cap status schema, write slice. */
|
|
153322
153600
|
const refreshFromCamera = async () => {
|
|
@@ -153359,7 +153637,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153359
153637
|
cap: dayNightCapability,
|
|
153360
153638
|
ownDeviceId: this.id,
|
|
153361
153639
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
153362
|
-
staleMs:
|
|
153640
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153641
|
+
staleRead: "serve-and-revalidate",
|
|
153363
153642
|
empty: () => ({
|
|
153364
153643
|
mode: "auto",
|
|
153365
153644
|
lastFetchedAt: 0
|
|
@@ -153452,7 +153731,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153452
153731
|
registerImageSettingsCap() {
|
|
153453
153732
|
const channel = this.getChannel();
|
|
153454
153733
|
const CAP_NAME = "image-settings";
|
|
153455
|
-
const STALE_MS = 1e4;
|
|
153456
153734
|
const SLIDER_MIN = 0;
|
|
153457
153735
|
const SLIDER_MAX = 255;
|
|
153458
153736
|
const NORMALIZED_RANGE = {
|
|
@@ -153496,7 +153774,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153496
153774
|
cap: imageSettingsCapability,
|
|
153497
153775
|
ownDeviceId: this.id,
|
|
153498
153776
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
153499
|
-
staleMs:
|
|
153777
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153778
|
+
staleRead: "serve-and-revalidate",
|
|
153500
153779
|
empty: () => ({ lastFetchedAt: 0 })
|
|
153501
153780
|
}).getStatus,
|
|
153502
153781
|
getOptions: async ({ deviceId }) => {
|
|
@@ -153813,7 +154092,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153813
154092
|
/** Stale-after window for served-from-cache reads. ~10s is short
|
|
153814
154093
|
* enough that the UI's 5s poll always sees a fresh round-trip,
|
|
153815
154094
|
* long enough that two consumers in the same tick fold into one. */
|
|
153816
|
-
const STALE_MS = 1e4;
|
|
153817
154095
|
const SUPPORTED_TARGET_TYPES = [
|
|
153818
154096
|
{
|
|
153819
154097
|
value: "people",
|
|
@@ -153879,7 +154157,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153879
154157
|
cap: ptzAutotrackCapability,
|
|
153880
154158
|
ownDeviceId: this.id,
|
|
153881
154159
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
153882
|
-
staleMs:
|
|
154160
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
154161
|
+
staleRead: "serve-and-revalidate",
|
|
153883
154162
|
empty: () => ({
|
|
153884
154163
|
enabled: false,
|
|
153885
154164
|
lastChangedAt: 0,
|
|
@@ -155640,7 +155919,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155640
155919
|
id: "driver-refresh",
|
|
155641
155920
|
tab: "general",
|
|
155642
155921
|
title: "Camera data",
|
|
155643
|
-
description: "
|
|
155922
|
+
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.",
|
|
155644
155923
|
columns: 1,
|
|
155645
155924
|
fields: [{
|
|
155646
155925
|
type: "device-action-button",
|
|
@@ -155648,7 +155927,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155648
155927
|
label: "Refresh from camera",
|
|
155649
155928
|
buttonLabel: "Refresh now",
|
|
155650
155929
|
action: "refresh-settings",
|
|
155651
|
-
variant: "default"
|
|
155930
|
+
variant: "default",
|
|
155931
|
+
successMessage: "Camera awake: {awake} · settings re-read: {snapshotRefreshed} · slices refreshed: {capsWarmed}/{capsTotal}"
|
|
155652
155932
|
}]
|
|
155653
155933
|
},
|
|
155654
155934
|
{
|
|
@@ -156135,20 +156415,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156135
156415
|
sleeping: this.isBattery && this.sleeping,
|
|
156136
156416
|
now: Date.now()
|
|
156137
156417
|
}),
|
|
156138
|
-
{
|
|
156139
|
-
id: "privacy-mask",
|
|
156140
|
-
tab: "image",
|
|
156141
|
-
title: "Privacy mask",
|
|
156142
|
-
description: "Master enable for the privacy zones configured on the camera (set up via the Reolink mobile app + camera UI). Pushed via `SetMask` (cmd_id 53). Disabling shows the full frame; enabling re-applies the persisted zones.",
|
|
156143
|
-
columns: 1,
|
|
156144
|
-
fields: [{
|
|
156145
|
-
type: "boolean",
|
|
156146
|
-
key: "privacyMaskEnabled",
|
|
156147
|
-
label: "Privacy mask enabled",
|
|
156148
|
-
default: cache?.maskSnapshot?.enabled ?? false,
|
|
156149
|
-
style: "switch"
|
|
156150
|
-
}]
|
|
156151
|
-
},
|
|
156152
156418
|
...cache?.autoFocusSnapshot?.supported === true ? [{
|
|
156153
156419
|
id: "auto-focus",
|
|
156154
156420
|
tab: "image",
|
|
@@ -156503,7 +156769,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156503
156769
|
audioVolume: this.config.get("audioVolume") ?? 50,
|
|
156504
156770
|
audioTalkAndReplyVolume: this.config.get("audioTalkAndReplyVolume") ?? 50,
|
|
156505
156771
|
audioVisitorVolume: this.config.get("audioVisitorVolume") ?? 50,
|
|
156506
|
-
privacyMaskEnabled: this.config.get("privacyMaskEnabled") ?? cache?.maskSnapshot?.enabled ?? false,
|
|
156507
156772
|
audioNoiseLevel: this.config.get("audioNoiseLevel") ?? (cache?.audioNoiseSnapshot?.enabled ? cache.audioNoiseSnapshot?.level ?? 1 : 0),
|
|
156508
156773
|
autoFocusEnabled: this.config.get("autoFocusEnabled") ?? cache?.autoFocusSnapshot?.enabled ?? true,
|
|
156509
156774
|
pirReduceFalseAlarm: this.config.get("pirReduceFalseAlarm") ?? cache?.pirSnapshot?.reduceFalseAlarm ?? false,
|
|
@@ -156519,6 +156784,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156519
156784
|
}
|
|
156520
156785
|
async applySettingsPatch(patch) {
|
|
156521
156786
|
const { emailSmtpServer, emailSmtpPort, emailUserName, emailPassword, emailSsl, emailRecipient1, emailRecipient2, emailRecipient3, emailSendNickname, emailScheduleEnabled, osdChannelEnabled, osdChannelName, osdChannelPosition, osdTimeEnabled, osdTimePosition, osdWatermark, ...rest } = patch;
|
|
156787
|
+
if ((osdChannelEnabled !== void 0 || osdChannelName !== void 0 || osdChannelPosition !== void 0 || osdTimeEnabled !== void 0 || osdTimePosition !== void 0 || osdWatermark !== void 0 || emailSmtpServer !== void 0 || emailRecipient1 !== void 0) && !await this.wakeIfSleeping()) this.ctx.logger.warn("settings write: battery cam did not wake — writing anyway", { tags: { deviceId: this.id } });
|
|
156522
156788
|
const emailFields = {
|
|
156523
156789
|
emailSmtpServer,
|
|
156524
156790
|
emailSmtpPort,
|
|
@@ -156829,13 +157095,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156829
157095
|
this.ctx.logger.warn("reolink setNtp push failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
156830
157096
|
}
|
|
156831
157097
|
}
|
|
156832
|
-
if ("privacyMaskEnabled" in patch) try {
|
|
156833
|
-
const api = await this.ensureApi();
|
|
156834
|
-
const v = typedPatch.privacyMaskEnabled;
|
|
156835
|
-
if (typeof v === "boolean") await api.setMask(this.getChannel(), { enable: v });
|
|
156836
|
-
} catch (err) {
|
|
156837
|
-
this.ctx.logger.warn("mask push failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
156838
|
-
}
|
|
156839
157098
|
if ("audioNoiseLevel" in patch) try {
|
|
156840
157099
|
const api = await this.ensureApi();
|
|
156841
157100
|
const v = typedPatch.audioNoiseLevel;
|
|
@@ -156893,15 +157152,40 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156893
157152
|
}
|
|
156894
157153
|
}
|
|
156895
157154
|
const changedSlices = slicesForPatch(patch);
|
|
156896
|
-
if (changedSlices.size > 0)
|
|
156897
|
-
this.
|
|
156898
|
-
|
|
156899
|
-
|
|
156900
|
-
|
|
156901
|
-
|
|
156902
|
-
|
|
157155
|
+
if (changedSlices.size > 0) {
|
|
157156
|
+
await this.refreshParentSettingsSnapshot(changedSlices, "post-write").catch((err) => {
|
|
157157
|
+
this.ctx.logger.info("reolink targeted settings refresh failed — snapshots left stale", {
|
|
157158
|
+
tags: { deviceId: this.id },
|
|
157159
|
+
meta: {
|
|
157160
|
+
slices: [...changedSlices],
|
|
157161
|
+
error: err instanceof Error ? err.message : String(err)
|
|
157162
|
+
}
|
|
157163
|
+
});
|
|
156903
157164
|
});
|
|
156904
|
-
|
|
157165
|
+
this.assertOsdWriteLanded(patch);
|
|
157166
|
+
}
|
|
157167
|
+
}
|
|
157168
|
+
/**
|
|
157169
|
+
* Compare what the operator asked for with what the camera now reports, and
|
|
157170
|
+
* REFUSE to call a lost write a success.
|
|
157171
|
+
*
|
|
157172
|
+
* Nothing used to do this, so an applied write and a write the camera never
|
|
157173
|
+
* took were indistinguishable — to the code and to the operator, who was
|
|
157174
|
+
* told `{success: true}` for a change the camera discarded (3825,
|
|
157175
|
+
* 2026-09-19). Throwing is the right shape: the admin UI's in-flight
|
|
157176
|
+
* overlay hands the operator their own edit back, marked unsaved, instead
|
|
157177
|
+
* of quietly reverting the field to the old value.
|
|
157178
|
+
*
|
|
157179
|
+
* Silence is the pass. A slice we could not re-read at all is already
|
|
157180
|
+
* reported by the caller as "snapshots left stale" — absence of a reading
|
|
157181
|
+
* is not evidence of a lost write, and must never be treated as one (D393).
|
|
157182
|
+
*/
|
|
157183
|
+
assertOsdWriteLanded(patch) {
|
|
157184
|
+
const asked = patch.osdTimePosition;
|
|
157185
|
+
if (typeof asked !== "string") return;
|
|
157186
|
+
const got = resolveOsdValues(this.config.get("deviceCache")?.osdSnapshot).osdTimePosition;
|
|
157187
|
+
if (typeof got !== "string" || got === asked) return;
|
|
157188
|
+
throw new Error(`the camera did not take the timestamp position: asked "${asked}", it reports "${got}"`);
|
|
156905
157189
|
}
|
|
156906
157190
|
/**
|
|
156907
157191
|
* Optionally inject an already-logged-in Baichuan api — used by
|