"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, };