Requests
Request parameters
All requests take a RequestParam
structure with the following fields:
UnitId
: A single Modbus channel can communicate with many devices. The 8-bit unit ID specifies the device for which the request is intended. On a serial line, this is used to implement a multi-drop scheme. On TCP/IP, it is used when communicating with a gateway that relays the requests to multiple devices, or a single device that supports multiple virtual sub-devices.Timeout
: This is the maximum time to wait for a response before reporting an error.
A common pattern is to create a RequestParam
for each device you communicate with
and reuse it on every request.
Read requests
The library supports four read requests:
Request name | Response type |
---|---|
read_coils | Single bits |
read_discrete_inputs | Single bits |
read_holding_registers | 16-bit registers |
read_input_registers | 16-bit registers |
Each read request takes an AddressRange
parameter to specify what values are requested.
It has a 16-bit start
index and a 16-bit count
value.
The sum of the start
and count
fields cannot exceed 65,536. The library will automatically fail such invalid requests
returning an error.
The asynchronous response will contain an iterator with the received values. If an error occurs, the iterator will be empty.
The following example demonstrates reading the first 5 coils from a device:
- Rust
- C
- C++
- Java
- C#
let params = RequestParam::new(UnitId::new(1), Duration::from_secs(1));
let result = channel
.read_coils(params, AddressRange::try_from(0, 5).unwrap())
.await;
void on_read_bits_complete(rodbus_bit_value_iterator_t *bits, void *ctx)
{
printf("success!\n");
rodbus_bit_value_t *bit = NULL;
while ((bit = rodbus_bit_value_iterator_next(bits))) {
printf("index: %d value: %d\n", bit->index, bit->value);
}
}
void on_read_bits_failure(rodbus_request_error_t error, void *ctx)
{
printf("error: %s\n", rodbus_request_error_to_string(error));
}
rodbus_request_param_t param = rodbus_request_param_init(1, // Unit ID
1000 // Timeout in ms
);
rodbus_address_range_t range = rodbus_address_range_init(0, // start
5 // count
);
rodbus_bit_read_callback_t bit_callback = {
.on_complete = &on_read_bits_complete,
.on_failure = &on_read_bits_failure,
};
rodbus_client_channel_read_coils(channel, param, range, bit_callback);
class BitReadCallback : public rodbus::BitReadCallback
{
void on_complete(rodbus::BitValueIterator& it) override
{
std::cout << "success!" << std::endl;
while (it.next()) {
const auto value = it.get();
std::cout << "index: " << value.index << "value: " << value.value << std::endl;
}
}
void on_failure(rodbus::RequestError err) override
{
std::cout << "error: " << rodbus::to_string(err) << std::endl;
}
};
const auto param = rodbus::RequestParam(1, // Unit ID
std::chrono::seconds(1) // Timeout
);
const auto range = rodbus::AddressRange(0, // start
5 // count
);
channel.read_coils(param, range, std::make_unique<BitReadCallback>());
private static void handleBitResult(List<BitValue> bits, Throwable ex) {
if (ex == null) {
System.out.println("success!");
for(BitValue bit : bits) {
System.out.println("index: " + bit.index + " value: " + bit.value);
}
} else {
System.out.println("error: " + ex.getMessage());
}
}
final RequestParam param = new RequestParam(ubyte(1), Duration.ofSeconds(1));
final AddressRange range = new AddressRange(ushort(0), ushort(5));
channel.readCoils(param, range).whenComplete(ClientExample::handleBitResult);
var param = new RequestParam(1, TimeSpan.FromSeconds(1));
var range = new AddressRange(0, 5);
try
{
var bits = await channel.ReadCoils(param, range);
Console.WriteLine("success!");
foreach (var bit in bits)
{
Console.WriteLine($"index: {bit.Index} value: {bit.Value}");
}
}
catch (Exception ex)
{
Console.WriteLine($"error: {ex}");
}
Write requests
The library supports four write requests:
write_single_coil
write_single_register
write_multiple_coils
write_multiple_registers
The registers that are written using the write_single_register
and write_multiple_registers
are holding registers.
The "single" requests, as their name suggests, writes a single point at a time. The "multiple" requests write multiple contiguous points of the same type in a single message.
In order to write a set of discontinuous points, you must perform multiple requests. Modbus does not have a transaction mechanism. Therefore, you must keep in mind that the device state might change in between requests.
The following example demonstrates how to write a coil:
- Rust
- C
- C++
- Java
- C#
let params = RequestParam::new(UnitId::new(1), Duration::from_secs(1));
let result = channel
.write_single_coil(params, Indexed::new(0, true))
.await;
void on_write_complete(rodbus_nothing_t nothing, void *ctx)
{
printf("success!\n");
}
void on_write_failure(rodbus_request_error_t error, void *ctx)
{
printf("error: %s\n", rodbus_request_error_to_string(error));
}
rodbus_request_param_t param = rodbus_request_param_init(1, // Unit ID
1000 // Timeout in ms
);
rodbus_write_callback_t write_callback = {
.on_complete = on_write_complete,
.on_failure = on_write_failure,
};
rodbus_bit_value_t bit_value = rodbus_bit_value_init(0, true);
rodbus_client_channel_write_single_coil(channel, param, bit_value, write_callback);
class WriteCallback : public rodbus::WriteCallback
{
void on_complete(rodbus::Nothing result) override
{
std::cout << "success!" << std::endl;
}
void on_failure(rodbus::RequestError err) override
{
std::cout << "error: " << rodbus::to_string(err) << std::endl;
}
};
const auto param = rodbus::RequestParam(1, // Unit ID
std::chrono::seconds(1) // Timeout
);
const auto register_value = rodbus::RegisterValue(0, 76);
channel.write_single_register(param, register_value, std::make_unique<WriteCallback>());
private static void handleWriteResult(Nothing nothing, Throwable ex) {
if (ex == null) {
System.out.println("success!");
} else {
System.out.println("error: " + ex.getMessage());
}
}
final RequestParam param = new RequestParam(ubyte(1), Duration.ofSeconds(1));
channel.writeSingleCoil(param, new BitValue(ushort(0), true)).whenComplete(ClientExample::handleWriteResult);
var param = new RequestParam(1, TimeSpan.FromSeconds(1));
try
{
await channel.WriteSingleCoil(param, new BitValue(0, true));
Console.WriteLine("success!");
}
catch (Exception ex)
{
Console.WriteLine($"error: {ex}");
}
The following example demonstrates writing the multiple registers:
- Rust
- C
- C++
- Java
- C#
let params = RequestParam::new(UnitId::new(1), Duration::from_secs(1));
let result = channel
.write_multiple_registers(
params,
WriteMultiple::from(0, vec![0xCA, 0xFE]).unwrap(),
)
.await;
print_write_result(result);
void on_write_complete(rodbus_nothing_t nothing, void *ctx)
{
printf("success!\n");
}
void on_write_failure(rodbus_request_error_t error, void *ctx)
{
printf("error: %s\n", rodbus_request_error_to_string(error));
}
rodbus_request_param_t param = rodbus_request_param_init(1, // Unit ID
1000 // Timeout in ms
);
rodbus_write_callback_t write_callback = {
.on_complete = on_write_complete,
.on_failure = on_write_failure,
};
rodbus_register_list_t *register_list = rodbus_register_list_create(2);
rodbus_register_list_add(register_list, 0xCA);
rodbus_register_list_add(register_list, 0xFE);
// send the request
rodbus_client_channel_write_multiple_registers(channel, param, 0, register_list, write_callback);
// destroy the register list
rodbus_register_list_destroy(register_list);
class WriteCallback : public rodbus::WriteCallback
{
void on_complete(rodbus::Nothing result) override
{
std::cout << "success!" << std::endl;
}
void on_failure(rodbus::RequestError err) override
{
std::cout << "error: " << rodbus::to_string(err) << std::endl;
}
};
const auto param = rodbus::RequestParam(1, // Unit ID
std::chrono::seconds(1) // Timeout
);
auto register_list = std::vector<uint16_t>();
register_list.emplace_back(0xCA);
register_list.emplace_back(0xFE);
// send the request
channel.write_multiple_registers(param, 0, register_list, std::make_unique<WriteCallback>());
private static void handleWriteResult(Nothing nothing, Throwable ex) {
if (ex == null) {
System.out.println("success!");
} else {
System.out.println("error: " + ex.getMessage());
}
}
final RequestParam param = new RequestParam(ubyte(1), Duration.ofSeconds(1));
channel.writeMultipleRegisters(param, ushort(0), Arrays.asList(ushort(0xCA), ushort(0xFE))).whenComplete(ClientExample::handleWriteResult);
var param = new RequestParam(1, TimeSpan.FromSeconds(1));
try
{
await channel.WriteMultipleRegisters(param, 0, new List<ushort>() { 0xCA, 0xFE });
Console.WriteLine("success!");
}
catch (Exception ex)
{
Console.WriteLine($"error: {ex}");
}