node-zserial 1.0.35 → 1.0.38

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 +115 -97
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-zserial",
3
- "version": "1.0.35",
3
+ "version": "1.0.38",
4
4
  "description": "Node-RED nodes to talk to serial ports",
5
5
  "dependencies": {
6
6
  "serialport": "^12.0.0"
package/zserial.js CHANGED
@@ -1,8 +1,4 @@
1
1
 
2
- /**
3
- *
4
- * version: 1.0.34
5
- */
6
2
  module.exports = function (RED) {
7
3
  /*jshint -W082 */
8
4
  "use strict";
@@ -356,8 +352,7 @@ module.exports = function (RED) {
356
352
  err.sendPayload = err.payload;
357
353
  err.payload = ''
358
354
  }
359
- let serialConfig = node._msg.serialConfigs.find(sitem=>sitem.serialport==port)
360
- node.errorMsg[port] = Object.assign(serialConfig,err)
355
+ node.errorMsg[port] = err
361
356
  node.send([null, node._msg, null]);
362
357
  }
363
358
  sendAll(done);
@@ -374,7 +369,7 @@ module.exports = function (RED) {
374
369
  for (var i = 0; i < msg.serialConfigs.length; i++) {
375
370
  var serialConfig = msg.serialConfigs[i];
376
371
  serialConfig._msgid = msg._msgid + "_" + i;
377
- getSerialServer(msg, serialConfig, done);
372
+ getSerialServer( msg, serialConfig, done);
378
373
  }
379
374
  }
380
375
 
@@ -557,12 +552,12 @@ module.exports = function (RED) {
557
552
  })
558
553
  }
559
554
 
560
- function afterClosed(port) {
555
+ function afterClosed(port){
561
556
  node[`_dataHandler_${port}`] = null
562
557
  node[`_timeoutHandler_${port}`] = null
563
558
  }
564
559
 
565
- if (!node._afterClosed) {
560
+ if(!node._afterClosed){
566
561
  node._afterClosed = afterClosed;
567
562
  serialPool.on('afterClosed', node._afterClosed);
568
563
  }
@@ -650,6 +645,7 @@ module.exports = function (RED) {
650
645
  addchar = addchar.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\e", "\e").replace("\\f", "\f").replace("\\0", "\0"); // jshint ignore:line
651
646
 
652
647
  if (addchar.substr(0, 2) == "0x") { addchar = new Buffer.from([addchar]); }
648
+ let assembleBuf = Buffer.alloc(0);
653
649
  connections[id] = (function () {
654
650
  var obj = {
655
651
  _emitter: new events.EventEmitter(),
@@ -660,11 +656,12 @@ module.exports = function (RED) {
660
656
  _retryNum: 0,
661
657
  tout: null,
662
658
  queue: [],
663
- // ---- 防重复推进:同一次 serial 'data' 回调只允许 dequeue 一次 ----
659
+ // 防重复推进:同一次 serial 'data' 回调只允许 dequeue 一次。
664
660
  _rxToken: 0,
665
661
  _rxDequeuedToken: -1,
666
- // ---- 发送序号:用于标记本次 writehead 的队首请求 ----
662
+ // 发送序号:用于标记本次 writehead 的队首请求。
667
663
  _txSeq: 0,
664
+ _framePending: null,
668
665
  on: function (a, b) { this._emitter.on(a, b); },
669
666
  once: function (a, b) { this._emitter.once(a, b); },
670
667
  close: function (cb) {
@@ -710,24 +707,36 @@ module.exports = function (RED) {
710
707
  writehead: function () {
711
708
  if (!this.queue.length) { return; }
712
709
  var qobj = this.queue[0];
713
- // 标记本次发送的队首请求(用于防止 timeout 与接收回包边界竞态导致双推进)
714
710
  qobj._done = false;
715
711
  qobj._txId = (++obj._txSeq);
716
712
  this.write(qobj.payload, qobj.cb);
717
713
  var msg = qobj.msg;
718
714
  var timeout = msg.timeout || responsetimeout;
719
- this.tout = setTimeout(function () {
720
- // 注意:回调内 this 不是 obj,统一使用 obj.tout
715
+ var maxPartialWait = msg.maxPartialWait || serialConfig.maxPartialWait || (timeout * 2);
716
+ var scheduleTimeout = function (delay) {
717
+ obj.tout = setTimeout(onResponseTimeout, delay);
718
+ };
719
+ var onResponseTimeout = function () {
721
720
  obj.tout = null;
722
721
 
723
- // 关键保护:timeout 触发时,只允许处理“仍然挂在队首的同一个 qobj”。
724
- // 若队列已因正常回包 dequeue 推进,则该 timeout 已失效,直接忽略,避免连续发送/错位。
722
+ // 只处理仍然挂在队首的同一个请求;正常回包已推进队列时,该 timeout 失效。
725
723
  if (!obj.queue || !obj.queue.length) { return; }
726
724
  if (obj.queue[0] !== qobj) { return; }
727
725
  if (qobj._done) { return; }
726
+
727
+ if (spliton === "frame" && obj._framePending && obj._framePending.proto === '698-len') {
728
+ var now = Date.now();
729
+ var startedAt = obj._framePending.since || now;
730
+ var waited = now - startedAt;
731
+ if (waited < maxPartialWait) {
732
+ scheduleTimeout(Math.max(50, Math.min(timeout, maxPartialWait - waited)));
733
+ return;
734
+ }
735
+ }
736
+
728
737
  qobj._done = true;
729
738
 
730
- var msgout = obj.dequeue() || {};
739
+ var msgout = obj.dequeue(true) || {};
731
740
  msgout.port = id;
732
741
  // // if we have some leftover stuff, just send it
733
742
  // if (i !== 0) {
@@ -754,8 +763,11 @@ module.exports = function (RED) {
754
763
  if (m) {
755
764
  if (spliton === "frame") {
756
765
  const mbuf = Buffer.from(m);
757
- msgout.payload = mbuf;
758
- msgout.payload_hex = mbuf.toString('hex').toUpperCase();
766
+ msgout.partial_payload = mbuf;
767
+ msgout.partial_payload_hex = mbuf.toString('hex').toUpperCase();
768
+ msgout.payload = Buffer.alloc(0);
769
+ msgout.payload_hex = "";
770
+ msgout.reason = "FRAME_TIMEOUT_PARTIAL";
759
771
  } else {
760
772
  if (binoutput !== "bin") { m = m.toString(); }
761
773
  msgout.payload = m;
@@ -769,25 +781,27 @@ module.exports = function (RED) {
769
781
  }
770
782
  }
771
783
  msgout.status = "ERR_TIMEOUT";
784
+ obj._framePending = null;
772
785
  /* Notify the sender that a timeout occurred */
773
786
  obj._emitter.emit('timeout', msgout, qobj.sender);
774
- }, timeout);
787
+ };
788
+ scheduleTimeout(timeout);
775
789
  },
776
- dequeue: function () {
790
+ dequeue: function (ignoreRxTokenGuard) {
777
791
  // if we are trying to dequeue stuff from an
778
792
  // empty queue, that's an unsolicited message
779
793
  if (!this.queue.length) { return null; }
780
794
 
781
- // 关键保护:同一次 serial 'data' 回调(同 _rxToken)内只允许 dequeue 一次。
782
- // 防止 frame 自动解析在同一批输入字节上被多协议/多分支误判两次,导致队列被推进两次(连续发送)。
783
- if (typeof this._rxToken === 'number' && this._rxDequeuedToken === this._rxToken) {
784
- return null;
785
- }
786
- if (typeof this._rxToken === 'number') {
787
- this._rxDequeuedToken = this._rxToken;
795
+ // 同一次 serial 'data' 回调内只允许一次出队,避免粘包/误判让队列连续推进。
796
+ if (!ignoreRxTokenGuard) {
797
+ if (typeof this._rxToken === 'number' && this._rxDequeuedToken === this._rxToken) {
798
+ return null;
799
+ }
800
+ if (typeof this._rxToken === 'number') {
801
+ this._rxDequeuedToken = this._rxToken;
802
+ }
788
803
  }
789
804
 
790
- // 出队前标记 done:用于 timeout 回调校验
791
805
  var qobj = this.queue[0];
792
806
  if (qobj && qobj._done !== true) { qobj._done = true; }
793
807
 
@@ -797,13 +811,10 @@ module.exports = function (RED) {
797
811
  request_msgid: msg._msgid,
798
812
  });
799
813
  delete msg.payload;
800
-
801
- // 取消当前请求的响应超时计时器
802
814
  if (obj.tout) {
803
815
  clearTimeout(obj.tout);
804
816
  obj.tout = null;
805
817
  }
806
-
807
818
  this.queue.shift();
808
819
  this.writehead();
809
820
  return msg;
@@ -997,8 +1008,7 @@ module.exports = function (RED) {
997
1008
  if (b.length >= 8 && b[7] !== 0x68) {
998
1009
  const n = b.indexOf(0x68, 1);
999
1010
  if (n > 0) return { ok: false, used: feCount + n, frame: input.slice(0, feCount + n), err: "645_SECOND_68_NOT_FOUND" };
1000
- // 已经有足够字节判定形态错误,但又找不到下一个 0x68:同步点错位,丢 1 字节推进
1001
- return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "645_BAD_SHAPE_DROP1" };
1011
+ return { ok: false };
1002
1012
  }
1003
1013
  if (b.length < 10) return { ok: false }; // 还缺 CTRL/LEN
1004
1014
 
@@ -1074,10 +1084,8 @@ module.exports = function (RED) {
1074
1084
 
1075
1085
  // 698(68-LEN 变体):68 LL LH C ... DATA ... FCS(2) 16 ;兼容 FE* 前导
1076
1086
  function tryParse698Len(input) {
1077
- // 统计 FE 前导(用于 used/frame 回到原始 input)
1078
- let feCount = 0;
1079
- while (feCount < input.length && input[feCount] === 0xFE) feCount++;
1080
- const b = input.slice(feCount);
1087
+ // 剥离前导 FE(注意:这里只做 698-LEN 侧的兼容;645 另有自己的 FE 处理)
1088
+ const b = stripFE(input);
1081
1089
  if (b.length < 6 || b[0] !== 0x68) return { ok: false };
1082
1090
 
1083
1091
  // ---- 读取长度域 ----
@@ -1093,6 +1101,16 @@ module.exports = function (RED) {
1093
1101
  // 绝大多数场景长度单位为“字节”;若遇到 KB 单位则折算
1094
1102
  const L = isKB ? (Lraw << 10) : Lraw; // KB -> *1024
1095
1103
 
1104
+ function isComplete645AtStart() {
1105
+ if (b.length < 12 || b[0] !== 0x68 || b[7] !== 0x68) return false;
1106
+ const len645 = b[9] >>> 0;
1107
+ const total645 = 12 + len645;
1108
+ if (total645 > b.length) return false;
1109
+ if (b[total645 - 1] !== 0x16) return false;
1110
+ const cs = b[total645 - 2] >>> 0;
1111
+ return cs === sum8(b, 0, total645 - 2) || cs === sum8(b, 8, total645 - 2);
1112
+ }
1113
+
1096
1114
  // ------------------------ 关键防误判(核心修复点) ------------------------
1097
1115
  // 误判根因:645 帧形态为 68 + 6字节地址 + 68 ...,地址字节中“偶然出现 0x16”
1098
1116
  // 例如:68 02 80 16 00 00 00 68 ...
@@ -1108,12 +1126,11 @@ module.exports = function (RED) {
1108
1126
  return { ok: false };
1109
1127
  }
1110
1128
 
1111
-
1112
- // 长度上限:防止错位/噪声把 L 解读成极大值导致 assembleBuf 误判为半包长期等待
1113
- // 掉电次数等业务回包通常远小于 2KB;超出则高度可疑,丢 1 字节推进重同步
1129
+ // 长度上限:防止错位/噪声把 L 解读成极大值导致 assembleBuf 长期等待。
1114
1130
  const MAX_698_LEN_L = 2048;
1115
1131
  if (L > MAX_698_LEN_L) {
1116
- return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "698_LEN_TOO_LARGE" };
1132
+ if (isComplete645AtStart()) return { ok: false };
1133
+ return { ok: false, used: 1, frame: input.slice(0, 1), err: "698_LEN_TOO_LARGE" };
1117
1134
  }
1118
1135
 
1119
1136
  // 约束 2:控制域 C 在 b[3],不可能是 0x16(结束符)
@@ -1121,13 +1138,10 @@ module.exports = function (RED) {
1121
1138
  if (b.length >= 4 && b[3] === 0x16) {
1122
1139
  return { ok: false };
1123
1140
  }
1124
- // 约束 3:控制域 C 必须看起来像 698 的控制域(功能码通常为 1=链路管理 或 3=用户数据)
1125
- // 目的:避免 645 的地址字节被当成 698 控制域,从而把 645 整帧误判为 698-LEN 候选帧并被消费掉。
1126
- // 698 控制域:bit0..bit3 为功能码(1 或 3 最常见);其他位为 DIR/PRM/分帧/扰码标志。
1141
+ // 约束 3:控制域功能码应为常见 698 值,避免把其它 0x68 开头数据误当成长 698 半包。
1127
1142
  const C = b[3] >>> 0;
1128
1143
  const func = C & 0x0F;
1129
1144
  if (!(func === 0x01 || func === 0x03)) {
1130
- // 不消费任何字节,让 645 解析器继续尝试
1131
1145
  return { ok: false };
1132
1146
  }
1133
1147
  // ------------------------------------------------------------------------
@@ -1137,13 +1151,25 @@ module.exports = function (RED) {
1137
1151
  // 你现有逻辑采用 expectedEnd = 1 + L,并要求 b[expectedEnd] == 0x16
1138
1152
  const expectedEnd = 1 + L;
1139
1153
 
1140
- // 半包:继续累积
1141
- if (b.length < expectedEnd + 1) return { ok: false };
1154
+ // 半包:继续累积。这里必须显式告诉主循环“这是 698-LEN 半包”,
1155
+ // 否则后续 645 parser 会把第一个 0x68 当作错位字节丢掉。
1156
+ if (b.length < expectedEnd + 1) {
1157
+ // 若当前位置已经是完整且 CS 正确的 645 帧,让 645 parser 接管。
1158
+ // 不能仅凭 b[7] === 0x68 判断,否则会误伤 APDU 中第 8 字节刚好为 0x68 的正常 698 帧。
1159
+ if (isComplete645AtStart()) return { ok: false };
1160
+ return {
1161
+ ok: false,
1162
+ pending: true,
1163
+ proto: '698-len',
1164
+ expected: expectedEnd + 1,
1165
+ have: b.length
1166
+ };
1167
+ }
1142
1168
 
1143
1169
  // 若 expectedEnd 位置不是 0x16,说明起点错位或存在脏字节
1144
1170
  // 这里消费 1 字节,避免死循环卡住
1145
1171
  if (b[expectedEnd] !== 0x16) {
1146
- return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "698_LEN_BAD_END" };
1172
+ return { ok: false, used: 1, frame: input.slice(0, 1), err: "698_LEN_BAD_END" };
1147
1173
  }
1148
1174
 
1149
1175
  // ------------------------ FCS 校验(CRC-16/X.25) ------------------------
@@ -1161,14 +1187,12 @@ module.exports = function (RED) {
1161
1187
  const calcB = crc16x25(b, 1, expectedEnd - 2);
1162
1188
  const fcsOK = (calcA === fcs) || (calcB === fcs);
1163
1189
 
1164
- // 现场经常遇到:帧内容完整(68..16 边界正确),但 FCS 因链路噪声/串口转换器问题偶发不一致。
1165
- // 若此处直接判为错误帧并 dequeue,会导致上层“偶尔无法解码”(尤其是请求-响应严格匹配的场景)。
1166
- // 因此:当边界与长度域一致时,允许“容错通过”,并在 _meta 中标记 fcs_ok=false 供上层排障。
1190
+ // 边界完整但 FCS 不通过时上报错误帧;队首请求继续等待后续正确帧或最终超时。
1167
1191
  if (!fcsOK) {
1168
1192
  return {
1169
1193
  ok: false,
1170
- used: feCount + expectedEnd + 1,
1171
- frame: input.slice(0, feCount + expectedEnd + 1),
1194
+ used: expectedEnd + 1,
1195
+ frame: b.slice(0, expectedEnd + 1),
1172
1196
  fcs_ok: false,
1173
1197
  fcs_frame: fcs,
1174
1198
  fcs_calc_a: calcA,
@@ -1180,8 +1204,8 @@ module.exports = function (RED) {
1180
1204
  // FCS 通过:返回完整帧(含 0x68..0x16)
1181
1205
  return {
1182
1206
  ok: true,
1183
- used: feCount + expectedEnd + 1,
1184
- frame: input.slice(0, feCount + expectedEnd + 1),
1207
+ used: expectedEnd + 1,
1208
+ frame: b.slice(0, expectedEnd + 1),
1185
1209
  fcs_ok: true
1186
1210
  };
1187
1211
  }
@@ -1197,34 +1221,40 @@ module.exports = function (RED) {
1197
1221
  const rawFrame = b.slice(0, endPos + 1);
1198
1222
  const payloadEscaped = b.slice(1, endPos); // 去 7E
1199
1223
  const payload = unescapeHDLC(payloadEscaped);
1200
- // if (payload.length < 3) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
1201
- if (payload.length < 3) return { ok: false };
1202
- // 严格形态约束:698-HDLC 的“帧格式域”通常以 0xA0/0xA8/0xB0 等开头(高四位为 0xA 或 0xB)。
1203
- // 若不满足,极可能只是链路噪声/误同步的 0x7E,不应在 frame 模式下被消费掉(否则会 dequeue 错配)。
1204
- const fmt = payload[0] >>> 0;
1205
- const hi = fmt & 0xF0;
1206
- if (!(hi === 0xA0 || hi === 0xB0)) {
1207
- return { ok: false }; // 不消费,交给 698-LEN/645 再尝试
1208
- }
1224
+ if (payload.length < 3) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
1209
1225
  const fcsLo = payload[payload.length - 2];
1210
1226
  const fcsHi = payload[payload.length - 1];
1211
1227
  const fcs = (fcsHi << 8) | fcsLo;
1212
1228
  const calc = crc16x25(payload, 0, payload.length - 2);
1213
- // if (calc !== fcs) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
1214
- if (calc !== fcs) return { ok: false };
1229
+ if (calc !== fcs) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
1215
1230
  return { ok: true, used: endPos + 1, frame: rawFrame }; // 原始含 7E
1216
1231
  }
1217
1232
 
1218
1233
  // 统一喂入器:抽帧、报错、剔噪
1219
- let assembleBuf = Buffer.alloc(0);
1220
1234
  function feedAndExtract(d, emitOk, emitErr) {
1221
1235
  if (!Buffer.isBuffer(d)) d = Buffer.from(d);
1222
1236
  assembleBuf = Buffer.concat([assembleBuf, d]);
1223
1237
 
1224
- // 修复:部分现场链路/中间层会把原始 8-bit 串口数据“扩展”为 UTF-16LE 形态
1225
- // (如 0x7E -> 0x7E00 0x7E00...),导致帧头无法识别。
1226
- // 需要自动识别并还原原始 8-bit 数据。
1227
- // (已移除 normalizeUtf16Interleave 修复块)
1238
+ // 防溢出
1239
+ if (assembleBuf.length > bufMaxSize) {
1240
+ emitErr(Buffer.from(assembleBuf), "BUFFER_OVERFLOW_DROP_OLD");
1241
+ assembleBuf = Buffer.alloc(0);
1242
+ obj._framePending = null;
1243
+ return;
1244
+ }
1245
+
1246
+ // 前导剔噪:仅保留以 FE/68/7E 开头;若以 FE 开头,剥离所有 FE 前导
1247
+ let s = 0;
1248
+ while (s < assembleBuf.length) {
1249
+ const c = assembleBuf[s];
1250
+ if (c === 0xFE || c === 0x68 || c === 0x7E) break;
1251
+ s++;
1252
+ }
1253
+ if (s > 0) assembleBuf = assembleBuf.slice(s);
1254
+ if (assembleBuf.length && assembleBuf[0] === 0xFE) {
1255
+ let k = 0; while (k < assembleBuf.length && assembleBuf[k] === 0xFE) k++;
1256
+ assembleBuf = assembleBuf.slice(k);
1257
+ }
1228
1258
 
1229
1259
  // 抽帧
1230
1260
  while (assembleBuf.length >= 5) {
@@ -1237,21 +1267,17 @@ module.exports = function (RED) {
1237
1267
  // 关键:698-LEN 必须优先于 645,否则遇到 698 帧内出现 0x68(如示例报文第7字节)会被 645 误判并卡住
1238
1268
  let r = tryParseBleGNW(assembleBuf);
1239
1269
  if (!r.ok) r = tryParse698HDLC(assembleBuf);
1240
-
1241
- // ---- 兜底:0x7E 起始已形成候选段(存在第二个 0x7E),但 BLE/HDLC 均无法校验通过 -> 丢 1 字节推进重同步 ----
1242
- // 仅在候选段“闭合”时执行(有第二个 0x7E),避免误伤半包;并要求 endPos>=5,避免误伤 7E7E/7E7E7E 前导
1243
- if (!r.ok && assembleBuf.length && assembleBuf[0] === 0x7E) {
1244
- const endPos = assembleBuf.indexOf(0x7E, 1);
1245
- if (endPos >= 5) {
1246
- r = { ok: false, used: 1, frame: assembleBuf.slice(0, 1), err: "DROP_7E_BADFRAME" };
1247
- }
1248
- }
1249
-
1250
-
1251
1270
  if (!r.ok) r = tryParse698Len(assembleBuf);
1271
+ if (!r.ok && r.pending) {
1272
+ obj._framePending = Object.assign({}, r, {
1273
+ since: (obj._framePending && obj._framePending.proto === r.proto) ? obj._framePending.since : Date.now()
1274
+ });
1275
+ break;
1276
+ }
1252
1277
  if (!r.ok) r = tryParse645(assembleBuf);
1253
1278
 
1254
1279
  if (r.ok) {
1280
+ obj._framePending = null;
1255
1281
  // 附加解析元数据(例如 698 FCS 校验结果),供上层排障
1256
1282
  if (r && typeof r === 'object') {
1257
1283
  try {
@@ -1272,14 +1298,10 @@ module.exports = function (RED) {
1272
1298
  continue;
1273
1299
  }
1274
1300
  if (r.used) {
1275
- // resync/drop1 类(通常 used 很小)不应上报 ERR_FRAME,否则会产生大量重复输出
1301
+ obj._framePending = null;
1276
1302
  const used = r.used >>> 0;
1277
1303
  const frameBuf = r.frame ? Buffer.from(r.frame) : null;
1278
1304
  const frameLen = frameBuf ? frameBuf.length : 0;
1279
-
1280
- // 判定是否为“仅推进同步点”的消费:
1281
- // - used == 1 或 frameLen <= 1:典型 drop1
1282
- // - 或 err 属于已知的 resync 类原因
1283
1305
  const errCode = (r && r.err) ? String(r.err) : "";
1284
1306
  const isResyncDrop = (used <= 1) || (frameLen <= 1) ||
1285
1307
  errCode === "DROP_7E_BADFRAME" ||
@@ -1289,7 +1311,7 @@ module.exports = function (RED) {
1289
1311
  errCode === "645_SECOND_68_NOT_FOUND";
1290
1312
 
1291
1313
  if (!isResyncDrop) {
1292
- // 只有“闭合候选帧但校验/结构失败”的情况才上报 ERR_FRAME
1314
+ obj._framePending = null;
1293
1315
  try {
1294
1316
  if (r.frame) {
1295
1317
  r.frame._meta = Object.assign({}, r.frame._meta || {}, {
@@ -1302,12 +1324,11 @@ module.exports = function (RED) {
1302
1324
  });
1303
1325
  }
1304
1326
  } catch (e) {
1305
- // ignore
1327
+ // ignore:诊断信息不能影响主流程
1306
1328
  }
1307
1329
  emitErr(r.frame, errCode || "FRAME_INVALID");
1308
1330
  }
1309
1331
 
1310
- // 无论是否上报,都必须消费,防止 assembleBuf 卡死
1311
1332
  assembleBuf = assembleBuf.slice(used);
1312
1333
  continue;
1313
1334
  }
@@ -1319,7 +1340,6 @@ module.exports = function (RED) {
1319
1340
 
1320
1341
 
1321
1342
  obj.serial.on('data', function (d) {
1322
- // 标记本次接收事件 token(用于防止同一次 data 回调内重复 dequeue 推进队列)
1323
1343
  obj._rxToken = (obj._rxToken || 0) + 1;
1324
1344
  // RED.log.info("data::::" + d);
1325
1345
  function emitData(data) {
@@ -1366,17 +1386,15 @@ module.exports = function (RED) {
1366
1386
  function (badBuf, reason) {
1367
1387
  // 错误帧同样保持 Buffer 输出,并提供 HEX 字符串,便于定位截断点/前导位置
1368
1388
  var m = Buffer.from(badBuf);
1369
- var last_sender = null;
1370
- if (obj.queue.length) { last_sender = obj.queue[0].sender; }
1371
- // 关键修复:错误帧(CRC/CS/结尾不符)不应 dequeue,否则会把“请求上下文”弹出队列,导致后续正确回包无法匹配
1372
- var msgout = (obj.queue && obj.queue.length) ? Object.assign({}, obj.queue[0].msg) : {};
1389
+ var msgout = {};
1373
1390
  msgout.payload = m;
1374
1391
  msgout.payload_hex = m.toString('hex').toUpperCase();
1375
1392
  msgout.port = port;
1376
1393
  msgout.status = "ERR_FRAME";
1377
1394
  msgout.reason = reason || "FRAME_INVALID";
1378
1395
  msgout.is_unsolicited = !(obj.queue && obj.queue.length);
1379
- obj._emitter.emit('data', msgout, last_sender);
1396
+ // 错误帧不绑定请求 sender,也不 dequeue;队首请求继续等完整帧或超时。
1397
+ obj._emitter.emit('data', msgout, null);
1380
1398
  }
1381
1399
  );
1382
1400
  return; // 已处理