@apocaliss92/nodelink-js 0.4.11 → 0.4.13
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/README.md +24 -0
- package/dist/BaichuanVideoStream-HGPU2MZ3.js +7 -0
- package/dist/{DiagnosticsTools-RNIDFEJK.js → DiagnosticsTools-BQOWJ23L.js} +3 -2
- package/dist/DiagnosticsTools-BQOWJ23L.js.map +1 -0
- package/dist/{chunk-EDLMKBG2.js → chunk-2JNXKT3C.js} +151 -2733
- package/dist/chunk-2JNXKT3C.js.map +1 -0
- package/dist/chunk-C57QV7IL.js +2723 -0
- package/dist/chunk-C57QV7IL.js.map +1 -0
- package/dist/{chunk-HGQ53FB3.js → chunk-R5AJV73A.js} +1348 -47
- package/dist/chunk-R5AJV73A.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +1422 -12
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -2
- package/dist/cli/rtsp-server.js.map +1 -1
- package/dist/index.cjs +1929 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1051 -172
- package/dist/index.d.ts +965 -46
- package/dist/index.js +547 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-EDLMKBG2.js.map +0 -1
- package/dist/chunk-HGQ53FB3.js.map +0 -1
- /package/dist/{DiagnosticsTools-RNIDFEJK.js.map → BaichuanVideoStream-HGPU2MZ3.js.map} +0 -0
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReolinkCgiApi,
|
|
3
|
+
ReolinkHttpClient,
|
|
4
|
+
applyStreamPatch,
|
|
5
|
+
applyXmlTagPatch,
|
|
6
|
+
buildAbilityInfoExtensionXml,
|
|
7
|
+
buildBinaryExtensionXml,
|
|
8
|
+
buildChannelExtensionXml,
|
|
9
|
+
buildFloodlightManualXml,
|
|
10
|
+
buildLoginXml,
|
|
11
|
+
buildLogoutXml,
|
|
12
|
+
buildPreviewStopXml,
|
|
13
|
+
buildPreviewStopXmlV11,
|
|
14
|
+
buildPreviewXml,
|
|
15
|
+
buildPreviewXmlV11,
|
|
16
|
+
buildPtzControlXml,
|
|
17
|
+
buildPtzPresetXml,
|
|
18
|
+
buildPtzPresetXmlV2,
|
|
19
|
+
buildRtspUrl,
|
|
20
|
+
buildSirenManualXml,
|
|
21
|
+
buildSirenTimesXml,
|
|
22
|
+
buildStartZoomFocusXml,
|
|
23
|
+
collectNvrDiagnostics,
|
|
24
|
+
ensureXmlHeader,
|
|
25
|
+
getXmlText,
|
|
26
|
+
normalizeDayNightMode,
|
|
27
|
+
normalizeOpenClose,
|
|
28
|
+
parseRecordingFileName,
|
|
29
|
+
patchNestedTag,
|
|
30
|
+
runAllDiagnosticsConsecutively,
|
|
31
|
+
runMultifocalDiagnosticsConsecutively,
|
|
32
|
+
xmlEscape
|
|
33
|
+
} from "./chunk-2JNXKT3C.js";
|
|
1
34
|
import {
|
|
2
35
|
BC_CLASS_FILE_DOWNLOAD,
|
|
3
36
|
BC_CLASS_LEGACY,
|
|
@@ -32,6 +65,7 @@ import {
|
|
|
32
65
|
BC_CMD_ID_GET_AUDIO_CFG,
|
|
33
66
|
BC_CMD_ID_GET_AUDIO_TASK,
|
|
34
67
|
BC_CMD_ID_GET_AUTO_FOCUS,
|
|
68
|
+
BC_CMD_ID_GET_AUTO_REBOOT,
|
|
35
69
|
BC_CMD_ID_GET_BATTERY_INFO,
|
|
36
70
|
BC_CMD_ID_GET_BATTERY_INFO_LIST,
|
|
37
71
|
BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
|
|
@@ -39,6 +73,8 @@ import {
|
|
|
39
73
|
BC_CMD_ID_GET_DING_DONG_CFG,
|
|
40
74
|
BC_CMD_ID_GET_DING_DONG_LIST,
|
|
41
75
|
BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
76
|
+
BC_CMD_ID_GET_DST,
|
|
77
|
+
BC_CMD_ID_GET_EMAIL,
|
|
42
78
|
BC_CMD_ID_GET_EMAIL_TASK,
|
|
43
79
|
BC_CMD_ID_GET_ENC,
|
|
44
80
|
BC_CMD_ID_GET_FTP_TASK,
|
|
@@ -46,6 +82,7 @@ import {
|
|
|
46
82
|
BC_CMD_ID_GET_KIT_AP_CFG,
|
|
47
83
|
BC_CMD_ID_GET_LED_STATE,
|
|
48
84
|
BC_CMD_ID_GET_MOTION_ALARM,
|
|
85
|
+
BC_CMD_ID_GET_NTP,
|
|
49
86
|
BC_CMD_ID_GET_ONLINE_USER_LIST,
|
|
50
87
|
BC_CMD_ID_GET_OSD_DATETIME,
|
|
51
88
|
BC_CMD_ID_GET_PIR_INFO,
|
|
@@ -61,6 +98,7 @@ import {
|
|
|
61
98
|
BC_CMD_ID_GET_SUPPORT,
|
|
62
99
|
BC_CMD_ID_GET_SYSTEM_GENERAL,
|
|
63
100
|
BC_CMD_ID_GET_TIMELAPSE_CFG,
|
|
101
|
+
BC_CMD_ID_GET_VERSION_INFO,
|
|
64
102
|
BC_CMD_ID_GET_VIDEO_INPUT,
|
|
65
103
|
BC_CMD_ID_GET_WHITE_LED,
|
|
66
104
|
BC_CMD_ID_GET_WIFI,
|
|
@@ -83,14 +121,21 @@ import {
|
|
|
83
121
|
BC_CMD_ID_SET_AUDIO_CFG,
|
|
84
122
|
BC_CMD_ID_SET_AUDIO_TASK,
|
|
85
123
|
BC_CMD_ID_SET_AUTO_FOCUS,
|
|
124
|
+
BC_CMD_ID_SET_AUTO_REBOOT,
|
|
86
125
|
BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD,
|
|
87
126
|
BC_CMD_ID_SET_DING_DONG_CFG,
|
|
88
127
|
BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
128
|
+
BC_CMD_ID_SET_DST,
|
|
129
|
+
BC_CMD_ID_SET_EMAIL,
|
|
130
|
+
BC_CMD_ID_SET_EMAIL_TASK,
|
|
89
131
|
BC_CMD_ID_SET_ENC,
|
|
90
132
|
BC_CMD_ID_SET_LED_STATE,
|
|
91
133
|
BC_CMD_ID_SET_MOTION_ALARM,
|
|
134
|
+
BC_CMD_ID_SET_NTP,
|
|
135
|
+
BC_CMD_ID_SET_OSD_DATETIME,
|
|
92
136
|
BC_CMD_ID_SET_PIR_INFO,
|
|
93
137
|
BC_CMD_ID_SET_PRIVACY_MASK,
|
|
138
|
+
BC_CMD_ID_SET_SYSTEM_GENERAL,
|
|
94
139
|
BC_CMD_ID_SET_VIDEO_INPUT,
|
|
95
140
|
BC_CMD_ID_SET_WHITE_LED_STATE,
|
|
96
141
|
BC_CMD_ID_SET_WHITE_LED_TASK,
|
|
@@ -100,6 +145,7 @@ import {
|
|
|
100
145
|
BC_CMD_ID_TALK_ABILITY,
|
|
101
146
|
BC_CMD_ID_TALK_CONFIG,
|
|
102
147
|
BC_CMD_ID_TALK_RESET,
|
|
148
|
+
BC_CMD_ID_TEST_EMAIL,
|
|
103
149
|
BC_CMD_ID_UDP_KEEP_ALIVE,
|
|
104
150
|
BC_CMD_ID_VIDEO,
|
|
105
151
|
BC_CMD_ID_VIDEO_STOP,
|
|
@@ -108,60 +154,29 @@ import {
|
|
|
108
154
|
BC_TCP_DEFAULT_PORT,
|
|
109
155
|
BaichuanVideoStream,
|
|
110
156
|
BcMediaAnnexBDecoder,
|
|
111
|
-
ReolinkCgiApi,
|
|
112
|
-
ReolinkHttpClient,
|
|
113
157
|
__require,
|
|
114
158
|
aesDecrypt,
|
|
115
159
|
aesEncrypt,
|
|
116
|
-
applyStreamPatch,
|
|
117
|
-
applyXmlTagPatch,
|
|
118
160
|
bcDecrypt,
|
|
119
161
|
bcEncrypt,
|
|
120
162
|
bcHeaderHasPayloadOffset,
|
|
121
|
-
buildAbilityInfoExtensionXml,
|
|
122
|
-
buildBinaryExtensionXml,
|
|
123
|
-
buildChannelExtensionXml,
|
|
124
|
-
buildFloodlightManualXml,
|
|
125
|
-
buildLoginXml,
|
|
126
|
-
buildLogoutXml,
|
|
127
|
-
buildPreviewStopXml,
|
|
128
|
-
buildPreviewStopXmlV11,
|
|
129
|
-
buildPreviewXml,
|
|
130
|
-
buildPreviewXmlV11,
|
|
131
|
-
buildPtzControlXml,
|
|
132
|
-
buildPtzPresetXml,
|
|
133
|
-
buildPtzPresetXmlV2,
|
|
134
|
-
buildRtspUrl,
|
|
135
|
-
buildSirenManualXml,
|
|
136
|
-
buildSirenTimesXml,
|
|
137
|
-
buildStartZoomFocusXml,
|
|
138
|
-
collectNvrDiagnostics,
|
|
139
163
|
convertToAnnexB,
|
|
140
164
|
convertToAnnexB2,
|
|
141
165
|
debugLog,
|
|
142
166
|
deriveAesKey,
|
|
143
|
-
ensureXmlHeader,
|
|
144
167
|
eventTraceLog,
|
|
145
168
|
extractPpsFromAnnexB,
|
|
146
169
|
extractSpsFromAnnexB,
|
|
147
170
|
extractVpsFromAnnexB,
|
|
148
|
-
getXmlText,
|
|
149
171
|
isH265Irap,
|
|
150
172
|
md5StrModern,
|
|
151
|
-
normalizeDayNightMode,
|
|
152
173
|
normalizeDebugOptions,
|
|
153
|
-
normalizeOpenClose,
|
|
154
|
-
parseRecordingFileName,
|
|
155
|
-
patchNestedTag,
|
|
156
174
|
recordingsTraceLog,
|
|
157
|
-
runAllDiagnosticsConsecutively,
|
|
158
|
-
runMultifocalDiagnosticsConsecutively,
|
|
159
175
|
splitAnnexBToNalPayloads,
|
|
160
176
|
splitAnnexBToNalPayloads2,
|
|
161
177
|
talkTraceLog,
|
|
162
|
-
traceLog
|
|
163
|
-
|
|
164
|
-
} from "./chunk-EDLMKBG2.js";
|
|
178
|
+
traceLog
|
|
179
|
+
} from "./chunk-C57QV7IL.js";
|
|
165
180
|
|
|
166
181
|
// src/protocol/framing.ts
|
|
167
182
|
function encodeHeader(h) {
|
|
@@ -1870,6 +1885,23 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
1870
1885
|
* even if the current client instance is idle/disconnected.
|
|
1871
1886
|
*/
|
|
1872
1887
|
static streamingRegistry = /* @__PURE__ */ new Map();
|
|
1888
|
+
/**
|
|
1889
|
+
* Per-device set of live BaichuanClient instances.
|
|
1890
|
+
*
|
|
1891
|
+
* Why: when a streaming client unsubscribes (e.g. RTSP grace timer expires
|
|
1892
|
+
* and SocketPool tears the streaming socket down), the global streaming
|
|
1893
|
+
* registry decrements but the GENERAL client of the same device has no
|
|
1894
|
+
* way of knowing — its idle-disconnect timer was last evaluated while
|
|
1895
|
+
* `isDeviceStreamingActive()` was still true (because the streaming socket
|
|
1896
|
+
* was still alive) and wasn't rescheduled. Without this registry the
|
|
1897
|
+
* general socket stays connected, the 60-second session-guard timer keeps
|
|
1898
|
+
* sending getOnlineUserList() to the camera, and a battery camera ends up
|
|
1899
|
+
* waking up every minute (issue #18).
|
|
1900
|
+
*
|
|
1901
|
+
* On streamingRegistry decrement-to-zero we walk this set and kick every
|
|
1902
|
+
* sibling's idle-disconnect timer so it can re-evaluate eligibility.
|
|
1903
|
+
*/
|
|
1904
|
+
static deviceClients = /* @__PURE__ */ new Map();
|
|
1873
1905
|
/**
|
|
1874
1906
|
* Per-host D2C_DISC backoff state that persists across client instance recreation.
|
|
1875
1907
|
*
|
|
@@ -1984,6 +2016,29 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
1984
2016
|
// AlarmEventList (cmdId=33) can be very chatty (often sent every second).
|
|
1985
2017
|
// Track last per-channel alarm state so we only emit on transitions.
|
|
1986
2018
|
alarmEventState = /* @__PURE__ */ new Map();
|
|
2019
|
+
/** Whether this instance is currently in BaichuanClient.deviceClients. */
|
|
2020
|
+
registeredInDeviceClients = false;
|
|
2021
|
+
registerInDeviceClients() {
|
|
2022
|
+
if (this.registeredInDeviceClients) return;
|
|
2023
|
+
const key = this.getDeviceRegistryKey();
|
|
2024
|
+
let set = _BaichuanClient.deviceClients.get(key);
|
|
2025
|
+
if (!set) {
|
|
2026
|
+
set = /* @__PURE__ */ new Set();
|
|
2027
|
+
_BaichuanClient.deviceClients.set(key, set);
|
|
2028
|
+
}
|
|
2029
|
+
set.add(this);
|
|
2030
|
+
this.registeredInDeviceClients = true;
|
|
2031
|
+
}
|
|
2032
|
+
unregisterFromDeviceClients() {
|
|
2033
|
+
if (!this.registeredInDeviceClients) return;
|
|
2034
|
+
const key = this.getDeviceRegistryKey();
|
|
2035
|
+
const set = _BaichuanClient.deviceClients.get(key);
|
|
2036
|
+
if (set) {
|
|
2037
|
+
set.delete(this);
|
|
2038
|
+
if (set.size === 0) _BaichuanClient.deviceClients.delete(key);
|
|
2039
|
+
}
|
|
2040
|
+
this.registeredInDeviceClients = false;
|
|
2041
|
+
}
|
|
1987
2042
|
constructor(options) {
|
|
1988
2043
|
super();
|
|
1989
2044
|
this.opts = options;
|
|
@@ -1998,6 +2053,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
1998
2053
|
code: err?.code
|
|
1999
2054
|
});
|
|
2000
2055
|
});
|
|
2056
|
+
this.registerInDeviceClients();
|
|
2001
2057
|
}
|
|
2002
2058
|
newSocketSessionId(transport) {
|
|
2003
2059
|
const short = randomUUID().split("-")[0] ?? randomUUID().slice(0, 8);
|
|
@@ -2254,6 +2310,18 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
2254
2310
|
activeStreamClients: nextCount
|
|
2255
2311
|
});
|
|
2256
2312
|
this.contributesToGlobalStreamingRegistry = shouldContribute;
|
|
2313
|
+
if (!shouldContribute && nextCount === 0) {
|
|
2314
|
+
const siblings = _BaichuanClient.deviceClients.get(key);
|
|
2315
|
+
if (siblings) {
|
|
2316
|
+
for (const sib of siblings) {
|
|
2317
|
+
if (sib === this) continue;
|
|
2318
|
+
try {
|
|
2319
|
+
sib.kickIdleDisconnectTimer();
|
|
2320
|
+
} catch {
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2257
2325
|
}
|
|
2258
2326
|
/**
|
|
2259
2327
|
* True if the device should be considered "awake" due to active streaming.
|
|
@@ -2718,6 +2786,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
2718
2786
|
`transport=tcp host=${this.opts.host} port=${port}${sid ? ` sid=${sid}` : ""}${remote ? ` remote=${remote}` : ""}${peer ? ` peer=${peer}` : ""}`
|
|
2719
2787
|
);
|
|
2720
2788
|
this.logSocketState("tcp_connected");
|
|
2789
|
+
this.registerInDeviceClients();
|
|
2721
2790
|
this.startKeepAlive();
|
|
2722
2791
|
this.kickIdleDisconnectTimer();
|
|
2723
2792
|
}
|
|
@@ -3034,6 +3103,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
3034
3103
|
this.logDebug("udp_close_error", e);
|
|
3035
3104
|
}
|
|
3036
3105
|
}
|
|
3106
|
+
this.unregisterFromDeviceClients();
|
|
3037
3107
|
}
|
|
3038
3108
|
handleFrame(frame) {
|
|
3039
3109
|
const now = Date.now();
|
|
@@ -8275,6 +8345,304 @@ function parseXmlFragmentToJson(xml) {
|
|
|
8275
8345
|
return parsed.root;
|
|
8276
8346
|
}
|
|
8277
8347
|
|
|
8348
|
+
// src/reolink/baichuan/utils/email.ts
|
|
8349
|
+
var parseInt01 = (text) => {
|
|
8350
|
+
if (text === void 0) return void 0;
|
|
8351
|
+
return text.trim() === "1" ? 1 : 0;
|
|
8352
|
+
};
|
|
8353
|
+
var parseNumberSafe = (text) => {
|
|
8354
|
+
if (text === void 0) return void 0;
|
|
8355
|
+
const n = Number(text);
|
|
8356
|
+
return Number.isFinite(n) ? n : void 0;
|
|
8357
|
+
};
|
|
8358
|
+
var VALID_ATTACHMENT_TYPES = [
|
|
8359
|
+
"picture",
|
|
8360
|
+
"video",
|
|
8361
|
+
"none"
|
|
8362
|
+
];
|
|
8363
|
+
var VALID_TEXT_TYPES = ["withText", "noText"];
|
|
8364
|
+
var normalizeAttachmentType = (text) => {
|
|
8365
|
+
const t = (text ?? "").trim();
|
|
8366
|
+
return VALID_ATTACHMENT_TYPES.includes(t) ? t : "picture";
|
|
8367
|
+
};
|
|
8368
|
+
var normalizeTextType = (text) => {
|
|
8369
|
+
const t = (text ?? "").trim();
|
|
8370
|
+
return VALID_TEXT_TYPES.includes(t) ? t : "withText";
|
|
8371
|
+
};
|
|
8372
|
+
function parseEmailConfigFromXml(xml) {
|
|
8373
|
+
const cfg = {
|
|
8374
|
+
smtpServer: getXmlText(xml, "smtpServer") ?? "",
|
|
8375
|
+
userName: getXmlText(xml, "userName") ?? "",
|
|
8376
|
+
password: getXmlText(xml, "password") ?? "",
|
|
8377
|
+
address1: getXmlText(xml, "address1") ?? "",
|
|
8378
|
+
address2: getXmlText(xml, "address2") ?? "",
|
|
8379
|
+
address3: getXmlText(xml, "address3") ?? "",
|
|
8380
|
+
smtpPort: parseNumberSafe(getXmlText(xml, "smtpPort")) ?? 0,
|
|
8381
|
+
sendNickname: getXmlText(xml, "sendNickname") ?? "",
|
|
8382
|
+
attachment: parseInt01(getXmlText(xml, "attachment")) ?? 0,
|
|
8383
|
+
attachmentType: normalizeAttachmentType(getXmlText(xml, "attachmentType")),
|
|
8384
|
+
textType: normalizeTextType(getXmlText(xml, "textType")),
|
|
8385
|
+
ssl: parseInt01(getXmlText(xml, "ssl")) ?? 0,
|
|
8386
|
+
interval: parseNumberSafe(getXmlText(xml, "interval")) ?? 30
|
|
8387
|
+
};
|
|
8388
|
+
const senderMaxLen = parseNumberSafe(getXmlText(xml, "senderMaxLen"));
|
|
8389
|
+
if (senderMaxLen !== void 0) cfg.senderMaxLen = senderMaxLen;
|
|
8390
|
+
const pwdMaxLen = parseNumberSafe(getXmlText(xml, "pwdMaxLen"));
|
|
8391
|
+
if (pwdMaxLen !== void 0) cfg.pwdMaxLen = pwdMaxLen;
|
|
8392
|
+
const ability = parseNumberSafe(getXmlText(xml, "emailAttachAbility"));
|
|
8393
|
+
if (ability !== void 0) cfg.emailAttachAbility = ability;
|
|
8394
|
+
return cfg;
|
|
8395
|
+
}
|
|
8396
|
+
function buildSetEmailXml(current, patch) {
|
|
8397
|
+
const merged = { ...current, ...patch };
|
|
8398
|
+
return ensureXmlHeader(
|
|
8399
|
+
`<body><Email version="1.1"><smtpServer>${xmlEscape(merged.smtpServer)}</smtpServer><userName>${xmlEscape(merged.userName)}</userName><password>${xmlEscape(merged.password)}</password><address1>${xmlEscape(merged.address1)}</address1><address2>${xmlEscape(merged.address2)}</address2><address3>${xmlEscape(merged.address3)}</address3><smtpPort>${merged.smtpPort}</smtpPort><sendNickname>${xmlEscape(merged.sendNickname)}</sendNickname><attachment>${merged.attachment}</attachment><attachmentType>${merged.attachmentType}</attachmentType><textType>${merged.textType}</textType><ssl>${merged.ssl}</ssl><interval>${merged.interval}</interval></Email></body>`
|
|
8400
|
+
);
|
|
8401
|
+
}
|
|
8402
|
+
function parseEmailTaskFromXml(xml) {
|
|
8403
|
+
const channelId = parseNumberSafe(getXmlText(xml, "channelId")) ?? 0;
|
|
8404
|
+
const enable = parseInt01(getXmlText(xml, "enable")) ?? 0;
|
|
8405
|
+
const items = [];
|
|
8406
|
+
const itemRe = /<item>([\s\S]*?)<\/item>/g;
|
|
8407
|
+
let match;
|
|
8408
|
+
while ((match = itemRe.exec(xml)) !== null) {
|
|
8409
|
+
const block = match[1] ?? "";
|
|
8410
|
+
items.push({
|
|
8411
|
+
type: getXmlText(block, "type") ?? "none",
|
|
8412
|
+
valueTable: getXmlText(block, "valueTable") ?? ""
|
|
8413
|
+
});
|
|
8414
|
+
}
|
|
8415
|
+
return { channelId, enable, typeScheduleList: items };
|
|
8416
|
+
}
|
|
8417
|
+
function buildEmailScheduleValueTable(spec) {
|
|
8418
|
+
if (spec.kind === "always") return "1".repeat(168);
|
|
8419
|
+
const grid = new Array(168).fill("0");
|
|
8420
|
+
if (spec.kind === "never") return grid.join("");
|
|
8421
|
+
for (const w of spec.windows) {
|
|
8422
|
+
const startH = Math.max(0, Math.min(24, w.startHour));
|
|
8423
|
+
const endH = Math.max(startH, Math.min(24, w.endHour));
|
|
8424
|
+
for (const d of w.days) {
|
|
8425
|
+
if (d < 0 || d > 6) continue;
|
|
8426
|
+
for (let h = startH; h < endH; h++) {
|
|
8427
|
+
grid[d * 24 + h] = "1";
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
}
|
|
8431
|
+
return grid.join("");
|
|
8432
|
+
}
|
|
8433
|
+
function buildSetEmailTaskXml(task) {
|
|
8434
|
+
const items = task.typeScheduleList.map(
|
|
8435
|
+
(item) => `<item><type>${xmlEscape(item.type)}</type><valueTable>${xmlEscape(item.valueTable)}</valueTable></item>`
|
|
8436
|
+
).join("");
|
|
8437
|
+
return ensureXmlHeader(
|
|
8438
|
+
`<body><EmailTask version="1.1"><channelId>${task.channelId}</channelId><enable>${task.enable}</enable><typeScheduleList>${items}</typeScheduleList></EmailTask></body>`
|
|
8439
|
+
);
|
|
8440
|
+
}
|
|
8441
|
+
|
|
8442
|
+
// src/reolink/baichuan/utils/ntp.ts
|
|
8443
|
+
var parseNumberSafe2 = (text) => {
|
|
8444
|
+
if (text === void 0) return void 0;
|
|
8445
|
+
const n = Number(text);
|
|
8446
|
+
return Number.isFinite(n) ? n : void 0;
|
|
8447
|
+
};
|
|
8448
|
+
var parseInt012 = (text) => {
|
|
8449
|
+
if (text === void 0) return void 0;
|
|
8450
|
+
return text.trim() === "1" ? 1 : 0;
|
|
8451
|
+
};
|
|
8452
|
+
function parseNtpConfigFromXml(xml) {
|
|
8453
|
+
return {
|
|
8454
|
+
enable: parseInt012(getXmlText(xml, "enable")) ?? 0,
|
|
8455
|
+
server: getXmlText(xml, "server") ?? "",
|
|
8456
|
+
synchronizeInterval: parseNumberSafe2(getXmlText(xml, "synchronizeInterval")) ?? 1440,
|
|
8457
|
+
port: parseNumberSafe2(getXmlText(xml, "port")) ?? 123
|
|
8458
|
+
};
|
|
8459
|
+
}
|
|
8460
|
+
function buildSetNtpXml(current, patch) {
|
|
8461
|
+
const merged = { ...current, ...patch };
|
|
8462
|
+
return ensureXmlHeader(
|
|
8463
|
+
`<body><Ntp version="1.1"><enable>${merged.enable}</enable><server>${xmlEscape(merged.server)}</server><synchronizeInterval>${merged.synchronizeInterval}</synchronizeInterval><port>${merged.port}</port></Ntp></body>`
|
|
8464
|
+
);
|
|
8465
|
+
}
|
|
8466
|
+
|
|
8467
|
+
// src/reolink/baichuan/utils/dst.ts
|
|
8468
|
+
var parseNumberSafe3 = (text) => {
|
|
8469
|
+
if (text === void 0) return void 0;
|
|
8470
|
+
const n = Number(text);
|
|
8471
|
+
return Number.isFinite(n) ? n : void 0;
|
|
8472
|
+
};
|
|
8473
|
+
var parseInt013 = (text) => {
|
|
8474
|
+
if (text === void 0) return void 0;
|
|
8475
|
+
return text.trim() === "1" ? 1 : 0;
|
|
8476
|
+
};
|
|
8477
|
+
var VALID_WEEKDAYS = [
|
|
8478
|
+
"Sunday",
|
|
8479
|
+
"Monday",
|
|
8480
|
+
"Tuesday",
|
|
8481
|
+
"Wednesday",
|
|
8482
|
+
"Thursday",
|
|
8483
|
+
"Friday",
|
|
8484
|
+
"Saturday"
|
|
8485
|
+
];
|
|
8486
|
+
var normalizeWeekday = (text) => {
|
|
8487
|
+
const t = (text ?? "").trim();
|
|
8488
|
+
return VALID_WEEKDAYS.includes(t) ? t : "Sunday";
|
|
8489
|
+
};
|
|
8490
|
+
function parseDstConfigFromXml(xml) {
|
|
8491
|
+
const cfg = {
|
|
8492
|
+
enable: parseInt013(getXmlText(xml, "enable")) ?? 0,
|
|
8493
|
+
offset: parseNumberSafe3(getXmlText(xml, "offset")) ?? 1,
|
|
8494
|
+
startMonth: parseNumberSafe3(getXmlText(xml, "startMonth")) ?? 3,
|
|
8495
|
+
startWeekIndex: parseNumberSafe3(getXmlText(xml, "startWeekIndex")) ?? 5,
|
|
8496
|
+
startWeekday: normalizeWeekday(getXmlText(xml, "startWeekday")),
|
|
8497
|
+
startHour: parseNumberSafe3(getXmlText(xml, "startHour")) ?? 2,
|
|
8498
|
+
startMinute: parseNumberSafe3(getXmlText(xml, "startMinute")) ?? 0,
|
|
8499
|
+
startSecond: parseNumberSafe3(getXmlText(xml, "startSecond")) ?? 0,
|
|
8500
|
+
endMonth: parseNumberSafe3(getXmlText(xml, "endMonth")) ?? 10,
|
|
8501
|
+
endWeekIndex: parseNumberSafe3(getXmlText(xml, "endWeekIndex")) ?? 4,
|
|
8502
|
+
endWeekday: normalizeWeekday(getXmlText(xml, "endWeekday")),
|
|
8503
|
+
endHour: parseNumberSafe3(getXmlText(xml, "endHour")) ?? 3,
|
|
8504
|
+
endMinute: parseNumberSafe3(getXmlText(xml, "endMinute")) ?? 0,
|
|
8505
|
+
endSecond: parseNumberSafe3(getXmlText(xml, "endSecond")) ?? 0
|
|
8506
|
+
};
|
|
8507
|
+
const version = parseNumberSafe3(getXmlText(xml, "version"));
|
|
8508
|
+
if (version !== void 0) cfg.version = version;
|
|
8509
|
+
return cfg;
|
|
8510
|
+
}
|
|
8511
|
+
function buildSetDstXml(current, patch) {
|
|
8512
|
+
const merged = { ...current, ...patch };
|
|
8513
|
+
return ensureXmlHeader(
|
|
8514
|
+
`<body><Dst version="1.1"><enable>${merged.enable}</enable><offset>${merged.offset}</offset><startMonth>${merged.startMonth}</startMonth><startWeekIndex>${merged.startWeekIndex}</startWeekIndex><startWeekday>${xmlEscape(merged.startWeekday)}</startWeekday><startHour>${merged.startHour}</startHour><startMinute>${merged.startMinute}</startMinute><startSecond>${merged.startSecond}</startSecond><endMonth>${merged.endMonth}</endMonth><endWeekIndex>${merged.endWeekIndex}</endWeekIndex><endWeekday>${xmlEscape(merged.endWeekday)}</endWeekday><endHour>${merged.endHour}</endHour><endMinute>${merged.endMinute}</endMinute><endSecond>${merged.endSecond}</endSecond></Dst></body>`
|
|
8515
|
+
);
|
|
8516
|
+
}
|
|
8517
|
+
|
|
8518
|
+
// src/reolink/baichuan/utils/autoReboot.ts
|
|
8519
|
+
var parseNumberSafe4 = (text) => {
|
|
8520
|
+
if (text === void 0) return void 0;
|
|
8521
|
+
const n = Number(text);
|
|
8522
|
+
return Number.isFinite(n) ? n : void 0;
|
|
8523
|
+
};
|
|
8524
|
+
var parseInt014 = (text) => {
|
|
8525
|
+
if (text === void 0) return void 0;
|
|
8526
|
+
return text.trim() === "1" ? 1 : 0;
|
|
8527
|
+
};
|
|
8528
|
+
var VALID_WEEKDAYS2 = [
|
|
8529
|
+
"Sunday",
|
|
8530
|
+
"Monday",
|
|
8531
|
+
"Tuesday",
|
|
8532
|
+
"Wednesday",
|
|
8533
|
+
"Thursday",
|
|
8534
|
+
"Friday",
|
|
8535
|
+
"Saturday",
|
|
8536
|
+
"everyday"
|
|
8537
|
+
];
|
|
8538
|
+
var normalizeWeekday2 = (text) => {
|
|
8539
|
+
const t = (text ?? "").trim();
|
|
8540
|
+
return VALID_WEEKDAYS2.includes(t) ? t : "Sunday";
|
|
8541
|
+
};
|
|
8542
|
+
function parseAutoRebootFromXml(xml) {
|
|
8543
|
+
return {
|
|
8544
|
+
enable: parseInt014(getXmlText(xml, "enable")) ?? 0,
|
|
8545
|
+
weekDay: normalizeWeekday2(getXmlText(xml, "weekDay")),
|
|
8546
|
+
hour: parseNumberSafe4(getXmlText(xml, "hour")) ?? 0,
|
|
8547
|
+
minute: parseNumberSafe4(getXmlText(xml, "minute")) ?? 0,
|
|
8548
|
+
second: parseNumberSafe4(getXmlText(xml, "second")) ?? 0
|
|
8549
|
+
};
|
|
8550
|
+
}
|
|
8551
|
+
function buildSetAutoRebootXml(current, patch) {
|
|
8552
|
+
const merged = { ...current, ...patch };
|
|
8553
|
+
return ensureXmlHeader(
|
|
8554
|
+
`<body><AutoReboot version="1.1"><enable>${merged.enable}</enable><weekDay>${xmlEscape(merged.weekDay)}</weekDay><hour>${merged.hour}</hour><minute>${merged.minute}</minute><second>${merged.second}</second></AutoReboot></body>`
|
|
8555
|
+
);
|
|
8556
|
+
}
|
|
8557
|
+
|
|
8558
|
+
// src/reolink/baichuan/utils/systemGeneral.ts
|
|
8559
|
+
var parseNumberSafe5 = (text) => {
|
|
8560
|
+
if (text === void 0) return void 0;
|
|
8561
|
+
const n = Number(text);
|
|
8562
|
+
return Number.isFinite(n) ? n : void 0;
|
|
8563
|
+
};
|
|
8564
|
+
var parseInt015 = (text) => {
|
|
8565
|
+
if (text === void 0) return void 0;
|
|
8566
|
+
return text.trim() === "1" ? 1 : 0;
|
|
8567
|
+
};
|
|
8568
|
+
var VALID_OSD_FORMATS = ["DMY", "MDY", "YMD"];
|
|
8569
|
+
var normalizeOsdFormat = (text) => {
|
|
8570
|
+
const t = (text ?? "").trim();
|
|
8571
|
+
return VALID_OSD_FORMATS.includes(t) ? t : "YMD";
|
|
8572
|
+
};
|
|
8573
|
+
function parseSystemGeneralFromXml(xml) {
|
|
8574
|
+
return {
|
|
8575
|
+
timeZone: parseNumberSafe5(getXmlText(xml, "timeZone")) ?? 0,
|
|
8576
|
+
osdFormat: normalizeOsdFormat(getXmlText(xml, "osdFormat")),
|
|
8577
|
+
year: parseNumberSafe5(getXmlText(xml, "year")) ?? 0,
|
|
8578
|
+
month: parseNumberSafe5(getXmlText(xml, "month")) ?? 0,
|
|
8579
|
+
day: parseNumberSafe5(getXmlText(xml, "day")) ?? 0,
|
|
8580
|
+
hour: parseNumberSafe5(getXmlText(xml, "hour")) ?? 0,
|
|
8581
|
+
minute: parseNumberSafe5(getXmlText(xml, "minute")) ?? 0,
|
|
8582
|
+
second: parseNumberSafe5(getXmlText(xml, "second")) ?? 0,
|
|
8583
|
+
deviceId: parseNumberSafe5(getXmlText(xml, "deviceId")) ?? 0,
|
|
8584
|
+
timeFormat: parseInt015(getXmlText(xml, "timeFormat")) ?? 0,
|
|
8585
|
+
language: getXmlText(xml, "language") ?? "English",
|
|
8586
|
+
deviceName: getXmlText(xml, "deviceName") ?? "",
|
|
8587
|
+
loginLock: parseInt015(getXmlText(xml, "loginLock")) ?? 0,
|
|
8588
|
+
lockTime: parseNumberSafe5(getXmlText(xml, "lockTime")) ?? 0,
|
|
8589
|
+
allowedTimes: parseNumberSafe5(getXmlText(xml, "allowedTimes")) ?? 0,
|
|
8590
|
+
isDst: parseInt015(getXmlText(xml, "isDst")) ?? 0
|
|
8591
|
+
};
|
|
8592
|
+
}
|
|
8593
|
+
function buildSetSystemGeneralXml(patch) {
|
|
8594
|
+
const parts = [];
|
|
8595
|
+
const isDeviceNameOnly = patch.deviceName !== void 0 && patch.timeZone === void 0 && patch.osdFormat === void 0 && patch.timeFormat === void 0 && patch.language === void 0 && patch.loginLock === void 0 && patch.lockTime === void 0 && patch.allowedTimes === void 0 && patch.manualTime === void 0;
|
|
8596
|
+
if (isDeviceNameOnly) {
|
|
8597
|
+
parts.push("<year>0</year>");
|
|
8598
|
+
parts.push(`<deviceName>${xmlEscape(patch.deviceName)}</deviceName>`);
|
|
8599
|
+
parts.push("<deviceNameOnly>1</deviceNameOnly>");
|
|
8600
|
+
} else if (patch.manualTime !== void 0) {
|
|
8601
|
+
const mt = patch.manualTime;
|
|
8602
|
+
if (patch.timeZone !== void 0)
|
|
8603
|
+
parts.push(`<timeZone>${patch.timeZone}</timeZone>`);
|
|
8604
|
+
if (patch.osdFormat !== void 0)
|
|
8605
|
+
parts.push(`<osdFormat>${patch.osdFormat}</osdFormat>`);
|
|
8606
|
+
parts.push(`<year>${mt.year}</year>`);
|
|
8607
|
+
parts.push(`<month>${mt.month}</month>`);
|
|
8608
|
+
parts.push(`<day>${mt.day}</day>`);
|
|
8609
|
+
parts.push(`<hour>${mt.hour}</hour>`);
|
|
8610
|
+
parts.push(`<minute>${mt.minute}</minute>`);
|
|
8611
|
+
parts.push(`<second>${mt.second}</second>`);
|
|
8612
|
+
if (patch.timeFormat !== void 0)
|
|
8613
|
+
parts.push(`<timeFormat>${patch.timeFormat}</timeFormat>`);
|
|
8614
|
+
if (patch.language !== void 0)
|
|
8615
|
+
parts.push(`<language>${xmlEscape(patch.language)}</language>`);
|
|
8616
|
+
if (patch.deviceName !== void 0)
|
|
8617
|
+
parts.push(`<deviceName>${xmlEscape(patch.deviceName)}</deviceName>`);
|
|
8618
|
+
if (patch.loginLock !== void 0)
|
|
8619
|
+
parts.push(`<loginLock>${patch.loginLock}</loginLock>`);
|
|
8620
|
+
if (patch.lockTime !== void 0)
|
|
8621
|
+
parts.push(`<lockTime>${patch.lockTime}</lockTime>`);
|
|
8622
|
+
if (patch.allowedTimes !== void 0)
|
|
8623
|
+
parts.push(`<allowedTimes>${patch.allowedTimes}</allowedTimes>`);
|
|
8624
|
+
} else {
|
|
8625
|
+
if (patch.timeZone !== void 0)
|
|
8626
|
+
parts.push(`<timeZone>${patch.timeZone}</timeZone>`);
|
|
8627
|
+
if (patch.osdFormat !== void 0)
|
|
8628
|
+
parts.push(`<osdFormat>${patch.osdFormat}</osdFormat>`);
|
|
8629
|
+
if (patch.timeFormat !== void 0)
|
|
8630
|
+
parts.push(`<timeFormat>${patch.timeFormat}</timeFormat>`);
|
|
8631
|
+
if (patch.language !== void 0)
|
|
8632
|
+
parts.push(`<language>${xmlEscape(patch.language)}</language>`);
|
|
8633
|
+
if (patch.loginLock !== void 0)
|
|
8634
|
+
parts.push(`<loginLock>${patch.loginLock}</loginLock>`);
|
|
8635
|
+
if (patch.lockTime !== void 0)
|
|
8636
|
+
parts.push(`<lockTime>${patch.lockTime}</lockTime>`);
|
|
8637
|
+
if (patch.allowedTimes !== void 0)
|
|
8638
|
+
parts.push(`<allowedTimes>${patch.allowedTimes}</allowedTimes>`);
|
|
8639
|
+
parts.push("<year>0</year>");
|
|
8640
|
+
}
|
|
8641
|
+
return ensureXmlHeader(
|
|
8642
|
+
`<body><SystemGeneral version="1.1">${parts.join("")}</SystemGeneral></body>`
|
|
8643
|
+
);
|
|
8644
|
+
}
|
|
8645
|
+
|
|
8278
8646
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
8279
8647
|
import { Jimp, JimpMime } from "jimp";
|
|
8280
8648
|
|
|
@@ -8335,6 +8703,31 @@ var parseAbilityInfoXml = (xml) => {
|
|
|
8335
8703
|
return abilities;
|
|
8336
8704
|
};
|
|
8337
8705
|
|
|
8706
|
+
// src/reolink/baichuan/utils/versionInfo.ts
|
|
8707
|
+
function parseVersionInfo(xml) {
|
|
8708
|
+
const out = {};
|
|
8709
|
+
const set = (key) => {
|
|
8710
|
+
const v = getXmlText(xml, key);
|
|
8711
|
+
if (v !== void 0) out[key] = v;
|
|
8712
|
+
};
|
|
8713
|
+
set("name");
|
|
8714
|
+
set("type");
|
|
8715
|
+
set("serialNumber");
|
|
8716
|
+
set("buildDay");
|
|
8717
|
+
set("hardwareVersion");
|
|
8718
|
+
set("cfgVersion");
|
|
8719
|
+
set("firmwareVersion");
|
|
8720
|
+
set("detail");
|
|
8721
|
+
set("IEClient");
|
|
8722
|
+
set("cc3200Version");
|
|
8723
|
+
set("spVersion");
|
|
8724
|
+
set("pakSuffix");
|
|
8725
|
+
set("itemNo");
|
|
8726
|
+
set("aiVersion");
|
|
8727
|
+
set("helpVersion");
|
|
8728
|
+
return out;
|
|
8729
|
+
}
|
|
8730
|
+
|
|
8338
8731
|
// src/reolink/baichuan/utils/logging.ts
|
|
8339
8732
|
var formatErrorForLog = (e) => {
|
|
8340
8733
|
if (e instanceof Error) {
|
|
@@ -8589,6 +8982,204 @@ var buildChannelPushDataLogSnapshot = (channelPushData) => {
|
|
|
8589
8982
|
return { result: resultObj, storedChannels: Object.keys(resultObj) };
|
|
8590
8983
|
};
|
|
8591
8984
|
|
|
8985
|
+
// src/reolink/baichuan/utils/detection.ts
|
|
8986
|
+
import * as lz4 from "lz4js";
|
|
8987
|
+
|
|
8988
|
+
// src/reolink/baichuan/utils/aiClassMap.ts
|
|
8989
|
+
function type1ToLabel(type1) {
|
|
8990
|
+
switch (type1) {
|
|
8991
|
+
case 1:
|
|
8992
|
+
return "people";
|
|
8993
|
+
case 2:
|
|
8994
|
+
return "vehicle";
|
|
8995
|
+
case 3:
|
|
8996
|
+
return "animal";
|
|
8997
|
+
case 11259375:
|
|
8998
|
+
return "face";
|
|
8999
|
+
default:
|
|
9000
|
+
return "unknown";
|
|
9001
|
+
}
|
|
9002
|
+
}
|
|
9003
|
+
|
|
9004
|
+
// src/reolink/baichuan/utils/detection.ts
|
|
9005
|
+
var MARKER_LENGTH = 8;
|
|
9006
|
+
var IFRAME_PREFIX_LENGTH = 8;
|
|
9007
|
+
var COUNTER_OFFSET = 8;
|
|
9008
|
+
var BASELINE_SIZE = 128;
|
|
9009
|
+
var FRAME_SIZE_TLV = Buffer.from([3, 4, 0]);
|
|
9010
|
+
var LZ4F_MAGIC = Buffer.from([4, 34, 77, 24]);
|
|
9011
|
+
var CONFIDENCE_DIVISOR = 100;
|
|
9012
|
+
var DEFAULT_AI_FRAME_WIDTH = 896;
|
|
9013
|
+
var DEFAULT_AI_FRAME_HEIGHT = 480;
|
|
9014
|
+
var LZ4_DECOMPRESS_MAX = 256 * 1024;
|
|
9015
|
+
function walkBoxes(buf, start, end, type1, type2, out) {
|
|
9016
|
+
let pos = start;
|
|
9017
|
+
while (pos + 3 <= end) {
|
|
9018
|
+
const t = buf[pos];
|
|
9019
|
+
if (t === 0) return;
|
|
9020
|
+
const length = buf[pos + 1] | buf[pos + 2] << 8;
|
|
9021
|
+
const recordEnd = pos + 3 + length;
|
|
9022
|
+
if (recordEnd > end) return;
|
|
9023
|
+
const isBoxType4 = t === 4 && (length === 10 || length === 13 || length === 14);
|
|
9024
|
+
const isBoxType2 = t === 2 && length === 10;
|
|
9025
|
+
if ((isBoxType4 || isBoxType2) && type1 !== 0 && type2 !== 0) {
|
|
9026
|
+
const x1 = buf.readUInt16LE(pos + 3);
|
|
9027
|
+
const y1 = buf.readUInt16LE(pos + 5);
|
|
9028
|
+
const x2 = buf.readUInt16LE(pos + 7);
|
|
9029
|
+
const y2 = buf.readUInt16LE(pos + 9);
|
|
9030
|
+
const conf = buf.readUInt16LE(pos + 11);
|
|
9031
|
+
if (x2 > x1 && y2 > y1) {
|
|
9032
|
+
out.push({ x1, y1, x2, y2, conf, label: type1ToLabel(type1) });
|
|
9033
|
+
}
|
|
9034
|
+
pos = recordEnd;
|
|
9035
|
+
continue;
|
|
9036
|
+
}
|
|
9037
|
+
if (type1 === 255 && type2 === 2 && t === 2 && length >= LZ4F_MAGIC.length && buf[pos + 3] === LZ4F_MAGIC[0] && buf[pos + 4] === LZ4F_MAGIC[1] && buf[pos + 5] === LZ4F_MAGIC[2] && buf[pos + 6] === LZ4F_MAGIC[3]) {
|
|
9038
|
+
try {
|
|
9039
|
+
const decompressed = lz4.decompress(
|
|
9040
|
+
buf.subarray(pos + 3, recordEnd),
|
|
9041
|
+
LZ4_DECOMPRESS_MAX
|
|
9042
|
+
);
|
|
9043
|
+
const decBuf = Buffer.from(decompressed);
|
|
9044
|
+
walkBoxes(decBuf, 0, decBuf.length, 0, 0, out);
|
|
9045
|
+
} catch {
|
|
9046
|
+
}
|
|
9047
|
+
pos = recordEnd;
|
|
9048
|
+
continue;
|
|
9049
|
+
}
|
|
9050
|
+
if (length > 0) {
|
|
9051
|
+
let nextT1 = type1;
|
|
9052
|
+
let nextT2 = type2;
|
|
9053
|
+
if (type1 === 0) nextT1 = t;
|
|
9054
|
+
else if (type2 === 0) nextT2 = t;
|
|
9055
|
+
walkBoxes(buf, pos + 3, recordEnd, nextT1, nextT2, out);
|
|
9056
|
+
}
|
|
9057
|
+
pos = recordEnd;
|
|
9058
|
+
}
|
|
9059
|
+
}
|
|
9060
|
+
function decodeDetectionHeader(raw, frameType) {
|
|
9061
|
+
const markerOffset = frameType === "Iframe" ? IFRAME_PREFIX_LENGTH : 0;
|
|
9062
|
+
const blockLength = raw.length - markerOffset;
|
|
9063
|
+
const empty = {
|
|
9064
|
+
state: "invalid-marker",
|
|
9065
|
+
markerOffset,
|
|
9066
|
+
blockLength,
|
|
9067
|
+
boxes: []
|
|
9068
|
+
};
|
|
9069
|
+
if (blockLength < MARKER_LENGTH) return empty;
|
|
9070
|
+
if (!hasStandardMarker(raw, markerOffset)) return empty;
|
|
9071
|
+
if (blockLength < COUNTER_OFFSET + 4) return empty;
|
|
9072
|
+
const counter = raw.readUInt32LE(markerOffset + COUNTER_OFFSET);
|
|
9073
|
+
const rawBoxes = [];
|
|
9074
|
+
walkBoxes(raw, markerOffset, raw.length, 0, 0, rawBoxes);
|
|
9075
|
+
let aiFrameWidth = DEFAULT_AI_FRAME_WIDTH;
|
|
9076
|
+
let aiFrameHeight = DEFAULT_AI_FRAME_HEIGHT;
|
|
9077
|
+
let frameSizeFound = false;
|
|
9078
|
+
const searchStart = markerOffset + MARKER_LENGTH;
|
|
9079
|
+
for (let i = searchStart; i + 7 <= raw.length; i++) {
|
|
9080
|
+
if (raw[i] === FRAME_SIZE_TLV[0] && raw[i + 1] === FRAME_SIZE_TLV[1] && raw[i + 2] === FRAME_SIZE_TLV[2]) {
|
|
9081
|
+
const w = raw.readUInt16LE(i + 3);
|
|
9082
|
+
const h = raw.readUInt16LE(i + 5);
|
|
9083
|
+
if (w >= 64 && w <= 8192 && h >= 64 && h <= 8192) {
|
|
9084
|
+
aiFrameWidth = w;
|
|
9085
|
+
aiFrameHeight = h;
|
|
9086
|
+
frameSizeFound = true;
|
|
9087
|
+
break;
|
|
9088
|
+
}
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
const specificity = {
|
|
9092
|
+
face: 4,
|
|
9093
|
+
animal: 3,
|
|
9094
|
+
people: 2,
|
|
9095
|
+
vehicle: 1,
|
|
9096
|
+
unknown: 0
|
|
9097
|
+
};
|
|
9098
|
+
const dedup = /* @__PURE__ */ new Map();
|
|
9099
|
+
for (const rb of rawBoxes) {
|
|
9100
|
+
if (rb.x2 > aiFrameWidth || rb.y2 > aiFrameHeight) continue;
|
|
9101
|
+
const key = `${rb.x1}_${rb.y1}_${rb.x2}_${rb.y2}`;
|
|
9102
|
+
const prev = dedup.get(key);
|
|
9103
|
+
if (!prev || (specificity[rb.label] ?? 0) > (specificity[prev.label] ?? 0)) {
|
|
9104
|
+
dedup.set(key, rb);
|
|
9105
|
+
}
|
|
9106
|
+
}
|
|
9107
|
+
const boxes = [];
|
|
9108
|
+
for (const rb of dedup.values()) {
|
|
9109
|
+
boxes.push({
|
|
9110
|
+
x: rb.x1 / aiFrameWidth,
|
|
9111
|
+
y: rb.y1 / aiFrameHeight,
|
|
9112
|
+
width: (rb.x2 - rb.x1) / aiFrameWidth,
|
|
9113
|
+
height: (rb.y2 - rb.y1) / aiFrameHeight,
|
|
9114
|
+
...rb.conf > 0 && rb.conf <= 100 ? { confidence: rb.conf / CONFIDENCE_DIVISOR } : {},
|
|
9115
|
+
...rb.label !== "unknown" ? { label: rb.label } : {}
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
let state;
|
|
9119
|
+
if (boxes.length > 0) state = "overlay-decoded";
|
|
9120
|
+
else if (blockLength === BASELINE_SIZE) state = "no-overlay";
|
|
9121
|
+
else state = "overlay-undecoded";
|
|
9122
|
+
return {
|
|
9123
|
+
state,
|
|
9124
|
+
markerOffset,
|
|
9125
|
+
blockLength,
|
|
9126
|
+
counter,
|
|
9127
|
+
...frameSizeFound ? { aiFrameWidth, aiFrameHeight } : {},
|
|
9128
|
+
boxes
|
|
9129
|
+
};
|
|
9130
|
+
}
|
|
9131
|
+
function hasStandardMarker(raw, offset) {
|
|
9132
|
+
if (raw.length < offset + MARKER_LENGTH) return false;
|
|
9133
|
+
return raw[offset] === 255 && raw[offset + 2] === 0 && raw[offset + 3] === 1 && raw[offset + 4] === 11 && raw[offset + 5] === 0 && raw[offset + 6] === 1 && raw[offset + 7] === 8;
|
|
9134
|
+
}
|
|
9135
|
+
|
|
9136
|
+
// src/reolink/baichuan/utils/encOptions.ts
|
|
9137
|
+
function buildEncOptions(list, channel) {
|
|
9138
|
+
const result = { channel };
|
|
9139
|
+
const main = aggregateByType(list, "mainStream");
|
|
9140
|
+
const sub = aggregateByType(list, "subStream");
|
|
9141
|
+
const third = aggregateByType(list, "thirdStream");
|
|
9142
|
+
if (main) result.mainStream = main;
|
|
9143
|
+
if (sub) result.subStream = sub;
|
|
9144
|
+
if (third) result.thirdStream = third;
|
|
9145
|
+
return result;
|
|
9146
|
+
}
|
|
9147
|
+
function aggregateByType(list, type) {
|
|
9148
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9149
|
+
for (const stream of list.streams) {
|
|
9150
|
+
for (const eb of stream.encodeTables) {
|
|
9151
|
+
if (eb.type !== type) continue;
|
|
9152
|
+
const mapped = mapEncodeTable(eb);
|
|
9153
|
+
if (!mapped) continue;
|
|
9154
|
+
const key = `${mapped.width}x${mapped.height}`;
|
|
9155
|
+
if (!seen.has(key)) seen.set(key, mapped);
|
|
9156
|
+
}
|
|
9157
|
+
}
|
|
9158
|
+
if (seen.size === 0) return void 0;
|
|
9159
|
+
return {
|
|
9160
|
+
type,
|
|
9161
|
+
resolutions: [...seen.values()],
|
|
9162
|
+
encoderTypes: ["vbr", "cbr"],
|
|
9163
|
+
encoderProfiles: ["high", "main", "baseline"]
|
|
9164
|
+
};
|
|
9165
|
+
}
|
|
9166
|
+
function mapEncodeTable(eb) {
|
|
9167
|
+
if (eb.width == null || eb.height == null) return void 0;
|
|
9168
|
+
const videoEncTypes = (eb.videoEncTypeList ?? (eb.videoEncType != null ? [eb.videoEncType] : [])).map(
|
|
9169
|
+
(t) => t === 0 ? "h264" : t === 1 ? "h265" : void 0
|
|
9170
|
+
).filter((t) => t !== void 0);
|
|
9171
|
+
return {
|
|
9172
|
+
width: eb.width,
|
|
9173
|
+
height: eb.height,
|
|
9174
|
+
videoEncTypes,
|
|
9175
|
+
...eb.defaultFramerate != null ? { defaultFramerate: eb.defaultFramerate } : {},
|
|
9176
|
+
...eb.defaultBitrate != null ? { defaultBitrate: eb.defaultBitrate } : {},
|
|
9177
|
+
...eb.defaultGop != null ? { defaultGop: eb.defaultGop } : {},
|
|
9178
|
+
framerateOptions: eb.framerateTable ?? [],
|
|
9179
|
+
bitrateOptions: eb.bitrateTable ?? []
|
|
9180
|
+
};
|
|
9181
|
+
}
|
|
9182
|
+
|
|
8592
9183
|
// src/reolink/baichuan/utils/events.ts
|
|
8593
9184
|
var mapToSimpleEvent = (event) => {
|
|
8594
9185
|
const timestamp = event.timestamp ?? Date.now();
|
|
@@ -10688,6 +11279,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10688
11279
|
sessionGuardIntervalTimer;
|
|
10689
11280
|
simpleEventListeners = /* @__PURE__ */ new Set();
|
|
10690
11281
|
simpleEventSubscribed = false;
|
|
11282
|
+
// Detection events are sourced from BcMedia additionalHeader on active video
|
|
11283
|
+
// streams. Unlike simpleEvent, no Baichuan subscribe command is needed — the
|
|
11284
|
+
// data flows whenever a stream is open. Active streams register themselves via
|
|
11285
|
+
// _registerVideoStreamForDetection (called from BaichuanVideoStream.start).
|
|
11286
|
+
detectionEventListeners = /* @__PURE__ */ new Set();
|
|
11287
|
+
detectionEventStreamHooks = /* @__PURE__ */ new Map();
|
|
11288
|
+
// Auto-managed substream for `onObjectDetections` listeners. Reference-counted
|
|
11289
|
+
// by the listener set: the substream is opened on the first listener and torn
|
|
11290
|
+
// down with the last one. Mirrors `onSimpleEvent`'s subscribe/unsubscribe
|
|
11291
|
+
// lifecycle so a caller never has to manage a video stream just to read AI
|
|
11292
|
+
// detections.
|
|
11293
|
+
objectDetectionListeners = /* @__PURE__ */ new Set();
|
|
11294
|
+
objectDetectionStream;
|
|
11295
|
+
objectDetectionStreamStartInFlight;
|
|
11296
|
+
objectDetectionInternalListener;
|
|
10691
11297
|
simpleEventSubscribeInFlight;
|
|
10692
11298
|
simpleEventUnsubscribeInFlight;
|
|
10693
11299
|
simpleEventResubscribeTimer;
|
|
@@ -12123,6 +12729,205 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12123
12729
|
}
|
|
12124
12730
|
}
|
|
12125
12731
|
}
|
|
12732
|
+
/**
|
|
12733
|
+
* Subscribe to per-frame detection events sourced from the BcMedia
|
|
12734
|
+
* `additionalHeader` block on active video streams.
|
|
12735
|
+
*
|
|
12736
|
+
* Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
|
|
12737
|
+
* one event fires for every I-frame / P-frame that carries an overlay block.
|
|
12738
|
+
* Coordinates are reported in normalized [0, 1] fractions of the source
|
|
12739
|
+
* frame, so the same box renders correctly on mainStream, subStream, and
|
|
12740
|
+
* externStream.
|
|
12741
|
+
*
|
|
12742
|
+
* Unlike `onSimpleEvent`, no Baichuan subscribe command is involved — events
|
|
12743
|
+
* only flow while a video stream is open. The library hooks every
|
|
12744
|
+
* `BaichuanVideoStream` created via this API for the listener's lifetime.
|
|
12745
|
+
*/
|
|
12746
|
+
onDetection(callback) {
|
|
12747
|
+
this.detectionEventListeners.add(callback);
|
|
12748
|
+
}
|
|
12749
|
+
/**
|
|
12750
|
+
* Remove a single detection callback, or all of them if `callback` is omitted.
|
|
12751
|
+
*/
|
|
12752
|
+
offDetection(callback) {
|
|
12753
|
+
if (callback) {
|
|
12754
|
+
this.detectionEventListeners.delete(callback);
|
|
12755
|
+
} else {
|
|
12756
|
+
this.detectionEventListeners.clear();
|
|
12757
|
+
}
|
|
12758
|
+
}
|
|
12759
|
+
/**
|
|
12760
|
+
* Subscribe to AI object detections (people / vehicle / animal / face boxes
|
|
12761
|
+
* with class label and confidence) without managing a video stream yourself.
|
|
12762
|
+
*
|
|
12763
|
+
* Mirrors {@link onSimpleEvent} end-to-end: the API opens a dedicated
|
|
12764
|
+
* substream behind the scenes on the first listener, forwards every box-bearing
|
|
12765
|
+
* `additionalHeader` to your callback, and tears the stream down when the last
|
|
12766
|
+
* listener unsubscribes. The substream is the lightest profile (typically
|
|
12767
|
+
* 640×360) so the additional bandwidth/CPU overhead is minimal.
|
|
12768
|
+
*
|
|
12769
|
+
* Each event carries normalized `[0, 1]` box coordinates, a class label, and
|
|
12770
|
+
* a confidence score — render-ready without further conversion.
|
|
12771
|
+
*/
|
|
12772
|
+
async onObjectDetections(callback) {
|
|
12773
|
+
this.objectDetectionListeners.add(callback);
|
|
12774
|
+
this.logger.debug?.(
|
|
12775
|
+
`[ReolinkBaichuanApi] onObjectDetections: registering listener (total=${this.objectDetectionListeners.size})`
|
|
12776
|
+
);
|
|
12777
|
+
await this.ensureObjectDetectionStream();
|
|
12778
|
+
}
|
|
12779
|
+
/**
|
|
12780
|
+
* Remove one detection callback, or all of them if `callback` is omitted.
|
|
12781
|
+
* When the last listener is removed the auto-managed substream is closed.
|
|
12782
|
+
*/
|
|
12783
|
+
async offObjectDetections(callback) {
|
|
12784
|
+
if (callback) {
|
|
12785
|
+
this.objectDetectionListeners.delete(callback);
|
|
12786
|
+
} else {
|
|
12787
|
+
this.objectDetectionListeners.clear();
|
|
12788
|
+
}
|
|
12789
|
+
if (this.objectDetectionListeners.size === 0) {
|
|
12790
|
+
await this.tearDownObjectDetectionStream();
|
|
12791
|
+
}
|
|
12792
|
+
}
|
|
12793
|
+
async ensureObjectDetectionStream() {
|
|
12794
|
+
if (this.objectDetectionStream) return;
|
|
12795
|
+
if (this.objectDetectionStreamStartInFlight) {
|
|
12796
|
+
await this.objectDetectionStreamStartInFlight;
|
|
12797
|
+
return;
|
|
12798
|
+
}
|
|
12799
|
+
this.objectDetectionStreamStartInFlight = (async () => {
|
|
12800
|
+
const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-HGPU2MZ3.js");
|
|
12801
|
+
const sessionKey = `live:object-detections:ch0:sub`;
|
|
12802
|
+
const dedicated = await this.createDedicatedSession(sessionKey);
|
|
12803
|
+
const stream = new BaichuanVideoStream2({
|
|
12804
|
+
client: dedicated.client,
|
|
12805
|
+
api: this,
|
|
12806
|
+
channel: 0,
|
|
12807
|
+
profile: "sub",
|
|
12808
|
+
logger: this.logger
|
|
12809
|
+
});
|
|
12810
|
+
this.objectDetectionInternalListener = (event) => {
|
|
12811
|
+
for (const cb of this.objectDetectionListeners) {
|
|
12812
|
+
try {
|
|
12813
|
+
void Promise.resolve(cb(event)).catch((e) => {
|
|
12814
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12815
|
+
this.logger,
|
|
12816
|
+
"[ReolinkBaichuanApi] onObjectDetections handler error",
|
|
12817
|
+
formatErrorForLog(e)
|
|
12818
|
+
);
|
|
12819
|
+
});
|
|
12820
|
+
} catch (e) {
|
|
12821
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12822
|
+
this.logger,
|
|
12823
|
+
"[ReolinkBaichuanApi] onObjectDetections handler error",
|
|
12824
|
+
formatErrorForLog(e)
|
|
12825
|
+
);
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12828
|
+
};
|
|
12829
|
+
this.detectionEventListeners.add(this.objectDetectionInternalListener);
|
|
12830
|
+
try {
|
|
12831
|
+
await stream.start();
|
|
12832
|
+
} catch (e) {
|
|
12833
|
+
if (this.objectDetectionInternalListener) {
|
|
12834
|
+
this.detectionEventListeners.delete(
|
|
12835
|
+
this.objectDetectionInternalListener
|
|
12836
|
+
);
|
|
12837
|
+
this.objectDetectionInternalListener = void 0;
|
|
12838
|
+
}
|
|
12839
|
+
await dedicated.release().catch(() => {
|
|
12840
|
+
});
|
|
12841
|
+
throw e;
|
|
12842
|
+
}
|
|
12843
|
+
this.objectDetectionStream = {
|
|
12844
|
+
stop: () => stream.stop(),
|
|
12845
|
+
release: () => dedicated.release()
|
|
12846
|
+
};
|
|
12847
|
+
this.logger.debug?.(
|
|
12848
|
+
`[ReolinkBaichuanApi] onObjectDetections: substream started (key=${sessionKey})`
|
|
12849
|
+
);
|
|
12850
|
+
})();
|
|
12851
|
+
try {
|
|
12852
|
+
await this.objectDetectionStreamStartInFlight;
|
|
12853
|
+
} finally {
|
|
12854
|
+
this.objectDetectionStreamStartInFlight = void 0;
|
|
12855
|
+
}
|
|
12856
|
+
}
|
|
12857
|
+
async tearDownObjectDetectionStream() {
|
|
12858
|
+
const handle = this.objectDetectionStream;
|
|
12859
|
+
this.objectDetectionStream = void 0;
|
|
12860
|
+
if (this.objectDetectionInternalListener) {
|
|
12861
|
+
this.detectionEventListeners.delete(this.objectDetectionInternalListener);
|
|
12862
|
+
this.objectDetectionInternalListener = void 0;
|
|
12863
|
+
}
|
|
12864
|
+
if (!handle) return;
|
|
12865
|
+
try {
|
|
12866
|
+
await handle.stop();
|
|
12867
|
+
} catch (e) {
|
|
12868
|
+
this.logger.debug?.(
|
|
12869
|
+
`[ReolinkBaichuanApi] onObjectDetections: stream stop error: ${formatErrorForLog(e)}`
|
|
12870
|
+
);
|
|
12871
|
+
}
|
|
12872
|
+
try {
|
|
12873
|
+
await handle.release();
|
|
12874
|
+
} catch (e) {
|
|
12875
|
+
this.logger.debug?.(
|
|
12876
|
+
`[ReolinkBaichuanApi] onObjectDetections: session release error: ${formatErrorForLog(e)}`
|
|
12877
|
+
);
|
|
12878
|
+
}
|
|
12879
|
+
this.logger.debug?.(
|
|
12880
|
+
`[ReolinkBaichuanApi] onObjectDetections: substream torn down`
|
|
12881
|
+
);
|
|
12882
|
+
}
|
|
12883
|
+
/**
|
|
12884
|
+
* Internal: invoked by BaichuanVideoStream when it starts so the API can hook
|
|
12885
|
+
* its `additionalHeader` event. Returns a teardown function the stream calls
|
|
12886
|
+
* on stop. Not intended for direct use by consumers.
|
|
12887
|
+
*/
|
|
12888
|
+
_registerVideoStreamForDetection(stream, context) {
|
|
12889
|
+
const listener = (info) => {
|
|
12890
|
+
if (this.detectionEventListeners.size === 0) return;
|
|
12891
|
+
const decoded = decodeDetectionHeader(info.raw, info.frameType);
|
|
12892
|
+
const event = {
|
|
12893
|
+
channel: context.channel,
|
|
12894
|
+
microseconds: info.microseconds,
|
|
12895
|
+
profile: context.profile,
|
|
12896
|
+
boxes: decoded.boxes,
|
|
12897
|
+
...info.frameWidth !== void 0 ? { frameWidth: info.frameWidth } : {},
|
|
12898
|
+
...info.frameHeight !== void 0 ? { frameHeight: info.frameHeight } : {},
|
|
12899
|
+
decodeState: decoded.state,
|
|
12900
|
+
rawHeader: info.raw
|
|
12901
|
+
};
|
|
12902
|
+
this.dispatchDetectionEvent(event);
|
|
12903
|
+
};
|
|
12904
|
+
stream.on("additionalHeader", listener);
|
|
12905
|
+
const teardown = () => {
|
|
12906
|
+
stream.off("additionalHeader", listener);
|
|
12907
|
+
this.detectionEventStreamHooks.delete(stream);
|
|
12908
|
+
};
|
|
12909
|
+
this.detectionEventStreamHooks.set(stream, teardown);
|
|
12910
|
+
return teardown;
|
|
12911
|
+
}
|
|
12912
|
+
dispatchDetectionEvent(evt) {
|
|
12913
|
+
for (const cb of this.detectionEventListeners) {
|
|
12914
|
+
try {
|
|
12915
|
+
void Promise.resolve(cb(evt)).catch((e) => {
|
|
12916
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12917
|
+
this.logger,
|
|
12918
|
+
"[ReolinkBaichuanApi] onDetection handler error",
|
|
12919
|
+
formatErrorForLog(e)
|
|
12920
|
+
);
|
|
12921
|
+
});
|
|
12922
|
+
} catch (e) {
|
|
12923
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12924
|
+
this.logger,
|
|
12925
|
+
"[ReolinkBaichuanApi] onDetection handler error",
|
|
12926
|
+
formatErrorForLog(e)
|
|
12927
|
+
);
|
|
12928
|
+
}
|
|
12929
|
+
}
|
|
12930
|
+
}
|
|
12126
12931
|
startSimpleEventResubscribeTimer() {
|
|
12127
12932
|
if (this.simpleEventResubscribeTimer) return;
|
|
12128
12933
|
if (this.simpleEventListeners.size === 0) return;
|
|
@@ -12505,6 +13310,9 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12505
13310
|
this.stopUdpSleepInference();
|
|
12506
13311
|
this.stopSimpleEventWatchdog();
|
|
12507
13312
|
this.stopSimpleEventResubscribeTimer();
|
|
13313
|
+
this.objectDetectionListeners.clear();
|
|
13314
|
+
await this.tearDownObjectDetectionStream().catch(() => {
|
|
13315
|
+
});
|
|
12508
13316
|
await this.cleanup();
|
|
12509
13317
|
await this.stopAllActiveStreams();
|
|
12510
13318
|
await this.cleanupSocketPool();
|
|
@@ -12852,6 +13660,53 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12852
13660
|
const xml = `<?xml version="1.0" encoding="UTF-8" ?><body><${tag} version="1.1"><enable>${params.enable ? 1 : 0}</enable></${tag}></body>`;
|
|
12853
13661
|
await this.sendXml({ cmdId: 36, payloadXml: xml });
|
|
12854
13662
|
}
|
|
13663
|
+
/**
|
|
13664
|
+
* Full port-config setter (cmd_id 36). Patches one or more of the six
|
|
13665
|
+
* service ports the camera serves — Server (Baichuan), HTTP, HTTPS,
|
|
13666
|
+
* RTSP, RTMP, ONVIF. Each entry takes an optional `port` (number) and
|
|
13667
|
+
* `enable` (boolean); fields the caller doesn't pass are left alone.
|
|
13668
|
+
*
|
|
13669
|
+
* Sends one block per port that has any field set, then issues a
|
|
13670
|
+
* single cmd_36 with the merged body. The camera accepts multiple
|
|
13671
|
+
* `<XxxPort>` siblings in the same payload.
|
|
13672
|
+
*
|
|
13673
|
+
* Wire format observed on E1 Zoom:
|
|
13674
|
+
*
|
|
13675
|
+
* <body>
|
|
13676
|
+
* <RtspPort version="1.1">
|
|
13677
|
+
* <rtspPort>554</rtspPort>
|
|
13678
|
+
* <enable>1</enable>
|
|
13679
|
+
* </RtspPort>
|
|
13680
|
+
* <HttpsPort version="1.1">
|
|
13681
|
+
* <enable>0</enable>
|
|
13682
|
+
* </HttpsPort>
|
|
13683
|
+
* ...
|
|
13684
|
+
* </body>
|
|
13685
|
+
*/
|
|
13686
|
+
async setPortConfig(patch) {
|
|
13687
|
+
const blocks = [];
|
|
13688
|
+
const append = (tag, portField, cfg) => {
|
|
13689
|
+
if (!cfg) return;
|
|
13690
|
+
if (cfg.port === void 0 && cfg.enable === void 0) return;
|
|
13691
|
+
const inner = [];
|
|
13692
|
+
if (cfg.port !== void 0) {
|
|
13693
|
+
inner.push(`<${portField}>${cfg.port}</${portField}>`);
|
|
13694
|
+
}
|
|
13695
|
+
if (cfg.enable !== void 0) {
|
|
13696
|
+
inner.push(`<enable>${cfg.enable ? 1 : 0}</enable>`);
|
|
13697
|
+
}
|
|
13698
|
+
blocks.push(`<${tag} version="1.1">${inner.join("")}</${tag}>`);
|
|
13699
|
+
};
|
|
13700
|
+
append("ServerPort", "serverPort", patch.server);
|
|
13701
|
+
append("HttpPort", "httpPort", patch.http);
|
|
13702
|
+
append("HttpsPort", "httpsPort", patch.https);
|
|
13703
|
+
append("RtspPort", "rtspPort", patch.rtsp);
|
|
13704
|
+
append("RtmpPort", "rtmpPort", patch.rtmp);
|
|
13705
|
+
append("OnvifPort", "onvifPort", patch.onvif);
|
|
13706
|
+
if (blocks.length === 0) return;
|
|
13707
|
+
const xml = `<?xml version="1.0" encoding="UTF-8" ?><body>${blocks.join("")}</body>`;
|
|
13708
|
+
await this.sendXml({ cmdId: 36, payloadXml: xml });
|
|
13709
|
+
}
|
|
12855
13710
|
/** GetDevInfo via Baichuan: host cmd_id 80, channel cmd_id 318 */
|
|
12856
13711
|
async getInfo(channel, options) {
|
|
12857
13712
|
const req = { cmdId: channel == null ? 80 : 318 };
|
|
@@ -16536,6 +17391,27 @@ ${stderr}`)
|
|
|
16536
17391
|
);
|
|
16537
17392
|
}
|
|
16538
17393
|
}
|
|
17394
|
+
async gotoPtzPreset(arg1, arg2) {
|
|
17395
|
+
const ch = arg2 === void 0 ? this.normalizeChannel(void 0) : this.normalizeChannel(arg1);
|
|
17396
|
+
const presetId = arg2 === void 0 ? arg1 : arg2;
|
|
17397
|
+
const channelId = ch;
|
|
17398
|
+
const payloadXml = buildPtzPresetXmlV2(channelId, presetId, "toPos");
|
|
17399
|
+
const extensionXml = buildChannelExtensionXml(channelId);
|
|
17400
|
+
const frame = await this.client.sendFrame({
|
|
17401
|
+
cmdId: BC_CMD_ID_PTZ_CONTROL_PRESET,
|
|
17402
|
+
channel: ch,
|
|
17403
|
+
channelIdOverride: channelId,
|
|
17404
|
+
extensionXml,
|
|
17405
|
+
payloadXml,
|
|
17406
|
+
messageClass: BC_CLASS_MODERN_24,
|
|
17407
|
+
streamType: 0
|
|
17408
|
+
});
|
|
17409
|
+
if (frame.header.responseCode !== 200) {
|
|
17410
|
+
throw new Error(
|
|
17411
|
+
`PTZ goto preset rejected (response_code ${frame.header.responseCode})`
|
|
17412
|
+
);
|
|
17413
|
+
}
|
|
17414
|
+
}
|
|
16539
17415
|
async deletePtzPreset(arg1, arg2) {
|
|
16540
17416
|
const ch = arg2 === void 0 ? this.normalizeChannel(void 0) : this.normalizeChannel(arg1);
|
|
16541
17417
|
const presetId = arg2 === void 0 ? arg1 : arg2;
|
|
@@ -17187,22 +18063,62 @@ ${stderr}`)
|
|
|
17187
18063
|
const channel = typeof arg1 === "number" ? arg1 : arg3;
|
|
17188
18064
|
const enabled = typeof arg1 === "number" ? arg2 : arg1;
|
|
17189
18065
|
const sensitivity = typeof arg1 === "number" ? arg3 : arg2;
|
|
17190
|
-
|
|
18066
|
+
return await this.setMotionAlarmFull({
|
|
18067
|
+
...channel !== void 0 ? { channel } : {},
|
|
18068
|
+
enabled,
|
|
18069
|
+
...sensitivity !== void 0 ? { sensitivity } : {}
|
|
18070
|
+
});
|
|
18071
|
+
}
|
|
18072
|
+
/**
|
|
18073
|
+
* Set motion alarm with full control, including the detection-zone grid.
|
|
18074
|
+
*
|
|
18075
|
+
* Wire format observed on E1 Zoom (cmd_id=47 SetMdAlarm body):
|
|
18076
|
+
*
|
|
18077
|
+
* <MD version="1.1">
|
|
18078
|
+
* <channelId>0</channelId>
|
|
18079
|
+
* <enable>1</enable>
|
|
18080
|
+
* <usepir>0</usepir>
|
|
18081
|
+
* <width>60</width> <height>33</height>
|
|
18082
|
+
* <scope>
|
|
18083
|
+
* <columns>96</columns> <rows>64</rows>
|
|
18084
|
+
* <valueTable>{base64 6144-bit bitmap}</valueTable>
|
|
18085
|
+
* </scope>
|
|
18086
|
+
* ... other camera-specific fields ...
|
|
18087
|
+
* </MD>
|
|
18088
|
+
*
|
|
18089
|
+
* We do a read-modify-write of the GET response so any camera-specific
|
|
18090
|
+
* extension fields are preserved untouched. Pass `valueTable` to update
|
|
18091
|
+
* the detection zone — see `encodeMotionScopeBitmap` for the bitmap layout.
|
|
18092
|
+
*
|
|
18093
|
+
* @param channel - 0-based channel
|
|
18094
|
+
* @param enabled - toggle motion detection on/off (optional)
|
|
18095
|
+
* @param sensitivity - 0-50, higher = more sensitive (optional)
|
|
18096
|
+
* @param valueTable - base64-encoded grid bitmap; size must match
|
|
18097
|
+
* `<scope><columns>×<rows></scope>` from the GET (optional)
|
|
18098
|
+
*/
|
|
18099
|
+
async setMotionAlarmFull(opts) {
|
|
18100
|
+
const ch = this.normalizeChannel(opts.channel);
|
|
17191
18101
|
const currentXml = await this.sendXml({
|
|
17192
18102
|
cmdId: BC_CMD_ID_GET_MOTION_ALARM,
|
|
17193
18103
|
channel: ch
|
|
17194
18104
|
});
|
|
17195
18105
|
let modifiedXml = currentXml;
|
|
17196
|
-
if (enabled !== void 0) {
|
|
18106
|
+
if (opts.enabled !== void 0) {
|
|
17197
18107
|
modifiedXml = modifiedXml.replace(
|
|
17198
18108
|
/<enable>[^<]*<\/enable>/,
|
|
17199
|
-
`<enable>${enabled ? "1" : "0"}</enable>`
|
|
18109
|
+
`<enable>${opts.enabled ? "1" : "0"}</enable>`
|
|
17200
18110
|
);
|
|
17201
18111
|
}
|
|
17202
|
-
if (sensitivity !== void 0) {
|
|
18112
|
+
if (opts.sensitivity !== void 0) {
|
|
17203
18113
|
modifiedXml = modifiedXml.replace(
|
|
17204
18114
|
/<sensitivityDefault>[^<]*<\/sensitivityDefault>/,
|
|
17205
|
-
`<sensitivityDefault>${sensitivity}</sensitivityDefault>`
|
|
18115
|
+
`<sensitivityDefault>${opts.sensitivity}</sensitivityDefault>`
|
|
18116
|
+
);
|
|
18117
|
+
}
|
|
18118
|
+
if (opts.valueTable !== void 0) {
|
|
18119
|
+
modifiedXml = modifiedXml.replace(
|
|
18120
|
+
/<valueTable>[^<]*<\/valueTable>/,
|
|
18121
|
+
`<valueTable>${opts.valueTable}</valueTable>`
|
|
17206
18122
|
);
|
|
17207
18123
|
}
|
|
17208
18124
|
await this.sendXml({
|
|
@@ -18477,7 +19393,7 @@ ${xml}`
|
|
|
18477
19393
|
* @returns Test results for all stream types and profiles
|
|
18478
19394
|
*/
|
|
18479
19395
|
async testChannelStreams(channel, logger) {
|
|
18480
|
-
const { testChannelStreams } = await import("./DiagnosticsTools-
|
|
19396
|
+
const { testChannelStreams } = await import("./DiagnosticsTools-BQOWJ23L.js");
|
|
18481
19397
|
return await testChannelStreams({
|
|
18482
19398
|
api: this,
|
|
18483
19399
|
channel: this.normalizeChannel(channel),
|
|
@@ -18493,7 +19409,7 @@ ${xml}`
|
|
|
18493
19409
|
* @returns Complete diagnostics for all channels and streams
|
|
18494
19410
|
*/
|
|
18495
19411
|
async collectMultifocalDiagnostics(logger) {
|
|
18496
|
-
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-
|
|
19412
|
+
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-BQOWJ23L.js");
|
|
18497
19413
|
return await collectMultifocalDiagnostics({
|
|
18498
19414
|
api: this,
|
|
18499
19415
|
logger
|
|
@@ -18546,12 +19462,24 @@ ${xml}`
|
|
|
18546
19462
|
}
|
|
18547
19463
|
/**
|
|
18548
19464
|
* SetEnc via Baichuan (cmdId=57). Read-modify-write — preserves
|
|
18549
|
-
* unspecified fields. Mirrors reolink_aio's `SetEnc
|
|
19465
|
+
* unspecified fields. Mirrors reolink_aio's `SetEnc` plus the additional
|
|
19466
|
+
* `width`/`height`/`encoderType`/`encoderProfile`/`gop`/`thirdStream`
|
|
19467
|
+
* fields observed in the official mobile app (see `pcap/resolution.pcapng`).
|
|
19468
|
+
*
|
|
19469
|
+
* Field meaning per stream:
|
|
19470
|
+
* - `audio` — 0/1 toggle
|
|
19471
|
+
* - `width`/`height` — resolution in pixels. Must be one of the
|
|
19472
|
+
* resolutions returned by {@link getStreamInfoList}.
|
|
19473
|
+
* - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
|
|
19474
|
+
* - `frameRate` — fps. Must match the table from `getStreamInfoList`.
|
|
19475
|
+
* - `videoEncType` — `"h264"` or `"h265"`
|
|
19476
|
+
* - `encoderType` — `"vbr"` or `"cbr"`
|
|
19477
|
+
* - `encoderProfile` — `"high"`, `"main"`, or `"baseline"`
|
|
19478
|
+
* - `gop` — keyframe interval in seconds (sets `<gop><cur>`)
|
|
18550
19479
|
*
|
|
18551
19480
|
* @param channel - Channel number (0-based)
|
|
18552
|
-
* @param patch - Fields to update
|
|
18553
|
-
*
|
|
18554
|
-
* to change.
|
|
19481
|
+
* @param patch - Fields to update. Pass only the fields you want to change;
|
|
19482
|
+
* everything else is preserved from the device's current configuration.
|
|
18555
19483
|
*/
|
|
18556
19484
|
async setEnc(channel, patch, options) {
|
|
18557
19485
|
const ch = this.normalizeChannel(channel);
|
|
@@ -18568,6 +19496,7 @@ ${xml}`
|
|
|
18568
19496
|
}
|
|
18569
19497
|
xml = applyStreamPatch(xml, "mainStream", patch.mainStream);
|
|
18570
19498
|
xml = applyStreamPatch(xml, "subStream", patch.subStream);
|
|
19499
|
+
xml = applyStreamPatch(xml, "thirdStream", patch.thirdStream);
|
|
18571
19500
|
await this.sendXml({
|
|
18572
19501
|
cmdId: BC_CMD_ID_SET_ENC,
|
|
18573
19502
|
channel: ch,
|
|
@@ -19175,6 +20104,71 @@ ${xml}`
|
|
|
19175
20104
|
`PCAP-derived settings GET failed for cmdId=${params.cmdId}: ${String(lastErr)}`
|
|
19176
20105
|
);
|
|
19177
20106
|
}
|
|
20107
|
+
/**
|
|
20108
|
+
* Update the OSD timestamp + channel-name overlay via cmd_id=45
|
|
20109
|
+
* (SetOsdDatetime). The schema is the same `<body><OsdDatetime>` +
|
|
20110
|
+
* `<OsdChannelName>` block returned by `getOsdDatetime` — we
|
|
20111
|
+
* read-modify-write so any extension fields the camera sent are
|
|
20112
|
+
* preserved.
|
|
20113
|
+
*
|
|
20114
|
+
* Position is in **camera pixel coordinates** (e.g. (1,1) for top-left,
|
|
20115
|
+
* not preset strings). Set `enable=0` to hide the overlay; the camera
|
|
20116
|
+
* keeps the stored position so re-enabling later restores it.
|
|
20117
|
+
*/
|
|
20118
|
+
async setOsdDatetime(channel, patch, options) {
|
|
20119
|
+
const ch = this.normalizeChannel(channel);
|
|
20120
|
+
const timeoutOpts = options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {};
|
|
20121
|
+
let xml = await this.sendPcapDerivedSettingsGetXml({
|
|
20122
|
+
cmdId: BC_CMD_ID_GET_OSD_DATETIME,
|
|
20123
|
+
channel: ch,
|
|
20124
|
+
...timeoutOpts
|
|
20125
|
+
});
|
|
20126
|
+
const patchBlock = (block, fields) => {
|
|
20127
|
+
const start = xml.indexOf(`<${block}`);
|
|
20128
|
+
if (start < 0) return;
|
|
20129
|
+
const end = xml.indexOf(`</${block}>`, start);
|
|
20130
|
+
if (end < 0) return;
|
|
20131
|
+
let body = xml.slice(start, end);
|
|
20132
|
+
for (const [tag, value] of Object.entries(fields)) {
|
|
20133
|
+
if (value === void 0) continue;
|
|
20134
|
+
const raw = typeof value === "boolean" ? value ? "1" : "0" : String(value);
|
|
20135
|
+
const escaped = raw.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
20136
|
+
if (body.includes(`<${tag}>`)) {
|
|
20137
|
+
body = body.replace(
|
|
20138
|
+
new RegExp(`<${tag}>[^<]*<\\/${tag}>`),
|
|
20139
|
+
`<${tag}>${escaped}</${tag}>`
|
|
20140
|
+
);
|
|
20141
|
+
} else {
|
|
20142
|
+
body += `<${tag}>${escaped}</${tag}>`;
|
|
20143
|
+
}
|
|
20144
|
+
}
|
|
20145
|
+
xml = xml.slice(0, start) + body + xml.slice(end);
|
|
20146
|
+
};
|
|
20147
|
+
if (patch.datetime) {
|
|
20148
|
+
patchBlock("OsdDatetime", {
|
|
20149
|
+
enable: patch.datetime.enable,
|
|
20150
|
+
topLeftX: patch.datetime.topLeftX,
|
|
20151
|
+
topLeftY: patch.datetime.topLeftY,
|
|
20152
|
+
language: patch.datetime.language
|
|
20153
|
+
});
|
|
20154
|
+
}
|
|
20155
|
+
if (patch.channelName) {
|
|
20156
|
+
patchBlock("OsdChannelName", {
|
|
20157
|
+
name: patch.channelName.name,
|
|
20158
|
+
enable: patch.channelName.enable,
|
|
20159
|
+
topLeftX: patch.channelName.topLeftX,
|
|
20160
|
+
topLeftY: patch.channelName.topLeftY,
|
|
20161
|
+
enWatermark: patch.channelName.enWatermark,
|
|
20162
|
+
enBgcolor: patch.channelName.enBgcolor
|
|
20163
|
+
});
|
|
20164
|
+
}
|
|
20165
|
+
await this.sendXml({
|
|
20166
|
+
cmdId: BC_CMD_ID_SET_OSD_DATETIME,
|
|
20167
|
+
channel: ch,
|
|
20168
|
+
payloadXml: ensureXmlHeader(xml),
|
|
20169
|
+
...timeoutOpts
|
|
20170
|
+
});
|
|
20171
|
+
}
|
|
19178
20172
|
async getOsdDatetime(channel, options) {
|
|
19179
20173
|
const rawXml = await this.sendPcapDerivedSettingsGetXml({
|
|
19180
20174
|
cmdId: BC_CMD_ID_GET_OSD_DATETIME,
|
|
@@ -19367,6 +20361,41 @@ ${xml}`
|
|
|
19367
20361
|
});
|
|
19368
20362
|
return { streams };
|
|
19369
20363
|
}
|
|
20364
|
+
/**
|
|
20365
|
+
* Return the set of values `setEnc` will accept on each stream of `channel`.
|
|
20366
|
+
* Aggregates `getStreamInfoList` (cmd_146) into a UI-friendly shape:
|
|
20367
|
+
* per-stream resolutions with their allowed codecs/framerates/bitrates plus
|
|
20368
|
+
* the enumerated encoder modes/profiles Reolink exposes.
|
|
20369
|
+
*
|
|
20370
|
+
* Useful for populating selectors and validating user input before calling
|
|
20371
|
+
* `setEnc` — picking an unsupported combination causes the camera to reject
|
|
20372
|
+
* the SET_ENC command (responseCode != 200).
|
|
20373
|
+
*/
|
|
20374
|
+
async getEncOptions(channel, options) {
|
|
20375
|
+
const list = await this.getStreamInfoList(channel, options);
|
|
20376
|
+
return buildEncOptions(list, channel);
|
|
20377
|
+
}
|
|
20378
|
+
/**
|
|
20379
|
+
* Read the camera's `<VersionInfo>` block (cmd_id=80). Returns the
|
|
20380
|
+
* friendly name, model code (e.g. `"E1 Zoom"`), serial number, firmware
|
|
20381
|
+
* version, hardware revision, build day, AI model bundle version, etc.
|
|
20382
|
+
*
|
|
20383
|
+
* This is the same info the Reolink mobile app shows in "About this
|
|
20384
|
+
* device" — distinct from `getSystemGeneral` (cmd_104) which carries
|
|
20385
|
+
* time/locale.
|
|
20386
|
+
*
|
|
20387
|
+
* No channel parameter: this command is device-global on NVRs/Hubs and
|
|
20388
|
+
* camera-global on standalone cameras. Pass an explicit channel via the
|
|
20389
|
+
* underlying `sendXml` only if a specific firmware demands it (none we've
|
|
20390
|
+
* tested do).
|
|
20391
|
+
*/
|
|
20392
|
+
async getVersionInfo(options) {
|
|
20393
|
+
const xml = await this.sendXml({
|
|
20394
|
+
cmdId: BC_CMD_ID_GET_VERSION_INFO,
|
|
20395
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20396
|
+
});
|
|
20397
|
+
return parseVersionInfo(xml);
|
|
20398
|
+
}
|
|
19370
20399
|
async getLedState(channel, options) {
|
|
19371
20400
|
const rawXml = await this.sendPcapDerivedSettingsGetXml({
|
|
19372
20401
|
cmdId: BC_CMD_ID_GET_LED_STATE,
|
|
@@ -19449,7 +20478,279 @@ ${xml}`
|
|
|
19449
20478
|
...channel != null ? { channel } : {},
|
|
19450
20479
|
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
19451
20480
|
});
|
|
19452
|
-
return
|
|
20481
|
+
return parseEmailTaskFromXml(xml);
|
|
20482
|
+
}
|
|
20483
|
+
/**
|
|
20484
|
+
* SetEmailTask via Baichuan (cmdId=216). Updates the email alarm schedule
|
|
20485
|
+
* (per-event-type 7×24 valueTable + master enable).
|
|
20486
|
+
*
|
|
20487
|
+
* Reolink expects the FULL `typeScheduleList` — pass the array from a prior
|
|
20488
|
+
* GET and only flip the entries you care about. Slots you don't track must
|
|
20489
|
+
* be sent back unchanged to avoid the camera dropping them.
|
|
20490
|
+
*/
|
|
20491
|
+
async setEmailTask(channel, task, options) {
|
|
20492
|
+
const ch = this.normalizeChannel(channel);
|
|
20493
|
+
const payloadXml = buildSetEmailTaskXml({ ...task, channelId: ch });
|
|
20494
|
+
await this.sendXml({
|
|
20495
|
+
cmdId: BC_CMD_ID_SET_EMAIL_TASK,
|
|
20496
|
+
channel: ch,
|
|
20497
|
+
payloadXml,
|
|
20498
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20499
|
+
});
|
|
20500
|
+
}
|
|
20501
|
+
/**
|
|
20502
|
+
* Convenience wrapper that patches the schedule of one or more trigger
|
|
20503
|
+
* types on the camera's EmailTask without touching the others.
|
|
20504
|
+
*
|
|
20505
|
+
* Pass a high-level schedule spec (`always` / `never` / explicit windows)
|
|
20506
|
+
* and the trigger types it should apply to. The method:
|
|
20507
|
+
*
|
|
20508
|
+
* 1. Reads the current EmailTask via GET (so we keep every existing slot).
|
|
20509
|
+
* 2. Builds the new `valueTable` once from `schedule`.
|
|
20510
|
+
* 3. Replaces the `valueTable` of every matching `type` in the list.
|
|
20511
|
+
* 4. Appends entries for any requested type not already present.
|
|
20512
|
+
* 5. Writes the merged list back via SET.
|
|
20513
|
+
*
|
|
20514
|
+
* Returns the list of types that were actually touched.
|
|
20515
|
+
*/
|
|
20516
|
+
async patchEmailSchedule(channel, spec, options) {
|
|
20517
|
+
const current = await this.getEmailTask(channel, options);
|
|
20518
|
+
const newValueTable = buildEmailScheduleValueTable(spec.schedule);
|
|
20519
|
+
const targetSet = new Set(spec.types);
|
|
20520
|
+
const touched = [];
|
|
20521
|
+
const updatedList = current.typeScheduleList.map((item) => {
|
|
20522
|
+
if (targetSet.has(item.type)) {
|
|
20523
|
+
touched.push(item.type);
|
|
20524
|
+
return { ...item, valueTable: newValueTable };
|
|
20525
|
+
}
|
|
20526
|
+
return item;
|
|
20527
|
+
});
|
|
20528
|
+
for (const t of spec.types) {
|
|
20529
|
+
if (!current.typeScheduleList.some((item) => item.type === t)) {
|
|
20530
|
+
updatedList.push({ type: t, valueTable: newValueTable });
|
|
20531
|
+
touched.push(t);
|
|
20532
|
+
}
|
|
20533
|
+
}
|
|
20534
|
+
await this.setEmailTask(
|
|
20535
|
+
channel,
|
|
20536
|
+
{
|
|
20537
|
+
channelId: current.channelId,
|
|
20538
|
+
enable: spec.enable ?? current.enable,
|
|
20539
|
+
typeScheduleList: updatedList
|
|
20540
|
+
},
|
|
20541
|
+
options
|
|
20542
|
+
);
|
|
20543
|
+
return { touchedTypes: touched };
|
|
20544
|
+
}
|
|
20545
|
+
// ====================================================================
|
|
20546
|
+
// Email server (cmdId 42/43/141), NTP (38/39), DST (106/107),
|
|
20547
|
+
// SystemGeneral SET (105), AutoReboot (100/101).
|
|
20548
|
+
// Schemas derived from Reolink Client pcap (2026-05-16).
|
|
20549
|
+
// ====================================================================
|
|
20550
|
+
/**
|
|
20551
|
+
* Read the SMTP email configuration (cmdId=42). Returns the full `<Email>`
|
|
20552
|
+
* block including capability hints (`senderMaxLen`, `pwdMaxLen`,
|
|
20553
|
+
* `emailAttachAbility`).
|
|
20554
|
+
*/
|
|
20555
|
+
async getEmail(options) {
|
|
20556
|
+
const xml = await this.sendXml({
|
|
20557
|
+
cmdId: BC_CMD_ID_GET_EMAIL,
|
|
20558
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20559
|
+
});
|
|
20560
|
+
return parseEmailConfigFromXml(xml);
|
|
20561
|
+
}
|
|
20562
|
+
/**
|
|
20563
|
+
* Patch the SMTP email configuration (cmdId=43). Reads the current config
|
|
20564
|
+
* first then merges the patch — Reolink rejects partial `<Email>` blocks.
|
|
20565
|
+
*/
|
|
20566
|
+
async setEmail(patch, options) {
|
|
20567
|
+
const current = await this.getEmail(options);
|
|
20568
|
+
const payloadXml = buildSetEmailXml(current, patch);
|
|
20569
|
+
await this.sendXml({
|
|
20570
|
+
cmdId: BC_CMD_ID_SET_EMAIL,
|
|
20571
|
+
payloadXml,
|
|
20572
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20573
|
+
});
|
|
20574
|
+
}
|
|
20575
|
+
/**
|
|
20576
|
+
* Send a test email using either the current config or an override patch
|
|
20577
|
+
* (cmdId=141). Returns true when the camera reports 200 (test succeeded),
|
|
20578
|
+
* false when it reports 482 (test failed — server unreachable / bad creds).
|
|
20579
|
+
* Other non-200 codes propagate as exceptions via `sendXml`.
|
|
20580
|
+
*/
|
|
20581
|
+
async testEmail(patch, options) {
|
|
20582
|
+
const current = await this.getEmail(options);
|
|
20583
|
+
const payloadXml = buildSetEmailXml(current, patch ?? {});
|
|
20584
|
+
const timeoutMs = options?.timeoutMs ?? 6e4;
|
|
20585
|
+
try {
|
|
20586
|
+
await this.sendXml({
|
|
20587
|
+
cmdId: BC_CMD_ID_TEST_EMAIL,
|
|
20588
|
+
payloadXml,
|
|
20589
|
+
timeoutMs
|
|
20590
|
+
});
|
|
20591
|
+
return true;
|
|
20592
|
+
} catch (err) {
|
|
20593
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20594
|
+
if (msg.includes("response_code 482") || msg.includes("response_code=482")) {
|
|
20595
|
+
return false;
|
|
20596
|
+
}
|
|
20597
|
+
throw err;
|
|
20598
|
+
}
|
|
20599
|
+
}
|
|
20600
|
+
/**
|
|
20601
|
+
* Read the NTP server configuration (cmdId=38).
|
|
20602
|
+
*/
|
|
20603
|
+
async getNtp(options) {
|
|
20604
|
+
const xml = await this.sendXml({
|
|
20605
|
+
cmdId: BC_CMD_ID_GET_NTP,
|
|
20606
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20607
|
+
});
|
|
20608
|
+
return parseNtpConfigFromXml(xml);
|
|
20609
|
+
}
|
|
20610
|
+
/**
|
|
20611
|
+
* Patch the NTP server configuration (cmdId=39). Reads the current state
|
|
20612
|
+
* first and merges the patch — Reolink rejects partial `<Ntp>` blocks.
|
|
20613
|
+
*/
|
|
20614
|
+
async setNtp(patch, options) {
|
|
20615
|
+
const current = await this.getNtp(options);
|
|
20616
|
+
const payloadXml = buildSetNtpXml(current, patch);
|
|
20617
|
+
await this.sendXml({
|
|
20618
|
+
cmdId: BC_CMD_ID_SET_NTP,
|
|
20619
|
+
payloadXml,
|
|
20620
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20621
|
+
});
|
|
20622
|
+
}
|
|
20623
|
+
/**
|
|
20624
|
+
* Patch SystemGeneral (cmdId=105). Supports partial payloads: include only
|
|
20625
|
+
* the fields you want to change. By default the builder emits `<year>0</year>`
|
|
20626
|
+
* as the "do not set manual clock" marker; pass `manualTime` to actually
|
|
20627
|
+
* set the date/time. Setting only `deviceName` automatically uses the
|
|
20628
|
+
* Reolink Client's `deviceNameOnly=1` shape.
|
|
20629
|
+
*/
|
|
20630
|
+
async setSystemGeneral(patch, options) {
|
|
20631
|
+
const payloadXml = buildSetSystemGeneralXml(patch);
|
|
20632
|
+
await this.sendXml({
|
|
20633
|
+
cmdId: BC_CMD_ID_SET_SYSTEM_GENERAL,
|
|
20634
|
+
payloadXml,
|
|
20635
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20636
|
+
});
|
|
20637
|
+
}
|
|
20638
|
+
/**
|
|
20639
|
+
* Read the Daylight Saving Time configuration (cmdId=106).
|
|
20640
|
+
*/
|
|
20641
|
+
async getDst(options) {
|
|
20642
|
+
const xml = await this.sendXml({
|
|
20643
|
+
cmdId: BC_CMD_ID_GET_DST,
|
|
20644
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20645
|
+
});
|
|
20646
|
+
return parseDstConfigFromXml(xml);
|
|
20647
|
+
}
|
|
20648
|
+
/**
|
|
20649
|
+
* Patch the DST configuration (cmdId=107). Reads the current state first
|
|
20650
|
+
* and merges the patch.
|
|
20651
|
+
*/
|
|
20652
|
+
async setDst(patch, options) {
|
|
20653
|
+
const current = await this.getDst(options);
|
|
20654
|
+
const payloadXml = buildSetDstXml(current, patch);
|
|
20655
|
+
await this.sendXml({
|
|
20656
|
+
cmdId: BC_CMD_ID_SET_DST,
|
|
20657
|
+
payloadXml,
|
|
20658
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20659
|
+
});
|
|
20660
|
+
}
|
|
20661
|
+
/**
|
|
20662
|
+
* Read the auto-reboot schedule (cmdId=101).
|
|
20663
|
+
*/
|
|
20664
|
+
async getAutoReboot(options) {
|
|
20665
|
+
const xml = await this.sendXml({
|
|
20666
|
+
cmdId: BC_CMD_ID_GET_AUTO_REBOOT,
|
|
20667
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20668
|
+
});
|
|
20669
|
+
return parseAutoRebootFromXml(xml);
|
|
20670
|
+
}
|
|
20671
|
+
/**
|
|
20672
|
+
* Patch the auto-reboot schedule (cmdId=100).
|
|
20673
|
+
*/
|
|
20674
|
+
async setAutoReboot(patch, options) {
|
|
20675
|
+
const current = await this.getAutoReboot(options);
|
|
20676
|
+
const payloadXml = buildSetAutoRebootXml(current, patch);
|
|
20677
|
+
await this.sendXml({
|
|
20678
|
+
cmdId: BC_CMD_ID_SET_AUTO_REBOOT,
|
|
20679
|
+
payloadXml,
|
|
20680
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20681
|
+
});
|
|
20682
|
+
}
|
|
20683
|
+
/**
|
|
20684
|
+
* High-level helper that configures the camera to deliver motion alerts via
|
|
20685
|
+
* SMTP to the local nodelink manager. Orchestrates `setEmail` + `setEmailTask`
|
|
20686
|
+
* in a single call so UI code can offer "auto-configure" without juggling
|
|
20687
|
+
* the underlying commands.
|
|
20688
|
+
*
|
|
20689
|
+
* Pass `runTest: true` to also send a test email (cmdId=141). Returns a
|
|
20690
|
+
* structured result describing each leg of the flow so the caller can show
|
|
20691
|
+
* granular feedback.
|
|
20692
|
+
*
|
|
20693
|
+
* @param params Auto-configuration parameters
|
|
20694
|
+
* @param channel Logical channel (default 0). Used for the EmailTask SET.
|
|
20695
|
+
*/
|
|
20696
|
+
async setupEmailPushToManager(params, channel, options) {
|
|
20697
|
+
const port = params.managerPort ?? 2525;
|
|
20698
|
+
const domain = params.domain ?? "nodelink.local";
|
|
20699
|
+
const recipient = `${params.recipientLocalPart}@${domain}`;
|
|
20700
|
+
const triggers = params.triggerTypes ?? ["MD", "people", "vehicle"];
|
|
20701
|
+
const attachmentType = params.attachmentType ?? "picture";
|
|
20702
|
+
const interval = params.interval ?? 30;
|
|
20703
|
+
const emailPatch = {
|
|
20704
|
+
smtpServer: params.managerHost,
|
|
20705
|
+
smtpPort: port,
|
|
20706
|
+
userName: params.authUsername ?? recipient,
|
|
20707
|
+
password: params.authPassword ?? "",
|
|
20708
|
+
address1: recipient,
|
|
20709
|
+
address2: "",
|
|
20710
|
+
address3: "",
|
|
20711
|
+
sendNickname: params.sendNickname ?? params.recipientLocalPart,
|
|
20712
|
+
attachment: attachmentType === "none" ? 0 : 1,
|
|
20713
|
+
attachmentType,
|
|
20714
|
+
textType: "withText",
|
|
20715
|
+
ssl: 0,
|
|
20716
|
+
interval
|
|
20717
|
+
};
|
|
20718
|
+
await this.setEmail(emailPatch, options);
|
|
20719
|
+
const fullWeekOn = "1".repeat(168);
|
|
20720
|
+
const current = await this.getEmailTask(channel, options);
|
|
20721
|
+
const triggerSet = new Set(triggers);
|
|
20722
|
+
const touched = [];
|
|
20723
|
+
const updatedList = current.typeScheduleList.map((item) => {
|
|
20724
|
+
if (triggerSet.has(item.type)) {
|
|
20725
|
+
touched.push(item.type);
|
|
20726
|
+
return { ...item, valueTable: fullWeekOn };
|
|
20727
|
+
}
|
|
20728
|
+
return item;
|
|
20729
|
+
});
|
|
20730
|
+
for (const t of triggers) {
|
|
20731
|
+
if (!current.typeScheduleList.some((item) => item.type === t)) {
|
|
20732
|
+
updatedList.push({ type: t, valueTable: fullWeekOn });
|
|
20733
|
+
touched.push(t);
|
|
20734
|
+
}
|
|
20735
|
+
}
|
|
20736
|
+
await this.setEmailTask(
|
|
20737
|
+
channel,
|
|
20738
|
+
{
|
|
20739
|
+
channelId: current.channelId,
|
|
20740
|
+
enable: 1,
|
|
20741
|
+
typeScheduleList: updatedList
|
|
20742
|
+
},
|
|
20743
|
+
options
|
|
20744
|
+
);
|
|
20745
|
+
const result = {
|
|
20746
|
+
setEmail: { applied: true },
|
|
20747
|
+
setEmailTask: { applied: true, touchedTypes: touched }
|
|
20748
|
+
};
|
|
20749
|
+
if (params.runTest) {
|
|
20750
|
+
const ok = await this.testEmail(emailPatch, options);
|
|
20751
|
+
result.testEmail = { success: ok };
|
|
20752
|
+
}
|
|
20753
|
+
return result;
|
|
19453
20754
|
}
|
|
19454
20755
|
/**
|
|
19455
20756
|
* Get siren-on-motion state via AudioTask (cmdId=232).
|
|
@@ -19718,7 +21019,7 @@ ${xml}`
|
|
|
19718
21019
|
cmdId: BC_CMD_ID_GET_SYSTEM_GENERAL,
|
|
19719
21020
|
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
19720
21021
|
});
|
|
19721
|
-
return
|
|
21022
|
+
return parseSystemGeneralFromXml(xml);
|
|
19722
21023
|
}
|
|
19723
21024
|
/**
|
|
19724
21025
|
* Get device support/capability flags.
|
|
@@ -22560,4 +23861,4 @@ export {
|
|
|
22560
23861
|
isTcpFailureThatShouldFallbackToUdp,
|
|
22561
23862
|
autoDetectDeviceType
|
|
22562
23863
|
};
|
|
22563
|
-
//# sourceMappingURL=chunk-
|
|
23864
|
+
//# sourceMappingURL=chunk-R5AJV73A.js.map
|