diff --git a/lib/eModbus/library.json b/lib/eModbus/library.json index 43e9b7e44..0f90e166e 100644 --- a/lib/eModbus/library.json +++ b/lib/eModbus/library.json @@ -1,6 +1,6 @@ { "name": "eModbus", - "version": "1.7.0", + "version": "1.7.2", "keywords": "Arduino, ESP32, Modbus, RTU, ASCII, ModbusASCII, ModbusRTU, ModbusTCP", "description": "ModbusRTU, ModbusASCII and ModbusTCP functions for ESP32", "homepage": "https://emodbus.github.io", @@ -23,6 +23,25 @@ "url": "https://github.com/eModbus/eModbus", "branch": "master" }, + "dependencies": [ + { + "owner": "me-no-dev", + "name": "AsyncTCP", + "version": "*", + "platforms": ["espressif32"] + }, + { + "owner": "me-no-dev", + "name": "ESPAsyncTCP", + "version": "*", + "platforms": ["espressif8266"] + }, + { + "name": "Ethernet", + "version": "https://github.com/arduino-libraries/Ethernet.git", + "platforms": ["espressif32"] + } + ], "export": { "include": [ diff --git a/lib/eModbus/library.properties b/lib/eModbus/library.properties index 4143f59eb..4dab600ac 100644 --- a/lib/eModbus/library.properties +++ b/lib/eModbus/library.properties @@ -1,5 +1,5 @@ name=eModbus -version=1.7.0 +version=1.7.2 author=bertmelis,Miq1 maintainer=Miq1 sentence=eModbus provides Modbus RTU, ASCII and TCP functions for ESP32. diff --git a/lib/eModbus/src/ModbusBridgeETH.h b/lib/eModbus/src/ModbusBridgeETH.h new file mode 100644 index 000000000..ba43abf80 --- /dev/null +++ b/lib/eModbus/src/ModbusBridgeETH.h @@ -0,0 +1,21 @@ +// ================================================================================================= +// eModbus: Copyright 2024 by Michael Harwerth, Bert Melis and the contributors to eModbus +// MIT license - see license.md for details +// ================================================================================================= +#ifndef _MODBUS_BRIDGE_ETHERNET_H +#define _MODBUS_BRIDGE_ETHERNET_H +#include "options.h" +#if HAS_ETHERNET == 1 +#include +#include + +#undef SERVER_END +#define SERVER_END // NIL for Ethernet + +#include "ModbusServerTCPtemp.h" +#include "ModbusBridgeTemp.h" + +using ModbusBridgeEthernet = ModbusBridge>; +#endif + +#endif diff --git a/lib/eModbus/src/ModbusBridgeTemp.h b/lib/eModbus/src/ModbusBridgeTemp.h index 31aa2e0ba..8bd2f039c 100644 --- a/lib/eModbus/src/ModbusBridgeTemp.h +++ b/lib/eModbus/src/ModbusBridgeTemp.h @@ -29,8 +29,8 @@ public: ModbusBridge(); // Constructors for the RTU variant. Parameters as are for ModbusServerRTU - ModbusBridge(HardwareSerial& serial, uint32_t timeout, int rtsPin = -1); - ModbusBridge(HardwareSerial& serial, uint32_t timeout, RTScallback rts); + ModbusBridge(uint32_t timeout, int rtsPin = -1); + ModbusBridge(uint32_t timeout, RTScallback rts); // Destructor ~ModbusBridge(); @@ -43,6 +43,12 @@ public: // Block a function code (respond with ILLEGAL_FUNCTION error) bool denyFunctionCode(uint8_t aliasID, uint8_t functionCode); + + // Add/remove request/response filters + bool addRequestFilter(uint8_t aliasID, MBSworker rF); + bool removeRequestFilter(uint8_t aliasID); + bool addResponseFilter(uint8_t aliasID, MBSworker rF); + bool removeResponseFilter(uint8_t aliasID); protected: // ServerData holds all data necessary to address a single server @@ -52,6 +58,8 @@ protected: ServerType serverType; // TCP_SERVER or RTU_SERVER IPAddress host; // TCP: host IP address, else 0.0.0.0 uint16_t port; // TCP: host port number, else 0 + MBSworker requestFilter; // optional filter requests before forwarding them + MBSworker responseFilter; // optional filter responses before forwarding them // RTU constructor ServerData(uint8_t sid, ModbusClient *c) : @@ -59,7 +67,9 @@ protected: client(c), serverType(RTU_SERVER), host(IPAddress(0, 0, 0, 0)), - port(0) {} + port(0), + requestFilter(nullptr), + responseFilter(nullptr) {} // TCP constructor ServerData(uint8_t sid, ModbusClient *c, IPAddress h, uint16_t p) : @@ -67,7 +77,9 @@ protected: client(c), serverType(TCP_SERVER), host(h), - port(p) {} + port(p), + requestFilter(nullptr), + responseFilter(nullptr) {} }; // Default worker functions @@ -85,13 +97,13 @@ ModbusBridge::ModbusBridge() : // Constructors for RTU variant template -ModbusBridge::ModbusBridge(HardwareSerial& serial, uint32_t timeout, int rtsPin) : - SERVERCLASS(serial, timeout, rtsPin) { } +ModbusBridge::ModbusBridge(uint32_t timeout, int rtsPin) : + SERVERCLASS(timeout, rtsPin) { } // Alternate constructors for RTU variant template -ModbusBridge::ModbusBridge(HardwareSerial& serial, uint32_t timeout, RTScallback rts) : - SERVERCLASS(serial, timeout, rts) { } +ModbusBridge::ModbusBridge(uint32_t timeout, RTScallback rts) : + SERVERCLASS(timeout, rts) { } // Destructor template @@ -156,6 +168,62 @@ bool ModbusBridge::denyFunctionCode(uint8_t aliasID, uint8_t functi return true; } +template +bool ModbusBridge::addRequestFilter(uint8_t aliasID, MBSworker rF) { + // Is there already an entry for the aliasID? + if (servers.find(aliasID) != servers.end()) { + // Yes. Chain in filter function + servers[aliasID]->requestFilter = rF; + LOG_D("Request filter added for server %02X\n", aliasID); + } else { + LOG_E("Server %d not attached to bridge, no request filter set!\n", aliasID); + return false; + } + return true; +} + +template +bool ModbusBridge::removeRequestFilter(uint8_t aliasID) { + // Is there already an entry for the aliasID? + if (servers.find(aliasID) != servers.end()) { + // Yes. Chain in filter function + servers[aliasID]->requestFilter = nullptr; + LOG_D("Request filter removed for server %02X\n", aliasID); + } else { + LOG_E("Server %d not attached to bridge, no request filter set!\n", aliasID); + return false; + } + return true; +} + +template +bool ModbusBridge::addResponseFilter(uint8_t aliasID, MBSworker rF) { + // Is there already an entry for the aliasID? + if (servers.find(aliasID) != servers.end()) { + // Yes. Chain in filter function + servers[aliasID]->responseFilter = rF; + LOG_D("Response filter added for server %02X\n", aliasID); + } else { + LOG_E("Server %d not attached to bridge, no response filter set!\n", aliasID); + return false; + } + return true; +} + +template +bool ModbusBridge::removeResponseFilter(uint8_t aliasID) { + // Is there already an entry for the aliasID? + if (servers.find(aliasID) != servers.end()) { + // Yes. Chain in filter function + servers[aliasID]->responseFilter = nullptr; + LOG_D("Response filter removed for server %02X\n", aliasID); + } else { + LOG_E("Server %d not attached to bridge, no response filter set!\n", aliasID); + return false; + } + return true; +} + // bridgeWorker: default worker function to process bridge requests template ModbusMessage ModbusBridge::bridgeWorker(ModbusMessage msg) { @@ -167,11 +235,17 @@ ModbusMessage ModbusBridge::bridgeWorker(ModbusMessage msg) { if (servers.find(aliasID) != servers.end()) { // Found it. We may use servers[aliasID] now without allocating a new map slot + // Request filter hook to be called here + if (servers[aliasID]->requestFilter) { + LOG_D("Calling request filter\n"); + msg = servers[aliasID]->requestFilter(msg); + } + // Set real target server ID msg.setServerID(servers[aliasID]->serverID); // Issue the request - LOG_D("Request (%02X/%02X) sent\n", servers[aliasID]->serverID, functionCode); + 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(servers[aliasID]->client)->syncRequestMT(msg, (uint32_t)millis(), servers[aliasID]->host, servers[aliasID]->port); @@ -179,8 +253,19 @@ ModbusMessage ModbusBridge::bridgeWorker(ModbusMessage msg) { response = servers[aliasID]->client->syncRequestM(msg, (uint32_t)millis()); } - // Re-set the requested server ID + // Response filter hook to be called here + if (servers[aliasID]->responseFilter) { + LOG_D("Calling response filter\n"); + response = servers[aliasID]->responseFilter(response); + } + + // Re-set the requested server ID and function code (may have been modified by filters) response.setServerID(aliasID); + if (response.getError() != SUCCESS) { + response.setFunctionCode(functionCode | 0x80); + } else { + response.setFunctionCode(functionCode); + } } else { // If we get here, something has gone wrong internally. We send back an error response anyway. response.setError(aliasID, functionCode, INVALID_SERVER); diff --git a/lib/eModbus/src/ModbusClientRTU.cpp b/lib/eModbus/src/ModbusClientRTU.cpp index ecb0fc9b5..9052ab2c2 100644 --- a/lib/eModbus/src/ModbusClientRTU.cpp +++ b/lib/eModbus/src/ModbusClientRTU.cpp @@ -54,20 +54,20 @@ ModbusClientRTU::~ModbusClientRTU() { } // 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; - doBegin(baudRate, coreID); + doBegin(baudRate, coreID, userInterval); } // 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; uint32_t baudRate = serial.baudRate(); 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 end(); @@ -77,6 +77,11 @@ void ModbusClientRTU::doBegin(uint32_t baudRate, int coreID) { // Set minimum interval time MR_interval = RTUutils::calculateInterval(baudRate); + // If user defined interval is longer, use that + if (MR_interval < userInterval) { + MR_interval = userInterval; + } + // Create unique task name char taskName[18]; snprintf(taskName, 18, "Modbus%02XRTU", instanceCounter); diff --git a/lib/eModbus/src/ModbusClientRTU.h b/lib/eModbus/src/ModbusClientRTU.h index fb4fedbf5..9f419ee34 100644 --- a/lib/eModbus/src/ModbusClientRTU.h +++ b/lib/eModbus/src/ModbusClientRTU.h @@ -31,9 +31,9 @@ public: ~ModbusClientRTU(); // 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 - void begin(HardwareSerial& serial, int coreID = -1); + void begin(HardwareSerial& serial, int coreID = -1, uint32_t userInterval = 0); // end: stop the worker void end(); @@ -87,7 +87,7 @@ protected: ModbusMessage receive(const ModbusMessage request); // 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 queue requests; // Queue to hold requests to be processed diff --git a/lib/eModbus/src/ModbusClientTCPasync.cpp b/lib/eModbus/src/ModbusClientTCPasync.cpp index 463820fb2..163d552a0 100644 --- a/lib/eModbus/src/ModbusClientTCPasync.cpp +++ b/lib/eModbus/src/ModbusClientTCPasync.cpp @@ -7,213 +7,210 @@ // #undef LOCAL_LOG_LEVEL #include "Logging.h" -ModbusClientTCPasync::ModbusClientTCPasync(IPAddress address, uint16_t port, uint16_t queueLimit) - : ModbusClient() - , txQueue() - , rxQueue() - , MTA_client() - , MTA_timeout(DEFAULTTIMEOUT) - , MTA_idleTimeout(DEFAULTIDLETIME) - , MTA_qLimit(queueLimit) - , MTA_maxInflightRequests(queueLimit) - , MTA_lastActivity(0) - , MTA_state(DISCONNECTED) - , MTA_host(address) - , MTA_port(port) { - // attach all handlers on async tcp events - MTA_client.onConnect([](void * i, AsyncClient * c) { (static_cast(i))->onConnected(); }, this); - MTA_client.onDisconnect([](void * i, AsyncClient * c) { (static_cast(i))->onDisconnected(); }, this); - MTA_client.onError([](void * i, AsyncClient * c, int8_t error) { (static_cast(i))->onACError(c, error); }, this); - // MTA_client.onTimeout([](void* i, AsyncClient* c, uint32_t time) { (static_cast(i))->onTimeout(time); }, this); - // MTA_client.onAck([](void* i, AsyncClient* c, size_t len, uint32_t time) { (static_cast(i))->onAck(len, time); }, this); - MTA_client.onData([](void * i, - AsyncClient * c, - void * data, - size_t len) { (static_cast(i))->onPacket(static_cast(data), len); }, - this); - MTA_client.onPoll([](void * i, AsyncClient * c) { (static_cast(i))->onPoll(); }, this); +ModbusClientTCPasync::ModbusClientTCPasync(IPAddress address, uint16_t port, uint16_t queueLimit) : + ModbusClient(), + txQueue(), + rxQueue(), + MTA_client(), + MTA_timeout(DEFAULTTIMEOUT), + MTA_idleTimeout(DEFAULTIDLETIME), + MTA_qLimit(queueLimit), + MTA_maxInflightRequests(queueLimit), + MTA_lastActivity(0), + MTA_state(DISCONNECTED), + MTA_host(address), + MTA_port(port) + { + // attach all handlers on async tcp events + MTA_client.onConnect([](void* i, AsyncClient* c) { (static_cast(i))->onConnected(); }, this); + MTA_client.onDisconnect([](void* i, AsyncClient* c) { (static_cast(i))->onDisconnected(); }, this); + MTA_client.onError([](void* i, AsyncClient* c, int8_t error) { (static_cast(i))->onACError(c, error); }, this); + // MTA_client.onTimeout([](void* i, AsyncClient* c, uint32_t time) { (static_cast(i))->onTimeout(time); }, this); + // MTA_client.onAck([](void* i, AsyncClient* c, size_t len, uint32_t time) { (static_cast(i))->onAck(len, time); }, this); + MTA_client.onData([](void* i, AsyncClient* c, void* data, size_t len) { (static_cast(i))->onPacket(static_cast(data), len); }, this); + MTA_client.onPoll([](void* i, AsyncClient* c) { (static_cast(i))->onPoll(); }, this); - // disable nagle algorithm ref Modbus spec - MTA_client.setNoDelay(true); -} + // disable nagle algorithm ref Modbus spec + MTA_client.setNoDelay(true); + } // Destructor: clean up queue, task etc. ModbusClientTCPasync::~ModbusClientTCPasync() { - // Clean up queue - { - // Safely lock access - LOCK_GUARD(lock1, qLock); - LOCK_GUARD(lock2, sLock); - // Delete all elements from queues - while (!txQueue.empty()) { - delete txQueue.front(); - txQueue.pop_front(); - } - for (auto it = rxQueue.cbegin(); it != rxQueue.cend(); /* no increment */) { - delete it->second; - it = rxQueue.erase(it); - } - } - // force close client - MTA_client.close(true); -} - -// optionally manually connect to modbus server. Otherwise connection will be made upon first request -void ModbusClientTCPasync::connect() { - LOG_D("connecting\n"); - LOCK_GUARD(lock1, sLock); - // only connect if disconnected - if (MTA_state == DISCONNECTED) { - MTA_state = CONNECTING; - MTA_client.connect(MTA_host, MTA_port); - } -} - -// connect to another modbus server. -void ModbusClientTCPasync::connect(IPAddress host, uint16_t port) { - // First disconnect, if connected - disconnect(true); - // Set new host and port - MTA_host = host; - MTA_port = port; - connect(); -} - -// manually disconnect from modbus server. Connection will also auto close after idle time -void ModbusClientTCPasync::disconnect(bool force) { - LOG_D("disconnecting\n"); - MTA_client.close(force); -} - -// Set timeout value -void ModbusClientTCPasync::setTimeout(uint32_t timeout) { - MTA_timeout = timeout; -} - -// Set idle timeout value (time before connection auto closes after being idle) -void ModbusClientTCPasync::setIdleTimeout(uint32_t timeout) { - MTA_idleTimeout = timeout; -} - -void ModbusClientTCPasync::setMaxInflightRequests(uint32_t maxInflightRequests) { - MTA_maxInflightRequests = maxInflightRequests; -} - -// Remove all pending request from queue -void ModbusClientTCPasync::clearQueue() { + // Clean up queue + { + // Safely lock access LOCK_GUARD(lock1, qLock); LOCK_GUARD(lock2, sLock); // Delete all elements from queues while (!txQueue.empty()) { - delete txQueue.front(); - txQueue.pop_front(); + delete txQueue.front(); + txQueue.pop_front(); } + for (auto it = rxQueue.cbegin(); it != rxQueue.cend();/* no increment */) { + delete it->second; + it = rxQueue.erase(it); + } + } + // force close client + MTA_client.close(true); +} + +// optionally manually connect to modbus server. Otherwise connection will be made upon first request +void ModbusClientTCPasync::connect() { + LOG_D("connecting\n"); + LOCK_GUARD(lock1, sLock); + // only connect if disconnected + if (MTA_state == DISCONNECTED) { + MTA_state = CONNECTING; + MTA_client.connect(MTA_host, MTA_port); + } +} + +// connect to another modbus server. +void ModbusClientTCPasync::connect(IPAddress host, uint16_t port) { + // First disconnect, if connected + disconnect(true); + // Set new host and port + MTA_host = host; + MTA_port = port; + connect(); +} + +// manually disconnect from modbus server. Connection will also auto close after idle time +void ModbusClientTCPasync::disconnect(bool force) { + LOG_D("disconnecting\n"); + MTA_client.close(force); +} + +// Set timeout value +void ModbusClientTCPasync::setTimeout(uint32_t timeout) { + MTA_timeout = timeout; +} + +// Set idle timeout value (time before connection auto closes after being idle) +void ModbusClientTCPasync::setIdleTimeout(uint32_t timeout) { + MTA_idleTimeout = timeout; +} + +void ModbusClientTCPasync::setMaxInflightRequests(uint32_t maxInflightRequests) { + MTA_maxInflightRequests = maxInflightRequests; +} + +// Remove all pending request from queue +void ModbusClientTCPasync::clearQueue() +{ + LOCK_GUARD(lock1, qLock); + LOCK_GUARD(lock2, sLock); + // Delete all elements from queues + while (!txQueue.empty()) { + delete txQueue.front(); + txQueue.pop_front(); + } } // Base addRequest for preformatted ModbusMessage and last set target Error ModbusClientTCPasync::addRequestM(ModbusMessage msg, uint32_t token) { - Error rc = SUCCESS; // Return value + Error rc = SUCCESS; // Return value - // Add it to the queue, if valid - if (msg) { - // Queue add successful? - if (!addToQueue(token, msg)) { - // No. Return error after deleting the allocated request. - rc = REQUEST_QUEUE_FULL; - } + // Add it to the queue, if valid + if (msg) { + // Queue add successful? + if (!addToQueue(token, msg)) { + // No. Return error after deleting the allocated request. + rc = REQUEST_QUEUE_FULL; } + } - LOG_D("Add TCP request result: %02X\n", rc); - return rc; + LOG_D("Add TCP request result: %02X\n", rc); + return rc; } // Base syncRequest follows the same pattern ModbusMessage ModbusClientTCPasync::syncRequestM(ModbusMessage msg, uint32_t token) { - ModbusMessage response; + ModbusMessage response; - if (msg) { - // Queue add successful? - if (!addToQueue(token, msg, true)) { - // No. Return error after deleting the allocated request. - response.setError(msg.getServerID(), msg.getFunctionCode(), REQUEST_QUEUE_FULL); - } else { - // Request is queued - wait for the result. - response = waitSync(msg.getServerID(), msg.getFunctionCode(), token); - } + if (msg) { + // Queue add successful? + if (!addToQueue(token, msg, true)) { + // No. Return error after deleting the allocated request. + response.setError(msg.getServerID(), msg.getFunctionCode(), REQUEST_QUEUE_FULL); } else { - response.setError(msg.getServerID(), msg.getFunctionCode(), EMPTY_MESSAGE); + // Request is queued - wait for the result. + response = waitSync(msg.getServerID(), msg.getFunctionCode(), token); } - return response; + } else { + response.setError(msg.getServerID(), msg.getFunctionCode(), EMPTY_MESSAGE); + } + return response; } // addToQueue: send freshly created request to queue bool ModbusClientTCPasync::addToQueue(int32_t token, ModbusMessage request, bool syncReq) { - // Did we get one? - if (request) { - LOCK_GUARD(lock1, qLock); - if (txQueue.size() + rxQueue.size() < MTA_qLimit) { - HEXDUMP_V("Enqueue", request.data(), request.size()); - RequestEntry * re = new RequestEntry(token, request, syncReq); - if (!re) - return false; // TODO proper error returning in case allocation fails - // inject proper transactionID - re->head.transactionID = messageCount++; - re->head.len = request.size(); - // if we're already connected, try to send and push to rxQueue - // or else push to txQueue and (re)connect - if (MTA_state == CONNECTED && send(re)) { - re->sentTime = millis(); - rxQueue[re->head.transactionID] = re; - } else { - txQueue.push_back(re); - if (MTA_state == DISCONNECTED) { - connect(); - } - } - return true; + // Did we get one? + if (request) { + LOCK_GUARD(lock1, qLock); + if (txQueue.size() + rxQueue.size() < MTA_qLimit) { + HEXDUMP_V("Enqueue", request.data(), request.size()); + RequestEntry *re = new RequestEntry(token, request, syncReq); + if (!re) return false; //TODO: proper error returning in case allocation fails + // inject proper transactionID + re->head.transactionID = messageCount++; + re->head.len = request.size(); + // if we're already connected, try to send and push to rxQueue + // or else push to txQueue and (re)connect + if (MTA_state == CONNECTED && send(re)) { + re->sentTime = millis(); + rxQueue[re->head.transactionID] = re; + } else { + txQueue.push_back(re); + if (MTA_state == DISCONNECTED) { + connect(); } - LOG_E("queue is full\n"); + } + return true; } - return false; + LOG_E("queue is full\n"); + } + return false; } void ModbusClientTCPasync::onConnected() { - LOG_D("connected\n"); - LOCK_GUARD(lock1, sLock); - MTA_state = CONNECTED; - MTA_lastActivity = millis(); - // from now on onPoll will be called every 500 msec + LOG_D("connected\n"); + LOCK_GUARD(lock1, sLock); + MTA_state = CONNECTED; + MTA_lastActivity = millis(); + // from now on onPoll will be called every 500 msec } void ModbusClientTCPasync::onDisconnected() { - LOG_D("disconnected\n"); - LOCK_GUARD(lock1, sLock); - MTA_state = DISCONNECTED; + LOG_D("disconnected\n"); + LOCK_GUARD(lock1, sLock); + MTA_state = DISCONNECTED; - // empty queue on disconnect, calling errorcode on every waiting request - LOCK_GUARD(lock2, qLock); - while (!txQueue.empty()) { - RequestEntry * r = txQueue.front(); - if (onError) { - onError(IP_CONNECTION_FAILED, r->token); - } - delete r; - txQueue.pop_front(); + // empty queue on disconnect, calling errorcode on every waiting request + LOCK_GUARD(lock2, qLock); + while (!txQueue.empty()) { + RequestEntry* r = txQueue.front(); + if (onError) { + onError(IP_CONNECTION_FAILED, r->token); } - while (!rxQueue.empty()) { - RequestEntry * r = rxQueue.begin()->second; - if (onError) { - onError(IP_CONNECTION_FAILED, r->token); - } - delete r; - rxQueue.erase(rxQueue.begin()); + delete r; + txQueue.pop_front(); + } + while (!rxQueue.empty()) { + RequestEntry *r = rxQueue.begin()->second; + if (onError) { + onError(IP_CONNECTION_FAILED, r->token); } + delete r; + rxQueue.erase(rxQueue.begin()); + } } -void ModbusClientTCPasync::onACError(AsyncClient * c, int8_t error) { - // onDisconnect will alse be called, so nothing to do here - LOG_W("TCP error: %s\n", c->errorToString(error)); +void ModbusClientTCPasync::onACError(AsyncClient* c, int8_t error) { + // onDisconnect will alse be called, so nothing to do here + LOG_W("TCP error: %s\n", c->errorToString(error)); } /* @@ -225,178 +222,180 @@ void onAck(size_t len, uint32_t time) { // assuming we don't need this } */ -void ModbusClientTCPasync::onPacket(uint8_t * data, size_t length) { - LOG_D("packet received (len:%d)\n", length); - // reset idle timeout - MTA_lastActivity = millis(); +void ModbusClientTCPasync::onPacket(uint8_t* data, size_t length) { + LOG_D("packet received (len:%d)\n", length); + // reset idle timeout + MTA_lastActivity = millis(); - if (length) { - LOG_D("parsing (len:%d)\n", length + 1); + if (length) { + LOG_D("parsing (len:%d)\n", length + 1); + } + while (length > 0) { + RequestEntry* request = nullptr; + ModbusMessage* response = nullptr; + uint16_t transactionID = 0; + uint16_t protocolID = 0; + uint16_t messageLength = 0; + bool isOkay = false; + + // 1. Check for valid modbus message + + // MBAP header is 6 bytes, we can't do anything with less + // total message should fit MBAP plus remaining bytes (in data[4], data[5]) + if (length > 6) { + transactionID = (data[0] << 8) | data[1]; + protocolID = (data[2] << 8) | data[3]; + messageLength = (data[4] << 8) | data[5]; + if (protocolID == 0 && + length >= (uint32_t)messageLength + 6 && + messageLength < 256) { + response = new ModbusMessage(messageLength); + response->add(&data[6], messageLength); + LOG_D("packet validated (len:%d)\n", messageLength); + + // on next iteration: adjust remaining length and pointer to data + length -= 6 + messageLength; + data += 6 + messageLength; + isOkay = true; + } } - while (length > 0) { - RequestEntry * request = nullptr; - ModbusMessage * response = nullptr; - uint16_t transactionID = 0; - uint16_t protocolID = 0; - uint16_t messageLength = 0; - bool isOkay = false; - // 1. Check for valid modbus message + if (!isOkay) { + // invalid packet, abort function + LOG_W("packet invalid\n"); + return; + } else { + // 2. we got a valid response, match with a request + LOCK_GUARD(lock1, qLock); + auto i = rxQueue.find(transactionID); + if (i != rxQueue.end()) { + // found it, handle it and stop iterating + request = i->second; + i = rxQueue.erase(i); + LOG_D("matched request\n"); + } else { + // TCP packet did not yield valid modbus response, abort function + LOG_W("no matching request found\n"); + return; + } + } - // MBAP header is 6 bytes, we can't do anything with less - // total message should fit MBAP plus remaining bytes (in data[4], data[5]) - if (length > 6) { - transactionID = (data[0] << 8) | data[1]; - protocolID = (data[2] << 8) | data[3]; - messageLength = (data[4] << 8) | data[5]; - if (protocolID == 0 && length >= (uint32_t)messageLength + 6 && messageLength < 256) { - response = new ModbusMessage(messageLength); - response->add(&data[6], messageLength); - LOG_D("packet validated (len:%d)\n", messageLength); + // 3. we have a valid request and a valid response, call appropriate callback + if (request) { + // compare request with response + Error error = SUCCESS; + if (request->msg.getFunctionCode() != (response->getFunctionCode() & 0x7F)) { + error = FC_MISMATCH; + } else if (request->msg.getServerID() != response->getServerID()) { + error = SERVER_ID_MISMATCH; + } else { + error = response->getError(); + } - // on next iteration: adjust remaining length and pointer to data - length -= 6 + messageLength; - data += 6 + messageLength; - isOkay = true; - } + if (error != SUCCESS) { + LOCK_GUARD(errorCntLock, countAccessM); + errorCount++; + } + + if (request->isSyncRequest) { + { + LOCK_GUARD(sL ,syncRespM); + syncResponse[request->token] = *response; } - - if (!isOkay) { - // invalid packet, abort function - LOG_W("packet invalid\n"); - return; + } else if (onResponse) { + onResponse(*response, request->token); + } else { + if (error == SUCCESS) { + if (onData) { + onData(*response, request->token); + } } else { - // 2. we got a valid response, match with a request - LOCK_GUARD(lock1, qLock); - auto i = rxQueue.find(transactionID); - if (i != rxQueue.end()) { - // found it, handle it and stop iterating - request = i->second; - i = rxQueue.erase(i); - LOG_D("matched request\n"); - } else { - // TCP packet did not yield valid modbus response, abort function - LOG_W("no matching request found\n"); - return; - } + if (onError) { + onError(response->getError(), request->token); + } } + } + delete request; + } + delete response; - // 3. we have a valid request and a valid response, call appropriate callback - if (request) { - // compare request with response - Error error = SUCCESS; - if (request->msg.getFunctionCode() != (response->getFunctionCode() & 0x7F)) { - error = FC_MISMATCH; - } else if (request->msg.getServerID() != response->getServerID()) { - error = SERVER_ID_MISMATCH; - } else { - error = response->getError(); - } + } // end processing of incoming data - if (error != SUCCESS) { - LOCK_GUARD(errorCntLock, countAccessM); - errorCount++; - } - - if (request->isSyncRequest) { - { - LOCK_GUARD(sL, syncRespM); - syncResponse[request->token] = *response; - } - } else if (onResponse) { - onResponse(*response, request->token); - } else { - if (error == SUCCESS) { - if (onData) { - onData(*response, request->token); - } - } else { - if (onError) { - onError(response->getError(), request->token); - } - } - } - delete request; - } - delete response; - - } // end processing of incoming data - - // check if we have to send the next request - LOCK_GUARD(lock1, qLock); - handleSendingQueue(); + // check if we have to send the next request + LOCK_GUARD(lock1, qLock); + handleSendingQueue(); } void ModbusClientTCPasync::onPoll() { - { - LOCK_GUARD(lock1, qLock); + { + LOCK_GUARD(lock1, qLock); - // try to send whatever is waiting - handleSendingQueue(); + // try to send whatever is waiting + handleSendingQueue(); - // next check if timeout has struck for oldest request - if (!rxQueue.empty()) { - RequestEntry * request = rxQueue.begin()->second; - if (millis() - request->sentTime > MTA_timeout) { - LOG_D("request timeouts (now:%lu-sent:%u)\n", millis(), request->sentTime); - // oldest element timeouts, call onError and clean up - if (onError) { - // Handle timeout error - onError(TIMEOUT, request->token); - } - delete request; - rxQueue.erase(rxQueue.begin()); - } - } - - } // end lockguard scope - - // if nothing happened during idle timeout, gracefully close connection - if (millis() - MTA_lastActivity > MTA_idleTimeout) { - disconnect(); + // next check if timeout has struck for oldest request + if (!rxQueue.empty()) { + RequestEntry* request = rxQueue.begin()->second; + if (millis() - request->sentTime > MTA_timeout) { + LOG_D("request timeouts (now:%lu-sent:%u)\n", millis(), request->sentTime); + // oldest element timeouts, call onError and clean up + if (onError) { + // Handle timeout error + onError(TIMEOUT, request->token); + } + delete request; + rxQueue.erase(rxQueue.begin()); } + } + + } // end lockguard scope + + // if nothing happened during idle timeout, gracefully close connection + if (millis() - MTA_lastActivity > MTA_idleTimeout) { + disconnect(); + } } void ModbusClientTCPasync::handleSendingQueue() { - // ATTENTION: This method does not have a lock guard. - // Calling sites must assure shared resources are protected - // by mutex. + // ATTENTION: This method does not have a lock guard. + // Calling sites must assure shared resources are protected + // by mutex. - // try to send everything we have waiting - std::list::iterator it = txQueue.begin(); - while (it != txQueue.end()) { - // get the actual element - if (send(*it)) { - // after sending, update timeout value, add to other queue and remove from this queue - (*it)->sentTime = millis(); - rxQueue[(*it)->head.transactionID] = (*it); // push request to other queue - it = txQueue.erase(it); // remove from toSend queue and point i to next request - } else { - // sending didn't succeed, try next request - ++it; - } + // try to send everything we have waiting + std::list::iterator it = txQueue.begin(); + while (it != txQueue.end()) { + // get the actual element + if (send(*it)) { + // after sending, update timeout value, add to other queue and remove from this queue + (*it)->sentTime = millis(); + rxQueue[(*it)->head.transactionID] = (*it); // push request to other queue + it = txQueue.erase(it); // remove from toSend queue and point i to next request + } else { + // sending didn't succeed, try next request + ++it; } + } } -bool ModbusClientTCPasync::send(RequestEntry * re) { - // ATTENTION: This method does not have a lock guard. - // Calling sites must assure shared resources are protected - // by mutex. +bool ModbusClientTCPasync::send(RequestEntry* re) { + // ATTENTION: This method does not have a lock guard. + // Calling sites must assure shared resources are protected + // by mutex. - if (rxQueue.size() >= MTA_maxInflightRequests) { - return false; - } - - // check if TCP client is able to send - if (MTA_client.space() > ((uint32_t)re->msg.size() + 6)) { - // Write TCP header first - MTA_client.add(reinterpret_cast((const uint8_t *)(re->head)), 6, ASYNC_WRITE_FLAG_COPY); - // Request comes next - MTA_client.add(reinterpret_cast(re->msg.data()), re->msg.size(), ASYNC_WRITE_FLAG_COPY); - // done - MTA_client.send(); - LOG_D("request sent (msgid:%d)\n", re->head.transactionID); - return true; - } + if (rxQueue.size() >= MTA_maxInflightRequests) { return false; + } + + // check if TCP client is able to send + if (MTA_client.space() > ((uint32_t)re->msg.size() + 6)) { + // Write TCP header first + MTA_client.add(reinterpret_cast((const uint8_t *)(re->head)), 6, ASYNC_WRITE_FLAG_COPY); + // Request comes next + MTA_client.add(reinterpret_cast(re->msg.data()), re->msg.size(), ASYNC_WRITE_FLAG_COPY); + // done + MTA_client.send(); + LOG_D("request sent (msgid:%d)\n", re->head.transactionID); + return true; + } + return false; } diff --git a/lib/eModbus/src/ModbusServer.cpp b/lib/eModbus/src/ModbusServer.cpp index 21ada8334..b814bd9b2 100644 --- a/lib/eModbus/src/ModbusServer.cpp +++ b/lib/eModbus/src/ModbusServer.cpp @@ -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 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 auto svmap = workerMap.find(serverID); // Is there one? 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 + bool functionCodeFound = false; auto fcmap = svmap->second.find(functionCode);; // Found it? if (fcmap != svmap->second.end()) { // Yes. Return the function pointer for it. - LOG_D("Worker found for %02X/%02X\n", serverID, functionCode); - return fcmap->second; + functionCodeFound = true; // No, no explicit worker found, but may be there is one for ANY_FUNCTION_CODE? } else { fcmap = svmap->second.find(ANY_FUNCTION_CODE);; // Found it? if (fcmap != svmap->second.end()) { // Yes. Return the function pointer for it. - LOG_D("Worker found for %02X/ANY\n", serverID); - return fcmap->second; + functionCodeFound = true; + 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 - LOG_D("No matching worker found\n"); + LOGRAW_D("No matching worker found\n"); return nullptr; } @@ -68,16 +86,29 @@ bool ModbusServer::unregisterWorker(uint8_t serverID, uint8_t functionCode) { return (numEntries ? true : false); } -// isServerFor: if any worker function is registered for the given serverID, return true -bool ModbusServer::isServerFor(uint8_t serverID) { - // Search the FC map for the serverID - auto svmap = workerMap.find(serverID); - // Is it there? Then return true - if (svmap != workerMap.end()) return true; - // No, serverID was not found. Return false +// isServerFor: if a worker function is registered for the given serverID, return true +// functionCode defaults to ANY_FUNCTION_CODE and will yield true for any function code, +// including ANY_FUNCTION_CODE :D +bool ModbusServer::isServerFor(uint8_t serverID, uint8_t functionCode) { + // Check if there is a non-nullptr function for the given combination + if (getWorker(serverID, functionCode)) { + return true; + } 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 uint32_t ModbusServer::getMessageCount() { return messageCount; diff --git a/lib/eModbus/src/ModbusServer.h b/lib/eModbus/src/ModbusServer.h index bdc4e90f2..933e27e9c 100644 --- a/lib/eModbus/src/ModbusServer.h +++ b/lib/eModbus/src/ModbusServer.h @@ -42,7 +42,10 @@ public: // Returns true if the worker was found and removed 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); // getMessageCount: read number of messages processed diff --git a/lib/eModbus/src/ModbusServerETH.h b/lib/eModbus/src/ModbusServerETH.h new file mode 100644 index 000000000..a2e045a28 --- /dev/null +++ b/lib/eModbus/src/ModbusServerETH.h @@ -0,0 +1,19 @@ +// ================================================================================================= +// eModbus: Copyright 2024 by Michael Harwerth, Bert Melis and the contributors to eModbus +// MIT license - see license.md for details +// ================================================================================================= +#ifndef _MODBUS_SERVER_ETH_H +#define _MODBUS_SERVER_ETH_H +#include "options.h" +#if HAS_ETHERNET == 1 +#include +#include + +#undef SERVER_END +#define SERVER_END // NIL for Ethernet + +#include "ModbusServerTCPtemp.h" +using ModbusServerEthernet = ModbusServerTCP; +#endif + +#endif diff --git a/lib/eModbus/src/ModbusServerEthernet.h b/lib/eModbus/src/ModbusServerEthernet.h index b1f9ceba1..0a8591031 100644 --- a/lib/eModbus/src/ModbusServerEthernet.h +++ b/lib/eModbus/src/ModbusServerEthernet.h @@ -12,8 +12,15 @@ #undef SERVER_END #define SERVER_END // NIL for Ethernet +// Create own non-virtual EthernetServer class +class EthernetServerEM : public EthernetServer { +public: + EthernetServerEM(uint16_t port) : EthernetServer(port) { } + void begin(uint16_t port = 0) { } +}; + #include "ModbusServerTCPtemp.h" -using ModbusServerEthernet = ModbusServerTCP; +using ModbusServerEthernet = ModbusServerTCP; #endif #endif diff --git a/lib/eModbus/src/ModbusServerRTU.cpp b/lib/eModbus/src/ModbusServerRTU.cpp index 5a70e675a..88859d8d3 100644 --- a/lib/eModbus/src/ModbusServerRTU.cpp +++ b/lib/eModbus/src/ModbusServerRTU.cpp @@ -64,26 +64,31 @@ ModbusServerRTU::~ModbusServerRTU() { } // 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; - doBegin(baudRate, coreID); + doBegin(baudRate, coreID, userInterval); } // 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; uint32_t baudRate = serial.baudRate(); 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. end(); // Set minimum interval time MSRinterval = RTUutils::calculateInterval(baudRate); + // If user defined interval is longer, use that + if (MSRinterval < userInterval) { + MSRinterval = userInterval; + } + // Create unique task name char taskName[18]; snprintf(taskName, 18, "MBsrv%02XRTU", instanceCounter); @@ -180,10 +185,12 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) { } // Is it a broadcast? if (request[0] == 0) { + LOG_D("Broadcast!\n"); // Yes. Do we have a listener? if (myServer->listener) { // Yes. call it myServer->listener(request); + LOG_D("Broadcast served.\n"); } // else we simply ignore it } else { diff --git a/lib/eModbus/src/ModbusServerRTU.h b/lib/eModbus/src/ModbusServerRTU.h index c4c601512..fd5b8ab96 100644 --- a/lib/eModbus/src/ModbusServerRTU.h +++ b/lib/eModbus/src/ModbusServerRTU.h @@ -32,8 +32,8 @@ public: ~ModbusServerRTU(); // begin: create task with RTU server to accept requests - void begin(Stream& serial, uint32_t baudRate, int coreID = -1); - void begin(HardwareSerial& serial, int coreID = -1); + void begin(Stream& serial, uint32_t baudRate, int coreID = -1, uint32_t userInterval = 0); + void begin(HardwareSerial& serial, int coreID = -1, uint32_t userInterval = 0); // end: kill server task void end(); @@ -64,7 +64,7 @@ protected: inline void isInstance() { } // Make class instantiable // 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) TaskHandle_t serverTask; // task of the started server diff --git a/lib/eModbus/src/ModbusServerTCPasync.cpp b/lib/eModbus/src/ModbusServerTCPasync.cpp index 3f869a36a..c08372454 100644 --- a/lib/eModbus/src/ModbusServerTCPasync.cpp +++ b/lib/eModbus/src/ModbusServerTCPasync.cpp @@ -8,260 +8,261 @@ // #undef LOCAL_LOG_LEVEL #include "Logging.h" -ModbusServerTCPasync::mb_client::mb_client(ModbusServerTCPasync * s, AsyncClient * c) - : server(s) - , client(c) - , lastActiveTime(millis()) - , message(nullptr) - , error(SUCCESS) - , outbox() { - client->onData([](void * i, AsyncClient * c, void * data, size_t len) { (static_cast(i))->onData(static_cast(data), len); }, this); - client->onPoll([](void * i, AsyncClient * c) { (static_cast(i))->onPoll(); }, this); - client->onDisconnect([](void * i, AsyncClient * c) { (static_cast(i))->onDisconnect(); }, this); +ModbusServerTCPasync::mb_client::mb_client(ModbusServerTCPasync* s, AsyncClient* c) : + server(s), + client(c), + lastActiveTime(millis()), + message(nullptr), + error(SUCCESS), + outbox() { + client->onData([](void* i, AsyncClient* c, void* data, size_t len) { (static_cast(i))->onData(static_cast(data), len); }, this); + client->onPoll([](void* i, AsyncClient* c) { (static_cast(i))->onPoll(); }, this); + client->onDisconnect([](void* i, AsyncClient* c) { (static_cast(i))->onDisconnect(); }, this); client->setNoDelay(true); } ModbusServerTCPasync::mb_client::~mb_client() { - // clear outbox, if data is left - while (!outbox.empty()) { - outbox.pop(); - } + // clear outbox, if data is left + while (!outbox.empty()) { + outbox.pop(); + } - delete client; // will also close connection, if any + delete client; // will also close connection, if any } -void ModbusServerTCPasync::mb_client::onData(uint8_t * data, size_t len) { - lastActiveTime = millis(); - LOG_D("data len %d\n", len); +void ModbusServerTCPasync::mb_client::onData(uint8_t* data, size_t len) { + lastActiveTime = millis(); + LOG_D("data len %d\n", len); - Error error = SUCCESS; - size_t i = 0; - while (i < len) { - // 0. start - if (!message) { - message = new ModbusMessage(8); - error = SUCCESS; - } + Error error = SUCCESS; + size_t i = 0; + while (i < len) { + // 0. start + if (!message) { + message = new ModbusMessage(8); + error = SUCCESS; + } - // 1. get minimal 8 bytes to move on - while (message->size() < 8 && i < len) { - message->push_back(data[i++]); - } + // 1. get minimal 8 bytes to move on + while (message->size() < 8 && i < len) { + message->push_back(data[i++]); + } + + // 2. preliminary validation: protocol bytes and message length + if ((*message)[2] != 0 || (*message)[3] != 0) { + error = TCP_HEAD_MISMATCH; + LOG_D("invalid protocol\n"); + } + size_t messageLength = (((*message)[4] << 8) | (*message)[5]) + 6; + if (messageLength > 262) { // 256 + MBAP(6) = 262 + error = PACKET_LENGTH_ERROR; + LOG_D("max length error\n"); + } + if (error != SUCCESS) { + ModbusMessage response; + response.setError(message->getServerID(), message->getFunctionCode(), error); + message->resize(4); + message->add(static_cast(3)); + message->append(response); + addResponseToOutbox(message); // outbox has pointer ownership now + // reset to starting values and process remaining data + message = nullptr; + return; // protocol validation, abort further parsing + } - // 2. preliminary validation: protocol bytes and message length - if ((*message)[2] != 0 || (*message)[3] != 0) { - error = TCP_HEAD_MISMATCH; - LOG_D("invalid protocol\n"); - } - size_t messageLength = (((*message)[4] << 8) | (*message)[5]) + 6; - if (messageLength > 262) { // 256 + MBAP(6) = 262 - error = PACKET_LENGTH_ERROR; - LOG_D("max length error\n"); - } - if (error != SUCCESS) { - ModbusMessage response; - response.setError(message->getServerID(), message->getFunctionCode(), error); - message->resize(4); - message->add(static_cast(3)); - message->append(response); - addResponseToOutbox(message); // outbox has pointer ownership now - // reset to starting values and process remaining data - message = nullptr; - return; // protocol validation, abort further parsing - } + // 3. receive until request is complete + while (message->size() < messageLength && i < len) { + message->push_back(data[i++]); + } + if (message->size() == messageLength) { + LOG_D("request complete (len:%d)\n", message->size()); + } else { + LOG_D("request incomplete (len:%d), waiting for next TCP packet\n", message->size()); + continue; + } - // 3. receive until request is complete - while (message->size() < messageLength && i < len) { - message->push_back(data[i++]); - } - if (message->size() == messageLength) { - LOG_D("request complete (len:%d)\n", message->size()); - } else { - LOG_D("request incomplete (len:%d), waiting for next TCP packet\n", message->size()); - continue; - } - - // 4. request complete, process - ModbusMessage request(messageLength - 6); // create request without MBAP, with server ID - request.add(message->data() + 6, message->size() - 6); - ModbusMessage userData; - if (server->isServerFor(request.getServerID())) { - MBSworker callback = server->getWorker(request.getServerID(), request.getFunctionCode()); - if (callback) { - // request is well formed and is being served by user API - userData = callback(request); - // Process Response - // One of the predefined types? - if (userData[0] == 0xFF && (userData[1] == 0xF0 || userData[1] == 0xF1)) { - // Yes. Check it - switch (userData[1]) { - case 0xF0: // NIL - userData.clear(); - LOG_D("NIL response\n"); - break; - case 0xF1: // ECHO - userData = request; - if (request.getFunctionCode() == WRITE_MULT_REGISTERS || request.getFunctionCode() == WRITE_MULT_COILS) { - userData.resize(6); - } - LOG_D("ECHO response\n"); - break; - default: // Will not get here! - break; - } - } else { - // No. User provided data response - LOG_D("Data response\n"); - } - error = SUCCESS; - } else { // no worker found - error = ILLEGAL_FUNCTION; + // 4. request complete, process + ModbusMessage request(messageLength - 6); // create request without MBAP, with server ID + request.add(message->data() + 6, message->size() - 6); + ModbusMessage userData; + if (server->isServerFor(request.getServerID())) { + MBSworker callback = server->getWorker(request.getServerID(), request.getFunctionCode()); + if (callback) { + // request is well formed and is being served by user API + userData = callback(request); + // Process Response + // One of the predefined types? + if (userData[0] == 0xFF && (userData[1] == 0xF0 || userData[1] == 0xF1)) { + // Yes. Check it + switch (userData[1]) { + case 0xF0: // NIL + userData.clear(); + LOG_D("NIL response\n"); + break; + case 0xF1: // ECHO + userData = request; + if (request.getFunctionCode() == WRITE_MULT_REGISTERS || + request.getFunctionCode() == WRITE_MULT_COILS) { + userData.resize(6); } - } else { // mismatch server ID - error = INVALID_SERVER; + LOG_D("ECHO response\n"); + break; + default: // Will not get here! + break; + } + } else { + // No. User provided data response + LOG_D("Data response\n"); } - if (error != SUCCESS) { - userData.setError(request.getServerID(), request.getFunctionCode(), error); - } - // Keep transaction id and protocol id - message->resize(4); - // Add new payload length - message->add(static_cast(userData.size())); - // Append payload - message->append(userData); - // Transfer message data to outbox - addResponseToOutbox(message); - message = nullptr; - } // end while loop iterating incoming data + error = SUCCESS; + } else { // no worker found + error = ILLEGAL_FUNCTION; + } + } else { // mismatch server ID + error = INVALID_SERVER; + } + if (error != SUCCESS) { + userData.setError(request.getServerID(), request.getFunctionCode(), error); + } + // Keep transaction id and protocol id + message->resize(4); + // Add new payload length + message->add(static_cast(userData.size())); + // Append payload + message->append(userData); + // Transfer message data to outbox + addResponseToOutbox(message); + message = nullptr; + } // end while loop iterating incoming data } void ModbusServerTCPasync::mb_client::onPoll() { - LOCK_GUARD(lock1, obLock); - handleOutbox(); - if (server->idle_timeout > 0 && millis() - lastActiveTime > server->idle_timeout) { - LOG_D("client idle, closing\n"); - client->close(); - } + LOCK_GUARD(lock1, obLock); + handleOutbox(); + if (server->idle_timeout > 0 && + millis() - lastActiveTime > server->idle_timeout) { + LOG_D("client idle, closing\n"); + client->close(); + } } void ModbusServerTCPasync::mb_client::onDisconnect() { - LOG_D("client disconnected\n"); - server->onClientDisconnect(this); + LOG_D("client disconnected\n"); + server->onClientDisconnect(this); } -void ModbusServerTCPasync::mb_client::addResponseToOutbox(ModbusMessage * response) { - if (response->size() > 0) { - LOCK_GUARD(lock1, obLock); - outbox.push(response); - handleOutbox(); - } +void ModbusServerTCPasync::mb_client::addResponseToOutbox(ModbusMessage* response) { + if (response->size() > 0) { + LOCK_GUARD(lock1, obLock); + outbox.push(response); + handleOutbox(); + } } void ModbusServerTCPasync::mb_client::handleOutbox() { - while (!outbox.empty()) { - ModbusMessage * m = outbox.front(); - if (m->size() <= client->space()) { - LOG_D("sending (%d)\n", m->size()); - client->add(reinterpret_cast(m->data()), m->size(), ASYNC_WRITE_FLAG_COPY); - client->send(); - delete m; - outbox.pop(); - } else { - return; - } + while (!outbox.empty()) { + ModbusMessage* m = outbox.front(); + if (m->size() <= client->space()) { + LOG_D("sending (%d)\n", m->size()); + client->add(reinterpret_cast(m->data()), m->size(), ASYNC_WRITE_FLAG_COPY); + client->send(); + delete m; + outbox.pop(); + } else { + return; } + } } -ModbusServerTCPasync::ModbusServerTCPasync() - : server(nullptr) - , clients() - , maxNoClients(5) - , idle_timeout(60000) { +ModbusServerTCPasync::ModbusServerTCPasync() : + server(nullptr), + clients(), + maxNoClients(5), + idle_timeout(60000) { // setup will be done in 'start' } ModbusServerTCPasync::~ModbusServerTCPasync() { - stop(); - delete server; + stop(); + delete server; } uint16_t ModbusServerTCPasync::activeClients() { - LOCK_GUARD(lock1, cListLock); - return clients.size(); + LOCK_GUARD(lock1, cListLock); + return clients.size(); } -bool ModbusServerTCPasync::start(uint16_t port, uint8_t max_clients, uint32_t timeout, int coreID) { - // don't restart if already running - if (server) { - LOG_W("Server already running.\n"); - return false; - } - - maxNoClients = max_clients; - idle_timeout = timeout; - server = new AsyncServer(port); - if (server) { - server->setNoDelay(true); - server->onClient([](void * i, AsyncClient * c) { (static_cast(i))->onClientConnect(c); }, this); - server->begin(); - LOG_D("Modbus server started\n"); - return true; - } - LOG_E("Could not start server\n"); +bool ModbusServerTCPasync::start(uint16_t port, uint8_t maxClients, uint32_t timeout, int coreID) { + // don't restart if already running + if (server) { + LOG_W("Server already running.\n"); return false; + } + + maxNoClients = maxClients; + idle_timeout = timeout; + server = new AsyncServer(port); + if (server) { + server->setNoDelay(true); + server->onClient([](void* i, AsyncClient* c) { (static_cast(i))->onClientConnect(c); }, this); + server->begin(); + LOG_D("Modbus server started\n"); + return true; + } + LOG_E("Could not start server\n"); + return false; } bool ModbusServerTCPasync::stop() { - if (!server) { - LOG_W("Server not running.\n"); - return false; - } + + if (!server) { + LOG_W("Server not running.\n"); + return false; + } + + // stop server to prevent new clients connecting + server->end(); - // stop server to prevent new clients connecting - server->end(); - - // now close existing clients - LOCK_GUARD(lock1, cListLock); - while (!clients.empty()) { - // prevent onDisconnect handler to be called, resulting in deadlock - clients.front()->client->onDisconnect(nullptr, nullptr); - delete clients.front(); - clients.pop_front(); - } - delete server; - server = nullptr; - LOG_D("Modbus server stopped\n"); - return true; + // now close existing clients + LOCK_GUARD(lock1, cListLock); + while (!clients.empty()) { + // prevent onDisconnect handler to be called, resulting in deadlock + clients.front()->client->onDisconnect(nullptr, nullptr); + delete clients.front(); + clients.pop_front(); + } + delete server; + server = nullptr; + LOG_D("Modbus server stopped\n"); + return true; } bool ModbusServerTCPasync::isRunning() { - if (server) - return true; - else - return false; + if (server) return true; + else return false; } -void ModbusServerTCPasync::onClientConnect(AsyncClient * client) { - LOG_D("new client\n"); - LOCK_GUARD(lock1, cListLock); - if (clients.size() < maxNoClients) { - clients.emplace_back(new mb_client(this, client)); - LOG_D("nr clients: %d\n", clients.size()); - } else { - LOG_D("max number of clients reached, closing new\n"); - client->close(true); - delete client; - } -} - -void ModbusServerTCPasync::onClientDisconnect(mb_client * client) { - LOCK_GUARD(lock1, cListLock); - // delete mb_client from list - clients.remove_if([client](mb_client * i) { return i->client == client->client; }); - // delete client itself - delete client; +void ModbusServerTCPasync::onClientConnect(AsyncClient* client) { + LOG_D("new client\n"); + LOCK_GUARD(lock1, cListLock); + if (clients.size() < maxNoClients) { + clients.emplace_back(new mb_client(this, client)); LOG_D("nr clients: %d\n", clients.size()); + } else { + LOG_D("max number of clients reached, closing new\n"); + client->close(true); + delete client; + } +} + +void ModbusServerTCPasync::onClientDisconnect(mb_client* client) { + LOCK_GUARD(lock1, cListLock); + // delete mb_client from list + clients.remove_if([client](mb_client* i) { return i->client == client->client; }); + // delete client itself + delete client; + LOG_D("nr clients: %d\n", clients.size()); } diff --git a/lib/eModbus/src/ModbusServerTCPasync.h b/lib/eModbus/src/ModbusServerTCPasync.h index 9f8ed3a80..410d8d6fb 100644 --- a/lib/eModbus/src/ModbusServerTCPasync.h +++ b/lib/eModbus/src/ModbusServerTCPasync.h @@ -14,7 +14,7 @@ #endif #include -#include // for millis() +#include // for millis() #if defined(ESP32) #include @@ -29,64 +29,64 @@ using std::lock_guard; #endif class ModbusServerTCPasync : public ModbusServer { - private: - class mb_client { - friend class ModbusServerTCPasync; - public: - mb_client(ModbusServerTCPasync * s, AsyncClient * c); - ~mb_client(); + private: + class mb_client { + friend class ModbusServerTCPasync; + + public: + mb_client(ModbusServerTCPasync* s, AsyncClient* c); + ~mb_client(); - private: - void onData(uint8_t * data, size_t len); - void onPoll(); - void onDisconnect(); - void addResponseToOutbox(ModbusMessage * response); - void handleOutbox(); - ModbusServerTCPasync * server; - AsyncClient * client; - uint32_t lastActiveTime; - ModbusMessage * message; - Modbus::Error error; - std::queue outbox; -#if USE_MUTEX - std::mutex obLock; // outbox protection -#endif - }; + private: + void onData(uint8_t* data, size_t len); + void onPoll(); + void onDisconnect(); + void addResponseToOutbox(ModbusMessage* response); + void handleOutbox(); + ModbusServerTCPasync* server; + AsyncClient* client; + uint32_t lastActiveTime; + ModbusMessage* message; + Modbus::Error error; + std::queue outbox; + #if USE_MUTEX + std::mutex obLock; // outbox protection + #endif + }; - public: - // Constructor - ModbusServerTCPasync(); + public: + // Constructor + ModbusServerTCPasync(); - // Destructor: closes the connections - ~ModbusServerTCPasync(); + // Destructor: closes the connections + ~ModbusServerTCPasync(); - // activeClients: return number of clients currently employed - uint16_t activeClients(); + // activeClients: return number of clients currently employed + uint16_t activeClients(); - // start: create task with TCP server to accept requests - bool start(uint16_t port, uint8_t max_clients, uint32_t timeout, int coreID = -1); + // start: create task with TCP server to accept requests + bool start(uint16_t port, uint8_t maxClients, uint32_t timeout, int coreID = -1); - // stop: drop all connections and kill server task - bool stop(); + // stop: drop all connections and kill server task + bool stop(); + + // isRunning: return true is server is running + bool isRunning(); - // isRunning: return true is server is running - bool isRunning(); + protected: + inline void isInstance() { } + void onClientConnect(AsyncClient* client); + void onClientDisconnect(mb_client* client); - protected: - inline void isInstance() { - } - void onClientConnect(AsyncClient * client); - void onClientDisconnect(mb_client * client); - - AsyncServer * server; - std::list clients; - uint8_t maxNoClients; - uint32_t idle_timeout; -#if USE_MUTEX - std::mutex cListLock; // client list protection -#endif + AsyncServer* server; + std::list clients; + uint8_t maxNoClients; + uint32_t idle_timeout; + #if USE_MUTEX + std::mutex cListLock; // client list protection + #endif }; #endif diff --git a/lib/eModbus/src/ModbusServerTCPtemp.h b/lib/eModbus/src/ModbusServerTCPtemp.h index 52341a3a4..7ae5b2bc9 100644 --- a/lib/eModbus/src/ModbusServerTCPtemp.h +++ b/lib/eModbus/src/ModbusServerTCPtemp.h @@ -6,7 +6,7 @@ #define _MODBUS_SERVER_TCP_TEMP_H #include -#include // NOLINT +#include // NOLINT #include "ModbusServer.h" #undef LOCAL_LOG_LEVEL // #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE @@ -17,155 +17,142 @@ extern "C" { #include } -using std::lock_guard; -using std::mutex; using std::vector; +using std::mutex; +using std::lock_guard; template class ModbusServerTCP : public ModbusServer { - public: - // Constructor - ModbusServerTCP(); +public: + // Constructor + ModbusServerTCP(); - // Destructor: closes the connections - ~ModbusServerTCP(); + // Destructor: closes the connections + ~ModbusServerTCP(); - // activeClients: return number of clients currently employed - uint16_t activeClients(); + // activeClients: return number of clients currently employed + uint16_t activeClients(); - // start: create task with TCP server to accept requests - bool start(uint16_t port, uint8_t max_clients, uint32_t timeout, int coreID = -1); + // start: create task with TCP server to accept requests + bool start(uint16_t port, uint8_t maxClients, uint32_t timeout, int coreID = -1); - // stop: drop all connections and kill server task - bool stop(); + // stop: drop all connections and kill server task + bool stop(); - protected: - // Prevent copy construction and assignment - ModbusServerTCP(ModbusServerTCP & m) = delete; - ModbusServerTCP & operator=(ModbusServerTCP & m) = delete; +protected: + // Prevent copy construction and assignment + ModbusServerTCP(ModbusServerTCP& m) = delete; + ModbusServerTCP& operator=(ModbusServerTCP& m) = delete; - inline void isInstance() { + inline void isInstance() { } + + uint8_t numClients; + TaskHandle_t serverTask; + uint16_t serverPort; + uint32_t serverTimeout; + bool serverGoDown; + mutex clientLock; + + struct ClientData { + ClientData() : task(nullptr), client(0), timeout(0), parent(nullptr) {} + ClientData(TaskHandle_t t, CT& c, uint32_t to, ModbusServerTCP *p) : + task(t), client(c), timeout(to), parent(p) {} + ~ClientData() { + if (client) { + client.stop(); + } + if (task != nullptr) { + vTaskDelete(task); + LOG_D("Killed client task %d\n", (uint32_t)task); + } } + TaskHandle_t task; + CT client; + uint32_t timeout; + ModbusServerTCP *parent; + }; + ClientData **clients; - uint8_t numClients; - TaskHandle_t serverTask; - uint16_t serverPort; - uint32_t serverTimeout; - bool serverGoDown; - mutex clientLock; + // serve: loop function for server task + static void serve(ModbusServerTCP *myself); - struct ClientData { - ClientData() - : task(nullptr) - , client(0) - , timeout(0) - , parent(nullptr) { - } - ClientData(TaskHandle_t t, CT & c, uint32_t to, ModbusServerTCP * p) - : task(t) - , client(c) - , timeout(to) - , parent(p) { - } - ~ClientData() { - if (client) { - client.stop(); - } - if (task != nullptr) { - vTaskDelete(task); - LOG_D("Killed client task %d\n", (uint32_t)task); - } - } - TaskHandle_t task; - CT client; - uint32_t timeout; - ModbusServerTCP * parent; - }; - ClientData ** clients; + // worker: loop function for client tasks + static void worker(ClientData *myData); - // serve: loop function for server task - static void serve(ModbusServerTCP * myself); + // receive: read data from TCP + ModbusMessage receive(CT& client, uint32_t timeWait); - // worker: loop function for client tasks - static void worker(ClientData * myData); + // accept: start a task to receive requests and respond to a given client + bool accept(CT& client, uint32_t timeout, int coreID = -1); - // receive: read data from TCP - ModbusMessage receive(CT & client, uint32_t timeWait); - - // accept: start a task to receive requests and respond to a given client - bool accept(CT & client, uint32_t timeout, int coreID = -1); - - // clientAvailable: return true,. if a client slot is currently unused - bool clientAvailable() { - return (numClients - activeClients()) > 0; - } + // clientAvailable: return true,. if a client slot is currently unused + bool clientAvailable() { return (numClients - activeClients()) > 0; } }; // Constructor template -ModbusServerTCP::ModbusServerTCP() - : ModbusServer() - , numClients(0) - , serverTask(nullptr) - , serverPort(502) - , serverTimeout(20000) - , serverGoDown(false) { - clients = new ClientData *[numClients](); -} +ModbusServerTCP::ModbusServerTCP() : + ModbusServer(), + numClients(0), + serverTask(nullptr), + serverPort(502), + serverTimeout(20000), + serverGoDown(false) { + clients = new ClientData*[numClients](); + } // Destructor: closes the connections template ModbusServerTCP::~ModbusServerTCP() { - for (uint8_t i = 0; i < numClients; ++i) { - if (clients[i] != nullptr) { - delete clients[i]; - } + for (uint8_t i = 0; i < numClients; ++i) { + if (clients[i] != nullptr) { + delete clients[i]; } - delete[] clients; - serverGoDown = true; + } + delete[] clients; + serverGoDown = true; } // activeClients: return number of clients currently employed template uint16_t ModbusServerTCP::activeClients() { - uint8_t cnt = 0; - for (uint8_t i = 0; i < numClients; ++i) { - // Current slot could have been previously used - look for cleared task handles - if (clients[i] != nullptr) { - // Empty task handle? - if (clients[i]->task == nullptr) { - // Yes. Delete entry and init client pointer - lock_guard cL(clientLock); - delete clients[i]; - LOG_V("Delete client %d\n", i); - clients[i] = nullptr; - } - } - if (clients[i] != nullptr) - cnt++; + uint8_t cnt = 0; + for (uint8_t i = 0; i < numClients; ++i) { + // Current slot could have been previously used - look for cleared task handles + if (clients[i] != nullptr) { + // Empty task handle? + if (clients[i]->task == nullptr) { + // Yes. Delete entry and init client pointer + lock_guard cL(clientLock); + delete clients[i]; + LOG_V("Delete client %d\n", i); + clients[i] = nullptr; + } } - return cnt; + if (clients[i] != nullptr) cnt++; + } + return cnt; } -// start: create task with TCP server to accept requests + // start: create task with TCP server to accept requests template -bool ModbusServerTCP::start(uint16_t port, uint8_t max_clients, uint32_t timeout, int coreID) { + bool ModbusServerTCP::start(uint16_t port, uint8_t maxClients, uint32_t timeout, int coreID) { // Task already running? if (serverTask != nullptr) { - // Yes. stop it first - stop(); + // Yes. stop it first + stop(); } // Does the required number of slots fit? - if (numClients != max_clients) { - // No. Drop array and allocate a new one - delete[] clients; - // Now allocate a new one - numClients = max_clients; - clients = new ClientData *[numClients](); + if (numClients != maxClients) { + // No. Drop array and allocate a new one + delete[] clients; + // Now allocate a new one + numClients = maxClients; + clients = new ClientData*[numClients](); } - serverPort = port; + serverPort = port; serverTimeout = timeout; - serverGoDown = false; + serverGoDown = false; // Create unique task name char taskName[18]; @@ -179,255 +166,255 @@ bool ModbusServerTCP::start(uint16_t port, uint8_t max_clients, uint32_t delay(2000); return true; -} + } -// stop: drop all connections and kill server task + // stop: drop all connections and kill server task template -bool ModbusServerTCP::stop() { + bool ModbusServerTCP::stop() { // Check for clients still connected for (uint8_t i = 0; i < numClients; ++i) { - // Client is alive? - if (clients[i] != nullptr) { - // Yes. Close the connection - delete clients[i]; - clients[i] = nullptr; - } + // Client is alive? + if (clients[i] != nullptr) { + // Yes. Close the connection + delete clients[i]; + clients[i] = nullptr; + } } if (serverTask != nullptr) { - // Signal server task to stop - serverGoDown = true; - delay(5000); - LOG_D("Killed server task %d\n", (uint32_t)(serverTask)); - serverTask = nullptr; - serverGoDown = false; + // Signal server task to stop + serverGoDown = true; + delay(5000); + LOG_D("Killed server task %d\n", (uint32_t)(serverTask)); + serverTask = nullptr; + serverGoDown = false; } return true; -} + } // accept: start a task to receive requests and respond to a given client template -bool ModbusServerTCP::accept(CT & client, uint32_t timeout, int coreID) { - // Look for an empty client slot - for (uint8_t i = 0; i < numClients; ++i) { - // Empty slot? - if (clients[i] == nullptr) { - // Yes. allocate new client data in slot - clients[i] = new ClientData(0, client, timeout, this); +bool ModbusServerTCP::accept(CT& client, uint32_t timeout, int coreID) { + // Look for an empty client slot + for (uint8_t i = 0; i < numClients; ++i) { + // Empty slot? + if (clients[i] == nullptr) { + // Yes. allocate new client data in slot + clients[i] = new ClientData(0, client, timeout, this); - // Create unique task name - char taskName[18]; - snprintf(taskName, 18, "MBsrv%02Xclnt", i); + // Create unique task name + char taskName[18]; + snprintf(taskName, 18, "MBsrv%02Xclnt", i); - // Start task to handle the client - xTaskCreatePinnedToCore((TaskFunction_t)&worker, taskName, SERVER_TASK_STACK, clients[i], 5, &clients[i]->task, coreID >= 0 ? coreID : NULL); - LOG_D("Started client %d task %d\n", i, (uint32_t)(clients[i]->task)); + // Start task to handle the client + xTaskCreatePinnedToCore((TaskFunction_t)&worker, taskName, SERVER_TASK_STACK, clients[i], 5, &clients[i]->task, coreID >= 0 ? coreID : NULL); + LOG_D("Started client %d task %d\n", i, (uint32_t)(clients[i]->task)); - return true; - } + return true; } - LOG_D("No client slot available.\n"); - return false; + } + LOG_D("No client slot available.\n"); + return false; } template -void ModbusServerTCP::serve(ModbusServerTCP * myself) { - // need a local scope here to delete the server at termination time - if (1) { - // Set up server with given port - ST server(myself->serverPort); +void ModbusServerTCP::serve(ModbusServerTCP *myself) { + // need a local scope here to delete the server at termination time + if (1) { + // Set up server with given port + ST server(myself->serverPort); - // Start it - server.begin(); + // Start it + server.begin(); - // Loop until being killed - while (!myself->serverGoDown) { - // Do we have clients left to use? - if (myself->clientAvailable()) { - // Yes. accept one, when it connects - CT ec = server.accept(); - // Did we get a connection? - if (ec) { - // Yes. Forward it to the Modbus server - myself->accept(ec, myself->serverTimeout, 0); - LOG_D("Accepted connection - %d clients running\n", myself->activeClients()); - } - } - // Give scheduler room to breathe - delay(10); + // Loop until being killed + while (!myself->serverGoDown) { + // Do we have clients left to use? + if (myself->clientAvailable()) { + // Yes. accept one, when it connects + CT ec = server.accept(); + // Did we get a connection? + if (ec) { + // Yes. Forward it to the Modbus server + myself->accept(ec, myself->serverTimeout, 0); + LOG_D("Accepted connection - %d clients running\n", myself->activeClients()); } - LOG_E("Server going down\n"); - // We must go down - SERVER_END; + } + // Give scheduler room to breathe + delay(10); } - vTaskDelete(NULL); + LOG_E("Server going down\n"); + // We must go down + SERVER_END; + } + vTaskDelete(NULL); } template -void ModbusServerTCP::worker(ClientData * myData) { - // Get own reference data in handier form - CT myClient = myData->client; - uint32_t myTimeOut = myData->timeout; - // TaskHandle_t myTask = myData->task; - ModbusServerTCP * myParent = myData->parent; - unsigned long myLastMessage = millis(); +void ModbusServerTCP::worker(ClientData *myData) { + // Get own reference data in handier form + CT myClient = myData->client; + uint32_t myTimeOut = myData->timeout; + // TaskHandle_t myTask = myData->task; + ModbusServerTCP *myParent = myData->parent; + unsigned long myLastMessage = millis(); - LOG_D("Worker started, timeout=%d\n", myTimeOut); + LOG_D("Worker started, timeout=%d\n", myTimeOut); - // loop forever, if timeout is 0, or until timeout was hit - while (myClient.connected() && (!myTimeOut || (millis() - myLastMessage < myTimeOut))) { - ModbusMessage response; // Data buffer to hold prepared response - // Get a request - if (myClient.available()) { - response.clear(); - ModbusMessage m = myParent->receive(myClient, 100); + // loop forever, if timeout is 0, or until timeout was hit + while (myClient.connected() && (!myTimeOut || (millis() - myLastMessage < myTimeOut))) { + ModbusMessage response; // Data buffer to hold prepared response + // Get a request + if (myClient.available()) { + response.clear(); + ModbusMessage m = myParent->receive(myClient, 100); - // has it the minimal length (6 bytes TCP header plus serverID plus FC)? - if (m.size() >= 8) { - { - LOCK_GUARD(cntLock, myParent->m); - myParent->messageCount++; - } - // Extract request data - ModbusMessage request; - request.add(m.data() + 6, m.size() - 6); - - // Protocol ID shall be 0x0000 - is it? - if (m[2] == 0 && m[3] == 0) { - // ServerID shall be at [6], FC at [7]. Check both - if (myParent->isServerFor(request.getServerID())) { - // Server is correct - in principle. Do we serve the FC? - MBSworker callBack = myParent->getWorker(request.getServerID(), request.getFunctionCode()); - if (callBack) { - // Yes, we do. - // Invoke the worker method to get a response - ModbusMessage data = callBack(request); - // Process Response - // One of the predefined types? - if (data[0] == 0xFF && (data[1] == 0xF0 || data[1] == 0xF1)) { - // Yes. Check it - switch (data[1]) { - case 0xF0: // NIL - response.clear(); - LOG_D("NIL response\n"); - break; - case 0xF1: // ECHO - response = request; - if (request.getFunctionCode() == WRITE_MULT_REGISTERS || request.getFunctionCode() == WRITE_MULT_COILS) { - response.resize(6); - } - LOG_D("ECHO response\n"); - break; - default: // Will not get here! - break; - } - } else { - // No. User provided data response - response = data; - LOG_D("Data response\n"); - } - } else { - // No, function code is not served here - response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_FUNCTION); - } - } else { - // No, serverID is not served here - response.setError(request.getServerID(), request.getFunctionCode(), INVALID_SERVER); - } - } else { - // No, protocol ID was something weird - response.setError(request.getServerID(), request.getFunctionCode(), TCP_HEAD_MISMATCH); - } - } - delay(1); - // Do we have a response to send? - if (response.size() >= 3) { - // Yes. Do it now. - // Cut off length and request data, then update TCP header - m.resize(4); - m.add(static_cast(response.size())); - // Append response - m.append(response); - myClient.write(m.data(), m.size()); - HEXDUMP_V("Response", m.data(), m.size()); - // count error responses - if (response.getError() != SUCCESS) { - LOCK_GUARD(cntLock, myParent->m); - myParent->errorCount++; - } - } - // We did something communicationally - rewind timeout timer - myLastMessage = millis(); + // has it the minimal length (6 bytes TCP header plus serverID plus FC)? + if (m.size() >= 8) { + { + LOCK_GUARD(cntLock, myParent->m); + myParent->messageCount++; } - delay(1); - } + // Extract request data + ModbusMessage request; + request.add(m.data() + 6, m.size() - 6); - if (millis() - myLastMessage >= myTimeOut) { - // Timeout! - LOG_D("Worker stopping due to timeout.\n"); - } else { - // Disconnected! - LOG_D("Worker stopping due to client disconnect.\n"); + // Protocol ID shall be 0x0000 - is it? + if (m[2] == 0 && m[3] == 0) { + // ServerID shall be at [6], FC at [7]. Check both + if (myParent->isServerFor(request.getServerID())) { + // Server is correct - in principle. Do we serve the FC? + MBSworker callBack = myParent->getWorker(request.getServerID(), request.getFunctionCode()); + if (callBack) { + // Yes, we do. + // Invoke the worker method to get a response + ModbusMessage data = callBack(request); + // Process Response + // One of the predefined types? + if (data[0] == 0xFF && (data[1] == 0xF0 || data[1] == 0xF1)) { + // Yes. Check it + switch (data[1]) { + case 0xF0: // NIL + response.clear(); + LOG_D("NIL response\n"); + break; + case 0xF1: // ECHO + response = request; + if (request.getFunctionCode() == WRITE_MULT_REGISTERS || + request.getFunctionCode() == WRITE_MULT_COILS) { + response.resize(6); + } + LOG_D("ECHO response\n"); + break; + default: // Will not get here! + break; + } + } else { + // No. User provided data response + response = data; + LOG_D("Data response\n"); + } + } else { + // No, function code is not served here + response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_FUNCTION); + } + } else { + // No, serverID is not served here + response.setError(request.getServerID(), request.getFunctionCode(), INVALID_SERVER); + } + } else { + // No, protocol ID was something weird + response.setError(request.getServerID(), request.getFunctionCode(), TCP_HEAD_MISMATCH); + } + } + delay(1); + // Do we have a response to send? + if (response.size() >= 3) { + // Yes. Do it now. + // Cut off length and request data, then update TCP header + m.resize(4); + m.add(static_cast(response.size())); + // Append response + m.append(response); + myClient.write(m.data(), m.size()); + HEXDUMP_V("Response", m.data(), m.size()); + // count error responses + if (response.getError() != SUCCESS) { + LOCK_GUARD(cntLock, myParent->m); + myParent->errorCount++; + } + } + // We did something communicationally - rewind timeout timer + myLastMessage = millis(); } + delay(1); + } - // Read away all that may still hang in the buffer - while (myClient.read() != -1) { - } - // Now stop the client - myClient.stop(); + if (millis() - myLastMessage >= myTimeOut) { + // Timeout! + LOG_D("Worker stopping due to timeout.\n"); + } else { + // Disconnected! + LOG_D("Worker stopping due to client disconnect.\n"); + } - { - lock_guard cL(myParent->clientLock); - myData->task = nullptr; - } + // Read away all that may still hang in the buffer + while (myClient.read() != -1) {} + // Now stop the client + myClient.stop(); - delay(50); - vTaskDelete(NULL); + { + lock_guard cL(myParent->clientLock); + myData->task = nullptr; + } + + delay(50); + vTaskDelete(NULL); } // receive: get request via Client connection template -ModbusMessage ModbusServerTCP::receive(CT & client, uint32_t timeWait) { - unsigned long lastMillis = millis(); // Timer to check for timeout - ModbusMessage m; // to take read data - uint16_t lengthVal = 0; - uint16_t cnt = 0; - const uint16_t BUFFERSIZE(300); - uint8_t buffer[BUFFERSIZE]; +ModbusMessage ModbusServerTCP::receive(CT& client, uint32_t timeWait) { + unsigned long lastMillis = millis(); // Timer to check for timeout + ModbusMessage m; // to take read data + uint16_t lengthVal = 0; + uint16_t cnt = 0; + const uint16_t BUFFERSIZE(300); + uint8_t buffer[BUFFERSIZE]; - // wait for sufficient packet data or timeout - while ((millis() - lastMillis < timeWait) && ((cnt < 6) || (cnt < lengthVal)) && (cnt < BUFFERSIZE)) { - // Is there data waiting? - if (client.available()) { - buffer[cnt] = client.read(); - // Are we at the TCP header length field byte #1? - if (cnt == 4) - lengthVal = buffer[cnt] << 8; - // Are we at the TCP header length field byte #2? - if (cnt == 5) { - lengthVal |= buffer[cnt]; - lengthVal += 6; - } - cnt++; - // Rewind EOT and timeout timers - lastMillis = millis(); - } else { - delay(1); // Give scheduler room to breathe + // wait for sufficient packet data or timeout + while ((millis() - lastMillis < timeWait) && ((cnt < 6) || (cnt < lengthVal)) && (cnt < BUFFERSIZE)) + { + // Is there data waiting? + if (client.available()) { + buffer[cnt] = client.read(); + // Are we at the TCP header length field byte #1? + if (cnt == 4) lengthVal = buffer[cnt] << 8; + // Are we at the TCP header length field byte #2? + if (cnt == 5) { + lengthVal |= buffer[cnt]; + lengthVal += 6; } + cnt++; + // Rewind EOT and timeout timers + lastMillis = millis(); + } else { + delay(1); // Give scheduler room to breathe } - // Did we receive some data? - if (cnt) { - // Yes. Is it too much? - if (cnt >= BUFFERSIZE) { - // Yes, likely a buffer overflow of some sort - // Adjust message size in TCP header - buffer[4] = (cnt >> 8) & 0xFF; - buffer[5] = cnt & 0xFF; - LOG_E("Potential buffer overrun (>%d)!\n", cnt); - } - // Get as much buffer as was read - m.add(buffer, cnt); + } + // Did we receive some data? + if (cnt) { + // Yes. Is it too much? + if (cnt >= BUFFERSIZE) { + // Yes, likely a buffer overflow of some sort + // Adjust message size in TCP header + buffer[4] = (cnt >> 8) & 0xFF; + buffer[5] = cnt & 0xFF; + LOG_E("Potential buffer overrun (>%d)!\n", cnt); } - return m; + // Get as much buffer as was read + m.add(buffer, cnt); + } + return m; } #endif diff --git a/lib/eModbus/src/ModbusTypeDefs.cpp b/lib/eModbus/src/ModbusTypeDefs.cpp index 7868cfca2..27f27cdad 100644 --- a/lib/eModbus/src/ModbusTypeDefs.cpp +++ b/lib/eModbus/src/ModbusTypeDefs.cpp @@ -50,7 +50,7 @@ FCType FCT::getType(uint8_t functionCode) { 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 // the effective type FCType FCT::redefineType(uint8_t functionCode, const FCType type) { diff --git a/lib/eModbus/src/ModbusTypeDefs.h b/lib/eModbus/src/ModbusTypeDefs.h index cd0217810..5aad1d5dd 100644 --- a/lib/eModbus/src/ModbusTypeDefs.h +++ b/lib/eModbus/src/ModbusTypeDefs.h @@ -84,6 +84,9 @@ enum Error : uint8_t { UNDEFINED_ERROR = 0xFF // otherwise uncovered communication error }; +// Readable expression for the "illegal" server ID of 0 +#define ANY_SERVER 0x00 + #ifndef MINIMAL // Constants for float and double re-ordering