modbus-rs
v0.15.3
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 (supports communicating with multiple unit IDs behind a single IP address and a serial port)
- Serial Client - Modbus RTU and ASCII over serial port
- TCP & Serial Servers - Build Modbus TCP servers, or Serial RTU and ASCII servers, using custom JavaScript handlers to respond to incoming requests
- Modbus Gateway - Deploy high-performance gateways supporting WebSockets, TCP, and Serial (RTU/ASCII) as upstream channels, and TCP/Serial (RTU/ASCII) as downstream channels (WebSocket downstream support planned for a future release), dynamically routing requests based on unit ID mapping tables
- Thread Safety & Concurrency - Rust-backed concurrent architecture ensures safe access across multiple async execution contexts
- Safety Locks - Integrated bus locking to prevent command collisions and state corruption
- Multi-drop Serial Support - Manage and communicate with multiple device unit IDs on a single physical RTU/ASCII bus
- 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
Examples
https://github.com/Raghava-Ch/modbus-rs/tree/main/mbus-ffi/nodejs/examples
TCP Client
const { AsyncTcpTransport } = require('modbus-rs');
async function main() {
const transport = await AsyncTcpTransport.connect({
host: '127.0.0.1',
port: 502,
requestTimeoutMs: 5000,
});
const client = transport.createClient({ unitId: 1 });
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 transport.close();
}
}
main().catch(console.error);Serial RTU Client
const { AsyncRtuTransport } = require('modbus-rs');
async function main() {
const transport = await AsyncRtuTransport.open({
portPath: '/dev/ttyUSB0',
baudRate: 19200,
dataBits: 8,
stopBits: 1,
parity: 'even',
});
const client = transport.createClient({ unitId: 1 });
try {
const registers = await client.readHoldingRegisters({
address: 0,
quantity: 10,
});
console.log('Registers:', registers);
} finally {
await transport.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, unitId: 1 },
{
onReadHoldingRegisters: (req) => {
return holdingRegisters.slice(req.address, req.address + req.quantity);
},
onWriteSingleRegister: (req) => {
holdingRegisters[req.address] = req.value;
},
}
);
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);Migration Guide
Detailed step-by-step migration guides are available in the Migration Guides directory.
Error Handling with Code Constants
Error code constants are now exported:
const { getModbusErrorCode, ModbusErrorCode } = require('modbus-rs');
try {
await client.readHoldingRegisters({ address: 0, quantity: 10 });
} catch (err) {
const code = getModbusErrorCode(err);
switch (code) {
case ModbusErrorCode.EXCEPTION: console.error('Modbus exception'); break;
case ModbusErrorCode.TIMEOUT: console.error('Request timed out'); break;
case ModbusErrorCode.CONNECTION_CLOSED: console.error('Disconnected'); break;
default: console.error('Unknown error:', err.message);
}
}Known Limitations
- AbortSignal: Uses
signal.onabortinstead ofsignal.addEventListener. Only one abort handler per signal object is supported. - Gateway route limit:
AsyncTcpGatewaysupports a maximum of 64 routing entries. Attempting to add more will throw atbind()time. - Gateway Downstream: WebSockets are currently not supported as a downstream channel (support is planned for a future release).
API Reference
AsyncTcpTransport
static connect(opts: TcpTransportOptions): Promise<AsyncTcpTransport>- Connect to a Modbus TCP serverclose(): Promise<void>- Close the connectionreconnect(): Promise<void>- Re-establish the connectioncreateClient(opts: CreateClientOptions): AsyncTcpModbusClient- Create a logical client instance bound to a specific unit ID (required)setRequestTimeout(ms: number): void- Set a global request timeout (in milliseconds)clearRequestTimeout(): void- Clear the global request timeoutpendingRequests: boolean- (Getter) Returns whether there are requests currently in flight
AsyncRtuTransport / AsyncAsciiTransport
static open(opts: RtuTransportOptions | AsciiTransportOptions): Promise<AsyncRtuTransport | AsyncAsciiTransport>- Open the serial portclose(): Promise<void>- Close the connectionreconnect(): Promise<void>- Re-establish the connectioncreateClient(opts: CreateClientOptions): AsyncSerialModbusClient- Create a logical client instance bound to a specific unit ID (required)setRequestTimeout(ms: number): void- Set a global request timeout (in milliseconds)clearRequestTimeout(): void- Clear the global request timeoutpendingRequests: boolean- (Getter) Returns whether there are requests currently in flight
AsyncTcpModbusClient / AsyncSerialModbusClient
These logical clients contain all the Modbus function code methods:
readCoils(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
AsyncTcpModbusServer
static bind(opts, handlers): Promise<AsyncTcpModbusServer>- Create and start a TCP servershutdown(): Promise<void>- Stop the server
AsyncSerialModbusServer
static bindRtu(opts, handlers): Promise<AsyncSerialModbusServer>- Create and start a Serial RTU serverstatic bindAscii(opts, handlers): Promise<AsyncSerialModbusServer>- Create and start a Serial ASCII servershutdown(): Promise<void>- Stop the server
AsyncTcpGateway
static bind(opts, config): Promise<AsyncTcpGateway>- Create and start a gatewayshutdown(): Promise<void>- Stop the gateway
Supported platforms
Pre-built binaries are published for:
- Linux x64 (glibc), Linux arm64 (glibc)
- macOS x64, macOS arm64
- Windows x64 (MSVC)
- WebAssembly modbus-rs-wasm
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].
