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