Skip to content

4.14 C99 Modbus

Overview

C99 Modbus has two layers: ArmHandle* represents the SDK session, and ArmModbusSlaveHandle* represents the context for one slave.

StepAPIDescription
1Arm_ConnectEstablishes the robot session.
2Arm_Modbus_GetParamReads serial parameters for the target channel as needed.
3Arm_Modbus_GetSlaveCreates a slave handle. This only creates a local context and does not verify whether the remote slave exists.
4Arm_ModbusSlave_Read* / Arm_ModbusSlave_Write*Reads or writes coils, holding registers, discrete inputs, and input registers.
5Arm_ModbusSlave_SerialSend / Arm_ModbusSlave_SerialReceiveUses serial pass-through as needed.
6Arm_ModbusSlave_DestroyDestroys the slave handle.

API Signatures

Arm_Modbus_GetSlave

c
int Arm_Modbus_GetSlave(ArmHandle* h, int channel, int32_t slaveId, int32_t masterId, ArmModbusSlaveHandle** outSlave);
ItemDescription
DescriptionCreates a Modbus slave handle.
Request parametersh : ArmHandle* , C99 session handle, usually created by Arm_Create() ; service APIs require a successful connection first
channel : int , Modbus channel enum value
slaveId : int32_t , Modbus slave ID
masterId : int32_t , master parameter ID or channel-context ID
outSlave : ArmModbusSlaveHandle** , output Modbus slave handle; the caller must destroy it after success
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_Modbus_GetParam

c
int Arm_Modbus_GetParam(ArmHandle* h, int channel, int32_t masterId, ArmSerialParams* outParams);
ItemDescription
DescriptionReads serial parameters for a Modbus channel.
Request parametersh : ArmHandle* , C99 session handle, usually created by Arm_Create() ; service APIs require a successful connection first
channel : int , Modbus channel enum value
masterId : int32_t , master parameter ID or channel-context ID
outParams : ArmSerialParams* , serial-parameter output-structure pointer
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_Modbus_SetParam

c
int Arm_Modbus_SetParam(ArmHandle* h, const ArmSerialParams* params, int32_t* outId);
ItemDescription
DescriptionSets serial parameters for a Modbus channel.
Request parametersh : ArmHandle* , C99 session handle, usually created by Arm_Create() ; service APIs require a successful connection first
params : const ArmSerialParams* , serial-parameter structure pointer
outId : int32_t* , parameter record ID output pointer
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_ModbusSlave_Destroy

c
void Arm_ModbusSlave_Destroy(ArmModbusSlaveHandle* slave);
ItemDescription
DescriptionDestroys a Modbus slave handle.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
Return valueNo return value

Arm_ModbusSlave_IsValid

c
int Arm_ModbusSlave_IsValid(ArmModbusSlaveHandle* slave);
ItemDescription
DescriptionChecks whether a Modbus slave handle is valid.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
Return value1 means the handle is valid, 0 means the handle has become invalid; when slave == NULL or an exception occurs, an error code is returned

Arm_ModbusSlave_GetParam

c
int Arm_ModbusSlave_GetParam(ArmModbusSlaveHandle* slave, ArmSerialParams* outParams);
ItemDescription
DescriptionReads serial parameters through the slave context.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
outParams : ArmSerialParams* , serial-parameter output-structure pointer
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_ModbusSlave_SetParam

c
int Arm_ModbusSlave_SetParam(ArmModbusSlaveHandle* slave, const ArmSerialParams* params, int32_t* outId);
ItemDescription
DescriptionSets serial parameters through the slave context.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
params : const ArmSerialParams* , serial-parameter structure pointer
outId : int32_t* , parameter record ID output pointer
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_ModbusSlave_ReadCoils

c
int Arm_ModbusSlave_ReadCoils(ArmModbusSlaveHandle* slave, int32_t addr, int32_t num, int32_t* outArray, size_t maxCount, size_t* outCount);
ItemDescription
DescriptionReads coils.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
addr : int32_t , start address
num : int32_t , number of values to read
outArray : int32_t* , output array allocated by the caller
maxCount : size_t , output array capacity, expressed as the maximum number of elements the caller can receive
outCount : size_t* , output-count pointer; on success, receives the actual number of elements
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions
NotesThe array output is allocated by the caller. maxCount is the capacity, and outCount returns the actual count.

Arm_ModbusSlave_WriteCoils

c
int Arm_ModbusSlave_WriteCoils(ArmModbusSlaveHandle* slave, int32_t addr, const int32_t* values, size_t count);
ItemDescription
DescriptionWrites coils.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
addr : int32_t , start address
values : const int32_t* , array of values to write
count : size_t , number of array elements
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_ModbusSlave_ReadHoldingRegs

c
int Arm_ModbusSlave_ReadHoldingRegs(ArmModbusSlaveHandle* slave, int32_t addr, int32_t num, int32_t* outArray, size_t maxCount, size_t* outCount);
ItemDescription
DescriptionReads holding registers.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
addr : int32_t , start address
num : int32_t , number of values to read
outArray : int32_t* , output array allocated by the caller
maxCount : size_t , output array capacity, expressed as the maximum number of elements the caller can receive
outCount : size_t* , output-count pointer; on success, receives the actual number of elements
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions
NotesThe array output is allocated by the caller. maxCount is the capacity, and outCount returns the actual count.

Arm_ModbusSlave_WriteHoldingRegs

c
int Arm_ModbusSlave_WriteHoldingRegs(ArmModbusSlaveHandle* slave, int32_t addr, const int32_t* values, size_t count);
ItemDescription
DescriptionWrites holding registers.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
addr : int32_t , start address
values : const int32_t* , array of values to write
count : size_t , number of array elements
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_ModbusSlave_ReadDiscreteInputs

c
int Arm_ModbusSlave_ReadDiscreteInputs(ArmModbusSlaveHandle* slave, int32_t addr, int32_t num, int32_t* outArray, size_t maxCount, size_t* outCount);
ItemDescription
DescriptionReads discrete inputs.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
addr : int32_t , start address
num : int32_t , number of values to read
outArray : int32_t* , output array allocated by the caller
maxCount : size_t , output array capacity, expressed as the maximum number of elements the caller can receive
outCount : size_t* , output-count pointer; on success, receives the actual number of elements
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions
NotesThe array output is allocated by the caller. maxCount is the capacity, and outCount returns the actual count.

Arm_ModbusSlave_ReadInputRegs

c
int Arm_ModbusSlave_ReadInputRegs(ArmModbusSlaveHandle* slave, int32_t addr, int32_t num, int32_t* outArray, size_t maxCount, size_t* outCount);
ItemDescription
DescriptionReads input registers.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
addr : int32_t , start address
num : int32_t , number of values to read
outArray : int32_t* , output array allocated by the caller
maxCount : size_t , output array capacity, expressed as the maximum number of elements the caller can receive
outCount : size_t* , output-count pointer; on success, receives the actual number of elements
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions
NotesThe array output is allocated by the caller. maxCount is the capacity, and outCount returns the actual count.

Arm_ModbusSlave_WriteInputRegs

c
int Arm_ModbusSlave_WriteInputRegs(ArmModbusSlaveHandle* slave, int32_t addr, const int32_t* values, size_t count);
ItemDescription
DescriptionWrites input registers.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
addr : int32_t , start address
values : const int32_t* , array of values to write
count : size_t , number of array elements
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_ModbusSlave_SerialSend

c
int Arm_ModbusSlave_SerialSend(ArmModbusSlaveHandle* slave, const char* msg);
ItemDescription
DescriptionSends serial pass-through data.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
msg : const char* , serial pass-through text to send
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions

Arm_ModbusSlave_SerialReceive

c
int Arm_ModbusSlave_SerialReceive(ArmModbusSlaveHandle* slave, char* outBuf, size_t bufSize);
ItemDescription
DescriptionReceives serial pass-through data.
Request parametersslave : ArmModbusSlaveHandle* , Modbus slave handle returned by Arm_Modbus_GetSlave()
outBuf : char* , output string buffer allocated by the caller
bufSize : size_t , output buffer size, including space for the trailing \0
Return valueInteger STATUS_CODE ; 0 means success, and other values should be handled according to the status-code definitions
NotesString output uses the caller-provided buffer. If the buffer is too small, the function reports that through the status code.

Common Types

ArmModbusChannel

Enum valueValueDescription
ARM_MODBUS_CHANNEL_CONTROLLER_TCP_TO_4852Controller TCP-to-485 channel
ARM_MODBUS_CHANNEL_WRIST_485_03Wrist 485_0 channel
ARM_MODBUS_CHANNEL_WRIST_485_14Wrist 485_1 channel
ARM_MODBUS_CHANNEL_CONTROLLER_4855Controller 485 channel

ArmModbusParity

Enum valueValueDescription
ARM_MODBUS_PARITY_NONE78No parity, ASCII N
ARM_MODBUS_PARITY_ODD79Odd parity, ASCII O
ARM_MODBUS_PARITY_EVEN69Even parity, ASCII E

ArmSerialParams

FieldTypeDescription
idint32_tParameter record ID
channelint32_tArmModbusChannel enum value
ipchar[128]Target IP
portint32_tPort
baudint32_tBaud rate
dataBitint32_tData bits
stopBitint32_tStop bits
parityint32_tArmModbusParity enum value
timeoutint32_tTimeout in milliseconds

Pointer and Buffer Rules

ScenarioReturn
h == NULL or slave == NULLReturns an error code
Required out pointer is NULLReturns INVALID_PARAMETER
Reading an array with outArray == NULL and outCount != NULLWrites only the count
Read-array buffer too smallReturns BUFFER_TOO_SMALL and sets outCount to 0
bufSize == 0 for Arm_ModbusSlave_SerialReceiveReturns BUFFER_TOO_SMALL
SerialReceive fails while outBuf is validWrites an empty string

Parameters and Lifetime

ItemRule
Connection prerequisiteArm_Connect() must succeed first
addrStart address must satisfy addr >= 0
Read countnum must be 1..120
Write countcount must be 1..1024
Address upper boundaddr + count - 1 or addr + num - 1 must not exceed 65535
valuesMust not be null when count > 0
Slave handleDepends on the ArmHandle* that created it and must not be reused across Arm_Disconnect()
outIdOn successful SetParam , receives the parameter record ID confirmed by the controller

Behavior conventions:

  • Arm_Modbus_GetSlave() creates a local handle; the remote slave state is reflected by later read/write results.
  • Arm_ModbusSlave_IsValid() checks whether the local handle is still bound to a valid session; remote online state is reflected by read/write results.
  • On the ARM_MODBUS_CHANNEL_CONTROLLER_TCP_TO_485 channel, Arm_Modbus_GetParam() sends masterId to the controller; other channels usually ignore masterId .
  • Serial pass-through works by channel and does not carry slaveId / masterId .
  • Read APIs return integer arrays returned by the controller; coil values are presented as controller results.
  • Write APIs validate address range and count; the business meaning of each values element is handled by the controller.

Failure Conventions

ScenarioReturn
Invalid slave handleINVALID_SESSION
Invalid channel enum or parity enumINVALID_PARAMETER
addr < 0INVALID_PARAMETER
num < 1 or num > 120INVALID_PARAMETER
count < 1 or count > 1024INVALID_PARAMETER
addr + count - 1 > 65535 or addr + num - 1 > 65535INVALID_PARAMETER
Controller returns an unexpected data structureOTHER_ERR

Minimal Example

c
#include <stdio.h>  // Provides printf for printing the Modbus query result.
#include "c_arm_api.h"  // Includes the aggregate C99 SDK header.
int main(void)  // Example program entry point.
{  // Enters the example main function.
    ArmHandle* h = Arm_Create();  // Creates a C99 session handle.
    ArmModbusSlaveHandle* slave = NULL;  // Prepares the Modbus slave handle output.
    int32_t coils[8] = {0};  // Prepares the buffer for coil values.
    size_t outCount = 0U;  // Prepares the variable for the actual read count.
    if (h == NULL) {  // Checks whether session handle creation failed.
        return 1;  // Exits when creation fails.
    }  // Ends the handle creation check.
    if (Arm_Connect(h, "10.27.1.254", NULL) != 0) {  // Connects to the controller or routed address.
        Arm_Destroy(h);  // Destroys the session handle after connection failure.
        return 1;  // Returns an error code.
    }  // Ends the connection check.
    if (Arm_Modbus_GetSlave(h, ARM_MODBUS_CHANNEL_CONTROLLER_485, 1, 0, &slave) != 0) {  // Creates a slave handle on the controller 485 channel.
        Arm_Disconnect(h);  // Disconnects the robot session after creation failure.
        Arm_Destroy(h);  // Destroys the session handle after creation failure.
        return 1;  // Returns an error code.
    }  // Ends the slave-handle creation check.
    int ret = Arm_ModbusSlave_ReadCoils(slave, 0, 2, coils, 8, &outCount);  // Reads 2 coil values from address 0.
    if (ret == 0) {  // Checks whether coil reading succeeded.
        printf("coil_count=%zu\n", outCount);  // Prints the actual read count.
    }  // Ends the coil-read check.
    Arm_ModbusSlave_Destroy(slave);  // Destroys the slave handle.
    Arm_Disconnect(h);  // Disconnects the robot session.
    Arm_Destroy(h);  // Destroys the session handle.
    return ret == 0 ? 0 : 1;  // Returns the example status according to the read result.
}  // Ends the example main function.

Scenario Examples

The following snippets assume that ArmHandle* h has already connected successfully. Setting parameters, writing registers, and serial pass-through affect field devices; execute them only after confirmation.

Query and Set Serial Parameters

c
ArmSerialParams params = {0};  // Prepares the serial-parameter output structure.
int32_t savedId = 0;  // Prepares the parameter record ID confirmed by the controller.
int getRet = Arm_Modbus_GetParam(h, ARM_MODBUS_CHANNEL_CONTROLLER_485, 0, &params);  // Queries controller 485 channel parameters.
params.channel = ARM_MODBUS_CHANNEL_CONTROLLER_485;  // Specifies the channel to set.
/* int setRet = Arm_Modbus_SetParam(h, &params, &savedId); */  // Setting serial parameters changes controller configuration; execute only after confirmation.
(void)getRet;  // Keeps the query status code in this example.
(void)savedId;  // Keeps the set-output variable in this example.

Slave Read/Write

c
int32_t values[4] = {0};  // Prepares the read output buffer.
int32_t writeValues[2] = {1, 0};  // Prepares values to write to coils.
size_t outCount = 0U;  // Prepares the read-count output.
int coilsRet = Arm_ModbusSlave_ReadCoils(slave, 0, 2, values, 4, &outCount);  // Reads coils.
int holdingRet = Arm_ModbusSlave_ReadHoldingRegs(slave, 0, 2, values, 4, &outCount);  // Reads holding registers.
int discreteRet = Arm_ModbusSlave_ReadDiscreteInputs(slave, 0, 2, values, 4, &outCount);  // Reads discrete inputs.
int inputRet = Arm_ModbusSlave_ReadInputRegs(slave, 0, 2, values, 4, &outCount);  // Reads input registers.
/* int writeCoilsRet = Arm_ModbusSlave_WriteCoils(slave, 0, writeValues, 2); */  // Writing coils changes slave state; execute only after confirmation.
(void)coilsRet;  // Keeps the coil-read status code in this example.
(void)holdingRet;  // Keeps the holding-register read status code in this example.
(void)discreteRet;  // Keeps the discrete-input read status code in this example.
(void)inputRet;  // Keeps the input-register read status code in this example.

Context Parameters and Serial Pass-through

c
ArmSerialParams params = {0};  // Prepares the serial-parameter output structure.
char recvBuf[1024] = {0};  // Prepares the serial pass-through receive buffer.
int valid = Arm_ModbusSlave_IsValid(slave);  // Checks whether the slave handle is still valid.
int getRet = Arm_ModbusSlave_GetParam(slave, &params);  // Queries serial parameters through the slave context.
/* int sendRet = Arm_ModbusSlave_SerialSend(slave, "010300000002"); */  // Serial pass-through send accesses the field bus; execute only after confirmation.
/* int recvRet = Arm_ModbusSlave_SerialReceive(slave, recvBuf, sizeof(recvBuf)); */  // Serial pass-through receive waits for field data; execute only after confirmation.
(void)valid;  // Keeps the validity-check result in this example.
(void)getRet;  // Keeps the query status code in this example.

Sample code

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;
}