chenyc
2026-05-09 c7d690bc224fb84e88d3033bf324876e4a64b008
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"use strict";
 
// protocol.js — GC-110N K 格式协议层(精简版)
// 负责:校验和、CRLF 切包、K 格式解析。
 
const CRLF = Buffer.from("\r\n", "ascii");
 
function checksum(body) {
  let sum = 0;
  for (const byte of body) {
    sum = (sum + byte) & 0xff;
  }
  return sum.toString(16).padStart(2, "0");
}
 
const GC110N_FIELD_LABELS = {
  A: "目标除水量", B: "当前除水量", C: "除水速度",
  D: "血液流量", E: "注射器泵速度", F: "透析液温度",
  G: "透析液浓度", H: "静脉压", I: "透析液压",
  J: "TMP", K: "治疗经过时间", L: "透析液流量",
  a: "液温报警", b: "浓度报警", c: "静脉压报警",
  d: "液压报警", e: "TMP报警", f: "气泡检测报警",
  g: "漏血报警", h: "其他报警",
  M: "治疗中标志", N: "治疗模式",
  O: "目标补液量", P: "当前补液量", Q: "补液速度",
  R: "补液温度",
  S: "血压测量时刻",
  T: "收缩压", U: "舒张压", V: "脉搏",
  i: "血压报警",
  W: "注射器泵累计量",
};
 
const GC110N_FIELD_UNITS = {
  A: "L", B: "L", C: "L/h", D: "mL/min", E: "mL/h",
  F: "℃", G: "mS/cm", H: "mmHg", I: "mmHg",
  J: "mmHg", K: "min", L: "mL/min",
  O: "L", P: "L", Q: "L/h", R: "℃",
  S: "HHMMSS",
  T: "mmHg", U: "mmHg", V: "bpm",
  W: "mL",
};
 
const GC110N_FIELD_ORDER = [
  "A","B","C","D","E","F","G","H","I","J","K","L",
  "a","b","c","d","e","f","g","h",
  "M","N",
  "O","P","Q","R",
  "S","T","U","V",
  "i","W",
];
 
const GC110N_FIXED_WIDTHS = {
  A: 5, B: 5, C: 5, D: 5, E: 5, F: 5, G: 5, H: 5, I: 5, J: 5,
  K: 5, L: 5,
  a: 1, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1, h: 1,
  M: 1, N: 1,
  O: 5, P: 5, Q: 5, R: 5,
  S: 6,
  T: 5, U: 5, V: 5,
  i: 1,
  W: 5,
};
 
function parseGC110NFrame(frame) {
  const text = frame.toString("ascii");
  if (text.length < 6 || text[0] !== "K") return null;
 
  const statusCode = text.substring(1, 5);
  let pos = 5;
  const items = [];
  const knownIds = new Set(Object.keys(GC110N_FIXED_WIDTHS));
 
  while (pos < text.length) {
    const id = text[pos];
    if (!knownIds.has(id)) break;
 
    pos++;
 
    let value;
    if (id === "S") {
      let end = text.length;
      for (let i = pos; i < text.length; i++) {
        if (knownIds.has(text[i])) { end = i; break; }
      }
      value = text.substring(pos, end);
    } else {
      const width = GC110N_FIXED_WIDTHS[id];
      if (pos + width > text.length) break;
      value = text.substring(pos, pos + width);
    }
    pos += value.length;
 
    const label = GC110N_FIELD_LABELS[id] || "";
    const unit = GC110N_FIELD_UNITS[id] || "";
    const displayValue = value.trim() || "(空)";
 
    items.push({ id, value, displayValue, name: label, unit });
  }
 
  return {
    type: "gc110n-status",
    prefix: "K",
    command: "STATUS",
    statusCode,
    items,
  };
}
 
function parseFrameAuto(frame) {
  if (Buffer.compare(frame, Buffer.from("K", "ascii")) === 0) {
    return { type: "request-status" };
  }
 
  const ascii = frame.toString("ascii");
  if (ascii.length >= 6 && ascii[0] === "K" && /^\d{4}/.test(ascii.substring(1, 5))) {
    return parseGC110NFrame(frame);
  }
 
  return null;
}
 
function extractFrames(buffer) {
  const frames = [];
  let remaining = buffer;
  let index;
 
  while ((index = remaining.indexOf(CRLF)) !== -1) {
    frames.push(remaining.subarray(0, index));
    remaining = remaining.subarray(index + CRLF.length);
  }
 
  return { frames, remaining };
}
 
module.exports = {
  CRLF,
  checksum,
  extractFrames,
  parseFrameAuto,
  parseGC110NFrame,
  GC110N_FIELD_LABELS,
  GC110N_FIELD_UNITS,
  GC110N_FIELD_ORDER,
  GC110N_FIXED_WIDTHS,
};