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;
|