From 7feff1dc5aebafc8256c6e08bfb30688ca2fbcf2 Mon Sep 17 00:00:00 2001 From: judsonupchurch Date: Sat, 14 Jun 2025 14:51:43 -0500 Subject: [PATCH] Added a 3rd version of the code. Need to check which is which --- modbus_tcp_client.cpp | 380 ++++++++++++----- modbus_tcp_client.h | 12 +- modbus_tcp_client_old.cpp | 741 ++++++++++++++++----------------- modbus_tcp_client_old.h | 90 ++-- modbus_tcp_client_old_old.cpp | 757 ++++++++++++++++++++++++++++++++++ modbus_tcp_client_old_old.h | 115 ++++++ 6 files changed, 1580 insertions(+), 515 deletions(-) create mode 100644 modbus_tcp_client_old_old.cpp create mode 100644 modbus_tcp_client_old_old.h diff --git a/modbus_tcp_client.cpp b/modbus_tcp_client.cpp index 68f86e6..d52884e 100644 --- a/modbus_tcp_client.cpp +++ b/modbus_tcp_client.cpp @@ -1,5 +1,4 @@ #include "modbus_tcp_client.h" -#include // --- Helper: Compute maximum read response size based on configured counts. int ModbusTCPClient::computeMaxReadResponseSize() const { @@ -33,12 +32,13 @@ ModbusTCPClient::ModbusTCPClient(const char* ip, int port, int numCoils, int num 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](); + coilsRead = (numCoils > 0) ? new bool[numCoils]() : nullptr; + coilsWrite = (numCoils > 0) ? new bool[numCoils]() : nullptr; + discreteInputs = (numDI > 0) ? new bool[numDI]() : nullptr; + inputRegisters = (numIR > 0) ? new uint16_t[numIR]() : nullptr; + holdingRegistersRead = (numHR > 0) ? new uint16_t[numHR]() : nullptr; + holdingRegistersWrite = (numHR > 0) ? new uint16_t[numHR]() : nullptr; + // Allocate shared communication buffers. commRequestBufferSize = computeMaxWriteRequestSize(); // Worst-case request size. @@ -46,8 +46,12 @@ ModbusTCPClient::ModbusTCPClient(const char* ip, int port, int numCoils, int num commRequestBuffer = new uint8_t[commRequestBufferSize]; commResponseBuffer = new uint8_t[commResponseBufferSize]; - // Initialize the socket mutex. - pthread_mutex_init(&socketMutex, NULL); + // Initialize the socket mutex with a recursive attribute. + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&socketMutex, &attr); + pthread_mutexattr_destroy(&attr); } ModbusTCPClient::ModbusTCPClient(const char* ip, int port) @@ -66,8 +70,12 @@ ModbusTCPClient::ModbusTCPClient(const char* ip, int port) commRequestBuffer = new uint8_t[commRequestBufferSize]; commResponseBuffer = new uint8_t[commResponseBufferSize]; - // Initialize the socket mutex. - pthread_mutex_init(&socketMutex, NULL); + // Initialize the socket mutex with a recursive attribute. + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&socketMutex, &attr); + pthread_mutexattr_destroy(&attr); } ModbusTCPClient::~ModbusTCPClient() { @@ -93,46 +101,134 @@ void ModbusTCPClient::setStartAddresses(int startCoils, int startDI, int startIR } // --- 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"); +// disconnectServer(); +// usleep(timeoutMilliseconds*1000); +// } +// //pthread_mutex_unlock(&socketMutex); +// return false; +// } + bool ModbusTCPClient::connectServer() { - pthread_mutex_lock(&socketMutex); + // If already connected, return true. if (socketFD != -1) { - pthread_mutex_unlock(&socketMutex); return true; } + for (int attempts = 0; attempts < 5; attempts++) { + // Create a new socket. 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; } + + // Set the socket to non-blocking mode. + int flags = fcntl(socketFD, F_GETFL, 0); + if (flags < 0) { + printf("MODBUS_TCP_CLIENT: fcntl F_GETFL"); + disconnectServer(); + return false; + } + if (fcntl(socketFD, F_SETFL, flags | O_NONBLOCK) < 0) { + printf("MODBUS_TCP_CLIENT: fcntl F_SETFL O_NONBLOCK"); + disconnectServer(); + return false; + } + + // Prepare the server address. 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); + int res = connect(socketFD, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); + if (res < 0) { + if (errno == EINPROGRESS) { + // Connection is in progress, use select() to wait. + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(socketFD, &wfds); + + struct timeval tv; + tv.tv_sec = timeoutMilliseconds / 1000; + tv.tv_usec = (timeoutMilliseconds % 1000) * 1000; + + int sel = select(socketFD + 1, NULL, &wfds, NULL, &tv); + if (sel > 0) { + int so_error = 0; + socklen_t len = sizeof(so_error); + if (getsockopt(socketFD, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) { + printf("MODBUS_TCP_CLIENT: getsockopt failed\n"); + disconnectServer(); + continue; + } + if (so_error == 0) { + // Connected successfully. Restore blocking mode. + flags = fcntl(socketFD, F_GETFL, 0); + fcntl(socketFD, F_SETFL, flags & ~O_NONBLOCK); + printf("MODBUS_TCP_CLIENT: Connected to %s:%d\n", serverIP, serverPort); + return true; + } else { + printf("MODBUS_TCP_CLIENT: Connect failed with error %d\n", so_error); + } + } else if (sel == 0) { + printf("MODBUS_TCP_CLIENT: Connect select timeout\n"); + } else { + printf("MODBUS_TCP_CLIENT: Select during connect failed\n"); + } + } else { + printf("MODBUS_TCP_CLIENT: Connect error\n"); + } + } else { + // Unexpected immediate success in non-blocking mode. + flags = fcntl(socketFD, F_GETFL, 0); + fcntl(socketFD, F_SETFL, flags & ~O_NONBLOCK); + printf("MODBUS_TCP_CLIENT: Connected immediately to %s:%d\n", serverIP, serverPort); return true; } + + // If connection failed, disconnect and wait before retrying. printf("MODBUS_TCP_CLIENT: Connection failed, retrying...\n"); - pthread_mutex_unlock(&socketMutex); disconnectServer(); - usleep(100000); + usleep(timeoutMilliseconds * 1000); } - pthread_mutex_unlock(&socketMutex); return false; } void ModbusTCPClient::disconnectServer() { - pthread_mutex_lock(&socketMutex); + ////pthread_mutex_lock(&socketMutex); if (socketFD != -1) { + shutdown(socketFD, SHUT_RDWR); close(socketFD); socketFD = -1; printf("MODBUS_TCP_CLIENT: Disconnected from server\n"); } - pthread_mutex_unlock(&socketMutex); + //thread_mutex_unlock(&socketMutex); } bool ModbusTCPClient::isConnected() const { @@ -150,7 +246,7 @@ void ModbusTCPClient::setTimeout(int milliseconds) { } bool ModbusTCPClient::sendRequest(uint8_t* request, int requestSize) { - if (socketFD == -1) { + if (!isConnected()) { printf("MODBUS_TCP_CLIENT: Not connected. Cannot send request.\n"); return false; } @@ -182,6 +278,11 @@ bool ModbusTCPClient::receiveResponse(uint8_t* response, int expectedSize) { disconnectServer(); return false; } + // Make sure we are still connected before we do a read to prevent a hardfault + if (!isConnected()) { + printf("MODBUS_TCP_CLIENT: Connection lost while reading. Disconnecting...\n"); + return false; + } int bytesReceived = read(socketFD, response + totalBytesReceived, expectedSize - totalBytesReceived); if (bytesReceived <= 0) { printf("MODBUS_TCP_CLIENT: Connection lost while reading. Disconnecting...\n"); @@ -262,33 +363,33 @@ void ModbusTCPClient::buildWriteMultipleRequest(uint8_t* buffer, ModbusFunction // --- High-Level Read/Write Functions --- ModbusError ModbusTCPClient::readCoil(int address, bool &coilState) { - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildReadRequest(commRequestBuffer, ModbusFunction::READ_COIL, address, 1); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } // Ensure the function code in the response matches the request. if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_COIL)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } @@ -297,28 +398,28 @@ ModbusError ModbusTCPClient::readMultipleCoils(int address, int count, bool coil printf("MODBUS_TCP_CLIENT: Invalid coil count (1-2000 allowed)\n"); return ModbusError::INVALID_RESPONSE; } - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildReadRequest(commRequestBuffer, ModbusFunction::READ_COIL, address, count); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } // Ensure the function code in the response matches the request. if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_COIL)) { - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } // Extract coil values: data starts at index 9. @@ -327,38 +428,38 @@ ModbusError ModbusTCPClient::readMultipleCoils(int address, int count, bool coil int bitIndex = i % 8; coilStates[i] = (commResponseBuffer[byteIndex] >> bitIndex) & 0x01; } - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::readDiscreteInput(int address, bool &discreteInput) { - pthread_mutex_lock(&socketMutex); + //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); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } // Validate function code if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_DISCRETE_INPUT)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } @@ -367,26 +468,26 @@ ModbusError ModbusTCPClient::readMultipleDiscreteInputs(int address, int count, printf("MODBUS_TCP_CLIENT: Invalid discrete input count (1- allowed)\n"); return ModbusError::INVALID_RESPONSE; } - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildReadRequest(commRequestBuffer, ModbusFunction::READ_DISCRETE_INPUT, address, count); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_DISCRETE_INPUT)) { - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } // Extract each discrete input bit from the data starting at index 9 @@ -395,35 +496,35 @@ ModbusError ModbusTCPClient::readMultipleDiscreteInputs(int address, int count, int bitIndex = i % 8; discreteInputsArray[i] = (commResponseBuffer[byteIndex] >> bitIndex) & 0x01; } - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::readHoldingRegister(int address, uint16_t &holdingRegister) { - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildReadRequest(commRequestBuffer, ModbusFunction::READ_HOLDING_REGISTER, address, 1); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_HOLDING_REGISTER)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } @@ -432,59 +533,59 @@ ModbusError ModbusTCPClient::readMultipleHoldingRegisters(int address, int count printf("MODBUS_TCP_CLIENT: Invalid holding register count (1-125 allowed)\n"); return ModbusError::INVALID_RESPONSE; } - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildReadRequest(commRequestBuffer, ModbusFunction::READ_HOLDING_REGISTER, address, count); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_HOLDING_REGISTER)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::readInputRegister(int address, uint16_t &inputRegister) { - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildReadRequest(commRequestBuffer, ModbusFunction::READ_INPUT_REGISTER, address, 1); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_INPUT_REGISTER)) { - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } inputRegister = (commResponseBuffer[9] << 8) | commResponseBuffer[10]; - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } @@ -493,63 +594,63 @@ ModbusError ModbusTCPClient::readMultipleInputRegisters(int address, int count, printf("MODBUS_TCP_CLIENT: Invalid input register count (1-125 allowed)\n"); return ModbusError::INVALID_RESPONSE; } - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildReadRequest(commRequestBuffer, ModbusFunction::READ_INPUT_REGISTER, address, count); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_INPUT_REGISTER)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::writeCoil(int address, bool value) { - pthread_mutex_lock(&socketMutex); + //pthread_mutex_lock(&socketMutex); buildWriteSingleRequest(commRequestBuffer, ModbusFunction::WRITE_SINGLE_COIL, address, value ? 0xFF00 : 0x0000); if (!sendRequest(commRequestBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::TIMEOUT; } if (!receiveResponse(commResponseBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } } - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::writeMultipleCoils(int address, int count, const bool values[]) { - pthread_mutex_lock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } @@ -572,14 +673,14 @@ ModbusError ModbusTCPClient::writeMultipleCoils(int address, int count, const bo buildWriteMultipleRequest(commRequestBuffer, ModbusFunction::WRITE_MULTIPLE_COILS, address, count, coilData, byteCount); // Send the request. if (!sendRequest(commRequestBuffer, requestSize)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::TIMEOUT; } @@ -589,45 +690,45 @@ ModbusError ModbusTCPClient::writeMultipleCoils(int address, int count, const bo 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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } } - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::writeHoldingRegister(int address, uint16_t value) { - pthread_mutex_lock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::TIMEOUT; } // Expect a full 12-byte echo response. if (!receiveResponse(commResponseBuffer, 12)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } } - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::writeMultipleHoldingRegisters(int address, int count, const uint16_t values[]) { - pthread_mutex_lock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } // Each register is 2 bytes. @@ -644,13 +745,13 @@ ModbusError ModbusTCPClient::writeMultipleHoldingRegisters(int address, int coun buildWriteMultipleRequest(commRequestBuffer, ModbusFunction::WRITE_MULTIPLE_HOLDING_REGISTERS, address, count, registerData, byteCount); // Send the request. if (!sendRequest(commRequestBuffer, requestSize)) { - pthread_mutex_unlock(&socketMutex); + //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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::TIMEOUT; } // Validate the response: Compare the first 10 bytes, skipping byte 5 (length field). @@ -658,16 +759,24 @@ ModbusError ModbusTCPClient::writeMultipleHoldingRegisters(int address, int coun 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); + //pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } } - pthread_mutex_unlock(&socketMutex); + //pthread_mutex_unlock(&socketMutex); return ModbusError::NONE; } ModbusError ModbusTCPClient::readAll() { // For brevity, call low-level functions that update internal storage. + printf("readAll()\n"); + if (!isConnected()) { + printf("MODBUS_TCP_CLIENT: Not connected when readAll() called. Connecting..."); + if (connectServer()) { + printf("MODBUS_TCP_CLIENT: Failed to connect to MODBUS server\n"); + } + return ModbusError::CONNECTION_LOST; + } ModbusError error = ModbusError::NONE; if (coilsRead) { error = readMultipleCoils(startCoils, numCoils, coilsRead); @@ -688,6 +797,13 @@ ModbusError ModbusTCPClient::readAll() { } ModbusError ModbusTCPClient::writeAll() { + if (!isConnected()) { + printf("MODBUS_TCP_CLIENT: Not connected when readAll() called. Connecting..."); + if (connectServer()) { + printf("MODBUS_TCP_CLIENT: Failed to connect to MODBUS server\n"); + } + return ModbusError::CONNECTION_LOST; + } ModbusError error = ModbusError::NONE; if (coilsWrite) { error = writeMultipleCoils(startCoils, numCoils, coilsWrite); @@ -735,4 +851,80 @@ uint16_t ModbusTCPClient::getDesiredHoldingRegister(int address) const { uint16_t ModbusTCPClient::getInputRegister(int address) const { return (address >= 0 && address < numInputRegisters) ? inputRegisters[address] : 0; -} \ No newline at end of file +} + +ModbusError ModbusTCPClient::getMultipleDiscreteInputs(int startAddress, int count, bool* destination) const { + // Validate inputs + if (startAddress < 0 || count <= 0 || destination == nullptr) { + return ModbusError::INVALID_REQUEST; + } + + // Check if the range is within bounds + if (startAddress + count > numDiscreteInputs) { + return ModbusError::INVALID_REQUEST; + } + + // Copy the data manually + for (int i = 0; i < count; ++i) { + destination[i] = discreteInputs[startAddress + i]; + } + + return ModbusError::NONE; +} + +ModbusError ModbusTCPClient::getMultipleCoils(int startAddress, int count, bool* destination) const { + // Validate inputs + if (startAddress < 0 || count <= 0 || destination == nullptr) { + return ModbusError::INVALID_REQUEST; + } + + // Check if the range is within bounds + if (startAddress + count > numCoils) { + return ModbusError::INVALID_REQUEST; + } + + // Copy the data manually + for (int i = 0; i < count; ++i) { + destination[i] = coilsRead[startAddress + i]; + } + + return ModbusError::NONE; +} + +ModbusError ModbusTCPClient::getMultipleInputRegisters(int startAddress, int count, uint16_t* destination) const { + // Validate inputs + if (startAddress < 0 || count <= 0 || destination == nullptr) { + return ModbusError::INVALID_REQUEST; + } + + // Check if the range is within bounds + if (startAddress + count > numInputRegisters) { + return ModbusError::INVALID_REQUEST; + } + + // Copy the data manually + for (int i = 0; i < count; ++i) { + destination[i] = inputRegisters[startAddress + i]; + } + + return ModbusError::NONE; +} + +ModbusError ModbusTCPClient::getMultipleHoldingRegisters(int startAddress, int count, uint16_t* destination) const { + // Validate inputs + if (startAddress < 0 || count <= 0 || destination == nullptr) { + return ModbusError::INVALID_REQUEST; + } + + // Check if the range is within bounds + if (startAddress + count > numHoldingRegisters) { + return ModbusError::INVALID_REQUEST; + } + + // Copy the data manually + for (int i = 0; i < count; ++i) { + destination[i] = holdingRegistersRead[startAddress + i]; + } + + return ModbusError::NONE; +} diff --git a/modbus_tcp_client.h b/modbus_tcp_client.h index ce03591..c7c091f 100644 --- a/modbus_tcp_client.h +++ b/modbus_tcp_client.h @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include // Use mutexes // Enum class for MODBUS function codes @@ -54,12 +56,18 @@ public: // 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 getDiscreteInput(int address) const; bool getCoil(int address) const; bool getDesiredCoil(int address) const; - bool getDiscreteInput(int address) const; + uint16_t getInputRegister(int address) const; uint16_t getHoldingRegister(int address) const; uint16_t getDesiredHoldingRegister(int address) const; - uint16_t getInputRegister(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; // High-level functions: readAll and writeAll update the internal buffers. ModbusError readAll(); diff --git a/modbus_tcp_client_old.cpp b/modbus_tcp_client_old.cpp index 101c3a3..68f86e6 100644 --- a/modbus_tcp_client_old.cpp +++ b/modbus_tcp_client_old.cpp @@ -1,47 +1,88 @@ #include "modbus_tcp_client.h" -#include // For select() timeout +#include +// --- 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) { + numCoils(numCoils), numDiscreteInputs(numDI), numInputRegisters(numIR), numHoldingRegisters(numHR), + startCoils(startCoils), startDiscreteInputs(startDI), startInputRegisters(startIR), startHoldingRegisters(startHR) { - // Allocate memory dynamically based on provided sizes + // Allocate internal storage for automatic readAll()/writeAll() mode. coilsRead = new bool[numCoils](); coilsWrite = new bool[numCoils](); - discreteInputs = new bool[numDiscreteInputs](); + discreteInputs = new bool[numDI](); + inputRegisters = new uint16_t[numIR](); + holdingRegistersRead = new uint16_t[numHR](); + holdingRegistersWrite = new uint16_t[numHR](); - inputRegisters = new uint16_t[numInputRegisters](); - holdingRegistersRead = new uint16_t[numHoldingRegisters](); - holdingRegistersWrite = new uint16_t[numHoldingRegisters](); + // 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) { - /* - 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 + : 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) { @@ -51,67 +92,47 @@ void ModbusTCPClient::setStartAddresses(int startCoils, int startDI, int startIR this->startHoldingRegisters = startHR; } +// --- Connection Functions --- bool ModbusTCPClient::connectServer() { - // Step 1: If socket is already open, verify it's still connected + pthread_mutex_lock(&socketMutex); 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(); - } + pthread_mutex_unlock(&socketMutex); + return true; } - - // Step 2: Create a new socket - for (int attempts = 0; attempts < 5; attempts++) { // Retry up to 5 times + 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); - - // Step 3: Attempt to connect - printf("MODBUS_TCP_CLIENT: Attempting to connect to MODBUS server (Try %d)...\n", attempts + 1); + 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 MODBUS server at %s:%d\n", serverIP, serverPort); + 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"); - disconnectServer(); // Close socket before retrying - usleep(100000); // Wait 100ms before retrying + 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 MODBUS server\n"); + printf("MODBUS_TCP_CLIENT: Disconnected from server\n"); } -} - -void ModbusTCPClient::setTimeout(int milliseconds) { - timeoutMilliseconds = milliseconds; - printf("MODBUS_TCP_CLIENT: Timeout set to %d ms\n", timeoutMilliseconds); + pthread_mutex_unlock(&socketMutex); } bool ModbusTCPClient::isConnected() const { @@ -119,70 +140,60 @@ bool ModbusTCPClient::isConnected() const { } 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; - } - } +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) { // Detect broken connection during write - printf("MODBUS_TCP_CLIENT: Write failed, connection lost. Disconnecting...\n"); + if (bytesSent <= 0) { + printf("MODBUS_TCP_CLIENT: Write failed, 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 - + timeout.tv_sec = timeoutMilliseconds / 1000; + timeout.tv_usec = (timeoutMilliseconds % 1000) * 1000; 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 + if (ready == 0) { + printf("MODBUS_TCP_CLIENT: Timeout waiting for response. Disconnecting...\n"); + disconnectServer(); return false; - } else if (ready < 0) { // Select failed + } else if (ready < 0) { printf("MODBUS_TCP_CLIENT: Select failed. Disconnecting...\n"); - disconnectServer(); // Close socket and reset socketFD + disconnectServer(); 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 + 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++; @@ -202,14 +213,13 @@ void ModbusTCPClient::buildReadRequest(uint8_t* buffer, ModbusFunction functionC // Start Address buffer[8] = startAddr >> 8; buffer[9] = startAddr & 0xFF; - // Quantity (How many coils to read) + // 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; @@ -249,475 +259,446 @@ void ModbusTCPClient::buildWriteMultipleRequest(uint8_t* buffer, ModbusFunction } } +// --- High-Level Read/Write Functions --- + 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)) { + 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 (response[7] & 0x80) { - printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]); + if (commResponseBuffer[7] & 0x80) { + printf("MODBUS_TCP_CLIENT: Exception Code %02X\n", commResponseBuffer[8]); + pthread_mutex_unlock(&socketMutex); return ModbusError::EXCEPTION_RESPONSE; } - - // Ensure function code matches the request (allowing for server behavior) - if (response[7] != static_cast(ModbusFunction::READ_COIL)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_COIL), response[7]); + // Ensure the function code in the response matches the request. + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_COIL)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Extract coil state (only first bit is used) - coilState = (response[9] & 0x01) != 0; // Adjusted index based on 10-byte 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) { + 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; } - - 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)) { + 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 (response[7] & 0x80) { - printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]); + 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 matches - if (response[7] != static_cast(ModbusFunction::READ_COIL)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_COIL), response[7]); + // Ensure the function code in the response matches the request. + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_COIL)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Read coil values + // Extract coil values: data starts at index 9. for (int i = 0; i < count; i++) { - int byteIndex = 9 + (i / 8); // Data starts at index 9 + int byteIndex = 9 + (i / 8); int bitIndex = i % 8; - coilStates[i] = (response[byteIndex] >> bitIndex) & 0x01; + coilStates[i] = (commResponseBuffer[byteIndex] >> bitIndex) & 0x01; } - + pthread_mutex_unlock(&socketMutex); 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)) { + 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; } - - // Handle MODBUS exception responses (0x80 + function code) - if (response[7] & 0x80) { - printf("MODBUS_TCP_CLIENT: MODBUS Exception Code %02X\n", response[8]); + // 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; } - - // Ensure function code matches the request - if (response[7] != static_cast(ModbusFunction::READ_DISCRETE_INPUT)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_DISCRETE_INPUT), response[7]); + // Validate function code + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_DISCRETE_INPUT)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Extract single discrete input state - discreteInput = (response[9] & 0x01) != 0; - return ModbusError::NONE; // Success + // 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 discreteInputs[]) { - if (count < 1 || count > 2000) { - printf("MODBUS_TCP_CLIENT: Invalid discrete input count (1-2000 allowed)\n"); +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; } - - 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)) { + pthread_mutex_lock(&socketMutex); + buildReadRequest(commRequestBuffer, ModbusFunction::READ_DISCRETE_INPUT, address, count); + if (!sendRequest(commRequestBuffer, 12)) { + pthread_mutex_unlock(&socketMutex); 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]); + // 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; } - - // Ensure the function code matches - if (response[7] != static_cast(ModbusFunction::READ_DISCRETE_INPUT)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_DISCRETE_INPUT), response[7]); + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_DISCRETE_INPUT)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Read discrete input values + // Extract each discrete input bit from the data starting at index 9 for (int i = 0; i < count; i++) { - int byteIndex = 9 + (i / 8); // Data starts at index 9 + int byteIndex = 9 + (i / 8); int bitIndex = i % 8; - discreteInputs[i] = (response[byteIndex] >> bitIndex) & 0x01; + discreteInputsArray[i] = (commResponseBuffer[byteIndex] >> bitIndex) & 0x01; } - + pthread_mutex_unlock(&socketMutex); 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)) { + pthread_mutex_lock(&socketMutex); + buildReadRequest(commRequestBuffer, ModbusFunction::READ_HOLDING_REGISTER, address, 1); + if (!sendRequest(commRequestBuffer, 12)) { + pthread_mutex_unlock(&socketMutex); 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]); + // 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; } - - // Ensure function code matches the request - if (response[7] != static_cast(ModbusFunction::READ_HOLDING_REGISTER)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_HOLDING_REGISTER), response[7]); + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_HOLDING_REGISTER)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Extract holding register value (Big-endian format) - holdingRegister = (response[9] << 8) | response[10]; - return ModbusError::NONE; // Success + // 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 holdingRegisters[]) { - if (count < 1 || count > 125) { // MODBUS limits reading up to 125 registers per request +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; } - - 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)) { + pthread_mutex_lock(&socketMutex); + buildReadRequest(commRequestBuffer, ModbusFunction::READ_HOLDING_REGISTER, address, count); + if (!sendRequest(commRequestBuffer, 12)) { + pthread_mutex_unlock(&socketMutex); 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]); + // 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; } - - // Ensure function code matches the request - if (response[7] != static_cast(ModbusFunction::READ_HOLDING_REGISTER)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_HOLDING_REGISTER), response[7]); + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_HOLDING_REGISTER)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Extract register values (each register is 2 bytes, big-endian) + // Extract each register value (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)]; + 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) { - 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)) { + pthread_mutex_lock(&socketMutex); + buildReadRequest(commRequestBuffer, ModbusFunction::READ_INPUT_REGISTER, address, 1); + if (!sendRequest(commRequestBuffer, 12)) { + pthread_mutex_unlock(&socketMutex); 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]); + // 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; } - - // Ensure function code matches the request - if (response[7] != static_cast(ModbusFunction::READ_INPUT_REGISTER)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_INPUT_REGISTER), response[7]); + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_INPUT_REGISTER)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Extract input register value (Big-endian format) - inputRegister = (response[9] << 8) | response[10]; - return ModbusError::NONE; // Success + inputRegister = (commResponseBuffer[9] << 8) | commResponseBuffer[10]; + pthread_mutex_unlock(&socketMutex); + return ModbusError::NONE; } -ModbusError ModbusTCPClient::readMultipleInputRegisters(int address, int count, uint16_t inputRegisters[]) { - if (count < 1 || count > 125) { // MODBUS limits reading up to 125 registers per request +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; } - - 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)) { + pthread_mutex_lock(&socketMutex); + buildReadRequest(commRequestBuffer, ModbusFunction::READ_INPUT_REGISTER, address, count); + if (!sendRequest(commRequestBuffer, 12)) { + pthread_mutex_unlock(&socketMutex); 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]); + // 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; } - - // Ensure function code matches the request - if (response[7] != static_cast(ModbusFunction::READ_INPUT_REGISTER)) { - printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", - static_cast(ModbusFunction::READ_INPUT_REGISTER), response[7]); + if (commResponseBuffer[7] != static_cast(ModbusFunction::READ_INPUT_REGISTER)) { + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } - - // Extract register values (each register is 2 bytes, big-endian) + // Extract each register value (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)]; + 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) { - 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 + 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; } - - // 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]) { + if (commRequestBuffer[i] != commResponseBuffer[i]) { printf("MODBUS_TCP_CLIENT: Response does not match request!\n"); + pthread_mutex_unlock(&socketMutex); return ModbusError::INVALID_RESPONSE; } } - - return ModbusError::NONE; // Success + pthread_mutex_unlock(&socketMutex); + return ModbusError::NONE; } 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"); + 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; } - int byteCount = (count + 7) / 8; // Each byte holds 8 coils + // Each coil is a bit; compute number of bytes required. + int byteCount = (count + 7) / 8; uint8_t coilData[byteCount]; - - // Initialize to 0 (since we may not use all bits) + // Initialize coilData to 0. for (int i = 0; i < byteCount; i++) { coilData[i] = 0; } - - // Pack coil data into bytes + // Pack each boolean value into the appropriate bit. 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)) { + // 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; } - // 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) + // 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; + } - if (request[i] != response[i]) { - printf("MODBUS_TCP_CLIENT: Response does not match request!\n"); + // 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; } } - - return ModbusError::NONE; // Success + pthread_mutex_unlock(&socketMutex); + return ModbusError::NONE; } 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)) { + 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; } - - // Ensure response matches request exactly (full 12 bytes) + // 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 (request[i] != response[i]) { - printf("MODBUS_TCP_CLIENT: Response does not match request!\n"); + 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; } } - - return ModbusError::NONE; // Success + pthread_mutex_unlock(&socketMutex); + return ModbusError::NONE; } 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"); + 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; } - - int byteCount = count * 2; // Each register is 2 bytes + // Each register is 2 bytes. + int byteCount = count * 2; uint8_t registerData[byteCount]; - - // Convert register values to byte array (big-endian format) + // 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 + 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)) { + // 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; } - - // Ensure response matches request (except for byte 5) + // 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 byte 5 (message length field) - - if (request[i] != response[i]) { - printf("MODBUS_TCP_CLIENT: Response does not match request!\n"); + 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; } } - - return ModbusError::NONE; // Success + pthread_mutex_unlock(&socketMutex); + return ModbusError::NONE; } 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; - } - + // For brevity, call low-level functions that update internal storage. ModbusError error = ModbusError::NONE; - if (coilsRead != nullptr) { + if (coilsRead) { error = readMultipleCoils(startCoils, numCoils, coilsRead); if (error != ModbusError::NONE) return error; } - - if (discreteInputs != nullptr) { + if (discreteInputs) { error = readMultipleDiscreteInputs(startDiscreteInputs, numDiscreteInputs, discreteInputs); if (error != ModbusError::NONE) return error; } - - if (inputRegisters != nullptr) { + if (inputRegisters) { error = readMultipleInputRegisters(startInputRegisters, numInputRegisters, inputRegisters); if (error != ModbusError::NONE) return error; } - - if (holdingRegistersRead != nullptr) { + if (holdingRegistersRead) { 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) { + if (coilsWrite) { error = writeMultipleCoils(startCoils, numCoils, coilsWrite); if (error != ModbusError::NONE) return error; } - - if (holdingRegistersWrite != nullptr) { + 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) { diff --git a/modbus_tcp_client_old.h b/modbus_tcp_client_old.h index ba45f14..ce03591 100644 --- a/modbus_tcp_client_old.h +++ b/modbus_tcp_client_old.h @@ -6,6 +6,7 @@ #include #include #include +#include // Use mutexes // Enum class for MODBUS function codes enum class ModbusFunction : uint8_t { @@ -19,68 +20,63 @@ enum class ModbusFunction : uint8_t { WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10 }; +// Enum class for error codes 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 + NONE = 0, + TIMEOUT, + INVALID_RESPONSE, + CONNECTION_LOST, + EXCEPTION_RESPONSE, + INVALID_REQUEST }; - class ModbusTCPClient { public: - ModbusTCPClient(const char* ip, int port); // Extra constructor that does CANNOT use readAll() or writeAll() + // 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); - + 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 the start address of each type. Either use this after creating the object or put them in the constructor + // 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; - - // Manually disconnectServer and return reconnectServer() bool reconnectServer(); - // Set the timeout for receiving responses + // Set the timeout (in milliseconds) for receiving responses. void setTimeout(int milliseconds); - // Setters (preferred to be used when calling writeAll()) + // Getters and setters for data values (if using the automatic mode). 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 getDesiredCoil(int address) const; 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 getDesiredHoldingRegister(int address) const; 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) + // 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[]); @@ -90,26 +86,42 @@ private: int serverPort; int socketFD; uint16_t transactionID; - int timeoutMilliseconds = 2000; // Default 2 second timeout on receiving responses + int timeoutMilliseconds = 2000; // Default 2-second timeout - // Storing MODBUS register information + // Start addresses for each data type + int startCoils, startDiscreteInputs, startInputRegisters, startHoldingRegisters; + // Number of items for each type 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 + // 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 \ No newline at end of file +#endif // MODBUS_TCP_CLIENT_H diff --git a/modbus_tcp_client_old_old.cpp b/modbus_tcp_client_old_old.cpp new file mode 100644 index 0000000..101c3a3 --- /dev/null +++ b/modbus_tcp_client_old_old.cpp @@ -0,0 +1,757 @@ +#include "modbus_tcp_client.h" +#include // 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(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(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(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(ModbusFunction::READ_COIL)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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(ModbusFunction::READ_COIL)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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(ModbusFunction::READ_DISCRETE_INPUT)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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(ModbusFunction::READ_DISCRETE_INPUT)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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(ModbusFunction::READ_HOLDING_REGISTER)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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(ModbusFunction::READ_HOLDING_REGISTER)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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(ModbusFunction::READ_INPUT_REGISTER)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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(ModbusFunction::READ_INPUT_REGISTER)) { + printf("MODBUS_TCP_CLIENT: Warning: Unexpected function code (Expected: %02X, Got: %02X)\n", + static_cast(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; +} \ No newline at end of file diff --git a/modbus_tcp_client_old_old.h b/modbus_tcp_client_old_old.h new file mode 100644 index 0000000..ba45f14 --- /dev/null +++ b/modbus_tcp_client_old_old.h @@ -0,0 +1,115 @@ +#ifndef MODBUS_TCP_CLIENT_H +#define MODBUS_TCP_CLIENT_H + +#include +#include +#include +#include +#include + +// 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 \ No newline at end of file