node-zserial 1.0.30 → 1.0.32

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 +93 -102
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-zserial",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "Node-RED nodes to talk to serial ports",
5
5
  "dependencies": {
6
6
  "serialport": "^12.0.0"
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( msg, serialConfig, done);
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 (binoutput !== "bin") { m = m.toString(); }
734
- msgout.payload = m;
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
- msgout.payload = (binoutput !== "bin") ? "" : Buffer.alloc(0);
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
- return { ok: false };
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
 
@@ -1023,8 +1035,10 @@ module.exports = function (RED) {
1023
1035
 
1024
1036
  // 698(68-LEN 变体):68 LL LH C ... DATA ... FCS(2) 16 ;兼容 FE* 前导
1025
1037
  function tryParse698Len(input) {
1026
- // 剥离前导 FE(注意:这里只做 698-LEN 侧的兼容;645 另有自己的 FE 处理)
1027
- const b = stripFE(input);
1038
+ // 统计 FE 前导(用于 used/frame 回到原始 input)
1039
+ let feCount = 0;
1040
+ while (feCount < input.length && input[feCount] === 0xFE) feCount++;
1041
+ const b = input.slice(feCount);
1028
1042
  if (b.length < 6 || b[0] !== 0x68) return { ok: false };
1029
1043
 
1030
1044
  // ---- 读取长度域 ----
@@ -1049,12 +1063,20 @@ module.exports = function (RED) {
1049
1063
  // 约束 1:698-LEN 的 L 不可能极短(至少应包含:C + 头字段 + FCS(2))
1050
1064
  // 这里给经验下限 6(偏保守,既能挡住 L=2,又尽量不误伤极端设备)
1051
1065
  // 若现场仍有碰撞,可把下限提高到 8 或 10(建议先保留 6)。
1052
- const MIN_698_LEN_L = 6;
1066
+ const MIN_698_LEN_L = 10;
1053
1067
  if (L < MIN_698_LEN_L) {
1054
1068
  // 不消费任何字节,让后续 parser(如 645)继续尝试
1055
1069
  return { ok: false };
1056
1070
  }
1057
1071
 
1072
+
1073
+ // 长度上限:防止错位/噪声把 L 解读成极大值导致 assembleBuf 误判为半包长期等待
1074
+ // 掉电次数等业务回包通常远小于 2KB;超出则高度可疑,丢 1 字节推进重同步
1075
+ const MAX_698_LEN_L = 2048;
1076
+ if (L > MAX_698_LEN_L) {
1077
+ return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "698_LEN_TOO_LARGE" };
1078
+ }
1079
+
1058
1080
  // 约束 2:控制域 C 在 b[3],不可能是 0x16(结束符)
1059
1081
  // 若 b[3] == 0x16,几乎必然是 645 地址域碰撞导致的误判
1060
1082
  if (b.length >= 4 && b[3] === 0x16) {
@@ -1073,7 +1095,7 @@ module.exports = function (RED) {
1073
1095
  // 若 expectedEnd 位置不是 0x16,说明起点错位或存在脏字节
1074
1096
  // 这里消费 1 字节,避免死循环卡住
1075
1097
  if (b[expectedEnd] !== 0x16) {
1076
- return { ok: false, used: 1, frame: b.slice(0, 1), err: "698_LEN_BAD_END" };
1098
+ return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "698_LEN_BAD_END" };
1077
1099
  }
1078
1100
 
1079
1101
  // ------------------------ FCS 校验(CRC-16/X.25) ------------------------
@@ -1096,22 +1118,22 @@ module.exports = function (RED) {
1096
1118
  // 因此:当边界与长度域一致时,允许“容错通过”,并在 _meta 中标记 fcs_ok=false 供上层排障。
1097
1119
  if (!fcsOK) {
1098
1120
  return {
1099
- ok: true,
1100
- used: expectedEnd + 1,
1101
- frame: b.slice(0, expectedEnd + 1),
1121
+ ok: false,
1122
+ used: feCount + expectedEnd + 1,
1123
+ frame: input.slice(0, feCount + expectedEnd + 1),
1102
1124
  fcs_ok: false,
1103
1125
  fcs_frame: fcs,
1104
1126
  fcs_calc_a: calcA,
1105
1127
  fcs_calc_b: calcB,
1106
- err: "698_LEN_FCS_FAIL_TOLERATED"
1128
+ err: "698_LEN_FCS_FAIL"
1107
1129
  };
1108
1130
  }
1109
1131
 
1110
1132
  // FCS 通过:返回完整帧(含 0x68..0x16)
1111
1133
  return {
1112
1134
  ok: true,
1113
- used: expectedEnd + 1,
1114
- frame: b.slice(0, expectedEnd + 1),
1135
+ used: feCount + expectedEnd + 1,
1136
+ frame: input.slice(0, feCount + expectedEnd + 1),
1115
1137
  fcs_ok: true
1116
1138
  };
1117
1139
  }
@@ -1127,12 +1149,21 @@ module.exports = function (RED) {
1127
1149
  const rawFrame = b.slice(0, endPos + 1);
1128
1150
  const payloadEscaped = b.slice(1, endPos); // 去 7E
1129
1151
  const payload = unescapeHDLC(payloadEscaped);
1130
- if (payload.length < 3) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
1152
+ // if (payload.length < 3) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
1153
+ if (payload.length < 3) return { ok: false };
1154
+ // 严格形态约束:698-HDLC 的“帧格式域”通常以 0xA0/0xA8/0xB0 等开头(高四位为 0xA 或 0xB)。
1155
+ // 若不满足,极可能只是链路噪声/误同步的 0x7E,不应在 frame 模式下被消费掉(否则会 dequeue 错配)。
1156
+ const fmt = payload[0] >>> 0;
1157
+ const hi = fmt & 0xF0;
1158
+ if (!(hi === 0xA0 || hi === 0xB0)) {
1159
+ return { ok: false }; // 不消费,交给 698-LEN/645 再尝试
1160
+ }
1131
1161
  const fcsLo = payload[payload.length - 2];
1132
1162
  const fcsHi = payload[payload.length - 1];
1133
1163
  const fcs = (fcsHi << 8) | fcsLo;
1134
1164
  const calc = crc16x25(payload, 0, payload.length - 2);
1135
- if (calc !== fcs) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
1165
+ // if (calc !== fcs) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
1166
+ if (calc !== fcs) return { ok: false };
1136
1167
  return { ok: true, used: endPos + 1, frame: rawFrame }; // 原始含 7E
1137
1168
  }
1138
1169
 
@@ -1142,72 +1173,10 @@ module.exports = function (RED) {
1142
1173
  if (!Buffer.isBuffer(d)) d = Buffer.from(d);
1143
1174
  assembleBuf = Buffer.concat([assembleBuf, d]);
1144
1175
 
1145
- // 防溢出
1146
- if (assembleBuf.length > bufMaxSize) {
1147
- emitErr(Buffer.from(assembleBuf), "BUFFER_OVERFLOW_DROP_OLD");
1148
- assembleBuf = Buffer.alloc(0);
1149
- return;
1150
- }
1151
-
1152
- // 前导剔噪:仅保留以 FE/68/7E 开头;若以 FE 开头,剥离所有 FE 前导
1153
- let s = 0;
1154
- while (s < assembleBuf.length) {
1155
- const c = assembleBuf[s];
1156
- if (c === 0xFE || c === 0x68 || c === 0x7E) break;
1157
- s++;
1158
- }
1159
- if (s > 0) assembleBuf = assembleBuf.slice(s);
1160
- if (assembleBuf.length && assembleBuf[0] === 0xFE) {
1161
- let k = 0; while (k < assembleBuf.length && assembleBuf[k] === 0xFE) k++;
1162
- assembleBuf = assembleBuf.slice(k);
1163
- }
1164
-
1165
- // 若缓存以 0x68 开始但迟迟无法凑齐完整帧,且中途出现新一轮 0xFE 0xFE 0xFE 0xFE 前导,
1166
- // 说明上一帧很可能被截断/丢字节(现场常见:串口/转换器插入前导或上层超时提前取走)。
1167
- // 这种情况下必须“强制丢弃”残片并从新的 FE 前导重新同步,否则会一直卡住导致后续帧都解析不出来。
1168
- function findFeRun4(buf) {
1169
- // 查找连续 4 个 0xFE 的起始位置
1170
- for (let i = 0; i <= buf.length - 4; i++) {
1171
- if (buf[i] === 0xFE && buf[i + 1] === 0xFE && buf[i + 2] === 0xFE && buf[i + 3] === 0xFE) return i;
1172
- }
1173
- return -1;
1174
- }
1175
-
1176
- // 仅对 698-LEN(0x68...0x16)这种“依赖长度域”的帧做强制重同步处理
1177
- if (assembleBuf.length >= 4 && assembleBuf[0] === 0x68) {
1178
- const LL0 = assembleBuf[1] >>> 0;
1179
- const LH0 = assembleBuf[2] >>> 0;
1180
- let Lraw0 = (LH0 << 8) | LL0;
1181
- const isKB0 = (Lraw0 & 0x4000) !== 0;
1182
- Lraw0 &= 0x3FFF;
1183
- const L0 = isKB0 ? (Lraw0 << 10) : Lraw0;
1184
- const expectedEnd0 = 1 + L0;
1185
-
1186
- // 只有在“明显是长帧但当前数据不足”时才触发该策略
1187
- if (L0 >= 6 && assembleBuf.length < expectedEnd0 + 1) {
1188
- const fePos = findFeRun4(assembleBuf);
1189
- if (fePos > 0) {
1190
- // fePos 之前的内容属于上一帧残片,作为错误帧上报并丢弃;
1191
- // 然后保留从 FE 开始的内容,留给下一轮解析。
1192
- const bad = assembleBuf.slice(0, fePos);
1193
- emitErr(Buffer.from(bad), "FRAME_TRUNCATED_RESYNC_FE");
1194
- assembleBuf = assembleBuf.slice(fePos);
1195
-
1196
- // 重新执行一次前导剔噪/FE 剥离(与上面逻辑一致)
1197
- let ss = 0;
1198
- while (ss < assembleBuf.length) {
1199
- const cc = assembleBuf[ss];
1200
- if (cc === 0xFE || cc === 0x68 || cc === 0x7E) break;
1201
- ss++;
1202
- }
1203
- if (ss > 0) assembleBuf = assembleBuf.slice(ss);
1204
- if (assembleBuf.length && assembleBuf[0] === 0xFE) {
1205
- let kk = 0; while (kk < assembleBuf.length && assembleBuf[kk] === 0xFE) kk++;
1206
- assembleBuf = assembleBuf.slice(kk);
1207
- }
1208
- }
1209
- }
1210
- }
1176
+ // 修复:部分现场链路/中间层会把原始 8-bit 串口数据“扩展”为 UTF-16LE 形态
1177
+ // (如 0x7E -> 0x7E00 0x7E00...),导致帧头无法识别。
1178
+ // 需要自动识别并还原原始 8-bit 数据。
1179
+ // (已移除 normalizeUtf16Interleave 修复块)
1211
1180
 
1212
1181
  // 抽帧
1213
1182
  while (assembleBuf.length >= 5) {
@@ -1220,6 +1189,17 @@ module.exports = function (RED) {
1220
1189
  // 关键:698-LEN 必须优先于 645,否则遇到 698 帧内出现 0x68(如示例报文第7字节)会被 645 误判并卡住
1221
1190
  let r = tryParseBleGNW(assembleBuf);
1222
1191
  if (!r.ok) r = tryParse698HDLC(assembleBuf);
1192
+
1193
+ // ---- 兜底:0x7E 起始已形成候选段(存在第二个 0x7E),但 BLE/HDLC 均无法校验通过 -> 丢 1 字节推进重同步 ----
1194
+ // 仅在候选段“闭合”时执行(有第二个 0x7E),避免误伤半包;并要求 endPos>=5,避免误伤 7E7E/7E7E7E 前导
1195
+ if (!r.ok && assembleBuf.length && assembleBuf[0] === 0x7E) {
1196
+ const endPos = assembleBuf.indexOf(0x7E, 1);
1197
+ if (endPos >= 5) {
1198
+ r = { ok: false, used: 1, frame: assembleBuf.slice(0, 1), err: "DROP_7E_BADFRAME" };
1199
+ }
1200
+ }
1201
+
1202
+
1223
1203
  if (!r.ok) r = tryParse698Len(assembleBuf);
1224
1204
  if (!r.ok) r = tryParse645(assembleBuf);
1225
1205
 
@@ -1244,34 +1224,43 @@ module.exports = function (RED) {
1244
1224
  continue;
1245
1225
  }
1246
1226
  if (r.used) {
1247
- // 如果 parser 返回了“可消费”的错误帧(例如 698-LEN FCS_FAIL),
1248
- // 这里把诊断信息写入 frame._meta,供上层日志/排障使用。
1249
- if (r && typeof r === 'object') {
1227
+ // resync/drop1 类(通常 used 很小)不应上报 ERR_FRAME,否则会产生大量重复输出
1228
+ const used = r.used >>> 0;
1229
+ const frameBuf = r.frame ? Buffer.from(r.frame) : null;
1230
+ const frameLen = frameBuf ? frameBuf.length : 0;
1231
+
1232
+ // 判定是否为“仅推进同步点”的消费:
1233
+ // - used == 1 或 frameLen <= 1:典型 drop1
1234
+ // - 或 err 属于已知的 resync 类原因
1235
+ const errCode = (r && r.err) ? String(r.err) : "";
1236
+ const isResyncDrop = (used <= 1) || (frameLen <= 1) ||
1237
+ errCode === "DROP_7E_BADFRAME" ||
1238
+ errCode === "645_BAD_SHAPE_DROP1" ||
1239
+ errCode === "698_LEN_BAD_END" ||
1240
+ errCode === "698_LEN_TOO_LARGE" ||
1241
+ errCode === "645_SECOND_68_NOT_FOUND";
1242
+
1243
+ if (!isResyncDrop) {
1244
+ // 只有“闭合候选帧但校验/结构失败”的情况才上报 ERR_FRAME
1250
1245
  try {
1251
1246
  if (r.frame) {
1252
- // 统一把元数据挂到 frame 上,避免引用未定义变量导致运行期异常
1253
1247
  r.frame._meta = Object.assign({}, r.frame._meta || {}, {
1254
- // proto:用于上层区分到底是 645 / 698-hdlc / 698-len / ble 等
1255
1248
  proto: (r.fcs_ok === false) ? '698-len' : (r.proto || undefined),
1256
- // FCS 校验信息(仅当 698-LEN 校验失败时有意义)
1257
1249
  fcs_ok: (typeof r.fcs_ok === 'boolean') ? r.fcs_ok : undefined,
1258
1250
  fcs_frame: r.fcs_frame,
1259
1251
  fcs_calc_a: r.fcs_calc_a,
1260
1252
  fcs_calc_b: r.fcs_calc_b,
1261
- // err:更精确的错误原因
1262
1253
  err: r.err || "FRAME_INVALID"
1263
1254
  });
1264
1255
  }
1265
1256
  } catch (e) {
1266
- // ignore:不要因为诊断信息影响主流程
1257
+ // ignore
1267
1258
  }
1259
+ emitErr(r.frame, errCode || "FRAME_INVALID");
1268
1260
  }
1269
1261
 
1270
- // 错误帧统一走 emitErr,reason 以 r.err 为准
1271
- emitErr(r.frame, (r && r.err) ? r.err : "FRAME_INVALID");
1272
-
1273
- // 消费掉该段,防止 assembleBuf 卡死
1274
- assembleBuf = assembleBuf.slice(r.used);
1262
+ // 无论是否上报,都必须消费,防止 assembleBuf 卡死
1263
+ assembleBuf = assembleBuf.slice(used);
1275
1264
  continue;
1276
1265
  }
1277
1266
  break; // 需要更多数据
@@ -1329,12 +1318,14 @@ module.exports = function (RED) {
1329
1318
  var m = Buffer.from(badBuf);
1330
1319
  var last_sender = null;
1331
1320
  if (obj.queue.length) { last_sender = obj.queue[0].sender; }
1332
- var msgout = obj.dequeue() || {};
1321
+ // 关键修复:错误帧(CRC/CS/结尾不符)不应 dequeue,否则会把“请求上下文”弹出队列,导致后续正确回包无法匹配
1322
+ var msgout = (obj.queue && obj.queue.length) ? Object.assign({}, obj.queue[0].msg) : {};
1333
1323
  msgout.payload = m;
1334
1324
  msgout.payload_hex = m.toString('hex').toUpperCase();
1335
1325
  msgout.port = port;
1336
1326
  msgout.status = "ERR_FRAME";
1337
1327
  msgout.reason = reason || "FRAME_INVALID";
1328
+ msgout.is_unsolicited = !(obj.queue && obj.queue.length);
1338
1329
  obj._emitter.emit('data', msgout, last_sender);
1339
1330
  }
1340
1331
  );