Update eModbus to v1.7.1stable

This commit is contained in:
Daniel 2024-03-28 23:59:38 +02:00
parent 73e01b8a37
commit ca452d120b
12 changed files with 114 additions and 43 deletions

View file

@ -98,7 +98,7 @@ public:
// Return number of coils set to 0 (or OFF) // Return number of coils set to 0 (or OFF)
uint16_t coilsSetOFF() const; uint16_t coilsSetOFF() const;
#if !ISLINUX #if !LINUX
// Helper function to dump out coils in logical order // Helper function to dump out coils in logical order
void print(const char *label, Print& s); void print(const char *label, Print& s);
#endif #endif

View file

@ -29,8 +29,8 @@ public:
ModbusBridge(); ModbusBridge();
// Constructors for the RTU variant. Parameters as are for ModbusServerRTU // Constructors for the RTU variant. Parameters as are for ModbusServerRTU
ModbusBridge(HardwareSerial& serial, uint32_t timeout, int rtsPin = -1); ModbusBridge(uint32_t timeout, int rtsPin = -1);
ModbusBridge(HardwareSerial& serial, uint32_t timeout, RTScallback rts); ModbusBridge(uint32_t timeout, RTScallback rts);
// Destructor // Destructor
~ModbusBridge(); ~ModbusBridge();
@ -85,13 +85,13 @@ ModbusBridge<SERVERCLASS>::ModbusBridge() :
// Constructors for RTU variant // Constructors for RTU variant
template<typename SERVERCLASS> template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::ModbusBridge(HardwareSerial& serial, uint32_t timeout, int rtsPin) : ModbusBridge<SERVERCLASS>::ModbusBridge(uint32_t timeout, int rtsPin) :
SERVERCLASS(serial, timeout, rtsPin) { } SERVERCLASS(timeout, rtsPin) { }
// Alternate constructors for RTU variant // Alternate constructors for RTU variant
template<typename SERVERCLASS> template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::ModbusBridge(HardwareSerial& serial, uint32_t timeout, RTScallback rts) : ModbusBridge<SERVERCLASS>::ModbusBridge(uint32_t timeout, RTScallback rts) :
SERVERCLASS(serial, timeout, rts) { } SERVERCLASS(timeout, rts) { }
// Destructor // Destructor
template<typename SERVERCLASS> template<typename SERVERCLASS>

View file

@ -10,7 +10,7 @@
// #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE // #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE
#include "Logging.h" #include "Logging.h"
// Constructor takes Serial reference and optional DE/RE pin // Constructor takes an optional DE/RE pin and queue size
ModbusClientRTU::ModbusClientRTU(int8_t rtsPin, uint16_t queueLimit) : ModbusClientRTU::ModbusClientRTU(int8_t rtsPin, uint16_t queueLimit) :
ModbusClient(), ModbusClient(),
MR_serial(nullptr), MR_serial(nullptr),
@ -32,7 +32,7 @@ ModbusClientRTU::ModbusClientRTU(int8_t rtsPin, uint16_t queueLimit) :
} }
} }
// Alternative constructor takes Serial reference and RTS callback function // Alternative constructor takes an RTS callback function
ModbusClientRTU::ModbusClientRTU(RTScallback rts, uint16_t queueLimit) : ModbusClientRTU::ModbusClientRTU(RTScallback rts, uint16_t queueLimit) :
ModbusClient(), ModbusClient(),
MR_serial(nullptr), MR_serial(nullptr),
@ -54,20 +54,20 @@ ModbusClientRTU::~ModbusClientRTU() {
} }
// begin: start worker task - general version // begin: start worker task - general version
void ModbusClientRTU::begin(Stream& serial, uint32_t baudRate, int coreID) { void ModbusClientRTU::begin(Stream& serial, uint32_t baudRate, int coreID, uint32_t userInterval) {
MR_serial = &serial; MR_serial = &serial;
doBegin(baudRate, coreID); doBegin(baudRate, coreID, userInterval);
} }
// begin: start worker task - HardwareSerial version // begin: start worker task - HardwareSerial version
void ModbusClientRTU::begin(HardwareSerial& serial, int coreID) { void ModbusClientRTU::begin(HardwareSerial& serial, int coreID, uint32_t userInterval) {
MR_serial = &serial; MR_serial = &serial;
uint32_t baudRate = serial.baudRate(); uint32_t baudRate = serial.baudRate();
serial.setRxFIFOFull(1); serial.setRxFIFOFull(1);
doBegin(baudRate, coreID); doBegin(baudRate, coreID, userInterval);
} }
void ModbusClientRTU::doBegin(uint32_t baudRate, int coreID) { void ModbusClientRTU::doBegin(uint32_t baudRate, int coreID, uint32_t userInterval) {
// Task already running? End it in case // Task already running? End it in case
end(); end();
@ -77,11 +77,16 @@ void ModbusClientRTU::doBegin(uint32_t baudRate, int coreID) {
// Set minimum interval time // Set minimum interval time
MR_interval = RTUutils::calculateInterval(baudRate); MR_interval = RTUutils::calculateInterval(baudRate);
// If user defined interval is longer, use that
if (MR_interval < userInterval) {
MR_interval = userInterval;
}
// Create unique task name // Create unique task name
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, 4096, this, 6, &worker, coreID >= 0 ? coreID : NULL); xTaskCreatePinnedToCore((TaskFunction_t)&handleConnection, taskName, CLIENT_TASK_STACK, this, 6, &worker, coreID >= 0 ? coreID : NULL);
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);
} }
@ -102,6 +107,7 @@ void ModbusClientRTU::end() {
// Kill task // Kill task
vTaskDelete(worker); vTaskDelete(worker);
LOG_D("Client task %d killed.\n", (uint32_t)worker); LOG_D("Client task %d killed.\n", (uint32_t)worker);
worker = nullptr;
} }
} }
@ -140,6 +146,14 @@ uint32_t ModbusClientRTU::pendingRequests() {
return requests.size(); return requests.size();
} }
// Remove all pending request from queue
void ModbusClientRTU::clearQueue()
{
std::queue<RequestEntry> empty;
LOCK_GUARD(lockGuard, qLock);
std::swap(requests, empty);
}
// Base addRequest taking a preformatted data buffer and length as parameters // Base addRequest taking a preformatted data buffer and length as parameters
Error ModbusClientRTU::addRequestM(ModbusMessage msg, uint32_t token) { Error ModbusClientRTU::addRequestM(ModbusMessage msg, uint32_t token) {
Error rc = SUCCESS; // Return value Error rc = SUCCESS; // Return value

View file

@ -21,19 +21,19 @@ using std::queue;
class ModbusClientRTU : public ModbusClient { class ModbusClientRTU : public ModbusClient {
public: public:
// Constructor takes Serial reference and optional DE/RE pin and queue limit // Constructor takes an optional DE/RE pin and queue limit
explicit ModbusClientRTU(int8_t rtsPin = -1, uint16_t queueLimit = 100); explicit ModbusClientRTU(int8_t rtsPin = -1, uint16_t queueLimit = 100);
// Alternative Constructor takes Serial reference and RTS line toggle callback // Alternative Constructor takes an RTS line toggle callback
explicit ModbusClientRTU(RTScallback rts, uint16_t queueLimit = 100); explicit ModbusClientRTU(RTScallback rts, uint16_t queueLimit = 100);
// Destructor: clean up queue, task etc. // Destructor: clean up queue, task etc.
~ModbusClientRTU(); ~ModbusClientRTU();
// begin: start worker task // begin: start worker task
void begin(Stream& serial, uint32_t baudrate, int coreID = -1); void begin(Stream& serial, uint32_t baudrate, int coreID = -1, uint32_t userInterval = 0);
// Special variant for HardwareSerial // Special variant for HardwareSerial
void begin(HardwareSerial& serial, int coreID = -1); void begin(HardwareSerial& serial, int coreID = -1, uint32_t userInterval = 0);
// end: stop the worker // end: stop the worker
void end(); void end();
@ -56,6 +56,9 @@ public:
// Return number of unprocessed requests in queue // Return number of unprocessed requests in queue
uint32_t pendingRequests(); uint32_t pendingRequests();
// Remove all pending request from queue
void clearQueue();
// addBroadcastMessage: create a fire-and-forget message to all servers on the RTU bus // addBroadcastMessage: create a fire-and-forget message to all servers on the RTU bus
Error addBroadcastMessage(const uint8_t *data, uint8_t len); Error addBroadcastMessage(const uint8_t *data, uint8_t len);
@ -84,7 +87,7 @@ protected:
ModbusMessage receive(const ModbusMessage request); ModbusMessage receive(const ModbusMessage request);
// start background task // start background task
void doBegin(uint32_t baudRate, int coreID); void doBegin(uint32_t baudRate, int coreID, uint32_t userInterval);
void isInstance() { return; } // make class instantiable 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

View file

@ -18,30 +18,48 @@ void ModbusServer::registerWorker(uint8_t serverID, uint8_t functionCode, MBSwor
// getWorker: if a worker function is registered, return its address, nullptr otherwise // getWorker: if a worker function is registered, return its address, nullptr otherwise
MBSworker ModbusServer::getWorker(uint8_t serverID, uint8_t functionCode) { MBSworker ModbusServer::getWorker(uint8_t serverID, uint8_t functionCode) {
bool serverFound = false;
LOG_D("Need worker for %02X-%02X : ", serverID, functionCode);
// Search the FC map associated with the serverID // Search the FC map associated with the serverID
auto svmap = workerMap.find(serverID); auto svmap = workerMap.find(serverID);
// Is there one? // Is there one?
if (svmap != workerMap.end()) { if (svmap != workerMap.end()) {
serverFound = true;
// No explicit serverID entry found, but we may have one for ANY_SERVER
} else {
svmap = workerMap.find(ANY_SERVER);
if (svmap != workerMap.end()) {
serverFound = true;
serverID = ANY_SERVER;
}
}
// Did we find a serverID?
if (serverFound) {
// Yes. Now look for the function code in the inner map // Yes. Now look for the function code in the inner map
bool functionCodeFound = false;
auto fcmap = svmap->second.find(functionCode);; auto fcmap = svmap->second.find(functionCode);;
// Found it? // Found it?
if (fcmap != svmap->second.end()) { if (fcmap != svmap->second.end()) {
// Yes. Return the function pointer for it. // Yes. Return the function pointer for it.
LOG_D("Worker found for %02X/%02X\n", serverID, functionCode); functionCodeFound = true;
return fcmap->second;
// No, no explicit worker found, but may be there is one for ANY_FUNCTION_CODE? // No, no explicit worker found, but may be there is one for ANY_FUNCTION_CODE?
} else { } else {
fcmap = svmap->second.find(ANY_FUNCTION_CODE);; fcmap = svmap->second.find(ANY_FUNCTION_CODE);;
// Found it? // Found it?
if (fcmap != svmap->second.end()) { if (fcmap != svmap->second.end()) {
// Yes. Return the function pointer for it. // Yes. Return the function pointer for it.
LOG_D("Worker found for %02X/ANY\n", serverID); functionCodeFound = true;
return fcmap->second; functionCode = ANY_FUNCTION_CODE;
} }
} }
if (functionCodeFound) {
// Yes. Return the function pointer for it.
LOGRAW_D("Worker found for %02X/%02X\n", serverID, functionCode);
return fcmap->second;
}
} }
// No matching function pointer found // No matching function pointer found
LOG_D("No matching worker found\n"); LOGRAW_D("No matching worker found\n");
return nullptr; return nullptr;
} }
@ -68,16 +86,29 @@ bool ModbusServer::unregisterWorker(uint8_t serverID, uint8_t functionCode) {
return (numEntries ? true : false); return (numEntries ? true : false);
} }
// isServerFor: if any worker function is registered for the given serverID, return true // isServerFor: if a worker function is registered for the given serverID, return true
bool ModbusServer::isServerFor(uint8_t serverID) { // functionCode defaults to ANY_FUNCTION_CODE and will yield true for any function code,
// Search the FC map for the serverID // including ANY_FUNCTION_CODE :D
auto svmap = workerMap.find(serverID); bool ModbusServer::isServerFor(uint8_t serverID, uint8_t functionCode) {
// Is it there? Then return true // Check if there is a non-nullptr function for the given combination
if (svmap != workerMap.end()) return true; if (getWorker(serverID, functionCode)) {
// No, serverID was not found. Return false return true;
}
return false; return false;
} }
// isServerFor: short version to look up if the server is known at all
bool ModbusServer::isServerFor(uint8_t serverID) {
// Check if there is a non-nullptr function for the given combination
auto svmap = workerMap.find(serverID);
// Is there one?
if (svmap != workerMap.end()) {
return true;
}
return false;
}
// getMessageCount: read number of messages processed // getMessageCount: read number of messages processed
uint32_t ModbusServer::getMessageCount() { uint32_t ModbusServer::getMessageCount() {
return messageCount; return messageCount;
@ -104,6 +135,7 @@ ModbusMessage ModbusServer::localRequest(ModbusMessage msg) {
uint8_t functionCode = msg.getFunctionCode(); uint8_t functionCode = msg.getFunctionCode();
LOG_D("Local request for %02X/%02X\n", serverID, functionCode); LOG_D("Local request for %02X/%02X\n", serverID, functionCode);
HEXDUMP_V("Request", msg.data(), msg.size()); HEXDUMP_V("Request", msg.data(), msg.size());
messageCount++;
// Try to get a worker for the request // Try to get a worker for the request
MBSworker worker = getWorker(serverID, functionCode); MBSworker worker = getWorker(serverID, functionCode);
// Did we get one? // Did we get one?
@ -129,6 +161,9 @@ ModbusMessage ModbusServer::localRequest(ModbusMessage msg) {
} }
} }
HEXDUMP_V("Response", m.data(), m.size()); HEXDUMP_V("Response", m.data(), m.size());
if (m.getError() != SUCCESS) {
errorCount++;
}
return m; return m;
} else { } else {
LOG_D("No worker found. Error response.\n"); LOG_D("No worker found. Error response.\n");
@ -140,11 +175,13 @@ ModbusMessage ModbusServer::localRequest(ModbusMessage msg) {
// No. Respond with "Invalid server ID" // No. Respond with "Invalid server ID"
m.setError(serverID, functionCode, INVALID_SERVER); m.setError(serverID, functionCode, INVALID_SERVER);
} }
errorCount++;
return m; return m;
} }
// We should never get here... // We should never get here...
LOG_C("Internal problem: should not get here!\n"); LOG_C("Internal problem: should not get here!\n");
m.setError(serverID, functionCode, UNDEFINED_ERROR); m.setError(serverID, functionCode, UNDEFINED_ERROR);
errorCount++;
return m; return m;
} }

View file

@ -42,7 +42,10 @@ public:
// Returns true if the worker was found and removed // Returns true if the worker was found and removed
bool unregisterWorker(uint8_t serverID, uint8_t functionCode = 0); bool unregisterWorker(uint8_t serverID, uint8_t functionCode = 0);
// isServerFor: if any worker function is registered for the given serverID, return true // isServerFor: if a worker function is registered for the given serverID, return true
bool isServerFor(uint8_t serverID, uint8_t functionCode);
// isServerFor: short version to look up if the server is known at all
bool isServerFor(uint8_t serverID); bool isServerFor(uint8_t serverID);
// getMessageCount: read number of messages processed // getMessageCount: read number of messages processed

View file

@ -64,32 +64,37 @@ ModbusServerRTU::~ModbusServerRTU() {
} }
// start: create task with RTU server - general version // start: create task with RTU server - general version
void ModbusServerRTU::begin(Stream& serial, uint32_t baudRate, int coreID) { void ModbusServerRTU::begin(Stream& serial, uint32_t baudRate, int coreID, uint32_t userInterval) {
MSRserial = &serial; MSRserial = &serial;
doBegin(baudRate, coreID); doBegin(baudRate, coreID, userInterval);
} }
// start: create task with RTU server - HardwareSerial versions // start: create task with RTU server - HardwareSerial versions
void ModbusServerRTU::begin(HardwareSerial& serial, int coreID) { void ModbusServerRTU::begin(HardwareSerial& serial, int coreID, uint32_t userInterval) {
MSRserial = &serial; MSRserial = &serial;
uint32_t baudRate = serial.baudRate(); uint32_t baudRate = serial.baudRate();
serial.setRxFIFOFull(1); serial.setRxFIFOFull(1);
doBegin(baudRate, coreID); doBegin(baudRate, coreID, userInterval);
} }
void ModbusServerRTU::doBegin(uint32_t baudRate, int coreID) { void ModbusServerRTU::doBegin(uint32_t baudRate, int coreID, uint32_t userInterval) {
// Task already running? Stop it in case. // Task already running? Stop it in case.
end(); end();
// Set minimum interval time // Set minimum interval time
MSRinterval = RTUutils::calculateInterval(baudRate); MSRinterval = RTUutils::calculateInterval(baudRate);
// If user defined interval is longer, use that
if (MSRinterval < userInterval) {
MSRinterval = userInterval;
}
// Create unique task name // Create unique task name
char taskName[18]; char taskName[18];
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, 4096, this, 8, &serverTask, coreID >= 0 ? coreID : NULL); xTaskCreatePinnedToCore((TaskFunction_t)&serve, taskName, SERVER_TASK_STACK, this, 8, &serverTask, coreID >= 0 ? coreID : NULL);
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);
} }
@ -180,10 +185,12 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
} }
// Is it a broadcast? // Is it a broadcast?
if (request[0] == 0) { if (request[0] == 0) {
LOG_D("Broadcast!\n");
// Yes. Do we have a listener? // Yes. Do we have a listener?
if (myServer->listener) { if (myServer->listener) {
// Yes. call it // Yes. call it
myServer->listener(request); myServer->listener(request);
LOG_D("Broadcast served.\n");
} }
// else we simply ignore it // else we simply ignore it
} else { } else {

View file

@ -32,8 +32,8 @@ public:
~ModbusServerRTU(); ~ModbusServerRTU();
// begin: create task with RTU server to accept requests // begin: create task with RTU server to accept requests
void begin(Stream& serial, uint32_t baudRate, int coreID = -1); void begin(Stream& serial, uint32_t baudRate, int coreID = -1, uint32_t userInterval = 0);
void begin(HardwareSerial& serial, int coreID = -1); void begin(HardwareSerial& serial, int coreID = -1, uint32_t userInterval = 0);
// end: kill server task // end: kill server task
void end(); void end();
@ -64,7 +64,7 @@ protected:
inline void isInstance() { } // Make class instantiable inline void isInstance() { } // Make class instantiable
// internal common begin function // internal common begin function
void doBegin(uint32_t baudRate, int coreID); void doBegin(uint32_t baudRate, int coreID, uint32_t userInterval);
static uint8_t instanceCounter; // Number of RTU servers created (for task names) static uint8_t instanceCounter; // Number of RTU servers created (for task names)
TaskHandle_t serverTask; // task of the started server TaskHandle_t serverTask; // task of the started server

View file

@ -50,7 +50,7 @@ FCType FCT::getType(uint8_t functionCode) {
return table[functionCode & 0x7F]; return table[functionCode & 0x7F];
} }
// setType: change the type of a function code. // redefineType: change the type of a function code.
// This is possible only for the codes undefined yet and will return // This is possible only for the codes undefined yet and will return
// the effective type // the effective type
FCType FCT::redefineType(uint8_t functionCode, const FCType type) { FCType FCT::redefineType(uint8_t functionCode, const FCType type) {

View file

@ -84,6 +84,9 @@ enum Error : uint8_t {
UNDEFINED_ERROR = 0xFF // otherwise uncovered communication error UNDEFINED_ERROR = 0xFF // otherwise uncovered communication error
}; };
// Readable expression for the "illegal" server ID of 0
#define ANY_SERVER 0x00
#ifndef MINIMAL #ifndef MINIMAL
// Constants for float and double re-ordering // Constants for float and double re-ordering

View file

@ -9,6 +9,7 @@
// #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
@ -463,3 +464,4 @@ 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

View file

@ -13,6 +13,8 @@
#define HAS_ETHERNET 1 #define HAS_ETHERNET 1
#define IS_LINUX 0 #define IS_LINUX 0
#define NEED_UART_PATCH 1 #define NEED_UART_PATCH 1
const unsigned int SERVER_TASK_STACK = 4096;
const unsigned int CLIENT_TASK_STACK = 4096;
/* === ESP8266 DEFINITIONS AND MACROS === */ /* === ESP8266 DEFINITIONS AND MACROS === */
#elif defined(ESP8266) #elif defined(ESP8266)