chenyc
2026-03-22 7885cede659f3255be56f77c1eef2ada7387d6f1
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
146
147
148
149
150
151
152
153
154
155
156
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;