@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.js
CHANGED
|
@@ -5386,7 +5386,7 @@ var ZodIssueCode = {
|
|
|
5386
5386
|
var ZodFirstPartyTypeKind;
|
|
5387
5387
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5388
5388
|
//#endregion
|
|
5389
|
-
//#region ../types/dist/sleep-
|
|
5389
|
+
//#region ../types/dist/sleep-CiR4z7r9.mjs
|
|
5390
5390
|
/**
|
|
5391
5391
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5392
5392
|
* window to float samples (D455).
|
|
@@ -6528,6 +6528,17 @@ var CamProfileSchema = _enum([
|
|
|
6528
6528
|
"mid",
|
|
6529
6529
|
"low"
|
|
6530
6530
|
]);
|
|
6531
|
+
/**
|
|
6532
|
+
* Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
|
|
6533
|
+
*
|
|
6534
|
+
* `auto` is a REAL value, not an absent one — it means "resolve it by the rule
|
|
6535
|
+
* that consumer owns". It lives beside `CamProfile` because a rung is the only
|
|
6536
|
+
* operator-facing way to name a stream: a cam-stream id is broker-internal and
|
|
6537
|
+
* moves when a slot is reassigned, so a stored id silently stops meaning what
|
|
6538
|
+
* the operator picked. Today's one user is the audio analyser, whose rule is
|
|
6539
|
+
* "the lowest AVAILABLE native rung" (D537).
|
|
6540
|
+
*/
|
|
6541
|
+
var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
|
|
6531
6542
|
var CamStreamKindSchema = _enum([
|
|
6532
6543
|
"pull-rtsp",
|
|
6533
6544
|
"pull-rtmp",
|
|
@@ -7794,6 +7805,7 @@ var CameraSwitchIdSchema = _enum([
|
|
|
7794
7805
|
"device-audio",
|
|
7795
7806
|
"broker-audio",
|
|
7796
7807
|
"audio-analysis",
|
|
7808
|
+
"event-saving",
|
|
7797
7809
|
"recording",
|
|
7798
7810
|
"notifications"
|
|
7799
7811
|
]);
|
|
@@ -8268,11 +8280,31 @@ var RecordingBandTriggersSchema = RecordingTriggersSchema;
|
|
|
8268
8280
|
* band engine's `applies` rule). `start`/`end` are `HH:MM`; an `end <= start`
|
|
8269
8281
|
* span wraps past midnight (handled by the band engine).
|
|
8270
8282
|
*/
|
|
8283
|
+
/**
|
|
8284
|
+
* Whether a band's footage carries sound.
|
|
8285
|
+
*
|
|
8286
|
+
* The recorder has always attached with `audio: 'aac'`, so every band of every
|
|
8287
|
+
* camera recorded sound whether anyone wanted it or not. The broker already
|
|
8288
|
+
* serves the other answer — `audio: 'none'` takes the video-only `/muted`
|
|
8289
|
+
* restream — so this was a choice the product could always have offered.
|
|
8290
|
+
*
|
|
8291
|
+
* It belongs on the BAND rather than the camera because one camera can want
|
|
8292
|
+
* both: silent across the shop floor during opening hours, sound overnight.
|
|
8293
|
+
* And it applies to BOTH modes — an events band firing on motion has the same
|
|
8294
|
+
* question to answer as a continuous one.
|
|
8295
|
+
*/
|
|
8296
|
+
var RecordingBandAudioSchema = _enum(["on", "muted"]);
|
|
8271
8297
|
var RecordingBandSchema = object({
|
|
8272
8298
|
days: array(RecordingWeekdaySchema),
|
|
8273
8299
|
start: string().regex(HHMM),
|
|
8274
8300
|
end: string().regex(HHMM),
|
|
8275
8301
|
mode: RecordingBandModeSchema,
|
|
8302
|
+
/**
|
|
8303
|
+
* Sound, or silence. OPTIONAL and absent-means-`on`: every recording
|
|
8304
|
+
* written before this existed carried sound, and a band whose key is
|
|
8305
|
+
* missing must keep meaning what it meant. See {@link RecordingBandAudioSchema}.
|
|
8306
|
+
*/
|
|
8307
|
+
audio: RecordingBandAudioSchema.optional(),
|
|
8276
8308
|
triggers: RecordingBandTriggersSchema.optional(),
|
|
8277
8309
|
/**
|
|
8278
8310
|
* RETIRED (D381). A band no longer carries a pre-buffer of its own.
|
|
@@ -20894,6 +20926,7 @@ var MediaFileKindEnum = _enum([
|
|
|
20894
20926
|
"keyFrameSmall",
|
|
20895
20927
|
"thumbnailSmall",
|
|
20896
20928
|
"confirmationCrop",
|
|
20929
|
+
"debugCrop",
|
|
20897
20930
|
"sceneFrame"
|
|
20898
20931
|
]);
|
|
20899
20932
|
/**
|
|
@@ -22558,6 +22591,18 @@ var RunnerCameraConfigSchema = object({
|
|
|
22558
22591
|
motionStreamId: string(),
|
|
22559
22592
|
detectionStreamId: string(),
|
|
22560
22593
|
/**
|
|
22594
|
+
* Which rung this camera's audio analysis listens on — `auto` (the lowest
|
|
22595
|
+
* AVAILABLE native rung) or an explicit one.
|
|
22596
|
+
*
|
|
22597
|
+
* Not consumed by the runner: carried here so it travels with the camera's
|
|
22598
|
+
* config the way `audioMotionWindowMs` does, and so the orchestrator's
|
|
22599
|
+
* watchdog resolves the same stream the audio subscription did. OPTIONAL
|
|
22600
|
+
* with no default — absent means `auto`, which is what every camera written
|
|
22601
|
+
* before this field had, and an absent optional adds nothing to the
|
|
22602
|
+
* generated cap-input defaults (D537).
|
|
22603
|
+
*/
|
|
22604
|
+
audioStreamPick: AudioStreamPickSchema.optional(),
|
|
22605
|
+
/**
|
|
22561
22606
|
* Motion sources that can activate the pipeline. At least one must
|
|
22562
22607
|
* be present. Multiple sources OR-ed: any one firing arms the
|
|
22563
22608
|
* cooldown timer and transitions watching → active. The same
|
|
@@ -25153,6 +25198,104 @@ var WebrtcStreamChoiceSchema = object({
|
|
|
25153
25198
|
/** Bitrate in kbps (null for adaptive). */
|
|
25154
25199
|
bitrateKbps: number().nullable()
|
|
25155
25200
|
});
|
|
25201
|
+
/**
|
|
25202
|
+
* The last move the adaptive ladder made on this session, and the numbers it
|
|
25203
|
+
* rested on. Mirrors `LadderDecision` in the stream-broker's
|
|
25204
|
+
* `adaptive-controller.ts` — the log line's evidence, made readable.
|
|
25205
|
+
*
|
|
25206
|
+
* Nullability is load-bearing: `lossRatio: null` means the loss tracker had
|
|
25207
|
+
* no window to report, which is NOT a link with zero loss.
|
|
25208
|
+
*/
|
|
25209
|
+
var WebrtcLadderDecisionSchema = object({
|
|
25210
|
+
/** `loss` — RTCP / client-reported packet loss; `stall` — the picture
|
|
25211
|
+
* stopped moving; `healthy` — a promotion. */
|
|
25212
|
+
cause: _enum([
|
|
25213
|
+
"loss",
|
|
25214
|
+
"stall",
|
|
25215
|
+
"healthy"
|
|
25216
|
+
]),
|
|
25217
|
+
/** The rung the ladder moved TO. Null for a move that changed no rung
|
|
25218
|
+
* (an MTU reduction, a forced transcode-down at the bottom). */
|
|
25219
|
+
tier: CamProfileSchema.nullable(),
|
|
25220
|
+
/** Whether that move forced the egress transcode-down rung. */
|
|
25221
|
+
forcedTranscodeDown: boolean(),
|
|
25222
|
+
/** Loss ratio over the tracker's window at the moment of the decision. */
|
|
25223
|
+
lossRatio: number().nullable(),
|
|
25224
|
+
/** The client's reported downlink at that moment, in kbps. */
|
|
25225
|
+
clientKbps: number().nullable(),
|
|
25226
|
+
/** How long the stall detector had been seeing nothing arrive. */
|
|
25227
|
+
stalledForMs: number().nullable(),
|
|
25228
|
+
/** Whether the broker WAS sending while that was measured — the half that
|
|
25229
|
+
* separates "the camera stopped" from "the link stopped". */
|
|
25230
|
+
serverSending: boolean().nullable(),
|
|
25231
|
+
/** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
|
|
25232
|
+
* must not read like one from this second. */
|
|
25233
|
+
at: number()
|
|
25234
|
+
});
|
|
25235
|
+
/** Per-tier count of MEASURED downgrades this viewer session has suffered. */
|
|
25236
|
+
var WebrtcTierFailureSchema = object({
|
|
25237
|
+
tier: CamProfileSchema,
|
|
25238
|
+
failures: number().int().nonnegative()
|
|
25239
|
+
});
|
|
25240
|
+
/**
|
|
25241
|
+
* What the SERVER decided for one WebRTC session — the half the client cannot
|
|
25242
|
+
* measure. See `getSessionDebug`.
|
|
25243
|
+
*/
|
|
25244
|
+
var WebrtcSessionDebugSchema = object({
|
|
25245
|
+
sessionId: string(),
|
|
25246
|
+
/** Whether the session still exists here. Everything below is null when
|
|
25247
|
+
* it does not — a dead session has no measurements, not zero ones. */
|
|
25248
|
+
alive: boolean(),
|
|
25249
|
+
/** Epoch ms this snapshot was taken, so the overlay can show its age. */
|
|
25250
|
+
observedAt: number(),
|
|
25251
|
+
/** The broker actually feeding this session (`<deviceId>/<camStreamId>`). */
|
|
25252
|
+
brokerId: string().nullable(),
|
|
25253
|
+
camStreamId: string().nullable(),
|
|
25254
|
+
/** `derived` = this hub BUILT the rung being served (a transcode/scale of
|
|
25255
|
+
* another stream); `native` = the camera's own encode. */
|
|
25256
|
+
sourceKind: _enum([
|
|
25257
|
+
"native",
|
|
25258
|
+
"derived",
|
|
25259
|
+
"unknown"
|
|
25260
|
+
]),
|
|
25261
|
+
/** Whether the ladder is running at all (an `adaptive` target). */
|
|
25262
|
+
adaptive: boolean(),
|
|
25263
|
+
/** The rung `auto` ACTUALLY resolved to right now. Null when the broker
|
|
25264
|
+
* is bound to no profile slot (a manually-activated stream). */
|
|
25265
|
+
servedTier: CamProfileSchema.nullable(),
|
|
25266
|
+
/** Which node ingests the source. Null when unresolved. */
|
|
25267
|
+
ingestNodeId: string().nullable(),
|
|
25268
|
+
/** Whether that node is a remote one (the media crosses a node boundary
|
|
25269
|
+
* before it reaches this session). Null when unresolved. */
|
|
25270
|
+
ingestIsRemote: boolean().nullable(),
|
|
25271
|
+
/** Codec off the source, and the one negotiated with this client. */
|
|
25272
|
+
sourceCodec: string().nullable(),
|
|
25273
|
+
negotiatedCodec: string().nullable(),
|
|
25274
|
+
/** Whether the EGRESS leg re-encodes for this client or forwards the
|
|
25275
|
+
* source bytes untouched. */
|
|
25276
|
+
egressLeg: _enum([
|
|
25277
|
+
"copy",
|
|
25278
|
+
"transcode",
|
|
25279
|
+
"unknown"
|
|
25280
|
+
]),
|
|
25281
|
+
/** What the broker itself reports about the rung it is serving. */
|
|
25282
|
+
brokerStatus: string().nullable(),
|
|
25283
|
+
brokerCodec: string().nullable(),
|
|
25284
|
+
brokerBitrateKbps: number().nullable(),
|
|
25285
|
+
brokerInputFps: number().nullable(),
|
|
25286
|
+
brokerUptimeMs: number().nullable(),
|
|
25287
|
+
/** RTP packets this session has sent — the server's own "am I sending?". */
|
|
25288
|
+
rtpPacketsSent: number().nullable(),
|
|
25289
|
+
/** D42: the track may be carrying the recorded feeder, not live. The ladder
|
|
25290
|
+
* is suspended there, so a frozen picture in `recorded` mode means
|
|
25291
|
+
* something entirely different. */
|
|
25292
|
+
trackMode: _enum(["live", "recorded"]).nullable(),
|
|
25293
|
+
lastDecision: WebrtcLadderDecisionSchema.nullable(),
|
|
25294
|
+
tierFailures: array(WebrtcTierFailureSchema),
|
|
25295
|
+
/** Successful promotions in this viewer session (drives the progressive
|
|
25296
|
+
* healthy timer). Null when no ladder is running. */
|
|
25297
|
+
upgradeCount: number().nullable()
|
|
25298
|
+
});
|
|
25156
25299
|
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(WebrtcStreamChoiceSchema)), method(object({
|
|
25157
25300
|
deviceId: number().int().nonnegative(),
|
|
25158
25301
|
target: WebrtcStreamTargetSchema,
|
|
@@ -25317,7 +25460,10 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
|
|
|
25317
25460
|
* the same answer as "it ended": either way nothing is holding it up.
|
|
25318
25461
|
*/
|
|
25319
25462
|
alive: boolean()
|
|
25320
|
-
}))
|
|
25463
|
+
})), method(object({
|
|
25464
|
+
deviceId: number().int().nonnegative(),
|
|
25465
|
+
sessionId: string()
|
|
25466
|
+
}), WebrtcSessionDebugSchema, { kind: "query" });
|
|
25321
25467
|
object({
|
|
25322
25468
|
/** All accessory children of the parent. */
|
|
25323
25469
|
childDeviceIds: array(number()).readonly(),
|
|
@@ -29716,16 +29862,37 @@ var GridProfileOfferSchema = object({
|
|
|
29716
29862
|
]),
|
|
29717
29863
|
offered: boolean(),
|
|
29718
29864
|
/** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
|
|
29719
|
-
missingSources: array(number().int().positive())
|
|
29865
|
+
missingSources: array(number().int().positive()),
|
|
29866
|
+
/**
|
|
29867
|
+
* The canvas this profile composes onto, `WxH`, or empty when it is not
|
|
29868
|
+
* offered. DERIVED from the cells and the sources' own size at this profile —
|
|
29869
|
+
* it is reported because nothing else in the system would ever say what the
|
|
29870
|
+
* grid came out as, and because it is the number an operator would otherwise
|
|
29871
|
+
* expect to type.
|
|
29872
|
+
*/
|
|
29873
|
+
canvas: string(),
|
|
29874
|
+
/**
|
|
29875
|
+
* Whether this profile is PUBLISHED, of the ones the grid could serve.
|
|
29876
|
+
*
|
|
29877
|
+
* A grid's `high` is composed of its sources' `high`, so on a 4K fleet it is
|
|
29878
|
+
* a 4K canvas built from 4K decodes — something to opt into, not something a
|
|
29879
|
+
* viewer's adaptive should be handed by climbing to the top rung it can see.
|
|
29880
|
+
* Default is `mid` + `low`.
|
|
29881
|
+
*/
|
|
29882
|
+
published: boolean()
|
|
29720
29883
|
});
|
|
29721
29884
|
var GridLayoutViewSchema = object({
|
|
29722
29885
|
/** The persisted grid row this camera was declared from. */
|
|
29723
29886
|
instanceId: string(),
|
|
29724
29887
|
deviceId: number().int().nonnegative(),
|
|
29725
29888
|
name: string(),
|
|
29726
|
-
/**
|
|
29727
|
-
|
|
29728
|
-
|
|
29889
|
+
/**
|
|
29890
|
+
* NO canvas size. A grid's resolution is not authored: each profile derives
|
|
29891
|
+
* its own from the cells and its sources' dimensions. The two numbers that
|
|
29892
|
+
* used to be here were a text field that silently decided both how much the
|
|
29893
|
+
* composite cost and how sharp it was — see `profiles[].canvas` for what it
|
|
29894
|
+
* came out as.
|
|
29895
|
+
*/
|
|
29729
29896
|
fps: number().int(),
|
|
29730
29897
|
cells: array(GridLayoutCellSchema),
|
|
29731
29898
|
/** What the catalog will publish, and what it refuses to. Read-only. */
|
|
@@ -29734,9 +29901,13 @@ var GridLayoutViewSchema = object({
|
|
|
29734
29901
|
var GridLayoutPatchSchema = object({
|
|
29735
29902
|
deviceId: number().int().nonnegative(),
|
|
29736
29903
|
name: string().min(1).max(160).optional(),
|
|
29737
|
-
width: number().int().min(160).max(7680).optional(),
|
|
29738
|
-
height: number().int().min(120).max(4320).optional(),
|
|
29739
29904
|
fps: number().int().min(1).max(60).optional(),
|
|
29905
|
+
/** Which profiles to publish. See `GridProfileOffer.published`. */
|
|
29906
|
+
publishedProfiles: array(_enum([
|
|
29907
|
+
"high",
|
|
29908
|
+
"mid",
|
|
29909
|
+
"low"
|
|
29910
|
+
])).max(3).optional(),
|
|
29740
29911
|
/**
|
|
29741
29912
|
* The whole cell list at once. A per-cell patch would need an ordering the
|
|
29742
29913
|
* editor does not have, and a half-applied layout is a picture nobody asked
|
|
@@ -32151,6 +32322,25 @@ var ptzAutotrackCapability = {
|
|
|
32151
32322
|
scope: "device",
|
|
32152
32323
|
deviceNative: true,
|
|
32153
32324
|
mode: "singleton",
|
|
32325
|
+
/**
|
|
32326
|
+
* Two kinds of provider behind one name, the same shape `snapshot` and
|
|
32327
|
+
* `doorbell` use:
|
|
32328
|
+
*
|
|
32329
|
+
* - **Native**: the camera's own firmware autotrack, registered per device by
|
|
32330
|
+
* the driver when the probe says it exists (Reolink: yes; Hikvision:
|
|
32331
|
+
* model-dependent).
|
|
32332
|
+
* - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
|
|
32333
|
+
* which follows a subject with `ptz.move` pulses driven by the detection
|
|
32334
|
+
* pipeline. `defaultActive: false` — the operator binds it per camera in
|
|
32335
|
+
* the bindings UI, exactly as `virtual-doorbell` is bound.
|
|
32336
|
+
*
|
|
32337
|
+
* The binding chooses WHICH implementation answers; `setEnabled` stays the
|
|
32338
|
+
* one switch for the function either way. A camera never has both driving its
|
|
32339
|
+
* head, which is the whole reason this is not a second cap with a second
|
|
32340
|
+
* toggle (D62, D520).
|
|
32341
|
+
*/
|
|
32342
|
+
kind: "wrapper",
|
|
32343
|
+
defaultActive: false,
|
|
32154
32344
|
deviceTypes: [DeviceType.Camera],
|
|
32155
32345
|
deviceConfig: { ui: {
|
|
32156
32346
|
kind: "widget",
|
|
@@ -43522,6 +43712,12 @@ Object.freeze({
|
|
|
43522
43712
|
addonId: null,
|
|
43523
43713
|
access: "view"
|
|
43524
43714
|
},
|
|
43715
|
+
"webrtcSession.getSessionDebug": {
|
|
43716
|
+
capName: "webrtc-session",
|
|
43717
|
+
capScope: "device",
|
|
43718
|
+
addonId: null,
|
|
43719
|
+
access: "view"
|
|
43720
|
+
},
|
|
43525
43721
|
"webrtcSession.getSessionState": {
|
|
43526
43722
|
capName: "webrtc-session",
|
|
43527
43723
|
capScope: "device",
|
|
@@ -45505,6 +45701,11 @@ Object.freeze({
|
|
|
45505
45701
|
form: "single",
|
|
45506
45702
|
optional: false
|
|
45507
45703
|
}],
|
|
45704
|
+
"webrtcSession.getSessionDebug": [{
|
|
45705
|
+
name: "deviceId",
|
|
45706
|
+
form: "single",
|
|
45707
|
+
optional: false
|
|
45708
|
+
}],
|
|
45508
45709
|
"webrtcSession.getSessionState": [{
|
|
45509
45710
|
name: "deviceId",
|
|
45510
45711
|
form: "single",
|
|
@@ -75190,7 +75391,7 @@ var require_lz4 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports) => {
|
|
|
75190
75391
|
};
|
|
75191
75392
|
}));
|
|
75192
75393
|
//#endregion
|
|
75193
|
-
//#region node_modules/@apocaliss92/nodelink-js/dist/chunk-
|
|
75394
|
+
//#region node_modules/@apocaliss92/nodelink-js/dist/chunk-7JMYBUEB.js
|
|
75194
75395
|
var import_fxp = require_fxp();
|
|
75195
75396
|
var import_lz4 = /* @__PURE__ */ require_chunk.__toESM(require_lz4(), 1);
|
|
75196
75397
|
function encodeHeader(h) {
|
|
@@ -83650,10 +83851,14 @@ function applyOsdDatetimePatch(rawXml, patch) {
|
|
|
83650
83851
|
return xml;
|
|
83651
83852
|
}
|
|
83652
83853
|
var OSD_POSITION_MAX = 65536;
|
|
83854
|
+
var OSD_POSITION_START_WRITE = 1;
|
|
83855
|
+
var OSD_POSITION_CENTER = 65537;
|
|
83653
83856
|
var OSD_CORNERS = [
|
|
83654
83857
|
"top-left",
|
|
83858
|
+
"top-center",
|
|
83655
83859
|
"top-right",
|
|
83656
83860
|
"bottom-left",
|
|
83861
|
+
"bottom-center",
|
|
83657
83862
|
"bottom-right"
|
|
83658
83863
|
];
|
|
83659
83864
|
function isStartEdge(value) {
|
|
@@ -83665,7 +83870,7 @@ function isEndEdge(value) {
|
|
|
83665
83870
|
function readOsdPosition(x, y) {
|
|
83666
83871
|
if (typeof x !== "number" || typeof y !== "number") return { kind: "unknown" };
|
|
83667
83872
|
if (!Number.isFinite(x) || !Number.isFinite(y)) return { kind: "unknown" };
|
|
83668
|
-
const horizontal = isStartEdge(x) ? "left" : isEndEdge(x) ? "right" : null;
|
|
83873
|
+
const horizontal = x === OSD_POSITION_CENTER ? "center" : isStartEdge(x) ? "left" : isEndEdge(x) ? "right" : null;
|
|
83669
83874
|
const vertical = isStartEdge(y) ? "top" : isEndEdge(y) ? "bottom" : null;
|
|
83670
83875
|
if (horizontal === null || vertical === null) return {
|
|
83671
83876
|
kind: "custom",
|
|
@@ -83679,8 +83884,8 @@ function readOsdPosition(x, y) {
|
|
|
83679
83884
|
}
|
|
83680
83885
|
function coordsForOsdCorner(corner) {
|
|
83681
83886
|
return {
|
|
83682
|
-
x: corner.endsWith("-right") ? OSD_POSITION_MAX :
|
|
83683
|
-
y: corner.startsWith("bottom-") ? OSD_POSITION_MAX :
|
|
83887
|
+
x: corner.endsWith("-center") ? OSD_POSITION_CENTER : corner.endsWith("-right") ? OSD_POSITION_MAX : OSD_POSITION_START_WRITE,
|
|
83888
|
+
y: corner.startsWith("bottom-") ? OSD_POSITION_MAX : OSD_POSITION_START_WRITE
|
|
83684
83889
|
};
|
|
83685
83890
|
}
|
|
83686
83891
|
function isOsdCorner(value) {
|
|
@@ -83691,8 +83896,10 @@ function formatOsdCoords(x, y) {
|
|
|
83691
83896
|
}
|
|
83692
83897
|
var OSD_CORNER_LABELS = {
|
|
83693
83898
|
"top-left": "Top left",
|
|
83899
|
+
"top-center": "Top centre",
|
|
83694
83900
|
"top-right": "Top right",
|
|
83695
83901
|
"bottom-left": "Bottom left",
|
|
83902
|
+
"bottom-center": "Bottom centre",
|
|
83696
83903
|
"bottom-right": "Bottom right"
|
|
83697
83904
|
};
|
|
83698
83905
|
function wireFloat(value) {
|
|
@@ -88219,8 +88426,12 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
88219
88426
|
/** Generic Baichuan cmd_id call, returns XML (if any). */
|
|
88220
88427
|
async sendXml(params, retry = 3) {
|
|
88221
88428
|
if (!this.client.loggedIn) await this.client.login();
|
|
88222
|
-
const
|
|
88223
|
-
|
|
88429
|
+
const addressed = params.channel != null && params.extensionXml == null ? {
|
|
88430
|
+
...params,
|
|
88431
|
+
extensionXml: buildChannelExtensionXml(params.channel)
|
|
88432
|
+
} : params;
|
|
88433
|
+
const frame = await this.client.sendFrame(addressed);
|
|
88434
|
+
if (frame.header.responseCode === 400) return await this.handleSendXml400(addressed, frame, retry);
|
|
88224
88435
|
if (frame.body.length === 0) return "";
|
|
88225
88436
|
return this.client.tryDecryptXml(frame.body, frame.header.channelId, this.client.enc);
|
|
88226
88437
|
}
|
|
@@ -94521,6 +94732,7 @@ ${xml}`);
|
|
|
94521
94732
|
await this.sendXml({
|
|
94522
94733
|
cmdId: 45,
|
|
94523
94734
|
channel: ch,
|
|
94735
|
+
extensionXml: buildChannelExtensionXml(ch),
|
|
94524
94736
|
payloadXml: ensureXmlHeader(xml),
|
|
94525
94737
|
...timeoutOpts
|
|
94526
94738
|
});
|
|
@@ -145555,6 +145767,7 @@ var RAW_READ_CATALOG = {
|
|
|
145555
145767
|
kind: "deviceCache",
|
|
145556
145768
|
snapshotKey: "autoFocusSnapshot"
|
|
145557
145769
|
},
|
|
145770
|
+
needs: "ptz",
|
|
145558
145771
|
invoke: (api, channel) => api.getAutoFocus(channel, { timeoutMs: 1500 })
|
|
145559
145772
|
},
|
|
145560
145773
|
netPort: {
|
|
@@ -145751,6 +145964,7 @@ var RawReadResultSchema = discriminatedUnion("ok", [object({
|
|
|
145751
145964
|
slice: RawReadSliceSchema,
|
|
145752
145965
|
reason: _enum([
|
|
145753
145966
|
"sleeping",
|
|
145967
|
+
"unsupported",
|
|
145754
145968
|
"login-failed",
|
|
145755
145969
|
"read-failed"
|
|
145756
145970
|
]),
|
|
@@ -145794,6 +146008,20 @@ function toPlainJson(value) {
|
|
|
145794
146008
|
async function performRawRead(slice, deps) {
|
|
145795
146009
|
const entry = RAW_READ_CATALOG[slice];
|
|
145796
146010
|
const believed = believedState(deps, entry);
|
|
146011
|
+
if (entry.needs === "ptz" && !deps.hasPtz) {
|
|
146012
|
+
deps.logger.info("reolink raw read refused — the camera has no PTZ head", {
|
|
146013
|
+
tags: { deviceId: deps.deviceId },
|
|
146014
|
+
meta: { slice }
|
|
146015
|
+
});
|
|
146016
|
+
return {
|
|
146017
|
+
ok: false,
|
|
146018
|
+
deviceId: deps.deviceId,
|
|
146019
|
+
slice,
|
|
146020
|
+
reason: "unsupported",
|
|
146021
|
+
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.`,
|
|
146022
|
+
believed
|
|
146023
|
+
};
|
|
146024
|
+
}
|
|
145797
146025
|
const servedWithoutTouchingCamera = deps.hubChild && entry.servedByParentHub === true;
|
|
145798
146026
|
if (deps.sleeping && !servedWithoutTouchingCamera) {
|
|
145799
146027
|
deps.logger.info("reolink raw read refused — battery cam is sleeping", {
|
|
@@ -149457,7 +149685,6 @@ var reolinkCameraSchema = object({
|
|
|
149457
149685
|
* read via 52). Toggles the `<Shelter><enable>` flag. Zone
|
|
149458
149686
|
* editing is a separate flow not exposed by this driver.
|
|
149459
149687
|
*/
|
|
149460
|
-
privacyMaskEnabled: boolean().optional(),
|
|
149461
149688
|
/**
|
|
149462
149689
|
* Audio noise reduction level — pushed via `setAudioNoise`
|
|
149463
149690
|
* (cmd_id 440, read via 439). 0 disables; 1..3 maps to
|
|
@@ -150291,7 +150518,6 @@ function slicesForPatch(patch) {
|
|
|
150291
150518
|
if (has("motionEnabled", "motionSensitivity")) out.add("motion");
|
|
150292
150519
|
if (Object.keys(patch).some((k) => /^ai(Person|Vehicle|Animal|Face|Package)/.test(k))) out.add("ai");
|
|
150293
150520
|
if (has("gop")) out.add("enc");
|
|
150294
|
-
if (has("privacyMaskEnabled")) out.add("mask");
|
|
150295
150521
|
if (has("audioNoiseLevel")) out.add("audioNoise");
|
|
150296
150522
|
if (has("autoFocusEnabled")) out.add("autofocus");
|
|
150297
150523
|
if (has("netHttpEnable", "netHttpPort", "netHttpsEnable", "netHttpsPort", "netRtspEnable", "netRtspPort", "netRtmpEnable", "netRtmpPort", "netOnvifEnable", "netOnvifPort")) out.add("netPort");
|
|
@@ -150303,6 +150529,20 @@ function slicesForPatch(patch) {
|
|
|
150303
150529
|
if (has("timeZoneId")) out.add("systemGeneral");
|
|
150304
150530
|
return out;
|
|
150305
150531
|
}
|
|
150532
|
+
/**
|
|
150533
|
+
* What the operator's "Refresh from camera" actually managed.
|
|
150534
|
+
*
|
|
150535
|
+
* Returned rather than logged, because the button is the one place a person
|
|
150536
|
+
* asks "is what I am looking at true right now" — and an answer of `void`
|
|
150537
|
+
* cannot distinguish a full re-read from a camera that never woke.
|
|
150538
|
+
*/
|
|
150539
|
+
var RefreshSettingsResultSchema = object({
|
|
150540
|
+
/** False when a battery camera did not come up — everything below is then stale. */
|
|
150541
|
+
awake: boolean(),
|
|
150542
|
+
snapshotRefreshed: boolean(),
|
|
150543
|
+
capsWarmed: number(),
|
|
150544
|
+
capsTotal: number()
|
|
150545
|
+
});
|
|
150306
150546
|
var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
150307
150547
|
type = DeviceType.Camera;
|
|
150308
150548
|
/**
|
|
@@ -150772,9 +151012,12 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
150772
151012
|
* `setVideoInput`); motion is writable via `setMotionAlarm` so its
|
|
150773
151013
|
* snapshot also drives the dispatch's known-good baseline.
|
|
150774
151014
|
*/
|
|
150775
|
-
async refreshParentSettingsSnapshot(slices) {
|
|
150776
|
-
if (this.isBattery && this.sleeping) {
|
|
150777
|
-
this.ctx.logger.info("refreshParentSettingsSnapshot skipped — battery cam is sleeping", { tags: {
|
|
151015
|
+
async refreshParentSettingsSnapshot(slices, reason = "background") {
|
|
151016
|
+
if (this.isBattery && this.sleeping && reason !== "post-write") {
|
|
151017
|
+
this.ctx.logger.info("refreshParentSettingsSnapshot skipped — battery cam is sleeping", { tags: {
|
|
151018
|
+
deviceId: this.id,
|
|
151019
|
+
reason
|
|
151020
|
+
} });
|
|
150778
151021
|
return;
|
|
150779
151022
|
}
|
|
150780
151023
|
let api;
|
|
@@ -151019,6 +151262,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151019
151262
|
channel: this.getChannel(),
|
|
151020
151263
|
sleeping: this.isBattery && this.sleeping,
|
|
151021
151264
|
hubChild: this.isHubChild(),
|
|
151265
|
+
hasPtz: this.getProbeFlags().hasPtz === true,
|
|
151022
151266
|
getApi: () => this.ensureApi(),
|
|
151023
151267
|
cache: this.config.get("deviceCache"),
|
|
151024
151268
|
batteryState: this.runtimeState.getCapState("battery"),
|
|
@@ -151112,6 +151356,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151112
151356
|
});
|
|
151113
151357
|
await sleep$2(1500);
|
|
151114
151358
|
})(), new Promise((_, rej) => setTimeout(() => rej(/* @__PURE__ */ new Error("wakeIfSleeping timeout")), timeoutMs))]);
|
|
151359
|
+
if (this.commitSleepState(false, "confirmed-wake")) {
|
|
151360
|
+
this.ctx.logger.info("proactive action: camera is up — committing the mirror", { tags: { deviceId: this.id } });
|
|
151361
|
+
this.onWakeTransition("confirmed-wake").catch(() => {});
|
|
151362
|
+
}
|
|
151115
151363
|
return true;
|
|
151116
151364
|
} catch (err) {
|
|
151117
151365
|
this.ctx.logger.warn("proactive action: wake did not complete in time", {
|
|
@@ -151283,8 +151531,26 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151283
151531
|
this.registerDeviceAction("auto-configure-email-push", deviceCustomAction(unknown(), _void(), { kind: "mutation" }), async () => {
|
|
151284
151532
|
await this.autoConfigureEmailPush();
|
|
151285
151533
|
});
|
|
151286
|
-
this.registerDeviceAction("refresh-settings", deviceCustomAction(unknown(),
|
|
151287
|
-
|
|
151534
|
+
this.registerDeviceAction("refresh-settings", deviceCustomAction(unknown(), RefreshSettingsResultSchema, { kind: "mutation" }), async () => {
|
|
151535
|
+
const awake = await this.wakeIfSleeping();
|
|
151536
|
+
const [snapshot, warm] = await Promise.allSettled([this.refreshParentSettingsSnapshot(void 0, "operator"), this.warmCapSlicesOnWake({ force: true })]);
|
|
151537
|
+
const warmed = warm.status === "fulfilled" ? warm.value.warmed : 0;
|
|
151538
|
+
const capsTotal = this.capWarmers.size;
|
|
151539
|
+
this.ctx.logger.info("refresh from camera: operator rehydrate", {
|
|
151540
|
+
tags: { deviceId: this.id },
|
|
151541
|
+
meta: {
|
|
151542
|
+
awake,
|
|
151543
|
+
snapshot: snapshot.status,
|
|
151544
|
+
warmed,
|
|
151545
|
+
capsTotal
|
|
151546
|
+
}
|
|
151547
|
+
});
|
|
151548
|
+
return {
|
|
151549
|
+
awake,
|
|
151550
|
+
snapshotRefreshed: snapshot.status === "fulfilled",
|
|
151551
|
+
capsWarmed: warmed,
|
|
151552
|
+
capsTotal
|
|
151553
|
+
};
|
|
151288
151554
|
});
|
|
151289
151555
|
this.registerDeviceAction("refresh-sessions", deviceCustomAction(unknown(), _void(), { kind: "mutation" }), async () => {
|
|
151290
151556
|
await this.refreshSessionsSnapshot();
|
|
@@ -151324,7 +151590,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151324
151590
|
* registration so the general "Refresh from camera" button can warm
|
|
151325
151591
|
* the `stream-params` runtime-state slice (the editable Stream
|
|
151326
151592
|
* section's current values read from it). */
|
|
151327
|
-
refreshStreamParamsFromCamera = null;
|
|
151328
151593
|
/** Single-flight guard for the motion-zones cap refresh. */
|
|
151329
151594
|
motionZonesRefreshInFlight = null;
|
|
151330
151595
|
/** Single-flight guard for the privacy-mask cap refresh. */
|
|
@@ -151458,6 +151723,16 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151458
151723
|
};
|
|
151459
151724
|
}
|
|
151460
151725
|
if (confirmed && !observedAwake && parent !== null) this.watchHubChildAwake(parent).catch(() => {});
|
|
151726
|
+
if (this.commitSleepState(false, "confirmed-wake")) {
|
|
151727
|
+
this.ctx.logger.info("battery wakeForStream: answering awake — committing the mirror", {
|
|
151728
|
+
tags: { deviceId: this.id },
|
|
151729
|
+
meta: {
|
|
151730
|
+
confirmedMs: Date.now() - startedAt,
|
|
151731
|
+
observedAwake
|
|
151732
|
+
}
|
|
151733
|
+
});
|
|
151734
|
+
this.onWakeTransition("confirmed-wake").catch(() => {});
|
|
151735
|
+
}
|
|
151461
151736
|
return {
|
|
151462
151737
|
awoke: true,
|
|
151463
151738
|
durationMs: Date.now() - startedAt
|
|
@@ -151583,7 +151858,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
151583
151858
|
commitSleepState(next, source) {
|
|
151584
151859
|
if (!this.isBattery) return false;
|
|
151585
151860
|
if (this.sleeping === next) return false;
|
|
151586
|
-
if (source
|
|
151861
|
+
if (source === "confirmed-wake" && next === false) this.sleepStateChangedAt = Date.now();
|
|
151862
|
+
else if (source !== "hub-summary" && !this.acceptSleepingTransition(next)) return false;
|
|
151587
151863
|
if (source === "hub-summary") {
|
|
151588
151864
|
const sinceCommitMs = Date.now() - this.sleepStateChangedAt;
|
|
151589
151865
|
if (next === true && this.sleepStateChangedAt > 0 && sinceCommitMs < ReolinkCamera.HUB_SUMMARY_SLEEP_GRACE_MS) {
|
|
@@ -152719,14 +152995,14 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152719
152995
|
* swallowed: the window is short, partial progress accumulates across
|
|
152720
152996
|
* wakes because the slices persist.
|
|
152721
152997
|
*/
|
|
152722
|
-
async warmCapSlicesOnWake() {
|
|
152998
|
+
async warmCapSlicesOnWake(opts) {
|
|
152723
152999
|
const now = Date.now();
|
|
152724
153000
|
let warmed = 0;
|
|
152725
153001
|
let skipped = 0;
|
|
152726
153002
|
for (const [capName, warm] of this.capWarmers) {
|
|
152727
153003
|
const slice = this.runtimeState.getCapState(capName);
|
|
152728
153004
|
const fetchedAt = typeof slice?.lastFetchedAt === "number" ? slice.lastFetchedAt : 0;
|
|
152729
|
-
if (fetchedAt > 0 && now - fetchedAt < ReolinkCamera.WAKE_WARM_MAX_AGE_MS) {
|
|
153005
|
+
if (opts?.force !== true && fetchedAt > 0 && now - fetchedAt < ReolinkCamera.WAKE_WARM_MAX_AGE_MS) {
|
|
152730
153006
|
skipped += 1;
|
|
152731
153007
|
continue;
|
|
152732
153008
|
}
|
|
@@ -152751,6 +153027,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152751
153027
|
total: this.capWarmers.size
|
|
152752
153028
|
}
|
|
152753
153029
|
});
|
|
153030
|
+
return {
|
|
153031
|
+
warmed,
|
|
153032
|
+
skipped
|
|
153033
|
+
};
|
|
152754
153034
|
}
|
|
152755
153035
|
/**
|
|
152756
153036
|
* Ask the snapshot wrapper for a frame while the camera is up, so its
|
|
@@ -152816,7 +153096,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152816
153096
|
registerStreamParamsCap() {
|
|
152817
153097
|
const channel = this.getChannel();
|
|
152818
153098
|
const CAP_NAME = "stream-params";
|
|
152819
|
-
const STALE_MS = 1e4;
|
|
152820
153099
|
/** Round-trip `getEnc`, map into the cap status schema, write slice. */
|
|
152821
153100
|
const refreshFromCamera = async () => {
|
|
152822
153101
|
if (this.streamParamsRefreshInFlight) return this.streamParamsRefreshInFlight;
|
|
@@ -152849,13 +153128,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152849
153128
|
this.streamParamsRefreshInFlight = null;
|
|
152850
153129
|
}
|
|
152851
153130
|
};
|
|
152852
|
-
this.refreshStreamParamsFromCamera = refreshFromCamera;
|
|
152853
153131
|
const bridge = createRuntimeStateBridge({
|
|
152854
153132
|
runtimeState: this.runtimeState,
|
|
152855
153133
|
cap: streamParamsCapability,
|
|
152856
153134
|
ownDeviceId: this.id,
|
|
152857
153135
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
152858
|
-
staleMs:
|
|
153136
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153137
|
+
staleRead: "serve-and-revalidate",
|
|
152859
153138
|
empty: () => ({ lastFetchedAt: 0 })
|
|
152860
153139
|
});
|
|
152861
153140
|
const resolveOptions = async () => this.resolveCapOptions({
|
|
@@ -152940,7 +153219,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152940
153219
|
registerMotionZonesCap() {
|
|
152941
153220
|
const channel = this.getChannel();
|
|
152942
153221
|
const CAP_NAME = "motion-zones";
|
|
152943
|
-
const STALE_MS = 1e4;
|
|
152944
153222
|
/** Round-trip `getMotionAlarm`, map into the cap status schema, write slice. */
|
|
152945
153223
|
const refreshFromCamera = async () => {
|
|
152946
153224
|
if (this.motionZonesRefreshInFlight) return this.motionZonesRefreshInFlight;
|
|
@@ -152990,7 +153268,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152990
153268
|
cap: motionZonesCapability,
|
|
152991
153269
|
ownDeviceId: this.id,
|
|
152992
153270
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
152993
|
-
staleMs:
|
|
153271
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153272
|
+
staleRead: "serve-and-revalidate",
|
|
152994
153273
|
empty: () => ({
|
|
152995
153274
|
enabled: false,
|
|
152996
153275
|
sensitivity: 1,
|
|
@@ -153321,7 +153600,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153321
153600
|
registerDayNightCap() {
|
|
153322
153601
|
const channel = this.getChannel();
|
|
153323
153602
|
const CAP_NAME = "day-night";
|
|
153324
|
-
const STALE_MS = 1e4;
|
|
153325
153603
|
/** Round-trip `getVideoInput` + `getDayNightThreshold`, map into the
|
|
153326
153604
|
* cap status schema, write slice. */
|
|
153327
153605
|
const refreshFromCamera = async () => {
|
|
@@ -153364,7 +153642,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153364
153642
|
cap: dayNightCapability,
|
|
153365
153643
|
ownDeviceId: this.id,
|
|
153366
153644
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
153367
|
-
staleMs:
|
|
153645
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153646
|
+
staleRead: "serve-and-revalidate",
|
|
153368
153647
|
empty: () => ({
|
|
153369
153648
|
mode: "auto",
|
|
153370
153649
|
lastFetchedAt: 0
|
|
@@ -153457,7 +153736,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153457
153736
|
registerImageSettingsCap() {
|
|
153458
153737
|
const channel = this.getChannel();
|
|
153459
153738
|
const CAP_NAME = "image-settings";
|
|
153460
|
-
const STALE_MS = 1e4;
|
|
153461
153739
|
const SLIDER_MIN = 0;
|
|
153462
153740
|
const SLIDER_MAX = 255;
|
|
153463
153741
|
const NORMALIZED_RANGE = {
|
|
@@ -153501,7 +153779,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153501
153779
|
cap: imageSettingsCapability,
|
|
153502
153780
|
ownDeviceId: this.id,
|
|
153503
153781
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
153504
|
-
staleMs:
|
|
153782
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
153783
|
+
staleRead: "serve-and-revalidate",
|
|
153505
153784
|
empty: () => ({ lastFetchedAt: 0 })
|
|
153506
153785
|
}).getStatus,
|
|
153507
153786
|
getOptions: async ({ deviceId }) => {
|
|
@@ -153818,7 +154097,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153818
154097
|
/** Stale-after window for served-from-cache reads. ~10s is short
|
|
153819
154098
|
* enough that the UI's 5s poll always sees a fresh round-trip,
|
|
153820
154099
|
* long enough that two consumers in the same tick fold into one. */
|
|
153821
|
-
const STALE_MS = 1e4;
|
|
153822
154100
|
const SUPPORTED_TARGET_TYPES = [
|
|
153823
154101
|
{
|
|
153824
154102
|
value: "people",
|
|
@@ -153884,7 +154162,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
153884
154162
|
cap: ptzAutotrackCapability,
|
|
153885
154163
|
ownDeviceId: this.id,
|
|
153886
154164
|
refresh: this.sleepGatedRefresh(CAP_NAME, refreshFromCamera),
|
|
153887
|
-
staleMs:
|
|
154165
|
+
staleMs: OPERATOR_WRITTEN_STALE_MS,
|
|
154166
|
+
staleRead: "serve-and-revalidate",
|
|
153888
154167
|
empty: () => ({
|
|
153889
154168
|
enabled: false,
|
|
153890
154169
|
lastChangedAt: 0,
|
|
@@ -155645,7 +155924,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155645
155924
|
id: "driver-refresh",
|
|
155646
155925
|
tab: "general",
|
|
155647
155926
|
title: "Camera data",
|
|
155648
|
-
description: "
|
|
155927
|
+
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.",
|
|
155649
155928
|
columns: 1,
|
|
155650
155929
|
fields: [{
|
|
155651
155930
|
type: "device-action-button",
|
|
@@ -155653,7 +155932,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155653
155932
|
label: "Refresh from camera",
|
|
155654
155933
|
buttonLabel: "Refresh now",
|
|
155655
155934
|
action: "refresh-settings",
|
|
155656
|
-
variant: "default"
|
|
155935
|
+
variant: "default",
|
|
155936
|
+
successMessage: "Camera awake: {awake} · settings re-read: {snapshotRefreshed} · slices refreshed: {capsWarmed}/{capsTotal}"
|
|
155657
155937
|
}]
|
|
155658
155938
|
},
|
|
155659
155939
|
{
|
|
@@ -156140,20 +156420,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156140
156420
|
sleeping: this.isBattery && this.sleeping,
|
|
156141
156421
|
now: Date.now()
|
|
156142
156422
|
}),
|
|
156143
|
-
{
|
|
156144
|
-
id: "privacy-mask",
|
|
156145
|
-
tab: "image",
|
|
156146
|
-
title: "Privacy mask",
|
|
156147
|
-
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.",
|
|
156148
|
-
columns: 1,
|
|
156149
|
-
fields: [{
|
|
156150
|
-
type: "boolean",
|
|
156151
|
-
key: "privacyMaskEnabled",
|
|
156152
|
-
label: "Privacy mask enabled",
|
|
156153
|
-
default: cache?.maskSnapshot?.enabled ?? false,
|
|
156154
|
-
style: "switch"
|
|
156155
|
-
}]
|
|
156156
|
-
},
|
|
156157
156423
|
...cache?.autoFocusSnapshot?.supported === true ? [{
|
|
156158
156424
|
id: "auto-focus",
|
|
156159
156425
|
tab: "image",
|
|
@@ -156508,7 +156774,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156508
156774
|
audioVolume: this.config.get("audioVolume") ?? 50,
|
|
156509
156775
|
audioTalkAndReplyVolume: this.config.get("audioTalkAndReplyVolume") ?? 50,
|
|
156510
156776
|
audioVisitorVolume: this.config.get("audioVisitorVolume") ?? 50,
|
|
156511
|
-
privacyMaskEnabled: this.config.get("privacyMaskEnabled") ?? cache?.maskSnapshot?.enabled ?? false,
|
|
156512
156777
|
audioNoiseLevel: this.config.get("audioNoiseLevel") ?? (cache?.audioNoiseSnapshot?.enabled ? cache.audioNoiseSnapshot?.level ?? 1 : 0),
|
|
156513
156778
|
autoFocusEnabled: this.config.get("autoFocusEnabled") ?? cache?.autoFocusSnapshot?.enabled ?? true,
|
|
156514
156779
|
pirReduceFalseAlarm: this.config.get("pirReduceFalseAlarm") ?? cache?.pirSnapshot?.reduceFalseAlarm ?? false,
|
|
@@ -156524,6 +156789,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156524
156789
|
}
|
|
156525
156790
|
async applySettingsPatch(patch) {
|
|
156526
156791
|
const { emailSmtpServer, emailSmtpPort, emailUserName, emailPassword, emailSsl, emailRecipient1, emailRecipient2, emailRecipient3, emailSendNickname, emailScheduleEnabled, osdChannelEnabled, osdChannelName, osdChannelPosition, osdTimeEnabled, osdTimePosition, osdWatermark, ...rest } = patch;
|
|
156792
|
+
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 } });
|
|
156527
156793
|
const emailFields = {
|
|
156528
156794
|
emailSmtpServer,
|
|
156529
156795
|
emailSmtpPort,
|
|
@@ -156834,13 +157100,6 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156834
157100
|
this.ctx.logger.warn("reolink setNtp push failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
156835
157101
|
}
|
|
156836
157102
|
}
|
|
156837
|
-
if ("privacyMaskEnabled" in patch) try {
|
|
156838
|
-
const api = await this.ensureApi();
|
|
156839
|
-
const v = typedPatch.privacyMaskEnabled;
|
|
156840
|
-
if (typeof v === "boolean") await api.setMask(this.getChannel(), { enable: v });
|
|
156841
|
-
} catch (err) {
|
|
156842
|
-
this.ctx.logger.warn("mask push failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
156843
|
-
}
|
|
156844
157103
|
if ("audioNoiseLevel" in patch) try {
|
|
156845
157104
|
const api = await this.ensureApi();
|
|
156846
157105
|
const v = typedPatch.audioNoiseLevel;
|
|
@@ -156898,15 +157157,40 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
156898
157157
|
}
|
|
156899
157158
|
}
|
|
156900
157159
|
const changedSlices = slicesForPatch(patch);
|
|
156901
|
-
if (changedSlices.size > 0)
|
|
156902
|
-
this.
|
|
156903
|
-
|
|
156904
|
-
|
|
156905
|
-
|
|
156906
|
-
|
|
156907
|
-
|
|
157160
|
+
if (changedSlices.size > 0) {
|
|
157161
|
+
await this.refreshParentSettingsSnapshot(changedSlices, "post-write").catch((err) => {
|
|
157162
|
+
this.ctx.logger.info("reolink targeted settings refresh failed — snapshots left stale", {
|
|
157163
|
+
tags: { deviceId: this.id },
|
|
157164
|
+
meta: {
|
|
157165
|
+
slices: [...changedSlices],
|
|
157166
|
+
error: err instanceof Error ? err.message : String(err)
|
|
157167
|
+
}
|
|
157168
|
+
});
|
|
156908
157169
|
});
|
|
156909
|
-
|
|
157170
|
+
this.assertOsdWriteLanded(patch);
|
|
157171
|
+
}
|
|
157172
|
+
}
|
|
157173
|
+
/**
|
|
157174
|
+
* Compare what the operator asked for with what the camera now reports, and
|
|
157175
|
+
* REFUSE to call a lost write a success.
|
|
157176
|
+
*
|
|
157177
|
+
* Nothing used to do this, so an applied write and a write the camera never
|
|
157178
|
+
* took were indistinguishable — to the code and to the operator, who was
|
|
157179
|
+
* told `{success: true}` for a change the camera discarded (3825,
|
|
157180
|
+
* 2026-09-19). Throwing is the right shape: the admin UI's in-flight
|
|
157181
|
+
* overlay hands the operator their own edit back, marked unsaved, instead
|
|
157182
|
+
* of quietly reverting the field to the old value.
|
|
157183
|
+
*
|
|
157184
|
+
* Silence is the pass. A slice we could not re-read at all is already
|
|
157185
|
+
* reported by the caller as "snapshots left stale" — absence of a reading
|
|
157186
|
+
* is not evidence of a lost write, and must never be treated as one (D393).
|
|
157187
|
+
*/
|
|
157188
|
+
assertOsdWriteLanded(patch) {
|
|
157189
|
+
const asked = patch.osdTimePosition;
|
|
157190
|
+
if (typeof asked !== "string") return;
|
|
157191
|
+
const got = resolveOsdValues(this.config.get("deviceCache")?.osdSnapshot).osdTimePosition;
|
|
157192
|
+
if (typeof got !== "string" || got === asked) return;
|
|
157193
|
+
throw new Error(`the camera did not take the timestamp position: asked "${asked}", it reports "${got}"`);
|
|
156910
157194
|
}
|
|
156911
157195
|
/**
|
|
156912
157196
|
* Optionally inject an already-logged-in Baichuan api — used by
|