// net-server.js const net = require('net'); const logger = require('./logger'); const { sendDataToAliyun } = require('./aliyun-iot'); function getDateString() { const now = new Date(); const localTime = new Date(now.getTime() - now.getTimezoneOffset() * 60000) .toISOString() .slice(0, 19) .replace('T', ' '); return localTime; } /** * 计算校验和 * @param {Buffer} data - 数据包的前12个字节 * @return {number} 校验和 */ function calculateChecksum(data) { try { let sum = 0; for (let i = 0; i < data.length; i++) { sum += data[i]; } return sum % 65536; } catch (err) { logger.error('计算校验和时发生错误', { 错误: err.message, 数据: data?.toString('hex') }); return 0; } } /** * 解析报警标志 * @param {number} alarmFlags - 报警标志字节 * @return {Array} 报警信息数组 */ function parseAlarmFlags(alarmFlags) { try { const alarms = { g: '0', // 漏血报警 ywbj: '0', // 液位报警 f: '0', // 气泡报警 p: '0', // 动脉压报警 e: '0', // 跨膜压报警 c: '0', // 静脉压报警 a: '0', // 温度透析液温度 b: '0', // 电导度 }; if (alarmFlags & 0x01) { alarms.g='1'; // 漏血报警 }else{ alarms.g='0'; // 无漏血报警 } if (alarmFlags & 0x02) { alarms.ywbj='1'; // 液位报警 } else{ alarms.ywbj='0'; // 无液位报警 } if (alarmFlags & 0x04) { alarms.f='1';// 气泡报警 }else{ alarms.f='0'; // 无气泡报警 } if (alarmFlags & 0x08) { alarms.p='1'; // 动脉压报警 }else{ alarms.p='0'; // 无动脉压报警 } if (alarmFlags & 0x10) { alarms.e='1'; // 跨膜压报警 }else{ alarms.e='0'; // 无跨膜压报警 } if (alarmFlags & 0x20) { alarms.c='1'; // 静脉压报警 }; if (alarmFlags & 0x40) { alarms.a='1'; // 温度报警 }else{ alarms.a='0'; // 无温度报警 } if (alarmFlags & 0x80) { alarms.b='1'; // 电导度报警 }else{ alarms.b='0'; // 无电导度报警 } return alarms; } catch (err) { logger.error('解析报警标志时发生错误', { 错误: err.message, alarmFlags }); return ['解析报警标志失败']; } } /** * 解析血透机数据包 * @param {Buffer} data - 数据包 */ function parseDialysisData(data) { try { // 检查输入是否有效 if (!data || !Buffer.isBuffer(data)) { logger.error("接收到的数据无效,非Buffer类型", { 数据类型: typeof data, 数据值: data }); return; } if (data.length !== 14) { logger.error("数据长度无效。期望长度为14字节。", { 实际长度: data.length, 数据: data.toString('hex') }); return; } const offsets = { start: [0, 2], deviceType: [2, 1], deviceId: [3, 3], modeAndValue: [6, 1], mainAlarmFlag: [7, 1], otherAlarmFlag: [8, 1], dataFlag: [9, 1], dataValue: [10, 2], checksum: [12, 2] }; // 解析开始标志 const startMarker = data.slice(0, 2).toString('hex'); if (startMarker !== '55aa') { logger.error("起始标志无效。期望值为 '55aa'。", { 起始标志: startMarker, 数据: data.toString('hex') }); return; } // 提取前12字节计算校验和 const dataForChecksum = data.slice(0, 12); const receivedChecksum = data.readUInt16BE(12); const calculatedChecksum = calculateChecksum(dataForChecksum); if (receivedChecksum !== calculatedChecksum) { logger.error(`校验和不匹配`, { 接收到的校验和: receivedChecksum, 计算出的校验和: calculatedChecksum, 数据: data.toString('hex') }); return; } else { logger.info(`校验和验证成功: ${receivedChecksum}`); } // 开始解析字段(每个都加 try 防止崩溃) let deviceType, deviceIdHex, modeAndValue, mainAlarmFlag, mainAlarmFlags,otherAlarmFlag, dataFlag, dataValue; try { deviceType = data.readUInt8(2); logger.info(`设备类型: ${deviceType}`); } catch (err) { logger.error('解析设备类型失败', { 错误: err.message }); deviceType = null; } try { deviceIdHex = data.slice(3, 6).toString('hex'); logger.info(`设备ID: ${deviceIdHex}`); } catch (err) { logger.error('解析设备ID失败', { 错误: err.message }); deviceIdHex = 'unknown'; } try { modeAndValue = data.readUInt8(6); logger.info(`运行模式&权值: ${modeAndValue}`); } catch (err) { logger.error('解析运行模式&权值失败', { 错误: err.message }); modeAndValue = null; } try { mainAlarmFlag = data.readUInt8(7); logger.info(`主要报警标志: ${mainAlarmFlag}`); const alarms = parseAlarmFlags(mainAlarmFlag); mainAlarmFlags = alarms; if (alarms.length > 0) { logger.info(`当前报警信息:${alarms.join(', ')}`, { 报警信息: alarms }); } else { logger.info('当前无报警'); } } catch (err) { logger.error('解析主要报警标志失败', { 错误: err.message }); mainAlarmFlag = 0; } try { otherAlarmFlag = data.readUInt8(8); logger.info(`其他报警标志: ${otherAlarmFlag}`); } catch (err) { logger.error('解析其他报警标志失败', { 错误: err.message }); otherAlarmFlag = null; } try { dataFlag = data.readUInt8(9); logger.info(`数据标识: ${dataFlag}`); } catch (err) { logger.error('解析数据标识失败', { 错误: err.message }); dataFlag = null; } try { dataValue = data.readUInt16BE(10); logger.info(`数据值: ${dataValue}`); } catch (err) { logger.error('解析数据值失败', { 错误: err.message }); dataValue = null; } // 构造 payload const payload = { deviceName:'', deviceType:deviceType, //设备类型 0是血透1 是血滤 n: deviceIdHex, //设备序列号 也就是id RunningState: modeAndValue, // 运行模式&权值 h:otherAlarmFlag.toFixed(0),//其他报警 suedtime: getDateString(), // 建议加上时间戳 // g:0, // 漏血报警 // f:0,//气泡报警 // e:0,//跨膜压报警 // ywbj:0,//液位报警 // c:0,//静脉压报警 // p:0,//动脉压报警 // b:0,//电导度 // a:0,//温度透析液温度, // h:0,//其他报警 // A:0,//脱水 // B:0,//当前脱水 // C:0,//脱水速率 // D:0,//血液流速 血流量 // L:0,//透析液流量 // fzl:0,//辅助泵流量 // zsq:0,//注射器 // F:0,//透析液温度 // G:0,//透析液电导度 // H:0,//静脉压 // J:0,//跨膜压 // o:0,//动脉压, // ytsj:0,//已经透析时间 // sysj:0,//剩余时间 // ssy:0,//收缩压 // szy:0,//舒张压 // mb:0,//脉搏 }; // 发送数据到阿里云(异步,可能抛错) try { // console.log('报警数据:', mainAlarmFlags); // console.log('其他数据:', otherAlarmFlag); // console.log('运行或者是授权:', modeAndValue); // console.log('数据标识:', dataFlag); // console.log('数据值:', dataValue); // console.log('发送到阿里云:', payload); // 计算是运行模式还是小数点计位 if(modeAndValue<=4){ // 继位小数点 if(modeAndValue){ dataValue=(dataValue/10**modeAndValue).toFixed(modeAndValue) }else{ dataValue=dataValue.toFixed(0) } if(dataFlag){ switch(dataFlag){ case 1: payload.A=dataValue break; case 2: payload.B=dataValue break; case 3: payload.C=dataValue break; case 4: payload.D=dataValue break; case 5: payload.fzl=dataValue break; case 6: payload.zsq=dataValue break; case 7: payload.L=dataValue break; case 8: payload.F=dataValue break; case 9: payload.G=dataValue break; case 10: payload.H=(Number(dataValue)-65536).toFixed(0) break; case 11: payload.J=dataValue break; case 12: payload.ytsj=dataValue break; case 13: payload.sysj=dataValue break; case 14: payload.o=dataValue break; case 15: payload.ssy=dataValue break; case 16: payload.szy=dataValue break; case 17: payload.mb=dataValue break; default: logger.warn('未知的数据标识', { dataFlag }); } } }else if(modeAndValue>=5){ payload.RunningState=modeAndValue dataValue=dataValue.toFixed(0) if(dataFlag){ switch(dataFlag){ case 1: payload.A=dataValue break; case 2: payload.B=dataValue break; case 3: payload.C=dataValue break; case 4: payload.D=dataValue break; case 5: payload.fzl=dataValue break; case 6: payload.zsq=dataValue break; case 7: payload.L=dataValue break; case 8: payload.F=dataValue break; case 9: payload.G=dataValue break; case 10: payload.H=(Number(dataValue)-65536).toFixed(0) break; case 11: payload.J=dataValue break; case 12: payload.ytsj=dataValue break; case 13: payload.sysj=dataValue break; case 14: payload.o=dataValue break; case 15: payload.ssy=dataValue break; case 16: payload.szy=dataValue break; case 17: payload.mb=dataValue break; default: logger.warn('未知的数据标识', { dataFlag }); } } } const payloadData={...payload,...mainAlarmFlags} console.log('最终发送到阿里云:', payloadData); sendDataToAliyun(deviceIdHex, payloadData) } catch (err) { logger.error('调用 sendDataToAliyun 时发生同步错误', { 错误: err, payload }); } } catch (err) { logger.error('parseDialysisData 发生未预期错误', { 错误: err.message, 堆栈: err.stack, 数据: data?.toString?.('hex') || 'unknown' }); } } /** * 创建TCP服务器(带异常防护) */ function createServer() { let server = null; try { server = net.createServer((socket) => { const remote = `${socket.remoteAddress}:${socket.remotePort}`; logger.info(`客户端已连接: ${remote}`); // 设置超时和错误处理 socket.setTimeout(300 * 1000); // 300秒超时 socket.setNoDelay(true); socket.on('data', (data) => { try { logger.info(`接收数据: ${remote}`, { 数据: data.toString('hex') }); parseDialysisData(data); } catch (err) { logger.error('处理客户端数据时发生异常', { 错误: err.message, 堆栈: err.stack, 远程地址: remote }); } }); socket.on('timeout', () => { logger.warn(`客户端连接超时,关闭连接: ${remote}`); socket.destroy(); }); socket.on('end', () => { logger.info(`客户端断开连接: ${remote}`); }); socket.on('error', (err) => { logger.error(`客户端连接发生错误: ${err.message}`, { 远程地址: remote, 错误码: err.code }); // 错误后 socket 可能已不可用,避免重复 destroy try { socket.destroy(); } catch (e) { /* 忽略 */ } }); }); const PORT = process.env.PORT || 60961; server.listen(PORT, '0.0.0.0', () => { logger.info(`✅ 血透机TCP服务器已启动,监听端口 ${PORT}`); }); server.on('error', (err) => { logger.error('服务器监听错误', { 错误: err.message, 堆栈: err.stack, 端口: PORT }); // 如果是端口占用,尝试重启或退出 if (err.code === 'EADDRINUSE') { logger.error(`端口 ${PORT} 被占用,请检查是否有其他进程占用。`); // 可在此加入延迟重试逻辑 setTimeout(() => { logger.info(`尝试重新启动服务器...`); server.close(() => { createServer(); // 递归重启 }); }, 5000); } }); server.on('close', () => { logger.info('服务器已关闭'); }); } catch (err) { logger.error('创建服务器时发生严重错误', { 错误: err.message, 堆栈: err.stack }); } } /** * 全局异常处理器(防止崩溃) */ function setupUncaughtExceptionHandler() { // 捕获未捕获的异常 process.on('uncaughtException', (err) => { logger.error('【严重】未捕获的异常', { 错误: err.message, 堆栈: err.stack }); // 避免进程无限重启 setTimeout(() => { logger.info('程序将在 3 秒后退出并尝试重启...'); process.exit(1); // 触发外部进程管理器(如 pm2)重启 }, 3000); }); // 捕获未处理的 Promise 拒绝 process.on('unhandledRejection', (reason, promise) => { logger.error('【警告】未处理的 Promise 拒绝', { 原因: reason?.message || reason, 堆栈: reason?.stack, promise }); // 不 exit,只记录 }); // SIGTERM 信号处理(优雅关闭) process.on('SIGTERM', () => { logger.info('收到 SIGTERM 信号,正在优雅关闭...'); process.exit(0); }); // SIGINT (Ctrl+C) process.on('SIGINT', () => { logger.info('收到 SIGINT 信号,正在关闭...'); process.exit(0); }); } // 启动服务器(带全局保护) setupUncaughtExceptionHandler(); // 使用 setInterval 或 PM2 等工具确保进程永生 // 例如:用 PM2 启动可自动重启 createServer(); logger.info('血透机服务已初始化,等待客户端连接...');