node-zserial 1.0.14 → 1.0.16
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 +257 -10
package/package.json
CHANGED
package/zserial.js
CHANGED
|
@@ -550,10 +550,12 @@ module.exports = function (RED) {
|
|
|
550
550
|
// State variables to be used by the on('data') handler
|
|
551
551
|
var i = 0; // position in the buffer
|
|
552
552
|
// .newline is misleading as its meaning depends on the split input policy:
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
//
|
|
553
|
+
// - "char" : send when a character equal to .newline is received
|
|
554
|
+
// - "time" : send after .newline milliseconds
|
|
555
|
+
// - "interbyte" : send when no byte arrives for .newline milliseconds
|
|
556
|
+
// - "count" : send after .newline characters
|
|
557
|
+
// - "frame" : (NEW) parse DL/T645 & DL/T698.45 (Len & HDLC) frames; emit on complete frame
|
|
558
|
+
// If "count", we already know how big the buffer will be
|
|
557
559
|
var bufSize = (spliton === "count") ? Number(newline) : bufMaxSize;
|
|
558
560
|
|
|
559
561
|
waitfor = waitfor.replace("\\n", "\n").replace("\\r", "\r")
|
|
@@ -638,14 +640,38 @@ module.exports = function (RED) {
|
|
|
638
640
|
this.tout = null;
|
|
639
641
|
var msgout = obj.dequeue() || {};
|
|
640
642
|
msgout.port = id;
|
|
641
|
-
// if we have some leftover stuff, just send it
|
|
642
|
-
if (i !== 0) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
643
|
+
// // if we have some leftover stuff, just send it
|
|
644
|
+
// if (i !== 0) {
|
|
645
|
+
// var m = buf.slice(0, i);
|
|
646
|
+
// m = Buffer.from(m);
|
|
647
|
+
// i = 0;
|
|
648
|
+
// if (binoutput !== "bin") { m = m.toString(); }
|
|
649
|
+
// msgout.payload = m;
|
|
650
|
+
// }
|
|
651
|
+
// Prefer flushing data depending on split mode
|
|
652
|
+
var m = null;
|
|
653
|
+
if (spliton === "frame") {
|
|
654
|
+
// In frame mode, partial bytes are accumulated in assembleBuf
|
|
655
|
+
if (typeof assembleBuf !== "undefined" && assembleBuf && assembleBuf.length) {
|
|
656
|
+
m = Buffer.from(assembleBuf);
|
|
657
|
+
// clear partials so they don't leak into next request
|
|
658
|
+
assembleBuf = Buffer.alloc(0);
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
// legacy modes use buf/i
|
|
662
|
+
if (i !== 0) {
|
|
663
|
+
m = buf.slice(0, i);
|
|
664
|
+
i = 0;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (m) {
|
|
646
668
|
if (binoutput !== "bin") { m = m.toString(); }
|
|
647
669
|
msgout.payload = m;
|
|
670
|
+
}else {
|
|
671
|
+
// ensure payload exists for upstream logic
|
|
672
|
+
msgout.payload = (binoutput !== "bin") ? "" : Buffer.alloc(0);
|
|
648
673
|
}
|
|
674
|
+
msgout.status = "ERR_TIMEOUT";
|
|
649
675
|
/* Notify the sender that a timeout occurred */
|
|
650
676
|
obj._emitter.emit('timeout', msgout, qobj.sender);
|
|
651
677
|
}, timeout);
|
|
@@ -745,10 +771,195 @@ module.exports = function (RED) {
|
|
|
745
771
|
obj._emitter.emit('ready', id);
|
|
746
772
|
});
|
|
747
773
|
|
|
774
|
+
|
|
775
|
+
/***** -------------------------------- Frame parsers for DL/T645 & DL/T698.45 (FULL) -------------------------------- *****/
|
|
776
|
+
// FE 去前导(645)
|
|
777
|
+
function stripFE(buf) {
|
|
778
|
+
let s = 0;
|
|
779
|
+
while (s < buf.length && buf[s] === 0xFE) s++;
|
|
780
|
+
return buf.slice(s);
|
|
781
|
+
}
|
|
782
|
+
// 计算 8bit 累加和([start,end))
|
|
783
|
+
function sum8(buf, start, end) {
|
|
784
|
+
let sum = 0;
|
|
785
|
+
for (let i = start; i < end; i++) sum = (sum + buf[i]) & 0xFF;
|
|
786
|
+
return sum;
|
|
787
|
+
}
|
|
788
|
+
// CRC-16/X25(HDLC/LAPB 常用):init 0xFFFF, poly 0x1021, refin/refout=true(实现中按位),xorout 0xFFFF
|
|
789
|
+
function crc16x25(buf, start, end) {
|
|
790
|
+
let crc = 0xFFFF;
|
|
791
|
+
for (let i = start; i < end; i++) {
|
|
792
|
+
crc ^= buf[i];
|
|
793
|
+
for (let b = 0; b < 8; b++) {
|
|
794
|
+
if (crc & 1) crc = (crc >>> 1) ^ 0x8408; // 0x8408 = reflect(0x1021)
|
|
795
|
+
else crc >>>= 1;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
crc ^= 0xFFFF;
|
|
799
|
+
// 返回 16-bit,低字节在前(与帧内 FCS 字节序一致)
|
|
800
|
+
return crc & 0xFFFF;
|
|
801
|
+
}
|
|
802
|
+
// HDLC 解转义:0x7D 0xXX -> 0xXX ^ 0x20
|
|
803
|
+
function unescapeHDLC(src) {
|
|
804
|
+
const out = [];
|
|
805
|
+
for (let i = 0; i < src.length; i++) {
|
|
806
|
+
const b = src[i];
|
|
807
|
+
if (b === 0x7D && i + 1 < src.length) {
|
|
808
|
+
out.push(src[i + 1] ^ 0x20);
|
|
809
|
+
i++;
|
|
810
|
+
} else {
|
|
811
|
+
out.push(b);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return Buffer.from(out);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// 解析 645:FE* 68 + 6 addr + 68 + ctrl + len + data + cs + 16
|
|
818
|
+
function tryParse645(input) {
|
|
819
|
+
let b = stripFE(input);
|
|
820
|
+
if (b.length < 12) return { ok: false };
|
|
821
|
+
if (b[0] !== 0x68) return { ok: false };
|
|
822
|
+
if (b.length >= 8 && b[7] !== 0x68) return { ok: false }; // 第二个 0x68 缺失
|
|
823
|
+
if (b.length < 12) return { ok: false };
|
|
824
|
+
|
|
825
|
+
const dataLen = b[9] >>> 0;
|
|
826
|
+
const total = 12 + dataLen;
|
|
827
|
+
if (b.length < total) return { ok: false }; // 半包
|
|
828
|
+
|
|
829
|
+
const csIdx = total - 2;
|
|
830
|
+
const endIdx = total - 1;
|
|
831
|
+
const csExpect = sum8(b, 8, csIdx); // 从 ctrl(索引8) 到 data 末
|
|
832
|
+
const csGot = b[csIdx];
|
|
833
|
+
const endFlag = b[endIdx];
|
|
834
|
+
const frame = b.slice(0, total);
|
|
835
|
+
|
|
836
|
+
if (endFlag !== 0x16) {
|
|
837
|
+
return { ok: false, used: total, frame, err: "645_END_FLAG_16_MISSING" };
|
|
838
|
+
}
|
|
839
|
+
if (csGot !== csExpect) {
|
|
840
|
+
return { ok: false, used: total, frame, err: "645_CHECKSUM_MISMATCH" };
|
|
841
|
+
}
|
|
842
|
+
return { ok: true, used: total, frame };
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// 解析 698 “Len 变体”:68 LL LH C ... DATA ... CS 16
|
|
846
|
+
function tryParse698Len(input) {
|
|
847
|
+
// 注意:不要 stripFE(698 不用 FE 前导);但现场可能混入 FE 噪声,外层会先剔除非 {FE,68,7E}
|
|
848
|
+
const b = input;
|
|
849
|
+
if (b.length < 5) return { ok: false };
|
|
850
|
+
if (b[0] !== 0x68) return { ok: false };
|
|
851
|
+
// 若字节7为 68,多为 645,交给 645 解析器
|
|
852
|
+
if (b.length >= 8 && b[7] === 0x68) return { ok: false };
|
|
853
|
+
|
|
854
|
+
const len = (b[1] | (b[2] << 8)) >>> 0;
|
|
855
|
+
if (len <= 0 || len > 4096) {
|
|
856
|
+
return { ok: false, used: 1, frame: b.slice(0, 1), err: "698_BAD_LENGTH" };
|
|
857
|
+
}
|
|
858
|
+
const total = 1 + 2 + len + 2; // 68 + LL,LH + payload(len) + CS,16
|
|
859
|
+
if (b.length < total) return { ok: false }; // 半包
|
|
860
|
+
|
|
861
|
+
const csIdx = total - 2;
|
|
862
|
+
const endIdx = total - 1;
|
|
863
|
+
const endFlag = b[endIdx];
|
|
864
|
+
const csExpect = sum8(b, 3, csIdx); // 从 control(索引3) 起,到 CS 前
|
|
865
|
+
const csGot = b[csIdx];
|
|
866
|
+
const frame = b.slice(0, total);
|
|
867
|
+
|
|
868
|
+
if (endFlag !== 0x16) {
|
|
869
|
+
return { ok: false, used: total, frame, err: "698_END_FLAG_16_MISSING" };
|
|
870
|
+
}
|
|
871
|
+
if (csGot !== csExpect) {
|
|
872
|
+
return { ok: false, used: total, frame, err: "698_CHECKSUM_MISMATCH" };
|
|
873
|
+
}
|
|
874
|
+
return { ok: true, used: total, frame };
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// 解析 698 标准 HDLC:7E ... FCS(lo,hi) 7E,支持 0x7D 透明传输与 FCS 校验
|
|
878
|
+
function tryParse698HDLC(input) {
|
|
879
|
+
const b = input;
|
|
880
|
+
if (b.length < 6) return { ok: false }; // 7E + 最小帧 + 7E
|
|
881
|
+
if (b[0] !== 0x7E) return { ok: false };
|
|
882
|
+
|
|
883
|
+
// 找到下一枚结束 0x7E(允许中间出现其他 0x7E? 正常应视为下一帧结束)
|
|
884
|
+
let endPos = -1;
|
|
885
|
+
for (let i = 1; i < b.length; i++) {
|
|
886
|
+
if (b[i] === 0x7E) { endPos = i; break; }
|
|
887
|
+
}
|
|
888
|
+
if (endPos === -1) return { ok: false }; // 半包
|
|
889
|
+
|
|
890
|
+
const rawFrame = b.slice(0, endPos + 1); // [0 .. endPos]
|
|
891
|
+
const payloadEscaped = b.slice(1, endPos); // 去掉前后 0x7E
|
|
892
|
+
// HDLC 透明传输反转义
|
|
893
|
+
const payload = unescapeHDLC(payloadEscaped);
|
|
894
|
+
if (payload.length < 3) {
|
|
895
|
+
// 至少 1 字节信息 + 2 字节 FCS
|
|
896
|
+
return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_TOO_SHORT" };
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// FCS:最后两字节,低字节在前
|
|
900
|
+
const fcsLo = payload[payload.length - 2];
|
|
901
|
+
const fcsHi = payload[payload.length - 1];
|
|
902
|
+
const fcs = (fcsHi << 8) | fcsLo;
|
|
903
|
+
const calc = crc16x25(payload, 0, payload.length - 2);
|
|
904
|
+
|
|
905
|
+
if (calc !== fcs) {
|
|
906
|
+
return { ok: false, used: endPos + 1, frame: rawFrame, err: "698_HDLC_CRC_FAIL" };
|
|
907
|
+
}
|
|
908
|
+
// 校验 OK,返回原始帧(包含 7E .. 7E),便于上层直接下发/记录
|
|
909
|
+
return { ok: true, used: endPos + 1, frame: rawFrame };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// 统一喂入器:抽取多帧、报告错误帧、剔除噪声
|
|
913
|
+
let assembleBuf = Buffer.alloc(0);
|
|
914
|
+
function feedAndExtract(d, emitOk, emitErr) {
|
|
915
|
+
if (!Buffer.isBuffer(d)) d = Buffer.from(d);
|
|
916
|
+
assembleBuf = Buffer.concat([assembleBuf, d]);
|
|
917
|
+
|
|
918
|
+
// 防溢出保护
|
|
919
|
+
if (assembleBuf.length > bufMaxSize) {
|
|
920
|
+
emitErr(Buffer.from(assembleBuf), "BUFFER_OVERFLOW_DROP_OLD");
|
|
921
|
+
assembleBuf = Buffer.alloc(0);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 剔除前导噪声:仅保留以 0xFE(645 前导)、0x68(645/698-Len)或 0x7E(698-HDLC)开头
|
|
926
|
+
let s = 0;
|
|
927
|
+
while (s < assembleBuf.length) {
|
|
928
|
+
const c = assembleBuf[s];
|
|
929
|
+
if (c === 0xFE || c === 0x68 || c === 0x7E) break;
|
|
930
|
+
s++;
|
|
931
|
+
}
|
|
932
|
+
if (s > 0) assembleBuf = assembleBuf.slice(s);
|
|
933
|
+
|
|
934
|
+
// 抽帧循环
|
|
935
|
+
while (assembleBuf.length >= 5) {
|
|
936
|
+
// 优先 HDLC(避免 7E 被误当噪声),其次 645,再次 698-Len
|
|
937
|
+
let r = tryParse698HDLC(assembleBuf);
|
|
938
|
+
if (!r.ok) r = tryParse645(assembleBuf);
|
|
939
|
+
if (!r.ok) r = tryParse698Len(assembleBuf);
|
|
940
|
+
|
|
941
|
+
if (r.ok) {
|
|
942
|
+
emitOk(r.frame);
|
|
943
|
+
assembleBuf = assembleBuf.slice(r.used);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (r.used) {
|
|
947
|
+
// 有足够信息判断该段为坏帧:直接丢弃并上报错误
|
|
948
|
+
emitErr(r.frame, r.err || "FRAME_INVALID");
|
|
949
|
+
assembleBuf = assembleBuf.slice(r.used);
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
// 需要更多数据
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/***** -------------------------------- Frame parsers for DL/T645 & DL/T698.45 (FULL) End-------------------------------- *****/
|
|
958
|
+
|
|
959
|
+
|
|
748
960
|
obj.serial.on('data', function (d) {
|
|
749
961
|
// RED.log.info("data::::" + d);
|
|
750
962
|
function emitData(data) {
|
|
751
|
-
|
|
752
963
|
if (active === true) {
|
|
753
964
|
var m = Buffer.from(data);
|
|
754
965
|
var last_sender = null;
|
|
@@ -762,6 +973,42 @@ module.exports = function (RED) {
|
|
|
762
973
|
active = (waitfor === "") ? true : false;
|
|
763
974
|
}
|
|
764
975
|
|
|
976
|
+
// —— 新增:frame 模式(645/698 全覆盖),完整帧即回,错帧带原始数据与原因 ——
|
|
977
|
+
if (spliton === "frame") {
|
|
978
|
+
feedAndExtract(
|
|
979
|
+
d,
|
|
980
|
+
// 完整 OK 帧
|
|
981
|
+
function (frameBuf) {
|
|
982
|
+
var m = Buffer.from(frameBuf);
|
|
983
|
+
var last_sender = null;
|
|
984
|
+
if (obj.queue.length) { last_sender = obj.queue[0].sender; }
|
|
985
|
+
var msgout = obj.dequeue() || {};
|
|
986
|
+
if (binoutput !== "bin") { m = m.toString(); } // 若需要字符串
|
|
987
|
+
msgout.payload = m;
|
|
988
|
+
msgout.port = port;
|
|
989
|
+
msgout.status = "OK";
|
|
990
|
+
obj._emitter.emit('data', msgout, last_sender);
|
|
991
|
+
},
|
|
992
|
+
// 错误帧(已有边界但校验/结束符不通过)
|
|
993
|
+
function (badBuf, reason) {
|
|
994
|
+
var m = Buffer.from(badBuf);
|
|
995
|
+
var last_sender = null;
|
|
996
|
+
if (obj.queue.length) { last_sender = obj.queue[0].sender; }
|
|
997
|
+
var msgout = obj.dequeue() || {};
|
|
998
|
+
if (binoutput !== "bin") { m = m.toString(); }
|
|
999
|
+
msgout.payload = m; // 原样数据(便于上层记录/重放)
|
|
1000
|
+
msgout.port = port;
|
|
1001
|
+
msgout.status = "ERR_FRAME";
|
|
1002
|
+
msgout.reason = reason || "FRAME_INVALID";
|
|
1003
|
+
obj._emitter.emit('data', msgout, last_sender);
|
|
1004
|
+
}
|
|
1005
|
+
);
|
|
1006
|
+
return; // 已处理
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// —— 其余兼容模式(time/interbyte/count/char)保持原逻辑 ——
|
|
1010
|
+
// -------- existing legacy split modes (time/interbyte/count/char) --------
|
|
1011
|
+
|
|
765
1012
|
for (var z = 0; z < d.length; z++) {
|
|
766
1013
|
var c = d[z];
|
|
767
1014
|
if (c === waitfor) { active = true; }
|