First stable compilation with ModbusRTU and ModbusTCP
This commit is contained in:
931
lib/modbus-esp8266/Modbus.cpp
Normal file
931
lib/modbus-esp8266/Modbus.cpp
Normal 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
363
lib/modbus-esp8266/Modbus.h
Normal 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
|
||||
507
lib/modbus-esp8266/ModbusAPI.h
Normal file
507
lib/modbus-esp8266/ModbusAPI.h
Normal 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);
|
||||
}
|
||||
59
lib/modbus-esp8266/ModbusEthernet.h
Normal file
59
lib/modbus-esp8266/ModbusEthernet.h
Normal 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
|
||||
};
|
||||
11
lib/modbus-esp8266/ModbusIP_ESP8266.h
Normal file
11
lib/modbus-esp8266/ModbusIP_ESP8266.h
Normal 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 {};
|
||||
329
lib/modbus-esp8266/ModbusRTU.cpp
Normal file
329
lib/modbus-esp8266/ModbusRTU.cpp
Normal 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;
|
||||
}
|
||||
99
lib/modbus-esp8266/ModbusRTU.h
Normal file
99
lib/modbus-esp8266/ModbusRTU.h
Normal 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> {};
|
||||
148
lib/modbus-esp8266/ModbusSettings.h
Normal file
148
lib/modbus-esp8266/ModbusSettings.h
Normal 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
|
||||
40
lib/modbus-esp8266/ModbusTCP.h
Normal file
40
lib/modbus-esp8266/ModbusTCP.h
Normal 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
|
||||
};
|
||||
606
lib/modbus-esp8266/ModbusTCPTemplate.h
Normal file
606
lib/modbus-esp8266/ModbusTCPTemplate.h
Normal 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;
|
||||
}
|
||||
|
||||
120
lib/modbus-esp8266/ModbusTLS.h
Normal file
120
lib/modbus-esp8266/ModbusTLS.h
Normal 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;
|
||||
}
|
||||
};
|
||||
70
lib/modbus-esp8266/darray.h
Normal file
70
lib/modbus-esp8266/darray.h
Normal 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];
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user