feat: modbus enableCRC

This commit is contained in:
Toby 2025-04-16 18:42:23 +08:00
parent 051046e891
commit d67cda3462
3 changed files with 58 additions and 20 deletions

View File

@ -1,8 +1,10 @@
{ {
"hostPort": "COM10", "hostPort": "/dev/cu.usbserial-350",
"devicePort": "COM11", "devicePort": "/dev/cu.usbserial-230",
"baudRate": 19200, "baudRate": 9600,
"dataBits": 8, "dataBits": 8,
"stopBits": 1, "stopBits": 1,
"parity": "none" "parity": "none",
"protocol": "custom",
"enableCRC": false
} }

View File

@ -5,10 +5,11 @@
"bin": "dist/index.js", "bin": "dist/index.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"dev": "ts-node src/idnex.ts", "dev": "ts-node src/index.ts",
"start": "node dist/index.js", "start": "node dist/index.js",
"exe": "nexe dist/index.js --build --resource /Users/daoyouyun/.codes/serial-proxy/nodejs/node-v20.18.0-win-x64.zip --output serial-proxy.exe", "exe": "nexe dist/index.js --build --resource /Users/daoyouyun/.codes/serial-proxy/nodejs/node-v20.18.0-win-x64.zip --output serial-proxy.exe",
"pkg": "pkg . --targets=node18-win-x64 --output=serial-proxy.exe" "pkg": "pkg . --targets=node18-win-x64 --output=serial-proxy.exe",
"pkg-arm": "pkg dist/index.js --targets=node18-win-arm64 --output=serial-proxy.exe"
}, },
"dependencies": { "dependencies": {
"serialport": "^11.0.0" "serialport": "^11.0.0"

View File

@ -10,6 +10,8 @@ interface Config {
dataBits: 8 | 5 | 6 | 7 | undefined; dataBits: 8 | 5 | 6 | 7 | undefined;
stopBits: 1 | 2 | 1.5 | undefined; stopBits: 1 | 2 | 1.5 | undefined;
parity: 'none' | 'even' | 'odd' | 'mark' | 'space'; parity: 'none' | 'even' | 'odd' | 'mark' | 'space';
protocol: 'custom' | 'modbus';
enableCRC?: boolean;
} }
function loadConfig(): Config { function loadConfig(): Config {
@ -30,7 +32,9 @@ function loadConfig(): Config {
logFile: raw.logFile || path.resolve(process.cwd(), 'serial.log'), logFile: raw.logFile || path.resolve(process.cwd(), 'serial.log'),
dataBits: raw.dataBits || 8, dataBits: raw.dataBits || 8,
stopBits: raw.stopBits || 1, stopBits: raw.stopBits || 1,
parity: raw.parity || 'none' parity: raw.parity || 'none',
protocol: raw.protocol === 'modbus' ? 'modbus' : 'custom',
enableCRC: raw.enableCRC === true
}; };
return config; return config;
@ -40,30 +44,52 @@ function loadConfig(): Config {
} }
} }
function formatTime(date: Date): string { function formatTime(date: Date): string {
const pad = (n: number) => n.toString().padStart(2, '0'); const pad = (n: number) => n.toString().padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} `
+ `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
} }
function formatLogLine(direction: string, data: Buffer): string { function formatLogLine(direction: string, data: Buffer, protocol: 'custom' | 'modbus'): string {
const timestamp = formatTime(new Date()); const timestamp = formatTime(new Date());
const hex = data.toString('hex').toUpperCase().replace(/(.{2})/g, '$1 ').trim(); const hex = data.toString('hex').toUpperCase().replace(/(.{2})/g, '$1 ').trim();
if (protocol === 'modbus') {
return `${timestamp} ${direction} HEX: ${hex}`;
} else {
const ascii = data.toString().replace(/[^\x20-\x7E]/g, '.'); const ascii = data.toString().replace(/[^\x20-\x7E]/g, '.');
return `${timestamp} ${direction} HEX: ${hex} ASCII: ${ascii}`; return `${timestamp} ${direction} HEX: ${hex} ASCII: ${ascii}`;
}
} }
function logAndPrint(stream: fs.WriteStream, direction: string, data: Buffer) { function logAndPrint(stream: fs.WriteStream, direction: string, data: Buffer, protocol: 'custom' | 'modbus') {
const line = formatLogLine(direction, data); const line = formatLogLine(direction, data, protocol);
stream.write(line + '\n'); stream.write(line + '\n');
console.log(line); console.log(line);
} }
function calculateCRC16(buffer: Buffer): Buffer {
let crc = 0xFFFF;
for (let i = 0; i < buffer.length; i++) {
crc ^= buffer[i];
for (let j = 0; j < 8; j++) {
if ((crc & 0x0001) !== 0) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
const lo = crc & 0xFF;
const hi = (crc >> 8) & 0xFF;
return Buffer.from([lo, hi]);
}
function startProxy(config: Config) { function startProxy(config: Config) {
const { hostPort, devicePort, baudRate, dataBits, stopBits, parity, logFile, protocol, enableCRC } = config;
const { hostPort, devicePort, baudRate, dataBits, stopBits, parity, logFile } = config;
const logStream = fs.createWriteStream(logFile, { flags: 'a' }); const logStream = fs.createWriteStream(logFile, { flags: 'a' });
@ -71,12 +97,19 @@ function startProxy(config: Config) {
const device = new SerialPort({ path: devicePort, baudRate, dataBits, stopBits, parity }); const device = new SerialPort({ path: devicePort, baudRate, dataBits, stopBits, parity });
host.on('data', data => { host.on('data', data => {
logAndPrint(logStream, '[主机 → 设备]', data); let outData = data;
device.write(data);
if (protocol === 'modbus' && enableCRC) {
const crc = calculateCRC16(data);
outData = Buffer.concat([data, crc]);
}
logAndPrint(logStream, '[主机 → 设备]', outData, protocol);
device.write(outData);
}); });
device.on('data', data => { device.on('data', data => {
logAndPrint(logStream, '[设备 → 主机]', data); logAndPrint(logStream, '[设备 → 主机]', data, protocol);
host.write(data); host.write(data);
}); });
@ -87,6 +120,8 @@ function startProxy(config: Config) {
${hostPort} ${hostPort}
${devicePort} ${devicePort}
${baudRate} ${baudRate}
${protocol}
CRC ${enableCRC ? '启用' : '关闭'}
${logFile}`); ${logFile}`);
} }