3.14 ModbusClient Master Capability
Overview
Arm::modbus corresponds to the robot acting as a Modbus master. It provides serial parameter management, slave session creation, register read/write, and serial passthrough capabilities. The current C++17 public capability is split into two layers:
ModbusClientinterface module: handlesGetSlave/GetParam/SetParammodbus::Slavesession: handles concrete reads/writes, serial send/receive, and per-session parameter retrieval
Recommended Call Path
Arm arm;
STATUS_CODE connectRet = arm.Connect("10.27.1.254", "");
if (connectRet != STATUS_CODE::OK) {
return;
}
auto [slave, slaveRet] = arm.modbusClient.GetSlave(
modbus::MODBUS_CHANNEL::CONTROLLER_485, 1,
0
);
if (slaveRet != STATUS_CODE::OK || !slave.IsValid()) {
return;
}
auto [values, readRet] = slave.ReadCoils(0, 2);
if (readRet != STATUS_CODE::OK) {
return;
}Interface Overview
ModbusClient Interface Module
GetSlave(channel, slaveId, masterId) -> std::pair<modbus::Slave, STATUS_CODE>
GetParam(channel, masterId = 1) -> std::pair<modbus::SerialParams, STATUS_CODE>
SetParam(const modbus::SerialParams& params) -> std::pair<int32_t, STATUS_CODE>| Method | Input | Output | Key Behavior |
|---|---|---|---|
GetSlave | Channel, slave ID, master ID | std::pair<modbus::Slave, STATUS_CODE> | Creates a local session; subsequent reads/writes access the controller |
GetParam | Channel, master ID | std::pair<modbus::SerialParams, STATUS_CODE> | For TCP_TO_485 , queries by masterId |
SetParam | Serial parameters | std::pair<int32_t, STATUS_CODE> | Returns the parameter record id confirmed by the controller |
modbus_client::Slave Session
IsValid() const -> bool
ReadCoils(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
WriteCoils(addr, values) -> STATUS_CODE
ReadHoldingRegs(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
WriteHoldingRegs(addr, values) -> STATUS_CODE
ReadDiscreteInputs(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
ReadInputRegs(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
WriteInputRegs(addr, values) -> STATUS_CODE
GetParam() -> std::pair<modbus::SerialParams, STATUS_CODE>
SetParam(params) -> std::pair<int32_t, STATUS_CODE>
SerialSend(msg) -> STATUS_CODE
SerialReceive() -> std::pair<std::string, STATUS_CODE>| Method | Input | Output | Key Behavior |
|---|---|---|---|
IsValid | None | bool | Checks only whether the session is still bound to a valid Arm |
ReadCoils | Start address, count | std::pair<std::vector<int32_t>, STATUS_CODE> | Locally validates 1..120 and address range |
WriteCoils | Start address, value list | STATUS_CODE | Locally validates write count 1..1024 |
ReadHoldingRegs | Start address, count | std::pair<std::vector<int32_t>, STATUS_CODE> | Same behavior as ReadCoils |
WriteHoldingRegs | Start address, value list | STATUS_CODE | Same behavior as WriteCoils |
ReadDiscreteInputs | Start address, count | std::pair<std::vector<int32_t>, STATUS_CODE> | Same behavior as ReadCoils |
ReadInputRegs | Start address, count | std::pair<std::vector<int32_t>, STATUS_CODE> | Same behavior as ReadCoils |
WriteInputRegs | Start address, value list | STATUS_CODE | Same behavior as WriteCoils |
GetParam | None | std::pair<modbus::SerialParams, STATUS_CODE> | Uses the channel / master context stored in the current Slave |
SetParam | Serial parameters | std::pair<int32_t, STATUS_CODE> | Calls the set interface through the current session |
SerialSend | Text serial message | STATUS_CODE | Uses the serial passthrough protocol for the channel |
SerialReceive | None | std::pair<std::string, STATUS_CODE> | Returns the raw string received through passthrough |
Prerequisites and Lifecycle
| Item | Rule |
|---|---|
| Connection prerequisite | Arm::Connect() must succeed first; otherwise the ModbusClient interface module returns NOT_CONNECTED |
GetSlave | Creates a local Slave ; the remote slave state is reflected by later read/write results |
| Session invalidation | After Arm::Disconnect() , all existing modbus::Slave sessions become invalid |
IsValid | Checks only whether the current session is still bound to a valid Arm ; remote online state is reflected by read/write results |
| Bus configuration conflict | The caller must confirm whether the controller's bus configuration conflicts with the requested use |
Parameter Validation and Failure Semantics
| Scenario | Return |
|---|---|
modbus::Slave session is invalid | INVALID_SESSION |
num < 1 or num > 120 | INVALID_PARAMETER |
addr + num - 1 > 65535 | INVALID_PARAMETER |
values.size() < 1 or values.size() > 1024 | INVALID_PARAMETER |
addr + values.size() - 1 > 65535 | INVALID_PARAMETER |
| Controller returns an unexpected JSON structure | OTHER_ERR |
Additional notes:
- The
std::vector<int32_t>returned by read APIs is the raw integer array returned by the controller. Coil values are presented as returned by the controller. - Write APIs validate address range and count; element semantics are handled by the controller.
SetParam()/GetParam()pass baud rate, port, and other business parameters to the controller for validity checks.
Detailed Semantics
GetSlave
Signature
std::pair<modbus_client::Slave, STATUS_CODE> GetSlave(
modbus_client::MODBUS_CHANNEL channel,
int32_t slaveId,
int32_t masterId
);| Input | Type | Description |
|---|---|---|
channel | modbus::MODBUS_CHANNEL | Channel enum |
slaveId | int32_t | Slave ID, recorded directly into the session at creation |
masterId | int32_t | Master ID, carried unchanged in later read/write requests |
| Output | Description |
|---|---|
modbus::Slave | Returns a usable session on success; returns a default empty session on failure |
Constraints and Behavior
GetSlave()creates a local session; remote slave state is reflected by later read/write results.slaveId/masterIdare passed according to controller semantics.- A successfully returned
Slavedepends on the currentArmlifecycle and cannot be reused acrossDisconnect().
Call Example
auto [slave, ret] = arm.modbusClient.GetSlave(
modbus_client::MODBUS_CHANNEL::CONTROLLER_485,
7,
0
);
if (ret != STATUS_CODE::OK || !slave.IsValid()) {
return;
}GetParam
Signature
std::pair<modbus_client::SerialParams, STATUS_CODE> GetParam(
modbus_client::MODBUS_CHANNEL channel,
int32_t masterId = 1
);| Input | Type | Description |
|---|---|---|
channel | modbus::MODBUS_CHANNEL | Channel to query |
masterId | int32_t | Meaningful only for CONTROLLER_TCP_TO_485 ; default 1 |
| Output | Description |
|---|---|
modbus::SerialParams | Returns parameters on success; returns the default value on failure |
STATUS_CODE | OK / NOT_CONNECTED / other controller error codes |
| Constraints and Behavior |
- For
CONTROLLER_TCP_TO_485, the SDK sendsmasterIdto the controller. - For other serial channels, the SDK sends the channel enum value to the controller and ignores
masterId. - This is the existing peer protocol convention, not a new SDK-defined rule.
Call Example
auto [params, ret] = arm.modbusClient.GetParam(
modbus_client::MODBUS_CHANNEL::CONTROLLER_TCP_TO_485,
12
);
if (ret == STATUS_CODE::OK) {
std::cout << "ip=" << params.ip << "\n";
}SetParam
Signature
std::pair<int32_t, STATUS_CODE> SetParam(const modbus_client::SerialParams& params);| Input | Type | Description |
|---|---|---|
params | modbus_client::SerialParams | Serial parameter object |
| Output | Description |
|---|---|
int32_t | On success, returns the parameter record id confirmed by the controller; on failure, returns 0 |
STATUS_CODE | OK / NOT_CONNECTED / other controller error codes |
Constraints and Behavior
channel,ip,port,baud,dataBit,stopBit,parity, andtimeoutare serialized and sent directly to the controller.
Call Example
modbus_client::SerialParams params;
params.channel = modbus_client::MODBUS_CHANNEL::CONTROLLER_485;
params.ip = "10.27.1.99";
params.port = 1502;
auto [id, ret] = arm.modbusClient.SetParam(params);if (ret == STATUS_CODE::OK) {
std::cout << "saved_param_id=" << id << "\n";
}ReadCoils / ReadHoldingRegs / ReadDiscreteInputs / ReadInputRegs
These four APIs share the same local validation rules and return semantics.
Signature Pattern
std::pair<std::vector<int32_t>, STATUS_CODE> ReadXxx(
int32_t addr,
int32_t num
);| Input | Description |
|---|---|
addr | Start address, requires addr >= 0 |
num | Read count, requires 1 <= num <= 120 |
| Output | Description |
|---|---|
std::vector<int32_t> | Returns an integer array on success; returns an empty array on failure |
STATUS_CODE | OK / INVALID_SESSION / INVALID_PARAMETER / other controller error codes |
Constraints and Behavior
addr + num - 1must not exceed65535.- If
Slaveis invalid,INVALID_SESSIONis returned directly. - If the controller returns a value that is not an integer array, the SDK returns
OTHER_ERR.
Call Example
auto [coils, ret] = slave.ReadCoils(10, 3);
if (ret == STATUS_CODE::OK) {
std::cout << "coils=" << coils.size() << "\n";
}WriteCoils / WriteHoldingRegs / WriteInputRegs
These write APIs differ only in the target Modbus area; all other rules are the same.
Signature Pattern
STATUS_CODE WriteXxx(int32_t addr, const std::vector<int32_t>& values);| Input | Description |
|---|---|
addr | Start address, requires addr >= 0 |
values | Values to write, count must be in 1..1024 |
| Output | Description |
|---|---|
STATUS_CODE | OK / INVALID_SESSION / INVALID_PARAMETER / other controller error codes |
Constraints and Behavior
addr + values.size() - 1must not exceed65535.- The SDK validates address and count. The controller handles the business semantics of each element.
Call Example
STATUS_CODE ret = slave.WriteHoldingRegs(20, {11, 12});
if (ret != STATUS_CODE::OK) {
return;
}
// STATUS_CODE inputRet = slave.WriteInputRegs(20, {11, 12});Slave::GetParam / Slave::SetParam
These two APIs reuse the semantics of Modbus::GetParam / SetParam . The only difference is that the context comes from the current Slave session:
Slave::GetParam()uses thechannel / masterIdrecorded when the session was created.Slave::SetParam(params)sends the request through the current valid session.
If the Slave has become invalid, both return INVALID_SESSION .
SerialSend / SerialReceive
Signature
STATUS_CODE SerialSend(const std::string& msg);
std::pair<std::string, STATUS_CODE> SerialReceive();| API | Input | Output | Key Behavior |
|---|---|---|---|
SerialSend | Raw string | STATUS_CODE | Sends through the current channel , without slaveId / masterId |
SerialReceive | None | std::pair<std::string, STATUS_CODE> | Returns the raw string on success, and an empty string on failure |
Constraints and Behavior
- The passthrough protocol works by channel, so the
slaveIdstored inSlavehas no effect on serial passthrough. SerialReceive()requires the controller'sdatafield to be a string on success; otherwise it returnsOTHER_ERR.
Common Types
modbus_client::MODBUS_CHANNEL
| Enum Value | Value | Description |
|---|---|---|
CONTROLLER_TCP_TO_485 | 2 | Controller TCP-to-485 channel |
WRIST_485_0 | 3 | Wrist 485_0 channel |
WRIST_485_1 | 4 | Wrist 485_1 channel |
CONTROLLER_485 | 5 | Controller 485 channel |
modbus_client::MODBUS_PARITY
| Enum Value | Value | Description |
|---|---|---|
NONE | 78 | ASCII N |
ODD | 79 | ASCII O |
EVEN | 69 | ASCII E |
modbus_client::SerialParams
| Field | Type | Default | Description |
|---|---|---|---|
id | int32_t | 1 | Parameter record ID |
channel | modbus_client::MODBUS_CHANNEL | CONTROLLER_TCP_TO_485 | Channel |
ip | std::string | Empty string | Target IP |
port | int32_t | 502 | Port |
baud | int32_t | 9600 | Baud rate |
dataBit | int32_t | 8 | Data bits |
stopBit | int32_t | 1 | Stop bit |
parity | modbus::MODBUS_PARITY | NONE | Parity |
Examples
C++17 Minimal Example
#include <iostream> // Standard output stream for printing Modbus query results
#include "arm_api.h" // Arm entry point; after connection, Modbus APIs are accessed through arm.modbusClient
int main()
{
Arm arm;
STATUS_CODE connectRet = arm.Connect("10.27.1.254", "");
if (connectRet != STATUS_CODE::OK) {
return 1;
}
auto [params, getRet] = arm.modbusClient.GetParam(
modbus::MODBUS_CHANNEL::CONTROLLER_485,
0
);
if (getRet == STATUS_CODE::OK) {
std::cout << "modbus_ip=" << params.ip << "\n";
}
auto [slave, slaveRet] = arm.modbusClient.GetSlave(
modbus::MODBUS_CHANNEL::CONTROLLER_485,
1,
0
);
if (slaveRet != STATUS_CODE::OK || !slave.IsValid()) {
return 1;
}
auto [coils, readRet] = slave.ReadCoils(0, 2);
if (readRet != STATUS_CODE::OK) {
return 1;
}
std::cout << "coil_count=" << coils.size() << "\n";
return 0;
}C++17 Scenario Examples
The snippets below cover Modbus parameter APIs, slave sessions, register reads/writes, and serial passthrough. They assume the arm object from the minimal example is already connected. Writing registers, changing serial parameters, and serial passthrough access field devices; run them only after confirming the site.
Query and Set Serial Parameters
auto [facadeParams, facadeGetRet] = arm.modbusClient.GetParam(
modbus::MODBUS_CHANNEL::CONTROLLER_485,
0
);
modbus::SerialParams params = facadeParams;
params.channel = modbus::MODBUS_CHANNEL::CONTROLLER_485;
// auto [savedId, facadeSetRet] = arm.modbusClient.SetParam(params);Create a Slave Session and Query Parameters Through the Session
auto [slave, slaveRet] = arm.modbusClient.GetSlave(
modbus::MODBUS_CHANNEL::CONTROLLER_485,
1,
0
);
if (slaveRet != STATUS_CODE::OK || !slave.IsValid()) {
return;
}
auto [slaveParams, slaveGetRet] = slave.GetParam();
// auto [slaveSavedId, slaveSetRet] = slave.SetParam(slaveParams);Read and Write Modbus Data Areas
auto [coils, coilsRet] = slave.ReadCoils(0, 2);
auto [holdingRegs, holdingRet] = slave.ReadHoldingRegs(0, 2);
auto [discreteInputs, discreteRet] = slave.ReadDiscreteInputs(0, 2);
auto [inputRegs, inputRet] = slave.ReadInputRegs(0, 2);
if (coilsRet == STATUS_CODE::OK || holdingRet == STATUS_CODE::OK || discreteRet == STATUS_CODE::OK || inputRet == STATUS_CODE::OK) {
std::cout << "coil_count=" << coils.size() << " holding_count=" << holdingRegs.size() << "\n";
}
// STATUS_CODE writeCoilsRet = slave.WriteCoils(0, {1, 0});
// STATUS_CODE writeHoldingRet = slave.WriteHoldingRegs(0, {100, 200});
// STATUS_CODE writeInputRet = slave.WriteInputRegs(0, {100, 200});Serial Passthrough
// STATUS_CODE serialSendRet = slave.SerialSend("010300000002");
// auto [serialText, serialReceiveRet] = slave.SerialReceive();Example code:
#include "query_master_params/run.h"
#include "operate_slave/run.h"
#include "set_master_params/run.h"
int main(void)
{
// [ZH] 默认只调用一个门面方法;如需体验其他接口,请把下一行替换成下面任意一行。
// [EN] The main function calls only one facade by default. Replace the next line with any line below to try other APIs.
return RunModbusBasicQueryMasterParams();
// return RunModbusBasicOperateSlave();
// return RunModbusBasicSetMasterParams();
}#include <stdio.h>
#include <string.h>
extern "C" {
#include "c_arm_api.h"
}
int main(void)
{
// [ZH] 本示例直接在源码中写死连接地址,不解析命令行参数。
// [EN] This example hard-codes the connection addresses in the source code and does not parse command-line arguments.
// [ZH] 创建并连接 SDK 句柄。
// [EN] Create the SDK handle and connect to the robot.
ArmHandle* handle = Arm_Create();
if (handle == NULL) {
printf("[c99_modbus] 创建句柄失败 / Failed to create the handle\n");
return 1;
}
int ret = Arm_Connect(handle, "10.27.1.2", "10.27.1.102");
if (ret != 0) {
printf("[c99_modbus] 连接失败 / Connect failed, 状态码 / Status code: %d\n", ret);
Arm_Destroy(handle);
return 1;
}
printf("[c99_modbus] 机器人连接成功 / Robot connected successfully\n");
// [ZH] 准备主站串口参数并读取当前值。
// [EN] Prepare master serial params and read the current values.
ArmSerialParams masterParams = {0};
int32_t paramId = 0;
snprintf(masterParams.ip, sizeof(masterParams.ip), "127.0.0.1");
masterParams.channel = ARM_MODBUS_CHANNEL_CONTROLLER_485;
masterParams.id = 1;
masterParams.port = 502;
masterParams.baud = 9600;
masterParams.dataBit = 8;
masterParams.stopBit = 1;
masterParams.parity = ARM_MODBUS_PARITY_NONE;
masterParams.timeout = 1000;
ret = Arm_Modbus_GetParam(handle, ARM_MODBUS_CHANNEL_CONTROLLER_485, 0, &masterParams);
printf("[c99_modbus] GetParam 状态码 / GetParam status code: %d, channel=%d, baud=%d\n", ret, masterParams.channel, masterParams.baud);
ret = Arm_Modbus_SetParam(handle, &masterParams, ¶mId);
printf("[c99_modbus] SetParam 状态码 / SetParam status code: %d, id=%d\n", ret, paramId);
// [ZH] 获取从站句柄并顺序执行全部从站接口。
// [EN] Acquire the slave handle and execute all slave APIs in sequence.
ArmModbusSlaveHandle* slave = NULL;
ret = Arm_Modbus_GetSlave(handle, ARM_MODBUS_CHANNEL_CONTROLLER_485, 1, 0, &slave);
printf("[c99_modbus] GetSlave 状态码 / GetSlave status code: %d\n", ret);
if (slave != NULL) {
int valid = Arm_ModbusSlave_IsValid(slave);
printf("[c99_modbus] Slave_IsValid / Slave_IsValid: %d\n", valid);
int32_t values[8] = {0};
size_t valueCount = 0U;
ret = Arm_ModbusSlave_ReadCoils(slave, 0, 2, values, 8U, &valueCount);
printf("[c99_modbus] ReadCoils 状态码 / ReadCoils status code: %d, count=%zu\n", ret, valueCount);
int32_t writeCoils[2] = {1, 0};
ret = Arm_ModbusSlave_WriteCoils(slave, 0, writeCoils, 2U);
printf("[c99_modbus] WriteCoils 状态码 / WriteCoils status code: %d\n", ret);
ret = Arm_ModbusSlave_ReadHoldingRegs(slave, 0, 2, values, 8U, &valueCount);
printf("[c99_modbus] ReadHoldingRegs 状态码 / ReadHoldingRegs status code: %d, count=%zu\n", ret, valueCount);
int32_t holdingRegs[2] = {1, 2};
ret = Arm_ModbusSlave_WriteHoldingRegs(slave, 0, holdingRegs, 2U);
printf("[c99_modbus] WriteHoldingRegs 状态码 / WriteHoldingRegs status code: %d\n", ret);
ret = Arm_ModbusSlave_ReadDiscreteInputs(slave, 0, 2, values, 8U, &valueCount);
printf("[c99_modbus] ReadDiscreteInputs 状态码 / ReadDiscreteInputs status code: %d, count=%zu\n", ret, valueCount);
ret = Arm_ModbusSlave_ReadInputRegs(slave, 0, 2, values, 8U, &valueCount);
printf("[c99_modbus] ReadInputRegs 状态码 / ReadInputRegs status code: %d, count=%zu\n", ret, valueCount);
int32_t inputRegs[2] = {3, 4};
ret = Arm_ModbusSlave_WriteInputRegs(slave, 0, inputRegs, 2U);
printf("[c99_modbus] WriteInputRegs 状态码 / WriteInputRegs status code: %d\n", ret);
ret = Arm_ModbusSlave_SetParam(slave, &masterParams, ¶mId);
printf("[c99_modbus] Slave_SetParam 状态码 / Slave_SetParam status code: %d, id=%d\n", ret, paramId);
ret = Arm_ModbusSlave_GetParam(slave, &masterParams);
printf("[c99_modbus] Slave_GetParam 状态码 / Slave_GetParam status code: %d, baud=%d\n", ret, masterParams.baud);
ret = Arm_ModbusSlave_SerialSend(slave, "hello from c99 example");
printf("[c99_modbus] SerialSend 状态码 / SerialSend status code: %d\n", ret);
char recvBuf[256] = {0};
ret = Arm_ModbusSlave_SerialReceive(slave, recvBuf, sizeof(recvBuf));
printf("[c99_modbus] SerialReceive 状态码 / SerialReceive status code: %d, 内容 / Text: %s\n", ret, recvBuf);
Arm_ModbusSlave_Destroy(slave);
printf("[c99_modbus] 从站句柄已释放 / Slave handle destroyed\n");
}
// [ZH] 断开连接并销毁句柄。
// [EN] Disconnect and destroy the handle.
Arm_Disconnect(handle);
Arm_Destroy(handle);
printf("[c99_modbus] 示例结束 / Example finished\n");
return 0;
}