Serial RTU Server
The library supports serial communication using the RTU transmission mode. RTU uses a similar Modbus addressing scheme but adds a CRC to each transmitted frame.
Multi-drop communications may be implemented by having a single client communicating with multiple servers sharing the same serial channel. Broadcasts messages may be used to write requests simultaneously to all server on a channel.
Special addresses
Broadcast
When a write request is received with the broadcast unit ID (0x00
), it is automatically forwarded to all registered
handlers. No response will be returned.
Reserved addresses
Addresses 248 (0xF8
) to 255 (0xFF
) (inclusive) are reserved in the specification and should not be used. The library does not
enforce this requirement, but a warning message is generated as a reminder that it may not be interoperable.
Creating a server
To create a RTU server, first build a DeviceMap
for each unit ID to which the server will respond. The is similar to how it's done in the TCP server.
Then use the Server.CreateRtu
factory method to create the background task.
The Server.CreateRtu
method takes the following arguments:
runtime
: tokio runtime used to drive the async process. See Runtime for more details.path
: path of the serial device.- On Windows, it's generally something like
COM3
- On Linux, it's generally something like
/dev/ttyS3
. You need to have the adequate permissions to access these devices.
- On Windows, it's generally something like
serial_port_settings
: structure with various serial port settings:- Baud rate in bit per second
- Data bits. Note that Modbus should use 8 data bits.
- Stop bits
- Parity
- Flow control
port_retry_delay
: how long to wait before reopening after a failed open or port error.map
: Map of unit ids and their corresponding callback handlers.level
: Initial decoding level for the port which can be adjusted later via the returned Channel.
The task handling the port is tolerant to the hardware device being added and removed from the system as might occur with USB to serial adapters.
- Rust
- C
- C++
- Java
- C#
let handler =
SimpleHandler::new(vec![false; 10], vec![false; 10], vec![0; 10], vec![0; 10]).wrap();
// map unit ids to a handler for processing requests
let map = ServerHandlerMap::single(UnitId::new(1), handler.clone());
let server = rodbus::server::spawn_rtu_server_task(
"/dev/ttySIM1",
rodbus::SerialSettings::default(),
default_retry_strategy(),
map,
DecodeLevel::new(
AppDecodeLevel::DataValues,
FrameDecodeLevel::Payload,
PhysDecodeLevel::Data,
),
)?;
rodbus_write_handler_t write_handler = {
.write_single_coil = on_write_single_coil,
.write_single_register = on_write_single_register,
.write_multiple_coils = on_write_multiple_coils,
.write_multiple_registers = on_write_multiple_registers,
};
rodbus_device_map_t* map = rodbus_device_map_create();
rodbus_device_map_add_endpoint(map,
1, // Unit ID
write_handler, // Handler for write requests
(rodbus_database_callback_t){.callback = configure_db } // Callback for the initial state of the database
);
rodbus_server_t* server = NULL;
rodbus_device_map_t* map = build_device_map();
rodbus_decode_level_t decode_level = rodbus_decode_level_nothing();
rodbus_param_error_t err = rodbus_server_create_rtu(runtime, "/dev/ttySIM1", rodbus_serial_port_settings_init(), rodbus_retry_strategy_init(), map, decode_level, &server);
rodbus_device_map_destroy(map);
if (err) {
printf("Unable to initialize server: %s\n", rodbus_param_error_to_string(err));
return -1;
}
// check error
auto device_map = rodbus::DeviceMap();
auto init_transaction = rodbus::functional::database_callback([](rodbus::Database& db) {
for (uint16_t i = 0; i < 10; ++i)
{
db.add_coil(i, false);
db.add_discrete_input(i, false);
db.add_holding_register(i, 0);
db.add_input_register(i, 0);
}
});
device_map.add_endpoint(1, std::make_unique<WriterHandler>(), init_transaction);
auto server = rodbus::Server::create_rtu(runtime, "/dev/ttySIM1", rodbus::SerialPortSettings(), rodbus::RetryStrategy(), device_map, rodbus::DecodeLevel::nothing());
DeviceMap map = new DeviceMap();
map.addEndpoint(ubyte(1), new ExampleWriteHandler(), db -> {
for(int i = 0; i < 10; i++) {
db.addCoil(ushort(i), false);
db.addDiscreteInput(ushort(i), false);
db.addHoldingRegister(ushort(i), ushort(0));
db.addInputRegister(ushort(i), ushort(0));
}
});
Server server = Server.createRtu(runtime, "/dev/ttySIM1", new SerialPortSettings(), new RetryStrategy(), map, DecodeLevel.nothing());
var map = new DeviceMap();
map.AddEndpoint(1, new WriteHandler(), db =>
{
for (ushort i = 0; i < 10; ++i)
{
db.AddCoil(i, false);
db.AddDiscreteInput(i, false);
db.AddHoldingRegister(i, 0);
db.AddInputRegister(i, 0);
}
});
var server = Server.CreateRtu(runtime, "/dev/ttySIM1", new SerialPortSettings(), new RetryStrategy(), map, DecodeLevel.Nothing());