From 747a86bb94006aaca721cfc0c0ce7061643a9ea6 Mon Sep 17 00:00:00 2001
From: chenyc <501753378@qq.com>
Date: 星期四, 20 八月 2026 12:19:35 +0800
Subject: [PATCH] gx

---
 tcp-simulator.js |  139 ++++++++++++++++++++++++++--------------------
 1 files changed, 79 insertions(+), 60 deletions(-)

diff --git a/tcp-simulator.js b/tcp-simulator.js
index 2d9d641..e9ebb1e 100644
--- a/tcp-simulator.js
+++ b/tcp-simulator.js
@@ -21,6 +21,7 @@
   },
 };
 
+// 模拟器支持三种方式:文件回放、单独血压、文件加血压混发。
 function parseArgs(argv) {
   const options = {
     host: '127.0.0.1',
@@ -31,6 +32,7 @@
     repeat: 0,
     localAddress: '',
     mode: 'file',
+    includeBloodPressure: false,
     bpSystolic: 120,
     bpDiastolic: 80,
     bpPulse: 89,
@@ -66,6 +68,8 @@
     } else if (arg === '--mode' && value) {
       options.mode = value.trim().toLowerCase();
       index += 1;
+    } else if (arg === '--with-blood-pressure' || arg === '--with-bp') {
+      options.includeBloodPressure = true;
     } else if (arg === '--blood-pressure' || arg === '--bp') {
       options.mode = 'blood-pressure';
     } else if (arg === '--bp-systolic' && value) {
@@ -91,30 +95,33 @@
 }
 
 function printHelp() {
-  console.log(`JHM TCP simulator
+  console.log(`JHM TCP 模拟客户端
 
-Usage:
+用法:
   node tcp-simulator.js --host 127.0.0.1 --port 9000
   node tcp-simulator.js --scenario jihua20260414 --host 127.0.0.1 --port 9000 --repeat 1
+  node tcp-simulator.js --host 127.0.0.1 --port 9000 --with-bp
   node tcp-simulator.js --bp --host 127.0.0.1 --port 9000 --bp-systolic 120 --bp-diastolic 80 --bp-pulse 89
   node tcp-simulator.js --bp --host 127.0.0.1 --port 9000 --bp-time "2026-04-15 09:30"
 
-Options:
-  --host <ip>             target TCP server, default 127.0.0.1
-  --port <number>         target TCP port, default 9000
-  --interval <ms>         interval between frames, default 1000ms
-  --frames <path>         hex frame file, default ./数据模拟.md
-  --scenario <name>       built-in scenario: ${Object.keys(BUILTIN_SCENARIOS).join(', ')}
-  --repeat <number>       send rounds, 0 means infinite loop, default 0
-  --local-address <ip>    bind local IP to simulate source device IP
+参数:
+  --host <ip>             目标 TCP 服务地址,默认 127.0.0.1
+  --port <number>         目标 TCP 服务端口,默认 9000
+  --interval <ms>         报文发送间隔,默认 1000ms
+  --frames <path>         十六进制报文文件,默认 ./数据模拟.md
+  --scenario <name>       内置场景:${Object.keys(BUILTIN_SCENARIOS).join(', ')}
+  --repeat <number>       发送轮数,0 表示无限循环,默认 0
+  --local-address <ip>    绑定本地 IP,用于模拟设备来源 IP
   --mode <file|blood-pressure>
-  --blood-pressure, --bp  shorthand for --mode blood-pressure
-  --bp-systolic <n>       blood pressure systolic value, default 120
-  --bp-diastolic <n>      blood pressure diastolic value, default 80
-  --bp-pulse <n>          blood pressure pulse value, default 89
-  --bp-time "<text>"      custom time, format YYYY-MM-DD HH:mm, default current local time
-  --bp-no-time            send zeroed time bytes
-  --help, -h              show help
+  --with-blood-pressure, --with-bp
+                          在每轮文件或场景回放后追加 1 条血压报文
+  --blood-pressure, --bp  等同于 --mode blood-pressure
+  --bp-systolic <n>       收缩压,默认 120
+  --bp-diastolic <n>      舒张压,默认 80
+  --bp-pulse <n>          脉搏,默认 89
+  --bp-time "<text>"      自定义时间,格式 YYYY-MM-DD HH:mm,默认当前本地时间
+  --bp-no-time            发送全 0 时间字节
+  --help, -h              显示帮助
 `);
 }
 
@@ -126,7 +133,7 @@
   const scenarioConfig = BUILTIN_SCENARIOS[options.scenario];
 
   if (!scenarioConfig) {
-    throw new Error(`unknown scenario: ${options.scenario}`);
+    throw new Error(`未知场景: ${options.scenario}`);
   }
 
   return {
@@ -137,45 +144,49 @@
   };
 }
 
+function validateBloodPressureOptions(options) {
+  for (const [name, value] of [
+    ['bp-systolic', options.bpSystolic],
+    ['bp-diastolic', options.bpDiastolic],
+    ['bp-pulse', options.bpPulse],
+  ]) {
+    if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
+      throw new Error(`${name} 必须是 0 到 65535 之间的整数`);
+    }
+  }
+
+  if (options.bpIncludeTime && options.bpTime) {
+    parseBloodPressureTimeText(options.bpTime);
+  }
+}
+
 function validateOptions(options) {
   if (!options.host) {
-    throw new Error('host is required');
+    throw new Error('必须指定 host');
   }
 
   if (!Number.isInteger(options.port) || options.port <= 0) {
-    throw new Error('port must be a positive integer');
+    throw new Error('port 必须是正整数');
   }
 
   if (!Number.isInteger(options.intervalMs) || options.intervalMs <= 0) {
-    throw new Error('interval must be a positive integer in ms');
+    throw new Error('interval 必须是大于 0 的毫秒整数');
   }
 
   if (!Number.isInteger(options.repeat) || options.repeat < 0) {
-    throw new Error('repeat must be an integer >= 0');
+    throw new Error('repeat 必须是大于等于 0 的整数');
   }
 
   if (!['file', 'blood-pressure'].includes(options.mode)) {
-    throw new Error(`unsupported mode: ${options.mode}`);
+    throw new Error(`不支持的模式: ${options.mode}`);
   }
 
   if (options.mode === 'file' && !fs.existsSync(options.framesFile)) {
-    throw new Error(`frames file not found: ${options.framesFile}`);
+    throw new Error(`未找到报文文件: ${options.framesFile}`);
   }
 
-  if (options.mode === 'blood-pressure') {
-    for (const [name, value] of [
-      ['bp-systolic', options.bpSystolic],
-      ['bp-diastolic', options.bpDiastolic],
-      ['bp-pulse', options.bpPulse],
-    ]) {
-      if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
-        throw new Error(`${name} must be an integer between 0 and 65535`);
-      }
-    }
-
-    if (options.bpIncludeTime && options.bpTime) {
-      parseBloodPressureTimeText(options.bpTime);
-    }
+  if (options.mode === 'blood-pressure' || options.includeBloodPressure) {
+    validateBloodPressureOptions(options);
   }
 }
 
@@ -193,7 +204,7 @@
 
   return Buffer.from(hexPairs.map((pair) => {
     if (pair.length > 2) {
-      throw new Error(`invalid byte: ${pair}`);
+      throw new Error(`非法字节: ${pair}`);
     }
 
     return Number.parseInt(pair, 16);
@@ -264,7 +275,7 @@
     };
   }
 
-  throw new Error('no sendable hex frames found in frames file');
+  throw new Error('报文文件中没有找到可发送的十六进制报文');
 }
 
 function analyzeFrames(frames, scenarioConfig) {
@@ -329,7 +340,7 @@
   const match = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})$/.exec(value);
 
   if (!match) {
-    throw new Error(`invalid --bp-time format: ${value}. Expected YYYY-MM-DD HH:mm`);
+    throw new Error(`--bp-time 格式不正确: ${value},应为 YYYY-MM-DD HH:mm`);
   }
 
   const [, year, month, day, hour, minute] = match;
@@ -393,10 +404,13 @@
   }
 
   const loadResult = loadFrames(options.framesFile);
-  const analysis = analyzeFrames(loadResult.frames, options.scenarioConfig);
+  const frames = options.includeBloodPressure
+    ? [...loadResult.frames, buildBloodPressureFrame(options)]
+    : loadResult.frames;
+  const analysis = analyzeFrames(frames, options.scenarioConfig);
 
   return {
-    frames: loadResult.frames,
+    frames,
     analysis,
     loadMode: loadResult.mode,
     scenarioConfig: options.scenarioConfig,
@@ -444,7 +458,7 @@
   function sendNextFrame() {
     const frame = prepared.frames[frameIndex];
     socket.write(frame.buffer);
-    console.log(`[SIM] sent round=${round + 1} frame=${frameIndex + 1} -> ${frame.raw}`);
+    console.log(`[SIM] 已发送 round=${round + 1} frame=${frameIndex + 1} -> ${frame.raw}`);
 
     frameIndex += 1;
 
@@ -453,43 +467,48 @@
       round += 1;
 
       if (options.repeat > 0 && round >= options.repeat) {
-        stop(`[SIM] finished ${options.repeat} round(s)`);
+        stop(`[SIM] 已完成 ${options.repeat} 轮发送`);
       }
     }
   }
 
   socket.on('connect', () => {
-    console.log(`[SIM] connected to ${options.host}:${options.port}`);
+    console.log(`[SIM] 已连接到 ${options.host}:${options.port}`);
 
     if (options.localAddress) {
-      console.log(`[SIM] local bind address ${options.localAddress}`);
+      console.log(`[SIM] 本地绑定地址 ${options.localAddress}`);
     }
 
     if (prepared.scenarioConfig) {
-      console.log(`[SIM] scenario ${options.scenario} -> ${prepared.scenarioConfig.name}`);
-      console.log(`[SIM] scenario description: ${prepared.scenarioConfig.description}`);
+      console.log(`[SIM] 场景 ${options.scenario} -> ${prepared.scenarioConfig.name}`);
+      console.log(`[SIM] 场景说明: ${prepared.scenarioConfig.description}`);
     } else if (options.mode === 'blood-pressure') {
-      console.log(`[SIM] mode blood-pressure systolic=${options.bpSystolic} diastolic=${options.bpDiastolic} pulse=${options.bpPulse}`);
-      console.log(`[SIM] blood-pressure time ${options.bpIncludeTime ? (options.bpTime || 'current local time') : 'disabled / zero bytes'}`);
+      console.log(`[SIM] 当前模式为血压报文 systolic=${options.bpSystolic} diastolic=${options.bpDiastolic} pulse=${options.bpPulse}`);
+      console.log(`[SIM] 血压时间 ${options.bpIncludeTime ? (options.bpTime || '当前本地时间') : '已禁用 / 发送全 0 字节'}`);
     } else {
-      console.log(`[SIM] loaded frames file ${path.basename(options.framesFile)}`);
+      console.log(`[SIM] 已加载报文文件 ${path.basename(options.framesFile)}`);
     }
 
-    console.log(`[SIM] data mode ${prepared.loadMode} frames=${prepared.analysis.totalFrames} publishable=${prepared.analysis.publishableFrames} unsupported=${prepared.analysis.unsupportedFrames}`);
+    if (options.mode === 'file' && options.includeBloodPressure) {
+      console.log(`[SIM] 每轮追加血压报文 systolic=${options.bpSystolic} diastolic=${options.bpDiastolic} pulse=${options.bpPulse}`);
+      console.log(`[SIM] 追加血压时间 ${options.bpIncludeTime ? (options.bpTime || '当前本地时间') : '已禁用 / 发送全 0 字节'}`);
+    }
+
+    console.log(`[SIM] 数据模式 ${prepared.loadMode} frames=${prepared.analysis.totalFrames} publishable=${prepared.analysis.publishableFrames} unsupported=${prepared.analysis.unsupportedFrames}`);
 
     if (prepared.analysis.fullCycles > 0 || prepared.analysis.extraFrames > 0) {
-      console.log(`[SIM] scenario cycles fullCycles=${prepared.analysis.fullCycles} extraFrames=${prepared.analysis.extraFrames}`);
+      console.log(`[SIM] 场景轮次统计 fullCycles=${prepared.analysis.fullCycles} extraFrames=${prepared.analysis.extraFrames}`);
     }
 
     if (prepared.analysis.stateTransitions.length > 0) {
       const transitionText = prepared.analysis.stateTransitions
         .map((item) => `frame#${item.frameIndex}:o=${item.value}`)
         .join(' -> ');
-      console.log(`[SIM] state transitions ${transitionText}`);
+      console.log(`[SIM] 状态变化 ${transitionText}`);
     }
 
     if (Object.keys(prepared.analysis.snapshot).length > 0) {
-      console.log(`[SIM] snapshot ${formatSnapshot(prepared.analysis.snapshot)}`);
+      console.log(`[SIM] 指标快照 ${formatSnapshot(prepared.analysis.snapshot)}`);
     }
 
     sendNextFrame();
@@ -497,23 +516,23 @@
   });
 
   socket.on('error', (error) => {
-    stop(`[SIM] connection error: ${error.message}`);
+    stop(`[SIM] 连接异常: ${error.message}`);
     process.exitCode = 1;
   });
 
   socket.on('close', () => {
     if (!stopped) {
-      stop('[SIM] connection closed');
+      stop('[SIM] 连接已关闭');
     }
   });
 
   process.on('SIGINT', () => {
-    stop('[SIM] received SIGINT, stopping');
+    stop('[SIM] 收到 SIGINT,停止发送');
     process.exit(0);
   });
 
   process.on('SIGTERM', () => {
-    stop('[SIM] received SIGTERM, stopping');
+    stop('[SIM] 收到 SIGTERM,停止发送');
     process.exit(0);
   });
 }

--
Gitblit v1.8.0