Requests
Request parameters
All requests take a RequestParam with two values:
UnitId: the device the request is for. On a serial line, it selects one of the servers sharing the line, and0x00is a broadcast. On TCP, it matters when talking to a gateway that relays requests to several devices, or to a device that hosts several units.Timeout: the time allowed to send the request and receive its response. A request whose response doesn't arrive in time fails with a response timeout. One that couldn't even be sent in time fails with an I/O error, and the library closes and reopens the connection or serial port.
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: a 16-bit start address and a count of values to read. One request can read up to
2000 coils or discrete inputs, or 125 registers. The library fails a request whose range is empty, runs past address 65535, or
exceeds that limit, without sending it; split larger reads into several requests.
The response contains exactly the values you asked for, in address order, each paired with its address. The library fails the request if the server's response doesn't match it.
The following example demonstrates reading the first 5 coils from a device:
- Rust
- C
- C++
- Java
- C#
let params = RequestParam::new(UnitId::new(UNIT_ID), REQUEST_TIMEOUT);
let result = channel
.read_coils(params, AddressRange::try_from(0, 5).unwrap())
.await;
// what a read callback prints: the command, then each value under this name
typedef struct read_context_t {
const char *cmd;
const char *name;
} read_context_t;
void on_read_bits_complete(rodbus_bit_value_iterator_t *bits, void *ctx)
{
const read_context_t *context = (const read_context_t *)ctx;
printf("%s: success\n", context->cmd);
rodbus_bit_value_t *bit = NULL;
while ((bit = rodbus_bit_value_iterator_next(bits))) {
printf("%s %u: %s\n", context->name, bit->index, bit->value ? "true" : "false");
}
}
void on_read_bits_failure(rodbus_request_error_t error, void *ctx)
{
const read_context_t *context = (const read_context_t *)ctx;
printf("%s: %s\n", context->cmd, rodbus_request_error_to_string(error));
}
rodbus_request_param_t param = rodbus_request_param_init(UNIT_ID, REQUEST_TIMEOUT);
rodbus_address_range_t range = rodbus_address_range_init(0, // start
5 // count
);
rodbus_bit_read_callback_t get_bit_read_callback(const read_context_t *context)
{
return (rodbus_bit_read_callback_t){
.on_complete = &on_read_bits_complete,
.on_failure = &on_read_bits_failure,
.on_destroy = NULL, // the context is static, nothing to free
.ctx = (void *)context, // passed to each callback
};
}
static const read_context_t context = {"rc", "coil"}; // static, so it outlives the request
rodbus_param_error_t err = rodbus_client_channel_read_coils(channel, param, range, get_bit_read_callback(&context));
// prints the command's result, then each value under the name
class BitReadCallback : public rodbus::BitReadCallback {
public:
BitReadCallback(const char *cmd, const char *name) : cmd(cmd), name(name) {}
void on_complete(rodbus::BitValueIterator &it) override
{
std::cout << std::string(cmd) + ": success\n";
while (it.next()) {
const auto value = it.get();
std::cout << std::string(name) + " " + std::to_string(value.index) + ": " + (value.value ? "true" : "false") + "\n";
}
}
void on_failure(rodbus::RequestError error) override
{
std::cout << std::string(cmd) + ": " + rodbus::to_string(error) + "\n";
}
private:
const char *cmd;
const char *name;
};
std::unique_ptr<rodbus::BitReadCallback> create_bit_read_callback(const char *cmd, const char *name)
{
return std::make_unique<BitReadCallback>(cmd, name);
}
const auto param = rodbus::RequestParam(UNIT_ID, REQUEST_TIMEOUT);
const auto range = rodbus::AddressRange(0, // start
5 // count
);
channel.read_coils(param, range, create_bit_read_callback("rc", "coil"));
// waits for the result and prints it
private static void handleBitResult(String cmd, String name, CompletionStage<List<BitValue>> result) {
try {
final List<BitValue> values = result.toCompletableFuture().join();
System.out.println(cmd + ": success");
for (BitValue value : values) {
System.out.println(name + " " + value.index.intValue() + ": " + value.value);
}
} catch (CompletionException ex) {
System.out.println(cmd + ": " + ex.getCause().getMessage());
}
}
final RequestParam param = new RequestParam(ubyte(UNIT_ID), REQUEST_TIMEOUT);
final AddressRange range = new AddressRange(ushort(0), ushort(5));
handleBitResult(cmd, "coil", channel.readCoils(param, range));
var param = new RequestParam(UnitId, RequestTimeout);
var range = new AddressRange(0, 5);
var coils = await channel.ReadCoils(param, range);
Write requests
The library supports four write requests:
| Request name | Writes |
|---|---|
write_single_coil | One coil |
write_single_register | One holding register |
write_multiple_coils | Contiguous coils |
write_multiple_registers | Contiguous holding registers |
The "multiple" requests write contiguous points of the same type in one message, up to 1968 coils or 123 registers. As with reads, the library fails an empty or larger write without sending it, and fails the request if the server's reply doesn't match it.
To write discontinuous points, make several requests. Modbus lacks a transaction mechanism, so device state may change between requests.
The following example demonstrates how to write a coil:
- Rust
- C
- C++
- Java
- C#
let params = RequestParam::new(UnitId::new(UNIT_ID), REQUEST_TIMEOUT);
let result = channel
.write_single_coil(params, Indexed::new(0, true))
.await;
void on_write_complete(rodbus_nothing_t nothing, void *ctx)
{
printf("%s: success\n", (const char *)ctx);
}
void on_write_failure(rodbus_request_error_t error, void *ctx)
{
printf("%s: %s\n", (const char *)ctx, rodbus_request_error_to_string(error));
}
rodbus_request_param_t param = rodbus_request_param_init(UNIT_ID, REQUEST_TIMEOUT);
// the command must outlive the request, like a string literal
rodbus_write_callback_t get_write_callback(const char *cmd)
{
return (rodbus_write_callback_t){
.on_complete = &on_write_complete,
.on_failure = &on_write_failure,
.on_destroy = NULL, // nothing to free
.ctx = (void *)cmd, // passed to each callback
};
}
rodbus_bit_value_t bit_value = rodbus_bit_value_init(0, true);
rodbus_param_error_t err = rodbus_client_channel_write_single_coil(channel, param, bit_value, get_write_callback("wsc"));
// prints "<cmd>: success" or "<cmd>: <error>"
class WriteCallback : public rodbus::WriteCallback {
public:
explicit WriteCallback(const char *cmd) : cmd(cmd) {}
void on_complete(rodbus::Nothing result) override
{
std::cout << std::string(cmd) + ": success\n";
}
void on_failure(rodbus::RequestError error) override
{
std::cout << std::string(cmd) + ": " + rodbus::to_string(error) + "\n";
}
private:
const char *cmd;
};
std::unique_ptr<rodbus::WriteCallback> create_write_callback(const char *cmd)
{
return std::make_unique<WriteCallback>(cmd);
}
const auto param = rodbus::RequestParam(UNIT_ID, REQUEST_TIMEOUT);
channel.write_single_coil(param, rodbus::BitValue(0, true), create_write_callback("wsc"));
// waits for the result and prints it
private static void handleWriteResult(String cmd, CompletionStage<Nothing> result) {
try {
result.toCompletableFuture().join();
System.out.println(cmd + ": success");
} catch (CompletionException ex) {
System.out.println(cmd + ": " + ex.getCause().getMessage());
}
}
final RequestParam param = new RequestParam(ubyte(UNIT_ID), REQUEST_TIMEOUT);
handleWriteResult(cmd, channel.writeSingleCoil(param, new BitValue(ushort(0), true)));
var param = new RequestParam(UnitId, RequestTimeout);
await channel.WriteSingleCoil(param, new BitValue(0, true));
The following example demonstrates writing multiple registers:
- Rust
- C
- C++
- Java
- C#
let params = RequestParam::new(UnitId::new(UNIT_ID), REQUEST_TIMEOUT);
let result = channel
.write_multiple_registers(params, WriteMultiple::from(0, vec![0xCA, 0xFE]).unwrap())
.await;
void on_write_complete(rodbus_nothing_t nothing, void *ctx)
{
printf("%s: success\n", (const char *)ctx);
}
void on_write_failure(rodbus_request_error_t error, void *ctx)
{
printf("%s: %s\n", (const char *)ctx, rodbus_request_error_to_string(error));
}
rodbus_request_param_t param = rodbus_request_param_init(UNIT_ID, REQUEST_TIMEOUT);
// the command must outlive the request, like a string literal
rodbus_write_callback_t get_write_callback(const char *cmd)
{
return (rodbus_write_callback_t){
.on_complete = &on_write_complete,
.on_failure = &on_write_failure,
.on_destroy = NULL, // nothing to free
.ctx = (void *)cmd, // passed to each callback
};
}
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);
// the request copies the list, so it can be destroyed right away
rodbus_param_error_t err = rodbus_client_channel_write_multiple_registers(channel, param, 0, register_list, get_write_callback("wmr"));
rodbus_register_list_destroy(register_list);
// prints "<cmd>: success" or "<cmd>: <error>"
class WriteCallback : public rodbus::WriteCallback {
public:
explicit WriteCallback(const char *cmd) : cmd(cmd) {}
void on_complete(rodbus::Nothing result) override
{
std::cout << std::string(cmd) + ": success\n";
}
void on_failure(rodbus::RequestError error) override
{
std::cout << std::string(cmd) + ": " + rodbus::to_string(error) + "\n";
}
private:
const char *cmd;
};
std::unique_ptr<rodbus::WriteCallback> create_write_callback(const char *cmd)
{
return std::make_unique<WriteCallback>(cmd);
}
const auto param = rodbus::RequestParam(UNIT_ID, REQUEST_TIMEOUT);
channel.write_multiple_registers(param, 0, std::vector<uint16_t>{0xCA, 0xFE}, create_write_callback("wmr"));
// waits for the result and prints it
private static void handleWriteResult(String cmd, CompletionStage<Nothing> result) {
try {
result.toCompletableFuture().join();
System.out.println(cmd + ": success");
} catch (CompletionException ex) {
System.out.println(cmd + ": " + ex.getCause().getMessage());
}
}
final RequestParam param = new RequestParam(ubyte(UNIT_ID), REQUEST_TIMEOUT);
handleWriteResult(cmd, channel.writeMultipleRegisters(param, ushort(0), Arrays.asList(ushort(0xCA), ushort(0xFE))));
var param = new RequestParam(UnitId, RequestTimeout);
await channel.WriteMultipleRegisters(param, 0, new List<ushort>() { 0xCA, 0xFE });