node-zserial 1.0.26 → 1.0.28
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 +168 -35
package/package.json
CHANGED
package/zserial.js
CHANGED
|
@@ -956,17 +956,42 @@ module.exports = function (RED) {
|
|
|
956
956
|
const endIdx = total - 1; // 末尾 0x16 索引
|
|
957
957
|
const csIdx = total - 2; // ★ CS 在倒数第2个字节
|
|
958
958
|
|
|
959
|
-
// CS
|
|
959
|
+
// CS 双模校验:
|
|
960
|
+
// - full:从第一个 0x68 起累加到 CS 前(不含 CS)
|
|
961
|
+
// - std :从第二个 0x68 后(C+L+DATA)累加到 CS 前
|
|
960
962
|
const csOK = (end) => {
|
|
961
|
-
const e = end;
|
|
962
|
-
const cs = b[e - 2]; //
|
|
963
|
-
|
|
964
|
-
|
|
963
|
+
const e = end; // e == total
|
|
964
|
+
const cs = b[e - 2]; // CS 在倒数第2字节
|
|
965
|
+
|
|
966
|
+
// full 模式
|
|
967
|
+
const sFull = sum8(b, 0, e - 2);
|
|
968
|
+
if (cs === sFull) return { ok: true, mode: 'full', calc: sFull };
|
|
969
|
+
|
|
970
|
+
// std 模式(需确保第二个 0x68 存在于固定位置7)
|
|
971
|
+
if (b.length >= 10 && b[7] === 0x68) {
|
|
972
|
+
// 第二个 0x68 后开始:CTRL(8) + LEN(9) + DATA...
|
|
973
|
+
const sStd = sum8(b, 8, e - 2);
|
|
974
|
+
if (cs === sStd) return { ok: true, mode: 'std', calc: sStd };
|
|
975
|
+
return { ok: false, mode: 'std', calc: sStd, calc_full: sFull };
|
|
976
|
+
}
|
|
977
|
+
return { ok: false, mode: 'full', calc: sFull };
|
|
965
978
|
};
|
|
966
979
|
|
|
967
980
|
// 1) 优先按 LEN 快速命中
|
|
968
|
-
if (b.length >= total && b[endIdx] === 0x16
|
|
969
|
-
|
|
981
|
+
if (b.length >= total && b[endIdx] === 0x16) {
|
|
982
|
+
const ck = csOK(total);
|
|
983
|
+
if (ck && ck.ok) {
|
|
984
|
+
const frame = input.slice(0, feCount + total);
|
|
985
|
+
// 透出校验模式,便于排障:部分设备使用 std 模式
|
|
986
|
+
try {
|
|
987
|
+
frame._meta = Object.assign({}, frame._meta || {}, {
|
|
988
|
+
proto: '645',
|
|
989
|
+
cs_mode: ck.mode,
|
|
990
|
+
cs_calc: ck.calc
|
|
991
|
+
});
|
|
992
|
+
} catch (e) { }
|
|
993
|
+
return { ok: true, used: feCount + total, frame };
|
|
994
|
+
}
|
|
970
995
|
}
|
|
971
996
|
|
|
972
997
|
// 2) 回溯:向后找 0x16 做候选结尾,再校验 CS(兼容异常 LEN/粘包)
|
|
@@ -974,10 +999,17 @@ module.exports = function (RED) {
|
|
|
974
999
|
if (b[end - 1] !== 0x16) continue;
|
|
975
1000
|
// 候选帧至少要有 …… + CS + 16,因此 end >= 13 且 csIdx=end-2 >= 0
|
|
976
1001
|
if (end >= 13) {
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1002
|
+
const ck = csOK(end);
|
|
1003
|
+
if (ck && ck.ok) {
|
|
1004
|
+
const frame = input.slice(0, feCount + end);
|
|
1005
|
+
try {
|
|
1006
|
+
frame._meta = Object.assign({}, frame._meta || {}, {
|
|
1007
|
+
proto: '645',
|
|
1008
|
+
cs_mode: ck.mode,
|
|
1009
|
+
cs_calc: ck.calc
|
|
1010
|
+
});
|
|
1011
|
+
} catch (e) { }
|
|
1012
|
+
return { ok: true, used: feCount + end, frame };
|
|
981
1013
|
}
|
|
982
1014
|
}
|
|
983
1015
|
}
|
|
@@ -991,45 +1023,96 @@ module.exports = function (RED) {
|
|
|
991
1023
|
|
|
992
1024
|
// 698(68-LEN 变体):68 LL LH C ... DATA ... FCS(2) 16 ;兼容 FE* 前导
|
|
993
1025
|
function tryParse698Len(input) {
|
|
994
|
-
// 剥离前导 FE
|
|
1026
|
+
// 剥离前导 FE(注意:这里只做 698-LEN 侧的兼容;645 另有自己的 FE 处理)
|
|
995
1027
|
const b = stripFE(input);
|
|
996
1028
|
if (b.length < 6 || b[0] !== 0x68) return { ok: false };
|
|
997
|
-
// 避免把 645 错误识别为 698-LEN(645 在 b[7] 处固定为 0x68)
|
|
998
|
-
if (b.length >= 8 && b[7] === 0x68) return { ok: false };
|
|
999
1029
|
|
|
1000
|
-
// ----
|
|
1030
|
+
// ---- 读取长度域 ----
|
|
1031
|
+
// 68 [LL] [LH] [C] ... [FCS(lo)] [FCS(hi)] 16
|
|
1001
1032
|
const LL = b[1] >>> 0;
|
|
1002
1033
|
const LH = b[2] >>> 0;
|
|
1003
|
-
let Lraw = (LH << 8) | LL; // 含单位位、保留位
|
|
1004
|
-
const isKB = (Lraw & 0x4000) !== 0; // bit14:单位位(0=字节,1=KB)
|
|
1005
|
-
Lraw &= 0x3FFF; // 清除单位位与保留位,仅保留长度值
|
|
1006
1034
|
|
|
1007
|
-
//
|
|
1008
|
-
let
|
|
1035
|
+
// Lraw 含单位位/保留位:bit14 为单位位(0=字节,1=KB)
|
|
1036
|
+
let Lraw = (LH << 8) | LL;
|
|
1037
|
+
const isKB = (Lraw & 0x4000) !== 0;
|
|
1038
|
+
Lraw &= 0x3FFF; // 清掉单位位/保留位,只留下数值
|
|
1039
|
+
|
|
1040
|
+
// 绝大多数场景长度单位为“字节”;若遇到 KB 单位则折算
|
|
1041
|
+
const L = isKB ? (Lraw << 10) : Lraw; // KB -> *1024
|
|
1042
|
+
|
|
1043
|
+
// ------------------------ 关键防误判(核心修复点) ------------------------
|
|
1044
|
+
// 误判根因:645 帧形态为 68 + 6字节地址 + 68 ...,地址字节中“偶然出现 0x16”
|
|
1045
|
+
// 例如:68 02 80 16 00 00 00 68 ...
|
|
1046
|
+
// 若把 LL=0x02、LH=0x80 解读为 698-LEN 长度,则 L=2,expectedEnd=3,b[3]=0x16
|
|
1047
|
+
// 从而被 698-LEN 误判为 “68 02 80 16” 一帧,导致吞掉 645 的帧头并产生残片。
|
|
1048
|
+
|
|
1049
|
+
// 约束 1:698-LEN 的 L 不可能极短(至少应包含:C + 头字段 + FCS(2))
|
|
1050
|
+
// 这里给经验下限 6(偏保守,既能挡住 L=2,又尽量不误伤极端设备)
|
|
1051
|
+
// 若现场仍有碰撞,可把下限提高到 8 或 10(建议先保留 6)。
|
|
1052
|
+
const MIN_698_LEN_L = 6;
|
|
1053
|
+
if (L < MIN_698_LEN_L) {
|
|
1054
|
+
// 不消费任何字节,让后续 parser(如 645)继续尝试
|
|
1055
|
+
return { ok: false };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// 约束 2:控制域 C 在 b[3],不可能是 0x16(结束符)
|
|
1059
|
+
// 若 b[3] == 0x16,几乎必然是 645 地址域碰撞导致的误判
|
|
1060
|
+
if (b.length >= 4 && b[3] === 0x16) {
|
|
1061
|
+
return { ok: false };
|
|
1062
|
+
}
|
|
1063
|
+
// ------------------------------------------------------------------------
|
|
1009
1064
|
|
|
1010
|
-
// 期望 0x16 的位置(相对 b
|
|
1065
|
+
// 期望 0x16 的位置(相对 b 起点):1 + L
|
|
1066
|
+
// 解释:L 表示从 LL 开始到 FCS(含) 之前的长度?各厂实现略有差异
|
|
1067
|
+
// 你现有逻辑采用 expectedEnd = 1 + L,并要求 b[expectedEnd] == 0x16
|
|
1011
1068
|
const expectedEnd = 1 + L;
|
|
1012
|
-
|
|
1069
|
+
|
|
1070
|
+
// 半包:继续累积
|
|
1013
1071
|
if (b.length < expectedEnd + 1) return { ok: false };
|
|
1014
|
-
|
|
1072
|
+
|
|
1073
|
+
// 若 expectedEnd 位置不是 0x16,说明起点错位或存在脏字节
|
|
1074
|
+
// 这里消费 1 字节,避免死循环卡住
|
|
1015
1075
|
if (b[expectedEnd] !== 0x16) {
|
|
1016
1076
|
return { ok: false, used: 1, frame: b.slice(0, 1), err: "698_LEN_BAD_END" };
|
|
1017
1077
|
}
|
|
1018
1078
|
|
|
1019
|
-
// FCS 校验(CRC
|
|
1079
|
+
// ------------------------ FCS 校验(CRC-16/X.25) ------------------------
|
|
1080
|
+
// FCS 位于 0x16 前两字节,低字节在前(lo, hi)
|
|
1081
|
+
if (expectedEnd - 2 < 0) return { ok: false };
|
|
1082
|
+
|
|
1020
1083
|
const fcsLo = b[expectedEnd - 2];
|
|
1021
1084
|
const fcsHi = b[expectedEnd - 1];
|
|
1022
1085
|
const fcs = (fcsHi << 8) | fcsLo;
|
|
1023
|
-
const calc = crc16x25(b, 1, expectedEnd - 2);
|
|
1024
|
-
if (calc !== fcs) {
|
|
1025
|
-
return { ok: false, used: 1, frame: b.slice(0, 1), err: "698_LEN_FCS_FAIL" };
|
|
1026
|
-
}
|
|
1027
1086
|
|
|
1028
|
-
//
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1087
|
+
// 不同实现对“CRC 覆盖范围”存在差异,常见两种:
|
|
1088
|
+
// A) 从 0x68 开始算到 FCS 前(不含 FCS 与 0x16)
|
|
1089
|
+
// B) 从 LL 开始算到 FCS 前(不含 FCS 与 0x16)
|
|
1090
|
+
const calcA = crc16x25(b, 0, expectedEnd - 2);
|
|
1091
|
+
const calcB = crc16x25(b, 1, expectedEnd - 2);
|
|
1092
|
+
const fcsOK = (calcA === fcs) || (calcB === fcs);
|
|
1093
|
+
|
|
1094
|
+
// 注意:FCS 不通过时不能返回 ok:true(否则会“强制截帧”吞掉其它协议帧)
|
|
1095
|
+
// 但为了防止卡死,这里仍消费掉该段,并作为错误帧上报给 emitErr。
|
|
1096
|
+
if (!fcsOK) {
|
|
1097
|
+
return {
|
|
1098
|
+
ok: false,
|
|
1099
|
+
used: expectedEnd + 1,
|
|
1100
|
+
frame: b.slice(0, expectedEnd + 1),
|
|
1101
|
+
fcs_ok: false,
|
|
1102
|
+
fcs_frame: fcs,
|
|
1103
|
+
fcs_calc_a: calcA,
|
|
1104
|
+
fcs_calc_b: calcB,
|
|
1105
|
+
err: "698_LEN_FCS_FAIL"
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1031
1108
|
|
|
1032
|
-
|
|
1109
|
+
// FCS 通过:返回完整帧(含 0x68..0x16)
|
|
1110
|
+
return {
|
|
1111
|
+
ok: true,
|
|
1112
|
+
used: expectedEnd + 1,
|
|
1113
|
+
frame: b.slice(0, expectedEnd + 1),
|
|
1114
|
+
fcs_ok: true
|
|
1115
|
+
};
|
|
1033
1116
|
}
|
|
1034
1117
|
|
|
1035
1118
|
// 698(HDLC):7E ... [FCS(lo,hi)] 7E,支持 0x7D 转义与 X.25 FCS
|
|
@@ -1085,19 +1168,61 @@ module.exports = function (RED) {
|
|
|
1085
1168
|
// if (!r.ok) r = tryParse645(assembleBuf);
|
|
1086
1169
|
// if (!r.ok) r = tryParse698Len(assembleBuf);
|
|
1087
1170
|
|
|
1088
|
-
// --fix-
|
|
1171
|
+
// --fix-20251213-先 BLE(7E7E7E5A…7EA5),再 698-HDLC,再 698-LEN,再 645
|
|
1172
|
+
// 关键:698-LEN 必须优先于 645,否则遇到 698 帧内出现 0x68(如示例报文第7字节)会被 645 误判并卡住
|
|
1089
1173
|
let r = tryParseBleGNW(assembleBuf);
|
|
1090
1174
|
if (!r.ok) r = tryParse698HDLC(assembleBuf);
|
|
1091
|
-
if (!r.ok) r = tryParse645(assembleBuf);
|
|
1092
1175
|
if (!r.ok) r = tryParse698Len(assembleBuf);
|
|
1176
|
+
if (!r.ok) r = tryParse645(assembleBuf);
|
|
1093
1177
|
|
|
1094
1178
|
if (r.ok) {
|
|
1179
|
+
// 附加解析元数据(例如 698 FCS 校验结果),供上层排障
|
|
1180
|
+
if (r && typeof r === 'object') {
|
|
1181
|
+
try {
|
|
1182
|
+
r.frame._meta = {
|
|
1183
|
+
proto: r.proto || undefined,
|
|
1184
|
+
fcs_ok: (typeof r.fcs_ok === 'boolean') ? r.fcs_ok : undefined,
|
|
1185
|
+
err: r.err || undefined,
|
|
1186
|
+
fcs_frame: r.fcs_frame,
|
|
1187
|
+
fcs_calc_a: r.fcs_calc_a,
|
|
1188
|
+
fcs_calc_b: r.fcs_calc_b
|
|
1189
|
+
};
|
|
1190
|
+
} catch (e) {
|
|
1191
|
+
// ignore
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1095
1194
|
emitOk(r.frame);
|
|
1096
1195
|
assembleBuf = assembleBuf.slice(r.used);
|
|
1097
1196
|
continue;
|
|
1098
1197
|
}
|
|
1099
1198
|
if (r.used) {
|
|
1100
|
-
|
|
1199
|
+
// 如果 parser 返回了“可消费”的错误帧(例如 698-LEN FCS_FAIL),
|
|
1200
|
+
// 这里把诊断信息写入 frame._meta,供上层日志/排障使用。
|
|
1201
|
+
if (r && typeof r === 'object') {
|
|
1202
|
+
try {
|
|
1203
|
+
if (r.frame) {
|
|
1204
|
+
// 统一把元数据挂到 frame 上,避免引用未定义变量导致运行期异常
|
|
1205
|
+
r.frame._meta = Object.assign({}, r.frame._meta || {}, {
|
|
1206
|
+
// proto:用于上层区分到底是 645 / 698-hdlc / 698-len / ble 等
|
|
1207
|
+
proto: (r.fcs_ok === false) ? '698-len' : (r.proto || undefined),
|
|
1208
|
+
// FCS 校验信息(仅当 698-LEN 校验失败时有意义)
|
|
1209
|
+
fcs_ok: (typeof r.fcs_ok === 'boolean') ? r.fcs_ok : undefined,
|
|
1210
|
+
fcs_frame: r.fcs_frame,
|
|
1211
|
+
fcs_calc_a: r.fcs_calc_a,
|
|
1212
|
+
fcs_calc_b: r.fcs_calc_b,
|
|
1213
|
+
// err:更精确的错误原因
|
|
1214
|
+
err: r.err || "FRAME_INVALID"
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
} catch (e) {
|
|
1218
|
+
// ignore:不要因为诊断信息影响主流程
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// 错误帧统一走 emitErr,reason 以 r.err 为准
|
|
1223
|
+
emitErr(r.frame, (r && r.err) ? r.err : "FRAME_INVALID");
|
|
1224
|
+
|
|
1225
|
+
// 消费掉该段,防止 assembleBuf 卡死
|
|
1101
1226
|
assembleBuf = assembleBuf.slice(r.used);
|
|
1102
1227
|
continue;
|
|
1103
1228
|
}
|
|
@@ -1138,6 +1263,14 @@ module.exports = function (RED) {
|
|
|
1138
1263
|
msgout.payload = m;
|
|
1139
1264
|
msgout.port = port;
|
|
1140
1265
|
msgout.status = "OK";
|
|
1266
|
+
|
|
1267
|
+
// 若解析器附带了元数据(例如 698 FCS 校验结果),一并输出
|
|
1268
|
+
if (frameBuf && frameBuf._meta) {
|
|
1269
|
+
msgout.frame_meta = frameBuf._meta;
|
|
1270
|
+
// 便于后续 GC:避免 assembleBuf 长期引用
|
|
1271
|
+
try { delete frameBuf._meta; } catch (e) { }
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1141
1274
|
obj._emitter.emit('data', msgout, last_sender);
|
|
1142
1275
|
},
|
|
1143
1276
|
// 错误帧(已有边界但校验/结束符不通过)
|