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