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.
- package/package.json +1 -1
- package/zserial.js +115 -97
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";
|
|
@@ -356,8 +352,7 @@ module.exports = function (RED) {
|
|
|
356
352
|
err.sendPayload = err.payload;
|
|
357
353
|
err.payload = ''
|
|
358
354
|
}
|
|
359
|
-
|
|
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
|
|
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
|
-
//
|
|
659
|
+
// 防重复推进:同一次 serial 'data' 回调只允许 dequeue 一次。
|
|
664
660
|
_rxToken: 0,
|
|
665
661
|
_rxDequeuedToken: -1,
|
|
666
|
-
//
|
|
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
|
-
|
|
720
|
-
|
|
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
|
-
//
|
|
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.
|
|
758
|
-
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";
|
|
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
|
-
}
|
|
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
|
-
//
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1078
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
//
|
|
1165
|
-
// 若此处直接判为错误帧并 dequeue,会导致上层“偶尔无法解码”(尤其是请求-响应严格匹配的场景)。
|
|
1166
|
-
// 因此:当边界与长度域一致时,允许“容错通过”,并在 _meta 中标记 fcs_ok=false 供上层排障。
|
|
1190
|
+
// 边界完整但 FCS 不通过时上报错误帧;队首请求继续等待后续正确帧或最终超时。
|
|
1167
1191
|
if (!fcsOK) {
|
|
1168
1192
|
return {
|
|
1169
1193
|
ok: false,
|
|
1170
|
-
used:
|
|
1171
|
-
frame:
|
|
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:
|
|
1184
|
-
frame:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1396
|
+
// 错误帧不绑定请求 sender,也不 dequeue;队首请求继续等完整帧或超时。
|
|
1397
|
+
obj._emitter.emit('data', msgout, null);
|
|
1380
1398
|
}
|
|
1381
1399
|
);
|
|
1382
1400
|
return; // 已处理
|