Skip to content

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:

  1. ModbusClient interface module: handles GetSlave / GetParam / SetParam
  2. modbus::Slave session: handles concrete reads/writes, serial send/receive, and per-session parameter retrieval
cpp
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

cpp
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>
MethodInputOutputKey Behavior
GetSlaveChannel, slave ID, master IDstd::pair<modbus::Slave, STATUS_CODE>Creates a local session; subsequent reads/writes access the controller
GetParamChannel, master IDstd::pair<modbus::SerialParams, STATUS_CODE>For TCP_TO_485 , queries by masterId
SetParamSerial parametersstd::pair<int32_t, STATUS_CODE>Returns the parameter record id confirmed by the controller

modbus_client::Slave Session

cpp
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>
MethodInputOutputKey Behavior
IsValidNoneboolChecks only whether the session is still bound to a valid Arm
ReadCoilsStart address, countstd::pair<std::vector<int32_t>, STATUS_CODE>Locally validates 1..120 and address range
WriteCoilsStart address, value listSTATUS_CODELocally validates write count 1..1024
ReadHoldingRegsStart address, countstd::pair<std::vector<int32_t>, STATUS_CODE>Same behavior as ReadCoils
WriteHoldingRegsStart address, value listSTATUS_CODESame behavior as WriteCoils
ReadDiscreteInputsStart address, countstd::pair<std::vector<int32_t>, STATUS_CODE>Same behavior as ReadCoils
ReadInputRegsStart address, countstd::pair<std::vector<int32_t>, STATUS_CODE>Same behavior as ReadCoils
WriteInputRegsStart address, value listSTATUS_CODESame behavior as WriteCoils
GetParamNonestd::pair<modbus::SerialParams, STATUS_CODE>Uses the channel / master context stored in the current Slave
SetParamSerial parametersstd::pair<int32_t, STATUS_CODE>Calls the set interface through the current session
SerialSendText serial messageSTATUS_CODEUses the serial passthrough protocol for the channel
SerialReceiveNonestd::pair<std::string, STATUS_CODE>Returns the raw string received through passthrough

Prerequisites and Lifecycle

ItemRule
Connection prerequisiteArm::Connect() must succeed first; otherwise the ModbusClient interface module returns NOT_CONNECTED
GetSlaveCreates a local Slave ; the remote slave state is reflected by later read/write results
Session invalidationAfter Arm::Disconnect() , all existing modbus::Slave sessions become invalid
IsValidChecks only whether the current session is still bound to a valid Arm ; remote online state is reflected by read/write results
Bus configuration conflictThe caller must confirm whether the controller's bus configuration conflicts with the requested use

Parameter Validation and Failure Semantics

ScenarioReturn
modbus::Slave session is invalidINVALID_SESSION
num < 1 or num > 120INVALID_PARAMETER
addr + num - 1 > 65535INVALID_PARAMETER
values.size() < 1 or values.size() > 1024INVALID_PARAMETER
addr + values.size() - 1 > 65535INVALID_PARAMETER
Controller returns an unexpected JSON structureOTHER_ERR

Additional notes:

  1. 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.
  2. Write APIs validate address range and count; element semantics are handled by the controller.
  3. SetParam() / GetParam() pass baud rate, port, and other business parameters to the controller for validity checks.

Detailed Semantics

GetSlave

Signature

cpp
std::pair<modbus_client::Slave, STATUS_CODE> GetSlave(
    modbus_client::MODBUS_CHANNEL channel,
    int32_t slaveId,
    int32_t masterId
);
InputTypeDescription
channelmodbus::MODBUS_CHANNELChannel enum
slaveIdint32_tSlave ID, recorded directly into the session at creation
masterIdint32_tMaster ID, carried unchanged in later read/write requests
OutputDescription
modbus::SlaveReturns 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 / masterId are passed according to controller semantics.
  • A successfully returned Slave depends on the current Arm lifecycle and cannot be reused across Disconnect() .

Call Example

cpp
auto [slave, ret] = arm.modbusClient.GetSlave(
    modbus_client::MODBUS_CHANNEL::CONTROLLER_485,
    7,
    0
);
if (ret != STATUS_CODE::OK || !slave.IsValid()) {
    return;
}

GetParam

Signature

cpp
std::pair<modbus_client::SerialParams, STATUS_CODE> GetParam(
    modbus_client::MODBUS_CHANNEL channel,
    int32_t masterId = 1
);
InputTypeDescription
channelmodbus::MODBUS_CHANNELChannel to query
masterIdint32_tMeaningful only for CONTROLLER_TCP_TO_485 ; default 1
OutputDescription
modbus::SerialParamsReturns parameters on success; returns the default value on failure
STATUS_CODEOK / NOT_CONNECTED / other controller error codes
Constraints and Behavior
  • For CONTROLLER_TCP_TO_485 , the SDK sends masterId to 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

cpp
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

cpp
std::pair<int32_t, STATUS_CODE> SetParam(const modbus_client::SerialParams& params);
InputTypeDescription
paramsmodbus_client::SerialParamsSerial parameter object
OutputDescription
int32_tOn success, returns the parameter record id confirmed by the controller; on failure, returns 0
STATUS_CODEOK / NOT_CONNECTED / other controller error codes

Constraints and Behavior

  • channel , ip , port , baud , dataBit , stopBit , parity , and timeout are serialized and sent directly to the controller.

Call Example

cpp
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

cpp
std::pair<std::vector<int32_t>, STATUS_CODE> ReadXxx(
    int32_t addr,
    int32_t num
);
InputDescription
addrStart address, requires addr >= 0
numRead count, requires 1 <= num <= 120
OutputDescription
std::vector<int32_t>Returns an integer array on success; returns an empty array on failure
STATUS_CODEOK / INVALID_SESSION / INVALID_PARAMETER / other controller error codes

Constraints and Behavior

  • addr + num - 1 must not exceed 65535 .
  • If Slave is invalid, INVALID_SESSION is returned directly.
  • If the controller returns a value that is not an integer array, the SDK returns OTHER_ERR .

Call Example

cpp
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

cpp
STATUS_CODE WriteXxx(int32_t addr, const std::vector<int32_t>& values);
InputDescription
addrStart address, requires addr >= 0
valuesValues to write, count must be in 1..1024
OutputDescription
STATUS_CODEOK / INVALID_SESSION / INVALID_PARAMETER / other controller error codes

Constraints and Behavior

  • addr + values.size() - 1 must not exceed 65535 .
  • The SDK validates address and count. The controller handles the business semantics of each element.

Call Example

cpp
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 the channel / masterId recorded 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

cpp
STATUS_CODE SerialSend(const std::string& msg);
std::pair<std::string, STATUS_CODE> SerialReceive();
APIInputOutputKey Behavior
SerialSendRaw stringSTATUS_CODESends through the current channel , without slaveId / masterId
SerialReceiveNonestd::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 slaveId stored in Slave has no effect on serial passthrough.
  • SerialReceive() requires the controller's data field to be a string on success; otherwise it returns OTHER_ERR .

Common Types

modbus_client::MODBUS_CHANNEL

Enum ValueValueDescription
CONTROLLER_TCP_TO_4852Controller TCP-to-485 channel
WRIST_485_03Wrist 485_0 channel
WRIST_485_14Wrist 485_1 channel
CONTROLLER_4855Controller 485 channel

modbus_client::MODBUS_PARITY

Enum ValueValueDescription
NONE78ASCII N
ODD79ASCII O
EVEN69ASCII E

modbus_client::SerialParams

FieldTypeDefaultDescription
idint32_t1Parameter record ID
channelmodbus_client::MODBUS_CHANNELCONTROLLER_TCP_TO_485Channel
ipstd::stringEmpty stringTarget IP
portint32_t502Port
baudint32_t9600Baud rate
dataBitint32_t8Data bits
stopBitint32_t1Stop bit
paritymodbus::MODBUS_PARITYNONEParity

Examples

C++17 Minimal Example

cpp
#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

cpp
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

cpp
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

cpp
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

cpp
// STATUS_CODE serialSendRet = slave.SerialSend("010300000002");
// auto [serialText, serialReceiveRet] = slave.SerialReceive();

Example code:

cpp17/modbus_basic/src/main.cpp
cpp
#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();
}
c99/modbus_basic/src/main.cpp
cpp
#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, &paramId);
    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, &paramId);
        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;
}