node-zserial 1.0.35 → 1.0.37

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 +101 -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.37",
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
  // ---- 读取长度域 ----
@@ -1108,12 +1116,10 @@ module.exports = function (RED) {
1108
1116
  return { ok: false };
1109
1117
  }
1110
1118
 
1111
-
1112
- // 长度上限:防止错位/噪声把 L 解读成极大值导致 assembleBuf 误判为半包长期等待
1113
- // 掉电次数等业务回包通常远小于 2KB;超出则高度可疑,丢 1 字节推进重同步
1119
+ // 长度上限:防止错位/噪声把 L 解读成极大值导致 assembleBuf 长期等待。
1114
1120
  const MAX_698_LEN_L = 2048;
1115
1121
  if (L > MAX_698_LEN_L) {
1116
- return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "698_LEN_TOO_LARGE" };
1122
+ return { ok: false, used: 1, frame: input.slice(0, 1), err: "698_LEN_TOO_LARGE" };
1117
1123
  }
1118
1124
 
1119
1125
  // 约束 2:控制域 C 在 b[3],不可能是 0x16(结束符)
@@ -1121,13 +1127,10 @@ module.exports = function (RED) {
1121
1127
  if (b.length >= 4 && b[3] === 0x16) {
1122
1128
  return { ok: false };
1123
1129
  }
1124
- // 约束 3:控制域 C 必须看起来像 698 的控制域(功能码通常为 1=链路管理 或 3=用户数据)
1125
- // 目的:避免 645 的地址字节被当成 698 控制域,从而把 645 整帧误判为 698-LEN 候选帧并被消费掉。
1126
- // 698 控制域:bit0..bit3 为功能码(1 或 3 最常见);其他位为 DIR/PRM/分帧/扰码标志。
1130
+ // 约束 3:控制域功能码应为常见 698 值,避免把其它 0x68 开头数据误当成长 698 半包。
1127
1131
  const C = b[3] >>> 0;
1128
1132
  const func = C & 0x0F;
1129
1133
  if (!(func === 0x01 || func === 0x03)) {
1130
- // 不消费任何字节,让 645 解析器继续尝试
1131
1134
  return { ok: false };
1132
1135
  }
1133
1136
  // ------------------------------------------------------------------------
@@ -1137,13 +1140,22 @@ module.exports = function (RED) {
1137
1140
  // 你现有逻辑采用 expectedEnd = 1 + L,并要求 b[expectedEnd] == 0x16
1138
1141
  const expectedEnd = 1 + L;
1139
1142
 
1140
- // 半包:继续累积
1141
- if (b.length < expectedEnd + 1) return { ok: false };
1143
+ // 半包:继续累积。这里必须显式告诉主循环“这是 698-LEN 半包”,
1144
+ // 否则后续 645 parser 会把第一个 0x68 当作错位字节丢掉。
1145
+ if (b.length < expectedEnd + 1) {
1146
+ return {
1147
+ ok: false,
1148
+ pending: true,
1149
+ proto: '698-len',
1150
+ expected: expectedEnd + 1,
1151
+ have: b.length
1152
+ };
1153
+ }
1142
1154
 
1143
1155
  // 若 expectedEnd 位置不是 0x16,说明起点错位或存在脏字节
1144
1156
  // 这里消费 1 字节,避免死循环卡住
1145
1157
  if (b[expectedEnd] !== 0x16) {
1146
- return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "698_LEN_BAD_END" };
1158
+ return { ok: false, used: 1, frame: input.slice(0, 1), err: "698_LEN_BAD_END" };
1147
1159
  }
1148
1160
 
1149
1161
  // ------------------------ FCS 校验(CRC-16/X.25) ------------------------
@@ -1161,14 +1173,12 @@ module.exports = function (RED) {
1161
1173
  const calcB = crc16x25(b, 1, expectedEnd - 2);
1162
1174
  const fcsOK = (calcA === fcs) || (calcB === fcs);
1163
1175
 
1164
- // 现场经常遇到:帧内容完整(68..16 边界正确),但 FCS 因链路噪声/串口转换器问题偶发不一致。
1165
- // 若此处直接判为错误帧并 dequeue,会导致上层“偶尔无法解码”(尤其是请求-响应严格匹配的场景)。
1166
- // 因此:当边界与长度域一致时,允许“容错通过”,并在 _meta 中标记 fcs_ok=false 供上层排障。
1176
+ // 边界完整但 FCS 不通过时上报错误帧;队首请求继续等待后续正确帧或最终超时。
1167
1177
  if (!fcsOK) {
1168
1178
  return {
1169
1179
  ok: false,
1170
- used: feCount + expectedEnd + 1,
1171
- frame: input.slice(0, feCount + expectedEnd + 1),
1180
+ used: expectedEnd + 1,
1181
+ frame: b.slice(0, expectedEnd + 1),
1172
1182
  fcs_ok: false,
1173
1183
  fcs_frame: fcs,
1174
1184
  fcs_calc_a: calcA,
@@ -1180,8 +1190,8 @@ module.exports = function (RED) {
1180
1190
  // FCS 通过:返回完整帧(含 0x68..0x16)
1181
1191
  return {
1182
1192
  ok: true,
1183
- used: feCount + expectedEnd + 1,
1184
- frame: input.slice(0, feCount + expectedEnd + 1),
1193
+ used: expectedEnd + 1,
1194
+ frame: b.slice(0, expectedEnd + 1),
1185
1195
  fcs_ok: true
1186
1196
  };
1187
1197
  }
@@ -1197,34 +1207,40 @@ module.exports = function (RED) {
1197
1207
  const rawFrame = b.slice(0, endPos + 1);
1198
1208
  const payloadEscaped = b.slice(1, endPos); // 去 7E
1199
1209
  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
- }
1210
+ if (payload.length < 3) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
1209
1211
  const fcsLo = payload[payload.length - 2];
1210
1212
  const fcsHi = payload[payload.length - 1];
1211
1213
  const fcs = (fcsHi << 8) | fcsLo;
1212
1214
  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 };
1215
+ if (calc !== fcs) return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
1215
1216
  return { ok: true, used: endPos + 1, frame: rawFrame }; // 原始含 7E
1216
1217
  }
1217
1218
 
1218
1219
  // 统一喂入器:抽帧、报错、剔噪
1219
- let assembleBuf = Buffer.alloc(0);
1220
1220
  function feedAndExtract(d, emitOk, emitErr) {
1221
1221
  if (!Buffer.isBuffer(d)) d = Buffer.from(d);
1222
1222
  assembleBuf = Buffer.concat([assembleBuf, d]);
1223
1223
 
1224
- // 修复:部分现场链路/中间层会把原始 8-bit 串口数据“扩展”为 UTF-16LE 形态
1225
- // (如 0x7E -> 0x7E00 0x7E00...),导致帧头无法识别。
1226
- // 需要自动识别并还原原始 8-bit 数据。
1227
- // (已移除 normalizeUtf16Interleave 修复块)
1224
+ // 防溢出
1225
+ if (assembleBuf.length > bufMaxSize) {
1226
+ emitErr(Buffer.from(assembleBuf), "BUFFER_OVERFLOW_DROP_OLD");
1227
+ assembleBuf = Buffer.alloc(0);
1228
+ obj._framePending = null;
1229
+ return;
1230
+ }
1231
+
1232
+ // 前导剔噪:仅保留以 FE/68/7E 开头;若以 FE 开头,剥离所有 FE 前导
1233
+ let s = 0;
1234
+ while (s < assembleBuf.length) {
1235
+ const c = assembleBuf[s];
1236
+ if (c === 0xFE || c === 0x68 || c === 0x7E) break;
1237
+ s++;
1238
+ }
1239
+ if (s > 0) assembleBuf = assembleBuf.slice(s);
1240
+ if (assembleBuf.length && assembleBuf[0] === 0xFE) {
1241
+ let k = 0; while (k < assembleBuf.length && assembleBuf[k] === 0xFE) k++;
1242
+ assembleBuf = assembleBuf.slice(k);
1243
+ }
1228
1244
 
1229
1245
  // 抽帧
1230
1246
  while (assembleBuf.length >= 5) {
@@ -1237,21 +1253,17 @@ module.exports = function (RED) {
1237
1253
  // 关键:698-LEN 必须优先于 645,否则遇到 698 帧内出现 0x68(如示例报文第7字节)会被 645 误判并卡住
1238
1254
  let r = tryParseBleGNW(assembleBuf);
1239
1255
  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
1256
  if (!r.ok) r = tryParse698Len(assembleBuf);
1257
+ if (!r.ok && r.pending) {
1258
+ obj._framePending = Object.assign({}, r, {
1259
+ since: (obj._framePending && obj._framePending.proto === r.proto) ? obj._framePending.since : Date.now()
1260
+ });
1261
+ break;
1262
+ }
1252
1263
  if (!r.ok) r = tryParse645(assembleBuf);
1253
1264
 
1254
1265
  if (r.ok) {
1266
+ obj._framePending = null;
1255
1267
  // 附加解析元数据(例如 698 FCS 校验结果),供上层排障
1256
1268
  if (r && typeof r === 'object') {
1257
1269
  try {
@@ -1272,14 +1284,10 @@ module.exports = function (RED) {
1272
1284
  continue;
1273
1285
  }
1274
1286
  if (r.used) {
1275
- // resync/drop1 类(通常 used 很小)不应上报 ERR_FRAME,否则会产生大量重复输出
1287
+ obj._framePending = null;
1276
1288
  const used = r.used >>> 0;
1277
1289
  const frameBuf = r.frame ? Buffer.from(r.frame) : null;
1278
1290
  const frameLen = frameBuf ? frameBuf.length : 0;
1279
-
1280
- // 判定是否为“仅推进同步点”的消费:
1281
- // - used == 1 或 frameLen <= 1:典型 drop1
1282
- // - 或 err 属于已知的 resync 类原因
1283
1291
  const errCode = (r && r.err) ? String(r.err) : "";
1284
1292
  const isResyncDrop = (used <= 1) || (frameLen <= 1) ||
1285
1293
  errCode === "DROP_7E_BADFRAME" ||
@@ -1289,7 +1297,7 @@ module.exports = function (RED) {
1289
1297
  errCode === "645_SECOND_68_NOT_FOUND";
1290
1298
 
1291
1299
  if (!isResyncDrop) {
1292
- // 只有“闭合候选帧但校验/结构失败”的情况才上报 ERR_FRAME
1300
+ obj._framePending = null;
1293
1301
  try {
1294
1302
  if (r.frame) {
1295
1303
  r.frame._meta = Object.assign({}, r.frame._meta || {}, {
@@ -1302,12 +1310,11 @@ module.exports = function (RED) {
1302
1310
  });
1303
1311
  }
1304
1312
  } catch (e) {
1305
- // ignore
1313
+ // ignore:诊断信息不能影响主流程
1306
1314
  }
1307
1315
  emitErr(r.frame, errCode || "FRAME_INVALID");
1308
1316
  }
1309
1317
 
1310
- // 无论是否上报,都必须消费,防止 assembleBuf 卡死
1311
1318
  assembleBuf = assembleBuf.slice(used);
1312
1319
  continue;
1313
1320
  }
@@ -1319,7 +1326,6 @@ module.exports = function (RED) {
1319
1326
 
1320
1327
 
1321
1328
  obj.serial.on('data', function (d) {
1322
- // 标记本次接收事件 token(用于防止同一次 data 回调内重复 dequeue 推进队列)
1323
1329
  obj._rxToken = (obj._rxToken || 0) + 1;
1324
1330
  // RED.log.info("data::::" + d);
1325
1331
  function emitData(data) {
@@ -1366,17 +1372,15 @@ module.exports = function (RED) {
1366
1372
  function (badBuf, reason) {
1367
1373
  // 错误帧同样保持 Buffer 输出,并提供 HEX 字符串,便于定位截断点/前导位置
1368
1374
  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) : {};
1375
+ var msgout = {};
1373
1376
  msgout.payload = m;
1374
1377
  msgout.payload_hex = m.toString('hex').toUpperCase();
1375
1378
  msgout.port = port;
1376
1379
  msgout.status = "ERR_FRAME";
1377
1380
  msgout.reason = reason || "FRAME_INVALID";
1378
1381
  msgout.is_unsolicited = !(obj.queue && obj.queue.length);
1379
- obj._emitter.emit('data', msgout, last_sender);
1382
+ // 错误帧不绑定请求 sender,也不 dequeue;队首请求继续等完整帧或超时。
1383
+ obj._emitter.emit('data', msgout, null);
1380
1384
  }
1381
1385
  );
1382
1386
  return; // 已处理