2 I/O Module Register Read/Write Examples

In an actual Modbus TCP network, it is necessary to read and write the internal registers of the I/O modules on the ED-EIOBRG-MT via software to control their I/O channels. The following sections detail the I/O address mapping rules and register read/write examples for this module.

2.1 I/O Module Address Mapping Rules

DI and DO modules support both Bit mapping and Word mapping register access methods. AI and AO modules only support Word mapping register access.

  • Description of Bit Mapping Method
Module TypeFunction CodeOffset Start AddressBit Address RangeData Length RangeOffset Addr + Length
DI (Input Bit)0x020x000~10231~1024≤1024(R)
DO (Input Bit)0x05
0x15
0x01(R)
0x00(R/W)0~10231~1024≤1024(R/W)
  • Description of Word Mapping Method
Module TypeFunction CodeOffset Start AddressRegister Address RangeData Length RangeOffset Addr + Length
DI (Input Word)0x03Hex: 0x5000
Decimal: 20480
0x5000~0x507F
20480~20607
1~128≤20608(R)
DO (Output Word)0x16
0x03(R)
Hex: 0x3000(W)
Decimal: 12288(W)
Hex: 0x4000(R)
Decimal: 16384(R)
0x3000~0x307F(W)
12288~12415(W)
0x4000~0x407F(R)
16384~16511(R)
1~128≤12416(W)
≤16512(R)
AI (Input Word)0x03
0x04
0x000~5111~512≤512(R)
AO (Output Word)0x06
0x16
0x03(R)
Hex: 0x00(W)
Decimal: 0(W)
Hex: 0x2000(R)
Decimal: 8192(R)
0x00~0x1FF(W)
0~511(W)
0x2000~0x21FF(R)
8192~8703(R)
1~512≤512(W)
≤8704(R)

TIP

  • DI and AI modules only support read functionality.
  • DO and AO modules support both read and write functionality.

2.2 Example Code for Reading and Writing I/O Module Registers

Example code is provided in both Python and C++. They are introduced separately below.

2.2.1 Python Example Code

Introduces Python example code for DI, DO, AI, and AO modules.

2.2.1.1 DI Module (Bit Mapping Method)

from pymodbus.client import ModbusTcpClient
import time

# Modbus coupler communication parameter configuration
COUPLER_IP = "192.168.0.50"  # Modbus coupler IP address
COUPLER_PORT = 502           # Default communication port of Modbus TCP
UNIT_ID = 1                  # Modbus slave address (slave id)
DI_ADDRESS = 0               # Starting offset address of DI (decimal)
DI_COUNT = 8                 # Number of DI points to be read

def read_modbus_di_bit(client: ModbusTcpClient, unit_id: int, address: int, count: int):
    """
    Modbus TCP read DI module by bit (Function Code 02)
    :param client: Pre-established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: Starting offset address of DI
    :param count: Number of DI bits to read
    :return: DI bits list on success, None on failure
    """
    # Read DI module status
    res = client.read_discrete_inputs(address=address, count=count, slave=unit_id)
    if res.isError():
        print(f"Failed to read DI module: {res}")
        return None
    return res.bits[:count] 

if __name__ == "__main__":
    # Establish persistent TCP connection
    client = ModbusTcpClient(COUPLER_IP, port=COUPLER_PORT)
    if not client.connect():
        print("Modbus connection failed")
        exit()
    print("Persistent connection established, cyclically reading DI, press Ctrl+C to stop")
    try:
        while True:
            # Call function to read DI by bit
            di_data = read_modbus_di_bit(client, UNIT_ID, DI_ADDRESS, DI_COUNT)
            if di_data is not None:
                print("DI Module Status List: ", di_data)
            time.sleep(1)   # Refresh every 1 second
    except KeyboardInterrupt:
        print("\nStop signal received by program")
    finally:
        # Close connection when program exits
        client.close()
        print("Modbus connection closed")

2.2.1.2 DI Module (Word Mapping Method)

from pymodbus.client import ModbusTcpClient
import time
# Modbus coupler communication parameter configuration
COUPLER_IP = "192.168.0.50"  # Modbus coupler IP address
COUPLER_PORT = 502           # Default communication port of Modbus TCP
UNIT_ID = 1                  # Modbus slave address (slave id)
DI_ADDRESS = 20480           # Starting offset address of DI (decimal)
DI_COUNT = 1                 # Number of DI registers to be read

# Read DI
def read_modbus_di(client: ModbusTcpClient, unit_id: int, address: int, count: int):
    """
    Modbus TCP read DI module (Function Code 03)
    :param client: Pre-established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: Starting offset address of DI
    :param count: Number of DI registers to read
    :return: DI register list on success, None on failure
    """
    # Read DI module status
    res = client.read_holding_registers(address=address, count=count, slave=unit_id)
    if res.isError():
        print(f"Failed to read DI module: {res}")
        return None
    return res.registers

if __name__ == "__main__":
    # Establish persistent TCP connection
    client = ModbusTcpClient(COUPLER_IP, port=COUPLER_PORT)
    if not client.connect():
        print("Modbus connection failed!")
        exit()
    try:
        while True:
            # Call function to read DI by word
            ret = read_modbus_di(client, UNIT_ID, DI_ADDRESS, DI_COUNT)
            if ret:
               print("DI Module Values: ",ret)   # Convert output value to binary for corresponding port bits
            time.sleep(1)  # Refresh every 1 second
    except KeyboardInterrupt:
        print("Program terminated")
    finally:
        # Close connection when program exits
        client.close()
        print("Modbus connection closed")

2.2.1.3 DO Module (Bit Mapping Method)

from pymodbus.client import ModbusTcpClient
import time

# Modbus coupler communication parameter configuration
COUPLER_IP = "192.168.0.50"  # Modbus coupler IP address
COUPLER_PORT = 502           # Default communication port of Modbus TCP
UNIT_ID = 1                  # Modbus slave address (slave id)
DO_ADDRESS = 0               # Starting offset address of DO (decimal)
DO_COUNT = 16                # Number of DO points to be read
DO_DATA = [0, 1, 1, 0, 1, 0, 1, 1]  # DO point values to be written in batch

def read_modbus_do_bit(client: ModbusTcpClient, unit_id: int, address: int, count: int):
    """
    Modbus TCP read DO module by bit (Function Code 01 Read Coils)
    :param client: Pre-established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: Starting offset address of DO
    :param count: Number of DO bits to read
    :return: DO bits list on success, None on failure
    """
    # Read DO output status
    res = client.read_coils(address=address, count=count, slave=unit_id)
    if res.isError():
        print(f"Failed to read DO module: {res}")
        return None
    return res.bits[:count]

# Write single DO point
def write_modbus_do_Single(client: ModbusTcpClient, unit_id: int, address: int, value: bool):
    """
    Modbus TCP write DO module (Function Code 05)
    :param client: Established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: DO starting offset address
    :param value: 1=ON, 0=OFF
    :return: True on success, None on failure
    """
    # Write single DO point
    res = client.write_coil(address=address, value=value, slave=unit_id)
    if res.isError():
        print(f"Failed to write DO module: {res}")
        return None
    return True

# Batch write DO points
def write_modbus_do_Batch(client: ModbusTcpClient, unit_id: int, address: int, coil_list: list[bool]):
    """
    Modbus TCP write DO module (Function Code 15)
    :param client: Established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: DO starting offset address
    :param coil_list: Boolean status list
    :return: True on success, None on failure
    """
    # Batch write DO points
    res = client.write_coils(address=address, values=coil_list, slave=unit_id)
    if res.isError():
        print(f"Failed to write DO module: {res}")
        return None
    return True

if __name__ == "__main__":
    # Establish persistent TCP connection
    client = ModbusTcpClient(COUPLER_IP, port=COUPLER_PORT)
    if not client.connect():
        print("Modbus connection failed")
        exit()
    print("Persistent connection established,  press Ctrl+C to stop program")
    try:
        while True:
            # Call function to read DO by bit
            # do_data = read_modbus_do_bit(client, UNIT_ID, DO_ADDRESS, DO_COUNT)
            # if do_data is not None:
            #     print("DO Module Status List: ", do_data)

            # Batch write
            # ret = write_modbus_do_Batch(client, UNIT_ID, DO_ADDRESS, DO_DATA)
            # if ret:
            #    print("Batch DO point write operation completed")

            # Single write
            # ret = write_modbus_do_Single(client, UNIT_ID, DO_ADDRESS, value=1)
            # if ret:
            #    print("Single DO point write operation completed")
            
            time.sleep(1)   # Refresh every 1 second
    except KeyboardInterrupt:
        print("\nStop signal received by program")
    finally:
        # Close connection when program exits
        client.close()
        print("Modbus connection closed")

2.2.1.4 DO Module (Word Mapping Method)

from pymodbus.client import ModbusTcpClient
from pymodbus.pdu.register_message import WriteSingleRegisterResponse
import struct
import time
# Modbus coupler communication parameter configuration
COUPLER_IP = "192.168.0.50"  # Modbus coupler IP address
COUPLER_PORT = 502           # Default communication port of Modbus TCP
UNIT_ID = 1                  # Modbus slave address (slave id)
DO_ADDRESS_READ = 16384      # Read the starting offset address of DO (decimal)
DO_COUNT = 1                 # Number of DO registers to be read
DO_ADDRESS_WRITE = 12288     # Write the starting offset address of DO (decimal)
DO_DATA = [0xFFFF]           # DO values written by word (hexadecimal)

# Write DO by word
def write_modbus_do_word(client: ModbusTcpClient, unit_id: int, address: int, reg_list: list[int]):
    """
    Modbus TCP write DO module (Function Code 16)
    :param client: Established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: DO starting offset address
    :param reg_list: Register value list to write
    :return: True on success, None on failure
    """
    # Write DO by word
    res = client.write_registers(address=address, values=reg_list, slave=unit_id)
    if res.isError():
        print(f"Failed to write DO module: {res}")
        return None
    return True

# Read DO by word
def read_modbus_do_word(client: ModbusTcpClient, unit_id: int, address: int, count: int):
    """
    Modbus TCP read DO module (Function Code 03)
    :param client: Pre-established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: Starting offset address of DO
    :param count: Number of DO registers to read
    :return: DO register list on success, None on failure
    """
    # Read DO module status
    res = client.read_holding_registers(address=address, count=count, slave=unit_id)
    if res.isError():
        print(f"Failed to read DO module: {res}")
        return None
    return res.registers

if __name__ == "__main__":
    # Establish persistent TCP connection
    client = ModbusTcpClient(COUPLER_IP, port=COUPLER_PORT)
    if not client.connect():
        print("Modbus connection failed!")
        exit()
    try:
        while True:
            # Call function to write DO by word
             ret = write_modbus_do_word(client, UNIT_ID, DO_ADDRESS_WRITE, DO_DATA)
             if ret:
                print("DO write operation by word completed")
            # Call function to read DO by word
            # ret = read_modbus_do_word(client, UNIT_ID, DO_ADDRESS_READ, DO_COUNT)
            # if ret:
            #    print("DO Module Values: ",ret)       # Convert output value to binary for corresponding port bits
                
                time.sleep(1)  # Refresh every 1 second to maintain DO status
    except KeyboardInterrupt:
        print("Program terminated")
    finally:
        # Close connection when program exits
        client.close()
        print("Modbus connection closed")

2.2.1.5 AI Module (Word Mapping Method)

from pymodbus.client import ModbusTcpClient
import time
# Modbus coupler communication parameter configuration
COUPLER_IP = "192.168.0.50"  # Modbus coupler IP address
COUPLER_PORT = 502           # The default communication port of Modbus TCP
UNIT_ID = 1                  # Modbus slave station address (slave ID)
AI_ADDRESS = 0               # Starting offset address of the AI module (decimal)
AI_COUNT = 4                 # The number of AI points that need to be read

# Function code 03 to read AI points
def read_modbus_ai_03(client: ModbusTcpClient, unit_id: int, address: int, count: int):
    """
    Modbus TCP read AI module (Function Code 03)
    :param client: Pre-established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: Starting offset address of AI
    :param count: Number of AI points to read
    :return: AI register list on success, None on failure
    """
    # Read AI module data
    res = client.read_holding_registers(address=address, count=count, slave=unit_id)
    if res.isError():
        print(f"Failed to read AI module: {res}")
        return None
    return res.registers[:count]

# Function code 04 to read AI points
def read_modbus_ai_04(client: ModbusTcpClient, unit_id: int, address: int, count: int):
    """
    Modbus TCP read AI module (Function Code 04)
    :param client: Pre-established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: Starting offset address of AI
    :param count: Number of AI points to read
    :return: AI register list on success, None on failure
    """
    # Read AI module data
    res = client.read_input_registers(address=address, count=count, slave=unit_id)
    if res.isError():
        print(f"Failed to read AI module: {res}")
        return None
    return res.registers

if __name__ == "__main__":
    # Establish persistent TCP connection
    client = ModbusTcpClient(COUPLER_IP, port=COUPLER_PORT)
    if not client.connect():
        print("Modbus connection failed!")
        exit()
    try:
        while True:
            # Call function using function code 03
            ret = read_modbus_ai_03(client, UNIT_ID, AI_ADDRESS, AI_COUNT)
            if ret:
               print("AI Module Values: ",ret)
            # Call function using function code 04
            # ret = read_modbus_ai_04(client, UNIT_ID, AI_ADDRESS, AI_COUNT)
            # if ret:
            #  print("AI Module Values: ",ret)
            time.sleep(1)  # Refresh every 1 second
    except KeyboardInterrupt:
        print("Program terminated")
    finally:
        # Close connection when program exits
        client.close()
        print("Modbus connection closed")

2.2.1.6 AO Module (Word Mapping Method)

from pymodbus.client import ModbusTcpClient
from pymodbus.pdu.register_message import WriteSingleRegisterResponse
import struct
import time
# Modbus coupler communication parameter configuration
COUPLER_IP = "192.168.0.50"  # Modbus coupler IP address
COUPLER_PORT = 502           # Default communication port of Modbus TCP
UNIT_ID = 1                  # Modbus slave address (slave id)
AO_ADDRESS_WRITE = 0         # Write the starting offset address of AO (in decimal)
AO_ADDRESS_READ = 8192       # Read the starting offset address of AO (decimal)
AO_COUNT = 4                 # Number of AO points to be read
AO_DATA = [1000, 2000, 3000, 4000] # AO point values to be written in batch

# Override decode method
def new_decode(self, data):
    data = data[:4]
    self.address, self.registers = struct.unpack(">HH", data)
WriteSingleRegisterResponse.decode = new_decode

# Read AO points
def read_modbus_ao(client: ModbusTcpClient, unit_id: int, address: int, count: int):
    """
    Modbus TCP read AO module (Function Code 03)
    :param client: Pre-established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: Starting offset address of AO
    :param count: Number of AO points to read
    :return: AO register list on success, None on failure
    """
    # Read AO module data
    res = client.read_holding_registers(address=address, count=count, slave=unit_id)
    if res.isError():
        print(f"Failed to read AO module: {res}")
        return None
    return res.registers[:count]

# Write single AO point
def write_modbus_ao_Single(client: ModbusTcpClient, unit_id: int, address: int, value: int):
    """
    Modbus TCP write AO module (Function Code 06)
    :param client: Established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: AO starting offset address
    :param value: Value to be written
    :return: True on success, None on failure
    """
    # Write single AO point
    res = client.write_register(address=address, value=value, slave=unit_id)
    if res.isError():
        print(f"Failed to write AO module: {res}")
        return None
    return True

# Batch write AO points
def write_modbus_ao_Batch(client: ModbusTcpClient, unit_id: int, address: int, reg_list: list[int]):
    """
    Modbus TCP write AO module (Function Code 16)
    :param client: Established ModbusTcpClient instance
    :param unit_id: Slave ID
    :param address: AO starting offset address
    :param reg_list: Register value list to write
    :return: True on success, None on failure
    """
    # Batch write AO points
    res = client.write_registers(address=address, values=reg_list, slave=unit_id)
    if res.isError():
        print(f"Failed to write AO module: {res}")
        return None
    return True

if __name__ == "__main__":
    # Establish persistent TCP connection
    client = ModbusTcpClient(COUPLER_IP, port=COUPLER_PORT)
    if not client.connect():
        print("Modbus connection failed!")
        exit()
    try:
        while True:
            # Call batch write function
            ret = write_modbus_ao_Batch(client, UNIT_ID, AO_ADDRESS_WRITE, AO_DATA)
            if ret:
              print("Batch AO point write operation completed")

            # Call single write function
            # ret = write_modbus_ao_Single(client, UNIT_ID, AO_ADDRESS_WRITE, value=2000)
            # if ret:
            #   print("Single AO point write operation completed")

            # Call AO reading function
            # ret = read_modbus_ao(client, UNIT_ID, AO_ADDRESS_READ, AO_COUNT)
            # if ret:
            #    print("AO Module Values: ",ret)    

            time.sleep(1)  # Refresh every 1 second to maintain AO status
    except KeyboardInterrupt:
        print("Program terminated")
    finally:
        # Close connection when program exits
        client.close()
        print("Modbus connection closed")

2.2.2 C++ Example Code

Introduces C++ example code for DI, DO, AI, and AO modules.

2.2.2.1 DI Module (Bit Mapping Method)

#include <iostream>
#include <thread>
#include <chrono>
#include <csignal>
#include <cstdlib>
#include <modbus/modbus.h>

using namespace std;
// Modbus coupler communication configuration
static const char* COUPLER_IP = "192.168.0.50";    // Modbus coupler IP address
static const int COUPLER_PORT = 502;               // Default Modbus TCP port
static const int UNIT_ID = 1;                      // Modbus slave ID
static const int DI_ADDRESS = 0;                   // DI start offset address (decimal)
static const int DI_COUNT = 8;                     // Number of DI points to read

static volatile bool g_running = true; // Control main loop

// Signal handler for Ctrl+C exit
void signal_handler(int sig) {
    if (sig == SIGINT) {
        cout << "\nInterrupt signal received, exiting..." << endl;
        g_running = false;
    }
}

/**
 * @brief Modbus FC02, read discrete inputs (DI)
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    DI start offset address
 * @param count      Number of DI points to read
 * @param dest       Bit receive buffer, uint8 array, allocated externally
 * @return true:success  false:failed to set slave or read data
 */
bool read_modbus_di_bit(modbus_t* ctx, int unit_id, int address, int count, uint8_t* dest) {
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1) {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Read DI module status
    int rc = modbus_read_input_bits(ctx, address, count, dest);
    if (rc == -1) {
        cerr << "Failed to read discrete inputs (FC02): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

int main() {
    // Register Ctrl+C signal handler
    signal(SIGINT, signal_handler);

    // Create Modbus TCP context
    modbus_t* ctx = modbus_new_tcp(COUPLER_IP, COUPLER_PORT);
    if (ctx == nullptr) {
        cerr << "Failed to create Modbus TCP context" << endl;
        return EXIT_FAILURE;
    }
    // Establish persistent TCP connection
    if (modbus_connect(ctx) == -1) {
        cerr << "Modbus connection failed: " << modbus_strerror(errno) << endl;
        modbus_free(ctx);
        return EXIT_FAILURE;
    }
    cout << "Modbus connected successfully" << endl;

    // DI bit data buffer
    uint8_t di_bits[DI_COUNT];
    while (g_running) {
        if (read_modbus_di_bit(ctx, UNIT_ID, DI_ADDRESS, DI_COUNT, di_bits)) {
            cout << "DI Channel Status: ";
            for (int i = 0; i < DI_COUNT; ++i) {
                cout << (int)di_bits[i] << " ";
            }
            cout << endl;
        }
        // Sleep for 1 second
        this_thread::sleep_for(chrono::seconds(1));
    }

    // Close connection and release resources
    modbus_close(ctx);
    modbus_free(ctx);
    cout << "Modbus connection closed" << endl;

    return EXIT_SUCCESS;
}

2.2.2.2 DI Module (Word Mapping Method)

#include <iostream>
#include <thread>
#include <chrono>
#include <csignal>
#include <cstdlib>
#include <modbus/modbus.h>

using namespace std;
// Modbus coupler communication configuration
static const char* COUPLER_IP = "192.168.0.50";    // Modbus coupler IP address
static const int COUPLER_PORT = 502;               // Default Modbus TCP port
static const int UNIT_ID = 1;                      // Modbus slave ID
static const int DI_ADDRESS = 20480;               // DI start offset address (decimal)
static const int DI_COUNT = 1;                     // Number of DI registers to read

static volatile bool g_running = true; // Control main loop

// Signal handler for Ctrl+C exit
void signal_handler(int sig) {
    if (sig == SIGINT) {
        cout << "\nInterrupt signal received, exiting..." << endl;
        g_running = false;
    }
}

/**
 * @brief Modbus FC03, read DI data stored in holding registers
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    DI start offset address
 * @param count      Number of registers to read
 * @param dest       Register receive buffer, uint16 array, allocated externally
 * @return true:success  false:failed to set slave or read data
 */
bool read_modbus_di_reg(modbus_t* ctx, int unit_id, int address, int count, uint16_t* dest) {
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1) {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Read holding registers (FC03)
    int rc = modbus_read_registers(ctx, address, count, dest);
    if (rc == -1) {
        cerr << "Failed to read DI holding registers (FC03): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

int main() {
    // Register Ctrl+C signal handler
    signal(SIGINT, signal_handler);

    // Create Modbus TCP context
    modbus_t* ctx = modbus_new_tcp(COUPLER_IP, COUPLER_PORT);
    if (ctx == nullptr) {
        cerr << "Failed to create Modbus TCP context" << endl;
        return EXIT_FAILURE;
    }
    // Establish persistent TCP connection
    if (modbus_connect(ctx) == -1) {
        cerr << "Modbus connection failed: " << modbus_strerror(errno) << endl;
        modbus_free(ctx);
        return EXIT_FAILURE;
    }
    cout << "Modbus connected successfully" << endl;

    // DI register buffer
    uint16_t di_regs[DI_COUNT];
    while (g_running) {
        if (read_modbus_di_reg(ctx, UNIT_ID, DI_ADDRESS, DI_COUNT, di_regs)) {
            cout << "DI Register Value: "; // Converting the output value to binary shows the status of each corresponding port bit
            for (int i = 0; i < DI_COUNT; ++i) {  
                cout << di_regs[i] << " ";
            }
            cout << endl;
        }
        // Sleep for 1 second
        this_thread::sleep_for(chrono::seconds(1));
    }

    // Close connection and release resources
    modbus_close(ctx);
    modbus_free(ctx);
    cout << "Modbus connection closed" << endl;

    return EXIT_SUCCESS;
}

2.2.2.3 DO Module (Bit Mapping Method)

#include <iostream>
#include <thread>
#include <chrono>
#include <csignal>
#include <cstdlib>
#include <modbus/modbus.h>

using namespace std;
// Modbus coupler communication parameters configuration
static const char* COUPLER_IP = "192.168.0.50";             // Modbus coupler IP address
static const int COUPLER_PORT = 502;                        // Default Modbus TCP communication port
static const int UNIT_ID = 1;                               // Modbus slave ID
static const int DO_ADDRESS = 0;                            // DO starting offset address (decimal)
static const int DO_COUNT = 16;                             // Number of DO points to read
static const uint8_t DO_DATA[] = {1, 1, 1, 1, 0, 0, 0, 0};  // Batch DO point values to be written (0=OFF, 1=ON)
static const int DO_WRITE_COUNT = sizeof(DO_DATA) / sizeof(DO_DATA[0]);  // Number of points to write

static volatile bool g_running = true; // Control main loop

// Signal handler for Ctrl+C exit
void signal_handler(int sig) {
    if (sig == SIGINT) {
        cout << "\nInterrupt signal received, exiting..." << endl;
        g_running = false;
    }
}

/**
 * @brief Modbus FC05, write single coil (single DO channel)
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    DO starting offset address
 * @param value      Target value (0=OFF, 1=ON)
 * @return true:success  false:failed
 */
bool write_modbus_do_single(modbus_t* ctx, int unit_id, int address, uint8_t value)
{
    // Set slave ID 
    if (modbus_set_slave(ctx, unit_id) == -1)
    {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Write single DO coil
    int rc = modbus_write_bit(ctx, address, value ? 1 : 0);
    if (rc == -1)
    {
        cerr << "Single DO write failed (FC05): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

/**
 * @brief Modbus FC05, write multiple coils (multiple DO channels)
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    DO starting coil address
 * @param count      Number of coils to write
 * @param bits       Array of target coil status
 * @return true:success  false:failed
 */
bool write_modbus_do_batch(modbus_t* ctx, int unit_id, int address, int count, const uint8_t* bits)
{
    // Set slave ID 
    if (modbus_set_slave(ctx, unit_id) == -1)
    {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Batch write DO coils
    int rc = modbus_write_bits(ctx, address, count, bits);
    if (rc == -1)
    {
        cerr << "Batch DO write failed (FC15): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

/**
 * @brief Modbus FC01, read DO coil status
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    DO start offset address
 * @param count      Number of DO points to read
 * @param dest       Bit receive buffer, uint8 array, allocated externally
 * @return true:success  false:failed to set slave or read data
 */
bool read_modbus_do_bit(modbus_t* ctx, int unit_id, int address, int count, uint8_t* dest) {
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1) {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // FC01 Read coils (DO)
    int rc = modbus_read_bits(ctx, address, count, dest);
    if (rc == -1) {
        cerr << "Failed to read DO coils (FC01): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

int main()
{
    // Register Ctrl+C signal handler
    signal(SIGINT, signal_handler);

    // Create Modbus TCP context
    modbus_t* ctx = modbus_new_tcp(COUPLER_IP, COUPLER_PORT);
    if (ctx == nullptr)
    {
        cerr << "Failed to create Modbus TCP context" << endl;
        return EXIT_FAILURE;
    }
    // Establish persistent TCP connection
    if (modbus_connect(ctx) == -1)
    {
        cerr << "Modbus connection failed: " << modbus_strerror(errno) << endl;
        modbus_free(ctx);
        return EXIT_FAILURE;
    }
    cout << "Modbus connected successfully" << endl;
    
    // DO bit data buffer
    uint8_t do_bits[DO_COUNT];
    while (g_running)
    {
        // Batch write DO channels
        if (write_modbus_do_batch(ctx, UNIT_ID, DO_ADDRESS, DO_WRITE_COUNT, DO_DATA))
        {
            cout << "Batch DO point write completed" << endl;
        }
        // Single DO write
        /* if (write_modbus_do_single(ctx, UNIT_ID, DO_ADDRESS, 1))
        {
            cout << "Single DO point write completed" << endl;
        } */
        // read DO 
        /* if (read_modbus_do_bit(ctx, UNIT_ID, DO_ADDRESS, DO_COUNT, do_bits)) 
        {
        cout << "DO Channel Status: ";
        for (int i = 0; i < DO_COUNT; ++i) {
            cout << (int)do_bits[i] << " ";
        }
        cout << endl;
        } */
        this_thread::sleep_for(chrono::seconds(1)); // Refresh every 1 second
    }

    // Close connection and release resources
    modbus_close(ctx);
    modbus_free(ctx);
    cout << "Modbus connection closed" << endl;

    return EXIT_SUCCESS;
}

2.2.2.4 DO Module (Word Mapping Method)

#include <iostream>
#include <thread>
#include <chrono>
#include <csignal>
#include <cstdlib>
#include <modbus/modbus.h>

using namespace std;
// Modbus coupler communication parameters configuration
static const char* COUPLER_IP = "192.168.0.50";                          // Modbus coupler IP address
static const int COUPLER_PORT = 502;                                     // Default Modbus TCP communication port
static const int UNIT_ID = 1;                                            // Modbus slave ID
static const int DO_ADDRESS_READ = 16384;                                // Read the starting offset address of DO (decimal)
static const int DO_COUNT = 1;                                           // Number of DO registers to read
static const int DO_ADDRESS_WRITE = 12288;                               // Write the starting offset address of DO (decimal)
static const uint16_t DO_DATA[] = {0xFFFF};                              // DO values written by word (hexadecimal)
static const int DO_WRITE_COUNT = sizeof(DO_DATA) / sizeof(DO_DATA[0]);  // Number of registers to write

static volatile bool g_running = true; // Control main loop

// Signal handler for Ctrl+C exit
void signal_handler(int sig) {
    if (sig == SIGINT) {
        cout << "\nInterrupt signal received, exiting..." << endl;
        g_running = false;
    }
}

/**
 * @brief Modbus FC16, write DO registers
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    Starting register address
 * @param count      Number of registers to write
 * @param regs       Target register value array
 * @return true:success  false:failed
 */
bool write_modbus_do_word_batch(modbus_t* ctx, int unit_id, int address, int count, const uint16_t* regs)
{
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1)
    {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Write registers (FC16)
    int rc = modbus_write_registers(ctx, address, count, regs);
    if (rc == -1)
    {
        cerr << "DO word-based write failed (FC16): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

/**
 * @brief Modbus FC03, read DO status mapped to holding registers
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    DO register start offset address
 * @param count      Number of registers to read
 * @param dest       Register receive buffer, uint16 array, allocated externally
 * @return true:success  false:failed to set slave or read data
 */
bool read_modbus_do_reg(modbus_t* ctx, int unit_id, int address, int count, uint16_t* dest) {
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1) {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Read DO module status (FC03)
    int rc = modbus_read_registers(ctx, address, count, dest);
    if (rc == -1) {
        cerr << "Failed to read DO holding registers (FC03): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

int main()
{
    // Register Ctrl+C signal handler
    signal(SIGINT, signal_handler);

    // Create Modbus TCP context
    modbus_t* ctx = modbus_new_tcp(COUPLER_IP, COUPLER_PORT);
    if (ctx == nullptr)
    {
        cerr << "Failed to create Modbus TCP context" << endl;
        return EXIT_FAILURE;
    }
    // Establish persistent TCP connection
    if (modbus_connect(ctx) == -1)
    {
        cerr << "Modbus connection failed: " << modbus_strerror(errno) << endl;
        modbus_free(ctx);
        return EXIT_FAILURE;
    }
    cout << "Modbus connected successfully" << endl;
    uint16_t do_regs[DO_COUNT];
    while (g_running)
    {
        // Write an example of DO module call
        /* if (write_modbus_do_word_batch(ctx, UNIT_ID, DO_ADDRESS_WRITE, DO_WRITE_COUNT, DO_DATA))
        {
            cout << "Word-based DO write completed" << endl;
        } */
        // Reading the example of DO module call
        if (read_modbus_do_reg(ctx, UNIT_ID, DO_ADDRESS_READ, DO_COUNT, do_regs)) {
            cout << "DO Register Value: "; // Converting the output value to binary shows the status of each corresponding port bit
            for (int i = 0; i < DO_COUNT; ++i) {
                cout << do_regs[i] << " ";
            }
            cout << endl;
        }

        this_thread::sleep_for(chrono::seconds(1)); // Refresh every 1 second
    }

    // Close connection and release resources
    modbus_close(ctx);
    modbus_free(ctx);
    cout << "Modbus connection closed" << endl;

    return EXIT_SUCCESS;
}

2.2.2.5 AI Module (Word Mapping Method)

#include <iostream>
#include <thread>
#include <chrono>
#include <csignal>
#include <cstdlib>
#include <modbus/modbus.h>

using namespace std;
// Modbus coupler communication configuration
static const char* COUPLER_IP = "192.168.0.50";    // Modbus coupler IP address
static const int COUPLER_PORT = 502;               // Default Modbus TCP port
static const int UNIT_ID = 1;                      // Modbus slave ID
static const int AI_ADDRESS = 0;                   // Start offset address of AI module (decimal)
static const int AI_COUNT = 4;                     // Number of AI points to read

static volatile bool g_running = true; // Control main loop

// Signal handler for Ctrl+C exit
void signal_handler(int sig) {
    if (sig == SIGINT) {
        cout << "\nInterrupt signal received, exiting..." << endl;
        g_running = false;
    }
}

/**
 * @brief Modbus function code 03, read holding registers
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    Start offset address of AI module
 * @param count      Number of AI points to read
 * @param dest       Receive buffer, provided externally, length >= count
 * @return true:success  false:failed to set slave or read registers
 */
bool read_modbus_ai_03(modbus_t* ctx, int unit_id, int address, int count, uint16_t* dest) {
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1) {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Read AI module data
    int rc = modbus_read_registers(ctx, address, count, dest);
    if (rc == -1) {
        cerr << "Failed to read AI holding registers (FC03): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

/**
 * @brief Modbus function code 04, read input registers
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    Start offset address of AI module
 * @param count      Number of AI points to read
 * @param dest       Receive buffer, provided externally, length >= count
 * @return true:success  false:failed to set slave or read registers
 */
bool read_modbus_ai_04(modbus_t* ctx, int unit_id, int address, int count, uint16_t* dest) {
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1) {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Read AI module data
    int rc = modbus_read_input_registers(ctx, address, count, dest);
    if (rc == -1) {
        cerr << "Failed to read AI input registers (FC04): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

int main() {
    // Register Ctrl+C signal handler
    signal(SIGINT, signal_handler);

    // Create Modbus TCP context
    modbus_t* ctx = modbus_new_tcp(COUPLER_IP, COUPLER_PORT);
    if (ctx == nullptr) {
        cerr << "Failed to create Modbus TCP context" << endl;
        return EXIT_FAILURE;
    }
    
    // Establish persistent TCP connection
    if (modbus_connect(ctx) == -1) {
        cerr << "Modbus connection failed: " << modbus_strerror(errno) << endl;
        modbus_free(ctx);
        return EXIT_FAILURE;
    }
    cout << "Modbus connected successfully" << endl;

    // Buffer for received register data
    uint16_t regs[AI_COUNT];
    while (g_running) {
        // Read data via function code 03
        if (read_modbus_ai_03(ctx, UNIT_ID, AI_ADDRESS, AI_COUNT, regs)) {
            cout << "AI Values (FC03): ";
            for (int i = 0; i < AI_COUNT; ++i) {
                cout << regs[i] << " ";
            }
            cout << endl;
        }
        // Uncomment block below and comment FC03 call if using function code 04
        /* if (read_modbus_ai_04(ctx, UNIT_ID, AI_ADDRESS, AI_COUNT, regs)) {
            cout << "AI Values (FC04): ";
            for (int i = 0; i < AI_COUNT; ++i) {
                cout << regs[i] << " ";
            }
            cout << endl;
        } */
        // Sleep for 1 second
        this_thread::sleep_for(chrono::seconds(1));
    }
    // Close connection and release resources
    modbus_close(ctx);
    modbus_free(ctx);
    cout << "Modbus connection closed" << endl;

    return EXIT_SUCCESS;
}

2.2.2.6 AO Module (Word Mapping Method)

#include <iostream>
#include <thread>
#include <chrono>
#include <csignal>
#include <cstdlib>
#include <cstdint>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <modbus/modbus.h>

using namespace std;

// Modbus coupler communication parameters
static const char* COUPLER_IP = "192.168.0.50";       // Modbus coupler IP address
static const int COUPLER_PORT = 502;                  // Default Modbus TCP communication port
static const int UNIT_ID = 1;                         // Modbus slave ID
static const int AO_ADDRESS_WRITE = 0;                // Write the starting offset address of AO (in decimal)
static const int AO_ADDRESS_READ = 8192;              // Read the starting offset address of AO (decimal)
static const int AO_COUNT = 4;                        // Number of AO points to read
static uint16_t AO_DATA[] = {1000, 2000, 3000, 4000}; // AO channel values to be written in batch
static const int AO_WRITE_COUNT = sizeof(AO_DATA)/sizeof(AO_DATA[0]);  // Number of registers to write

static volatile bool g_running = true; // Main loop control flag

// Signal handler for Ctrl+C exit
void signal_handler(int sig) {
    if (sig == SIGINT) {
        cout << "\nInterrupt signal received, exiting..." << endl;
        g_running = false;
    }
}

// Flush TCP socket receive buffer
bool flush_tcp_buffer(int fd)
{
    if(fd < 0)
        return false;
    // Save original blocking flag
    int flags = fcntl(fd, F_GETFL, 0);
    if(flags == -1)
        return false;
    // Set non-blocking mode
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    uint8_t tmp[64];
    while (true)
    {
        ssize_t r = recv(fd, tmp, sizeof(tmp), MSG_DONTWAIT);
        if(r <= 0)
            break;
    }
    // Restore blocking mode
    fcntl(fd, F_SETFL, flags);
    return true;
}

/**
 * @brief Modbus Function Code 06, write single holding register (single AO channel)
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    register address
 * @param value      value to write into register
 * @return true:success  false:failure
 */
bool write_modbus_ao_single(modbus_t* ctx, int unit_id, int address, uint16_t value)
{
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1)
    {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Write single AO register
    int rc = modbus_write_register(ctx, address, value);

    int sockfd = modbus_get_socket(ctx);
    flush_tcp_buffer(sockfd);

    if (rc == -1)
    {
        cerr << "Single AO write failed (FC06): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

/**
 * @brief Modbus Function Code 16, batch write holding registers (multiple AO channels)
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    starting register address
 * @param count      number of registers to write
 * @param regs       array of register values to write
 * @return true:success  false:failure
 */
bool write_modbus_ao_batch(modbus_t* ctx, int unit_id, int address, int count, uint16_t* regs)
{
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1)
    {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Batch write AO registers
    int rc = modbus_write_registers(ctx, address, count, regs);
    if (rc == -1)
    {
        cerr << "Batch AO write failed (FC16): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

/**
 * @brief Modbus FC03, read holding registers (AO registers)
 * @param ctx        modbus context handle
 * @param unit_id    Modbus slave ID
 * @param address    AO module start offset address
 * @param count      Number of points to read
 * @param dest       Receive buffer, provided externally, length >= count
 * @return true:success  false:failed to set slave or read registers
 */
bool read_modbus_ao(modbus_t* ctx, int unit_id, int address, int count, uint16_t* dest) {
    // Set slave ID
    if (modbus_set_slave(ctx, unit_id) == -1) {
        cerr << "Failed to set slave ID: " << modbus_strerror(errno) << endl;
        return false;
    }
    // Read holding registers (FC03)
    int rc = modbus_read_registers(ctx, address, count, dest);
    if (rc == -1) {
        cerr << "Failed to read AO holding registers (FC03): " << modbus_strerror(errno) << endl;
        return false;
    }
    return true;
}

uint16_t regs[AO_COUNT];

int main()
{
    // Register Ctrl+C signal handler
    signal(SIGINT, signal_handler);

    // Create Modbus TCP context
    modbus_t* ctx = modbus_new_tcp(COUPLER_IP, COUPLER_PORT);
    if (ctx == nullptr)
    {
        cerr << "Failed to create Modbus TCP context" << endl;
        return EXIT_FAILURE;
    }
    // Establish persistent TCP connection
    if (modbus_connect(ctx) == -1)
    {
        cerr << "Modbus connection failed: " << modbus_strerror(errno) << endl;
        modbus_free(ctx);
        return EXIT_FAILURE;
    }
    cout << "Modbus connected successfully" << endl;

    while (g_running)
    {
        // Batch write AO (FC16)
        if (write_modbus_ao_batch(ctx, UNIT_ID, AO_ADDRESS_WRITE, AO_WRITE_COUNT, AO_DATA))
        {
            cout << "Batch AO write completed" << endl;
        }
        // Single channel write (FC06)
        /* if (write_modbus_ao_single(ctx, UNIT_ID, AO_ADDRESS_WRITE, 1000))
        {
            cout << "Single AO write completed" << endl;
        } */
        // Read AO(FC03)
       /*  if (read_modbus_ao(ctx, UNIT_ID, AO_ADDRESS_READ, AO_COUNT, regs)) {
            cout << "AO Values: ";
            for (int i = 0; i < AO_COUNT; ++i) {
                cout << regs[i] << " ";
            }
            cout << endl;
        } */
        this_thread::sleep_for(chrono::seconds(1));
    }

    // Close connection and release resources
    modbus_close(ctx);
    modbus_free(ctx);
    cout << "Modbus connection closed" << endl;

    return EXIT_SUCCESS;
}