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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/zserial.js +168 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-zserial",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Node-RED nodes to talk to serial ports",
5
5
  "dependencies": {
6
6
  "serialport": "^12.0.0"
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 = 从第一个 0x68 起累加到 CS 之前(不含 CS)
959
+ // CS 双模校验:
960
+ // - full:从第一个 0x68 起累加到 CS 前(不含 CS)
961
+ // - std :从第二个 0x68 后(C+L+DATA)累加到 CS 前
960
962
  const csOK = (end) => {
961
- const e = end; // e == total
962
- const cs = b[e - 2]; // csIdx
963
- const s = sum8(b, 0, e - 2);
964
- return cs === s;
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 && csOK(total)) {
969
- return { ok: true, used: feCount + total, frame: input.slice(0, feCount + total) };
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 cs = b[end - 2];
978
- const s = sum8(b, 0, end - 2);
979
- if (cs === s) {
980
- return { ok: true, used: feCount + end, frame: input.slice(0, feCount + end) };
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
- // ---- 严格按长度域 L 判帧 ----
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
- // 仅支持常见“字节”为单位的场景;若为 KB 单位,展开至字节
1008
- let L = isKB ? (Lraw << 10) : Lraw; // KB -> *1024
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 的起点):1 + L
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
- // 不是以 0x16 结尾,说明前面有脏字节或起点错位,丢弃 1 字节继续
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 校验(CRC16/X.25),计算区间:[LL..FCS 前一字节]
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
- // (可选)HCS 校验:从 LL 开始至 HCS 前一字节
1029
- // 698.45,HCS 位于地址域之后;为避免复杂度,这里不强制校验 HCS。
1030
- // 仅在需要时可补:解析地址域长度后再计算 HCS。
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
- return { ok: true, used: expectedEnd + 1, frame: b.slice(0, expectedEnd + 1) };
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-20251108-先 BLE(7E7E7E5A…7EA5),再 698-HDLC,再 645,再 698-LEN
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
- emitErr(r.frame, r.err || "FRAME_INVALID");
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
  // 错误帧(已有边界但校验/结束符不通过)