@apocaliss92/nodelink-js 0.2.1 → 0.2.2
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 +1 -1
- package/dist/{DiagnosticsTools-NUMCYEKQ.js → DiagnosticsTools-FNLGCOVA.js} +2 -2
- package/dist/{chunk-PCPEXOWB.js → chunk-MN7GUZT7.js} +750 -213
- package/dist/chunk-MN7GUZT7.js.map +1 -0
- package/dist/{chunk-YPU7RAEY.js → chunk-NLTB7GTA.js} +17 -1
- package/dist/{chunk-YPU7RAEY.js.map → chunk-NLTB7GTA.js.map} +1 -1
- package/dist/cli/rtsp-server.cjs +747 -210
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +771 -212
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -12
- package/dist/index.d.ts +249 -11
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PCPEXOWB.js.map +0 -1
- /package/dist/{DiagnosticsTools-NUMCYEKQ.js.map → DiagnosticsTools-FNLGCOVA.js.map} +0 -0
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
BC_CMD_ID_CMD_209,
|
|
11
11
|
BC_CMD_ID_CMD_265,
|
|
12
12
|
BC_CMD_ID_CMD_440,
|
|
13
|
+
BC_CMD_ID_DING_DONG_CTRL,
|
|
14
|
+
BC_CMD_ID_DING_DONG_OPT,
|
|
13
15
|
BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|
|
14
16
|
BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO,
|
|
15
17
|
BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD,
|
|
@@ -33,6 +35,9 @@ import {
|
|
|
33
35
|
BC_CMD_ID_GET_BATTERY_INFO_LIST,
|
|
34
36
|
BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
|
|
35
37
|
BC_CMD_ID_GET_DAY_RECORDS,
|
|
38
|
+
BC_CMD_ID_GET_DING_DONG_CFG,
|
|
39
|
+
BC_CMD_ID_GET_DING_DONG_LIST,
|
|
40
|
+
BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
36
41
|
BC_CMD_ID_GET_EMAIL_TASK,
|
|
37
42
|
BC_CMD_ID_GET_FTP_TASK,
|
|
38
43
|
BC_CMD_ID_GET_HDD_INFO_LIST,
|
|
@@ -68,9 +73,12 @@ import {
|
|
|
68
73
|
BC_CMD_ID_PUSH_SERIAL,
|
|
69
74
|
BC_CMD_ID_PUSH_SLEEP_STATUS,
|
|
70
75
|
BC_CMD_ID_PUSH_VIDEO_INPUT,
|
|
76
|
+
BC_CMD_ID_QUICK_REPLY_PLAY,
|
|
71
77
|
BC_CMD_ID_SET_AI_ALARM,
|
|
72
78
|
BC_CMD_ID_SET_AI_CFG,
|
|
73
79
|
BC_CMD_ID_SET_AUDIO_TASK,
|
|
80
|
+
BC_CMD_ID_SET_DING_DONG_CFG,
|
|
81
|
+
BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
74
82
|
BC_CMD_ID_SET_MOTION_ALARM,
|
|
75
83
|
BC_CMD_ID_SET_PIR_INFO,
|
|
76
84
|
BC_CMD_ID_SET_WHITE_LED_STATE,
|
|
@@ -136,7 +144,7 @@ import {
|
|
|
136
144
|
talkTraceLog,
|
|
137
145
|
traceLog,
|
|
138
146
|
xmlEscape
|
|
139
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-NLTB7GTA.js";
|
|
140
148
|
|
|
141
149
|
// src/protocol/framing.ts
|
|
142
150
|
function encodeHeader(h) {
|
|
@@ -5392,6 +5400,8 @@ var NativeStreamFanout = class {
|
|
|
5392
5400
|
} finally {
|
|
5393
5401
|
for (const q of this.queues.values()) q.close();
|
|
5394
5402
|
this.queues.clear();
|
|
5403
|
+
this.running = false;
|
|
5404
|
+
this.opts.onEnd?.();
|
|
5395
5405
|
}
|
|
5396
5406
|
})();
|
|
5397
5407
|
}
|
|
@@ -5717,7 +5727,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5717
5727
|
this.logger.warn(
|
|
5718
5728
|
`[BaichuanRtspServer] Could not get stream metadata: ${error}`
|
|
5719
5729
|
);
|
|
5720
|
-
this.streamMetadata = { frameRate: 25
|
|
5730
|
+
this.streamMetadata = { frameRate: 25 };
|
|
5721
5731
|
this.setFlowVideoType("H264", "metadata unavailable");
|
|
5722
5732
|
}
|
|
5723
5733
|
this.clientConnectionServer = net2.createServer((socket) => {
|
|
@@ -5749,7 +5759,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5749
5759
|
*/
|
|
5750
5760
|
handleRtspConnection(socket) {
|
|
5751
5761
|
const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
5752
|
-
|
|
5762
|
+
const connectTime = Date.now();
|
|
5763
|
+
this.logger.info(
|
|
5764
|
+
`[rebroadcast] client connected client=${clientId} path=${this.path} profile=${this.profile} channel=${this.channel}`
|
|
5765
|
+
);
|
|
5753
5766
|
let sessionId = "";
|
|
5754
5767
|
let buffer = Buffer.alloc(0);
|
|
5755
5768
|
let clientFfmpeg;
|
|
@@ -5757,6 +5770,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5757
5770
|
let clientUdpSocket = null;
|
|
5758
5771
|
let clientUdpSocketAudio = null;
|
|
5759
5772
|
const cleanup = () => {
|
|
5773
|
+
const sessionDurationMs = Date.now() - connectTime;
|
|
5774
|
+
const res = this.clientResources.get(clientId);
|
|
5775
|
+
const framesSent = res?.framesSent ?? 0;
|
|
5776
|
+
this.logger.info(
|
|
5777
|
+
`[rebroadcast] client disconnected client=${clientId} path=${this.path} profile=${this.profile} duration=${sessionDurationMs}ms frames=${framesSent}`
|
|
5778
|
+
);
|
|
5760
5779
|
this.removeClient(clientId);
|
|
5761
5780
|
this.authNonces.delete(clientId);
|
|
5762
5781
|
const resources = this.clientResources.get(clientId);
|
|
@@ -5898,7 +5917,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5898
5917
|
Public: "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS"
|
|
5899
5918
|
});
|
|
5900
5919
|
} else if (method === "DESCRIBE") {
|
|
5901
|
-
if (!this.
|
|
5920
|
+
if (!this.flow.getFmtp().hasParamSets && this.connectedClients.size === 0) {
|
|
5902
5921
|
try {
|
|
5903
5922
|
if (!this.nativeStreamActive) {
|
|
5904
5923
|
await this.startNativeStream();
|
|
@@ -5980,7 +5999,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5980
5999
|
seenFirstVideoKeyframe: false,
|
|
5981
6000
|
setupTrack0: false,
|
|
5982
6001
|
setupTrack1: false,
|
|
5983
|
-
isPlaying: false
|
|
6002
|
+
isPlaying: false,
|
|
6003
|
+
connectTime
|
|
5984
6004
|
});
|
|
5985
6005
|
} else {
|
|
5986
6006
|
existing.rtspSocket = socket;
|
|
@@ -6027,8 +6047,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6027
6047
|
if (resources) {
|
|
6028
6048
|
if (isTrack1) resources.setupTrack1 = true;
|
|
6029
6049
|
else resources.setupTrack0 = true;
|
|
6030
|
-
|
|
6031
|
-
|
|
6050
|
+
const transport2 = useTcpInterleaved ? "TCP/interleaved" : "UDP";
|
|
6051
|
+
const track = isTrack1 ? "track1(audio)" : "track0(video)";
|
|
6052
|
+
this.logger.info(
|
|
6053
|
+
`[rebroadcast] SETUP client=${clientId} ${track} transport=${transport2} session=${sessionId}`
|
|
6032
6054
|
);
|
|
6033
6055
|
}
|
|
6034
6056
|
}
|
|
@@ -6053,8 +6075,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6053
6075
|
const resources = this.clientResources.get(clientId);
|
|
6054
6076
|
if (resources) {
|
|
6055
6077
|
resources.isPlaying = true;
|
|
6056
|
-
|
|
6057
|
-
|
|
6078
|
+
const hasAudio = !!resources.setupTrack1;
|
|
6079
|
+
this.logger.info(
|
|
6080
|
+
`[rebroadcast] PLAY client=${clientId} path=${this.path} profile=${this.profile} channel=${this.channel} codec=${this.flow.sdpCodec} audio=${hasAudio} session=${sessionId}`
|
|
6058
6081
|
);
|
|
6059
6082
|
}
|
|
6060
6083
|
}
|
|
@@ -6063,6 +6086,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6063
6086
|
Range: "npt=0.000-"
|
|
6064
6087
|
});
|
|
6065
6088
|
} else if (method === "TEARDOWN") {
|
|
6089
|
+
this.logger.info(
|
|
6090
|
+
`[rebroadcast] TEARDOWN client=${clientId} session=${sessionId}`
|
|
6091
|
+
);
|
|
6066
6092
|
cleanup();
|
|
6067
6093
|
sendResponse(200, "OK", {
|
|
6068
6094
|
Session: sessionId
|
|
@@ -6157,7 +6183,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6157
6183
|
this.logger.warn(
|
|
6158
6184
|
`[BaichuanRtspServer] Could not fetch stream metadata: ${error}`
|
|
6159
6185
|
);
|
|
6160
|
-
streamMetadata = { frameRate: 25
|
|
6186
|
+
streamMetadata = { frameRate: 25 };
|
|
6161
6187
|
}
|
|
6162
6188
|
}
|
|
6163
6189
|
const ffmpegFormat = this.flow.ffmpegFormat;
|
|
@@ -6745,15 +6771,17 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6745
6771
|
`Sent ${frameCount} frames to client ${clientId} (frame size: ${frame.data.length} bytes)`
|
|
6746
6772
|
);
|
|
6747
6773
|
}
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6774
|
+
if (!useDirectRtp) {
|
|
6775
|
+
const now = Date.now();
|
|
6776
|
+
const timeSinceLastFrame = now - lastFrameTime;
|
|
6777
|
+
const waitTime = targetFrameInterval - timeSinceLastFrame;
|
|
6778
|
+
if (waitTime > 0) {
|
|
6779
|
+
await new Promise(
|
|
6780
|
+
(resolve) => setTimeout(resolve, Math.min(waitTime, targetFrameInterval * 2))
|
|
6781
|
+
);
|
|
6782
|
+
}
|
|
6783
|
+
lastFrameTime = Date.now();
|
|
6755
6784
|
}
|
|
6756
|
-
lastFrameTime = Date.now();
|
|
6757
6785
|
if (useDirectRtp) {
|
|
6758
6786
|
const videoType = frame.videoType ?? this.flow.videoType;
|
|
6759
6787
|
const normalizedVideoData = videoType === "H264" ? convertToAnnexB(frame.data) : convertToAnnexB2(frame.data);
|
|
@@ -6826,6 +6854,11 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6826
6854
|
}
|
|
6827
6855
|
if (!firstVideoWriteLogged) {
|
|
6828
6856
|
firstVideoWriteLogged = true;
|
|
6857
|
+
const clientConnectTime = resources?.connectTime ?? Date.now();
|
|
6858
|
+
const ttffMs = Date.now() - clientConnectTime;
|
|
6859
|
+
this.logger.info(
|
|
6860
|
+
`[rebroadcast] first keyframe \u2192 client client=${clientId} codec=${videoType} ttff=${ttffMs}ms`
|
|
6861
|
+
);
|
|
6829
6862
|
if (rtspDebug) {
|
|
6830
6863
|
const headHex = frame.data.subarray(0, 16).toString("hex");
|
|
6831
6864
|
rtspDebugLog(
|
|
@@ -6833,6 +6866,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6833
6866
|
);
|
|
6834
6867
|
}
|
|
6835
6868
|
}
|
|
6869
|
+
if (resources) {
|
|
6870
|
+
resources.framesSent = (resources.framesSent ?? 0) + 1;
|
|
6871
|
+
}
|
|
6836
6872
|
sendVideoAccessUnit(videoType, normalizedVideoData, true);
|
|
6837
6873
|
} else {
|
|
6838
6874
|
try {
|
|
@@ -6917,8 +6953,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6917
6953
|
this.firstAudioPromise = new Promise((resolve) => {
|
|
6918
6954
|
this.firstAudioResolve = resolve;
|
|
6919
6955
|
});
|
|
6920
|
-
this.
|
|
6921
|
-
`
|
|
6956
|
+
this.logger.info(
|
|
6957
|
+
`[rebroadcast] native stream starting profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
|
|
6922
6958
|
);
|
|
6923
6959
|
await this.flow.startKeepAlive(this.api);
|
|
6924
6960
|
this.nativeFanout = new NativeStreamFanout({
|
|
@@ -6961,6 +6997,23 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6961
6997
|
this.logger.warn(
|
|
6962
6998
|
`[BaichuanRtspServer] Shared native stream error: ${error}`
|
|
6963
6999
|
);
|
|
7000
|
+
},
|
|
7001
|
+
onEnd: () => {
|
|
7002
|
+
if (!this.nativeStreamActive) return;
|
|
7003
|
+
this.nativeStreamActive = false;
|
|
7004
|
+
this.firstFrameReceived = false;
|
|
7005
|
+
this.firstFramePromise = null;
|
|
7006
|
+
this.firstFrameResolve = null;
|
|
7007
|
+
this.nativeFanout = null;
|
|
7008
|
+
this.logger.info(
|
|
7009
|
+
`[rebroadcast] native stream ended (camera sleeping or connection lost) profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
|
|
7010
|
+
);
|
|
7011
|
+
if (this.connectedClients.size > 0) {
|
|
7012
|
+
this.logger.info(
|
|
7013
|
+
`[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
|
|
7014
|
+
);
|
|
7015
|
+
setImmediate(() => void this.startNativeStream());
|
|
7016
|
+
}
|
|
6964
7017
|
}
|
|
6965
7018
|
});
|
|
6966
7019
|
this.nativeFanout.start();
|
|
@@ -6999,7 +7052,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6999
7052
|
if (!this.nativeStreamActive) {
|
|
7000
7053
|
return;
|
|
7001
7054
|
}
|
|
7002
|
-
this.
|
|
7055
|
+
this.logger.info(
|
|
7056
|
+
`[rebroadcast] native stream stopping profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
|
|
7057
|
+
);
|
|
7003
7058
|
this.flow.stopKeepAlive();
|
|
7004
7059
|
this.clearNoClientAutoStopTimer();
|
|
7005
7060
|
this.nativeStreamActive = false;
|
|
@@ -7033,9 +7088,6 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7033
7088
|
if (this.connectedClients.has(clientId)) {
|
|
7034
7089
|
this.connectedClients.delete(clientId);
|
|
7035
7090
|
this.emit("clientDisconnected", clientId);
|
|
7036
|
-
this.logger.info(
|
|
7037
|
-
`[BaichuanRtspServer] RTSP client disconnected: ${clientId}`
|
|
7038
|
-
);
|
|
7039
7091
|
if (this.connectedClients.size === 0) {
|
|
7040
7092
|
void this.stopNativeStream();
|
|
7041
7093
|
}
|
|
@@ -7268,10 +7320,12 @@ function parseSupportXml(xml) {
|
|
|
7268
7320
|
}
|
|
7269
7321
|
function getSupportItemForChannel(support, channel) {
|
|
7270
7322
|
if (!support?.items?.length) return void 0;
|
|
7271
|
-
const
|
|
7323
|
+
const candidates = support.items.filter((i) => i.chnID === channel);
|
|
7324
|
+
if (!candidates.length) return void 0;
|
|
7325
|
+
const score = (item) => {
|
|
7272
7326
|
const anyItem = item;
|
|
7273
|
-
let
|
|
7274
|
-
if (anyItem.name == null)
|
|
7327
|
+
let result = 0;
|
|
7328
|
+
if (anyItem.name == null) result += 100;
|
|
7275
7329
|
const capabilityKeys = [
|
|
7276
7330
|
"ptzType",
|
|
7277
7331
|
"ptzControl",
|
|
@@ -7283,20 +7337,17 @@ function getSupportItemForChannel(support, channel) {
|
|
|
7283
7337
|
"motion",
|
|
7284
7338
|
"encCtrl",
|
|
7285
7339
|
"newIspCfg",
|
|
7286
|
-
"remoteAbility"
|
|
7340
|
+
"remoteAbility",
|
|
7341
|
+
"aitype",
|
|
7342
|
+
"videoClip",
|
|
7343
|
+
"snap"
|
|
7287
7344
|
];
|
|
7288
7345
|
for (const k of capabilityKeys) {
|
|
7289
|
-
if (anyItem[k] !== void 0)
|
|
7346
|
+
if (anyItem[k] !== void 0) result += 3;
|
|
7290
7347
|
}
|
|
7291
|
-
|
|
7292
|
-
return score;
|
|
7293
|
-
};
|
|
7294
|
-
const pickBest = (chnId) => {
|
|
7295
|
-
const candidates = support.items.filter((i) => i.chnID === chnId);
|
|
7296
|
-
if (!candidates.length) return void 0;
|
|
7297
|
-
return candidates.slice().sort((a, b) => scoreSupportItem(b) - scoreSupportItem(a))[0];
|
|
7348
|
+
return result;
|
|
7298
7349
|
};
|
|
7299
|
-
return
|
|
7350
|
+
return candidates.sort((a, b) => score(b) - score(a))[0];
|
|
7300
7351
|
}
|
|
7301
7352
|
function computeDeviceCapabilities(params) {
|
|
7302
7353
|
const { channel } = params;
|
|
@@ -7328,6 +7379,7 @@ function computeDeviceCapabilities(params) {
|
|
|
7328
7379
|
flat,
|
|
7329
7380
|
/white\s*led|whiteLed|flood\s*light|floodlight/i
|
|
7330
7381
|
);
|
|
7382
|
+
const hasSirenFromSupport = supportItem ? isTruthyNumberLike(supportItem.audioVersion) : false;
|
|
7331
7383
|
const hasSirenFromAbilities = abilitiesHasAny(
|
|
7332
7384
|
flat,
|
|
7333
7385
|
/audio\s*alarm|audioAlarm|siren|pushAlarn|audioPlay/i
|
|
@@ -7340,6 +7392,9 @@ function computeDeviceCapabilities(params) {
|
|
|
7340
7392
|
const hasPirFromSupport = supportItem ? isTruthyNumberLike(supportItem.rfCfg) || isTruthyNumberLike(supportItem.newRfCfg) || isTruthyNumberLike(supportItem.rfVersion) || isTruthyNumberLike(supportItem.battery) : false;
|
|
7341
7393
|
const hasAutotrackingFromSupport = supportItem ? isTruthyNumberLike(supportItem.autoPt) || isTruthyNumberLike(supportItem.smartAI) : false;
|
|
7342
7394
|
const hasAutotrackingFromAbilities = abilitiesHasAny(flat, /smartTrack/i);
|
|
7395
|
+
const hasBattery = hasBatteryFromSupport || hasBatteryFromAbilities;
|
|
7396
|
+
const isDoorbell = isDoorbellFromSupport || isDoorbellFromModel;
|
|
7397
|
+
const hasWirelessChimeFromAbilities = abilitiesHasAny(flat, /dingDong|dingdong/i);
|
|
7343
7398
|
const hasPan = hasPanTiltFromSupport || hasPanTiltFromAbilities;
|
|
7344
7399
|
const hasTilt = hasPanTiltFromSupport || hasPanTiltFromAbilities;
|
|
7345
7400
|
const hasZoom = hasZoomFromSupport || hasZoomFromAbilities;
|
|
@@ -7355,14 +7410,15 @@ function computeDeviceCapabilities(params) {
|
|
|
7355
7410
|
hasZoom: finalHasZoom,
|
|
7356
7411
|
hasPresets: finalHasPresets,
|
|
7357
7412
|
hasPtz: ptzDisabledBySupport ? false : hasPtzFromSupport || finalHasPan || finalHasTilt || finalHasZoom || finalHasPresets,
|
|
7358
|
-
hasBattery
|
|
7413
|
+
hasBattery,
|
|
7359
7414
|
hasIntercom: hasIntercomFromSupport,
|
|
7360
|
-
hasSiren: hasSirenFromAbilities,
|
|
7415
|
+
hasSiren: hasSirenFromSupport || hasSirenFromAbilities,
|
|
7361
7416
|
// lightType >= 2 indicates controllable white LED / floodlight (1 = IR only)
|
|
7362
7417
|
hasFloodlight: Number.isFinite(lightType) ? lightType >= 2 : hasFloodlightFromAbilities,
|
|
7363
7418
|
hasPir: hasPirFromAbilities || hasPirFromSupport,
|
|
7364
|
-
isDoorbell
|
|
7365
|
-
hasAutotracking: hasAutotrackingFromSupport || hasAutotrackingFromAbilities
|
|
7419
|
+
isDoorbell,
|
|
7420
|
+
hasAutotracking: ptzDisabledBySupport ? false : hasAutotrackingFromSupport || hasAutotrackingFromAbilities,
|
|
7421
|
+
hasWirelessChime: isDoorbell || hasWirelessChimeFromAbilities
|
|
7366
7422
|
};
|
|
7367
7423
|
if (ptzMode !== void 0) result.ptzMode = ptzMode;
|
|
7368
7424
|
return result;
|
|
@@ -9237,6 +9293,161 @@ var discoverDeviceUidViaBaichuanGetP2p = async (params) => {
|
|
|
9237
9293
|
return extractReolinkUidLike(p2pXml);
|
|
9238
9294
|
};
|
|
9239
9295
|
|
|
9296
|
+
// src/reolink/baichuan/utils/chime.ts
|
|
9297
|
+
var buildDingDongGetParamsXml = (chimeId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9298
|
+
<body>
|
|
9299
|
+
<dingdongDeviceOpt version="1.1">
|
|
9300
|
+
<id>${chimeId}</id>
|
|
9301
|
+
<opt>getParam</opt>
|
|
9302
|
+
</dingdongDeviceOpt>
|
|
9303
|
+
</body>`;
|
|
9304
|
+
var buildDingDongSetParamsXml = (chimeId, params) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9305
|
+
<body>
|
|
9306
|
+
<dingdongDeviceOpt version="1.1">
|
|
9307
|
+
<opt>setParam</opt>
|
|
9308
|
+
<id>${chimeId}</id>
|
|
9309
|
+
${params.volLevel !== void 0 ? `<volLevel>${params.volLevel}</volLevel>` : ""}
|
|
9310
|
+
${params.ledState !== void 0 ? `<ledState>${params.ledState}</ledState>` : ""}
|
|
9311
|
+
${params.name !== void 0 ? `<name>${params.name}</name>` : ""}
|
|
9312
|
+
</dingdongDeviceOpt>
|
|
9313
|
+
</body>`;
|
|
9314
|
+
var buildDingDongRingXml = (chimeId, musicId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9315
|
+
<body>
|
|
9316
|
+
<dingdongDeviceOpt version="1.1">
|
|
9317
|
+
<id>${chimeId}</id>
|
|
9318
|
+
<opt>ringWithMusic</opt>
|
|
9319
|
+
<musicId>${musicId}</musicId>
|
|
9320
|
+
</dingdongDeviceOpt>
|
|
9321
|
+
</body>`;
|
|
9322
|
+
var buildSetDingDongCfgXml = (chimeId, eventType, state, musicId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9323
|
+
<body>
|
|
9324
|
+
<dingdongCfg version="1.1">
|
|
9325
|
+
<deviceCfg>
|
|
9326
|
+
<id>${chimeId}</id>
|
|
9327
|
+
<alarminCfg>
|
|
9328
|
+
<valid>${state}</valid>
|
|
9329
|
+
<musicId>${musicId}</musicId>
|
|
9330
|
+
<type>${eventType}</type>
|
|
9331
|
+
</alarminCfg>
|
|
9332
|
+
</deviceCfg>
|
|
9333
|
+
</dingdongCfg>
|
|
9334
|
+
</body>`;
|
|
9335
|
+
var buildGetDingDongCtrlXml = () => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9336
|
+
<body>
|
|
9337
|
+
<dingdongCtrl version="1.1">
|
|
9338
|
+
<opt>machineStateGet</opt>
|
|
9339
|
+
</dingdongCtrl>
|
|
9340
|
+
</body>`;
|
|
9341
|
+
var buildSetDingDongCtrlXml = (chimeType, enabled, time) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9342
|
+
<body>
|
|
9343
|
+
<dingdongCtrl version="1.1">
|
|
9344
|
+
<opt>machineStateSet</opt>
|
|
9345
|
+
<type>${chimeType}</type>
|
|
9346
|
+
<bopen>${enabled}</bopen>
|
|
9347
|
+
<bsave>1</bsave>
|
|
9348
|
+
<time>${time}</time>
|
|
9349
|
+
</dingdongCtrl>
|
|
9350
|
+
</body>`;
|
|
9351
|
+
var buildQuickReplyPlayXml = (channel, fileId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9352
|
+
<body>
|
|
9353
|
+
<audioFileInfo version="1.1">
|
|
9354
|
+
<channelId>${channel}</channelId>
|
|
9355
|
+
<id>${fileId}</id>
|
|
9356
|
+
<timeout>0</timeout>
|
|
9357
|
+
</audioFileInfo>
|
|
9358
|
+
</body>`;
|
|
9359
|
+
var parseDingDongListFromXml = (xml) => {
|
|
9360
|
+
const devices = [];
|
|
9361
|
+
const blocks = getXmlBlocks(xml, "dingdongDeviceInfo");
|
|
9362
|
+
for (const block of blocks) {
|
|
9363
|
+
const idText = getXmlText(block, "deviceId") ?? getXmlText(block, "id");
|
|
9364
|
+
const name = getXmlText(block, "deviceName") ?? getXmlText(block, "name") ?? "";
|
|
9365
|
+
const netStateText = getXmlText(block, "netState") ?? getXmlText(block, "netstate");
|
|
9366
|
+
if (idText === void 0) continue;
|
|
9367
|
+
const id = Number(idText);
|
|
9368
|
+
if (!Number.isFinite(id)) continue;
|
|
9369
|
+
devices.push({
|
|
9370
|
+
id,
|
|
9371
|
+
name,
|
|
9372
|
+
netState: netStateText !== void 0 ? Number(netStateText) : 0
|
|
9373
|
+
});
|
|
9374
|
+
}
|
|
9375
|
+
return devices;
|
|
9376
|
+
};
|
|
9377
|
+
var parseDingDongParamsFromXml = (xml) => {
|
|
9378
|
+
const name = getXmlText(xml, "name");
|
|
9379
|
+
const volLevelText = getXmlText(xml, "volLevel");
|
|
9380
|
+
const ledStateText = getXmlText(xml, "ledState");
|
|
9381
|
+
const result = {};
|
|
9382
|
+
if (name !== void 0) result.name = name;
|
|
9383
|
+
if (volLevelText !== void 0) {
|
|
9384
|
+
const n = Number(volLevelText);
|
|
9385
|
+
if (Number.isFinite(n)) result.volLevel = n;
|
|
9386
|
+
}
|
|
9387
|
+
if (ledStateText !== void 0) {
|
|
9388
|
+
const n = Number(ledStateText);
|
|
9389
|
+
if (Number.isFinite(n)) result.ledState = n;
|
|
9390
|
+
}
|
|
9391
|
+
return result;
|
|
9392
|
+
};
|
|
9393
|
+
var parseDingDongCfgFromXml = (xml) => {
|
|
9394
|
+
const configs = [];
|
|
9395
|
+
const deviceBlocks = getXmlBlocks(xml, "deviceCfg");
|
|
9396
|
+
for (const deviceBlock of deviceBlocks) {
|
|
9397
|
+
const idText = getXmlText(deviceBlock, "ringId") ?? getXmlText(deviceBlock, "id");
|
|
9398
|
+
if (idText === void 0) continue;
|
|
9399
|
+
const id = Number(idText);
|
|
9400
|
+
if (!Number.isFinite(id)) continue;
|
|
9401
|
+
const typeMap = {};
|
|
9402
|
+
const alarmBlocks = getXmlBlocks(deviceBlock, "alarminCfg");
|
|
9403
|
+
for (const alarmBlock of alarmBlocks) {
|
|
9404
|
+
const type = getXmlText(alarmBlock, "type");
|
|
9405
|
+
if (!type) continue;
|
|
9406
|
+
const validText = getXmlText(alarmBlock, "switch") ?? getXmlText(alarmBlock, "valid");
|
|
9407
|
+
const musicIdText = getXmlText(alarmBlock, "musicId");
|
|
9408
|
+
typeMap[type] = {
|
|
9409
|
+
valid: validText !== void 0 ? Number(validText) : 0,
|
|
9410
|
+
musicId: musicIdText !== void 0 ? Number(musicIdText) : 0
|
|
9411
|
+
};
|
|
9412
|
+
}
|
|
9413
|
+
configs.push({ id, type: typeMap });
|
|
9414
|
+
}
|
|
9415
|
+
return configs;
|
|
9416
|
+
};
|
|
9417
|
+
var parseHardwiredChimeFromXml = (xml) => {
|
|
9418
|
+
const type = getXmlText(xml, "type") ?? "";
|
|
9419
|
+
const bopenText = getXmlText(xml, "bopen") ?? getXmlText(xml, "enable");
|
|
9420
|
+
const timeText = getXmlText(xml, "time");
|
|
9421
|
+
return {
|
|
9422
|
+
type,
|
|
9423
|
+
enabled: bopenText === "1",
|
|
9424
|
+
time: timeText !== void 0 ? Number(timeText) : 0
|
|
9425
|
+
};
|
|
9426
|
+
};
|
|
9427
|
+
var buildGetDingDongSilentXml = (chimeId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9428
|
+
<body>
|
|
9429
|
+
<dingdongSilentMode version="1.1">
|
|
9430
|
+
<id>${chimeId}</id>
|
|
9431
|
+
</dingdongSilentMode>
|
|
9432
|
+
</body>`;
|
|
9433
|
+
var buildSetDingDongSilentXml = (chimeId, time) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
9434
|
+
<body>
|
|
9435
|
+
<dingdongSilentMode version="1.1">
|
|
9436
|
+
<id>${chimeId}</id>
|
|
9437
|
+
<time>${time}</time>
|
|
9438
|
+
<type>63</type>
|
|
9439
|
+
</dingdongSilentMode>
|
|
9440
|
+
</body>`;
|
|
9441
|
+
var parseWirelessChimeSilentFromXml = (xml, chimeId) => {
|
|
9442
|
+
const timeText = getXmlText(xml, "time");
|
|
9443
|
+
const time = timeText !== void 0 ? Number(timeText) : 0;
|
|
9444
|
+
return {
|
|
9445
|
+
id: chimeId,
|
|
9446
|
+
time,
|
|
9447
|
+
active: time === 0
|
|
9448
|
+
};
|
|
9449
|
+
};
|
|
9450
|
+
|
|
9240
9451
|
// src/reolink/baichuan/utils/eventsGetEvents.ts
|
|
9241
9452
|
var parseAiTypeToken = (aiTypeRaw) => {
|
|
9242
9453
|
const raw = (aiTypeRaw ?? "").trim();
|
|
@@ -9546,6 +9757,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
9546
9757
|
host;
|
|
9547
9758
|
username;
|
|
9548
9759
|
password;
|
|
9760
|
+
/**
|
|
9761
|
+
* Set to `true` after `close()` is called.
|
|
9762
|
+
* Once closed, the API instance should not be reused.
|
|
9763
|
+
*/
|
|
9764
|
+
_closed = false;
|
|
9549
9765
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9550
9766
|
// SOCKET POOL - Tag-based socket management
|
|
9551
9767
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -9575,10 +9791,194 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
9575
9791
|
get client() {
|
|
9576
9792
|
const entry = this.socketPool.get("general");
|
|
9577
9793
|
if (!entry) {
|
|
9794
|
+
if (this._closed) {
|
|
9795
|
+
throw new Error(
|
|
9796
|
+
"[ReolinkBaichuanApi] API has been closed \u2014 create a new instance to reconnect"
|
|
9797
|
+
);
|
|
9798
|
+
}
|
|
9578
9799
|
throw new Error("[ReolinkBaichuanApi] General socket not initialized");
|
|
9579
9800
|
}
|
|
9580
9801
|
return entry.client;
|
|
9581
9802
|
}
|
|
9803
|
+
/**
|
|
9804
|
+
* `true` after `close()` has been called. A closed API should not be reused;
|
|
9805
|
+
* the consumer should create a new instance.
|
|
9806
|
+
*/
|
|
9807
|
+
get isClosed() {
|
|
9808
|
+
return this._closed;
|
|
9809
|
+
}
|
|
9810
|
+
/**
|
|
9811
|
+
* `true` when the API is usable: not closed, general socket exists, socket
|
|
9812
|
+
* is connected and the client is logged in.
|
|
9813
|
+
*
|
|
9814
|
+
* This is the recommended way for consumers to check whether the API is
|
|
9815
|
+
* still valid before issuing commands, instead of directly accessing
|
|
9816
|
+
* `api.client.isSocketConnected()` / `api.client.loggedIn` (which throws
|
|
9817
|
+
* if the socket pool was already destroyed).
|
|
9818
|
+
*/
|
|
9819
|
+
get isReady() {
|
|
9820
|
+
if (this._closed) return false;
|
|
9821
|
+
const entry = this.socketPool.get("general");
|
|
9822
|
+
if (!entry) return false;
|
|
9823
|
+
try {
|
|
9824
|
+
return entry.client.isSocketConnected() && entry.client.loggedIn;
|
|
9825
|
+
} catch {
|
|
9826
|
+
return false;
|
|
9827
|
+
}
|
|
9828
|
+
}
|
|
9829
|
+
/** Promise tracking an in-flight reconnection from `ensureConnected()`. */
|
|
9830
|
+
_ensureConnectedPromise;
|
|
9831
|
+
/**
|
|
9832
|
+
* Ensure the "general" socket is connected and logged in.
|
|
9833
|
+
* If the socket is disconnected or the pool entry was destroyed, a new
|
|
9834
|
+
* general socket is created, logged in, and all event/push/guard listeners
|
|
9835
|
+
* are re-attached automatically.
|
|
9836
|
+
*
|
|
9837
|
+
* This is a **no-op** when the API is already {@link isReady}.
|
|
9838
|
+
*
|
|
9839
|
+
* @throws If `close()` was called — the API is permanently closed and a new
|
|
9840
|
+
* instance must be created.
|
|
9841
|
+
*/
|
|
9842
|
+
async ensureConnected() {
|
|
9843
|
+
if (this._closed) {
|
|
9844
|
+
throw new Error(
|
|
9845
|
+
"[ReolinkBaichuanApi] API has been closed \u2014 create a new instance to reconnect"
|
|
9846
|
+
);
|
|
9847
|
+
}
|
|
9848
|
+
if (this.isReady) return;
|
|
9849
|
+
if (this._ensureConnectedPromise) {
|
|
9850
|
+
return this._ensureConnectedPromise;
|
|
9851
|
+
}
|
|
9852
|
+
this._ensureConnectedPromise = this.reconnectGeneralSocket();
|
|
9853
|
+
try {
|
|
9854
|
+
await this._ensureConnectedPromise;
|
|
9855
|
+
} finally {
|
|
9856
|
+
this._ensureConnectedPromise = void 0;
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
/**
|
|
9860
|
+
* Internal: destroy the current general socket (if any), create a new one,
|
|
9861
|
+
* login, and re-attach all listeners.
|
|
9862
|
+
*/
|
|
9863
|
+
async reconnectGeneralSocket() {
|
|
9864
|
+
const oldEntry = this.socketPool.get("general");
|
|
9865
|
+
if (oldEntry) {
|
|
9866
|
+
oldEntry.client.removeAllListeners();
|
|
9867
|
+
if (oldEntry.idleCloseTimer) clearTimeout(oldEntry.idleCloseTimer);
|
|
9868
|
+
if (oldEntry.generalPermitRelease) {
|
|
9869
|
+
try {
|
|
9870
|
+
oldEntry.generalPermitRelease();
|
|
9871
|
+
} catch {
|
|
9872
|
+
}
|
|
9873
|
+
}
|
|
9874
|
+
this.socketPool.delete("general");
|
|
9875
|
+
try {
|
|
9876
|
+
await oldEntry.client.close({ reason: "reconnect", skipLogout: true });
|
|
9877
|
+
} catch {
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
const newClient = new BaichuanClient(this.clientOptions);
|
|
9881
|
+
this.socketPool.set("general", {
|
|
9882
|
+
client: newClient,
|
|
9883
|
+
refCount: 1,
|
|
9884
|
+
// general socket is always "in use"
|
|
9885
|
+
createdAt: Date.now(),
|
|
9886
|
+
lastUsedAt: Date.now(),
|
|
9887
|
+
idleCloseTimer: void 0,
|
|
9888
|
+
generalPermitRelease: void 0
|
|
9889
|
+
});
|
|
9890
|
+
this.setupGeneralClientListeners();
|
|
9891
|
+
await this.client.login();
|
|
9892
|
+
this.logger.log?.(
|
|
9893
|
+
"[ReolinkBaichuanApi] General socket reconnected successfully"
|
|
9894
|
+
);
|
|
9895
|
+
if (this.simpleEventListeners.size > 0) {
|
|
9896
|
+
this.simpleEventSubscribed = false;
|
|
9897
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
9898
|
+
this.simpleEventWatchdogLastRecoveryAt = 0;
|
|
9899
|
+
try {
|
|
9900
|
+
await this.ensureSimpleEventSubscribed();
|
|
9901
|
+
this.simpleEventLastReceivedAt = Date.now();
|
|
9902
|
+
this.logger.log?.(
|
|
9903
|
+
`[ReolinkBaichuanApi] Events re-subscribed after reconnection (listeners=${this.simpleEventListeners.size})`
|
|
9904
|
+
);
|
|
9905
|
+
} catch (e) {
|
|
9906
|
+
(this.logger.debug ?? this.logger.log).call(
|
|
9907
|
+
this.logger,
|
|
9908
|
+
`[ReolinkBaichuanApi] Event re-subscribe after reconnection failed, watchdog will retry`,
|
|
9909
|
+
formatErrorForLog(e)
|
|
9910
|
+
);
|
|
9911
|
+
}
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9914
|
+
/**
|
|
9915
|
+
* Attach event, push, channelInfo, and guard listeners to the current
|
|
9916
|
+
* "general" client. Called from the constructor and from
|
|
9917
|
+
* {@link reconnectGeneralSocket}.
|
|
9918
|
+
*/
|
|
9919
|
+
setupGeneralClientListeners() {
|
|
9920
|
+
const client = this.client;
|
|
9921
|
+
client.on("event", (event) => {
|
|
9922
|
+
const mapped = mapToSimpleEvent(event);
|
|
9923
|
+
if (!mapped) return;
|
|
9924
|
+
this.dispatchSimpleEvent(mapped);
|
|
9925
|
+
});
|
|
9926
|
+
client.on("channelInfo", (xml) => {
|
|
9927
|
+
try {
|
|
9928
|
+
this.parseAndStoreChannelInfo(xml);
|
|
9929
|
+
} catch (e) {
|
|
9930
|
+
this.logger.warn?.(
|
|
9931
|
+
"[ReolinkBaichuanApi] Error parsing channel info from push",
|
|
9932
|
+
formatErrorForLog(e)
|
|
9933
|
+
);
|
|
9934
|
+
}
|
|
9935
|
+
});
|
|
9936
|
+
client.on("push", (frame) => {
|
|
9937
|
+
const cmdId = frame.header.cmdId;
|
|
9938
|
+
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST) {
|
|
9939
|
+
return;
|
|
9940
|
+
}
|
|
9941
|
+
try {
|
|
9942
|
+
if (frame.body.length === 0) return;
|
|
9943
|
+
const xml = client.tryDecryptXml(
|
|
9944
|
+
frame.body,
|
|
9945
|
+
frame.header.channelId,
|
|
9946
|
+
client.enc
|
|
9947
|
+
);
|
|
9948
|
+
if (!xml || !xml.startsWith("<?xml")) return;
|
|
9949
|
+
this.parseAndStoreSettingsPush(cmdId, xml, frame.header.channelId);
|
|
9950
|
+
} catch (e) {
|
|
9951
|
+
this.logger.debug?.(
|
|
9952
|
+
"[ReolinkBaichuanApi] Error parsing settings push",
|
|
9953
|
+
formatErrorForLog(e)
|
|
9954
|
+
);
|
|
9955
|
+
}
|
|
9956
|
+
});
|
|
9957
|
+
if (this.rebootAfterDisconnectionsPerMinute > 0) {
|
|
9958
|
+
client.on("close", () => {
|
|
9959
|
+
try {
|
|
9960
|
+
void this.maybeRebootOnDisconnectStorm();
|
|
9961
|
+
} catch {
|
|
9962
|
+
}
|
|
9963
|
+
});
|
|
9964
|
+
}
|
|
9965
|
+
if (this.rebootAfterConsecutiveEconnreset > 0) {
|
|
9966
|
+
client.on("close", () => {
|
|
9967
|
+
try {
|
|
9968
|
+
void this.maybeRebootOnEconnresetStorm();
|
|
9969
|
+
} catch {
|
|
9970
|
+
}
|
|
9971
|
+
});
|
|
9972
|
+
}
|
|
9973
|
+
if (!this.sessionGuardIntervalTimer) {
|
|
9974
|
+
client.once("push", () => {
|
|
9975
|
+
void this.logActiveSessionsOnStartup();
|
|
9976
|
+
this.sessionGuardIntervalTimer = setInterval(() => {
|
|
9977
|
+
void this.maybeRebootOnTooManySessions();
|
|
9978
|
+
}, 6e4);
|
|
9979
|
+
});
|
|
9980
|
+
}
|
|
9981
|
+
}
|
|
9582
9982
|
/**
|
|
9583
9983
|
* Cached camera UID. May be initially undefined if not provided in the constructor.
|
|
9584
9984
|
* Will be lazily populated on demand when needed (e.g. for recordings).
|
|
@@ -10519,42 +10919,6 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10519
10919
|
logger: this.logger,
|
|
10520
10920
|
debugConfig: generalClient.getDebugConfig?.()
|
|
10521
10921
|
});
|
|
10522
|
-
this.client.on("event", (event) => {
|
|
10523
|
-
const mapped = mapToSimpleEvent(event);
|
|
10524
|
-
if (!mapped) return;
|
|
10525
|
-
this.dispatchSimpleEvent(mapped);
|
|
10526
|
-
});
|
|
10527
|
-
this.client.on("channelInfo", (xml) => {
|
|
10528
|
-
try {
|
|
10529
|
-
this.parseAndStoreChannelInfo(xml);
|
|
10530
|
-
} catch (e) {
|
|
10531
|
-
this.logger.warn?.(
|
|
10532
|
-
"[ReolinkBaichuanApi] Error parsing channel info from push",
|
|
10533
|
-
formatErrorForLog(e)
|
|
10534
|
-
);
|
|
10535
|
-
}
|
|
10536
|
-
});
|
|
10537
|
-
this.client.on("push", (frame) => {
|
|
10538
|
-
const cmdId = frame.header.cmdId;
|
|
10539
|
-
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST) {
|
|
10540
|
-
return;
|
|
10541
|
-
}
|
|
10542
|
-
try {
|
|
10543
|
-
if (frame.body.length === 0) return;
|
|
10544
|
-
const xml = this.client.tryDecryptXml(
|
|
10545
|
-
frame.body,
|
|
10546
|
-
frame.header.channelId,
|
|
10547
|
-
this.client.enc
|
|
10548
|
-
);
|
|
10549
|
-
if (!xml || !xml.startsWith("<?xml")) return;
|
|
10550
|
-
this.parseAndStoreSettingsPush(cmdId, xml, frame.header.channelId);
|
|
10551
|
-
} catch (e) {
|
|
10552
|
-
this.logger.debug?.(
|
|
10553
|
-
"[ReolinkBaichuanApi] Error parsing settings push",
|
|
10554
|
-
formatErrorForLog(e)
|
|
10555
|
-
);
|
|
10556
|
-
}
|
|
10557
|
-
});
|
|
10558
10922
|
const maxSessions = opts.maxDedicatedSessionsBeforeReboot;
|
|
10559
10923
|
if (typeof maxSessions === "number" && Number.isFinite(maxSessions) && maxSessions > 0) {
|
|
10560
10924
|
this.maxDedicatedSessionsBeforeReboot = Math.floor(maxSessions);
|
|
@@ -10563,32 +10927,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10563
10927
|
if (typeof disconnectThreshold === "number" && Number.isFinite(disconnectThreshold)) {
|
|
10564
10928
|
this.rebootAfterDisconnectionsPerMinute = Math.floor(disconnectThreshold);
|
|
10565
10929
|
}
|
|
10566
|
-
if (this.rebootAfterDisconnectionsPerMinute > 0) {
|
|
10567
|
-
this.client.on("close", () => {
|
|
10568
|
-
try {
|
|
10569
|
-
void this.maybeRebootOnDisconnectStorm();
|
|
10570
|
-
} catch {
|
|
10571
|
-
}
|
|
10572
|
-
});
|
|
10573
|
-
}
|
|
10574
10930
|
const econnresetThreshold = opts.rebootAfterConsecutiveEconnreset;
|
|
10575
10931
|
if (typeof econnresetThreshold === "number" && Number.isFinite(econnresetThreshold)) {
|
|
10576
10932
|
this.rebootAfterConsecutiveEconnreset = Math.floor(econnresetThreshold);
|
|
10577
10933
|
}
|
|
10578
|
-
|
|
10579
|
-
this.client.on("close", () => {
|
|
10580
|
-
try {
|
|
10581
|
-
void this.maybeRebootOnEconnresetStorm();
|
|
10582
|
-
} catch {
|
|
10583
|
-
}
|
|
10584
|
-
});
|
|
10585
|
-
}
|
|
10586
|
-
this.client.once("push", () => {
|
|
10587
|
-
void this.logActiveSessionsOnStartup();
|
|
10588
|
-
this.sessionGuardIntervalTimer = setInterval(() => {
|
|
10589
|
-
void this.maybeRebootOnTooManySessions();
|
|
10590
|
-
}, 6e4);
|
|
10591
|
-
});
|
|
10934
|
+
this.setupGeneralClientListeners();
|
|
10592
10935
|
}
|
|
10593
10936
|
/**
|
|
10594
10937
|
* CGI forward: fetch RTSP URL for a channel via `GetRtspUrl`.
|
|
@@ -11419,6 +11762,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11419
11762
|
);
|
|
11420
11763
|
}
|
|
11421
11764
|
async close(options) {
|
|
11765
|
+
if (this._closed) return;
|
|
11766
|
+
this._closed = true;
|
|
11422
11767
|
if (this.sessionGuardIntervalTimer) {
|
|
11423
11768
|
clearInterval(this.sessionGuardIntervalTimer);
|
|
11424
11769
|
this.sessionGuardIntervalTimer = void 0;
|
|
@@ -11481,7 +11826,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11481
11826
|
}
|
|
11482
11827
|
async handleSendXml400(params, frame, retry) {
|
|
11483
11828
|
const emptyBody = frame.body.length === 0;
|
|
11484
|
-
const emptyBody400Msg = "Baichuan request failed (responseCode 400, empty body). Possible causes:
|
|
11829
|
+
const emptyBody400Msg = "Baichuan request failed (responseCode 400, empty body). Possible causes: expired session, invalid username/password, or unsupported command on NVR/Hub.";
|
|
11485
11830
|
if (this.isSendXmlFailFast400(params, frame.body.length)) {
|
|
11486
11831
|
throw new Error(emptyBody400Msg);
|
|
11487
11832
|
}
|
|
@@ -11997,11 +12342,50 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11997
12342
|
* Minimal per-channel inventory for NVR-connected devices.
|
|
11998
12343
|
*
|
|
11999
12344
|
* Intended to be fast: avoids AI/abilities and returns only the common identity + battery hints.
|
|
12345
|
+
*
|
|
12346
|
+
* @param options.source - Data source for the channel list (default: `"cgi"`):
|
|
12347
|
+
* - `"cgi"`: Uses HTTP `GetChannelstatus` — returns the channel list immediately,
|
|
12348
|
+
* no dependency on async push messages. Recommended for first-call discovery.
|
|
12349
|
+
* - `"baichuan"`: Uses the cmd_id 145 push cache populated when the NVR sends channel
|
|
12350
|
+
* info after login + event subscription. This push is *asynchronous*: if it has not
|
|
12351
|
+
* arrived yet, the result will have zero channels. Callers must retry (nvr.ts does this
|
|
12352
|
+
* with a 1-second loop). Note: explicitly requesting cmd_id 145 is not supported.
|
|
12000
12353
|
*/
|
|
12001
12354
|
async getNvrChannelsSummary(options) {
|
|
12002
|
-
const source = options?.source ?? "
|
|
12003
|
-
|
|
12004
|
-
const
|
|
12355
|
+
const source = options?.source ?? "cgi";
|
|
12356
|
+
let channels;
|
|
12357
|
+
const cgiStatusByChannel = /* @__PURE__ */ new Map();
|
|
12358
|
+
if (options?.channels?.length) {
|
|
12359
|
+
channels = options.channels.map((c) => Number(c)).filter((n) => Number.isFinite(n));
|
|
12360
|
+
} else if (source === "cgi") {
|
|
12361
|
+
try {
|
|
12362
|
+
const { channels: cgiChannels, channelsResponse } = await this.cgiApi.getChannels();
|
|
12363
|
+
const status = channelsResponse?.[0]?.value?.status ?? [];
|
|
12364
|
+
for (const s of status) {
|
|
12365
|
+
const ch = Number(s?.channel);
|
|
12366
|
+
if (!Number.isFinite(ch)) continue;
|
|
12367
|
+
cgiStatusByChannel.set(ch, {
|
|
12368
|
+
...s.name != null ? { name: s.name } : {},
|
|
12369
|
+
...s.uid != null ? { uid: s.uid } : {},
|
|
12370
|
+
sleeping: s.sleep === 1
|
|
12371
|
+
});
|
|
12372
|
+
}
|
|
12373
|
+
channels = cgiChannels;
|
|
12374
|
+
this.logger.debug?.(
|
|
12375
|
+
`[ReolinkBaichuanApi] getNvrChannelsSummary: CGI found ${channels.length} channel(s): [${channels.join(", ")}]`
|
|
12376
|
+
);
|
|
12377
|
+
} catch (e) {
|
|
12378
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
12379
|
+
this.logger.warn?.(
|
|
12380
|
+
`[ReolinkBaichuanApi] getNvrChannelsSummary: CGI GetChannelstatus failed (${msg}), returning empty`
|
|
12381
|
+
);
|
|
12382
|
+
channels = [];
|
|
12383
|
+
}
|
|
12384
|
+
} else {
|
|
12385
|
+
const pushInfo2 = this.getChannelInfoFromPushCache();
|
|
12386
|
+
channels = Array.from(pushInfo2.keys()).map((c) => Number(c)).filter((n) => Number.isFinite(n));
|
|
12387
|
+
}
|
|
12388
|
+
channels = channels.sort((a, b) => a - b);
|
|
12005
12389
|
const support = await this.getSupportInfo().catch(() => {
|
|
12006
12390
|
this.logger.error?.(
|
|
12007
12391
|
"[ReolinkBaichuanApi] getNvrChannelsSummary: failed to get support info"
|
|
@@ -12031,7 +12415,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12031
12415
|
);
|
|
12032
12416
|
}
|
|
12033
12417
|
}
|
|
12034
|
-
const cacheKey =
|
|
12418
|
+
const cacheKey = `${source}:${channels.join(",")}`;
|
|
12035
12419
|
const cached = this.nvrChannelsSummaryCache.get(cacheKey);
|
|
12036
12420
|
if (cached) {
|
|
12037
12421
|
return {
|
|
@@ -12052,8 +12436,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12052
12436
|
} catch {
|
|
12053
12437
|
}
|
|
12054
12438
|
}
|
|
12439
|
+
const pushInfo = this.getChannelInfoFromPushCache();
|
|
12055
12440
|
const devices = channels.map((channel) => {
|
|
12056
|
-
const
|
|
12441
|
+
const pushCached = pushInfo.get(channel);
|
|
12442
|
+
const cgiStatus = cgiStatusByChannel.get(channel);
|
|
12057
12443
|
const info = infoPerChannel.get(channel);
|
|
12058
12444
|
const networkInfo = networkInfoPerChannel.get(channel);
|
|
12059
12445
|
const isBattery = isBatteryByChannel.get(channel) ?? false;
|
|
@@ -12061,6 +12447,9 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12061
12447
|
const isDoorbell = (isDoorbellByChannel.get(channel) ?? false) || /doorbell/i.test(model);
|
|
12062
12448
|
const normalizedModel = model ? model.trim() : void 0;
|
|
12063
12449
|
const isMultifocal = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
12450
|
+
const name = pushCached?.name || cgiStatus?.name || "";
|
|
12451
|
+
const uid = pushCached?.uid || cgiStatus?.uid || "";
|
|
12452
|
+
const sleeping = pushCached?.sleeping ?? cgiStatus?.sleeping;
|
|
12064
12453
|
return {
|
|
12065
12454
|
channel,
|
|
12066
12455
|
isBattery,
|
|
@@ -12070,19 +12459,19 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12070
12459
|
...networkInfo?.ip ? { ip: networkInfo.ip } : {},
|
|
12071
12460
|
...networkInfo?.mac ? { mac: networkInfo.mac } : {},
|
|
12072
12461
|
...networkInfo?.activeLink ? { activeLink: networkInfo.activeLink } : {},
|
|
12073
|
-
...
|
|
12074
|
-
...
|
|
12075
|
-
...
|
|
12076
|
-
...typeof
|
|
12077
|
-
...
|
|
12078
|
-
...
|
|
12079
|
-
...
|
|
12080
|
-
...typeof
|
|
12081
|
-
...typeof
|
|
12082
|
-
...typeof
|
|
12083
|
-
...typeof
|
|
12084
|
-
...
|
|
12085
|
-
...typeof
|
|
12462
|
+
...name ? { name } : {},
|
|
12463
|
+
...uid ? { uid } : {},
|
|
12464
|
+
...pushCached?.state ? { state: pushCached.state } : {},
|
|
12465
|
+
...typeof pushCached?.index === "number" ? { index: pushCached.index } : {},
|
|
12466
|
+
...pushCached?.streamSupport?.length ? { streamSupport: pushCached.streamSupport } : {},
|
|
12467
|
+
...pushCached?.wifiState ? { wifiState: pushCached.wifiState } : {},
|
|
12468
|
+
...pushCached?.networkSegment ? { networkSegment: pushCached.networkSegment } : {},
|
|
12469
|
+
...typeof pushCached?.changed === "boolean" ? { changed: pushCached.changed } : {},
|
|
12470
|
+
...typeof pushCached?.abilityChanged === "boolean" ? { abilityChanged: pushCached.abilityChanged } : {},
|
|
12471
|
+
...typeof pushCached?.online === "boolean" ? { online: pushCached.online } : {},
|
|
12472
|
+
...typeof sleeping === "boolean" ? { sleeping } : {},
|
|
12473
|
+
...pushCached?.loginState ? { loginState: pushCached.loginState } : {},
|
|
12474
|
+
...typeof pushCached?.updatedAtMs === "number" ? { updatedAtMs: pushCached.updatedAtMs } : {}
|
|
12086
12475
|
};
|
|
12087
12476
|
});
|
|
12088
12477
|
const result = { channels, devices };
|
|
@@ -16350,13 +16739,12 @@ ${xml}`
|
|
|
16350
16739
|
]);
|
|
16351
16740
|
const support = supportResult.status === "fulfilled" ? supportResult.value : void 0;
|
|
16352
16741
|
const abilities = abilitiesResult.status === "fulfilled" ? abilitiesResult.value : void 0;
|
|
16353
|
-
const supportItem =
|
|
16354
|
-
const capabilities =
|
|
16355
|
-
ch,
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
);
|
|
16742
|
+
const supportItem = getSupportItemForChannel(support, ch);
|
|
16743
|
+
const capabilities = computeDeviceCapabilities({
|
|
16744
|
+
channel: ch,
|
|
16745
|
+
...support != null && { support },
|
|
16746
|
+
...abilities != null && { abilities }
|
|
16747
|
+
});
|
|
16360
16748
|
const item = supportItem;
|
|
16361
16749
|
const lightType = item?.lightType;
|
|
16362
16750
|
const ledCtrl = item?.ledCtrl;
|
|
@@ -16372,6 +16760,25 @@ ${xml}`
|
|
|
16372
16760
|
});
|
|
16373
16761
|
capabilities.hasFloodlight = probed;
|
|
16374
16762
|
}
|
|
16763
|
+
let dingDongListIds;
|
|
16764
|
+
let dingDongCfgIds;
|
|
16765
|
+
let wirelessChimeError;
|
|
16766
|
+
if (capabilities.hasWirelessChime) {
|
|
16767
|
+
try {
|
|
16768
|
+
const list = await this.getDingDongList(ch);
|
|
16769
|
+
dingDongListIds = list.map((d) => d.id);
|
|
16770
|
+
const first = list[0];
|
|
16771
|
+
const fromList = first !== void 0 && first.id >= 0;
|
|
16772
|
+
if (!fromList) {
|
|
16773
|
+
const configs = await this.getDingDongCfg(ch);
|
|
16774
|
+
dingDongCfgIds = configs.map((c) => c.id);
|
|
16775
|
+
capabilities.hasWirelessChime = configs.some((c) => c.id >= 0);
|
|
16776
|
+
}
|
|
16777
|
+
} catch (e) {
|
|
16778
|
+
capabilities.hasWirelessChime = false;
|
|
16779
|
+
wirelessChimeError = e instanceof Error ? e.message : String(e);
|
|
16780
|
+
}
|
|
16781
|
+
}
|
|
16375
16782
|
const features = this.parseFeaturesFromSupport(support);
|
|
16376
16783
|
const objects = await this.getAiDetectTypes(ch, { timeoutMs: 1500 });
|
|
16377
16784
|
const autotrackingProbed = await this.probeAutotrackingSupport(ch, {
|
|
@@ -16408,7 +16815,10 @@ ${xml}`
|
|
|
16408
16815
|
...abilities && {
|
|
16409
16816
|
abilityMergedKeyCount: Object.keys(abilities).length
|
|
16410
16817
|
},
|
|
16411
|
-
...support?.items && { supportItemCount: support.items.length }
|
|
16818
|
+
...support?.items && { supportItemCount: support.items.length },
|
|
16819
|
+
...dingDongListIds !== void 0 && { dingDongListIds },
|
|
16820
|
+
...dingDongCfgIds !== void 0 && { dingDongCfgIds },
|
|
16821
|
+
...wirelessChimeError !== void 0 && { wirelessChimeError }
|
|
16412
16822
|
};
|
|
16413
16823
|
const result = {
|
|
16414
16824
|
capabilities,
|
|
@@ -16435,90 +16845,6 @@ ${xml}`
|
|
|
16435
16845
|
this.deviceCapabilitiesCache.clear();
|
|
16436
16846
|
}
|
|
16437
16847
|
}
|
|
16438
|
-
/**
|
|
16439
|
-
* Pick the best SupportItem for a channel.
|
|
16440
|
-
* Prefers items without a name (capability items) over named items (googleHome, amazonAlexa).
|
|
16441
|
-
*/
|
|
16442
|
-
pickBestSupportItem(support, channel) {
|
|
16443
|
-
if (!support?.items?.length) return void 0;
|
|
16444
|
-
const candidates = support.items.filter((i) => i.chnID === channel);
|
|
16445
|
-
if (!candidates.length) return void 0;
|
|
16446
|
-
const score = (item) => {
|
|
16447
|
-
const anyItem = item;
|
|
16448
|
-
let result = 0;
|
|
16449
|
-
if (anyItem.name == null) result += 100;
|
|
16450
|
-
const capabilityKeys = [
|
|
16451
|
-
"ptzType",
|
|
16452
|
-
"ptzControl",
|
|
16453
|
-
"ptzPreset",
|
|
16454
|
-
"ledCtrl",
|
|
16455
|
-
"lightType",
|
|
16456
|
-
"battery",
|
|
16457
|
-
"audioVersion",
|
|
16458
|
-
"motion",
|
|
16459
|
-
"encCtrl",
|
|
16460
|
-
"newIspCfg",
|
|
16461
|
-
"remoteAbility",
|
|
16462
|
-
"aitype",
|
|
16463
|
-
"videoClip",
|
|
16464
|
-
"snap"
|
|
16465
|
-
];
|
|
16466
|
-
for (const k of capabilityKeys) {
|
|
16467
|
-
if (anyItem[k] !== void 0) result += 3;
|
|
16468
|
-
}
|
|
16469
|
-
return result;
|
|
16470
|
-
};
|
|
16471
|
-
return candidates.sort((a, b) => score(b) - score(a))[0];
|
|
16472
|
-
}
|
|
16473
|
-
/**
|
|
16474
|
-
* Parse device capabilities from SupportInfo.
|
|
16475
|
-
* Uses SupportInfo as the single source of truth with AbilityInfo as fallback.
|
|
16476
|
-
*/
|
|
16477
|
-
parseCapabilitiesFromSupport(channel, supportItem, support, abilities) {
|
|
16478
|
-
const truthy = (v) => {
|
|
16479
|
-
if (typeof v === "number") return v > 0;
|
|
16480
|
-
if (typeof v === "string") {
|
|
16481
|
-
const n = Number(v);
|
|
16482
|
-
return Number.isFinite(n) ? n > 0 : v.length > 0 && v !== "0";
|
|
16483
|
-
}
|
|
16484
|
-
return Boolean(v);
|
|
16485
|
-
};
|
|
16486
|
-
const item = supportItem;
|
|
16487
|
-
const ptzMode = support?.ptzMode?.toLowerCase();
|
|
16488
|
-
const ptzType = item ? truthy(item.ptzType) : false;
|
|
16489
|
-
const ptzControl = item ? truthy(item.ptzControl) : false;
|
|
16490
|
-
const hasPtzFromItem = ptzType || ptzControl;
|
|
16491
|
-
const hasPtzFromMode = ptzMode ? ptzMode !== "none" && ptzMode !== "0" : false;
|
|
16492
|
-
const hasPanTilt = ptzMode ? ptzMode.includes("pt") || ptzMode === "ptz" : hasPtzFromItem;
|
|
16493
|
-
const hasZoom = ptzMode ? ptzMode.includes("z") : hasPtzFromItem;
|
|
16494
|
-
const hasPresets = item ? truthy(item.ptzPreset) : false;
|
|
16495
|
-
const hasBattery = item ? truthy(item.battery) : false;
|
|
16496
|
-
const hasSiren = item ? truthy(item.audioVersion) : false;
|
|
16497
|
-
const lightType = item?.lightType;
|
|
16498
|
-
const hasFloodlight = typeof lightType === "number" ? lightType >= 2 : false;
|
|
16499
|
-
const hasPir = item ? truthy(item.rfCfg) || truthy(item.newRfCfg) || truthy(item.rfVersion) : false;
|
|
16500
|
-
const isDoorbell = item ? truthy(item.doorbellVersion) : false;
|
|
16501
|
-
const hasIntercom = truthy(support?.audioTalk) || (item ? truthy(item.ipcAudioTalk) : false);
|
|
16502
|
-
return {
|
|
16503
|
-
channel,
|
|
16504
|
-
...ptzMode && { ptzMode },
|
|
16505
|
-
hasPan: hasPanTilt,
|
|
16506
|
-
hasTilt: hasPanTilt,
|
|
16507
|
-
hasZoom,
|
|
16508
|
-
hasPresets,
|
|
16509
|
-
hasPtz: hasPtzFromItem || hasPtzFromMode || hasPanTilt || hasZoom,
|
|
16510
|
-
hasBattery,
|
|
16511
|
-
hasIntercom,
|
|
16512
|
-
hasSiren,
|
|
16513
|
-
hasFloodlight,
|
|
16514
|
-
hasPir,
|
|
16515
|
-
isDoorbell,
|
|
16516
|
-
// Autotracking: explicit flags only (autoPt or smartAI)
|
|
16517
|
-
// Note: the heuristic (ptzControl && aitype) was too aggressive and caused false positives
|
|
16518
|
-
// on cameras that have PTZ and AI detection but NOT autotracking capability.
|
|
16519
|
-
hasAutotracking: item ? truthy(item.autoPt) || truthy(item.smartAI) : false
|
|
16520
|
-
};
|
|
16521
|
-
}
|
|
16522
16848
|
/**
|
|
16523
16849
|
* Parse support features from SupportInfo.
|
|
16524
16850
|
*/
|
|
@@ -17287,7 +17613,7 @@ ${xml}`
|
|
|
17287
17613
|
* @returns Test results for all stream types and profiles
|
|
17288
17614
|
*/
|
|
17289
17615
|
async testChannelStreams(channel, logger) {
|
|
17290
|
-
const { testChannelStreams } = await import("./DiagnosticsTools-
|
|
17616
|
+
const { testChannelStreams } = await import("./DiagnosticsTools-FNLGCOVA.js");
|
|
17291
17617
|
return await testChannelStreams({
|
|
17292
17618
|
api: this,
|
|
17293
17619
|
channel: this.normalizeChannel(channel),
|
|
@@ -17303,7 +17629,7 @@ ${xml}`
|
|
|
17303
17629
|
* @returns Complete diagnostics for all channels and streams
|
|
17304
17630
|
*/
|
|
17305
17631
|
async collectMultifocalDiagnostics(logger) {
|
|
17306
|
-
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-
|
|
17632
|
+
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-FNLGCOVA.js");
|
|
17307
17633
|
return await collectMultifocalDiagnostics({
|
|
17308
17634
|
api: this,
|
|
17309
17635
|
logger
|
|
@@ -19391,6 +19717,216 @@ ${scheduleItems}
|
|
|
19391
19717
|
const channel = 0;
|
|
19392
19718
|
return await this.getSnapshot(channel);
|
|
19393
19719
|
}
|
|
19720
|
+
// --------------------
|
|
19721
|
+
// Chime / DingDong APIs
|
|
19722
|
+
// --------------------
|
|
19723
|
+
/**
|
|
19724
|
+
* Get the list of paired wireless chime devices.
|
|
19725
|
+
* cmd_id: 484 (GetDingDongList)
|
|
19726
|
+
*
|
|
19727
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19728
|
+
* @returns Array of paired chime devices
|
|
19729
|
+
*/
|
|
19730
|
+
async getDingDongList(channel) {
|
|
19731
|
+
const ch = this.normalizeChannel(channel);
|
|
19732
|
+
const xml = await this.sendXml({
|
|
19733
|
+
cmdId: BC_CMD_ID_GET_DING_DONG_LIST,
|
|
19734
|
+
channel: ch
|
|
19735
|
+
});
|
|
19736
|
+
return parseDingDongListFromXml(xml);
|
|
19737
|
+
}
|
|
19738
|
+
/**
|
|
19739
|
+
* Get parameters (name, volume, LED state) for a specific wireless chime.
|
|
19740
|
+
* cmd_id: 485 (DingDongOpt, option getParam)
|
|
19741
|
+
*
|
|
19742
|
+
* @param chimeId - The chime device ID
|
|
19743
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19744
|
+
* @returns Chime parameters
|
|
19745
|
+
*/
|
|
19746
|
+
async getDingDongParams(chimeId, channel) {
|
|
19747
|
+
const ch = this.normalizeChannel(channel);
|
|
19748
|
+
const payloadXml = buildDingDongGetParamsXml(chimeId);
|
|
19749
|
+
const xml = await this.sendXml({
|
|
19750
|
+
cmdId: BC_CMD_ID_DING_DONG_OPT,
|
|
19751
|
+
channel: ch,
|
|
19752
|
+
payloadXml
|
|
19753
|
+
});
|
|
19754
|
+
return parseDingDongParamsFromXml(xml);
|
|
19755
|
+
}
|
|
19756
|
+
/**
|
|
19757
|
+
* Set parameters (name, volume, LED state) for a specific wireless chime.
|
|
19758
|
+
* cmd_id: 485 (DingDongOpt, option setParam)
|
|
19759
|
+
*
|
|
19760
|
+
* @param chimeId - The chime device ID
|
|
19761
|
+
* @param params - Parameters to set (volLevel, ledState, name)
|
|
19762
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19763
|
+
*/
|
|
19764
|
+
async setDingDongParams(chimeId, params, channel) {
|
|
19765
|
+
const ch = this.normalizeChannel(channel);
|
|
19766
|
+
const payloadXml = buildDingDongSetParamsXml(chimeId, params);
|
|
19767
|
+
await this.sendXml({
|
|
19768
|
+
cmdId: BC_CMD_ID_DING_DONG_OPT,
|
|
19769
|
+
channel: ch,
|
|
19770
|
+
payloadXml
|
|
19771
|
+
});
|
|
19772
|
+
}
|
|
19773
|
+
/**
|
|
19774
|
+
* Trigger a wireless chime to ring with a specific ringtone.
|
|
19775
|
+
* cmd_id: 485 (DingDongOpt, option ringWithMusic)
|
|
19776
|
+
*
|
|
19777
|
+
* @param chimeId - The chime device ID
|
|
19778
|
+
* @param musicId - The ringtone/music ID to play
|
|
19779
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19780
|
+
*/
|
|
19781
|
+
async ringDingDong(chimeId, musicId, channel) {
|
|
19782
|
+
const ch = this.normalizeChannel(channel);
|
|
19783
|
+
const payloadXml = buildDingDongRingXml(chimeId, musicId);
|
|
19784
|
+
await this.sendXml({
|
|
19785
|
+
cmdId: BC_CMD_ID_DING_DONG_OPT,
|
|
19786
|
+
channel: ch,
|
|
19787
|
+
payloadXml
|
|
19788
|
+
});
|
|
19789
|
+
}
|
|
19790
|
+
/**
|
|
19791
|
+
* Get the per-event alarm configuration for paired wireless chimes.
|
|
19792
|
+
* cmd_id: 486 (GetDingDongCfg)
|
|
19793
|
+
*
|
|
19794
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19795
|
+
* @returns Array of chime configurations (one per paired chime)
|
|
19796
|
+
*/
|
|
19797
|
+
async getDingDongCfg(channel) {
|
|
19798
|
+
const ch = this.normalizeChannel(channel);
|
|
19799
|
+
const xml = await this.sendXml({
|
|
19800
|
+
cmdId: BC_CMD_ID_GET_DING_DONG_CFG,
|
|
19801
|
+
channel: ch
|
|
19802
|
+
});
|
|
19803
|
+
return parseDingDongCfgFromXml(xml);
|
|
19804
|
+
}
|
|
19805
|
+
/**
|
|
19806
|
+
* Set the per-event alarm configuration for a specific wireless chime.
|
|
19807
|
+
* cmd_id: 487 (SetDingDongCfg)
|
|
19808
|
+
*
|
|
19809
|
+
* @param chimeId - The chime ring/device ID
|
|
19810
|
+
* @param eventType - Event type string (e.g. "doorbell", "package", "people")
|
|
19811
|
+
* @param state - 0 = disabled, 1 = enabled
|
|
19812
|
+
* @param musicId - Ringtone ID to use for this event type
|
|
19813
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19814
|
+
*/
|
|
19815
|
+
async setDingDongCfg(chimeId, eventType, state, musicId, channel) {
|
|
19816
|
+
const ch = this.normalizeChannel(channel);
|
|
19817
|
+
const payloadXml = buildSetDingDongCfgXml(chimeId, eventType, state, musicId);
|
|
19818
|
+
await this.sendXml({
|
|
19819
|
+
cmdId: BC_CMD_ID_SET_DING_DONG_CFG,
|
|
19820
|
+
channel: ch,
|
|
19821
|
+
payloadXml
|
|
19822
|
+
});
|
|
19823
|
+
}
|
|
19824
|
+
/** Cache of last known hardwired chime state per channel, used to avoid re-fetching on every set. */
|
|
19825
|
+
_hardwiredChimeCache = /* @__PURE__ */ new Map();
|
|
19826
|
+
/**
|
|
19827
|
+
* Get the hardwired (wired-in) chime state.
|
|
19828
|
+
* cmd_id: 483 (GetDingDongCtrl)
|
|
19829
|
+
*
|
|
19830
|
+
* Note: calling this may briefly trigger the physical chime to rattle.
|
|
19831
|
+
*
|
|
19832
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19833
|
+
* @returns Hardwired chime state (type, enabled, time)
|
|
19834
|
+
*/
|
|
19835
|
+
async getHardwiredChime(channel) {
|
|
19836
|
+
const ch = this.normalizeChannel(channel);
|
|
19837
|
+
const payloadXml = buildGetDingDongCtrlXml();
|
|
19838
|
+
const xml = await this.sendXml({
|
|
19839
|
+
cmdId: BC_CMD_ID_DING_DONG_CTRL,
|
|
19840
|
+
channel: ch,
|
|
19841
|
+
payloadXml
|
|
19842
|
+
});
|
|
19843
|
+
const state = parseHardwiredChimeFromXml(xml);
|
|
19844
|
+
this._hardwiredChimeCache.set(ch, state);
|
|
19845
|
+
return state;
|
|
19846
|
+
}
|
|
19847
|
+
/**
|
|
19848
|
+
* Set the hardwired (wired-in) chime state.
|
|
19849
|
+
* cmd_id: 483 (SetDingDongCtrl)
|
|
19850
|
+
*
|
|
19851
|
+
* Uses the cached state from a previous getHardwiredChime call to fill in
|
|
19852
|
+
* missing type/time fields, avoiding a double round-trip on every set.
|
|
19853
|
+
* Falls back to fetching if no cache is available.
|
|
19854
|
+
*
|
|
19855
|
+
* @param params - Chime configuration (type, enabled, time)
|
|
19856
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19857
|
+
*/
|
|
19858
|
+
async setHardwiredChime(params, channel) {
|
|
19859
|
+
const ch = this.normalizeChannel(channel);
|
|
19860
|
+
let current = this._hardwiredChimeCache.get(ch);
|
|
19861
|
+
if (!current) {
|
|
19862
|
+
current = await this.getHardwiredChime(ch);
|
|
19863
|
+
}
|
|
19864
|
+
const chimeType = params.type ?? current.type;
|
|
19865
|
+
const enabled = params.enabled ? 1 : 0;
|
|
19866
|
+
const time = params.time ?? current.time;
|
|
19867
|
+
const payloadXml = buildSetDingDongCtrlXml(chimeType, enabled, time);
|
|
19868
|
+
const xml = await this.sendXml({
|
|
19869
|
+
cmdId: BC_CMD_ID_DING_DONG_CTRL,
|
|
19870
|
+
channel: ch,
|
|
19871
|
+
payloadXml
|
|
19872
|
+
});
|
|
19873
|
+
const newState = parseHardwiredChimeFromXml(xml);
|
|
19874
|
+
this._hardwiredChimeCache.set(ch, newState);
|
|
19875
|
+
return newState;
|
|
19876
|
+
}
|
|
19877
|
+
/**
|
|
19878
|
+
* Play an audio file on the doorbell / chime device.
|
|
19879
|
+
* cmd_id: 349 (QuickReplyPlay)
|
|
19880
|
+
*
|
|
19881
|
+
* @param fileId - The audio file ID to play
|
|
19882
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19883
|
+
*/
|
|
19884
|
+
async quickReplyPlay(fileId, channel) {
|
|
19885
|
+
const ch = this.normalizeChannel(channel);
|
|
19886
|
+
const payloadXml = buildQuickReplyPlayXml(ch, fileId);
|
|
19887
|
+
await this.sendXml({
|
|
19888
|
+
cmdId: BC_CMD_ID_QUICK_REPLY_PLAY,
|
|
19889
|
+
channel: ch,
|
|
19890
|
+
payloadXml
|
|
19891
|
+
});
|
|
19892
|
+
}
|
|
19893
|
+
/**
|
|
19894
|
+
* Get the silent mode state of a paired wireless chime.
|
|
19895
|
+
* cmd_id: 609 (GetDingDongSilent)
|
|
19896
|
+
*
|
|
19897
|
+
* @param chimeId - The wireless chime device ID (from getDingDongList)
|
|
19898
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19899
|
+
* @returns Wireless chime silent state (time=0 means active/not silenced)
|
|
19900
|
+
*/
|
|
19901
|
+
async getDingDongSilent(chimeId, channel) {
|
|
19902
|
+
const ch = this.normalizeChannel(channel);
|
|
19903
|
+
const payloadXml = buildGetDingDongSilentXml(chimeId);
|
|
19904
|
+
const xml = await this.sendXml({
|
|
19905
|
+
cmdId: BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
19906
|
+
channel: ch,
|
|
19907
|
+
payloadXml
|
|
19908
|
+
});
|
|
19909
|
+
return parseWirelessChimeSilentFromXml(xml, chimeId);
|
|
19910
|
+
}
|
|
19911
|
+
/**
|
|
19912
|
+
* Set the silent mode of a paired wireless chime.
|
|
19913
|
+
* cmd_id: 610 (SetDingDongSilent)
|
|
19914
|
+
*
|
|
19915
|
+
* @param chimeId - The wireless chime device ID (from getDingDongList)
|
|
19916
|
+
* @param time - Silence duration in seconds. 0 = not silenced (chime active), >0 = silenced for this many seconds.
|
|
19917
|
+
* @param channel - Channel number (0-based, default 0)
|
|
19918
|
+
* @returns Updated wireless chime silent state
|
|
19919
|
+
*/
|
|
19920
|
+
async setDingDongSilent(chimeId, time, channel) {
|
|
19921
|
+
const ch = this.normalizeChannel(channel);
|
|
19922
|
+
const payloadXml = buildSetDingDongSilentXml(chimeId, time);
|
|
19923
|
+
const xml = await this.sendXml({
|
|
19924
|
+
cmdId: BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
19925
|
+
channel: ch,
|
|
19926
|
+
payloadXml
|
|
19927
|
+
});
|
|
19928
|
+
return parseWirelessChimeSilentFromXml(xml, chimeId);
|
|
19929
|
+
}
|
|
19394
19930
|
};
|
|
19395
19931
|
|
|
19396
19932
|
// src/reolink/discovery.ts
|
|
@@ -20388,6 +20924,7 @@ export {
|
|
|
20388
20924
|
flattenAbilitiesForChannel,
|
|
20389
20925
|
abilitiesHasAny,
|
|
20390
20926
|
parseSupportXml,
|
|
20927
|
+
getSupportItemForChannel,
|
|
20391
20928
|
computeDeviceCapabilities,
|
|
20392
20929
|
DUAL_LENS_DUAL_MOTION_MODELS,
|
|
20393
20930
|
DUAL_LENS_SINGLE_MOTION_MODELS,
|
|
@@ -20406,4 +20943,4 @@ export {
|
|
|
20406
20943
|
isTcpFailureThatShouldFallbackToUdp,
|
|
20407
20944
|
autoDetectDeviceType
|
|
20408
20945
|
};
|
|
20409
|
-
//# sourceMappingURL=chunk-
|
|
20946
|
+
//# sourceMappingURL=chunk-MN7GUZT7.js.map
|