First stable compilation with ModbusRTU and ModbusTCP

This commit is contained in:
2025-09-14 12:01:55 -05:00
commit ac20b82496
59 changed files with 6841 additions and 0 deletions

View File

@@ -0,0 +1,931 @@
/*
Modbus Library for Arduino
Core functions
Copyright (C) 2014 Andr<64> Sarmento Barbosa
2017-2023 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#include "Modbus.h"
#if defined(MODBUS_GLOBAL_REGS)
#if defined(MODBUS_USE_STL)
std::vector<TRegister> Modbus::_regs;
std::vector<TCallback> Modbus::_callbacks;
#if defined(MODBUS_FILES)
std::function<Modbus::ResultCode(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> Modbus::_onFile;
#endif
#else
DArray<TRegister, 1, 1> Modbus::_regs;
DArray<TCallback, 1, 1> Modbus::_callbacks;
#if defined(MODBUS_FILES)
cbModbusFileOp Modbus::_onFile = nullptr;
#endif
#endif
#endif
uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType t) {
#define MODBUS_COMPARE_CB [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}
uint16_t newVal = val;
#if defined(MODBUS_USE_STL)
std::vector<TCallback>::iterator it = _callbacks.begin();
do {
it = std::find_if(it, _callbacks.end(), MODBUS_COMPARE_CB);
if (it != _callbacks.end()) {
newVal = it->cb(reg, newVal);
it++;
}
} while (it != _callbacks.end());
#else
size_t r = 0;
do {
r = _callbacks.find(MODBUS_COMPARE_CB, r);
if (r < _callbacks.size())
newVal = _callbacks[r].cb(reg, newVal);
r++;
} while (r < _callbacks.size());
#endif
return newVal;
}
TRegister* Modbus::searchRegister(TAddress address) {
#define MODBUS_COMPARE_REG [address](TRegister& addr){return (addr.address == address);}
#if defined(MODBUS_USE_STL)
std::vector<TRegister>::iterator it = std::find_if(_regs.begin(), _regs.end(), MODBUS_COMPARE_REG);
if (it != _regs.end()) return &*it;
#else
size_t r = _regs.find(MODBUS_COMPARE_REG);
if (r < _regs.size()) return _regs.entry(r);
#endif
return nullptr;
}
bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) {
#if defined(MODBUS_MAX_REGS)
if (_regs.size() + numregs > MODBUS_MAX_REGS) return false;
#endif
if (0xFFFF - address.address < numregs)
numregs = 0xFFFF - address.address;
for (uint16_t i = 0; i < numregs; i++) {
if (!searchRegister(address + i))
_regs.push_back({address + i, value});
}
//std::sort(_regs.begin(), _regs.end());
return true;
}
bool Modbus::Reg(TAddress address, uint16_t value) {
TRegister* reg;
reg = searchRegister(address); //search for the register address
if (reg) { //if found then assign the register value to the new value.
if (cbEnabled) {
reg->value = callback(reg, value, TCallback::ON_SET);
} else {
reg->value = value;
}
return true;
} else
return false;
}
uint16_t Modbus::Reg(TAddress address) {
TRegister* reg;
reg = searchRegister(address);
if(reg)
if (cbEnabled) {
return callback(reg, reg->value, TCallback::ON_GET);
} else {
return reg->value;
}
else
return 0;
}
bool Modbus::removeReg(TAddress address, uint16_t numregs) {
TRegister* reg;
bool atLeastOne = false;
if (0xFFFF - address.address < numregs)
numregs = 0xFFFF - address.address;
for (uint16_t i = 0; i < numregs; i++) {
reg = searchRegister(address + i);
if (reg) {
atLeastOne = true;
removeOnSet(address + i);
removeOnGet(address + i);
#if defined(MODBUS_USE_STL)
_regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() );
#else
_regs.remove(_regs.find(MODBUS_COMPARE_REG));
#endif
}
}
return atLeastOne;
}
bool Modbus::addReg(TAddress address, uint16_t* value, uint16_t numregs) {
if (0xFFFF - address.address < numregs)
numregs = 0xFFFF - address.address;
for (uint16_t k = 0; k < numregs; k++)
addReg(address + k, value[k]);
return true;
}
void Modbus::slavePDU(uint8_t* frame) {
FunctionCode fcode = (FunctionCode)frame[0];
uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2];
uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4];
uint16_t field3 = 0;
uint16_t field4 = 0;
uint16_t bytecount_calc;
uint16_t k;
ResultCode ex;
switch (fcode) {
case FC_WRITE_REG:
//field1 = reg, field2 = value
ex = _onRequest(fcode, {HREG(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
if (!Reg(HREG(field1), field2)) { //Check Address and execute (reg exists?)
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
if (Reg(HREG(field1)) != field2) { //Check for failure
exceptionResponse(fcode, EX_SLAVE_FAILURE);
return;
}
_reply = REPLY_ECHO;
_onRequestSuccess(fcode, {HREG(field1), field2});
break;
case FC_READ_REGS:
//field1 = startreg, field2 = numregs, header len = 3
ex = _onRequest(fcode, {HREG(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
ex = readWords(HREG(field1), field2, fcode);
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
_onRequestSuccess(fcode, {HREG(field1), field2});
break;
case FC_WRITE_REGS:
//field1 = startreg, field2 = numregs, frame[5] = data lenght, header len = 6
ex = _onRequest(fcode, {HREG(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || 0xFFFF - field1 < field2 || frame[5] != 2 * field2) { //Check constrains
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
return;
}
for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs)
if (!searchRegister(HREG(field1) + k)) {
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
}
if (!setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2)) {
exceptionResponse(fcode, EX_SLAVE_FAILURE);
return;
}
successResponce(HREG(field1), field2, fcode);
_reply = REPLY_NORMAL;
_onRequestSuccess(fcode, {HREG(field1), field2});
break;
case FC_READ_COILS:
//field1 = startreg, field2 = numregs
ex = _onRequest(fcode, {COIL(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
ex = readBits(COIL(field1), field2, fcode);
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
_onRequestSuccess(fcode, {COIL(field1), field2});
break;
case FC_READ_INPUT_STAT:
//field1 = startreg, field2 = numregs
ex = _onRequest(fcode, {ISTS(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
ex = readBits(ISTS(field1), field2, fcode);
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
_onRequestSuccess(fcode, {ISTS(field1), field2});
break;
case FC_READ_INPUT_REGS:
//field1 = startreg, field2 = numregs
ex = _onRequest(fcode, {IREG(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
ex = readWords(IREG(field1), field2, fcode);
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
_onRequestSuccess(fcode, {IREG(field1), field2});
break;
case FC_WRITE_COIL:
//field1 = reg, field2 = status, header len = 3
ex = _onRequest(fcode, {COIL(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
if (field2 != 0xFF00 && field2 != 0x0000) { //Check value (status)
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
return;
}
if (!Reg(COIL(field1), field2)) { //Check Address and execute (reg exists?)
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
if (Reg(COIL(field1)) != field2) { //Check for failure
exceptionResponse(fcode, EX_SLAVE_FAILURE);
return;
}
_reply = REPLY_ECHO;
_onRequestSuccess(fcode, {COIL(field1), field2});
break;
case FC_WRITE_COILS:
//field1 = startreg, field2 = numregs, frame[5] = bytecount, header len = 6
ex = _onRequest(fcode, {COIL(field1), field2});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
bytecount_calc = field2 / 8;
if (field2%8) bytecount_calc++;
if (field2 < 0x0001 || field2 > MODBUS_MAX_BITS || 0xFFFF - field1 < field2 || frame[5] != bytecount_calc) { //Check registers range and data size maches
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
return;
}
for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs)
if (!searchRegister(COIL(field1) + k)) {
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
}
if (!setMultipleBits(frame + 6, COIL(field1), field2)) {
exceptionResponse(fcode, EX_SLAVE_FAILURE);
return;
}
successResponce(COIL(field1), field2, fcode);
_reply = REPLY_NORMAL;
_onRequestSuccess(fcode, {COIL(field1), field2});
break;
#if defined(MODBUS_FILES)
case FC_READ_FILE_REC:
if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
return;
}
{
uint8_t bufSize = 2; // 2 bytes for frame header
uint8_t* recs = frame + 2; // Begin of sub-recs blocks
uint8_t recsCount = frame[1] / 7; // Count of sub-rec blocks
for (uint8_t p = 0; p < recsCount; p++) { // Calc output buffer size required
//uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2];
uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4];
uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6];
//Serial.printf("%d, %d, %d\n", fileNum, recNum, recLen);
if (recs[0] != 0x06 || recNum > 0x270F) { // Wrong ref type or count of records
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
bufSize += recLen * 2 + 2; // 4 bytes for header + data
recs += 7;
}
// if (bufSize > MODBUS_MAX_FRAME) { // Frame to return too large
// exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
// return;
// }
uint8_t* srcFrame = _frame;
_frame = (uint8_t*)malloc(bufSize);
if (!_frame) {
free(srcFrame);
exceptionResponse(fcode, EX_SLAVE_FAILURE);
return;
}
_len = bufSize;
recs = frame + 2; // Begin of sub-recs blocks
uint8_t* data = _frame + 2;
for (uint8_t p = 0; p < recsCount; p++) {
uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2];
uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4];
uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6];
ResultCode res = fileOp(fcode, fileNum, recNum, recLen, data + 2);
if (res != EX_SUCCESS) { // File read failed
free(srcFrame);
exceptionResponse(fcode, res);
return;
}
data[0] = recLen * 2 + 1;
data[1] = 0x06;
data += recLen * 2 + 2;
recs += 7;
}
_frame[0] = fcode;
_frame[1] = bufSize;
_reply = REPLY_NORMAL;
free(srcFrame);
}
break;
case FC_WRITE_FILE_REC: {
if (frame[1] < 0x09 || frame[1] > 0xFB) { // Wrong request data size
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
return;
}
uint8_t* recs = frame + 2; // Begin of sub-recs blocks
while (recs < frame + frame[1]) {
if (recs[0] != 0x06) {
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2];
uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4];
uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6];
if (recs + recLen * 2 > frame + frame[1]) {
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
ResultCode res = fileOp(fcode, fileNum, recNum, recLen, recs + 7);
if (res != EX_SUCCESS) { // File write failed
exceptionResponse(fcode, res);
return;
}
recs += 7 + recLen * 2;
}
}
_reply = REPLY_ECHO;
break;
#endif
case FC_MASKWRITE_REG:
//field1 = reg, field2 = AND mask, field3 = OR mask
// Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask))
field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6];
ex = _onRequest(fcode, {HREG(field1), field2, field3});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
field4 = Reg(HREG(field1));
field4 = (field4 & field2) | (field3 & ~field2);
if (!Reg(HREG(field1), field4)) { //Check Address and execute (reg exists?)
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
return;
}
if (Reg(HREG(field1)) != field4) { //Check for failure
exceptionResponse(fcode, EX_SLAVE_FAILURE);
return;
}
_reply = REPLY_ECHO;
_onRequestSuccess(fcode, {HREG(field1), field2, field3});
break;
case FC_READWRITE_REGS:
//field1 = readreg, field2 = read count, frame[9] = data lenght, header len = 10
//field3 = wtitereg, field4 = write count
field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6];
field4 = (uint16_t)frame[7] << 8 | (uint16_t)frame[8];
ex = _onRequest(fcode, {HREG(field1), field2, HREG(field3), field4});
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS ||
field4 < 0x0001 || field4 > MODBUS_MAX_WORDS ||
0xFFFF - field1 < field2 || 0xFFFF - field1 < field2 ||
frame[9] != 2 * field4) { //Check value
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
return;
}
if (!setMultipleWords((uint16_t*)(frame + 10), HREG(field3), field4)) {
exceptionResponse(fcode, EX_SLAVE_FAILURE);
return;
}
ex = readWords(HREG(field1), field2, fcode);
if (ex != EX_SUCCESS) {
exceptionResponse(fcode, ex);
return;
}
_onRequestSuccess(fcode, {HREG(field1), field2, HREG(field3), field4});
break;
default:
ex = _onRequest(fcode, {frame + 1});
if (ex != EX_PASSTHROUGH) {
exceptionResponse(fcode, EX_ILLEGAL_FUNCTION);
}
return;
}
}
void Modbus::successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn) {
free(_frame);
_len = 5;
_frame = (uint8_t*) malloc(_len);
if (!_frame) {
_reply = REPLY_OFF;
return;
}
_frame[0] = fn;
_frame[1] = startreg.address >> 8;
_frame[2] = startreg.address & 0x00FF;
_frame[3] = numoutputs >> 8;
_frame[4] = numoutputs & 0x00FF;
}
void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) {
free(_frame);
_len = 2;
_frame = (uint8_t*) malloc(_len);
if (!_frame) {
_reply = REPLY_OFF;
return;
}
_frame[0] = fn + 0x80;
_frame[1] = excode;
_reply = REPLY_NORMAL;
}
void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs) {
uint8_t bitn = 0;
uint16_t i = 0;
while (numregs--) {
if (BIT_BOOL(Reg(startreg)))
bitSet(frame[i], bitn);
else
bitClear(frame[i], bitn);
bitn++; //increment the bit index
if (bitn == 8) {
i++;
bitn = 0;
}
startreg++; //increment the register
}
}
void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) {
for (uint8_t i = 0; i < numregs; i++) {
frame[i] = __swap_16(Reg(startreg + i));
}
}
Modbus::ResultCode Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) {
if (numregs < 0x0001 || numregs > MODBUS_MAX_BITS || (0xFFFF - startreg.address) < numregs)
return EX_ILLEGAL_ADDRESS;
//Check Address
//Check only startreg. Is this correct?
//When I check all registers in range I got errors in ScadaBR
//I think that ScadaBR request more than one in the single request
//when you have more then one datapoint configured from same type.
#if defined(MODBUS_STRICT_REG)
for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs)
if (!searchRegister(startreg + k))
return EX_ILLEGAL_ADDRESS;
}
#else
if (!searchRegister(startreg))
return EX_ILLEGAL_ADDRESS;
#endif
free(_frame);
//Determine the message length = function type, byte count and
//for each group of 8 registers the message length increases by 1
_len = 2 + numregs/8;
if (numregs % 8) _len++; //Add 1 to the message length for the partial byte.
_frame = (uint8_t*) malloc(_len);
if (!_frame)
return EX_SLAVE_FAILURE;
_frame[0] = fn;
_frame[1] = _len - 2; //byte count (_len - function code and byte count)
_frame[_len - 1] = 0; //Clean last probably partial byte
getMultipleBits(_frame+2, startreg, numregs);
_reply = REPLY_NORMAL;
return EX_SUCCESS;
}
Modbus::ResultCode Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) {
//Check value (numregs)
if (numregs < 0x0001 || numregs > MODBUS_MAX_WORDS || 0xFFFF - startreg.address < numregs)
return EX_ILLEGAL_ADDRESS;
#if defined(MODBUS_STRICT_REG)
for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs)
if (!searchRegister(startreg + k))
return EX_ILLEGAL_ADDRESS;
}
#else
if (!searchRegister(startreg))
return EX_ILLEGAL_ADDRESS;
#endif
free(_frame);
_len = 2 + numregs * 2; //calculate the query reply message length. 2 bytes per register + 2 bytes for header
_frame = (uint8_t*) malloc(_len);
if (!_frame)
return EX_SLAVE_FAILURE;
_frame[0] = fn;
_frame[1] = _len - 2; //byte count
getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs);
_reply = REPLY_NORMAL;
return EX_SUCCESS;
}
bool Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) {
uint8_t bitn = 0;
uint16_t i = 0;
bool result = true;
while (numoutputs--) {
Reg(startreg, BIT_VAL(bitRead(frame[i], bitn)));
if (Reg(startreg) != BIT_VAL(bitRead(frame[i], bitn)))
result = false;
bitn++; //increment the bit index
if (bitn == 8) {
i++;
bitn = 0;
}
startreg++; //increment the register
}
return result;
}
bool Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) {
bool result = true;
for (uint8_t i = 0; i < numregs; i++) {
Reg(startreg + i, __swap_16(frame[i]));
if (Reg(startreg + i) != __swap_16(frame[i]))
result = false;
}
return result;
}
bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) {
TRegister* reg;
bool atLeastOne = false;
if (!cb) {
return removeOnGet(address, nullptr, numregs);
}
while (numregs > 0) {
reg = searchRegister(address);
if (reg) {
_callbacks.push_back({TCallback::ON_GET, address, cb});
atLeastOne = true;
}
address++;
numregs--;
}
return atLeastOne;
}
bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) {
TRegister* reg;
bool atLeastOne = false;
if (!cb) {
return removeOnSet(address, nullptr, numregs);
}
while (numregs > 0) {
reg = searchRegister(address);
if (reg) {
_callbacks.push_back({TCallback::ON_SET, address, cb});
atLeastOne = true;
}
address++;
numregs--;
}
return atLeastOne;
}
bool Modbus::removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb, uint16_t numregs) {
size_t s = _callbacks.size();
#if defined(MODBUS_USE_STL)
#define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){\
return entry.type == t && entry.address == address \
&& (!cb || std::addressof(cb) == std::addressof(entry.cb));}
while(numregs--) {
_callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ON), _callbacks.end());
address++;
}
#else
#define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){ \
return entry.type == t && entry.address == address \
&& (!cb || entry.cb == cb);}
while(numregs--) {
size_t r = 0;
do {
r = _callbacks.find(MODBUS_COMPARE_ON);
_callbacks.remove(r);
} while (r < _callbacks.size());
address++;
}
#endif
return s == _callbacks.size();
}
bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) {
return removeOn(TCallback::ON_SET, address, cb, numregs);
}
bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) {
return removeOn(TCallback::ON_GET, address, cb, numregs);
}
bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) {
free(_frame);
_len = 5;
_frame = (uint8_t*) malloc(_len);
if (!_frame) {
_reply = REPLY_OFF;
return false;
}
_frame[0] = fn;
_frame[1] = address >> 8;
_frame[2] = address & 0x00FF;
_frame[3] = numregs >> 8;
_frame[4] = numregs & 0x00FF;
return true;
}
bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data) {
free(_frame);
_len = 6 + numregs/8;
if (numregs % 8) _len++; //Add 1 to the message length for the partial byte.
_frame = (uint8_t*) malloc(_len);
if (!_frame) {
_reply = REPLY_OFF;
return false;
}
_frame[0] = fn;
_frame[1] = to >> 8;
_frame[2] = to & 0x00FF;
_frame[3] = numregs >> 8;
_frame[4] = numregs & 0x00FF;
_frame[5] = _len - 6;
_frame[_len - 1] = 0; //Clean last probably partial byte
if (data) {
boolToBits(_frame + 6, data, numregs);
} else {
getMultipleBits(_frame + 6, startreg, numregs);
}
_reply = REPLY_NORMAL;
return true;
}
bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data) {
free(_frame);
_len = 6 + 2 * numregs;
_frame = (uint8_t*) malloc(_len);
if (!_frame) {
_reply = REPLY_OFF;
return false;
}
_frame[0] = fn;
_frame[1] = to >> 8;
_frame[2] = to & 0x00FF;
_frame[3] = numregs >> 8;
_frame[4] = numregs & 0x00FF;
_frame[5] = _len - 6;
if (data) {
uint16_t* frame = (uint16_t*)(_frame + 6);
for (uint8_t i = 0; i < numregs; i++) {
frame[i] = __swap_16(data[i]);
}
} else {
getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs);
}
return true;
}
void Modbus::boolToBits(uint8_t* dst, bool* src, uint16_t numregs) {
uint8_t bitn = 0;
uint16_t i = 0;
uint16_t j = 0;
while (numregs--) {
if (src[j])
bitSet(dst[i], bitn);
else
bitClear(dst[i], bitn);
bitn++; //increment the bit index
if (bitn == 8) {
i++;
bitn = 0;
}
j++; //increment the register
}
}
void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) {
uint8_t bitn = 0;
uint16_t i = 0;
uint16_t j = 0;
while (numregs--) {
dst[j] = bitRead(src[i], bitn);
bitn++; //increment the bit index
if (bitn == 8) {
i++;
bitn = 0;
}
j++; //increment the register
}
}
void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output) {
uint8_t fcode = frame[0];
if ((fcode & 0x80) != 0) { // Check if error responce
_reply = frame[1];
return;
}
if (fcode != sourceFrame[0]) { // Check if responce matches the request
_reply = EX_DATA_MISMACH;
return;
}
_reply = EX_SUCCESS;
uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4];
uint8_t bytecount_calc;
switch (fcode) {
case FC_READ_REGS:
case FC_READ_INPUT_REGS:
case FC_READWRITE_REGS:
//field2 = numregs, frame[1] = data lenght, header len = 2
if (frame[1] != 2 * field2) { //Check if data size matches
_reply = EX_DATA_MISMACH;
break;
}
if (output) {
uint16_t* from = (uint16_t*)(frame + 2);
uint16_t* to = (uint16_t*)output;
while(field2--) {
*(to++) = __swap_16(*(from++));
}
} else {
setMultipleWords((uint16_t*)(frame + 2), startreg, field2);
}
break;
case FC_READ_COILS:
case FC_READ_INPUT_STAT:
//field2 = numregs, frame[1] = data length, header len = 2
bytecount_calc = field2 / 8;
if (field2 % 8) bytecount_calc++;
if (frame[1] != bytecount_calc) { // check if data size matches
_reply = EX_DATA_MISMACH;
break;
}
if (output) {
bitsToBool((bool*)output, frame + 2, field2);
} else {
setMultipleBits(frame + 2, startreg, field2);
}
break;
#if defined(MODBUS_FILES)
case FC_READ_FILE_REC:
// Should check if byte order swap needed
if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size
_reply = EX_ILLEGAL_VALUE;
return;
}
{
uint8_t* data = frame + 2;
uint8_t* eoFrame = frame + frame[1];
while (data < eoFrame) {
//data[0] - sub-req length
//data[1] = 0x06
if (data[1] != 0x06 || data[0] < 0x07 || data[0] > 0xF5 || data + data[0] > eoFrame) { // Wrong request data size
_reply = EX_ILLEGAL_VALUE;
return;
}
memcpy(output, data + 2, data[0]);
data += data[0] + 1;
output += data[0] - 1;
}
}
break;
case FC_WRITE_FILE_REC:
#endif
case FC_WRITE_REG:
case FC_WRITE_REGS:
case FC_WRITE_COIL:
case FC_WRITE_COILS:
case FC_MASKWRITE_REG:
break;
default:
_reply = EX_GENERAL_FAILURE;
}
}
bool Modbus::cbEnable(const bool state) {
const bool old_state = state;
cbEnabled = state;
return old_state;
}
bool Modbus::cbDisable() {
return cbEnable(false);
}
Modbus::~Modbus() {
free(_frame);
}
#if defined(MODBUS_FILES)
#if defined(MODBUS_USE_STL)
bool Modbus::onFile(std::function<Modbus::ResultCode(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> cb) {
#else
bool Modbus::onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)) {
#endif
_onFile = cb;
return true;
}
Modbus::ResultCode Modbus::fileOp(Modbus::FunctionCode fc, uint16_t fileNum, uint16_t recNum, uint16_t recLen, uint8_t* frame) {
if (!_onFile) return EX_ILLEGAL_ADDRESS;
return _onFile(fc, fileNum, recNum, recLen, frame);
}
bool Modbus::readSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn) {
_len = count * 7 + 2;
if (_len > MODBUS_MAX_FRAME) return false;
free(_frame);
_frame = (uint8_t*) malloc(_len);
if (!_frame) return false;
_frame[0] = fn;
_frame[1] = _len - 2;
uint8_t* subReq = _frame + 2;
for (uint8_t i = 0; i < count; i++) {
subReq[0] = 0x06;
subReq[1] = fileNum[i] >> 8;
subReq[2] = fileNum[i] & 0x00FF;
subReq[3] = startRec[i] >> 8;
subReq[4] = startRec[i] & 0x00FF;
subReq[5] = len[i] >> 8;
subReq[6] = len[i] & 0x00FF;
subReq += 7;
}
return true;
}
bool Modbus::writeSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn, uint8_t* data) {
_len = 2;
for (uint8_t i = 0; i < count; i++) {
_len += len[i] * 2 + 7;
}
if (_len > MODBUS_MAX_FRAME) return false;
free(_frame);
_frame = (uint8_t*) malloc(_len);
if (!_frame) return false;
_frame[0] = fn;
_frame[1] = _len - 2;
uint8_t* subReq = _frame + 2;
for (uint8_t i = 0; i < count; i++) {
subReq[0] = 0x06;
subReq[1] = fileNum[i] >> 8;
subReq[2] = fileNum[i] & 0x00FF;
subReq[3] = startRec[i] >> 8;
subReq[4] = startRec[i] & 0x00FF;
subReq[5] = len[i] >> 8;
subReq[6] = len[i] & 0x00FF;
uint8_t clen = len[i] * 2;
memcpy(subReq + 7, data, clen);
subReq += 7 + clen;
data += clen;
}
return true;
}
#endif
bool Modbus::onRaw(cbRaw cb) {
_cbRaw = cb;
return true;
}
Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, const RequestData data) {
return EX_SUCCESS;
}
bool Modbus::onRequest(cbRequest cb) {
_onRequest = cb;
return true;
}
#if defined (MODBUSAPI_OPTIONAL)
bool Modbus::onRequestSuccess(cbRequest cb) {
_onRequestSuccess = cb;
return true;
}
#endif
#if defined(ARDUINO_SAM_DUE_STL)
namespace std {
void __throw_bad_function_call() {
Serial.println(F("STL ERROR - __throw_bad_function_call"));
__builtin_unreachable();
}
}
#endif

363
lib/modbus-esp8266/Modbus.h Normal file
View File

@@ -0,0 +1,363 @@
/*
Modbus Library for Arduino
Core functions
Copyright (C) 2014 Andr<64> Sarmento Barbosa
2017-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#pragma once
#include "ModbusSettings.h"
#include "Arduino.h"
#if defined(MODBUS_USE_STL)
#include <vector>
#include <algorithm>
#include <functional>
#include <memory>
#else
#include "darray.h"
#endif
static inline uint16_t __swap_16(uint16_t num) { return (num >> 8) | (num << 8); }
#define COIL(n) (TAddress){TAddress::COIL, n}
#define ISTS(n) (TAddress){TAddress::ISTS, n}
#define IREG(n) (TAddress){TAddress::IREG, n}
#define HREG(n) (TAddress){TAddress::HREG, n}
#define NULLREG (TAddress){TAddress::NONE, 0xFFFF}
#define BIT_VAL(v) (v?0xFF00:0x0000)
#define BIT_BOOL(v) (v==0xFF00)
#define COIL_VAL(v) (v?0xFF00:0x0000)
#define COIL_BOOL(v) (v==0xFF00)
#define ISTS_VAL(v) (v?0xFF00:0x0000)
#define ISTS_BOOL(v) (v==0xFF00)
// For depricated (v1.xx) onSet/onGet format compatibility
#define cbDefault nullptr
struct TRegister;
#if defined(MODBUS_USE_STL)
typedef std::function<uint16_t(TRegister* reg, uint16_t val)> cbModbus; // Callback function Type
#else
typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type
#endif
struct TAddress {
enum RegType {COIL, ISTS, IREG, HREG, NONE = 0xFF};
RegType type;
uint16_t address;
bool operator==(const TAddress &obj) const { // TAddress == TAddress
return type == obj.type && address == obj.address;
}
bool operator!=(const TAddress &obj) const { // TAddress != TAddress
return type != obj.type || address != obj.address;
}
TAddress& operator++() { // ++TAddress
address++;
return *this;
}
TAddress operator++(int) { // TAddress++
TAddress result(*this);
++(*this);
return result;
}
TAddress& operator+=(const int& inc) { // TAddress += integer
address += inc;
return *this;
}
const TAddress operator+(const int& inc) const { // TAddress + integer
TAddress result(*this);
result.address += inc;
return result;
}
bool isCoil() {
return type == COIL;
}
bool isIsts() {
return type == ISTS;
}
bool isIreg() {
return type == IREG;
}
bool isHreg() {
return type == HREG;
}
};
struct TCallback {
enum CallbackType {ON_SET, ON_GET};
CallbackType type;
TAddress address;
cbModbus cb;
};
struct TRegister {
TAddress address;
uint16_t value;
bool operator ==(const TRegister &obj) const {
return address == obj.address;
}
};
class Modbus {
public:
//Function Codes
enum FunctionCode {
FC_READ_COILS = 0x01, // Read Coils (Output) Status
FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs)
FC_READ_REGS = 0x03, // Read Holding Registers
FC_READ_INPUT_REGS = 0x04, // Read Input Registers
FC_WRITE_COIL = 0x05, // Write Single Coil (Output)
FC_WRITE_REG = 0x06, // Preset Single Register
FC_DIAGNOSTICS = 0x08, // Not implemented. Diagnostics (Serial Line only)
FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs)
FC_WRITE_REGS = 0x10, // Write block of contiguous registers
FC_READ_FILE_REC = 0x14, // Read File Record
FC_WRITE_FILE_REC = 0x15, // Write File Record
FC_MASKWRITE_REG = 0x16, // Mask Write Register
FC_READWRITE_REGS = 0x17 // Read/Write Multiple registers
};
//Exception Codes
//Custom result codes used internally and for callbacks but never used for Modbus responce
enum ResultCode {
EX_SUCCESS = 0x00, // Custom. No error
EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported
EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists
EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range
EX_SLAVE_FAILURE = 0x04, // Slave or Master Device Fails to process request
EX_ACKNOWLEDGE = 0x05, // Not used
EX_SLAVE_DEVICE_BUSY = 0x06, // Not used
EX_MEMORY_PARITY_ERROR = 0x08, // Not used
EX_PATH_UNAVAILABLE = 0x0A, // Not used
EX_DEVICE_FAILED_TO_RESPOND = 0x0B, // Not used
EX_GENERAL_FAILURE = 0xE1, // Custom. Unexpected master error
EX_DATA_MISMACH = 0xE2, // Custom. Inpud data size mismach
EX_UNEXPECTED_RESPONSE = 0xE3, // Custom. Returned result doesn't mach transaction
EX_TIMEOUT = 0xE4, // Custom. Operation not finished within reasonable time
EX_CONNECTION_LOST = 0xE5, // Custom. Connection with device lost
EX_CANCEL = 0xE6, // Custom. Transaction/request canceled
EX_PASSTHROUGH = 0xE7, // Custom. Raw callback. Indicate to normal processing on callback exit
EX_FORCE_PROCESS = 0xE8 // Custom. Raw callback. Indicate to force processing on callback exit
};
union RequestData {
struct {
TAddress reg;
uint16_t regCount;
};
struct {
TAddress regRead;
uint16_t regReadCount;
TAddress regWrite;
uint16_t regWriteCount;
};
struct {
TAddress regMask;
uint16_t andMask;
uint16_t orMask;
};
uint8_t* data;
RequestData(TAddress r1, uint16_t c1) {
reg = r1;
regCount = c1;
};
RequestData(TAddress r1, uint16_t c1, TAddress r2, uint16_t c2) {
regRead = r1;
regReadCount = c1;
regWrite = r2;
regWriteCount = c2;
};
RequestData(TAddress r1, uint16_t m1, uint16_t m2) {
regMask = r1;
andMask = m1;
orMask = m2;
};
RequestData(uint8_t* d) {
data = d;
};
};
struct frame_arg_t {
bool to_server;
union {
uint8_t slaveId;
struct {
uint8_t unitId;
uint32_t ipaddr;
uint16_t transactionId;
};
};
frame_arg_t(uint8_t s, bool m = false) {
slaveId = s;
to_server = m;
};
frame_arg_t(uint8_t u, uint32_t a, uint16_t t, bool m = false) {
unitId = u;
ipaddr = a;
transactionId = t;
to_server = m;
};
};
~Modbus();
bool cbEnable(const bool state = true);
bool cbDisable();
private:
ResultCode readBits(TAddress startreg, uint16_t numregs, FunctionCode fn);
ResultCode readWords(TAddress startreg, uint16_t numregs, FunctionCode fn);
bool setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs);
bool setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs);
void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs);
void getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs);
void bitsToBool(bool* dst, uint8_t* src, uint16_t numregs);
void boolToBits(uint8_t* dst, bool* src, uint16_t numregs);
protected:
//Reply Types
enum ReplyCode {
REPLY_OFF = 0x01,
REPLY_ECHO = 0x02,
REPLY_NORMAL = 0x03,
REPLY_ERROR = 0x04,
REPLY_UNEXPECTED = 0x05
};
#if defined(MODBUS_USE_STL)
#if defined(MODBUS_GLOBAL_REGS)
static std::vector<TRegister> _regs;
static std::vector<TCallback> _callbacks;
#if defined(MODBUS_FILES)
static std::function<ResultCode(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> _onFile;
#endif
#else
std::vector<TRegister> _regs;
std::vector<TCallback> _callbacks;
#if defined(MODBUS_FILES)
std::function<ResultCode(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> _onFile;
#endif
#endif
#else
#if defined(MODBUS_GLOBAL_REGS)
static DArray<TRegister, 1, 1> _regs;
static DArray<TCallback, 1, 1> _callbacks;
#if defined(MODBUS_FILES)
static ResultCode (*_onFile)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*);
#endif
#else
DArray<TRegister, 1, 1> _regs;
DArray<TCallback, 1, 1> _callbacks;
#if defined(MODBUS_FILES)
ResultCode (*_onFile)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)= nullptr;
#endif
#endif
#endif
uint8_t* _frame = nullptr;
uint16_t _len = 0;
uint8_t _reply = 0;
bool cbEnabled = true;
uint16_t callback(TRegister* reg, uint16_t val, TCallback::CallbackType t);
virtual TRegister* searchRegister(TAddress addr);
void exceptionResponse(FunctionCode fn, ResultCode excode); // Fills _frame with response
void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); // Fills frame with response
void slavePDU(uint8_t* frame); //For Slave
void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output = nullptr); //For Master
// frame - data received form slave
// sourceFrame - data have sent fo slave
// startreg - local register to start put data to
// output - if not null put data to the buffer insted local registers. output assumed to by array of uint16_t or boolean
bool readSlave(uint16_t address, uint16_t numregs, FunctionCode fn);
bool writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data = nullptr);
bool writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data = nullptr);
// startreg - local register to get data from
// to - slave register to write data to
// numregs - number of registers
// fn - Modbus function
// data - if null use local registers. Otherwise use data from array to erite to slave
bool removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
public:
bool addReg(TAddress address, uint16_t value = 0, uint16_t numregs = 1);
bool Reg(TAddress address, uint16_t value);
uint16_t Reg(TAddress address);
bool removeReg(TAddress address, uint16_t numregs = 1);
bool addReg(TAddress address, uint16_t* value, uint16_t numregs = 1);
bool Reg(TAddress address, uint16_t* value, uint16_t numregs = 1);
bool onGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
virtual uint32_t eventSource() {return 0;}
#if defined(MODBUS_USE_STL)
typedef std::function<ResultCode(FunctionCode, const RequestData)> cbRequest; // Callback function Type
typedef std::function<ResultCode(uint8_t*, uint8_t, void*)> cbRaw; // Callback function Type
#else
typedef ResultCode (*cbRequest)(FunctionCode fc, const RequestData data); // Callback function Type
typedef ResultCode (*cbRaw)(uint8_t*, uint8_t, void*); // Callback function Type
#endif
protected:
cbRaw _cbRaw = nullptr;
static ResultCode _onRequestDefault(FunctionCode fc, const RequestData data);
cbRequest _onRequest = _onRequestDefault;
public:
bool onRaw(cbRaw cb = nullptr);
bool onRequest(cbRequest cb = _onRequestDefault);
#if defined (MODBUSAPI_OPTIONAL)
protected:
cbRequest _onRequestSuccess = _onRequestDefault;
public:
bool onRequestSuccess(cbRequest cb = _onRequestDefault);
#endif
#if defined(MODBUS_FILES)
public:
#if defined(MODBUS_USE_STL)
bool onFile(std::function<ResultCode(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)>);
#else
bool onFile(ResultCode (*cb)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*));
#endif
private:
ResultCode fileOp(FunctionCode fc, uint16_t fileNum, uint16_t recNum, uint16_t recLen, uint8_t* frame);
protected:
bool readSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn);
// fileNum - sequental array of files numbers to read
// startRec - array of strart records for each file
// len - array of counts of records to read in terms of register size (2 bytes) for each file
// count - count of records to be compose in the single request
// fn - Modbus function. Assumed to be 0x14
bool writeSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn, uint8_t* data);
// fileNum - sequental array of files numbers to read
// startRec - array of strart records for each file
// len - array of counts of records to read in terms of register size (2 bytes) for each file
// count - count of records to be compose in the single request
// fn - Modbus function. Assumed to be 0x15
// data - sequental set of data records
#endif
};
#if defined(MODBUS_USE_STL)
typedef std::function<bool(Modbus::ResultCode, uint16_t, void*)> cbTransaction; // Callback skeleton for requests
#else
typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests
#endif
//typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode func, TRegister* reg, uint16_t regCount); // Callback function Type
#if defined(MODBUS_FILES)
// Callback skeleton for file read/write
#if defined(MODBUS_USE_STL)
typedef std::function<Modbus::ResultCode(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> cbModbusFileOp;
#else
typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame);
#endif
#endif
#if defined(ARDUINO_SAM_DUE_STL)
// Arduino Due STL workaround
namespace std {
void __throw_bad_function_call();
}
#endif

View File

@@ -0,0 +1,507 @@
/*
Modbus Library for Arduino
Modbus public API implementation
Copyright (C) 2014 Andr<64> Sarmento Barbosa
2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#pragma once
#include "Modbus.h"
template <class T>
class ModbusAPI : public T {
public:
// Alternative API
template <typename TYPEID>
uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t write(TYPEID id, TAddress reg, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t write(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
/*
template <typename TYPEID>
uint16_t push(TYPEID id, TAddress to, TAddress from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
*/
// Classic API
bool Hregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(HREG(offset), value);}
bool Coils(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(COIL(offset), value);}
bool Istss(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(ISTS(offset), value);}
bool Iregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(IREG(offset), value);}
//bool addHreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(HREG(offset), value);}
//bool addCoil(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(COIL(offset), value);}
//bool addIsts(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(ISTS(offset), value);}
//bool addIreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(IREG(offset), value);}
bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1);
bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1);
bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1);
bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1);
bool Hreg(uint16_t offset, uint16_t value);
bool Coil(uint16_t offset, bool value);
bool Ists(uint16_t offset, bool value);
bool Ireg(uint16_t offset, uint16_t value);
bool Coil(uint16_t offset);
bool Ists(uint16_t offset);
uint16_t Ireg(uint16_t offset);
uint16_t Hreg(uint16_t offset);
bool removeCoil(uint16_t offset, uint16_t numregs = 1);
bool removeIsts(uint16_t offset, uint16_t numregs = 1);
bool removeIreg(uint16_t offset, uint16_t numregs = 1);
bool removeHreg(uint16_t offset, uint16_t numregs = 1);
bool onGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool onSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
template <typename TYPEID>
uint16_t writeCoil(TYPEID id, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t writeCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t readCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t readIsts(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t readHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t readIreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pushCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pullCoil(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pullIsts(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pushHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pullHreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pullIreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pullHregToIreg(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pullCoilToIsts(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pushIstsToCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t pushIregToHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t readWriteHreg(TYPEID slaveId, uint16_t readOffset, uint16_t* readValue, uint16_t readNumregs, uint16_t writeOffset, uint16_t* writeValue, uint16_t writeNumregs, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t rawRequest(TYPEID ip, const uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t rawResponce(TYPEID ip, const uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT);
template <typename TYPEID>
uint16_t errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT);
};
// FNAME writeCoil, writeIsts, writeHreg, writeIreg
// REG COIL, ISTS, HREG, IREG
// FUNC Modbus function
// MAXNUM Register count limit
// VALTYPE bool, uint16_t
// VALUE
#define IMPLEMENT_WRITEREG(FNAME, REG, FUNC, VALUE, VALTYPE) \
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t offset, VALTYPE value, cbTransaction cb, uint8_t unit) { \
this->readSlave(offset, VALUE(value), Modbus::FUNC); \
return this->send(ip, REG(offset), cb, unit); \
}
IMPLEMENT_WRITEREG(writeCoil, COIL, FC_WRITE_COIL, COIL_VAL, bool)
IMPLEMENT_WRITEREG(writeHreg, HREG, FC_WRITE_REG, , uint16_t)
#define IMPLEMENT_WRITEREGS(FNAME, REG, FUNC, VALUE, MAXNUM, VALTYPE) \
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
this->VALUE(REG(offset), offset, numregs, Modbus::FUNC, value); \
return this->send(ip, REG(offset), cb, unit); \
}
IMPLEMENT_WRITEREGS(writeCoil, COIL, FC_WRITE_COILS, writeSlaveBits, MODBUS_MAX_BITS, bool)
IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, MODBUS_MAX_WORDS, uint16_t)
#define IMPLEMENT_READREGS(FNAME, REG, FUNC, MAXNUM, VALTYPE) \
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
this->readSlave(offset, numregs, Modbus::FUNC); \
return this->send(ip, REG(offset), cb, unit, (uint8_t*)value); \
}
IMPLEMENT_READREGS(readCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS, bool)
IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS, uint16_t)
IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS, bool)
IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS, uint16_t)
#if defined(MODBUS_ADD_REG)
#define ADDREG(R) this->addReg(R(to), (uint16_t)0, numregs);
#else
#define ADDREG(R) ;
#endif
#define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
ADDREG(REG) \
this->readSlave(from, numregs, Modbus::FUNC); \
return this->send(ip, REG(to), cb, unit); \
}
IMPLEMENT_PULL(pullCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS)
IMPLEMENT_PULL(pullIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS)
IMPLEMENT_PULL(pullHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS)
IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS)
IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, MODBUS_MAX_WORDS)
IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, MODBUS_MAX_BITS)
#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM, FINT) \
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
if (!this->searchRegister(REG(from))) return false; \
this->FINT(REG(from), to, numregs, Modbus::FUNC); \
return this->send(ip, REG(from), cb, unit); \
}
IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits)
IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords)
IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords)
IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits)
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
switch (reg.type) {
case TAddress::HREG:
return readHreg(id, reg.address, value, numregs, cb, unit);
case TAddress::IREG:
return readIreg(id, reg.address, value, numregs, cb, unit);
default:
return 0;
}
}
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::read(TYPEID id, TAddress reg, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
switch (reg.type) {
case TAddress::COIL:
return readCoil(id, reg.address, value, numregs, cb, unit);
case TAddress::ISTS:
return readIsts(id, reg.address, value, numregs, cb, unit);
default:
return 0;
}
}
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb, uint8_t unit) {
switch (reg.type) {
case TAddress::COIL:
return writeCoil(id, reg.address, value, cb, unit);
case TAddress::HREG:
return writeHreg(id, reg.address, value, cb, unit);
default:
return 0;
}
}
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, bool value, cbTransaction cb, uint8_t unit) {
switch (reg.type) {
case TAddress::COIL:
return writeCoil(id, reg.address, value, cb, unit);
default:
return 0;
}
}
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
switch (reg.type) {
case TAddress::COIL:
return writeCoil(id, reg.address, value, numregs, cb, unit);
case TAddress::HREG:
return writeHreg(id, reg.address, value, numregs, cb, unit);
default:
return 0;
}
}
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
switch (reg.type) {
case TAddress::COIL:
return writeCoil(id, reg.address, value, cb, numregs, unit);
default:
return 0;
}
}
template <class T> \
bool ModbusAPI<T>::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) {
return this->addReg(HREG(offset), value, numregs);
}
template <class T> \
bool ModbusAPI<T>::Hreg(uint16_t offset, uint16_t value) {
return this->Reg(HREG(offset), value);
}
template <class T> \
uint16_t ModbusAPI<T>::Hreg(uint16_t offset) {
return this->Reg(HREG(offset));
}
template <class T> \
bool ModbusAPI<T>::removeHreg(uint16_t offset, uint16_t numregs) {
return this->removeReg(HREG(offset), numregs);
}
template <class T> \
bool ModbusAPI<T>::addCoil(uint16_t offset, bool value, uint16_t numregs) {
return this->addReg(COIL(offset), COIL_VAL(value), numregs);
}
template <class T> \
bool ModbusAPI<T>::addIsts(uint16_t offset, bool value, uint16_t numregs) {
return this->addReg(ISTS(offset), ISTS_VAL(value), numregs);
}
template <class T> \
bool ModbusAPI<T>::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) {
return this->addReg(IREG(offset), value, numregs);
}
template <class T> \
bool ModbusAPI<T>::Coil(uint16_t offset, bool value) {
return this->Reg(COIL(offset), COIL_VAL(value));
}
template <class T> \
bool ModbusAPI<T>::Ists(uint16_t offset, bool value) {
return this->Reg(ISTS(offset), ISTS_VAL(value));
}
template <class T> \
bool ModbusAPI<T>::Ireg(uint16_t offset, uint16_t value) {
return this->Reg(IREG(offset), value);
}
template <class T> \
bool ModbusAPI<T>::Coil(uint16_t offset) {
return COIL_BOOL(this->Reg(COIL(offset)));
}
template <class T> \
bool ModbusAPI<T>::Ists(uint16_t offset) {
return ISTS_BOOL(this->Reg(ISTS(offset)));
}
template <class T> \
uint16_t ModbusAPI<T>::Ireg(uint16_t offset) {
return this->Reg(IREG(offset));
}
template <class T> \
bool ModbusAPI<T>::removeCoil(uint16_t offset, uint16_t numregs) {
return this->removeReg(COIL(offset), numregs);
}
template <class T> \
bool ModbusAPI<T>::removeIsts(uint16_t offset, uint16_t numregs) {
return this->removeReg(ISTS(offset), numregs);
}
template <class T> \
bool ModbusAPI<T>::removeIreg(uint16_t offset, uint16_t numregs) {
return this->removeReg(IREG(offset), numregs);
}
template <class T> \
bool ModbusAPI<T>::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onGet(COIL(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onSet(COIL(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onGet(HREG(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onSet(HREG(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onGet(ISTS(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onSet(ISTS(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onGet(IREG(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->onSet(IREG(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnGet(COIL(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnSet(COIL(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnGet(HREG(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnSet(HREG(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnGet(ISTS(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnSet(ISTS(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnGet(IREG(offset), cb, numregs);
}
template <class T> \
bool ModbusAPI<T>::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
return this->removeOnSet(IREG(offset), cb, numregs);
}
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) {
if (startRec > MODBUS_MAX_FILES) return 0;
if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0;
return this->send(slaveId, NULLREG, cb, unit, data);
};
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) {
if (startRec > MODBUS_MAX_FILES) return 0;
if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0;
return this->send(slaveId, NULLREG, cb, unit);
};
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb, uint8_t unit) {
free(this->_frame);
this->_len = 7;
this->_frame = (uint8_t*) malloc(this->_len);
this->_frame[0] = Modbus::FC_MASKWRITE_REG;
this->_frame[1] = offset >> 8;
this->_frame[2] = offset & 0x00FF;
this->_frame[3] = andMask >> 8;
this->_frame[4] = andMask & 0x00FF;
this->_frame[5] = orMask >> 8;
this->_frame[6] = orMask & 0x00FF;
return this->send(slaveId, HREG(offset), cb, unit);
};
template <class T> \
template <typename TYPEID> \
uint16_t ModbusAPI<T>::readWriteHreg(TYPEID ip, \
uint16_t readOffset, uint16_t* readValue, uint16_t readNumregs, \
uint16_t writeOffset, uint16_t* writeValue, uint16_t writeNumregs, \
cbTransaction cb, uint8_t unit) {
const uint8_t _header = 10;
if (readNumregs < 0x0001 || readNumregs > MODBUS_MAX_WORDS || writeNumregs < 0x0001 || writeNumregs > 0X0079 || !readValue || !writeValue) return 0;
free(this->_frame);
this->_len = _header + 2 * writeNumregs;
this->_frame = (uint8_t*) malloc(this->_len);
if (!this->_frame) {
this->_reply = Modbus::REPLY_OFF;
return 0;
}
this->_frame[0] = Modbus::FC_READWRITE_REGS;
this->_frame[1] = readOffset >> 8;
this->_frame[2] = readOffset & 0x00FF;
this->_frame[3] = readNumregs >> 8;
this->_frame[4] = readNumregs & 0x00FF;
this->_frame[5] = writeOffset >> 8;
this->_frame[6] = writeOffset & 0x00FF;
this->_frame[7] = writeNumregs >> 8;
this->_frame[8] = writeNumregs & 0x00FF;
this->_frame[9] = this->_len - _header;
uint16_t* frame = (uint16_t*)(this->_frame + _header);
for (uint8_t i = 0; i < writeNumregs; i++) {
frame[i] = __swap_16(writeValue[i]);
}
return this->send(ip, HREG(readOffset), cb, unit, (uint8_t*)readValue);
};
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::rawRequest(TYPEID ip, \
const uint8_t* data, uint16_t len,
cbTransaction cb, uint8_t unit) {
free(this->_frame);
this->_frame = (uint8_t*)malloc(len);
if (!this->_frame)
return 0;
this->_len = len;
memcpy(this->_frame, data, len);
return this->send(ip, NULLREG, cb, unit);
};
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::rawResponce(TYPEID ip, \
const uint8_t* data, uint16_t len, uint8_t unit) {
free(this->_frame);
this->_frame = (uint8_t*)malloc(len);
if (!this->_frame)
return 0;
this->_len = len;
memcpy(this->_frame, data, len);
return this->send(ip, NULLREG, nullptr, unit, nullptr, false);
};
template <class T>
template <typename TYPEID>
uint16_t ModbusAPI<T>::errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit) {
this->exceptionResponse(fn, excode);
return this->send(ip, NULLREG, nullptr, unit, nullptr, false);
}

View File

@@ -0,0 +1,59 @@
/*
Modbus Library for Arduino
ModbusTCP for W5x00 Ethernet
Copyright (C) 2022 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#pragma once
#if defined(MODBUSIP_USE_DNS)
#include <Dns.h>
#endif
#include "ModbusAPI.h"
#include "ModbusTCPTemplate.h"
#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_X8)
#define MODBUS_ETH_WRAP_ACCEPT
#undef MODBUS_ETH_WRAP_BEGIN
#elif defined(ESP32)
#undef MODBUS_ETH_WRAP_ACCEPT
#define MODBUS_ETH_WRAP_BEGIN
#else
#undef MODBUS_ETH_WRAP_ACCEPT
#undef MODBUS_ETH_WRAP_BEGIN
#endif
// Ethernet class wrapper to be able to compile for ESP32
class EthernetServerWrapper : public EthernetServer {
public:
EthernetServerWrapper(uint16_t port) : EthernetServer(port) {
}
#if defined(MODBUS_ETH_WRAP_BEGIN)
void begin(uint16_t port=0) {
EthernetServer::begin();
}
#endif
#if defined(MODBUS_ETH_WRAP_ACCEPT)
inline EthernetClient accept() {
return available();
}
#endif
};
class ModbusEthernet : public ModbusAPI<ModbusTCPTemplate<EthernetServerWrapper, EthernetClient>> {
#if defined(MODBUSIP_USE_DNS)
private:
static IPAddress resolver (const char* host) {
DNSClient dns;
IPAddress ip;
dns.begin(Ethernet.dnsServerIP());
if (dns.getHostByName(host, ip) == 1)
return ip;
else
return IPADDR_NONE;
}
public:
ModbusEthernet() : ModbusAPI() {
resolve = resolver;
}
#endif
};

View File

@@ -0,0 +1,11 @@
/*
Modbus Library for Arduino
ModbusIP class compatibility wrapper
Copyright (C) 2014 Andr<64> Sarmento Barbosa
2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#pragma once
#include "ModbusTCP.h"
class ModbusIP : public ModbusTCP {};

View File

@@ -0,0 +1,329 @@
/*
Modbus Library for Arduino
ModbusRTU implementation
Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
https://github.com/emelianov/modbus-esp8266
This code is licensed under the BSD New License. See LICENSE.txt for more info.
*/
#include "ModbusRTU.h"
// Table of CRC values
static const uint16_t _auchCRC[] PROGMEM = {
0x0000, 0xC1C0, 0x81C1, 0x4001, 0x01C3, 0xC003, 0x8002, 0x41C2, 0x01C6, 0xC006, 0x8007, 0x41C7, 0x0005, 0xC1C5, 0x81C4,
0x4004, 0x01CC, 0xC00C, 0x800D, 0x41CD, 0x000F, 0xC1CF, 0x81CE, 0x400E, 0x000A, 0xC1CA, 0x81CB, 0x400B, 0x01C9, 0xC009,
0x8008, 0x41C8, 0x01D8, 0xC018, 0x8019, 0x41D9, 0x001B, 0xC1DB, 0x81DA, 0x401A, 0x001E, 0xC1DE, 0x81DF, 0x401F, 0x01DD,
0xC01D, 0x801C, 0x41DC, 0x0014, 0xC1D4, 0x81D5, 0x4015, 0x01D7, 0xC017, 0x8016, 0x41D6, 0x01D2, 0xC012, 0x8013, 0x41D3,
0x0011, 0xC1D1, 0x81D0, 0x4010, 0x01F0, 0xC030, 0x8031, 0x41F1, 0x0033, 0xC1F3, 0x81F2, 0x4032, 0x0036, 0xC1F6, 0x81F7,
0x4037, 0x01F5, 0xC035, 0x8034, 0x41F4, 0x003C, 0xC1FC, 0x81FD, 0x403D, 0x01FF, 0xC03F, 0x803E, 0x41FE, 0x01FA, 0xC03A,
0x803B, 0x41FB, 0x0039, 0xC1F9, 0x81F8, 0x4038, 0x0028, 0xC1E8, 0x81E9, 0x4029, 0x01EB, 0xC02B, 0x802A, 0x41EA, 0x01EE,
0xC02E, 0x802F, 0x41EF, 0x002D, 0xC1ED, 0x81EC, 0x402C, 0x01E4, 0xC024, 0x8025, 0x41E5, 0x0027, 0xC1E7, 0x81E6, 0x4026,
0x0022, 0xC1E2, 0x81E3, 0x4023, 0x01E1, 0xC021, 0x8020, 0x41E0, 0x01A0, 0xC060, 0x8061, 0x41A1, 0x0063, 0xC1A3, 0x81A2,
0x4062, 0x0066, 0xC1A6, 0x81A7, 0x4067, 0x01A5, 0xC065, 0x8064, 0x41A4, 0x006C, 0xC1AC, 0x81AD, 0x406D, 0x01AF, 0xC06F,
0x806E, 0x41AE, 0x01AA, 0xC06A, 0x806B, 0x41AB, 0x0069, 0xC1A9, 0x81A8, 0x4068, 0x0078, 0xC1B8, 0x81B9, 0x4079, 0x01BB,
0xC07B, 0x807A, 0x41BA, 0x01BE, 0xC07E, 0x807F, 0x41BF, 0x007D, 0xC1BD, 0x81BC, 0x407C, 0x01B4, 0xC074, 0x8075, 0x41B5,
0x0077, 0xC1B7, 0x81B6, 0x4076, 0x0072, 0xC1B2, 0x81B3, 0x4073, 0x01B1, 0xC071, 0x8070, 0x41B0, 0x0050, 0xC190, 0x8191,
0x4051, 0x0193, 0xC053, 0x8052, 0x4192, 0x0196, 0xC056, 0x8057, 0x4197, 0x0055, 0xC195, 0x8194, 0x4054, 0x019C, 0xC05C,
0x805D, 0x419D, 0x005F, 0xC19F, 0x819E, 0x405E, 0x005A, 0xC19A, 0x819B, 0x405B, 0x0199, 0xC059, 0x8058, 0x4198, 0x0188,
0xC048, 0x8049, 0x4189, 0x004B, 0xC18B, 0x818A, 0x404A, 0x004E, 0xC18E, 0x818F, 0x404F, 0x018D, 0xC04D, 0x804C, 0x418C,
0x0044, 0xC184, 0x8185, 0x4045, 0x0187, 0xC047, 0x8046, 0x4186, 0x0182, 0xC042, 0x8043, 0x4183, 0x0041, 0xC181, 0x8180,
0x4040, 0x0000
};
uint16_t ModbusRTUTemplate::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) {
uint8_t i = 0xFF ^ address;
uint16_t val = pgm_read_word(_auchCRC + i);
uint8_t CRCHi = 0xFF ^ highByte(val); // Hi
uint8_t CRCLo = lowByte(val); //Low
while (pduLen--) {
i = CRCHi ^ *frame++;
val = pgm_read_word(_auchCRC + i);
CRCHi = CRCLo ^ highByte(val); // Hi
CRCLo = lowByte(val); //Low
}
return (CRCHi << 8) | CRCLo;
}
/*
uint16_t ModbusRTUTemplate::crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen) {
uint16_t temp, temp2, flag;
temp = 0xFFFF ^ address;
for (uint8_t i = 0; i < pduLen; i++)
{
temp = temp ^ frame[i];
for (uint8_t j = 1; j <= 8; j++)
{
flag = temp & 0x0001;
temp >>= 1;
if (flag)
temp ^= 0xA001;
}
}
// Reverse byte order.
temp2 = temp >> 8;
temp = (temp << 8) | temp2;
temp &= 0xFFFF;
return temp;
}
*/
uint32_t ModbusRTUTemplate::charSendTime(uint32_t baud, uint8_t char_bits) {
return (uint32_t)char_bits * 1000000UL / baud;
}
uint32_t ModbusRTUTemplate::calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits) {
// baud = baudrate of the serial port
// char_bits = size of 1 modbus character (defined a 11 bits in modbus specificacion)
// Returns: The minimum time between frames (defined as 3.5 characters time in modbus specification)
// According to standard, the Modbus frame is always 11 bits long:
// 1 start + 8 data + 1 parity + 1 stop
// 1 start + 8 data + 2 stops
// And the minimum time between frames is defined as 3.5 characters time in modbus specification.
// This means the time between frames (in microseconds) should be calculated as follows:
// _t = 3.5 x 11 x 1000000 / baudrate = 38500000 / baudrate
// Eg: For 9600 baudrate _t = 38500000 / 9600 = 4010 us
// For baudrates grater than 19200 the _t should be fixed at 1750 us.
// If the used modbus frame length is 10 bits (out of standard - 1 start + 8 data + 1 stop), then
// it can be set using char_bits = 10.
if (baud > 19200) {
return 1750UL;
} else {
return 3.5 * charSendTime(baud, char_bits);
}
}
// Kept for backward compatibility
void ModbusRTUTemplate::setBaudrate(uint32_t baud) {
setInterFrameTime(calculateMinimumInterFrameTime(baud));
}
void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) {
// This function sets the inter frame time. This time is the time that task() waits before considering that the frame being transmitted on the RS485 bus has finished.
// If the interframe calculated by calculateMinimumInterFrameTime() is not enough, you can set the interframe time manually with this function.
// The time must be set in micro seconds.
// This is useful when you are receiving data as a slave and you notice that the slave is dividing a frame in two or more pieces (and obviously the CRC is failing on all pieces).
// This is because it is detecting an interframe time inbetween bytes of the frame and thus it interprets one single frame as two or more frames.
// In that case it is useful to be able to set a more "permissive" interframe time.
_t = t_us;
}
bool ModbusRTUTemplate::begin(Stream* port, int16_t txEnablePin, bool txEnableDirect) {
_port = port;
_t = 1750UL;
#if defined(MODBUSRTU_FLUSH_DELAY)
_t1 = charSendTime(0);
#endif
if (txEnablePin >= 0) {
_txEnablePin = txEnablePin;
_direct = txEnableDirect;
pinMode(_txEnablePin, OUTPUT);
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
}
return true;
}
bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) {
uint16_t newCrc = crc16(slaveId, frame, len);
#if defined(MODBUSRTU_DEBUG)
for (uint8_t i=0 ; i < _len ; i++) {
Serial.print(_frame[i], HEX);
Serial.print(" ");
}
Serial.println();
#endif
#if defined(MODBUSRTU_REDE)
if (_txEnablePin >= 0 || _rxPin >= 0) {
if (_txEnablePin >= 0)
digitalWrite(_txEnablePin, _direct?HIGH:LOW);
if (_rxPin >= 0)
digitalWrite(_rxPin, _direct?HIGH:LOW);
#if !defined(ESP32)
delayMicroseconds(MODBUSRTU_REDE_SWITCH_US);
#endif
}
#else
if (_txEnablePin >= 0) {
digitalWrite(_txEnablePin, _direct?HIGH:LOW);
#if !defined(ESP32)
delayMicroseconds(MODBUSRTU_REDE_SWITCH_US);
#endif
}
#endif
#if defined(ESP32)
vTaskDelay(0);
#endif
_port->write(slaveId); //Send slaveId
_port->write(frame, len); // Send PDU
_port->write(newCrc >> 8); //Send CRC
_port->write(newCrc & 0xFF);//Send CRC
_port->flush();
#if defined(MODBUSRTU_REDE)
if (_txEnablePin >= 0 || _rxPin >= 0) {
#if defined(MODBUSRTU_FLUSH_DELAY)
delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY);
#endif
if (_txEnablePin >= 0)
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
if (_rxPin >= 0)
digitalWrite(_rxPin, _direct?LOW:HIGH);
}
#else
if (_txEnablePin >= 0) {
#if defined(MODBUSRTU_FLUSH_DELAY)
delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY);
#endif
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
}
#endif
return true;
}
uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
bool result = false;
if ((!isMaster || !_slaveId) && _len && _frame) { // Check if waiting for previous request result and _frame filled
//if (_len && _frame) { // Check if waiting for previous request result and _frame filled
rawSend(slaveId, _frame, _len);
if (waitResponse && slaveId) {
_slaveId = slaveId;
_timestamp = micros();
_cb = cb;
_data = data;
_sentFrame = _frame;
_sentReg = startreg;
_frame = nullptr;
}
result = true;
}
free(_frame);
_frame = nullptr;
_len = 0;
return result;
}
void ModbusRTUTemplate::task() {
#if defined(ESP32)
vTaskDelay(0);
#endif
if (_port->available() > _len) {
_len = _port->available();
t = micros();
}
if (_len == 0) {
if (isMaster) cleanup();
return;
}
if (isMaster) {
if (micros() - t < _t) {
return;
}
}
else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached)
uint32_t taskStart = micros();
while (micros() - t < _t) { // Wait data whitespace
if (_port->available() > _len) {
_len = _port->available();
t = micros();
}
if (micros() - taskStart > MODBUSRTU_MAX_READ_US) { // Prevent from task() executed too long
return;
}
}
}
bool valid_frame = true;
address = _port->read(); //first byte of frame = address
_len--; // Decrease by slaveId byte
if (isMaster && _slaveId == 0) { // Check if slaveId is set
valid_frame = false;
}
if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check
valid_frame = false;
}
if (!valid_frame && !_cbRaw) {
for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach
_len = 0;
if (isMaster) cleanup();
return;
}
free(_frame); //Just in case
_frame = (uint8_t*) malloc(_len);
if (!_frame) { // Fail to allocate buffer
for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if can't allocate buffer
_len = 0;
if (isMaster) cleanup();
return;
}
for (uint8_t i=0 ; i < _len ; i++) {
_frame[i] = _port->read(); // read data + crc
#if defined(MODBUSRTU_DEBUG)
Serial.print(_frame[i], HEX);
Serial.print(" ");
#endif
}
#if defined(MODBUSRTU_DEBUG)
Serial.println();
#endif
//_port->readBytes(_frame, _len);
uint16_t frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc
_len = _len - 2; // Decrease by CRC 2 bytes
if (frameCrc != crc16(address, _frame, _len)) { // CRC Check
goto cleanup;
}
_reply = EX_PASSTHROUGH;
if (_cbRaw) {
frame_arg_t header_data = { address, !isMaster };
_reply = _cbRaw(_frame, _len, (void*)&header_data);
}
if (!valid_frame && _reply != EX_FORCE_PROCESS) {
goto cleanup;
}
if (isMaster) {
if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested
// Procass incoming frame as master
if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS)
masterPDU(_frame, _sentFrame, _sentReg, _data);
if (_cb) {
_cb((ResultCode)_reply, 0, nullptr);
_cb = nullptr;
}
free(_sentFrame);
_sentFrame = nullptr;
_data = nullptr;
_slaveId = 0;
}
_reply = Modbus::REPLY_OFF; // No reply if master
} else {
if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) {
slavePDU(_frame);
if (address == MODBUSRTU_BROADCAST)
_reply = Modbus::REPLY_OFF; // No reply for Broadcasts
if (_reply != Modbus::REPLY_OFF)
rawSend(address, _frame, _len);
}
}
// Cleanup
cleanup:
free(_frame);
_frame = nullptr;
_len = 0;
if (isMaster) cleanup();
}
bool ModbusRTUTemplate::cleanup() {
// Remove timeouted request and forced event
if (_slaveId && (micros() - _timestamp > MODBUSRTU_TIMEOUT_US)) {
if (_cb) {
_cb(Modbus::EX_TIMEOUT, 0, nullptr);
_cb = nullptr;
}
free(_sentFrame);
_sentFrame = nullptr;
_data = nullptr;
_slaveId = 0;
return true;
}
return false;
}

View File

@@ -0,0 +1,99 @@
/*
Modbus Library for Arduino
ModbusRTU
Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
https://github.com/emelianov/modbus-esp8266
This code is licensed under the BSD New License. See LICENSE.txt for more info.
*/
#pragma once
#include "ModbusAPI.h"
class ModbusRTUTemplate : public Modbus {
protected:
Stream* _port;
int16_t _txEnablePin = -1;
#if defined(MODBUSRTU_REDE)
int16_t _rxPin = -1;
#endif
bool _direct = true; // Transmit control logic (true=txEnableDirect, false=inverse)
uint32_t _t; // inter-frame delay in uS
#if defined(MODBUSRTU_FLUSH_DELAY)
uint32_t _t1; // char send time
#endif
uint32_t t = 0; // time sience last data byte arrived
bool isMaster = false;
uint8_t _slaveId;
uint32_t _timestamp = 0;
cbTransaction _cb = nullptr;
uint8_t* _data = nullptr;
uint8_t* _sentFrame = nullptr;
TAddress _sentReg = COIL(0);
uint16_t maxRegs = MODBUS_MAX_WORDS;
uint8_t address = 0;
uint16_t send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
// Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data
// slaveId - slave id
// startreg - first local register to save returned data to (miningless for write to slave operations)
// cb - transaction callback function
// data - if not null use buffer to save returned data instead of local registers
bool rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len);
bool cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events
uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen);
uint16_t crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen);
public:
void setBaudrate(uint32_t baud = -1);
uint32_t calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits = 11);
void setInterFrameTime(uint32_t t_us);
uint32_t charSendTime(uint32_t baud, uint8_t char_bits = 11);
template <class T>
bool begin(T* port, int16_t txEnablePin = -1, bool txEnableDirect = true);
#if defined(MODBUSRTU_REDE)
template <class T>
bool begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect);
#endif
bool begin(Stream* port, int16_t txEnablePin = -1, bool txEnableDirect = true);
void task();
void client() { isMaster = true; };
inline void master() {client();}
void server(uint8_t serverId) {_slaveId = serverId;};
inline void slave(uint8_t slaveId) {server(slaveId);}
uint8_t server() { return _slaveId; }
inline uint8_t slave() { return server(); }
uint32_t eventSource() override {return address;}
};
template <class T>
bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, bool txEnableDirect) {
uint32_t baud = 0;
#if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266
baud = port->baudRate();
#else
baud = 9600;
#endif
setInterFrameTime(calculateMinimumInterFrameTime(baud));
#if defined(MODBUSRTU_FLUSH_DELAY)
_t1 = charSendTime(baud);
#endif
_port = port;
if (txEnablePin >= 0) {
_txEnablePin = txEnablePin;
_direct = txEnableDirect;
pinMode(_txEnablePin, OUTPUT);
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
}
return true;
}
#if defined(MODBUSRTU_REDE)
template <class T>
bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect) {
begin(port, txEnablePin, txEnableDirect);
if (rxEnablePin > 0) {
_rxPin = rxEnablePin;
pinMode(_rxPin, OUTPUT);
digitalWrite(_rxPin, _direct?LOW:HIGH);
}
return true;
}
#endif
class ModbusRTU : public ModbusAPI<ModbusRTUTemplate> {};

View File

@@ -0,0 +1,148 @@
/*
Modbus Library for Arduino
Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
https://github.com/emelianov/modbus-esp8266
This code is licensed under the BSD New License. See LICENSE.txt for more info.
Prefixes:
MODBUS_ Global library settings
MODBUSIP_ Settings for TCP and TLS both
MODBUSTCP_ Settings for TCP
MODBUSTLS_ Settings for TLS
MODBUSRTU_ Settings for RTU
MODBUSAPI_ Settings for API
*/
#pragma once
/*
#define MODBUS_GLOBAL_REGS
If defined Modbus registers will be shared across all Modbus* instances.
If not defined each Modbus object will have own registers set.
*/
#define MODBUS_GLOBAL_REGS
//#define MODBUS_FREE_REGS
/*
#define ARDUINO_SAM_DUE_STL
Use STL with Arduino Due. Was able to use with Arduino IDE but not with PlatformIO
Also note STL issue workaround code in Modbus.cpp
*/
#if defined(ARDUINO_SAM_DUE)
//#define ARDUINO_SAM_DUE_STL
#endif
/*
#define MODBUS_USE_STL
If defined C STL will be used.
*/
#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_SAM_DUE_STL)
#define MODBUS_USE_STL
#endif
/*
#define MODBUS_MAX_REGS 32
If defined regisers count will be limited.
*/
// Add limitation for specific STL implementation
#if defined(MODBUS_USE_STL) && (defined(ESP8266) || defined(ESP32))
#undef MODBUS_MAX_REGS
#define MODBUS_MAX_REGS 4000
#endif
#define MODBUS_ADD_REG
//#define MODBUS_STRICT_REG
#define MODBUS_MAX_FRAME 256
//#define MODBUS_STATIC_FRAME
#define MODBUS_MAX_WORDS 0x007D
#define MODBUS_MAX_BITS 0x07D0
#define MODBUS_FILES
#define MODBUS_MAX_FILES 0x270F
#define MODBUSTCP_PORT 502
#define MODBUSTLS_PORT 802
#define MODBUSIP_MINFRAME 2
#define MODBUSIP_MAXFRAME 200
/*
ModbusTCP and ModbusTLS timeouts
#define MODBUSIP_TIMEOUT 1000
Outgoing request timeout
#define MODBUSIP_CONNECT_TIMEOUT 1000
ESP32 only. Outgoing connection attempt timeout
*/
#define MODBUSIP_TIMEOUT 1000
//#define MODBUSIP_CONNECT_TIMEOUT 1000
#define MODBUSIP_UNIT 255
#define MODBUSIP_MAX_TRANSACTIONS 16
#if defined(ESP32)
#define MODBUSIP_MAX_CLIENTS 8
#else
#define MODBUSIP_MAX_CLIENTS 4
#endif
#define MODBUSIP_UNIQUE_CLIENTS
#define MODBUSIP_MAX_READMS 100
/*
Use available() instead of accept() to get TCP client
#define MODBUSIP_USE_AVAILABLE
Used to wrap variation in Ethernet/WiFi client API implementations
*/
//#define MODBUSIP_USE_AVAILABLE
#define MODBUSIP_FULL
//#define MODBUSIP_DEBUG
/*
Allows to use DNS names as target
Otherwise IP addresses only must be used
#define MODBUS_IP_USE_DNS
*/
//#define MODBUS_IP_USE_DNS
//#define MODBUSRTU_DEBUG
#define MODBUSRTU_BROADCAST 0
#define MB_RESERVE 248
#define MB_SERIAL_BUFFER 128
#ifndef MODBUSRTU_TIMEOUT
#define MODBUSRTU_TIMEOUT 1000
#endif
#define MODBUSRTU_MAX_READMS 100
/*
#define MODBUSRTU_REDE
Enable using separate pins for RE DE
*/
//#define MODBUSRTU_REDE
// Define for internal use. Do not change.
#define MODBUSRTU_TIMEOUT_US 1000UL * MODBUSRTU_TIMEOUT
#define MODBUSRTU_MAX_READ_US 1000UL * MODBUSRTU_MAX_READMS
/*
#defone MODBUSRTU_FLUSH_DELAY 1
Set extraa delay after serial buffer flush before changing RE/DE pin state.
Specified in chars. That is 1 is means to add delay enough to send 1 char at current port baudrate
*/
//#define MODBUSRTU_FLUSH_DELAY 1
#define MODBUSRTU_REDE_SWITCH_US 1000
#define MODBUSAPI_LEGACY
#define MODBUSAPI_OPTIONAL
// Workaround for RP2040 flush() bug
#if defined(ARDUINO_ARCH_RP2040)
#define MODBUSRTU_FLUSH_DELAY 1
#endif
// Limit resources usage for entry level boards
#if defined(ARDUINO_UNO) || defined(ARDUINO_LEONARDO)
#undef MODBUS_MAX_REGS
#undef MODBUSIP_MAX_TRANSACTIONS
#undef MODBUS_MAX_WORDS
#undef MODBUS_MAX_BITS
#define MODBUS_MAX_REGS 32
#define MODBUSIP_MAX_TRANSACTIONS 4
#define MODBUS_MAX_WORDS 0x0020
#define MODBUS_MAX_BITS 0x0200
#endif

View File

@@ -0,0 +1,40 @@
/*
Modbus Library for Arduino
ModbusTCP for ESP8266/ESP32
Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#pragma once
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include "ModbusAPI.h"
#include "ModbusTCPTemplate.h"
class WiFiServerESPWrapper : public WiFiServer {
public:
WiFiServerESPWrapper(uint16_t port) : WiFiServer(port) {}
inline WiFiClient accept() {
return available();
}
};
class ModbusTCP : public ModbusAPI<ModbusTCPTemplate<WiFiServerESPWrapper, WiFiClient>> {
#if defined(MODBUSIP_USE_DNS)
private:
static IPAddress resolver(const char *host) {
IPAddress remote_addr;
if (WiFi.hostByName(host, remote_addr))
return remote_addr;
return IPADDR_NONE;
}
public:
ModbusTCP() : ModbusAPI() {
resolve = resolver;
}
#endif
};

View File

@@ -0,0 +1,606 @@
/*
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;
}

View File

@@ -0,0 +1,120 @@
/*
Modbus Library for Arduino
ModbusTLS - ModbusTCP Security for ESP8266
Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com)
*/
#pragma once
#if !defined(ESP8266) && !defined(ESP32)
#error Unsupported architecture
#endif
#include <WiFiClientSecure.h>
#if defined(ESP8266)
#include <WiFiServerSecure.h>
#else
// Just emty stub
class WiFiServerSecure {
public:
WiFiServerSecure(uint16_t){}
WiFiClientSecure available(){}
void begin();
inline WiFiClientSecure accept() {
return available();
}
};
#endif
#include "ModbusTCPTemplate.h"
#include "ModbusAPI.h"
class ModbusTLS : public ModbusAPI<ModbusTCPTemplate<WiFiServerSecure, WiFiClientSecure>> {
private:
int8_t _connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr) {
int8_t p = getFreeClient();
if (p < 0)
return p;
tcpclient[p] = new WiFiClientSecure();
BIT_CLEAR(tcpServerConnection, p);
#if defined(ESP8266)
BearSSL::X509List *clientCertList = new BearSSL::X509List(client_cert);
BearSSL::PrivateKey *clientPrivKey = new BearSSL::PrivateKey(client_private_key);
tcpclient[p]->setClientRSACert(clientCertList, clientPrivKey);
tcpclient[p]->setBufferSizes(512, 512);
#else
tcpclient[p]->setCertificate(client_cert);
tcpclient[p]->setPrivateKey(client_private_key);
#endif
return p;
}
#if defined(MODBUSIP_USE_DNS)
static IPAddress resolver (const char* host) {
IPAddress remote_addr;
if (WiFi.hostByName(host, remote_addr))
return remote_addr;
return IPADDR_NONE;
}
#endif
public:
ModbusTLS() : ModbusAPI() {
defaultPort = MODBUSTLS_PORT;
#if defined(MODBUSIP_USE_DNS)
resolve = resolver;
#endif
}
#if defined(ESP8266)
void server(uint16_t port, const char* server_cert = nullptr, const char* server_private_key = nullptr, const char* ca_cert = nullptr) {
serverPort = port;
tcpserver = new WiFiServerSecure(serverPort);
BearSSL::X509List *serverCertList = new BearSSL::X509List(server_cert);
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(server_private_key);
tcpserver->setRSACert(serverCertList, serverPrivKey);
if (ca_cert) {
BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert);
tcpserver->setClientTrustAnchor(trustedCA);
}
//tcpserver->setBufferSizes(512, 512);
tcpserver->begin();
}
bool connectWithKnownKey(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* key = nullptr) {
if(getSlave(ip) >= 0)
return true;
int8_t p = _connect(ip, port, client_cert, client_private_key);
BearSSL::PublicKey *clientPublicKey = new BearSSL::PublicKey(key);
tcpclient[p]->setKnownKey(clientPublicKey);
return tcpclient[p]->connect(ip, port);
}
#endif
#if defined(MODBUSIP_USE_DNS)
bool connect(String host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) {
return connect(resolver(host.c_str()), port, client_cert, client_private_key, ca_cert);
}
bool connect(const char* host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) {
return connect(resolver(host), port, client_cert, client_private_key, ca_cert);
}
#endif
bool connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) {
if (!ip)
return false;
if(getSlave(ip) >= 0)
return false;
int8_t p = _connect(ip, port, client_cert, client_private_key);
if (p < 0)
return false;
#if defined(ESP8266)
if (ca_cert) {
BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert);
tcpclient[p]->setTrustAnchors(trustedCA);
} else {
tcpclient[p]->setInsecure();
}
#else
if (ca_cert) {
tcpclient[p]->setCACert(ca_cert);
}
#endif
//return tcpclient[p]->connect(ip, port);
if (!tcpclient[p]->connect(ip, port))
return false;
return true;
}
};

View File

@@ -0,0 +1,70 @@
/*
Very Basic Dynamic Array
Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com)
https://github.com/emelianov/modbus-esp8266
This code is licensed under the BSD New License. See LICENSE.txt for more info.
*/
template <typename T, int SIZE, int INCREMENT>
class DArray {
public:
typedef bool (*Compare)(T);
T* data = nullptr;
size_t resSize = 0;
size_t last = 0;
bool isEmpty = true;
DArray(size_t i = SIZE) {
data = (T*)malloc(i * sizeof(T));
if (data) resSize = i;
}
size_t push_back(const T& v) {
if (!data) {
data = (T*)malloc(resSize * sizeof(T));
if (!data) return 1;
}
if (last >= resSize - 1) {
if (INCREMENT == 0) return last + 1;
void* tmp = realloc(data, (resSize + INCREMENT) * sizeof(T));
if (!tmp) return last + 1;
resSize += INCREMENT;
data = (T*)tmp;
}
if (!isEmpty)
last++;
else
isEmpty = false;
data[last] = v;
return last;
}
size_t size() {
if (isEmpty) return 0;
return last + 1;
}
template <class UnaryPredicate>
size_t find(UnaryPredicate func, size_t i = 0) {
if (isEmpty) return 1;
for (; i <= last; i++)
if (func(data[i])) break;
return i;
}
void remove(size_t i) {
if (isEmpty) return;
if (i > last) return;
if (last == 0) {
isEmpty = true;
return;
}
if (i < last)
memcpy(&data[i], &data[i + 1], (last - i) * sizeof(T));
last --;
}
T operator[](int i) {
return data[i];
}
T* entry(size_t i) {
if (i > last) return nullptr;
return &data[i];
}
};