modbus-rs
v0.11.0
Published
High-performance Modbus TCP/RTU/ASCII client, server and gateway for Node.js, powered by Rust
Maintainers
Readme
modbus-rs
High-performance Modbus TCP/RTU/ASCII client, server, and gateway for Node.js, powered by Rust.
Features
- Async/Promise-based API - All operations return Promises
- TCP Client - Full Modbus TCP/IP client implementation
- Serial Client - Modbus RTU and ASCII over serial port
- TCP Server - Build Modbus TCP servers with JavaScript handlers
- TCP Gateway - Route requests to multiple downstream servers based on unit ID
- High Performance - Native Rust core with napi-rs bindings
- Type Safe - Full TypeScript definitions included
- Cross Platform - Pre-built binaries for Linux, macOS, and Windows
Installation
npm install modbus-rsQuick Start
TCP Client
const { AsyncTcpModbusClient } = require('modbus-rs');
async function main() {
const client = await AsyncTcpModbusClient.connect({
host: '127.0.0.1',
port: 502,
unitId: 1,
timeoutMs: 5000,
});
try {
// Read holding registers (FC03)
const registers = await client.readHoldingRegisters({
address: 0,
quantity: 10,
});
console.log('Registers:', registers);
// Write single register (FC06)
await client.writeSingleRegister({
address: 0,
value: 12345,
});
} finally {
await client.close();
}
}
main().catch(console.error);Serial RTU Client
const { AsyncSerialModbusClient } = require('modbus-rs');
async function main() {
const client = await AsyncSerialModbusClient.connectRtu({
portPath: '/dev/ttyUSB0',
unitId: 1,
baudRate: 19200,
dataBits: 8,
stopBits: 1,
parity: 'even',
});
try {
const registers = await client.readHoldingRegisters({
address: 0,
quantity: 10,
});
console.log('Registers:', registers);
} finally {
await client.close();
}
}
main().catch(console.error);TCP Server
const { AsyncTcpModbusServer } = require('modbus-rs');
const holdingRegisters = new Array(1000).fill(0);
async function main() {
const server = await AsyncTcpModbusServer.bind(
{ host: '0.0.0.0', port: 502 },
{
onReadHoldingRegisters: (req) => {
return holdingRegisters.slice(req.address, req.address + req.count);
},
onWriteSingleRegister: (req) => {
holdingRegisters[req.address] = req.value;
return true;
},
}
);
console.log('Server listening on port 502');
process.on('SIGINT', async () => {
await server.shutdown();
process.exit(0);
});
}
main().catch(console.error);TCP Gateway
const { AsyncTcpGateway } = require('modbus-rs');
async function main() {
const gateway = await AsyncTcpGateway.bind(
{ host: '0.0.0.0', port: 502 },
{
downstreams: [
{ host: '192.168.1.10', port: 502 },
{ host: '192.168.1.11', port: 502 },
],
routes: [
{ unitId: 1, channel: 0 },
{ unitId: 2, channel: 1 },
],
}
);
console.log('Gateway listening on port 502');
process.on('SIGINT', async () => {
await gateway.shutdown();
process.exit(0);
});
}
main().catch(console.error);API Reference
AsyncTcpModbusClient
connect(opts: TcpClientOptions)- Connect to a Modbus TCP serverclose()- Close the connectionreadCoils(opts)- FC01: Read CoilsreadDiscreteInputs(opts)- FC02: Read Discrete InputsreadHoldingRegisters(opts)- FC03: Read Holding RegistersreadInputRegisters(opts)- FC04: Read Input RegisterswriteSingleCoil(opts)- FC05: Write Single CoilwriteSingleRegister(opts)- FC06: Write Single RegisterwriteMultipleCoils(opts)- FC15: Write Multiple CoilswriteMultipleRegisters(opts)- FC16: Write Multiple RegistersreadWriteMultipleRegisters(opts)- FC23: Read/Write Multiple RegistersreadFileRecord(opts)- FC20: Read File RecordwriteFileRecord(opts)- FC21: Write File RecordreadFifoQueue(opts)- FC24: Read FIFO QueuereadExceptionStatus()- FC07: Read Exception Statusdiagnostics(opts)- FC08: DiagnosticsreadDeviceIdentification(opts)- FC43/14: Read Device Identification
AsyncSerialModbusClient
Same methods as AsyncTcpModbusClient, with different connection options:
connectRtu(opts: SerialClientOptions)- Connect using Modbus RTUconnectAscii(opts: SerialClientOptions)- Connect using Modbus ASCII
AsyncTcpModbusServer
bind(opts, handlers)- Create and start a TCP servershutdown()- Stop the server
AsyncTcpGateway
bind(opts, config)- Create and start a gatewayshutdown()- Stop the gateway
Error Handling
All errors are thrown as JavaScript Error objects with descriptive messages:
try {
await client.readHoldingRegisters({ address: 0, count: 10 });
} catch (err) {
if (err.message.includes('MODBUS_EXCEPTION')) {
console.error('Modbus exception:', err.message);
} else if (err.message.includes('MODBUS_TIMEOUT')) {
console.error('Request timed out');
} else {
console.error('Error:', err.message);
}
}Status & known limitations (v0.8)
The Node.js bindings are an early release. The following work is planned for v0.9 and beyond:
- Server JS handler dispatch —
AsyncTcpModbusServer.bind()accepts a handlers object today, but only the lifecycle (bind/shutdown) and write-request echo are wired up. JS handler callbacks for read requests are not yet invoked; the server returnsIllegalFunctionfor reads. The client API is fully functional — use it against any third-party Modbus server today. AsyncSerialModbusServer— not yet exposed in the JS API. Use the Rustmbus-asynccrate directly for serial servers.AbortSignalsupport on per-request methods. UsetimeoutMsat connect time as a workaround.- A separate browser/WASM npm package built on the existing
wasmfeature.
Supported platforms
Pre-built binaries are published for:
- Linux x64 (glibc), Linux arm64 (glibc)
- macOS x64, macOS arm64
- Windows x64 (MSVC)
Other targets can be built locally via cargo build -p mbus-ffi --features nodejs,full
followed by npm run build.
License
GPL-3.0-only — see LICENSE. A commercial license is available for proprietary use; contact [email protected].
