@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
package/dist/cli/rtsp-server.cjs
CHANGED
|
@@ -60,7 +60,7 @@ var init_urls = __esm({
|
|
|
60
60
|
function bcHeaderHasPayloadOffset(messageClass) {
|
|
61
61
|
return messageClass === BC_CLASS_MODERN_24 || messageClass === BC_CLASS_MODERN_24_ALT || messageClass === BC_CLASS_FILE_DOWNLOAD;
|
|
62
62
|
}
|
|
63
|
-
var BC_TCP_DEFAULT_PORT, BC_MAGIC, BC_MAGIC_REV, BC_XML_KEY, BC_AES_IV, BC_CLASS_LEGACY, BC_CLASS_MODERN_20, BC_CLASS_MODERN_24, BC_CLASS_MODERN_24_ALT, BC_CLASS_FILE_DOWNLOAD, BC_CMD_ID_LOGOUT, BC_CMD_ID_VIDEO, BC_CMD_ID_VIDEO_STOP, BC_CMD_ID_FILE_INFO_LIST_REPLAY, BC_CMD_ID_FILE_INFO_LIST_STOP, BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO, BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD, BC_CMD_ID_FILE_INFO_LIST_OPEN, BC_CMD_ID_FILE_INFO_LIST_GET, BC_CMD_ID_FILE_INFO_LIST_CLOSE, BC_CMD_ID_FIND_REC_VIDEO_OPEN, BC_CMD_ID_FIND_REC_VIDEO_GET, BC_CMD_ID_FIND_REC_VIDEO_CLOSE, BC_CMD_ID_TALK_ABILITY, BC_CMD_ID_TALK_RESET, BC_CMD_ID_TALK_CONFIG, BC_CMD_ID_TALK, BC_CMD_ID_PTZ_CONTROL, BC_CMD_ID_PTZ_CONTROL_PRESET, BC_CMD_ID_GET_PTZ_PRESET, BC_CMD_ID_GET_PTZ_POSITION, BC_CMD_ID_GET_ZOOM_FOCUS, BC_CMD_ID_SET_ZOOM_FOCUS, BC_CMD_ID_GET_BATTERY_INFO_LIST, BC_CMD_ID_GET_BATTERY_INFO, BC_CMD_ID_UDP_KEEP_ALIVE, BC_CMD_ID_GET_PIR_INFO, BC_CMD_ID_SET_PIR_INFO, BC_CMD_ID_GET_MOTION_ALARM, BC_CMD_ID_SET_MOTION_ALARM, BC_CMD_ID_GET_AI_ALARM, BC_CMD_ID_SET_AI_ALARM, BC_CMD_ID_GET_AUDIO_ALARM, BC_CMD_ID_AUDIO_ALARM_PLAY, BC_CMD_ID_GET_WHITE_LED, BC_CMD_ID_SET_WHITE_LED_STATE, BC_CMD_ID_SET_WHITE_LED_TASK, BC_CMD_ID_FLOODLIGHT_STATUS_LIST, BC_CMD_ID_ABILITY_INFO, BC_CMD_ID_SUPPORT, BC_CMD_ID_PING, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_RECORD, BC_CMD_ID_GET_HDD_INFO_LIST, BC_CMD_ID_GET_WIFI_SIGNAL, BC_CMD_ID_GET_WIFI, BC_CMD_ID_GET_ONLINE_USER_LIST, BC_CMD_ID_GET_DAY_RECORDS, BC_CMD_ID_GET_STREAM_INFO_LIST, BC_CMD_ID_GET_LED_STATE, BC_CMD_ID_GET_EMAIL_TASK, BC_CMD_ID_GET_AUDIO_TASK, BC_CMD_ID_GET_AUDIO_CFG, BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_TIMELAPSE_CFG, BC_CMD_ID_GET_AI_DENOISE, BC_CMD_ID_GET_KIT_AP_CFG, BC_CMD_ID_GET_REC_ENC_CFG, BC_CMD_ID_GET_ACCESS_USER_LIST, BC_CMD_ID_GET_SLEEP_STATE, BC_CMD_ID_GET_VIDEO_INPUT, BC_CMD_ID_GET_SYSTEM_GENERAL, BC_CMD_ID_GET_SUPPORT, BC_CMD_ID_GET_AI_CFG, BC_CMD_ID_SET_AI_CFG, BC_CMD_ID_GET_SIREN_STATUS, BC_CMD_ID_SET_AUDIO_TASK, BC_CMD_ID_SET_VIDEO_INPUT, BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_ENC, BC_CMD_ID_SET_ENC, BC_CMD_ID_GET_PRIVACY_MASK, BC_CMD_ID_SET_PRIVACY_MASK, BC_CMD_ID_SET_AI_DENOISE, BC_CMD_ID_SET_LED_STATE, BC_CMD_ID_SET_AUDIO_CFG, BC_CMD_ID_GET_AUTO_FOCUS, BC_CMD_ID_SET_AUTO_FOCUS, BC_CMD_ID_CMD_123, BC_CMD_ID_CMD_209, BC_CMD_ID_CMD_265, BC_CMD_ID_CMD_440, BC_CMD_ID_PUSH_VIDEO_INPUT, BC_CMD_ID_PUSH_SERIAL, BC_CMD_ID_PUSH_NET_INFO, BC_CMD_ID_PUSH_DINGDONG_LIST, BC_CMD_ID_PUSH_SLEEP_STATUS, BC_CMD_ID_PUSH_COORDINATE_POINT_LIST, BC_CMD_ID_DING_DONG_CTRL, BC_CMD_ID_GET_DING_DONG_LIST, BC_CMD_ID_DING_DONG_OPT, BC_CMD_ID_GET_DING_DONG_CFG, BC_CMD_ID_SET_DING_DONG_CFG, BC_CMD_ID_QUICK_REPLY_PLAY, BC_CMD_ID_GET_DING_DONG_SILENT, BC_CMD_ID_SET_DING_DONG_SILENT;
|
|
63
|
+
var BC_TCP_DEFAULT_PORT, BC_MAGIC, BC_MAGIC_REV, BC_XML_KEY, BC_AES_IV, BC_CLASS_LEGACY, BC_CLASS_MODERN_20, BC_CLASS_MODERN_24, BC_CLASS_MODERN_24_ALT, BC_CLASS_FILE_DOWNLOAD, BC_CMD_ID_LOGOUT, BC_CMD_ID_VIDEO, BC_CMD_ID_VIDEO_STOP, BC_CMD_ID_FILE_INFO_LIST_REPLAY, BC_CMD_ID_FILE_INFO_LIST_STOP, BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO, BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD, BC_CMD_ID_FILE_INFO_LIST_OPEN, BC_CMD_ID_FILE_INFO_LIST_GET, BC_CMD_ID_FILE_INFO_LIST_CLOSE, BC_CMD_ID_FIND_REC_VIDEO_OPEN, BC_CMD_ID_FIND_REC_VIDEO_GET, BC_CMD_ID_FIND_REC_VIDEO_CLOSE, BC_CMD_ID_TALK_ABILITY, BC_CMD_ID_TALK_RESET, BC_CMD_ID_TALK_CONFIG, BC_CMD_ID_TALK, BC_CMD_ID_PTZ_CONTROL, BC_CMD_ID_PTZ_CONTROL_PRESET, BC_CMD_ID_GET_PTZ_PRESET, BC_CMD_ID_GET_PTZ_POSITION, BC_CMD_ID_GET_ZOOM_FOCUS, BC_CMD_ID_SET_ZOOM_FOCUS, BC_CMD_ID_GET_BATTERY_INFO_LIST, BC_CMD_ID_GET_BATTERY_INFO, BC_CMD_ID_UDP_KEEP_ALIVE, BC_CMD_ID_GET_PIR_INFO, BC_CMD_ID_SET_PIR_INFO, BC_CMD_ID_GET_MOTION_ALARM, BC_CMD_ID_SET_MOTION_ALARM, BC_CMD_ID_GET_AI_ALARM, BC_CMD_ID_SET_AI_ALARM, BC_CMD_ID_GET_AUDIO_ALARM, BC_CMD_ID_AUDIO_ALARM_PLAY, BC_CMD_ID_GET_WHITE_LED, BC_CMD_ID_SET_WHITE_LED_STATE, BC_CMD_ID_SET_WHITE_LED_TASK, BC_CMD_ID_FLOODLIGHT_STATUS_LIST, BC_CMD_ID_ABILITY_INFO, BC_CMD_ID_SUPPORT, BC_CMD_ID_PING, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_SET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_VERSION_INFO, BC_CMD_ID_GET_RECORD, BC_CMD_ID_GET_HDD_INFO_LIST, BC_CMD_ID_GET_WIFI_SIGNAL, BC_CMD_ID_GET_WIFI, BC_CMD_ID_GET_ONLINE_USER_LIST, BC_CMD_ID_GET_DAY_RECORDS, BC_CMD_ID_GET_STREAM_INFO_LIST, BC_CMD_ID_GET_LED_STATE, BC_CMD_ID_GET_EMAIL_TASK, BC_CMD_ID_GET_AUDIO_TASK, BC_CMD_ID_GET_AUDIO_CFG, BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_TIMELAPSE_CFG, BC_CMD_ID_GET_AI_DENOISE, BC_CMD_ID_GET_KIT_AP_CFG, BC_CMD_ID_GET_REC_ENC_CFG, BC_CMD_ID_GET_ACCESS_USER_LIST, BC_CMD_ID_GET_SLEEP_STATE, BC_CMD_ID_GET_VIDEO_INPUT, BC_CMD_ID_GET_SYSTEM_GENERAL, BC_CMD_ID_GET_SUPPORT, BC_CMD_ID_GET_AI_CFG, BC_CMD_ID_SET_AI_CFG, BC_CMD_ID_GET_SIREN_STATUS, BC_CMD_ID_SET_AUDIO_TASK, BC_CMD_ID_SET_VIDEO_INPUT, BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_ENC, BC_CMD_ID_SET_ENC, BC_CMD_ID_GET_PRIVACY_MASK, BC_CMD_ID_SET_PRIVACY_MASK, BC_CMD_ID_SET_AI_DENOISE, BC_CMD_ID_SET_LED_STATE, BC_CMD_ID_SET_AUDIO_CFG, BC_CMD_ID_SET_EMAIL_TASK, BC_CMD_ID_GET_AUTO_FOCUS, BC_CMD_ID_SET_AUTO_FOCUS, BC_CMD_ID_GET_EMAIL, BC_CMD_ID_SET_EMAIL, BC_CMD_ID_TEST_EMAIL, BC_CMD_ID_GET_NTP, BC_CMD_ID_SET_NTP, BC_CMD_ID_SET_SYSTEM_GENERAL, BC_CMD_ID_GET_DST, BC_CMD_ID_SET_DST, BC_CMD_ID_GET_AUTO_REBOOT, BC_CMD_ID_SET_AUTO_REBOOT, BC_CMD_ID_CMD_123, BC_CMD_ID_CMD_209, BC_CMD_ID_CMD_265, BC_CMD_ID_CMD_440, BC_CMD_ID_PUSH_VIDEO_INPUT, BC_CMD_ID_PUSH_SERIAL, BC_CMD_ID_PUSH_NET_INFO, BC_CMD_ID_PUSH_DINGDONG_LIST, BC_CMD_ID_PUSH_SLEEP_STATUS, BC_CMD_ID_PUSH_COORDINATE_POINT_LIST, BC_CMD_ID_DING_DONG_CTRL, BC_CMD_ID_GET_DING_DONG_LIST, BC_CMD_ID_DING_DONG_OPT, BC_CMD_ID_GET_DING_DONG_CFG, BC_CMD_ID_SET_DING_DONG_CFG, BC_CMD_ID_QUICK_REPLY_PLAY, BC_CMD_ID_GET_DING_DONG_SILENT, BC_CMD_ID_SET_DING_DONG_SILENT;
|
|
64
64
|
var init_constants = __esm({
|
|
65
65
|
"src/protocol/constants.ts"() {
|
|
66
66
|
"use strict";
|
|
@@ -126,9 +126,11 @@ var init_constants = __esm({
|
|
|
126
126
|
BC_CMD_ID_PING = 93;
|
|
127
127
|
BC_CMD_ID_CHANNEL_INFO_ALL = 145;
|
|
128
128
|
BC_CMD_ID_GET_OSD_DATETIME = 44;
|
|
129
|
+
BC_CMD_ID_SET_OSD_DATETIME = 45;
|
|
129
130
|
BC_CMD_ID_GET_RECORD_CFG = 54;
|
|
130
131
|
BC_CMD_ID_GET_ABILITY_SUPPORT = 58;
|
|
131
132
|
BC_CMD_ID_GET_FTP_TASK = 70;
|
|
133
|
+
BC_CMD_ID_GET_VERSION_INFO = 80;
|
|
132
134
|
BC_CMD_ID_GET_RECORD = 81;
|
|
133
135
|
BC_CMD_ID_GET_HDD_INFO_LIST = 102;
|
|
134
136
|
BC_CMD_ID_GET_WIFI_SIGNAL = 115;
|
|
@@ -163,8 +165,19 @@ var init_constants = __esm({
|
|
|
163
165
|
BC_CMD_ID_SET_AI_DENOISE = 440;
|
|
164
166
|
BC_CMD_ID_SET_LED_STATE = 209;
|
|
165
167
|
BC_CMD_ID_SET_AUDIO_CFG = 265;
|
|
168
|
+
BC_CMD_ID_SET_EMAIL_TASK = 216;
|
|
166
169
|
BC_CMD_ID_GET_AUTO_FOCUS = 224;
|
|
167
170
|
BC_CMD_ID_SET_AUTO_FOCUS = 225;
|
|
171
|
+
BC_CMD_ID_GET_EMAIL = 42;
|
|
172
|
+
BC_CMD_ID_SET_EMAIL = 43;
|
|
173
|
+
BC_CMD_ID_TEST_EMAIL = 141;
|
|
174
|
+
BC_CMD_ID_GET_NTP = 38;
|
|
175
|
+
BC_CMD_ID_SET_NTP = 39;
|
|
176
|
+
BC_CMD_ID_SET_SYSTEM_GENERAL = 105;
|
|
177
|
+
BC_CMD_ID_GET_DST = 106;
|
|
178
|
+
BC_CMD_ID_SET_DST = 107;
|
|
179
|
+
BC_CMD_ID_GET_AUTO_REBOOT = 101;
|
|
180
|
+
BC_CMD_ID_SET_AUTO_REBOOT = 100;
|
|
168
181
|
BC_CMD_ID_CMD_123 = 123;
|
|
169
182
|
BC_CMD_ID_CMD_209 = 209;
|
|
170
183
|
BC_CMD_ID_CMD_265 = 265;
|
|
@@ -488,10 +501,15 @@ var init_BcMediaCodec = __esm({
|
|
|
488
501
|
strict;
|
|
489
502
|
amountSkipped = 0;
|
|
490
503
|
logger;
|
|
504
|
+
onUnknownChunk;
|
|
491
505
|
constructor(strict = false, logger) {
|
|
492
506
|
this.strict = strict;
|
|
493
507
|
this.logger = logger;
|
|
494
508
|
}
|
|
509
|
+
/** Register a listener that fires for every unknown chunk before recovery. */
|
|
510
|
+
setUnknownChunkListener(listener) {
|
|
511
|
+
this.onUnknownChunk = listener;
|
|
512
|
+
}
|
|
495
513
|
/**
|
|
496
514
|
* Push data into the codec buffer and try to parse complete BcMedia packets.
|
|
497
515
|
* Returns an array of complete BcMedia packets found.
|
|
@@ -552,6 +570,13 @@ var init_BcMediaCodec = __esm({
|
|
|
552
570
|
}
|
|
553
571
|
}
|
|
554
572
|
if (next > 0) {
|
|
573
|
+
if (this.onUnknownChunk) {
|
|
574
|
+
this.onUnknownChunk({
|
|
575
|
+
magic,
|
|
576
|
+
preview: Buffer.from(this.buffer.subarray(0, Math.min(256, next))),
|
|
577
|
+
skipped: next
|
|
578
|
+
});
|
|
579
|
+
}
|
|
555
580
|
this.amountSkipped += next;
|
|
556
581
|
this.buffer = this.buffer.subarray(next);
|
|
557
582
|
continue;
|
|
@@ -1414,6 +1439,10 @@ var init_BcMediaAnnexBDecoder = __esm({
|
|
|
1414
1439
|
});
|
|
1415
1440
|
|
|
1416
1441
|
// src/baichuan/stream/BaichuanVideoStream.ts
|
|
1442
|
+
var BaichuanVideoStream_exports = {};
|
|
1443
|
+
__export(BaichuanVideoStream_exports, {
|
|
1444
|
+
BaichuanVideoStream: () => BaichuanVideoStream
|
|
1445
|
+
});
|
|
1417
1446
|
function removeEmulationPreventionBytes(rbsp) {
|
|
1418
1447
|
const out = [];
|
|
1419
1448
|
for (let i = 0; i < rbsp.length; i++) {
|
|
@@ -1547,6 +1576,15 @@ var init_BaichuanVideoStream = __esm({
|
|
|
1547
1576
|
acceptAnyStreamType;
|
|
1548
1577
|
lockedChannelId;
|
|
1549
1578
|
bcMediaCodec;
|
|
1579
|
+
/**
|
|
1580
|
+
* Diagnostic-only accessor for the BcMedia codec. Used by tools that need to
|
|
1581
|
+
* inspect unknown chunks (for example to discover undocumented audio
|
|
1582
|
+
* sub-packets the parser currently skips). Not part of the supported public
|
|
1583
|
+
* surface — do not rely on it in application code.
|
|
1584
|
+
*/
|
|
1585
|
+
get _bcMediaCodec() {
|
|
1586
|
+
return this.bcMediaCodec;
|
|
1587
|
+
}
|
|
1550
1588
|
debugH264LogsLeft;
|
|
1551
1589
|
debugSavedSamples;
|
|
1552
1590
|
warnedNonAnnexBOnce = false;
|
|
@@ -1578,6 +1616,14 @@ var init_BaichuanVideoStream = __esm({
|
|
|
1578
1616
|
// Stateful AES decryptor for fragmented BcMedia packets (full_aes mode)
|
|
1579
1617
|
// In CFB mode, continuation frames must use the cipher state from previous frames.
|
|
1580
1618
|
aesStreamDecryptor = null;
|
|
1619
|
+
// Latest frame dimensions reported by BcMedia InfoV1/V2 packets.
|
|
1620
|
+
// Used to attach width/height context to the `additionalHeader` event so
|
|
1621
|
+
// consumers can normalize box coordinates to a fraction of the stream size.
|
|
1622
|
+
latestFrameWidth;
|
|
1623
|
+
latestFrameHeight;
|
|
1624
|
+
// Teardown returned by ReolinkBaichuanApi._registerVideoStreamForDetection.
|
|
1625
|
+
// Called from stop() to detach the detection bridge.
|
|
1626
|
+
detectionTeardown;
|
|
1581
1627
|
/**
|
|
1582
1628
|
* Pending startup error stashed when emitSafeError is called before any
|
|
1583
1629
|
* "error" listener is registered (e.g. camera returns 400 during start()).
|
|
@@ -2240,6 +2286,16 @@ var init_BaichuanVideoStream = __esm({
|
|
|
2240
2286
|
}
|
|
2241
2287
|
videoType = detectedCodec;
|
|
2242
2288
|
}
|
|
2289
|
+
if (media.additionalHeader && media.additionalHeader.length > 0) {
|
|
2290
|
+
this.emit("additionalHeader", {
|
|
2291
|
+
raw: media.additionalHeader,
|
|
2292
|
+
frameType: "Iframe",
|
|
2293
|
+
videoType,
|
|
2294
|
+
microseconds: media.microseconds,
|
|
2295
|
+
...this.latestFrameWidth !== void 0 ? { frameWidth: this.latestFrameWidth } : {},
|
|
2296
|
+
...this.latestFrameHeight !== void 0 ? { frameHeight: this.latestFrameHeight } : {}
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2243
2299
|
const annexBData = videoType === "H265" ? convertToAnnexB2(media.data) : convertToAnnexB(media.data);
|
|
2244
2300
|
const isKeyframe = true;
|
|
2245
2301
|
maybeCacheParamSets(annexBData, "Iframe", videoType);
|
|
@@ -2321,6 +2377,18 @@ var init_BaichuanVideoStream = __esm({
|
|
|
2321
2377
|
}
|
|
2322
2378
|
} else if (media.type === "Pframe") {
|
|
2323
2379
|
const chunk = media.data;
|
|
2380
|
+
if (media.additionalHeader && media.additionalHeader.length > 0) {
|
|
2381
|
+
const detected = detectVideoCodecFromNal(chunk);
|
|
2382
|
+
const videoTypeForHeader = detected ?? media.videoType;
|
|
2383
|
+
this.emit("additionalHeader", {
|
|
2384
|
+
raw: media.additionalHeader,
|
|
2385
|
+
frameType: "Pframe",
|
|
2386
|
+
videoType: videoTypeForHeader,
|
|
2387
|
+
microseconds: media.microseconds,
|
|
2388
|
+
...this.latestFrameWidth !== void 0 ? { frameWidth: this.latestFrameWidth } : {},
|
|
2389
|
+
...this.latestFrameHeight !== void 0 ? { frameHeight: this.latestFrameHeight } : {}
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2324
2392
|
let videoType = media.videoType;
|
|
2325
2393
|
const detectedCodec = detectVideoCodecFromNal(chunk);
|
|
2326
2394
|
if (detectedCodec && detectedCodec !== videoType) {
|
|
@@ -2363,6 +2431,8 @@ var init_BaichuanVideoStream = __esm({
|
|
|
2363
2431
|
this.emit("audioFrame", media.data);
|
|
2364
2432
|
}
|
|
2365
2433
|
if (media.type === "InfoV1" || media.type === "InfoV2") {
|
|
2434
|
+
if (media.videoWidth > 0) this.latestFrameWidth = media.videoWidth;
|
|
2435
|
+
if (media.videoHeight > 0) this.latestFrameHeight = media.videoHeight;
|
|
2366
2436
|
}
|
|
2367
2437
|
}
|
|
2368
2438
|
if (totalFramesReceived <= 10 || totalFramesReceived % 20 === 0 && (videoFramesEmitted > 0 || audioFramesEmitted > 0)) {
|
|
@@ -2382,6 +2452,12 @@ var init_BaichuanVideoStream = __esm({
|
|
|
2382
2452
|
this.client.on("push", this.videoFrameHandler);
|
|
2383
2453
|
this.active = true;
|
|
2384
2454
|
this.startWatchdog();
|
|
2455
|
+
if (this.api && typeof this.api._registerVideoStreamForDetection === "function") {
|
|
2456
|
+
this.detectionTeardown = this.api._registerVideoStreamForDetection(this, {
|
|
2457
|
+
channel: this.channel,
|
|
2458
|
+
profile: this.profile
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2385
2461
|
this.lastMediaAtMs = Date.now();
|
|
2386
2462
|
if (this.api) {
|
|
2387
2463
|
try {
|
|
@@ -2479,6 +2555,13 @@ var init_BaichuanVideoStream = __esm({
|
|
|
2479
2555
|
}
|
|
2480
2556
|
}
|
|
2481
2557
|
this.active = false;
|
|
2558
|
+
if (this.detectionTeardown) {
|
|
2559
|
+
try {
|
|
2560
|
+
this.detectionTeardown();
|
|
2561
|
+
} catch {
|
|
2562
|
+
}
|
|
2563
|
+
this.detectionTeardown = void 0;
|
|
2564
|
+
}
|
|
2482
2565
|
this.emit("close");
|
|
2483
2566
|
}
|
|
2484
2567
|
isActive() {
|
|
@@ -2714,6 +2797,15 @@ function applyXmlTagPatch(xml, tag, value) {
|
|
|
2714
2797
|
const re = new RegExp(`<${tag}>[^<]*</${tag}>`);
|
|
2715
2798
|
return xml.replace(re, `<${tag}>${v}</${tag}>`);
|
|
2716
2799
|
}
|
|
2800
|
+
function upsertXmlTag(xml, tag, value) {
|
|
2801
|
+
if (value === void 0) return xml;
|
|
2802
|
+
const v = typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
2803
|
+
const re = new RegExp(`<${tag}>[^<]*</${tag}>`);
|
|
2804
|
+
if (re.test(xml)) {
|
|
2805
|
+
return xml.replace(re, `<${tag}>${v}</${tag}>`);
|
|
2806
|
+
}
|
|
2807
|
+
return `${xml}<${tag}>${v}</${tag}>`;
|
|
2808
|
+
}
|
|
2717
2809
|
function patchNestedTag(xml, parent, child, value) {
|
|
2718
2810
|
if (value === void 0) return xml;
|
|
2719
2811
|
const v = typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
@@ -2729,6 +2821,15 @@ function applyStreamPatch(xml, streamTag, patch) {
|
|
|
2729
2821
|
);
|
|
2730
2822
|
return xml.replace(re, (_match, open, body, close) => {
|
|
2731
2823
|
let next = body;
|
|
2824
|
+
if (patch.audio !== void 0) {
|
|
2825
|
+
next = applyXmlTagPatch(next, "audio", patch.audio);
|
|
2826
|
+
}
|
|
2827
|
+
if (patch.width !== void 0) {
|
|
2828
|
+
next = applyXmlTagPatch(next, "width", patch.width);
|
|
2829
|
+
}
|
|
2830
|
+
if (patch.height !== void 0) {
|
|
2831
|
+
next = applyXmlTagPatch(next, "height", patch.height);
|
|
2832
|
+
}
|
|
2732
2833
|
if (patch.bitRate !== void 0) {
|
|
2733
2834
|
next = applyXmlTagPatch(next, "bitRate", patch.bitRate);
|
|
2734
2835
|
}
|
|
@@ -2740,6 +2841,23 @@ function applyStreamPatch(xml, streamTag, patch) {
|
|
|
2740
2841
|
const intVal = patch.videoEncType === "h265" ? 1 : 0;
|
|
2741
2842
|
next = applyXmlTagPatch(next, "videoEncType", intVal);
|
|
2742
2843
|
}
|
|
2844
|
+
if (patch.encoderType !== void 0) {
|
|
2845
|
+
next = upsertXmlTag(next, "encoderType", patch.encoderType);
|
|
2846
|
+
}
|
|
2847
|
+
if (patch.encoderProfile !== void 0) {
|
|
2848
|
+
next = upsertXmlTag(next, "encoderProfile", patch.encoderProfile);
|
|
2849
|
+
}
|
|
2850
|
+
if (patch.gop !== void 0) {
|
|
2851
|
+
const gopBlockRe = /(<gop[^>]*>)([\s\S]*?)(<\/gop>)/;
|
|
2852
|
+
if (gopBlockRe.test(next)) {
|
|
2853
|
+
next = next.replace(
|
|
2854
|
+
gopBlockRe,
|
|
2855
|
+
(_m, gOpen, gBody, gClose) => `${gOpen}${applyXmlTagPatch(gBody, "cur", patch.gop)}${gClose}`
|
|
2856
|
+
);
|
|
2857
|
+
} else {
|
|
2858
|
+
next = `${next}<gop><cur>${patch.gop}</cur></gop>`;
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2743
2861
|
return `${open}${next}${close}`;
|
|
2744
2862
|
});
|
|
2745
2863
|
}
|
|
@@ -12313,6 +12431,23 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
|
|
|
12313
12431
|
* even if the current client instance is idle/disconnected.
|
|
12314
12432
|
*/
|
|
12315
12433
|
static streamingRegistry = /* @__PURE__ */ new Map();
|
|
12434
|
+
/**
|
|
12435
|
+
* Per-device set of live BaichuanClient instances.
|
|
12436
|
+
*
|
|
12437
|
+
* Why: when a streaming client unsubscribes (e.g. RTSP grace timer expires
|
|
12438
|
+
* and SocketPool tears the streaming socket down), the global streaming
|
|
12439
|
+
* registry decrements but the GENERAL client of the same device has no
|
|
12440
|
+
* way of knowing — its idle-disconnect timer was last evaluated while
|
|
12441
|
+
* `isDeviceStreamingActive()` was still true (because the streaming socket
|
|
12442
|
+
* was still alive) and wasn't rescheduled. Without this registry the
|
|
12443
|
+
* general socket stays connected, the 60-second session-guard timer keeps
|
|
12444
|
+
* sending getOnlineUserList() to the camera, and a battery camera ends up
|
|
12445
|
+
* waking up every minute (issue #18).
|
|
12446
|
+
*
|
|
12447
|
+
* On streamingRegistry decrement-to-zero we walk this set and kick every
|
|
12448
|
+
* sibling's idle-disconnect timer so it can re-evaluate eligibility.
|
|
12449
|
+
*/
|
|
12450
|
+
static deviceClients = /* @__PURE__ */ new Map();
|
|
12316
12451
|
/**
|
|
12317
12452
|
* Per-host D2C_DISC backoff state that persists across client instance recreation.
|
|
12318
12453
|
*
|
|
@@ -12427,6 +12562,29 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
|
|
|
12427
12562
|
// AlarmEventList (cmdId=33) can be very chatty (often sent every second).
|
|
12428
12563
|
// Track last per-channel alarm state so we only emit on transitions.
|
|
12429
12564
|
alarmEventState = /* @__PURE__ */ new Map();
|
|
12565
|
+
/** Whether this instance is currently in BaichuanClient.deviceClients. */
|
|
12566
|
+
registeredInDeviceClients = false;
|
|
12567
|
+
registerInDeviceClients() {
|
|
12568
|
+
if (this.registeredInDeviceClients) return;
|
|
12569
|
+
const key = this.getDeviceRegistryKey();
|
|
12570
|
+
let set = _BaichuanClient.deviceClients.get(key);
|
|
12571
|
+
if (!set) {
|
|
12572
|
+
set = /* @__PURE__ */ new Set();
|
|
12573
|
+
_BaichuanClient.deviceClients.set(key, set);
|
|
12574
|
+
}
|
|
12575
|
+
set.add(this);
|
|
12576
|
+
this.registeredInDeviceClients = true;
|
|
12577
|
+
}
|
|
12578
|
+
unregisterFromDeviceClients() {
|
|
12579
|
+
if (!this.registeredInDeviceClients) return;
|
|
12580
|
+
const key = this.getDeviceRegistryKey();
|
|
12581
|
+
const set = _BaichuanClient.deviceClients.get(key);
|
|
12582
|
+
if (set) {
|
|
12583
|
+
set.delete(this);
|
|
12584
|
+
if (set.size === 0) _BaichuanClient.deviceClients.delete(key);
|
|
12585
|
+
}
|
|
12586
|
+
this.registeredInDeviceClients = false;
|
|
12587
|
+
}
|
|
12430
12588
|
constructor(options) {
|
|
12431
12589
|
super();
|
|
12432
12590
|
this.opts = options;
|
|
@@ -12441,6 +12599,7 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
|
|
|
12441
12599
|
code: err?.code
|
|
12442
12600
|
});
|
|
12443
12601
|
});
|
|
12602
|
+
this.registerInDeviceClients();
|
|
12444
12603
|
}
|
|
12445
12604
|
newSocketSessionId(transport) {
|
|
12446
12605
|
const short = (0, import_node_crypto2.randomUUID)().split("-")[0] ?? (0, import_node_crypto2.randomUUID)().slice(0, 8);
|
|
@@ -12697,6 +12856,18 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
|
|
|
12697
12856
|
activeStreamClients: nextCount
|
|
12698
12857
|
});
|
|
12699
12858
|
this.contributesToGlobalStreamingRegistry = shouldContribute;
|
|
12859
|
+
if (!shouldContribute && nextCount === 0) {
|
|
12860
|
+
const siblings = _BaichuanClient.deviceClients.get(key);
|
|
12861
|
+
if (siblings) {
|
|
12862
|
+
for (const sib of siblings) {
|
|
12863
|
+
if (sib === this) continue;
|
|
12864
|
+
try {
|
|
12865
|
+
sib.kickIdleDisconnectTimer();
|
|
12866
|
+
} catch {
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
}
|
|
12870
|
+
}
|
|
12700
12871
|
}
|
|
12701
12872
|
/**
|
|
12702
12873
|
* True if the device should be considered "awake" due to active streaming.
|
|
@@ -13161,6 +13332,7 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
|
|
|
13161
13332
|
`transport=tcp host=${this.opts.host} port=${port}${sid ? ` sid=${sid}` : ""}${remote ? ` remote=${remote}` : ""}${peer ? ` peer=${peer}` : ""}`
|
|
13162
13333
|
);
|
|
13163
13334
|
this.logSocketState("tcp_connected");
|
|
13335
|
+
this.registerInDeviceClients();
|
|
13164
13336
|
this.startKeepAlive();
|
|
13165
13337
|
this.kickIdleDisconnectTimer();
|
|
13166
13338
|
}
|
|
@@ -13477,6 +13649,7 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
|
|
|
13477
13649
|
this.logDebug("udp_close_error", e);
|
|
13478
13650
|
}
|
|
13479
13651
|
}
|
|
13652
|
+
this.unregisterFromDeviceClients();
|
|
13480
13653
|
}
|
|
13481
13654
|
handleFrame(frame) {
|
|
13482
13655
|
const now = Date.now();
|
|
@@ -15528,6 +15701,309 @@ function parseXmlFragmentToJson(xml) {
|
|
|
15528
15701
|
return parsed.root;
|
|
15529
15702
|
}
|
|
15530
15703
|
|
|
15704
|
+
// src/reolink/baichuan/utils/email.ts
|
|
15705
|
+
init_xml();
|
|
15706
|
+
var parseInt01 = (text) => {
|
|
15707
|
+
if (text === void 0) return void 0;
|
|
15708
|
+
return text.trim() === "1" ? 1 : 0;
|
|
15709
|
+
};
|
|
15710
|
+
var parseNumberSafe = (text) => {
|
|
15711
|
+
if (text === void 0) return void 0;
|
|
15712
|
+
const n = Number(text);
|
|
15713
|
+
return Number.isFinite(n) ? n : void 0;
|
|
15714
|
+
};
|
|
15715
|
+
var VALID_ATTACHMENT_TYPES = [
|
|
15716
|
+
"picture",
|
|
15717
|
+
"video",
|
|
15718
|
+
"none"
|
|
15719
|
+
];
|
|
15720
|
+
var VALID_TEXT_TYPES = ["withText", "noText"];
|
|
15721
|
+
var normalizeAttachmentType = (text) => {
|
|
15722
|
+
const t = (text ?? "").trim();
|
|
15723
|
+
return VALID_ATTACHMENT_TYPES.includes(t) ? t : "picture";
|
|
15724
|
+
};
|
|
15725
|
+
var normalizeTextType = (text) => {
|
|
15726
|
+
const t = (text ?? "").trim();
|
|
15727
|
+
return VALID_TEXT_TYPES.includes(t) ? t : "withText";
|
|
15728
|
+
};
|
|
15729
|
+
function parseEmailConfigFromXml(xml) {
|
|
15730
|
+
const cfg = {
|
|
15731
|
+
smtpServer: getXmlText(xml, "smtpServer") ?? "",
|
|
15732
|
+
userName: getXmlText(xml, "userName") ?? "",
|
|
15733
|
+
password: getXmlText(xml, "password") ?? "",
|
|
15734
|
+
address1: getXmlText(xml, "address1") ?? "",
|
|
15735
|
+
address2: getXmlText(xml, "address2") ?? "",
|
|
15736
|
+
address3: getXmlText(xml, "address3") ?? "",
|
|
15737
|
+
smtpPort: parseNumberSafe(getXmlText(xml, "smtpPort")) ?? 0,
|
|
15738
|
+
sendNickname: getXmlText(xml, "sendNickname") ?? "",
|
|
15739
|
+
attachment: parseInt01(getXmlText(xml, "attachment")) ?? 0,
|
|
15740
|
+
attachmentType: normalizeAttachmentType(getXmlText(xml, "attachmentType")),
|
|
15741
|
+
textType: normalizeTextType(getXmlText(xml, "textType")),
|
|
15742
|
+
ssl: parseInt01(getXmlText(xml, "ssl")) ?? 0,
|
|
15743
|
+
interval: parseNumberSafe(getXmlText(xml, "interval")) ?? 30
|
|
15744
|
+
};
|
|
15745
|
+
const senderMaxLen = parseNumberSafe(getXmlText(xml, "senderMaxLen"));
|
|
15746
|
+
if (senderMaxLen !== void 0) cfg.senderMaxLen = senderMaxLen;
|
|
15747
|
+
const pwdMaxLen = parseNumberSafe(getXmlText(xml, "pwdMaxLen"));
|
|
15748
|
+
if (pwdMaxLen !== void 0) cfg.pwdMaxLen = pwdMaxLen;
|
|
15749
|
+
const ability = parseNumberSafe(getXmlText(xml, "emailAttachAbility"));
|
|
15750
|
+
if (ability !== void 0) cfg.emailAttachAbility = ability;
|
|
15751
|
+
return cfg;
|
|
15752
|
+
}
|
|
15753
|
+
function buildSetEmailXml(current, patch) {
|
|
15754
|
+
const merged = { ...current, ...patch };
|
|
15755
|
+
return ensureXmlHeader(
|
|
15756
|
+
`<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>`
|
|
15757
|
+
);
|
|
15758
|
+
}
|
|
15759
|
+
function parseEmailTaskFromXml(xml) {
|
|
15760
|
+
const channelId = parseNumberSafe(getXmlText(xml, "channelId")) ?? 0;
|
|
15761
|
+
const enable = parseInt01(getXmlText(xml, "enable")) ?? 0;
|
|
15762
|
+
const items = [];
|
|
15763
|
+
const itemRe = /<item>([\s\S]*?)<\/item>/g;
|
|
15764
|
+
let match;
|
|
15765
|
+
while ((match = itemRe.exec(xml)) !== null) {
|
|
15766
|
+
const block = match[1] ?? "";
|
|
15767
|
+
items.push({
|
|
15768
|
+
type: getXmlText(block, "type") ?? "none",
|
|
15769
|
+
valueTable: getXmlText(block, "valueTable") ?? ""
|
|
15770
|
+
});
|
|
15771
|
+
}
|
|
15772
|
+
return { channelId, enable, typeScheduleList: items };
|
|
15773
|
+
}
|
|
15774
|
+
function buildEmailScheduleValueTable(spec) {
|
|
15775
|
+
if (spec.kind === "always") return "1".repeat(168);
|
|
15776
|
+
const grid = new Array(168).fill("0");
|
|
15777
|
+
if (spec.kind === "never") return grid.join("");
|
|
15778
|
+
for (const w of spec.windows) {
|
|
15779
|
+
const startH = Math.max(0, Math.min(24, w.startHour));
|
|
15780
|
+
const endH = Math.max(startH, Math.min(24, w.endHour));
|
|
15781
|
+
for (const d of w.days) {
|
|
15782
|
+
if (d < 0 || d > 6) continue;
|
|
15783
|
+
for (let h = startH; h < endH; h++) {
|
|
15784
|
+
grid[d * 24 + h] = "1";
|
|
15785
|
+
}
|
|
15786
|
+
}
|
|
15787
|
+
}
|
|
15788
|
+
return grid.join("");
|
|
15789
|
+
}
|
|
15790
|
+
function buildSetEmailTaskXml(task) {
|
|
15791
|
+
const items = task.typeScheduleList.map(
|
|
15792
|
+
(item) => `<item><type>${xmlEscape(item.type)}</type><valueTable>${xmlEscape(item.valueTable)}</valueTable></item>`
|
|
15793
|
+
).join("");
|
|
15794
|
+
return ensureXmlHeader(
|
|
15795
|
+
`<body><EmailTask version="1.1"><channelId>${task.channelId}</channelId><enable>${task.enable}</enable><typeScheduleList>${items}</typeScheduleList></EmailTask></body>`
|
|
15796
|
+
);
|
|
15797
|
+
}
|
|
15798
|
+
|
|
15799
|
+
// src/reolink/baichuan/utils/ntp.ts
|
|
15800
|
+
init_xml();
|
|
15801
|
+
var parseNumberSafe2 = (text) => {
|
|
15802
|
+
if (text === void 0) return void 0;
|
|
15803
|
+
const n = Number(text);
|
|
15804
|
+
return Number.isFinite(n) ? n : void 0;
|
|
15805
|
+
};
|
|
15806
|
+
var parseInt012 = (text) => {
|
|
15807
|
+
if (text === void 0) return void 0;
|
|
15808
|
+
return text.trim() === "1" ? 1 : 0;
|
|
15809
|
+
};
|
|
15810
|
+
function parseNtpConfigFromXml(xml) {
|
|
15811
|
+
return {
|
|
15812
|
+
enable: parseInt012(getXmlText(xml, "enable")) ?? 0,
|
|
15813
|
+
server: getXmlText(xml, "server") ?? "",
|
|
15814
|
+
synchronizeInterval: parseNumberSafe2(getXmlText(xml, "synchronizeInterval")) ?? 1440,
|
|
15815
|
+
port: parseNumberSafe2(getXmlText(xml, "port")) ?? 123
|
|
15816
|
+
};
|
|
15817
|
+
}
|
|
15818
|
+
function buildSetNtpXml(current, patch) {
|
|
15819
|
+
const merged = { ...current, ...patch };
|
|
15820
|
+
return ensureXmlHeader(
|
|
15821
|
+
`<body><Ntp version="1.1"><enable>${merged.enable}</enable><server>${xmlEscape(merged.server)}</server><synchronizeInterval>${merged.synchronizeInterval}</synchronizeInterval><port>${merged.port}</port></Ntp></body>`
|
|
15822
|
+
);
|
|
15823
|
+
}
|
|
15824
|
+
|
|
15825
|
+
// src/reolink/baichuan/utils/dst.ts
|
|
15826
|
+
init_xml();
|
|
15827
|
+
var parseNumberSafe3 = (text) => {
|
|
15828
|
+
if (text === void 0) return void 0;
|
|
15829
|
+
const n = Number(text);
|
|
15830
|
+
return Number.isFinite(n) ? n : void 0;
|
|
15831
|
+
};
|
|
15832
|
+
var parseInt013 = (text) => {
|
|
15833
|
+
if (text === void 0) return void 0;
|
|
15834
|
+
return text.trim() === "1" ? 1 : 0;
|
|
15835
|
+
};
|
|
15836
|
+
var VALID_WEEKDAYS = [
|
|
15837
|
+
"Sunday",
|
|
15838
|
+
"Monday",
|
|
15839
|
+
"Tuesday",
|
|
15840
|
+
"Wednesday",
|
|
15841
|
+
"Thursday",
|
|
15842
|
+
"Friday",
|
|
15843
|
+
"Saturday"
|
|
15844
|
+
];
|
|
15845
|
+
var normalizeWeekday = (text) => {
|
|
15846
|
+
const t = (text ?? "").trim();
|
|
15847
|
+
return VALID_WEEKDAYS.includes(t) ? t : "Sunday";
|
|
15848
|
+
};
|
|
15849
|
+
function parseDstConfigFromXml(xml) {
|
|
15850
|
+
const cfg = {
|
|
15851
|
+
enable: parseInt013(getXmlText(xml, "enable")) ?? 0,
|
|
15852
|
+
offset: parseNumberSafe3(getXmlText(xml, "offset")) ?? 1,
|
|
15853
|
+
startMonth: parseNumberSafe3(getXmlText(xml, "startMonth")) ?? 3,
|
|
15854
|
+
startWeekIndex: parseNumberSafe3(getXmlText(xml, "startWeekIndex")) ?? 5,
|
|
15855
|
+
startWeekday: normalizeWeekday(getXmlText(xml, "startWeekday")),
|
|
15856
|
+
startHour: parseNumberSafe3(getXmlText(xml, "startHour")) ?? 2,
|
|
15857
|
+
startMinute: parseNumberSafe3(getXmlText(xml, "startMinute")) ?? 0,
|
|
15858
|
+
startSecond: parseNumberSafe3(getXmlText(xml, "startSecond")) ?? 0,
|
|
15859
|
+
endMonth: parseNumberSafe3(getXmlText(xml, "endMonth")) ?? 10,
|
|
15860
|
+
endWeekIndex: parseNumberSafe3(getXmlText(xml, "endWeekIndex")) ?? 4,
|
|
15861
|
+
endWeekday: normalizeWeekday(getXmlText(xml, "endWeekday")),
|
|
15862
|
+
endHour: parseNumberSafe3(getXmlText(xml, "endHour")) ?? 3,
|
|
15863
|
+
endMinute: parseNumberSafe3(getXmlText(xml, "endMinute")) ?? 0,
|
|
15864
|
+
endSecond: parseNumberSafe3(getXmlText(xml, "endSecond")) ?? 0
|
|
15865
|
+
};
|
|
15866
|
+
const version = parseNumberSafe3(getXmlText(xml, "version"));
|
|
15867
|
+
if (version !== void 0) cfg.version = version;
|
|
15868
|
+
return cfg;
|
|
15869
|
+
}
|
|
15870
|
+
function buildSetDstXml(current, patch) {
|
|
15871
|
+
const merged = { ...current, ...patch };
|
|
15872
|
+
return ensureXmlHeader(
|
|
15873
|
+
`<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>`
|
|
15874
|
+
);
|
|
15875
|
+
}
|
|
15876
|
+
|
|
15877
|
+
// src/reolink/baichuan/utils/autoReboot.ts
|
|
15878
|
+
init_xml();
|
|
15879
|
+
var parseNumberSafe4 = (text) => {
|
|
15880
|
+
if (text === void 0) return void 0;
|
|
15881
|
+
const n = Number(text);
|
|
15882
|
+
return Number.isFinite(n) ? n : void 0;
|
|
15883
|
+
};
|
|
15884
|
+
var parseInt014 = (text) => {
|
|
15885
|
+
if (text === void 0) return void 0;
|
|
15886
|
+
return text.trim() === "1" ? 1 : 0;
|
|
15887
|
+
};
|
|
15888
|
+
var VALID_WEEKDAYS2 = [
|
|
15889
|
+
"Sunday",
|
|
15890
|
+
"Monday",
|
|
15891
|
+
"Tuesday",
|
|
15892
|
+
"Wednesday",
|
|
15893
|
+
"Thursday",
|
|
15894
|
+
"Friday",
|
|
15895
|
+
"Saturday",
|
|
15896
|
+
"everyday"
|
|
15897
|
+
];
|
|
15898
|
+
var normalizeWeekday2 = (text) => {
|
|
15899
|
+
const t = (text ?? "").trim();
|
|
15900
|
+
return VALID_WEEKDAYS2.includes(t) ? t : "Sunday";
|
|
15901
|
+
};
|
|
15902
|
+
function parseAutoRebootFromXml(xml) {
|
|
15903
|
+
return {
|
|
15904
|
+
enable: parseInt014(getXmlText(xml, "enable")) ?? 0,
|
|
15905
|
+
weekDay: normalizeWeekday2(getXmlText(xml, "weekDay")),
|
|
15906
|
+
hour: parseNumberSafe4(getXmlText(xml, "hour")) ?? 0,
|
|
15907
|
+
minute: parseNumberSafe4(getXmlText(xml, "minute")) ?? 0,
|
|
15908
|
+
second: parseNumberSafe4(getXmlText(xml, "second")) ?? 0
|
|
15909
|
+
};
|
|
15910
|
+
}
|
|
15911
|
+
function buildSetAutoRebootXml(current, patch) {
|
|
15912
|
+
const merged = { ...current, ...patch };
|
|
15913
|
+
return ensureXmlHeader(
|
|
15914
|
+
`<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>`
|
|
15915
|
+
);
|
|
15916
|
+
}
|
|
15917
|
+
|
|
15918
|
+
// src/reolink/baichuan/utils/systemGeneral.ts
|
|
15919
|
+
init_xml();
|
|
15920
|
+
var parseNumberSafe5 = (text) => {
|
|
15921
|
+
if (text === void 0) return void 0;
|
|
15922
|
+
const n = Number(text);
|
|
15923
|
+
return Number.isFinite(n) ? n : void 0;
|
|
15924
|
+
};
|
|
15925
|
+
var parseInt015 = (text) => {
|
|
15926
|
+
if (text === void 0) return void 0;
|
|
15927
|
+
return text.trim() === "1" ? 1 : 0;
|
|
15928
|
+
};
|
|
15929
|
+
var VALID_OSD_FORMATS = ["DMY", "MDY", "YMD"];
|
|
15930
|
+
var normalizeOsdFormat = (text) => {
|
|
15931
|
+
const t = (text ?? "").trim();
|
|
15932
|
+
return VALID_OSD_FORMATS.includes(t) ? t : "YMD";
|
|
15933
|
+
};
|
|
15934
|
+
function parseSystemGeneralFromXml(xml) {
|
|
15935
|
+
return {
|
|
15936
|
+
timeZone: parseNumberSafe5(getXmlText(xml, "timeZone")) ?? 0,
|
|
15937
|
+
osdFormat: normalizeOsdFormat(getXmlText(xml, "osdFormat")),
|
|
15938
|
+
year: parseNumberSafe5(getXmlText(xml, "year")) ?? 0,
|
|
15939
|
+
month: parseNumberSafe5(getXmlText(xml, "month")) ?? 0,
|
|
15940
|
+
day: parseNumberSafe5(getXmlText(xml, "day")) ?? 0,
|
|
15941
|
+
hour: parseNumberSafe5(getXmlText(xml, "hour")) ?? 0,
|
|
15942
|
+
minute: parseNumberSafe5(getXmlText(xml, "minute")) ?? 0,
|
|
15943
|
+
second: parseNumberSafe5(getXmlText(xml, "second")) ?? 0,
|
|
15944
|
+
deviceId: parseNumberSafe5(getXmlText(xml, "deviceId")) ?? 0,
|
|
15945
|
+
timeFormat: parseInt015(getXmlText(xml, "timeFormat")) ?? 0,
|
|
15946
|
+
language: getXmlText(xml, "language") ?? "English",
|
|
15947
|
+
deviceName: getXmlText(xml, "deviceName") ?? "",
|
|
15948
|
+
loginLock: parseInt015(getXmlText(xml, "loginLock")) ?? 0,
|
|
15949
|
+
lockTime: parseNumberSafe5(getXmlText(xml, "lockTime")) ?? 0,
|
|
15950
|
+
allowedTimes: parseNumberSafe5(getXmlText(xml, "allowedTimes")) ?? 0,
|
|
15951
|
+
isDst: parseInt015(getXmlText(xml, "isDst")) ?? 0
|
|
15952
|
+
};
|
|
15953
|
+
}
|
|
15954
|
+
function buildSetSystemGeneralXml(patch) {
|
|
15955
|
+
const parts = [];
|
|
15956
|
+
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;
|
|
15957
|
+
if (isDeviceNameOnly) {
|
|
15958
|
+
parts.push("<year>0</year>");
|
|
15959
|
+
parts.push(`<deviceName>${xmlEscape(patch.deviceName)}</deviceName>`);
|
|
15960
|
+
parts.push("<deviceNameOnly>1</deviceNameOnly>");
|
|
15961
|
+
} else if (patch.manualTime !== void 0) {
|
|
15962
|
+
const mt = patch.manualTime;
|
|
15963
|
+
if (patch.timeZone !== void 0)
|
|
15964
|
+
parts.push(`<timeZone>${patch.timeZone}</timeZone>`);
|
|
15965
|
+
if (patch.osdFormat !== void 0)
|
|
15966
|
+
parts.push(`<osdFormat>${patch.osdFormat}</osdFormat>`);
|
|
15967
|
+
parts.push(`<year>${mt.year}</year>`);
|
|
15968
|
+
parts.push(`<month>${mt.month}</month>`);
|
|
15969
|
+
parts.push(`<day>${mt.day}</day>`);
|
|
15970
|
+
parts.push(`<hour>${mt.hour}</hour>`);
|
|
15971
|
+
parts.push(`<minute>${mt.minute}</minute>`);
|
|
15972
|
+
parts.push(`<second>${mt.second}</second>`);
|
|
15973
|
+
if (patch.timeFormat !== void 0)
|
|
15974
|
+
parts.push(`<timeFormat>${patch.timeFormat}</timeFormat>`);
|
|
15975
|
+
if (patch.language !== void 0)
|
|
15976
|
+
parts.push(`<language>${xmlEscape(patch.language)}</language>`);
|
|
15977
|
+
if (patch.deviceName !== void 0)
|
|
15978
|
+
parts.push(`<deviceName>${xmlEscape(patch.deviceName)}</deviceName>`);
|
|
15979
|
+
if (patch.loginLock !== void 0)
|
|
15980
|
+
parts.push(`<loginLock>${patch.loginLock}</loginLock>`);
|
|
15981
|
+
if (patch.lockTime !== void 0)
|
|
15982
|
+
parts.push(`<lockTime>${patch.lockTime}</lockTime>`);
|
|
15983
|
+
if (patch.allowedTimes !== void 0)
|
|
15984
|
+
parts.push(`<allowedTimes>${patch.allowedTimes}</allowedTimes>`);
|
|
15985
|
+
} else {
|
|
15986
|
+
if (patch.timeZone !== void 0)
|
|
15987
|
+
parts.push(`<timeZone>${patch.timeZone}</timeZone>`);
|
|
15988
|
+
if (patch.osdFormat !== void 0)
|
|
15989
|
+
parts.push(`<osdFormat>${patch.osdFormat}</osdFormat>`);
|
|
15990
|
+
if (patch.timeFormat !== void 0)
|
|
15991
|
+
parts.push(`<timeFormat>${patch.timeFormat}</timeFormat>`);
|
|
15992
|
+
if (patch.language !== void 0)
|
|
15993
|
+
parts.push(`<language>${xmlEscape(patch.language)}</language>`);
|
|
15994
|
+
if (patch.loginLock !== void 0)
|
|
15995
|
+
parts.push(`<loginLock>${patch.loginLock}</loginLock>`);
|
|
15996
|
+
if (patch.lockTime !== void 0)
|
|
15997
|
+
parts.push(`<lockTime>${patch.lockTime}</lockTime>`);
|
|
15998
|
+
if (patch.allowedTimes !== void 0)
|
|
15999
|
+
parts.push(`<allowedTimes>${patch.allowedTimes}</allowedTimes>`);
|
|
16000
|
+
parts.push("<year>0</year>");
|
|
16001
|
+
}
|
|
16002
|
+
return ensureXmlHeader(
|
|
16003
|
+
`<body><SystemGeneral version="1.1">${parts.join("")}</SystemGeneral></body>`
|
|
16004
|
+
);
|
|
16005
|
+
}
|
|
16006
|
+
|
|
15531
16007
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
15532
16008
|
var import_jimp = require("jimp");
|
|
15533
16009
|
init_ReolinkCgiApi();
|
|
@@ -15804,6 +16280,32 @@ var parseAbilityInfoXml = (xml) => {
|
|
|
15804
16280
|
return abilities;
|
|
15805
16281
|
};
|
|
15806
16282
|
|
|
16283
|
+
// src/reolink/baichuan/utils/versionInfo.ts
|
|
16284
|
+
init_xml();
|
|
16285
|
+
function parseVersionInfo(xml) {
|
|
16286
|
+
const out = {};
|
|
16287
|
+
const set = (key) => {
|
|
16288
|
+
const v = getXmlText(xml, key);
|
|
16289
|
+
if (v !== void 0) out[key] = v;
|
|
16290
|
+
};
|
|
16291
|
+
set("name");
|
|
16292
|
+
set("type");
|
|
16293
|
+
set("serialNumber");
|
|
16294
|
+
set("buildDay");
|
|
16295
|
+
set("hardwareVersion");
|
|
16296
|
+
set("cfgVersion");
|
|
16297
|
+
set("firmwareVersion");
|
|
16298
|
+
set("detail");
|
|
16299
|
+
set("IEClient");
|
|
16300
|
+
set("cc3200Version");
|
|
16301
|
+
set("spVersion");
|
|
16302
|
+
set("pakSuffix");
|
|
16303
|
+
set("itemNo");
|
|
16304
|
+
set("aiVersion");
|
|
16305
|
+
set("helpVersion");
|
|
16306
|
+
return out;
|
|
16307
|
+
}
|
|
16308
|
+
|
|
15807
16309
|
// src/reolink/baichuan/utils/aiState.ts
|
|
15808
16310
|
init_constants();
|
|
15809
16311
|
init_xml();
|
|
@@ -16110,6 +16612,204 @@ var buildChannelPushDataLogSnapshot = (channelPushData) => {
|
|
|
16110
16612
|
return { result: resultObj, storedChannels: Object.keys(resultObj) };
|
|
16111
16613
|
};
|
|
16112
16614
|
|
|
16615
|
+
// src/reolink/baichuan/utils/detection.ts
|
|
16616
|
+
var lz4 = __toESM(require("lz4js"), 1);
|
|
16617
|
+
|
|
16618
|
+
// src/reolink/baichuan/utils/aiClassMap.ts
|
|
16619
|
+
function type1ToLabel(type1) {
|
|
16620
|
+
switch (type1) {
|
|
16621
|
+
case 1:
|
|
16622
|
+
return "people";
|
|
16623
|
+
case 2:
|
|
16624
|
+
return "vehicle";
|
|
16625
|
+
case 3:
|
|
16626
|
+
return "animal";
|
|
16627
|
+
case 11259375:
|
|
16628
|
+
return "face";
|
|
16629
|
+
default:
|
|
16630
|
+
return "unknown";
|
|
16631
|
+
}
|
|
16632
|
+
}
|
|
16633
|
+
|
|
16634
|
+
// src/reolink/baichuan/utils/detection.ts
|
|
16635
|
+
var MARKER_LENGTH = 8;
|
|
16636
|
+
var IFRAME_PREFIX_LENGTH = 8;
|
|
16637
|
+
var COUNTER_OFFSET = 8;
|
|
16638
|
+
var BASELINE_SIZE = 128;
|
|
16639
|
+
var FRAME_SIZE_TLV = Buffer.from([3, 4, 0]);
|
|
16640
|
+
var LZ4F_MAGIC = Buffer.from([4, 34, 77, 24]);
|
|
16641
|
+
var CONFIDENCE_DIVISOR = 100;
|
|
16642
|
+
var DEFAULT_AI_FRAME_WIDTH = 896;
|
|
16643
|
+
var DEFAULT_AI_FRAME_HEIGHT = 480;
|
|
16644
|
+
var LZ4_DECOMPRESS_MAX = 256 * 1024;
|
|
16645
|
+
function walkBoxes(buf, start, end, type1, type2, out) {
|
|
16646
|
+
let pos = start;
|
|
16647
|
+
while (pos + 3 <= end) {
|
|
16648
|
+
const t = buf[pos];
|
|
16649
|
+
if (t === 0) return;
|
|
16650
|
+
const length = buf[pos + 1] | buf[pos + 2] << 8;
|
|
16651
|
+
const recordEnd = pos + 3 + length;
|
|
16652
|
+
if (recordEnd > end) return;
|
|
16653
|
+
const isBoxType4 = t === 4 && (length === 10 || length === 13 || length === 14);
|
|
16654
|
+
const isBoxType2 = t === 2 && length === 10;
|
|
16655
|
+
if ((isBoxType4 || isBoxType2) && type1 !== 0 && type2 !== 0) {
|
|
16656
|
+
const x1 = buf.readUInt16LE(pos + 3);
|
|
16657
|
+
const y1 = buf.readUInt16LE(pos + 5);
|
|
16658
|
+
const x2 = buf.readUInt16LE(pos + 7);
|
|
16659
|
+
const y2 = buf.readUInt16LE(pos + 9);
|
|
16660
|
+
const conf = buf.readUInt16LE(pos + 11);
|
|
16661
|
+
if (x2 > x1 && y2 > y1) {
|
|
16662
|
+
out.push({ x1, y1, x2, y2, conf, label: type1ToLabel(type1) });
|
|
16663
|
+
}
|
|
16664
|
+
pos = recordEnd;
|
|
16665
|
+
continue;
|
|
16666
|
+
}
|
|
16667
|
+
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]) {
|
|
16668
|
+
try {
|
|
16669
|
+
const decompressed = lz4.decompress(
|
|
16670
|
+
buf.subarray(pos + 3, recordEnd),
|
|
16671
|
+
LZ4_DECOMPRESS_MAX
|
|
16672
|
+
);
|
|
16673
|
+
const decBuf = Buffer.from(decompressed);
|
|
16674
|
+
walkBoxes(decBuf, 0, decBuf.length, 0, 0, out);
|
|
16675
|
+
} catch {
|
|
16676
|
+
}
|
|
16677
|
+
pos = recordEnd;
|
|
16678
|
+
continue;
|
|
16679
|
+
}
|
|
16680
|
+
if (length > 0) {
|
|
16681
|
+
let nextT1 = type1;
|
|
16682
|
+
let nextT2 = type2;
|
|
16683
|
+
if (type1 === 0) nextT1 = t;
|
|
16684
|
+
else if (type2 === 0) nextT2 = t;
|
|
16685
|
+
walkBoxes(buf, pos + 3, recordEnd, nextT1, nextT2, out);
|
|
16686
|
+
}
|
|
16687
|
+
pos = recordEnd;
|
|
16688
|
+
}
|
|
16689
|
+
}
|
|
16690
|
+
function decodeDetectionHeader(raw, frameType) {
|
|
16691
|
+
const markerOffset = frameType === "Iframe" ? IFRAME_PREFIX_LENGTH : 0;
|
|
16692
|
+
const blockLength = raw.length - markerOffset;
|
|
16693
|
+
const empty = {
|
|
16694
|
+
state: "invalid-marker",
|
|
16695
|
+
markerOffset,
|
|
16696
|
+
blockLength,
|
|
16697
|
+
boxes: []
|
|
16698
|
+
};
|
|
16699
|
+
if (blockLength < MARKER_LENGTH) return empty;
|
|
16700
|
+
if (!hasStandardMarker(raw, markerOffset)) return empty;
|
|
16701
|
+
if (blockLength < COUNTER_OFFSET + 4) return empty;
|
|
16702
|
+
const counter = raw.readUInt32LE(markerOffset + COUNTER_OFFSET);
|
|
16703
|
+
const rawBoxes = [];
|
|
16704
|
+
walkBoxes(raw, markerOffset, raw.length, 0, 0, rawBoxes);
|
|
16705
|
+
let aiFrameWidth = DEFAULT_AI_FRAME_WIDTH;
|
|
16706
|
+
let aiFrameHeight = DEFAULT_AI_FRAME_HEIGHT;
|
|
16707
|
+
let frameSizeFound = false;
|
|
16708
|
+
const searchStart = markerOffset + MARKER_LENGTH;
|
|
16709
|
+
for (let i = searchStart; i + 7 <= raw.length; i++) {
|
|
16710
|
+
if (raw[i] === FRAME_SIZE_TLV[0] && raw[i + 1] === FRAME_SIZE_TLV[1] && raw[i + 2] === FRAME_SIZE_TLV[2]) {
|
|
16711
|
+
const w = raw.readUInt16LE(i + 3);
|
|
16712
|
+
const h = raw.readUInt16LE(i + 5);
|
|
16713
|
+
if (w >= 64 && w <= 8192 && h >= 64 && h <= 8192) {
|
|
16714
|
+
aiFrameWidth = w;
|
|
16715
|
+
aiFrameHeight = h;
|
|
16716
|
+
frameSizeFound = true;
|
|
16717
|
+
break;
|
|
16718
|
+
}
|
|
16719
|
+
}
|
|
16720
|
+
}
|
|
16721
|
+
const specificity = {
|
|
16722
|
+
face: 4,
|
|
16723
|
+
animal: 3,
|
|
16724
|
+
people: 2,
|
|
16725
|
+
vehicle: 1,
|
|
16726
|
+
unknown: 0
|
|
16727
|
+
};
|
|
16728
|
+
const dedup = /* @__PURE__ */ new Map();
|
|
16729
|
+
for (const rb of rawBoxes) {
|
|
16730
|
+
if (rb.x2 > aiFrameWidth || rb.y2 > aiFrameHeight) continue;
|
|
16731
|
+
const key = `${rb.x1}_${rb.y1}_${rb.x2}_${rb.y2}`;
|
|
16732
|
+
const prev = dedup.get(key);
|
|
16733
|
+
if (!prev || (specificity[rb.label] ?? 0) > (specificity[prev.label] ?? 0)) {
|
|
16734
|
+
dedup.set(key, rb);
|
|
16735
|
+
}
|
|
16736
|
+
}
|
|
16737
|
+
const boxes = [];
|
|
16738
|
+
for (const rb of dedup.values()) {
|
|
16739
|
+
boxes.push({
|
|
16740
|
+
x: rb.x1 / aiFrameWidth,
|
|
16741
|
+
y: rb.y1 / aiFrameHeight,
|
|
16742
|
+
width: (rb.x2 - rb.x1) / aiFrameWidth,
|
|
16743
|
+
height: (rb.y2 - rb.y1) / aiFrameHeight,
|
|
16744
|
+
...rb.conf > 0 && rb.conf <= 100 ? { confidence: rb.conf / CONFIDENCE_DIVISOR } : {},
|
|
16745
|
+
...rb.label !== "unknown" ? { label: rb.label } : {}
|
|
16746
|
+
});
|
|
16747
|
+
}
|
|
16748
|
+
let state;
|
|
16749
|
+
if (boxes.length > 0) state = "overlay-decoded";
|
|
16750
|
+
else if (blockLength === BASELINE_SIZE) state = "no-overlay";
|
|
16751
|
+
else state = "overlay-undecoded";
|
|
16752
|
+
return {
|
|
16753
|
+
state,
|
|
16754
|
+
markerOffset,
|
|
16755
|
+
blockLength,
|
|
16756
|
+
counter,
|
|
16757
|
+
...frameSizeFound ? { aiFrameWidth, aiFrameHeight } : {},
|
|
16758
|
+
boxes
|
|
16759
|
+
};
|
|
16760
|
+
}
|
|
16761
|
+
function hasStandardMarker(raw, offset) {
|
|
16762
|
+
if (raw.length < offset + MARKER_LENGTH) return false;
|
|
16763
|
+
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;
|
|
16764
|
+
}
|
|
16765
|
+
|
|
16766
|
+
// src/reolink/baichuan/utils/encOptions.ts
|
|
16767
|
+
function buildEncOptions(list, channel) {
|
|
16768
|
+
const result = { channel };
|
|
16769
|
+
const main2 = aggregateByType(list, "mainStream");
|
|
16770
|
+
const sub = aggregateByType(list, "subStream");
|
|
16771
|
+
const third = aggregateByType(list, "thirdStream");
|
|
16772
|
+
if (main2) result.mainStream = main2;
|
|
16773
|
+
if (sub) result.subStream = sub;
|
|
16774
|
+
if (third) result.thirdStream = third;
|
|
16775
|
+
return result;
|
|
16776
|
+
}
|
|
16777
|
+
function aggregateByType(list, type) {
|
|
16778
|
+
const seen = /* @__PURE__ */ new Map();
|
|
16779
|
+
for (const stream of list.streams) {
|
|
16780
|
+
for (const eb of stream.encodeTables) {
|
|
16781
|
+
if (eb.type !== type) continue;
|
|
16782
|
+
const mapped = mapEncodeTable(eb);
|
|
16783
|
+
if (!mapped) continue;
|
|
16784
|
+
const key = `${mapped.width}x${mapped.height}`;
|
|
16785
|
+
if (!seen.has(key)) seen.set(key, mapped);
|
|
16786
|
+
}
|
|
16787
|
+
}
|
|
16788
|
+
if (seen.size === 0) return void 0;
|
|
16789
|
+
return {
|
|
16790
|
+
type,
|
|
16791
|
+
resolutions: [...seen.values()],
|
|
16792
|
+
encoderTypes: ["vbr", "cbr"],
|
|
16793
|
+
encoderProfiles: ["high", "main", "baseline"]
|
|
16794
|
+
};
|
|
16795
|
+
}
|
|
16796
|
+
function mapEncodeTable(eb) {
|
|
16797
|
+
if (eb.width == null || eb.height == null) return void 0;
|
|
16798
|
+
const videoEncTypes = (eb.videoEncTypeList ?? (eb.videoEncType != null ? [eb.videoEncType] : [])).map(
|
|
16799
|
+
(t) => t === 0 ? "h264" : t === 1 ? "h265" : void 0
|
|
16800
|
+
).filter((t) => t !== void 0);
|
|
16801
|
+
return {
|
|
16802
|
+
width: eb.width,
|
|
16803
|
+
height: eb.height,
|
|
16804
|
+
videoEncTypes,
|
|
16805
|
+
...eb.defaultFramerate != null ? { defaultFramerate: eb.defaultFramerate } : {},
|
|
16806
|
+
...eb.defaultBitrate != null ? { defaultBitrate: eb.defaultBitrate } : {},
|
|
16807
|
+
...eb.defaultGop != null ? { defaultGop: eb.defaultGop } : {},
|
|
16808
|
+
framerateOptions: eb.framerateTable ?? [],
|
|
16809
|
+
bitrateOptions: eb.bitrateTable ?? []
|
|
16810
|
+
};
|
|
16811
|
+
}
|
|
16812
|
+
|
|
16113
16813
|
// src/reolink/baichuan/utils/events.ts
|
|
16114
16814
|
var mapToSimpleEvent = (event) => {
|
|
16115
16815
|
const timestamp = event.timestamp ?? Date.now();
|
|
@@ -18233,6 +18933,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18233
18933
|
sessionGuardIntervalTimer;
|
|
18234
18934
|
simpleEventListeners = /* @__PURE__ */ new Set();
|
|
18235
18935
|
simpleEventSubscribed = false;
|
|
18936
|
+
// Detection events are sourced from BcMedia additionalHeader on active video
|
|
18937
|
+
// streams. Unlike simpleEvent, no Baichuan subscribe command is needed — the
|
|
18938
|
+
// data flows whenever a stream is open. Active streams register themselves via
|
|
18939
|
+
// _registerVideoStreamForDetection (called from BaichuanVideoStream.start).
|
|
18940
|
+
detectionEventListeners = /* @__PURE__ */ new Set();
|
|
18941
|
+
detectionEventStreamHooks = /* @__PURE__ */ new Map();
|
|
18942
|
+
// Auto-managed substream for `onObjectDetections` listeners. Reference-counted
|
|
18943
|
+
// by the listener set: the substream is opened on the first listener and torn
|
|
18944
|
+
// down with the last one. Mirrors `onSimpleEvent`'s subscribe/unsubscribe
|
|
18945
|
+
// lifecycle so a caller never has to manage a video stream just to read AI
|
|
18946
|
+
// detections.
|
|
18947
|
+
objectDetectionListeners = /* @__PURE__ */ new Set();
|
|
18948
|
+
objectDetectionStream;
|
|
18949
|
+
objectDetectionStreamStartInFlight;
|
|
18950
|
+
objectDetectionInternalListener;
|
|
18236
18951
|
simpleEventSubscribeInFlight;
|
|
18237
18952
|
simpleEventUnsubscribeInFlight;
|
|
18238
18953
|
simpleEventResubscribeTimer;
|
|
@@ -19668,6 +20383,205 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19668
20383
|
}
|
|
19669
20384
|
}
|
|
19670
20385
|
}
|
|
20386
|
+
/**
|
|
20387
|
+
* Subscribe to per-frame detection events sourced from the BcMedia
|
|
20388
|
+
* `additionalHeader` block on active video streams.
|
|
20389
|
+
*
|
|
20390
|
+
* Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
|
|
20391
|
+
* one event fires for every I-frame / P-frame that carries an overlay block.
|
|
20392
|
+
* Coordinates are reported in normalized [0, 1] fractions of the source
|
|
20393
|
+
* frame, so the same box renders correctly on mainStream, subStream, and
|
|
20394
|
+
* externStream.
|
|
20395
|
+
*
|
|
20396
|
+
* Unlike `onSimpleEvent`, no Baichuan subscribe command is involved — events
|
|
20397
|
+
* only flow while a video stream is open. The library hooks every
|
|
20398
|
+
* `BaichuanVideoStream` created via this API for the listener's lifetime.
|
|
20399
|
+
*/
|
|
20400
|
+
onDetection(callback) {
|
|
20401
|
+
this.detectionEventListeners.add(callback);
|
|
20402
|
+
}
|
|
20403
|
+
/**
|
|
20404
|
+
* Remove a single detection callback, or all of them if `callback` is omitted.
|
|
20405
|
+
*/
|
|
20406
|
+
offDetection(callback) {
|
|
20407
|
+
if (callback) {
|
|
20408
|
+
this.detectionEventListeners.delete(callback);
|
|
20409
|
+
} else {
|
|
20410
|
+
this.detectionEventListeners.clear();
|
|
20411
|
+
}
|
|
20412
|
+
}
|
|
20413
|
+
/**
|
|
20414
|
+
* Subscribe to AI object detections (people / vehicle / animal / face boxes
|
|
20415
|
+
* with class label and confidence) without managing a video stream yourself.
|
|
20416
|
+
*
|
|
20417
|
+
* Mirrors {@link onSimpleEvent} end-to-end: the API opens a dedicated
|
|
20418
|
+
* substream behind the scenes on the first listener, forwards every box-bearing
|
|
20419
|
+
* `additionalHeader` to your callback, and tears the stream down when the last
|
|
20420
|
+
* listener unsubscribes. The substream is the lightest profile (typically
|
|
20421
|
+
* 640×360) so the additional bandwidth/CPU overhead is minimal.
|
|
20422
|
+
*
|
|
20423
|
+
* Each event carries normalized `[0, 1]` box coordinates, a class label, and
|
|
20424
|
+
* a confidence score — render-ready without further conversion.
|
|
20425
|
+
*/
|
|
20426
|
+
async onObjectDetections(callback) {
|
|
20427
|
+
this.objectDetectionListeners.add(callback);
|
|
20428
|
+
this.logger.debug?.(
|
|
20429
|
+
`[ReolinkBaichuanApi] onObjectDetections: registering listener (total=${this.objectDetectionListeners.size})`
|
|
20430
|
+
);
|
|
20431
|
+
await this.ensureObjectDetectionStream();
|
|
20432
|
+
}
|
|
20433
|
+
/**
|
|
20434
|
+
* Remove one detection callback, or all of them if `callback` is omitted.
|
|
20435
|
+
* When the last listener is removed the auto-managed substream is closed.
|
|
20436
|
+
*/
|
|
20437
|
+
async offObjectDetections(callback) {
|
|
20438
|
+
if (callback) {
|
|
20439
|
+
this.objectDetectionListeners.delete(callback);
|
|
20440
|
+
} else {
|
|
20441
|
+
this.objectDetectionListeners.clear();
|
|
20442
|
+
}
|
|
20443
|
+
if (this.objectDetectionListeners.size === 0) {
|
|
20444
|
+
await this.tearDownObjectDetectionStream();
|
|
20445
|
+
}
|
|
20446
|
+
}
|
|
20447
|
+
async ensureObjectDetectionStream() {
|
|
20448
|
+
if (this.objectDetectionStream) return;
|
|
20449
|
+
if (this.objectDetectionStreamStartInFlight) {
|
|
20450
|
+
await this.objectDetectionStreamStartInFlight;
|
|
20451
|
+
return;
|
|
20452
|
+
}
|
|
20453
|
+
this.objectDetectionStreamStartInFlight = (async () => {
|
|
20454
|
+
const { BaichuanVideoStream: BaichuanVideoStream2 } = await Promise.resolve().then(() => (init_BaichuanVideoStream(), BaichuanVideoStream_exports));
|
|
20455
|
+
const sessionKey = `live:object-detections:ch0:sub`;
|
|
20456
|
+
const dedicated = await this.createDedicatedSession(sessionKey);
|
|
20457
|
+
const stream = new BaichuanVideoStream2({
|
|
20458
|
+
client: dedicated.client,
|
|
20459
|
+
api: this,
|
|
20460
|
+
channel: 0,
|
|
20461
|
+
profile: "sub",
|
|
20462
|
+
logger: this.logger
|
|
20463
|
+
});
|
|
20464
|
+
this.objectDetectionInternalListener = (event) => {
|
|
20465
|
+
for (const cb of this.objectDetectionListeners) {
|
|
20466
|
+
try {
|
|
20467
|
+
void Promise.resolve(cb(event)).catch((e) => {
|
|
20468
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
20469
|
+
this.logger,
|
|
20470
|
+
"[ReolinkBaichuanApi] onObjectDetections handler error",
|
|
20471
|
+
formatErrorForLog(e)
|
|
20472
|
+
);
|
|
20473
|
+
});
|
|
20474
|
+
} catch (e) {
|
|
20475
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
20476
|
+
this.logger,
|
|
20477
|
+
"[ReolinkBaichuanApi] onObjectDetections handler error",
|
|
20478
|
+
formatErrorForLog(e)
|
|
20479
|
+
);
|
|
20480
|
+
}
|
|
20481
|
+
}
|
|
20482
|
+
};
|
|
20483
|
+
this.detectionEventListeners.add(this.objectDetectionInternalListener);
|
|
20484
|
+
try {
|
|
20485
|
+
await stream.start();
|
|
20486
|
+
} catch (e) {
|
|
20487
|
+
if (this.objectDetectionInternalListener) {
|
|
20488
|
+
this.detectionEventListeners.delete(
|
|
20489
|
+
this.objectDetectionInternalListener
|
|
20490
|
+
);
|
|
20491
|
+
this.objectDetectionInternalListener = void 0;
|
|
20492
|
+
}
|
|
20493
|
+
await dedicated.release().catch(() => {
|
|
20494
|
+
});
|
|
20495
|
+
throw e;
|
|
20496
|
+
}
|
|
20497
|
+
this.objectDetectionStream = {
|
|
20498
|
+
stop: () => stream.stop(),
|
|
20499
|
+
release: () => dedicated.release()
|
|
20500
|
+
};
|
|
20501
|
+
this.logger.debug?.(
|
|
20502
|
+
`[ReolinkBaichuanApi] onObjectDetections: substream started (key=${sessionKey})`
|
|
20503
|
+
);
|
|
20504
|
+
})();
|
|
20505
|
+
try {
|
|
20506
|
+
await this.objectDetectionStreamStartInFlight;
|
|
20507
|
+
} finally {
|
|
20508
|
+
this.objectDetectionStreamStartInFlight = void 0;
|
|
20509
|
+
}
|
|
20510
|
+
}
|
|
20511
|
+
async tearDownObjectDetectionStream() {
|
|
20512
|
+
const handle = this.objectDetectionStream;
|
|
20513
|
+
this.objectDetectionStream = void 0;
|
|
20514
|
+
if (this.objectDetectionInternalListener) {
|
|
20515
|
+
this.detectionEventListeners.delete(this.objectDetectionInternalListener);
|
|
20516
|
+
this.objectDetectionInternalListener = void 0;
|
|
20517
|
+
}
|
|
20518
|
+
if (!handle) return;
|
|
20519
|
+
try {
|
|
20520
|
+
await handle.stop();
|
|
20521
|
+
} catch (e) {
|
|
20522
|
+
this.logger.debug?.(
|
|
20523
|
+
`[ReolinkBaichuanApi] onObjectDetections: stream stop error: ${formatErrorForLog(e)}`
|
|
20524
|
+
);
|
|
20525
|
+
}
|
|
20526
|
+
try {
|
|
20527
|
+
await handle.release();
|
|
20528
|
+
} catch (e) {
|
|
20529
|
+
this.logger.debug?.(
|
|
20530
|
+
`[ReolinkBaichuanApi] onObjectDetections: session release error: ${formatErrorForLog(e)}`
|
|
20531
|
+
);
|
|
20532
|
+
}
|
|
20533
|
+
this.logger.debug?.(
|
|
20534
|
+
`[ReolinkBaichuanApi] onObjectDetections: substream torn down`
|
|
20535
|
+
);
|
|
20536
|
+
}
|
|
20537
|
+
/**
|
|
20538
|
+
* Internal: invoked by BaichuanVideoStream when it starts so the API can hook
|
|
20539
|
+
* its `additionalHeader` event. Returns a teardown function the stream calls
|
|
20540
|
+
* on stop. Not intended for direct use by consumers.
|
|
20541
|
+
*/
|
|
20542
|
+
_registerVideoStreamForDetection(stream, context) {
|
|
20543
|
+
const listener = (info) => {
|
|
20544
|
+
if (this.detectionEventListeners.size === 0) return;
|
|
20545
|
+
const decoded = decodeDetectionHeader(info.raw, info.frameType);
|
|
20546
|
+
const event = {
|
|
20547
|
+
channel: context.channel,
|
|
20548
|
+
microseconds: info.microseconds,
|
|
20549
|
+
profile: context.profile,
|
|
20550
|
+
boxes: decoded.boxes,
|
|
20551
|
+
...info.frameWidth !== void 0 ? { frameWidth: info.frameWidth } : {},
|
|
20552
|
+
...info.frameHeight !== void 0 ? { frameHeight: info.frameHeight } : {},
|
|
20553
|
+
decodeState: decoded.state,
|
|
20554
|
+
rawHeader: info.raw
|
|
20555
|
+
};
|
|
20556
|
+
this.dispatchDetectionEvent(event);
|
|
20557
|
+
};
|
|
20558
|
+
stream.on("additionalHeader", listener);
|
|
20559
|
+
const teardown = () => {
|
|
20560
|
+
stream.off("additionalHeader", listener);
|
|
20561
|
+
this.detectionEventStreamHooks.delete(stream);
|
|
20562
|
+
};
|
|
20563
|
+
this.detectionEventStreamHooks.set(stream, teardown);
|
|
20564
|
+
return teardown;
|
|
20565
|
+
}
|
|
20566
|
+
dispatchDetectionEvent(evt) {
|
|
20567
|
+
for (const cb of this.detectionEventListeners) {
|
|
20568
|
+
try {
|
|
20569
|
+
void Promise.resolve(cb(evt)).catch((e) => {
|
|
20570
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
20571
|
+
this.logger,
|
|
20572
|
+
"[ReolinkBaichuanApi] onDetection handler error",
|
|
20573
|
+
formatErrorForLog(e)
|
|
20574
|
+
);
|
|
20575
|
+
});
|
|
20576
|
+
} catch (e) {
|
|
20577
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
20578
|
+
this.logger,
|
|
20579
|
+
"[ReolinkBaichuanApi] onDetection handler error",
|
|
20580
|
+
formatErrorForLog(e)
|
|
20581
|
+
);
|
|
20582
|
+
}
|
|
20583
|
+
}
|
|
20584
|
+
}
|
|
19671
20585
|
startSimpleEventResubscribeTimer() {
|
|
19672
20586
|
if (this.simpleEventResubscribeTimer) return;
|
|
19673
20587
|
if (this.simpleEventListeners.size === 0) return;
|
|
@@ -20050,6 +20964,9 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
20050
20964
|
this.stopUdpSleepInference();
|
|
20051
20965
|
this.stopSimpleEventWatchdog();
|
|
20052
20966
|
this.stopSimpleEventResubscribeTimer();
|
|
20967
|
+
this.objectDetectionListeners.clear();
|
|
20968
|
+
await this.tearDownObjectDetectionStream().catch(() => {
|
|
20969
|
+
});
|
|
20053
20970
|
await this.cleanup();
|
|
20054
20971
|
await this.stopAllActiveStreams();
|
|
20055
20972
|
await this.cleanupSocketPool();
|
|
@@ -20397,6 +21314,53 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
20397
21314
|
const xml = `<?xml version="1.0" encoding="UTF-8" ?><body><${tag} version="1.1"><enable>${params.enable ? 1 : 0}</enable></${tag}></body>`;
|
|
20398
21315
|
await this.sendXml({ cmdId: 36, payloadXml: xml });
|
|
20399
21316
|
}
|
|
21317
|
+
/**
|
|
21318
|
+
* Full port-config setter (cmd_id 36). Patches one or more of the six
|
|
21319
|
+
* service ports the camera serves — Server (Baichuan), HTTP, HTTPS,
|
|
21320
|
+
* RTSP, RTMP, ONVIF. Each entry takes an optional `port` (number) and
|
|
21321
|
+
* `enable` (boolean); fields the caller doesn't pass are left alone.
|
|
21322
|
+
*
|
|
21323
|
+
* Sends one block per port that has any field set, then issues a
|
|
21324
|
+
* single cmd_36 with the merged body. The camera accepts multiple
|
|
21325
|
+
* `<XxxPort>` siblings in the same payload.
|
|
21326
|
+
*
|
|
21327
|
+
* Wire format observed on E1 Zoom:
|
|
21328
|
+
*
|
|
21329
|
+
* <body>
|
|
21330
|
+
* <RtspPort version="1.1">
|
|
21331
|
+
* <rtspPort>554</rtspPort>
|
|
21332
|
+
* <enable>1</enable>
|
|
21333
|
+
* </RtspPort>
|
|
21334
|
+
* <HttpsPort version="1.1">
|
|
21335
|
+
* <enable>0</enable>
|
|
21336
|
+
* </HttpsPort>
|
|
21337
|
+
* ...
|
|
21338
|
+
* </body>
|
|
21339
|
+
*/
|
|
21340
|
+
async setPortConfig(patch) {
|
|
21341
|
+
const blocks = [];
|
|
21342
|
+
const append = (tag, portField, cfg) => {
|
|
21343
|
+
if (!cfg) return;
|
|
21344
|
+
if (cfg.port === void 0 && cfg.enable === void 0) return;
|
|
21345
|
+
const inner = [];
|
|
21346
|
+
if (cfg.port !== void 0) {
|
|
21347
|
+
inner.push(`<${portField}>${cfg.port}</${portField}>`);
|
|
21348
|
+
}
|
|
21349
|
+
if (cfg.enable !== void 0) {
|
|
21350
|
+
inner.push(`<enable>${cfg.enable ? 1 : 0}</enable>`);
|
|
21351
|
+
}
|
|
21352
|
+
blocks.push(`<${tag} version="1.1">${inner.join("")}</${tag}>`);
|
|
21353
|
+
};
|
|
21354
|
+
append("ServerPort", "serverPort", patch.server);
|
|
21355
|
+
append("HttpPort", "httpPort", patch.http);
|
|
21356
|
+
append("HttpsPort", "httpsPort", patch.https);
|
|
21357
|
+
append("RtspPort", "rtspPort", patch.rtsp);
|
|
21358
|
+
append("RtmpPort", "rtmpPort", patch.rtmp);
|
|
21359
|
+
append("OnvifPort", "onvifPort", patch.onvif);
|
|
21360
|
+
if (blocks.length === 0) return;
|
|
21361
|
+
const xml = `<?xml version="1.0" encoding="UTF-8" ?><body>${blocks.join("")}</body>`;
|
|
21362
|
+
await this.sendXml({ cmdId: 36, payloadXml: xml });
|
|
21363
|
+
}
|
|
20400
21364
|
/** GetDevInfo via Baichuan: host cmd_id 80, channel cmd_id 318 */
|
|
20401
21365
|
async getInfo(channel, options) {
|
|
20402
21366
|
const req = { cmdId: channel == null ? 80 : 318 };
|
|
@@ -24081,6 +25045,27 @@ ${stderr}`)
|
|
|
24081
25045
|
);
|
|
24082
25046
|
}
|
|
24083
25047
|
}
|
|
25048
|
+
async gotoPtzPreset(arg1, arg2) {
|
|
25049
|
+
const ch = arg2 === void 0 ? this.normalizeChannel(void 0) : this.normalizeChannel(arg1);
|
|
25050
|
+
const presetId = arg2 === void 0 ? arg1 : arg2;
|
|
25051
|
+
const channelId = ch;
|
|
25052
|
+
const payloadXml = buildPtzPresetXmlV2(channelId, presetId, "toPos");
|
|
25053
|
+
const extensionXml = buildChannelExtensionXml(channelId);
|
|
25054
|
+
const frame = await this.client.sendFrame({
|
|
25055
|
+
cmdId: BC_CMD_ID_PTZ_CONTROL_PRESET,
|
|
25056
|
+
channel: ch,
|
|
25057
|
+
channelIdOverride: channelId,
|
|
25058
|
+
extensionXml,
|
|
25059
|
+
payloadXml,
|
|
25060
|
+
messageClass: BC_CLASS_MODERN_24,
|
|
25061
|
+
streamType: 0
|
|
25062
|
+
});
|
|
25063
|
+
if (frame.header.responseCode !== 200) {
|
|
25064
|
+
throw new Error(
|
|
25065
|
+
`PTZ goto preset rejected (response_code ${frame.header.responseCode})`
|
|
25066
|
+
);
|
|
25067
|
+
}
|
|
25068
|
+
}
|
|
24084
25069
|
async deletePtzPreset(arg1, arg2) {
|
|
24085
25070
|
const ch = arg2 === void 0 ? this.normalizeChannel(void 0) : this.normalizeChannel(arg1);
|
|
24086
25071
|
const presetId = arg2 === void 0 ? arg1 : arg2;
|
|
@@ -24732,22 +25717,62 @@ ${stderr}`)
|
|
|
24732
25717
|
const channel = typeof arg1 === "number" ? arg1 : arg3;
|
|
24733
25718
|
const enabled = typeof arg1 === "number" ? arg2 : arg1;
|
|
24734
25719
|
const sensitivity = typeof arg1 === "number" ? arg3 : arg2;
|
|
24735
|
-
|
|
25720
|
+
return await this.setMotionAlarmFull({
|
|
25721
|
+
...channel !== void 0 ? { channel } : {},
|
|
25722
|
+
enabled,
|
|
25723
|
+
...sensitivity !== void 0 ? { sensitivity } : {}
|
|
25724
|
+
});
|
|
25725
|
+
}
|
|
25726
|
+
/**
|
|
25727
|
+
* Set motion alarm with full control, including the detection-zone grid.
|
|
25728
|
+
*
|
|
25729
|
+
* Wire format observed on E1 Zoom (cmd_id=47 SetMdAlarm body):
|
|
25730
|
+
*
|
|
25731
|
+
* <MD version="1.1">
|
|
25732
|
+
* <channelId>0</channelId>
|
|
25733
|
+
* <enable>1</enable>
|
|
25734
|
+
* <usepir>0</usepir>
|
|
25735
|
+
* <width>60</width> <height>33</height>
|
|
25736
|
+
* <scope>
|
|
25737
|
+
* <columns>96</columns> <rows>64</rows>
|
|
25738
|
+
* <valueTable>{base64 6144-bit bitmap}</valueTable>
|
|
25739
|
+
* </scope>
|
|
25740
|
+
* ... other camera-specific fields ...
|
|
25741
|
+
* </MD>
|
|
25742
|
+
*
|
|
25743
|
+
* We do a read-modify-write of the GET response so any camera-specific
|
|
25744
|
+
* extension fields are preserved untouched. Pass `valueTable` to update
|
|
25745
|
+
* the detection zone — see `encodeMotionScopeBitmap` for the bitmap layout.
|
|
25746
|
+
*
|
|
25747
|
+
* @param channel - 0-based channel
|
|
25748
|
+
* @param enabled - toggle motion detection on/off (optional)
|
|
25749
|
+
* @param sensitivity - 0-50, higher = more sensitive (optional)
|
|
25750
|
+
* @param valueTable - base64-encoded grid bitmap; size must match
|
|
25751
|
+
* `<scope><columns>×<rows></scope>` from the GET (optional)
|
|
25752
|
+
*/
|
|
25753
|
+
async setMotionAlarmFull(opts) {
|
|
25754
|
+
const ch = this.normalizeChannel(opts.channel);
|
|
24736
25755
|
const currentXml = await this.sendXml({
|
|
24737
25756
|
cmdId: BC_CMD_ID_GET_MOTION_ALARM,
|
|
24738
25757
|
channel: ch
|
|
24739
25758
|
});
|
|
24740
25759
|
let modifiedXml = currentXml;
|
|
24741
|
-
if (enabled !== void 0) {
|
|
25760
|
+
if (opts.enabled !== void 0) {
|
|
24742
25761
|
modifiedXml = modifiedXml.replace(
|
|
24743
25762
|
/<enable>[^<]*<\/enable>/,
|
|
24744
|
-
`<enable>${enabled ? "1" : "0"}</enable>`
|
|
25763
|
+
`<enable>${opts.enabled ? "1" : "0"}</enable>`
|
|
24745
25764
|
);
|
|
24746
25765
|
}
|
|
24747
|
-
if (sensitivity !== void 0) {
|
|
25766
|
+
if (opts.sensitivity !== void 0) {
|
|
24748
25767
|
modifiedXml = modifiedXml.replace(
|
|
24749
25768
|
/<sensitivityDefault>[^<]*<\/sensitivityDefault>/,
|
|
24750
|
-
`<sensitivityDefault>${sensitivity}</sensitivityDefault>`
|
|
25769
|
+
`<sensitivityDefault>${opts.sensitivity}</sensitivityDefault>`
|
|
25770
|
+
);
|
|
25771
|
+
}
|
|
25772
|
+
if (opts.valueTable !== void 0) {
|
|
25773
|
+
modifiedXml = modifiedXml.replace(
|
|
25774
|
+
/<valueTable>[^<]*<\/valueTable>/,
|
|
25775
|
+
`<valueTable>${opts.valueTable}</valueTable>`
|
|
24751
25776
|
);
|
|
24752
25777
|
}
|
|
24753
25778
|
await this.sendXml({
|
|
@@ -26091,12 +27116,24 @@ ${xml}`
|
|
|
26091
27116
|
}
|
|
26092
27117
|
/**
|
|
26093
27118
|
* SetEnc via Baichuan (cmdId=57). Read-modify-write — preserves
|
|
26094
|
-
* unspecified fields. Mirrors reolink_aio's `SetEnc
|
|
27119
|
+
* unspecified fields. Mirrors reolink_aio's `SetEnc` plus the additional
|
|
27120
|
+
* `width`/`height`/`encoderType`/`encoderProfile`/`gop`/`thirdStream`
|
|
27121
|
+
* fields observed in the official mobile app (see `pcap/resolution.pcapng`).
|
|
27122
|
+
*
|
|
27123
|
+
* Field meaning per stream:
|
|
27124
|
+
* - `audio` — 0/1 toggle
|
|
27125
|
+
* - `width`/`height` — resolution in pixels. Must be one of the
|
|
27126
|
+
* resolutions returned by {@link getStreamInfoList}.
|
|
27127
|
+
* - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
|
|
27128
|
+
* - `frameRate` — fps. Must match the table from `getStreamInfoList`.
|
|
27129
|
+
* - `videoEncType` — `"h264"` or `"h265"`
|
|
27130
|
+
* - `encoderType` — `"vbr"` or `"cbr"`
|
|
27131
|
+
* - `encoderProfile` — `"high"`, `"main"`, or `"baseline"`
|
|
27132
|
+
* - `gop` — keyframe interval in seconds (sets `<gop><cur>`)
|
|
26095
27133
|
*
|
|
26096
27134
|
* @param channel - Channel number (0-based)
|
|
26097
|
-
* @param patch - Fields to update
|
|
26098
|
-
*
|
|
26099
|
-
* to change.
|
|
27135
|
+
* @param patch - Fields to update. Pass only the fields you want to change;
|
|
27136
|
+
* everything else is preserved from the device's current configuration.
|
|
26100
27137
|
*/
|
|
26101
27138
|
async setEnc(channel, patch, options) {
|
|
26102
27139
|
const ch = this.normalizeChannel(channel);
|
|
@@ -26113,6 +27150,7 @@ ${xml}`
|
|
|
26113
27150
|
}
|
|
26114
27151
|
xml = applyStreamPatch(xml, "mainStream", patch.mainStream);
|
|
26115
27152
|
xml = applyStreamPatch(xml, "subStream", patch.subStream);
|
|
27153
|
+
xml = applyStreamPatch(xml, "thirdStream", patch.thirdStream);
|
|
26116
27154
|
await this.sendXml({
|
|
26117
27155
|
cmdId: BC_CMD_ID_SET_ENC,
|
|
26118
27156
|
channel: ch,
|
|
@@ -26720,6 +27758,71 @@ ${xml}`
|
|
|
26720
27758
|
`PCAP-derived settings GET failed for cmdId=${params.cmdId}: ${String(lastErr)}`
|
|
26721
27759
|
);
|
|
26722
27760
|
}
|
|
27761
|
+
/**
|
|
27762
|
+
* Update the OSD timestamp + channel-name overlay via cmd_id=45
|
|
27763
|
+
* (SetOsdDatetime). The schema is the same `<body><OsdDatetime>` +
|
|
27764
|
+
* `<OsdChannelName>` block returned by `getOsdDatetime` — we
|
|
27765
|
+
* read-modify-write so any extension fields the camera sent are
|
|
27766
|
+
* preserved.
|
|
27767
|
+
*
|
|
27768
|
+
* Position is in **camera pixel coordinates** (e.g. (1,1) for top-left,
|
|
27769
|
+
* not preset strings). Set `enable=0` to hide the overlay; the camera
|
|
27770
|
+
* keeps the stored position so re-enabling later restores it.
|
|
27771
|
+
*/
|
|
27772
|
+
async setOsdDatetime(channel, patch, options) {
|
|
27773
|
+
const ch = this.normalizeChannel(channel);
|
|
27774
|
+
const timeoutOpts = options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {};
|
|
27775
|
+
let xml = await this.sendPcapDerivedSettingsGetXml({
|
|
27776
|
+
cmdId: BC_CMD_ID_GET_OSD_DATETIME,
|
|
27777
|
+
channel: ch,
|
|
27778
|
+
...timeoutOpts
|
|
27779
|
+
});
|
|
27780
|
+
const patchBlock = (block, fields) => {
|
|
27781
|
+
const start = xml.indexOf(`<${block}`);
|
|
27782
|
+
if (start < 0) return;
|
|
27783
|
+
const end = xml.indexOf(`</${block}>`, start);
|
|
27784
|
+
if (end < 0) return;
|
|
27785
|
+
let body = xml.slice(start, end);
|
|
27786
|
+
for (const [tag, value] of Object.entries(fields)) {
|
|
27787
|
+
if (value === void 0) continue;
|
|
27788
|
+
const raw = typeof value === "boolean" ? value ? "1" : "0" : String(value);
|
|
27789
|
+
const escaped = raw.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
27790
|
+
if (body.includes(`<${tag}>`)) {
|
|
27791
|
+
body = body.replace(
|
|
27792
|
+
new RegExp(`<${tag}>[^<]*<\\/${tag}>`),
|
|
27793
|
+
`<${tag}>${escaped}</${tag}>`
|
|
27794
|
+
);
|
|
27795
|
+
} else {
|
|
27796
|
+
body += `<${tag}>${escaped}</${tag}>`;
|
|
27797
|
+
}
|
|
27798
|
+
}
|
|
27799
|
+
xml = xml.slice(0, start) + body + xml.slice(end);
|
|
27800
|
+
};
|
|
27801
|
+
if (patch.datetime) {
|
|
27802
|
+
patchBlock("OsdDatetime", {
|
|
27803
|
+
enable: patch.datetime.enable,
|
|
27804
|
+
topLeftX: patch.datetime.topLeftX,
|
|
27805
|
+
topLeftY: patch.datetime.topLeftY,
|
|
27806
|
+
language: patch.datetime.language
|
|
27807
|
+
});
|
|
27808
|
+
}
|
|
27809
|
+
if (patch.channelName) {
|
|
27810
|
+
patchBlock("OsdChannelName", {
|
|
27811
|
+
name: patch.channelName.name,
|
|
27812
|
+
enable: patch.channelName.enable,
|
|
27813
|
+
topLeftX: patch.channelName.topLeftX,
|
|
27814
|
+
topLeftY: patch.channelName.topLeftY,
|
|
27815
|
+
enWatermark: patch.channelName.enWatermark,
|
|
27816
|
+
enBgcolor: patch.channelName.enBgcolor
|
|
27817
|
+
});
|
|
27818
|
+
}
|
|
27819
|
+
await this.sendXml({
|
|
27820
|
+
cmdId: BC_CMD_ID_SET_OSD_DATETIME,
|
|
27821
|
+
channel: ch,
|
|
27822
|
+
payloadXml: ensureXmlHeader(xml),
|
|
27823
|
+
...timeoutOpts
|
|
27824
|
+
});
|
|
27825
|
+
}
|
|
26723
27826
|
async getOsdDatetime(channel, options) {
|
|
26724
27827
|
const rawXml = await this.sendPcapDerivedSettingsGetXml({
|
|
26725
27828
|
cmdId: BC_CMD_ID_GET_OSD_DATETIME,
|
|
@@ -26912,6 +28015,41 @@ ${xml}`
|
|
|
26912
28015
|
});
|
|
26913
28016
|
return { streams };
|
|
26914
28017
|
}
|
|
28018
|
+
/**
|
|
28019
|
+
* Return the set of values `setEnc` will accept on each stream of `channel`.
|
|
28020
|
+
* Aggregates `getStreamInfoList` (cmd_146) into a UI-friendly shape:
|
|
28021
|
+
* per-stream resolutions with their allowed codecs/framerates/bitrates plus
|
|
28022
|
+
* the enumerated encoder modes/profiles Reolink exposes.
|
|
28023
|
+
*
|
|
28024
|
+
* Useful for populating selectors and validating user input before calling
|
|
28025
|
+
* `setEnc` — picking an unsupported combination causes the camera to reject
|
|
28026
|
+
* the SET_ENC command (responseCode != 200).
|
|
28027
|
+
*/
|
|
28028
|
+
async getEncOptions(channel, options) {
|
|
28029
|
+
const list = await this.getStreamInfoList(channel, options);
|
|
28030
|
+
return buildEncOptions(list, channel);
|
|
28031
|
+
}
|
|
28032
|
+
/**
|
|
28033
|
+
* Read the camera's `<VersionInfo>` block (cmd_id=80). Returns the
|
|
28034
|
+
* friendly name, model code (e.g. `"E1 Zoom"`), serial number, firmware
|
|
28035
|
+
* version, hardware revision, build day, AI model bundle version, etc.
|
|
28036
|
+
*
|
|
28037
|
+
* This is the same info the Reolink mobile app shows in "About this
|
|
28038
|
+
* device" — distinct from `getSystemGeneral` (cmd_104) which carries
|
|
28039
|
+
* time/locale.
|
|
28040
|
+
*
|
|
28041
|
+
* No channel parameter: this command is device-global on NVRs/Hubs and
|
|
28042
|
+
* camera-global on standalone cameras. Pass an explicit channel via the
|
|
28043
|
+
* underlying `sendXml` only if a specific firmware demands it (none we've
|
|
28044
|
+
* tested do).
|
|
28045
|
+
*/
|
|
28046
|
+
async getVersionInfo(options) {
|
|
28047
|
+
const xml = await this.sendXml({
|
|
28048
|
+
cmdId: BC_CMD_ID_GET_VERSION_INFO,
|
|
28049
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28050
|
+
});
|
|
28051
|
+
return parseVersionInfo(xml);
|
|
28052
|
+
}
|
|
26915
28053
|
async getLedState(channel, options) {
|
|
26916
28054
|
const rawXml = await this.sendPcapDerivedSettingsGetXml({
|
|
26917
28055
|
cmdId: BC_CMD_ID_GET_LED_STATE,
|
|
@@ -26994,7 +28132,279 @@ ${xml}`
|
|
|
26994
28132
|
...channel != null ? { channel } : {},
|
|
26995
28133
|
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
26996
28134
|
});
|
|
26997
|
-
return
|
|
28135
|
+
return parseEmailTaskFromXml(xml);
|
|
28136
|
+
}
|
|
28137
|
+
/**
|
|
28138
|
+
* SetEmailTask via Baichuan (cmdId=216). Updates the email alarm schedule
|
|
28139
|
+
* (per-event-type 7×24 valueTable + master enable).
|
|
28140
|
+
*
|
|
28141
|
+
* Reolink expects the FULL `typeScheduleList` — pass the array from a prior
|
|
28142
|
+
* GET and only flip the entries you care about. Slots you don't track must
|
|
28143
|
+
* be sent back unchanged to avoid the camera dropping them.
|
|
28144
|
+
*/
|
|
28145
|
+
async setEmailTask(channel, task, options) {
|
|
28146
|
+
const ch = this.normalizeChannel(channel);
|
|
28147
|
+
const payloadXml = buildSetEmailTaskXml({ ...task, channelId: ch });
|
|
28148
|
+
await this.sendXml({
|
|
28149
|
+
cmdId: BC_CMD_ID_SET_EMAIL_TASK,
|
|
28150
|
+
channel: ch,
|
|
28151
|
+
payloadXml,
|
|
28152
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28153
|
+
});
|
|
28154
|
+
}
|
|
28155
|
+
/**
|
|
28156
|
+
* Convenience wrapper that patches the schedule of one or more trigger
|
|
28157
|
+
* types on the camera's EmailTask without touching the others.
|
|
28158
|
+
*
|
|
28159
|
+
* Pass a high-level schedule spec (`always` / `never` / explicit windows)
|
|
28160
|
+
* and the trigger types it should apply to. The method:
|
|
28161
|
+
*
|
|
28162
|
+
* 1. Reads the current EmailTask via GET (so we keep every existing slot).
|
|
28163
|
+
* 2. Builds the new `valueTable` once from `schedule`.
|
|
28164
|
+
* 3. Replaces the `valueTable` of every matching `type` in the list.
|
|
28165
|
+
* 4. Appends entries for any requested type not already present.
|
|
28166
|
+
* 5. Writes the merged list back via SET.
|
|
28167
|
+
*
|
|
28168
|
+
* Returns the list of types that were actually touched.
|
|
28169
|
+
*/
|
|
28170
|
+
async patchEmailSchedule(channel, spec, options) {
|
|
28171
|
+
const current = await this.getEmailTask(channel, options);
|
|
28172
|
+
const newValueTable = buildEmailScheduleValueTable(spec.schedule);
|
|
28173
|
+
const targetSet = new Set(spec.types);
|
|
28174
|
+
const touched = [];
|
|
28175
|
+
const updatedList = current.typeScheduleList.map((item) => {
|
|
28176
|
+
if (targetSet.has(item.type)) {
|
|
28177
|
+
touched.push(item.type);
|
|
28178
|
+
return { ...item, valueTable: newValueTable };
|
|
28179
|
+
}
|
|
28180
|
+
return item;
|
|
28181
|
+
});
|
|
28182
|
+
for (const t of spec.types) {
|
|
28183
|
+
if (!current.typeScheduleList.some((item) => item.type === t)) {
|
|
28184
|
+
updatedList.push({ type: t, valueTable: newValueTable });
|
|
28185
|
+
touched.push(t);
|
|
28186
|
+
}
|
|
28187
|
+
}
|
|
28188
|
+
await this.setEmailTask(
|
|
28189
|
+
channel,
|
|
28190
|
+
{
|
|
28191
|
+
channelId: current.channelId,
|
|
28192
|
+
enable: spec.enable ?? current.enable,
|
|
28193
|
+
typeScheduleList: updatedList
|
|
28194
|
+
},
|
|
28195
|
+
options
|
|
28196
|
+
);
|
|
28197
|
+
return { touchedTypes: touched };
|
|
28198
|
+
}
|
|
28199
|
+
// ====================================================================
|
|
28200
|
+
// Email server (cmdId 42/43/141), NTP (38/39), DST (106/107),
|
|
28201
|
+
// SystemGeneral SET (105), AutoReboot (100/101).
|
|
28202
|
+
// Schemas derived from Reolink Client pcap (2026-05-16).
|
|
28203
|
+
// ====================================================================
|
|
28204
|
+
/**
|
|
28205
|
+
* Read the SMTP email configuration (cmdId=42). Returns the full `<Email>`
|
|
28206
|
+
* block including capability hints (`senderMaxLen`, `pwdMaxLen`,
|
|
28207
|
+
* `emailAttachAbility`).
|
|
28208
|
+
*/
|
|
28209
|
+
async getEmail(options) {
|
|
28210
|
+
const xml = await this.sendXml({
|
|
28211
|
+
cmdId: BC_CMD_ID_GET_EMAIL,
|
|
28212
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28213
|
+
});
|
|
28214
|
+
return parseEmailConfigFromXml(xml);
|
|
28215
|
+
}
|
|
28216
|
+
/**
|
|
28217
|
+
* Patch the SMTP email configuration (cmdId=43). Reads the current config
|
|
28218
|
+
* first then merges the patch — Reolink rejects partial `<Email>` blocks.
|
|
28219
|
+
*/
|
|
28220
|
+
async setEmail(patch, options) {
|
|
28221
|
+
const current = await this.getEmail(options);
|
|
28222
|
+
const payloadXml = buildSetEmailXml(current, patch);
|
|
28223
|
+
await this.sendXml({
|
|
28224
|
+
cmdId: BC_CMD_ID_SET_EMAIL,
|
|
28225
|
+
payloadXml,
|
|
28226
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28227
|
+
});
|
|
28228
|
+
}
|
|
28229
|
+
/**
|
|
28230
|
+
* Send a test email using either the current config or an override patch
|
|
28231
|
+
* (cmdId=141). Returns true when the camera reports 200 (test succeeded),
|
|
28232
|
+
* false when it reports 482 (test failed — server unreachable / bad creds).
|
|
28233
|
+
* Other non-200 codes propagate as exceptions via `sendXml`.
|
|
28234
|
+
*/
|
|
28235
|
+
async testEmail(patch, options) {
|
|
28236
|
+
const current = await this.getEmail(options);
|
|
28237
|
+
const payloadXml = buildSetEmailXml(current, patch ?? {});
|
|
28238
|
+
const timeoutMs = options?.timeoutMs ?? 6e4;
|
|
28239
|
+
try {
|
|
28240
|
+
await this.sendXml({
|
|
28241
|
+
cmdId: BC_CMD_ID_TEST_EMAIL,
|
|
28242
|
+
payloadXml,
|
|
28243
|
+
timeoutMs
|
|
28244
|
+
});
|
|
28245
|
+
return true;
|
|
28246
|
+
} catch (err) {
|
|
28247
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28248
|
+
if (msg.includes("response_code 482") || msg.includes("response_code=482")) {
|
|
28249
|
+
return false;
|
|
28250
|
+
}
|
|
28251
|
+
throw err;
|
|
28252
|
+
}
|
|
28253
|
+
}
|
|
28254
|
+
/**
|
|
28255
|
+
* Read the NTP server configuration (cmdId=38).
|
|
28256
|
+
*/
|
|
28257
|
+
async getNtp(options) {
|
|
28258
|
+
const xml = await this.sendXml({
|
|
28259
|
+
cmdId: BC_CMD_ID_GET_NTP,
|
|
28260
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28261
|
+
});
|
|
28262
|
+
return parseNtpConfigFromXml(xml);
|
|
28263
|
+
}
|
|
28264
|
+
/**
|
|
28265
|
+
* Patch the NTP server configuration (cmdId=39). Reads the current state
|
|
28266
|
+
* first and merges the patch — Reolink rejects partial `<Ntp>` blocks.
|
|
28267
|
+
*/
|
|
28268
|
+
async setNtp(patch, options) {
|
|
28269
|
+
const current = await this.getNtp(options);
|
|
28270
|
+
const payloadXml = buildSetNtpXml(current, patch);
|
|
28271
|
+
await this.sendXml({
|
|
28272
|
+
cmdId: BC_CMD_ID_SET_NTP,
|
|
28273
|
+
payloadXml,
|
|
28274
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28275
|
+
});
|
|
28276
|
+
}
|
|
28277
|
+
/**
|
|
28278
|
+
* Patch SystemGeneral (cmdId=105). Supports partial payloads: include only
|
|
28279
|
+
* the fields you want to change. By default the builder emits `<year>0</year>`
|
|
28280
|
+
* as the "do not set manual clock" marker; pass `manualTime` to actually
|
|
28281
|
+
* set the date/time. Setting only `deviceName` automatically uses the
|
|
28282
|
+
* Reolink Client's `deviceNameOnly=1` shape.
|
|
28283
|
+
*/
|
|
28284
|
+
async setSystemGeneral(patch, options) {
|
|
28285
|
+
const payloadXml = buildSetSystemGeneralXml(patch);
|
|
28286
|
+
await this.sendXml({
|
|
28287
|
+
cmdId: BC_CMD_ID_SET_SYSTEM_GENERAL,
|
|
28288
|
+
payloadXml,
|
|
28289
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28290
|
+
});
|
|
28291
|
+
}
|
|
28292
|
+
/**
|
|
28293
|
+
* Read the Daylight Saving Time configuration (cmdId=106).
|
|
28294
|
+
*/
|
|
28295
|
+
async getDst(options) {
|
|
28296
|
+
const xml = await this.sendXml({
|
|
28297
|
+
cmdId: BC_CMD_ID_GET_DST,
|
|
28298
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28299
|
+
});
|
|
28300
|
+
return parseDstConfigFromXml(xml);
|
|
28301
|
+
}
|
|
28302
|
+
/**
|
|
28303
|
+
* Patch the DST configuration (cmdId=107). Reads the current state first
|
|
28304
|
+
* and merges the patch.
|
|
28305
|
+
*/
|
|
28306
|
+
async setDst(patch, options) {
|
|
28307
|
+
const current = await this.getDst(options);
|
|
28308
|
+
const payloadXml = buildSetDstXml(current, patch);
|
|
28309
|
+
await this.sendXml({
|
|
28310
|
+
cmdId: BC_CMD_ID_SET_DST,
|
|
28311
|
+
payloadXml,
|
|
28312
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28313
|
+
});
|
|
28314
|
+
}
|
|
28315
|
+
/**
|
|
28316
|
+
* Read the auto-reboot schedule (cmdId=101).
|
|
28317
|
+
*/
|
|
28318
|
+
async getAutoReboot(options) {
|
|
28319
|
+
const xml = await this.sendXml({
|
|
28320
|
+
cmdId: BC_CMD_ID_GET_AUTO_REBOOT,
|
|
28321
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28322
|
+
});
|
|
28323
|
+
return parseAutoRebootFromXml(xml);
|
|
28324
|
+
}
|
|
28325
|
+
/**
|
|
28326
|
+
* Patch the auto-reboot schedule (cmdId=100).
|
|
28327
|
+
*/
|
|
28328
|
+
async setAutoReboot(patch, options) {
|
|
28329
|
+
const current = await this.getAutoReboot(options);
|
|
28330
|
+
const payloadXml = buildSetAutoRebootXml(current, patch);
|
|
28331
|
+
await this.sendXml({
|
|
28332
|
+
cmdId: BC_CMD_ID_SET_AUTO_REBOOT,
|
|
28333
|
+
payloadXml,
|
|
28334
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
28335
|
+
});
|
|
28336
|
+
}
|
|
28337
|
+
/**
|
|
28338
|
+
* High-level helper that configures the camera to deliver motion alerts via
|
|
28339
|
+
* SMTP to the local nodelink manager. Orchestrates `setEmail` + `setEmailTask`
|
|
28340
|
+
* in a single call so UI code can offer "auto-configure" without juggling
|
|
28341
|
+
* the underlying commands.
|
|
28342
|
+
*
|
|
28343
|
+
* Pass `runTest: true` to also send a test email (cmdId=141). Returns a
|
|
28344
|
+
* structured result describing each leg of the flow so the caller can show
|
|
28345
|
+
* granular feedback.
|
|
28346
|
+
*
|
|
28347
|
+
* @param params Auto-configuration parameters
|
|
28348
|
+
* @param channel Logical channel (default 0). Used for the EmailTask SET.
|
|
28349
|
+
*/
|
|
28350
|
+
async setupEmailPushToManager(params, channel, options) {
|
|
28351
|
+
const port = params.managerPort ?? 2525;
|
|
28352
|
+
const domain = params.domain ?? "nodelink.local";
|
|
28353
|
+
const recipient = `${params.recipientLocalPart}@${domain}`;
|
|
28354
|
+
const triggers = params.triggerTypes ?? ["MD", "people", "vehicle"];
|
|
28355
|
+
const attachmentType = params.attachmentType ?? "picture";
|
|
28356
|
+
const interval = params.interval ?? 30;
|
|
28357
|
+
const emailPatch = {
|
|
28358
|
+
smtpServer: params.managerHost,
|
|
28359
|
+
smtpPort: port,
|
|
28360
|
+
userName: params.authUsername ?? recipient,
|
|
28361
|
+
password: params.authPassword ?? "",
|
|
28362
|
+
address1: recipient,
|
|
28363
|
+
address2: "",
|
|
28364
|
+
address3: "",
|
|
28365
|
+
sendNickname: params.sendNickname ?? params.recipientLocalPart,
|
|
28366
|
+
attachment: attachmentType === "none" ? 0 : 1,
|
|
28367
|
+
attachmentType,
|
|
28368
|
+
textType: "withText",
|
|
28369
|
+
ssl: 0,
|
|
28370
|
+
interval
|
|
28371
|
+
};
|
|
28372
|
+
await this.setEmail(emailPatch, options);
|
|
28373
|
+
const fullWeekOn = "1".repeat(168);
|
|
28374
|
+
const current = await this.getEmailTask(channel, options);
|
|
28375
|
+
const triggerSet = new Set(triggers);
|
|
28376
|
+
const touched = [];
|
|
28377
|
+
const updatedList = current.typeScheduleList.map((item) => {
|
|
28378
|
+
if (triggerSet.has(item.type)) {
|
|
28379
|
+
touched.push(item.type);
|
|
28380
|
+
return { ...item, valueTable: fullWeekOn };
|
|
28381
|
+
}
|
|
28382
|
+
return item;
|
|
28383
|
+
});
|
|
28384
|
+
for (const t of triggers) {
|
|
28385
|
+
if (!current.typeScheduleList.some((item) => item.type === t)) {
|
|
28386
|
+
updatedList.push({ type: t, valueTable: fullWeekOn });
|
|
28387
|
+
touched.push(t);
|
|
28388
|
+
}
|
|
28389
|
+
}
|
|
28390
|
+
await this.setEmailTask(
|
|
28391
|
+
channel,
|
|
28392
|
+
{
|
|
28393
|
+
channelId: current.channelId,
|
|
28394
|
+
enable: 1,
|
|
28395
|
+
typeScheduleList: updatedList
|
|
28396
|
+
},
|
|
28397
|
+
options
|
|
28398
|
+
);
|
|
28399
|
+
const result = {
|
|
28400
|
+
setEmail: { applied: true },
|
|
28401
|
+
setEmailTask: { applied: true, touchedTypes: touched }
|
|
28402
|
+
};
|
|
28403
|
+
if (params.runTest) {
|
|
28404
|
+
const ok = await this.testEmail(emailPatch, options);
|
|
28405
|
+
result.testEmail = { success: ok };
|
|
28406
|
+
}
|
|
28407
|
+
return result;
|
|
26998
28408
|
}
|
|
26999
28409
|
/**
|
|
27000
28410
|
* Get siren-on-motion state via AudioTask (cmdId=232).
|
|
@@ -27263,7 +28673,7 @@ ${xml}`
|
|
|
27263
28673
|
cmdId: BC_CMD_ID_GET_SYSTEM_GENERAL,
|
|
27264
28674
|
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
27265
28675
|
});
|
|
27266
|
-
return
|
|
28676
|
+
return parseSystemGeneralFromXml(xml);
|
|
27267
28677
|
}
|
|
27268
28678
|
/**
|
|
27269
28679
|
* Get device support/capability flags.
|