This commit is contained in:
proddy
2025-03-22 10:32:03 +01:00
parent e418b7d8e7
commit eaa277fef0
281 changed files with 15297 additions and 21851 deletions

View File

@@ -1,257 +0,0 @@
#######################################
# Syntax Coloring Map For the current project.
# This file was generated by doxygen2keywords.xsl.
#######################################
#######################################
# Classes and structs (KEYWORD1)
#######################################
ModbusServerTCP::ClientData KEYWORD1
CoilData KEYWORD1
Modbus::FCT KEYWORD1
ModbusBridge KEYWORD1
ModbusClient KEYWORD1
ModbusClientTCP KEYWORD1
ModbusClientRTU KEYWORD1
ModbusClientTCPasync KEYWORD1
ModbusError KEYWORD1
ModbusMessage KEYWORD1
ModbusServer KEYWORD1
ModbusServerTCP KEYWORD1
ModbusServerRTU KEYWORD1
ModbusServerTCPasync KEYWORD1
RTUutils KEYWORD1
#######################################
# Methods (KEYWORD2)
#######################################
ClientData KEYWORD2
~ClientData KEYWORD2
CoilData KEYWORD2
~CoilData KEYWORD2
coils KEYWORD2
coilsSetON KEYWORD2
coilsSetOFF KEYWORD2
FCT KEYWORD2
getType KEYWORD2
redefineType KEYWORD2
ModbusBridge KEYWORD2
attachServer KEYWORD2
addFunctionCode KEYWORD2
denyFunctionCode KEYWORD2
bridgeWorker KEYWORD2
bridgeDenyWorker KEYWORD2
onDataHandler KEYWORD2
onErrorHandler KEYWORD2
onResponseHandler KEYWORD2
getMessageCount KEYWORD2
getErrorCount KEYWORD2
resetCounts KEYWORD2
addRequest KEYWORD2
syncRequest KEYWORD2
buildErrorMsg KEYWORD2
addRequest KEYWORD2
ModbusClient KEYWORD2
waitSync KEYWORD2
ModbusClientTCPasync KEYWORD2
setTimeout KEYWORD2
setIdleTimeout KEYWORD2
setMaxInflightRequests KEYWORD2
addToQueue KEYWORD2
ModbusError KEYWORD2
getText KEYWORD2
ModbusMessage KEYWORD2
data KEYWORD2
size KEYWORD2
push_back KEYWORD2
clear KEYWORD2
resize KEYWORD2
begin KEYWORD2
end KEYWORD2
append KEYWORD2
getServerID KEYWORD2
getFunctionCode KEYWORD2
getError KEYWORD2
setFunctionCode KEYWORD2
add KEYWORD2
get KEYWORD2
setMessage KEYWORD2
setError KEYWORD2
determineFloatOrder KEYWORD2
determineDoubleOrder KEYWORD2
swapFloat KEYWORD2
swapDouble KEYWORD2
getOne KEYWORD2
registerWorker KEYWORD2
getWorker KEYWORD2
unregisterWorker KEYWORD2
isServerFor KEYWORD2
getMessageCount KEYWORD2
getErrorCount KEYWORD2
resetCounts KEYWORD2
localRequest KEYWORD2
listServer KEYWORD2
ModbusServer KEYWORD2
ModbusServerTCP KEYWORD2
activeClients KEYWORD2
start KEYWORD2
stop KEYWORD2
clientAvailable KEYWORD2
ModbusServerTCPasync KEYWORD2
isRunning KEYWORD2
calcCRC KEYWORD2
validCRC KEYWORD2
addCRC KEYWORD2
calculateInterval KEYWORD2
prepareHardwareSerial KEYWORD2
RTUutils KEYWORD2
ServerData KEYWORD2
NIL_RESPONSE KEYWORD2
ECHO_RESPONSE KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
DISCONNECTED LITERAL1
CONNECTING LITERAL1
CONNECTED LITERAL1
ANY_FUNCTION_CODE LITERAL1
READ_COIL LITERAL1
READ_DISCR_INPUT LITERAL1
READ_HOLD_REGISTER LITERAL1
READ_INPUT_REGISTER LITERAL1
WRITE_COIL LITERAL1
WRITE_HOLD_REGISTER LITERAL1
READ_EXCEPTION_SERIAL LITERAL1
DIAGNOSTICS_SERIAL LITERAL1
READ_COMM_CNT_SERIAL LITERAL1
READ_COMM_LOG_SERIAL LITERAL1
WRITE_MULT_COILS LITERAL1
WRITE_MULT_REGISTERS LITERAL1
REPORT_SERVER_ID_SERIAL LITERAL1
READ_FILE_RECORD LITERAL1
WRITE_FILE_RECORD LITERAL1
MASK_WRITE_REGISTER LITERAL1
R_W_MULT_REGISTERS LITERAL1
READ_FIFO_QUEUE LITERAL1
ENCAPSULATED_INTERFACE LITERAL1
USER_DEFINED_41 LITERAL1
USER_DEFINED_42 LITERAL1
USER_DEFINED_43 LITERAL1
USER_DEFINED_44 LITERAL1
USER_DEFINED_45 LITERAL1
USER_DEFINED_46 LITERAL1
USER_DEFINED_47 LITERAL1
USER_DEFINED_48 LITERAL1
USER_DEFINED_64 LITERAL1
USER_DEFINED_65 LITERAL1
USER_DEFINED_66 LITERAL1
USER_DEFINED_67 LITERAL1
USER_DEFINED_68 LITERAL1
USER_DEFINED_69 LITERAL1
USER_DEFINED_6A LITERAL1
USER_DEFINED_6B LITERAL1
USER_DEFINED_6C LITERAL1
USER_DEFINED_6D LITERAL1
USER_DEFINED_6E LITERAL1
SUCCESS LITERAL1
ILLEGAL_FUNCTION LITERAL1
ILLEGAL_DATA_ADDRESS LITERAL1
ILLEGAL_DATA_VALUE LITERAL1
SERVER_DEVICE_FAILURE LITERAL1
ACKNOWLEDGE LITERAL1
SERVER_DEVICE_BUSY LITERAL1
NEGATIVE_ACKNOWLEDGE LITERAL1
MEMORY_PARITY_ERROR LITERAL1
GATEWAY_PATH_UNAVAIL LITERAL1
GATEWAY_TARGET_NO_RESP LITERAL1
TIMEOUT LITERAL1
INVALID_SERVER LITERAL1
CRC_ERROR LITERAL1
FC_MISMATCH LITERAL1
SERVER_ID_MISMATCH LITERAL1
PACKET_LENGTH_ERROR LITERAL1
PARAMETER_COUNT_ERROR LITERAL1
PARAMETER_LIMIT_ERROR LITERAL1
REQUEST_QUEUE_FULL LITERAL1
ILLEGAL_IP_OR_PORT LITERAL1
IP_CONNECTION_FAILED LITERAL1
TCP_HEAD_MISMATCH LITERAL1
EMPTY_MESSAGE LITERAL1
ASCII_FRAME_ERR LITERAL1
ASCII_CRC_ERR LITERAL1
ASCII_INVALID_CHAR LITERAL1
BROADCAST_ERROR LITERAL1
UNDEFINED_ERROR LITERAL1
FC01_TYPE LITERAL1
FC07_TYPE LITERAL1
FC0F_TYPE LITERAL1
FC10_TYPE LITERAL1
FC16_TYPE LITERAL1
FC18_TYPE LITERAL1
FCGENERIC LITERAL1
FCUSER LITERAL1
FCILLEGAL LITERAL1
PrintOut LITERAL1
LOG_LEVEL LITERAL1
LOCAL_LOG_LEVEL LITERAL1
LOG_LEVEL_NONE LITERAL1
LOG_LEVEL_CRITICAL LITERAL1
LOG_LEVEL_ERROR LITERAL1
LOG_LEVEL_WARNING LITERAL1
LOG_LEVEL_INFO LITERAL1
LOG_LEVEL_DEBUG LITERAL1
LOG_LEVEL_VERBOSE LITERAL1
LL_RED LITERAL1
LL_GREEN LITERAL1
LL_YELLOW LITERAL1
LL_BLUE LITERAL1
LL_MAGENTA LITERAL1
LL_CYAN LITERAL1
LL_NORM LITERAL1
LOG_HEADER LITERAL1
LOG_LINE_C LITERAL1
LOG_LINE_E LITERAL1
LOG_LINE_T LITERAL1
LOG_RAW_C LITERAL1
LOG_RAW_E LITERAL1
LOG_RAW_T LITERAL1
HEX_DUMP_T LITERAL1
LOG_N LITERAL1
LOGRAW_N LITERAL1
HEXDUMP_N LITERAL1
LOG_C LITERAL1
LOGRAW_C LITERAL1
HEXDUMP_C LITERAL1
LOG_E LITERAL1
LOGRAW_E LITERAL1
HEXDUMP_E LITERAL1
LOG_W LITERAL1
LOGRAW_W LITERAL1
HEXDUMP_W LITERAL1
LOG_I LITERAL1
LOGRAW_I LITERAL1
HEXDUMP_I LITERAL1
LOG_D LITERAL1
LOGRAW_D LITERAL1
HEXDUMP_D LITERAL1
LOG_V LITERAL1
LOGRAW_V LITERAL1
HEXDUMP_V LITERAL1
LOCAL_LOG_LEVEL LITERAL1
TCP_SERVER LITERAL1
RTU_SERVER LITERAL1
SERVER_END LITERAL1
LOCAL_LOG_LEVEL LITERAL1
DEFAULTTIMEOUT LITERAL1
DEFAULTIDLETIME LITERAL1
LOCAL_LOG_LEVEL LITERAL1
SERVER_END LITERAL1
SWAP_BYTES LITERAL1
SWAP_REGISTERS LITERAL1
SWAP_WORDS LITERAL1
SWAP_NIBBLES LITERAL1
LOCK_GUARD LITERAL1

View File

@@ -1,46 +0,0 @@
{
"name": "eModbus",
"version": "1.7.0",
"keywords": "Arduino, ESP32, Modbus, RTU, ASCII, ModbusASCII, ModbusRTU, ModbusTCP",
"description": "ModbusRTU, ModbusASCII and ModbusTCP functions for ESP32",
"homepage": "https://emodbus.github.io",
"license": "MIT",
"authors": [
{
"name": "Bert Melis",
"url": "https://github.com/bertmelis",
"maintainer": true
},
{
"name": "Michael Harwerth",
"url": "https://github.com/Miq1",
"email": "miq1@gmx.de",
"maintainer": true
}
],
"repository": {
"type": "git",
"url": "https://github.com/eModbus/eModbus",
"branch": "master"
},
"export": {
"include":
[
"src/*.cpp",
"src/*.h",
"examples/*",
"Test/*",
".gitignore",
"README.md",
"license.md",
"keywords.txt",
"library.properties",
"library.json"
]
},
"frameworks": "arduino",
"platforms": [
"espressif32",
"espressif8266"
]
}

View File

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

View File

@@ -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 <ETH.h>
#include <SPI.h>
#undef SERVER_END
#define SERVER_END // NIL for Ethernet
#include "ModbusServerTCPtemp.h"
#include "ModbusBridgeTemp.h"
using ModbusBridgeEthernet = ModbusBridge<ModbusServerTCP<WiFiServer, WiFiClient>>;
#endif
#endif

View File

@@ -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<SERVERCLASS>::ModbusBridge() :
// Constructors for RTU variant
template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::ModbusBridge(HardwareSerial& serial, uint32_t timeout, int rtsPin) :
SERVERCLASS(serial, timeout, rtsPin) { }
ModbusBridge<SERVERCLASS>::ModbusBridge(uint32_t timeout, int rtsPin) :
SERVERCLASS(timeout, rtsPin) { }
// Alternate constructors for RTU variant
template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::ModbusBridge(HardwareSerial& serial, uint32_t timeout, RTScallback rts) :
SERVERCLASS(serial, timeout, rts) { }
ModbusBridge<SERVERCLASS>::ModbusBridge(uint32_t timeout, RTScallback rts) :
SERVERCLASS(timeout, rts) { }
// Destructor
template<typename SERVERCLASS>
@@ -156,6 +168,62 @@ bool ModbusBridge<SERVERCLASS>::denyFunctionCode(uint8_t aliasID, uint8_t functi
return true;
}
template<typename SERVERCLASS>
bool ModbusBridge<SERVERCLASS>::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<typename SERVERCLASS>
bool ModbusBridge<SERVERCLASS>::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<typename SERVERCLASS>
bool ModbusBridge<SERVERCLASS>::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<typename SERVERCLASS>
bool ModbusBridge<SERVERCLASS>::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<typename SERVERCLASS>
ModbusMessage ModbusBridge<SERVERCLASS>::bridgeWorker(ModbusMessage msg) {
@@ -167,11 +235,17 @@ ModbusMessage ModbusBridge<SERVERCLASS>::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<ModbusClientTCP *>(servers[aliasID]->client)->syncRequestMT(msg, (uint32_t)millis(), servers[aliasID]->host, servers[aliasID]->port);
@@ -179,8 +253,19 @@ ModbusMessage ModbusBridge<SERVERCLASS>::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);

View File

@@ -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);

View File

@@ -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<RequestEntry> requests; // Queue to hold requests to be processed

View File

@@ -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<ModbusClientTCPasync *>(i))->onConnected(); }, this);
MTA_client.onDisconnect([](void * i, AsyncClient * c) { (static_cast<ModbusClientTCPasync *>(i))->onDisconnected(); }, this);
MTA_client.onError([](void * i, AsyncClient * c, int8_t error) { (static_cast<ModbusClientTCPasync *>(i))->onACError(c, error); }, this);
// MTA_client.onTimeout([](void* i, AsyncClient* c, uint32_t time) { (static_cast<ModbusClientTCPasync*>(i))->onTimeout(time); }, this);
// MTA_client.onAck([](void* i, AsyncClient* c, size_t len, uint32_t time) { (static_cast<ModbusClientTCPasync*>(i))->onAck(len, time); }, this);
MTA_client.onData([](void * i,
AsyncClient * c,
void * data,
size_t len) { (static_cast<ModbusClientTCPasync *>(i))->onPacket(static_cast<uint8_t *>(data), len); },
this);
MTA_client.onPoll([](void * i, AsyncClient * c) { (static_cast<ModbusClientTCPasync *>(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<ModbusClientTCPasync*>(i))->onConnected(); }, this);
MTA_client.onDisconnect([](void* i, AsyncClient* c) { (static_cast<ModbusClientTCPasync*>(i))->onDisconnected(); }, this);
MTA_client.onError([](void* i, AsyncClient* c, int8_t error) { (static_cast<ModbusClientTCPasync*>(i))->onACError(c, error); }, this);
// MTA_client.onTimeout([](void* i, AsyncClient* c, uint32_t time) { (static_cast<ModbusClientTCPasync*>(i))->onTimeout(time); }, this);
// MTA_client.onAck([](void* i, AsyncClient* c, size_t len, uint32_t time) { (static_cast<ModbusClientTCPasync*>(i))->onAck(len, time); }, this);
MTA_client.onData([](void* i, AsyncClient* c, void* data, size_t len) { (static_cast<ModbusClientTCPasync*>(i))->onPacket(static_cast<uint8_t*>(data), len); }, this);
MTA_client.onPoll([](void* i, AsyncClient* c) { (static_cast<ModbusClientTCPasync*>(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<RequestEntry *>::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<RequestEntry*>::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 char *>((const uint8_t *)(re->head)), 6, ASYNC_WRITE_FLAG_COPY);
// Request comes next
MTA_client.add(reinterpret_cast<const char *>(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 char *>((const uint8_t *)(re->head)), 6, ASYNC_WRITE_FLAG_COPY);
// Request comes next
MTA_client.add(reinterpret_cast<const char*>(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;
}

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
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;

View File

@@ -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

View File

@@ -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 <ETH.h>
#include <SPI.h>
#undef SERVER_END
#define SERVER_END // NIL for Ethernet
#include "ModbusServerTCPtemp.h"
using ModbusServerEthernet = ModbusServerTCP<WiFiServer, WiFiClient>;
#endif
#endif

View File

@@ -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<EthernetServer, EthernetClient>;
using ModbusServerEthernet = ModbusServerTCP<EthernetServerEM, EthernetClient>;
#endif
#endif

View File

@@ -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 {

View File

@@ -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

View File

@@ -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<mb_client *>(i))->onData(static_cast<uint8_t *>(data), len); }, this);
client->onPoll([](void * i, AsyncClient * c) { (static_cast<mb_client *>(i))->onPoll(); }, this);
client->onDisconnect([](void * i, AsyncClient * c) { (static_cast<mb_client *>(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<mb_client*>(i))->onData(static_cast<uint8_t*>(data), len); }, this);
client->onPoll([](void* i, AsyncClient* c) { (static_cast<mb_client*>(i))->onPoll(); }, this);
client->onDisconnect([](void* i, AsyncClient* c) { (static_cast<mb_client*>(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<uint16_t>(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<uint16_t>(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<uint16_t>(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<uint16_t>(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<const char *>(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<const char*>(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<ModbusServerTCPasync *>(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<ModbusServerTCPasync*>(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());
}

View File

@@ -14,7 +14,7 @@
#endif
#include <vector>
#include <Arduino.h> // for millis()
#include <Arduino.h> // for millis()
#if defined(ESP32)
#include <AsyncTCP.h>
@@ -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<ModbusMessage *> 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<ModbusMessage*> 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<mb_client *> clients;
uint8_t maxNoClients;
uint32_t idle_timeout;
#if USE_MUTEX
std::mutex cListLock; // client list protection
#endif
AsyncServer* server;
std::list<mb_client*> clients;
uint8_t maxNoClients;
uint32_t idle_timeout;
#if USE_MUTEX
std::mutex cListLock; // client list protection
#endif
};
#endif

View File

@@ -6,7 +6,7 @@
#define _MODBUS_SERVER_TCP_TEMP_H
#include <Arduino.h>
#include <mutex> // NOLINT
#include <mutex> // NOLINT
#include "ModbusServer.h"
#undef LOCAL_LOG_LEVEL
// #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE
@@ -17,155 +17,142 @@ extern "C" {
#include <freertos/task.h>
}
using std::lock_guard;
using std::mutex;
using std::vector;
using std::mutex;
using std::lock_guard;
template <typename ST, typename CT>
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<ST, CT> *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<ST, CT> *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<ST, CT> *myself);
struct ClientData {
ClientData()
: task(nullptr)
, client(0)
, timeout(0)
, parent(nullptr) {
}
ClientData(TaskHandle_t t, CT & c, uint32_t to, ModbusServerTCP<ST, CT> * 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<ST, CT> * parent;
};
ClientData ** clients;
// worker: loop function for client tasks
static void worker(ClientData *myData);
// serve: loop function for server task
static void serve(ModbusServerTCP<ST, CT> * 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 <typename ST, typename CT>
ModbusServerTCP<ST, CT>::ModbusServerTCP()
: ModbusServer()
, numClients(0)
, serverTask(nullptr)
, serverPort(502)
, serverTimeout(20000)
, serverGoDown(false) {
clients = new ClientData *[numClients]();
}
ModbusServerTCP<ST, CT>::ModbusServerTCP() :
ModbusServer(),
numClients(0),
serverTask(nullptr),
serverPort(502),
serverTimeout(20000),
serverGoDown(false) {
clients = new ClientData*[numClients]();
}
// Destructor: closes the connections
template <typename ST, typename CT>
ModbusServerTCP<ST, CT>::~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 <typename ST, typename CT>
uint16_t ModbusServerTCP<ST, CT>::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<mutex> 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<mutex> 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 <typename ST, typename CT>
bool ModbusServerTCP<ST, CT>::start(uint16_t port, uint8_t max_clients, uint32_t timeout, int coreID) {
bool ModbusServerTCP<ST, CT>::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<ST, CT>::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 <typename ST, typename CT>
bool ModbusServerTCP<ST, CT>::stop() {
bool ModbusServerTCP<ST, CT>::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 <typename ST, typename CT>
bool ModbusServerTCP<ST, CT>::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<ST, CT>::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 <typename ST, typename CT>
void ModbusServerTCP<ST, CT>::serve(ModbusServerTCP<ST, CT> * 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<ST, CT>::serve(ModbusServerTCP<ST, CT> *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 <typename ST, typename CT>
void ModbusServerTCP<ST, CT>::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<ST, CT> * myParent = myData->parent;
unsigned long myLastMessage = millis();
void ModbusServerTCP<ST, CT>::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<ST, CT> *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<uint16_t>(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<uint16_t>(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<mutex> 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<mutex> cL(myParent->clientLock);
myData->task = nullptr;
}
delay(50);
vTaskDelete(NULL);
}
// receive: get request via Client connection
template <typename ST, typename CT>
ModbusMessage ModbusServerTCP<ST, CT>::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<ST, CT>::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

View File

@@ -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) {

View File

@@ -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