ModbusRx
ModbusRx is a modern, reactive implementation of the Modbus protocol for .NET applications. Built on the foundation of NModbus4 and leveraging Reactive Extensions (Rx.NET), it provides a powerful, observable-based API for industrial communication scenarios with comprehensive support for all major Modbus variants.
Install / Use
/learn @ChrisPulman/ModbusRxREADME
ModbusRx - A Reactive Modbus Implementation
Overview
ModbusRx is a modern, reactive implementation of the Modbus protocol for .NET applications. Built on the foundation of NModbus4 and leveraging Reactive Extensions (Rx.NET), it provides a powerful, observable-based API for industrial communication scenarios with comprehensive support for all major Modbus variants.
Key Features
- 🔧 Full Modbus Protocol Support: RTU, ASCII, TCP, and UDP variants
- ⚡ Reactive Design: Built with Rx.NET for responsive, event-driven applications
- 🏭 Master/Slave Architecture: Complete client and server implementations
- 🚀 High Performance: Optimized for speed with memory-efficient operations
- ✅ Comprehensive Testing: Extensive unit and integration test coverage
- 📊 Advanced Simulation: Built-in simulation capabilities for testing and development
- 📡 Connection Management: Automatic reconnection and health monitoring
- 🔄 Data Type Conversions: Built-in support for float, double, and custom data types
- 🛠️ SerialPortRx: Built on CP.IO.Ports for robust, Reactive serial communication
Supported Protocols & Target Frameworks
Protocols:
- ✅ Modbus RTU Master/Slave (Serial)
- ✅ Modbus ASCII Master/Slave (Serial)
- ✅ Modbus TCP Master/Slave (Ethernet)
- ✅ Modbus UDP Master/Slave (Ethernet)
- ✅ Modbus TCP/UDP Server with client aggregation
Target Frameworks:
.NET Standard 2.0(Cross-platform compatibility).NET 8(Long-term support).NET 9(Latest features).NET Framework 4.8(Legacy support)
Installation
dotnet add package ModbusRx
Or via Package Manager Console:
Install-Package ModbusRx
Quick Start Guide
1. Basic TCP Master Operations
Simple TCP Master Connection
using ModbusRx.Device;
using CP.IO.Ports;
// Create a TCP master
var client = new TcpClientRx("192.168.1.100", 502);
using var master = ModbusIpMaster.CreateIp(client);
// Read holding registers (Function Code 03)
var registers = await master.ReadHoldingRegistersAsync(
slaveAddress: 1,
startAddress: 0,
numberOfPoints: 10);
Console.WriteLine($"Read {registers.Length} registers: [{string.Join(", ", registers)}]");
// Write a single register (Function Code 06)
await master.WriteSingleRegisterAsync(
slaveAddress: 1,
registerAddress: 0,
value: 12345);
// Write multiple registers (Function Code 16)
var dataToWrite = new ushort[] { 100, 200, 300, 400, 500 };
await master.WriteMultipleRegistersAsync(
slaveAddress: 1,
startAddress: 10,
data: dataToWrite);
Advanced TCP Master with Error Handling
using ModbusRx.Device;
using CP.IO.Ports;
try
{
var client = new TcpClientRx("192.168.1.100", 502)
{
ReadTimeout = 5000, // 5 second timeout
WriteTimeout = 5000
};
using var master = ModbusIpMaster.CreateIp(client);
// Configure transport settings
master.Transport!.ReadTimeout = 5000;
master.Transport.Retries = 3;
master.Transport.WaitToRetryMilliseconds = 1000;
// Read all data types
var coils = await master.ReadCoilsAsync(1, 0, 16);
var discreteInputs = await master.ReadInputsAsync(1, 0, 16);
var holdingRegisters = await master.ReadHoldingRegistersAsync(1, 0, 10);
var inputRegisters = await master.ReadInputRegistersAsync(1, 0, 10);
Console.WriteLine($"Coils: {string.Join("", coils.Select(c => c ? "1" : "0"))}");
Console.WriteLine($"Discrete Inputs: {string.Join("", discreteInputs.Select(d => d ? "1" : "0"))}");
Console.WriteLine($"Holding Registers: [{string.Join(", ", holdingRegisters)}]");
Console.WriteLine($"Input Registers: [{string.Join(", ", inputRegisters)}]");
}
catch (ModbusException ex)
{
Console.WriteLine($"Modbus Error: {ex.Message}");
}
catch (TimeoutException ex)
{
Console.WriteLine($"Timeout Error: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"General Error: {ex.Message}");
}
2. Reactive Masters with Automatic Connection Management
TCP and UDP
using ModbusRx.Reactive;
using System.Reactive.Linq;
// Reactive TCP master
var tcp = Create.TcpIpMaster("192.168.1.100", 502);
var tcpSub = tcp
.ReadHoldingRegisters(startAddress: 0, numberOfPoints: 10, interval: 1000)
.Subscribe(result =>
{
if (result.error == null && result.data != null)
{
Console.WriteLine($"TCP: [{string.Join(", ", result.data)}]");
}
});
// Reactive UDP master
var udp = Create.UdpIpMaster("192.168.1.101", 502);
var udpSub = udp
.ReadHoldingRegisters(startAddress: 0, numberOfPoints: 10, interval: 1000)
.Subscribe(result =>
{
if (result.error == null && result.data != null)
{
Console.WriteLine($"UDP: [{string.Join(", ", result.data)}]");
}
});
Reactive Serial RTU/ASCII Masters
using ModbusRx.Reactive;
using System.IO.Ports;
using System.Reactive.Linq;
// Reactive Serial RTU master
var rtu = Create.SerialRtuMaster("COM3", 19200, 8, Parity.None, StopBits.One);
var rtuSub = rtu
.ReadHoldingRegisters(slaveAddress: 1, startAddress: 0, numberOfPoints: 10, interval: 500)
.Subscribe(result =>
{
if (result.error == null && result.data != null)
{
Console.WriteLine($"RTU: [{string.Join(", ", result.data)}]");
}
});
// Convenience overload (defaults slave 1)
var rtuQuickSub = rtu
.ReadHoldingRegisters(startAddress: 0, numberOfPoints: 5, interval: 1000)
.Subscribe();
// Reactive Serial ASCII master
var ascii = Create.SerialAsciiMaster("COM4", 9600, 7, Parity.Even, StopBits.One);
var asciiSub = ascii
.ReadCoils(slaveAddress: 1, startAddress: 0, numberOfPoints: 8, interval: 1000)
.Subscribe(result =>
{
if (result.error == null && result.data != null)
{
Console.WriteLine($"ASCII Coils: {string.Join("", result.data.Select(c => c ? "1" : "0"))}");
}
});
3. UDP Master Operations
using ModbusRx.Device;
using CP.IO.Ports;
using System.Net;
// Create UDP master
var client = new UdpClientRx();
var endPoint = new IPEndPoint(IPAddress.Parse("192.168.1.100"), 502);
client.Connect(endPoint);
using var master = ModbusIpMaster.CreateIp(client);
// UDP operations are similar to TCP
var registers = await master.ReadHoldingRegistersAsync(1, 0, 10);
Console.WriteLine($"UDP Read: [{string.Join(", ", registers)}]");
// Write coils (Function Code 15)
var coilData = new bool[] { true, false, true, true, false, false, true, false };
await master.WriteMultipleCoilsAsync(1, 0, coilData);
4. Serial RTU Master
using CP.IO.Ports;
using ModbusRx.Device;
using System.IO.Ports;
// Configure serial port
using var port = new SerialPortRx("COM1")
{
BaudRate = 9600,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
Handshake = Handshake.None
};
await port.OpenAsync();
// Create RTU master
using var master = ModbusSerialMaster.CreateRtu(port);
try
{
// Read coils
var coils = await master.ReadCoilsAsync(
slaveAddress: 1,
startAddress: 0,
numberOfPoints: 16);
Console.WriteLine($"Coils: {string.Join("", coils.Select(c => c ? "1" : "0"))}");
// Read/Write multiple registers (Function Code 23)
var writeData = new ushort[] { 1000, 2000, 3000 };
var readData = await master.ReadWriteMultipleRegistersAsync(
slaveAddress: 1,
startReadAddress: 0,
numberOfPointsToRead: 5,
startWriteAddress: 10,
writeData: writeData);
Console.WriteLine($"Read/Write Result: [{string.Join(", ", readData)}]");
}
finally
{
await port.CloseAsync();
}
5. Serial ASCII Master
using CP.IO.Ports;
using ModbusRx.Device;
using System.IO.Ports;
// Configure serial port for ASCII
using var port = new SerialPortRx("COM2")
{
BaudRate = 9600,
DataBits = 7, // ASCII typically uses 7 data bits
Parity = Parity.Even, // ASCII typically uses even parity
StopBits = StopBits.One
};
await port.OpenAsync();
// Create ASCII master
using var master = ModbusSerialMaster.CreateAscii(port);
// ASCII operations are identical to RTU in terms of function calls
var registers = await master.ReadHoldingRegistersAsync(1, 0, 10);
Console.WriteLine($"ASCII Read: [{string.Join(", ", registers)}]");
Advanced Server Implementations
1. Creating a Comprehensive Modbus Server
using ModbusRx.Device;
using ModbusRx.Data;
// Create and configure a server
using var server = new ModbusServer();
// Start multiple protocol endpoints
var tcpSubscription = server.StartTcpServer(502, unitId: 1);
var udpSubscription = server.StartUdpServer(503, unitId: 1);
// Enable simulation mode for testing
server.SimulationMode = true;
// Start the server
server.Start();
// Load initial test data
server.LoadSimulationData(
holdingRegisters: new ushort[] { 1, 2, 3, 4, 5, 100, 200, 300, 400, 500 },
inputRegisters: new ushort[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 },
coils:
