const net = require("net"); const config = require("./config"); const logger = require("./logger"); const DeviceManager = require("./deviceManager"); const ProtocolParser = require("./protocol"); // 将解析后的数据整理成人类可读的中文列表,便于在日志中快速查看关键参数 function formatFrameSummary(data) { const lines = []; lines.push(`机器号: ${data.n || ""}`); lines.push(`机型: ${data.deviceType || ""}`); lines.push(`运行模式: ${data.jqyxms || ""}`); // SetTreatmentTime 和 K 现在已在解析阶段转换为“分钟” lines.push( `治疗时间(设/已)(分): ${data.SetTreatmentTime ?? ""} / ${data.K ?? ""}` ); // A/B/C 已在解析阶段转换为“升(L)”或“升每小时(L/h)”并保留三位小数 lines.push(`超滤总量A/已超滤量B(L): ${data.A ?? ""} / ${data.B ?? ""}`); lines.push(`超滤率C(L/h): ${data.C ?? ""}`); lines.push(`血泵流量D(ml/min): ${data.D ?? ""}`); lines.push(`透析液流量L(ml/min): ${data.L ?? ""}`); lines.push( `动/静脉压/跨膜压(mmHg): ${data.o ?? ""} / ${data.H ?? ""} / ${ data.J ?? "" }` ); if (data.N != null || data.O != null || data.P != null || data.BPMPJDMY != null) { lines.push( `血压N/O/P/平均压(mmHg,bpm): ${data.N ?? ""} / ${data.O ?? ""} / ${ data.P ?? "" } / ${data.BPMPJDMY ?? ""}` ); } if (data.alarmCode != null || data.bjlx != null || data.bjsj) { lines.push( `报警编号/类型/时间: ${data.alarmCode ?? ""} / ${ data.bjlx ?? "" } / ${data.bjsj || ""}` ); } if (data.pyzl != null || data.ypyzhyl != null) { lines.push( `补液总量pyzl/已补入置换液量(ml): ${data.pyzl ?? ""} / ${ data.ypyzhyl ?? "" }` ); } return lines.join("; "); } function createTcpServer({ dataCache, mqttPublisher, aliyunReporter }) { const protocolParser = new ProtocolParser(); const deviceManager = new DeviceManager({ idleTimeoutMs: config.tcp.idleTimeoutMs, maxBufferBytes: config.tcp.maxBufferBytes, maxFramesPerChunk: config.tcp.maxFramesPerChunk, onFrameParsed: ({ deviceNumber, data }) => { try { // 每次成功解析一帧透析机数据时,记录一条带中文说明的概要日志,方便排查问题 logger.info("Dialysis frame parsed", { deviceNumber, frameType: data.frameType, runMode: data.jqyxms, ip: data.IPAddress, suedtime: data.suedtime, summary: formatFrameSummary(data) }); // 同时输出完整解析结果,便于查看所有字段 logger.debug("Dialysis frame payload", { deviceNumber, payload: data }); if (dataCache) { dataCache.setDeviceData(deviceNumber, data); } if (mqttPublisher) { try { mqttPublisher(deviceNumber, data); } catch (err) { logger.error("mqttPublisher error", { deviceNumber, error: err.message || err }); } } if (aliyunReporter) { // 阿里云上报是异步的,这里不等待其完成 Promise.resolve(aliyunReporter(deviceNumber, data)).catch((err) => { logger.error("aliyunReporter error", { deviceNumber, error: err.message || err }); }); } } catch (err) { logger.error("onFrameParsed handler error", err.message || err); } } }); const server = net.createServer((socket) => { const key = `${socket.remoteAddress}:${socket.remotePort}`; logger.info("Incoming TCP connection", { key }); deviceManager.addConnection(socket); socket.setNoDelay(true); socket.setKeepAlive(true, 30 * 1000); socket.on("data", (chunk) => { // 记录收到的原始 TCP 数据(十六进制),便于现场抓包对比 try { const hex = chunk.toString("hex").match(/.{1,2}/g)?.join(" ") || ""; logger.debug("Raw TCP data received", { key, length: chunk.length, hex }); } catch (e) { logger.error("Failed to log raw TCP data", { key, error: e.message || e }); } try { deviceManager.handleData(socket, chunk, protocolParser); } catch (err) { logger.error("handleData crashed", { key, error: err.message || err }); } }); socket.on("close", () => { deviceManager.removeConnection(socket); }); socket.on("error", (err) => { logger.error("Socket error", { key, error: err.message || err }); deviceManager.removeConnection(socket); }); }); server.on("error", (err) => { logger.error("TCP server error", err.message || err); }); if (config.tcp.maxConnections) { server.maxConnections = config.tcp.maxConnections; } server.listen(config.tcp.port, config.tcp.host, () => { logger.info("TCP server listening", { host: config.tcp.host, port: config.tcp.port }); }); return { server, deviceManager }; } module.exports = createTcpServer;