// 帮助函数:按 protocol.md 构造测试数据帧 const net = require("net"); const config = require("../src/config"); /** * 构造公共头 * @param {object} opts * @param {number} opts.frameType 0x1F/0x26/0x29 * @param {number|string} [opts.deviceNumber] 机器号(5 字节小端整数,测试时可用数字或字符串) */ function buildHeader({ frameType, deviceNumber = 12345 }) { const header = Buffer.alloc(20); // 0-3: 帧头 0x55555555 header[0] = 0x55; header[1] = 0x55; header[2] = 0x55; header[3] = 0x55; // 4: 机器类型(随便选一个:0x01=SWS-4000) header[4] = 0x01; // 5-9: 机器编号,协议为 5 字节无符号整数,小端在前 let idNum = 0; if (typeof deviceNumber === "string") { // 字符串时按十进制解析 idNum = parseInt(deviceNumber, 10) || 0; } else { idNum = Number(deviceNumber) || 0; } for (let i = 0; i < 5; i++) { header[5 + i] = idNum & 0xff; idNum = Math.floor(idNum / 256); } // 10: 机器运行模式(0x01=透析,参见 protocol.md 2.3) header[10] = 0x01; // 11: 数据帧类型 header[11] = frameType; // 12: 协议版本,当前 V1.10 => 0x6E(见 protocol.md 2.5) header[12] = 0x6e; // 13-19: 保留 7 字节,全部置 0 // Buffer.alloc 已经填 0,无需再写 return header; } /** * 写入无符号整数(运行参数帧采用小端编码),与解析端 readUInt 对齐 */ function writeUInt(buf, offset, value, length) { if (length === 1) return buf.writeUInt8(value, offset); if (length === 2) return buf.writeUInt16LE(value, offset); if (length === 4) return buf.writeUInt32LE(value, offset); } /** * 构造运行参数帧(0x1F),字段偏移参见 docs/protocol.md 3.2 */ function buildRunParamsFrame(deviceNumber = 12345) { const data = Buffer.alloc(220); // 只是示例填一些看得见的值,便于验证解析 writeUInt(data, 0, 3600, 4); // SetTreatmentTime: 3600s writeUInt(data, 4, 1200, 4); // K: 已治疗 1200s writeUInt(data, 8, 300, 2); // D: 血泵流量 300 ml/min writeUInt(data, 10, 1, 1); // xlyxbj: 血泵运行中 writeUInt(data, 11, 1, 1); // klfs: 肝素抗凝 writeUInt(data, 12, 1, 1); // z: 肝素泵运行 writeUInt(data, 13, 500, 2); // E: 肝素泵流量 50.0 ml/h (×10) writeUInt(data, 17, 10, 2); // gstqjssj: 提前结束 10 min writeUInt(data, 21, 2000, 4); // A: 超滤总量 2000 ml writeUInt(data, 25, 500, 4); // B: 已超滤量 500 ml writeUInt(data, 29, 1, 1); // cllyxbj: 超滤泵运行 writeUInt(data, 30, 0, 1); // plbj: 旁路关闭 writeUInt(data, 31, 500, 2); // L: 透析液流量 500 ml/min writeUInt(data, 33, 370, 2); // F: 37.0℃ (×10) writeUInt(data, 35, 1450, 2); // G: 14.50 mS/cm (×100) writeUInt(data, 37, 100, 2); // C: 示例值 writeUInt(data, 41, 1000, 4); // ypyzhyl: 已补入置换液量 writeUInt(data, 45, 0, 1); // pyprfs: 前稀释 writeUInt(data, 46, 100, 2); // ldslq writeUInt(data, 48, 50, 2); // ldslq2sysj writeUInt(data, 50, 600, 4); // jqzyxsj: 总运行 600min writeUInt(data, 54, 120, 2); // o: 动脉压 120 mmHg writeUInt(data, 56, 200, 2); // H: 静脉压 200 mmHg writeUInt(data, 58, 250, 2); // J: 跨膜压 250 mmHg writeUInt(data, 60, 150, 2); // I: 15.0 kPa (×10) writeUInt(data, 62, 20, 1); // lsxjl: 尿素下降率 20% writeUInt(data, 64, 3450, 2); // ssqclz: 34.50 (×100) writeUInt(data, 66, 365, 2); // jmyxh: 36.5℃ (×10) writeUInt(data, 68, 368, 2); // dmyxw: 36.8℃ (×10) writeUInt(data, 69, 80, 1); // xdxrl: 相对血容量 80% const header = buildHeader({ frameType: 0x1f, deviceNumber }); return Buffer.concat([header, data]); } /** * 构造报警信息帧(0x26) * * 数据区字段偏移对应 src/protocol.js::parseAlarm: * - 0:2 报警编号 alarmCode * - 2:1 报警类型 bjlx (0=解除,1=产生) * - 3:2 年, 5:1 月,6:1 日,7:1 时,8:1 分,9:1 秒 */ function buildAlarmFrame(deviceNumber = 12345, { alarmType = 1, alarmCode = 283 } = {}) { const data = Buffer.alloc(150); // 报警编号在实际帧中为小端编码,这里按小端写入, // 确保抓包看到的字节序与透析机一致,例如 0x011B = 283 → 1B 01。 data.writeUInt16LE(alarmCode, 0); // alarmCode: 示例编号 writeUInt(data, 2, alarmType, 1); // bjlx: 0/1=解除/产生 const now = new Date(); const year = now.getFullYear(); const month = now.getMonth() + 1; const day = now.getDate(); const hour = now.getHours(); const minute = now.getMinutes(); const second = now.getSeconds(); // 时间中的年份在协议中也是小端编码 data.writeUInt16LE(year, 3); writeUInt(data, 5, month, 1); writeUInt(data, 6, day, 1); writeUInt(data, 7, hour, 1); writeUInt(data, 8, minute, 1); writeUInt(data, 9, second, 1); const header = buildHeader({ frameType: 0x26, deviceNumber }); return Buffer.concat([header, data]); } /** * 构造血压测量数据帧(0x29) * * 注意:血压帧的数据区多字节字段使用小端序, * 与运行参数帧/报警帧不同,因此这里直接用 Buffer 的 writeUInt16LE 写入。 * * 数据区字段偏移对应 src/protocol.js::parseBloodPressure: * - 0:1 测量模式 bpMode (0/1=手动/自动) * - 1:1 测量结果 bpResult (0/1=出错/成功) * - 2 起 年/月/日/时/分/秒(年为 2 字节小端) * - 9:2 收缩压 N (mmHg,小端) * - 11:2 舒张压 O (mmHg,小端) * - 13:2 脉搏 P (bpm,小端) * - 15:2 平均动脉压 BPMPJDMY (mmHg,小端) */ function buildBloodPressureFrame( deviceNumber = 12345, { bpMode = 1, bpResult = 1, systolic = 129, diastolic = 93, heartRate = 86, map = 105 } = {} ) { const data = Buffer.alloc(150); // 单字节字段 data.writeUInt8(bpMode, 0); data.writeUInt8(bpResult, 1); const now = new Date(); const year = now.getFullYear(); const month = now.getMonth() + 1; const day = now.getDate(); const hour = now.getHours(); const minute = now.getMinutes(); const second = now.getSeconds(); // 时间:年为 2 字节小端,其它为 1 字节 data.writeUInt16LE(year, 2); data.writeUInt8(month, 4); data.writeUInt8(day, 5); data.writeUInt8(hour, 6); data.writeUInt8(minute, 7); data.writeUInt8(second, 8); // 血压相关字段,小端写入 data.writeUInt16LE(systolic, 9); data.writeUInt16LE(diastolic, 11); data.writeUInt16LE(heartRate, 13); data.writeUInt16LE(map, 15); const header = buildHeader({ frameType: 0x29, deviceNumber }); return Buffer.concat([header, data]); } /** * 通用发送函数:连上 TCP -> 发送一帧 -> 关闭 */ function sendFrameOnce(label, buildFn) { const client = new net.Socket(); client.connect(config.tcp.port, "192.168.220.1", () => { console.log(`[CLIENT] Connected to server ${config.tcp.port} for ${label}`); const frame = buildFn(12345); client.write(frame, () => { console.log(`[CLIENT] ${label} frame sent, length=`, frame.length); setTimeout(() => client.end(), 500); }); }); client.on("error", (err) => { console.error(`[CLIENT] ${label} socket error:`, err.message || err); }); client.on("close", () => { console.log(`[CLIENT] ${label} connection closed`); }); } function sendRunParamsOnce() { sendFrameOnce("RunParams(0x1F)", buildRunParamsFrame); } function sendAlarmOnce() { sendFrameOnce("Alarm(0x26)", buildAlarmFrame); } function sendBloodPressureOnce() { sendFrameOnce("BloodPressure(0x29)", buildBloodPressureFrame); } if (require.main === module) { // 直接运行本文件时,按顺序各发一帧三种类型,方便联调 sendRunParamsOnce(); setTimeout(() => sendAlarmOnce(), 800); setTimeout(() => sendBloodPressureOnce(), 1600); } module.exports = { buildRunParamsFrame, buildAlarmFrame, buildBloodPressureFrame, sendRunParamsOnce, sendAlarmOnce, sendBloodPressureOnce };