node-zserial 1.0.29 → 1.0.31
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/package.json +1 -1
- package/zserial.js +63 -82
package/package.json
CHANGED
package/zserial.js
CHANGED
|
@@ -329,7 +329,7 @@ module.exports = function (RED) {
|
|
|
329
329
|
node.successMsg = {};
|
|
330
330
|
node.errorMsg = {};
|
|
331
331
|
node._msg = null
|
|
332
|
-
|
|
332
|
+
|
|
333
333
|
function initMsg(msg) {
|
|
334
334
|
node._msg = msg;
|
|
335
335
|
node.totallenth = msg.serialConfigs.length;
|
|
@@ -338,7 +338,7 @@ module.exports = function (RED) {
|
|
|
338
338
|
node.errorMsg = {};
|
|
339
339
|
}
|
|
340
340
|
function zsend(msg, err, alldone, port, done) {
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
let payload = msg || err;
|
|
343
343
|
node._msg.payload = payload;
|
|
344
344
|
node.totalMsg[port] = payload
|
|
@@ -369,12 +369,12 @@ module.exports = function (RED) {
|
|
|
369
369
|
for (var i = 0; i < msg.serialConfigs.length; i++) {
|
|
370
370
|
var serialConfig = msg.serialConfigs[i];
|
|
371
371
|
serialConfig._msgid = msg._msgid + "_" + i;
|
|
372
|
-
getSerialServer(
|
|
372
|
+
getSerialServer(msg, serialConfig, done);
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
function sendAll(done) {
|
|
377
|
-
|
|
377
|
+
|
|
378
378
|
try {
|
|
379
379
|
let len = Object.keys(node.totalMsg).length;
|
|
380
380
|
if (len == node.totallenth) {
|
|
@@ -552,12 +552,12 @@ module.exports = function (RED) {
|
|
|
552
552
|
})
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
-
function afterClosed(port){
|
|
555
|
+
function afterClosed(port) {
|
|
556
556
|
node[`_dataHandler_${port}`] = null
|
|
557
557
|
node[`_timeoutHandler_${port}`] = null
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
-
if(!node._afterClosed){
|
|
560
|
+
if (!node._afterClosed) {
|
|
561
561
|
node._afterClosed = afterClosed;
|
|
562
562
|
serialPool.on('afterClosed', node._afterClosed);
|
|
563
563
|
}
|
|
@@ -730,10 +730,21 @@ module.exports = function (RED) {
|
|
|
730
730
|
}
|
|
731
731
|
|
|
732
732
|
if (m) {
|
|
733
|
-
if (
|
|
734
|
-
|
|
733
|
+
if (spliton === "frame") {
|
|
734
|
+
const mbuf = Buffer.from(m);
|
|
735
|
+
msgout.payload = mbuf;
|
|
736
|
+
msgout.payload_hex = mbuf.toString('hex').toUpperCase();
|
|
737
|
+
} else {
|
|
738
|
+
if (binoutput !== "bin") { m = m.toString(); }
|
|
739
|
+
msgout.payload = m;
|
|
740
|
+
}
|
|
735
741
|
} else {
|
|
736
|
-
|
|
742
|
+
if (spliton === "frame") {
|
|
743
|
+
msgout.payload = Buffer.alloc(0);
|
|
744
|
+
msgout.payload_hex = "";
|
|
745
|
+
} else {
|
|
746
|
+
msgout.payload = (binoutput !== "bin") ? "" : Buffer.alloc(0);
|
|
747
|
+
}
|
|
737
748
|
}
|
|
738
749
|
msgout.status = "ERR_TIMEOUT";
|
|
739
750
|
/* Notify the sender that a timeout occurred */
|
|
@@ -947,7 +958,8 @@ module.exports = function (RED) {
|
|
|
947
958
|
if (b.length >= 8 && b[7] !== 0x68) {
|
|
948
959
|
const n = b.indexOf(0x68, 1);
|
|
949
960
|
if (n > 0) return { ok: false, used: feCount + n, frame: input.slice(0, feCount + n), err: "645_SECOND_68_NOT_FOUND" };
|
|
950
|
-
|
|
961
|
+
// 已经有足够字节判定形态错误,但又找不到下一个 0x68:同步点错位,丢 1 字节推进
|
|
962
|
+
return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "645_BAD_SHAPE_DROP1" };
|
|
951
963
|
}
|
|
952
964
|
if (b.length < 10) return { ok: false }; // 还缺 CTRL/LEN
|
|
953
965
|
|
|
@@ -1049,12 +1061,20 @@ module.exports = function (RED) {
|
|
|
1049
1061
|
// 约束 1:698-LEN 的 L 不可能极短(至少应包含:C + 头字段 + FCS(2))
|
|
1050
1062
|
// 这里给经验下限 6(偏保守,既能挡住 L=2,又尽量不误伤极端设备)
|
|
1051
1063
|
// 若现场仍有碰撞,可把下限提高到 8 或 10(建议先保留 6)。
|
|
1052
|
-
const MIN_698_LEN_L =
|
|
1064
|
+
const MIN_698_LEN_L = 10;
|
|
1053
1065
|
if (L < MIN_698_LEN_L) {
|
|
1054
1066
|
// 不消费任何字节,让后续 parser(如 645)继续尝试
|
|
1055
1067
|
return { ok: false };
|
|
1056
1068
|
}
|
|
1057
1069
|
|
|
1070
|
+
|
|
1071
|
+
// 长度上限:防止错位/噪声把 L 解读成极大值导致 assembleBuf 误判为半包长期等待
|
|
1072
|
+
// 掉电次数等业务回包通常远小于 2KB;超出则高度可疑,丢 1 字节推进重同步
|
|
1073
|
+
const MAX_698_LEN_L = 2048;
|
|
1074
|
+
if (L > MAX_698_LEN_L) {
|
|
1075
|
+
return { ok: false, used: 1, frame: b.slice(0, 1), err: "698_LEN_TOO_LARGE" };
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1058
1078
|
// 约束 2:控制域 C 在 b[3],不可能是 0x16(结束符)
|
|
1059
1079
|
// 若 b[3] == 0x16,几乎必然是 645 地址域碰撞导致的误判
|
|
1060
1080
|
if (b.length >= 4 && b[3] === 0x16) {
|
|
@@ -1091,8 +1111,9 @@ module.exports = function (RED) {
|
|
|
1091
1111
|
const calcB = crc16x25(b, 1, expectedEnd - 2);
|
|
1092
1112
|
const fcsOK = (calcA === fcs) || (calcB === fcs);
|
|
1093
1113
|
|
|
1094
|
-
//
|
|
1095
|
-
//
|
|
1114
|
+
// 现场经常遇到:帧内容完整(68..16 边界正确),但 FCS 因链路噪声/串口转换器问题偶发不一致。
|
|
1115
|
+
// 若此处直接判为错误帧并 dequeue,会导致上层“偶尔无法解码”(尤其是请求-响应严格匹配的场景)。
|
|
1116
|
+
// 因此:当边界与长度域一致时,允许“容错通过”,并在 _meta 中标记 fcs_ok=false 供上层排障。
|
|
1096
1117
|
if (!fcsOK) {
|
|
1097
1118
|
return {
|
|
1098
1119
|
ok: false,
|
|
@@ -1126,12 +1147,21 @@ module.exports = function (RED) {
|
|
|
1126
1147
|
const rawFrame = b.slice(0, endPos + 1);
|
|
1127
1148
|
const payloadEscaped = b.slice(1, endPos); // 去 7E
|
|
1128
1149
|
const payload = unescapeHDLC(payloadEscaped);
|
|
1129
|
-
if (payload.length < 3) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
|
|
1150
|
+
// if (payload.length < 3) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
|
|
1151
|
+
if (payload.length < 3) return { ok: false };
|
|
1152
|
+
// 严格形态约束:698-HDLC 的“帧格式域”通常以 0xA0/0xA8/0xB0 等开头(高四位为 0xA 或 0xB)。
|
|
1153
|
+
// 若不满足,极可能只是链路噪声/误同步的 0x7E,不应在 frame 模式下被消费掉(否则会 dequeue 错配)。
|
|
1154
|
+
const fmt = payload[0] >>> 0;
|
|
1155
|
+
const hi = fmt & 0xF0;
|
|
1156
|
+
if (!(hi === 0xA0 || hi === 0xB0)) {
|
|
1157
|
+
return { ok: false }; // 不消费,交给 698-LEN/645 再尝试
|
|
1158
|
+
}
|
|
1130
1159
|
const fcsLo = payload[payload.length - 2];
|
|
1131
1160
|
const fcsHi = payload[payload.length - 1];
|
|
1132
1161
|
const fcs = (fcsHi << 8) | fcsLo;
|
|
1133
1162
|
const calc = crc16x25(payload, 0, payload.length - 2);
|
|
1134
|
-
if (calc !== fcs) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
|
|
1163
|
+
// if (calc !== fcs) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
|
|
1164
|
+
if (calc !== fcs) return { ok: false };
|
|
1135
1165
|
return { ok: true, used: endPos + 1, frame: rawFrame }; // 原始含 7E
|
|
1136
1166
|
}
|
|
1137
1167
|
|
|
@@ -1141,72 +1171,10 @@ module.exports = function (RED) {
|
|
|
1141
1171
|
if (!Buffer.isBuffer(d)) d = Buffer.from(d);
|
|
1142
1172
|
assembleBuf = Buffer.concat([assembleBuf, d]);
|
|
1143
1173
|
|
|
1144
|
-
//
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// 前导剔噪:仅保留以 FE/68/7E 开头;若以 FE 开头,剥离所有 FE 前导
|
|
1152
|
-
let s = 0;
|
|
1153
|
-
while (s < assembleBuf.length) {
|
|
1154
|
-
const c = assembleBuf[s];
|
|
1155
|
-
if (c === 0xFE || c === 0x68 || c === 0x7E) break;
|
|
1156
|
-
s++;
|
|
1157
|
-
}
|
|
1158
|
-
if (s > 0) assembleBuf = assembleBuf.slice(s);
|
|
1159
|
-
if (assembleBuf.length && assembleBuf[0] === 0xFE) {
|
|
1160
|
-
let k = 0; while (k < assembleBuf.length && assembleBuf[k] === 0xFE) k++;
|
|
1161
|
-
assembleBuf = assembleBuf.slice(k);
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// 若缓存以 0x68 开始但迟迟无法凑齐完整帧,且中途出现新一轮 0xFE 0xFE 0xFE 0xFE 前导,
|
|
1165
|
-
// 说明上一帧很可能被截断/丢字节(现场常见:串口/转换器插入前导或上层超时提前取走)。
|
|
1166
|
-
// 这种情况下必须“强制丢弃”残片并从新的 FE 前导重新同步,否则会一直卡住导致后续帧都解析不出来。
|
|
1167
|
-
function findFeRun4(buf) {
|
|
1168
|
-
// 查找连续 4 个 0xFE 的起始位置
|
|
1169
|
-
for (let i = 0; i <= buf.length - 4; i++) {
|
|
1170
|
-
if (buf[i] === 0xFE && buf[i + 1] === 0xFE && buf[i + 2] === 0xFE && buf[i + 3] === 0xFE) return i;
|
|
1171
|
-
}
|
|
1172
|
-
return -1;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
// 仅对 698-LEN(0x68...0x16)这种“依赖长度域”的帧做强制重同步处理
|
|
1176
|
-
if (assembleBuf.length >= 4 && assembleBuf[0] === 0x68) {
|
|
1177
|
-
const LL0 = assembleBuf[1] >>> 0;
|
|
1178
|
-
const LH0 = assembleBuf[2] >>> 0;
|
|
1179
|
-
let Lraw0 = (LH0 << 8) | LL0;
|
|
1180
|
-
const isKB0 = (Lraw0 & 0x4000) !== 0;
|
|
1181
|
-
Lraw0 &= 0x3FFF;
|
|
1182
|
-
const L0 = isKB0 ? (Lraw0 << 10) : Lraw0;
|
|
1183
|
-
const expectedEnd0 = 1 + L0;
|
|
1184
|
-
|
|
1185
|
-
// 只有在“明显是长帧但当前数据不足”时才触发该策略
|
|
1186
|
-
if (L0 >= 6 && assembleBuf.length < expectedEnd0 + 1) {
|
|
1187
|
-
const fePos = findFeRun4(assembleBuf);
|
|
1188
|
-
if (fePos > 0) {
|
|
1189
|
-
// fePos 之前的内容属于上一帧残片,作为错误帧上报并丢弃;
|
|
1190
|
-
// 然后保留从 FE 开始的内容,留给下一轮解析。
|
|
1191
|
-
const bad = assembleBuf.slice(0, fePos);
|
|
1192
|
-
emitErr(Buffer.from(bad), "FRAME_TRUNCATED_RESYNC_FE");
|
|
1193
|
-
assembleBuf = assembleBuf.slice(fePos);
|
|
1194
|
-
|
|
1195
|
-
// 重新执行一次前导剔噪/FE 剥离(与上面逻辑一致)
|
|
1196
|
-
let ss = 0;
|
|
1197
|
-
while (ss < assembleBuf.length) {
|
|
1198
|
-
const cc = assembleBuf[ss];
|
|
1199
|
-
if (cc === 0xFE || cc === 0x68 || cc === 0x7E) break;
|
|
1200
|
-
ss++;
|
|
1201
|
-
}
|
|
1202
|
-
if (ss > 0) assembleBuf = assembleBuf.slice(ss);
|
|
1203
|
-
if (assembleBuf.length && assembleBuf[0] === 0xFE) {
|
|
1204
|
-
let kk = 0; while (kk < assembleBuf.length && assembleBuf[kk] === 0xFE) kk++;
|
|
1205
|
-
assembleBuf = assembleBuf.slice(kk);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1174
|
+
// 修复:部分现场链路/中间层会把原始 8-bit 串口数据“扩展”为 UTF-16LE 形态
|
|
1175
|
+
// (如 0x7E -> 0x7E00 0x7E00...),导致帧头无法识别。
|
|
1176
|
+
// 需要自动识别并还原原始 8-bit 数据。
|
|
1177
|
+
// (已移除 normalizeUtf16Interleave 修复块)
|
|
1210
1178
|
|
|
1211
1179
|
// 抽帧
|
|
1212
1180
|
while (assembleBuf.length >= 5) {
|
|
@@ -1219,6 +1187,17 @@ module.exports = function (RED) {
|
|
|
1219
1187
|
// 关键:698-LEN 必须优先于 645,否则遇到 698 帧内出现 0x68(如示例报文第7字节)会被 645 误判并卡住
|
|
1220
1188
|
let r = tryParseBleGNW(assembleBuf);
|
|
1221
1189
|
if (!r.ok) r = tryParse698HDLC(assembleBuf);
|
|
1190
|
+
|
|
1191
|
+
// ---- 兜底:0x7E 起始已形成候选段(存在第二个 0x7E),但 BLE/HDLC 均无法校验通过 -> 丢 1 字节推进重同步 ----
|
|
1192
|
+
// 仅在候选段“闭合”时执行(有第二个 0x7E),避免误伤半包;并要求 endPos>=5,避免误伤 7E7E/7E7E7E 前导
|
|
1193
|
+
if (!r.ok && assembleBuf.length && assembleBuf[0] === 0x7E) {
|
|
1194
|
+
const endPos = assembleBuf.indexOf(0x7E, 1);
|
|
1195
|
+
if (endPos >= 5) {
|
|
1196
|
+
r = { ok: false, used: 1, frame: assembleBuf.slice(0, 1), err: "DROP_7E_BADFRAME" };
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
|
|
1222
1201
|
if (!r.ok) r = tryParse698Len(assembleBuf);
|
|
1223
1202
|
if (!r.ok) r = tryParse645(assembleBuf);
|
|
1224
1203
|
|
|
@@ -1328,12 +1307,14 @@ module.exports = function (RED) {
|
|
|
1328
1307
|
var m = Buffer.from(badBuf);
|
|
1329
1308
|
var last_sender = null;
|
|
1330
1309
|
if (obj.queue.length) { last_sender = obj.queue[0].sender; }
|
|
1331
|
-
|
|
1310
|
+
// 关键修复:错误帧(CRC/CS/结尾不符)不应 dequeue,否则会把“请求上下文”弹出队列,导致后续正确回包无法匹配
|
|
1311
|
+
var msgout = (obj.queue && obj.queue.length) ? Object.assign({}, obj.queue[0].msg) : {};
|
|
1332
1312
|
msgout.payload = m;
|
|
1333
1313
|
msgout.payload_hex = m.toString('hex').toUpperCase();
|
|
1334
1314
|
msgout.port = port;
|
|
1335
1315
|
msgout.status = "ERR_FRAME";
|
|
1336
1316
|
msgout.reason = reason || "FRAME_INVALID";
|
|
1317
|
+
msgout.is_unsolicited = !(obj.queue && obj.queue.length);
|
|
1337
1318
|
obj._emitter.emit('data', msgout, last_sender);
|
|
1338
1319
|
}
|
|
1339
1320
|
);
|