Readme
This commit is contained in:
parent
7feff1dc5a
commit
3366083d42
130
README.md
130
README.md
@ -0,0 +1,130 @@
|
|||||||
|
# Purpose
|
||||||
|
The purpose of this library is to provide a lightweight, minimal import C++ structure for connecting to a MODBUS TCP client.
|
||||||
|
|
||||||
|
Specifically, this library was created for use in PX4 development using the PX4 toolkit in Simulink.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
There are two ways to use the class. The first method is to construct the class with the server's IP address and port. Then, the class provides functions for reading/writing coils, discrete inputs, and registers. The second, preferred method, has the constructor allocate memory for the coils, discrete inputs, and registers that should exist for the entire session. A separate set of functions are exposed that has the class update all values at once, saving on network throughput and latency.
|
||||||
|
|
||||||
|
## Types
|
||||||
|
The following enum class is used internally for the class to create the correct packet for MODBUS TCP communication.
|
||||||
|
```
|
||||||
|
enum class ModbusFunction : uint8_t {
|
||||||
|
READ_COIL = 0x01,
|
||||||
|
READ_DISCRETE_INPUT = 0x02,
|
||||||
|
READ_HOLDING_REGISTER = 0x03,
|
||||||
|
READ_INPUT_REGISTER = 0x04,
|
||||||
|
WRITE_SINGLE_COIL = 0x05,
|
||||||
|
WRITE_SINGLE_HOLDING_REGISTER = 0x06,
|
||||||
|
WRITE_MULTIPLE_COILS = 0x0F,
|
||||||
|
WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The ```ModbusError``` enum class is used for returning errors that could have occurred during function calls.
|
||||||
|
```
|
||||||
|
enum class ModbusError {
|
||||||
|
NONE = 0,
|
||||||
|
TIMEOUT,
|
||||||
|
INVALID_RESPONSE,
|
||||||
|
CONNECTION_LOST,
|
||||||
|
EXCEPTION_RESPONSE,
|
||||||
|
INVALID_REQUEST
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method 1: Manual Control
|
||||||
|
The manual control method is best for when the client only needs to query a handful of coils/DI/registers at a time. For large server's, the automatic control method is preferred.
|
||||||
|
|
||||||
|
The manual control constructor requires only the server's IP and port.
|
||||||
|
```
|
||||||
|
ModbusTCPClient(const char* ip, int port);
|
||||||
|
```
|
||||||
|
|
||||||
|
The class then exposed the following functions for use. The first group provides read access to the server. The return types are all ```ModbusError``` and the data is stored in the final argument passed into the function call. This means the client must allocate a buffer before calling the read functions. For embedded devices, like STM32s, it is better to allocate the total expected size that the coils/DI/registers will take up and use the same buffer rather than allocating, freeing, re-allocating, ....
|
||||||
|
|
||||||
|
```
|
||||||
|
ModbusError readCoil(int address, bool &coilState);
|
||||||
|
ModbusError readMultipleCoils(int address, int count, bool coilStates[]);
|
||||||
|
ModbusError readDiscreteInput(int address, bool &discreteInput);
|
||||||
|
ModbusError readMultipleDiscreteInputs(int address, int count, bool discreteInputs[]);
|
||||||
|
ModbusError readHoldingRegister(int address, uint16_t &holdingRegister);
|
||||||
|
ModbusError readMultipleHoldingRegisters(int address, int count, uint16_t holdingRegisters[]);
|
||||||
|
ModbusError readInputRegister(int address, uint16_t &inputRegister);
|
||||||
|
ModbusError readMultipleInputRegisters(int address, int count, uint16_t inputRegisters[]);
|
||||||
|
```
|
||||||
|
|
||||||
|
The writing functions similarly return ```ModbusError``` and are straight-forward.
|
||||||
|
```
|
||||||
|
ModbusError writeCoil(int address, bool value);
|
||||||
|
ModbusError writeMultipleCoils(int address, int count, const bool values[]);
|
||||||
|
ModbusError writeHoldingRegister(int address, uint16_t value);
|
||||||
|
ModbusError writeMultipleHoldingRegisters(int address, int count, const uint16_t values[]);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Method 2: Automatic Control (Preferred for Larger Number of Coils/DI/Registers)
|
||||||
|
The automatic control method is preferred for clients that need to request large amounts of data at a time, usually at fixed time intervals. This method ensures the use of the "multiple" read and write functions rather than reading/writing to a single coil/DI/register at a time. Additionally, this class allocates the memory to store all of the server's data on the client. This removes the need for the client to manually allocate space before every read.
|
||||||
|
|
||||||
|
The automatic control constructor takes in the server IP and port, as well as the number of coils, discrete inputs (DI), input registers, holding registers, as well as the start offset for each.
|
||||||
|
```
|
||||||
|
ModbusTCPClient(const char* ip, int port, int numCoils, int numDI, int numIR, int numHR,
|
||||||
|
int startCoils = 0, int startDI = 0, int startIR = 0, int startHR = 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
The constructor initializes the following pointers for storing data from reads and for buffering the data before a write. These are all private and accessed through getters.
|
||||||
|
```
|
||||||
|
bool* coilsRead; // Actual state from PLC
|
||||||
|
bool* coilsWrite; // Desired state to write
|
||||||
|
bool* discreteInputs; // Only read
|
||||||
|
uint16_t* inputRegisters; // Only read
|
||||||
|
uint16_t* holdingRegistersRead; // Actual values from PLC
|
||||||
|
uint16_t* holdingRegistersWrite; // Desired values to write
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reading
|
||||||
|
In order to read data from the server, all the user must do is call ```ModbusError readAll();```. This will update all of the internal pointers to the data from the server. Then the getters can be used to retrieve the data for use elsewhere.
|
||||||
|
|
||||||
|
The following getters are used for retrieving the values from the internal buffers. For retrieving single values, the return type is the type requested. For requests of multiple data elements, the return type is ```ModbusError``` but is limited to either ```ModbusError::NONE``` or ```ModbusError::INVALID_REQUEST``` if the user requested data out of range based on the constructor.
|
||||||
|
```
|
||||||
|
bool getDiscreteInput(int address) const;
|
||||||
|
bool getCoil(int address) const;
|
||||||
|
bool getDesiredCoil(int address) const;
|
||||||
|
uint16_t getInputRegister(int address) const;
|
||||||
|
uint16_t getHoldingRegister(int address) const;
|
||||||
|
uint16_t getDesiredHoldingRegister(int address) const;
|
||||||
|
|
||||||
|
ModbusError getMultipleCoils(int startAddress, int count, bool* destination) const;
|
||||||
|
ModbusError getMultipleDiscreteInputs(int startAddress, int count, bool* destination) const;
|
||||||
|
ModbusError getMultipleInputRegisters(int startAddress, int count, uint16_t* destination) const;
|
||||||
|
ModbusError getMultipleHoldingRegisters(int startAddress, int count, uint16_t* destination) const;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Writing
|
||||||
|
In order to write data to the server, the user must first set the internal buffers to the correct data before writing. This is achieved through the following functions.
|
||||||
|
|
||||||
|
```
|
||||||
|
void setCoil(int address, bool value);
|
||||||
|
void setHoldingRegister(int address, uint16_t value);
|
||||||
|
```
|
||||||
|
TODO: Add setMultipleCoil and setMultipleHoldingRegisters
|
||||||
|
|
||||||
|
Then, the user can call ```ModbusError writeAll();``` while will write the data from ```bool* coilsWrite;``` and ```uint16_t* holdingRegistersWrite;``` to the server.
|
||||||
|
|
||||||
|
|
||||||
|
# Example Simulink Setup
|
||||||
|
Add the header and source files into the code generation setup of the Simulink model. Then, create custom C blocks for reading and writing. In the context of PX4, the uORB topics are created for each of the data types.
|
||||||
|
|
||||||
|
TODO: Add details on setting up uORB topics and Simulink code generation setup.
|
||||||
|
|
||||||
|
## PX4 Reading Setup
|
||||||
|
The setup can easily be used to transfer the data from the coils, DI, and registers from the server into the client's (in this case a Pixhawk running PX4) uORB topic bus. This is very convenient as the uORB message bus is very well supported and provides incredible ease of use for data management and telemetry.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## PX4 Writing Setup
|
||||||
|
Likewise, it is convenient to take the values stored in the uORB message bus and write them to the server.
|
||||||
|
|
||||||
|

|
||||||
BIN
images/simulink_readall.png
Normal file
BIN
images/simulink_readall.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
BIN
images/simulink_writeall.png
Normal file
BIN
images/simulink_writeall.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@ -1,738 +0,0 @@
|
|||||||
#include "modbus_tcp_client.h"
|
|
||||||
#include <sys/select.h>
|
|
||||||
|
|
||||||
// --- Helper: Compute maximum read response size based on configured counts.
|
|
||||||
int ModbusTCPClient::computeMaxReadResponseSize() const {
|
|
||||||
// For registers: 9 + (count * 2)
|
|
||||||
int rIR = 9 + (numInputRegisters * 2);
|
|
||||||
int rHR = 9 + (numHoldingRegisters * 2);
|
|
||||||
// For coils/discrete: 9 + ceil(count/8)
|
|
||||||
int rCoils = 9 + ((numCoils + 7) / 8);
|
|
||||||
int rDI = 9 + ((numDiscreteInputs + 7) / 8);
|
|
||||||
int maxVal = rIR;
|
|
||||||
if (rHR > maxVal) maxVal = rHR;
|
|
||||||
if (rCoils > maxVal) maxVal = rCoils;
|
|
||||||
if (rDI > maxVal) maxVal = rDI;
|
|
||||||
return maxVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Helper: Compute maximum write request size based on configured counts.
|
|
||||||
int ModbusTCPClient::computeMaxWriteRequestSize() const {
|
|
||||||
// For write multiple holding registers: 13 + (numHoldingRegisters * 2)
|
|
||||||
int wHR = 13 + (numHoldingRegisters * 2);
|
|
||||||
// For write multiple coils: 13 + ceil(numCoils/8)
|
|
||||||
int wCoils = 13 + ((numCoils + 7) / 8);
|
|
||||||
return (wHR > wCoils) ? wHR : wCoils;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Constructors ---
|
|
||||||
ModbusTCPClient::ModbusTCPClient(const char* ip, int port, int numCoils, int numDI, int numIR, int numHR,
|
|
||||||
int startCoils, int startDI, int startIR, int startHR)
|
|
||||||
: serverIP(ip), serverPort(port), socketFD(-1), transactionID(1),
|
|
||||||
numCoils(numCoils), numDiscreteInputs(numDI), numInputRegisters(numIR), numHoldingRegisters(numHR),
|
|
||||||
startCoils(startCoils), startDiscreteInputs(startDI), startInputRegisters(startIR), startHoldingRegisters(startHR) {
|
|
||||||
|
|
||||||
// Allocate internal storage for automatic readAll()/writeAll() mode.
|
|
||||||
coilsRead = new bool[numCoils]();
|
|
||||||
coilsWrite = new bool[numCoils]();
|
|
||||||
discreteInputs = new bool[numDI]();
|
|
||||||
inputRegisters = new uint16_t[numIR]();
|
|
||||||
holdingRegistersRead = new uint16_t[numHR]();
|
|
||||||
holdingRegistersWrite = new uint16_t[numHR]();
|
|
||||||
|
|
||||||
// Allocate shared communication buffers.
|
|
||||||
commRequestBufferSize = computeMaxWriteRequestSize(); // Worst-case request size.
|
|
||||||
commResponseBufferSize = computeMaxReadResponseSize(); // Worst-case response size.
|
|
||||||
commRequestBuffer = new uint8_t[commRequestBufferSize];
|
|
||||||
commResponseBuffer = new uint8_t[commResponseBufferSize];
|
|
||||||
|
|
||||||
// Initialize the socket mutex.
|
|
||||||
pthread_mutex_init(&socketMutex, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusTCPClient::ModbusTCPClient(const char* ip, int port)
|
|
||||||
: serverIP(ip), serverPort(port), socketFD(-1), transactionID(1) {
|
|
||||||
// This constructor is for manual MODBUS function calls; readAll/writeAll will not be available.
|
|
||||||
coilsRead = nullptr;
|
|
||||||
coilsWrite = nullptr;
|
|
||||||
discreteInputs = nullptr;
|
|
||||||
inputRegisters = nullptr;
|
|
||||||
holdingRegistersRead = nullptr;
|
|
||||||
holdingRegistersWrite = nullptr;
|
|
||||||
|
|
||||||
// Allocate default communication buffers for simple operations.
|
|
||||||
commRequestBufferSize = 12;
|
|
||||||
commResponseBufferSize = 9 + (256 * 2); // Default for 256 registers worst-case.
|
|
||||||
commRequestBuffer = new uint8_t[commRequestBufferSize];
|
|
||||||
commResponseBuffer = new uint8_t[commResponseBufferSize];
|
|
||||||
|
|
||||||
// Initialize the socket mutex.
|
|
||||||
pthread_mutex_init(&socketMutex, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusTCPClient::~ModbusTCPClient() {
|
|
||||||
delete[] coilsRead;
|
|
||||||
delete[] coilsWrite;
|
|
||||||
delete[] discreteInputs;
|
|
||||||
delete[] inputRegisters;
|
|
||||||
delete[] holdingRegistersRead;
|
|
||||||
delete[] holdingRegistersWrite;
|
|
||||||
|
|
||||||
delete[] commRequestBuffer;
|
|
||||||
delete[] commResponseBuffer;
|
|
||||||
|
|
||||||
// Destroy the pthread mutex.
|
|
||||||
pthread_mutex_destroy(&socketMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::setStartAddresses(int startCoils, int startDI, int startIR, int startHR) {
|
|
||||||
this->startCoils = startCoils;
|
|
||||||
this->startDiscreteInputs = startDI;
|
|
||||||
this->startInputRegisters = startIR;
|
|
||||||
this->startHoldingRegisters = startHR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Connection Functions ---
|
|
||||||
bool ModbusTCPClient::connectServer() {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
if (socketFD != -1) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (int attempts = 0; attempts < 5; attempts++) {
|
|
||||||
socketFD = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (socketFD < 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Could not create socket\n");
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
struct sockaddr_in serverAddr;
|
|
||||||
serverAddr.sin_family = AF_INET;
|
|
||||||
serverAddr.sin_port = htons(serverPort);
|
|
||||||
inet_pton(AF_INET, serverIP, &serverAddr.sin_addr);
|
|
||||||
printf("MODBUS_TCP_CLIENT: Attempting to connect (Try %d)...\n", attempts + 1);
|
|
||||||
if (connect(socketFD, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Connected to %s:%d\n", serverIP, serverPort);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
printf("MODBUS_TCP_CLIENT: Connection failed, retrying...\n");
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
disconnectServer();
|
|
||||||
usleep(100000);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::disconnectServer() {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
if (socketFD != -1) {
|
|
||||||
close(socketFD);
|
|
||||||
socketFD = -1;
|
|
||||||
printf("MODBUS_TCP_CLIENT: Disconnected from server\n");
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::isConnected() const {
|
|
||||||
return socketFD != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::reconnectServer() {
|
|
||||||
disconnectServer();
|
|
||||||
return connectServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::setTimeout(int milliseconds) {
|
|
||||||
timeoutMilliseconds = milliseconds;
|
|
||||||
printf("MODBUS_TCP_CLIENT: Timeout set to %d ms\n", timeoutMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::sendRequest(uint8_t* request, int requestSize) {
|
|
||||||
if (socketFD == -1) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Not connected. Cannot send request.\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int bytesSent = write(socketFD, request, requestSize);
|
|
||||||
if (bytesSent <= 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Write failed, disconnecting...\n");
|
|
||||||
disconnectServer();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return bytesSent == requestSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::receiveResponse(uint8_t* response, int expectedSize) {
|
|
||||||
int totalBytesReceived = 0;
|
|
||||||
while (totalBytesReceived < expectedSize) {
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = timeoutMilliseconds / 1000;
|
|
||||||
timeout.tv_usec = (timeoutMilliseconds % 1000) * 1000;
|
|
||||||
fd_set read_fds;
|
|
||||||
FD_ZERO(&read_fds);
|
|
||||||
FD_SET(socketFD, &read_fds);
|
|
||||||
int ready = select(socketFD + 1, &read_fds, NULL, NULL, &timeout);
|
|
||||||
if (ready == 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Timeout waiting for response. Disconnecting...\n");
|
|
||||||
disconnectServer();
|
|
||||||
return false;
|
|
||||||
} else if (ready < 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Select failed. Disconnecting...\n");
|
|
||||||
disconnectServer();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int bytesReceived = read(socketFD, response + totalBytesReceived, expectedSize - totalBytesReceived);
|
|
||||||
if (bytesReceived <= 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Connection lost while reading. Disconnecting...\n");
|
|
||||||
disconnectServer();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
totalBytesReceived += bytesReceived;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Message Building Functions ---
|
|
||||||
void ModbusTCPClient::buildReadRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t startAddr, uint16_t quantity) {
|
|
||||||
transactionID++;
|
|
||||||
|
|
||||||
// Transaction ID
|
|
||||||
buffer[0] = transactionID >> 8;
|
|
||||||
buffer[1] = transactionID & 0xFF;
|
|
||||||
// Protocol ID, always 00 00
|
|
||||||
buffer[2] = 0x00;
|
|
||||||
buffer[3] = 0x00;
|
|
||||||
// Length (remaining bytes after Unit ID)
|
|
||||||
buffer[4] = 0x00;
|
|
||||||
buffer[5] = 0x06;
|
|
||||||
// Unit ID (Slave Address)
|
|
||||||
buffer[6] = 0x01;
|
|
||||||
// Function code
|
|
||||||
buffer[7] = static_cast<uint8_t>(functionCode);
|
|
||||||
// Start Address
|
|
||||||
buffer[8] = startAddr >> 8;
|
|
||||||
buffer[9] = startAddr & 0xFF;
|
|
||||||
// Quantity (How many coils/registers to read)
|
|
||||||
buffer[10] = quantity >> 8;
|
|
||||||
buffer[11] = quantity & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::buildWriteSingleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t value) {
|
|
||||||
transactionID++;
|
|
||||||
buffer[0] = transactionID >> 8;
|
|
||||||
buffer[1] = transactionID & 0xFF;
|
|
||||||
buffer[2] = 0x00;
|
|
||||||
buffer[3] = 0x00;
|
|
||||||
buffer[4] = 0x00;
|
|
||||||
buffer[5] = 0x06;
|
|
||||||
buffer[6] = 0x01;
|
|
||||||
buffer[7] = static_cast<uint8_t>(functionCode);
|
|
||||||
buffer[8] = address >> 8;
|
|
||||||
buffer[9] = address & 0xFF;
|
|
||||||
buffer[10] = value >> 8;
|
|
||||||
buffer[11] = value & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::buildWriteMultipleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t count, const void* values, uint8_t byteCount) {
|
|
||||||
transactionID++;
|
|
||||||
|
|
||||||
// MODBUS TCP Header
|
|
||||||
buffer[0] = transactionID >> 8;
|
|
||||||
buffer[1] = transactionID & 0xFF;
|
|
||||||
buffer[2] = 0x00; // Protocol ID (always 0x0000)
|
|
||||||
buffer[3] = 0x00;
|
|
||||||
buffer[4] = 0x00; // Length (remaining bytes after Unit ID)
|
|
||||||
buffer[5] = 7 + byteCount;
|
|
||||||
buffer[6] = 0x01; // Unit ID
|
|
||||||
buffer[7] = static_cast<uint8_t>(functionCode); // Function Code (0x0F for coils, 0x10 for registers)
|
|
||||||
buffer[8] = address >> 8; // Start address high byte
|
|
||||||
buffer[9] = address & 0xFF; // Start address low byte
|
|
||||||
buffer[10] = count >> 8; // Quantity high byte
|
|
||||||
buffer[11] = count & 0xFF; // Quantity low byte
|
|
||||||
buffer[12] = byteCount; // Byte count
|
|
||||||
|
|
||||||
// Copy data payload manually (instead of memcpy)
|
|
||||||
const uint8_t* data = (const uint8_t*)values;
|
|
||||||
for (uint8_t i = 0; i < byteCount; i++) {
|
|
||||||
buffer[13 + i] = data[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- High-Level Read/Write Functions ---
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readCoil(int address, bool &coilState) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_COIL, address, 1);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Expect 10 bytes: header (9 bytes) + 1 byte data
|
|
||||||
int expectedSize = 9 + 1;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
// Ensure the function code in the response matches the request.
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_COIL)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Set the internal coil state from the response
|
|
||||||
coilState = (commResponseBuffer[9] & 0x01) != 0;
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleCoils(int address, int count, bool coilStates[]) {
|
|
||||||
if (count < 1 || count > 2000) { // MODBUS limits: 1-2000 coils per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid coil count (1-2000 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_COIL, address, count);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Expected response size: fixed header (9 bytes) + variable byte count.
|
|
||||||
int byteCount = (count + 7) / 8; // 1 byte per 8 coils
|
|
||||||
int expectedSize = 9 + byteCount;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
// Ensure the function code in the response matches the request.
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_COIL)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Extract coil values: data starts at index 9.
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
int byteIndex = 9 + (i / 8);
|
|
||||||
int bitIndex = i % 8;
|
|
||||||
coilStates[i] = (commResponseBuffer[byteIndex] >> bitIndex) & 0x01;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readDiscreteInput(int address, bool &discreteInput) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
// Build a request for one discrete input (quantity = 1)
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_DISCRETE_INPUT, address, 1);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Expect 10 bytes: header (9 bytes) + 1 byte data
|
|
||||||
int expectedSize = 9 + 1;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Check for MODBUS exception response
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
// Validate function code
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_DISCRETE_INPUT)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Extract the discrete input state (first bit of the data byte at index 9)
|
|
||||||
discreteInput = (commResponseBuffer[9] & 0x01) != 0;
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleDiscreteInputs(int address, int count, bool discreteInputsArray[]) {
|
|
||||||
if (count < 1 || count > 2000) { // MODBUS limits: 1-2000 discrete inputs per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid discrete input count (1- allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_DISCRETE_INPUT, address, count);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Expected response: 9-byte header + ceil(count/8) bytes of data
|
|
||||||
int byteCount = (count + 7) / 8;
|
|
||||||
int expectedSize = 9 + byteCount;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_DISCRETE_INPUT)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Extract each discrete input bit from the data starting at index 9
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
int byteIndex = 9 + (i / 8);
|
|
||||||
int bitIndex = i % 8;
|
|
||||||
discreteInputsArray[i] = (commResponseBuffer[byteIndex] >> bitIndex) & 0x01;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readHoldingRegister(int address, uint16_t &holdingRegister) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_HOLDING_REGISTER, address, 1);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// For one register, expect 9-byte header + 2 bytes data = 11 bytes total
|
|
||||||
int expectedSize = 9 + 2;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_HOLDING_REGISTER)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Extract the register value (big-endian: data at indices 9 and 10)
|
|
||||||
holdingRegister = (commResponseBuffer[9] << 8) | commResponseBuffer[10];
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleHoldingRegisters(int address, int count, uint16_t holdingRegistersArray[]) {
|
|
||||||
if (count < 1 || count > 125) { // MODBUS limits: 1-125 registers per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid holding register count (1-125 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_HOLDING_REGISTER, address, count);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// For multiple registers: expected size = 9 + (count * 2)
|
|
||||||
int expectedSize = 9 + (count * 2);
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_HOLDING_REGISTER)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Extract each register value (each register is 2 bytes, big-endian)
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
holdingRegistersArray[i] = (commResponseBuffer[9 + (i * 2)] << 8) | commResponseBuffer[10 + (i * 2)];
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readInputRegister(int address, uint16_t &inputRegister) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_INPUT_REGISTER, address, 1);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// For one input register: expected size = 9 + 2 = 11 bytes
|
|
||||||
int expectedSize = 9 + 2;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_INPUT_REGISTER)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
inputRegister = (commResponseBuffer[9] << 8) | commResponseBuffer[10];
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleInputRegisters(int address, int count, uint16_t inputRegistersArray[]) {
|
|
||||||
if (count < 1 || count > 125) { // MODBUS limits: 1-125 registers per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid input register count (1-125 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildReadRequest(commRequestBuffer, ModbusFunction::READ_INPUT_REGISTER, address, count);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// For multiple registers: expected size = 9 + (count * 2)
|
|
||||||
int expectedSize = 9 + (count * 2);
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (commResponseBuffer[7] != static_cast<uint8_t>(ModbusFunction::READ_INPUT_REGISTER)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Extract each register value (each register is 2 bytes, big-endian)
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
inputRegistersArray[i] = (commResponseBuffer[9 + (i * 2)] << 8) | commResponseBuffer[10 + (i * 2)];
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeCoil(int address, bool value) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
buildWriteSingleRequest(commRequestBuffer, ModbusFunction::WRITE_SINGLE_COIL, address, value ? 0xFF00 : 0x0000);
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
if (!receiveResponse(commResponseBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
if (commRequestBuffer[i] != commResponseBuffer[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request!\n");
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeMultipleCoils(int address, int count, const bool values[]) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
|
|
||||||
if (count < 1 || count > numCoils) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid coil count (1-%d allowed)\n", numCoils);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each coil is a bit; compute number of bytes required.
|
|
||||||
int byteCount = (count + 7) / 8;
|
|
||||||
uint8_t coilData[byteCount];
|
|
||||||
// Initialize coilData to 0.
|
|
||||||
for (int i = 0; i < byteCount; i++) {
|
|
||||||
coilData[i] = 0;
|
|
||||||
}
|
|
||||||
// Pack each boolean value into the appropriate bit.
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
if (values[i]) {
|
|
||||||
coilData[i / 8] |= (1 << (i % 8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Build the write multiple coils request.
|
|
||||||
// Request size is 13 + byteCount.
|
|
||||||
int requestSize = 13 + byteCount;
|
|
||||||
buildWriteMultipleRequest(commRequestBuffer, ModbusFunction::WRITE_MULTIPLE_COILS, address, count, coilData, byteCount);
|
|
||||||
// Send the request.
|
|
||||||
if (!sendRequest(commRequestBuffer, requestSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The expected response size for a write multiple coils request is always 12 bytes.
|
|
||||||
int expectedResponseSize = 12;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedResponseSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that the response echoes the request correctly.
|
|
||||||
// According to MODBUS TCP, the response echoes the first 10 bytes (except byte 5, which is the length field).
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
if (i == 5) continue; // Skip the length field.
|
|
||||||
if (commRequestBuffer[i] != commResponseBuffer[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request at byte %d!\n", i);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeHoldingRegister(int address, uint16_t value) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
// Build the write single holding register request.
|
|
||||||
buildWriteSingleRequest(commRequestBuffer, ModbusFunction::WRITE_SINGLE_HOLDING_REGISTER, address, value);
|
|
||||||
// Send the 12-byte request.
|
|
||||||
if (!sendRequest(commRequestBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Expect a full 12-byte echo response.
|
|
||||||
if (!receiveResponse(commResponseBuffer, 12)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Validate that the entire 12-byte response matches the request.
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
if (commRequestBuffer[i] != commResponseBuffer[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request at byte %d!\n", i);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeMultipleHoldingRegisters(int address, int count, const uint16_t values[]) {
|
|
||||||
pthread_mutex_lock(&socketMutex);
|
|
||||||
if (count < 1 || count > numHoldingRegisters) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid register count (1-%d allowed)\n", numHoldingRegisters);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
// Each register is 2 bytes.
|
|
||||||
int byteCount = count * 2;
|
|
||||||
uint8_t registerData[byteCount];
|
|
||||||
// Convert each register value into big-endian format.
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
registerData[i * 2] = values[i] >> 8; // High byte
|
|
||||||
registerData[i * 2 + 1] = values[i] & 0xFF; // Low byte
|
|
||||||
}
|
|
||||||
// Build the write multiple holding registers request.
|
|
||||||
// Request size is 13 + byteCount.
|
|
||||||
int requestSize = 13 + byteCount;
|
|
||||||
buildWriteMultipleRequest(commRequestBuffer, ModbusFunction::WRITE_MULTIPLE_HOLDING_REGISTERS, address, count, registerData, byteCount);
|
|
||||||
// Send the request.
|
|
||||||
if (!sendRequest(commRequestBuffer, requestSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// The expected response size for a write multiple holding registers request is 12 bytes.
|
|
||||||
int expectedResponseSize = 12;
|
|
||||||
if (!receiveResponse(commResponseBuffer, expectedResponseSize)) {
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
// Validate the response: Compare the first 10 bytes, skipping byte 5 (length field).
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
if (i == 5) continue; // Skip the length field.
|
|
||||||
if (commRequestBuffer[i] != commResponseBuffer[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request at byte %d!\n", i);
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&socketMutex);
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readAll() {
|
|
||||||
// For brevity, call low-level functions that update internal storage.
|
|
||||||
ModbusError error = ModbusError::NONE;
|
|
||||||
if (coilsRead) {
|
|
||||||
error = readMultipleCoils(startCoils, numCoils, coilsRead);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
if (discreteInputs) {
|
|
||||||
error = readMultipleDiscreteInputs(startDiscreteInputs, numDiscreteInputs, discreteInputs);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
if (inputRegisters) {
|
|
||||||
error = readMultipleInputRegisters(startInputRegisters, numInputRegisters, inputRegisters);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
if (holdingRegistersRead) {
|
|
||||||
error = readMultipleHoldingRegisters(startHoldingRegisters, numHoldingRegisters, holdingRegistersRead);
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeAll() {
|
|
||||||
ModbusError error = ModbusError::NONE;
|
|
||||||
if (coilsWrite) {
|
|
||||||
error = writeMultipleCoils(startCoils, numCoils, coilsWrite);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
if (holdingRegistersWrite) {
|
|
||||||
error = writeMultipleHoldingRegisters(startHoldingRegisters, numHoldingRegisters, holdingRegistersWrite);
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setters for writing values (preferred to be used)
|
|
||||||
void ModbusTCPClient::setCoil(int address, bool value) {
|
|
||||||
if (address >= 0 && address < numCoils) {
|
|
||||||
coilsWrite[address] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::setHoldingRegister(int address, uint16_t value) {
|
|
||||||
if (address >= 0 && address < numHoldingRegisters) {
|
|
||||||
holdingRegistersWrite[address] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters (preferred to be used as long as readAll is being called)
|
|
||||||
bool ModbusTCPClient::getCoil(int address) const {
|
|
||||||
return (address >= 0 && address < numCoils) ? coilsRead[address] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::getDesiredCoil(int address) const {
|
|
||||||
return (address >= 0 && address < numCoils) ? coilsWrite[address] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::getDiscreteInput(int address) const {
|
|
||||||
return (address >= 0 && address < numDiscreteInputs) ? discreteInputs[address] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusTCPClient::getHoldingRegister(int address) const {
|
|
||||||
return (address >= 0 && address < numHoldingRegisters) ? holdingRegistersRead[address] : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusTCPClient::getDesiredHoldingRegister(int address) const {
|
|
||||||
return (address >= 0 && address < numHoldingRegisters) ? holdingRegistersWrite[address] : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusTCPClient::getInputRegister(int address) const {
|
|
||||||
return (address >= 0 && address < numInputRegisters) ? inputRegisters[address] : 0;
|
|
||||||
}
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
#ifndef MODBUS_TCP_CLIENT_H
|
|
||||||
#define MODBUS_TCP_CLIENT_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <pthread.h> // Use mutexes
|
|
||||||
|
|
||||||
// Enum class for MODBUS function codes
|
|
||||||
enum class ModbusFunction : uint8_t {
|
|
||||||
READ_COIL = 0x01,
|
|
||||||
READ_DISCRETE_INPUT = 0x02,
|
|
||||||
READ_HOLDING_REGISTER = 0x03,
|
|
||||||
READ_INPUT_REGISTER = 0x04,
|
|
||||||
WRITE_SINGLE_COIL = 0x05,
|
|
||||||
WRITE_SINGLE_HOLDING_REGISTER = 0x06,
|
|
||||||
WRITE_MULTIPLE_COILS = 0x0F,
|
|
||||||
WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enum class for error codes
|
|
||||||
enum class ModbusError {
|
|
||||||
NONE = 0,
|
|
||||||
TIMEOUT,
|
|
||||||
INVALID_RESPONSE,
|
|
||||||
CONNECTION_LOST,
|
|
||||||
EXCEPTION_RESPONSE,
|
|
||||||
INVALID_REQUEST
|
|
||||||
};
|
|
||||||
|
|
||||||
class ModbusTCPClient {
|
|
||||||
public:
|
|
||||||
// Constructor that allocates dynamic buffers based on provided counts and start addresses.
|
|
||||||
ModbusTCPClient(const char* ip, int port, int numCoils, int numDI, int numIR, int numHR,
|
|
||||||
int startCoils = 0, int startDI = 0, int startIR = 0, int startHR = 0);
|
|
||||||
// Simpler constructor for manual MODBUS function calls (without readAll/writeAll)
|
|
||||||
ModbusTCPClient(const char* ip, int port);
|
|
||||||
~ModbusTCPClient();
|
|
||||||
|
|
||||||
// Set start addresses for each type.
|
|
||||||
void setStartAddresses(int startCoils, int startDI, int startIR, int startHR);
|
|
||||||
|
|
||||||
// Connection functions
|
|
||||||
bool connectServer();
|
|
||||||
void disconnectServer();
|
|
||||||
bool isConnected() const;
|
|
||||||
bool reconnectServer();
|
|
||||||
|
|
||||||
// Set the timeout (in milliseconds) for receiving responses.
|
|
||||||
void setTimeout(int milliseconds);
|
|
||||||
|
|
||||||
// Getters and setters for data values (if using the automatic mode).
|
|
||||||
void setCoil(int address, bool value);
|
|
||||||
void setHoldingRegister(int address, uint16_t value);
|
|
||||||
bool getCoil(int address) const;
|
|
||||||
bool getDesiredCoil(int address) const;
|
|
||||||
bool getDiscreteInput(int address) const;
|
|
||||||
uint16_t getHoldingRegister(int address) const;
|
|
||||||
uint16_t getDesiredHoldingRegister(int address) const;
|
|
||||||
uint16_t getInputRegister(int address) const;
|
|
||||||
|
|
||||||
// High-level functions: readAll and writeAll update the internal buffers.
|
|
||||||
ModbusError readAll();
|
|
||||||
ModbusError writeAll();
|
|
||||||
|
|
||||||
// Low-level MODBUS functions (manual calls)
|
|
||||||
ModbusError readCoil(int address, bool &coilState);
|
|
||||||
ModbusError readMultipleCoils(int address, int count, bool coilStates[]);
|
|
||||||
ModbusError readDiscreteInput(int address, bool &discreteInput);
|
|
||||||
ModbusError readMultipleDiscreteInputs(int address, int count, bool discreteInputs[]);
|
|
||||||
ModbusError readHoldingRegister(int address, uint16_t &holdingRegister);
|
|
||||||
ModbusError readMultipleHoldingRegisters(int address, int count, uint16_t holdingRegisters[]);
|
|
||||||
ModbusError readInputRegister(int address, uint16_t &inputRegister);
|
|
||||||
ModbusError readMultipleInputRegisters(int address, int count, uint16_t inputRegisters[]);
|
|
||||||
|
|
||||||
ModbusError writeCoil(int address, bool value);
|
|
||||||
ModbusError writeMultipleCoils(int address, int count, const bool values[]);
|
|
||||||
ModbusError writeHoldingRegister(int address, uint16_t value);
|
|
||||||
ModbusError writeMultipleHoldingRegisters(int address, int count, const uint16_t values[]);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// TCP settings
|
|
||||||
const char* serverIP;
|
|
||||||
int serverPort;
|
|
||||||
int socketFD;
|
|
||||||
uint16_t transactionID;
|
|
||||||
int timeoutMilliseconds = 2000; // Default 2-second timeout
|
|
||||||
|
|
||||||
// Start addresses for each data type
|
|
||||||
int startCoils, startDiscreteInputs, startInputRegisters, startHoldingRegisters;
|
|
||||||
// Number of items for each type
|
|
||||||
int numCoils, numDiscreteInputs, numInputRegisters, numHoldingRegisters;
|
|
||||||
|
|
||||||
// Internal storage for automatic readAll()/writeAll() mode
|
|
||||||
bool* coilsRead; // Actual state from PLC
|
|
||||||
bool* coilsWrite; // Desired state to write
|
|
||||||
bool* discreteInputs; // Only read
|
|
||||||
uint16_t* inputRegisters; // Only read
|
|
||||||
uint16_t* holdingRegistersRead; // Actual values from PLC
|
|
||||||
uint16_t* holdingRegistersWrite; // Desired values to write
|
|
||||||
|
|
||||||
// --- Communication buffers (shared for both reading and writing)
|
|
||||||
uint8_t* commRequestBuffer; // Preallocated request buffer
|
|
||||||
uint8_t* commResponseBuffer; // Preallocated response buffer
|
|
||||||
int commRequestBufferSize; // Maximum request size needed
|
|
||||||
int commResponseBufferSize; // Maximum response size needed
|
|
||||||
|
|
||||||
// Global mutex to protect the TCP socket and communication buffers.
|
|
||||||
pthread_mutex_t socketMutex;
|
|
||||||
|
|
||||||
// Low-level communication functions using the shared buffers.
|
|
||||||
bool sendRequest(uint8_t* request, int requestSize);
|
|
||||||
bool receiveResponse(uint8_t* response, int expectedSize);
|
|
||||||
|
|
||||||
// Message building functions – they write into a provided buffer.
|
|
||||||
void buildReadRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t startAddr, uint16_t quantity);
|
|
||||||
void buildWriteSingleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t value);
|
|
||||||
void buildWriteMultipleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t count, const void* values, uint8_t byteCount);
|
|
||||||
|
|
||||||
// Helper functions to compute maximum buffer sizes.
|
|
||||||
int computeMaxReadResponseSize() const;
|
|
||||||
int computeMaxWriteRequestSize() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MODBUS_TCP_CLIENT_H
|
|
||||||
@ -1,757 +0,0 @@
|
|||||||
#include "modbus_tcp_client.h"
|
|
||||||
#include <sys/select.h> // For select() timeout
|
|
||||||
|
|
||||||
ModbusTCPClient::ModbusTCPClient(const char* ip, int port, int numCoils, int numDI, int numIR, int numHR,
|
|
||||||
int startCoils, int startDI, int startIR, int startHR)
|
|
||||||
: serverIP(ip), serverPort(port), socketFD(-1), transactionID(1),
|
|
||||||
numCoils(numCoils), numDiscreteInputs(numDI), numInputRegisters(numIR), numHoldingRegisters(numHR),
|
|
||||||
startCoils(startCoils), startDiscreteInputs(startDI), startInputRegisters(startIR), startHoldingRegisters(startHR) {
|
|
||||||
|
|
||||||
// Allocate memory dynamically based on provided sizes
|
|
||||||
coilsRead = new bool[numCoils]();
|
|
||||||
coilsWrite = new bool[numCoils]();
|
|
||||||
discreteInputs = new bool[numDiscreteInputs]();
|
|
||||||
|
|
||||||
inputRegisters = new uint16_t[numInputRegisters]();
|
|
||||||
holdingRegistersRead = new uint16_t[numHoldingRegisters]();
|
|
||||||
holdingRegistersWrite = new uint16_t[numHoldingRegisters]();
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusTCPClient::ModbusTCPClient(const char* ip, int port)
|
|
||||||
: serverIP(ip), serverPort(port), socketFD(-1) {
|
|
||||||
/*
|
|
||||||
This constructor is if the user manually wants to call MODBUS TCP functions and not
|
|
||||||
use readAll() and writeAll() and the setters/getters
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Set everything to nullptr so that readAll() and writeAll() won't work
|
|
||||||
coilsRead = nullptr;
|
|
||||||
coilsWrite = nullptr;
|
|
||||||
discreteInputs = nullptr;
|
|
||||||
|
|
||||||
inputRegisters = nullptr;
|
|
||||||
holdingRegistersRead = nullptr;
|
|
||||||
holdingRegistersWrite = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusTCPClient::~ModbusTCPClient() {
|
|
||||||
delete[] coilsRead;
|
|
||||||
delete[] coilsWrite;
|
|
||||||
delete[] discreteInputs;
|
|
||||||
|
|
||||||
delete[] inputRegisters;
|
|
||||||
delete[] holdingRegistersRead;
|
|
||||||
delete[] holdingRegistersWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::setStartAddresses(int startCoils, int startDI, int startIR, int startHR) {
|
|
||||||
this->startCoils = startCoils;
|
|
||||||
this->startDiscreteInputs = startDI;
|
|
||||||
this->startInputRegisters = startIR;
|
|
||||||
this->startHoldingRegisters = startHR;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::connectServer() {
|
|
||||||
// Step 1: If socket is already open, verify it's still connected
|
|
||||||
if (socketFD != -1) {
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = 0;
|
|
||||||
timeout.tv_usec = 100000; // 100ms timeout for connection check
|
|
||||||
|
|
||||||
fd_set read_fds;
|
|
||||||
FD_ZERO(&read_fds);
|
|
||||||
FD_SET(socketFD, &read_fds);
|
|
||||||
|
|
||||||
int result = select(socketFD + 1, &read_fds, NULL, NULL, &timeout);
|
|
||||||
|
|
||||||
if (result == 0) {
|
|
||||||
// No error in select, socket is still connected
|
|
||||||
printf("MODBUS_TCP_CLIENT: Already connected to MODBUS server\n");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// Connection is broken, close and reset
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Connection lost, reconnecting...\n");
|
|
||||||
disconnectServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Create a new socket
|
|
||||||
for (int attempts = 0; attempts < 5; attempts++) { // Retry up to 5 times
|
|
||||||
socketFD = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (socketFD < 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Could not create socket\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in serverAddr;
|
|
||||||
serverAddr.sin_family = AF_INET;
|
|
||||||
serverAddr.sin_port = htons(serverPort);
|
|
||||||
inet_pton(AF_INET, serverIP, &serverAddr.sin_addr);
|
|
||||||
|
|
||||||
// Step 3: Attempt to connect
|
|
||||||
printf("MODBUS_TCP_CLIENT: Attempting to connect to MODBUS server (Try %d)...\n", attempts + 1);
|
|
||||||
if (connect(socketFD, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == 0) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Connected to MODBUS server at %s:%d\n", serverIP, serverPort);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("MODBUS_TCP_CLIENT: Connection failed, retrying...\n");
|
|
||||||
disconnectServer(); // Close socket before retrying
|
|
||||||
usleep(100000); // Wait 100ms before retrying
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::disconnectServer() {
|
|
||||||
if (socketFD != -1) {
|
|
||||||
close(socketFD);
|
|
||||||
socketFD = -1;
|
|
||||||
printf("MODBUS_TCP_CLIENT: Disconnected from MODBUS server\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::setTimeout(int milliseconds) {
|
|
||||||
timeoutMilliseconds = milliseconds;
|
|
||||||
printf("MODBUS_TCP_CLIENT: Timeout set to %d ms\n", timeoutMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::isConnected() const {
|
|
||||||
return socketFD != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::reconnectServer() {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Attempting manual reconnection...\n");
|
|
||||||
disconnectServer();
|
|
||||||
return connectServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::sendRequest(uint8_t* request, int requestSize) {
|
|
||||||
// Ensure we're connected before sending
|
|
||||||
if (socketFD == -1) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Connection lost. Attempting to reconnect...\n");
|
|
||||||
if (!connectServer()) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Reconnection failed. Cannot send request.\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytesSent = write(socketFD, request, requestSize);
|
|
||||||
if (bytesSent <= 0) { // Detect broken connection during write
|
|
||||||
printf("MODBUS_TCP_CLIENT: Write failed, connection lost. Disconnecting...\n");
|
|
||||||
disconnectServer();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesSent == requestSize;
|
|
||||||
return bytesSent == requestSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::receiveResponse(uint8_t* response, int expectedSize) {
|
|
||||||
int totalBytesReceived = 0;
|
|
||||||
|
|
||||||
while (totalBytesReceived < expectedSize) {
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = timeoutMilliseconds / 1000; // Convert ms to seconds
|
|
||||||
timeout.tv_usec = (timeoutMilliseconds % 1000) * 1000; // Convert remaining ms to µs
|
|
||||||
|
|
||||||
fd_set read_fds;
|
|
||||||
FD_ZERO(&read_fds);
|
|
||||||
FD_SET(socketFD, &read_fds);
|
|
||||||
|
|
||||||
// Use select() to wait for data before reading
|
|
||||||
int ready = select(socketFD + 1, &read_fds, NULL, NULL, &timeout);
|
|
||||||
if (ready == 0) { // Timeout case
|
|
||||||
printf("MODBUS_TCP_CLIENT: Timeout waiting for MODBUS response. Disconnecting...\n");
|
|
||||||
disconnectServer(); // Close socket and reset socketFD
|
|
||||||
return false;
|
|
||||||
} else if (ready < 0) { // Select failed
|
|
||||||
printf("MODBUS_TCP_CLIENT: Select failed. Disconnecting...\n");
|
|
||||||
disconnectServer(); // Close socket and reset socketFD
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read available data
|
|
||||||
int bytesReceived = read(socketFD, response + totalBytesReceived, expectedSize - totalBytesReceived);
|
|
||||||
if (bytesReceived <= 0) { // Connection lost or no data received
|
|
||||||
printf("MODBUS_TCP_CLIENT: Connection lost. Disconnecting...\n");
|
|
||||||
disconnectServer(); // Close socket and reset socketFD
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalBytesReceived += bytesReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::buildReadRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t startAddr, uint16_t quantity) {
|
|
||||||
transactionID++;
|
|
||||||
|
|
||||||
// Transaction ID
|
|
||||||
buffer[0] = transactionID >> 8;
|
|
||||||
buffer[1] = transactionID & 0xFF;
|
|
||||||
// Protocol ID, always 00 00
|
|
||||||
buffer[2] = 0x00;
|
|
||||||
buffer[3] = 0x00;
|
|
||||||
// Length (remaining bytes after Unit ID)
|
|
||||||
buffer[4] = 0x00;
|
|
||||||
buffer[5] = 0x06;
|
|
||||||
// Unit ID (Slave Address)
|
|
||||||
buffer[6] = 0x01;
|
|
||||||
// Function code
|
|
||||||
buffer[7] = static_cast<uint8_t>(functionCode);
|
|
||||||
// Start Address
|
|
||||||
buffer[8] = startAddr >> 8;
|
|
||||||
buffer[9] = startAddr & 0xFF;
|
|
||||||
// Quantity (How many coils to read)
|
|
||||||
buffer[10] = quantity >> 8;
|
|
||||||
buffer[11] = quantity & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::buildWriteSingleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t value) {
|
|
||||||
transactionID++;
|
|
||||||
|
|
||||||
buffer[0] = transactionID >> 8;
|
|
||||||
buffer[1] = transactionID & 0xFF;
|
|
||||||
buffer[2] = 0x00;
|
|
||||||
buffer[3] = 0x00;
|
|
||||||
buffer[4] = 0x00;
|
|
||||||
buffer[5] = 0x06;
|
|
||||||
buffer[6] = 0x01;
|
|
||||||
buffer[7] = static_cast<uint8_t>(functionCode);
|
|
||||||
buffer[8] = address >> 8;
|
|
||||||
buffer[9] = address & 0xFF;
|
|
||||||
buffer[10] = value >> 8;
|
|
||||||
buffer[11] = value & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::buildWriteMultipleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t count, const void* values, uint8_t byteCount) {
|
|
||||||
transactionID++;
|
|
||||||
|
|
||||||
// MODBUS TCP Header
|
|
||||||
buffer[0] = transactionID >> 8;
|
|
||||||
buffer[1] = transactionID & 0xFF;
|
|
||||||
buffer[2] = 0x00; // Protocol ID (always 0x0000)
|
|
||||||
buffer[3] = 0x00;
|
|
||||||
buffer[4] = 0x00; // Length (remaining bytes after Unit ID)
|
|
||||||
buffer[5] = 7 + byteCount;
|
|
||||||
buffer[6] = 0x01; // Unit ID
|
|
||||||
buffer[7] = static_cast<uint8_t>(functionCode); // Function Code (0x0F for coils, 0x10 for registers)
|
|
||||||
buffer[8] = address >> 8; // Start address high byte
|
|
||||||
buffer[9] = address & 0xFF; // Start address low byte
|
|
||||||
buffer[10] = count >> 8; // Quantity high byte
|
|
||||||
buffer[11] = count & 0xFF; // Quantity low byte
|
|
||||||
buffer[12] = byteCount; // Byte count
|
|
||||||
|
|
||||||
// Copy data payload manually (instead of memcpy)
|
|
||||||
const uint8_t* data = (const uint8_t*)values;
|
|
||||||
for (uint8_t i = 0; i < byteCount; i++) {
|
|
||||||
buffer[13 + i] = data[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readCoil(int address, bool &coilState) {
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_COIL, address, 1);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
uint8_t response[10]; // Expecting 10 bytes
|
|
||||||
if (!receiveResponse(response, 10)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure function code matches the request (allowing for server behavior)
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_COIL)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_COIL), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract coil state (only first bit is used)
|
|
||||||
coilState = (response[9] & 0x01) != 0; // Adjusted index based on 10-byte response
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleCoils(int address, int count, bool coilStates[]) {
|
|
||||||
if (count < 1 || count > 2000) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid coil count (1-2000 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_COIL, address, count);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
// Expected response size: Fixed header (9) + variable byte count
|
|
||||||
int byteCount = (count + 7) / 8; // 1 byte per 8 coils
|
|
||||||
int expectedSize = 9 + byteCount;
|
|
||||||
|
|
||||||
uint8_t response[256]; // Ensure buffer is large enough
|
|
||||||
if (!receiveResponse(response, expectedSize)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the function code matches
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_COIL)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_COIL), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read coil values
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
int byteIndex = 9 + (i / 8); // Data starts at index 9
|
|
||||||
int bitIndex = i % 8;
|
|
||||||
coilStates[i] = (response[byteIndex] >> bitIndex) & 0x01;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readDiscreteInput(int address, bool &discreteInput) {
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_DISCRETE_INPUT, address, 1);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
uint8_t response[10]; // Expected response size
|
|
||||||
if (!receiveResponse(response, 10)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure function code matches the request
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_DISCRETE_INPUT)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_DISCRETE_INPUT), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract single discrete input state
|
|
||||||
discreteInput = (response[9] & 0x01) != 0;
|
|
||||||
return ModbusError::NONE; // Success
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleDiscreteInputs(int address, int count, bool discreteInputs[]) {
|
|
||||||
if (count < 1 || count > 2000) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid discrete input count (1-2000 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_DISCRETE_INPUT, address, count);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
// Expected response size: Fixed header (9) + variable byte count
|
|
||||||
int byteCount = (count + 7) / 8; // 1 byte per 8 inputs
|
|
||||||
int expectedSize = 9 + byteCount;
|
|
||||||
|
|
||||||
uint8_t response[256]; // Ensure buffer is large enough
|
|
||||||
if (!receiveResponse(response, expectedSize)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the function code matches
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_DISCRETE_INPUT)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_DISCRETE_INPUT), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read discrete input values
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
int byteIndex = 9 + (i / 8); // Data starts at index 9
|
|
||||||
int bitIndex = i % 8;
|
|
||||||
discreteInputs[i] = (response[byteIndex] >> bitIndex) & 0x01;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readHoldingRegister(int address, uint16_t &holdingRegister) {
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_HOLDING_REGISTER, address, 1);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
uint8_t response[11]; // Expected response size for reading 1 register
|
|
||||||
if (!receiveResponse(response, 11)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure function code matches the request
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_HOLDING_REGISTER)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_HOLDING_REGISTER), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract holding register value (Big-endian format)
|
|
||||||
holdingRegister = (response[9] << 8) | response[10];
|
|
||||||
return ModbusError::NONE; // Success
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleHoldingRegisters(int address, int count, uint16_t holdingRegisters[]) {
|
|
||||||
if (count < 1 || count > 125) { // MODBUS limits reading up to 125 registers per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid holding register count (1-125 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_HOLDING_REGISTER, address, count);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
// Expected response size: 9-byte header + 2 * count registers
|
|
||||||
int expectedSize = 9 + (count * 2);
|
|
||||||
uint8_t response[256]; // Ensure buffer is large enough
|
|
||||||
|
|
||||||
if (!receiveResponse(response, expectedSize)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure function code matches the request
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_HOLDING_REGISTER)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_HOLDING_REGISTER), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract register values (each register is 2 bytes, big-endian)
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
holdingRegisters[i] = (response[9 + (i * 2)] << 8) | response[10 + (i * 2)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readInputRegister(int address, uint16_t &inputRegister) {
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_INPUT_REGISTER, address, 1);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
uint8_t response[11]; // Expected response size for reading 1 register
|
|
||||||
if (!receiveResponse(response, 11)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure function code matches the request
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_INPUT_REGISTER)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_INPUT_REGISTER), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract input register value (Big-endian format)
|
|
||||||
inputRegister = (response[9] << 8) | response[10];
|
|
||||||
return ModbusError::NONE; // Success
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readMultipleInputRegisters(int address, int count, uint16_t inputRegisters[]) {
|
|
||||||
if (count < 1 || count > 125) { // MODBUS limits reading up to 125 registers per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid input register count (1-125 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t request[12];
|
|
||||||
buildReadRequest(request, ModbusFunction::READ_INPUT_REGISTER, address, count);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
// Expected response size: 9-byte header + 2 * count registers
|
|
||||||
int expectedSize = 9 + (count * 2);
|
|
||||||
uint8_t response[256]; // Ensure buffer is large enough
|
|
||||||
|
|
||||||
if (!receiveResponse(response, expectedSize)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle MODBUS exception responses (0x80 + function code)
|
|
||||||
if (response[7] & 0x80) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]);
|
|
||||||
return ModbusError::EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure function code matches the request
|
|
||||||
if (response[7] != static_cast<uint8_t>(ModbusFunction::READ_INPUT_REGISTER)) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n",
|
|
||||||
static_cast<uint8_t>(ModbusFunction::READ_INPUT_REGISTER), response[7]);
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract register values (each register is 2 bytes, big-endian)
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
inputRegisters[i] = (response[9 + (i * 2)] << 8) | response[10 + (i * 2)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeCoil(int address, bool value) {
|
|
||||||
uint8_t request[12];
|
|
||||||
buildWriteSingleRequest(request, ModbusFunction::WRITE_SINGLE_COIL, address, value ? 0xFF00 : 0x0000);
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
// Receive response
|
|
||||||
uint8_t response[12];
|
|
||||||
if (!receiveResponse(response, 12)) {
|
|
||||||
return ModbusError::TIMEOUT; // No response received
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if response matches request (MODBUS TCP should echo back the same request)
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
if (request[i] != response[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request!\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE; // Success
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeMultipleCoils(int address, int count, const bool values[]) {
|
|
||||||
if (count < 1 || count > 1968) { // MODBUS limit: max 1968 coils per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid coil count (1-1968 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int byteCount = (count + 7) / 8; // Each byte holds 8 coils
|
|
||||||
uint8_t coilData[byteCount];
|
|
||||||
|
|
||||||
// Initialize to 0 (since we may not use all bits)
|
|
||||||
for (int i = 0; i < byteCount; i++) {
|
|
||||||
coilData[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pack coil data into bytes
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
if (values[i]) {
|
|
||||||
coilData[i / 8] |= (1 << (i % 8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t request[13 + byteCount];
|
|
||||||
buildWriteMultipleRequest(request, ModbusFunction::WRITE_MULTIPLE_COILS, address, count, coilData, byteCount);
|
|
||||||
|
|
||||||
sendRequest(request, 13 + byteCount);
|
|
||||||
|
|
||||||
// The expected response is always **12 bytes**
|
|
||||||
uint8_t response[12];
|
|
||||||
if (!receiveResponse(response, 12)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the response matches the **first 10 bytes**, except for byte 5 (message length byte)
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
if (i == 5) continue; // Skip byte 5 (message length field)
|
|
||||||
|
|
||||||
if (request[i] != response[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request!\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE; // Success
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeHoldingRegister(int address, uint16_t value) {
|
|
||||||
uint8_t request[12];
|
|
||||||
buildWriteSingleRequest(request, ModbusFunction::WRITE_SINGLE_HOLDING_REGISTER, address, value);
|
|
||||||
|
|
||||||
sendRequest(request, 12);
|
|
||||||
|
|
||||||
// Expected response size = 12 bytes (full echo of the request)
|
|
||||||
uint8_t response[12];
|
|
||||||
if (!receiveResponse(response, 12)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure response matches request exactly (full 12 bytes)
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
if (request[i] != response[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request!\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE; // Success
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeMultipleHoldingRegisters(int address, int count, const uint16_t values[]) {
|
|
||||||
if (count < 1 || count > 123) { // MODBUS limit: max 123 registers per request
|
|
||||||
printf("MODBUS_TCP_CLIENT: Invalid register count (1-123 allowed)\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int byteCount = count * 2; // Each register is 2 bytes
|
|
||||||
uint8_t registerData[byteCount];
|
|
||||||
|
|
||||||
// Convert register values to byte array (big-endian format)
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
registerData[i * 2] = values[i] >> 8; // High byte
|
|
||||||
registerData[i * 2 + 1] = values[i] & 0xFF; // Low byte
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t request[13 + byteCount];
|
|
||||||
buildWriteMultipleRequest(request, ModbusFunction::WRITE_MULTIPLE_HOLDING_REGISTERS, address, count, registerData, byteCount);
|
|
||||||
|
|
||||||
sendRequest(request, 13 + byteCount);
|
|
||||||
|
|
||||||
// Expected response size = 12 bytes (first 10 bytes + 2 bytes for number of registers written)
|
|
||||||
uint8_t response[12];
|
|
||||||
if (!receiveResponse(response, 12)) {
|
|
||||||
return ModbusError::TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure response matches request (except for byte 5)
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
if (i == 5) continue; // Skip byte 5 (message length field)
|
|
||||||
|
|
||||||
if (request[i] != response[i]) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: Response does not match request!\n");
|
|
||||||
return ModbusError::INVALID_RESPONSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModbusError::NONE; // Success
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::readAll() {
|
|
||||||
/*
|
|
||||||
Reads every coil, discrete input, input register, and holding register
|
|
||||||
assuming that they start at address 0
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (coilsRead == nullptr && discreteInputs == nullptr &&
|
|
||||||
inputRegisters == nullptr && holdingRegistersRead == nullptr) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: readAll() called, but wrong constructor was used.\n");
|
|
||||||
return ModbusError::INVALID_REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError error = ModbusError::NONE;
|
|
||||||
if (coilsRead != nullptr) {
|
|
||||||
error = readMultipleCoils(startCoils, numCoils, coilsRead);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discreteInputs != nullptr) {
|
|
||||||
error = readMultipleDiscreteInputs(startDiscreteInputs, numDiscreteInputs, discreteInputs);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputRegisters != nullptr) {
|
|
||||||
error = readMultipleInputRegisters(startInputRegisters, numInputRegisters, inputRegisters);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (holdingRegistersRead != nullptr) {
|
|
||||||
error = readMultipleHoldingRegisters(startHoldingRegisters, numHoldingRegisters, holdingRegistersRead);
|
|
||||||
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError ModbusTCPClient::writeAll() {
|
|
||||||
/*
|
|
||||||
Reads every coil and holding register assuming that they start at address 0
|
|
||||||
*/
|
|
||||||
if (coilsWrite == nullptr && holdingRegistersWrite) {
|
|
||||||
printf("MODBUS_TCP_CLIENT: writeAll() called, but wrong constructor was used.\n");
|
|
||||||
return ModbusError::INVALID_REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusError error = ModbusError::NONE;
|
|
||||||
if (coilsWrite != nullptr) {
|
|
||||||
error = writeMultipleCoils(startCoils, numCoils, coilsWrite);
|
|
||||||
if (error != ModbusError::NONE) return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (holdingRegistersWrite != nullptr) {
|
|
||||||
error = writeMultipleHoldingRegisters(startHoldingRegisters, numHoldingRegisters, holdingRegistersWrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Setters for writing values (preferred to be used)
|
|
||||||
void ModbusTCPClient::setCoil(int address, bool value) {
|
|
||||||
if (address >= 0 && address < numCoils) {
|
|
||||||
coilsWrite[address] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusTCPClient::setHoldingRegister(int address, uint16_t value) {
|
|
||||||
if (address >= 0 && address < numHoldingRegisters) {
|
|
||||||
holdingRegistersWrite[address] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters (preferred to be used as long as readAll is being called)
|
|
||||||
bool ModbusTCPClient::getCoil(int address) const {
|
|
||||||
return (address >= 0 && address < numCoils) ? coilsRead[address] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::getDesiredCoil(int address) const {
|
|
||||||
return (address >= 0 && address < numCoils) ? coilsWrite[address] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusTCPClient::getDiscreteInput(int address) const {
|
|
||||||
return (address >= 0 && address < numDiscreteInputs) ? discreteInputs[address] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusTCPClient::getHoldingRegister(int address) const {
|
|
||||||
return (address >= 0 && address < numHoldingRegisters) ? holdingRegistersRead[address] : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusTCPClient::getDesiredHoldingRegister(int address) const {
|
|
||||||
return (address >= 0 && address < numHoldingRegisters) ? holdingRegistersWrite[address] : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusTCPClient::getInputRegister(int address) const {
|
|
||||||
return (address >= 0 && address < numInputRegisters) ? inputRegisters[address] : 0;
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
#ifndef MODBUS_TCP_CLIENT_H
|
|
||||||
#define MODBUS_TCP_CLIENT_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
// Enum class for MODBUS function codes
|
|
||||||
enum class ModbusFunction : uint8_t {
|
|
||||||
READ_COIL = 0x01,
|
|
||||||
READ_DISCRETE_INPUT = 0x02,
|
|
||||||
READ_HOLDING_REGISTER = 0x03,
|
|
||||||
READ_INPUT_REGISTER = 0x04,
|
|
||||||
WRITE_SINGLE_COIL = 0x05,
|
|
||||||
WRITE_SINGLE_HOLDING_REGISTER = 0x06,
|
|
||||||
WRITE_MULTIPLE_COILS = 0x0F,
|
|
||||||
WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ModbusError {
|
|
||||||
NONE = 0, // No error
|
|
||||||
TIMEOUT, // No response from server
|
|
||||||
INVALID_RESPONSE, // Response does not match request
|
|
||||||
CONNECTION_LOST, // Connection issue
|
|
||||||
EXCEPTION_RESPONSE, // MODBUS Exception response (error code)
|
|
||||||
INVALID_REQUEST, // User tries doing something they shouldn't
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class ModbusTCPClient {
|
|
||||||
public:
|
|
||||||
ModbusTCPClient(const char* ip, int port); // Extra constructor that does CANNOT use readAll() or writeAll()
|
|
||||||
ModbusTCPClient(const char* ip, int port, int numCoils, int numDI, int numIR, int numHR,
|
|
||||||
int startCoils = 0, int startDI = 0, int startIR = 0, int startHR = 0);
|
|
||||||
|
|
||||||
~ModbusTCPClient();
|
|
||||||
|
|
||||||
// Set the start address of each type. Either use this after creating the object or put them in the constructor
|
|
||||||
void setStartAddresses(int startCoils, int startDI, int startIR, int startHR);
|
|
||||||
|
|
||||||
bool connectServer();
|
|
||||||
void disconnectServer();
|
|
||||||
bool isConnected() const;
|
|
||||||
|
|
||||||
// Manually disconnectServer and return reconnectServer()
|
|
||||||
bool reconnectServer();
|
|
||||||
|
|
||||||
// Set the timeout for receiving responses
|
|
||||||
void setTimeout(int milliseconds);
|
|
||||||
|
|
||||||
// Setters (preferred to be used when calling writeAll())
|
|
||||||
void setCoil(int address, bool value);
|
|
||||||
void setHoldingRegister(int address, uint16_t value);
|
|
||||||
|
|
||||||
// Getters (preferred to be used when calling readAll())
|
|
||||||
bool getCoil(int address) const;
|
|
||||||
bool getDesiredCoil(int address) const; // Retrieves the "to be written" value
|
|
||||||
bool getDiscreteInput(int address) const;
|
|
||||||
uint16_t getHoldingRegister(int address) const;
|
|
||||||
uint16_t getDesiredHoldingRegister(int address) const; // Retrieves the "to be written" value
|
|
||||||
uint16_t getInputRegister(int address) const;
|
|
||||||
|
|
||||||
ModbusError readAll(); // Reads every coil, DI, IR, and HR
|
|
||||||
ModbusError writeAll(); // Writes every coil and HR
|
|
||||||
|
|
||||||
// Manual MODBUS TCP actions (not preferred to be called by user)
|
|
||||||
ModbusError readCoil(int address, bool &coilState);
|
|
||||||
ModbusError readMultipleCoils(int address, int count, bool coilStates[]);
|
|
||||||
|
|
||||||
ModbusError readDiscreteInput(int address, bool &discreteInput);
|
|
||||||
ModbusError readMultipleDiscreteInputs(int address, int count, bool discreteInputs[]);
|
|
||||||
|
|
||||||
ModbusError readHoldingRegister(int address, uint16_t &holdingRegister);
|
|
||||||
ModbusError readMultipleHoldingRegisters(int address, int count, uint16_t holdingRegisters[]);
|
|
||||||
|
|
||||||
ModbusError readInputRegister(int address, uint16_t &inputRegister);
|
|
||||||
ModbusError readMultipleInputRegisters(int address, int count, uint16_t inputRegisters[]);
|
|
||||||
|
|
||||||
ModbusError writeCoil(int address, bool value);
|
|
||||||
ModbusError writeMultipleCoils(int address, int count, const bool values[]);
|
|
||||||
|
|
||||||
ModbusError writeHoldingRegister(int address, uint16_t value);
|
|
||||||
ModbusError writeMultipleHoldingRegisters(int address, int count, const uint16_t values[]);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// TCP settings
|
|
||||||
const char* serverIP;
|
|
||||||
int serverPort;
|
|
||||||
int socketFD;
|
|
||||||
uint16_t transactionID;
|
|
||||||
int timeoutMilliseconds = 2000; // Default 2 second timeout on receiving responses
|
|
||||||
|
|
||||||
// Storing MODBUS register information
|
|
||||||
int numCoils, numDiscreteInputs, numInputRegisters, numHoldingRegisters;
|
|
||||||
int startCoils, startDiscreteInputs, startInputRegisters, startHoldingRegisters; // The start address of each type of register
|
|
||||||
|
|
||||||
bool* coilsRead; // Stores the actual state of coils on the MODBUS server
|
|
||||||
bool* coilsWrite; // Stores the desired state of coils to be written
|
|
||||||
bool* discreteInputs; // Only read from the MODBUS server (no writes)
|
|
||||||
|
|
||||||
uint16_t* inputRegisters; // Only read from the MODBUS server
|
|
||||||
uint16_t* holdingRegistersRead; // Stores actual values from the MODBUS server
|
|
||||||
uint16_t* holdingRegistersWrite; // Stores desired values to write
|
|
||||||
|
|
||||||
bool sendRequest(uint8_t* request, int requestSize);
|
|
||||||
bool receiveResponse(uint8_t* response, int expectedSize);
|
|
||||||
|
|
||||||
void buildReadRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t startAddr, uint16_t quantity);
|
|
||||||
void buildWriteSingleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t value);
|
|
||||||
void buildWriteMultipleRequest(uint8_t* buffer, ModbusFunction functionCode, uint16_t address, uint16_t count, const void* values, uint8_t byteCount);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MODBUS_TCP_CLIENT_H
|
|
||||||
Loading…
Reference in New Issue
Block a user