Files
Industrial_Emulator/lib/modbus-esp8266/ModbusTCPTemplate.h

607 lines
19 KiB
C++
Raw Blame History

/*
Modbus Library for Arduino
ModbusTCP general implementation
Copyright (C) 2014 Andr<64> Sarmento Barbosa
2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#pragma once
#include "Modbus.h"
#define BIT_SET(a,b) ((a) |= (1ULL<<(b)))
#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b)))
#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b)))) // '!!' to make sure this returns 0 or 1
#ifndef IPADDR_NONE
#define IPADDR_NONE ((uint32_t)0xffffffffUL)
#endif
// Callback function Type
#if defined(MODBUS_USE_STL)
typedef std::function<bool(IPAddress)> cbModbusConnect;
typedef std::function<IPAddress(const char*)> cbModbusResolver;
#else
typedef bool (*cbModbusConnect)(IPAddress ip);
typedef IPAddress (*cbModbusResolver)(const char*);
#endif
struct TTransaction {
uint16_t transactionId;
uint32_t timestamp;
cbTransaction cb = nullptr;
uint8_t* _frame = nullptr;
uint8_t* data = nullptr;
TAddress startreg;
Modbus::ResultCode forcedEvent = Modbus::EX_SUCCESS; // EX_SUCCESS means no forced event here. Forced EX_SUCCESS is not possible.
bool operator ==(const TTransaction &obj) const {
return transactionId == obj.transactionId;
}
};
template <class SERVER, class CLIENT>
class ModbusTCPTemplate : public Modbus {
protected:
union MBAP_t {
struct {
uint16_t transactionId;
uint16_t protocolId;
uint16_t length;
uint8_t unitId;
};
uint8_t raw[7];
};
cbModbusConnect cbConnect = nullptr;
cbModbusConnect cbDisconnect = nullptr;
SERVER* tcpserver = nullptr;
CLIENT* tcpclient[MODBUSIP_MAX_CLIENTS];
#if MODBUSIP_MAX_CLIENTS <= 8
uint8_t tcpServerConnection = 0;
#elif MODBUSIP_MAX_CLIENTS <= 16
uint16_t tcpServerConnection = 0;
#else
uint32_t tcpServerConnection = 0;
#endif
#if defined(MODBUS_USE_STL)
std::vector<TTransaction> _trans;
#else
DArray<TTransaction, 2, 2> _trans;
#endif
int16_t transactionId = 1; // Last started transaction. Increments on unsuccessful transaction start too.
int8_t n = -1;
bool autoConnectMode = false;
uint16_t serverPort = 0;
uint16_t defaultPort = MODBUSTCP_PORT;
cbModbusResolver resolve = nullptr;
TTransaction* searchTransaction(uint16_t id);
void cleanupConnections(); // Free clients if not connected
void cleanupTransactions(); // Remove timedout transactions and forced event
int8_t getFreeClient(); // Returns free slot position
int8_t getSlave(IPAddress ip);
int8_t getMaster(IPAddress ip);
public:
uint16_t send(String host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
uint16_t send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
// Prepare and send ModbusIP frame. _frame buffer and _len should be filled with Modbus data
// ip - slave ip address
// startreg - first local register to save returned data to (miningless for write to slave operations)
// cb - transaction callback function
// unit - slave modbus unit id
// data - if not null use buffer to save returned data instead of local registers
public:
ModbusTCPTemplate();
~ModbusTCPTemplate();
bool isTransaction(uint16_t id);
#if defined(MODBUSIP_USE_DNS)
bool isConnected(String host);
bool isConnected(const char* host);
bool connect(String host, uint16_t port = 0);
bool connect(const char* host, uint16_t port = 0);
bool disconnect(String host);
bool disconnect(const char* host);
#endif
bool isConnected(IPAddress ip);
bool connect(IPAddress ip, uint16_t port = 0);
bool disconnect(IPAddress ip);
// ModbusTCP
void server(uint16_t port = 0);
// ModbusTCP depricated
inline void slave(uint16_t port = 0) { server(port); } // Depricated
inline void master() { client(); } // Depricated
inline void begin() { server(); }; // Depricated
void client();
void task();
void onConnect(cbModbusConnect cb = nullptr);
void onDisconnect(cbModbusConnect cb = nullptr);
uint32_t eventSource() override;
void autoConnect(bool enabled = true);
void dropTransactions();
uint16_t setTransactionId(uint16_t);
#if defined(MODBUS_USE_STL)
static IPAddress defaultResolver(const char*) {return IPADDR_NONE;}
#else
static IPAddress defaultResolver(const char*) {return IPADDR_NONE;}
#endif
};
template <class SERVER, class CLIENT>
ModbusTCPTemplate<SERVER, CLIENT>::ModbusTCPTemplate() {
//_trans.reserve(MODBUSIP_MAX_TRANSACIONS);
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
tcpclient[i] = nullptr;
resolve = defaultResolver;
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::client() {
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::server(uint16_t port) {
if (port)
serverPort = port;
else
serverPort = defaultPort;
tcpserver = new SERVER(serverPort);
tcpserver->begin();
}
#if defined(MODBUSIP_USE_DNS)
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::connect(String host, uint16_t port) {
return connect(resolve(host.c_str()), port);
}
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::connect(const char* host, uint16_t port) {
return connect(resolve(host), port);
}
#endif
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::connect(IPAddress ip, uint16_t port) {
//cleanupConnections();
if (!ip)
return false;
if(getSlave(ip) != -1)
return true;
int8_t p = getFreeClient();
if (p == -1)
return false;
tcpclient[p] = new CLIENT();
BIT_CLEAR(tcpServerConnection, p);
#if defined(ESP32) && defined(MODBUSIP_CONNECT_TIMEOUT)
if (!tcpclient[p]->connect(ip, port?port:defaultPort, MODBUSIP_CONNECT_TIMEOUT)) {
#else
if (!tcpclient[p]->connect(ip, port?port:defaultPort)) {
#endif
delete(tcpclient[p]);
tcpclient[p] = nullptr;
return false;
}
return true;
}
template <class SERVER, class CLIENT>
uint32_t ModbusTCPTemplate<SERVER, CLIENT>::eventSource() { // Returns IP of current processing client query
if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && tcpclient[n])
#if !defined(ethernet_h)
return (uint32_t)tcpclient[n]->remoteIP();
#else
return 1;
#endif
return (uint32_t)INADDR_NONE;
}
template <class SERVER, class CLIENT>
TTransaction* ModbusTCPTemplate<SERVER, CLIENT>::searchTransaction(uint16_t id) {
#define MODBUSIP_COMPARE_TRANS [id](TTransaction& trans){return trans.transactionId == id;}
#if defined(MODBUS_USE_STL)
std::vector<TTransaction>::iterator it = std::find_if(_trans.begin(), _trans.end(), MODBUSIP_COMPARE_TRANS);
if (it != _trans.end()) return &*it;
return nullptr;
#else
return _trans.entry(_trans.find(MODBUSIP_COMPARE_TRANS));
#endif
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::task() {
MBAP_t _MBAP;
uint32_t taskStart = millis();
cleanupConnections();
if (tcpserver) {
CLIENT c;
// WiFiServer.available() == Ethernet.accept() and should wrapped to get code to be compatible with Ethernet library (See ModbusTCP.h code).
// WiFiServer.available() != Ethernet.available() internally
#if defined(MODBUSIP_USE_AVAILABLE)
while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->available())) {
#else
while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) {
#endif
#if defined(MODBUSIP_DEBUG)
Serial.println("IP: Accepted");
#endif
CLIENT* currentClient = new CLIENT(c);
if (!currentClient || !currentClient->connected()) {
delete currentClient;
continue;
}
#if defined(MODBUSIP_DEBUG)
Serial.println("IP: Connected");
#endif
if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) {
#if defined(MODBUSIP_UNIQUE_CLIENTS)
// Disconnect previous connection from same IP if present
n = getMaster(currentClient->remoteIP());
if (n != -1) {
tcpclient[n]->flush();
delete tcpclient[n];
tcpclient[n] = nullptr;
}
#endif
n = getFreeClient();
if (n > -1) {
tcpclient[n] = currentClient;
BIT_SET(tcpServerConnection, n);
#if defined(MODBUSIP_DEBUG)
Serial.print("IP: Conn ");
Serial.println(n);
#endif
#if defined(MODBUSIP_USE_AVAILABLE)
break; // while
#else
continue; // while
#endif
}
}
// Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached
delete currentClient;
}
}
for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) {
if (!tcpclient[n]) continue;
if (!tcpclient[n]->connected()) continue;
while ((size_t)tcpclient[n]->available() > sizeof(_MBAP) && millis() - taskStart < MODBUSIP_MAX_READMS) {
#if defined(MODBUSIP_DEBUG)
Serial.print(n);
Serial.print(": Bytes available ");
Serial.println(tcpclient[n]->available());
#endif
tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP
if (__swap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __swap is usless there.
while (tcpclient[n]->available()) // Drop all incoming if wrong packet
tcpclient[n]->read();
continue;
}
_len = __swap_16(_MBAP.length);
if (_len < MODBUSIP_MINFRAME) { // Length is shorter than MODBUSIP_MINFRAME
Modbus::FunctionCode fc = FC_READ_COILS; // Just placeholder
while (tcpclient[n]->available()) // Drop rest of the packet
tcpclient[n]->read();
exceptionResponse(fc, EX_ILLEGAL_VALUE);
}
_len--; // Do not count with last byte from MBAP
if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME
Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read();
_len--; // Subtract for read byte
for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet
tcpclient[n]->read();
exceptionResponse(fc, EX_SLAVE_FAILURE);
}
else {
free(_frame);
_frame = (uint8_t*) malloc(_len);
if (!_frame) {
Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read();
_len--; // Subtract for read byte
for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet
tcpclient[n]->read();
exceptionResponse(fc, EX_SLAVE_FAILURE);
}
else {
if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame
exceptionResponse((Modbus::FunctionCode)_frame[0], EX_ILLEGAL_VALUE);
//while (tcpclient[n]->available()) // Drop all incoming (if any)
// tcpclient[n]->read();
}
else {
_reply = EX_PASSTHROUGH;
// Note on _reply usage
// it's used and set as ReplyCode by slavePDU and as exceptionCode by masterPDU
if (_cbRaw) {
frame_arg_t transData = { _MBAP.unitId, tcpclient[n]->remoteIP(), __swap_16(_MBAP.transactionId), BIT_CHECK(tcpServerConnection, n) };
_reply = _cbRaw(_frame, _len, &transData);
}
if (BIT_CHECK(tcpServerConnection, n)) {
if (_reply == EX_PASSTHROUGH)
slavePDU(_frame); // Process incoming frame as slave
else
_reply = REPLY_OFF;
}
else {
// Process reply to master request
TTransaction* trans = searchTransaction(__swap_16(_MBAP.transactionId));
if (trans) { // if valid transaction id
if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested
if (_reply == EX_PASSTHROUGH)
masterPDU(_frame, trans->_frame, trans->startreg, trans->data); // Process incoming frame as master
}
else {
_reply = EX_UNEXPECTED_RESPONSE;
}
if (trans->cb) {
trans->cb((ResultCode)_reply, trans->transactionId, nullptr);
}
free(trans->_frame);
#if defined(MODBUS_USE_STL)
//_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() );
std::vector<TTransaction>::iterator it = std::find(_trans.begin(), _trans.end(), *trans);
if (it != _trans.end())
_trans.erase(it);
#else
size_t r = _trans.find([trans](TTransaction& t){return *trans == t;});
_trans.remove(r);
#endif
}
}
}
}
}
if (!BIT_CHECK(tcpServerConnection, n)) _reply = REPLY_OFF; // No replay if it was responce to master
if (_reply != REPLY_OFF) {
_MBAP.length = __swap_16(_len+1); // _len+1 for last byte from MBAP
size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw);
uint8_t sbuf[send_len];
memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw));
memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len);
tcpclient[n]->write(sbuf, send_len);
//tcpclient[n]->flush();
}
if (_frame) {
free(_frame);
_frame = nullptr;
}
_len = 0;
}
}
n = -1;
cleanupTransactions();
}
template <class SERVER, class CLIENT>
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::send(String host, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
return send(resolve(host.c_str()), startreg, cb, unit, data, waitResponse);
}
template <class SERVER, class CLIENT>
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
return send(resolve(host), startreg, cb, unit, data, waitResponse);
}
template <class SERVER, class CLIENT>
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
MBAP_t _MBAP;
uint16_t result = 0;
int8_t p;
#if defined(MODBUSIP_MAX_TRANSACTIONS)
if (_trans.size() >= MODBUSIP_MAX_TRANSACTIONS)
goto cleanup;
#endif
if (!ip)
return 0;
if (tcpserver) {
p = getMaster(ip);
} else {
p = getSlave(ip);
}
if (p == -1 || !tcpclient[p]->connected()) {
if (!autoConnectMode)
goto cleanup;
if (!connect(ip))
goto cleanup;
}
_MBAP.transactionId = __swap_16(transactionId);
_MBAP.protocolId = __swap_16(0);
_MBAP.length = __swap_16(_len+1); //_len+1 for last byte from MBAP
_MBAP.unitId = unit;
bool writeResult;
{ // for sbuf isolation
size_t send_len = _len + sizeof(_MBAP.raw);
uint8_t sbuf[send_len];
memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw));
memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len);
writeResult = (tcpclient[p]->write(sbuf, send_len) == send_len);
}
if (!writeResult)
goto cleanup;
//tcpclient[p]->flush();
if (waitResponse) {
TTransaction tmp;
tmp.transactionId = transactionId;
tmp.timestamp = millis();
tmp.cb = cb;
tmp.data = data; // BUG: Should data be saved? It may lead to memory leak or double free.
tmp._frame = _frame;
tmp.startreg = startreg;
_trans.push_back(tmp);
_frame = nullptr;
}
result = transactionId;
transactionId++;
if (!transactionId)
transactionId = 1;
cleanup:
free(_frame);
_frame = nullptr;
_len = 0;
return result;
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::onConnect(cbModbusConnect cb) {
cbConnect = cb;
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::onDisconnect(cbModbusConnect cb) {
cbDisconnect = cb;
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::cleanupConnections() {
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) {
if (tcpclient[i] && !tcpclient[i]->connected()) {
//IPAddress ip = tcpclient[i]->remoteIP();
tcpclient[i]->stop();
delete tcpclient[i];
tcpclient[i] = nullptr;
if (cbDisconnect && cbEnabled)
cbDisconnect(IPADDR_NONE);
}
}
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::cleanupTransactions() {
#if defined(MODBUS_USE_STL)
for (auto it = _trans.begin(); it != _trans.end();) {
if (millis() - it->timestamp > MODBUSIP_TIMEOUT || it->forcedEvent != Modbus::EX_SUCCESS) {
Modbus::ResultCode res = (it->forcedEvent != Modbus::EX_SUCCESS)?it->forcedEvent:Modbus::EX_TIMEOUT;
if (it->cb)
it->cb(res, it->transactionId, nullptr);
free(it->_frame);
it = _trans.erase(it);
} else
it++;
}
#else
size_t i = 0;
while (i < _trans.size()) {
TTransaction t = _trans[i];
if (millis() - t.timestamp > MODBUSIP_TIMEOUT || t.forcedEvent != Modbus::EX_SUCCESS) {
Modbus::ResultCode res = (t.forcedEvent != Modbus::EX_SUCCESS)?t.forcedEvent:Modbus::EX_TIMEOUT;
if (t.cb)
t.cb(res, t.transactionId, nullptr);
free(t._frame);
_trans.remove(i);
} else
i++;
}
#endif
}
template <class SERVER, class CLIENT>
int8_t ModbusTCPTemplate<SERVER, CLIENT>::getFreeClient() {
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
if (!tcpclient[i])
return i;
return -1;
}
template <class SERVER, class CLIENT>
int8_t ModbusTCPTemplate<SERVER, CLIENT>::getSlave(IPAddress ip) {
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && !BIT_CHECK(tcpServerConnection, i))
return i;
return -1;
}
template <class SERVER, class CLIENT>
int8_t ModbusTCPTemplate<SERVER, CLIENT>::getMaster(IPAddress ip) {
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && BIT_CHECK(tcpServerConnection, i))
return i;
return -1;
}
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::isTransaction(uint16_t id) {
return searchTransaction(id) != nullptr;
}
#if defined(MODBUSIP_USE_DNS)
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::isConnected(String host) {
return isConnected(resolve(host.c_str()));
}
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::isConnected(const char* host) {
return isConnected(resolve(host));
}
#endif
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::isConnected(IPAddress ip) {
if (!ip)
return false;
int8_t p = getSlave(ip);
return p != -1 && tcpclient[p]->connected();
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::autoConnect(bool enabled) {
autoConnectMode = enabled;
}
#if defined(MODBUSIP_USE_DNS)
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::disconnect(String host) {
return disconnect(resolve(host.c_str()));
}
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::disconnect(const char* host) {
return disconnect(resolve(host));
}
#endif
template <class SERVER, class CLIENT>
bool ModbusTCPTemplate<SERVER, CLIENT>::disconnect(IPAddress ip) {
if (!ip)
return false;
int8_t p = getSlave(ip);
if (p != -1) {
tcpclient[p]->stop();
delete tcpclient[p];
tcpclient[p] = nullptr;
return true;
}
return false;
}
template <class SERVER, class CLIENT>
void ModbusTCPTemplate<SERVER, CLIENT>::dropTransactions() {
#if defined(MODBUS_USE_STL)
for (auto &t : _trans) t.forcedEvent = EX_CANCEL;
#else
for (size_t i = 0; i < _trans.size(); i++)
_trans.entry(i)->forcedEvent = EX_CANCEL;
#endif
}
template <class SERVER, class CLIENT>
ModbusTCPTemplate<SERVER, CLIENT>::~ModbusTCPTemplate() {
free(_frame);
_frame = nullptr;
dropTransactions();
cleanupConnections();
cleanupTransactions();
delete tcpserver;
tcpserver = nullptr;
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) {
delete tcpclient[i];
tcpclient[i] = nullptr;
}
}
template <class SERVER, class CLIENT>
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::setTransactionId(uint16_t t) {
transactionId = t;
if (!transactionId)
transactionId = 1;
return transactionId;
}