update to eModbus 1.7.4

This commit is contained in:
MichaelDvP
2025-08-04 10:16:50 +02:00
parent f10f3d5305
commit 7b0169bb68
24 changed files with 178 additions and 101 deletions

View File

@@ -0,0 +1,9 @@
name=eModbus
version=1.7.4
author=bertmelis,Miq1 <miq1@gmx.de>
maintainer=Miq1 <miq1@gmx.de>
sentence=eModbus provides Modbus RTU, ASCII and TCP functions for ESP32.
paragraph=This library is non-blocking for the program using it. Modbus requests and responses will be returned to user-supplied callback functions. All Modbus function codes are supported implicitly, the codes specified by the Modbus specs are parameter-checked.
category=Communication
url=https://github.com/eModbus/eModbus
architectures=esp32,FreeRTOS

View File

@@ -47,6 +47,8 @@ CoilData::~CoilData() {
// Assignment operator // Assignment operator
CoilData& CoilData::operator=(const CoilData& m) { CoilData& CoilData::operator=(const CoilData& m) {
// Avoid self-assignment
if (this == &m) return *this;
// Remove old data // Remove old data
if (CDbuffer) { if (CDbuffer) {
delete CDbuffer; delete CDbuffer;

View File

@@ -8,6 +8,7 @@
#include <map> #include <map>
#include <functional> #include <functional>
#include "ModbusClient.h" #include "ModbusClient.h"
#include "ModbusServer.h"
#include "ModbusClientTCP.h" // Needed for client.setTarget() #include "ModbusClientTCP.h" // Needed for client.setTarget()
#include "RTUutils.h" // Needed for RTScallback #include "RTUutils.h" // Needed for RTScallback
@@ -29,7 +30,7 @@ public:
ModbusBridge(); ModbusBridge();
// Constructors for the RTU variant. Parameters as are for ModbusServerRTU // Constructors for the RTU variant. Parameters as are for ModbusServerRTU
ModbusBridge(uint32_t timeout, int rtsPin = -1); explicit ModbusBridge(uint32_t timeout, int rtsPin = -1);
ModbusBridge(uint32_t timeout, RTScallback rts); ModbusBridge(uint32_t timeout, RTScallback rts);
// Destructor // Destructor
@@ -230,42 +231,56 @@ ModbusMessage ModbusBridge<SERVERCLASS>::bridgeWorker(ModbusMessage msg) {
uint8_t aliasID = msg.getServerID(); uint8_t aliasID = msg.getServerID();
uint8_t functionCode = msg.getFunctionCode(); uint8_t functionCode = msg.getFunctionCode();
ModbusMessage response; ModbusMessage response;
bool foundServer = false;
uint8_t usableID = 255;
// Find the (alias) serverID // Find the (alias) serverID
if (servers.find(aliasID) != servers.end()) { if (servers.find(aliasID) != servers.end()) {
foundServer = true;
usableID = aliasID;
} else {
if (servers.find(ANY_SERVER) != servers.end()) {
foundServer = true;
usableID = ANY_SERVER;
}
}
if (foundServer) {
// Found it. We may use servers[aliasID] now without allocating a new map slot // Found it. We may use servers[aliasID] now without allocating a new map slot
// Request filter hook to be called here // Request filter hook to be called here
if (servers[aliasID]->requestFilter) { if (servers[usableID]->requestFilter) {
LOG_D("Calling request filter\n"); LOG_D("Calling request filter\n");
msg = servers[aliasID]->requestFilter(msg); msg = servers[usableID]->requestFilter(msg);
} }
// Set real target server ID // Set real target server ID
msg.setServerID(servers[aliasID]->serverID); if (servers[usableID]->serverID != ANY_SERVER) {
msg.setServerID(servers[usableID]->serverID);
// Issue the request
LOG_D("Request (%02X/%02X) sent\n", servers[aliasID]->serverID, msg.getFunctionCode());
// TCP servers have a target host/port that needs to be set in the client
if (servers[aliasID]->serverType == TCP_SERVER) {
response = reinterpret_cast<ModbusClientTCP *>(servers[aliasID]->client)->syncRequestMT(msg, (uint32_t)millis(), servers[aliasID]->host, servers[aliasID]->port);
} else {
response = servers[aliasID]->client->syncRequestM(msg, (uint32_t)millis());
} }
// Response filter hook to be called here // Issue the request
if (servers[aliasID]->responseFilter) { LOG_D("Request (%02X/%02X) sent\n", servers[usableID]->serverID, msg.getFunctionCode());
LOG_D("Calling response filter\n"); // TCP servers have a target host/port that needs to be set in the client
response = servers[aliasID]->responseFilter(response); if (servers[usableID]->serverType == TCP_SERVER) {
response = reinterpret_cast<ModbusClientTCP *>(servers[usableID]->client)->syncRequestMT(msg, (uint32_t)micros(), servers[usableID]->host, servers[usableID]->port);
} else {
response = servers[usableID]->client->syncRequestM(msg, (uint32_t)micros());
} }
// Re-set the requested server ID and function code (may have been modified by filters) // Re-set the requested server ID and function code (may have been modified by filters)
response.setServerID(aliasID); response.setServerID(aliasID);
if (response.getError() != SUCCESS) { if (response.getError() != SUCCESS) {
response.setFunctionCode(functionCode | 0x80); response.setFunctionCode(functionCode | 0x80);
} else { } else {
response.setFunctionCode(functionCode); response.setFunctionCode(functionCode);
} }
// Response filter hook to be called here
if (servers[usableID]->responseFilter) {
LOG_D("Calling response filter\n");
response = servers[usableID]->responseFilter(response);
}
} else { } else {
// If we get here, something has gone wrong internally. We send back an error response anyway. // If we get here, something has gone wrong internally. We send back an error response anyway.
response.setError(aliasID, functionCode, INVALID_SERVER); response.setError(aliasID, functionCode, INVALID_SERVER);

View File

@@ -21,6 +21,13 @@ ModbusClient::ModbusClient() :
onError(nullptr), onError(nullptr),
onResponse(nullptr) { instanceCounter++; } onResponse(nullptr) { instanceCounter++; }
// Default destructor: reduce number of clients by one
ModbusClient::~ModbusClient() {
if (instanceCounter) {
instanceCounter--;
}
}
// onDataHandler: register callback for data responses // onDataHandler: register callback for data responses
bool ModbusClient::onDataHandler(MBOnData handler) { bool ModbusClient::onDataHandler(MBOnData handler) {
if (onData) { if (onData) {

View File

@@ -37,8 +37,8 @@ public:
uint32_t getMessageCount(); // Informative: return number of messages created uint32_t getMessageCount(); // Informative: return number of messages created
uint32_t getErrorCount(); // Informative: return number of errors received uint32_t getErrorCount(); // Informative: return number of errors received
void resetCounts(); // Set both message and error counts to zero void resetCounts(); // Set both message and error counts to zero
inline Error addRequest(ModbusMessage m, uint32_t token) { return addRequestM(m, token); } inline Error addRequest(const ModbusMessage& m, uint32_t token) { return addRequestM(m, token); }
inline ModbusMessage syncRequest(ModbusMessage m, uint32_t token) { return syncRequestM(m, token); } inline ModbusMessage syncRequest(const ModbusMessage& m, uint32_t token) { return syncRequestM(m, token); }
// Template function to generate syncRequest functions as long as there is a // Template function to generate syncRequest functions as long as there is a
// matching ModbusMessage::setMessage() call // matching ModbusMessage::setMessage() call
@@ -85,7 +85,7 @@ public:
protected: protected:
ModbusClient(); // Default constructor ModbusClient(); // Default constructor
virtual void isInstance() = 0; // Make class abstract virtual ~ModbusClient(); // Destructor
ModbusMessage waitSync(uint8_t serverID, uint8_t functionCode, uint32_t token); // wait for syncRequest response to arrive ModbusMessage waitSync(uint8_t serverID, uint8_t functionCode, uint32_t token); // wait for syncRequest response to arrive
// Virtual addRequest variant needed internally. All others done by template! // Virtual addRequest variant needed internally. All others done by template!
virtual Error addRequestM(ModbusMessage msg, uint32_t token) = 0; virtual Error addRequestM(ModbusMessage msg, uint32_t token) = 0;

View File

@@ -86,7 +86,7 @@ void ModbusClientRTU::doBegin(uint32_t baudRate, int coreID, uint32_t userInterv
char taskName[18]; char taskName[18];
snprintf(taskName, 18, "Modbus%02XRTU", instanceCounter); snprintf(taskName, 18, "Modbus%02XRTU", instanceCounter);
// Start task to handle the queue // Start task to handle the queue
xTaskCreatePinnedToCore((TaskFunction_t)&handleConnection, taskName, CLIENT_TASK_STACK, this, 6, &worker, coreID >= 0 ? coreID : NULL); xTaskCreatePinnedToCore((TaskFunction_t)&handleConnection, taskName, CLIENT_TASK_STACK, this, 6, &worker, coreID >= 0 ? coreID : tskNO_AFFINITY);
LOG_D("Client task %d started. Interval=%d\n", (uint32_t)worker, MR_interval); LOG_D("Client task %d started. Interval=%d\n", (uint32_t)worker, MR_interval);
} }
@@ -151,6 +151,7 @@ void ModbusClientRTU::clearQueue()
{ {
std::queue<RequestEntry> empty; std::queue<RequestEntry> empty;
LOCK_GUARD(lockGuard, qLock); LOCK_GUARD(lockGuard, qLock);
// Empty queue
std::swap(requests, empty); std::swap(requests, empty);
} }
@@ -342,8 +343,11 @@ void ModbusClientRTU::handleConnection(ModbusClientRTU *instance) {
{ {
// Safely lock the queue // Safely lock the queue
LOCK_GUARD(lockGuard, instance->qLock); LOCK_GUARD(lockGuard, instance->qLock);
// Remove the front queue entry
instance->requests.pop(); // Remove the front queue entry if the queue is not empty
if (!instance->requests.empty()) {
instance->requests.pop();
}
} }
} else { } else {
delay(1); delay(1);

View File

@@ -67,15 +67,15 @@ protected:
uint32_t token; uint32_t token;
ModbusMessage msg; ModbusMessage msg;
bool isSyncRequest; bool isSyncRequest;
RequestEntry(uint32_t t, ModbusMessage m, bool syncReq = false) : RequestEntry(uint32_t t, const ModbusMessage& m, bool syncReq = false) :
token(t), token(t),
msg(m), msg(m),
isSyncRequest(syncReq) {} isSyncRequest(syncReq) {}
}; };
// Base addRequest and syncRequest must be present // Base addRequest and syncRequest must be present
Error addRequestM(ModbusMessage msg, uint32_t token); Error addRequestM(ModbusMessage msg, uint32_t token) override;
ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token); ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token) override;
// addToQueue: send freshly created request to queue // addToQueue: send freshly created request to queue
bool addToQueue(uint32_t token, ModbusMessage msg, bool syncReq = false); bool addToQueue(uint32_t token, ModbusMessage msg, bool syncReq = false);
@@ -89,7 +89,6 @@ protected:
// start background task // start background task
void doBegin(uint32_t baudRate, int coreID, uint32_t userInterval); void doBegin(uint32_t baudRate, int coreID, uint32_t userInterval);
void isInstance() { return; } // make class instantiable
queue<RequestEntry> requests; // Queue to hold requests to be processed queue<RequestEntry> requests; // Queue to hold requests to be processed
#if USE_MUTEX #if USE_MUTEX
mutex qLock; // Mutex to protect queue mutex qLock; // Mutex to protect queue

View File

@@ -18,7 +18,8 @@ ModbusClientTCP::ModbusClientTCP(Client& client, uint16_t queueLimit) :
MT_target(IPAddress(0, 0, 0, 0), 0, DEFAULTTIMEOUT, TARGETHOSTINTERVAL), MT_target(IPAddress(0, 0, 0, 0), 0, DEFAULTTIMEOUT, TARGETHOSTINTERVAL),
MT_defaultTimeout(DEFAULTTIMEOUT), MT_defaultTimeout(DEFAULTTIMEOUT),
MT_defaultInterval(TARGETHOSTINTERVAL), MT_defaultInterval(TARGETHOSTINTERVAL),
MT_qLimit(queueLimit) MT_qLimit(queueLimit),
MT_timeoutsToClose(0)
{ } { }
// Alternative Constructor takes reference to Client (EthernetClient or WiFiClient) plus initial target host // Alternative Constructor takes reference to Client (EthernetClient or WiFiClient) plus initial target host
@@ -29,7 +30,8 @@ ModbusClientTCP::ModbusClientTCP(Client& client, IPAddress host, uint16_t port,
MT_target(host, port, DEFAULTTIMEOUT, TARGETHOSTINTERVAL), MT_target(host, port, DEFAULTTIMEOUT, TARGETHOSTINTERVAL),
MT_defaultTimeout(DEFAULTTIMEOUT), MT_defaultTimeout(DEFAULTTIMEOUT),
MT_defaultInterval(TARGETHOSTINTERVAL), MT_defaultInterval(TARGETHOSTINTERVAL),
MT_qLimit(queueLimit) MT_qLimit(queueLimit),
MT_timeoutsToClose(0)
{ } { }
// Destructor: clean up queue, task etc. // Destructor: clean up queue, task etc.
@@ -64,7 +66,7 @@ void ModbusClientTCP::end() {
// begin: start worker task // begin: start worker task
#if IS_LINUX #if IS_LINUX
void *ModbusClientTCP::pHandle(void *p) { void *ModbusClientTCP::pHandle(void *p) {
handleConnection((ModbusClientTCP *)p); handleConnection(static_cast<ModbusClientTCP *>(p));
return nullptr; return nullptr;
} }
#endif #endif
@@ -84,7 +86,7 @@ void ModbusClientTCP::begin(int coreID) {
char taskName[18]; char taskName[18];
snprintf(taskName, 18, "Modbus%02XTCP", instanceCounter); snprintf(taskName, 18, "Modbus%02XTCP", instanceCounter);
// Start task to handle the queue // Start task to handle the queue
xTaskCreatePinnedToCore((TaskFunction_t)&handleConnection, taskName, CLIENT_TASK_STACK, this, 5, &worker, coreID >= 0 ? coreID : NULL); xTaskCreatePinnedToCore((TaskFunction_t)&handleConnection, taskName, CLIENT_TASK_STACK, this, 5, &worker, coreID >= 0 ? coreID : tskNO_AFFINITY);
LOG_D("TCP client worker %s started\n", taskName); LOG_D("TCP client worker %s started\n", taskName);
#endif #endif
} else { } else {
@@ -119,9 +121,25 @@ uint32_t ModbusClientTCP::pendingRequests() {
void ModbusClientTCP::clearQueue() { void ModbusClientTCP::clearQueue() {
std::queue<RequestEntry *> empty; std::queue<RequestEntry *> empty;
LOCK_GUARD(lockGuard, qLock); LOCK_GUARD(lockGuard, qLock);
// Delete queue entries if still on the queue
while (!requests.empty()) {
RequestEntry *re = requests.front();
delete re;
requests.pop();
}
// Now flush the queue
std::swap(requests, empty); std::swap(requests, empty);
} }
// Set number of timeouts to tolerate before a connection is forcibly closed.
// 0: never, 1..255: desired number
// Returns previous value.
uint8_t ModbusClientTCP::closeConnectionOnTimeouts(uint8_t n) {
uint8_t oldValue = MT_timeoutsToClose;
MT_timeoutsToClose = n;
return oldValue;
}
// Base addRequest for preformatted ModbusMessage and last set target // Base addRequest for preformatted ModbusMessage and last set target
Error ModbusClientTCP::addRequestM(ModbusMessage msg, uint32_t token) { Error ModbusClientTCP::addRequestM(ModbusMessage msg, uint32_t token) {
Error rc = SUCCESS; // Return value Error rc = SUCCESS; // Return value
@@ -225,6 +243,7 @@ bool ModbusClientTCP::addToQueue(uint32_t token, ModbusMessage request, TargetHo
void ModbusClientTCP::handleConnection(ModbusClientTCP *instance) { void ModbusClientTCP::handleConnection(ModbusClientTCP *instance) {
bool doNotPop; bool doNotPop;
unsigned long lastRequest = millis(); unsigned long lastRequest = millis();
uint16_t timeoutCount = 0; // Run time counter of consecutive timeouts.
// Loop forever - or until task is killed // Loop forever - or until task is killed
while (1) { while (1) {
@@ -273,6 +292,8 @@ void ModbusClientTCP::handleConnection(ModbusClientTCP *instance) {
// Did we get a normal response? // Did we get a normal response?
if (response.getError()==SUCCESS) { if (response.getError()==SUCCESS) {
LOG_D("Data response.\n"); LOG_D("Data response.\n");
// Reset timeout counter
timeoutCount = 0;
// Yes. Is it a synchronous request? // Yes. Is it a synchronous request?
if (request->isSyncRequest) { if (request->isSyncRequest) {
// Yes. Put the response into the response map // Yes. Put the response into the response map
@@ -299,6 +320,25 @@ void ModbusClientTCP::handleConnection(ModbusClientTCP *instance) {
LOCK_GUARD(responseCnt, instance->countAccessM); LOCK_GUARD(responseCnt, instance->countAccessM);
instance->errorCount++; instance->errorCount++;
} }
// Is it a TIMEOUT and do we need to track it?
if (response.getError()==TIMEOUT && instance->MT_timeoutsToClose) {
LOG_D("Checking timeout sequence\n");
// Yes. First count timeout conter up
timeoutCount++;
// Is the count above the limit?
if (timeoutCount > instance->MT_timeoutsToClose) {
LOG_D("Timeouts: %d exceeding limit (%d), closing connection\n",
timeoutCount, instance->MT_timeoutsToClose);
// Yes. We need to cut the connection
instance->MT_client.stop();
delay(1);
// reset timeout count
timeoutCount = 0;
}
} else {
// No TIMEOUT or no limit: reset timeout count
timeoutCount = 0;
}
// Is it a synchronous request? // Is it a synchronous request?
if (request->isSyncRequest) { if (request->isSyncRequest) {
// Yes. Put the response into the response map // Yes. Put the response into the response map
@@ -345,8 +385,11 @@ void ModbusClientTCP::handleConnection(ModbusClientTCP *instance) {
{ {
// Safely lock the queue // Safely lock the queue
LOCK_GUARD(lockGuard, instance->qLock); LOCK_GUARD(lockGuard, instance->qLock);
// Remove the front queue entry
instance->requests.pop(); // Remove the front queue entry if the queue is not empty
if (!instance->requests.empty()) {
instance->requests.pop();
}
// Delete request // Delete request
delete request; delete request;
LOG_D("Request popped from queue.\n"); LOG_D("Request popped from queue.\n");

View File

@@ -50,6 +50,11 @@ public:
// Remove all pending request from queue // Remove all pending request from queue
void clearQueue(); void clearQueue();
// Set number of timeouts to tolerate before a connection is forcibly closed.
// 0: never, 1..255: desired number
// Returns previous value.
uint8_t closeConnectionOnTimeouts(uint8_t n=3);
protected: protected:
// class describing a target server // class describing a target server
struct TargetHost { struct TargetHost {
@@ -58,7 +63,7 @@ protected:
uint32_t timeout; // Time in ms waiting for a response uint32_t timeout; // Time in ms waiting for a response
uint32_t interval; // Time in ms to wait between requests uint32_t interval; // Time in ms to wait between requests
inline TargetHost& operator=(TargetHost& t) { inline TargetHost& operator=(const TargetHost& t) {
host = t.host; host = t.host;
port = t.port; port = t.port;
timeout = t.timeout; timeout = t.timeout;
@@ -66,7 +71,7 @@ protected:
return *this; return *this;
} }
inline TargetHost(TargetHost& t) : inline TargetHost(const TargetHost& t) :
host(t.host), host(t.host),
port(t.port), port(t.port),
timeout(t.timeout), timeout(t.timeout),
@@ -86,13 +91,13 @@ protected:
interval(interval) interval(interval)
{ } { }
inline bool operator==(TargetHost& t) { inline bool operator==(const TargetHost& t) {
if (host != t.host) return false; if (host != t.host) return false;
if (port != t.port) return false; if (port != t.port) return false;
return true; return true;
} }
inline bool operator!=(TargetHost& t) { inline bool operator!=(const TargetHost& t) {
if (host != t.host) return true; if (host != t.host) return true;
if (port != t.port) return true; if (port != t.port) return true;
return false; return false;
@@ -135,7 +140,7 @@ protected:
} }
protected: protected:
uint8_t headRoom[6]; // Buffer to hold MSB-first TCP header uint8_t headRoom[6] = {0,0,0,0,0,0}; // Buffer to hold MSB-first TCP header
}; };
struct RequestEntry { struct RequestEntry {
@@ -144,7 +149,7 @@ protected:
TargetHost target; TargetHost target;
ModbusTCPhead head; ModbusTCPhead head;
bool isSyncRequest; bool isSyncRequest;
RequestEntry(uint32_t t, ModbusMessage m, TargetHost tg, bool syncReq = false) : RequestEntry(uint32_t t, const ModbusMessage& m, TargetHost tg, bool syncReq = false) :
token(t), token(t),
msg(m), msg(m),
target(tg), target(tg),
@@ -153,8 +158,8 @@ protected:
}; };
// Base addRequest and syncRequest must be present // Base addRequest and syncRequest must be present
Error addRequestM(ModbusMessage msg, uint32_t token); Error addRequestM(ModbusMessage msg, uint32_t token) override;
ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token); ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token) override;
// TCP-specific addition "...MT()" including adhoc target - used by bridge // TCP-specific addition "...MT()" including adhoc target - used by bridge
Error addRequestMT(ModbusMessage msg, uint32_t token, IPAddress targetHost, uint16_t targetPort); Error addRequestMT(ModbusMessage msg, uint32_t token, IPAddress targetHost, uint16_t targetPort);
ModbusMessage syncRequestMT(ModbusMessage msg, uint32_t token, IPAddress targetHost, uint16_t targetPort); ModbusMessage syncRequestMT(ModbusMessage msg, uint32_t token, IPAddress targetHost, uint16_t targetPort);
@@ -174,7 +179,6 @@ protected:
// receive: get response via Client connection // receive: get response via Client connection
ModbusMessage receive(RequestEntry *request); ModbusMessage receive(RequestEntry *request);
void isInstance() { return; } // make class instantiable
queue<RequestEntry *> requests; // Queue to hold requests to be processed queue<RequestEntry *> requests; // Queue to hold requests to be processed
#if USE_MUTEX #if USE_MUTEX
mutex qLock; // Mutex to protect queue mutex qLock; // Mutex to protect queue
@@ -185,6 +189,8 @@ protected:
uint32_t MT_defaultTimeout; // Standard timeout value taken if no dedicated was set uint32_t MT_defaultTimeout; // Standard timeout value taken if no dedicated was set
uint32_t MT_defaultInterval; // Standard interval value taken if no dedicated was set uint32_t MT_defaultInterval; // Standard interval value taken if no dedicated was set
uint16_t MT_qLimit; // Maximum number of requests to accept in queue uint16_t MT_qLimit; // Maximum number of requests to accept in queue
uint8_t MT_timeoutsToClose; // 0: disregard, 1-255: number of timeouts to tolerate before
// forcibly closing a connection.
// Let any ModbusBridge class use protected members // Let any ModbusBridge class use protected members
template<typename SERVERCLASS> friend class ModbusBridge; template<typename SERVERCLASS> friend class ModbusBridge;

View File

@@ -223,19 +223,17 @@ void onAck(size_t len, uint32_t time) {
} }
*/ */
void ModbusClientTCPasync::onPacket(uint8_t* data, size_t length) { void ModbusClientTCPasync::onPacket(uint8_t* data, size_t length) {
LOG_D("packet received (len:%d)\n", length); LOG_D("packet received (len:%u)\n", length);
// reset idle timeout // reset idle timeout
MTA_lastActivity = millis(); MTA_lastActivity = millis();
if (length) { if (length) {
LOG_D("parsing (len:%d)\n", length + 1); LOG_D("parsing (len:%u)\n", length + 1);
} }
while (length > 0) { while (length > 0) {
RequestEntry* request = nullptr; RequestEntry* request = nullptr;
ModbusMessage* response = nullptr; ModbusMessage* response = nullptr;
uint16_t transactionID = 0; uint16_t transactionID = 0;
uint16_t protocolID = 0;
uint16_t messageLength = 0;
bool isOkay = false; bool isOkay = false;
// 1. Check for valid modbus message // 1. Check for valid modbus message
@@ -244,8 +242,8 @@ void ModbusClientTCPasync::onPacket(uint8_t* data, size_t length) {
// total message should fit MBAP plus remaining bytes (in data[4], data[5]) // total message should fit MBAP plus remaining bytes (in data[4], data[5])
if (length > 6) { if (length > 6) {
transactionID = (data[0] << 8) | data[1]; transactionID = (data[0] << 8) | data[1];
protocolID = (data[2] << 8) | data[3]; uint16_t protocolID = (data[2] << 8) | data[3];
messageLength = (data[4] << 8) | data[5]; uint16_t messageLength = (data[4] << 8) | data[5];
if (protocolID == 0 && if (protocolID == 0 &&
length >= (uint32_t)messageLength + 6 && length >= (uint32_t)messageLength + 6 &&
messageLength < 256) { messageLength < 256) {

View File

@@ -83,7 +83,7 @@ protected:
return headRoom; return headRoom;
} }
inline ModbusTCPhead& operator= (ModbusTCPhead& t) { inline ModbusTCPhead& operator= (const ModbusTCPhead& t) {
transactionID = t.transactionID; transactionID = t.transactionID;
protocolID = t.protocolID; protocolID = t.protocolID;
len = t.len; len = t.len;
@@ -91,7 +91,7 @@ protected:
} }
protected: protected:
uint8_t headRoom[6]; // Buffer to hold MSB-first TCP header uint8_t headRoom[6] = {0,0,0,0,0,0}; // Buffer to hold MSB-first TCP header
}; };
struct RequestEntry { struct RequestEntry {
@@ -100,7 +100,7 @@ protected:
ModbusTCPhead head; ModbusTCPhead head;
uint32_t sentTime; uint32_t sentTime;
bool isSyncRequest; bool isSyncRequest;
RequestEntry(uint32_t t, ModbusMessage m, bool syncReq = false) : RequestEntry(uint32_t t, const ModbusMessage& m, bool syncReq = false) :
token(t), token(t),
msg(m), msg(m),
head(ModbusTCPhead()), head(ModbusTCPhead()),
@@ -109,8 +109,8 @@ protected:
}; };
// Base addRequest and syncRequest both must be present // Base addRequest and syncRequest both must be present
Error addRequestM(ModbusMessage msg, uint32_t token); Error addRequestM(ModbusMessage msg, uint32_t token) override;
ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token); ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token) override;
// addToQueue: send freshly created request to queue // addToQueue: send freshly created request to queue
bool addToQueue(int32_t token, ModbusMessage request, bool syncReq = false); bool addToQueue(int32_t token, ModbusMessage request, bool syncReq = false);
@@ -121,8 +121,6 @@ protected:
// receive: get response via Client connection // receive: get response via Client connection
// TCPResponse* receive(uint8_t* data, size_t length); // TCPResponse* receive(uint8_t* data, size_t length);
void isInstance() { return; } // make class instantiable
// TCP handling code, all static taking a class instancs as param // TCP handling code, all static taking a class instancs as param
void onConnected(); void onConnected();
void onDisconnected(); void onDisconnected();

View File

@@ -6,6 +6,7 @@
#undef LOCAL_LOG_LEVEL #undef LOCAL_LOG_LEVEL
// #define LOCAL_LOG_LEVEL LOG_LEVEL_ERROR // #define LOCAL_LOG_LEVEL LOG_LEVEL_ERROR
#include "Logging.h" #include "Logging.h"
#include <algorithm>
// Default Constructor - takes optional size of MM_data to allocate memory // Default Constructor - takes optional size of MM_data to allocate memory
ModbusMessage::ModbusMessage(uint16_t dataLen) { ModbusMessage::ModbusMessage(uint16_t dataLen) {
@@ -146,21 +147,19 @@ void ModbusMessage::setServerID(uint8_t serverID) {
} }
void ModbusMessage::setFunctionCode(uint8_t FC) { void ModbusMessage::setFunctionCode(uint8_t FC) {
// We accept here that [0], [1] may allocate bytes! if (MM_data.size() < 2) {
if (MM_data.empty()) { MM_data.resize(2); // Resize to at least 2 to ensure indices 0 and 1 are valid
MM_data.reserve(3); // At least an error message should fit MM_data[0] = 0; // Optional: Invalid server ID as a placeholder
} }
// No serverID set yet? use a 0 to initialize it to an error-generating value MM_data[1] = FC; // Safely set the function code
if (MM_data.size() < 2) MM_data[0] = 0; // intentional invalid server ID!
MM_data[1] = FC;
} }
// add() variant to copy a buffer into MM_data. Returns updated size // add() variant to copy a buffer into MM_data. Returns updated size
uint16_t ModbusMessage::add(const uint8_t *arrayOfBytes, uint16_t count) { uint16_t ModbusMessage::add(const uint8_t *arrayOfBytes, uint16_t count) {
uint16_t originalSize = MM_data.size();
MM_data.resize(originalSize + count);
// Copy it // Copy it
while (count--) { std::copy(arrayOfBytes, arrayOfBytes + count, MM_data.begin() + originalSize);
MM_data.push_back(*arrayOfBytes++);
}
// Return updated size (logical length of message so far) // Return updated size (logical length of message so far)
return MM_data.size(); return MM_data.size();
} }
@@ -181,7 +180,7 @@ uint8_t ModbusMessage::determineFloatOrder() {
uint32_t i = 77230; // int value to go into a float without rounding error uint32_t i = 77230; // int value to go into a float without rounding error
float f = i; // assign it float f = i; // assign it
uint8_t *b = (uint8_t *)&f; // Pointer to bytes of f uint8_t *b = (uint8_t *)&f; // Pointer to bytes of f
uint8_t expect[floatSize] = { 0x47, 0x96, 0xd7, 0x00 }; // IEEE754 representation const uint8_t expect[floatSize] = { 0x47, 0x96, 0xd7, 0x00 }; // IEEE754 representation
uint8_t matches = 0; // number of bytes successfully matched uint8_t matches = 0; // number of bytes successfully matched
// Loop over the bytes of the expected sequence // Loop over the bytes of the expected sequence
@@ -225,7 +224,7 @@ uint8_t ModbusMessage::determineDoubleOrder() {
uint64_t i = 5791007487489389; // int64 value to go into a double without rounding error uint64_t i = 5791007487489389; // int64 value to go into a double without rounding error
double f = i; // assign it double f = i; // assign it
uint8_t *b = (uint8_t *)&f; // Pointer to bytes of f uint8_t *b = (uint8_t *)&f; // Pointer to bytes of f
uint8_t expect[doubleSize] = { 0x43, 0x34, 0x92, 0xE4, 0x00, 0x2E, 0xF5, 0x6D }; // IEEE754 representation const uint8_t expect[doubleSize] = { 0x43, 0x34, 0x92, 0xE4, 0x00, 0x2E, 0xF5, 0x6D }; // IEEE754 representation
uint8_t matches = 0; // number of bytes successfully matched uint8_t matches = 0; // number of bytes successfully matched
// Loop over the bytes of the expected sequence // Loop over the bytes of the expected sequence
@@ -306,10 +305,7 @@ double ModbusMessage::swapDouble(double& f, int swapRule) {
// add() variant for a vector of uint8_t // add() variant for a vector of uint8_t
uint16_t ModbusMessage::add(vector<uint8_t> v) { uint16_t ModbusMessage::add(vector<uint8_t> v) {
for (auto& b: v) { return add(v.data(), v.size());
MM_data.push_back(b);
}
return MM_data.size();
} }
// add() variants for float and double values // add() variants for float and double values

View File

@@ -30,7 +30,6 @@ MBSworker ModbusServer::getWorker(uint8_t serverID, uint8_t functionCode) {
svmap = workerMap.find(ANY_SERVER); svmap = workerMap.find(ANY_SERVER);
if (svmap != workerMap.end()) { if (svmap != workerMap.end()) {
serverFound = true; serverFound = true;
serverID = ANY_SERVER;
} }
} }
// Did we find a serverID? // Did we find a serverID?
@@ -49,7 +48,6 @@ MBSworker ModbusServer::getWorker(uint8_t serverID, uint8_t functionCode) {
if (fcmap != svmap->second.end()) { if (fcmap != svmap->second.end()) {
// Yes. Return the function pointer for it. // Yes. Return the function pointer for it.
functionCodeFound = true; functionCodeFound = true;
functionCode = ANY_FUNCTION_CODE;
} }
} }
if (functionCodeFound) { if (functionCodeFound) {
@@ -104,6 +102,11 @@ bool ModbusServer::isServerFor(uint8_t serverID) {
// Is there one? // Is there one?
if (svmap != workerMap.end()) { if (svmap != workerMap.end()) {
return true; return true;
} else {
svmap = workerMap.find(ANY_SERVER);
if (svmap != workerMap.end()) {
return true;
}
} }
return false; return false;
} }

View File

@@ -68,15 +68,12 @@ protected:
ModbusServer(); ModbusServer();
// Destructor // Destructor
~ModbusServer(); virtual ~ModbusServer();
// Prevent copy construction or assignment // Prevent copy construction or assignment
ModbusServer(ModbusServer& other) = delete; ModbusServer(ModbusServer& other) = delete;
ModbusServer& operator=(ModbusServer& other) = delete; ModbusServer& operator=(ModbusServer& other) = delete;
// Virtual function to prevent this class being instantiated
virtual void isInstance() = 0;
std::map<uint8_t, std::map<uint8_t, MBSworker>> workerMap; // map on serverID->functionCode->worker function std::map<uint8_t, std::map<uint8_t, MBSworker>> workerMap; // map on serverID->functionCode->worker function
uint32_t messageCount; // Number of Requests processed uint32_t messageCount; // Number of Requests processed
uint32_t errorCount; // Number of errors responded uint32_t errorCount; // Number of errors responded

View File

@@ -15,8 +15,8 @@
// Create own non-virtual EthernetServer class // Create own non-virtual EthernetServer class
class EthernetServerEM : public EthernetServer { class EthernetServerEM : public EthernetServer {
public: public:
EthernetServerEM(uint16_t port) : EthernetServer(port) { } explicit EthernetServerEM(uint16_t port) : EthernetServer(port) { }
void begin(uint16_t port = 0) { } void begin(uint16_t port = 0) override { }
}; };
#include "ModbusServerTCPtemp.h" #include "ModbusServerTCPtemp.h"

View File

@@ -94,7 +94,7 @@ void ModbusServerRTU::doBegin(uint32_t baudRate, int coreID, uint32_t userInterv
snprintf(taskName, 18, "MBsrv%02XRTU", instanceCounter); snprintf(taskName, 18, "MBsrv%02XRTU", instanceCounter);
// Start task to handle the client // Start task to handle the client
xTaskCreatePinnedToCore((TaskFunction_t)&serve, taskName, SERVER_TASK_STACK, this, 8, &serverTask, coreID >= 0 ? coreID : NULL); xTaskCreatePinnedToCore((TaskFunction_t)&serve, taskName, SERVER_TASK_STACK, this, 8, &serverTask, coreID >= 0 ? coreID : tskNO_AFFINITY);
LOG_D("Server task %d started. Interval=%d\n", (uint32_t)serverTask, MSRinterval); LOG_D("Server task %d started. Interval=%d\n", (uint32_t)serverTask, MSRinterval);
} }
@@ -126,6 +126,12 @@ bool ModbusServerRTU::isModbusASCII() {
return MSRuseASCII; return MSRuseASCII;
} }
// set timeout
void ModbusServerRTU::setModbusTimeout(unsigned long timeout)
{
serverTimeout = timeout;
}
// Toggle skipping of leading 0x00 byte // Toggle skipping of leading 0x00 byte
void ModbusServerRTU::skipLeading0x00(bool onOff) { void ModbusServerRTU::skipLeading0x00(bool onOff) {
MSRskipLeadingZeroByte = onOff; MSRskipLeadingZeroByte = onOff;
@@ -231,8 +237,8 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
response = m; response = m;
} }
} else { } else {
// No callback. Is at least the serverID valid and no broadcast? // No callback. Is at least the serverID valid?
if (myServer->isServerFor(request[0]) && request[0] != 0x00) { if (myServer->isServerFor(request[0])) {
// Yes. Send back a ILLEGAL_FUNCTION error // Yes. Send back a ILLEGAL_FUNCTION error
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_FUNCTION); response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_FUNCTION);
} }

View File

@@ -47,6 +47,9 @@ public:
// Inquire protocol mode // Inquire protocol mode
bool isModbusASCII(); bool isModbusASCII();
// set timeout
void setModbusTimeout(unsigned long timeout);
// Toggle skipping of leading 0x00 byte // Toggle skipping of leading 0x00 byte
void skipLeading0x00(bool onOff = true); void skipLeading0x00(bool onOff = true);
@@ -61,8 +64,6 @@ protected:
ModbusServerRTU(ModbusServerRTU& m) = delete; ModbusServerRTU(ModbusServerRTU& m) = delete;
ModbusServerRTU& operator=(ModbusServerRTU& m) = delete; ModbusServerRTU& operator=(ModbusServerRTU& m) = delete;
inline void isInstance() { } // Make class instantiable
// internal common begin function // internal common begin function
void doBegin(uint32_t baudRate, int coreID, uint32_t userInterval); void doBegin(uint32_t baudRate, int coreID, uint32_t userInterval);

View File

@@ -32,7 +32,7 @@ ModbusServerTCPasync::mb_client::~mb_client() {
void ModbusServerTCPasync::mb_client::onData(uint8_t* data, size_t len) { void ModbusServerTCPasync::mb_client::onData(uint8_t* data, size_t len) {
lastActiveTime = millis(); lastActiveTime = millis();
LOG_D("data len %d\n", len); LOG_D("data len %u\n", len);
Error error = SUCCESS; Error error = SUCCESS;
size_t i = 0; size_t i = 0;
@@ -250,7 +250,7 @@ void ModbusServerTCPasync::onClientConnect(AsyncClient* client) {
LOCK_GUARD(lock1, cListLock); LOCK_GUARD(lock1, cListLock);
if (clients.size() < maxNoClients) { if (clients.size() < maxNoClients) {
clients.emplace_back(new mb_client(this, client)); clients.emplace_back(new mb_client(this, client));
LOG_D("nr clients: %d\n", clients.size()); LOG_D("nr clients: %u\n", clients.size());
} else { } else {
LOG_D("max number of clients reached, closing new\n"); LOG_D("max number of clients reached, closing new\n");
client->close(true); client->close(true);
@@ -264,5 +264,5 @@ void ModbusServerTCPasync::onClientDisconnect(mb_client* client) {
clients.remove_if([client](mb_client* i) { return i->client == client->client; }); clients.remove_if([client](mb_client* i) { return i->client == client->client; });
// delete client itself // delete client itself
delete client; delete client;
LOG_D("nr clients: %d\n", clients.size()); LOG_D("nr clients: %u\n", clients.size());
} }

View File

@@ -76,7 +76,6 @@ class ModbusServerTCPasync : public ModbusServer {
bool isRunning(); bool isRunning();
protected: protected:
inline void isInstance() { }
void onClientConnect(AsyncClient* client); void onClientConnect(AsyncClient* client);
void onClientDisconnect(mb_client* client); void onClientDisconnect(mb_client* client);

View File

@@ -44,8 +44,6 @@ protected:
ModbusServerTCP(ModbusServerTCP& m) = delete; ModbusServerTCP(ModbusServerTCP& m) = delete;
ModbusServerTCP& operator=(ModbusServerTCP& m) = delete; ModbusServerTCP& operator=(ModbusServerTCP& m) = delete;
inline void isInstance() { }
uint8_t numClients; uint8_t numClients;
TaskHandle_t serverTask; TaskHandle_t serverTask;
uint16_t serverPort; uint16_t serverPort;
@@ -159,7 +157,7 @@ template <typename ST, typename CT>
snprintf(taskName, 18, "MBserve%04X", port); snprintf(taskName, 18, "MBserve%04X", port);
// Start task to handle the client // Start task to handle the client
xTaskCreatePinnedToCore((TaskFunction_t)&serve, taskName, SERVER_TASK_STACK, this, 5, &serverTask, coreID >= 0 ? coreID : NULL); xTaskCreatePinnedToCore((TaskFunction_t)&serve, taskName, SERVER_TASK_STACK, this, 5, &serverTask, coreID >= 0 ? coreID : tskNO_AFFINITY);
LOG_D("Server task %s started (%d).\n", taskName, (uint32_t)serverTask); LOG_D("Server task %s started (%d).\n", taskName, (uint32_t)serverTask);
// Wait two seconds for it to establish // Wait two seconds for it to establish
@@ -206,7 +204,7 @@ bool ModbusServerTCP<ST, CT>::accept(CT& client, uint32_t timeout, int coreID) {
snprintf(taskName, 18, "MBsrv%02Xclnt", i); snprintf(taskName, 18, "MBsrv%02Xclnt", i);
// Start task to handle the client // Start task to handle the client
xTaskCreatePinnedToCore((TaskFunction_t)&worker, taskName, SERVER_TASK_STACK, clients[i], 5, &clients[i]->task, coreID >= 0 ? coreID : NULL); xTaskCreatePinnedToCore((TaskFunction_t)&worker, taskName, SERVER_TASK_STACK, clients[i], 5, &clients[i]->task, coreID >= 0 ? coreID : tskNO_AFFINITY);
LOG_D("Started client %d task %d\n", i, (uint32_t)(clients[i]->task)); LOG_D("Started client %d task %d\n", i, (uint32_t)(clients[i]->task));
return true; return true;

View File

@@ -109,8 +109,8 @@ const uint8_t swapTables[8][8] = {
enum FCType : uint8_t { enum FCType : uint8_t {
FC01_TYPE, // Two uint16_t parameters (FC 0x01, 0x02, 0x03, 0x04, 0x05, 0x06) FC01_TYPE, // Two uint16_t parameters (FC 0x01, 0x02, 0x03, 0x04, 0x05, 0x06)
FC07_TYPE, // no additional parameter (FCs 0x07, 0x0b, 0x0c, 0x11) FC07_TYPE, // no additional parameter (FCs 0x07, 0x0b, 0x0c, 0x11)
FC0F_TYPE, // two uint16_t parameters, a uint8_t length byte and a uint16_t* pointer to array of bytes (FC 0x0f) FC0F_TYPE, // two uint16_t parameters, a uint8_t length byte and a uint8_t* pointer to array of bytes (FC 0x0f)
FC10_TYPE, // two uint16_t parameters, a uint8_t length byte and a uint8_t* pointer to array of words (FC 0x10) FC10_TYPE, // two uint16_t parameters, a uint8_t length byte and a uint16_t* pointer to array of words (FC 0x10)
FC16_TYPE, // three uint16_t parameters (FC 0x16) FC16_TYPE, // three uint16_t parameters (FC 0x16)
FC18_TYPE, // one uint16_t parameter (FC 0x18) FC18_TYPE, // one uint16_t parameter (FC 0x18)
FCGENERIC, // for FCs not yet explicitly coded (or too complex) FCGENERIC, // for FCs not yet explicitly coded (or too complex)

View File

@@ -3,13 +3,13 @@
// MIT license - see license.md for details // MIT license - see license.md for details
// ================================================================================================= // =================================================================================================
#include "options.h" #include "options.h"
#if HAS_FREERTOS
#include "ModbusMessage.h" #include "ModbusMessage.h"
#include "RTUutils.h" #include "RTUutils.h"
#undef LOCAL_LOG_LEVEL #undef LOCAL_LOG_LEVEL
// #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE // #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE
#include "Logging.h" #include "Logging.h"
#if HAS_FREERTOS
// calcCRC: calculate Modbus CRC16 on a given array of bytes // calcCRC: calculate Modbus CRC16 on a given array of bytes
uint16_t RTUutils::calcCRC(const uint8_t *data, uint16_t len) { uint16_t RTUutils::calcCRC(const uint8_t *data, uint16_t len) {
// CRC16 pre-calculated tables // CRC16 pre-calculated tables
@@ -57,10 +57,9 @@ uint16_t RTUutils::calcCRC(const uint8_t *data, uint16_t len) {
uint8_t crcHi = 0xFF; uint8_t crcHi = 0xFF;
uint8_t crcLo = 0xFF; uint8_t crcLo = 0xFF;
uint8_t index;
while (len--) { while (len--) {
index = crcLo ^ *data++; uint8_t index = crcLo ^ *data++;
crcLo = crcHi ^ crcHiTable[index]; crcLo = crcHi ^ crcHiTable[index];
crcHi = crcLoTable[index]; crcHi = crcLoTable[index];
} }
@@ -464,4 +463,5 @@ const char RTUutils::ASCIIread[] = {
const char RTUutils::ASCIIwrite[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, const char RTUutils::ASCIIwrite[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46
}; };
#endif #endif

View File

@@ -5,9 +5,6 @@
#ifndef _RTU_UTILS_H #ifndef _RTU_UTILS_H
#define _RTU_UTILS_H #define _RTU_UTILS_H
#include <stdint.h> #include <stdint.h>
#if NEED_UART_PATCH
#include <soc/uart_struct.h>
#endif
#include <vector> #include <vector>
#include "Stream.h" #include "Stream.h"
#include "ModbusTypeDefs.h" #include "ModbusTypeDefs.h"
@@ -52,11 +49,13 @@ public:
// RTSauto: dummy callback for auto half duplex RS485 boards // RTSauto: dummy callback for auto half duplex RS485 boards
inline static void RTSauto(bool level) { return; } // NOLINT inline static void RTSauto(bool level) { return; } // NOLINT
#if HAS_FREERTOS
// Necessary preparations for a HardwareSerial // Necessary preparations for a HardwareSerial
static void prepareHardwareSerial(HardwareSerial& s, uint16_t bufferSize = 260) { static void prepareHardwareSerial(HardwareSerial& s, uint16_t bufferSize = 260) {
s.setRxBufferSize(bufferSize); s.setRxBufferSize(bufferSize);
s.setTxBufferSize(bufferSize); s.setTxBufferSize(bufferSize);
} }
#endif
protected: protected:
// Printable characters for ASCII protocol: 012345678ABCDEF // Printable characters for ASCII protocol: 012345678ABCDEF

View File

@@ -12,7 +12,6 @@
#define HAS_FREERTOS 1 #define HAS_FREERTOS 1
#define HAS_ETHERNET 1 #define HAS_ETHERNET 1
#define IS_LINUX 0 #define IS_LINUX 0
#define NEED_UART_PATCH 1
const unsigned int SERVER_TASK_STACK = 4096; const unsigned int SERVER_TASK_STACK = 4096;
const unsigned int CLIENT_TASK_STACK = 4096; const unsigned int CLIENT_TASK_STACK = 4096;
@@ -23,7 +22,6 @@ const unsigned int CLIENT_TASK_STACK = 4096;
#define HAS_FREERTOS 0 #define HAS_FREERTOS 0
#define HAS_ETHERNET 0 #define HAS_ETHERNET 0
#define IS_LINUX 0 #define IS_LINUX 0
#define NEED_UART_PATCH 0
/* === LINUX DEFINITIONS AND MACROS === */ /* === LINUX DEFINITIONS AND MACROS === */
#elif defined(__linux__) #elif defined(__linux__)
@@ -31,7 +29,6 @@ const unsigned int CLIENT_TASK_STACK = 4096;
#define HAS_FREERTOS 0 #define HAS_FREERTOS 0
#define HAS_ETHERNET 0 #define HAS_ETHERNET 0
#define IS_LINUX 1 #define IS_LINUX 1
#define NEED_UART_PATCH 0
#include <cstdio> // for printf() #include <cstdio> // for printf()
#include <cstring> // for memcpy(), strlen() etc. #include <cstring> // for memcpy(), strlen() etc.
#include <cinttypes> // for uint32_t etc. #include <cinttypes> // for uint32_t etc.