node-zserial 1.0.17 → 1.0.19

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 +89 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-zserial",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Node-RED nodes to talk to serial ports",
5
5
  "dependencies": {
6
6
  "serialport": "^12.0.0"
package/zserial.js CHANGED
@@ -810,43 +810,102 @@ module.exports = function (RED) {
810
810
  return Buffer.from(out);
811
811
  }
812
812
 
813
+ // 645:FE* 68 + 6 addr + 68 + ctrl + len + data + cs + 16
813
814
  // 645:FE* 68 + 6 addr + 68 + ctrl + len + data + cs + 16
814
815
  function tryParse645(input) {
815
- let b = stripFE(input);
816
+ // 统计 FE 前导(仅用于 used,不参与 CS 计算)
817
+ let feCount = 0;
818
+ while (feCount < input.length && input[feCount] === 0xFE) feCount++;
819
+ const b = input.slice(feCount);
816
820
  if (b.length < 12) return { ok: false };
817
- if (b[0] !== 0x68) return { ok: false };
818
- if (b.length >= 8 && b[7] !== 0x68) return { ok: false }; // 第二个 0x68
819
- const dataLen = b[9] >>> 0;
820
- const total = 12 + dataLen;
821
- if (b.length < total) return { ok: false }; // 半包
822
- const csIdx = total - 2, endIdx = total - 1;
823
- const csExpect = sum8(b, 8, csIdx);
824
- const csGot = b[csIdx];
825
- const endFlag = b[endIdx];
826
- const frame = b.slice(0, total);
827
- if (endFlag !== 0x16) return { ok: false, used: total, frame, err: "645_END_FLAG_16_MISSING" };
828
- if (csGot !== csExpect) return { ok: false, used: total, frame, err: "645_CHECKSUM_MISMATCH" };
829
- return { ok: true, used: total, frame };
821
+
822
+ // 形态:68 + addr(6) + 68 + CTRL + LEN + DATA + CS + 16
823
+ if (b[0] !== 0x68) {
824
+ const n = b.indexOf(0x68, 1);
825
+ if (n > 0) return { ok: false, used: feCount + n, frame: input.slice(0, feCount + n), err: "645_NO_68_AT_START" };
826
+ return { ok: false };
827
+ }
828
+ if (b.length >= 8 && b[7] !== 0x68) {
829
+ const n = b.indexOf(0x68, 1);
830
+ if (n > 0) return { ok: false, used: feCount + n, frame: input.slice(0, feCount + n), err: "645_SECOND_68_NOT_FOUND" };
831
+ return { ok: false };
832
+ }
833
+ if (b.length < 10) return { ok: false }; // 还缺 CTRL/LEN
834
+
835
+ const len = b[9] >>> 0;
836
+ const total = 12 + len; // 帧总长(含 CS 与 0x16)
837
+ const endIdx = total - 1; // 末尾 0x16 索引
838
+ const csIdx = total - 2; // ★ CS 在倒数第2个字节
839
+
840
+ // CS = 从第一个 0x68 起累加到 CS 之前(不含 CS)
841
+ const csOK = (end) => {
842
+ const e = end; // e == total
843
+ const cs = b[e - 2]; // csIdx
844
+ const s = sum8(b, 0, e - 2);
845
+ return cs === s;
846
+ };
847
+
848
+ // 1) 优先按 LEN 快速命中
849
+ if (b.length >= total && b[endIdx] === 0x16 && csOK(total)) {
850
+ return { ok: true, used: feCount + total, frame: input.slice(0, feCount + total) };
851
+ }
852
+
853
+ // 2) 回溯:向后找 0x16 做候选结尾,再校验 CS(兼容异常 LEN/粘包)
854
+ for (let end = 12; end <= b.length; end++) {
855
+ if (b[end - 1] !== 0x16) continue;
856
+ // 候选帧至少要有 …… + CS + 16,因此 end >= 13 且 csIdx=end-2 >= 0
857
+ if (end >= 13) {
858
+ const cs = b[end - 2];
859
+ const s = sum8(b, 0, end - 2);
860
+ if (cs === s) {
861
+ return { ok: true, used: feCount + end, frame: input.slice(0, feCount + end) };
862
+ }
863
+ }
864
+ }
865
+
866
+ // 3) 起始形态确定不对则丢 1 字节;否则继续等
867
+ if (b.length >= 8 && b[7] !== 0x68) {
868
+ return { ok: false, used: feCount + 1, frame: input.slice(0, feCount + 1), err: "645_BAD_SHAPE_DROP1" };
869
+ }
870
+ return { ok: false };
830
871
  }
831
872
 
832
873
  // 698(68-LEN 变体):68 LL LH C ... DATA ... CS 16 ;兼容 FE* 前导
833
874
  function tryParse698Len(input) {
834
- const b = stripFE(input); // 关键:剥 FE 前导
835
- if (b.length < 5) return { ok: false };
836
- if (b[0] !== 0x68) return { ok: false };
837
- if (b.length >= 8 && b[7] === 0x68) return { ok: false }; // 645 645 解析
838
- const len = (b[1] | (b[2] << 8)) >>> 0;
839
- if (len <= 0 || len > 4096) return { ok: false, used: 1, frame: b.slice(0, 1), err: "698_BAD_LENGTH" };
840
- const total = 1 + 2 + len + 2; // 68 + len2 + payload(len) + CS,16
841
- if (b.length < total) return { ok: false }; // 半包
842
- const csIdx = total - 2, endIdx = total - 1;
843
- const endFlag = b[endIdx];
844
- const csExpect = sum8(b, 3, csIdx); // 从 control 起到 CS 前
845
- const csGot = b[csIdx];
846
- const frame = b.slice(0, total);
847
- if (endFlag !== 0x16) return { ok: false, used: total, frame, err: "698_END_FLAG_16_MISSING" };
848
- if (csGot !== csExpect) return { ok: false, used: total, frame, err: "698_CHECKSUM_MISMATCH" };
849
- return { ok: true, used: total, frame };
875
+ // FE 前导
876
+ const b = stripFE(input);
877
+ if (b.length < 6 || b[0] !== 0x68) return { ok: false };
878
+ // 避免把 645 误判成 698-LEN
879
+ if (b.length >= 8 && b[7] === 0x68) return { ok: false };
880
+
881
+ // 在缓冲里寻找候选的 0x16 作为帧尾(支持一包多帧/脏数据)
882
+ let end = b.indexOf(0x16, 5);
883
+ while (end !== -1) {
884
+ // —— 先试 2 字节 CRC-16/X.25(低字节在前),计算区间:从 LL 开始到 CRC 前一字节 ——
885
+ if (end >= 3) {
886
+ const lo = b[end - 2], hi = b[end - 1];
887
+ const fcs = (hi << 8) | lo;
888
+ const calc = crc16x25(b, /*start=*/1, /*end=*/end - 2); // [LL..CRC-1]
889
+ if (calc === fcs) {
890
+ return { ok: true, used: end + 1, frame: b.slice(0, end + 1) };
891
+ }
892
+ }
893
+
894
+ // —— ② 再试 1 字节和校验(sum8),计算区间:从 control 起到 CS 前 ——
895
+ if (end >= 2) {
896
+ const cs = b[end - 1];
897
+ const sum = sum8(b, /*start=*/3, /*end=*/end - 1);
898
+ if (cs === sum) {
899
+ return { ok: true, used: end + 1, frame: b.slice(0, end + 1) };
900
+ }
901
+ }
902
+
903
+ // 找下一个 0x16
904
+ end = b.indexOf(0x16, end + 1);
905
+ }
906
+
907
+ // 还不能定论,继续累计字节
908
+ return { ok: false };
850
909
  }
851
910
 
852
911
  // 698(HDLC):7E ... [FCS(lo,hi)] 7E,支持 0x7D 转义与 X.25 FCS