mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
update eModbus to 1.7.2, #2254
This commit is contained in:
@@ -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":
|
||||
[
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name=eModbus
|
||||
version=1.7.0
|
||||
version=1.7.2
|
||||
author=bertmelis,Miq1 <miq1@gmx.de>
|
||||
maintainer=Miq1 <miq1@gmx.de>
|
||||
sentence=eModbus provides Modbus RTU, ASCII and TCP functions for ESP32.
|
||||
|
||||
21
lib/eModbus/src/ModbusBridgeETH.h
Normal file
21
lib/eModbus/src/ModbusBridgeETH.h
Normal 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
|
||||
@@ -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();
|
||||
@@ -44,6 +44,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
|
||||
struct ServerData {
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,30 +7,27 @@
|
||||
// #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) {
|
||||
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.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
|
||||
@@ -100,7 +97,8 @@ void ModbusClientTCPasync::setMaxInflightRequests(uint32_t maxInflightRequests)
|
||||
}
|
||||
|
||||
// Remove all pending request from queue
|
||||
void ModbusClientTCPasync::clearQueue() {
|
||||
void ModbusClientTCPasync::clearQueue()
|
||||
{
|
||||
LOCK_GUARD(lock1, qLock);
|
||||
LOCK_GUARD(lock2, sLock);
|
||||
// Delete all elements from queues
|
||||
@@ -154,8 +152,7 @@ bool ModbusClientTCPasync::addToQueue(int32_t token, ModbusMessage request, bool
|
||||
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
|
||||
if (!re) return false; //TODO: proper error returning in case allocation fails
|
||||
// inject proper transactionID
|
||||
re->head.transactionID = messageCount++;
|
||||
re->head.len = request.size();
|
||||
@@ -249,7 +246,9 @@ void ModbusClientTCPasync::onPacket(uint8_t * data, size_t length) {
|
||||
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) {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
19
lib/eModbus/src/ModbusServerETH.h
Normal file
19
lib/eModbus/src/ModbusServerETH.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
// #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() {
|
||||
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);
|
||||
@@ -101,7 +101,8 @@ void ModbusServerTCPasync::mb_client::onData(uint8_t * data, size_t len) {
|
||||
break;
|
||||
case 0xF1: // ECHO
|
||||
userData = request;
|
||||
if (request.getFunctionCode() == WRITE_MULT_REGISTERS || request.getFunctionCode() == WRITE_MULT_COILS) {
|
||||
if (request.getFunctionCode() == WRITE_MULT_REGISTERS ||
|
||||
request.getFunctionCode() == WRITE_MULT_COILS) {
|
||||
userData.resize(6);
|
||||
}
|
||||
LOG_D("ECHO response\n");
|
||||
@@ -138,7 +139,8 @@ void ModbusServerTCPasync::mb_client::onData(uint8_t * data, size_t len) {
|
||||
void ModbusServerTCPasync::mb_client::onPoll() {
|
||||
LOCK_GUARD(lock1, obLock);
|
||||
handleOutbox();
|
||||
if (server->idle_timeout > 0 && millis() - lastActiveTime > server->idle_timeout) {
|
||||
if (server->idle_timeout > 0 &&
|
||||
millis() - lastActiveTime > server->idle_timeout) {
|
||||
LOG_D("client idle, closing\n");
|
||||
client->close();
|
||||
}
|
||||
@@ -172,11 +174,11 @@ void ModbusServerTCPasync::mb_client::handleOutbox() {
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -193,14 +195,14 @@ uint16_t ModbusServerTCPasync::activeClients() {
|
||||
}
|
||||
|
||||
|
||||
bool ModbusServerTCPasync::start(uint16_t port, uint8_t max_clients, uint32_t timeout, int coreID) {
|
||||
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 = max_clients;
|
||||
maxNoClients = maxClients;
|
||||
idle_timeout = timeout;
|
||||
server = new AsyncServer(port);
|
||||
if (server) {
|
||||
@@ -215,6 +217,7 @@ bool ModbusServerTCPasync::start(uint16_t port, uint8_t max_clients, uint32_t ti
|
||||
}
|
||||
|
||||
bool ModbusServerTCPasync::stop() {
|
||||
|
||||
if (!server) {
|
||||
LOG_W("Server not running.\n");
|
||||
return false;
|
||||
@@ -238,10 +241,8 @@ bool ModbusServerTCPasync::stop() {
|
||||
}
|
||||
|
||||
bool ModbusServerTCPasync::isRunning() {
|
||||
if (server)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
if (server) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
void ModbusServerTCPasync::onClientConnect(AsyncClient* client) {
|
||||
|
||||
@@ -29,6 +29,7 @@ using std::lock_guard;
|
||||
#endif
|
||||
|
||||
class ModbusServerTCPasync : public ModbusServer {
|
||||
|
||||
private:
|
||||
class mb_client {
|
||||
friend class ModbusServerTCPasync;
|
||||
@@ -66,7 +67,7 @@ class ModbusServerTCPasync : public ModbusServer {
|
||||
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);
|
||||
bool start(uint16_t port, uint8_t maxClients, uint32_t timeout, int coreID = -1);
|
||||
|
||||
// stop: drop all connections and kill server task
|
||||
bool stop();
|
||||
@@ -75,8 +76,7 @@ class ModbusServerTCPasync : public ModbusServer {
|
||||
bool isRunning();
|
||||
|
||||
protected:
|
||||
inline void isInstance() {
|
||||
}
|
||||
inline void isInstance() { }
|
||||
void onClientConnect(AsyncClient* client);
|
||||
void onClientDisconnect(mb_client* client);
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ 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 {
|
||||
@@ -34,7 +34,7 @@ class ModbusServerTCP : public ModbusServer {
|
||||
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);
|
||||
bool start(uint16_t port, uint8_t maxClients, uint32_t timeout, int coreID = -1);
|
||||
|
||||
// stop: drop all connections and kill server task
|
||||
bool stop();
|
||||
@@ -44,8 +44,7 @@ class ModbusServerTCP : public ModbusServer {
|
||||
ModbusServerTCP(ModbusServerTCP& m) = delete;
|
||||
ModbusServerTCP& operator=(ModbusServerTCP& m) = delete;
|
||||
|
||||
inline void isInstance() {
|
||||
}
|
||||
inline void isInstance() { }
|
||||
|
||||
uint8_t numClients;
|
||||
TaskHandle_t serverTask;
|
||||
@@ -55,18 +54,9 @@ class ModbusServerTCP : public ModbusServer {
|
||||
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() : 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();
|
||||
@@ -96,20 +86,18 @@ class ModbusServerTCP : public ModbusServer {
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
ModbusServerTCP<ST, CT>::ModbusServerTCP() :
|
||||
ModbusServer(),
|
||||
numClients(0),
|
||||
serverTask(nullptr),
|
||||
serverPort(502),
|
||||
serverTimeout(20000),
|
||||
serverGoDown(false) {
|
||||
clients = new ClientData*[numClients]();
|
||||
}
|
||||
|
||||
@@ -141,26 +129,25 @@ uint16_t ModbusServerTCP<ST, CT>::activeClients() {
|
||||
clients[i] = nullptr;
|
||||
}
|
||||
}
|
||||
if (clients[i] != nullptr)
|
||||
cnt++;
|
||||
if (clients[i] != nullptr) cnt++;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
// Does the required number of slots fit?
|
||||
if (numClients != max_clients) {
|
||||
if (numClients != maxClients) {
|
||||
// No. Drop array and allocate a new one
|
||||
delete[] clients;
|
||||
// Now allocate a new one
|
||||
numClients = max_clients;
|
||||
numClients = maxClients;
|
||||
clients = new ClientData*[numClients]();
|
||||
}
|
||||
serverPort = port;
|
||||
@@ -312,7 +299,8 @@ void ModbusServerTCP<ST, CT>::worker(ClientData * myData) {
|
||||
break;
|
||||
case 0xF1: // ECHO
|
||||
response = request;
|
||||
if (request.getFunctionCode() == WRITE_MULT_REGISTERS || request.getFunctionCode() == WRITE_MULT_COILS) {
|
||||
if (request.getFunctionCode() == WRITE_MULT_REGISTERS ||
|
||||
request.getFunctionCode() == WRITE_MULT_COILS) {
|
||||
response.resize(6);
|
||||
}
|
||||
LOG_D("ECHO response\n");
|
||||
@@ -370,8 +358,7 @@ void ModbusServerTCP<ST, CT>::worker(ClientData * myData) {
|
||||
}
|
||||
|
||||
// Read away all that may still hang in the buffer
|
||||
while (myClient.read() != -1) {
|
||||
}
|
||||
while (myClient.read() != -1) {}
|
||||
// Now stop the client
|
||||
myClient.stop();
|
||||
|
||||
@@ -395,13 +382,13 @@ ModbusMessage ModbusServerTCP<ST, CT>::receive(CT & client, uint32_t timeWait) {
|
||||
uint8_t buffer[BUFFERSIZE];
|
||||
|
||||
// wait for sufficient packet data or timeout
|
||||
while ((millis() - lastMillis < timeWait) && ((cnt < 6) || (cnt < lengthVal)) && (cnt < BUFFERSIZE)) {
|
||||
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;
|
||||
if (cnt == 4) lengthVal = buffer[cnt] << 8;
|
||||
// Are we at the TCP header length field byte #2?
|
||||
if (cnt == 5) {
|
||||
lengthVal |= buffer[cnt];
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user