From 55590c5993a4708c0fa37c5fe76bce3554bd2a52 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 8 Aug 2017 17:24:55 +0500 Subject: [PATCH 001/288] Change directory structure --- arduinoIDE/ModbusIP_ESP8266/keywords.txt | 11 ----------- .../TestAnalogInput/TestAnalogInput.ino | 0 .../TestHoldingReg/TestHoldingReg.ino | 0 .../examples => examples}/TestLed/TestLed.ino | 0 .../TestSwitchStatus/TestSwitchStatus.ino | 0 expressifSDK/In development.txt | 0 arduinoIDE/Modbus/keywords.txt => keywords.txt | 12 ++++++++++++ library.properties | 9 +++++++++ {arduinoIDE/Modbus => src}/Modbus.cpp | 0 {arduinoIDE/Modbus => src}/Modbus.h | 0 .../ModbusIP_ESP8266 => src}/ModbusIP_ESP8266.cpp | 10 +++++++--- .../ModbusIP_ESP8266 => src}/ModbusIP_ESP8266.h | 3 ++- 12 files changed, 30 insertions(+), 15 deletions(-) delete mode 100644 arduinoIDE/ModbusIP_ESP8266/keywords.txt rename {arduinoIDE/ModbusIP_ESP8266/examples => examples}/TestAnalogInput/TestAnalogInput.ino (100%) rename {arduinoIDE/ModbusIP_ESP8266/examples => examples}/TestHoldingReg/TestHoldingReg.ino (100%) rename {arduinoIDE/ModbusIP_ESP8266/examples => examples}/TestLed/TestLed.ino (100%) rename {arduinoIDE/ModbusIP_ESP8266/examples => examples}/TestSwitchStatus/TestSwitchStatus.ino (100%) delete mode 100644 expressifSDK/In development.txt rename arduinoIDE/Modbus/keywords.txt => keywords.txt (85%) create mode 100644 library.properties rename {arduinoIDE/Modbus => src}/Modbus.cpp (100%) rename {arduinoIDE/Modbus => src}/Modbus.h (100%) rename {arduinoIDE/ModbusIP_ESP8266 => src}/ModbusIP_ESP8266.cpp (97%) rename {arduinoIDE/ModbusIP_ESP8266 => src}/ModbusIP_ESP8266.h (91%) diff --git a/arduinoIDE/ModbusIP_ESP8266/keywords.txt b/arduinoIDE/ModbusIP_ESP8266/keywords.txt deleted file mode 100644 index f8960e6..0000000 --- a/arduinoIDE/ModbusIP_ESP8266/keywords.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Syntax Coloring Map For ModbusIP - -# Datatypes (KEYWORD1) -ModbusIP KEYWORD1 -ModbusIP_ESP8266 KEYWORD1 - -# Methods and Functions (KEYWORD2) -config KEYWORD2 -task KEYWORD2 - -# Constants (LITERAL1) diff --git a/arduinoIDE/ModbusIP_ESP8266/examples/TestAnalogInput/TestAnalogInput.ino b/examples/TestAnalogInput/TestAnalogInput.ino similarity index 100% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestAnalogInput/TestAnalogInput.ino rename to examples/TestAnalogInput/TestAnalogInput.ino diff --git a/arduinoIDE/ModbusIP_ESP8266/examples/TestHoldingReg/TestHoldingReg.ino b/examples/TestHoldingReg/TestHoldingReg.ino similarity index 100% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestHoldingReg/TestHoldingReg.ino rename to examples/TestHoldingReg/TestHoldingReg.ino diff --git a/arduinoIDE/ModbusIP_ESP8266/examples/TestLed/TestLed.ino b/examples/TestLed/TestLed.ino similarity index 100% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestLed/TestLed.ino rename to examples/TestLed/TestLed.ino diff --git a/arduinoIDE/ModbusIP_ESP8266/examples/TestSwitchStatus/TestSwitchStatus.ino b/examples/TestSwitchStatus/TestSwitchStatus.ino similarity index 100% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestSwitchStatus/TestSwitchStatus.ino rename to examples/TestSwitchStatus/TestSwitchStatus.ino diff --git a/expressifSDK/In development.txt b/expressifSDK/In development.txt deleted file mode 100644 index e69de29..0000000 diff --git a/arduinoIDE/Modbus/keywords.txt b/keywords.txt similarity index 85% rename from arduinoIDE/Modbus/keywords.txt rename to keywords.txt index 4c183a2..6e1cb2a 100644 --- a/arduinoIDE/Modbus/keywords.txt +++ b/keywords.txt @@ -1,3 +1,15 @@ +# Syntax Coloring Map For ModbusIP + +# Datatypes (KEYWORD1) +ModbusIP KEYWORD1 +ModbusIP_ESP8266 KEYWORD1 + +# Methods and Functions (KEYWORD2) +config KEYWORD2 +task KEYWORD2 + +# Constants (LITERAL1) + # Syntax Coloring Map For Modbus # Datatypes (KEYWORD1) diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..0b0bd83 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=modbus-esp8266 +version=0.1 +author=Andre Sarmento Barbosa +maintainer=Alexander Emelianov +sentence=Modbus Library for ESP8266 +paragraph=This library allows your ESP8266 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. +category=Communication +url=https://github.com/emelianov/modbus-esp8266 +architectures=esp8266 diff --git a/arduinoIDE/Modbus/Modbus.cpp b/src/Modbus.cpp similarity index 100% rename from arduinoIDE/Modbus/Modbus.cpp rename to src/Modbus.cpp diff --git a/arduinoIDE/Modbus/Modbus.h b/src/Modbus.h similarity index 100% rename from arduinoIDE/Modbus/Modbus.h rename to src/Modbus.h diff --git a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp similarity index 97% rename from arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.cpp rename to src/ModbusIP_ESP8266.cpp index 70c103f..9c36890 100644 --- a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -6,15 +6,19 @@ WiFiServer server(MODBUSIP_PORT); -ModbusIP::ModbusIP() { - -} +//ModbusIP::ModbusIP() { +// +//} void ModbusIP::config(const char* ssid, const char* password) { WiFi.begin(ssid, password); server.begin(); } +void ModbusIP::config() { + server.begin(); +} + void ModbusIP::task() { WiFiClient client = server.available(); diff --git a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h similarity index 91% rename from arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.h rename to src/ModbusIP_ESP8266.h index 907b0f2..a8ae91a 100644 --- a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -16,8 +16,9 @@ class ModbusIP : public Modbus { private: byte _MBAP[7]; public: - ModbusIP(); +// ModbusIP(); void config(const char* ssid, const char* password); + void config(); void task(); }; From 3c72eb0ff2a22a928eba9d4ba14303a85058d691 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Aug 2017 00:38:36 +0500 Subject: [PATCH 002/288] Keep alive connection --- src/ModbusIP_ESP8266.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 9c36890..a2e0e98 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -18,9 +18,10 @@ void ModbusIP::config(const char* ssid, const char* password) { void ModbusIP::config() { server.begin(); } - + WiFiClient client; void ModbusIP::task() { - WiFiClient client = server.available(); +if (client == NULL || !client.connected()) + client = server.available(); int raw_len = 0; @@ -67,7 +68,7 @@ void ModbusIP::task() { client.write(sbuf, send_len); } - client.stop(); + //client.stop(); free(_frame); _len = 0; } From e4bc600253570fa1ebb211d16ba495e7e6b90dfd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Aug 2017 08:18:52 +0500 Subject: [PATCH 003/288] Keep alive control #def --- src/ModbusIP_ESP8266.cpp | 20 +++++++++++++++----- src/ModbusIP_ESP8266.h | 2 ++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index a2e0e98..52310c1 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -18,10 +18,19 @@ void ModbusIP::config(const char* ssid, const char* password) { void ModbusIP::config() { server.begin(); } - WiFiClient client; +#ifdef TCP_KEEP_ALIVE +WiFiClient client; +#endif void ModbusIP::task() { -if (client == NULL || !client.connected()) - client = server.available(); +#ifdef TCP_KEEP_ALIVE + if (!client || !client.connected()) { + // if (client) + // delete client; + client = server.available(); + } +#else + WiFiClient client; +#endif int raw_len = 0; @@ -67,8 +76,9 @@ if (client == NULL || !client.connected()) client.write(sbuf, send_len); } - - //client.stop(); + #ifndef TCP_KEEPALIVE + client.stop(); + #endif free(_frame); _len = 0; } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index a8ae91a..4eadaba 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -12,6 +12,8 @@ #define MODBUSIP_MAXFRAME 200 #define MODBUSIP_TIMEOUT 10 +#define TCP_KEEP_ALIVE + class ModbusIP : public Modbus { private: byte _MBAP[7]; From c23e6a40b978222ea900d9a2e6da3daf02db2e35 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Aug 2017 16:14:31 +0500 Subject: [PATCH 004/288] Callback added --- src/Modbus.cpp | 34 ++++++++++++++++++++-------------- src/Modbus.h | 14 +++++++++++++- src/ModbusIP_ESP8266.cpp | 28 ++++++++++++++++++---------- src/ModbusIP_ESP8266.h | 10 ++++++++-- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 6e9c7da..69ee849 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -4,6 +4,10 @@ */ #include "Modbus.h" +word cbDefault(TRegister* reg, word val) { + return val; +} + Modbus::Modbus() { _regs_head = 0; _regs_last = 0; @@ -28,6 +32,8 @@ void Modbus::addReg(word address, word value) { newreg = (TRegister *) malloc(sizeof(TRegister)); newreg->address = address; newreg->value = value; + newreg->get = cbDefault; + newreg->set = cbDefault; newreg->next = 0; if(_regs_head == 0) { @@ -47,7 +53,7 @@ bool Modbus::Reg(word address, word value) { reg = this->searchRegister(address); //if found then assign the register value to the new value. if (reg) { - reg->value = value; + reg->value = reg->set(reg, value); return true; } else return false; @@ -57,62 +63,62 @@ word Modbus::Reg(word address) { TRegister *reg; reg = this->searchRegister(address); if(reg) - return(reg->value); + return(reg->get(reg, reg->value)); else return(0); } void Modbus::addHreg(word offset, word value) { - this->addReg(offset + 40001, value); + this->addReg(offset + HREG_BASE, value); } bool Modbus::Hreg(word offset, word value) { - return Reg(offset + 40001, value); + return Reg(offset + HREG_BASE, value); } word Modbus::Hreg(word offset) { - return Reg(offset + 40001); + return Reg(offset + HREG_BASE); } #ifndef USE_HOLDING_REGISTERS_ONLY void Modbus::addCoil(word offset, bool value) { - this->addReg(offset + 1, value?0xFF00:0x0000); + this->addReg(offset + COIL_BASE, value?0xFF00:0x0000); } void Modbus::addIsts(word offset, bool value) { - this->addReg(offset + 10001, value?0xFF00:0x0000); + this->addReg(offset + ISTS_BASE, value?0xFF00:0x0000); } void Modbus::addIreg(word offset, word value) { - this->addReg(offset + 30001, value); + this->addReg(offset + IREG_BASE, value); } bool Modbus::Coil(word offset, bool value) { - return Reg(offset + 1, value?0xFF00:0x0000); + return Reg(offset + COIL_BASE, value?0xFF00:0x0000); } bool Modbus::Ists(word offset, bool value) { - return Reg(offset + 10001, value?0xFF00:0x0000); + return Reg(offset + ISTS_BASE, value?0xFF00:0x0000); } bool Modbus::Ireg(word offset, word value) { - return Reg(offset + 30001, value); + return Reg(offset + IREG_BASE, value); } bool Modbus::Coil(word offset) { - if (Reg(offset + 1) == 0xFF00) { + if (Reg(offset + COIL_BASE) == 0xFF00) { return true; } else return false; } bool Modbus::Ists(word offset) { - if (Reg(offset + 10001) == 0xFF00) { + if (Reg(offset + ISTS_BASE) == 0xFF00) { return true; } else return false; } word Modbus::Ireg(word offset) { - return Reg(offset + 30001); + return Reg(offset + IREG_BASE); } #endif diff --git a/src/Modbus.h b/src/Modbus.h index a52c875..e4bb307 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -7,8 +7,13 @@ #ifndef MODBUS_H #define MODBUS_H -#define MAX_REGS 32 +#define MAX_REGS 32 #define MAX_FRAME 128 +#define COIL_BASE 1 +#define ISTS_BASE 10001 +#define IREG_BASE 30001 +#define HREG_BASE 40001 + //#define USE_HOLDING_REGISTERS_ONLY typedef unsigned int u_int; @@ -40,10 +45,17 @@ enum { MB_REPLY_NORMAL = 0x03, }; +typedef struct TRegister; + +typedef uint32_t (*cbModbus)(TRegister* reg, word val); + typedef struct TRegister { word address; word value; + word index; struct TRegister* next; + cbModbus get; + cbModbus set; } TRegister; class Modbus { diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 52310c1..7af0786 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -4,29 +4,37 @@ */ #include "ModbusIP_ESP8266.h" -WiFiServer server(MODBUSIP_PORT); +//WiFiServer server(MODBUSIP_PORT); //ModbusIP::ModbusIP() { // //} -void ModbusIP::config(const char* ssid, const char* password) { - WiFi.begin(ssid, password); - server.begin(); -} +//void ModbusIP::config(const char* ssid, const char* password) { +// WiFi.begin(ssid, password); +// server.begin(); +//} void ModbusIP::config() { - server.begin(); + //server.begin(); + begin(); } -#ifdef TCP_KEEP_ALIVE -WiFiClient client; -#endif + +void ModbusIP::begin() { + //server.begin(); + WiFiServer::begin(); +} + +//#ifdef TCP_KEEP_ALIVE +//WiFiClient client; +//#endif void ModbusIP::task() { #ifdef TCP_KEEP_ALIVE if (!client || !client.connected()) { // if (client) // delete client; - client = server.available(); + // + client = available(); } #else WiFiClient client; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 4eadaba..895e760 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -14,13 +14,19 @@ #define TCP_KEEP_ALIVE -class ModbusIP : public Modbus { +class ModbusIP : public Modbus, public WiFiServer { private: byte _MBAP[7]; + #ifdef TCP_KEEP_ALIVE + WiFiClient client; + #endif public: // ModbusIP(); - void config(const char* ssid, const char* password); +// void config(const char* ssid, const char* password); + ModbusIP() : WiFiServer(MODBUSIP_PORT) { + } void config(); + void begin(); void task(); }; From 9f083b9631491a7f678940a3d79f4c46095ed898 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 14 Aug 2017 12:07:08 +0500 Subject: [PATCH 005/288] Code cleanup, change word=>uint16_t --- .gitmodules | 0 src/Modbus.cpp | 82 ++++++++++++++++++++-------------------- src/Modbus.h | 59 ++++++++++++++--------------- src/ModbusIP_ESP8266.cpp | 18 +-------- src/ModbusIP_ESP8266.h | 3 -- 5 files changed, 71 insertions(+), 91 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 69ee849..b513faa 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -4,7 +4,7 @@ */ #include "Modbus.h" -word cbDefault(TRegister* reg, word val) { +uint16_t cbDefault(TRegister* reg, uint16_t val) { return val; } @@ -13,7 +13,7 @@ Modbus::Modbus() { _regs_last = 0; } -TRegister* Modbus::searchRegister(word address) { +TRegister* Modbus::searchRegister(uint16_t address) { TRegister *reg = _regs_head; //if there is no register configured, bail if(reg == 0) return(0); @@ -26,7 +26,7 @@ TRegister* Modbus::searchRegister(word address) { return(0); } -void Modbus::addReg(word address, word value) { +void Modbus::addReg(uint16_t address, uint16_t value) { TRegister *newreg; newreg = (TRegister *) malloc(sizeof(TRegister)); @@ -47,7 +47,7 @@ void Modbus::addReg(word address, word value) { } } -bool Modbus::Reg(word address, word value) { +bool Modbus::Reg(uint16_t address, uint16_t value) { TRegister *reg; //search for the register address reg = this->searchRegister(address); @@ -59,7 +59,7 @@ bool Modbus::Reg(word address, word value) { return false; } -word Modbus::Reg(word address) { +uint16_t Modbus::Reg(uint16_t address) { TRegister *reg; reg = this->searchRegister(address); if(reg) @@ -68,56 +68,56 @@ word Modbus::Reg(word address) { return(0); } -void Modbus::addHreg(word offset, word value) { +void Modbus::addHreg(uint16_t offset, uint16_t value) { this->addReg(offset + HREG_BASE, value); } -bool Modbus::Hreg(word offset, word value) { +bool Modbus::Hreg(uint16_t offset, uint16_t value) { return Reg(offset + HREG_BASE, value); } -word Modbus::Hreg(word offset) { +uint16_t Modbus::Hreg(uint16_t offset) { return Reg(offset + HREG_BASE); } #ifndef USE_HOLDING_REGISTERS_ONLY - void Modbus::addCoil(word offset, bool value) { + void Modbus::addCoil(uint16_t offset, bool value) { this->addReg(offset + COIL_BASE, value?0xFF00:0x0000); } - void Modbus::addIsts(word offset, bool value) { + void Modbus::addIsts(uint16_t offset, bool value) { this->addReg(offset + ISTS_BASE, value?0xFF00:0x0000); } - void Modbus::addIreg(word offset, word value) { + void Modbus::addIreg(uint16_t offset, uint16_t value) { this->addReg(offset + IREG_BASE, value); } - bool Modbus::Coil(word offset, bool value) { + bool Modbus::Coil(uint16_t offset, bool value) { return Reg(offset + COIL_BASE, value?0xFF00:0x0000); } - bool Modbus::Ists(word offset, bool value) { + bool Modbus::Ists(uint16_t offset, bool value) { return Reg(offset + ISTS_BASE, value?0xFF00:0x0000); } - bool Modbus::Ireg(word offset, word value) { + bool Modbus::Ireg(uint16_t offset, uint16_t value) { return Reg(offset + IREG_BASE, value); } - bool Modbus::Coil(word offset) { + bool Modbus::Coil(uint16_t offset) { if (Reg(offset + COIL_BASE) == 0xFF00) { return true; } else return false; } - bool Modbus::Ists(word offset) { + bool Modbus::Ists(uint16_t offset) { if (Reg(offset + ISTS_BASE) == 0xFF00) { return true; } else return false; } - word Modbus::Ireg(word offset) { + uint16_t Modbus::Ireg(uint16_t offset) { return Reg(offset + IREG_BASE); } #endif @@ -125,8 +125,8 @@ word Modbus::Hreg(word offset) { void Modbus::receivePDU(byte* frame) { byte fcode = frame[0]; - word field1 = (word)frame[1] << 8 | (word)frame[2]; - word field2 = (word)frame[3] << 8 | (word)frame[4]; + uint16_t field1 = (word)frame[1] << 8 | (word)frame[2]; + uint16_t field2 = (word)frame[3] << 8 | (word)frame[4]; switch (fcode) { @@ -188,7 +188,7 @@ void Modbus::exceptionResponse(byte fcode, byte excode) { _reply = MB_REPLY_NORMAL; } -void Modbus::readRegisters(word startreg, word numregs) { +void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { this->exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_VALUE); @@ -220,8 +220,8 @@ void Modbus::readRegisters(word startreg, word numregs) { _frame[0] = MB_FC_READ_REGS; _frame[1] = _len - 2; //byte count - word val; - word i = 0; + uint16_t val; + uint16_t i = 0; while(numregs--) { //retrieve the value from the register bank for the current register val = this->Hreg(startreg + i); @@ -235,8 +235,8 @@ void Modbus::readRegisters(word startreg, word numregs) { _reply = MB_REPLY_NORMAL; } -void Modbus::writeSingleRegister(word reg, word value) { - //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using word (0x0000 - 0x0FFFF) +void Modbus::writeSingleRegister(uint16_t reg, uint16_t value) { + //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using uint16_t (0x0000 - 0x0FFFF) //Check Address and execute (reg exists?) if (!this->Hreg(reg, value)) { this->exceptionResponse(MB_FC_WRITE_REG, MB_EX_ILLEGAL_ADDRESS); @@ -252,7 +252,7 @@ void Modbus::writeSingleRegister(word reg, word value) { _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleRegisters(byte* frame,word startreg, word numoutputs, byte bytecount) { +void Modbus::writeMultipleRegisters(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount) { //Check value if (numoutputs < 0x0001 || numoutputs > 0x007B || bytecount != 2 * numoutputs) { this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_VALUE); @@ -282,8 +282,8 @@ void Modbus::writeMultipleRegisters(byte* frame,word startreg, word numoutputs, _frame[3] = numoutputs >> 8; _frame[4] = numoutputs & 0x00FF; - word val; - word i = 0; + uint16_t val; + uint16_t i = 0; while(numoutputs--) { val = (word)frame[6+i*2] << 8 | (word)frame[7+i*2]; this->Hreg(startreg + i, val); @@ -294,7 +294,7 @@ void Modbus::writeMultipleRegisters(byte* frame,word startreg, word numoutputs, } #ifndef USE_HOLDING_REGISTERS_ONLY -void Modbus::readCoils(word startreg, word numregs) { +void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x07D0) { this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_VALUE); @@ -330,8 +330,8 @@ void Modbus::readCoils(word startreg, word numregs) { _frame[1] = _len - 2; //byte count (_len - function code and byte count) byte bitn = 0; - word totregs = numregs; - word i; + uint16_t totregs = numregs; + uint16_t i; while (numregs--) { i = (totregs - numregs) / 8; if (this->Coil(startreg)) @@ -348,7 +348,7 @@ void Modbus::readCoils(word startreg, word numregs) { _reply = MB_REPLY_NORMAL; } -void Modbus::readInputStatus(word startreg, word numregs) { +void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x07D0) { this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_VALUE); @@ -381,8 +381,8 @@ void Modbus::readInputStatus(word startreg, word numregs) { _frame[1] = _len - 2; byte bitn = 0; - word totregs = numregs; - word i; + uint16_t totregs = numregs; + uint16_t i; while (numregs--) { i = (totregs - numregs) / 8; if (this->Ists(startreg)) @@ -399,7 +399,7 @@ void Modbus::readInputStatus(word startreg, word numregs) { _reply = MB_REPLY_NORMAL; } -void Modbus::readInputRegisters(word startreg, word numregs) { +void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_VALUE); @@ -430,8 +430,8 @@ void Modbus::readInputRegisters(word startreg, word numregs) { _frame[0] = MB_FC_READ_INPUT_REGS; _frame[1] = _len - 2; - word val; - word i = 0; + uint16_t val; + uint16_t i = 0; while(numregs--) { //retrieve the value from the register bank for the current register val = this->Ireg(startreg + i); @@ -445,7 +445,7 @@ void Modbus::readInputRegisters(word startreg, word numregs) { _reply = MB_REPLY_NORMAL; } -void Modbus::writeSingleCoil(word reg, word status) { +void Modbus::writeSingleCoil(uint16_t reg, uint16_t status) { //Check value (status) if (status != 0xFF00 && status != 0x0000) { this->exceptionResponse(MB_FC_WRITE_COIL, MB_EX_ILLEGAL_VALUE); @@ -467,9 +467,9 @@ void Modbus::writeSingleCoil(word reg, word status) { _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleCoils(byte* frame,word startreg, word numoutputs, byte bytecount) { +void Modbus::writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount) { //Check value - word bytecount_calc = numoutputs / 8; + uint16_t bytecount_calc = numoutputs / 8; if (numoutputs%8) bytecount_calc++; if (numoutputs < 0x0001 || numoutputs > 0x07B0 || bytecount != bytecount_calc) { this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_VALUE); @@ -500,8 +500,8 @@ void Modbus::writeMultipleCoils(byte* frame,word startreg, word numoutputs, byte _frame[4] = numoutputs & 0x00FF; byte bitn = 0; - word totoutputs = numoutputs; - word i; + uint16_t totoutputs = numoutputs; + uint16_t i; while (numoutputs--) { i = (totoutputs - numoutputs) / 8; this->Coil(startreg, bitRead(frame[6+i], bitn)); diff --git a/src/Modbus.h b/src/Modbus.h index e4bb307..f0e8678 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -16,8 +16,6 @@ //#define USE_HOLDING_REGISTERS_ONLY -typedef unsigned int u_int; - //Function Codes enum { MB_FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx @@ -47,12 +45,13 @@ enum { typedef struct TRegister; -typedef uint32_t (*cbModbus)(TRegister* reg, word val); +// Callback function Type +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); typedef struct TRegister { - word address; - word value; - word index; + uint16_t address; + uint16_t value; + uint16_t index; struct TRegister* next; cbModbus get; cbModbus set; @@ -63,23 +62,23 @@ class Modbus { TRegister *_regs_head; TRegister *_regs_last; - void readRegisters(word startreg, word numregs); - void writeSingleRegister(word reg, word value); - void writeMultipleRegisters(byte* frame,word startreg, word numoutputs, byte bytecount); + void readRegisters(uint16_t startreg, uint16_t numregs); + void writeSingleRegister(uint16_t reg, uint16_t value); + void writeMultipleRegisters(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount); void exceptionResponse(byte fcode, byte excode); #ifndef USE_HOLDING_REGISTERS_ONLY - void readCoils(word startreg, word numregs); - void readInputStatus(word startreg, word numregs); - void readInputRegisters(word startreg, word numregs); - void writeSingleCoil(word reg, word status); - void writeMultipleCoils(byte* frame,word startreg, word numoutputs, byte bytecount); + void readCoils(uint16_t startreg, uint16_t numregs); + void readInputStatus(uint16_t startreg, uint16_t numregs); + void readInputRegisters(uint16_t startreg, uint16_t numregs); + void writeSingleCoil(uint16_t reg, uint16_t status); + void writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount); #endif - TRegister* searchRegister(word addr); + TRegister* searchRegister(uint16_t addr); - void addReg(word address, word value = 0); - bool Reg(word address, word value); - word Reg(word address); + void addReg(uint16_t address, uint16_t value = 0); + bool Reg(uint16_t address, uint16_t value); + uint16_t Reg(uint16_t address); protected: byte *_frame; @@ -90,22 +89,22 @@ class Modbus { public: Modbus(); - void addHreg(word offset, word value = 0); - bool Hreg(word offset, word value); - word Hreg(word offset); + void addHreg(uint16_t offset, uint16_t value = 0); + bool Hreg(uint16_t offset, uint16_t value); + uint16_t Hreg(uint16_t offset); #ifndef USE_HOLDING_REGISTERS_ONLY - void addCoil(word offset, bool value = false); - void addIsts(word offset, bool value = false); - void addIreg(word offset, word value = 0); + void addCoil(uint16_t offset, bool value = false); + void addIsts(uint16_t offset, bool value = false); + void addIreg(uint16_t offset, uint16_t value = 0); - bool Coil(word offset, bool value); - bool Ists(word offset, bool value); - bool Ireg(word offset, word 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(word offset); - bool Ists(word offset); - word Ireg(word offset); + bool Coil(uint16_t offset); + bool Ists(uint16_t offset); + uint16_t Ireg(uint16_t offset); #endif }; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 7af0786..90bfb42 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -4,33 +4,17 @@ */ #include "ModbusIP_ESP8266.h" -//WiFiServer server(MODBUSIP_PORT); - //ModbusIP::ModbusIP() { // //} -//void ModbusIP::config(const char* ssid, const char* password) { -// WiFi.begin(ssid, password); -// server.begin(); -//} - -void ModbusIP::config() { - //server.begin(); - begin(); -} - void ModbusIP::begin() { - //server.begin(); WiFiServer::begin(); } -//#ifdef TCP_KEEP_ALIVE -//WiFiClient client; -//#endif void ModbusIP::task() { #ifdef TCP_KEEP_ALIVE - if (!client || !client.connected()) { + if (client == NULL || !client.connected()) { // if (client) // delete client; // diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 895e760..4190e86 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -21,11 +21,8 @@ class ModbusIP : public Modbus, public WiFiServer { WiFiClient client; #endif public: -// ModbusIP(); -// void config(const char* ssid, const char* password); ModbusIP() : WiFiServer(MODBUSIP_PORT) { } - void config(); void begin(); void task(); }; From 1841474da5aad70e02f5ed00b98c92bee1e3cb49 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 14 Aug 2017 13:54:08 +0500 Subject: [PATCH 006/288] Change examples to work with new API --- LICENSE.txt | 2 ++ README.md | 4 ++++ examples/TestAnalogInput/TestAnalogInput.ino | 15 +++++++++------ examples/TestHoldingReg/TestHoldingReg.ino | 11 ++++++++--- examples/TestLed/TestLed.ino | 12 +++++++++--- examples/TestSwitchStatus/TestSwitchStatus.ino | 14 ++++++++++++-- keywords.txt | 3 +-- src/Modbus.cpp | 7 ++++--- src/Modbus.h | 1 + src/ModbusIP_ESP8266.cpp | 5 +++-- src/ModbusIP_ESP8266.h | 5 +++-- 11 files changed, 56 insertions(+), 23 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 5810ccb..f7aa0ba 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,8 @@ Copyright (c) 2015, André Sarmento Barbosa + 2017, Alexander Emelianov (a.m.emelianov@gmail.com) All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index dc26c87..482fc85 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ This README is under development, for now, see the examples of the library. Contributions ============= +https://github.com/emelinov/modbus-esp8266 +a.m.emelianov@gmail.com + +Original version: http://github.com/andresarmento/modbus-esp8266
prof (at) andresarmento (dot) com diff --git a/examples/TestAnalogInput/TestAnalogInput.ino b/examples/TestAnalogInput/TestAnalogInput.ino index 1a29954..509b337 100644 --- a/examples/TestAnalogInput/TestAnalogInput.ino +++ b/examples/TestAnalogInput/TestAnalogInput.ino @@ -1,12 +1,16 @@ /* Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) Read Analog sensor on Pin ADC (ADC input between 0 ... 1V) + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ #include -#include #include //Modbus Registers Offsets (0-9999) @@ -18,21 +22,20 @@ ModbusIP mb; long ts; void setup() { - Serial.begin(115200); + Serial.begin(74880); - //Config Modbus IP - mb.config("your_ssid", "your_password"); - + WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } - + Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); + mb.begin(); //Sart Modbus IP // Add SENSOR_IREG register - Use addIreg() for analog Inputs mb.addIreg(SENSOR_IREG); diff --git a/examples/TestHoldingReg/TestHoldingReg.ino b/examples/TestHoldingReg/TestHoldingReg.ino index 982fadc..9ce6b35 100644 --- a/examples/TestHoldingReg/TestHoldingReg.ino +++ b/examples/TestHoldingReg/TestHoldingReg.ino @@ -2,12 +2,16 @@ Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) Configure Holding Register (offset 100) with initial value 0xABCD You can get or set this holding register + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ #include -#include #include // Modbus Registers Offsets (0-9999) @@ -18,9 +22,9 @@ const int TEST_HREG = 100; ModbusIP mb; void setup() { - Serial.begin(115200); + Serial.begin(74880); - mb.config("your_ssid", "your_password"); + WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -32,6 +36,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); + mb.begin(); mb.addHreg(TEST_HREG, 0xABCD); } diff --git a/examples/TestLed/TestLed.ino b/examples/TestLed/TestLed.ino index c314041..6e9633b 100644 --- a/examples/TestLed/TestLed.ino +++ b/examples/TestLed/TestLed.ino @@ -1,12 +1,16 @@ /* Modbus-Arduino Example - Test Led (Modbus IP ESP8266) Control a Led on GPIO0 pin using Write Single Coil Modbus Function + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ #include -#include #include //Modbus Registers Offsets (0-9999) @@ -18,9 +22,9 @@ const int ledPin = 0; //GPIO0 ModbusIP mb; void setup() { - Serial.begin(115200); + Serial.begin(74880); - mb.config("your_ssid", "your_password"); + WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -32,6 +36,8 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); + mb.begin(); + pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); } diff --git a/examples/TestSwitchStatus/TestSwitchStatus.ino b/examples/TestSwitchStatus/TestSwitchStatus.ino index b156ece..6508fc8 100644 --- a/examples/TestSwitchStatus/TestSwitchStatus.ino +++ b/examples/TestSwitchStatus/TestSwitchStatus.ino @@ -1,12 +1,16 @@ /* Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) Read Switch Status on pin GPIO0 + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ #include -#include #include //Modbus Registers Offsets (0-9999) @@ -18,8 +22,14 @@ const int switchPin = 0; //GPIO0 ModbusIP mb; void setup() { + + WiFi.begin("your_ssid", "your_password"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } //Config Modbus IP - mb.config("your_ssid", "your_password"); + mb.begin(); //Set ledPin mode pinMode(switchPin, INPUT); // Add SWITCH_ISTS register - Use addIsts() for digital inputs diff --git a/keywords.txt b/keywords.txt index 6e1cb2a..af67d23 100644 --- a/keywords.txt +++ b/keywords.txt @@ -5,7 +5,7 @@ ModbusIP KEYWORD1 ModbusIP_ESP8266 KEYWORD1 # Methods and Functions (KEYWORD2) -config KEYWORD2 +begin KEYWORD2 task KEYWORD2 # Constants (LITERAL1) @@ -14,7 +14,6 @@ task KEYWORD2 # Datatypes (KEYWORD1) Modbus KEYWORD1 -u_int KEYWORD1 TRegister KEYWORD1 # Methods and Functions (KEYWORD2) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index b513faa..8017fb8 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,6 +1,7 @@ /* - Modbus.cpp - Source for Modbus Base Library + Modbus.h - Header for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa + 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" @@ -197,7 +198,7 @@ void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. - if (!this->searchRegister(startreg + 40001)) { + if (!this->searchRegister(startreg + HREG_BASE)) { this->exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_ADDRESS); return; } @@ -261,7 +262,7 @@ void Modbus::writeMultipleRegisters(byte* frame,uint16_t startreg, uint16_t numo //Check Address (startreg...startreg + numregs) for (int k = 0; k < numoutputs; k++) { - if (!this->searchRegister(startreg + 40001 + k)) { + if (!this->searchRegister(startreg + HREG_BASE + k)) { this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_ADDRESS); return; } diff --git a/src/Modbus.h b/src/Modbus.h index f0e8678..a049d3a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,6 +1,7 @@ /* Modbus.h - Header for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa + 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Arduino.h" diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 90bfb42..cc0798f 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -1,6 +1,7 @@ /* - ModbusIP_ESP8266.cpp - Source for Modbus IP ESP8266 Library - Copyright (C) 2015 André Sarmento Barbosa + Modbus.h - Header for Modbus Base Library + Copyright (C) 2014 André Sarmento Barbosa + 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "ModbusIP_ESP8266.h" diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 4190e86..4121937 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,6 +1,7 @@ /* - ModbusIP_ESP8266.h - Header for Modbus IP ESP8266 Library - Copyright (C) 2015 André Sarmento Barbosa + Modbus.h - Header for Modbus Base Library + Copyright (C) 2014 André Sarmento Barbosa + 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include #include From 34b753064b61deef8316a7fc7269426615bbf647 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 14 Aug 2017 17:48:35 +0500 Subject: [PATCH 007/288] ESP32 compatibility (not tested) --- README.md | 26 +++++++++---------- TODO.txt | 8 ------ examples/TestAnalogInput/TestAnalogInput.ino | 6 ++++- examples/TestHoldingReg/TestHoldingReg.ino | 6 ++++- examples/TestLed/TestLed.ino | 6 ++++- .../TestSwitchStatus/TestSwitchStatus.ino | 6 ++++- library.properties | 2 +- src/ModbusIP_ESP8266.h | 6 ++++- 8 files changed, 39 insertions(+), 27 deletions(-) delete mode 100644 TODO.txt diff --git a/README.md b/README.md index 482fc85..639e77d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -Modbus Library for ESP8266 -========================== +# Modbus Library for ESP8266/ESP32 -This library allows your ESP8266 to communicate via Modbus protocol. The Modbus is a master-slave protocol +This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). @@ -11,12 +10,13 @@ In the current version the library allows the ESP8266 operate as a slave, suppor http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf -Features -======== +## Features + +Keep alive and ESP32 support added to original library. This features is under testing.
  • Operates as a slave
  • -
  • Supports Modbus IP (TCP, not keep-alive)
  • +
  • Supports Modbus IP (TCP)
  • Reply exception messages for all supported functions
  • Modbus functions supported:
    • @@ -33,7 +33,7 @@ Features Notes: -1. When using Modbus IP the transport protocol is TCP (port 502) and the connection is terminated to each transmitted message, that is, is not a keep-alive type connection. +1. When using Modbus IP the transport protocol is TCP (port 502). 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in ScadaBR (http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the CAS Modbus Scanner @@ -54,23 +54,23 @@ Thus, only the following functions are supported:
    -How to -====== +## How to ``` This README is under development, for now, see the examples of the library. ``` -Contributions -============= +## Contributions + https://github.com/emelinov/modbus-esp8266 + a.m.emelianov@gmail.com Original version: http://github.com/andresarmento/modbus-esp8266
    prof (at) andresarmento (dot) com -License -======= +## License + The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 5cd9a53..0000000 --- a/TODO.txt +++ /dev/null @@ -1,8 +0,0 @@ -TODO -==== -. Modbus for Expressif SDK (in development) - - - - - diff --git a/examples/TestAnalogInput/TestAnalogInput.ino b/examples/TestAnalogInput/TestAnalogInput.ino index 509b337..6769420 100644 --- a/examples/TestAnalogInput/TestAnalogInput.ino +++ b/examples/TestAnalogInput/TestAnalogInput.ino @@ -10,7 +10,11 @@ https://github.com/emelianov/modbus-esp8266 */ -#include +#ifdef ESP8266 + #include +#else + #include +#endif #include //Modbus Registers Offsets (0-9999) diff --git a/examples/TestHoldingReg/TestHoldingReg.ino b/examples/TestHoldingReg/TestHoldingReg.ino index 9ce6b35..93d4edd 100644 --- a/examples/TestHoldingReg/TestHoldingReg.ino +++ b/examples/TestHoldingReg/TestHoldingReg.ino @@ -11,7 +11,11 @@ https://github.com/emelianov/modbus-esp8266 */ -#include +#ifdef ESP8266 + #include +#else + #include +#endif #include // Modbus Registers Offsets (0-9999) diff --git a/examples/TestLed/TestLed.ino b/examples/TestLed/TestLed.ino index 6e9633b..1115ded 100644 --- a/examples/TestLed/TestLed.ino +++ b/examples/TestLed/TestLed.ino @@ -10,7 +10,11 @@ https://github.com/emelianov/modbus-esp8266 */ -#include +#ifdef ESP8266 + #include +#else + #include +#endif #include //Modbus Registers Offsets (0-9999) diff --git a/examples/TestSwitchStatus/TestSwitchStatus.ino b/examples/TestSwitchStatus/TestSwitchStatus.ino index 6508fc8..70d7fba 100644 --- a/examples/TestSwitchStatus/TestSwitchStatus.ino +++ b/examples/TestSwitchStatus/TestSwitchStatus.ino @@ -10,7 +10,11 @@ https://github.com/emelianov/modbus-esp8266 */ -#include +#ifdef ESP8266 + #include +#else + #include +#endif #include //Modbus Registers Offsets (0-9999) diff --git a/library.properties b/library.properties index 0b0bd83..965f268 100644 --- a/library.properties +++ b/library.properties @@ -6,4 +6,4 @@ sentence=Modbus Library for ESP8266 paragraph=This library allows your ESP8266 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 -architectures=esp8266 +architectures=esp8266,esp32 diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 4121937..4444d5a 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -4,7 +4,11 @@ 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include -#include +#ifdef ESP8266 + #include +#else + #include +#endif #ifndef MODBUSIP_ESP8266_H #define MODBUSIP_ESP8266_H From 508bfe14c2a48266754fe8f33c011da89ecf3467 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 14 Aug 2017 19:21:39 +0500 Subject: [PATCH 008/288] Misc --- src/ModbusIP_ESP8266.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 7af0786..0c82fd0 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -30,7 +30,7 @@ void ModbusIP::begin() { //#endif void ModbusIP::task() { #ifdef TCP_KEEP_ALIVE - if (!client || !client.connected()) { + if (client == NULL || !client.connected()) { // if (client) // delete client; // @@ -84,7 +84,7 @@ void ModbusIP::task() { client.write(sbuf, send_len); } - #ifndef TCP_KEEPALIVE + #ifndef TCP_KEEP_ALIVE client.stop(); #endif free(_frame); From a79e01642d609f3731e0dff516ce696141670b61 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 18 Aug 2017 17:57:48 +0500 Subject: [PATCH 009/288] Multiple connection support --- README.md | 5 +-- library.properties | 4 +- src/Modbus.cpp | 1 - src/Modbus.h | 1 - src/ModbusIP_ESP8266.cpp | 83 ++++++++-------------------------------- src/ModbusIP_ESP8266.h | 3 +- 6 files changed, 21 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 639e77d..256d36e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ used in industrial automation and can be used in other areas, such as home autom The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). -In the current version the library allows the ESP8266 operate as a slave, supporting Modbus IP via wireless network. For more information about Modbus see: +In the current version the library allows the ESP8266/ESP32 operate as a slave, supporting Modbus IP via wireless network. For more information about Modbus see: http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf @@ -62,8 +62,7 @@ This README is under development, for now, see the examples of the library. ## Contributions -https://github.com/emelinov/modbus-esp8266 - +https://github.com/emelinov/modbus-esp8266
    a.m.emelianov@gmail.com Original version: diff --git a/library.properties b/library.properties index 965f268..c3712eb 100644 --- a/library.properties +++ b/library.properties @@ -2,8 +2,8 @@ name=modbus-esp8266 version=0.1 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov -sentence=Modbus Library for ESP8266 -paragraph=This library allows your ESP8266 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. +sentence=Modbus Library for ESP8266/ESP32 +paragraph=This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 architectures=esp8266,esp32 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 8017fb8..dbf11aa 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,7 +1,6 @@ /* Modbus.h - Header for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" diff --git a/src/Modbus.h b/src/Modbus.h index a049d3a..f0e8678 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,7 +1,6 @@ /* Modbus.h - Header for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Arduino.h" diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 0ae0155..130aa65 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -14,25 +14,19 @@ void ModbusIP::begin() { } void ModbusIP::task() { -#ifdef TCP_KEEP_ALIVE - if (client == NULL || !client.connected()) { - // if (client) - // delete client; - // - client = available(); - } -#else - WiFiClient client; -#endif + for (uint8_t n = 0; n < TCP_MAX_CLIENTS; n++) { + if (!client[n] || !client[n].connected()) { + client[n] = available(); + } int raw_len = 0; - if (client) { - if (client.connected()) { + if (client[n]) { + if (client[n].connected()) { for (int x = 0; x < 300; x++) { // Time to have data available - if (client.available()) { - while (client.available() > raw_len) { //Computes data length - raw_len = client.available(); + if (client[n].available()) { + while (client[n].available() > raw_len) { //Computes data length + raw_len = client[n].available(); delay(1); } break; @@ -42,7 +36,7 @@ void ModbusIP::task() { } if (raw_len > 7) { - for (int i=0; i<7; i++) _MBAP[i] = client.read(); //Get MBAP + for (int i=0; i<7; i++) _MBAP[i] = client[n].read(); //Get MBAP _len = _MBAP[4] << 8 | _MBAP[5]; _len--; // Do not count with last byte from MBAP @@ -51,10 +45,10 @@ void ModbusIP::task() { _frame = (byte*) malloc(_len); raw_len = raw_len - 7; - for (int i=0; i< raw_len; i++) _frame[i] = client.read(); //Get Modbus PDU + for (int i=0; i< raw_len; i++) _frame[i] = client[n].read(); //Get Modbus PDU this->receivePDU(_frame); - client.flush(); + client[n].flush(); if (_reply != MB_REPLY_OFF) { //MBAP @@ -67,61 +61,14 @@ void ModbusIP::task() { for (int i=0; i<7; i++) sbuf[i] = _MBAP[i]; for (int i=0; i<_len; i++) sbuf[i+7] = _frame[i]; - client.write(sbuf, send_len); + client[n].write(sbuf, send_len); } #ifndef TCP_KEEP_ALIVE - client.stop(); + client[n].stop(); #endif free(_frame); _len = 0; } } } - /* - uint8_t buffer[128] = {0}; - uint8_t mux_id; - uint32_t len = _wifi->recv(&mux_id, buffer, sizeof(buffer), 100); - - if (len > 0) { - int i = 0; - while (i < 7) { - _MBAP[i] = buffer[i]; - i++; - } - - _len = _MBAP[4] << 8 | _MBAP[5]; - _len--; // Do not count with last byte from MBAP - if (_MBAP[2] !=0 || _MBAP[3] !=0) return; //Not a MODBUSIP packet - if (_len > MODBUSIP_MAXFRAME) return; //Length is over MODBUSIP_MAXFRAME - - _frame = (byte*) malloc(_len); - i = 0; - while (i < _len){ - _frame[i] = buffer[7+i]; //Forget MBAP and take just modbus pdu - i++; - } - - this->receivePDU(_frame); - - if (_reply != MB_REPLY_OFF) { - //MBAP - _MBAP[4] = _len >> 8; - _MBAP[5] = _len | 0x00FF; - buffer[4] = _MBAP[4]; - buffer[5] = _MBAP[5]; - - i = 0; - while (i < _len){ - buffer[i+7] = _frame[i]; - i++; - } - _wifi->send(mux_id, buffer, _len + 7); - _wifi->releaseTCP(mux_id); - } - - free(_frame); - _len = 0; - } - -} -*/ +} \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 4444d5a..eb88153 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -18,12 +18,13 @@ #define MODBUSIP_TIMEOUT 10 #define TCP_KEEP_ALIVE +#define TCP_MAX_CLIENTS 4 class ModbusIP : public Modbus, public WiFiServer { private: byte _MBAP[7]; #ifdef TCP_KEEP_ALIVE - WiFiClient client; + WiFiClient client[TCP_MAX_CLIENTS]; #endif public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { From 1baae6edcc55b0b0a668018e660c1569a4a366ce Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 18 Aug 2017 23:55:18 +0500 Subject: [PATCH 010/288] Multiple connection fix. ESP32 tested --- src/ModbusIP_ESP8266.cpp | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 130aa65..df362f5 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -11,28 +11,34 @@ void ModbusIP::begin() { WiFiServer::begin(); + for (uint8_t i = 0; i < TCP_MAX_CLIENTS; i++) { + client[i] = WiFiClient(); + } } void ModbusIP::task() { for (uint8_t n = 0; n < TCP_MAX_CLIENTS; n++) { if (!client[n] || !client[n].connected()) { client[n] = available(); + //if (client[n] && client[n].connected()) { + // Serial.println(client[n].remoteIP()); + //} } int raw_len = 0; - if (client[n]) { - if (client[n].connected()) { - for (int x = 0; x < 300; x++) { // Time to have data available - if (client[n].available()) { - while (client[n].available() > raw_len) { //Computes data length + //if (client[n]) { + if (client[n] && client[n].connected()) { +// for (int x = 0; x < 300; x++) { // Time to have data available +// if (client[n].available()) { +// while (client[n].available() > raw_len) { //Computes data length raw_len = client[n].available(); - delay(1); - } - break; - } - delay(10); - } +// delay(1); +// } +// break; +// } +// delay(10); +// } } if (raw_len > 7) { @@ -40,8 +46,8 @@ void ModbusIP::task() { _len = _MBAP[4] << 8 | _MBAP[5]; _len--; // Do not count with last byte from MBAP - if (_MBAP[2] !=0 || _MBAP[3] !=0) return; //Not a MODBUSIP packet - if (_len > MODBUSIP_MAXFRAME) return; //Length is over MODBUSIP_MAXFRAME + if (_MBAP[2] !=0 || _MBAP[3] !=0) continue; //Not a MODBUSIP packet + if (_len > MODBUSIP_MAXFRAME) continue; //Length is over MODBUSIP_MAXFRAME _frame = (byte*) malloc(_len); raw_len = raw_len - 7; @@ -69,6 +75,6 @@ void ModbusIP::task() { free(_frame); _len = 0; } - } + //} } } \ No newline at end of file From 4d4b869a6b1c2fb4fe8d8bc1e794f80d149f889e Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 19 Aug 2017 19:33:16 +0500 Subject: [PATCH 011/288] Add callback assign methods, callback example --- examples/TestCallback/TestCallback.ino | 60 ++++++++++++++++++++++++++ src/Modbus.cpp | 18 ++++++++ src/Modbus.h | 12 +++++- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 examples/TestCallback/TestCallback.ino diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino new file mode 100644 index 0000000..04ceb65 --- /dev/null +++ b/examples/TestCallback/TestCallback.ino @@ -0,0 +1,60 @@ +/* + Modbus-Arduino Example - Test Led using callback (Modbus IP ESP8266/ESP32) + Control a Led on D4 pin using Write Single Coil Modbus Function + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +//Modbus Registers Offsets (0-9999) +const int LED_COIL = 100; +//Used Pins +const int ledPin = D4; // Builtin ESP8266 LED + +//ModbusIP object +ModbusIP mb; + +// Callback function +uint16_t cbLed(TRegister* reg, uint16_t val) { + //Attach ledPin to LED_COIL register + digitalWrite(ledPin, (val == 0xFF00)); + return val; +} + +void setup() { + Serial.begin(74880); + + WiFi.begin("ssid", "pass"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.begin(); + + pinMode(ledPin, OUTPUT); + mb.addCoil(LED_COIL); // Add Coil + mb.onSet(COIL(LED_COIL), cbLed); // Add callback on Coil LED_COIL value set +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); +} diff --git a/src/Modbus.cpp b/src/Modbus.cpp index dbf11aa..bff6cee 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,6 +1,7 @@ /* Modbus.h - Header for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa + 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" @@ -515,6 +516,23 @@ void Modbus::writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutpu _reply = MB_REPLY_NORMAL; } #endif +bool Modbus::onGet(uint16_t address, cbModbus cb) { + TRegister* reg = this->searchRegister(address); + if (reg) { + reg->get = cb; + return true; + } + return false; +} +bool Modbus::onSet(uint16_t address, cbModbus cb) { + TRegister* reg = this->searchRegister(address); + if (reg) { + reg->set = cb; + return true; + } + return false; +} + diff --git a/src/Modbus.h b/src/Modbus.h index f0e8678..957df7c 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,18 +1,23 @@ /* Modbus.h - Header for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa + 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Arduino.h" #ifndef MODBUS_H #define MODBUS_H -#define MAX_REGS 32 +#define MAX_REGS 32 #define MAX_FRAME 128 #define COIL_BASE 1 #define ISTS_BASE 10001 #define IREG_BASE 30001 #define HREG_BASE 40001 +#define COIL(n) (n + COIL_BASE) +#define ISTS(n) (n + ISTS_BASE) +#define IREG(n) (n + IREG_BASE) +#define HERG(n) (n + HREG_BASE) //#define USE_HOLDING_REGISTERS_ONLY @@ -57,6 +62,8 @@ typedef struct TRegister { cbModbus set; } TRegister; +uint16_t cbDefault(TRegister* reg, uint16_t val); + class Modbus { private: TRegister *_regs_head; @@ -106,6 +113,9 @@ class Modbus { bool Ists(uint16_t offset); uint16_t Ireg(uint16_t offset); #endif + + bool onGet(uint16_t address, cbModbus cb = cbDefault); + bool onSet(uint16_t address, cbModbus cb = cbDefault); }; #endif //MODBUS_H From 592bad230e0a71d1b0026da59241fd767f83e778 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 19 Aug 2017 21:33:17 +0500 Subject: [PATCH 012/288] Keywords added --- keywords.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/keywords.txt b/keywords.txt index af67d23..e09c827 100644 --- a/keywords.txt +++ b/keywords.txt @@ -5,8 +5,10 @@ ModbusIP KEYWORD1 ModbusIP_ESP8266 KEYWORD1 # Methods and Functions (KEYWORD2) -begin KEYWORD2 +begin KEYWORD2 task KEYWORD2 +onGet KEYWORD2 +onSet KEYWORD2 # Constants (LITERAL1) @@ -50,3 +52,11 @@ MB_FC_WRITE_REGS LITERAL1 MB_REPLY_OFF LITERAL1 MB_REPLY_ECHO LITERAL1 MB_REPLY_NORMAL LITERAL1 +COIL_BASE LITERAL1 +ISTS_BASE LITERAL1 +IREG_BASE LITERAL1 +HREG_BASE LITERAL1 +COIL LITERAL1 +ISTS LITERAL1 +IREG LITERAL1 +HERG LITERAL1 \ No newline at end of file From 2d4617153a2fb94883ee73382f0f22b47ee0f191 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 19 Aug 2017 21:38:32 +0500 Subject: [PATCH 013/288] Mistype fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 256d36e..20f329f 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ This README is under development, for now, see the examples of the library. ## Contributions -https://github.com/emelinov/modbus-esp8266
    +https://github.com/emelianov/modbus-esp8266
    a.m.emelianov@gmail.com Original version: From 8b427eda99cb59c1a58fddbe27ba923a3c499228 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 30 Aug 2017 22:15:16 +0500 Subject: [PATCH 014/288] onConnect callback added --- examples/TestCallback/TestCallback.ino | 16 +++++++--- src/ModbusIP_ESP8266.cpp | 44 ++++++++++---------------- src/ModbusIP_ESP8266.h | 9 ++++-- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index 04ceb65..d449657 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -25,17 +25,23 @@ const int ledPin = D4; // Builtin ESP8266 LED //ModbusIP object ModbusIP mb; -// Callback function +// Callback function for write (set) Coil. Returns value to store. uint16_t cbLed(TRegister* reg, uint16_t val) { //Attach ledPin to LED_COIL register digitalWrite(ledPin, (val == 0xFF00)); return val; } + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} void setup() { Serial.begin(74880); - WiFi.begin("ssid", "pass"); + WiFi.begin("ssid", "password"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -46,7 +52,8 @@ void setup() { Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); - + + mb.onConnect(cbConn); // Add callback on connection event mb.begin(); pinMode(ledPin, OUTPUT); @@ -57,4 +64,5 @@ void setup() { void loop() { //Call once inside loop() - all magic here mb.task(); -} + yield(); +} \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index df362f5..c06db10 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -5,42 +5,29 @@ */ #include "ModbusIP_ESP8266.h" -//ModbusIP::ModbusIP() { -// -//} - void ModbusIP::begin() { WiFiServer::begin(); - for (uint8_t i = 0; i < TCP_MAX_CLIENTS; i++) { + for (uint8_t i = 0; i < TCP_MAX_CLIENTS; i++) client[i] = WiFiClient(); - } } void ModbusIP::task() { for (uint8_t n = 0; n < TCP_MAX_CLIENTS; n++) { if (!client[n] || !client[n].connected()) { client[n] = available(); - //if (client[n] && client[n].connected()) { - // Serial.println(client[n].remoteIP()); - //} + if (client[n] && client[n].connected()) { + if (cbConnect && !cbConnect(client[n].remoteIP())) { + client[n].stop(); // If callback returns false immediate close connection + continue; + } + } } - int raw_len = 0; + int raw_len = 0; - //if (client[n]) { - if (client[n] && client[n].connected()) { -// for (int x = 0; x < 300; x++) { // Time to have data available -// if (client[n].available()) { -// while (client[n].available() > raw_len) { //Computes data length - raw_len = client[n].available(); -// delay(1); -// } -// break; -// } -// delay(10); -// } - } - + if (client[n] && client[n].connected()) + raw_len = client[n].available(); + if (raw_len > 7) { for (int i=0; i<7; i++) _MBAP[i] = client[n].read(); //Get MBAP @@ -69,12 +56,15 @@ void ModbusIP::task() { client[n].write(sbuf, send_len); } - #ifndef TCP_KEEP_ALIVE + #ifndef TCP_KEEP_ALIVE client[n].stop(); - #endif + #endif free(_frame); _len = 0; } - //} + } } + +void ModbusIP::onConnect(cbModbusConnect cb = NULL) { + cbConnect = cb; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index eb88153..987a0b0 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -20,17 +20,22 @@ #define TCP_KEEP_ALIVE #define TCP_MAX_CLIENTS 4 +// Callback function Type +typedef bool (*cbModbusConnect)(IPAddress ip); + class ModbusIP : public Modbus, public WiFiServer { private: - byte _MBAP[7]; + byte _MBAP[7]; #ifdef TCP_KEEP_ALIVE WiFiClient client[TCP_MAX_CLIENTS]; #endif + cbModbusConnect cbConnect = NULL; public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { } void begin(); - void task(); + void task(); + void onConnect(cbModbusConnect cb); }; #endif //MODBUSIP_ESP8266_H From be34e07c80db1a8977c8255edc28bdee43de3e88 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 23 Oct 2017 18:39:22 +0500 Subject: [PATCH 015/288] Packet processing fix, Client handling refactoring --- README.md | 80 +++++++++++++++++++++++++++++++++++++--- keywords.txt | 1 + src/Modbus.h | 10 +++-- src/ModbusIP_ESP8266.cpp | 64 +++++++++++++++++++------------- src/ModbusIP_ESP8266.h | 7 +--- 5 files changed, 122 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 20f329f..4e018e5 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,6 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf ## Features -Keep alive and ESP32 support added to original library. This features is under testing. -
    • Operates as a slave
    • Supports Modbus IP (TCP)
    • @@ -54,18 +52,90 @@ Thus, only the following functions are supported:
    -## How to +## API + +### Add regs +``` +void addHreg(uint16_t offset, uint16_t value = 0) +void addCoil(uint16_t offset, bool value = false) +void addIsts(uint16_t offset, bool value = false) +void addIreg(uint16_t offset, uint16_t value = 0) +``` +### Write regs +``` +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) +``` +### Read regs +``` +uint16_t Hreg(uint16_t offset) +bool Coil(uint16_t offset) +bool Ists(uint16_t offset) +uint16_t Ireg(uint16_t offset) +``` +### Callbacks + +``` +bool onGet(uint16_t address, cbModbus cb = cbDefault) +bool onSet(uint16_t address, cbModbus cb = cbDefault) +void onConnect(cbModbusConnect cb) +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) +typedef bool (*cbModbusConnect)(IPAddress ip) +#define COIL(n) +#define ISTS(n) +#define IREG(n) +#define HERG(n) +#define COIL_VAL(v) +#define COIL_BOOL(v) +``` +###ModBus IP specific + +``` +void begin() +void task() +``` + +### Callback example ``` -This README is under development, for now, see the examples of the library. +bool coil = false; +uint16_t cbCoilSet(TRegister* reg, uint16_t val) { + coil = COIL_BOOL(val); + return val; // Returns value to be saved to TRegister structure +} +uint16_t cbCoilGet(TRegister* reg, uint16_t val) { + return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request +} +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} +ModbusIP mb; //ModbusIP object +void setup() { +... + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + mb.addCoil(COIL_NR); // Add Coil + mb.onSet(COIL(COIL_NR), cbCoilSet); // Add callback on Coil COIL_NR value set + mb.onGet(COIL(COIL_NR), cbCoilGet); // Add callback on Coil COIL_NR value get +... +} +void loop() { +... + mb.task(); +... +} ``` + ## Contributions https://github.com/emelianov/modbus-esp8266
    a.m.emelianov@gmail.com -Original version: +Original version:
    http://github.com/andresarmento/modbus-esp8266
    prof (at) andresarmento (dot) com diff --git a/keywords.txt b/keywords.txt index e09c827..17c2f28 100644 --- a/keywords.txt +++ b/keywords.txt @@ -9,6 +9,7 @@ begin KEYWORD2 task KEYWORD2 onGet KEYWORD2 onSet KEYWORD2 +onConnect KEYWORD2 # Constants (LITERAL1) diff --git a/src/Modbus.h b/src/Modbus.h index 957df7c..a0e700a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -18,6 +18,8 @@ #define ISTS(n) (n + ISTS_BASE) #define IREG(n) (n + IREG_BASE) #define HERG(n) (n + HREG_BASE) +#define COIL_VAL(v) (v?0xFF00:0x0000) +#define COIL_BOOL(v) (v==0xFF00) //#define USE_HOLDING_REGISTERS_ONLY @@ -56,7 +58,7 @@ typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); typedef struct TRegister { uint16_t address; uint16_t value; - uint16_t index; + //uint16_t index; struct TRegister* next; cbModbus get; cbModbus set; @@ -88,9 +90,9 @@ class Modbus { uint16_t Reg(uint16_t address); protected: - byte *_frame; - byte _len; - byte _reply; + uint8_t *_frame; + uint8_t _len; + uint8_t _reply; void receivePDU(byte* frame); public: diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index c06db10..1ab1b4c 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -7,58 +7,70 @@ void ModbusIP::begin() { WiFiServer::begin(); - for (uint8_t i = 0; i < TCP_MAX_CLIENTS; i++) - client[i] = WiFiClient(); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + client[i] = NULL; } void ModbusIP::task() { - for (uint8_t n = 0; n < TCP_MAX_CLIENTS; n++) { - if (!client[n] || !client[n].connected()) { - client[n] = available(); - if (client[n] && client[n].connected()) { - if (cbConnect && !cbConnect(client[n].remoteIP())) { - client[n].stop(); // If callback returns false immediate close connection - continue; + uint8_t n, i; + while (hasClient()) { + WiFiClient* currentClient = new WiFiClient(available()); + if (currentClient != NULL && currentClient->connected()) { + if (cbConnect == NULL || cbConnect(currentClient->remoteIP())) { + for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + if (client[n] == NULL) { + client[n] = currentClient; + break; // for + } } + if (n < MODBUSIP_MAX_CLIENTS) // If client added process next + continue; // while } + // If callback returns false or _MAX_CLIENTS reached immediate close connection + currentClient->flush(); + currentClient->stop(); + delete currentClient; } - - int raw_len = 0; - - if (client[n] && client[n].connected()) - raw_len = client[n].available(); - + } + for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + if (client[n] == NULL) + continue; + if (!client[n]->connected()) { + delete client[n]; + client[n] = NULL; + continue; + } + uint16_t raw_len = 0; + raw_len = client[n]->available(); if (raw_len > 7) { - for (int i=0; i<7; i++) _MBAP[i] = client[n].read(); //Get MBAP + for (i = 0; i < 7; i++) _MBAP[i] = client[n]->read(); //Get MBAP _len = _MBAP[4] << 8 | _MBAP[5]; _len--; // Do not count with last byte from MBAP - if (_MBAP[2] !=0 || _MBAP[3] !=0) continue; //Not a MODBUSIP packet + if (_MBAP[2] != 0 || _MBAP[3] != 0) continue; //Not a MODBUSIP packet if (_len > MODBUSIP_MAXFRAME) continue; //Length is over MODBUSIP_MAXFRAME _frame = (byte*) malloc(_len); raw_len = raw_len - 7; - for (int i=0; i< raw_len; i++) _frame[i] = client[n].read(); //Get Modbus PDU + for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU +// for (i = 0; i < raw_len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU this->receivePDU(_frame); - client[n].flush(); + client[n]->flush(); if (_reply != MB_REPLY_OFF) { //MBAP _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP _MBAP[5] = (_len+1) & 0x00FF; - size_t send_len = (unsigned int)_len + 7; + size_t send_len = (uint16_t)_len + 7; uint8_t sbuf[send_len]; - for (int i=0; i<7; i++) sbuf[i] = _MBAP[i]; - for (int i=0; i<_len; i++) sbuf[i+7] = _frame[i]; + for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; + for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; - client[n].write(sbuf, send_len); + client[n]->write(sbuf, send_len); } - #ifndef TCP_KEEP_ALIVE - client[n].stop(); - #endif free(_frame); _len = 0; } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 987a0b0..79c89f2 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -17,8 +17,7 @@ #define MODBUSIP_MAXFRAME 200 #define MODBUSIP_TIMEOUT 10 -#define TCP_KEEP_ALIVE -#define TCP_MAX_CLIENTS 4 +#define MODBUSIP_MAX_CLIENTS 4 // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); @@ -26,9 +25,7 @@ typedef bool (*cbModbusConnect)(IPAddress ip); class ModbusIP : public Modbus, public WiFiServer { private: byte _MBAP[7]; - #ifdef TCP_KEEP_ALIVE - WiFiClient client[TCP_MAX_CLIENTS]; - #endif + WiFiClient* client[MODBUSIP_MAX_CLIENTS]; cbModbusConnect cbConnect = NULL; public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { From 9724853db242be9539494c8b39f812458fb5a252 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 4 Dec 2017 14:57:56 +0500 Subject: [PATCH 016/288] malloc checking, cosmetics --- src/Modbus.cpp | 124 +++++++++++++++++++-------------------- src/Modbus.h | 31 +++++----- src/ModbusIP_ESP8266.cpp | 3 +- src/ModbusIP_ESP8266.h | 2 +- 4 files changed, 77 insertions(+), 83 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index bff6cee..67e0a29 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -10,42 +10,35 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { } Modbus::Modbus() { - _regs_head = 0; - _regs_last = 0; + _regs_head = NULL; } TRegister* Modbus::searchRegister(uint16_t address) { TRegister *reg = _regs_head; - //if there is no register configured, bail - if(reg == 0) return(0); + //scan through the linked list until the end of the list or the register is found. //return the pointer. - do { - if (reg->address == address) return(reg); + while (reg) { + if (reg->address == address) return reg; reg = reg->next; - } while(reg); - return(0); + } + return NULL; } -void Modbus::addReg(uint16_t address, uint16_t value) { +bool Modbus::addReg(uint16_t address, uint16_t value, uint8_t count) { TRegister *newreg; newreg = (TRegister *) malloc(sizeof(TRegister)); + if (!newreg) return false; newreg->address = address; - newreg->value = value; - newreg->get = cbDefault; - newreg->set = cbDefault; - newreg->next = 0; - - if(_regs_head == 0) { + newreg->value = value; + newreg->get = cbDefault; + newreg->set = cbDefault; + // Link previous first record to new register next pointer + newreg->next = _regs_head; + // Add new register to list _regs_head = newreg; - _regs_last = _regs_head; - } else { - //Assign the last register's next pointer to newreg. - _regs_last->next = newreg; - //then make temp the last register in the list. - _regs_last = newreg; - } + return true; } bool Modbus::Reg(uint16_t address, uint16_t value) { @@ -64,68 +57,64 @@ uint16_t Modbus::Reg(uint16_t address) { TRegister *reg; reg = this->searchRegister(address); if(reg) - return(reg->get(reg, reg->value)); + return reg->get(reg, reg->value); else - return(0); + return 0; } -void Modbus::addHreg(uint16_t offset, uint16_t value) { - this->addReg(offset + HREG_BASE, value); +bool Modbus::addHreg(uint16_t offset, uint16_t value, uint8_t count = 1) { + return this->addReg(HREG(offset), value); } bool Modbus::Hreg(uint16_t offset, uint16_t value) { - return Reg(offset + HREG_BASE, value); + return Reg(HREG(offset), value); } uint16_t Modbus::Hreg(uint16_t offset) { - return Reg(offset + HREG_BASE); + return Reg(HREG(offset)); } #ifndef USE_HOLDING_REGISTERS_ONLY - void Modbus::addCoil(uint16_t offset, bool value) { - this->addReg(offset + COIL_BASE, value?0xFF00:0x0000); + bool Modbus::addCoil(uint16_t offset, bool value, uint8_t count = 1) { + return this->addReg(COIL(offset), COIL_VAL(value)); } - void Modbus::addIsts(uint16_t offset, bool value) { - this->addReg(offset + ISTS_BASE, value?0xFF00:0x0000); + bool Modbus::addIsts(uint16_t offset, bool value, uint8_t count = 1) { + return this->addReg(ISTS(offset), ISTS_VAL(value)); } - void Modbus::addIreg(uint16_t offset, uint16_t value) { - this->addReg(offset + IREG_BASE, value); + bool Modbus::addIreg(uint16_t offset, uint16_t value, uint8_t count = 1) { + return this->addReg(IREG(offset), value); } bool Modbus::Coil(uint16_t offset, bool value) { - return Reg(offset + COIL_BASE, value?0xFF00:0x0000); + return Reg(COIL(offset), COIL_VAL(value)); } bool Modbus::Ists(uint16_t offset, bool value) { - return Reg(offset + ISTS_BASE, value?0xFF00:0x0000); + return Reg(ISTS(offset), ISTS_VAL(value)); } bool Modbus::Ireg(uint16_t offset, uint16_t value) { - return Reg(offset + IREG_BASE, value); + return Reg(IREG(offset), value); } bool Modbus::Coil(uint16_t offset) { - if (Reg(offset + COIL_BASE) == 0xFF00) { - return true; - } else return false; + return COIL_BOOL(Reg(COIL(offset)); } bool Modbus::Ists(uint16_t offset) { - if (Reg(offset + ISTS_BASE) == 0xFF00) { - return true; - } else return false; + return ISTS_BOOL(Reg(ISTS(offset)); } uint16_t Modbus::Ireg(uint16_t offset) { - return Reg(offset + IREG_BASE); + return Reg(IREG(offset)); } #endif -void Modbus::receivePDU(byte* frame) { - byte fcode = frame[0]; +void Modbus::receivePDU(uint8_t* frame) { + uint8_t fcode = frame[0]; uint16_t field1 = (word)frame[1] << 8 | (word)frame[2]; uint16_t field2 = (word)frame[3] << 8 | (word)frame[4]; @@ -178,11 +167,16 @@ void Modbus::receivePDU(byte* frame) { } } -void Modbus::exceptionResponse(byte fcode, byte excode) { +void Modbus::exceptionResponse(uint8_t fcode, uint8_t excode) { //Clean frame buffer free(_frame); _len = 2; - _frame = (byte *) malloc(_len); + _frame = (uint8_t*) malloc(_len); + if (!_malloc) { + // Don't send reply if can't build frame + _reply = MB_REPLY_OFF; + return; + } _frame[0] = fcode + 0x80; _frame[1] = excode; @@ -198,7 +192,7 @@ void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. - if (!this->searchRegister(startreg + HREG_BASE)) { + if (!this->searchRegister(HREG(startreg))) { this->exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_ADDRESS); return; } @@ -212,7 +206,7 @@ void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { //for each register queried add 2 bytes _len = 2 + numregs * 2; - _frame = (byte *) malloc(_len); + _frame = (uint8_t*) malloc(_len); if (!_frame) { this->exceptionResponse(MB_FC_READ_REGS, MB_EX_SLAVE_FAILURE); return; @@ -253,7 +247,7 @@ void Modbus::writeSingleRegister(uint16_t reg, uint16_t value) { _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleRegisters(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount) { +void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount) { //Check value if (numoutputs < 0x0001 || numoutputs > 0x007B || bytecount != 2 * numoutputs) { this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_VALUE); @@ -262,7 +256,7 @@ void Modbus::writeMultipleRegisters(byte* frame,uint16_t startreg, uint16_t numo //Check Address (startreg...startreg + numregs) for (int k = 0; k < numoutputs; k++) { - if (!this->searchRegister(startreg + HREG_BASE + k)) { + if (!this->searchRegister(HREG(startreg) + k)) { this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_ADDRESS); return; } @@ -271,7 +265,7 @@ void Modbus::writeMultipleRegisters(byte* frame,uint16_t startreg, uint16_t numo //Clean frame buffer free(_frame); _len = 5; - _frame = (byte *) malloc(_len); + _frame = (uint8_t*) malloc(_len); if (!_frame) { this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_SLAVE_FAILURE); return; @@ -307,7 +301,7 @@ void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { //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 (!this->searchRegister(startreg + 1)) { + if (!this->searchRegister(COIL(startreg))) { this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); return; } @@ -321,7 +315,7 @@ void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { _len = 2 + numregs/8; if (numregs%8) _len++; //Add 1 to the message length for the partial byte. - _frame = (byte *) malloc(_len); + _frame = (uint8_t*) malloc(_len); if (!_frame) { this->exceptionResponse(MB_FC_READ_COILS, MB_EX_SLAVE_FAILURE); return; @@ -330,7 +324,7 @@ void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { _frame[0] = MB_FC_READ_COILS; _frame[1] = _len - 2; //byte count (_len - function code and byte count) - byte bitn = 0; + uint8_t bitn = 0; uint16_t totregs = numregs; uint16_t i; while (numregs--) { @@ -358,7 +352,7 @@ void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. - if (!this->searchRegister(startreg + 10001)) { + if (!this->searchRegister(ISTS(startreg))) { this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); return; } @@ -372,7 +366,7 @@ void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { _len = 2 + numregs/8; if (numregs%8) _len++; //Add 1 to the message length for the partial byte. - _frame = (byte *) malloc(_len); + _frame = (uint8_t*) malloc(_len); if (!_frame) { this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_SLAVE_FAILURE); return; @@ -381,7 +375,7 @@ void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { _frame[0] = MB_FC_READ_INPUT_STAT; _frame[1] = _len - 2; - byte bitn = 0; + uint8_t bitn = 0; uint16_t totregs = numregs; uint16_t i; while (numregs--) { @@ -409,7 +403,7 @@ void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. - if (!this->searchRegister(startreg + 30001)) { + if (!this->searchRegister(IREG(startreg))) { this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); return; } @@ -422,7 +416,7 @@ void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { //for each register queried add 2 bytes _len = 2 + numregs * 2; - _frame = (byte *) malloc(_len); + _frame = (uint8_t*) malloc(_len); if (!_frame) { this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_SLAVE_FAILURE); return; @@ -468,7 +462,7 @@ void Modbus::writeSingleCoil(uint16_t reg, uint16_t status) { _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount) { +void Modbus::writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount) { //Check value uint16_t bytecount_calc = numoutputs / 8; if (numoutputs%8) bytecount_calc++; @@ -479,7 +473,7 @@ void Modbus::writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutpu //Check Address (startreg...startreg + numregs) for (int k = 0; k < numoutputs; k++) { - if (!this->searchRegister(startreg + 1 + k)) { + if (!this->searchRegister(COIL(startreg) + k)) { this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_ADDRESS); return; } @@ -488,7 +482,7 @@ void Modbus::writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutpu //Clean frame buffer free(_frame); _len = 5; - _frame = (byte *) malloc(_len); + _frame = (uint8_t*) malloc(_len); if (!_frame) { this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_SLAVE_FAILURE); return; @@ -500,7 +494,7 @@ void Modbus::writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutpu _frame[3] = numoutputs >> 8; _frame[4] = numoutputs & 0x00FF; - byte bitn = 0; + uint8_t bitn = 0; uint16_t totoutputs = numoutputs; uint16_t i; while (numoutputs--) { diff --git a/src/Modbus.h b/src/Modbus.h index a0e700a..ba8b5d2 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -20,6 +20,9 @@ #define HERG(n) (n + HREG_BASE) #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) + //#define USE_HOLDING_REGISTERS_ONLY @@ -58,7 +61,6 @@ typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); typedef struct TRegister { uint16_t address; uint16_t value; - //uint16_t index; struct TRegister* next; cbModbus get; cbModbus set; @@ -68,44 +70,43 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { private: - TRegister *_regs_head; - TRegister *_regs_last; + TRegister* _regs_head; void readRegisters(uint16_t startreg, uint16_t numregs); void writeSingleRegister(uint16_t reg, uint16_t value); - void writeMultipleRegisters(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount); - void exceptionResponse(byte fcode, byte excode); + void writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); + void exceptionResponse(uint8_t fcode, uint8_t excode); #ifndef USE_HOLDING_REGISTERS_ONLY void readCoils(uint16_t startreg, uint16_t numregs); void readInputStatus(uint16_t startreg, uint16_t numregs); void readInputRegisters(uint16_t startreg, uint16_t numregs); void writeSingleCoil(uint16_t reg, uint16_t status); - void writeMultipleCoils(byte* frame,uint16_t startreg, uint16_t numoutputs, byte bytecount); + void writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); #endif TRegister* searchRegister(uint16_t addr); - void addReg(uint16_t address, uint16_t value = 0); + bool addReg(uint16_t address, uint16_t value = 0, uint8_t count = 1); bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); protected: - uint8_t *_frame; + uint8_t* _frame; uint8_t _len; uint8_t _reply; - void receivePDU(byte* frame); + void receivePDU(uint8_t* frame); public: Modbus(); - void addHreg(uint16_t offset, uint16_t value = 0); + bool addHreg(uint16_t offset, uint16_t value = 0, uint8_t count = 1); bool Hreg(uint16_t offset, uint16_t value); uint16_t Hreg(uint16_t offset); #ifndef USE_HOLDING_REGISTERS_ONLY - void addCoil(uint16_t offset, bool value = false); - void addIsts(uint16_t offset, bool value = false); - void addIreg(uint16_t offset, uint16_t value = 0); + bool addCoil(uint16_t offset, bool value = false, uint8_t count = 1); + bool addIsts(uint16_t offset, bool value = false, uint8_t count = 1); + bool addIreg(uint16_t offset, uint16_t value = 0, uint8_t count = 1); bool Coil(uint16_t offset, bool value); bool Ists(uint16_t offset, bool value); @@ -116,8 +117,8 @@ class Modbus { uint16_t Ireg(uint16_t offset); #endif - bool onGet(uint16_t address, cbModbus cb = cbDefault); - bool onSet(uint16_t address, cbModbus cb = cbDefault); + bool onGet(uint16_t address, cbModbus cb = cbDefault, uint8_t count = 1); + bool onSet(uint16_t address, cbModbus cb = cbDefault, uint8_t count = 1); }; #endif //MODBUS_H diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 1ab1b4c..f376f3c 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -49,11 +49,10 @@ void ModbusIP::task() { _len--; // Do not count with last byte from MBAP if (_MBAP[2] != 0 || _MBAP[3] != 0) continue; //Not a MODBUSIP packet if (_len > MODBUSIP_MAXFRAME) continue; //Length is over MODBUSIP_MAXFRAME - _frame = (byte*) malloc(_len); + _frame = (uint8_t*) malloc(_len); raw_len = raw_len - 7; for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU -// for (i = 0; i < raw_len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU this->receivePDU(_frame); client[n]->flush(); diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 79c89f2..491d974 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -24,7 +24,7 @@ typedef bool (*cbModbusConnect)(IPAddress ip); class ModbusIP : public Modbus, public WiFiServer { private: - byte _MBAP[7]; + uint_8t _MBAP[7]; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; cbModbusConnect cbConnect = NULL; public: From 3961c3a2b3987aa4a6b663a34af5313a0078de12 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 4 Dec 2017 16:18:27 +0500 Subject: [PATCH 017/288] Multiple regs adding --- README.md | 18 +++++++------- src/Modbus.cpp | 56 +++++++++++++++++++++++------------------- src/Modbus.h | 19 +++++++------- src/ModbusIP_ESP8266.h | 2 +- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 4e018e5..5856f6c 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,12 @@ Thus, only the following functions are supported: ## API -### Add regs +### Add multiple regs ``` -void addHreg(uint16_t offset, uint16_t value = 0) -void addCoil(uint16_t offset, bool value = false) -void addIsts(uint16_t offset, bool value = false) -void addIreg(uint16_t offset, uint16_t value = 0) +void addHreg(uint16_t offset, uint16_t value = 0, uint16_t count = 1) +void addCoil(uint16_t offset, bool value = false, uint16_t count = 1) +void addIsts(uint16_t offset, bool value = false, uint16_t count = 1) +void addIreg(uint16_t offset, uint16_t value = 0, uint16_t count = 1) ``` ### Write regs ``` @@ -78,15 +78,15 @@ uint16_t Ireg(uint16_t offset) ### Callbacks ``` -bool onGet(uint16_t address, cbModbus cb = cbDefault) -bool onSet(uint16_t address, cbModbus cb = cbDefault) +bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1) +bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1) void onConnect(cbModbusConnect cb) typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) typedef bool (*cbModbusConnect)(IPAddress ip) #define COIL(n) #define ISTS(n) #define IREG(n) -#define HERG(n) +#define HREG(n) #define COIL_VAL(v) #define COIL_BOOL(v) ``` @@ -100,7 +100,7 @@ void task() ### Callback example ``` -bool coil = false; +bool coil = false; // Define external variable to get/set value uint16_t cbCoilSet(TRegister* reg, uint16_t val) { coil = COIL_BOOL(val); return val; // Returns value to be saved to TRegister structure diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 67e0a29..ffa9edd 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -9,10 +9,6 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { return val; } -Modbus::Modbus() { - _regs_head = NULL; -} - TRegister* Modbus::searchRegister(uint16_t address) { TRegister *reg = _regs_head; @@ -25,19 +21,29 @@ TRegister* Modbus::searchRegister(uint16_t address) { return NULL; } -bool Modbus::addReg(uint16_t address, uint16_t value, uint8_t count) { +bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t count) { TRegister *newreg; - - newreg = (TRegister *) malloc(sizeof(TRegister)); - if (!newreg) return false; - newreg->address = address; - newreg->value = value; - newreg->get = cbDefault; - newreg->set = cbDefault; - // Link previous first record to new register next pointer - newreg->next = _regs_head; - // Add new register to list - _regs_head = newreg; + TRegister *root = NULL; + while (count > 0) { + newreg = (TRegister*) malloc(sizeof(TRegister)); + if (!newreg) { + newreg = root; + while (newreg) { + root = newreg->next; + free(newreg); + } + return false; + } + newreg->address = address; + newreg->value = value; + newreg->get = cbDefault; + newreg->set = cbDefault; + newreg->next = root; + root = newreg; + count--; + address++; + } + _regs_head = root; return true; } @@ -62,7 +68,7 @@ uint16_t Modbus::Reg(uint16_t address) { return 0; } -bool Modbus::addHreg(uint16_t offset, uint16_t value, uint8_t count = 1) { +bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t count) { return this->addReg(HREG(offset), value); } @@ -75,15 +81,15 @@ uint16_t Modbus::Hreg(uint16_t offset) { } #ifndef USE_HOLDING_REGISTERS_ONLY - bool Modbus::addCoil(uint16_t offset, bool value, uint8_t count = 1) { + bool Modbus::addCoil(uint16_t offset, bool value, uint16_t count) { return this->addReg(COIL(offset), COIL_VAL(value)); } - bool Modbus::addIsts(uint16_t offset, bool value, uint8_t count = 1) { + bool Modbus::addIsts(uint16_t offset, bool value, uint16_t count) { return this->addReg(ISTS(offset), ISTS_VAL(value)); } - bool Modbus::addIreg(uint16_t offset, uint16_t value, uint8_t count = 1) { + bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t count) { return this->addReg(IREG(offset), value); } @@ -100,11 +106,11 @@ uint16_t Modbus::Hreg(uint16_t offset) { } bool Modbus::Coil(uint16_t offset) { - return COIL_BOOL(Reg(COIL(offset)); + return COIL_BOOL(Reg(COIL(offset))); } bool Modbus::Ists(uint16_t offset) { - return ISTS_BOOL(Reg(ISTS(offset)); + return ISTS_BOOL(Reg(ISTS(offset))); } uint16_t Modbus::Ireg(uint16_t offset) { @@ -172,7 +178,7 @@ void Modbus::exceptionResponse(uint8_t fcode, uint8_t excode) { free(_frame); _len = 2; _frame = (uint8_t*) malloc(_len); - if (!_malloc) { + if (!_frame) { // Don't send reply if can't build frame _reply = MB_REPLY_OFF; return; @@ -510,7 +516,7 @@ void Modbus::writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numou _reply = MB_REPLY_NORMAL; } #endif -bool Modbus::onGet(uint16_t address, cbModbus cb) { +bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t count) { TRegister* reg = this->searchRegister(address); if (reg) { reg->get = cb; @@ -518,7 +524,7 @@ bool Modbus::onGet(uint16_t address, cbModbus cb) { } return false; } -bool Modbus::onSet(uint16_t address, cbModbus cb) { +bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t count) { TRegister* reg = this->searchRegister(address); if (reg) { reg->set = cb; diff --git a/src/Modbus.h b/src/Modbus.h index ba8b5d2..aaa2bfe 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -17,7 +17,7 @@ #define COIL(n) (n + COIL_BASE) #define ISTS(n) (n + ISTS_BASE) #define IREG(n) (n + IREG_BASE) -#define HERG(n) (n + HREG_BASE) +#define HREG(n) (n + HREG_BASE) #define COIL_VAL(v) (v?0xFF00:0x0000) #define COIL_BOOL(v) (v==0xFF00) #define ISTS_VAL(v) (v?0xFF00:0x0000) @@ -70,7 +70,7 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { private: - TRegister* _regs_head; + TRegister* _regs_head = NULL; void readRegisters(uint16_t startreg, uint16_t numregs); void writeSingleRegister(uint16_t reg, uint16_t value); @@ -86,7 +86,7 @@ class Modbus { TRegister* searchRegister(uint16_t addr); - bool addReg(uint16_t address, uint16_t value = 0, uint8_t count = 1); + bool addReg(uint16_t address, uint16_t value = 0, uint16_t count = 1); bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); @@ -97,16 +97,15 @@ class Modbus { void receivePDU(uint8_t* frame); public: - Modbus(); - bool addHreg(uint16_t offset, uint16_t value = 0, uint8_t count = 1); + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t count = 1); bool Hreg(uint16_t offset, uint16_t value); uint16_t Hreg(uint16_t offset); #ifndef USE_HOLDING_REGISTERS_ONLY - bool addCoil(uint16_t offset, bool value = false, uint8_t count = 1); - bool addIsts(uint16_t offset, bool value = false, uint8_t count = 1); - bool addIreg(uint16_t offset, uint16_t value = 0, uint8_t count = 1); + bool addCoil(uint16_t offset, bool value = false, uint16_t count = 1); + bool addIsts(uint16_t offset, bool value = false, uint16_t count = 1); + bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t count = 1); bool Coil(uint16_t offset, bool value); bool Ists(uint16_t offset, bool value); @@ -117,8 +116,8 @@ class Modbus { uint16_t Ireg(uint16_t offset); #endif - bool onGet(uint16_t address, cbModbus cb = cbDefault, uint8_t count = 1); - bool onSet(uint16_t address, cbModbus cb = cbDefault, uint8_t count = 1); + bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1); + bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1); }; #endif //MODBUS_H diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 491d974..9b5d9fa 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -24,7 +24,7 @@ typedef bool (*cbModbusConnect)(IPAddress ip); class ModbusIP : public Modbus, public WiFiServer { private: - uint_8t _MBAP[7]; + uint8_t _MBAP[7]; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; cbModbusConnect cbConnect = NULL; public: From 262d32e523120b076d62e2ab929e197295921445 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 4 Dec 2017 23:05:47 +0500 Subject: [PATCH 018/288] Misc doc and callback example esp32 fix --- README.md | 8 +++++++- examples/TestCallback/TestCallback.ino | 7 +++++-- library.properties | 2 +- src/ModbusIP_ESP8266.cpp | 4 +++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4e018e5..ff882f7 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf
  • 0x0F - Write Multiple Coils
  • 0x10 - Write Multiple Registers
+
  • Callbacks for
  • +
      +
    • - Incoming IP connection
    • +
    • - Read specific Register
    • +
    • - Write specific Register
    • +
    Notes: @@ -112,7 +118,7 @@ bool cbConn(IPAddress ip) { Serial.println(ip); return true; } -ModbusIP mb; //ModbusIP object +ModbusIP mb; // ModbusIP object void setup() { ... mb.onConnect(cbConn); // Add callback on connection event diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index d449657..83c94cc 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -20,8 +20,11 @@ //Modbus Registers Offsets (0-9999) const int LED_COIL = 100; //Used Pins -const int ledPin = D4; // Builtin ESP8266 LED - +#ifdef ESP8266 + const int ledPin = D4; // Builtin ESP8266 LED +#else + const int ledPin = TX; // ESP32 TX LED +#endif //ModbusIP object ModbusIP mb; diff --git a/library.properties b/library.properties index c3712eb..068d823 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=0.1 +version=1.0 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov sentence=Modbus Library for ESP8266/ESP32 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 1ab1b4c..1668cca 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -13,8 +13,11 @@ void ModbusIP::begin() { void ModbusIP::task() { uint8_t n, i; + Serial.println("-1-"); while (hasClient()) { + Serial.println("-2-"); WiFiClient* currentClient = new WiFiClient(available()); + Serial.println("-3-"); if (currentClient != NULL && currentClient->connected()) { if (cbConnect == NULL || cbConnect(currentClient->remoteIP())) { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { @@ -53,7 +56,6 @@ void ModbusIP::task() { raw_len = raw_len - 7; for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU -// for (i = 0; i < raw_len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU this->receivePDU(_frame); client[n]->flush(); From 39e53784f780a70740933253ba9e89a6bfad3278 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 4 Dec 2017 23:28:55 +0500 Subject: [PATCH 019/288] onGet/onSet multiple regs assign --- library.properties | 2 +- src/Modbus.cpp | 62 +++++++++++++++++++++++++--------------------- src/Modbus.h | 14 +++++------ 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/library.properties b/library.properties index 068d823..85432ba 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=1.0 +version=1.1 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov sentence=Modbus Library for ESP8266/ESP32 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index ffa9edd..3e0b5b6 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -21,10 +21,10 @@ TRegister* Modbus::searchRegister(uint16_t address) { return NULL; } -bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t count) { +bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { TRegister *newreg; TRegister *root = NULL; - while (count > 0) { + while (numregs > 0) { newreg = (TRegister*) malloc(sizeof(TRegister)); if (!newreg) { newreg = root; @@ -40,7 +40,7 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t count) { newreg->set = cbDefault; newreg->next = root; root = newreg; - count--; + numregs--; address++; } _regs_head = root; @@ -68,8 +68,8 @@ uint16_t Modbus::Reg(uint16_t address) { return 0; } -bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t count) { - return this->addReg(HREG(offset), value); +bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return this->addReg(HREG(offset), value, numregs); } bool Modbus::Hreg(uint16_t offset, uint16_t value) { @@ -81,16 +81,16 @@ uint16_t Modbus::Hreg(uint16_t offset) { } #ifndef USE_HOLDING_REGISTERS_ONLY - bool Modbus::addCoil(uint16_t offset, bool value, uint16_t count) { - return this->addReg(COIL(offset), COIL_VAL(value)); + bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(COIL(offset), COIL_VAL(value), numregs); } - bool Modbus::addIsts(uint16_t offset, bool value, uint16_t count) { - return this->addReg(ISTS(offset), ISTS_VAL(value)); + bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); } - bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t count) { - return this->addReg(IREG(offset), value); + bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return this->addReg(IREG(offset), value, numregs); } bool Modbus::Coil(uint16_t offset, bool value) { @@ -516,23 +516,29 @@ void Modbus::writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numou _reply = MB_REPLY_NORMAL; } #endif -bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t count) { - TRegister* reg = this->searchRegister(address); - if (reg) { - reg->get = cb; - return true; +bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + while (numregs > 0) { + numregs--; + reg = this->searchRegister(address + numregs); + if (reg) { + reg->get = cb; + atLeastOne = true; + } } - return false; + return atLeastOne; } -bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t count) { - TRegister* reg = this->searchRegister(address); - if (reg) { - reg->set = cb; - return true; +bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + while (numregs > 0) { + numregs--; + reg = this->searchRegister(address + numregs); + if (reg) { + reg->set = cb; + atLeastOne = true; + } } - return false; -} - - - - + return atLeastOne; +} \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index aaa2bfe..6f233a2 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -86,7 +86,7 @@ class Modbus { TRegister* searchRegister(uint16_t addr); - bool addReg(uint16_t address, uint16_t value = 0, uint16_t count = 1); + bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); @@ -98,14 +98,14 @@ class Modbus { public: - bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t count = 1); + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); bool Hreg(uint16_t offset, uint16_t value); uint16_t Hreg(uint16_t offset); #ifndef USE_HOLDING_REGISTERS_ONLY - bool addCoil(uint16_t offset, bool value = false, uint16_t count = 1); - bool addIsts(uint16_t offset, bool value = false, uint16_t count = 1); - bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t count = 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 Coil(uint16_t offset, bool value); bool Ists(uint16_t offset, bool value); @@ -116,8 +116,8 @@ class Modbus { uint16_t Ireg(uint16_t offset); #endif - bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1); - bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1); + bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); }; #endif //MODBUS_H From 4e7200b8180033fc98581dacde4000ac222c13ab Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 11 Feb 2018 00:20:38 +0500 Subject: [PATCH 020/288] Mass register add fixes --- README.md | 14 +++++++------- src/Modbus.cpp | 36 +++++++++++++++++++++++------------- src/ModbusIP_ESP8266.cpp | 6 +++--- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index af77883..d496b7a 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ Thus, only the following functions are supported: ## API -### Add multiple regs +### Add [multiple] regs ``` -void addHreg(uint16_t offset, uint16_t value = 0, uint16_t count = 1) -void addCoil(uint16_t offset, bool value = false, uint16_t count = 1) -void addIsts(uint16_t offset, bool value = false, uint16_t count = 1) -void addIreg(uint16_t offset, uint16_t value = 0, uint16_t count = 1) +void addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) +void addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) +void addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) +void addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1) ``` ### Write regs ``` @@ -84,8 +84,8 @@ uint16_t Ireg(uint16_t offset) ### Callbacks ``` -bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1) -bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t count = 1) +bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) void onConnect(cbModbusConnect cb) typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) typedef bool (*cbModbusConnect)(IPAddress ip) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 3e0b5b6..b62ae60 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -6,15 +6,15 @@ #include "Modbus.h" uint16_t cbDefault(TRegister* reg, uint16_t val) { +Serial.println(val); return val; } TRegister* Modbus::searchRegister(uint16_t address) { TRegister *reg = _regs_head; - //scan through the linked list until the end of the list or the register is found. //return the pointer. - while (reg) { + while (reg != NULL) { if (reg->address == address) return reg; reg = reg->next; } @@ -26,10 +26,10 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { TRegister *root = NULL; while (numregs > 0) { newreg = (TRegister*) malloc(sizeof(TRegister)); - if (!newreg) { - newreg = root; - while (newreg) { - root = newreg->next; + if (newreg == NULL) { //Cleanup if unable to add all regs + while (root != NULL) { + newreg = root; + root = root->next; free(newreg); } return false; @@ -40,10 +40,18 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { newreg->set = cbDefault; newreg->next = root; root = newreg; - numregs--; address++; + numregs--; } - _regs_head = root; + if (_regs_head == NULL) { + _regs_head = root; + } else { + newreg = _regs_head; + while (newreg->next != NULL) { + newreg = newreg->next; + } + newreg->next = root; + } return true; } @@ -326,7 +334,7 @@ void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { this->exceptionResponse(MB_FC_READ_COILS, MB_EX_SLAVE_FAILURE); return; } - + _frame[_len - 1] = 0; //Clean last probably partial byte _frame[0] = MB_FC_READ_COILS; _frame[1] = _len - 2; //byte count (_len - function code and byte count) @@ -520,12 +528,13 @@ bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; while (numregs > 0) { - numregs--; - reg = this->searchRegister(address + numregs); + reg = this->searchRegister(address); if (reg) { reg->get = cb; atLeastOne = true; } + address++; + numregs--; } return atLeastOne; } @@ -533,12 +542,13 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; while (numregs > 0) { - numregs--; - reg = this->searchRegister(address + numregs); + reg = this->searchRegister(address); if (reg) { reg->set = cb; atLeastOne = true; } + address++; + numregs--; } return atLeastOne; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 9acc79c..2f60259 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -13,11 +13,11 @@ void ModbusIP::begin() { void ModbusIP::task() { uint8_t n, i; - Serial.println("-1-"); + //Serial.println("-1-"); while (hasClient()) { - Serial.println("-2-"); + //Serial.println("-2-"); WiFiClient* currentClient = new WiFiClient(available()); - Serial.println("-3-"); + //Serial.println("-3-"); if (currentClient != NULL && currentClient->connected()) { if (cbConnect == NULL || cbConnect(currentClient->remoteIP())) { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { From 0a9f61a30b531a77668f40b2c503455dd7d91c28 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 11 Feb 2018 12:04:02 +0500 Subject: [PATCH 021/288] Multipe resisters operations example added --- README.md | 5 +- .../TestMassOperations/TestMassOperations.ino | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 examples/TestMassOperations/TestMassOperations.ino diff --git a/README.md b/README.md index d496b7a..4f8657c 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,6 @@ bool Ists(uint16_t offset) uint16_t Ireg(uint16_t offset) ``` ### Callbacks - ``` bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) @@ -107,7 +106,7 @@ void task() ``` bool coil = false; // Define external variable to get/set value -uint16_t cbCoilSet(TRegister* reg, uint16_t val) { +uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value coil = COIL_BOOL(val); return val; // Returns value to be saved to TRegister structure } @@ -116,7 +115,7 @@ uint16_t cbCoilGet(TRegister* reg, uint16_t val) { } bool cbConn(IPAddress ip) { Serial.println(ip); - return true; + return true; // Return 'true' to allow connection or 'false' to drop connection } ModbusIP mb; // ModbusIP object void setup() { diff --git a/examples/TestMassOperations/TestMassOperations.ino b/examples/TestMassOperations/TestMassOperations.ino new file mode 100644 index 0000000..4c208b4 --- /dev/null +++ b/examples/TestMassOperations/TestMassOperations.ino @@ -0,0 +1,78 @@ +/* + Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Used Pins +#ifdef ESP8266 + uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; +#else //ESP32 + uint8_t pinList[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; +#endif +#define LEN sizeof(pinList)/sizeof(uint8_t) + +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + if(reg->address < COIL_BASE) + return 0; + uint8_t offset = reg->address - COIL_BASE; + if(offset >= LEN) + return 0; + return COIL_VAL(digitalRead(pinList[offset])); +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + return reg->value; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(74880); + + WiFi.begin("ssid", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + + mb.addCoil(COIL_BASE, LEN); // Add Coils + mb.onGet(COIL(COIL_BASE), cbRead, LEN); // Add callback on Coil LED_COIL value set + mb.onSet(COIL(COIL_BASE), cbWrite, LEN); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + yield(); +} \ No newline at end of file From d3872f088b60707f4f2fe0d709899c94f11376a1 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 11 Feb 2018 13:02:53 +0500 Subject: [PATCH 022/288] Multipe resisters operations example added --- examples/TestMassOperations/TestMassOperations.ino | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/TestMassOperations/TestMassOperations.ino b/examples/TestMassOperations/TestMassOperations.ino index 4c208b4..c6ff9bb 100644 --- a/examples/TestMassOperations/TestMassOperations.ino +++ b/examples/TestMassOperations/TestMassOperations.ino @@ -35,6 +35,7 @@ uint16_t cbRead(TRegister* reg, uint16_t val) { uint8_t offset = reg->address - COIL_BASE; if(offset >= LEN) return 0; + Serial.println(COIL_VAL(digitalRead(pinList[offset]))); return COIL_VAL(digitalRead(pinList[offset])); } // Callback function to write-protect DI @@ -60,13 +61,14 @@ void setup() { Serial.println(""); Serial.println("WiFi connected"); - Serial.println("IP address: "); + Serial.print("IP address: "); Serial.println(WiFi.localIP()); - + for (uint8_t i = 0; i < LEN; i++) + pinMode(pinList[i], INPUT); mb.onConnect(cbConn); // Add callback on connection event mb.begin(); - mb.addCoil(COIL_BASE, LEN); // Add Coils + mb.addCoil(COIL_BASE, false, LEN); // Add Coils mb.onGet(COIL(COIL_BASE), cbRead, LEN); // Add callback on Coil LED_COIL value set mb.onSet(COIL(COIL_BASE), cbWrite, LEN); } From b532eae6ee33b3dbec85eac30aa8a4674bc256b0 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 11 Feb 2018 18:26:43 +0500 Subject: [PATCH 023/288] Multipe resisters operations example added --- examples/TestMassOperations/TestMassOperations.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/TestMassOperations/TestMassOperations.ino b/examples/TestMassOperations/TestMassOperations.ino index c6ff9bb..6617a5f 100644 --- a/examples/TestMassOperations/TestMassOperations.ino +++ b/examples/TestMassOperations/TestMassOperations.ino @@ -69,7 +69,7 @@ void setup() { mb.begin(); mb.addCoil(COIL_BASE, false, LEN); // Add Coils - mb.onGet(COIL(COIL_BASE), cbRead, LEN); // Add callback on Coil LED_COIL value set + mb.onGet(COIL(COIL_BASE), cbRead, LEN); // Add callback on Coils value get mb.onSet(COIL(COIL_BASE), cbWrite, LEN); } From d4e00ae972c502a964e6615c058589d5dbba4b50 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 11 Feb 2018 18:46:44 +0500 Subject: [PATCH 024/288] Debug info cleanup --- src/Modbus.cpp | 11 +++++------ src/ModbusIP_ESP8266.cpp | 3 --- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index b62ae60..4dde77c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -6,7 +6,6 @@ #include "Modbus.h" uint16_t cbDefault(TRegister* reg, uint16_t val) { -Serial.println(val); return val; } @@ -14,7 +13,7 @@ TRegister* Modbus::searchRegister(uint16_t address) { TRegister *reg = _regs_head; //scan through the linked list until the end of the list or the register is found. //return the pointer. - while (reg != NULL) { + while (reg) { if (reg->address == address) return reg; reg = reg->next; } @@ -26,8 +25,8 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { TRegister *root = NULL; while (numregs > 0) { newreg = (TRegister*) malloc(sizeof(TRegister)); - if (newreg == NULL) { //Cleanup if unable to add all regs - while (root != NULL) { + if (!newreg) { //Cleanup if unable to add all regs + while (root) { newreg = root; root = root->next; free(newreg); @@ -43,11 +42,11 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { address++; numregs--; } - if (_regs_head == NULL) { + if (!_regs_head) { _regs_head = root; } else { newreg = _regs_head; - while (newreg->next != NULL) { + while (newreg->next) { newreg = newreg->next; } newreg->next = root; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 2f60259..f376f3c 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -13,11 +13,8 @@ void ModbusIP::begin() { void ModbusIP::task() { uint8_t n, i; - //Serial.println("-1-"); while (hasClient()) { - //Serial.println("-2-"); WiFiClient* currentClient = new WiFiClient(available()); - //Serial.println("-3-"); if (currentClient != NULL && currentClient->connected()) { if (cbConnect == NULL || cbConnect(currentClient->remoteIP())) { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { From b9d6f37b8534009211d581719fd094ceda053aa7 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 13 Feb 2018 10:22:18 +0500 Subject: [PATCH 025/288] Reply bitmap last byte clean --- src/Modbus.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 4dde77c..b82520d 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -333,9 +333,9 @@ void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { this->exceptionResponse(MB_FC_READ_COILS, MB_EX_SLAVE_FAILURE); return; } - _frame[_len - 1] = 0; //Clean last probably partial byte _frame[0] = MB_FC_READ_COILS; _frame[1] = _len - 2; //byte count (_len - function code and byte count) + _frame[_len - 1] = 0; //Clean last probably partial byte uint8_t bitn = 0; uint16_t totregs = numregs; @@ -366,7 +366,7 @@ void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. if (!this->searchRegister(ISTS(startreg))) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_ADDRESS); return; } @@ -387,6 +387,7 @@ void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { _frame[0] = MB_FC_READ_INPUT_STAT; _frame[1] = _len - 2; + _frame[_len - 1] = 0; //Clean last probably partial byte uint8_t bitn = 0; uint16_t totregs = numregs; @@ -417,7 +418,7 @@ void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. if (!this->searchRegister(IREG(startreg))) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_ADDRESS); return; } From 716c1ec2320419dd9a08004146b7509c4a9d07c9 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 13 Feb 2018 10:45:32 +0500 Subject: [PATCH 026/288] Change examples to addReg notation --- examples/TestCallback/TestCallback.ino | 4 ++-- examples/TestMassOperations/TestMassOperations.ino | 2 +- src/Modbus.cpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index 83c94cc..59e80b9 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -53,14 +53,14 @@ void setup() { Serial.println(""); Serial.println("WiFi connected"); - Serial.println("IP address: "); + Serial.print("IP address: "); Serial.println(WiFi.localIP()); mb.onConnect(cbConn); // Add callback on connection event mb.begin(); pinMode(ledPin, OUTPUT); - mb.addCoil(LED_COIL); // Add Coil + mb.addReg(COIL(LED_COIL)); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) mb.onSet(COIL(LED_COIL), cbLed); // Add callback on Coil LED_COIL value set } diff --git a/examples/TestMassOperations/TestMassOperations.ino b/examples/TestMassOperations/TestMassOperations.ino index 6617a5f..3a3f848 100644 --- a/examples/TestMassOperations/TestMassOperations.ino +++ b/examples/TestMassOperations/TestMassOperations.ino @@ -68,7 +68,7 @@ void setup() { mb.onConnect(cbConn); // Add callback on connection event mb.begin(); - mb.addCoil(COIL_BASE, false, LEN); // Add Coils + mb.addReg(COIL(COIL_BASE), false, LEN); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) mb.onGet(COIL(COIL_BASE), cbRead, LEN); // Add callback on Coils value get mb.onSet(COIL(COIL_BASE), cbWrite, LEN); } diff --git a/src/Modbus.cpp b/src/Modbus.cpp index b82520d..c1a2ad4 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -186,9 +186,9 @@ void Modbus::exceptionResponse(uint8_t fcode, uint8_t excode) { _len = 2; _frame = (uint8_t*) malloc(_len); if (!_frame) { - // Don't send reply if can't build frame - _reply = MB_REPLY_OFF; - return; + // Don't send reply if can't build frame + _reply = MB_REPLY_OFF; + return; } _frame[0] = fcode + 0x80; _frame[1] = excode; From 2a80bb2eecea5ac7b6d4ba9cfd76abbc1ded94c5 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 13 Feb 2018 10:55:26 +0500 Subject: [PATCH 027/288] Declare addReg and Reg as public --- README.md | 17 +++++++++++------ src/Modbus.h | 8 ++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4f8657c..340bef4 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,15 @@ Thus, only the following functions are supported: ### Add [multiple] regs ``` -void addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) -void addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) -void addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) -void addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1) +bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1) +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 nemregs = 1) ``` ### Write regs ``` +bool Reg(uint16_t address, uint16_t value) bool Hreg(uint16_t offset, uint16_t value) bool Coil(uint16_t offset, bool value) bool Ists(uint16_t offset, bool value) @@ -76,6 +78,7 @@ bool Ireg(uint16_t offset, uint16_t value) ``` ### Read regs ``` +uint16_t Reg(uint16_t address) uint16_t Hreg(uint16_t offset) bool Coil(uint16_t offset) bool Ists(uint16_t offset) @@ -88,6 +91,9 @@ bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) void onConnect(cbModbusConnect cb) typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) typedef bool (*cbModbusConnect)(IPAddress ip) +``` +### Macros +``` #define COIL(n) #define ISTS(n) #define IREG(n) @@ -95,8 +101,7 @@ typedef bool (*cbModbusConnect)(IPAddress ip) #define COIL_VAL(v) #define COIL_BOOL(v) ``` -###ModBus IP specific - +### ModBus IP specific ``` void begin() void task() diff --git a/src/Modbus.h b/src/Modbus.h index 6f233a2..c5e526a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -86,10 +86,6 @@ class Modbus { TRegister* searchRegister(uint16_t addr); - bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); - bool Reg(uint16_t address, uint16_t value); - uint16_t Reg(uint16_t address); - protected: uint8_t* _frame; uint8_t _len; @@ -98,6 +94,10 @@ class Modbus { public: + bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); + bool Reg(uint16_t address, uint16_t value); + uint16_t Reg(uint16_t address); + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); bool Hreg(uint16_t offset, uint16_t value); uint16_t Hreg(uint16_t offset); From e64b3742554e4da47c164a2eb921883ae8f10e01 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 13 Feb 2018 14:32:54 +0500 Subject: [PATCH 028/288] malloc control and error processing not-critical mistype fix --- README.md | 8 ++++++-- library.properties | 2 +- src/Modbus.cpp | 10 ++++++++-- src/Modbus.h | 2 ++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4e018e5..9678655 100644 --- a/README.md +++ b/README.md @@ -76,21 +76,25 @@ bool Ists(uint16_t offset) uint16_t Ireg(uint16_t offset) ``` ### Callbacks - ``` bool onGet(uint16_t address, cbModbus cb = cbDefault) bool onSet(uint16_t address, cbModbus cb = cbDefault) void onConnect(cbModbusConnect cb) typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) typedef bool (*cbModbusConnect)(IPAddress ip) +``` +### Macros +``` #define COIL(n) #define ISTS(n) #define IREG(n) #define HERG(n) #define COIL_VAL(v) #define COIL_BOOL(v) +#define ISTS_VAL(v) +#define ISTS_BOOL(v) ``` -###ModBus IP specific +### ModBus IP specific ``` void begin() diff --git a/library.properties b/library.properties index c3712eb..068d823 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=0.1 +version=1.0 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov sentence=Modbus Library for ESP8266/ESP32 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index bff6cee..228decf 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -31,6 +31,7 @@ void Modbus::addReg(uint16_t address, uint16_t value) { TRegister *newreg; newreg = (TRegister *) malloc(sizeof(TRegister)); + if (!newreg) return; // Return if unable to allocate memory newreg->address = address; newreg->value = value; newreg->get = cbDefault; @@ -183,6 +184,11 @@ void Modbus::exceptionResponse(byte fcode, byte excode) { free(_frame); _len = 2; _frame = (byte *) malloc(_len); + if (!_frame) { + // Don't send reply if can't build frame + _reply = MB_REPLY_OFF; + return; + } _frame[0] = fcode + 0x80; _frame[1] = excode; @@ -359,7 +365,7 @@ void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. if (!this->searchRegister(startreg + 10001)) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_ADDRESS); return; } @@ -410,7 +416,7 @@ void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { //Check Address //*** See comments on readCoils method. if (!this->searchRegister(startreg + 30001)) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_ADDRESS); return; } diff --git a/src/Modbus.h b/src/Modbus.h index a0e700a..5f58378 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -20,6 +20,8 @@ #define HERG(n) (n + HREG_BASE) #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) //#define USE_HOLDING_REGISTERS_ONLY From 9049c250955760451d2c1e29ea212b9c8a208b43 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 13 Feb 2018 20:24:53 +0500 Subject: [PATCH 029/288] Remove debug output --- examples/TestMassOperations/TestMassOperations.ino | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/TestMassOperations/TestMassOperations.ino b/examples/TestMassOperations/TestMassOperations.ino index 3a3f848..488d744 100644 --- a/examples/TestMassOperations/TestMassOperations.ino +++ b/examples/TestMassOperations/TestMassOperations.ino @@ -21,7 +21,7 @@ #ifdef ESP8266 uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; #else //ESP32 - uint8_t pinList[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + uint8_t pinList[] = {12, 13, 14, 14, 16, 17, 18, 21, 22, 23}; #endif #define LEN sizeof(pinList)/sizeof(uint8_t) @@ -35,7 +35,6 @@ uint16_t cbRead(TRegister* reg, uint16_t val) { uint8_t offset = reg->address - COIL_BASE; if(offset >= LEN) return 0; - Serial.println(COIL_VAL(digitalRead(pinList[offset]))); return COIL_VAL(digitalRead(pinList[offset])); } // Callback function to write-protect DI @@ -68,7 +67,7 @@ void setup() { mb.onConnect(cbConn); // Add callback on connection event mb.begin(); - mb.addReg(COIL(COIL_BASE), false, LEN); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) + mb.addReg(COIL(COIL_BASE), COIL_VAL(false), LEN); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) mb.onGet(COIL(COIL_BASE), cbRead, LEN); // Add callback on Coils value get mb.onSet(COIL(COIL_BASE), cbWrite, LEN); } From d68edeed7101648fb12118cd4eac7a530cc01a84 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 15 Feb 2018 18:37:36 +0500 Subject: [PATCH 030/288] Modbus master implementation start --- src/Modbus.cpp | 231 ++++++++++++++++++++------------------- src/Modbus.h | 16 ++- src/ModbusIP_ESP8266.cpp | 58 +++++++++- src/ModbusIP_ESP8266.h | 30 ++++- 4 files changed, 209 insertions(+), 126 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index c1a2ad4..8da23e5 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -87,7 +87,6 @@ uint16_t Modbus::Hreg(uint16_t offset) { return Reg(HREG(offset)); } -#ifndef USE_HOLDING_REGISTERS_ONLY bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { return this->addReg(COIL(offset), COIL_VAL(value), numregs); } @@ -123,13 +122,12 @@ uint16_t Modbus::Hreg(uint16_t offset) { uint16_t Modbus::Ireg(uint16_t offset) { return Reg(IREG(offset)); } -#endif void Modbus::receivePDU(uint8_t* frame) { uint8_t fcode = frame[0]; - uint16_t field1 = (word)frame[1] << 8 | (word)frame[2]; - uint16_t field2 = (word)frame[3] << 8 | (word)frame[4]; + uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; + uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; switch (fcode) { @@ -148,7 +146,6 @@ void Modbus::receivePDU(uint8_t* frame) { this->writeMultipleRegisters(frame,field1, field2, frame[5]); break; - #ifndef USE_HOLDING_REGISTERS_ONLY case MB_FC_READ_COILS: //field1 = startreg, field2 = numregs this->readCoils(field1, field2); @@ -174,7 +171,6 @@ void Modbus::receivePDU(uint8_t* frame) { this->writeMultipleCoils(frame,field1, field2, frame[5]); break; - #endif default: this->exceptionResponse(fcode, MB_EX_ILLEGAL_FUNCTION); } @@ -301,113 +297,6 @@ void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t n _reply = MB_REPLY_NORMAL; } -#ifndef USE_HOLDING_REGISTERS_ONLY -void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x07D0) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_VALUE); - return; - } - - //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 (!this->searchRegister(COIL(startreg))) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Clean frame buffer - free(_frame); - _len = 0; - - //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) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_SLAVE_FAILURE); - return; - } - _frame[0] = MB_FC_READ_COILS; - _frame[1] = _len - 2; //byte count (_len - function code and byte count) - _frame[_len - 1] = 0; //Clean last probably partial byte - - uint8_t bitn = 0; - uint16_t totregs = numregs; - uint16_t i; - while (numregs--) { - i = (totregs - numregs) / 8; - if (this->Coil(startreg)) - bitSet(_frame[2+i], bitn); - else - bitClear(_frame[2+i], bitn); - //increment the bit index - bitn++; - if (bitn == 8) bitn = 0; - //increment the register - startreg++; - } - - _reply = MB_REPLY_NORMAL; -} - -void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x07D0) { - this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address - //*** See comments on readCoils method. - if (!this->searchRegister(ISTS(startreg))) { - this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Clean frame buffer - free(_frame); - _len = 0; - - //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) { - this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_READ_INPUT_STAT; - _frame[1] = _len - 2; - _frame[_len - 1] = 0; //Clean last probably partial byte - - uint8_t bitn = 0; - uint16_t totregs = numregs; - uint16_t i; - while (numregs--) { - i = (totregs - numregs) / 8; - if (this->Ists(startreg)) - bitSet(_frame[2+i], bitn); - else - bitClear(_frame[2+i], bitn); - //increment the bit index - bitn++; - if (bitn == 8) bitn = 0; - //increment the register - startreg++; - } - - _reply = MB_REPLY_NORMAL; -} - void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { @@ -523,7 +412,7 @@ void Modbus::writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numou _reply = MB_REPLY_NORMAL; } -#endif + bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; @@ -551,4 +440,116 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { numregs--; } return atLeastOne; -} \ No newline at end of file +} +bool Modbus::getSlaveRegisters(uint16_t startreg, uint16_t numregs) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(length); + if (!_frame) return false; + _frame[0] = MB_FC_READ_REGS; + _frame[1] = startreg >> 8; //0x00; + _frame[2] = startreg & 0x00FF; //0x00; + _frame[3] = numregs >> 8; //0x00; + _frame[4] = numregs & 0x00FF; //0x01; + return true; +} + +void Modbus::responcePDU(uint8_t* frame) { + uint8_t fcode = frame[0]; + if (fcode & 0x80h) { + _reply = MB_REPLY_ERROR; + return; + } + uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; + uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; + + switch (fcode) { + case MB_FC_WRITE_REG: + _reply = MB_REPLY_ECHO; + break; + case MB_FC_READ_REGS: + //field1 = startreg, field2 = status + this->writeMultipleRegisters(frame,field1, field2, frame[5]); + break; + case MB_FC_WRITE_REGS: + _reply = MB_REPLY_ECHO; + break; + case MB_FC_READ_COILS: + //field1 = startreg, field2 = numoutputs + this->writeMultipleCoils(frame,field1, field2, frame[5]); + break; + case MB_FC_READ_INPUT_STAT: + break; + case MB_FC_READ_INPUT_REGS: + break; + case MB_FC_WRITE_COIL: + _reply = MB_REPLY_ECHO; + break; + case MB_FC_WRITE_COILS: + _reply = MB_REPLY_ECHO; + break; + default: + _reply = MB_REPLY_ERROR; + } +} + +void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS) { + //Check value (numregs) + if (numregs < 0x0001 || numregs > 0x07D0) { + this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); + return; + } + + //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 (!this->searchRegister(startreg)) { + this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); + return; + } + + //Clean frame buffer + free(_frame); + _len = 0; + + //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) { + this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); + return; + } + _frame[0] = fn; + _frame[1] = _len - 2; //byte count (_len - function code and byte count) + _frame[_len - 1] = 0; //Clean last probably partial byte + + uint8_t bitn = 0; + uint16_t totregs = numregs; + uint16_t i; + while (numregs--) { + i = (totregs - numregs) / 8; + if (BIT_BOOL(this->Reg(startreg))) + bitSet(_frame[2+i], bitn); + else + bitClear(_frame[2+i], bitn); + //increment the bit index + bitn++; + if (bitn == 8) bitn = 0; + //increment the register + startreg++; + } + + _reply = MB_REPLY_NORMAL; +} + +void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { + this->readBits(COIL(startreg), numregs, MB_FC_READ_COILS); +} +void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { + this->readBits(ISTS(startreg), numreg, MB_FC_READ_INPUT_STAT); +} diff --git a/src/Modbus.h b/src/Modbus.h index d05ca99..e5ea8e5 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -18,6 +18,8 @@ #define ISTS(n) (n + ISTS_BASE) #define IREG(n) (n + IREG_BASE) #define HREG(n) (n + HREG_BASE) +#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) @@ -39,10 +41,12 @@ enum { //Exception Codes enum { - MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported - MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists - MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range - MB_EX_SLAVE_FAILURE = 0x04, // Slave Deive Fails to process request + MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported + MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists + MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range + MB_EX_SLAVE_FAILURE = 0x04, // Slave Deive Fails to process request + MB_EX_ACKNOWLEDGE = 0x05, + MB_EX_SLAVE_DEVICE_BUSY = 0x06 }; //Reply Types @@ -50,6 +54,7 @@ enum { MB_REPLY_OFF = 0x01, MB_REPLY_ECHO = 0x02, MB_REPLY_NORMAL = 0x03, + MB_REPLY_ERROR = 0x04 }; typedef struct TRegister; @@ -84,7 +89,8 @@ class Modbus { #endif TRegister* searchRegister(uint16_t addr); - + void getSlaveRegisters(uint16_t startreg, uint16_t numregs); + //void setSlaveregister protected: uint8_t* _frame; uint8_t _len; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index f376f3c..4f1a914 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -5,10 +5,64 @@ */ #include "ModbusIP_ESP8266.h" -void ModbusIP::begin() { +void ModbusIP::begin(uint8_t mode) { WiFiServer::begin(); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { client[i] = NULL; + server[i] = NULL; + pull[i] = NULL; + pullMs[i] = MODBUSIP_PULL_MS; + pullStatus[i] = 0; + } +} +TRegister* ModbusIP::searchRegister(uint16_t address, IPAddress from) { + int8_t i = getSlaveConnection(from); + if (i == -1) return NULL; + TRegisterList* root = pull[i]; + while (root) { + if (root->reg->address == address) return root->reg; + root = root->next; + } + return NULL; +} +// Add ONE register +bool ModbusIP::pullReg(uint16_t address, IPAddress from, uint32_t interval = MODBUSIP_PULL_MS) { + TRegisterList* reg; + reg = searchReg(address); + if (!reg) return false; + int8_t i = getSlaveConnection(from); + if (i == -1) { + for (i = 0; i < MODBUSIP_MAX_CLIENTS && server[i]; i++) {} + if (i >= MODBEUSIP_MAX_CLIENTS) return false; // No free entries + server[i] = new WiFiClient(); + if (!server[i]) return false; + status[i] = MODBUSIP_SLAVE_DISCONNECTED; + } + if (!pull[i]) { + pull[i] = malloc(sizeof(TRegisterList)); + if (!pull[i]) return false; + pull[i]->reg = reg; + pull[i]->next = NULL; + } + TRegisterList* root = pull[i]; + while (root->next) root = root->next; + if (searchRegister(address, from)) return true; + root->next = malloc(sizeof(TRegisterList)); + if (!root->next) { + return false; //Need to implement cleanup later + } + root->reg = reg; + return true; +} + +bool ModbusIP::unpullReg(uint16_t address, IPAddress from); +bool ModbusIP::setPullMs(IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); +uint8_t ModbusIP::getPoolStatus(IPAddress from); +int8_t ModbusIP::getSlaveConnection(IPAddress address) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + if (server[i].remoteAddress == address) return i; + } + return -1; } void ModbusIP::task() { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 9b5d9fa..c2d2516 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -19,21 +19,43 @@ #define MODBUSIP_MAX_CLIENTS 4 +#define MODBUSIP_SLAVE 1 +#define MODBUSIP_MASTER 2 +#define MODBUSIP_PULL_MS 100 + +typedef struct TRegisterList { + TRegister* reg; + TRegisterList* next; +} TRegisterList; + // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); - +class ModbusMaster : public WiFiClient { + TRegister* pull; + uint32_t pullMs; + uint8_t status; +} class ModbusIP : public Modbus, public WiFiServer { private: uint8_t _MBAP[7]; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; + WiFiClient* server[MODBUSIP_MAX_CLIENTS]; + TRegisterList* pull[MODBUSIP_MAX_CLIENTS]; + uint32_t pullMs[MODBUSIP_MAX_CLIENTS]; + uint8_t pullStatus[MODBUSIP_MAX_CLIENTS]; cbModbusConnect cbConnect = NULL; + WiFiClient* getSlaveConnection(IPAddress address); + TRegister* searchRegister(uint16_t address, IPAddress from); public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { } - void begin(); + void begin(uint8_t mode = MODBUSIP_SLAVE); + bool pullReg(uint16_t address, IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); + bool unpullReg(uint16_t address, IPAddress from); + bool setPullMs(IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); + uint8_t getPoolStatus(IPAddress from); void task(); void onConnect(cbModbusConnect cb); }; -#endif //MODBUSIP_ESP8266_H - +#endif //MODBUSIP_ESP8266_H \ No newline at end of file From 7285b28d2a7d5b8495325aef7ca3c48a92031f4a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 15 Feb 2018 23:14:15 +0500 Subject: [PATCH 031/288] readRegisters/Coils/etc refactoring --- README.md | 20 +--- keywords.txt | 18 ---- library.properties | 4 +- src/Modbus.cpp | 243 +++++++++++++++++++-------------------------- src/Modbus.h | 44 ++++---- 5 files changed, 125 insertions(+), 204 deletions(-) diff --git a/README.md b/README.md index c034d21..f08602e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ used in industrial automation and can be used in other areas, such as home autom The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). -In the current version the library allows the ESP8266/ESP32 operate as a slave, supporting Modbus IP via wireless network. For more information about Modbus see: +In the current version the library allows the ESP8266/ESP32 operate async as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf @@ -13,7 +13,8 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf ## Features
      -
    • Operates as a slave
    • +
    • Operates as a slave, master or both
    • +
    • Fully async operations. No loop locks.
    • Supports Modbus IP (TCP)
    • Reply exception messages for all supported functions
    • Modbus functions supported:
    • @@ -43,21 +44,6 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the CAS Modbus Scanner (http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -3. Early in the library Modbus.h file there is an option to limit the operation -to the functions of Holding Registers, saving space in the program memory. -Just comment out the following line: - -``` -#define USE_HOLDING_REGISTERS_ONLY -``` -Thus, only the following functions are supported: -
        -
      • 0x03 - Read Holding Registers
      • -
      • 0x06 - Write Single Register
      • -
      • 0x10 - Write Multiple Registers
      • -
      - - ## API ### Add [multiple] regs diff --git a/keywords.txt b/keywords.txt index 17c2f28..e86994e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -17,7 +17,6 @@ onConnect KEYWORD2 # Datatypes (KEYWORD1) Modbus KEYWORD1 -TRegister KEYWORD1 # Methods and Functions (KEYWORD2) readCoils KEYWORD2 @@ -28,8 +27,6 @@ writeSingleCoil KEYWORD2 writeSingleRegister KEYWORD2 writeMultipleCoils KEYWORD2 writeMultipleRegisters KEYWORD2 -searchRegister KEYWORD2 -receivePDU KEYWORD2 addReg KEYWORD2 addCoil KEYWORD2 addIsts KEYWORD2 @@ -42,21 +39,6 @@ Ireg KEYWORD2 Hreg KEYWORD2 # Constants (LITERAL1) -MB_FC_READ_COILS LITERAL1 -MB_FC_READ_INPUTS LITERAL1 -MB_FC_READ_REGS LITERAL1 -MB_FC_READ_INPUT_STAT LITERAL1 -MB_FC_WRITE_COIL LITERAL1 -MB_FC_WRITE_REG LITERAL1 -MB_FC_WRITE_COILS LITERAL1 -MB_FC_WRITE_REGS LITERAL1 -MB_REPLY_OFF LITERAL1 -MB_REPLY_ECHO LITERAL1 -MB_REPLY_NORMAL LITERAL1 -COIL_BASE LITERAL1 -ISTS_BASE LITERAL1 -IREG_BASE LITERAL1 -HREG_BASE LITERAL1 COIL LITERAL1 ISTS LITERAL1 IREG LITERAL1 diff --git a/library.properties b/library.properties index 85432ba..74dd8f0 100644 --- a/library.properties +++ b/library.properties @@ -1,8 +1,8 @@ name=modbus-esp8266 -version=1.1 +version=2.0 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov -sentence=Modbus Library for ESP8266/ESP32 +sentence=Modbus Master-Slave Library for ESP8266/ESP32 paragraph=This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 8da23e5..20fae15 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -87,42 +87,41 @@ uint16_t Modbus::Hreg(uint16_t offset) { return Reg(HREG(offset)); } - bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { - return this->addReg(COIL(offset), COIL_VAL(value), numregs); - } - - bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { - return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); - } +bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(COIL(offset), COIL_VAL(value), numregs); +} - bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { - return this->addReg(IREG(offset), value, numregs); - } +bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); +} - bool Modbus::Coil(uint16_t offset, bool value) { - return Reg(COIL(offset), COIL_VAL(value)); - } +bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return this->addReg(IREG(offset), value, numregs); +} - bool Modbus::Ists(uint16_t offset, bool value) { - return Reg(ISTS(offset), ISTS_VAL(value)); - } +bool Modbus::Coil(uint16_t offset, bool value) { + return Reg(COIL(offset), COIL_VAL(value)); +} - bool Modbus::Ireg(uint16_t offset, uint16_t value) { - return Reg(IREG(offset), value); - } +bool Modbus::Ists(uint16_t offset, bool value) { + return Reg(ISTS(offset), ISTS_VAL(value)); +} - bool Modbus::Coil(uint16_t offset) { - return COIL_BOOL(Reg(COIL(offset))); - } +bool Modbus::Ireg(uint16_t offset, uint16_t value) { + return Reg(IREG(offset), value); +} - bool Modbus::Ists(uint16_t offset) { - return ISTS_BOOL(Reg(ISTS(offset))); - } +bool Modbus::Coil(uint16_t offset) { + return COIL_BOOL(Reg(COIL(offset))); +} - uint16_t Modbus::Ireg(uint16_t offset) { - return Reg(IREG(offset)); - } +bool Modbus::Ists(uint16_t offset) { + return ISTS_BOOL(Reg(ISTS(offset))); +} +uint16_t Modbus::Ireg(uint16_t offset) { + return Reg(IREG(offset)); +} void Modbus::receivePDU(uint8_t* frame) { uint8_t fcode = frame[0]; @@ -192,21 +191,81 @@ void Modbus::exceptionResponse(uint8_t fcode, uint8_t excode) { _reply = MB_REPLY_NORMAL; } -void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { +void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { + //Check value (numregs) + if (numregs < 0x0001 || numregs > 0x07D0) { + this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); + return; + } + + //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 (!this->searchRegister(startreg)) { + this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); + return; + } + + //Clean frame buffer + free(_frame); + _len = 0; + + //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) { + this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); + return; + } + _frame[0] = fn; + _frame[1] = _len - 2; //byte count (_len - function code and byte count) + _frame[_len - 1] = 0; //Clean last probably partial byte + + uint8_t bitn = 0; + uint16_t totregs = numregs; + uint16_t i; + while (numregs--) { + i = (totregs - numregs) / 8; + if (BIT_BOOL(this->Reg(startreg))) + bitSet(_frame[2+i], bitn); + else + bitClear(_frame[2+i], bitn); + //increment the bit index + bitn++; + if (bitn == 8) bitn = 0; + //increment the register + startreg++; + } + + _reply = MB_REPLY_NORMAL; +} + +void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { + this->readBits(COIL(startreg), numregs, MB_FC_READ_COILS); +} +void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { + this->readBits(ISTS(startreg), numreg, MB_FC_READ_INPUT_STAT); +} + +void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint32_t fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { - this->exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); return; } //Check Address //*** See comments on readCoils method. - if (!this->searchRegister(HREG(startreg))) { - this->exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_ADDRESS); + if (!this->searchRegister(startreg)) { + this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); return; } - //Clean frame buffer free(_frame); _len = 0; @@ -217,18 +276,18 @@ void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { _frame = (uint8_t*) malloc(_len); if (!_frame) { - this->exceptionResponse(MB_FC_READ_REGS, MB_EX_SLAVE_FAILURE); + this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); return; } - _frame[0] = MB_FC_READ_REGS; + _frame[0] = fn; _frame[1] = _len - 2; //byte count uint16_t val; uint16_t i = 0; while(numregs--) { //retrieve the value from the register bank for the current register - val = this->Hreg(startreg + i); + val = this->Reg(startreg + i); //write the high byte of the register value _frame[2 + i * 2] = val >> 8; //write the low byte of the register value @@ -238,6 +297,12 @@ void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { _reply = MB_REPLY_NORMAL; } +void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { + this->readWords(HREG(startreg), numregs, MB_FC_READ_REGS); +} +void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { + this->readWords(IREG(startreg), numreg, MB_FC_READ_INPUT_REGS); +} void Modbus::writeSingleRegister(uint16_t reg, uint16_t value) { //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using uint16_t (0x0000 - 0x0FFFF) @@ -297,52 +362,6 @@ void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t n _reply = MB_REPLY_NORMAL; } -void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x007D) { - this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address - //*** See comments on readCoils method. - if (!this->searchRegister(IREG(startreg))) { - this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Clean frame buffer - free(_frame); - _len = 0; - - //calculate the query reply message length - //for each register queried add 2 bytes - _len = 2 + numregs * 2; - - _frame = (uint8_t*) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_READ_INPUT_REGS; - _frame[1] = _len - 2; - - uint16_t val; - uint16_t i = 0; - while(numregs--) { - //retrieve the value from the register bank for the current register - val = this->Ireg(startreg + i); - //write the high byte of the register value - _frame[2 + i * 2] = val >> 8; - //write the low byte of the register value - _frame[3 + i * 2] = val & 0xFF; - i++; - } - - _reply = MB_REPLY_NORMAL; -} - void Modbus::writeSingleCoil(uint16_t reg, uint16_t status) { //Check value (status) if (status != 0xFF00 && status != 0x0000) { @@ -493,63 +512,3 @@ void Modbus::responcePDU(uint8_t* frame) { } } -void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x07D0) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); - return; - } - - //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 (!this->searchRegister(startreg)) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Clean frame buffer - free(_frame); - _len = 0; - - //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) { - this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); - return; - } - _frame[0] = fn; - _frame[1] = _len - 2; //byte count (_len - function code and byte count) - _frame[_len - 1] = 0; //Clean last probably partial byte - - uint8_t bitn = 0; - uint16_t totregs = numregs; - uint16_t i; - while (numregs--) { - i = (totregs - numregs) / 8; - if (BIT_BOOL(this->Reg(startreg))) - bitSet(_frame[2+i], bitn); - else - bitClear(_frame[2+i], bitn); - //increment the bit index - bitn++; - if (bitn == 8) bitn = 0; - //increment the register - startreg++; - } - - _reply = MB_REPLY_NORMAL; -} - -void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { - this->readBits(COIL(startreg), numregs, MB_FC_READ_COILS); -} -void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { - this->readBits(ISTS(startreg), numreg, MB_FC_READ_INPUT_STAT); -} diff --git a/src/Modbus.h b/src/Modbus.h index e5ea8e5..f6ffe4f 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -25,8 +25,6 @@ #define ISTS_VAL(v) (v?0xFF00:0x0000) #define ISTS_BOOL(v) (v==0xFF00) -//#define USE_HOLDING_REGISTERS_ONLY - //Function Codes enum { MB_FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx @@ -75,30 +73,29 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { private: TRegister* _regs_head = NULL; - + void readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS); + void readWords(uint16_t startreg, uint16_t numregs, uint32_t fn = MB_FC_READ_REGS); void readRegisters(uint16_t startreg, uint16_t numregs); void writeSingleRegister(uint16_t reg, uint16_t value); void writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); void exceptionResponse(uint8_t fcode, uint8_t excode); - #ifndef USE_HOLDING_REGISTERS_ONLY - void readCoils(uint16_t startreg, uint16_t numregs); - void readInputStatus(uint16_t startreg, uint16_t numregs); - void readInputRegisters(uint16_t startreg, uint16_t numregs); - void writeSingleCoil(uint16_t reg, uint16_t status); - void writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); - #endif + void readCoils(uint16_t startreg, uint16_t numregs); + void readInputStatus(uint16_t startreg, uint16_t numregs); + void readInputRegisters(uint16_t startreg, uint16_t numregs); + void writeSingleCoil(uint16_t reg, uint16_t status); + void writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); TRegister* searchRegister(uint16_t addr); - void getSlaveRegisters(uint16_t startreg, uint16_t numregs); - //void setSlaveregister + void getSlaveRegisters(uint16_t startreg, uint16_t numregs); + protected: uint8_t* _frame; uint8_t _len; uint8_t _reply; void receivePDU(uint8_t* frame); + void responcePDU(uint8_t* frame); public: - bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); @@ -106,20 +103,17 @@ class Modbus { bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); bool Hreg(uint16_t offset, uint16_t value); uint16_t Hreg(uint16_t offset); + 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); - #ifndef USE_HOLDING_REGISTERS_ONLY - 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 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 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); - #endif + bool Coil(uint16_t offset); + bool Ists(uint16_t offset); + uint16_t Ireg(uint16_t offset); bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); From f8459062cfcda36ac27e1182549dffeb0abb80dd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 16 Feb 2018 08:28:58 +0500 Subject: [PATCH 032/288] Sync direction API declaration --- src/Modbus.h | 2 ++ src/ModbusIP_ESP8266.h | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index f6ffe4f..89b6124 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -35,6 +35,8 @@ enum { MB_FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx MB_FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx MB_FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx + MB_FC_MASKWRITE_REG = 0x16, + MB_FC_READWRITE_REGS = 0x17 }; //Exception Codes diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index c2d2516..71a8bbd 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -22,10 +22,12 @@ #define MODBUSIP_SLAVE 1 #define MODBUSIP_MASTER 2 #define MODBUSIP_PULL_MS 100 - +#define MODBUSIP_PUSH 3 +#define MODBUSIP_PULL 4 typedef struct TRegisterList { TRegister* reg; TRegisterList* next; + uint8_t way; } TRegisterList; // Callback function Type @@ -51,7 +53,9 @@ class ModbusIP : public Modbus, public WiFiServer { } void begin(uint8_t mode = MODBUSIP_SLAVE); bool pullReg(uint16_t address, IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); - bool unpullReg(uint16_t address, IPAddress from); + bool pushReg(uint16_t address, IPAddress to, uint32_t interval = MODBUSIP_PULL_MS); + bool connectReg(uint16_t address, IPAddress to, uint32_t interval = MODBUSIP_PULL_MS, uint8_t way = MODBUSIP_PULL); + bool diconnectReg(uint16_t address, IPAddress from); bool setPullMs(IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); uint8_t getPoolStatus(IPAddress from); void task(); From 3b4a3746bbf2bb1e9783b93227618314285c255b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 16 Feb 2018 20:07:46 +0500 Subject: [PATCH 033/288] Modbus master pull/push API implementation start --- src/ModbusIP_ESP8266.cpp | 2 +- src/ModbusIP_ESP8266.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 4f1a914..4bb8184 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -15,7 +15,7 @@ void ModbusIP::begin(uint8_t mode) { pullStatus[i] = 0; } } -TRegister* ModbusIP::searchRegister(uint16_t address, IPAddress from) { +TRegister* ModbusIP::searchRegister(uint16_t address, IPAddress from, uint8_t way = MODBUSIP_PULL) { int8_t i = getSlaveConnection(from); if (i == -1) return NULL; TRegisterList* root = pull[i]; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 71a8bbd..13c9c4d 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -47,7 +47,7 @@ class ModbusIP : public Modbus, public WiFiServer { uint8_t pullStatus[MODBUSIP_MAX_CLIENTS]; cbModbusConnect cbConnect = NULL; WiFiClient* getSlaveConnection(IPAddress address); - TRegister* searchRegister(uint16_t address, IPAddress from); + TRegister* searchRegister(uint16_t address, IPAddress from, uint8_t way = MODBUSIP_PULL) { public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { } From d74094f64eb4dca4c7605ab61fc4db7b997b902a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 19 Feb 2018 23:37:08 +0500 Subject: [PATCH 034/288] Misc --- src/ModbusIP_ESP8266.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 13c9c4d..15626ce 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -33,8 +33,8 @@ typedef struct TRegisterList { // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); class ModbusMaster : public WiFiClient { - TRegister* pull; - uint32_t pullMs; + TRegister* reg; + uint32_t interval; uint8_t status; } class ModbusIP : public Modbus, public WiFiServer { From 48c1b2bed252900e00e6afb0b5c5d4c78905dac0 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 23 Feb 2018 21:32:57 +0500 Subject: [PATCH 035/288] eventSource API --- src/Modbus.h | 10 +++++----- src/ModbusIP_ESP8266.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index 89b6124..321cc0d 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -75,20 +75,20 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { private: TRegister* _regs_head = NULL; - void readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS); - void readWords(uint16_t startreg, uint16_t numregs, uint32_t fn = MB_FC_READ_REGS); + void readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS); + void readWords(uint16_t startreg, uint16_t numregs, uint32_t fn = MB_FC_READ_REGS); void readRegisters(uint16_t startreg, uint16_t numregs); void writeSingleRegister(uint16_t reg, uint16_t value); - void writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); + void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); void exceptionResponse(uint8_t fcode, uint8_t excode); void readCoils(uint16_t startreg, uint16_t numregs); void readInputStatus(uint16_t startreg, uint16_t numregs); void readInputRegisters(uint16_t startreg, uint16_t numregs); void writeSingleCoil(uint16_t reg, uint16_t status); - void writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); + void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); TRegister* searchRegister(uint16_t addr); - void getSlaveRegisters(uint16_t startreg, uint16_t numregs); + void getSlaveRegisters(uint16_t startreg, uint16_t numregs); protected: uint8_t* _frame; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 13c9c4d..780bc2e 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -60,6 +60,7 @@ class ModbusIP : public Modbus, public WiFiServer { uint8_t getPoolStatus(IPAddress from); void task(); void onConnect(cbModbusConnect cb); + IPAddreess eventSource(); }; #endif //MODBUSIP_ESP8266_H \ No newline at end of file From 46d7a40c4d3cc5ebd324a69d91527770be262455 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 4 Mar 2018 23:35:54 +0500 Subject: [PATCH 036/288] Modbus style refactoring --- README.md | 12 +- examples/.DS_Store | Bin 0 -> 10244 bytes .../MultipleHRegDebug/MultipleHRegDebug.ino | 79 ++++++++++++ src/Modbus.cpp | 100 +++------------ src/Modbus.h | 116 +++++++++++++----- src/ModbusIP_ESP8266.cpp | 72 ++++++++++- src/ModbusIP_ESP8266.h | 14 ++- 7 files changed, 271 insertions(+), 122 deletions(-) create mode 100644 examples/.DS_Store create mode 100644 examples/MultipleHRegDebug/MultipleHRegDebug.ino diff --git a/README.md b/README.md index f08602e..24d0db5 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,19 @@ uint16_t Ireg(uint16_t offset) ``` ### Callbacks ``` -bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) void onConnect(cbModbusConnect cb) typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) typedef bool (*cbModbusConnect)(IPAddress ip) +bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) ``` ### Macros ``` diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..29973dde78af21ccb70da8b7aa585e7f39785c8b GIT binary patch literal 10244 zcmeHMU2GLa6h7xtV1_Mpi%45QTq(cdLT(|H%AdFQwnzm6;oj0xe#+jv3vAf#*1LOe zfl^aVG(I3k(HQ@Y`i2QlB*qsLHU316KWY4pKIn^&Mx%*OdS+&CBZZPa0Uy91_`jLR5- z7=f!15X+_(@?b*%;T8M$BAhG?1%n0GFHoOP3WKf_gsIfGn3z^Gea1}2X0chbXC2O+ z@P~4t8J2^LSvn|cd(H4fA-d1|zB_(z7WO;#P`^Ge>wBT;cy>t|JC;pd4)r*mn#_Y2g^=)daS-qpTiAp z3D+;PLl(KQ+qc9>mTb`sZ8>8Hg+5!dskG!vtz4z?d_ZpKCgW1GW-aIWlQg$_K(8QP zeBFAkb-QN4c6Ez0soL1awVsOdRo@vYw`}EFZ_ytZAQyvrOp;o>la|psnEo+4BF5fj z2H~DT+Y`e#HZ^QFH0^-Uw_BENG1i=3sit3|(40=Gnl7SE6C`09?1Mfif(Juz4xWJX z@FctlZ^9+G4DZ56@ELpoU&2@L1N;KN!5{D^`~`o*zsQiIhPBv)ORyQ2VhZoYZJ5S( z%-~Mki~BH#c|43owDA}o$3Z-WXK)zL;-h#TpTei{0=|SVyRsw9!~c_k}}-3zoueDP%L?j{b}r4CacSq zQ#CBeW6Sw0I>>@tqu^W4^7yF2hmgJ*@B@D2P72@T?_z1D`F?^hOc>$lnXYmz$ zV*(4is{B(D-?|QOY8TexpiC6Ds6!JV|*RPZe>lD9>I<7oECDyEN&A{iu?3 aN9l(D84&OPT|NjrU6AM=W literal 0 HcmV?d00001 diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/MultipleHRegDebug/MultipleHRegDebug.ino new file mode 100644 index 0000000..1b06d88 --- /dev/null +++ b/examples/MultipleHRegDebug/MultipleHRegDebug.ino @@ -0,0 +1,79 @@ +/* + Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +#define LEN 10 + +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + Serial.print("Read. Reg RAW#: "); + Serial.print(reg->address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + Serial.print("Write. Reg RAW#: "); + Serial.print(reg->address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(74880); + + WiFi.begin("ssid", "pass"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + + if (!mb.addReg(HREG(0), 0xF0F0, LEN)) Serial.println("Error"); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) + mb.onGet(HREG(0), cbRead, LEN); // Add callback on Coils value get + mb.onSet(HREG(0), cbWrite, LEN); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + yield(); +} diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 20fae15..8c9863b 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,6 +1,6 @@ /* - Modbus.h - Header for Modbus Base Library - Copyright (C) 2014 André Sarmento Barbosa + Modbus.cpp - Modbus Base Library Implementation + Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" @@ -75,54 +75,6 @@ uint16_t Modbus::Reg(uint16_t address) { return 0; } -bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { - return this->addReg(HREG(offset), value, numregs); -} - -bool Modbus::Hreg(uint16_t offset, uint16_t value) { - return Reg(HREG(offset), value); -} - -uint16_t Modbus::Hreg(uint16_t offset) { - return Reg(HREG(offset)); -} - -bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { - return this->addReg(COIL(offset), COIL_VAL(value), numregs); -} - -bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { - return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); -} - -bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { - return this->addReg(IREG(offset), value, numregs); -} - -bool Modbus::Coil(uint16_t offset, bool value) { - return Reg(COIL(offset), COIL_VAL(value)); -} - -bool Modbus::Ists(uint16_t offset, bool value) { - return Reg(ISTS(offset), ISTS_VAL(value)); -} - -bool Modbus::Ireg(uint16_t offset, uint16_t value) { - return Reg(IREG(offset), value); -} - -bool Modbus::Coil(uint16_t offset) { - return COIL_BOOL(Reg(COIL(offset))); -} - -bool Modbus::Ists(uint16_t offset) { - return ISTS_BOOL(Reg(ISTS(offset))); -} - -uint16_t Modbus::Ireg(uint16_t offset) { - return Reg(IREG(offset)); -} - void Modbus::receivePDU(uint8_t* frame) { uint8_t fcode = frame[0]; uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; @@ -245,13 +197,6 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { _reply = MB_REPLY_NORMAL; } -void Modbus::readCoils(uint16_t startreg, uint16_t numregs) { - this->readBits(COIL(startreg), numregs, MB_FC_READ_COILS); -} -void Modbus::readInputStatus(uint16_t startreg, uint16_t numregs) { - this->readBits(ISTS(startreg), numreg, MB_FC_READ_INPUT_STAT); -} - void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint32_t fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { @@ -297,12 +242,6 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint32_t fn) { _reply = MB_REPLY_NORMAL; } -void Modbus::readRegisters(uint16_t startreg, uint16_t numregs) { - this->readWords(HREG(startreg), numregs, MB_FC_READ_REGS); -} -void Modbus::readInputRegisters(uint16_t startreg, uint16_t numregs) { - this->readWords(IREG(startreg), numreg, MB_FC_READ_INPUT_REGS); -} void Modbus::writeSingleRegister(uint16_t reg, uint16_t value) { //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using uint16_t (0x0000 - 0x0FFFF) @@ -460,55 +399,56 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { } return atLeastOne; } -bool Modbus::getSlaveRegisters(uint16_t startreg, uint16_t numregs) { +bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(length); if (!_frame) return false; - _frame[0] = MB_FC_READ_REGS; - _frame[1] = startreg >> 8; //0x00; - _frame[2] = startreg & 0x00FF; //0x00; - _frame[3] = numregs >> 8; //0x00; - _frame[4] = numregs & 0x00FF; //0x01; + _frame[0] = fn; + _frame[1] = startreg >> 8; + _frame[2] = startreg & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; return true; } void Modbus::responcePDU(uint8_t* frame) { uint8_t fcode = frame[0]; if (fcode & 0x80h) { - _reply = MB_REPLY_ERROR; - return; + _reply = MB_REPLY_ERROR; + return; } uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; switch (fcode) { - case MB_FC_WRITE_REG: - _reply = MB_REPLY_ECHO; - break; case MB_FC_READ_REGS: //field1 = startreg, field2 = status this->writeMultipleRegisters(frame,field1, field2, frame[5]); - break; - case MB_FC_WRITE_REGS: - _reply = MB_REPLY_ECHO; + _reply = MB_REPLY_OFF; break; case MB_FC_READ_COILS: //field1 = startreg, field2 = numoutputs - this->writeMultipleCoils(frame,field1, field2, frame[5]); + //this->writeMultipleCoils(frame,field1, field2, frame[5]); + //_reply = MB_REPLY_OFF; break; case MB_FC_READ_INPUT_STAT: + _reply = MB_REPLY_OFF; break; case MB_FC_READ_INPUT_REGS: + _reply = MB_REPLY_OFF; + break; + case MB_FC_WRITE_REG: + break; + case MB_FC_WRITE_REGS: break; case MB_FC_WRITE_COIL: - _reply = MB_REPLY_ECHO; break; case MB_FC_WRITE_COILS: - _reply = MB_REPLY_ECHO; break; default: _reply = MB_REPLY_ERROR; } + _reply = MB_REPLY_OFF; } diff --git a/src/Modbus.h b/src/Modbus.h index 321cc0d..e063bb4 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,6 +1,6 @@ /* Modbus.h - Header for Modbus Base Library - Copyright (C) 2014 André Sarmento Barbosa + Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Arduino.h" @@ -35,8 +35,8 @@ enum { MB_FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx MB_FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx MB_FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx - MB_FC_MASKWRITE_REG = 0x16, - MB_FC_READWRITE_REGS = 0x17 + MB_FC_MASKWRITE_REG = 0x16, // Not implemented + MB_FC_READWRITE_REGS = 0x17 // Not implemented }; //Exception Codes @@ -45,16 +45,17 @@ enum { MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range MB_EX_SLAVE_FAILURE = 0x04, // Slave Deive Fails to process request - MB_EX_ACKNOWLEDGE = 0x05, - MB_EX_SLAVE_DEVICE_BUSY = 0x06 + MB_EX_ACKNOWLEDGE = 0x05, // Not used + MB_EX_SLAVE_DEVICE_BUSY = 0x06 // Not used }; //Reply Types enum { - MB_REPLY_OFF = 0x01, - MB_REPLY_ECHO = 0x02, - MB_REPLY_NORMAL = 0x03, - MB_REPLY_ERROR = 0x04 + MB_REPLY_OFF = 0x01, + MB_REPLY_ECHO = 0x02, + MB_REPLY_NORMAL = 0x03, + MB_REPLY_ERROR = 0x04, + MB_REPLY_UNEXPECTED = 0x05 }; typedef struct TRegister; @@ -75,20 +76,29 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { private: TRegister* _regs_head = NULL; - void readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS); - void readWords(uint16_t startreg, uint16_t numregs, uint32_t fn = MB_FC_READ_REGS); - void readRegisters(uint16_t startreg, uint16_t numregs); + void readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS); + void readWords(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); + void readRegisters(uint16_t startreg, uint16_t numregs) { + readWords(HREG(startreg), numregs, MB_FC_READ_REGS); + } void writeSingleRegister(uint16_t reg, uint16_t value); void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); void exceptionResponse(uint8_t fcode, uint8_t excode); - void readCoils(uint16_t startreg, uint16_t numregs); - void readInputStatus(uint16_t startreg, uint16_t numregs); - void readInputRegisters(uint16_t startreg, uint16_t numregs); + void readCoils(uint16_t startreg, uint16_t numregs) { + readBits(COIL(startreg), numregs, MB_FC_READ_COILS); + } + void readInputStatus(uint16_t startreg, uint16_t numregs) { + readBits(ISTS(startreg), numreg, MB_FC_READ_INPUT_STAT); + } + void readInputRegisters(uint16_t startreg, uint16_t numregs) { + this->readWords(IREG(startreg), numreg, MB_FC_READ_INPUT_REGS); + } void writeSingleCoil(uint16_t reg, uint16_t status); void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); TRegister* searchRegister(uint16_t addr); - void getSlaveRegisters(uint16_t startreg, uint16_t numregs); + void readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); + //void writeSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_REGS); protected: uint8_t* _frame; @@ -102,23 +112,69 @@ class Modbus { bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); - bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); - bool Hreg(uint16_t offset, uint16_t value); - uint16_t Hreg(uint16_t offset); - 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 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); + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + return addReg(HREG(offset), value, numregs); + } + bool Hreg(uint16_t offset, uint16_t value) { + return Reg(HREG(offset), value); + } + uint16_t Hreg(uint16_t offset) { + return Reg(HREG(offset)); + } + bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { + return addReg(COIL(offset), COIL_VAL(value), numregs); + } + bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { + return addReg(ISTS(offset), ISTS_VAL(value), numregs); + } + bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + return addReg(IREG(offset), value, numregs); + } + bool Coil(uint16_t offset, bool value) { + return Reg(COIL(offset), COIL_VAL(value)); + } + bool Ists(uint16_t offset, bool value) { + return Reg(ISTS(offset), ISTS_VAL(value)); + } + bool Ireg(uint16_t offset, uint16_t value) { + return Reg(IREG(offset), value); + } + bool Coil(uint16_t offset) { + return COIL_BOOL(Reg(COIL(offset))); + } + bool Ists(uint16_t offset) { + return ISTS_BOOL(Reg(ISTS(offset))); + } + uint16_t Ireg(uint16_t offset) { + return Reg(IREG(offset)); + } bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onGet(COIL(address), cb, numregs); + } + bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onSet(COIL(address), cb, numresg); + } + bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onGet(HREG(address), cb, numregs); + } + bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onSet(HREG(address), cb, numresg); + } + bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onGet(ISTS(address), cb, numregs); + } + bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onSet(ISTS(address), cb, numresg); + } + bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onGet(IREG(address), cb, numregs); + } + bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onSet(IREG(address), cb, numresg); + } }; #endif //MODBUS_H diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 4bb8184..ee2cc6f 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -1,6 +1,6 @@ /* Modbus.h - Header for Modbus Base Library - Copyright (C) 2014 André Sarmento Barbosa + Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "ModbusIP_ESP8266.h" @@ -55,18 +55,29 @@ bool ModbusIP::pullReg(uint16_t address, IPAddress from, uint32_t interval = MOD return true; } -bool ModbusIP::unpullReg(uint16_t address, IPAddress from); -bool ModbusIP::setPullMs(IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); -uint8_t ModbusIP::getPoolStatus(IPAddress from); +bool ModbusIP::unpullReg(uint16_t address, IPAddress from) { + +} +bool ModbusIP::setPullMs(IPAddress from, uint32_t interval = MODBUSIP_PULL_MS) { + +} +uint8_t ModbusIP::getPoolStatus(IPAddress from) { + +} int8_t ModbusIP::getSlaveConnection(IPAddress address) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (server[i].remoteAddress == address) return i; } return -1; } +IPAddreess eventSource() { // Returns IP of current processing client query + if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) + return client[n]->remoteIP(); + return INADDR_NONE; +} void ModbusIP::task() { - uint8_t n, i; + uint8_t i; while (hasClient()) { WiFiClient* currentClient = new WiFiClient(available()); if (currentClient != NULL && currentClient->connected()) { @@ -128,6 +139,57 @@ void ModbusIP::task() { _len = 0; } } + n = -1; +} + +void ModbusIP::master() { + for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + if (_regs[n] == NULL) + continue; + if (!server[n]) { + server[n] = new WiFiClient(); + } + if (!server[n]->connected()) { + server[n]->connect(_ip[n], MODBUSIP_PORT); + continue; + } + if (status[n] == MODBUSIP_IDLE) { + + } + uint16_t raw_len = 0; + raw_len = client[n]->available(); + if (raw_len > 7) { + for (i = 0; i < 7; i++) _MBAP[i] = client[n]->read(); //Get MBAP + + _len = _MBAP[4] << 8 | _MBAP[5]; + _len--; // Do not count with last byte from MBAP + if (_MBAP[2] != 0 || _MBAP[3] != 0) continue; //Not a MODBUSIP packet + if (_len > MODBUSIP_MAXFRAME) continue; //Length is over MODBUSIP_MAXFRAME + _frame = (uint8_t*) malloc(_len); + + raw_len = raw_len - 7; + for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU + + this->receivePDU(_frame); + client[n]->flush(); + + if (_reply != MB_REPLY_OFF) { + //MBAP + _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP + _MBAP[5] = (_len+1) & 0x00FF; + + size_t send_len = (uint16_t)_len + 7; + uint8_t sbuf[send_len]; + + for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; + for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; + + client[n]->write(sbuf, send_len); + } + free(_frame); + _len = 0; + } + } } void ModbusIP::onConnect(cbModbusConnect cb = NULL) { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 647d932..e71f37b 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,6 +1,6 @@ /* - Modbus.h - Header for Modbus Base Library - Copyright (C) 2014 André Sarmento Barbosa + ModbusIP.h - Header for ModbusIP Library + Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include @@ -24,6 +24,7 @@ #define MODBUSIP_PULL_MS 100 #define MODBUSIP_PUSH 3 #define MODBUSIP_PULL 4 +#define MODBUSIP_IDLE 5 typedef struct TRegisterList { TRegister* reg; TRegisterList* next; @@ -42,12 +43,15 @@ class ModbusIP : public Modbus, public WiFiServer { uint8_t _MBAP[7]; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; WiFiClient* server[MODBUSIP_MAX_CLIENTS]; - TRegisterList* pull[MODBUSIP_MAX_CLIENTS]; + TRegisterList* _regs[MODBUSIP_MAX_CLIENTS]; + IPAddress _ip[MODBUSIP_MAX_CLIENTS]; uint32_t pullMs[MODBUSIP_MAX_CLIENTS]; - uint8_t pullStatus[MODBUSIP_MAX_CLIENTS]; + uint8_t status[MODBUSIP_MAX_CLIENTS]; + uint32_t lastChange[MODBUSIP_MAX_CLIENTS]; cbModbusConnect cbConnect = NULL; WiFiClient* getSlaveConnection(IPAddress address); - TRegister* searchRegister(uint16_t address, IPAddress from, uint8_t way = MODBUSIP_PULL) { + TRegister* searchRegister(uint16_t address, IPAddress from, uint8_t way = MODBUSIP_PULL); + int8_t n = -1; public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { } From 1398feeded4f82b7b22395bc83659c05a13f8209 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 6 Mar 2018 08:39:21 +0500 Subject: [PATCH 037/288] Shift to ModbusMasterIP --- src/ModbusIP_ESP8266.cpp | 2 +- src/ModbusIP_ESP8266.h | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index ee2cc6f..770e66a 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -12,7 +12,7 @@ void ModbusIP::begin(uint8_t mode) { server[i] = NULL; pull[i] = NULL; pullMs[i] = MODBUSIP_PULL_MS; - pullStatus[i] = 0; + status[i] = MODBUSIP_IDLE; } } TRegister* ModbusIP::searchRegister(uint16_t address, IPAddress from, uint8_t way = MODBUSIP_PULL) { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index e71f37b..59fb19c 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -33,10 +33,31 @@ typedef struct TRegisterList { // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); -class ModbusMaster : public WiFiClient { - TRegister* reg; +class ModbusMasterIP : public Modbus, public WiFiClient { + private: + TRegisterList* reg; uint32_t interval; uint8_t status; + IPAddress ip; + uint32_t lastChange; + void connect(IPAddress address); + public: + ModbusMasterIP() : WiFiClient() { + + } + void task(); + void pushBits(uint16_t address, uint16_t numregs); + void pullBits(uint16_t address, uint16_t numregs; + void pushWords(uint16_t address, uint16_t numregs); + void pullWords(uint16_t address, uint16_t numregs; + void pushCoil(); + void pullCoil(); + void pushIsts(); + void pullIsts(); + void pushHreg(); + void pullHreg(); + void pushIreg(); + void pullIreg(); } class ModbusIP : public Modbus, public WiFiServer { private: From ab5019c31ffb3ec0806051d59d6ac9a094585ddc Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 6 Mar 2018 18:02:05 +0500 Subject: [PATCH 038/288] ModbusIP cleanup from test code --- API.md | 117 +++++++++++++++++++++++++++++++++++++++ README.md | 2 + keywords.txt | 69 +++++++++++------------ src/Modbus.cpp | 60 ++++++++++++++++---- src/Modbus.h | 20 ++++--- src/ModbusIP_ESP8266.cpp | 112 +------------------------------------ src/ModbusIP_ESP8266.h | 43 +++++--------- 7 files changed, 231 insertions(+), 192 deletions(-) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..27fccc6 --- /dev/null +++ b/API.md @@ -0,0 +1,117 @@ +# Modbus Library for ESP8266/ESP32 + +## API + +### Add [multiple] regs +``` +bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1) +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 nemregs = 1) +``` +### Write regs +``` +bool Reg(uint16_t address, uint16_t value) +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) +``` +### Read regs +``` +uint16_t Reg(uint16_t address) +uint16_t Hreg(uint16_t offset) +bool Coil(uint16_t offset) +bool Ists(uint16_t offset) +uint16_t Ireg(uint16_t offset) +``` +### Callbacks +`void cbEnable(bool state = TRUE) +Callback generation control. Callback generation is enabled by default. + +`void cbDisable() +Disable callback generation. + +`void onConnect(cbModbusConnect cb) +Assign callback function on new incoming connection event. + +`typedef bool (*cbModbusConnect)(IPAddress ip) +Connect event callback function definition. Client IP address is passed as argument. + +`typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) + +bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +``` +### Macros +``` +#define COIL(n) +#define ISTS(n) +#define IREG(n) +#define HREG(n) +#define COIL_VAL(v) +#define COIL_BOOL(v) +#define ISTS_VAL(v) +#define ISTS_BOOL(v) +``` +### ModBus IP specific +``` +void begin() +void task() +``` + +### Callback example + +``` +bool coil = false; // Define external variable to get/set value +uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value + coil = COIL_BOOL(val); + return val; // Returns value to be saved to TRegister structure +} +uint16_t cbCoilGet(TRegister* reg, uint16_t val) { + return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request +} +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; // Return 'true' to allow connection or 'false' to drop connection +} +ModbusIP mb; // ModbusIP object +void setup() { +... + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + mb.addCoil(COIL_NR); // Add Coil + mb.onSet(COIL(COIL_NR), cbCoilSet); // Add callback on Coil COIL_NR value set + mb.onGet(COIL(COIL_NR), cbCoilGet); // Add callback on Coil COIL_NR value get +... +} +void loop() { +... + mb.task(); +... +} +``` + + +## Contributions + +https://github.com/emelianov/modbus-esp8266
      +a.m.emelianov@gmail.com + +Original version:
      +http://github.com/andresarmento/modbus-esp8266
      +prof (at) andresarmento (dot) com + +## License + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. + diff --git a/README.md b/README.md index 24d0db5..53aa692 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ uint16_t Ireg(uint16_t offset) ``` ### Callbacks ``` +void cbEnable(bool state = TRUE) +void cbDisable() void onConnect(cbModbusConnect cb) typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) typedef bool (*cbModbusConnect)(IPAddress ip) diff --git a/keywords.txt b/keywords.txt index e86994e..0a1051a 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,7 +1,9 @@ -# Syntax Coloring Map For ModbusIP +# Syntax Coloring Map For ModbusIP_ESP8266 # Datatypes (KEYWORD1) -ModbusIP KEYWORD1 +Modbus KEYWORD1 +ModbusIP KEYWORD1 +ModbusMasterIP KEYWORD1 ModbusIP_ESP8266 KEYWORD1 # Methods and Functions (KEYWORD2) @@ -10,36 +12,35 @@ task KEYWORD2 onGet KEYWORD2 onSet KEYWORD2 onConnect KEYWORD2 +cbEnable KEYWORD2 +cbDisable KEYWORD2 +onGetCoil KEYWORD2 +onSetCoil KEYWORD2 +onGetHreg KEYWORD2 +onSetHreg KEYWORD2 +onGetIreg KEYWORD2 +onSetIreg KEYWORD2 +onGetIsts KEYWORD2 +onSetIsts KEYWORD2 +addReg KEYWORD2 +addCoil KEYWORD2 +addIsts KEYWORD2 +addIreg KEYWORD2 +addHreg KEYWORD2 +Reg KEYWORD2 +Coil KEYWORD2 +Ists KEYWORD2 +Ireg KEYWORD2 +Hreg KEYWORD2 -# Constants (LITERAL1) - -# Syntax Coloring Map For Modbus - -# Datatypes (KEYWORD1) -Modbus KEYWORD1 - -# Methods and Functions (KEYWORD2) -readCoils KEYWORD2 -readInputStatus KEYWORD2 -readRegisters KEYWORD2 -readInputRegisters KEYWORD2 -writeSingleCoil KEYWORD2 -writeSingleRegister KEYWORD2 -writeMultipleCoils KEYWORD2 -writeMultipleRegisters KEYWORD2 -addReg KEYWORD2 -addCoil KEYWORD2 -addIsts KEYWORD2 -addIreg KEYWORD2 -addHreg KEYWORD2 -Reg KEYWORD2 -Coil KEYWORD2 -Ists KEYWORD2 -Ireg KEYWORD2 -Hreg KEYWORD2 - -# Constants (LITERAL1) -COIL LITERAL1 -ISTS LITERAL1 -IREG LITERAL1 -HERG LITERAL1 \ No newline at end of file +# Constants and Macros (LITERAL1) +COIL LITERAL1 +ISTS LITERAL1 +IREG LITERAL1 +HERG LITERAL1 +BIT_VAL LITERAL1 +BIT_BOOL LITERAL1 +COIL_VAL LITERAL1 +COIL_BOOL LITERAL1 +ISTS_VAL LITERAL1 +ISTS_BOOL LITERAL1 \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 8c9863b..a58842f 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -60,7 +60,11 @@ bool Modbus::Reg(uint16_t address, uint16_t value) { reg = this->searchRegister(address); //if found then assign the register value to the new value. if (reg) { - reg->value = reg->set(reg, value); + if (cbEnabled) { + reg->value = reg->set(reg, value); + } else { + reg->value = value; + } return true; } else return false; @@ -70,7 +74,11 @@ uint16_t Modbus::Reg(uint16_t address) { TRegister *reg; reg = this->searchRegister(address); if(reg) - return reg->get(reg, reg->value); + if (cbEnabled) { + return reg->get(reg, reg->value); + } esle { + return reg->value; + } else return 0; } @@ -323,19 +331,19 @@ void Modbus::writeSingleCoil(uint16_t reg, uint16_t status) { _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount) { +void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, uint8_t fh = MB_FC_WRITE_COILS) { //Check value uint16_t bytecount_calc = numoutputs / 8; if (numoutputs%8) bytecount_calc++; if (numoutputs < 0x0001 || numoutputs > 0x07B0 || bytecount != bytecount_calc) { - this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); return; } //Check Address (startreg...startreg + numregs) for (int k = 0; k < numoutputs; k++) { if (!this->searchRegister(COIL(startreg) + k)) { - this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); return; } } @@ -345,11 +353,11 @@ void Modbus::writeMultipleCoils(uint8_t* frame,uint16_t startreg, uint16_t numou _len = 5; _frame = (uint8_t*) malloc(_len); if (!_frame) { - this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_SLAVE_FAILURE); + this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); return; } - _frame[0] = MB_FC_WRITE_COILS; + _frame[0] = fn; _frame[1] = startreg >> 8; _frame[2] = startreg & 0x00FF; _frame[3] = numoutputs >> 8; @@ -399,10 +407,37 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { } return atLeastOne; } + bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn) { free(_frame); _len = 5; - _frame = (uint8_t*) malloc(length); + _frame = (uint8_t*) malloc(_len); + if (!_frame) return false; + _frame[0] = fn; + _frame[1] = startreg >> 8; + _frame[2] = startreg & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + return true; +} + +bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(_len; + if (!_frame) return false; + _frame[0] = fn; + _frame[1] = startreg >> 8; + _frame[2] = startreg & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + return true; +} + +bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(_len); if (!_frame) return false; _frame[0] = fn; _frame[1] = startreg >> 8; @@ -424,13 +459,13 @@ void Modbus::responcePDU(uint8_t* frame) { switch (fcode) { case MB_FC_READ_REGS: //field1 = startreg, field2 = status - this->writeMultipleRegisters(frame,field1, field2, frame[5]); + this->writeMultipleRegisters(frame, field1, field2, frame[5]); _reply = MB_REPLY_OFF; break; case MB_FC_READ_COILS: //field1 = startreg, field2 = numoutputs - //this->writeMultipleCoils(frame,field1, field2, frame[5]); - //_reply = MB_REPLY_OFF; + this->writeMultipleCoils(frame, field1, field2, frame[5]); + _reply = MB_REPLY_OFF; break; case MB_FC_READ_INPUT_STAT: _reply = MB_REPLY_OFF; @@ -452,3 +487,6 @@ void Modbus::responcePDU(uint8_t* frame) { _reply = MB_REPLY_OFF; } +void Modbus::cbEnable(bool state = TRUE) { + cbEnabled = state; +} \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index e063bb4..ffbabbb 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -97,9 +97,10 @@ class Modbus { void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); TRegister* searchRegister(uint16_t addr); - void readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); - //void writeSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_REGS); - + bool readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); + bool writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_COILS); + bool writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_REGS); + bool cbEnabled = true; protected: uint8_t* _frame; uint8_t _len; @@ -148,14 +149,17 @@ class Modbus { uint16_t Ireg(uint16_t offset) { return Reg(IREG(offset)); } - + void cbEnable(bool state = TRUE); + void cbDisable() { + cbEnable(FALSE); + } bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onGet(COIL(address), cb, numregs); + bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onGet(COIL(offset), cb, numregs); } - bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onSet(COIL(address), cb, numresg); + bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + onSet(COIL(offset), cb, numresg); } bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { onGet(HREG(address), cb, numregs); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 770e66a..59c3d12 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -5,72 +5,14 @@ */ #include "ModbusIP_ESP8266.h" -void ModbusIP::begin(uint8_t mode) { +void ModbusIP::begin() { WiFiServer::begin(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { client[i] = NULL; - server[i] = NULL; - pull[i] = NULL; - pullMs[i] = MODBUSIP_PULL_MS; - status[i] = MODBUSIP_IDLE; } } -TRegister* ModbusIP::searchRegister(uint16_t address, IPAddress from, uint8_t way = MODBUSIP_PULL) { - int8_t i = getSlaveConnection(from); - if (i == -1) return NULL; - TRegisterList* root = pull[i]; - while (root) { - if (root->reg->address == address) return root->reg; - root = root->next; - } - return NULL; -} -// Add ONE register -bool ModbusIP::pullReg(uint16_t address, IPAddress from, uint32_t interval = MODBUSIP_PULL_MS) { - TRegisterList* reg; - reg = searchReg(address); - if (!reg) return false; - int8_t i = getSlaveConnection(from); - if (i == -1) { - for (i = 0; i < MODBUSIP_MAX_CLIENTS && server[i]; i++) {} - if (i >= MODBEUSIP_MAX_CLIENTS) return false; // No free entries - server[i] = new WiFiClient(); - if (!server[i]) return false; - status[i] = MODBUSIP_SLAVE_DISCONNECTED; - } - if (!pull[i]) { - pull[i] = malloc(sizeof(TRegisterList)); - if (!pull[i]) return false; - pull[i]->reg = reg; - pull[i]->next = NULL; - } - TRegisterList* root = pull[i]; - while (root->next) root = root->next; - if (searchRegister(address, from)) return true; - root->next = malloc(sizeof(TRegisterList)); - if (!root->next) { - return false; //Need to implement cleanup later - } - root->reg = reg; - return true; -} -bool ModbusIP::unpullReg(uint16_t address, IPAddress from) { - -} -bool ModbusIP::setPullMs(IPAddress from, uint32_t interval = MODBUSIP_PULL_MS) { - -} -uint8_t ModbusIP::getPoolStatus(IPAddress from) { - -} -int8_t ModbusIP::getSlaveConnection(IPAddress address) { - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - if (server[i].remoteAddress == address) return i; - } - return -1; -} -IPAddreess eventSource() { // Returns IP of current processing client query +IPAddreess ModbusIP::eventSource() { // Returns IP of current processing client query if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) return client[n]->remoteIP(); return INADDR_NONE; @@ -142,56 +84,6 @@ void ModbusIP::task() { n = -1; } -void ModbusIP::master() { - for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - if (_regs[n] == NULL) - continue; - if (!server[n]) { - server[n] = new WiFiClient(); - } - if (!server[n]->connected()) { - server[n]->connect(_ip[n], MODBUSIP_PORT); - continue; - } - if (status[n] == MODBUSIP_IDLE) { - - } - uint16_t raw_len = 0; - raw_len = client[n]->available(); - if (raw_len > 7) { - for (i = 0; i < 7; i++) _MBAP[i] = client[n]->read(); //Get MBAP - - _len = _MBAP[4] << 8 | _MBAP[5]; - _len--; // Do not count with last byte from MBAP - if (_MBAP[2] != 0 || _MBAP[3] != 0) continue; //Not a MODBUSIP packet - if (_len > MODBUSIP_MAXFRAME) continue; //Length is over MODBUSIP_MAXFRAME - _frame = (uint8_t*) malloc(_len); - - raw_len = raw_len - 7; - for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU - - this->receivePDU(_frame); - client[n]->flush(); - - if (_reply != MB_REPLY_OFF) { - //MBAP - _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP - _MBAP[5] = (_len+1) & 0x00FF; - - size_t send_len = (uint16_t)_len + 7; - uint8_t sbuf[send_len]; - - for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; - for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; - - client[n]->write(sbuf, send_len); - } - free(_frame); - _len = 0; - } - } -} - void ModbusIP::onConnect(cbModbusConnect cb = NULL) { cbConnect = cb; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 59fb19c..8f81e7b 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -25,31 +25,26 @@ #define MODBUSIP_PUSH 3 #define MODBUSIP_PULL 4 #define MODBUSIP_IDLE 5 -typedef struct TRegisterList { - TRegister* reg; - TRegisterList* next; - uint8_t way; -} TRegisterList; // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); + class ModbusMasterIP : public Modbus, public WiFiClient { private: - TRegisterList* reg; - uint32_t interval; + TRegister* reg; + TRegister* next; uint8_t status; IPAddress ip; - uint32_t lastChange; + uint32_t queryStart; + uint32_t timeout; void connect(IPAddress address); public: ModbusMasterIP() : WiFiClient() { - } - void task(); - void pushBits(uint16_t address, uint16_t numregs); - void pullBits(uint16_t address, uint16_t numregs; - void pushWords(uint16_t address, uint16_t numregs); - void pullWords(uint16_t address, uint16_t numregs; + void pushBits(uint16_t address, uint16_t numregs, uint8_t fn); + void pullBits(uint16_t address, uint16_t numregs, uint8_t fn); + void pushWords(uint16_t address, uint16_t numregs, uint8_t fn); + void pullWords(uint16_t address, uint16_t numregs, uint8_t fn); void pushCoil(); void pullCoil(); void pushIsts(); @@ -58,31 +53,21 @@ class ModbusMasterIP : public Modbus, public WiFiClient { void pullHreg(); void pushIreg(); void pullIreg(); + public: + void task(); + uint16_t regGroupsCount(); } + class ModbusIP : public Modbus, public WiFiServer { private: uint8_t _MBAP[7]; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - WiFiClient* server[MODBUSIP_MAX_CLIENTS]; - TRegisterList* _regs[MODBUSIP_MAX_CLIENTS]; - IPAddress _ip[MODBUSIP_MAX_CLIENTS]; - uint32_t pullMs[MODBUSIP_MAX_CLIENTS]; - uint8_t status[MODBUSIP_MAX_CLIENTS]; - uint32_t lastChange[MODBUSIP_MAX_CLIENTS]; cbModbusConnect cbConnect = NULL; - WiFiClient* getSlaveConnection(IPAddress address); - TRegister* searchRegister(uint16_t address, IPAddress from, uint8_t way = MODBUSIP_PULL); int8_t n = -1; public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { } - void begin(uint8_t mode = MODBUSIP_SLAVE); - bool pullReg(uint16_t address, IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); - bool pushReg(uint16_t address, IPAddress to, uint32_t interval = MODBUSIP_PULL_MS); - bool connectReg(uint16_t address, IPAddress to, uint32_t interval = MODBUSIP_PULL_MS, uint8_t way = MODBUSIP_PULL); - bool diconnectReg(uint16_t address, IPAddress from); - bool setPullMs(IPAddress from, uint32_t interval = MODBUSIP_PULL_MS); - uint8_t getPoolStatus(IPAddress from); + void begin(); void task(); void onConnect(cbModbusConnect cb); IPAddreess eventSource(); From 94691d7d526b2d139febb1ba3ba91a904043f69a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 7 Mar 2018 08:30:00 +0500 Subject: [PATCH 039/288] Cosmetic changes --- src/Modbus.cpp | 30 +++++++++++++------------- src/Modbus.h | 57 ++++++++++++++++++++++++++------------------------ 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index a58842f..b0ea0c9 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -22,22 +22,22 @@ TRegister* Modbus::searchRegister(uint16_t address) { bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { TRegister *newreg; - TRegister *root = NULL; + TRegister *root = NULL; while (numregs > 0) { newreg = (TRegister*) malloc(sizeof(TRegister)); if (!newreg) { //Cleanup if unable to add all regs while (root) { - newreg = root; - root = root->next; + newreg = root; + root = root->next; free(newreg); } return false; } newreg->address = address; - newreg->value = value; - newreg->get = cbDefault; - newreg->set = cbDefault; - newreg->next = root; + newreg->value = value; + newreg->get = cbDefault; + newreg->set = cbDefault; + newreg->next = root; root = newreg; address++; numregs--; @@ -66,7 +66,7 @@ bool Modbus::Reg(uint16_t address, uint16_t value) { reg->value = value; } return true; - } else + } else return false; } @@ -76,7 +76,7 @@ uint16_t Modbus::Reg(uint16_t address) { if(reg) if (cbEnabled) { return reg->get(reg, reg->value); - } esle { + } else { return reg->value; } else @@ -170,7 +170,6 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { //Clean frame buffer free(_frame); - _len = 0; //Determine the message length = function type, byte count and //for each group of 8 registers the message length increases by 1 @@ -221,7 +220,6 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint32_t fn) { //Clean frame buffer free(_frame); - _len = 0; //calculate the query reply message length //for each register queried add 2 bytes @@ -309,29 +307,29 @@ void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t n _reply = MB_REPLY_NORMAL; } -void Modbus::writeSingleCoil(uint16_t reg, uint16_t status) { +void Modbus::writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn) { //Check value (status) if (status != 0xFF00 && status != 0x0000) { - this->exceptionResponse(MB_FC_WRITE_COIL, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); return; } //Check Address and execute (reg exists?) if (!this->Coil(reg, (bool)status)) { - this->exceptionResponse(MB_FC_WRITE_COIL, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); return; } //Check for failure if (this->Coil(reg) != (bool)status) { - this->exceptionResponse(MB_FC_WRITE_COIL, MB_EX_SLAVE_FAILURE); + this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); return; } _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, uint8_t fh = MB_FC_WRITE_COILS) { +void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, uint8_t fh) { //Check value uint16_t bytecount_calc = numoutputs / 8; if (numoutputs%8) bytecount_calc++; diff --git a/src/Modbus.h b/src/Modbus.h index ffbabbb..54d422b 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -26,7 +26,7 @@ #define ISTS_BOOL(v) (v==0xFF00) //Function Codes -enum { +enum modbusFunctionCode { MB_FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx MB_FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx MB_FC_READ_REGS = 0x03, // Read Holding Registers 4xxxx @@ -40,7 +40,7 @@ enum { }; //Exception Codes -enum { +enum modbusResultCode { MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range @@ -50,12 +50,12 @@ enum { }; //Reply Types -enum { - MB_REPLY_OFF = 0x01, - MB_REPLY_ECHO = 0x02, - MB_REPLY_NORMAL = 0x03, - MB_REPLY_ERROR = 0x04, - MB_REPLY_UNEXPECTED = 0x05 +enum modbusReplyCode { + MB_REPLY_OFF = 0x01, + MB_REPLY_ECHO = 0x02, + MB_REPLY_NORMAL = 0x03, + MB_REPLY_ERROR = 0x04, + MB_REPLY_UNEXPECTED = 0x05 }; typedef struct TRegister; @@ -69,7 +69,7 @@ typedef struct TRegister { struct TRegister* next; cbModbus get; cbModbus set; -} TRegister; +}; uint16_t cbDefault(TRegister* reg, uint16_t val); @@ -81,8 +81,8 @@ class Modbus { void readRegisters(uint16_t startreg, uint16_t numregs) { readWords(HREG(startreg), numregs, MB_FC_READ_REGS); } - void writeSingleRegister(uint16_t reg, uint16_t value); - void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); + void writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn = MB_FC_WRITE_REG); + void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_REGS); void exceptionResponse(uint8_t fcode, uint8_t excode); void readCoils(uint16_t startreg, uint16_t numregs) { readBits(COIL(startreg), numregs, MB_FC_READ_COILS); @@ -93,10 +93,11 @@ class Modbus { void readInputRegisters(uint16_t startreg, uint16_t numregs) { this->readWords(IREG(startreg), numreg, MB_FC_READ_INPUT_REGS); } - void writeSingleCoil(uint16_t reg, uint16_t status); - void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount); + void writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn = MB_FC_WRITE_COIL); + void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); TRegister* searchRegister(uint16_t addr); + bool readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); bool writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_COILS); bool writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_REGS); @@ -149,35 +150,37 @@ class Modbus { uint16_t Ireg(uint16_t offset) { return Reg(IREG(offset)); } + void cbEnable(bool state = TRUE); void cbDisable() { cbEnable(FALSE); } bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onGet(COIL(offset), cb, numregs); + return onGet(COIL(offset), cb, numregs); } bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onSet(COIL(offset), cb, numresg); + return onSet(COIL(offset), cb, numresg); } - bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onGet(HREG(address), cb, numregs); + bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onGet(HREG(offset), cb, numregs); } - bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onSet(HREG(address), cb, numresg); + bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onSet(HREG(offset), cb, numresg); } - bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onGet(ISTS(address), cb, numregs); + bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onGet(ISTS(offset), cb, numregs); } - bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onSet(ISTS(address), cb, numresg); + bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onSet(ISTS(offset), cb, numresg); } - bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onGet(IREG(address), cb, numregs); + bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onGet(IREG(offset), cb, numregs); } - bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) { - onSet(IREG(address), cb, numresg); + bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onSet(IREG(offset), cb, numresg); } }; From 5b92e4eb05ff7abdd824f0443ab7862ec69960ed Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 7 Mar 2018 17:08:20 +0500 Subject: [PATCH 040/288] Move common part of ModbusIP services to root class --- API.md | 15 ++++++----- keywords.txt | 1 + src/Modbus.h | 9 +++---- src/ModbusIP_ESP8266.cpp | 25 ++++++++++++++++-- src/ModbusIP_ESP8266.h | 55 +++++++++++++++++++++++++--------------- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/API.md b/API.md index 27fccc6..65421b0 100644 --- a/API.md +++ b/API.md @@ -27,21 +27,24 @@ bool Ists(uint16_t offset) uint16_t Ireg(uint16_t offset) ``` ### Callbacks -`void cbEnable(bool state = TRUE) +`void cbEnable(bool state = TRUE)` Callback generation control. Callback generation is enabled by default. -`void cbDisable() +`void cbDisable()` Disable callback generation. -`void onConnect(cbModbusConnect cb) +`void onConnect(cbModbusConnect cb)` Assign callback function on new incoming connection event. -`typedef bool (*cbModbusConnect)(IPAddress ip) +`typedef bool (*cbModbusConnect)(IPAddress ip)` Connect event callback function definition. Client IP address is passed as argument. -`typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) +`typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val)` +Get/Set register callback function definition. Pointer to TRegister structure of the register and new value are passed as arguments. + +`bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` +Assign register get value callback. -bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) diff --git a/keywords.txt b/keywords.txt index 0a1051a..bf07273 100644 --- a/keywords.txt +++ b/keywords.txt @@ -14,6 +14,7 @@ onSet KEYWORD2 onConnect KEYWORD2 cbEnable KEYWORD2 cbDisable KEYWORD2 +eventSource KEYWORD2 onGetCoil KEYWORD2 onSetCoil KEYWORD2 onGetHreg KEYWORD2 diff --git a/src/Modbus.h b/src/Modbus.h index 54d422b..cdcb2a5 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -3,10 +3,9 @@ Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ -#include "Arduino.h" +#pragma once -#ifndef MODBUS_H -#define MODBUS_H +#include "Arduino.h" #define MAX_REGS 32 #define MAX_FRAME 128 @@ -182,6 +181,4 @@ class Modbus { bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(IREG(offset), cb, numresg); } -}; - -#endif //MODBUS_H +}; \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 59c3d12..87ee4d2 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -1,5 +1,5 @@ /* - Modbus.h - Header for Modbus Base Library + ModbusIP_ESP8266.cpp - ModbusIP Library Implementation Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ @@ -84,6 +84,27 @@ void ModbusIP::task() { n = -1; } -void ModbusIP::onConnect(cbModbusConnect cb = NULL) { +//void ModbusIP::onConnect(cbModbusConnect cb = NULL) { +// cbConnect = cb; +//} + +void ModbusMasterIP::connect(IPAddress address) { +} +void ModbusMasterIP::pushBits(uint16_t address, uint16_t numregs, uint8_t fn){ +} +void ModbusMasterIP::pullBits(uint16_t address, uint16_t numregs, uint8_t fn) { +} +void ModbusMasterIP::pushWords(uint16_t address, uint16_t numregs, uint8_t fn) { +} +void ModbusMasterIP::pullWords(uint16_t address, uint16_t numregs, uint8_t fn) { +} +void ModbusMasterIP::task() { +} +uint16_t ModbusMasterIP::regGroupsCount() { +} +IPAddreess ModbusMasterIP::eventSource() { +} + +void ModbusCoreIP::onConnect(cbModbusConnect cb = NULL) { cbConnect = cb; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 8f81e7b..0a0278c 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,8 +1,10 @@ /* - ModbusIP.h - Header for ModbusIP Library + ModbusIP_ESP8266.h - Header for ModbusIP Library Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ +#pragma once + #include #ifdef ESP8266 #include @@ -10,9 +12,6 @@ #include #endif -#ifndef MODBUSIP_ESP8266_H -#define MODBUSIP_ESP8266_H - #define MODBUSIP_PORT 502 #define MODBUSIP_MAXFRAME 200 #define MODBUSIP_TIMEOUT 10 @@ -29,7 +28,16 @@ // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); -class ModbusMasterIP : public Modbus, public WiFiClient { +class ModbusCoreIP : public Modbus { + private: + uint8_t _MBAP[7]; + cbModbusConnect cbConnect = NULL; + public: + void onConnect(cbModbusConnect cb); + virtual IPAddreess eventSource(); +} + +class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { private: TRegister* reg; TRegister* next; @@ -45,32 +53,39 @@ class ModbusMasterIP : public Modbus, public WiFiClient { void pullBits(uint16_t address, uint16_t numregs, uint8_t fn); void pushWords(uint16_t address, uint16_t numregs, uint8_t fn); void pullWords(uint16_t address, uint16_t numregs, uint8_t fn); - void pushCoil(); - void pullCoil(); - void pushIsts(); - void pullIsts(); - void pushHreg(); - void pullHreg(); - void pushIreg(); - void pullIreg(); + void pushCoil() { + } + void pullCoil() { + } + void pushIsts() { + } + void pullIsts() { + } + void pushHreg() { + } + void pullHreg() { + } + void pushIreg() { + } + void pullIreg() { + } public: void task(); uint16_t regGroupsCount(); + IPAddreess eventSource(); } -class ModbusIP : public Modbus, public WiFiServer { +class ModbusIP : public ModbusCoreIP, public WiFiServer { private: - uint8_t _MBAP[7]; + //uint8_t _MBAP[7]; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - cbModbusConnect cbConnect = NULL; + //cbModbusConnect cbConnect = NULL; int8_t n = -1; public: ModbusIP() : WiFiServer(MODBUSIP_PORT) { } void begin(); void task(); - void onConnect(cbModbusConnect cb); + //void onConnect(cbModbusConnect cb); IPAddreess eventSource(); -}; - -#endif //MODBUSIP_ESP8266_H \ No newline at end of file +}; \ No newline at end of file From ce3fe916f4bbf3a04d6832a602147a6d4c576a9e Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 12 Mar 2018 08:57:13 +0500 Subject: [PATCH 041/288] Working version --- API.md | 48 ++++++++++++++++++++------- examples/Master/Master.ino | 54 ++++++++++++++++++++++++++++++ src/Modbus.cpp | 14 ++++---- src/Modbus.h | 27 +++++++-------- src/ModbusIP_ESP8266.cpp | 4 +-- src/ModbusIP_ESP8266.h | 68 ++++++++++++++++++++++++++++++++++---- 6 files changed, 174 insertions(+), 41 deletions(-) create mode 100644 examples/Master/Master.ino diff --git a/API.md b/API.md index 65421b0..d1425f5 100644 --- a/API.md +++ b/API.md @@ -3,50 +3,70 @@ ## API ### Add [multiple] regs -``` + +```c bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1) 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 nemregs = 1) ``` + ### Write regs -``` + +```c bool Reg(uint16_t address, uint16_t value) 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) ``` + ### Read regs -``` + +```c uint16_t Reg(uint16_t address) uint16_t Hreg(uint16_t offset) bool Coil(uint16_t offset) bool Ists(uint16_t offset) uint16_t Ireg(uint16_t offset) ``` + ### Callbacks + `void cbEnable(bool state = TRUE)` + Callback generation control. Callback generation is enabled by default. `void cbDisable()` + Disable callback generation. `void onConnect(cbModbusConnect cb)` + Assign callback function on new incoming connection event. `typedef bool (*cbModbusConnect)(IPAddress ip)` + Connect event callback function definition. Client IP address is passed as argument. `typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val)` + Get/Set register callback function definition. Pointer to TRegister structure of the register and new value are passed as arguments. `bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` + Assign register get value callback. -bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +`bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` + +Assign callback function on register modify event. + +`bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` + +Assign callback function on register modify event. + +```c bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) @@ -55,8 +75,10 @@ bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) ``` + ### Macros -``` + +```c #define COIL(n) #define ISTS(n) #define IREG(n) @@ -67,14 +89,14 @@ bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) #define ISTS_BOOL(v) ``` ### ModBus IP specific -``` +```c void begin() void task() ``` ### Callback example -``` +```c bool coil = false; // Define external variable to get/set value uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value coil = COIL_BOOL(val); @@ -107,14 +129,16 @@ void loop() { ## Contributions -https://github.com/emelianov/modbus-esp8266
      +https://github.com/emelianov/modbus-esp8266 + a.m.emelianov@gmail.com -Original version:
      -http://github.com/andresarmento/modbus-esp8266
      +Original version: + +http://github.com/andresarmento/modbus-esp8266 + prof (at) andresarmento (dot) com ## License The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. - diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino new file mode 100644 index 0000000..0e16b9d --- /dev/null +++ b/examples/Master/Master.ino @@ -0,0 +1,54 @@ +/* + Modbus-Arduino Example - Master (Modbus IP ESP8266) + Control a Led on GPIO0 pin using Write Single Coil Modbus Function + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +//Modbus Registers Offsets (0-9999) +const int LED_COIL = 100; +//Used Pins +const int ledPin = 0; //GPIO0 + +//ModbusIP object +ModbusMasterIP mb; + +void setup() { + Serial.begin(74880); + + WiFi.begin("your_ssid", "your_password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.begin(); + + pinMode(ledPin, OUTPUT); + mb.addCoil(LED_COIL); + mb.readSlave(COIL(LED_COIL), 1, MB_FC_READ_COILS); + mb.send(); +} + +void loop() { + //Call once inside loop() - all magic here + mb.get(); + + //Attach ledPin to LED_COIL register + digitalWrite(ledPin, mb.Coil(LED_COIL)); +} \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index b0ea0c9..6d1c016 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -204,7 +204,7 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { _reply = MB_REPLY_NORMAL; } -void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint32_t fn) { +void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint8_t fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); @@ -249,7 +249,7 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint32_t fn) { _reply = MB_REPLY_NORMAL; } -void Modbus::writeSingleRegister(uint16_t reg, uint16_t value) { +void Modbus::writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn) { //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using uint16_t (0x0000 - 0x0FFFF) //Check Address and execute (reg exists?) if (!this->Hreg(reg, value)) { @@ -266,7 +266,7 @@ void Modbus::writeSingleRegister(uint16_t reg, uint16_t value) { _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount) { +void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn) { //Check value if (numoutputs < 0x0001 || numoutputs > 0x007B || bytecount != 2 * numoutputs) { this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_VALUE); @@ -329,7 +329,7 @@ void Modbus::writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode f _reply = MB_REPLY_ECHO; } -void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, uint8_t fh) { +void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn) { //Check value uint16_t bytecount_calc = numoutputs / 8; if (numoutputs%8) bytecount_calc++; @@ -422,7 +422,7 @@ bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn) { bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { free(_frame); _len = 5; - _frame = (uint8_t*) malloc(_len; + _frame = (uint8_t*) malloc(_len); if (!_frame) return false; _frame[0] = fn; _frame[1] = startreg >> 8; @@ -447,7 +447,7 @@ bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn) { void Modbus::responcePDU(uint8_t* frame) { uint8_t fcode = frame[0]; - if (fcode & 0x80h) { + if ((fcode & 0x80) != 0) { _reply = MB_REPLY_ERROR; return; } @@ -485,6 +485,6 @@ void Modbus::responcePDU(uint8_t* frame) { _reply = MB_REPLY_OFF; } -void Modbus::cbEnable(bool state = TRUE) { +void Modbus::cbEnable(bool state) { cbEnabled = state; } \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index cdcb2a5..dc2aef2 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -87,21 +87,18 @@ class Modbus { readBits(COIL(startreg), numregs, MB_FC_READ_COILS); } void readInputStatus(uint16_t startreg, uint16_t numregs) { - readBits(ISTS(startreg), numreg, MB_FC_READ_INPUT_STAT); + readBits(ISTS(startreg), numregs, MB_FC_READ_INPUT_STAT); } void readInputRegisters(uint16_t startreg, uint16_t numregs) { - this->readWords(IREG(startreg), numreg, MB_FC_READ_INPUT_REGS); + this->readWords(IREG(startreg), numregs, MB_FC_READ_INPUT_REGS); } void writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn = MB_FC_WRITE_COIL); void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); TRegister* searchRegister(uint16_t addr); - - bool readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); - bool writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_COILS); - bool writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_REGS); bool cbEnabled = true; - protected: + + protected: uint8_t* _frame; uint8_t _len; uint8_t _reply; @@ -109,6 +106,10 @@ class Modbus { void responcePDU(uint8_t* frame); public: + bool readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); + bool writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_COILS); + bool writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_REGS); + bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); @@ -150,9 +151,9 @@ class Modbus { return Reg(IREG(offset)); } - void cbEnable(bool state = TRUE); + void cbEnable(bool state = true); void cbDisable() { - cbEnable(FALSE); + cbEnable(false); } bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); @@ -161,24 +162,24 @@ class Modbus { return onGet(COIL(offset), cb, numregs); } bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(COIL(offset), cb, numresg); + return onSet(COIL(offset), cb, numregs); } bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(HREG(offset), cb, numregs); } bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(HREG(offset), cb, numresg); + return onSet(HREG(offset), cb, numregs); } bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(ISTS(offset), cb, numregs); } bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(ISTS(offset), cb, numresg); + return onSet(ISTS(offset), cb, numregs); } bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(IREG(offset), cb, numregs); } bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(IREG(offset), cb, numresg); + return onSet(IREG(offset), cb, numregs); } }; \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 87ee4d2..28d391d 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -12,7 +12,7 @@ void ModbusIP::begin() { } } -IPAddreess ModbusIP::eventSource() { // Returns IP of current processing client query +IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) return client[n]->remoteIP(); return INADDR_NONE; @@ -102,7 +102,7 @@ void ModbusMasterIP::task() { } uint16_t ModbusMasterIP::regGroupsCount() { } -IPAddreess ModbusMasterIP::eventSource() { +IPAddress ModbusMasterIP::eventSource() { } void ModbusCoreIP::onConnect(cbModbusConnect cb = NULL) { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 0a0278c..9d21178 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -27,15 +27,25 @@ // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); - +typedef union MBAP { + struct { + uint16_t transactionId; + uint16_t protocolId; + uint16_t length; + uint8_t unitId; + }; + uint8_t raw[7]; +}; class ModbusCoreIP : public Modbus { - private: + protected: uint8_t _MBAP[7]; cbModbusConnect cbConnect = NULL; public: void onConnect(cbModbusConnect cb); - virtual IPAddreess eventSource(); -} + virtual IPAddress eventSource() { + + } +}; class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { private: @@ -53,9 +63,47 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pullBits(uint16_t address, uint16_t numregs, uint8_t fn); void pushWords(uint16_t address, uint16_t numregs, uint8_t fn); void pullWords(uint16_t address, uint16_t numregs, uint8_t fn); + void send() { + uint16_t i; + //MBAP + _MBAP[0] = 0; + _MBAP[1] = 1; + _MBAP[2] = 0; + _MBAP[3] = 0; + _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP + _MBAP[5] = (_len+1) & 0x00FF; + + size_t send_len = (uint16_t)_len + 7; + uint8_t sbuf[send_len]; + + for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; + for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; + write(sbuf, send_len); + } + void get() { + uint8_t i; + if (!connected()) return; + uint16_t raw_len = 0; + raw_len = available(); + if (raw_len > 7) { + for (i = 0; i < 7; i++) _MBAP[i] = read(); //Get MBAP + _len = _MBAP[4] << 8 | _MBAP[5]; + _len--; // Do not count with last byte from MBAP + if (_MBAP[2] == 0 && _MBAP[3] == 0 && _len < MODBUSIP_MAXFRAME) { + _frame = (uint8_t*) malloc(_len); + raw_len = raw_len - 7; + for (i = 0; i < _len; i++) + _frame[i] = read(); //Get Modbus PDU + responcePDU(_frame); + } + flush(); + } + } void pushCoil() { } void pullCoil() { + // readSlave(COIL(offset), numregs, MB_FC_READ_COILS); + // send(); } void pushIsts() { } @@ -70,10 +118,16 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pullIreg() { } public: + void begin() { + } void task(); uint16_t regGroupsCount(); - IPAddreess eventSource(); -} + IPAddress eventSource(); + bool pullReg(uint16_t address, uint16_t numregs) { + addReg(address, numregs); + } + bool pushReg(uint16_t address, uint16_t numregs); +}; class ModbusIP : public ModbusCoreIP, public WiFiServer { private: @@ -87,5 +141,5 @@ class ModbusIP : public ModbusCoreIP, public WiFiServer { void begin(); void task(); //void onConnect(cbModbusConnect cb); - IPAddreess eventSource(); + IPAddress eventSource(); }; \ No newline at end of file From 290e17836a8e5e4a111a6a55902103261dedc297 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 25 Mar 2018 21:48:48 +0500 Subject: [PATCH 042/288] Documentation --- .vscode/arduino.json | 5 ++++ .vscode/c_cpp_properties.json | 22 ++++++++++++++++++ API.md | 44 ++++++++++++++++++++++++++++------- examples/Master/Master.ino | 1 + 4 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 .vscode/arduino.json create mode 100644 .vscode/c_cpp_properties.json diff --git a/.vscode/arduino.json b/.vscode/arduino.json new file mode 100644 index 0000000..96e66a1 --- /dev/null +++ b/.vscode/arduino.json @@ -0,0 +1,5 @@ +{ + "board": "esp8266:esp8266:nodemcuv2", + "configuration": "CpuFrequency=80,FlashSize=4M1M,LwIPVariant=v2mss536,Debug=Serial,DebugLevel=None____,FlashErase=none,UploadSpeed=921600", + "port": "/dev/cu.SLAB_USBtoUART" +} \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..bae8b0a --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "/Users/aem/Library/Arduino15/packages/esp8266/hardware/esp8266/2.4.1/cores/esp8266" + ], + "browse": { + "limitSymbolsToIncludedHeaders": false, + "path": [ + "/Users/aem/Library/Arduino15/packages/esp8266/hardware/esp8266/2.4.1/cores/esp8266" + ] + }, + "intelliSenseMode": "clang-x64", + "macFrameworkPath": [ + "/System/Library/Frameworks", + "/Library/Frameworks" + ] + } + ], + "version": 3 +} \ No newline at end of file diff --git a/API.md b/API.md index d1425f5..a82c6bf 100644 --- a/API.md +++ b/API.md @@ -6,9 +6,21 @@ ```c bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1) +``` + +```c bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) +``` + +```c bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) +``` + +```c bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) +``` + +```c bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1) ``` @@ -34,35 +46,51 @@ uint16_t Ireg(uint16_t offset) ### Callbacks -`void cbEnable(bool state = TRUE)` +```c +void cbEnable(bool state = TRUE) +``` Callback generation control. Callback generation is enabled by default. -`void cbDisable()` +```c +void cbDisable() +``` Disable callback generation. -`void onConnect(cbModbusConnect cb)` +```c +void onConnect(cbModbusConnect cb) +``` Assign callback function on new incoming connection event. -`typedef bool (*cbModbusConnect)(IPAddress ip)` +```c +typedef bool (*cbModbusConnect)(IPAddress ip) +``` Connect event callback function definition. Client IP address is passed as argument. -`typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val)` +```c +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) +``` Get/Set register callback function definition. Pointer to TRegister structure of the register and new value are passed as arguments. -`bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` +```c +bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +``` Assign register get value callback. -`bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` +```c +bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +``` Assign callback function on register modify event. -`bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` +```c +bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +``` Assign callback function on register modify event. diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 0e16b9d..49fe0cf 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -41,6 +41,7 @@ void setup() { pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); + mb.connect() mb.readSlave(COIL(LED_COIL), 1, MB_FC_READ_COILS); mb.send(); } From 30e03df37e6e3c975e8b94143b06163bb3c6368b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 30 Mar 2018 08:47:41 +0500 Subject: [PATCH 043/288] Master able to compile fiexs --- .vscode/arduino.json | 3 ++- src/Modbus.cpp | 10 +++++----- src/Modbus.h | 10 +++++----- src/ModbusIP_ESP8266.cpp | 9 +++++---- src/ModbusIP_ESP8266.h | 18 ++++++++++-------- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.vscode/arduino.json b/.vscode/arduino.json index 96e66a1..5d5054e 100644 --- a/.vscode/arduino.json +++ b/.vscode/arduino.json @@ -1,5 +1,6 @@ { "board": "esp8266:esp8266:nodemcuv2", "configuration": "CpuFrequency=80,FlashSize=4M1M,LwIPVariant=v2mss536,Debug=Serial,DebugLevel=None____,FlashErase=none,UploadSpeed=921600", - "port": "/dev/cu.SLAB_USBtoUART" + "port": "/dev/cu.SLAB_USBtoUART", + "sketch": "examples/Master/Master.ino" } \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 6d1c016..e03efa6 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -151,7 +151,7 @@ void Modbus::exceptionResponse(uint8_t fcode, uint8_t excode) { _reply = MB_REPLY_NORMAL; } -void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { +void Modbus::readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x07D0) { this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); @@ -204,7 +204,7 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { _reply = MB_REPLY_NORMAL; } -void Modbus::readWords(uint16_t startreg, uint16_t numregs, uint8_t fn) { +void Modbus::readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); @@ -406,7 +406,7 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn) { +bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); @@ -419,7 +419,7 @@ bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn) { return true; } -bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { +bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); @@ -432,7 +432,7 @@ bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn) { return true; } -bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn) { +bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); diff --git a/src/Modbus.h b/src/Modbus.h index dc2aef2..7b63f76 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -75,8 +75,8 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { private: TRegister* _regs_head = NULL; - void readBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_COILS); - void readWords(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); + void readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_COILS); + void readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_REGS); void readRegisters(uint16_t startreg, uint16_t numregs) { readWords(HREG(startreg), numregs, MB_FC_READ_REGS); } @@ -106,9 +106,9 @@ class Modbus { void responcePDU(uint8_t* frame); public: - bool readSlave(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_READ_REGS); - bool writeSlaveBits(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_COILS); - bool writeSlaveWords(uint16_t startreg, uint16_t numregs, uint8_t fn = MB_FC_WRITE_REGS); + bool readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_REGS); + bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_COILS); + bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_REGS); bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 28d391d..6e27a3c 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -89,14 +89,15 @@ void ModbusIP::task() { //} void ModbusMasterIP::connect(IPAddress address) { + this->connect(adderss, MODBUSIP_PORT); } -void ModbusMasterIP::pushBits(uint16_t address, uint16_t numregs, uint8_t fn){ +void ModbusMasterIP::pushBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn){ } -void ModbusMasterIP::pullBits(uint16_t address, uint16_t numregs, uint8_t fn) { +void ModbusMasterIP::pullBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn) { } -void ModbusMasterIP::pushWords(uint16_t address, uint16_t numregs, uint8_t fn) { +void ModbusMasterIP::pushWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn) { } -void ModbusMasterIP::pullWords(uint16_t address, uint16_t numregs, uint8_t fn) { +void ModbusMasterIP::pullWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn) { } void ModbusMasterIP::task() { } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 9d21178..e9c2011 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -55,15 +55,15 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { IPAddress ip; uint32_t queryStart; uint32_t timeout; - void connect(IPAddress address); public: ModbusMasterIP() : WiFiClient() { } - void pushBits(uint16_t address, uint16_t numregs, uint8_t fn); - void pullBits(uint16_t address, uint16_t numregs, uint8_t fn); - void pushWords(uint16_t address, uint16_t numregs, uint8_t fn); - void pullWords(uint16_t address, uint16_t numregs, uint8_t fn); - void send() { + void connect(IPAddress address); + void pushBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn); + void pullBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn); + void pushWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn); + void pullWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn); + bool send() { uint16_t i; //MBAP _MBAP[0] = 0; @@ -80,9 +80,9 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; write(sbuf, send_len); } - void get() { + bool get() { uint8_t i; - if (!connected()) return; + if (!connected()) return false; uint16_t raw_len = 0; raw_len = available(); if (raw_len > 7) { @@ -95,9 +95,11 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { for (i = 0; i < _len; i++) _frame[i] = read(); //Get Modbus PDU responcePDU(_frame); + return send(); } flush(); } + return false; } void pushCoil() { } From 59faa93345d5a5df61e6990d1b3ffde55e8e2ec7 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 30 Mar 2018 16:53:14 +0500 Subject: [PATCH 044/288] Cosmetics --- API.md | 32 ++++++++++++++++++++++++-------- src/Modbus.h | 2 +- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/API.md b/API.md index d1425f5..54f9822 100644 --- a/API.md +++ b/API.md @@ -34,35 +34,51 @@ uint16_t Ireg(uint16_t offset) ### Callbacks -`void cbEnable(bool state = TRUE)` +```c +void cbEnable(bool state = TRUE) +``` Callback generation control. Callback generation is enabled by default. -`void cbDisable()` +```c +void cbDisable() +``` Disable callback generation. -`void onConnect(cbModbusConnect cb)` +```c +void onConnect(cbModbusConnect cb) +``` Assign callback function on new incoming connection event. -`typedef bool (*cbModbusConnect)(IPAddress ip)` +```c +typedef bool (*cbModbusConnect)(IPAddress ip) +``` Connect event callback function definition. Client IP address is passed as argument. -`typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val)` +```c +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) +``` Get/Set register callback function definition. Pointer to TRegister structure of the register and new value are passed as arguments. -`bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` +```c +bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +``` Assign register get value callback. -`bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` +```c +bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +``` Assign callback function on register modify event. -`bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1)` +```c +bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +``` Assign callback function on register modify event. diff --git a/src/Modbus.h b/src/Modbus.h index dc2aef2..87906c6 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,5 +1,5 @@ /* - Modbus.h - Header for Modbus Base Library + Modbuc.h - Header for Modbus Base Library Copyright (C) 2014 Andr� Sarmento Barbosa 2017 Alexander Emelianov (a.m.emelianov@gmail.com) */ From 0f94bee7f911a1d44cc39809ca6e5630320d5dec Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 1 Apr 2018 13:35:09 +0500 Subject: [PATCH 045/288] Debug --- .vscode/c_cpp_properties.json | 5 ++++- src/Modbus.cpp | 10 +++++++--- src/Modbus.h | 7 +++++-- src/ModbusIP_ESP8266.cpp | 2 +- src/ModbusIP_ESP8266.h | 12 +++++++----- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index bae8b0a..f676ab5 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -15,7 +15,10 @@ "macFrameworkPath": [ "/System/Library/Frameworks", "/Library/Frameworks" - ] + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17" } ], "version": 3 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e03efa6..9aa611c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -84,7 +84,7 @@ uint16_t Modbus::Reg(uint16_t address) { } void Modbus::receivePDU(uint8_t* frame) { - uint8_t fcode = frame[0]; + modbusFunctionCode fcode = (modbusFunctionCode)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]; @@ -135,7 +135,7 @@ void Modbus::receivePDU(uint8_t* frame) { } } -void Modbus::exceptionResponse(uint8_t fcode, uint8_t excode) { +void Modbus::exceptionResponse(modbusFunctionCode fn, modbusResultCode excode) { //Clean frame buffer free(_frame); _len = 2; @@ -145,7 +145,7 @@ void Modbus::exceptionResponse(uint8_t fcode, uint8_t excode) { _reply = MB_REPLY_OFF; return; } - _frame[0] = fcode + 0x80; + _frame[0] = fn + 0x80; _frame[1] = excode; _reply = MB_REPLY_NORMAL; @@ -416,6 +416,8 @@ bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode f _frame[2] = startreg & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; + Serial.print("_frame[2] = "); + Serial.println(_frame[2]); return true; } @@ -448,6 +450,8 @@ bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunction void Modbus::responcePDU(uint8_t* frame) { uint8_t fcode = frame[0]; if ((fcode & 0x80) != 0) { + Serial.print("Error: "); + Serial.println(_frame[1]); _reply = MB_REPLY_ERROR; return; } diff --git a/src/Modbus.h b/src/Modbus.h index 7b63f76..0630b0a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -75,6 +75,9 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { private: TRegister* _regs_head = NULL; + // All function assuming to process Modbus frame from Slave perspective + // I.e. readRegisters fill frame with local regisers vales + // writeRegisters sets local registers according to frame values void readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_COILS); void readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_REGS); void readRegisters(uint16_t startreg, uint16_t numregs) { @@ -82,7 +85,7 @@ class Modbus { } void writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn = MB_FC_WRITE_REG); void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_REGS); - void exceptionResponse(uint8_t fcode, uint8_t excode); + void exceptionResponse(modbusFunctionCode fn, modbusResultCode excode); void readCoils(uint16_t startreg, uint16_t numregs) { readBits(COIL(startreg), numregs, MB_FC_READ_COILS); } @@ -90,7 +93,7 @@ class Modbus { readBits(ISTS(startreg), numregs, MB_FC_READ_INPUT_STAT); } void readInputRegisters(uint16_t startreg, uint16_t numregs) { - this->readWords(IREG(startreg), numregs, MB_FC_READ_INPUT_REGS); + readWords(IREG(startreg), numregs, MB_FC_READ_INPUT_REGS); } void writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn = MB_FC_WRITE_COIL); void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 6e27a3c..05d5c3e 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -89,7 +89,7 @@ void ModbusIP::task() { //} void ModbusMasterIP::connect(IPAddress address) { - this->connect(adderss, MODBUSIP_PORT); + WiFiClient::connect(address, MODBUSIP_PORT); } void ModbusMasterIP::pushBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn){ } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index e9c2011..aae88aa 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -78,13 +78,15 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; - write(sbuf, send_len); + write(sbuf, send_len); + //Serial.println(_frame[0]); } bool get() { uint8_t i; if (!connected()) return false; uint16_t raw_len = 0; raw_len = available(); + //Serial.println(raw_len); if (raw_len > 7) { for (i = 0; i < 7; i++) _MBAP[i] = read(); //Get MBAP _len = _MBAP[4] << 8 | _MBAP[5]; @@ -95,7 +97,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { for (i = 0; i < _len; i++) _frame[i] = read(); //Get Modbus PDU responcePDU(_frame); - return send(); + return true; } flush(); } @@ -103,9 +105,9 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { } void pushCoil() { } - void pullCoil() { - // readSlave(COIL(offset), numregs, MB_FC_READ_COILS); - // send(); + void pullCoil(uint16_t offset, uint16_t numregs = 1) { + readSlave(COIL(offset), numregs, MB_FC_READ_COILS); + send(); } void pushIsts() { } From 903605e0117dbcf51190a965aa29b837576af107 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 3 Apr 2018 08:35:30 +0500 Subject: [PATCH 046/288] Slave [task] refactoting start --- src/ModbusIP_ESP8266.cpp | 8 ++++---- src/ModbusIP_ESP8266.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 05d5c3e..f82931b 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -49,16 +49,16 @@ void ModbusIP::task() { } uint16_t raw_len = 0; raw_len = client[n]->available(); - if (raw_len > 7) { - for (i = 0; i < 7; i++) _MBAP[i] = client[n]->read(); //Get MBAP - + if (raw_len > sizeof(_MBAP)) { + //for (i = 0; i < 7; i++) _MBAP[i] = client[n]->read(); //Get MBAP + client[n]->readBytes(_MBAP, sizeof(_MBAP)); _len = _MBAP[4] << 8 | _MBAP[5]; _len--; // Do not count with last byte from MBAP if (_MBAP[2] != 0 || _MBAP[3] != 0) continue; //Not a MODBUSIP packet if (_len > MODBUSIP_MAXFRAME) continue; //Length is over MODBUSIP_MAXFRAME _frame = (uint8_t*) malloc(_len); - raw_len = raw_len - 7; + raw_len = raw_len - sizeof(_MBAP); for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU this->receivePDU(_frame); diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index aae88aa..51c8cb9 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -72,6 +72,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { _MBAP[3] = 0; _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP _MBAP[5] = (_len+1) & 0x00FF; + _MABP[6] = 0xFF; size_t send_len = (uint16_t)_len + 7; uint8_t sbuf[send_len]; From 28314d9084abb9fa6937ab992f8b810516162f67 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 3 Apr 2018 18:16:23 +0500 Subject: [PATCH 047/288] Modbus class refactoring in progress --- .vscode/c_cpp_properties.json | 62 ++++++++++++++++++++--- API.md | 68 +++++++++++++------------- examples/TestCallback/TestCallback.ino | 9 ++-- keywords.txt | 9 ---- src/Modbus.cpp | 2 +- src/Modbus.h | 55 ++++++++++++++------- src/ModbusIP_ESP8266.cpp | 46 ++++++++++------- src/ModbusIP_ESP8266.h | 47 ++++++++++++++---- 8 files changed, 196 insertions(+), 102 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index f676ab5..5637303 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,22 +3,68 @@ { "name": "Mac", "includePath": [ - "/Users/aem/Library/Arduino15/packages/esp8266/hardware/esp8266/2.4.1/cores/esp8266" + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" ], + "defines": [], + "intelliSenseMode": "clang-x64", "browse": { - "limitSymbolsToIncludedHeaders": false, "path": [ - "/Users/aem/Library/Arduino15/packages/esp8266/hardware/esp8266/2.4.1/cores/esp8266" - ] + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" }, - "intelliSenseMode": "clang-x64", "macFrameworkPath": [ "/System/Library/Frameworks", "/Library/Frameworks" + ] + }, + { + "name": "Linux", + "includePath": [ + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" ], - "compilerPath": "/usr/bin/clang", - "cStandard": "c11", - "cppStandard": "c++17" + "defines": [], + "intelliSenseMode": "clang-x64", + "browse": { + "path": [ + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + } + }, + { + "name": "Win32", + "includePath": [ + "C:\\Program Files (x86)\\Arduino\\hardware\\arduino\\avr\\cores\\arduino", + "C:\\Program Files (x86)\\Arduino\\hardware\\tools\\avr\\avr\\include", + "C:\\Program Files (x86)\\Arduino\\hardware\\tools\\avr\\lib\\gcc\\avr\\4.9.2\\include", + "C:\\Documents and Settings\\Alex\\Documents\\Arduino\\hardware\\espressif\\esp32\\libraries\\WiFi\\src", + "C:\\Documents and Settings\\Alex\\Local Settings\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\2.4.0-rc2\\variants\\nodemcu", + "${workspaceRoot}" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "intelliSenseMode": "msvc-x64", + "browse": { + "path": [ + "${workspaceRoot}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + } } ], "version": 3 diff --git a/API.md b/API.md index a82c6bf..eb8e52b 100644 --- a/API.md +++ b/API.md @@ -4,30 +4,16 @@ ### Add [multiple] regs -```c -bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1) -``` - ```c bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) -``` - -```c bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) -``` - -```c bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) -``` - -```c bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1) ``` ### Write regs ```c -bool Reg(uint16_t address, uint16_t value) bool Hreg(uint16_t offset, uint16_t value) bool Coil(uint16_t offset, bool value) bool Ists(uint16_t offset, bool value) @@ -62,7 +48,7 @@ Disable callback generation. void onConnect(cbModbusConnect cb) ``` -Assign callback function on new incoming connection event. +*ModbusIP only.* Assign callback function on new incoming connection event. ```c typedef bool (*cbModbusConnect)(IPAddress ip) @@ -74,63 +60,75 @@ Connect event callback function definition. Client IP address is passed as argum typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) ``` -Get/Set register callback function definition. Pointer to TRegister structure of the register and new value are passed as arguments. +Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. ```c -bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +IPAddress eventSource() ``` -Assign register get value callback. +*ModbusIP and ModbusMasterIP only.* Should be called from onGet/onSet callback function. Returns IP address of remote requesting operation or IPADDR_NONE for local. ```c -bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) ``` -Assign callback function on register modify event. +Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onSetCoil(regId)` form to disconnect callback. -```c -bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -``` - -Assign callback function on register modify event. ```c -bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) ``` +Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onGet(regId)` form to disconnect callback. + ### Macros ```c -#define COIL(n) -#define ISTS(n) -#define IREG(n) -#define HREG(n) #define COIL_VAL(v) #define COIL_BOOL(v) #define ISTS_VAL(v) #define ISTS_BOOL(v) ``` + ### ModBus IP specific + ```c void begin() void task() ``` +### ModBus IP Master specific + +```c +void pullCoils() +void pushIstss() +void pullIstss() +void pushHregs() +void pullHregs() +void pushIregs() +void pullIregs() +``` + ### Callback example ```c +ModbusIP mb; bool coil = false; // Define external variable to get/set value uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value + Serial.print("Set query from "); + Serial.println(mb.eventSource().toString()); coil = COIL_BOOL(val); return val; // Returns value to be saved to TRegister structure } uint16_t cbCoilGet(TRegister* reg, uint16_t val) { + Serial.print("Get query from "); + Serial.println(mb.eventSource().toString()); return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request } bool cbConn(IPAddress ip) { @@ -143,8 +141,8 @@ void setup() { mb.onConnect(cbConn); // Add callback on connection event mb.begin(); mb.addCoil(COIL_NR); // Add Coil - mb.onSet(COIL(COIL_NR), cbCoilSet); // Add callback on Coil COIL_NR value set - mb.onGet(COIL(COIL_NR), cbCoilGet); // Add callback on Coil COIL_NR value get + mb.onSetCoil(COIL_NR, cbCoilSet); // Add callback on Coil COIL_NR value set + mb.onGetCoil(COIL_NR, cbCoilGet); // Add callback on Coil COIL_NR value get ... } void loop() { diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index 59e80b9..a9ad59e 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -17,6 +17,9 @@ #endif #include +#include +std::list test; + //Modbus Registers Offsets (0-9999) const int LED_COIL = 100; //Used Pins @@ -60,12 +63,12 @@ void setup() { mb.begin(); pinMode(ledPin, OUTPUT); - mb.addReg(COIL(LED_COIL)); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) - mb.onSet(COIL(LED_COIL), cbLed); // Add callback on Coil LED_COIL value set + mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) + mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set } void loop() { //Call once inside loop() - all magic here mb.task(); yield(); -} \ No newline at end of file +} diff --git a/keywords.txt b/keywords.txt index bf07273..e6a8ea1 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,7 +1,6 @@ # Syntax Coloring Map For ModbusIP_ESP8266 # Datatypes (KEYWORD1) -Modbus KEYWORD1 ModbusIP KEYWORD1 ModbusMasterIP KEYWORD1 ModbusIP_ESP8266 KEYWORD1 @@ -9,8 +8,6 @@ ModbusIP_ESP8266 KEYWORD1 # Methods and Functions (KEYWORD2) begin KEYWORD2 task KEYWORD2 -onGet KEYWORD2 -onSet KEYWORD2 onConnect KEYWORD2 cbEnable KEYWORD2 cbDisable KEYWORD2 @@ -23,22 +20,16 @@ onGetIreg KEYWORD2 onSetIreg KEYWORD2 onGetIsts KEYWORD2 onSetIsts KEYWORD2 -addReg KEYWORD2 addCoil KEYWORD2 addIsts KEYWORD2 addIreg KEYWORD2 addHreg KEYWORD2 -Reg KEYWORD2 Coil KEYWORD2 Ists KEYWORD2 Ireg KEYWORD2 Hreg KEYWORD2 # Constants and Macros (LITERAL1) -COIL LITERAL1 -ISTS LITERAL1 -IREG LITERAL1 -HERG LITERAL1 BIT_VAL LITERAL1 BIT_BOOL LITERAL1 COIL_VAL LITERAL1 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 9aa611c..29a2646 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -406,7 +406,7 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { +bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); diff --git a/src/Modbus.h b/src/Modbus.h index 8d99ec6..434c06e 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -23,6 +23,10 @@ #define COIL_BOOL(v) (v==0xFF00) #define ISTS_VAL(v) (v?0xFF00:0x0000) #define ISTS_BOOL(v) (v==0xFF00) +#define IS_COIL(n) (n < ISTS_BASE) +#define IS_ISTS(n) (n >= ISTS_BASE && n < IREG_BASE) +#define IS_IREG(n) (n >= IREG_BASE && n < HREG_BASE) +#define IS_HREG(n) (n >= HREG_BASE) //Function Codes enum modbusFunctionCode { @@ -43,20 +47,11 @@ enum modbusResultCode { MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range - MB_EX_SLAVE_FAILURE = 0x04, // Slave Deive Fails to process request + MB_EX_SLAVE_FAILURE = 0x04, // Slave or Master Deice Fails to process request MB_EX_ACKNOWLEDGE = 0x05, // Not used MB_EX_SLAVE_DEVICE_BUSY = 0x06 // Not used }; -//Reply Types -enum modbusReplyCode { - MB_REPLY_OFF = 0x01, - MB_REPLY_ECHO = 0x02, - MB_REPLY_NORMAL = 0x03, - MB_REPLY_ERROR = 0x04, - MB_REPLY_UNEXPECTED = 0x05 -}; - typedef struct TRegister; // Callback function Type @@ -85,7 +80,6 @@ class Modbus { } void writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn = MB_FC_WRITE_REG); void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_REGS); - void exceptionResponse(modbusFunctionCode fn, modbusResultCode excode); void readCoils(uint16_t startreg, uint16_t numregs) { readBits(COIL(startreg), numregs, MB_FC_READ_COILS); } @@ -99,17 +93,43 @@ class Modbus { void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); TRegister* searchRegister(uint16_t addr); +// TRegister* searchRegister(uint32_t addr); bool cbEnabled = true; - protected: + protected: + //Reply Types + enum modbusReplyCode { + MB_REPLY_OFF = 0x01, + MB_REPLY_ECHO = 0x02, + MB_REPLY_NORMAL = 0x03, + MB_REPLY_ERROR = 0x04, + MB_REPLY_UNEXPECTED = 0x05 + }; + uint8_t* _frame; uint8_t _len; uint8_t _reply; + void exceptionResponse(modbusFunctionCode fn, modbusResultCode excode); void receivePDU(uint8_t* frame); void responcePDU(uint8_t* frame); public: - bool readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_REGS); + enum FunctionCode { + READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx + READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx + READ_REGS = 0x03, // Read Holding Registers 4xxxx + READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx + WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx + WRITE_REG = 0x06, // Preset Single Register 4xxxx + WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx + WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx + MASKWRITE_REG = 0x16, // Not implemented + READWRITE_REGS = 0x17 // Not implemented + }; + TRegister* getHead() { + return _regs_head; + } + bool readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn); bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_COILS); bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_REGS); @@ -126,7 +146,7 @@ class Modbus { uint16_t Hreg(uint16_t offset) { return Reg(HREG(offset)); } - bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { + inline bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { return addReg(COIL(offset), COIL_VAL(value), numregs); } bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { @@ -155,16 +175,17 @@ class Modbus { } void cbEnable(bool state = true); - void cbDisable() { + inline void cbDisable() { cbEnable(false); } + bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(COIL(offset), cb, numregs); } - bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(COIL(offset), cb, numregs); } bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index f82931b..9c6a1be 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -41,29 +41,37 @@ void ModbusIP::task() { } for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (client[n] == NULL) - continue; + continue; // for (n) if (!client[n]->connected()) { delete client[n]; client[n] = NULL; - continue; + continue; // for (n) } - uint16_t raw_len = 0; - raw_len = client[n]->available(); - if (raw_len > sizeof(_MBAP)) { + if (client[n]->available() > sizeof(_MBAP)) { //for (i = 0; i < 7; i++) _MBAP[i] = client[n]->read(); //Get MBAP - client[n]->readBytes(_MBAP, sizeof(_MBAP)); + client[n]->readBytes(_MBAP, sizeof(_MBAP)); //Get MBAP _len = _MBAP[4] << 8 | _MBAP[5]; _len--; // Do not count with last byte from MBAP - if (_MBAP[2] != 0 || _MBAP[3] != 0) continue; //Not a MODBUSIP packet - if (_len > MODBUSIP_MAXFRAME) continue; //Length is over MODBUSIP_MAXFRAME - _frame = (uint8_t*) malloc(_len); - - raw_len = raw_len - sizeof(_MBAP); - for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU + + if (_MBAP[2] != 0 || _MBAP[3] != 0) { //Not a MODBUSIP packet + client[n]->flush(); + continue; // for (n) + } + if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME + exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_SLAVE_FAILURE); + client[n]->flush(); + } else { + _frame = (uint8_t*) malloc(_len); - this->receivePDU(_frame); - client[n]->flush(); - + // for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU + if (client[i]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame + exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_ILLEGAL_VALUE); + client[i]->flush(); + } else { + this->receivePDU(_frame); + //client[n]->flush(); + } + } if (_reply != MB_REPLY_OFF) { //MBAP _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP @@ -72,8 +80,10 @@ void ModbusIP::task() { size_t send_len = (uint16_t)_len + 7; uint8_t sbuf[send_len]; - for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; - for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; + //for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; + //for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; + memcpy(_MBAP, sbuf, sizeof(_MBAP)); + memcpy(_frame, sbuf + sizeof(_MBAP), _len); client[n]->write(sbuf, send_len); } @@ -101,8 +111,6 @@ void ModbusMasterIP::pullWords(uint16_t address, uint16_t numregs, modbusFunctio } void ModbusMasterIP::task() { } -uint16_t ModbusMasterIP::regGroupsCount() { -} IPAddress ModbusMasterIP::eventSource() { } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 51c8cb9..5a3ac96 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -55,6 +55,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { IPAddress ip; uint32_t queryStart; uint32_t timeout; + uint16_t transactionId = 0; public: ModbusMasterIP() : WiFiClient() { } @@ -72,7 +73,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { _MBAP[3] = 0; _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP _MBAP[5] = (_len+1) & 0x00FF; - _MABP[6] = 0xFF; + _MBAP[6] = 0xFF; size_t send_len = (uint16_t)_len + 7; uint8_t sbuf[send_len]; @@ -88,17 +89,20 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { uint16_t raw_len = 0; raw_len = available(); //Serial.println(raw_len); - if (raw_len > 7) { - for (i = 0; i < 7; i++) _MBAP[i] = read(); //Get MBAP + if (available() > sizeof(_MBAP)) { + //for (i = 0; i < 7; i++) _MBAP[i] = read(); //Get MBAP + readBytes(_MBAP, sizeof(_MBAP)); //Get MBAP _len = _MBAP[4] << 8 | _MBAP[5]; _len--; // Do not count with last byte from MBAP if (_MBAP[2] == 0 && _MBAP[3] == 0 && _len < MODBUSIP_MAXFRAME) { _frame = (uint8_t*) malloc(_len); - raw_len = raw_len - 7; - for (i = 0; i < _len; i++) - _frame[i] = read(); //Get Modbus PDU - responcePDU(_frame); - return true; + //raw_len = raw_len - 7; + //for (i = 0; i < _len; i++) + // _frame[i] = read(); //Get Modbus PDU + if (readBytes(_frame, _len) == _len) { + responcePDU(_frame); + return true; + } } flush(); } @@ -107,9 +111,32 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pushCoil() { } void pullCoil(uint16_t offset, uint16_t numregs = 1) { - readSlave(COIL(offset), numregs, MB_FC_READ_COILS); + readSlave(offset, numregs, READ_COILS); send(); } + void pullCoils() { + uint16_t offset; + uint16_t numregs = 1; + TRegister* creg = getHead(); + if (!creg) return; + while (creg && !IS_COIL(creg->address)) creg = creg->next; + if (creg) { + offset = creg->address; + while (creg->next) { + creg = creg->next; + if (IS_COIL(creg->address)) { + if ((offset + 1) == creg->address) { + numregs++; + continue; + } + pullCoil(offset, numregs); + offset = creg->address; + numregs = 1; + } + } + pullCoil(offset, numregs); + } + } void pushIsts() { } void pullIsts() { @@ -126,10 +153,10 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void begin() { } void task(); - uint16_t regGroupsCount(); IPAddress eventSource(); bool pullReg(uint16_t address, uint16_t numregs) { addReg(address, numregs); + return true; } bool pushReg(uint16_t address, uint16_t numregs); }; From 3c5d32af1d4dc51c71378c31ff5ee29b3f3a5b11 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 4 Apr 2018 08:29:54 +0500 Subject: [PATCH 048/288] Modbus enum localization --- .vscode/c_cpp_properties.json | 5 ++- src/Modbus.cpp | 2 +- src/Modbus.h | 76 +++++++++++++++-------------------- src/ModbusIP_ESP8266.cpp | 22 +++++----- 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5637303..8eddd0a 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -21,7 +21,10 @@ "macFrameworkPath": [ "/System/Library/Frameworks", "/Library/Frameworks" - ] + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17" }, { "name": "Linux", diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 29a2646..9aa611c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -406,7 +406,7 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); diff --git a/src/Modbus.h b/src/Modbus.h index 434c06e..f522ab4 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -28,30 +28,6 @@ #define IS_IREG(n) (n >= IREG_BASE && n < HREG_BASE) #define IS_HREG(n) (n >= HREG_BASE) -//Function Codes -enum modbusFunctionCode { - MB_FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx - MB_FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx - MB_FC_READ_REGS = 0x03, // Read Holding Registers 4xxxx - MB_FC_READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx - MB_FC_WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx - MB_FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx - MB_FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx - MB_FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx - MB_FC_MASKWRITE_REG = 0x16, // Not implemented - MB_FC_READWRITE_REGS = 0x17 // Not implemented -}; - -//Exception Codes -enum modbusResultCode { - MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported - MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists - MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range - MB_EX_SLAVE_FAILURE = 0x04, // Slave or Master Deice Fails to process request - MB_EX_ACKNOWLEDGE = 0x05, // Not used - MB_EX_SLAVE_DEVICE_BUSY = 0x06 // Not used -}; - typedef struct TRegister; // Callback function Type @@ -93,7 +69,7 @@ class Modbus { void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); TRegister* searchRegister(uint16_t addr); -// TRegister* searchRegister(uint32_t addr); + bool cbEnabled = true; protected: @@ -113,23 +89,11 @@ class Modbus { void receivePDU(uint8_t* frame); void responcePDU(uint8_t* frame); - public: - enum FunctionCode { - READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx - READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx - READ_REGS = 0x03, // Read Holding Registers 4xxxx - READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx - WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx - WRITE_REG = 0x06, // Preset Single Register 4xxxx - WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx - WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx - MASKWRITE_REG = 0x16, // Not implemented - READWRITE_REGS = 0x17 // Not implemented - }; TRegister* getHead() { return _regs_head; } - bool readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn); + + bool readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_COILS); bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_REGS); @@ -137,6 +101,33 @@ class Modbus { bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); + bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + + public: + //Function Codes + enum modbusFunctionCode { + MB_FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx + MB_FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx + MB_FC_READ_REGS = 0x03, // Read Holding Registers 4xxxx + MB_FC_READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx + MB_FC_WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx + MB_FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx + MB_FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx + MB_FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx + MB_FC_MASKWRITE_REG = 0x16, // Not implemented + MB_FC_READWRITE_REGS = 0x17 // Not implemented + }; + //Exception Codes + enum modbusResultCode { + MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported + MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists + MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range + MB_EX_SLAVE_FAILURE = 0x04, // Slave or Master Deice Fails to process request + MB_EX_ACKNOWLEDGE = 0x05, // Not used + MB_EX_SLAVE_DEVICE_BUSY = 0x06 // Not used + }; + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { return addReg(HREG(offset), value, numregs); } @@ -179,13 +170,10 @@ class Modbus { cbEnable(false); } - bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - - inline bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(COIL(offset), cb, numregs); } - inline bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(COIL(offset), cb, numregs); } bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 9c6a1be..1ca6927 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -62,14 +62,18 @@ void ModbusIP::task() { client[n]->flush(); } else { _frame = (uint8_t*) malloc(_len); - - // for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU - if (client[i]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame - exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_ILLEGAL_VALUE); - client[i]->flush(); + if (!_frame) { + exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_SLAVE_FAILURE); + client[n]->flush(); } else { - this->receivePDU(_frame); - //client[n]->flush(); + //for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU + if (client[i]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame + exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_ILLEGAL_VALUE); + client[i]->flush(); + } else { + this->receivePDU(_frame); + client[n]->flush(); + } } } if (_reply != MB_REPLY_OFF) { @@ -82,8 +86,8 @@ void ModbusIP::task() { //for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; //for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; - memcpy(_MBAP, sbuf, sizeof(_MBAP)); - memcpy(_frame, sbuf + sizeof(_MBAP), _len); + memcpy(sbuf, _MBAP, sizeof(_MBAP)); + memcpy(sbuf + sizeof(_MBAP), _frame, _len); client[n]->write(sbuf, send_len); } From cf28cabf9253e9d99e77f1ae921966d19c9a8e2c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 4 Apr 2018 08:30:17 +0500 Subject: [PATCH 049/288] Modbus enum localization --- examples/TestCallback/TestCallback.ino | 5 +---- src/Modbus.h | 12 ++++++------ src/ModbusIP_ESP8266.h | 14 +++++++++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index a9ad59e..833a14c 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -1,6 +1,6 @@ /* Modbus-Arduino Example - Test Led using callback (Modbus IP ESP8266/ESP32) - Control a Led on D4 pin using Write Single Coil Modbus Function + Control a Led on D4 or TX pin using Write Single Coil Modbus Function Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino @@ -17,9 +17,6 @@ #endif #include -#include -std::list test; - //Modbus Registers Offsets (0-9999) const int LED_COIL = 100; //Used Pins diff --git a/src/Modbus.h b/src/Modbus.h index f522ab4..ad1ccdf 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -49,8 +49,8 @@ class Modbus { // All function assuming to process Modbus frame from Slave perspective // I.e. readRegisters fill frame with local regisers vales // writeRegisters sets local registers according to frame values - void readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_COILS); - void readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_READ_REGS); + void readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + void readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); void readRegisters(uint16_t startreg, uint16_t numregs) { readWords(HREG(startreg), numregs, MB_FC_READ_REGS); } @@ -86,16 +86,16 @@ class Modbus { uint8_t _len; uint8_t _reply; void exceptionResponse(modbusFunctionCode fn, modbusResultCode excode); - void receivePDU(uint8_t* frame); - void responcePDU(uint8_t* frame); + void receivePDU(uint8_t* frame); //For Slave + void responcePDU(uint8_t* frame); //For Master TRegister* getHead() { return _regs_head; } bool readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_COILS); - bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn = MB_FC_WRITE_REGS); + bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 5a3ac96..4ce1d4e 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -46,11 +46,19 @@ class ModbusCoreIP : public Modbus { } }; - +typedef struct TTransaction; +typedef uint16_t (*cbModbusSlave)(TTransaction* query, bool result); +typedef struct TTransaction { + uint16_t id; + uint16_t startreg; + uint16_t numregs; + uint32_t timestamp; + TQuery* next; + cbModbusSlave cb; +} class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { private: - TRegister* reg; - TRegister* next; + TTransaction* _trans; uint8_t status; IPAddress ip; uint32_t queryStart; From e93ae9adb1e9e95da8358b1a7134331849013609 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 4 Apr 2018 17:33:35 +0500 Subject: [PATCH 050/288] Documentation update --- API.md | 69 +++++++++++------------- README.md | 159 ++++++++++++------------------------------------------ 2 files changed, 65 insertions(+), 163 deletions(-) diff --git a/API.md b/API.md index eb8e52b..961fca1 100644 --- a/API.md +++ b/API.md @@ -5,45 +5,39 @@ ### Add [multiple] regs ```c -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 nemregs = 1) +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 nemregs = 1); ``` ### Write regs ```c -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 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); ``` ### Read regs ```c -uint16_t Reg(uint16_t address) -uint16_t Hreg(uint16_t offset) -bool Coil(uint16_t offset) -bool Ists(uint16_t offset) -uint16_t Ireg(uint16_t offset) +uint16_t Hreg(uint16_t offset); +bool Coil(uint16_t offset); +bool Ists(uint16_t offset); +uint16_t Ireg(uint16_t offset); ``` ### Callbacks ```c -void cbEnable(bool state = TRUE) +void cbEnable(bool state = TRUE); +void cbDisable(); ``` Callback generation control. Callback generation is enabled by default. -```c -void cbDisable() -``` - -Disable callback generation. - ```c void onConnect(cbModbusConnect cb) ``` @@ -69,20 +63,20 @@ IPAddress eventSource() *ModbusIP and ModbusMasterIP only.* Should be called from onGet/onSet callback function. Returns IP address of remote requesting operation or IPADDR_NONE for local. ```c -bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); ``` Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onSetCoil(regId)` form to disconnect callback. ```c -bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) +bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); ``` Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onGet(regId)` form to disconnect callback. @@ -99,20 +93,21 @@ Assign callback function on register query event. Multiple sequental registers c ### ModBus IP specific ```c -void begin() -void task() +void begin(); +void task(); ``` ### ModBus IP Master specific ```c -void pullCoils() -void pushIstss() -void pullIstss() -void pushHregs() -void pullHregs() -void pushIregs() -void pullIregs() +void (cbModbusResult*)(TTransaction* trans, Modbus::ResultCode); +void pullCoils(cbModbusResult cb = NULL); +void pushIstss(cbModbusResult cb = NULL); +void pullIstss(cbModbusResult cb = NULL); +void pushHregs(cbModbusResult cb = NULL); +void pullHregs(cbModbusResult cb = NULL); +void pushIregs(cbModbusResult cb = NULL); +void pullIregs(cbModbusResult cb = NULL); ``` ### Callback example diff --git a/README.md b/README.md index 53aa692..4859a01 100644 --- a/README.md +++ b/README.md @@ -7,147 +7,54 @@ The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called In the current version the library allows the ESP8266/ESP32 operate async as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: -http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf +http://pt.wikipedia.org/wiki/Modbus +http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf ## Features -
        -
      • Operates as a slave, master or both
      • -
      • Fully async operations. No loop locks.
      • -
      • Supports Modbus IP (TCP)
      • -
      • Reply exception messages for all supported functions
      • -
      • Modbus functions supported:
      • -
          -
        • 0x01 - Read Coils
        • -
        • 0x02 - Read Input Status (Read Discrete Inputs)
        • -
        • 0x03 - Read Holding Registers
        • -
        • 0x04 - Read Input Registers
        • -
        • 0x05 - Write Single Coil
        • -
        • 0x06 - Write Single Register
        • -
        • 0x0F - Write Multiple Coils
        • -
        • 0x10 - Write Multiple Registers
        • -
        -
      • Callbacks for
      • -
          -
        • - Incoming IP connection
        • -
        • - Read specific Register
        • -
        • - Write specific Register
        • -
        -
      - -Notes: +* Supported platforms are + * ESP8266 + * ESP32 +* Operates as a slave, master or both +* Fully async operations. No loop locks. +* Supports Modbus IP (TCP) +* Reply exception messages for all supported functions +* Modbus functions supported: + * 0x01 - Read Coils + * 0x02 - Read Input Status (Read Discrete Inputs) + * 0x03 - Read Holding Registers + * 0x04 - Read Input Registers + * 0x05 - Write Single Coil + * 0x06 - Write Single Register + * 0x0F - Write Multiple Coils + * 0x10 - Write Multiple Registers +* Callbacks for + * Incoming IP connection + * Read specific Register + * Write specific Register + +## Notes: 1. When using Modbus IP the transport protocol is TCP (port 502). -2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in ScadaBR (http://www.scadabr.com.br) -offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the CAS Modbus Scanner -(http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. - -## API - -### Add [multiple] regs -``` -bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1) -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 nemregs = 1) -``` -### Write regs -``` -bool Reg(uint16_t address, uint16_t value) -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) -``` -### Read regs -``` -uint16_t Reg(uint16_t address) -uint16_t Hreg(uint16_t offset) -bool Coil(uint16_t offset) -bool Ists(uint16_t offset) -uint16_t Ireg(uint16_t offset) -``` -### Callbacks -``` -void cbEnable(bool state = TRUE) -void cbDisable() -void onConnect(cbModbusConnect cb) -typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) -typedef bool (*cbModbusConnect)(IPAddress ip) -bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -``` -### Macros -``` -#define COIL(n) -#define ISTS(n) -#define IREG(n) -#define HREG(n) -#define COIL_VAL(v) -#define COIL_BOOL(v) -#define ISTS_VAL(v) -#define ISTS_BOOL(v) -``` -### ModBus IP specific -``` -void begin() -void task() -``` - -### Callback example - -``` -bool coil = false; // Define external variable to get/set value -uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value - coil = COIL_BOOL(val); - return val; // Returns value to be saved to TRegister structure -} -uint16_t cbCoilGet(TRegister* reg, uint16_t val) { - return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request -} -bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; // Return 'true' to allow connection or 'false' to drop connection -} -ModbusIP mb; // ModbusIP object -void setup() { -... - mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); - mb.addCoil(COIL_NR); // Add Coil - mb.onSet(COIL(COIL_NR), cbCoilSet); // Add callback on Coil COIL_NR value set - mb.onGet(COIL(COIL_NR), cbCoilGet); // Add callback on Coil COIL_NR value get -... -} -void loop() { -... - mb.task(); -... -} -``` +2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) +offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. +For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/API/md) ## Contributions -https://github.com/emelianov/modbus-esp8266
      +https://github.com/emelianov/modbus-esp8266 + a.m.emelianov@gmail.com -Original version:
      -http://github.com/andresarmento/modbus-esp8266
      +Original version: + +http://github.com/andresarmento/modbus-esp8266 + prof (at) andresarmento (dot) com ## License The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. - From afaa96fa0426d3da259f1286eaa357d3d7d5facb Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 5 Apr 2018 10:47:01 +0500 Subject: [PATCH 051/288] Small fixes --- examples/TestCallback/TestCallback.ino | 9 +- src/Modbus.h | 119 ++++++++++++------------- src/ModbusIP_ESP8266.h | 6 +- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index 833a14c..2efeed4 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -1,6 +1,6 @@ /* Modbus-Arduino Example - Test Led using callback (Modbus IP ESP8266/ESP32) - Control a Led on D4 or TX pin using Write Single Coil Modbus Function + Control a Led on D4 pin using Write Single Coil Modbus Function Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino @@ -17,6 +17,9 @@ #endif #include +#include +std::list test; + //Modbus Registers Offsets (0-9999) const int LED_COIL = 100; //Used Pins @@ -44,7 +47,7 @@ bool cbConn(IPAddress ip) { void setup() { Serial.begin(74880); - WiFi.begin("ssid", "password"); + WiFi.begin("EW", "iMpress6264"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -62,6 +65,8 @@ void setup() { pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set + test.push_front({10,10,NULL,NULL,NULL}); + Serial.println(test.begin()->address); } void loop() { diff --git a/src/Modbus.h b/src/Modbus.h index ad1ccdf..ccca039 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -44,66 +44,6 @@ typedef struct TRegister { uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { - private: - TRegister* _regs_head = NULL; - // All function assuming to process Modbus frame from Slave perspective - // I.e. readRegisters fill frame with local regisers vales - // writeRegisters sets local registers according to frame values - void readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - void readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - void readRegisters(uint16_t startreg, uint16_t numregs) { - readWords(HREG(startreg), numregs, MB_FC_READ_REGS); - } - void writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn = MB_FC_WRITE_REG); - void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_REGS); - void readCoils(uint16_t startreg, uint16_t numregs) { - readBits(COIL(startreg), numregs, MB_FC_READ_COILS); - } - void readInputStatus(uint16_t startreg, uint16_t numregs) { - readBits(ISTS(startreg), numregs, MB_FC_READ_INPUT_STAT); - } - void readInputRegisters(uint16_t startreg, uint16_t numregs) { - readWords(IREG(startreg), numregs, MB_FC_READ_INPUT_REGS); - } - void writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn = MB_FC_WRITE_COIL); - void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); - - TRegister* searchRegister(uint16_t addr); - - bool cbEnabled = true; - - protected: - //Reply Types - enum modbusReplyCode { - MB_REPLY_OFF = 0x01, - MB_REPLY_ECHO = 0x02, - MB_REPLY_NORMAL = 0x03, - MB_REPLY_ERROR = 0x04, - MB_REPLY_UNEXPECTED = 0x05 - }; - - uint8_t* _frame; - uint8_t _len; - uint8_t _reply; - void exceptionResponse(modbusFunctionCode fn, modbusResultCode excode); - void receivePDU(uint8_t* frame); //For Slave - void responcePDU(uint8_t* frame); //For Master - - TRegister* getHead() { - return _regs_head; - } - - bool readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - - bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); - bool Reg(uint16_t address, uint16_t value); - uint16_t Reg(uint16_t address); - - bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - public: //Function Codes enum modbusFunctionCode { @@ -194,4 +134,63 @@ class Modbus { bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(IREG(offset), cb, numregs); } + private: + TRegister* _regs_head = NULL; + // All function assuming to process Modbus frame from Slave perspective + // I.e. readRegisters fill frame with local regisers vales + // writeRegisters sets local registers according to frame values + void readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + void readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + void readRegisters(uint16_t startreg, uint16_t numregs) { + readWords(HREG(startreg), numregs, MB_FC_READ_REGS); + } + void writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn = MB_FC_WRITE_REG); + void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_REGS); + void readCoils(uint16_t startreg, uint16_t numregs) { + readBits(COIL(startreg), numregs, MB_FC_READ_COILS); + } + void readInputStatus(uint16_t startreg, uint16_t numregs) { + readBits(ISTS(startreg), numregs, MB_FC_READ_INPUT_STAT); + } + void readInputRegisters(uint16_t startreg, uint16_t numregs) { + readWords(IREG(startreg), numregs, MB_FC_READ_INPUT_REGS); + } + void writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn = MB_FC_WRITE_COIL); + void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); + + TRegister* searchRegister(uint16_t addr); + + bool cbEnabled = true; + + protected: + //Reply Types + enum modbusReplyCode { + MB_REPLY_OFF = 0x01, + MB_REPLY_ECHO = 0x02, + MB_REPLY_NORMAL = 0x03, + MB_REPLY_ERROR = 0x04, + MB_REPLY_UNEXPECTED = 0x05 + }; + + uint8_t* _frame; + uint8_t _len; + uint8_t _reply; + void exceptionResponse(modbusFunctionCode fn, modbusResultCode excode); + void receivePDU(uint8_t* frame); //For Slave + void responcePDU(uint8_t* frame); //For Master + + TRegister* getHead() { + return _regs_head; + } + + bool readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + + bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); + bool Reg(uint16_t address, uint16_t value); + uint16_t Reg(uint16_t address); + + bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); }; \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 4ce1d4e..a4c0e00 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -53,9 +53,9 @@ typedef struct TTransaction { uint16_t startreg; uint16_t numregs; uint32_t timestamp; - TQuery* next; + TTransaction* next; cbModbusSlave cb; -} +}; class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { private: TTransaction* _trans; @@ -119,7 +119,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pushCoil() { } void pullCoil(uint16_t offset, uint16_t numregs = 1) { - readSlave(offset, numregs, READ_COILS); + readSlave(offset, numregs, MB_FC_READ_COILS); send(); } void pullCoils() { From f7ff19788abc8f05e1f178fd94fd4942d682fea5 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 5 Apr 2018 18:20:14 +0500 Subject: [PATCH 052/288] Fine-writing code, nameing, std::list --- README.md | 14 ++ .../AnalogInput.ino} | 0 examples/Callback/Callback.ino | 74 +++++++ .../HoldingReg.ino} | 0 examples/{TestLed/TestLed.ino => Led/Led.ino} | 0 .../MassOperations.ino} | 6 +- examples/Master/Master.ino | 3 +- .../MultipleHRegDebug/MultipleHRegDebug.ino | 6 +- .../SwitchStatus.ino} | 0 examples/TestCallback/TestCallback.ino | 4 +- src/Modbus.cpp | 181 +++++++----------- src/Modbus.h | 140 +++++++------- src/ModbusIP_ESP8266.cpp | 19 +- src/ModbusIP_ESP8266.h | 32 +++- 14 files changed, 276 insertions(+), 203 deletions(-) rename examples/{TestAnalogInput/TestAnalogInput.ino => AnalogInput/AnalogInput.ino} (100%) create mode 100644 examples/Callback/Callback.ino rename examples/{TestHoldingReg/TestHoldingReg.ino => HoldingReg/HoldingReg.ino} (100%) rename examples/{TestLed/TestLed.ino => Led/Led.ino} (100%) rename examples/{TestMassOperations/TestMassOperations.ino => MassOperations/MassOperations.ino} (88%) rename examples/{TestSwitchStatus/TestSwitchStatus.ino => SwitchStatus/SwitchStatus.ino} (100%) diff --git a/README.md b/README.md index 4859a01..9557ffc 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,20 @@ offsets are 0-based, then, a register configured as 100 in the library is set to For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/API/md) +## Last Changes + +* Internal changes + * Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. + * Move object's list implementation to std::list + * Modbus class refactoring + * ModbusIP networking code refactoring and error reporting + * Modbus master implementation preparation +* Public API changes + * Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL + * Mark as private onSet, onGet, addReg, Reg + * Added callback-related functions: eventSource, onSetCoil, onGetCoil, onSetReg, onGetReg,.. + + ## Contributions https://github.com/emelianov/modbus-esp8266 diff --git a/examples/TestAnalogInput/TestAnalogInput.ino b/examples/AnalogInput/AnalogInput.ino similarity index 100% rename from examples/TestAnalogInput/TestAnalogInput.ino rename to examples/AnalogInput/AnalogInput.ino diff --git a/examples/Callback/Callback.ino b/examples/Callback/Callback.ino new file mode 100644 index 0000000..330b486 --- /dev/null +++ b/examples/Callback/Callback.ino @@ -0,0 +1,74 @@ +/* + Modbus-Arduino Example - Test Led using callback (Modbus IP ESP8266/ESP32) + Control a Led on D4 pin using Write Single Coil Modbus Function + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +#include +std::list test; + +//Modbus Registers Offsets (0-9999) +const int LED_COIL = 100; +//Used Pins +#ifdef ESP8266 + const int ledPin = D4; // Builtin ESP8266 LED +#else + const int ledPin = TX; // ESP32 TX LED +#endif +//ModbusIP object +ModbusIP mb; + +// Callback function for write (set) Coil. Returns value to store. +uint16_t cbLed(TRegister* reg, uint16_t val) { + //Attach ledPin to LED_COIL register + digitalWrite(ledPin, (val == 0xFF00)); + return val; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(74880); + + WiFi.begin("EW", "iMpress6264"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + + pinMode(ledPin, OUTPUT); + mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) + mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + yield(); +} diff --git a/examples/TestHoldingReg/TestHoldingReg.ino b/examples/HoldingReg/HoldingReg.ino similarity index 100% rename from examples/TestHoldingReg/TestHoldingReg.ino rename to examples/HoldingReg/HoldingReg.ino diff --git a/examples/TestLed/TestLed.ino b/examples/Led/Led.ino similarity index 100% rename from examples/TestLed/TestLed.ino rename to examples/Led/Led.ino diff --git a/examples/TestMassOperations/TestMassOperations.ino b/examples/MassOperations/MassOperations.ino similarity index 88% rename from examples/TestMassOperations/TestMassOperations.ino rename to examples/MassOperations/MassOperations.ino index 488d744..5f3a77d 100644 --- a/examples/TestMassOperations/TestMassOperations.ino +++ b/examples/MassOperations/MassOperations.ino @@ -67,9 +67,9 @@ void setup() { mb.onConnect(cbConn); // Add callback on connection event mb.begin(); - mb.addReg(COIL(COIL_BASE), COIL_VAL(false), LEN); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) - mb.onGet(COIL(COIL_BASE), cbRead, LEN); // Add callback on Coils value get - mb.onSet(COIL(COIL_BASE), cbWrite, LEN); + mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. + mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add callback on Coils value get + mb.onSetCoil(COIL_BASE, cbWrite, LEN); } void loop() { diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 49fe0cf..7aea8af 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -2,7 +2,8 @@ Modbus-Arduino Example - Master (Modbus IP ESP8266) Control a Led on GPIO0 pin using Write Single Coil Modbus Function - Current version +THIS IS NOT WORKING SAMPLE -- JUST DEVELOPNEMT VERSION + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 */ diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/MultipleHRegDebug/MultipleHRegDebug.ino index 1b06d88..10697ad 100644 --- a/examples/MultipleHRegDebug/MultipleHRegDebug.ino +++ b/examples/MultipleHRegDebug/MultipleHRegDebug.ino @@ -67,9 +67,9 @@ void setup() { mb.onConnect(cbConn); // Add callback on connection event mb.begin(); - if (!mb.addReg(HREG(0), 0xF0F0, LEN)) Serial.println("Error"); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) - mb.onGet(HREG(0), cbRead, LEN); // Add callback on Coils value get - mb.onSet(HREG(0), cbWrite, LEN); + if (!mb.addHReg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) + mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get + mb.onSetHreg(0, cbWrite, LEN); } void loop() { diff --git a/examples/TestSwitchStatus/TestSwitchStatus.ino b/examples/SwitchStatus/SwitchStatus.ino similarity index 100% rename from examples/TestSwitchStatus/TestSwitchStatus.ino rename to examples/SwitchStatus/SwitchStatus.ino diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index 2efeed4..25f714c 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -65,8 +65,8 @@ void setup() { pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set - test.push_front({10,10,NULL,NULL,NULL}); - Serial.println(test.begin()->address); + //test.push_front({10,10,NULL,NULL,NULL}); + //Serial.println(test.begin()->address); } void loop() { diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 9aa611c..5df127f 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -10,52 +10,26 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { } TRegister* Modbus::searchRegister(uint16_t address) { - TRegister *reg = _regs_head; - //scan through the linked list until the end of the list or the register is found. - //return the pointer. - while (reg) { - if (reg->address == address) return reg; - reg = reg->next; - } + const TRegister tmp = {address, 0, cbDefault, cbDefault}; + std::list::iterator it = std::find(_regs.begin(), _regs.end(), tmp); + if (it != _regs.end()) return &*it; return NULL; } bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { - TRegister *newreg; - TRegister *root = NULL; - while (numregs > 0) { - newreg = (TRegister*) malloc(sizeof(TRegister)); - if (!newreg) { //Cleanup if unable to add all regs - while (root) { - newreg = root; - root = root->next; - free(newreg); - } - return false; - } - newreg->address = address; - newreg->value = value; - newreg->get = cbDefault; - newreg->set = cbDefault; - newreg->next = root; - root = newreg; - address++; - numregs--; - } - if (!_regs_head) { - _regs_head = root; - } else { - newreg = _regs_head; - while (newreg->next) { - newreg = newreg->next; - } - newreg->next = root; + #ifdef MB_MAX_REGS + if (_regs.size() + numregs > MB_MAX_REGS) return false; + #endif + for (uint16_t i = 0; i < numregs; i++) { + _regs.push_front({address + i, value, cbDefault, cbDefault}); } + _regs.sort(); + _regs.unique(); return true; } bool Modbus::Reg(uint16_t address, uint16_t value) { - TRegister *reg; + TRegister* reg; //search for the register address reg = this->searchRegister(address); //if found then assign the register value to the new value. @@ -71,7 +45,7 @@ bool Modbus::Reg(uint16_t address, uint16_t value) { } uint16_t Modbus::Reg(uint16_t address) { - TRegister *reg; + TRegister* reg; reg = this->searchRegister(address); if(reg) if (cbEnabled) { @@ -84,77 +58,72 @@ uint16_t Modbus::Reg(uint16_t address) { } void Modbus::receivePDU(uint8_t* frame) { - modbusFunctionCode fcode = (modbusFunctionCode)frame[0]; + 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]; switch (fcode) { - case MB_FC_WRITE_REG: + case FC_WRITE_REG: //field1 = reg, field2 = value this->writeSingleRegister(field1, field2); break; - case MB_FC_READ_REGS: + case FC_READ_REGS: //field1 = startreg, field2 = numregs this->readRegisters(field1, field2); break; - case MB_FC_WRITE_REGS: + case FC_WRITE_REGS: //field1 = startreg, field2 = status this->writeMultipleRegisters(frame,field1, field2, frame[5]); break; - case MB_FC_READ_COILS: + case FC_READ_COILS: //field1 = startreg, field2 = numregs this->readCoils(field1, field2); break; - case MB_FC_READ_INPUT_STAT: + case FC_READ_INPUT_STAT: //field1 = startreg, field2 = numregs this->readInputStatus(field1, field2); break; - case MB_FC_READ_INPUT_REGS: + case FC_READ_INPUT_REGS: //field1 = startreg, field2 = numregs this->readInputRegisters(field1, field2); break; - case MB_FC_WRITE_COIL: + case FC_WRITE_COIL: //field1 = reg, field2 = status this->writeSingleCoil(field1, field2); break; - case MB_FC_WRITE_COILS: + case FC_WRITE_COILS: //field1 = startreg, field2 = numoutputs this->writeMultipleCoils(frame,field1, field2, frame[5]); break; default: - this->exceptionResponse(fcode, MB_EX_ILLEGAL_FUNCTION); + this->exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); } } -void Modbus::exceptionResponse(modbusFunctionCode fn, modbusResultCode excode) { +void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) { //Clean frame buffer free(_frame); _len = 2; _frame = (uint8_t*) malloc(_len); - if (!_frame) { - // Don't send reply if can't build frame - _reply = MB_REPLY_OFF; - return; - } _frame[0] = fn + 0x80; _frame[1] = excode; - _reply = MB_REPLY_NORMAL; + _reply = REPLY_NORMAL; } -void Modbus::readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { +void Modbus::readBits(uint16_t startreg, uint16_t numregs, FunctionCode fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x07D0) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } @@ -164,7 +133,7 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn //I think that ScadaBR request more than one in the single request //when you have more then one datapoint configured from same type. if (!this->searchRegister(startreg)) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } @@ -178,7 +147,7 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn _frame = (uint8_t*) malloc(_len); if (!_frame) { - this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); + this->exceptionResponse(fn, EX_SLAVE_FAILURE); return; } _frame[0] = fn; @@ -201,20 +170,20 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn startreg++; } - _reply = MB_REPLY_NORMAL; + _reply = REPLY_NORMAL; } -void Modbus::readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { +void Modbus::readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } //Check Address //*** See comments on readCoils method. if (!this->searchRegister(startreg)) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } @@ -227,7 +196,7 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode f _frame = (uint8_t*) malloc(_len); if (!_frame) { - this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); + this->exceptionResponse(fn, EX_SLAVE_FAILURE); return; } @@ -246,37 +215,37 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode f i++; } - _reply = MB_REPLY_NORMAL; + _reply = REPLY_NORMAL; } -void Modbus::writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn) { +void Modbus::writeSingleRegister(uint16_t reg, uint16_t value, FunctionCode fn) { //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using uint16_t (0x0000 - 0x0FFFF) //Check Address and execute (reg exists?) if (!this->Hreg(reg, value)) { - this->exceptionResponse(MB_FC_WRITE_REG, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } //Check for failure if (this->Hreg(reg) != value) { - this->exceptionResponse(MB_FC_WRITE_REG, MB_EX_SLAVE_FAILURE); + this->exceptionResponse(fn, EX_SLAVE_FAILURE); return; } - _reply = MB_REPLY_ECHO; + _reply = REPLY_ECHO; } -void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn) { +void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn) { //Check value if (numoutputs < 0x0001 || numoutputs > 0x007B || bytecount != 2 * numoutputs) { - this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } //Check Address (startreg...startreg + numregs) for (int k = 0; k < numoutputs; k++) { if (!this->searchRegister(HREG(startreg) + k)) { - this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } } @@ -285,12 +254,8 @@ void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t n free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_SLAVE_FAILURE); - return; - } - _frame[0] = MB_FC_WRITE_REGS; + _frame[0] = FC_WRITE_REGS; _frame[1] = startreg >> 8; _frame[2] = startreg & 0x00FF; _frame[3] = numoutputs >> 8; @@ -304,44 +269,44 @@ void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t n i++; } - _reply = MB_REPLY_NORMAL; + _reply = REPLY_NORMAL; } -void Modbus::writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn) { +void Modbus::writeSingleCoil(uint16_t reg, uint16_t status, FunctionCode fn) { //Check value (status) if (status != 0xFF00 && status != 0x0000) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } //Check Address and execute (reg exists?) if (!this->Coil(reg, (bool)status)) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } //Check for failure if (this->Coil(reg) != (bool)status) { - this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); + this->exceptionResponse(fn, EX_SLAVE_FAILURE); return; } - _reply = MB_REPLY_ECHO; + _reply = REPLY_ECHO; } -void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn) { +void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn) { //Check value uint16_t bytecount_calc = numoutputs / 8; if (numoutputs%8) bytecount_calc++; if (numoutputs < 0x0001 || numoutputs > 0x07B0 || bytecount != bytecount_calc) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_VALUE); + this->exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } //Check Address (startreg...startreg + numregs) for (int k = 0; k < numoutputs; k++) { if (!this->searchRegister(COIL(startreg) + k)) { - this->exceptionResponse(fn, MB_EX_ILLEGAL_ADDRESS); + this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } } @@ -350,10 +315,6 @@ void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numo free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); - if (!_frame) { - this->exceptionResponse(fn, MB_EX_SLAVE_FAILURE); - return; - } _frame[0] = fn; _frame[1] = startreg >> 8; @@ -374,7 +335,7 @@ void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numo startreg++; } - _reply = MB_REPLY_NORMAL; + _reply = REPLY_NORMAL; } bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { @@ -406,11 +367,11 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { +bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); - if (!_frame) return false; + _frame[0] = fn; _frame[1] = startreg >> 8; _frame[2] = startreg & 0x00FF; @@ -421,7 +382,7 @@ bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode f return true; } -bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { +bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); @@ -434,7 +395,7 @@ bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionC return true; } -bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn) { +bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); @@ -452,41 +413,41 @@ void Modbus::responcePDU(uint8_t* frame) { if ((fcode & 0x80) != 0) { Serial.print("Error: "); Serial.println(_frame[1]); - _reply = MB_REPLY_ERROR; + _reply = REPLY_ERROR; return; } uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; switch (fcode) { - case MB_FC_READ_REGS: + case FC_READ_REGS: //field1 = startreg, field2 = status this->writeMultipleRegisters(frame, field1, field2, frame[5]); - _reply = MB_REPLY_OFF; + _reply = REPLY_OFF; break; - case MB_FC_READ_COILS: + case FC_READ_COILS: //field1 = startreg, field2 = numoutputs this->writeMultipleCoils(frame, field1, field2, frame[5]); - _reply = MB_REPLY_OFF; + _reply = REPLY_OFF; break; - case MB_FC_READ_INPUT_STAT: - _reply = MB_REPLY_OFF; + case FC_READ_INPUT_STAT: + _reply = REPLY_OFF; break; - case MB_FC_READ_INPUT_REGS: - _reply = MB_REPLY_OFF; + case FC_READ_INPUT_REGS: + _reply = REPLY_OFF; break; - case MB_FC_WRITE_REG: + case FC_WRITE_REG: break; - case MB_FC_WRITE_REGS: + case FC_WRITE_REGS: break; - case MB_FC_WRITE_COIL: + case FC_WRITE_COIL: break; - case MB_FC_WRITE_COILS: + case FC_WRITE_COILS: break; default: - _reply = MB_REPLY_ERROR; + _reply = REPLY_ERROR; } - _reply = MB_REPLY_OFF; + _reply = REPLY_OFF; } void Modbus::cbEnable(bool state) { diff --git a/src/Modbus.h b/src/Modbus.h index ccca039..cc2f312 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -6,9 +6,10 @@ #pragma once #include "Arduino.h" +#include -#define MAX_REGS 32 -#define MAX_FRAME 128 +#define MB_MAX_REGS 32 +#define MB_MAX_FRAME 128 #define COIL_BASE 1 #define ISTS_BASE 10001 #define IREG_BASE 30001 @@ -36,9 +37,16 @@ typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); typedef struct TRegister { uint16_t address; uint16_t value; - struct TRegister* next; cbModbus get; cbModbus set; + bool operator <(const TRegister &obj) const + { + return address < obj.address; + } + bool operator ==(const TRegister &obj) const + { + return address == obj.address; + } }; uint16_t cbDefault(TRegister* reg, uint16_t val); @@ -46,62 +54,62 @@ uint16_t cbDefault(TRegister* reg, uint16_t val); class Modbus { public: //Function Codes - enum modbusFunctionCode { - MB_FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx - MB_FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx - MB_FC_READ_REGS = 0x03, // Read Holding Registers 4xxxx - MB_FC_READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx - MB_FC_WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx - MB_FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx - MB_FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx - MB_FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx - MB_FC_MASKWRITE_REG = 0x16, // Not implemented - MB_FC_READWRITE_REGS = 0x17 // Not implemented + enum FunctionCode { + FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx + FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx + FC_READ_REGS = 0x03, // Read Holding Registers 4xxxx + FC_READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx + FC_WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx + FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx + FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx + FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx + FC_MASKWRITE_REG = 0x16, // Not implemented + FC_READWRITE_REGS = 0x17 // Not implemented }; //Exception Codes - enum modbusResultCode { - MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported - MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists - MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range - MB_EX_SLAVE_FAILURE = 0x04, // Slave or Master Deice Fails to process request - MB_EX_ACKNOWLEDGE = 0x05, // Not used - MB_EX_SLAVE_DEVICE_BUSY = 0x06 // Not used + enum ResultCode { + 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 Deice Fails to process request + EX_ACKNOWLEDGE = 0x05, // Not used + EX_SLAVE_DEVICE_BUSY = 0x06 // Not used }; - bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + inline bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { return addReg(HREG(offset), value, numregs); } - bool Hreg(uint16_t offset, uint16_t value) { + inline bool Hreg(uint16_t offset, uint16_t value) { return Reg(HREG(offset), value); } - uint16_t Hreg(uint16_t offset) { + inline uint16_t Hreg(uint16_t offset) { return Reg(HREG(offset)); } inline bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { return addReg(COIL(offset), COIL_VAL(value), numregs); } - bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { + inline bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { return addReg(ISTS(offset), ISTS_VAL(value), numregs); } - bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + inline bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { return addReg(IREG(offset), value, numregs); } - bool Coil(uint16_t offset, bool value) { + inline bool Coil(uint16_t offset, bool value) { return Reg(COIL(offset), COIL_VAL(value)); } - bool Ists(uint16_t offset, bool value) { + inline bool Ists(uint16_t offset, bool value) { return Reg(ISTS(offset), ISTS_VAL(value)); } - bool Ireg(uint16_t offset, uint16_t value) { + inline bool Ireg(uint16_t offset, uint16_t value) { return Reg(IREG(offset), value); } - bool Coil(uint16_t offset) { + inline bool Coil(uint16_t offset) { return COIL_BOOL(Reg(COIL(offset))); } - bool Ists(uint16_t offset) { + inline bool Ists(uint16_t offset) { return ISTS_BOOL(Reg(ISTS(offset))); } - uint16_t Ireg(uint16_t offset) { + inline uint16_t Ireg(uint16_t offset) { return Reg(IREG(offset)); } @@ -110,53 +118,52 @@ class Modbus { cbEnable(false); } - bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(COIL(offset), cb, numregs); } - bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(COIL(offset), cb, numregs); } - bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(HREG(offset), cb, numregs); } - bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(HREG(offset), cb, numregs); } - bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(ISTS(offset), cb, numregs); } - bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(ISTS(offset), cb, numregs); } - bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(IREG(offset), cb, numregs); } - bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + inline bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(IREG(offset), cb, numregs); } private: - TRegister* _regs_head = NULL; // All function assuming to process Modbus frame from Slave perspective // I.e. readRegisters fill frame with local regisers vales // writeRegisters sets local registers according to frame values - void readBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - void readWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + void readBits(uint16_t startreg, uint16_t numregs, FunctionCode fn); + void readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn); void readRegisters(uint16_t startreg, uint16_t numregs) { - readWords(HREG(startreg), numregs, MB_FC_READ_REGS); + readWords(HREG(startreg), numregs, FC_READ_REGS); } - void writeSingleRegister(uint16_t reg, uint16_t value, modbusFunctionCode fn = MB_FC_WRITE_REG); - void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_REGS); - void readCoils(uint16_t startreg, uint16_t numregs) { - readBits(COIL(startreg), numregs, MB_FC_READ_COILS); + void writeSingleRegister(uint16_t reg, uint16_t value, FunctionCode fn = FC_WRITE_REG); + void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn = FC_WRITE_REGS); + inline void readCoils(uint16_t startreg, uint16_t numregs) { + readBits(COIL(startreg), numregs, FC_READ_COILS); } - void readInputStatus(uint16_t startreg, uint16_t numregs) { - readBits(ISTS(startreg), numregs, MB_FC_READ_INPUT_STAT); + inline void readInputStatus(uint16_t startreg, uint16_t numregs) { + readBits(ISTS(startreg), numregs, FC_READ_INPUT_STAT); } - void readInputRegisters(uint16_t startreg, uint16_t numregs) { - readWords(IREG(startreg), numregs, MB_FC_READ_INPUT_REGS); + inline void readInputRegisters(uint16_t startreg, uint16_t numregs) { + readWords(IREG(startreg), numregs, FC_READ_INPUT_REGS); } - void writeSingleCoil(uint16_t reg, uint16_t status, modbusFunctionCode fn = MB_FC_WRITE_COIL); - void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, modbusFunctionCode fn = MB_FC_WRITE_COILS); + void writeSingleCoil(uint16_t reg, uint16_t status, FunctionCode fn = FC_WRITE_COIL); + void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn = FC_WRITE_COILS); TRegister* searchRegister(uint16_t addr); @@ -164,28 +171,25 @@ class Modbus { protected: //Reply Types - enum modbusReplyCode { - MB_REPLY_OFF = 0x01, - MB_REPLY_ECHO = 0x02, - MB_REPLY_NORMAL = 0x03, - MB_REPLY_ERROR = 0x04, - MB_REPLY_UNEXPECTED = 0x05 + enum ReplyCode { + REPLY_OFF = 0x01, + REPLY_ECHO = 0x02, + REPLY_NORMAL = 0x03, + REPLY_ERROR = 0x04, + REPLY_UNEXPECTED = 0x05 }; + std::list _regs; uint8_t* _frame; uint8_t _len; uint8_t _reply; - void exceptionResponse(modbusFunctionCode fn, modbusResultCode excode); + void exceptionResponse(FunctionCode fn, ResultCode excode); void receivePDU(uint8_t* frame); //For Slave void responcePDU(uint8_t* frame); //For Master - TRegister* getHead() { - return _regs_head; - } - - bool readSlave(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - bool writeSlaveBits(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); - bool writeSlaveWords(uint16_t startreg, uint16_t numregs, modbusFunctionCode fn); + bool readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn); + bool writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn); + bool writeSlaveWords(uint16_t startreg, uint16_t numregs, FunctionCode fn); bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 1ca6927..5a9fb17 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -58,17 +58,17 @@ void ModbusIP::task() { continue; // for (n) } if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME - exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_SLAVE_FAILURE); + exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); client[n]->flush(); } else { _frame = (uint8_t*) malloc(_len); if (!_frame) { - exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_SLAVE_FAILURE); + exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); client[n]->flush(); } else { //for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU if (client[i]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame - exceptionResponse((modbusFunctionCode)client[n]->read(), MB_EX_ILLEGAL_VALUE); + exceptionResponse((FunctionCode)client[n]->read(), EX_ILLEGAL_VALUE); client[i]->flush(); } else { this->receivePDU(_frame); @@ -76,7 +76,7 @@ void ModbusIP::task() { } } } - if (_reply != MB_REPLY_OFF) { + if (_reply != REPLY_OFF) { //MBAP _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP _MBAP[5] = (_len+1) & 0x00FF; @@ -88,8 +88,9 @@ void ModbusIP::task() { //for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; memcpy(sbuf, _MBAP, sizeof(_MBAP)); memcpy(sbuf + sizeof(_MBAP), _frame, _len); - client[n]->write(sbuf, send_len); + //client[n]->write(_MBAP, sizeof(_MBAP));; + //client[n]->write(_frame, _len); } free(_frame); _len = 0; @@ -105,13 +106,13 @@ void ModbusIP::task() { void ModbusMasterIP::connect(IPAddress address) { WiFiClient::connect(address, MODBUSIP_PORT); } -void ModbusMasterIP::pushBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn){ +void ModbusMasterIP::pushBits(uint16_t address, uint16_t numregs, FunctionCode fn){ } -void ModbusMasterIP::pullBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn) { +void ModbusMasterIP::pullBits(uint16_t address, uint16_t numregs, FunctionCode fn) { } -void ModbusMasterIP::pushWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn) { +void ModbusMasterIP::pushWords(uint16_t address, uint16_t numregs, FunctionCode fn) { } -void ModbusMasterIP::pullWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn) { +void ModbusMasterIP::pullWords(uint16_t address, uint16_t numregs, FunctionCode fn) { } void ModbusMasterIP::task() { } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index a4c0e00..47faa88 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -50,15 +50,15 @@ typedef struct TTransaction; typedef uint16_t (*cbModbusSlave)(TTransaction* query, bool result); typedef struct TTransaction { uint16_t id; + Modbus::FunctionCode fn; uint16_t startreg; uint16_t numregs; uint32_t timestamp; - TTransaction* next; cbModbusSlave cb; }; class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { private: - TTransaction* _trans; + std::list _trans; uint8_t status; IPAddress ip; uint32_t queryStart; @@ -68,10 +68,10 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { ModbusMasterIP() : WiFiClient() { } void connect(IPAddress address); - void pushBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn); - void pullBits(uint16_t address, uint16_t numregs, modbusFunctionCode fn); - void pushWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn); - void pullWords(uint16_t address, uint16_t numregs, modbusFunctionCode fn); + void pushBits(uint16_t address, uint16_t numregs, FunctionCode fn); + void pullBits(uint16_t address, uint16_t numregs, FunctionCode fn); + void pushWords(uint16_t address, uint16_t numregs, FunctionCode fn); + void pullWords(uint16_t address, uint16_t numregs, FunctionCode fn); bool send() { uint16_t i; //MBAP @@ -119,12 +119,29 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pushCoil() { } void pullCoil(uint16_t offset, uint16_t numregs = 1) { - readSlave(offset, numregs, MB_FC_READ_COILS); + readSlave(offset, numregs, FC_READ_COILS); send(); } void pullCoils() { uint16_t offset; uint16_t numregs = 1; + std::list::iterator it = _regs.begin(); + if (it == _regs.end()) return; + offset = it->address; + while(++it != _regs.end()) + { + if (!IS_COIL(it->address)) continue; + if (it->address == offset + numregs) { + numregs++; + continue; + } + pullCoil(offset, numregs); + offset = it->address; + numregs = 1; + } + pullCoil(offset, numregs); + } + /* TRegister* creg = getHead(); if (!creg) return; while (creg && !IS_COIL(creg->address)) creg = creg->next; @@ -145,6 +162,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { pullCoil(offset, numregs); } } + */ void pushIsts() { } void pullIsts() { From 7846e2d474a3aed517547e1c9300f6e00f0abf12 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 29 May 2018 20:13:26 +0500 Subject: [PATCH 053/288] for merge --- keywords.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/keywords.txt b/keywords.txt index e09c827..bc62805 100644 --- a/keywords.txt +++ b/keywords.txt @@ -9,6 +9,7 @@ begin KEYWORD2 task KEYWORD2 onGet KEYWORD2 onSet KEYWORD2 +onConnect KEYWORD2 # Constants (LITERAL1) From 752000932ca50a863f12ee8c563ba559a9ecaedd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 31 May 2018 09:56:24 +0500 Subject: [PATCH 054/288] Callback API changed. Examples renamed. --- API.md | 143 +++++++++++++++ README.md | 165 ++++-------------- .../AnalogInput.ino} | 9 +- .../Callback.ino} | 17 +- .../HoldingReg.ino} | 8 +- examples/{TestLed/TestLed.ino => Led/Led.ino} | 7 +- .../MassOperations.ino} | 8 +- .../MultipleHRegDebug/MultipleHRegDebug.ino | 83 +++++++++ .../SwitchStatus.ino} | 8 +- keywords.txt | 83 +++------ library.properties | 2 +- src/Modbus.cpp | 70 ++------ src/Modbus.h | 94 +++++++--- src/ModbusIP_ESP8266.cpp | 4 +- src/ModbusIP_ESP8266.h | 4 +- 15 files changed, 425 insertions(+), 280 deletions(-) create mode 100644 API.md rename examples/{TestAnalogInput/TestAnalogInput.ino => AnalogInput/AnalogInput.ino} (88%) rename examples/{TestCallback/TestCallback.ino => Callback/Callback.ino} (84%) rename examples/{TestHoldingReg/TestHoldingReg.ino => HoldingReg/HoldingReg.ino} (92%) rename examples/{TestLed/TestLed.ino => Led/Led.ino} (93%) rename examples/{TestMassOperations/TestMassOperations.ino => MassOperations/MassOperations.ino} (93%) create mode 100644 examples/MultipleHRegDebug/MultipleHRegDebug.ino rename examples/{TestSwitchStatus/TestSwitchStatus.ino => SwitchStatus/SwitchStatus.ino} (90%) diff --git a/API.md b/API.md new file mode 100644 index 0000000..ef86f4d --- /dev/null +++ b/API.md @@ -0,0 +1,143 @@ +# Modbus Library for ESP8266/ESP32 + +## API + +### Add [multiple] regs + +```c +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 nemregs = 1); +``` + +### Write regs + +```c +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); +``` + +### Read regs + +```c +uint16_t Hreg(uint16_t offset); +bool Coil(uint16_t offset); +bool Ists(uint16_t offset); +uint16_t Ireg(uint16_t offset); +``` + +### Callbacks + +```c +void cbEnable(bool state = TRUE); +void cbDisable(); +``` + +Callback generation control. Callback generation is enabled by default. + +```c +void onConnect(cbModbusConnect cb) +``` + +*ModbusIP only.* Assign callback function on new incoming connection event. + +```c +typedef bool (*cbModbusConnect)(IPAddress ip) +``` + +Connect event callback function definition. Client IP address is passed as argument. + +```c +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) +``` + +Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. + + +```c +bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +``` + +Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onSetCoil(regId)` form to disconnect callback. + + +```c +bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +``` + +Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onGet(regId)` form to disconnect callback. + +### Macros + +```c +#define COIL_VAL(v) +#define COIL_BOOL(v) +#define ISTS_VAL(v) +#define ISTS_BOOL(v) +``` + +### ModBus IP specific + +```c +void begin(); +void task(); +``` + +### Callback example + +```c +ModbusIP mb; +bool coil = false; // Define external variable to get/set value +uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value + coil = COIL_BOOL(val); + return val; // Returns value to be saved to TRegister structure +} +uint16_t cbCoilGet(TRegister* reg, uint16_t val) { + return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request +} +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; // Return 'true' to allow connection or 'false' to drop connection +} +ModbusIP mb; // ModbusIP object +void setup() { +... + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + mb.addCoil(COIL_NR); // Add Coil + mb.onSetCoil(COIL_NR, cbCoilSet); // Add callback on Coil COIL_NR value set + mb.onGetCoil(COIL_NR, cbCoilGet); // Add callback on Coil COIL_NR value get +... +} +void loop() { +... + mb.task(); +... +} +``` + + +## Contributions + +https://github.com/emelianov/modbus-esp8266 + +a.m.emelianov@gmail.com + +Original version: + +http://github.com/andresarmento/modbus-esp8266 + +prof (at) andresarmento (dot) com + +## License + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/README.md b/README.md index c034d21..6c756c3 100644 --- a/README.md +++ b/README.md @@ -5,153 +5,56 @@ used in industrial automation and can be used in other areas, such as home autom The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). -In the current version the library allows the ESP8266/ESP32 operate as a slave, supporting Modbus IP via wireless network. For more information about Modbus see: +In the current version the library allows the ESP8266/ESP32 operate async as a slave, supporting Modbus IP via wireless network. For more information about Modbus see: -http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf +http://pt.wikipedia.org/wiki/Modbus +http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf ## Features -
        -
      • Operates as a slave
      • -
      • Supports Modbus IP (TCP)
      • -
      • Reply exception messages for all supported functions
      • -
      • Modbus functions supported:
      • -
          -
        • 0x01 - Read Coils
        • -
        • 0x02 - Read Input Status (Read Discrete Inputs)
        • -
        • 0x03 - Read Holding Registers
        • -
        • 0x04 - Read Input Registers
        • -
        • 0x05 - Write Single Coil
        • -
        • 0x06 - Write Single Register
        • -
        • 0x0F - Write Multiple Coils
        • -
        • 0x10 - Write Multiple Registers
        • -
        -
      • Callbacks for
      • -
          -
        • - Incoming IP connection
        • -
        • - Read specific Register
        • -
        • - Write specific Register
        • -
        -
      - -Notes: +* Supported platforms are + * ESP8266 + * ESP32 +* Operates as a slave +* Fully async operations. No loop locks. +* Supports Modbus IP (TCP) +* Reply exception messages for all supported functions +* Modbus functions supported: + * 0x01 - Read Coils + * 0x02 - Read Input Status (Read Discrete Inputs) + * 0x03 - Read Holding Registers + * 0x04 - Read Input Registers + * 0x05 - Write Single Coil + * 0x06 - Write Single Register + * 0x0F - Write Multiple Coils + * 0x10 - Write Multiple Registers +* Callbacks for + * Incoming IP connection + * Read specific Register + * Write specific Register + +## Notes: 1. When using Modbus IP the transport protocol is TCP (port 502). -2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in ScadaBR (http://www.scadabr.com.br) -offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the CAS Modbus Scanner -(http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. - -3. Early in the library Modbus.h file there is an option to limit the operation -to the functions of Holding Registers, saving space in the program memory. -Just comment out the following line: - -``` -#define USE_HOLDING_REGISTERS_ONLY -``` -Thus, only the following functions are supported: -
        -
      • 0x03 - Read Holding Registers
      • -
      • 0x06 - Write Single Register
      • -
      • 0x10 - Write Multiple Registers
      • -
      - - -## API - -### Add [multiple] regs -``` -bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1) -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 nemregs = 1) -``` -### Write regs -``` -bool Reg(uint16_t address, uint16_t value) -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) -``` -### Read regs -``` -uint16_t Reg(uint16_t address) -uint16_t Hreg(uint16_t offset) -bool Coil(uint16_t offset) -bool Ists(uint16_t offset) -uint16_t Ireg(uint16_t offset) -``` -### Callbacks -``` -bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1) -void onConnect(cbModbusConnect cb) -typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) -typedef bool (*cbModbusConnect)(IPAddress ip) -``` -### Macros -``` -#define COIL(n) -#define ISTS(n) -#define IREG(n) -#define HREG(n) -#define COIL_VAL(v) -#define COIL_BOOL(v) -#define ISTS_VAL(v) -#define ISTS_BOOL(v) -``` -### ModBus IP specific -``` -void begin() -void task() -``` - -### Callback example - -``` -bool coil = false; // Define external variable to get/set value -uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value - coil = COIL_BOOL(val); - return val; // Returns value to be saved to TRegister structure -} -uint16_t cbCoilGet(TRegister* reg, uint16_t val) { - return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request -} -bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; // Return 'true' to allow connection or 'false' to drop connection -} -ModbusIP mb; // ModbusIP object -void setup() { -... - mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); - mb.addCoil(COIL_NR); // Add Coil - mb.onSet(COIL(COIL_NR), cbCoilSet); // Add callback on Coil COIL_NR value set - mb.onGet(COIL(COIL_NR), cbCoilGet); // Add callback on Coil COIL_NR value get -... -} -void loop() { -... - mb.task(); -... -} -``` +2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) +offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. +For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/API/md) ## Contributions -https://github.com/emelianov/modbus-esp8266
      +https://github.com/emelianov/modbus-esp8266 + a.m.emelianov@gmail.com -Original version:
      -http://github.com/andresarmento/modbus-esp8266
      +Original version: + +http://github.com/andresarmento/modbus-esp8266 + prof (at) andresarmento (dot) com ## License The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. - diff --git a/examples/TestAnalogInput/TestAnalogInput.ino b/examples/AnalogInput/AnalogInput.ino similarity index 88% rename from examples/TestAnalogInput/TestAnalogInput.ino rename to examples/AnalogInput/AnalogInput.ino index 6769420..e53ed72 100644 --- a/examples/TestAnalogInput/TestAnalogInput.ino +++ b/examples/AnalogInput/AnalogInput.ino @@ -1,5 +1,5 @@ /* - Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) + Modbus-Arduino Example - Test Analog Input (Modbus IP ESP8266) Read Analog sensor on Pin ADC (ADC input between 0 ... 1V) Original library Copyright by André Sarmento Barbosa @@ -12,7 +12,7 @@ #ifdef ESP8266 #include -#else +#else //ESP32 #include #endif #include @@ -26,7 +26,11 @@ ModbusIP mb; long ts; void setup() { + #ifdef ESP8266 Serial.begin(74880); + #else + Serial.begin(115200); + #endif WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { @@ -56,4 +60,5 @@ void loop() { //Setting raw value (0-1024) mb.Ireg(SENSOR_IREG, analogRead(A0)); } + delay(100); } diff --git a/examples/TestCallback/TestCallback.ino b/examples/Callback/Callback.ino similarity index 84% rename from examples/TestCallback/TestCallback.ino rename to examples/Callback/Callback.ino index 59e80b9..058c28f 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/Callback/Callback.ino @@ -12,7 +12,7 @@ #ifdef ESP8266 #include -#else +#else //ESP32 #include #endif #include @@ -42,9 +42,12 @@ bool cbConn(IPAddress ip) { } void setup() { + #ifdef ESP8266 Serial.begin(74880); - - WiFi.begin("ssid", "password"); + #else + Serial.begin(115200); + #endif + WiFi.begin("SID", "PASSWORD"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -60,12 +63,12 @@ void setup() { mb.begin(); pinMode(ledPin, OUTPUT); - mb.addReg(COIL(LED_COIL)); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) - mb.onSet(COIL(LED_COIL), cbLed); // Add callback on Coil LED_COIL value set + mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) + mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set } void loop() { //Call once inside loop() - all magic here mb.task(); - yield(); -} \ No newline at end of file + delay(100); +} diff --git a/examples/TestHoldingReg/TestHoldingReg.ino b/examples/HoldingReg/HoldingReg.ino similarity index 92% rename from examples/TestHoldingReg/TestHoldingReg.ino rename to examples/HoldingReg/HoldingReg.ino index 93d4edd..93a09c9 100644 --- a/examples/TestHoldingReg/TestHoldingReg.ino +++ b/examples/HoldingReg/HoldingReg.ino @@ -13,7 +13,7 @@ #ifdef ESP8266 #include -#else +#else //ESP32 #include #endif #include @@ -26,7 +26,11 @@ const int TEST_HREG = 100; ModbusIP mb; void setup() { + #ifdef ESP8266 Serial.begin(74880); + #else + Serial.begin(115200); + #endif WiFi.begin("your_ssid", "your_password"); @@ -47,5 +51,5 @@ void setup() { void loop() { //Call once inside loop() - all magic here mb.task(); - + delay(100); } diff --git a/examples/TestLed/TestLed.ino b/examples/Led/Led.ino similarity index 93% rename from examples/TestLed/TestLed.ino rename to examples/Led/Led.ino index 1115ded..666392a 100644 --- a/examples/TestLed/TestLed.ino +++ b/examples/Led/Led.ino @@ -12,7 +12,7 @@ #ifdef ESP8266 #include -#else +#else //ESP32 #include #endif #include @@ -26,7 +26,11 @@ const int ledPin = 0; //GPIO0 ModbusIP mb; void setup() { + #ifdef ESP8266 Serial.begin(74880); + #else + Serial.begin(115200); + #endif WiFi.begin("your_ssid", "your_password"); @@ -52,4 +56,5 @@ void loop() { //Attach ledPin to LED_COIL register digitalWrite(ledPin, mb.Coil(LED_COIL)); + delay(100); } \ No newline at end of file diff --git a/examples/TestMassOperations/TestMassOperations.ino b/examples/MassOperations/MassOperations.ino similarity index 93% rename from examples/TestMassOperations/TestMassOperations.ino rename to examples/MassOperations/MassOperations.ino index 488d744..ce73048 100644 --- a/examples/TestMassOperations/TestMassOperations.ino +++ b/examples/MassOperations/MassOperations.ino @@ -6,7 +6,7 @@ http://github.com/andresarmento/modbus-arduino Current version - (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 */ @@ -49,7 +49,11 @@ bool cbConn(IPAddress ip) { } void setup() { + #ifdef ESP8266 Serial.begin(74880); + #else + Serial.begin(115200); + #endif WiFi.begin("ssid", "password"); @@ -75,5 +79,5 @@ void setup() { void loop() { //Call once inside loop() - all magic here mb.task(); - yield(); + delay(100); } \ No newline at end of file diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/MultipleHRegDebug/MultipleHRegDebug.ino new file mode 100644 index 0000000..0a0650c --- /dev/null +++ b/examples/MultipleHRegDebug/MultipleHRegDebug.ino @@ -0,0 +1,83 @@ +/* + Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +#define LEN 10 + +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + Serial.print("Read. Reg RAW#: "); + Serial.print(reg->address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + Serial.print("Write. Reg RAW#: "); + Serial.print(reg->address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif + + WiFi.begin("ssid", "pass"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + + if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs + mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get + mb.onSetHreg(0, cbWrite, LEN); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(100); +} diff --git a/examples/TestSwitchStatus/TestSwitchStatus.ino b/examples/SwitchStatus/SwitchStatus.ino similarity index 90% rename from examples/TestSwitchStatus/TestSwitchStatus.ino rename to examples/SwitchStatus/SwitchStatus.ino index 70d7fba..5d9730a 100644 --- a/examples/TestSwitchStatus/TestSwitchStatus.ino +++ b/examples/SwitchStatus/SwitchStatus.ino @@ -12,7 +12,7 @@ #ifdef ESP8266 #include -#else +#else //ESP32 #include #endif #include @@ -26,6 +26,11 @@ const int switchPin = 0; //GPIO0 ModbusIP mb; void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { @@ -46,4 +51,5 @@ void loop() { //Attach switchPin to SWITCH_ISTS register mb.Ists(SWITCH_ISTS, digitalRead(switchPin)); + delay(100); } diff --git a/keywords.txt b/keywords.txt index 17c2f28..e6a8ea1 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,63 +1,38 @@ -# Syntax Coloring Map For ModbusIP +# Syntax Coloring Map For ModbusIP_ESP8266 # Datatypes (KEYWORD1) -ModbusIP KEYWORD1 +ModbusIP KEYWORD1 +ModbusMasterIP KEYWORD1 ModbusIP_ESP8266 KEYWORD1 # Methods and Functions (KEYWORD2) begin KEYWORD2 task KEYWORD2 -onGet KEYWORD2 -onSet KEYWORD2 onConnect KEYWORD2 +cbEnable KEYWORD2 +cbDisable KEYWORD2 +eventSource KEYWORD2 +onGetCoil KEYWORD2 +onSetCoil KEYWORD2 +onGetHreg KEYWORD2 +onSetHreg KEYWORD2 +onGetIreg KEYWORD2 +onSetIreg KEYWORD2 +onGetIsts KEYWORD2 +onSetIsts KEYWORD2 +addCoil KEYWORD2 +addIsts KEYWORD2 +addIreg KEYWORD2 +addHreg KEYWORD2 +Coil KEYWORD2 +Ists KEYWORD2 +Ireg KEYWORD2 +Hreg KEYWORD2 -# Constants (LITERAL1) - -# Syntax Coloring Map For Modbus - -# Datatypes (KEYWORD1) -Modbus KEYWORD1 -TRegister KEYWORD1 - -# Methods and Functions (KEYWORD2) -readCoils KEYWORD2 -readInputStatus KEYWORD2 -readRegisters KEYWORD2 -readInputRegisters KEYWORD2 -writeSingleCoil KEYWORD2 -writeSingleRegister KEYWORD2 -writeMultipleCoils KEYWORD2 -writeMultipleRegisters KEYWORD2 -searchRegister KEYWORD2 -receivePDU KEYWORD2 -addReg KEYWORD2 -addCoil KEYWORD2 -addIsts KEYWORD2 -addIreg KEYWORD2 -addHreg KEYWORD2 -Reg KEYWORD2 -Coil KEYWORD2 -Ists KEYWORD2 -Ireg KEYWORD2 -Hreg KEYWORD2 - -# Constants (LITERAL1) -MB_FC_READ_COILS LITERAL1 -MB_FC_READ_INPUTS LITERAL1 -MB_FC_READ_REGS LITERAL1 -MB_FC_READ_INPUT_STAT LITERAL1 -MB_FC_WRITE_COIL LITERAL1 -MB_FC_WRITE_REG LITERAL1 -MB_FC_WRITE_COILS LITERAL1 -MB_FC_WRITE_REGS LITERAL1 -MB_REPLY_OFF LITERAL1 -MB_REPLY_ECHO LITERAL1 -MB_REPLY_NORMAL LITERAL1 -COIL_BASE LITERAL1 -ISTS_BASE LITERAL1 -IREG_BASE LITERAL1 -HREG_BASE LITERAL1 -COIL LITERAL1 -ISTS LITERAL1 -IREG LITERAL1 -HERG LITERAL1 \ No newline at end of file +# Constants and Macros (LITERAL1) +BIT_VAL LITERAL1 +BIT_BOOL LITERAL1 +COIL_VAL LITERAL1 +COIL_BOOL LITERAL1 +ISTS_VAL LITERAL1 +ISTS_BOOL LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties index 85432ba..6b09965 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=1.1 +version=1.2 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov sentence=Modbus Library for ESP8266/ESP32 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index c1a2ad4..be59a62 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,7 +1,7 @@ /* - Modbus.h - Header for Modbus Base Library + Modbus.cpp - Implementation for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" @@ -60,7 +60,11 @@ bool Modbus::Reg(uint16_t address, uint16_t value) { reg = this->searchRegister(address); //if found then assign the register value to the new value. if (reg) { - reg->value = reg->set(reg, value); + if (cbEnabled) { + reg->value = reg->set(reg, value); + } else { + reg->value = value; + } return true; } else return false; @@ -70,62 +74,15 @@ uint16_t Modbus::Reg(uint16_t address) { TRegister *reg; reg = this->searchRegister(address); if(reg) - return reg->get(reg, reg->value); + if (cbEnabled) { + return reg->get(reg, reg->value); + } else { + return reg->value; + } else return 0; } -bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { - return this->addReg(HREG(offset), value, numregs); -} - -bool Modbus::Hreg(uint16_t offset, uint16_t value) { - return Reg(HREG(offset), value); -} - -uint16_t Modbus::Hreg(uint16_t offset) { - return Reg(HREG(offset)); -} - -#ifndef USE_HOLDING_REGISTERS_ONLY - bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { - return this->addReg(COIL(offset), COIL_VAL(value), numregs); - } - - bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { - return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); - } - - bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { - return this->addReg(IREG(offset), value, numregs); - } - - bool Modbus::Coil(uint16_t offset, bool value) { - return Reg(COIL(offset), COIL_VAL(value)); - } - - bool Modbus::Ists(uint16_t offset, bool value) { - return Reg(ISTS(offset), ISTS_VAL(value)); - } - - bool Modbus::Ireg(uint16_t offset, uint16_t value) { - return Reg(IREG(offset), value); - } - - bool Modbus::Coil(uint16_t offset) { - return COIL_BOOL(Reg(COIL(offset))); - } - - bool Modbus::Ists(uint16_t offset) { - return ISTS_BOOL(Reg(ISTS(offset))); - } - - uint16_t Modbus::Ireg(uint16_t offset) { - return Reg(IREG(offset)); - } -#endif - - void Modbus::receivePDU(uint8_t* frame) { uint8_t fcode = frame[0]; uint16_t field1 = (word)frame[1] << 8 | (word)frame[2]; @@ -551,4 +508,7 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { numregs--; } return atLeastOne; +} +void Modbus::cbEnable(bool state) { + cbEnabled = state; } \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index d05ca99..5c6a128 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,7 +1,7 @@ /* Modbus.h - Header for Modbus Base Library Copyright (C) 2014 André Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Arduino.h" @@ -84,39 +84,93 @@ class Modbus { #endif TRegister* searchRegister(uint16_t addr); - + bool cbEnabled = true; protected: uint8_t* _frame; uint8_t _len; uint8_t _reply; void receivePDU(uint8_t* frame); - public: - bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); + + bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); - bool Hreg(uint16_t offset, uint16_t value); - uint16_t Hreg(uint16_t offset); - - #ifndef USE_HOLDING_REGISTERS_ONLY - 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); + public: - bool Coil(uint16_t offset, bool value); - bool Ists(uint16_t offset, bool value); - bool Ireg(uint16_t offset, uint16_t value); + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + return addReg(HREG(offset), value, numregs); + } + bool Hreg(uint16_t offset, uint16_t value) { + return Reg(HREG(offset), value); + } + uint16_t Hreg(uint16_t offset) { + return Reg(HREG(offset)); + } - bool Coil(uint16_t offset); - bool Ists(uint16_t offset); - uint16_t Ireg(uint16_t offset); + #ifndef USE_HOLDING_REGISTERS_ONLY + bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { + return addReg(COIL(offset), COIL_VAL(value), numregs); + } + bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { + return addReg(ISTS(offset), ISTS_VAL(value), numregs); + } + bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + return addReg(IREG(offset), value, numregs); + } + + bool Coil(uint16_t offset, bool value) { + return Reg(COIL(offset), COIL_VAL(value)); + } + bool Ists(uint16_t offset, bool value) { + return Reg(ISTS(offset), ISTS_VAL(value)); + } + bool Ireg(uint16_t offset, uint16_t value) { + return Reg(IREG(offset), value); + } + + bool Coil(uint16_t offset) { + return COIL_BOOL(Reg(COIL(offset))); + } + bool Ists(uint16_t offset) { + return ISTS_BOOL(Reg(ISTS(offset))); + } + uint16_t Ireg(uint16_t offset) { + return Reg(IREG(offset)); + } #endif + + void cbEnable(bool state = true); + void cbDisable() { + cbEnable(false); + } - bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onGet(COIL(offset), cb, numregs); + } + bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onSet(COIL(offset), cb, numregs); + } + bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onGet(HREG(offset), cb, numregs); + } + bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onSet(HREG(offset), cb, numregs); + } + bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onGet(ISTS(offset), cb, numregs); + } + bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onSet(ISTS(offset), cb, numregs); + } + bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onGet(IREG(offset), cb, numregs); + } + bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + return onSet(IREG(offset), cb, numregs); + } }; #endif //MODBUS_H diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index f376f3c..599e169 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -1,7 +1,7 @@ /* - Modbus.h - Header for Modbus Base Library + ModbusIP_ESP8266.cpp - Implementation for ModbusIP Library Copyright (C) 2014 André Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "ModbusIP_ESP8266.h" diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 9b5d9fa..143f874 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,7 +1,7 @@ /* - Modbus.h - Header for Modbus Base Library + ModbusIP_ESP8266.h - Header for ModbusIP Library Copyright (C) 2014 André Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include #ifdef ESP8266 From 694f2b8c6708f12978da123f0fb00979c8d4dbcf Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 31 May 2018 10:04:53 +0500 Subject: [PATCH 055/288] Mistype fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c756c3..b6a77f1 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/API/md) +For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/API.md) ## Contributions From 34dbe9f080f521e683f54654ca0d940a8fbaf299 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 31 May 2018 10:05:38 +0500 Subject: [PATCH 056/288] Mistype fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6a77f1..c09501a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/API.md) +For API specefication see [API.md](https://github.com/emelianov/modbus-esp8266/API.md) ## Contributions From 57698aaf3634421cdd0c2349bd1def46b6c87587 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 31 May 2018 10:06:57 +0500 Subject: [PATCH 057/288] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c09501a..5738e8b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -For API specefication see [API.md](https://github.com/emelianov/modbus-esp8266/API.md) +For API specefication see [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) ## Contributions From 366c4c60c2f7d143833048dfa3f860e7c76500be Mon Sep 17 00:00:00 2001 From: per1234 Date: Wed, 30 May 2018 23:40:27 -0700 Subject: [PATCH 058/288] Use correct separator in keywords.txt The Arduino IDE currently requires the use of a single true tab separator between the name and identifier. Without this tab the keyword is not highlighted. Reference: https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#keywords --- keywords.txt | 62 ++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/keywords.txt b/keywords.txt index e6a8ea1..33a2793 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,38 +1,38 @@ # Syntax Coloring Map For ModbusIP_ESP8266 # Datatypes (KEYWORD1) -ModbusIP KEYWORD1 -ModbusMasterIP KEYWORD1 -ModbusIP_ESP8266 KEYWORD1 +ModbusIP KEYWORD1 +ModbusMasterIP KEYWORD1 +ModbusIP_ESP8266 KEYWORD1 # Methods and Functions (KEYWORD2) -begin KEYWORD2 -task KEYWORD2 -onConnect KEYWORD2 -cbEnable KEYWORD2 -cbDisable KEYWORD2 -eventSource KEYWORD2 -onGetCoil KEYWORD2 -onSetCoil KEYWORD2 -onGetHreg KEYWORD2 -onSetHreg KEYWORD2 -onGetIreg KEYWORD2 -onSetIreg KEYWORD2 -onGetIsts KEYWORD2 -onSetIsts KEYWORD2 -addCoil KEYWORD2 -addIsts KEYWORD2 -addIreg KEYWORD2 -addHreg KEYWORD2 -Coil KEYWORD2 -Ists KEYWORD2 -Ireg KEYWORD2 -Hreg KEYWORD2 +begin KEYWORD2 +task KEYWORD2 +onConnect KEYWORD2 +cbEnable KEYWORD2 +cbDisable KEYWORD2 +eventSource KEYWORD2 +onGetCoil KEYWORD2 +onSetCoil KEYWORD2 +onGetHreg KEYWORD2 +onSetHreg KEYWORD2 +onGetIreg KEYWORD2 +onSetIreg KEYWORD2 +onGetIsts KEYWORD2 +onSetIsts KEYWORD2 +addCoil KEYWORD2 +addIsts KEYWORD2 +addIreg KEYWORD2 +addHreg KEYWORD2 +Coil KEYWORD2 +Ists KEYWORD2 +Ireg KEYWORD2 +Hreg KEYWORD2 # Constants and Macros (LITERAL1) -BIT_VAL LITERAL1 -BIT_BOOL LITERAL1 -COIL_VAL LITERAL1 -COIL_BOOL LITERAL1 -ISTS_VAL LITERAL1 -ISTS_BOOL LITERAL1 \ No newline at end of file +BIT_VAL LITERAL1 +BIT_BOOL LITERAL1 +COIL_VAL LITERAL1 +COIL_BOOL LITERAL1 +ISTS_VAL LITERAL1 +ISTS_BOOL LITERAL1 From e75cf2ce8ad292a769c0c041a7565b0b4b90b294 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 5 Jun 2018 14:36:59 +0500 Subject: [PATCH 059/288] Current changes --- examples/Master/Master.ino | 25 +++++++--- src/Modbus.cpp | 96 +++++++++++++++++++++++++------------- src/Modbus.h | 3 ++ src/ModbusIP_ESP8266.h | 12 ++--- 4 files changed, 88 insertions(+), 48 deletions(-) diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 7aea8af..3818107 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -22,11 +22,16 @@ const int ledPin = 0; //GPIO0 //ModbusIP object ModbusMasterIP mb; - +ModbusIP slave; +uint16_t gc(TRegister* r, uint16_t v) { + Serial.print("Set ger: "); + Serial.println(v); + return v; +} void setup() { Serial.begin(74880); - WiFi.begin("your_ssid", "your_password"); + WiFi.begin("EW", "iMpress6264"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -42,15 +47,21 @@ void setup() { pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); - mb.connect() - mb.readSlave(COIL(LED_COIL), 1, MB_FC_READ_COILS); - mb.send(); + mb.onSetCoil(LED_COIL, gc); + mb.connect(IPAddress(192, 168, 30, 116)); + mb.pullCoil(LED_COIL); + + slave.begin(); + slave.addCoil(LED_COIL); + slave.onSetCoil(LED_COIL, gc); + } void loop() { //Call once inside loop() - all magic here mb.get(); - + slave.task(); //Attach ledPin to LED_COIL register digitalWrite(ledPin, mb.Coil(LED_COIL)); -} \ No newline at end of file + delay(100); +} diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 5df127f..5e50542 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -12,6 +12,7 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { TRegister* Modbus::searchRegister(uint16_t address) { const TRegister tmp = {address, 0, cbDefault, cbDefault}; std::list::iterator it = std::find(_regs.begin(), _regs.end(), tmp); + //std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); if (it != _regs.end()) return &*it; return NULL; } @@ -21,10 +22,15 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { if (_regs.size() + numregs > MB_MAX_REGS) return false; #endif for (uint16_t i = 0; i < numregs; i++) { - _regs.push_front({address + i, value, cbDefault, cbDefault}); + if (!searchRegister(address + i)) + _regs.push_back({address + i, value, cbDefault, cbDefault}); } _regs.sort(); - _regs.unique(); + //std::sort(_regs.begin(),_regs.end()); + //_regs.unique(); + //std::vector::iterator it; + //it = std::unique (_regs.begin(), _regs.end()); + //_regs.resize( std::distance(_regs.begin(),it) ); return true; } @@ -61,7 +67,7 @@ void Modbus::receivePDU(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 bytecount_calc; switch (fcode) { case FC_WRITE_REG: @@ -96,12 +102,47 @@ void Modbus::receivePDU(uint8_t* frame) { case FC_WRITE_COIL: //field1 = reg, field2 = status - this->writeSingleCoil(field1, field2); + //this->writeSingleCoil(field1, field2); + //Check value (status) + if (field2 != 0xFF00 && field2 != 0x0000) { + this->exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + + //Check Address and execute (reg exists?) + if (!this->Coil(field1, (bool)field2)) { + this->exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + + //Check for failure + if (this->Coil(reg) != (bool)status) { + this->exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + + _reply = REPLY_ECHO; break; case FC_WRITE_COILS: - //field1 = startreg, field2 = numoutputs - this->writeMultipleCoils(frame,field1, field2, frame[5]); + //field1 = startreg, field2 = numoutputs, frame[5] = bytecount + bytecount_calc = field2 / 8; + if (field2%8) bytecount_calc++; + if (field2 < 0x0001 || field2 > 0x07B0 || frame[5] != bytecount_calc) { + this->exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + Serial.println("Size ok"); + //Check Address (startreg...startreg + numregs) + for (int k = 0; k < field2; k++) { + if (!this->searchRegister(COIL(field1) + k)) { + this->exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + } + this->writeMultipleCoils(frame + MB_FRAME_HEADER, field1, field2, frame[5]); + successResponce(field1, field2, FC_WRITE_COILS); + _reply = REPLY_NORMAL; break; default: @@ -255,7 +296,7 @@ void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t n _len = 5; _frame = (uint8_t*) malloc(_len); - _frame[0] = FC_WRITE_REGS; + _frame[0] = fn; _frame[1] = startreg >> 8; _frame[2] = startreg & 0x00FF; _frame[3] = numoutputs >> 8; @@ -294,23 +335,7 @@ void Modbus::writeSingleCoil(uint16_t reg, uint16_t status, FunctionCode fn) { _reply = REPLY_ECHO; } -void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn) { - //Check value - uint16_t bytecount_calc = numoutputs / 8; - if (numoutputs%8) bytecount_calc++; - if (numoutputs < 0x0001 || numoutputs > 0x07B0 || bytecount != bytecount_calc) { - this->exceptionResponse(fn, EX_ILLEGAL_VALUE); - return; - } - - //Check Address (startreg...startreg + numregs) - for (int k = 0; k < numoutputs; k++) { - if (!this->searchRegister(COIL(startreg) + k)) { - this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; - } - } - +void Modbus::successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn) { //Clean frame buffer free(_frame); _len = 5; @@ -321,13 +346,15 @@ void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numo _frame[2] = startreg & 0x00FF; _frame[3] = numoutputs >> 8; _frame[4] = numoutputs & 0x00FF; +} +void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn) { uint8_t bitn = 0; uint16_t totoutputs = numoutputs; uint16_t i; while (numoutputs--) { i = (totoutputs - numoutputs) / 8; - this->Coil(startreg, bitRead(frame[6+i], bitn)); + this->Coil(startreg, bitRead(frame[i], bitn)); //increment the bit index bitn++; if (bitn == 8) bitn = 0; @@ -335,7 +362,7 @@ void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numo startreg++; } - _reply = REPLY_NORMAL; + //_reply = REPLY_NORMAL; } bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { @@ -377,8 +404,8 @@ bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn) { _frame[2] = startreg & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; - Serial.print("_frame[2] = "); - Serial.println(_frame[2]); + // Serial.print("_frame[2] = "); + //Serial.println(_frame[2]); return true; } @@ -416,9 +443,11 @@ void Modbus::responcePDU(uint8_t* frame) { _reply = REPLY_ERROR; return; } - 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 field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; + //uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; + uint16_t field1 = 100; + uint16_t field2 = 1; +Serial.println(fcode); switch (fcode) { case FC_READ_REGS: //field1 = startreg, field2 = status @@ -427,7 +456,10 @@ void Modbus::responcePDU(uint8_t* frame) { break; case FC_READ_COILS: //field1 = startreg, field2 = numoutputs - this->writeMultipleCoils(frame, field1, field2, frame[5]); + Serial.println(field1); + Serial.println(field2); + Serial.println(frame[0]); + this->writeMultipleCoils(frame, field1, field2, frame[0]); _reply = REPLY_OFF; break; case FC_READ_INPUT_STAT: diff --git a/src/Modbus.h b/src/Modbus.h index cc2f312..32d8b99 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -10,6 +10,7 @@ #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 +#define MB_FRAME_HEADER 6 #define COIL_BASE 1 #define ISTS_BASE 10001 #define IREG_BASE 30001 @@ -180,10 +181,12 @@ class Modbus { }; std::list _regs; + //std::vector _regs; uint8_t* _frame; uint8_t _len; uint8_t _reply; void exceptionResponse(FunctionCode fn, ResultCode excode); + void successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn); void receivePDU(uint8_t* frame); //For Slave void responcePDU(uint8_t* frame); //For Master diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 47faa88..7a2ce51 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -18,13 +18,6 @@ #define MODBUSIP_MAX_CLIENTS 4 -#define MODBUSIP_SLAVE 1 -#define MODBUSIP_MASTER 2 -#define MODBUSIP_PULL_MS 100 -#define MODBUSIP_PUSH 3 -#define MODBUSIP_PULL 4 -#define MODBUSIP_IDLE 5 - // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); typedef union MBAP { @@ -119,13 +112,14 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pushCoil() { } void pullCoil(uint16_t offset, uint16_t numregs = 1) { - readSlave(offset, numregs, FC_READ_COILS); + readSlave(COIL(offset), numregs, FC_READ_COILS); send(); } void pullCoils() { uint16_t offset; uint16_t numregs = 1; - std::list::iterator it = _regs.begin(); + std::list::iterator it = _regs.begin(); + //std::vector::iterator it = _regs.begin(); if (it == _regs.end()) return; offset = it->address; while(++it != _regs.end()) From 328c47bc692da079edde5cac562a5f9db6a84eab Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 8 Jun 2018 09:55:06 +0500 Subject: [PATCH 060/288] Test Mster routines --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa85607 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode/arduino.json +.vscode/c_cpp_properties.json From 9fe29f1d3c95b118c4bb6d2960a705b0c64280fa Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 8 Jun 2018 09:55:22 +0500 Subject: [PATCH 061/288] Test master routines --- .vscode/c_cpp_properties.json | 10 +++++++--- src/Modbus.cpp | 2 +- src/ModbusIP_ESP8266.h | 9 +++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 8eddd0a..0f232f7 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -43,7 +43,9 @@ ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" - } + }, + "cStandard": "c11", + "cppStandard": "c++17" }, { "name": "Win32", @@ -67,8 +69,10 @@ ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" - } + }, + "cStandard": "c11", + "cppStandard": "c++17" } ], - "version": 3 + "version": 4 } \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 5e50542..50f127e 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -116,7 +116,7 @@ void Modbus::receivePDU(uint8_t* frame) { } //Check for failure - if (this->Coil(reg) != (bool)status) { + if (this->Coil(field1) != (bool)field2) { this->exceptionResponse(fcode, EX_SLAVE_FAILURE); return; } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 7a2ce51..e9d0ad2 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -66,6 +66,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pushWords(uint16_t address, uint16_t numregs, FunctionCode fn); void pullWords(uint16_t address, uint16_t numregs, FunctionCode fn); bool send() { + if (connected()) Serial.println("Connected"); uint16_t i; //MBAP _MBAP[0] = 0; @@ -82,6 +83,11 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; write(sbuf, send_len); + for (uint8_t c = 0; c < send_len; c++) { + Serial.print(sbuf[c], HEX); + Serial.print(" "); + } + Serial.println(); //Serial.println(_frame[0]); } bool get() { @@ -89,7 +95,6 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { if (!connected()) return false; uint16_t raw_len = 0; raw_len = available(); - //Serial.println(raw_len); if (available() > sizeof(_MBAP)) { //for (i = 0; i < 7; i++) _MBAP[i] = read(); //Get MBAP readBytes(_MBAP, sizeof(_MBAP)); //Get MBAP @@ -112,7 +117,7 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { void pushCoil() { } void pullCoil(uint16_t offset, uint16_t numregs = 1) { - readSlave(COIL(offset), numregs, FC_READ_COILS); + readSlave(offset, numregs, FC_READ_COILS); send(); } void pullCoils() { From 88da5154368ae62d6a7e1edd67b8af975869266c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 9 Jun 2018 08:19:51 +0500 Subject: [PATCH 062/288] Checkpiont before merge to one class --- README.md | 4 +- src/ModbusIP_ESP8266.cpp | 31 +++++++------- src/ModbusIP_ESP8266.h | 92 +++++++++++++++++++++++++--------------- 3 files changed, 74 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 9557ffc..35650b2 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/A * Modbus master implementation preparation * Public API changes * Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL - * Mark as private onSet, onGet, addReg, Reg - * Added callback-related functions: eventSource, onSetCoil, onGetCoil, onSetReg, onGetReg,.. + * Back to marking private for onSet, onGet, addReg and Reg methods + * Added callback-related eventSource method ## Contributions diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 5a9fb17..aa7be40 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -9,6 +9,8 @@ void ModbusIP::begin() { WiFiServer::begin(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { client[i] = NULL; + _trans[i] = NULL; + ip[i] = INADDR_NONE; } } @@ -48,12 +50,12 @@ void ModbusIP::task() { continue; // for (n) } if (client[n]->available() > sizeof(_MBAP)) { - //for (i = 0; i < 7; i++) _MBAP[i] = client[n]->read(); //Get MBAP - client[n]->readBytes(_MBAP, sizeof(_MBAP)); //Get MBAP - _len = _MBAP[4] << 8 | _MBAP[5]; + client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP + _len = __bswap_16(_MBAP.length); //_MBAP.raw[4] << 8 | _MBAP.raw[5]; _len--; // Do not count with last byte from MBAP - if (_MBAP[2] != 0 || _MBAP[3] != 0) { //Not a MODBUSIP packet + //if (_MBAP.raw[2] != 0 || _MBAP.raw[3] != 0) { //Not a MODBUSIP packet + if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. client[n]->flush(); continue; // for (n) } @@ -66,30 +68,27 @@ void ModbusIP::task() { exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); client[n]->flush(); } else { - //for (i = 0; i < _len; i++) _frame[i] = client[n]->read(); //Get Modbus PDU if (client[i]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame - exceptionResponse((FunctionCode)client[n]->read(), EX_ILLEGAL_VALUE); + exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); client[i]->flush(); } else { this->receivePDU(_frame); - client[n]->flush(); + client[n]->flush(); // Not sure if we need flush rest of data available } } } if (_reply != REPLY_OFF) { //MBAP - _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP - _MBAP[5] = (_len+1) & 0x00FF; - - size_t send_len = (uint16_t)_len + 7; + _MBAP.length = __bswap_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]; - //for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; - //for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; - memcpy(sbuf, _MBAP, sizeof(_MBAP)); - memcpy(sbuf + sizeof(_MBAP), _frame, _len); + memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); + memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); client[n]->write(sbuf, send_len); - //client[n]->write(_MBAP, sizeof(_MBAP));; + // Need to test if we can do not use double buffering + //client[n]->write(_MBAP.raw, sizeof(_MBAP.raw));; //client[n]->write(_frame, _len); } free(_frame); diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index e9d0ad2..2adf551 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -10,6 +10,11 @@ #include #else #include + #include +#endif + +#ifndef __bswap_16 + #define __bswap_16(num) ((uint16_t)num>>8) | ((uint16_t)num<<8) #endif #define MODBUSIP_PORT 502 @@ -20,7 +25,7 @@ // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); -typedef union MBAP { +typedef union MBAP_t { struct { uint16_t transactionId; uint16_t protocolId; @@ -31,7 +36,7 @@ typedef union MBAP { }; class ModbusCoreIP : public Modbus { protected: - uint8_t _MBAP[7]; + MBAP_t _MBAP; cbModbusConnect cbConnect = NULL; public: void onConnect(cbModbusConnect cb); @@ -40,24 +45,21 @@ class ModbusCoreIP : public Modbus { } }; typedef struct TTransaction; -typedef uint16_t (*cbModbusSlave)(TTransaction* query, bool result); -typedef struct TTransaction { - uint16_t id; - Modbus::FunctionCode fn; - uint16_t startreg; - uint16_t numregs; - uint32_t timestamp; - cbModbusSlave cb; -}; + class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { private: std::list _trans; - uint8_t status; IPAddress ip; - uint32_t queryStart; uint32_t timeout; uint16_t transactionId = 0; public: + enum ResultCode { + RESULT_OK = 0x01, + RESULT_TIMEOUT = 0x02, + RESULT_DISCONNECT = 0x03, + RESULT_ERROR = 0x04 + }; + ModbusMasterIP() : WiFiClient() { } void connect(IPAddress address); @@ -69,19 +71,19 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { if (connected()) Serial.println("Connected"); uint16_t i; //MBAP - _MBAP[0] = 0; - _MBAP[1] = 1; - _MBAP[2] = 0; - _MBAP[3] = 0; - _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP - _MBAP[5] = (_len+1) & 0x00FF; - _MBAP[6] = 0xFF; + _MBAP.transactionId = __bswap_16(1); + _MBAP.protocolId = __bswap_16(0); + _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.unitId = 0xFF; - size_t send_len = (uint16_t)_len + 7; + size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); uint8_t sbuf[send_len]; - for (i = 0; i < 7; i++) sbuf[i] = _MBAP[i]; - for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; +// for (i = 0; i < 7; i++) sbuf[i] = _MBAP.raw[i]; +// for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; + + memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); + memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); write(sbuf, send_len); for (uint8_t c = 0; c < send_len; c++) { Serial.print(sbuf[c], HEX); @@ -95,19 +97,27 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { if (!connected()) return false; uint16_t raw_len = 0; raw_len = available(); - if (available() > sizeof(_MBAP)) { - //for (i = 0; i < 7; i++) _MBAP[i] = read(); //Get MBAP - readBytes(_MBAP, sizeof(_MBAP)); //Get MBAP - _len = _MBAP[4] << 8 | _MBAP[5]; + if (available() > sizeof(_MBAP.raw)) { + readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP + + for (uint8_t c = 0; c < sizeof(_MBAP.raw); c++) { + Serial.print(_MBAP.raw[c], HEX); + Serial.print(" "); + } + + _len = __bswap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP - if (_MBAP[2] == 0 && _MBAP[3] == 0 && _len < MODBUSIP_MAXFRAME) { + if (__bswap_16(_MBAP.protocolId) == 0 && _len < MODBUSIP_MAXFRAME) { _frame = (uint8_t*) malloc(_len); - //raw_len = raw_len - 7; - //for (i = 0; i < _len; i++) - // _frame[i] = read(); //Get Modbus PDU - if (readBytes(_frame, _len) == _len) { - responcePDU(_frame); - return true; + if (_frame) { + if (readBytes(_frame, _len) == _len) { + for (uint8_t c = 0; c < _len; c++) { + Serial.print(_frame[c], HEX); + Serial.print(" "); + } + responcePDU(_frame); + return true; + } } } flush(); @@ -186,10 +196,22 @@ class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { bool pushReg(uint16_t address, uint16_t numregs); }; +typedef uint16_t (*cbModbusSlave)(TTransaction* query, ModbusMasterIP::ResultCode result); +typedef struct TTransaction { + uint16_t transactionId; + Modbus::FunctionCode fn; + uint16_t startreg; + uint16_t numregs; + time_t timestamp; + cbModbusSlave cb; +}; + class ModbusIP : public ModbusCoreIP, public WiFiServer { private: - //uint8_t _MBAP[7]; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; + std::list _trans[MODBUSIP_MAX_CLIENTS]; + IPAddress ip[MODBUSIP_MAX_CLIENTS]; + //cbModbusConnect cbConnect = NULL; int8_t n = -1; public: From 6c06311a89a3f2414c1a53b4c447db11eb5f9473 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 13 Jun 2018 16:10:59 +0500 Subject: [PATCH 063/288] Before ModbusMasterIP cleanup --- src/Modbus.cpp | 10 +-- src/Modbus.h | 16 ++--- src/ModbusIP_ESP8266.cpp | 37 ++++++---- src/ModbusIP_ESP8266.h | 149 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 183 insertions(+), 29 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 50f127e..538205f 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -445,9 +445,8 @@ void Modbus::responcePDU(uint8_t* frame) { } //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 field1 = 100; + uint16_t field1 = 0; uint16_t field2 = 1; -Serial.println(fcode); switch (fcode) { case FC_READ_REGS: //field1 = startreg, field2 = status @@ -456,8 +455,11 @@ Serial.println(fcode); break; case FC_READ_COILS: //field1 = startreg, field2 = numoutputs - Serial.println(field1); - Serial.println(field2); + Serial.print("Start: "); + Serial.print(field1); + Serial.print(" Count: "); + Serial.print(field2); + Serial.print(" Data: "); Serial.println(frame[0]); this->writeMultipleCoils(frame, field1, field2, frame[0]); _reply = REPLY_OFF; diff --git a/src/Modbus.h b/src/Modbus.h index 32d8b99..f0e1468 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -56,14 +56,14 @@ class Modbus { public: //Function Codes enum FunctionCode { - FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx - FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx - FC_READ_REGS = 0x03, // Read Holding Registers 4xxxx - FC_READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx - FC_WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx - FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx - FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx - FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx + 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_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) + FC_WRITE_REGS = 0x10, // Write block of contiguous registers FC_MASKWRITE_REG = 0x16, // Not implemented FC_READWRITE_REGS = 0x17 // Not implemented }; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index aa7be40..cbe7f8d 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -9,8 +9,8 @@ void ModbusIP::begin() { WiFiServer::begin(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { client[i] = NULL; - _trans[i] = NULL; - ip[i] = INADDR_NONE; + //_trans[i] = NULL; + slaveIp[i] = INADDR_NONE; } } @@ -72,11 +72,20 @@ void ModbusIP::task() { exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); client[i]->flush(); } else { - this->receivePDU(_frame); + if (slaveIp[i] == INADDR_NONE) { + receivePDU(_frame); // Slave + } else { + for (uint8_t c = 0; c < _len; c++) { + Serial.print(_frame[c], HEX); + Serial.print(" "); + } + responcePDU(_frame);// Master + } client[n]->flush(); // Not sure if we need flush rest of data available } } } + if (slaveIp[i] != INADDR_NONE) _reply = REPLY_OFF; if (_reply != REPLY_OFF) { //MBAP _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP @@ -102,21 +111,21 @@ void ModbusIP::task() { // cbConnect = cb; //} -void ModbusMasterIP::connect(IPAddress address) { - WiFiClient::connect(address, MODBUSIP_PORT); -} -void ModbusMasterIP::pushBits(uint16_t address, uint16_t numregs, FunctionCode fn){ -} -void ModbusMasterIP::pullBits(uint16_t address, uint16_t numregs, FunctionCode fn) { -} -void ModbusMasterIP::pushWords(uint16_t address, uint16_t numregs, FunctionCode fn) { +//void ModbusMasterIP::connect(IPAddress address) { +// WiFiClient::connect(address, MODBUSIP_PORT); +//} +void ModbusIP::pushBits(uint16_t address, uint16_t numregs, FunctionCode fn){ } -void ModbusMasterIP::pullWords(uint16_t address, uint16_t numregs, FunctionCode fn) { +void ModbusIP::pullBits(uint16_t address, uint16_t numregs, FunctionCode fn) { } -void ModbusMasterIP::task() { +void ModbusIP::pushWords(uint16_t address, uint16_t numregs, FunctionCode fn) { } -IPAddress ModbusMasterIP::eventSource() { +void ModbusIP::pullWords(uint16_t address, uint16_t numregs, FunctionCode fn) { } +//void ModbusMasterIP::task() { +//} +//IPAddress ModbusMasterIP::eventSource() { +//} void ModbusCoreIP::onConnect(cbModbusConnect cb = NULL) { cbConnect = cb; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 2adf551..c3b2908 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -207,11 +207,55 @@ typedef struct TTransaction { }; class ModbusIP : public ModbusCoreIP, public WiFiServer { - private: + public: WiFiClient* client[MODBUSIP_MAX_CLIENTS]; std::list _trans[MODBUSIP_MAX_CLIENTS]; - IPAddress ip[MODBUSIP_MAX_CLIENTS]; - + IPAddress slaveIp[MODBUSIP_MAX_CLIENTS]; + int8_t getFreeClient() { + for (uint8_t i = 0; n < MODBUSIP_MAX_CLIENTS; i++) { + if (!client[i]) + return i; + if (!client[i]->connected()) { // Free client if not connected + delete client[i]; + client[i] = NULL; + slaveIp[i] = INADDR_NONE; + return i; + } + } + return -1; + } + int8_t getClient(IPAddress ip) { + Serial.println(0); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (client[i] && client[i]->remoteIP() == ip) + return i; + return -1; + } + bool isConnected(IPAddress ip) { + int8_t p = getClient(ip); + return p != -1 && client[p]->connected(); + } + bool connectSlave(IPAddress ip) { + if(getClient(ip) == -1) { + Serial.println(1); + uint8_t p = getFreeClient(); + Serial.println(2); + if (p != -1) { + Serial.println(3); + client[p] = new WiFiClient(); + client[p]->connect(ip, MODBUSIP_PORT); + Serial.println(4); + slaveIp[p] = ip; + return true; + } + } + return false; + } + bool disconnect(IPAddress addr) {} + void onMasterConnect(cbModbusConnect cb) {} + void onMasterDisconnect(cbModbusConnect cb) {} + void slave() {} + void master() {} //cbModbusConnect cbConnect = NULL; int8_t n = -1; public: @@ -221,4 +265,103 @@ class ModbusIP : public ModbusCoreIP, public WiFiServer { void task(); //void onConnect(cbModbusConnect cb); IPAddress eventSource(); + + void pushBits(uint16_t address, uint16_t numregs, FunctionCode fn); + void pullBits(uint16_t address, uint16_t numregs, FunctionCode fn); + void pushWords(uint16_t address, uint16_t numregs, FunctionCode fn); + void pullWords(uint16_t address, uint16_t numregs, FunctionCode fn); + bool send(IPAddress ip) { + //if (connected()) Serial.println("Connected"); + uint16_t i; + //MBAP + _MBAP.transactionId = __bswap_16(1); + _MBAP.protocolId = __bswap_16(0); + _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.unitId = 0xFF; + + size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); + uint8_t sbuf[send_len]; + +// for (i = 0; i < 7; i++) sbuf[i] = _MBAP.raw[i]; +// for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; + + memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); + memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); + int8_t p = getClient(ip); + if (p != -1 && client[p]->connected()) { + Serial.println(client[p]->write(sbuf, send_len)); + for (uint8_t c = 0; c < send_len; c++) { + Serial.print(sbuf[c], HEX); + Serial.print(" "); + } + Serial.println(); + //Serial.println(_frame[0]); + } + } + void pushIsts() { + } + void pullIsts() { + } + void pushHreg() { + } + void pullHreg() { + } + void pushIreg() { + } + void pullIreg() { + } + public: + bool pullReg(uint16_t address, uint16_t numregs) { + addReg(address, numregs); + return true; + } + bool pushReg(uint16_t address, uint16_t numregs); + void pushCoil() { + } + void pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1) { + readSlave(offset, numregs, FC_READ_COILS); + send(ip); + } + void pullCoils() { + uint16_t offset; + uint16_t numregs = 1; + std::list::iterator it = _regs.begin(); + //std::vector::iterator it = _regs.begin(); + if (it == _regs.end()) return; + offset = it->address; + while(++it != _regs.end()) + { + if (!IS_COIL(it->address)) continue; + if (it->address == offset + numregs) { + numregs++; + continue; + } + pullCoil(offset, numregs); + offset = it->address; + numregs = 1; + } + pullCoil(offset, numregs); + } + /* + TRegister* creg = getHead(); + if (!creg) return; + while (creg && !IS_COIL(creg->address)) creg = creg->next; + if (creg) { + offset = creg->address; + while (creg->next) { + creg = creg->next; + if (IS_COIL(creg->address)) { + if ((offset + 1) == creg->address) { + numregs++; + continue; + } + pullCoil(offset, numregs); + offset = creg->address; + numregs = 1; + } + } + pullCoil(offset, numregs); + } + } + */ }; \ No newline at end of file From 8e92ec908154be33f34bc79a89d21c6b467bca23 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 16 Jun 2018 12:22:17 +0500 Subject: [PATCH 064/288] 2.0.ALFA --- API.md | 1 + src/Modbus.cpp | 411 ++++++++++++++++--------------------- src/Modbus.h | 107 +++++----- src/ModbusIP_ESP8266.cpp | 90 ++++----- src/ModbusIP_ESP8266.h | 426 ++++++++++++++------------------------- 5 files changed, 423 insertions(+), 612 deletions(-) diff --git a/API.md b/API.md index 961fca1..54b005c 100644 --- a/API.md +++ b/API.md @@ -40,6 +40,7 @@ Callback generation control. Callback generation is enabled by default. ```c void onConnect(cbModbusConnect cb) +void onDisonnect(cbModbusConnect cb) ``` *ModbusIP only.* Assign callback function on new incoming connection event. diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 538205f..0e87aa5 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,7 +1,7 @@ /* Modbus.cpp - Modbus Base Library Implementation Copyright (C) 2014 Andr� Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" @@ -12,9 +12,8 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { TRegister* Modbus::searchRegister(uint16_t address) { const TRegister tmp = {address, 0, cbDefault, cbDefault}; std::list::iterator it = std::find(_regs.begin(), _regs.end(), tmp); - //std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); if (it != _regs.end()) return &*it; - return NULL; + return nullptr; } bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { @@ -26,18 +25,14 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { _regs.push_back({address + i, value, cbDefault, cbDefault}); } _regs.sort(); - //std::sort(_regs.begin(),_regs.end()); //_regs.unique(); - //std::vector::iterator it; - //it = std::unique (_regs.begin(), _regs.end()); - //_regs.resize( std::distance(_regs.begin(),it) ); return true; } bool Modbus::Reg(uint16_t address, uint16_t value) { TRegister* reg; //search for the register address - reg = this->searchRegister(address); + reg = searchRegister(address); //if found then assign the register value to the new value. if (reg) { if (cbEnabled) { @@ -52,7 +47,7 @@ bool Modbus::Reg(uint16_t address, uint16_t value) { uint16_t Modbus::Reg(uint16_t address) { TRegister* reg; - reg = this->searchRegister(address); + reg = searchRegister(address); if(reg) if (cbEnabled) { return reg->get(reg, reg->value); @@ -63,313 +58,238 @@ uint16_t Modbus::Reg(uint16_t address) { return 0; } -void Modbus::receivePDU(uint8_t* frame) { +bool Modbus::removeReg(uint16_t address) { + // Empty stub +} + +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 bytecount_calc; switch (fcode) { - case FC_WRITE_REG: //field1 = reg, field2 = value - this->writeSingleRegister(field1, field2); + //writeSingleRegister(field1, field2); + if (!Hreg(field1, field2)) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + break; + } + if (Hreg(field1) != field2) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + break; + } + _reply = REPLY_ECHO; break; case FC_READ_REGS: - //field1 = startreg, field2 = numregs - this->readRegisters(field1, field2); + //field1 = startreg, field2 = numregs, header len = 3 + readWords(HREG(field1), field2, fcode); break; case FC_WRITE_REGS: - //field1 = startreg, field2 = status - this->writeMultipleRegisters(frame,field1, field2, frame[5]); + //field1 = startreg, field2 = numregs, frame[5] = data lenght, header len = 6 + //writeMultipleRegisters(frame, field1, field2, frame[5]); + if (field2 < 0x0001 || field2 > 0x007B || frame[5] != 2 * field2) { //Check value + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + break; + } + for (int k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(HREG(field1) + k)) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + break; + } + } + setMultipleWords(frame + 6, HREG(field1), field2); + successResponce(field1, field2, fcode); + _reply = REPLY_NORMAL; break; case FC_READ_COILS: //field1 = startreg, field2 = numregs - this->readCoils(field1, field2); + readBits(COIL(field1), field2, fcode); break; case FC_READ_INPUT_STAT: //field1 = startreg, field2 = numregs - this->readInputStatus(field1, field2); + readBits(ISTS(field1), field2, fcode); break; case FC_READ_INPUT_REGS: //field1 = startreg, field2 = numregs - this->readInputRegisters(field1, field2); + readWords(IREG(field1), field2, fcode); break; case FC_WRITE_COIL: - //field1 = reg, field2 = status - //this->writeSingleCoil(field1, field2); - //Check value (status) - if (field2 != 0xFF00 && field2 != 0x0000) { - this->exceptionResponse(fcode, EX_ILLEGAL_VALUE); - return; + //field1 = reg, field2 = status, header len = 3 + if (field2 != 0xFF00 && field2 != 0x0000) { //Check value (status) + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + break; } - - //Check Address and execute (reg exists?) - if (!this->Coil(field1, (bool)field2)) { - this->exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); - return; + if (!Coil(field1, COIL_BOOL(field2))) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + break; } - - //Check for failure - if (this->Coil(field1) != (bool)field2) { - this->exceptionResponse(fcode, EX_SLAVE_FAILURE); - return; + if (Coil(field1) != COIL_BOOL(field2)) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + break; } - _reply = REPLY_ECHO; break; case FC_WRITE_COILS: - //field1 = startreg, field2 = numoutputs, frame[5] = bytecount + //field1 = startreg, field2 = numregs, frame[5] = bytecount, header len = 6 bytecount_calc = field2 / 8; if (field2%8) bytecount_calc++; - if (field2 < 0x0001 || field2 > 0x07B0 || frame[5] != bytecount_calc) { - this->exceptionResponse(fcode, EX_ILLEGAL_VALUE); - return; + if (field2 < 0x0001 || field2 > 0x07B0 || frame[5] != bytecount_calc) { //Check registers range and data size maches + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + break; } - Serial.println("Size ok"); - //Check Address (startreg...startreg + numregs) - for (int k = 0; k < field2; k++) { - if (!this->searchRegister(COIL(field1) + k)) { - this->exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); - return; + for (int k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(COIL(field1) + k)) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + break; } } - this->writeMultipleCoils(frame + MB_FRAME_HEADER, field1, field2, frame[5]); - successResponce(field1, field2, FC_WRITE_COILS); + setMultipleBits(frame + 6, field1, field2); + successResponce(field1, field2, fcode); _reply = REPLY_NORMAL; break; default: - this->exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); } } +void Modbus::successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(_len); + _frame[0] = fn; + _frame[1] = startreg >> 8; + _frame[2] = startreg & 0x00FF; + _frame[3] = numoutputs >> 8; + _frame[4] = numoutputs & 0x00FF; +} + void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) { - //Clean frame buffer free(_frame); _len = 2; _frame = (uint8_t*) malloc(_len); _frame[0] = fn + 0x80; _frame[1] = excode; - _reply = REPLY_NORMAL; } +void Modbus::getMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numregs) { + uint8_t bitn = 0; + uint16_t totregs = numregs; + uint16_t i; + while (numregs--) { + i = (totregs - numregs) / 8; + if (BIT_BOOL(Reg(startreg))) + bitSet(frame[2+i], bitn); + else + bitClear(frame[2+i], bitn); + bitn++; //increment the bit index + if (bitn == 8) bitn = 0; + startreg++; //increment the register + } +} + +void Modbus::getMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numregs) { + uint16_t val; + uint16_t i = 0; + while(numregs--) { + val = Reg(startreg + i); //retrieve the value from the register bank for the current register + frame[i * 2] = val >> 8; //write the high byte of the register value + frame[i * 2 + 1] = val & 0xFF; //write the low byte of the register value + i++; + } +} + void Modbus::readBits(uint16_t startreg, uint16_t numregs, FunctionCode fn) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x07D0) { - this->exceptionResponse(fn, EX_ILLEGAL_VALUE); + if (numregs < 0x0001 || numregs > 0x07D0) { //Check value (numregs) + exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } - //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 (!this->searchRegister(startreg)) { - this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); + if (!searchRegister(startreg)) { + exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } - - //Clean frame buffer 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) { - this->exceptionResponse(fn, EX_SLAVE_FAILURE); + exceptionResponse(fn, EX_SLAVE_FAILURE); return; } _frame[0] = fn; _frame[1] = _len - 2; //byte count (_len - function code and byte count) _frame[_len - 1] = 0; //Clean last probably partial byte - - uint8_t bitn = 0; - uint16_t totregs = numregs; - uint16_t i; - while (numregs--) { - i = (totregs - numregs) / 8; - if (BIT_BOOL(this->Reg(startreg))) - bitSet(_frame[2+i], bitn); - else - bitClear(_frame[2+i], bitn); - //increment the bit index - bitn++; - if (bitn == 8) bitn = 0; - //increment the register - startreg++; - } - + getMultipleBits(_frame+2, startreg, numregs); _reply = REPLY_NORMAL; } void Modbus::readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { - this->exceptionResponse(fn, EX_ILLEGAL_VALUE); + exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } - - //Check Address - //*** See comments on readCoils method. - if (!this->searchRegister(startreg)) { - this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); + if (searchRegister(startreg)) { //Check Address + exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } - - //Clean frame buffer free(_frame); - - //calculate the query reply message length - //for each register queried add 2 bytes - _len = 2 + numregs * 2; - + _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) { - this->exceptionResponse(fn, EX_SLAVE_FAILURE); + exceptionResponse(fn, EX_SLAVE_FAILURE); return; } - _frame[0] = fn; _frame[1] = _len - 2; //byte count - - uint16_t val; - uint16_t i = 0; - while(numregs--) { - //retrieve the value from the register bank for the current register - val = this->Reg(startreg + i); - //write the high byte of the register value - _frame[2 + i * 2] = val >> 8; - //write the low byte of the register value - _frame[3 + i * 2] = val & 0xFF; - i++; - } - + getMultipleWords(_frame + 2, startreg, numregs); _reply = REPLY_NORMAL; } -void Modbus::writeSingleRegister(uint16_t reg, uint16_t value, FunctionCode fn) { - //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using uint16_t (0x0000 - 0x0FFFF) - //Check Address and execute (reg exists?) - if (!this->Hreg(reg, value)) { - this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; - } - - //Check for failure - if (this->Hreg(reg) != value) { - this->exceptionResponse(fn, EX_SLAVE_FAILURE); - return; - } - - _reply = REPLY_ECHO; -} - -void Modbus::writeMultipleRegisters(uint8_t* frame,uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn) { - //Check value - if (numoutputs < 0x0001 || numoutputs > 0x007B || bytecount != 2 * numoutputs) { - this->exceptionResponse(fn, EX_ILLEGAL_VALUE); - return; - } - - //Check Address (startreg...startreg + numregs) - for (int k = 0; k < numoutputs; k++) { - if (!this->searchRegister(HREG(startreg) + k)) { - this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; - } - } - - //Clean frame buffer - free(_frame); - _len = 5; - _frame = (uint8_t*) malloc(_len); - - _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; - _frame[3] = numoutputs >> 8; - _frame[4] = numoutputs & 0x00FF; - - uint16_t val; - uint16_t i = 0; - while(numoutputs--) { - val = (word)frame[6+i*2] << 8 | (word)frame[7+i*2]; - this->Hreg(startreg + i, val); - i++; - } - - _reply = REPLY_NORMAL; -} - -void Modbus::writeSingleCoil(uint16_t reg, uint16_t status, FunctionCode fn) { - //Check value (status) - if (status != 0xFF00 && status != 0x0000) { - this->exceptionResponse(fn, EX_ILLEGAL_VALUE); - return; - } - - //Check Address and execute (reg exists?) - if (!this->Coil(reg, (bool)status)) { - this->exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; - } - - //Check for failure - if (this->Coil(reg) != (bool)status) { - this->exceptionResponse(fn, EX_SLAVE_FAILURE); - return; - } - - _reply = REPLY_ECHO; -} - -void Modbus::successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn) { - //Clean frame buffer - free(_frame); - _len = 5; - _frame = (uint8_t*) malloc(_len); - - _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; - _frame[3] = numoutputs >> 8; - _frame[4] = numoutputs & 0x00FF; -} - -void Modbus::writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn) { +void Modbus::setMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numoutputs) { uint8_t bitn = 0; uint16_t totoutputs = numoutputs; uint16_t i; while (numoutputs--) { i = (totoutputs - numoutputs) / 8; - this->Coil(startreg, bitRead(frame[i], bitn)); - //increment the bit index - bitn++; + Reg(startreg, bitRead(frame[i], bitn)); + bitn++; //increment the bit index if (bitn == 8) bitn = 0; - //increment the register - startreg++; + startreg++; //increment the register } +} - //_reply = REPLY_NORMAL; +void Modbus::setMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numregs) { + uint16_t val; + uint16_t i = 0; + while(numregs--) { + val = (uint16_t)frame[i*2] << 8 | (uint16_t)frame[i*2 + 1]; + Reg(startreg + i, val); + i++; + } } bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; while (numregs > 0) { - reg = this->searchRegister(address); + reg = searchRegister(address); if (reg) { reg->get = cb; atLeastOne = true; @@ -383,7 +303,7 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; while (numregs > 0) { - reg = this->searchRegister(address); + reg = searchRegister(address); if (reg) { reg->set = cb; atLeastOne = true; @@ -398,35 +318,39 @@ bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); - _frame[0] = fn; _frame[1] = startreg >> 8; _frame[2] = startreg & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; - // Serial.print("_frame[2] = "); - //Serial.println(_frame[2]); return true; } bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); - _len = 5; - _frame = (uint8_t*) malloc(_len); - if (!_frame) return false; - _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; - _frame[3] = numregs >> 8; - _frame[4] = numregs & 0x00FF; - return true; + _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) { + _frame[0] = fn; + _frame[1] = startreg >> 8; + _frame[2] = startreg & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + _frame[5] = _len - 6; + _frame[_len - 1] = 0; //Clean last probably partial byte + getMultipleBits(_frame + 6, startreg, numregs); + _reply = REPLY_NORMAL; + return true; + } + _reply = REPLY_OFF; + return false; } bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); - if (!_frame) return false; _frame[0] = fn; _frame[1] = startreg >> 8; _frame[2] = startreg & 0x00FF; @@ -435,40 +359,60 @@ bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, FunctionCode f return true; } -void Modbus::responcePDU(uint8_t* frame) { +void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame) { uint8_t fcode = frame[0]; + _reply = 0; if ((fcode & 0x80) != 0) { Serial.print("Error: "); Serial.println(_frame[1]); - _reply = REPLY_ERROR; + _reply = _frame[1]; return; } - //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 field1 = 0; - uint16_t field2 = 1; + uint16_t field1 = (uint16_t)sourceFrame[1] << 8 | (uint16_t)sourceFrame[2]; + uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; + uint8_t bytecount_calc; switch (fcode) { case FC_READ_REGS: - //field1 = startreg, field2 = status - this->writeMultipleRegisters(frame, field1, field2, frame[5]); - _reply = REPLY_OFF; + //field1 = startreg, field2 = status, frame[1] = data lenght, header len = 2 + if (frame[1] != 2 * field2) { //Check if data size matches + _reply = EX_DATA_MISMACH; + break; + } + setMultipleWords(frame + 2, HREG(field1), field2); break; case FC_READ_COILS: - //field1 = startreg, field2 = numoutputs + //field1 = startreg, field2 = numregs, frame[1] = data length, header len = 2 Serial.print("Start: "); Serial.print(field1); Serial.print(" Count: "); Serial.print(field2); Serial.print(" Data: "); Serial.println(frame[0]); - this->writeMultipleCoils(frame, field1, field2, frame[0]); - _reply = REPLY_OFF; + bytecount_calc = field2 / 8; + if (field2%8) bytecount_calc++; + if (frame[1] != bytecount_calc) { // check if data size matches + _reply = EX_DATA_MISMACH; + break; + } + setMultipleBits(frame + 2, COIL(field1), field2); break; case FC_READ_INPUT_STAT: - _reply = REPLY_OFF; + //field1 = startreg, 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; + } + setMultipleBits(frame + 2, ISTS(field1), field2); break; case FC_READ_INPUT_REGS: - _reply = REPLY_OFF; + //field1 = startreg, field2 = status, frame[1] = data lenght, header len = 2 + if (frame[1] != 2 * field2) { //Check if data size matches + _reply = EX_DATA_MISMACH; + break; + } + setMultipleWords(frame + 2, IREG(field1), field2); break; case FC_WRITE_REG: break; @@ -479,9 +423,8 @@ void Modbus::responcePDU(uint8_t* frame) { case FC_WRITE_COILS: break; default: - _reply = REPLY_ERROR; + _reply = EX_GENERAL_FAILURE; } - _reply = REPLY_OFF; } void Modbus::cbEnable(bool state) { diff --git a/src/Modbus.h b/src/Modbus.h index f0e1468..517865a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,7 +1,7 @@ /* Modbuc.h - Header for Modbus Base Library Copyright (C) 2014 Andr� Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once @@ -10,7 +10,6 @@ #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 -#define MB_FRAME_HEADER 6 #define COIL_BASE 1 #define ISTS_BASE 10001 #define IREG_BASE 30001 @@ -69,106 +68,101 @@ class Modbus { }; //Exception Codes enum ResultCode { - 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 Deice Fails to process request - EX_ACKNOWLEDGE = 0x05, // Not used - EX_SLAVE_DEVICE_BUSY = 0x06 // Not used + 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 Deice 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 }; - inline bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { return addReg(HREG(offset), value, numregs); } - inline bool Hreg(uint16_t offset, uint16_t value) { + bool Hreg(uint16_t offset, uint16_t value) { return Reg(HREG(offset), value); } - inline uint16_t Hreg(uint16_t offset) { + uint16_t Hreg(uint16_t offset) { return Reg(HREG(offset)); } - inline bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { + uint16_t removeHreg(uint16_t offset) { + return removeReg(HREG(offset)); + } + bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { return addReg(COIL(offset), COIL_VAL(value), numregs); } - inline bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { + bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { return addReg(ISTS(offset), ISTS_VAL(value), numregs); } - inline bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { return addReg(IREG(offset), value, numregs); } - inline bool Coil(uint16_t offset, bool value) { + bool Coil(uint16_t offset, bool value) { return Reg(COIL(offset), COIL_VAL(value)); } - inline bool Ists(uint16_t offset, bool value) { + bool Ists(uint16_t offset, bool value) { return Reg(ISTS(offset), ISTS_VAL(value)); } - inline bool Ireg(uint16_t offset, uint16_t value) { + bool Ireg(uint16_t offset, uint16_t value) { return Reg(IREG(offset), value); } - inline bool Coil(uint16_t offset) { + bool Coil(uint16_t offset) { return COIL_BOOL(Reg(COIL(offset))); } - inline bool Ists(uint16_t offset) { + bool Ists(uint16_t offset) { return ISTS_BOOL(Reg(ISTS(offset))); } - inline uint16_t Ireg(uint16_t offset) { + uint16_t Ireg(uint16_t offset) { return Reg(IREG(offset)); } void cbEnable(bool state = true); - inline void cbDisable() { + void cbDisable() { cbEnable(false); } - inline bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(COIL(offset), cb, numregs); } - inline bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(COIL(offset), cb, numregs); } - inline bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(HREG(offset), cb, numregs); } - inline bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(HREG(offset), cb, numregs); } - inline bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(ISTS(offset), cb, numregs); } - inline bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(ISTS(offset), cb, numregs); } - inline bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onGet(IREG(offset), cb, numregs); } - inline bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { + bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { return onSet(IREG(offset), cb, numregs); } private: - // All function assuming to process Modbus frame from Slave perspective - // I.e. readRegisters fill frame with local regisers vales - // writeRegisters sets local registers according to frame values void readBits(uint16_t startreg, uint16_t numregs, FunctionCode fn); void readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn); - void readRegisters(uint16_t startreg, uint16_t numregs) { - readWords(HREG(startreg), numregs, FC_READ_REGS); - } - void writeSingleRegister(uint16_t reg, uint16_t value, FunctionCode fn = FC_WRITE_REG); - void writeMultipleRegisters(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn = FC_WRITE_REGS); - inline void readCoils(uint16_t startreg, uint16_t numregs) { - readBits(COIL(startreg), numregs, FC_READ_COILS); - } - inline void readInputStatus(uint16_t startreg, uint16_t numregs) { - readBits(ISTS(startreg), numregs, FC_READ_INPUT_STAT); - } - inline void readInputRegisters(uint16_t startreg, uint16_t numregs) { - readWords(IREG(startreg), numregs, FC_READ_INPUT_REGS); - } - void writeSingleCoil(uint16_t reg, uint16_t status, FunctionCode fn = FC_WRITE_COIL); - void writeMultipleCoils(uint8_t* frame, uint16_t startreg, uint16_t numoutputs, uint8_t bytecount, FunctionCode fn = FC_WRITE_COILS); + + void setMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numoutputs); + void setMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numoutputs); + + void getMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numregs); + void getMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numregs); TRegister* searchRegister(uint16_t addr); - - bool cbEnabled = true; protected: //Reply Types @@ -181,14 +175,14 @@ class Modbus { }; std::list _regs; - //std::vector _regs; - uint8_t* _frame; - uint8_t _len; - uint8_t _reply; + uint8_t* _frame; + uint16_t _len; + uint8_t _reply; + bool cbEnabled = true; void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn); - void receivePDU(uint8_t* frame); //For Slave - void responcePDU(uint8_t* frame); //For Master + void slavePDU(uint8_t* frame); //For Slave + void masterPDU(uint8_t* frame, uint8_t* sourceFrame); //For Master bool readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn); bool writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn); @@ -197,6 +191,7 @@ class Modbus { bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); uint16_t Reg(uint16_t address); + bool removeReg(uint16_t address); // Not implemented bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index cbe7f8d..a19100d 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -1,17 +1,15 @@ /* ModbusIP_ESP8266.cpp - ModbusIP Library Implementation Copyright (C) 2014 Andr� Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "ModbusIP_ESP8266.h" void ModbusIP::begin() { - WiFiServer::begin(); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + server = new WiFiServer(MODBUSIP_PORT); + server->begin(); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) client[i] = NULL; - //_trans[i] = NULL; - slaveIp[i] = INADDR_NONE; - } } IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query @@ -22,18 +20,23 @@ IPAddress ModbusIP::eventSource() { // Returns IP of current processing client void ModbusIP::task() { uint8_t i; - while (hasClient()) { - WiFiClient* currentClient = new WiFiClient(available()); + clientsCleanup(); + while (server->hasClient()) { + WiFiClient* currentClient = new WiFiClient(server->available()); if (currentClient != NULL && currentClient->connected()) { if (cbConnect == NULL || cbConnect(currentClient->remoteIP())) { - for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - if (client[n] == NULL) { - client[n] = currentClient; - break; // for - } - } - if (n < MODBUSIP_MAX_CLIENTS) // If client added process next + n = getFreeClient(); + if (n > -1) { + client[n] = currentClient; + //for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + // if (client[n] == NULL) { + // client[n] = currentClient; + // break; // for + // } + //} + //if (n < MODBUSIP_MAX_CLIENTS) // If client added process next continue; // while + } } // If callback returns false or _MAX_CLIENTS reached immediate close connection currentClient->flush(); @@ -44,17 +47,16 @@ void ModbusIP::task() { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (client[n] == NULL) continue; // for (n) - if (!client[n]->connected()) { - delete client[n]; - client[n] = NULL; - continue; // for (n) - } +// if (!client[n]->connected()) { +// delete client[n]; +// client[n] = NULL; +// continue; // for (n) +// } if (client[n]->available() > sizeof(_MBAP)) { client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP - _len = __bswap_16(_MBAP.length); //_MBAP.raw[4] << 8 | _MBAP.raw[5]; + _len = __bswap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP - //if (_MBAP.raw[2] != 0 || _MBAP.raw[3] != 0) { //Not a MODBUSIP packet if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. client[n]->flush(); continue; // for (n) @@ -72,22 +74,34 @@ void ModbusIP::task() { exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); client[i]->flush(); } else { - if (slaveIp[i] == INADDR_NONE) { - receivePDU(_frame); // Slave + if (client[i]->localPort() == MODBUSIP_PORT) { + slavePDU(_frame); // Slave } else { for (uint8_t c = 0; c < _len; c++) { Serial.print(_frame[c], HEX); Serial.print(" "); } - responcePDU(_frame);// Master + _reply = 0; + TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); + if (trans) { + if ((_frame[0] & 0x7F) == trans->_frame[0]) { + masterPDU(_frame, trans->_frame); // Master + } else { + _reply = EX_UNEXPECTED_RESPONSE; + } + if (cbEnabled && trans->cb) { + trans->cb(_reply, client[i]->remoteIP()); + } + free(trans->_frame); + _trans.remove(*trans); + } } client[n]->flush(); // Not sure if we need flush rest of data available } } } - if (slaveIp[i] != INADDR_NONE) _reply = REPLY_OFF; + if (client[i]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; if (_reply != REPLY_OFF) { - //MBAP _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); @@ -107,26 +121,6 @@ void ModbusIP::task() { n = -1; } -//void ModbusIP::onConnect(cbModbusConnect cb = NULL) { -// cbConnect = cb; -//} - -//void ModbusMasterIP::connect(IPAddress address) { -// WiFiClient::connect(address, MODBUSIP_PORT); -//} -void ModbusIP::pushBits(uint16_t address, uint16_t numregs, FunctionCode fn){ -} -void ModbusIP::pullBits(uint16_t address, uint16_t numregs, FunctionCode fn) { -} -void ModbusIP::pushWords(uint16_t address, uint16_t numregs, FunctionCode fn) { -} -void ModbusIP::pullWords(uint16_t address, uint16_t numregs, FunctionCode fn) { -} -//void ModbusMasterIP::task() { -//} -//IPAddress ModbusMasterIP::eventSource() { -//} - -void ModbusCoreIP::onConnect(cbModbusConnect cb = NULL) { +void ModbusIP::onConnect(cbModbusConnect cb = NULL) { cbConnect = cb; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index c3b2908..6f13fa2 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,7 +1,7 @@ /* ModbusIP_ESP8266.h - Header for ModbusIP Library Copyright (C) 2014 Andr� Sarmento Barbosa - 2017 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once @@ -20,313 +20,201 @@ #define MODBUSIP_PORT 502 #define MODBUSIP_MAXFRAME 200 #define MODBUSIP_TIMEOUT 10 +#define MODBUSIP_UNIT 255 #define MODBUSIP_MAX_CLIENTS 4 // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); -typedef union MBAP_t { - struct { - uint16_t transactionId; - uint16_t protocolId; - uint16_t length; - uint8_t unitId; - }; - uint8_t raw[7]; -}; -class ModbusCoreIP : public Modbus { - protected: - MBAP_t _MBAP; - cbModbusConnect cbConnect = NULL; - public: - void onConnect(cbModbusConnect cb); - virtual IPAddress eventSource() { - - } -}; -typedef struct TTransaction; - -class ModbusMasterIP : public ModbusCoreIP, public WiFiClient { - private: - std::list _trans; - IPAddress ip; - uint32_t timeout; - uint16_t transactionId = 0; - public: - enum ResultCode { - RESULT_OK = 0x01, - RESULT_TIMEOUT = 0x02, - RESULT_DISCONNECT = 0x03, - RESULT_ERROR = 0x04 - }; - ModbusMasterIP() : WiFiClient() { - } - void connect(IPAddress address); - void pushBits(uint16_t address, uint16_t numregs, FunctionCode fn); - void pullBits(uint16_t address, uint16_t numregs, FunctionCode fn); - void pushWords(uint16_t address, uint16_t numregs, FunctionCode fn); - void pullWords(uint16_t address, uint16_t numregs, FunctionCode fn); - bool send() { - if (connected()) Serial.println("Connected"); - uint16_t i; - //MBAP - _MBAP.transactionId = __bswap_16(1); - _MBAP.protocolId = __bswap_16(0); - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP - _MBAP.unitId = 0xFF; - - size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); - uint8_t sbuf[send_len]; - -// for (i = 0; i < 7; i++) sbuf[i] = _MBAP.raw[i]; -// for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; +typedef struct TTransaction TTransaction; - memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); - memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); - write(sbuf, send_len); - for (uint8_t c = 0; c < send_len; c++) { - Serial.print(sbuf[c], HEX); - Serial.print(" "); - } - Serial.println(); - //Serial.println(_frame[0]); - } - bool get() { - uint8_t i; - if (!connected()) return false; - uint16_t raw_len = 0; - raw_len = available(); - if (available() > sizeof(_MBAP.raw)) { - readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP - - for (uint8_t c = 0; c < sizeof(_MBAP.raw); c++) { - Serial.print(_MBAP.raw[c], HEX); - Serial.print(" "); - } - - _len = __bswap_16(_MBAP.length); - _len--; // Do not count with last byte from MBAP - if (__bswap_16(_MBAP.protocolId) == 0 && _len < MODBUSIP_MAXFRAME) { - _frame = (uint8_t*) malloc(_len); - if (_frame) { - if (readBytes(_frame, _len) == _len) { - for (uint8_t c = 0; c < _len; c++) { - Serial.print(_frame[c], HEX); - Serial.print(" "); - } - responcePDU(_frame); - return true; - } - } - } - flush(); - } - return false; - } - void pushCoil() { - } - void pullCoil(uint16_t offset, uint16_t numregs = 1) { - readSlave(offset, numregs, FC_READ_COILS); - send(); - } - void pullCoils() { - uint16_t offset; - uint16_t numregs = 1; - std::list::iterator it = _regs.begin(); - //std::vector::iterator it = _regs.begin(); - if (it == _regs.end()) return; - offset = it->address; - while(++it != _regs.end()) - { - if (!IS_COIL(it->address)) continue; - if (it->address == offset + numregs) { - numregs++; - continue; - } - pullCoil(offset, numregs); - offset = it->address; - numregs = 1; - } - pullCoil(offset, numregs); - } - /* - TRegister* creg = getHead(); - if (!creg) return; - while (creg && !IS_COIL(creg->address)) creg = creg->next; - if (creg) { - offset = creg->address; - while (creg->next) { - creg = creg->next; - if (IS_COIL(creg->address)) { - if ((offset + 1) == creg->address) { - numregs++; - continue; - } - pullCoil(offset, numregs); - offset = creg->address; - numregs = 1; - } - } - pullCoil(offset, numregs); - } - } - */ - void pushIsts() { - } - void pullIsts() { - } - void pushHreg() { - } - void pullHreg() { - } - void pushIreg() { - } - void pullIreg() { - } - public: - void begin() { - } - void task(); - IPAddress eventSource(); - bool pullReg(uint16_t address, uint16_t numregs) { - addReg(address, numregs); - return true; - } - bool pushReg(uint16_t address, uint16_t numregs); -}; +typedef bool (*cbTransaction)(uint8_t event, IPAddress ip); -typedef uint16_t (*cbModbusSlave)(TTransaction* query, ModbusMasterIP::ResultCode result); typedef struct TTransaction { uint16_t transactionId; - Modbus::FunctionCode fn; - uint16_t startreg; - uint16_t numregs; - time_t timestamp; - cbModbusSlave cb; + uint32_t timestamp; + cbTransaction cb = nullptr; + uint8_t* _frame; + bool operator ==(const TTransaction &obj) const + { + return transactionId == obj.transactionId; + } + bool operator >(const TTransaction &obj) const + { + return timestamp > obj.timestamp; + } }; -class ModbusIP : public ModbusCoreIP, public WiFiServer { - public: +class ModbusIP : public Modbus { + public: + enum EventCode { + SUCCESS = 0x00, + MASTER_CONNECT = 0xD1, + MASTER_DISCONNECT = 0xD2, + TRANSACTION_ERROR = 0xD4, + TRANSACTION_TIMEOUT = 0xD5, + SLAVE_CONNECT = 0xD6, + SLAVE_DISCONNECT = 0xD7 + }; + protected: + typedef union MBAP_t { + struct { + uint16_t transactionId; + uint16_t protocolId; + uint16_t length; + uint8_t unitId; + }; + uint8_t raw[7]; + }; + MBAP_t _MBAP; + cbModbusConnect cbConnect = nullptr; + cbModbusConnect cbDisconnect = nullptr; + WiFiServer* server; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - std::list _trans[MODBUSIP_MAX_CLIENTS]; - IPAddress slaveIp[MODBUSIP_MAX_CLIENTS]; + std::list _trans; + int16_t transactionId = 0; + int8_t n = -1; + + TTransaction* searchTransaction(uint16_t id) { + TTransaction tmp; + // = {id, 0, NULL, NULL}; + tmp.transactionId = id; + tmp.timestamp = 0; + tmp.cb = nullptr; + tmp._frame = nullptr; + std::list::iterator it = std::find(_trans.begin(), _trans.end(), tmp); + if (it != _trans.end()) return &*it; + return nullptr; + } + void clientsCleanup() { // Free clients if not connected + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + if (client[i] && !client[i]->connected()) { + IPAddress ip = client[i]->remoteIP(); + delete client[i]; + client[i] = nullptr; + if (cbDisconnect && cbEnabled) + cbDisconnect(ip); + } + } + } int8_t getFreeClient() { - for (uint8_t i = 0; n < MODBUSIP_MAX_CLIENTS; i++) { + //clientsCleanup(); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) if (!client[i]) return i; - if (!client[i]->connected()) { // Free client if not connected - delete client[i]; - client[i] = NULL; - slaveIp[i] = INADDR_NONE; - return i; - } - } return -1; } - int8_t getClient(IPAddress ip) { - Serial.println(0); + + int8_t getSlave(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->remoteIP() == ip) + if (client[i] && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) return i; return -1; } - bool isConnected(IPAddress ip) { - int8_t p = getClient(ip); - return p != -1 && client[p]->connected(); - } - bool connectSlave(IPAddress ip) { - if(getClient(ip) == -1) { - Serial.println(1); - uint8_t p = getFreeClient(); - Serial.println(2); - if (p != -1) { - Serial.println(3); - client[p] = new WiFiClient(); - client[p]->connect(ip, MODBUSIP_PORT); - Serial.println(4); - slaveIp[p] = ip; - return true; - } - } - return false; - } - bool disconnect(IPAddress addr) {} - void onMasterConnect(cbModbusConnect cb) {} - void onMasterDisconnect(cbModbusConnect cb) {} - void slave() {} - void master() {} - //cbModbusConnect cbConnect = NULL; - int8_t n = -1; - public: - ModbusIP() : WiFiServer(MODBUSIP_PORT) { - } - void begin(); - void task(); - //void onConnect(cbModbusConnect cb); - IPAddress eventSource(); - void pushBits(uint16_t address, uint16_t numregs, FunctionCode fn); - void pullBits(uint16_t address, uint16_t numregs, FunctionCode fn); - void pushWords(uint16_t address, uint16_t numregs, FunctionCode fn); - void pullWords(uint16_t address, uint16_t numregs, FunctionCode fn); - bool send(IPAddress ip) { - //if (connected()) Serial.println("Connected"); + bool send(IPAddress ip, cbTransaction cb) { uint16_t i; - //MBAP - _MBAP.transactionId = __bswap_16(1); + transactionId++; + _MBAP.transactionId = __bswap_16(transactionId); _MBAP.protocolId = __bswap_16(0); _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP _MBAP.unitId = 0xFF; - size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); uint8_t sbuf[send_len]; - -// for (i = 0; i < 7; i++) sbuf[i] = _MBAP.raw[i]; -// for (i = 0; i < _len; i++) sbuf[i+7] = _frame[i]; - memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); - int8_t p = getClient(ip); + int8_t p = getSlave(ip); if (p != -1 && client[p]->connected()) { - Serial.println(client[p]->write(sbuf, send_len)); + if (client[p]->write(sbuf, send_len) == send_len) { + //if (client[p]->write(_MBAP.raw, sizeof(_MBAP.raw)) == sizeof(_MBAP.raw) && client[p]->write(_frame, _len) == _len) { + //_trans.push_back({transactionId, millis(), cb, _len, _frame}); + TTransaction tmp;// = {transactionId, millis(), cb, _frame}; + tmp.transactionId = transactionId; + tmp.timestamp = millis(); + tmp.cb = cb; + tmp._frame = _frame; + _trans.push_back(tmp); + _frame = NULL; + return true; + } +/* for (uint8_t c = 0; c < send_len; c++) { Serial.print(sbuf[c], HEX); Serial.print(" "); } Serial.println(); - //Serial.println(_frame[0]); +*/ } + return false; } - void pushIsts() { + + public: + uint16_t lastTransaction() { + return transactionId; } - void pullIsts() { + bool isTransaction(uint16_t id) { + searchTransaction(id) != nullptr; } - void pushHreg() { + bool isConnected(IPAddress ip) { + int8_t p = getSlave(ip); + return p != -1 && client[p]->connected(); } - void pullHreg() { + + bool connect(IPAddress ip) { + if(getSlave(ip) == -1) { + uint8_t p = getFreeClient(); + if (p != -1) { + client[p] = new WiFiClient(); + client[p]->connect(ip, MODBUSIP_PORT); + return true; + } + } + return false; } - void pushIreg() { + + bool disconnect(IPAddress addr) {} + + void onDisconnect(cbModbusConnect cb = nullptr) { + cbDisconnect = cb; } - void pullIreg() { + + void slave() { + begin(); } - public: + + void master() { + + } + + ModbusIP() { + } + void begin(); // Depricated + void task(); + void onConnect(cbModbusConnect cb); + IPAddress eventSource(); + bool pullReg(uint16_t address, uint16_t numregs) { addReg(address, numregs); return true; } bool pushReg(uint16_t address, uint16_t numregs); - void pushCoil() { + + public: + bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = NULL) { + //addCoil(offset, numregs); // Should registers requre to be added there or use existing? + writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); + return send(ip, cb); } - void pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1) { - readSlave(offset, numregs, FC_READ_COILS); - send(ip); + bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = NULL) { + addCoil(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(COIL(offset), numregs, FC_READ_COILS); + return send(ip, cb); } - void pullCoils() { + void pullCoils() { // Not implemented. Just test code. uint16_t offset; uint16_t numregs = 1; std::list::iterator it = _regs.begin(); - //std::vector::iterator it = _regs.begin(); if (it == _regs.end()) return; offset = it->address; while(++it != _regs.end()) @@ -342,26 +230,16 @@ class ModbusIP : public ModbusCoreIP, public WiFiServer { } pullCoil(offset, numregs); } - /* - TRegister* creg = getHead(); - if (!creg) return; - while (creg && !IS_COIL(creg->address)) creg = creg->next; - if (creg) { - offset = creg->address; - while (creg->next) { - creg = creg->next; - if (IS_COIL(creg->address)) { - if ((offset + 1) == creg->address) { - numregs++; - continue; - } - pullCoil(offset, numregs); - offset = creg->address; - numregs = 1; - } - } - pullCoil(offset, numregs); - } + void pushIsts() { + } + void pullIsts() { + } + void pushHreg() { + } + void pullHreg() { + } + void pushIreg() { + } + void pullIreg() { } - */ }; \ No newline at end of file From 4ba119c730d143ff52a2ab2ff4db38dc9b6d68cf Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 18 Jun 2018 18:32:20 +0500 Subject: [PATCH 065/288] All Modbus Master functions is implemented --- API.md | 41 +++++-- README.md | 18 +-- examples/TestCallback/TestCallback.ino | 2 +- keywords.txt | 7 +- src/Modbus.cpp | 28 +++-- src/Modbus.h | 4 +- src/ModbusIP_ESP8266.cpp | 142 ++++++++++------------- src/ModbusIP_ESP8266.h | 152 ++++++++++++++----------- 8 files changed, 216 insertions(+), 178 deletions(-) diff --git a/API.md b/API.md index 54b005c..a1db336 100644 --- a/API.md +++ b/API.md @@ -29,6 +29,24 @@ bool Ists(uint16_t offset); uint16_t Ireg(uint16_t offset); ``` +### Query [multiple] regs from remote slave + +```c +bool pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +bool pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +bool pullIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); +``` + +### Send [multiple] regs to remote slave + +```c +bool pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +bool pushIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +bool pushIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); +``` + ### Callbacks ```c @@ -43,7 +61,7 @@ void onConnect(cbModbusConnect cb) void onDisonnect(cbModbusConnect cb) ``` -*ModbusIP only.* Assign callback function on new incoming connection event. +Assign callback function on new incoming connection event. ```c typedef bool (*cbModbusConnect)(IPAddress ip) @@ -57,11 +75,19 @@ typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. +```c +typedef uint16_t (*cbTransaction)(uint8_t eventCode, TTransaction* transaction) +``` + +Transaction end callback function definition. Pointer to TTransaction structure (see source for details) is passed. + ```c IPAddress eventSource() ``` -*ModbusIP and ModbusMasterIP only.* Should be called from onGet/onSet callback function. Returns IP address of remote requesting operation or IPADDR_NONE for local. +Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. + +*Note:* In case of transaction callback INADDR_NONE returned in case if transaction is timedout. ```c bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); @@ -96,19 +122,16 @@ Assign callback function on register query event. Multiple sequental registers c ```c void begin(); void task(); +void master(); ``` ### ModBus IP Master specific ```c +void slave(); +bool connect(IPAddress ip); +bool disconnect(IPAddress ip); void (cbModbusResult*)(TTransaction* trans, Modbus::ResultCode); -void pullCoils(cbModbusResult cb = NULL); -void pushIstss(cbModbusResult cb = NULL); -void pullIstss(cbModbusResult cb = NULL); -void pushHregs(cbModbusResult cb = NULL); -void pullHregs(cbModbusResult cb = NULL); -void pushIregs(cbModbusResult cb = NULL); -void pullIregs(cbModbusResult cb = NULL); ``` ### Callback example diff --git a/README.md b/README.md index 35650b2..11e11c7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Modbus Library for ESP8266/ESP32 +# Modbus Master/Slave Library for ESP8266/ESP32 v2.0.ALFA This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. @@ -16,7 +16,9 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf * Supported platforms are * ESP8266 * ESP32 -* Operates as a slave, master or both +* Operates as + * slave + * master * Fully async operations. No loop locks. * Supports Modbus IP (TCP) * Reply exception messages for all supported functions @@ -30,9 +32,11 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf * 0x0F - Write Multiple Coils * 0x10 - Write Multiple Registers * Callbacks for - * Incoming IP connection + * Master connect + * Slave disconnect * Read specific Register * Write specific Register + * Slave transaction finish ## Notes: @@ -41,20 +45,20 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -For API specefication see [API.md](https://githab.com/emelianov/modbus-esp8266/API/md) +For API specefication see [API.md](https://github.com/emelianov/modbus-esp8266/API.md) ## Last Changes * Internal changes * Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. - * Move object's list implementation to std::list + * Change object's list implementation to std::list * Modbus class refactoring * ModbusIP networking code refactoring and error reporting - * Modbus master implementation preparation * Public API changes + * Modbus master implementation * Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL * Back to marking private for onSet, onGet, addReg and Reg methods - * Added callback-related eventSource method + * Added callback-related eventSource method, onDisconnect, onEvent callbacks ## Contributions diff --git a/examples/TestCallback/TestCallback.ino b/examples/TestCallback/TestCallback.ino index 25f714c..14d4b3b 100644 --- a/examples/TestCallback/TestCallback.ino +++ b/examples/TestCallback/TestCallback.ino @@ -65,7 +65,7 @@ void setup() { pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set - //test.push_front({10,10,NULL,NULL,NULL}); + //test.push_front({10,10,nullptr,nullptr,nullptr}); //Serial.println(test.begin()->address); } diff --git a/keywords.txt b/keywords.txt index e6a8ea1..e99d136 100644 --- a/keywords.txt +++ b/keywords.txt @@ -2,13 +2,15 @@ # Datatypes (KEYWORD1) ModbusIP KEYWORD1 -ModbusMasterIP KEYWORD1 ModbusIP_ESP8266 KEYWORD1 # Methods and Functions (KEYWORD2) begin KEYWORD2 +master KEYWORD2 +slave KEYWORD2 task KEYWORD2 onConnect KEYWORD2 +onDisconnect KEYWORD2 cbEnable KEYWORD2 cbDisable KEYWORD2 eventSource KEYWORD2 @@ -35,4 +37,5 @@ BIT_BOOL LITERAL1 COIL_VAL LITERAL1 COIL_BOOL LITERAL1 ISTS_VAL LITERAL1 -ISTS_BOOL LITERAL1 \ No newline at end of file +ISTS_BOOL LITERAL1 +ResultCode LITERAL1 \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 0e87aa5..2f31069 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -151,7 +151,7 @@ void Modbus::slavePDU(uint8_t* frame) { break; } } - setMultipleBits(frame + 6, field1, field2); + setMultipleBits(frame + 6, COIL(field1), field2); successResponce(field1, field2, fcode); _reply = REPLY_NORMAL; break; @@ -188,9 +188,9 @@ void Modbus::getMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numregs while (numregs--) { i = (totregs - numregs) / 8; if (BIT_BOOL(Reg(startreg))) - bitSet(frame[2+i], bitn); + bitSet(frame[i], bitn); else - bitClear(frame[2+i], bitn); + bitClear(frame[i], bitn); bitn++; //increment the bit index if (bitn == 8) bitn = 0; startreg++; //increment the register @@ -268,7 +268,7 @@ void Modbus::setMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numoutp uint16_t i; while (numoutputs--) { i = (totoutputs - numoutputs) / 8; - Reg(startreg, bitRead(frame[i], bitn)); + Reg(startreg, BIT_VAL(bitRead(frame[i], bitn))); bitn++; //increment the bit index if (bitn == 8) bitn = 0; startreg++; //increment the register @@ -349,14 +349,20 @@ bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); - _len = 5; + _len = 6 + 2 * numregs; _frame = (uint8_t*) malloc(_len); - _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; - _frame[3] = numregs >> 8; - _frame[4] = numregs & 0x00FF; - return true; + if (_frame) { + _frame[0] = fn; + _frame[1] = startreg >> 8; + _frame[2] = startreg & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + _frame[5] = _len - 6; + getMultipleWords(_frame + 6, startreg, numregs); + return true; + } + _reply = REPLY_OFF; + return false; } void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame) { diff --git a/src/Modbus.h b/src/Modbus.h index 517865a..6802e2c 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -68,6 +68,7 @@ class Modbus { }; //Exception Codes 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 @@ -80,7 +81,8 @@ class Modbus { 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_TIMEOUT = 0xE4, // Custom. Operation not finished within reasonable time + EX_CONNECTION_LOST = 0xE5 // Custom. Connection with device lost }; bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index a19100d..714fd12 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -9,7 +9,7 @@ void ModbusIP::begin() { server = new WiFiServer(MODBUSIP_PORT); server->begin(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - client[i] = NULL; + client[i] = nullptr; } IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query @@ -19,108 +19,92 @@ IPAddress ModbusIP::eventSource() { // Returns IP of current processing client } void ModbusIP::task() { - uint8_t i; - clientsCleanup(); + cleanup(); while (server->hasClient()) { WiFiClient* currentClient = new WiFiClient(server->available()); - if (currentClient != NULL && currentClient->connected()) { - if (cbConnect == NULL || cbConnect(currentClient->remoteIP())) { - n = getFreeClient(); - if (n > -1) { - client[n] = currentClient; - //for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - // if (client[n] == NULL) { - // client[n] = currentClient; - // break; // for - // } - //} - //if (n < MODBUSIP_MAX_CLIENTS) // If client added process next - continue; // while - } + if (currentClient == nullptr && !currentClient->connected()) + continue; + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + n = getFreeClient(); + if (n > -1) { + client[n] = currentClient; + continue; // while } - // If callback returns false or _MAX_CLIENTS reached immediate close connection - currentClient->flush(); - currentClient->stop(); - delete currentClient; } + // If callback returns false or _MAX_CLIENTS reached immediate close connection + currentClient->flush(); + currentClient->stop(); + delete currentClient; } for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - if (client[n] == NULL) + if (client[n] == nullptr) continue; // for (n) -// if (!client[n]->connected()) { -// delete client[n]; -// client[n] = NULL; -// continue; // for (n) -// } - if (client[n]->available() > sizeof(_MBAP)) { - client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP - _len = __bswap_16(_MBAP.length); - _len--; // Do not count with last byte from MBAP + if (client[n]->available() < sizeof(_MBAP) + 1) + continue; + client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP + _len = __bswap_16(_MBAP.length); + _len--; // Do not count with last byte from MBAP - if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. - client[n]->flush(); - continue; // for (n) - } - if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME + if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. + client[n]->flush(); + continue; // for (n) + } + if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME + exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + client[n]->flush(); + } else { + _frame = (uint8_t*) malloc(_len); + if (!_frame) { exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); client[n]->flush(); } else { - _frame = (uint8_t*) malloc(_len); - if (!_frame) { - exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + if (client[n]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame + exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); client[n]->flush(); } else { - if (client[i]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame - exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - client[i]->flush(); + if (client[n]->localPort() == MODBUSIP_PORT) { + slavePDU(_frame); // Slave } else { - if (client[i]->localPort() == MODBUSIP_PORT) { - slavePDU(_frame); // Slave - } else { - for (uint8_t c = 0; c < _len; c++) { - Serial.print(_frame[c], HEX); - Serial.print(" "); +// for (uint8_t c = 0; c < _len; c++) { +// Serial.print(_frame[c], HEX); +// Serial.print(" "); +// } + _reply = EX_SUCCESS; + TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); + if (trans) { + if ((_frame[0] & 0x7F) == trans->_frame[0]) { + masterPDU(_frame, trans->_frame); // Master + } else { + _reply = EX_UNEXPECTED_RESPONSE; } - _reply = 0; - TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); - if (trans) { - if ((_frame[0] & 0x7F) == trans->_frame[0]) { - masterPDU(_frame, trans->_frame); // Master - } else { - _reply = EX_UNEXPECTED_RESPONSE; - } - if (cbEnabled && trans->cb) { - trans->cb(_reply, client[i]->remoteIP()); - } - free(trans->_frame); - _trans.remove(*trans); + if (cbEnabled && trans->cb) { + trans->cb((ResultCode)_reply, trans); } + free(trans->_frame); + _trans.remove(*trans); } - client[n]->flush(); // Not sure if we need flush rest of data available } + client[n]->flush(); // Not sure if we need flush rest of data available } } - if (client[i]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; - if (_reply != REPLY_OFF) { - _MBAP.length = __bswap_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); - client[n]->write(sbuf, send_len); - // Need to test if we can do not use double buffering - //client[n]->write(_MBAP.raw, sizeof(_MBAP.raw));; - //client[n]->write(_frame, _len); - } - free(_frame); - _len = 0; } + if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; + if (_reply != REPLY_OFF) { + _MBAP.length = __bswap_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); + client[n]->write(sbuf, send_len); + } + free(_frame); + _frame = nullptr; + _len = 0; } n = -1; } -void ModbusIP::onConnect(cbModbusConnect cb = NULL) { +void ModbusIP::onConnect(cbModbusConnect cb = nullptr) { cbConnect = cb; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 6f13fa2..0849371 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -19,8 +19,9 @@ #define MODBUSIP_PORT 502 #define MODBUSIP_MAXFRAME 200 -#define MODBUSIP_TIMEOUT 10 +#define MODBUSIP_TIMEOUT 1000 #define MODBUSIP_UNIT 255 +#define MODBUSIP_MAX_TRANSACIONS 32 #define MODBUSIP_MAX_CLIENTS 4 @@ -29,7 +30,7 @@ typedef bool (*cbModbusConnect)(IPAddress ip); typedef struct TTransaction TTransaction; -typedef bool (*cbTransaction)(uint8_t event, IPAddress ip); +typedef bool (*cbTransaction)(Modbus::ResultCode event, TTransaction* t); typedef struct TTransaction { uint16_t transactionId; @@ -47,16 +48,6 @@ typedef struct TTransaction { }; class ModbusIP : public Modbus { - public: - enum EventCode { - SUCCESS = 0x00, - MASTER_CONNECT = 0xD1, - MASTER_DISCONNECT = 0xD2, - TRANSACTION_ERROR = 0xD4, - TRANSACTION_TIMEOUT = 0xD5, - SLAVE_CONNECT = 0xD6, - SLAVE_DISCONNECT = 0xD7 - }; protected: typedef union MBAP_t { struct { @@ -78,7 +69,7 @@ class ModbusIP : public Modbus { TTransaction* searchTransaction(uint16_t id) { TTransaction tmp; - // = {id, 0, NULL, NULL}; + // = {id, 0, nullptr, nullptr}; tmp.transactionId = id; tmp.timestamp = 0; tmp.cb = nullptr; @@ -87,7 +78,7 @@ class ModbusIP : public Modbus { if (it != _trans.end()) return &*it; return nullptr; } - void clientsCleanup() { // Free clients if not connected + void cleanup() { // Free clients if not connected and remove timedout transactions for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (client[i] && !client[i]->connected()) { IPAddress ip = client[i]->remoteIP(); @@ -95,8 +86,16 @@ class ModbusIP : public Modbus { client[i] = nullptr; if (cbDisconnect && cbEnabled) cbDisconnect(ip); - } - } + } + } + for (TTransaction& t : _trans) { + if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { + _trans.remove(t); + if (cbEnabled && t.cb) + t.cb(EX_TIMEOUT, &t); + } + } + } int8_t getFreeClient() { //clientsCleanup(); @@ -108,45 +107,38 @@ class ModbusIP : public Modbus { int8_t getSlave(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) return i; return -1; } bool send(IPAddress ip, cbTransaction cb) { - uint16_t i; + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) + return false; + int8_t p = getSlave(ip); + if (p == -1 || !client[p]->connected()) + return false; transactionId++; _MBAP.transactionId = __bswap_16(transactionId); _MBAP.protocolId = __bswap_16(0); _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP - _MBAP.unitId = 0xFF; + _MBAP.unitId = MODBUSIP_UNIT; 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); - int8_t p = getSlave(ip); - if (p != -1 && client[p]->connected()) { - if (client[p]->write(sbuf, send_len) == send_len) { - //if (client[p]->write(_MBAP.raw, sizeof(_MBAP.raw)) == sizeof(_MBAP.raw) && client[p]->write(_frame, _len) == _len) { - //_trans.push_back({transactionId, millis(), cb, _len, _frame}); - TTransaction tmp;// = {transactionId, millis(), cb, _frame}; - tmp.transactionId = transactionId; - tmp.timestamp = millis(); - tmp.cb = cb; - tmp._frame = _frame; - _trans.push_back(tmp); - _frame = NULL; - return true; - } -/* - for (uint8_t c = 0; c < send_len; c++) { - Serial.print(sbuf[c], HEX); - Serial.print(" "); - } - Serial.println(); -*/ - } - return false; + if (client[p]->write(sbuf, send_len) != send_len) + return false; + //if (client[p]->write(_MBAP.raw, sizeof(_MBAP.raw)) == sizeof(_MBAP.raw) && client[p]->write(_frame, _len) == _len) { + //_trans.push_back({transactionId, millis(), cb, _len, _frame}); + TTransaction tmp;// = {transactionId, millis(), cb, _frame}; + tmp.transactionId = transactionId; + tmp.timestamp = millis(); + tmp.cb = cb; + tmp._frame = _frame; + _trans.push_back(tmp); + _frame = nullptr; + return true; } public: @@ -162,15 +154,15 @@ class ModbusIP : public Modbus { } bool connect(IPAddress ip) { - if(getSlave(ip) == -1) { - uint8_t p = getFreeClient(); - if (p != -1) { - client[p] = new WiFiClient(); - client[p]->connect(ip, MODBUSIP_PORT); - return true; - } - } - return false; + cleanup(); + if(getSlave(ip) != -1) + return true; + int8_t p = getFreeClient(); + if (p == -1) + return false; + client[p] = new WiFiClient(); + client[p]->connect(ip, MODBUSIP_PORT); + return true; } bool disconnect(IPAddress addr) {} @@ -194,23 +186,26 @@ class ModbusIP : public Modbus { void onConnect(cbModbusConnect cb); IPAddress eventSource(); - bool pullReg(uint16_t address, uint16_t numregs) { - addReg(address, numregs); - return true; - } - bool pushReg(uint16_t address, uint16_t numregs); - public: - bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = NULL) { + bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? - writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); + if (numregs == 1) { + readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); + } else { + writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); + } return send(ip, cb); } - bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = NULL) { + bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; addCoil(offset, numregs); // Should registers requre to be added there or use existing? readSlave(COIL(offset), numregs, FC_READ_COILS); return send(ip, cb); } +/* void pullCoils() { // Not implemented. Just test code. uint16_t offset; uint16_t numregs = 1; @@ -230,16 +225,37 @@ class ModbusIP : public Modbus { } pullCoil(offset, numregs); } - void pushIsts() { - } - void pullIsts() { - } - void pushHreg() { +*/ + bool pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + addIsts(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); + return send(ip, cb); } - void pullHreg() { + bool pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + //addCoil(offset, numregs); // Should registers requre to be added there or use existing? + if (numregs == 1) { + readSlave(HREG(offset), Hreg(offset), FC_WRITE_REG); + } else { + writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS); + } + return send(ip, cb); } - void pushIreg() { + bool pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + addHreg(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(HREG(offset), numregs, FC_READ_REGS); + return send(ip, cb); } - void pullIreg() { + bool pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + addIreg(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); + return send(ip, cb); } }; \ No newline at end of file From 7469ade8a60a17df40bbd5807ba626b158c6c366 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 18 Jun 2018 23:52:44 +0500 Subject: [PATCH 066/288] Direct slave write --- API.md | 11 +++++++++-- src/Modbus.cpp | 6 ++---- src/ModbusIP_ESP8266.h | 31 +++++++++++++++++-------------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/API.md b/API.md index a1db336..460a36c 100644 --- a/API.md +++ b/API.md @@ -1,4 +1,4 @@ -# Modbus Library for ESP8266/ESP32 +# Modbus Master/Slave Library for ESP8266/ESP32 ## API @@ -47,6 +47,13 @@ bool pushIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction bool pushIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); ``` +### Write value to remote slave reg + +```c +bool Coil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); +bool Hreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +``` + ### Callbacks ```c @@ -54,7 +61,7 @@ void cbEnable(bool state = TRUE); void cbDisable(); ``` -Callback generation control. Callback generation is enabled by default. +Callback generation control. Callback generation is enabled by default. Affect all callbacks. ```c void onConnect(cbModbusConnect cb) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 2f31069..ee76313 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -31,10 +31,8 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { bool Modbus::Reg(uint16_t address, uint16_t value) { TRegister* reg; - //search for the register address - reg = searchRegister(address); - //if found then assign the register value to the new value. - if (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 = reg->set(reg, value); } else { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 0849371..56d772c 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -41,10 +41,6 @@ typedef struct TTransaction { { return transactionId == obj.transactionId; } - bool operator >(const TTransaction &obj) const - { - return timestamp > obj.timestamp; - } }; class ModbusIP : public Modbus { @@ -64,12 +60,11 @@ class ModbusIP : public Modbus { WiFiServer* server; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; std::list _trans; - int16_t transactionId = 0; + int16_t transactionId = 0; // Last started transaction. Increments on on unsuccessful transaction start too. int8_t n = -1; TTransaction* searchTransaction(uint16_t id) { TTransaction tmp; - // = {id, 0, nullptr, nullptr}; tmp.transactionId = id; tmp.timestamp = 0; tmp.cb = nullptr; @@ -88,7 +83,7 @@ class ModbusIP : public Modbus { cbDisconnect(ip); } } - for (TTransaction& t : _trans) { + for (TTransaction& t : _trans) { // Cleanup transactions on timeout if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { _trans.remove(t); if (cbEnabled && t.cb) @@ -97,7 +92,7 @@ class ModbusIP : public Modbus { } } - int8_t getFreeClient() { + int8_t getFreeClient() { // Returns free slot position //clientsCleanup(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) if (!client[i]) @@ -112,9 +107,11 @@ class ModbusIP : public Modbus { return -1; } - bool send(IPAddress ip, cbTransaction cb) { + bool send(IPAddress ip, cbTransaction cb) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data + #ifdef MODBUSIP_MAX_TRANSACIONS if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; + #endif int8_t p = getSlave(ip); if (p == -1 || !client[p]->connected()) return false; @@ -129,9 +126,7 @@ class ModbusIP : public Modbus { memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); if (client[p]->write(sbuf, send_len) != send_len) return false; - //if (client[p]->write(_MBAP.raw, sizeof(_MBAP.raw)) == sizeof(_MBAP.raw) && client[p]->write(_frame, _len) == _len) { - //_trans.push_back({transactionId, millis(), cb, _len, _frame}); - TTransaction tmp;// = {transactionId, millis(), cb, _frame}; + TTransaction tmp; tmp.transactionId = transactionId; tmp.timestamp = millis(); tmp.cb = cb; @@ -145,7 +140,7 @@ class ModbusIP : public Modbus { uint16_t lastTransaction() { return transactionId; } - bool isTransaction(uint16_t id) { + bool isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) searchTransaction(id) != nullptr; } bool isConnected(IPAddress ip) { @@ -165,7 +160,7 @@ class ModbusIP : public Modbus { return true; } - bool disconnect(IPAddress addr) {} + bool disconnect(IPAddress addr) {} // Not implemented yet void onDisconnect(cbModbusConnect cb = nullptr) { cbDisconnect = cb; @@ -187,6 +182,14 @@ class ModbusIP : public Modbus { IPAddress eventSource(); public: + bool Coil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr) { + readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); + return send(ip, cb); + } + bool Hreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr) { + readSlave(HREG(offset), value, FC_WRITE_REG); + return send(ip, cb); + } bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { if (numregs < 0x0001 || numregs > 0x007B) return false; From 6ecb4aed4c6d22bc326b1ac217912cc3693034dd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 19 Jun 2018 15:57:29 +0500 Subject: [PATCH 067/288] Stability fixes --- src/Modbus.cpp | 4 +--- src/ModbusIP_ESP8266.cpp | 30 ++++++++++++++++++++++++++++++ src/ModbusIP_ESP8266.h | 40 ++++++---------------------------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index ee76313..2e2c2d0 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -243,7 +243,7 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { exceptionResponse(fn, EX_ILLEGAL_VALUE); return; } - if (searchRegister(startreg)) { //Check Address + if (!searchRegister(startreg)) { //Check Address exceptionResponse(fn, EX_ILLEGAL_ADDRESS); return; } @@ -367,8 +367,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame) { uint8_t fcode = frame[0]; _reply = 0; if ((fcode & 0x80) != 0) { - Serial.print("Error: "); - Serial.println(_frame[1]); _reply = _frame[1]; return; } diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 714fd12..e937b57 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -105,6 +105,36 @@ void ModbusIP::task() { n = -1; } +bool ModbusIP::send(IPAddress ip, cbTransaction cb) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data +#ifdef MODBUSIP_MAX_TRANSACIONS + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) + return false; +#endif + int8_t p = getSlave(ip); + if (p == -1 || !client[p]->connected()) + return false; + transactionId++; + _MBAP.transactionId = __bswap_16(transactionId); + _MBAP.protocolId = __bswap_16(0); + _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.unitId = MODBUSIP_UNIT; + 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); + if (client[p]->write(sbuf, send_len) != send_len) + return false; + TTransaction tmp; + tmp.transactionId = transactionId; + tmp.timestamp = millis(); + tmp.cb = cb; + tmp._frame = _frame; + _trans.push_back(tmp); + _frame = nullptr; + return true; +} + + void ModbusIP::onConnect(cbModbusConnect cb = nullptr) { cbConnect = cb; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 56d772c..69eb5c8 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -22,7 +22,6 @@ #define MODBUSIP_TIMEOUT 1000 #define MODBUSIP_UNIT 255 #define MODBUSIP_MAX_TRANSACIONS 32 - #define MODBUSIP_MAX_CLIENTS 4 // Callback function Type @@ -85,9 +84,9 @@ class ModbusIP : public Modbus { } for (TTransaction& t : _trans) { // Cleanup transactions on timeout if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { - _trans.remove(t); if (cbEnabled && t.cb) t.cb(EX_TIMEOUT, &t); + _trans.remove(t); } } @@ -107,34 +106,7 @@ class ModbusIP : public Modbus { return -1; } - bool send(IPAddress ip, cbTransaction cb) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data - #ifdef MODBUSIP_MAX_TRANSACIONS - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) - return false; - #endif - int8_t p = getSlave(ip); - if (p == -1 || !client[p]->connected()) - return false; - transactionId++; - _MBAP.transactionId = __bswap_16(transactionId); - _MBAP.protocolId = __bswap_16(0); - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP - _MBAP.unitId = MODBUSIP_UNIT; - 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); - if (client[p]->write(sbuf, send_len) != send_len) - return false; - TTransaction tmp; - tmp.transactionId = transactionId; - tmp.timestamp = millis(); - tmp.cb = cb; - tmp._frame = _frame; - _trans.push_back(tmp); - _frame = nullptr; - return true; - } + bool send(IPAddress ip, cbTransaction cb); public: uint16_t lastTransaction() { @@ -145,11 +117,11 @@ class ModbusIP : public Modbus { } bool isConnected(IPAddress ip) { int8_t p = getSlave(ip); - return p != -1 && client[p]->connected(); + return p != -1;// && client[p]->connected(); } bool connect(IPAddress ip) { - cleanup(); + //cleanup(); if(getSlave(ip) != -1) return true; int8_t p = getFreeClient(); @@ -182,11 +154,11 @@ class ModbusIP : public Modbus { IPAddress eventSource(); public: - bool Coil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr) { + bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr) { readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); return send(ip, cb); } - bool Hreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr) { + bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr) { readSlave(HREG(offset), value, FC_WRITE_REG); return send(ip, cb); } From 5671c35b6610ffc156d6d1d32ad4cf30767689cc Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 19 Jun 2018 18:01:27 +0500 Subject: [PATCH 068/288] Switch to std::vector --- src/Modbus.cpp | 6 ++++-- src/Modbus.h | 5 +++-- src/ModbusIP_ESP8266.cpp | 4 +++- src/ModbusIP_ESP8266.h | 11 ++++++++--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 2e2c2d0..0fdcf41 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -11,7 +11,8 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { TRegister* Modbus::searchRegister(uint16_t address) { const TRegister tmp = {address, 0, cbDefault, cbDefault}; - std::list::iterator it = std::find(_regs.begin(), _regs.end(), tmp); + //std::list::iterator it = std::find(_regs.begin(), _regs.end(), tmp); + std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); if (it != _regs.end()) return &*it; return nullptr; } @@ -24,7 +25,8 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { if (!searchRegister(address + i)) _regs.push_back({address + i, value, cbDefault, cbDefault}); } - _regs.sort(); + std::sort(_regs.begin(), _regs.end()); + //_regs.sort(); //_regs.unique(); return true; } diff --git a/src/Modbus.h b/src/Modbus.h index 6802e2c..cf89111 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -6,7 +6,7 @@ #pragma once #include "Arduino.h" -#include +//#include #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 @@ -176,7 +176,8 @@ class Modbus { REPLY_UNEXPECTED = 0x05 }; - std::list _regs; + //std::list _regs; + std::vector _regs; uint8_t* _frame; uint16_t _len; uint8_t _reply; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index e937b57..ce8fe6e 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -81,7 +81,9 @@ void ModbusIP::task() { trans->cb((ResultCode)_reply, trans); } free(trans->_frame); - _trans.remove(*trans); + //_trans.remove(*trans); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); + _trans.erase(it); } } client[n]->flush(); // Not sure if we need flush rest of data available diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 69eb5c8..d805441 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -58,7 +58,8 @@ class ModbusIP : public Modbus { cbModbusConnect cbDisconnect = nullptr; WiFiServer* server; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - std::list _trans; + //std::list _trans; + std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on on unsuccessful transaction start too. int8_t n = -1; @@ -68,7 +69,8 @@ class ModbusIP : public Modbus { tmp.timestamp = 0; tmp.cb = nullptr; tmp._frame = nullptr; - std::list::iterator it = std::find(_trans.begin(), _trans.end(), tmp); + //std::list::iterator it = std::find(_trans.begin(), _trans.end(), tmp); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), tmp); if (it != _trans.end()) return &*it; return nullptr; } @@ -86,7 +88,9 @@ class ModbusIP : public Modbus { if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { if (cbEnabled && t.cb) t.cb(EX_TIMEOUT, &t); - _trans.remove(t); + //_trans.remove(t); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), t); + _trans.erase(it); } } @@ -129,6 +133,7 @@ class ModbusIP : public Modbus { return false; client[p] = new WiFiClient(); client[p]->connect(ip, MODBUSIP_PORT); + if (client[p]->connected()) {Serial.println("Connected");} else Serial.println("Not yet"); return true; } From a20223dfec5096dbbee1be7c35a94e53bdc17350 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 20 Jun 2018 08:32:42 +0500 Subject: [PATCH 069/288] Documentation changes --- API.md | 21 ++++++++++----------- README.md | 27 ++++++++++++--------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/API.md b/API.md index 460a36c..478a911 100644 --- a/API.md +++ b/API.md @@ -50,8 +50,8 @@ bool pushIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction ### Write value to remote slave reg ```c -bool Coil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); -bool Hreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); +bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); ``` ### Callbacks @@ -146,26 +146,26 @@ void (cbModbusResult*)(TTransaction* trans, Modbus::ResultCode); ```c ModbusIP mb; bool coil = false; // Define external variable to get/set value -uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value +uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value Serial.print("Set query from "); Serial.println(mb.eventSource().toString()); coil = COIL_BOOL(val); - return val; // Returns value to be saved to TRegister structure + return val; // Returns value to be saved to TRegister structure } uint16_t cbCoilGet(TRegister* reg, uint16_t val) { Serial.print("Get query from "); Serial.println(mb.eventSource().toString()); - return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request + return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request } bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; // Return 'true' to allow connection or 'false' to drop connection + Serial.println(ip); + return true; // Return 'true' to allow connection or 'false' to drop connection } -ModbusIP mb; // ModbusIP object +ModbusIP mb; // ModbusIP object void setup() { ... mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); + mb.slave(); mb.addCoil(COIL_NR); // Add Coil mb.onSetCoil(COIL_NR, cbCoilSet); // Add callback on Coil COIL_NR value set mb.onGetCoil(COIL_NR, cbCoilGet); // Add callback on Coil COIL_NR value get @@ -173,12 +173,11 @@ void setup() { } void loop() { ... - mb.task(); + mb.task(); ... } ``` - ## Contributions https://github.com/emelianov/modbus-esp8266 diff --git a/README.md b/README.md index 11e11c7..7beddf4 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf * 0x10 - Write Multiple Registers * Callbacks for * Master connect - * Slave disconnect + * Slave disconnect * Read specific Register * Write specific Register * Slave transaction finish @@ -41,25 +41,22 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf ## Notes: 1. When using Modbus IP the transport protocol is TCP (port 502). - -2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) -offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. - -For API specefication see [API.md](https://github.com/emelianov/modbus-esp8266/API.md) +2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. +3. All type register's addresses are limited to 0..9999 for local and remote both. +4. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/API.md) ## Last Changes * Internal changes - * Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. - * Change object's list implementation to std::list - * Modbus class refactoring - * ModbusIP networking code refactoring and error reporting + * Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. + * Change object's list implementation to *std::vector* + * Modbus class refactoring + * ModbusIP networking code refactoring and error reporting * Public API changes - * Modbus master implementation - * Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL - * Back to marking private for onSet, onGet, addReg and Reg methods - * Added callback-related eventSource method, onDisconnect, onEvent callbacks - + * Modbus master implementation + * Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL + * Back to marking private for onSet, onGet, addReg and Reg methods + * Added callback-related eventSource method, onDisconnect and transaction result callbacks ## Contributions From 6436f9649d22c52282690dee2271b24674ffedcb Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 20 Jun 2018 12:24:35 +0500 Subject: [PATCH 070/288] ModbusIP header cleanup --- API.md | 20 ++--- src/Modbus.cpp | 13 +-- src/Modbus.h | 2 - src/ModbusIP_ESP8266.cpp | 153 ++++++++++++++++++++++++++++++-- src/ModbusIP_ESP8266.h | 182 +++++---------------------------------- 5 files changed, 177 insertions(+), 193 deletions(-) diff --git a/API.md b/API.md index 478a911..7eed5f7 100644 --- a/API.md +++ b/API.md @@ -43,10 +43,10 @@ bool pullIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction ```c bool pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -bool pushIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -bool pushIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); ``` +Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. + ### Write value to remote slave reg ```c @@ -94,7 +94,7 @@ IPAddress eventSource() Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. -*Note:* In case of transaction callback INADDR_NONE returned in case if transaction is timedout. +*Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. ```c bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); @@ -129,13 +129,13 @@ Assign callback function on register query event. Multiple sequental registers c ```c void begin(); void task(); -void master(); +void slave(); ``` ### ModBus IP Master specific ```c -void slave(); +void master(); bool connect(IPAddress ip); bool disconnect(IPAddress ip); void (cbModbusResult*)(TTransaction* trans, Modbus::ResultCode); @@ -146,16 +146,16 @@ void (cbModbusResult*)(TTransaction* trans, Modbus::ResultCode); ```c ModbusIP mb; bool coil = false; // Define external variable to get/set value -uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg to modify, 'val' is new register value +uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg structure to modify, 'val' is new register value Serial.print("Set query from "); Serial.println(mb.eventSource().toString()); - coil = COIL_BOOL(val); + coil = COIL_BOOL(val); // Assign value to external variable return val; // Returns value to be saved to TRegister structure } uint16_t cbCoilGet(TRegister* reg, uint16_t val) { Serial.print("Get query from "); Serial.println(mb.eventSource().toString()); - return COIL_VAL(coil); // Returns value to be returned to ModBus master as reply for current request + return COIL_VAL(coil); // Override value to be returned to ModBus master as reply for current request } bool cbConn(IPAddress ip) { Serial.println(ip); @@ -164,8 +164,8 @@ bool cbConn(IPAddress ip) { ModbusIP mb; // ModbusIP object void setup() { ... - mb.onConnect(cbConn); // Add callback on connection event - mb.slave(); + mb.onConnect(cbConn); // Add callback on connection event + mb.slave(); // Accept incoming Modbus connections mb.addCoil(COIL_NR); // Add Coil mb.onSetCoil(COIL_NR, cbCoilSet); // Add callback on Coil COIL_NR value set mb.onGetCoil(COIL_NR, cbCoilGet); // Add callback on Coil COIL_NR value get diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 0fdcf41..fbc4726 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -11,7 +11,6 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { TRegister* Modbus::searchRegister(uint16_t address) { const TRegister tmp = {address, 0, cbDefault, cbDefault}; - //std::list::iterator it = std::find(_regs.begin(), _regs.end(), tmp); std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); if (it != _regs.end()) return &*it; return nullptr; @@ -25,9 +24,7 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { if (!searchRegister(address + i)) _regs.push_back({address + i, value, cbDefault, cbDefault}); } - std::sort(_regs.begin(), _regs.end()); - //_regs.sort(); - //_regs.unique(); + //std::sort(_regs.begin(), _regs.end()); return true; } @@ -70,7 +67,6 @@ void Modbus::slavePDU(uint8_t* frame) { switch (fcode) { case FC_WRITE_REG: //field1 = reg, field2 = value - //writeSingleRegister(field1, field2); if (!Hreg(field1, field2)) { //Check Address and execute (reg exists?) exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); break; @@ -89,7 +85,6 @@ void Modbus::slavePDU(uint8_t* frame) { case FC_WRITE_REGS: //field1 = startreg, field2 = numregs, frame[5] = data lenght, header len = 6 - //writeMultipleRegisters(frame, field1, field2, frame[5]); if (field2 < 0x0001 || field2 > 0x007B || frame[5] != 2 * field2) { //Check value exceptionResponse(fcode, EX_ILLEGAL_VALUE); break; @@ -386,12 +381,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame) { break; case FC_READ_COILS: //field1 = startreg, field2 = numregs, frame[1] = data length, header len = 2 - Serial.print("Start: "); - Serial.print(field1); - Serial.print(" Count: "); - Serial.print(field2); - Serial.print(" Data: "); - Serial.println(frame[0]); bytecount_calc = field2 / 8; if (field2%8) bytecount_calc++; if (frame[1] != bytecount_calc) { // check if data size matches diff --git a/src/Modbus.h b/src/Modbus.h index cf89111..98f1934 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -6,7 +6,6 @@ #pragma once #include "Arduino.h" -//#include #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 @@ -176,7 +175,6 @@ class Modbus { REPLY_UNEXPECTED = 0x05 }; - //std::list _regs; std::vector _regs; uint8_t* _frame; uint16_t _len; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index ce8fe6e..92b7d4c 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -5,19 +5,53 @@ */ #include "ModbusIP_ESP8266.h" -void ModbusIP::begin() { - server = new WiFiServer(MODBUSIP_PORT); - server->begin(); +ModbusIP::ModbusIP() { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) client[i] = nullptr; } +void ModbusIP::master() { + +} + +void ModbusIP::slave() { + server = new WiFiServer(MODBUSIP_PORT); + server->begin(); +} + +void ModbusIP::begin() { + slave(); +} + +bool ModbusIP::connect(IPAddress ip) { + //cleanup(); + if(getSlave(ip) != -1) + return true; + int8_t p = getFreeClient(); + if (p == -1) + return false; + client[p] = new WiFiClient(); + return client[p]->connect(ip, MODBUSIP_PORT); +} + IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) return client[n]->remoteIP(); return INADDR_NONE; } +TTransaction* ModbusIP::searchTransaction(uint16_t id) { + TTransaction tmp; + tmp.transactionId = id; + tmp.timestamp = 0; + tmp.cb = nullptr; + tmp._frame = nullptr; + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), tmp); + if (it != _trans.end()) return &*it; + return nullptr; +} + + void ModbusIP::task() { cleanup(); while (server->hasClient()) { @@ -31,7 +65,7 @@ void ModbusIP::task() { continue; // while } } - // If callback returns false or _MAX_CLIENTS reached immediate close connection + // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached currentClient->flush(); currentClient->stop(); delete currentClient; @@ -65,10 +99,6 @@ void ModbusIP::task() { if (client[n]->localPort() == MODBUSIP_PORT) { slavePDU(_frame); // Slave } else { -// for (uint8_t c = 0; c < _len; c++) { -// Serial.print(_frame[c], HEX); -// Serial.print(" "); -// } _reply = EX_SUCCESS; TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); if (trans) { @@ -137,6 +167,111 @@ bool ModbusIP::send(IPAddress ip, cbTransaction cb) { // Prepare and send Modbus } -void ModbusIP::onConnect(cbModbusConnect cb = nullptr) { +void ModbusIP::onConnect(cbModbusConnect cb) { cbConnect = cb; +} + +void ModbusIP::onDisconnect(cbModbusConnect cb) { + cbDisconnect = cb; +} + +void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + if (client[i] && !client[i]->connected()) { + IPAddress ip = client[i]->remoteIP(); + delete client[i]; + client[i] = nullptr; + if (cbDisconnect && cbEnabled) + cbDisconnect(ip); + } + } + for (TTransaction& t : _trans) { // Cleanup transactions on timeout + if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { + if (cbEnabled && t.cb) + t.cb(EX_TIMEOUT, &t); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), t); + _trans.erase(it); + } + } +} + +int8_t ModbusIP::getFreeClient() { // Returns free slot position + //clientsCleanup(); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (!client[i]) + return i; + return -1; +} + +int8_t ModbusIP::getSlave(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) + return i; + return -1; +} + +bool ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { + readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); + return send(ip, cb); + } + +bool ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { + readSlave(HREG(offset), value, FC_WRITE_REG); + return send(ip, cb); +} + +bool ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + //addCoil(offset, numregs); // Should registers requre to be added there or use existing? + if (numregs == 1) { + readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); + } else { + writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); + } + return send(ip, cb); +} + +bool ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + addCoil(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(COIL(offset), numregs, FC_READ_COILS); + return send(ip, cb); +} + +bool ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + addIsts(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); + return send(ip, cb); +} + +bool ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + //addCoil(offset, numregs); // Should registers requre to be added there or use existing? + if (numregs == 1) { + readSlave(HREG(offset), Hreg(offset), FC_WRITE_REG); + } else { + writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS); + } + return send(ip, cb); +} + +bool ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + addHreg(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(HREG(offset), numregs, FC_READ_REGS); + return send(ip, cb); +} + +bool ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) + return false; + addIreg(offset, numregs); // Should registers requre to be added there or use existing? + readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); + return send(ip, cb); } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index d805441..1855041 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -22,7 +22,7 @@ #define MODBUSIP_TIMEOUT 1000 #define MODBUSIP_UNIT 255 #define MODBUSIP_MAX_TRANSACIONS 32 -#define MODBUSIP_MAX_CLIENTS 4 +#define MODBUSIP_MAX_CLIENTS 4 // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); @@ -58,58 +58,14 @@ class ModbusIP : public Modbus { cbModbusConnect cbDisconnect = nullptr; WiFiServer* server; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - //std::list _trans; std::vector _trans; - int16_t transactionId = 0; // Last started transaction. Increments on on unsuccessful transaction start too. + int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; - TTransaction* searchTransaction(uint16_t id) { - TTransaction tmp; - tmp.transactionId = id; - tmp.timestamp = 0; - tmp.cb = nullptr; - tmp._frame = nullptr; - //std::list::iterator it = std::find(_trans.begin(), _trans.end(), tmp); - std::vector::iterator it = std::find(_trans.begin(), _trans.end(), tmp); - if (it != _trans.end()) return &*it; - return nullptr; - } - void cleanup() { // Free clients if not connected and remove timedout transactions - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - if (client[i] && !client[i]->connected()) { - IPAddress ip = client[i]->remoteIP(); - delete client[i]; - client[i] = nullptr; - if (cbDisconnect && cbEnabled) - cbDisconnect(ip); - } - } - for (TTransaction& t : _trans) { // Cleanup transactions on timeout - if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { - if (cbEnabled && t.cb) - t.cb(EX_TIMEOUT, &t); - //_trans.remove(t); - std::vector::iterator it = std::find(_trans.begin(), _trans.end(), t); - _trans.erase(it); - } - } - - } - int8_t getFreeClient() { // Returns free slot position - //clientsCleanup(); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (!client[i]) - return i; - return -1; - } - - int8_t getSlave(IPAddress ip) { - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) - return i; - return -1; - } - + TTransaction* searchTransaction(uint16_t id); + void cleanup(); // Free clients if not connected and remove timedout transactions + int8_t getFreeClient(); // Returns free slot position + int8_t getSlave(IPAddress ip); bool send(IPAddress ip, cbTransaction cb); public: @@ -124,118 +80,24 @@ class ModbusIP : public Modbus { return p != -1;// && client[p]->connected(); } - bool connect(IPAddress ip) { - //cleanup(); - if(getSlave(ip) != -1) - return true; - int8_t p = getFreeClient(); - if (p == -1) - return false; - client[p] = new WiFiClient(); - client[p]->connect(ip, MODBUSIP_PORT); - if (client[p]->connected()) {Serial.println("Connected");} else Serial.println("Not yet"); - return true; - } - + ModbusIP(); + bool connect(IPAddress ip); bool disconnect(IPAddress addr) {} // Not implemented yet + void slave(); + void master(); + void task(); + void begin(); // Depricated - void onDisconnect(cbModbusConnect cb = nullptr) { - cbDisconnect = cb; - } - - void slave() { - begin(); - } - - void master() { - - } - - ModbusIP() { - } - void begin(); // Depricated - void task(); - void onConnect(cbModbusConnect cb); + void onConnect(cbModbusConnect cb = nullptr); + void onDisconnect(cbModbusConnect cb = nullptr); IPAddress eventSource(); - public: - bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr) { - readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); - return send(ip, cb); - } - bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr) { - readSlave(HREG(offset), value, FC_WRITE_REG); - return send(ip, cb); - } - bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; - //addCoil(offset, numregs); // Should registers requre to be added there or use existing? - if (numregs == 1) { - readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); - } else { - writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); - } - return send(ip, cb); - } - bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; - addCoil(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(COIL(offset), numregs, FC_READ_COILS); - return send(ip, cb); - } -/* - void pullCoils() { // Not implemented. Just test code. - uint16_t offset; - uint16_t numregs = 1; - std::list::iterator it = _regs.begin(); - if (it == _regs.end()) return; - offset = it->address; - while(++it != _regs.end()) - { - if (!IS_COIL(it->address)) continue; - if (it->address == offset + numregs) { - numregs++; - continue; - } - pullCoil(offset, numregs); - offset = it->address; - numregs = 1; - } - pullCoil(offset, numregs); - } -*/ - bool pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; - addIsts(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); - return send(ip, cb); - } - bool pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; - //addCoil(offset, numregs); // Should registers requre to be added there or use existing? - if (numregs == 1) { - readSlave(HREG(offset), Hreg(offset), FC_WRITE_REG); - } else { - writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS); - } - return send(ip, cb); - } - bool pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; - addHreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(HREG(offset), numregs, FC_READ_REGS); - return send(ip, cb); - } - bool pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; - addIreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); - return send(ip, cb); - } + bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); + bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); + bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + bool pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + bool pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + bool pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + bool pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); }; \ No newline at end of file From 388e4416f70e49f87ce8ccb1182fcdb1d50fe2e9 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 20 Jun 2018 15:04:51 +0500 Subject: [PATCH 071/288] Set zerro base for remote registers addressing --- API.md | 4 ++ README.md | 6 ++- examples/Master/Master.ino | 75 +++++++++++++++++++++----------------- src/Modbus.cpp | 21 ++++++----- src/Modbus.h | 13 ++++--- src/ModbusIP_ESP8266.cpp | 48 ++++++++++++------------ src/ModbusIP_ESP8266.h | 2 +- 7 files changed, 95 insertions(+), 74 deletions(-) diff --git a/API.md b/API.md index 7eed5f7..bbd6def 100644 --- a/API.md +++ b/API.md @@ -11,6 +11,8 @@ bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1); ``` +Offset is 0..9999 + ### Write regs ```c @@ -54,6 +56,8 @@ bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nul bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); ``` +Write value to remote Hreg/Coil. Offset is 0..65535. + ### Callbacks ```c diff --git a/README.md b/README.md index 7beddf4..335ab4e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# Modbus Master/Slave Library for ESP8266/ESP32 v2.0.ALFA +# Modbus Master-Slave Library for ESP8266/ESP32 + +#v2.0.BETA This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. @@ -33,7 +35,7 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf * 0x10 - Write Multiple Registers * Callbacks for * Master connect - * Slave disconnect + * Master/Slave disconnect * Read specific Register * Write specific Register * Slave transaction finish diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 3818107..3a2ac6a 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -1,10 +1,8 @@ /* Modbus-Arduino Example - Master (Modbus IP ESP8266) - Control a Led on GPIO0 pin using Write Single Coil Modbus Function + Control Led on D4/TX pin by remote Modbus devise using Read Single Coil Modbus Function -THIS IS NOT WORKING SAMPLE -- JUST DEVELOPNEMT VERSION - - (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 */ @@ -15,21 +13,38 @@ THIS IS NOT WORKING SAMPLE -- JUST DEVELOPNEMT VERSION #endif #include -//Modbus Registers Offsets (0-9999) -const int LED_COIL = 100; + +const int LED_COIL = 1; // Modbus Coil Offset (0-9999) +IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device + //Used Pins -const int ledPin = 0; //GPIO0 +#ifdef ESP8266 + #define USE_LED D4 + #else + $define UES_LED TX + #endif -//ModbusIP object -ModbusMasterIP mb; -ModbusIP slave; -uint16_t gc(TRegister* r, uint16_t v) { - Serial.print("Set ger: "); - Serial.println(v); +ModbusIP mb; //ModbusIP object + +uint16_t gc(TRegister* r, uint16_t v) { // Callback function + if (r->value != v) { // Check if Coil state is going to be changed + Serial.print("Set reg: "); + Serial.println(v); + if (COIL_BOOL(v)) { + digitalWrite(USE_LED, LOW); + } else { + digitalWrite(USE_LED, HIGH); + } + } return v; } + void setup() { + #ifdef ESP8266 Serial.begin(74880); + #else + Serial.begin(115200); + #endif WiFi.begin("EW", "iMpress6264"); @@ -43,25 +58,19 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.begin(); - - pinMode(ledPin, OUTPUT); - mb.addCoil(LED_COIL); - mb.onSetCoil(LED_COIL, gc); - mb.connect(IPAddress(192, 168, 30, 116)); - mb.pullCoil(LED_COIL); - - slave.begin(); - slave.addCoil(LED_COIL); - slave.onSetCoil(LED_COIL, gc); - + mb.master(); // Initialize local Modbus Master + pinMode(USE_LED, OUTPUT); + mb.addCoil(LED_COIL); // Add Coil + mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil } - + void loop() { - //Call once inside loop() - all magic here - mb.get(); - slave.task(); - //Attach ledPin to LED_COIL register - digitalWrite(ledPin, mb.Coil(LED_COIL)); - delay(100); -} + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.pullCoil(remote, LED_COIL); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + delay(100); // Additional deleay for conection + } + mb.task(); // Common local Modbus task + delay(100); // Pooling interval +} \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index fbc4726..b643199 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -19,6 +19,9 @@ TRegister* Modbus::searchRegister(uint16_t address) { bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { #ifdef MB_MAX_REGS if (_regs.size() + numregs > MB_MAX_REGS) return false; + #endif + #ifdef MB_MAX_ADDRESS + if (address > MB_MAX_ADDRESS) return false; #endif for (uint16_t i = 0; i < numregs; i++) { if (!searchRegister(address + i)) @@ -309,27 +312,27 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; + _frame[1] = address >> 8; + _frame[2] = address & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; return true; } -bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::writeSlaveBits(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn) { 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) { _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; + _frame[1] = address >> 8; + _frame[2] = address & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; @@ -342,14 +345,14 @@ bool Modbus::writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn return false; } -bool Modbus::writeSlaveWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::writeSlaveWords(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 6 + 2 * numregs; _frame = (uint8_t*) malloc(_len); if (_frame) { _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; + _frame[1] = address >> 8; + _frame[2] = address & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; diff --git a/src/Modbus.h b/src/Modbus.h index 98f1934..078da52 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -9,6 +9,7 @@ #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 +#define MB_MAX_ADDRESS 9999 #define COIL_BASE 1 #define ISTS_BASE 10001 #define IREG_BASE 30001 @@ -176,18 +177,18 @@ class Modbus { }; std::vector _regs; - uint8_t* _frame; - uint16_t _len; - uint8_t _reply; + uint8_t* _frame = nullptr; + uint16_t _len = 0; + uint8_t _reply = 0; bool cbEnabled = true; void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave void masterPDU(uint8_t* frame, uint8_t* sourceFrame); //For Master - bool readSlave(uint16_t startreg, uint16_t numregs, FunctionCode fn); - bool writeSlaveBits(uint16_t startreg, uint16_t numregs, FunctionCode fn); - bool writeSlaveWords(uint16_t startreg, uint16_t numregs, FunctionCode fn); + bool readSlave(uint16_t address, uint16_t numregs, FunctionCode fn); + bool writeSlaveBits(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn); + bool writeSlaveWords(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn); bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(uint16_t address, uint16_t value); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 92b7d4c..c463f64 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -54,21 +54,23 @@ TTransaction* ModbusIP::searchTransaction(uint16_t id) { void ModbusIP::task() { cleanup(); - while (server->hasClient()) { - WiFiClient* currentClient = new WiFiClient(server->available()); - if (currentClient == nullptr && !currentClient->connected()) - continue; - if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { - n = getFreeClient(); - if (n > -1) { - client[n] = currentClient; - continue; // while + if (server) { + while (server->hasClient()) { + WiFiClient* currentClient = new WiFiClient(server->available()); + if (currentClient == nullptr && !currentClient->connected()) + continue; + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + n = getFreeClient(); + if (n > -1) { + client[n] = currentClient; + continue; // while + } } + // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached + currentClient->flush(); + currentClient->stop(); + delete currentClient; } - // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached - currentClient->flush(); - currentClient->stop(); - delete currentClient; } for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (client[n] == nullptr) @@ -211,12 +213,12 @@ int8_t ModbusIP::getSlave(IPAddress ip) { } bool ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { - readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); + readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); return send(ip, cb); } bool ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { - readSlave(HREG(offset), value, FC_WRITE_REG); + readSlave(offset, value, FC_WRITE_REG); return send(ip, cb); } @@ -225,9 +227,9 @@ bool ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? if (numregs == 1) { - readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); + readSlave(offset, COIL_VAL(Coil(offset)), FC_WRITE_COIL); } else { - writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); + writeSlaveBits(offset, COIL(offset), numregs, FC_WRITE_COILS); } return send(ip, cb); } @@ -236,7 +238,7 @@ bool ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addCoil(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(COIL(offset), numregs, FC_READ_COILS); + readSlave(offset, numregs, FC_READ_COILS); return send(ip, cb); } @@ -244,7 +246,7 @@ bool ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addIsts(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); + readSlave(offset, numregs, FC_READ_INPUT_STAT); return send(ip, cb); } @@ -253,9 +255,9 @@ bool ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? if (numregs == 1) { - readSlave(HREG(offset), Hreg(offset), FC_WRITE_REG); + readSlave(offset, Hreg(offset), FC_WRITE_REG); } else { - writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS); + writeSlaveWords(offset, HREG(offset), numregs, FC_WRITE_REGS); } return send(ip, cb); } @@ -264,7 +266,7 @@ bool ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addHreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(HREG(offset), numregs, FC_READ_REGS); + readSlave(offset, numregs, FC_READ_REGS); return send(ip, cb); } @@ -272,6 +274,6 @@ bool ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addIreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); + readSlave(offset, numregs, FC_READ_INPUT_REGS); return send(ip, cb); } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 1855041..aa5d974 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -56,7 +56,7 @@ class ModbusIP : public Modbus { MBAP_t _MBAP; cbModbusConnect cbConnect = nullptr; cbModbusConnect cbDisconnect = nullptr; - WiFiServer* server; + WiFiServer* server = nullptr; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. From 201cbefeee1591ab074c2f479ba815b5504d9169 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 21 Jun 2018 11:41:04 +0500 Subject: [PATCH 072/288] adsReg fix --- examples/Master/Master.ino | 2 +- src/Modbus.cpp | 5 +---- src/Modbus.h | 12 ++++++++++++ src/ModbusIP_ESP8266.cpp | 28 +++++++++++++++++++--------- src/ModbusIP_ESP8266.h | 5 +++-- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 3a2ac6a..1e4091b 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -1,5 +1,5 @@ /* - Modbus-Arduino Example - Master (Modbus IP ESP8266) + Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) Control Led on D4/TX pin by remote Modbus devise using Read Single Coil Modbus Function (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index b643199..6737b01 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -19,9 +19,6 @@ TRegister* Modbus::searchRegister(uint16_t address) { bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { #ifdef MB_MAX_REGS if (_regs.size() + numregs > MB_MAX_REGS) return false; - #endif - #ifdef MB_MAX_ADDRESS - if (address > MB_MAX_ADDRESS) return false; #endif for (uint16_t i = 0; i < numregs; i++) { if (!searchRegister(address + i)) @@ -375,7 +372,7 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame) { uint8_t bytecount_calc; switch (fcode) { case FC_READ_REGS: - //field1 = startreg, field2 = status, frame[1] = data lenght, header len = 2 + //field1 = startreg, field2 = numregs, frame[1] = data lenght, header len = 2 if (frame[1] != 2 * field2) { //Check if data size matches _reply = EX_DATA_MISMACH; break; diff --git a/src/Modbus.h b/src/Modbus.h index 078da52..e92af54 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -86,6 +86,9 @@ class Modbus { }; bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + #ifdef MB_MAX_ADDRESS + if (offset > MB_MAX_ADDRESS) return false; + #endif return addReg(HREG(offset), value, numregs); } bool Hreg(uint16_t offset, uint16_t value) { @@ -98,12 +101,21 @@ class Modbus { return removeReg(HREG(offset)); } bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { + #ifdef MB_MAX_ADDRESS + if (offset > MB_MAX_ADDRESS) return false; + #endif return addReg(COIL(offset), COIL_VAL(value), numregs); } bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { + #ifdef MB_MAX_ADDRESS + if (offset > MB_MAX_ADDRESS) return false; + #endif return addReg(ISTS(offset), ISTS_VAL(value), numregs); } bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { + #ifdef MB_MAX_ADDRESS + if (offset > MB_MAX_ADDRESS) return false; + #endif return addReg(IREG(offset), value, numregs); } bool Coil(uint16_t offset, bool value) { diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index c463f64..58ed9e5 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -152,7 +152,7 @@ bool ModbusIP::send(IPAddress ip, cbTransaction cb) { // Prepare and send Modbus _MBAP.protocolId = __bswap_16(0); _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP _MBAP.unitId = MODBUSIP_UNIT; - size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); + 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); @@ -177,6 +177,15 @@ void ModbusIP::onDisconnect(cbModbusConnect cb) { cbDisconnect = cb; } +bool ifExpired(TTransaction& t) { + if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { + if (t.cb) + t.cb(Modbus::EX_TIMEOUT, &t); + free(t._frame); + return true; + } + return false; +} void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (client[i] && !client[i]->connected()) { @@ -187,14 +196,15 @@ void ModbusIP::cleanup() { // Free clients if not connected and remove timedout cbDisconnect(ip); } } - for (TTransaction& t : _trans) { // Cleanup transactions on timeout - if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { - if (cbEnabled && t.cb) - t.cb(EX_TIMEOUT, &t); - std::vector::iterator it = std::find(_trans.begin(), _trans.end(), t); - _trans.erase(it); - } - } +// for (TTransaction& t : _trans) { // Cleanup transactions on timeout +// if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { +// if (cbEnabled && t.cb) +// t.cb(EX_TIMEOUT, &t); +// //std::vector::iterator it = std::find(_trans.begin(), _trans.end(), t); +// //if (it) _trans.erase(it); +// } + _trans.erase( remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); +// } } int8_t ModbusIP::getFreeClient() { // Returns free slot position diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index aa5d974..ed2411f 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -21,7 +21,7 @@ #define MODBUSIP_MAXFRAME 200 #define MODBUSIP_TIMEOUT 1000 #define MODBUSIP_UNIT 255 -#define MODBUSIP_MAX_TRANSACIONS 32 +#define MODBUSIP_MAX_TRANSACIONS 16 #define MODBUSIP_MAX_CLIENTS 4 // Callback function Type @@ -58,7 +58,7 @@ class ModbusIP : public Modbus { cbModbusConnect cbDisconnect = nullptr; WiFiServer* server = nullptr; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - std::vector _trans; + //std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; @@ -69,6 +69,7 @@ class ModbusIP : public Modbus { bool send(IPAddress ip, cbTransaction cb); public: + std::vector _trans; uint16_t lastTransaction() { return transactionId; } From fa8e15dda84f3d13e1f6c46b65de72e5cd97f11c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 21 Jun 2018 12:02:18 +0500 Subject: [PATCH 073/288] Documentation fixes --- API.md | 9 ++++----- README.md | 24 ++++++++++++++---------- examples/Master/Master.ino | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/API.md b/API.md index bbd6def..9381e8f 100644 --- a/API.md +++ b/API.md @@ -13,7 +13,7 @@ bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1); Offset is 0..9999 -### Write regs +### Write reg ```c bool Hreg(uint16_t offset, uint16_t value); @@ -22,7 +22,7 @@ bool Ists(uint16_t offset, bool value); bool Ireg(uint16_t offset, uint16_t value); ``` -### Read regs +### Read reg ```c uint16_t Hreg(uint16_t offset); @@ -65,7 +65,7 @@ void cbEnable(bool state = TRUE); void cbDisable(); ``` -Callback generation control. Callback generation is enabled by default. Affect all callbacks. +Callback generation control. Callback generation is enabled by default. *Has no effect on transactions callbacks.* ```c void onConnect(cbModbusConnect cb) @@ -78,7 +78,7 @@ Assign callback function on new incoming connection event. typedef bool (*cbModbusConnect)(IPAddress ip) ``` -Connect event callback function definition. Client IP address is passed as argument. +Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. ```c typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) @@ -142,7 +142,6 @@ void slave(); void master(); bool connect(IPAddress ip); bool disconnect(IPAddress ip); -void (cbModbusResult*)(TTransaction* trans, Modbus::ResultCode); ``` ### Callback example diff --git a/README.md b/README.md index 335ab4e..4a82f8d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ #v2.0.BETA +*Be careful using current version in production. Satble version from 'realeses' page is recommended.* + This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. @@ -49,16 +51,18 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf ## Last Changes -* Internal changes - * Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. - * Change object's list implementation to *std::vector* - * Modbus class refactoring - * ModbusIP networking code refactoring and error reporting -* Public API changes - * Modbus master implementation - * Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL - * Back to marking private for onSet, onGet, addReg and Reg methods - * Added callback-related eventSource method, onDisconnect and transaction result callbacks +```diff +//Internal changes +- Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. ++ Change object's list implementation to *std::vector* ++ Modbus class refactoring ++ ModbusIP networking code refactoring and error reporting +//Public API changes ++ Modbus master implementation +- Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL +- Back to marking private for onSet, onGet, addReg and Reg methods ++ Added callback-related eventSource method, onDisconnect and transaction result callbacks +``` ## Contributions diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 1e4091b..f73744f 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -46,7 +46,7 @@ void setup() { Serial.begin(115200); #endif - WiFi.begin("EW", "iMpress6264"); + WiFi.begin("SSID", "password"); while (WiFi.status() != WL_CONNECTED) { delay(500); From b80b8ad0fa51ee7cf8dcd385f62a78402df7a575 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 21 Jun 2018 12:22:22 +0500 Subject: [PATCH 074/288] Documentation changes --- API.md | 14 +++++++++++--- README.md | 4 ++-- keywords.txt | 22 ++++++++++++++++++---- src/ModbusIP_ESP8266.h | 3 +-- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/API.md b/API.md index 9381e8f..9e6328b 100644 --- a/API.md +++ b/API.md @@ -1,4 +1,4 @@ -# Modbus Master/Slave Library for ESP8266/ESP32 +# Modbus Master-Slave Library for ESP8266/ESP32 ## API @@ -131,8 +131,13 @@ Assign callback function on register query event. Multiple sequental registers c ### ModBus IP specific ```c -void begin(); void task(); +``` + +### ModBus IP Slave specific + +```c +void begin(); // Depricated. Use slave() instead. void slave(); ``` @@ -141,7 +146,10 @@ void slave(); ```c void master(); bool connect(IPAddress ip); -bool disconnect(IPAddress ip); +bool disconnect(IPAddress ip); // Not implemented yet. +uint16_t lastTransaction(); +bool isTransaction(uint16_t id); +bool isConnected(IPAddress ip); ``` ### Callback example diff --git a/README.md b/README.md index 4a82f8d..118e544 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Modbus Master-Slave Library for ESP8266/ESP32 -#v2.0.BETA +# v2.0.BETA -*Be careful using current version in production. Satble version from 'realeses' page is recommended.* +*Be careful using current version in production. Satble version from 'releses' page is recommended.* This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. diff --git a/keywords.txt b/keywords.txt index e99d136..be8a901 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,8 +1,10 @@ -# Syntax Coloring Map For ModbusIP_ESP8266 +# Syntax Coloring Map For ModbusIP-ESP8266 # Datatypes (KEYWORD1) -ModbusIP KEYWORD1 -ModbusIP_ESP8266 KEYWORD1 +ModbusIP KEYWORD1 +Modbus KEYWORD1 +TRegister KEYWORD1 +TTransaction KEYWORD1 # Methods and Functions (KEYWORD2) begin KEYWORD2 @@ -30,6 +32,17 @@ Coil KEYWORD2 Ists KEYWORD2 Ireg KEYWORD2 Hreg KEYWORD2 +lastTransaction KEYWORD2 +isTransaction KEYWORD2 +isConnected KEYWORD2 +writeCoil KEYWORD2 +writeHreg KEYWORD2 +pushCoil KEYWORD2 +pullCoil KEYWORD2 +pullIsts KEYWORD2 +pushHreg KEYWORD2 +pullHreg KEYWORD2 +pullIreg KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 @@ -38,4 +51,5 @@ COIL_VAL LITERAL1 COIL_BOOL LITERAL1 ISTS_VAL LITERAL1 ISTS_BOOL LITERAL1 -ResultCode LITERAL1 \ No newline at end of file +ResultCode LITERAL1 +FunctionCode LITERAL1 \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index ed2411f..91cb659 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -58,7 +58,7 @@ class ModbusIP : public Modbus { cbModbusConnect cbDisconnect = nullptr; WiFiServer* server = nullptr; WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - //std::vector _trans; + std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; @@ -69,7 +69,6 @@ class ModbusIP : public Modbus { bool send(IPAddress ip, cbTransaction cb); public: - std::vector _trans; uint16_t lastTransaction() { return transactionId; } From 7d41657706838eaf3483f57b5e3b01645bcf780a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 21 Jun 2018 13:11:33 +0500 Subject: [PATCH 075/288] Mistype correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59bb199..f2765de 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # v2.0.BETA -*Be careful using current version in production. Satble version from 'releses' page is recommended.* +*Be careful using current version in production. Stable version from 'releses' page is recommended.* This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. From 499c2359a121efb5ad3b4e7580b88eb156cf2e30 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 21 Jun 2018 13:20:48 +0500 Subject: [PATCH 076/288] Mistype correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2765de..06cb9a1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # v2.0.BETA -*Be careful using current version in production. Stable version from 'releses' page is recommended.* +*Be careful using current version in production. Stable version from 'releases' page is recommended.* This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. From b6b6f36d13b9ed6122bdc8a451233de45753d54b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 21 Jun 2018 15:42:24 +0500 Subject: [PATCH 077/288] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06cb9a1..48dc6b4 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf 1. When using Modbus IP the transport protocol is TCP (port 502). 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 3. All type register's addresses are limited to 0..9999 for local and remote both. -4. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/API.md) +4. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) ## Last Changes From 4120affb9866fc3f909af05a4d478782f55ddb89 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 21 Jun 2018 23:00:12 +0500 Subject: [PATCH 078/288] Wrong bit-array processing fix (get/setMultipleBits) --- src/Modbus.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 6737b01..e5ba243 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -178,16 +178,17 @@ void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) { void Modbus::getMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numregs) { uint8_t bitn = 0; - uint16_t totregs = numregs; - uint16_t i; + uint16_t i = 0; while (numregs--) { - i = (totregs - numregs) / 8; if (BIT_BOOL(Reg(startreg))) bitSet(frame[i], bitn); - else + else bitClear(frame[i], bitn); bitn++; //increment the bit index - if (bitn == 8) bitn = 0; + if (bitn == 8) { + i++; + bitn = 0; + } startreg++; //increment the register } } @@ -259,13 +260,14 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { void Modbus::setMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numoutputs) { uint8_t bitn = 0; - uint16_t totoutputs = numoutputs; - uint16_t i; + uint16_t i = 0; while (numoutputs--) { - i = (totoutputs - numoutputs) / 8; Reg(startreg, BIT_VAL(bitRead(frame[i], bitn))); bitn++; //increment the bit index - if (bitn == 8) bitn = 0; + if (bitn == 8) { + i++; + bitn = 0; + } startreg++; //increment the register } } From 33953341addb30f35df555603dbeb77cd9093dd3 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 12 Jul 2018 12:31:23 +0500 Subject: [PATCH 079/288] Global regs definition --- src/Modbus.cpp | 4 ++++ src/Modbus.h | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e5ba243..0d06e63 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -5,6 +5,10 @@ */ #include "Modbus.h" +#ifdef MB_GLOBAL_REGS +std::vector _regs; +#endif + uint16_t cbDefault(TRegister* reg, uint16_t val) { return val; } diff --git a/src/Modbus.h b/src/Modbus.h index 9b9ae4e..1f5b250 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -7,6 +7,7 @@ #include "Arduino.h" +#define MB_GLOBAL_REGS #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 #define MB_MAX_ADDRESS 9999 @@ -187,8 +188,9 @@ class Modbus { REPLY_ERROR = 0x04, REPLY_UNEXPECTED = 0x05 }; - + #ifndef MB_GLOBAL_REGS std::vector _regs; + #endif uint8_t* _frame = nullptr; uint16_t _len = 0; uint8_t _reply = 0; From 33a92ac521c01745e70497f3403fc1628eb86347 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 12 Jul 2018 17:44:08 +0500 Subject: [PATCH 080/288] Comments and address expansion core types --- API.md | 2 +- README.md | 14 +++++++++++--- src/Modbus.h | 32 +++++++++++++++++++++----------- src/ModbusIP_ESP8266.cpp | 17 +++++++++-------- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/API.md b/API.md index 9e6328b..9d986e6 100644 --- a/API.md +++ b/API.md @@ -56,7 +56,7 @@ bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nul bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); ``` -Write value to remote Hreg/Coil. Offset is 0..65535. +Write value to remote Hreg/Coil. Offset is 1..65535. ### Callbacks diff --git a/README.md b/README.md index 48dc6b4..82dc262 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ In the current version the library allows the ESP8266/ESP32 operate async as a m http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf +http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf ## Features @@ -54,15 +55,22 @@ http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf ```diff //Internal changes -- Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. ++ Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. + Change object's list implementation to *std::vector* + Modbus class refactoring + ModbusIP networking code refactoring and error reporting ++ Global registers storage to share between multiple Modbus* instances //Public API changes + Modbus master implementation -- Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL -- Back to marking private for onSet, onGet, addReg and Reg methods ++ Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL ++ Back to marking private for onSet, onGet, addReg and Reg methods + Added callback-related eventSource method, onDisconnect and transaction result callbacks +// ToDo +- Extend register addressing to 1..65535 +- ModbusSerial (over RS-485) +- Read/Write file records function +- Write mask register function +- Serial line-specific functions ``` ## Contributions diff --git a/src/Modbus.h b/src/Modbus.h index 1f5b250..a53313b 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -7,7 +7,7 @@ #include "Arduino.h" -#define MB_GLOBAL_REGS +//#define MB_GLOBAL_REGS #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 #define MB_MAX_ADDRESS 9999 @@ -19,6 +19,10 @@ #define ISTS(n) (n + ISTS_BASE) #define IREG(n) (n + IREG_BASE) #define HREG(n) (n + HREG_BASE) +//#define COIL(n) {TReg::COIL, n} +//#define ISTS(n) {TReg::ISTS, n} +//#define IREG(n) {TReg::IREG, n} +//#define HREG(n) {TReg::HREG, n} #define BIT_VAL(v) (v?0xFF00:0x0000) #define BIT_BOOL(v) (v==0xFF00) #define COIL_VAL(v) (v?0xFF00:0x0000) @@ -32,22 +36,25 @@ typedef struct TRegister; -// Callback function Type -typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type +typedef struct TReg { + enum RegType {COIL, ISTS, IREG, HREG}; + RegType type; + uint16_t address; + bool operator ==(const TReg &obj) const { + return type == obj.type && address == obj.address; + } +}; typedef struct TRegister { + //TReg address; uint16_t address; uint16_t value; cbModbus get; cbModbus set; - bool operator <(const TRegister &obj) const - { - return address < obj.address; - } - bool operator ==(const TRegister &obj) const - { - return address == obj.address; - } + bool operator ==(const TRegister &obj) const { + return address == obj.address; + } }; uint16_t cbDefault(TRegister* reg, uint16_t val); @@ -64,10 +71,13 @@ class Modbus { FC_WRITE_REG = 0x06, // Preset Single Register FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) FC_WRITE_REGS = 0x10, // Write block of contiguous registers + FC_READ_FILE_REC = 0x14, // Not implemented + FC_WRITE_FILE_REC = 0x15, // Not implemented FC_MASKWRITE_REG = 0x16, // Not implemented FC_READWRITE_REGS = 0x17 // Not implemented }; //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 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index e1ec046..e602e54 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -100,13 +100,16 @@ void ModbusIP::task() { client[n]->flush(); } else { if (client[n]->localPort() == MODBUSIP_PORT) { - slavePDU(_frame); // Slave + // Process incoming frame as slave + slavePDU(_frame); } else { + // Process reply to master request _reply = EX_SUCCESS; TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); - if (trans) { - if ((_frame[0] & 0x7F) == trans->_frame[0]) { - masterPDU(_frame, trans->_frame); // Master + if (trans) { // if valid transaction id + if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + masterPDU(_frame, trans->_frame); } else { _reply = EX_UNEXPECTED_RESPONSE; } @@ -114,7 +117,6 @@ void ModbusIP::task() { trans->cb((ResultCode)_reply, trans); } free(trans->_frame); - //_trans.remove(*trans); std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); _trans.erase(it); } @@ -123,10 +125,9 @@ void ModbusIP::task() { } } } - if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; + if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master if (_reply != REPLY_OFF) { - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP - + _MBAP.length = __bswap_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)); From b44974d878e124419c1fe859339f8ac0bcdee061 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 13 Jul 2018 11:20:59 +0500 Subject: [PATCH 081/288] Add TAddress +/++/+= overloads --- README.md | 4 +++- src/Modbus.h | 32 +++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 82dc262..57dedcf 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,13 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + Back to marking private for onSet, onGet, addReg and Reg methods + Added callback-related eventSource method, onDisconnect and transaction result callbacks // ToDo -- Extend register addressing to 1..65535 +- Extend register addressing to 0..65535 +- Move rest of implementations from Modbus.h - ModbusSerial (over RS-485) - Read/Write file records function - Write mask register function - Serial line-specific functions +- Implement removeReg, removeCoil, removeIsts, removeIreg, removeHreg ``` ## Contributions diff --git a/src/Modbus.h b/src/Modbus.h index a53313b..c555083 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -19,10 +19,10 @@ #define ISTS(n) (n + ISTS_BASE) #define IREG(n) (n + IREG_BASE) #define HREG(n) (n + HREG_BASE) -//#define COIL(n) {TReg::COIL, n} -//#define ISTS(n) {TReg::ISTS, n} -//#define IREG(n) {TReg::IREG, n} -//#define HREG(n) {TReg::HREG, n} +//#define COIL(n) {TAddress::COIL, n} +//#define ISTS(n) {TAddress::ISTS, n} +//#define IREG(n) {TAddress::IREG, n} +//#define HREG(n) {TAddress::HREG, n} #define BIT_VAL(v) (v?0xFF00:0x0000) #define BIT_BOOL(v) (v==0xFF00) #define COIL_VAL(v) (v?0xFF00:0x0000) @@ -37,17 +37,35 @@ typedef struct TRegister; typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type -typedef struct TReg { +typedef struct TAddress { enum RegType {COIL, ISTS, IREG, HREG}; RegType type; uint16_t address; - bool operator ==(const TReg &obj) const { + 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); + address++; + return result; + } + TAddress& operator+=(const int& inc) { // TAddress += integer + address += inc; + return *this; + } + TAddress operator+(const int &inc) { // TAddress + integer + TAddress result(*this); + result.address += inc; + return result; + } }; typedef struct TRegister { - //TReg address; + //TAddress address; uint16_t address; uint16_t value; cbModbus get; From 1fb57ba6a7dcb102cdf478053a50ea5157ee9ebf Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 13 Jul 2018 12:44:49 +0500 Subject: [PATCH 082/288] Expand register addressing to 0..65535 --- API.md | 4 +-- README.md | 5 ++- src/Modbus.cpp | 54 +++++++++++++++---------------- src/Modbus.h | 70 +++++++++++++--------------------------- src/ModbusIP_ESP8266.cpp | 20 ++++++------ 5 files changed, 63 insertions(+), 90 deletions(-) diff --git a/API.md b/API.md index 9d986e6..791582f 100644 --- a/API.md +++ b/API.md @@ -11,7 +11,7 @@ bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1); ``` -Offset is 0..9999 +Offset is 0..65535 ### Write reg @@ -56,7 +56,7 @@ bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nul bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); ``` -Write value to remote Hreg/Coil. Offset is 1..65535. +Write value to remote Hreg/Coil. Offset is 0..65535. ### Callbacks diff --git a/README.md b/README.md index 57dedcf..62c3c9e 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf 1. When using Modbus IP the transport protocol is TCP (port 502). 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -3. All type register's addresses are limited to 0..9999 for local and remote both. -4. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) +3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) ## Last Changes @@ -65,8 +64,8 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL + Back to marking private for onSet, onGet, addReg and Reg methods + Added callback-related eventSource method, onDisconnect and transaction result callbacks ++ Extend register addressing to 0..65535 // ToDo -- Extend register addressing to 0..65535 - Move rest of implementations from Modbus.h - ModbusSerial (over RS-485) - Read/Write file records function diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 0d06e63..4ff3b66 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -13,14 +13,14 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { return val; } -TRegister* Modbus::searchRegister(uint16_t address) { +TRegister* Modbus::searchRegister(TAddress address) { const TRegister tmp = {address, 0, cbDefault, cbDefault}; std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); if (it != _regs.end()) return &*it; return nullptr; } -bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { +bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) { #ifdef MB_MAX_REGS if (_regs.size() + numregs > MB_MAX_REGS) return false; #endif @@ -32,7 +32,7 @@ bool Modbus::addReg(uint16_t address, uint16_t value, uint16_t numregs) { return true; } -bool Modbus::Reg(uint16_t address, uint16_t value) { +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. @@ -46,7 +46,7 @@ bool Modbus::Reg(uint16_t address, uint16_t value) { return false; } -uint16_t Modbus::Reg(uint16_t address) { +uint16_t Modbus::Reg(TAddress address) { TRegister* reg; reg = searchRegister(address); if(reg) @@ -59,7 +59,7 @@ uint16_t Modbus::Reg(uint16_t address) { return 0; } -bool Modbus::removeReg(uint16_t address) { +bool Modbus::removeReg(TAddress address) { // Empty stub } @@ -100,7 +100,7 @@ void Modbus::slavePDU(uint8_t* frame) { } } setMultipleWords(frame + 6, HREG(field1), field2); - successResponce(field1, field2, fcode); + successResponce(HREG(field1), field2, fcode); _reply = REPLY_NORMAL; break; @@ -151,7 +151,7 @@ void Modbus::slavePDU(uint8_t* frame) { } } setMultipleBits(frame + 6, COIL(field1), field2); - successResponce(field1, field2, fcode); + successResponce(COIL(field1), field2, fcode); _reply = REPLY_NORMAL; break; @@ -160,13 +160,13 @@ void Modbus::slavePDU(uint8_t* frame) { } } -void Modbus::successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn) { +void Modbus::successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); _frame[0] = fn; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; + _frame[1] = startreg.address >> 8; + _frame[2] = startreg.address & 0x00FF; _frame[3] = numoutputs >> 8; _frame[4] = numoutputs & 0x00FF; } @@ -180,7 +180,7 @@ void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) { _reply = REPLY_NORMAL; } -void Modbus::getMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numregs) { +void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs) { uint8_t bitn = 0; uint16_t i = 0; while (numregs--) { @@ -197,7 +197,7 @@ void Modbus::getMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numregs } } -void Modbus::getMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numregs) { +void Modbus::getMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numregs) { uint16_t val; uint16_t i = 0; while(numregs--) { @@ -208,7 +208,7 @@ void Modbus::getMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numreg } } -void Modbus::readBits(uint16_t startreg, uint16_t numregs, FunctionCode fn) { +void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { if (numregs < 0x0001 || numregs > 0x07D0) { //Check value (numregs) exceptionResponse(fn, EX_ILLEGAL_VALUE); return; @@ -239,7 +239,7 @@ void Modbus::readBits(uint16_t startreg, uint16_t numregs, FunctionCode fn) { _reply = REPLY_NORMAL; } -void Modbus::readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { +void Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { exceptionResponse(fn, EX_ILLEGAL_VALUE); @@ -262,7 +262,7 @@ void Modbus::readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn) { _reply = REPLY_NORMAL; } -void Modbus::setMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numoutputs) { +void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) { uint8_t bitn = 0; uint16_t i = 0; while (numoutputs--) { @@ -276,7 +276,7 @@ void Modbus::setMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numoutp } } -void Modbus::setMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numregs) { +void Modbus::setMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numregs) { uint16_t val; uint16_t i = 0; while(numregs--) { @@ -286,7 +286,7 @@ void Modbus::setMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numreg } } -bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { +bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; while (numregs > 0) { @@ -300,7 +300,7 @@ bool Modbus::onGet(uint16_t address, cbModbus cb, uint16_t numregs) { } return atLeastOne; } -bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { +bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; while (numregs > 0) { @@ -315,27 +315,27 @@ bool Modbus::onSet(uint16_t address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) { +bool Modbus::readSlave(TAddress address, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); _frame[0] = fn; - _frame[1] = address >> 8; - _frame[2] = address & 0x00FF; + _frame[1] = address.address >> 8; + _frame[2] = address.address & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; return true; } -bool Modbus::writeSlaveBits(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { 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) { _frame[0] = fn; - _frame[1] = address >> 8; - _frame[2] = address & 0x00FF; + _frame[1] = startreg.address >> 8; + _frame[2] = startreg.address & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; @@ -348,14 +348,14 @@ bool Modbus::writeSlaveBits(uint16_t address, uint16_t startreg, uint16_t numreg return false; } -bool Modbus::writeSlaveWords(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 6 + 2 * numregs; _frame = (uint8_t*) malloc(_len); if (_frame) { _frame[0] = fn; - _frame[1] = address >> 8; - _frame[2] = address & 0x00FF; + _frame[1] = startreg.address >> 8; + _frame[2] = startreg.address & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; diff --git a/src/Modbus.h b/src/Modbus.h index c555083..736b84a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -10,29 +10,16 @@ //#define MB_GLOBAL_REGS #define MB_MAX_REGS 32 #define MB_MAX_FRAME 128 -#define MB_MAX_ADDRESS 9999 -#define COIL_BASE 1 -#define ISTS_BASE 10001 -#define IREG_BASE 30001 -#define HREG_BASE 40001 -#define COIL(n) (n + COIL_BASE) -#define ISTS(n) (n + ISTS_BASE) -#define IREG(n) (n + IREG_BASE) -#define HREG(n) (n + HREG_BASE) -//#define COIL(n) {TAddress::COIL, n} -//#define ISTS(n) {TAddress::ISTS, n} -//#define IREG(n) {TAddress::IREG, n} -//#define HREG(n) {TAddress::HREG, n} +#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 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) -#define IS_COIL(n) (n < ISTS_BASE) -#define IS_ISTS(n) (n >= ISTS_BASE && n < IREG_BASE) -#define IS_IREG(n) (n >= IREG_BASE && n < HREG_BASE) -#define IS_HREG(n) (n >= HREG_BASE) typedef struct TRegister; @@ -65,8 +52,7 @@ typedef struct TAddress { }; typedef struct TRegister { - //TAddress address; - uint16_t address; + TAddress address; uint16_t value; cbModbus get; cbModbus set; @@ -115,9 +101,6 @@ class Modbus { }; bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { - #ifdef MB_MAX_ADDRESS - if (offset > MB_MAX_ADDRESS) return false; - #endif return addReg(HREG(offset), value, numregs); } bool Hreg(uint16_t offset, uint16_t value) { @@ -130,21 +113,12 @@ class Modbus { return removeReg(HREG(offset)); } bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { - #ifdef MB_MAX_ADDRESS - if (offset > MB_MAX_ADDRESS) return false; - #endif return addReg(COIL(offset), COIL_VAL(value), numregs); } bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { - #ifdef MB_MAX_ADDRESS - if (offset > MB_MAX_ADDRESS) return false; - #endif return addReg(ISTS(offset), ISTS_VAL(value), numregs); } bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { - #ifdef MB_MAX_ADDRESS - if (offset > MB_MAX_ADDRESS) return false; - #endif return addReg(IREG(offset), value, numregs); } bool Coil(uint16_t offset, bool value) { @@ -196,16 +170,16 @@ class Modbus { return onSet(IREG(offset), cb, numregs); } private: - void readBits(uint16_t startreg, uint16_t numregs, FunctionCode fn); - void readWords(uint16_t startreg, uint16_t numregs, FunctionCode fn); + void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); + void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); - void setMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numoutputs); - void setMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numoutputs); + void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); + void setMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numoutputs); - void getMultipleBits(uint8_t* frame, uint16_t startreg, uint16_t numregs); - void getMultipleWords(uint8_t* frame, uint16_t startreg, uint16_t numregs); + void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); + void getMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numregs); - TRegister* searchRegister(uint16_t addr); + TRegister* searchRegister(TAddress addr); protected: //Reply Types @@ -224,19 +198,19 @@ class Modbus { uint8_t _reply = 0; bool cbEnabled = true; void exceptionResponse(FunctionCode fn, ResultCode excode); - void successResponce(uint16_t startreg, uint16_t numoutputs, FunctionCode fn); + void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave void masterPDU(uint8_t* frame, uint8_t* sourceFrame); //For Master - bool readSlave(uint16_t address, uint16_t numregs, FunctionCode fn); - bool writeSlaveBits(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn); - bool writeSlaveWords(uint16_t address, uint16_t startreg, uint16_t numregs, FunctionCode fn); + bool readSlave(TAddress address, uint16_t numregs, FunctionCode fn); + bool writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn); + bool writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn); - bool addReg(uint16_t address, uint16_t value = 0, uint16_t numregs = 1); - bool Reg(uint16_t address, uint16_t value); - uint16_t Reg(uint16_t address); - bool removeReg(uint16_t address); // Not implemented + 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); // Not implemented - bool onGet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSet(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onGet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); }; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index e602e54..0186195 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -225,12 +225,12 @@ int8_t ModbusIP::getSlave(IPAddress ip) { } bool ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { - readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); + readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); return send(ip, cb); } bool ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { - readSlave(offset, value, FC_WRITE_REG); + readSlave(HREG(offset), value, FC_WRITE_REG); return send(ip, cb); } @@ -239,9 +239,9 @@ bool ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? if (numregs == 1) { - readSlave(offset, COIL_VAL(Coil(offset)), FC_WRITE_COIL); + readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); } else { - writeSlaveBits(offset, COIL(offset), numregs, FC_WRITE_COILS); + writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); } return send(ip, cb); } @@ -250,7 +250,7 @@ bool ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addCoil(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(offset, numregs, FC_READ_COILS); + readSlave(COIL(offset), numregs, FC_READ_COILS); return send(ip, cb); } @@ -258,7 +258,7 @@ bool ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addIsts(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(offset, numregs, FC_READ_INPUT_STAT); + readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); return send(ip, cb); } @@ -267,9 +267,9 @@ bool ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? if (numregs == 1) { - readSlave(offset, Hreg(offset), FC_WRITE_REG); + readSlave(HREG(offset), Hreg(offset), FC_WRITE_REG); } else { - writeSlaveWords(offset, HREG(offset), numregs, FC_WRITE_REGS); + writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS); } return send(ip, cb); } @@ -278,7 +278,7 @@ bool ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addHreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(offset, numregs, FC_READ_REGS); + readSlave(HREG(offset), numregs, FC_READ_REGS); return send(ip, cb); } @@ -286,6 +286,6 @@ bool ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans if (numregs < 0x0001 || numregs > 0x007B) return false; addIreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(offset, numregs, FC_READ_INPUT_REGS); + readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); return send(ip, cb); } \ No newline at end of file From b4fe5819ac82d26aa762320500d72e7fd6e32579 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 13 Jul 2018 16:22:12 +0500 Subject: [PATCH 083/288] Experimental: ModbusSerial (slave) --- API.md | 3 +- README.md | 5 +- src/ModbusIP_ESP8266.cpp | 13 +++- src/ModbusIP_ESP8266.h | 14 +--- src/ModbusSerial.cpp | 161 +++++++++++++++++++++++++++++++++++++++ src/ModbusSerial.h | 82 ++++++++++++++++++++ 6 files changed, 263 insertions(+), 15 deletions(-) create mode 100644 src/ModbusSerial.cpp create mode 100644 src/ModbusSerial.h diff --git a/API.md b/API.md index 791582f..4147392 100644 --- a/API.md +++ b/API.md @@ -197,7 +197,8 @@ a.m.emelianov@gmail.com Original version: -http://github.com/andresarmento/modbus-esp8266 +https://github.com/andresarmento/modbus-esp8266 +https://github.com/andresarmento/modbus-arduino prof (at) andresarmento (dot) com diff --git a/README.md b/README.md index 62c3c9e..3783bb8 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf - Read/Write file records function - Write mask register function - Serial line-specific functions -- Implement removeReg, removeCoil, removeIsts, removeIreg, removeHreg +- removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) ``` ## Contributions @@ -82,7 +82,8 @@ a.m.emelianov@gmail.com Original version: -http://github.com/andresarmento/modbus-esp8266 +https://github.com/andresarmento/modbus-esp8266 +https://github.com/andresarmento/modbus-arduino prof (at) andresarmento (dot) com diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 0186195..63e5935 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -288,4 +288,15 @@ bool ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans addIreg(offset, numregs); // Should registers requre to be added there or use existing? readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); return send(ip, cb); -} \ No newline at end of file +} + +uint16_t ModbusIP::lastTransaction() { + return transactionId; +} +bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) + searchTransaction(id) != nullptr; +} +bool ModbusIP::isConnected(IPAddress ip) { + int8_t p = getSlave(ip); + return p != -1;// && client[p]->connected(); +} diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 91cb659..5f3e3e3 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -69,18 +69,10 @@ class ModbusIP : public Modbus { bool send(IPAddress ip, cbTransaction cb); public: - uint16_t lastTransaction() { - return transactionId; - } - bool isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) - searchTransaction(id) != nullptr; - } - bool isConnected(IPAddress ip) { - int8_t p = getSlave(ip); - return p != -1;// && client[p]->connected(); - } - ModbusIP(); + uint16_t lastTransaction(); + bool isTransaction(uint16_t id); + bool isConnected(IPAddress ip); bool connect(IPAddress ip); bool disconnect(IPAddress addr) {} // Not implemented yet void slave(); diff --git a/src/ModbusSerial.cpp b/src/ModbusSerial.cpp new file mode 100644 index 0000000..360d3eb --- /dev/null +++ b/src/ModbusSerial.cpp @@ -0,0 +1,161 @@ +/* + ModbusSerial.cpp - Source for Modbus Serial Library + Copyright (C) 2014 André Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#include "ModbusSerial.h" + +uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { + uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; + + Index = CRCHi ^ address; + CRCHi = CRCLo ^ _auchCRCHi[Index]; + CRCLo = _auchCRCLo[Index]; + + while (pduLen--) { + Index = CRCHi ^ *pduFrame++; + CRCHi = CRCLo ^ _auchCRCHi[Index]; + CRCLo = _auchCRCLo[Index]; + } + + return (CRCHi << 8) | CRCLo; +} + +ModbusSerial::ModbusSerial() { + +} + +bool ModbusSerial::setSlaveId(uint8_t slaveId){ + _slaveId = slaveId; + return true; +} + +uint8_t ModbusSerial::getSlaveId() { + return _slaveId; +} + +#ifdef MB_SOFTWARE_SERIAL +bool ModbusSerial::config(SoftwareSerial* port, uint32_t baud, int16_t txPin) { + (*port).begin(baud); +#else +bool ModbusSerial::config(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { + (*port).begin(baud, format); +#endif + _port = port; + _txPin = txPin; + + delay(2000); // ??? + + if (txPin >= 0) { + pinMode(txPin, OUTPUT); + digitalWrite(txPin, LOW); + } + + if (baud > 19200) { + _t15 = 750; + _t35 = 1750; + } else { + _t15 = 15000000/baud; // 1T * 1.5 = T1.5 + _t35 = 35000000/baud; // 1T * 3.5 = T3.5 + } + + return true; +} + +bool ModbusSerial::receive(uint8_t* frame) { + //first byte of frame = address + uint8_t address = frame[0]; + //Last two byts = crc + u_int crc = ((frame[_len - 2] << 8) | frame[_len - 1]); + + //Slave Check + if (address != 0xFF && address != getSlaveId()) { + return false; + } + + //CRC Check + if (crc != calcCrc(_frame[0], _frame+1, _len-3)) { + return false; + } + + //PDU starts after first uint8_t + //framesize PDU = framesize - address(1) - crc(2) + receivePDU(frame+1); + //No reply to Broadcasts + if (address == 0xFF) _reply = MB_REPLY_OFF; + return true; +} + +bool ModbusSerial::send(uint8_t* frame) { + uint8_t i; + + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + + for (i = 0 ; i < _len ; i++) { + (*_port).write(frame[i]); + } + + (*_port).flush(); + delayMicroseconds(_t35); + + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } +} + +bool ModbusSerial::sendPDU(uint8_t* pduframe) { + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + + //Send slaveId + (*_port).write(_slaveId); + + //Send PDU + uint8_t i; + for (i = 0 ; i < _len ; i++) { + (*_port).write(pduframe[i]); + } + + //Send CRC + uint16_t crc = calcCrc(_slaveId, _frame, _len); + (*_port).write(crc >> 8); + (*_port).write(crc & 0xFF); + + (*_port).flush(); + delayMicroseconds(_t35); + + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } +} + +void ModbusSerial::task() { + _len = 0; + + while ((*_port).available() > _len) { + _len = (*_port).available(); + delayMicroseconds(_t15); + } + + if (_len == 0) return; + + uint8_t i; + _frame = (uint8_t*) malloc(_len); + for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); + + if (receive(_frame)) { + if (_reply == MB_REPLY_NORMAL) + sendPDU(_frame); + else + if (_reply == MB_REPLY_ECHO) + send(_frame); + } + + free(_frame); + _len = 0; +} \ No newline at end of file diff --git a/src/ModbusSerial.h b/src/ModbusSerial.h new file mode 100644 index 0000000..4ab616e --- /dev/null +++ b/src/ModbusSerial.h @@ -0,0 +1,82 @@ +/* + ModbusSerial.h - Header for ModbusSerial Library + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once + +#include + +//#define MB_SOFTWARE_SERIAL + +#ifdef MB_SOFTWARE_SERIAL +#include +#endif + +uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); + +class ModbusSerial : public Modbus { + private: + Stream* _port; + uint32_t _baud; + u_int _format; + int _txPin; + unsigned int _t15; // inter character time out + unsigned int _t35; // frame delay + uint8_t _slaveId; + public: + ModbusSerial(); + bool setSlaveId(uint8_t slaveId); + uint8_t getSlaveId(); + #ifdef MB_SOFTWARE_SERIAL + bool config(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); + #else + bool config(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); + #endif + void task(); + bool receive(uint8_t* frame); + bool sendPDU(uint8_t* pduframe); + bool send(uint8_t* frame); +}; + +/* Table of CRC values for high�order byte */ +const uint8_t _auchCRCHi[] = { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40}; + +/* Table of CRC values for low�order byte */ +const uint8_t _auchCRCLo[] = { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, + 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, + 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, + 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, + 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, + 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, + 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, + 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, + 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, + 0x40}; \ No newline at end of file From 382c0e239e25a4141b1a66f7e6ccefc8f7d2c3a9 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 13 Jul 2018 17:14:11 +0500 Subject: [PATCH 084/288] Examples: Callback removed changes made by exident --- examples/Callback/Callback.ino | 21 ++------------------- src/ModbusSerial.cpp | 10 +++++----- src/ModbusSerial.h | 2 +- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/examples/Callback/Callback.ino b/examples/Callback/Callback.ino index 9f6d9f2..058337b 100644 --- a/examples/Callback/Callback.ino +++ b/examples/Callback/Callback.ino @@ -17,10 +17,7 @@ #endif #include -#include -std::list test; - -//Modbus Registers Offsets (0-9999) +//Modbus Registers Offsets (0-65535) const int LED_COIL = 100; //Used Pins #ifdef ESP8266 @@ -34,7 +31,7 @@ ModbusIP mb; // Callback function for write (set) Coil. Returns value to store. uint16_t cbLed(TRegister* reg, uint16_t val) { //Attach ledPin to LED_COIL register - digitalWrite(ledPin, (val == 0xFF00)); + digitalWrite(ledPin, COIL_BOOL(val)); return val; } @@ -47,15 +44,10 @@ bool cbConn(IPAddress ip) { void setup() { #ifdef ESP8266 Serial.begin(74880); -<<<<<<< HEAD:examples/Callback/Callback.ino #else Serial.begin(115200); #endif WiFi.begin("SID", "PASSWORD"); -======= - - WiFi.begin("EW", "iMpress6264"); ->>>>>>> std-list:examples/TestCallback/TestCallback.ino while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -73,19 +65,10 @@ void setup() { pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set -<<<<<<< HEAD:examples/Callback/Callback.ino -======= - //test.push_front({10,10,nullptr,nullptr,nullptr}); - //Serial.println(test.begin()->address); ->>>>>>> std-list:examples/TestCallback/TestCallback.ino } void loop() { //Call once inside loop() - all magic here mb.task(); -<<<<<<< HEAD:examples/Callback/Callback.ino delay(100); -======= - yield(); ->>>>>>> std-list:examples/TestCallback/TestCallback.ino } diff --git a/src/ModbusSerial.cpp b/src/ModbusSerial.cpp index 360d3eb..9512292 100644 --- a/src/ModbusSerial.cpp +++ b/src/ModbusSerial.cpp @@ -38,7 +38,7 @@ uint8_t ModbusSerial::getSlaveId() { bool ModbusSerial::config(SoftwareSerial* port, uint32_t baud, int16_t txPin) { (*port).begin(baud); #else -bool ModbusSerial::config(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { +bool ModbusSerial::config(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { (*port).begin(baud, format); #endif _port = port; @@ -80,9 +80,9 @@ bool ModbusSerial::receive(uint8_t* frame) { //PDU starts after first uint8_t //framesize PDU = framesize - address(1) - crc(2) - receivePDU(frame+1); + slavePDU(frame+1); //No reply to Broadcasts - if (address == 0xFF) _reply = MB_REPLY_OFF; + if (address == 0xFF) _reply = Modbus::REPLY_OFF; return true; } @@ -149,10 +149,10 @@ void ModbusSerial::task() { for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); if (receive(_frame)) { - if (_reply == MB_REPLY_NORMAL) + if (_reply == Modbus::REPLY_NORMAL) sendPDU(_frame); else - if (_reply == MB_REPLY_ECHO) + if (_reply == Modbus::REPLY_ECHO) send(_frame); } diff --git a/src/ModbusSerial.h b/src/ModbusSerial.h index 4ab616e..413059a 100644 --- a/src/ModbusSerial.h +++ b/src/ModbusSerial.h @@ -31,7 +31,7 @@ class ModbusSerial : public Modbus { #ifdef MB_SOFTWARE_SERIAL bool config(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); #else - bool config(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); + bool config(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); #endif void task(); bool receive(uint8_t* frame); From 22ffd6eb07e95f3f44a4d352ba90afeb8be61da4 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 16 Jul 2018 09:22:38 +0500 Subject: [PATCH 085/288] Experimental: ModbusSerial (slave) --- .gitignore | 2 +- src/ModbusSerial.cpp | 8 ++------ src/ModbusSerial.h | 7 ++----- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index aa85607..73d9cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .vscode/arduino.json -.vscode/c_cpp_properties.json +.vscode/c_cpp_properties.json \ No newline at end of file diff --git a/src/ModbusSerial.cpp b/src/ModbusSerial.cpp index 9512292..e8066ac 100644 --- a/src/ModbusSerial.cpp +++ b/src/ModbusSerial.cpp @@ -21,10 +21,6 @@ uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } -ModbusSerial::ModbusSerial() { - -} - bool ModbusSerial::setSlaveId(uint8_t slaveId){ _slaveId = slaveId; return true; @@ -35,10 +31,10 @@ uint8_t ModbusSerial::getSlaveId() { } #ifdef MB_SOFTWARE_SERIAL -bool ModbusSerial::config(SoftwareSerial* port, uint32_t baud, int16_t txPin) { +bool ModbusSerial::slave(SoftwareSerial* port, uint32_t baud, int16_t txPin) { (*port).begin(baud); #else -bool ModbusSerial::config(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { +bool ModbusSerial::slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { (*port).begin(baud, format); #endif _port = port; diff --git a/src/ModbusSerial.h b/src/ModbusSerial.h index 413059a..64bb02d 100644 --- a/src/ModbusSerial.h +++ b/src/ModbusSerial.h @@ -18,20 +18,17 @@ uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); class ModbusSerial : public Modbus { private: Stream* _port; - uint32_t _baud; - u_int _format; int _txPin; unsigned int _t15; // inter character time out unsigned int _t35; // frame delay uint8_t _slaveId; public: - ModbusSerial(); bool setSlaveId(uint8_t slaveId); uint8_t getSlaveId(); #ifdef MB_SOFTWARE_SERIAL - bool config(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); + bool slave(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); #else - bool config(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); + bool slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); #endif void task(); bool receive(uint8_t* frame); From abc75a939fb522b84ab6358303ea54d4b6369525 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 23 Jul 2018 18:05:19 +0500 Subject: [PATCH 086/288] Add destructors for Modbus and TTransaction --- src/Modbus.h | 4 ++++ src/ModbusIP_ESP8266.cpp | 2 +- src/ModbusIP_ESP8266.h | 10 ++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index 736b84a..bc1fe32 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -213,4 +213,8 @@ class Modbus { bool onGet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); + + ~Modbus() { + free(_frame); + } }; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 63e5935..3973089 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -183,7 +183,7 @@ bool ifExpired(TTransaction& t) { if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { if (t.cb) t.cb(Modbus::EX_TIMEOUT, &t); - free(t._frame); + //free(t._frame); return true; } return false; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 5f3e3e3..06639b3 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -35,11 +35,13 @@ typedef struct TTransaction { uint16_t transactionId; uint32_t timestamp; cbTransaction cb = nullptr; - uint8_t* _frame; - bool operator ==(const TTransaction &obj) const - { + uint8_t* _frame = nullptr; + bool operator ==(const TTransaction &obj) const { return transactionId == obj.transactionId; - } + } + ~TTransaction() { + free(_frame); + } }; class ModbusIP : public Modbus { From 863bf3d02c9bb5b3c72087eb6ab956f48bba5eb0 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 7 Sep 2018 00:18:31 +0500 Subject: [PATCH 087/288] Bugfix and API change and expansion Fixed crash at transaction timeout Change transaction callback function arguments Prepare to add readCoil/Ists/Ireg/Hreg Add writeHreg for multiple registers write pull/push/read/write* methods returns transaction id or 0 on error Implement removeCoil/etc methods remove lastTransaction method --- API.md | 48 +++++++++++++++++---------- README.md | 8 ++--- src/Modbus.cpp | 20 ++++++++--- src/Modbus.h | 19 ++++++++--- src/ModbusIP_ESP8266.cpp | 71 ++++++++++++++++++---------------------- src/ModbusIP_ESP8266.h | 32 ++++++++++-------- 6 files changed, 113 insertions(+), 85 deletions(-) diff --git a/API.md b/API.md index 4147392..3501bd8 100644 --- a/API.md +++ b/API.md @@ -31,20 +31,29 @@ bool Ists(uint16_t offset); uint16_t Ireg(uint16_t offset); ``` +### Remove reg + +```c +bool removeHreg(uint16_t offset); +bool removeCoil(uint16_t offset); +bool removeIsts(uint16_t offset); +bool removeIreg(uint16_t offset); +``` + ### Query [multiple] regs from remote slave ```c -bool pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -bool pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -bool pullIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); +uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); ``` ### Send [multiple] regs to remote slave ```c -bool pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. @@ -52,11 +61,17 @@ Write Register/Coil or Write Multiple Registers/Coils Modbus function selected a ### Write value to remote slave reg ```c -bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); -bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +``` + +Write values to remote Hreg/Coil. Offset is 0..65535. + +```c +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` -Write value to remote Hreg/Coil. Offset is 0..65535. +Write multiple values from array to remote Hreg. Offset is 0..65535. ### Callbacks @@ -68,32 +83,32 @@ void cbDisable(); Callback generation control. Callback generation is enabled by default. *Has no effect on transactions callbacks.* ```c -void onConnect(cbModbusConnect cb) -void onDisonnect(cbModbusConnect cb) +void onConnect(cbModbusConnect cb); +void onDisonnect(cbModbusConnect cb); ``` Assign callback function on new incoming connection event. ```c -typedef bool (*cbModbusConnect)(IPAddress ip) +typedef bool (*cbModbusConnect)(IPAddress ip); ``` Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. ```c -typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val) +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); ``` Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. ```c -typedef uint16_t (*cbTransaction)(uint8_t eventCode, TTransaction* transaction) +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); ``` -Transaction end callback function definition. Pointer to TTransaction structure (see source for details) is passed. +Transaction end callback function definition. *data* is currently reserved. ```c -IPAddress eventSource() +IPAddress eventSource(); ``` Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. @@ -147,7 +162,6 @@ void slave(); void master(); bool connect(IPAddress ip); bool disconnect(IPAddress ip); // Not implemented yet. -uint16_t lastTransaction(); bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); ``` diff --git a/README.md b/README.md index 3783bb8..51b9bc0 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,13 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + Back to marking private for onSet, onGet, addReg and Reg methods + Added callback-related eventSource method, onDisconnect and transaction result callbacks + Extend register addressing to 0..65535 ++ removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) // ToDo - Move rest of implementations from Modbus.h - ModbusSerial (over RS-485) -- Read/Write file records function -- Write mask register function -- Serial line-specific functions -- removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) +- Modbus Read/Write File Records function +- Modbus Write Mask Register function +- Modbus Serial line-specific functions ``` ## Contributions diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 4ff3b66..1ea8b8a 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -60,7 +60,13 @@ uint16_t Modbus::Reg(TAddress address) { } bool Modbus::removeReg(TAddress address) { - // Empty stub + TRegister* reg = searchRegister(address); + if (reg) { + _regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() ); + return true; + } + return false; + } void Modbus::slavePDU(uint8_t* frame) { @@ -327,7 +333,7 @@ bool Modbus::readSlave(TAddress address, uint16_t numregs, FunctionCode fn) { return true; } -bool Modbus::writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::writeSlaveBits(TAddress startreg, 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. @@ -348,7 +354,7 @@ bool Modbus::writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn return false; } -bool Modbus::writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn, uint16_t* data) { free(_frame); _len = 6 + 2 * numregs; _frame = (uint8_t*) malloc(_len); @@ -359,14 +365,18 @@ bool Modbus::writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode f _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; - getMultipleWords(_frame + 6, startreg, numregs); + if (data) { + memcpy(_frame + 6, data, numregs * 2); + } else { + getMultipleWords(_frame + 6, startreg, numregs); + } return true; } _reply = REPLY_OFF; return false; } -void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame) { +void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint8_t* output) { uint8_t fcode = frame[0]; _reply = 0; if ((fcode & 0x80) != 0) { diff --git a/src/Modbus.h b/src/Modbus.h index bc1fe32..d1a83d1 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -139,7 +139,16 @@ class Modbus { uint16_t Ireg(uint16_t offset) { return Reg(IREG(offset)); } - + bool removeCoil(uint16_t offset) { + return removeReg(COIL(offset)); + } + bool removeIsts(uint16_t offset) { + return removeReg(ISTS(offset)); + } + bool removeIreg(uint16_t offset) { + return removeReg(IREG(offset)); + } + void cbEnable(bool state = true); void cbDisable() { cbEnable(false); @@ -200,16 +209,16 @@ class Modbus { void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave - void masterPDU(uint8_t* frame, uint8_t* sourceFrame); //For Master + void masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint8_t* output= nullptr); //For Master bool readSlave(TAddress address, uint16_t numregs, FunctionCode fn); - bool writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn); - bool writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn); + bool writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn, bool* data = nullptr); + bool writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn, uint16_t* data = nullptr); 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); // Not implemented + bool removeReg(TAddress address); bool onGet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 3973089..5fa1a9b 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -109,12 +109,12 @@ void ModbusIP::task() { if (trans) { // if valid transaction id if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested // Procass incoming frame as master - masterPDU(_frame, trans->_frame); + masterPDU(_frame, trans->_frame, trans->data); } else { _reply = EX_UNEXPECTED_RESPONSE; } if (cbEnabled && trans->cb) { - trans->cb((ResultCode)_reply, trans); + trans->cb((ResultCode)_reply, trans->transactionId, nullptr); } free(trans->_frame); std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); @@ -141,7 +141,7 @@ void ModbusIP::task() { n = -1; } -bool ModbusIP::send(IPAddress ip, cbTransaction cb) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data +uint16_t ModbusIP::send(IPAddress ip, cbTransaction cb, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data #ifdef MODBUSIP_MAX_TRANSACIONS if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; @@ -150,6 +150,7 @@ bool ModbusIP::send(IPAddress ip, cbTransaction cb) { // Prepare and send Modbus if (p == -1 || !client[p]->connected()) return false; transactionId++; + if (!transactionId) transactionId = 1; _MBAP.transactionId = __bswap_16(transactionId); _MBAP.protocolId = __bswap_16(0); _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP @@ -164,10 +165,11 @@ bool ModbusIP::send(IPAddress ip, cbTransaction cb) { // Prepare and send Modbus tmp.transactionId = transactionId; tmp.timestamp = millis(); tmp.cb = cb; + tmp.data = data; tmp._frame = _frame; _trans.push_back(tmp); _frame = nullptr; - return true; + return transactionId; } @@ -182,8 +184,8 @@ void ModbusIP::onDisconnect(cbModbusConnect cb) { bool ifExpired(TTransaction& t) { if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { if (t.cb) - t.cb(Modbus::EX_TIMEOUT, &t); - //free(t._frame); + t.cb(Modbus::EX_TIMEOUT, t.transactionId, nullptr); + free(t._frame); return true; } return false; @@ -198,15 +200,7 @@ void ModbusIP::cleanup() { // Free clients if not connected and remove timedout cbDisconnect(ip); } } -// for (TTransaction& t : _trans) { // Cleanup transactions on timeout -// if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { -// if (cbEnabled && t.cb) -// t.cb(EX_TIMEOUT, &t); -// //std::vector::iterator it = std::find(_trans.begin(), _trans.end(), t); -// //if (it) _trans.erase(it); -// } - _trans.erase( remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); -// } + _trans.erase( remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); } int8_t ModbusIP::getFreeClient() { // Returns free slot position @@ -224,19 +218,24 @@ int8_t ModbusIP::getSlave(IPAddress ip) { return -1; } -bool ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { +uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); return send(ip, cb); - } +} -bool ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { +uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { readSlave(HREG(offset), value, FC_WRITE_REG); return send(ip, cb); } -bool ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; +uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS, value); + return send(ip, cb); +} + +uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? if (numregs == 1) { readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); @@ -246,25 +245,22 @@ bool ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans return send(ip, cb); } -bool ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; +uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; addCoil(offset, numregs); // Should registers requre to be added there or use existing? readSlave(COIL(offset), numregs, FC_READ_COILS); return send(ip, cb); } -bool ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; +uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; addIsts(offset, numregs); // Should registers requre to be added there or use existing? readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); return send(ip, cb); } -bool ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; +uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? if (numregs == 1) { readSlave(HREG(offset), Hreg(offset), FC_WRITE_REG); @@ -274,27 +270,22 @@ bool ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTrans return send(ip, cb); } -bool ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; +uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; addHreg(offset, numregs); // Should registers requre to be added there or use existing? readSlave(HREG(offset), numregs, FC_READ_REGS); return send(ip, cb); } -bool ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) - return false; +uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; addIreg(offset, numregs); // Should registers requre to be added there or use existing? readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); return send(ip, cb); } -uint16_t ModbusIP::lastTransaction() { - return transactionId; -} bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) - searchTransaction(id) != nullptr; + return searchTransaction(id) != nullptr; } bool ModbusIP::isConnected(IPAddress ip) { int8_t p = getSlave(ip); diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 06639b3..626ff5e 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -29,19 +29,17 @@ typedef bool (*cbModbusConnect)(IPAddress ip); typedef struct TTransaction TTransaction; -typedef bool (*cbTransaction)(Modbus::ResultCode event, TTransaction* t); +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); typedef struct TTransaction { uint16_t transactionId; uint32_t timestamp; cbTransaction cb = nullptr; uint8_t* _frame = nullptr; + void* data = nullptr; bool operator ==(const TTransaction &obj) const { return transactionId == obj.transactionId; } - ~TTransaction() { - free(_frame); - } }; class ModbusIP : public Modbus { @@ -68,11 +66,11 @@ class ModbusIP : public Modbus { void cleanup(); // Free clients if not connected and remove timedout transactions int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); - bool send(IPAddress ip, cbTransaction cb); + uint16_t send(IPAddress ip, cbTransaction cb, void* data = nullptr); public: ModbusIP(); - uint16_t lastTransaction(); + //uint16_t lastTransaction(); bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); bool connect(IPAddress ip); @@ -86,12 +84,18 @@ class ModbusIP : public Modbus { void onDisconnect(cbModbusConnect cb = nullptr); IPAddress eventSource(); - bool writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); - bool writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); - bool pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - bool pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - bool pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - bool pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - bool pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - bool pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); + uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +// uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +// uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +// uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +// uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +// uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); }; \ No newline at end of file From c94944493f9e81040343cbb88e52235e29e4f8b8 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 7 Sep 2018 00:50:29 +0500 Subject: [PATCH 088/288] Remove ModbusSerial.* --- src/ModbusSerial.cpp | 157 ------------------------------------------- src/ModbusSerial.h | 79 ---------------------- 2 files changed, 236 deletions(-) delete mode 100644 src/ModbusSerial.cpp delete mode 100644 src/ModbusSerial.h diff --git a/src/ModbusSerial.cpp b/src/ModbusSerial.cpp deleted file mode 100644 index e8066ac..0000000 --- a/src/ModbusSerial.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - ModbusSerial.cpp - Source for Modbus Serial Library - Copyright (C) 2014 André Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#include "ModbusSerial.h" - -uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { - uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; - - Index = CRCHi ^ address; - CRCHi = CRCLo ^ _auchCRCHi[Index]; - CRCLo = _auchCRCLo[Index]; - - while (pduLen--) { - Index = CRCHi ^ *pduFrame++; - CRCHi = CRCLo ^ _auchCRCHi[Index]; - CRCLo = _auchCRCLo[Index]; - } - - return (CRCHi << 8) | CRCLo; -} - -bool ModbusSerial::setSlaveId(uint8_t slaveId){ - _slaveId = slaveId; - return true; -} - -uint8_t ModbusSerial::getSlaveId() { - return _slaveId; -} - -#ifdef MB_SOFTWARE_SERIAL -bool ModbusSerial::slave(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - (*port).begin(baud); -#else -bool ModbusSerial::slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { - (*port).begin(baud, format); -#endif - _port = port; - _txPin = txPin; - - delay(2000); // ??? - - if (txPin >= 0) { - pinMode(txPin, OUTPUT); - digitalWrite(txPin, LOW); - } - - if (baud > 19200) { - _t15 = 750; - _t35 = 1750; - } else { - _t15 = 15000000/baud; // 1T * 1.5 = T1.5 - _t35 = 35000000/baud; // 1T * 3.5 = T3.5 - } - - return true; -} - -bool ModbusSerial::receive(uint8_t* frame) { - //first byte of frame = address - uint8_t address = frame[0]; - //Last two byts = crc - u_int crc = ((frame[_len - 2] << 8) | frame[_len - 1]); - - //Slave Check - if (address != 0xFF && address != getSlaveId()) { - return false; - } - - //CRC Check - if (crc != calcCrc(_frame[0], _frame+1, _len-3)) { - return false; - } - - //PDU starts after first uint8_t - //framesize PDU = framesize - address(1) - crc(2) - slavePDU(frame+1); - //No reply to Broadcasts - if (address == 0xFF) _reply = Modbus::REPLY_OFF; - return true; -} - -bool ModbusSerial::send(uint8_t* frame) { - uint8_t i; - - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - - for (i = 0 ; i < _len ; i++) { - (*_port).write(frame[i]); - } - - (*_port).flush(); - delayMicroseconds(_t35); - - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); - } -} - -bool ModbusSerial::sendPDU(uint8_t* pduframe) { - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - - //Send slaveId - (*_port).write(_slaveId); - - //Send PDU - uint8_t i; - for (i = 0 ; i < _len ; i++) { - (*_port).write(pduframe[i]); - } - - //Send CRC - uint16_t crc = calcCrc(_slaveId, _frame, _len); - (*_port).write(crc >> 8); - (*_port).write(crc & 0xFF); - - (*_port).flush(); - delayMicroseconds(_t35); - - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); - } -} - -void ModbusSerial::task() { - _len = 0; - - while ((*_port).available() > _len) { - _len = (*_port).available(); - delayMicroseconds(_t15); - } - - if (_len == 0) return; - - uint8_t i; - _frame = (uint8_t*) malloc(_len); - for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); - - if (receive(_frame)) { - if (_reply == Modbus::REPLY_NORMAL) - sendPDU(_frame); - else - if (_reply == Modbus::REPLY_ECHO) - send(_frame); - } - - free(_frame); - _len = 0; -} \ No newline at end of file diff --git a/src/ModbusSerial.h b/src/ModbusSerial.h deleted file mode 100644 index 64bb02d..0000000 --- a/src/ModbusSerial.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - ModbusSerial.h - Header for ModbusSerial Library - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#pragma once - -#include - -//#define MB_SOFTWARE_SERIAL - -#ifdef MB_SOFTWARE_SERIAL -#include -#endif - -uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); - -class ModbusSerial : public Modbus { - private: - Stream* _port; - int _txPin; - unsigned int _t15; // inter character time out - unsigned int _t35; // frame delay - uint8_t _slaveId; - public: - bool setSlaveId(uint8_t slaveId); - uint8_t getSlaveId(); - #ifdef MB_SOFTWARE_SERIAL - bool slave(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); - #else - bool slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); - #endif - void task(); - bool receive(uint8_t* frame); - bool sendPDU(uint8_t* pduframe); - bool send(uint8_t* frame); -}; - -/* Table of CRC values for high�order byte */ -const uint8_t _auchCRCHi[] = { - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, - 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40}; - -/* Table of CRC values for low�order byte */ -const uint8_t _auchCRCLo[] = { - 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, - 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, - 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, - 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, - 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, - 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, - 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, - 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, - 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, - 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, - 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, - 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, - 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, - 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, - 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, - 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, - 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, - 0x40}; \ No newline at end of file From 6b604d6fe81d62f9826d3715c76c73ee432b919f Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 7 Sep 2018 13:16:44 +0500 Subject: [PATCH 089/288] Implement remote slave read/wirte for all register's types --- API.md | 22 ++++-- README.md | 8 +- keywords.txt | 5 +- src/Modbus.cpp | 153 +++++++++++++++++++++++++++++++++++--- src/Modbus.h | 111 ++++++++------------------- src/ModbusIP_ESP8266.cpp | 39 +++++++++- src/ModbusIP_ESP8266.h | 13 ++-- src/ModbusSerial.cpp | 157 +++++++++++++++++++++++++++++++++++++++ src/ModbusSerial.h | 79 ++++++++++++++++++++ 9 files changed, 478 insertions(+), 109 deletions(-) create mode 100644 src/ModbusSerial.cpp create mode 100644 src/ModbusSerial.h diff --git a/API.md b/API.md index 3501bd8..8d77af9 100644 --- a/API.md +++ b/API.md @@ -11,8 +11,6 @@ bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1); ``` -Offset is 0..65535 - ### Write reg ```c @@ -49,6 +47,8 @@ uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransac uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); ``` +Result is saved to local registers. Method returns corresponding transaction id. + ### Send [multiple] regs to remote slave ```c @@ -58,20 +58,32 @@ uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransac Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. -### Write value to remote slave reg +### Write [multiple] values to remote slave reg ```c uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); ``` -Write values to remote Hreg/Coil. Offset is 0..65535. +Write single value to remote Hreg/Coil. ```c +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` -Write multiple values from array to remote Hreg. Offset is 0..65535. +Write multiple values from array to remote Coil/Hreg. + +### Read values from multiple remote slave regs + +```c + uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +``` + +Read values from remote Hreg/Coil/Ireg/Ists to array. ### Callbacks diff --git a/README.md b/README.md index 51b9bc0..fd7aa88 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 +# Modbus Master-Slave Library for ESP8266/ESP32 v2.0 -# v2.0.BETA +# Status: Release Candidate *Be careful using current version in production. Stable version from 'releases' page is recommended.* @@ -9,7 +9,6 @@ used in industrial automation and can be used in other areas, such as home autom The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). - In the current version the library allows the ESP8266/ESP32 operate async as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: http://pt.wikipedia.org/wiki/Modbus @@ -59,6 +58,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + Modbus class refactoring + ModbusIP networking code refactoring and error reporting + Global registers storage to share between multiple Modbus* instances ++ Move rest of implementations from Modbus.h //Public API changes + Modbus master implementation + Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL @@ -66,8 +66,8 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + Added callback-related eventSource method, onDisconnect and transaction result callbacks + Extend register addressing to 0..65535 + removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) ++ readCoil, readHreg, readIsts, readIreg // ToDo -- Move rest of implementations from Modbus.h - ModbusSerial (over RS-485) - Modbus Read/Write File Records function - Modbus Write Mask Register function diff --git a/keywords.txt b/keywords.txt index 94df304..147d3c3 100644 --- a/keywords.txt +++ b/keywords.txt @@ -31,11 +31,14 @@ Coil KEYWORD2 Ists KEYWORD2 Ireg KEYWORD2 Hreg KEYWORD2 -lastTransaction KEYWORD2 isTransaction KEYWORD2 isConnected KEYWORD2 writeCoil KEYWORD2 +readCoil KEYWORD2 writeHreg KEYWORD2 +readHreg KEYWORD2 +readIsts KEYWORD2 +readIreg KEYWORD2 pushCoil KEYWORD2 pullCoil KEYWORD2 pullIsts KEYWORD2 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 1ea8b8a..a647ea2 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -232,7 +232,7 @@ void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { //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. + if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse(fn, EX_SLAVE_FAILURE); @@ -336,7 +336,7 @@ bool Modbus::readSlave(TAddress address, uint16_t numregs, FunctionCode fn) { bool Modbus::writeSlaveBits(TAddress startreg, 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. + if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. _frame = (uint8_t*) malloc(_len); if (_frame) { _frame[0] = fn; @@ -346,7 +346,11 @@ bool Modbus::writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; _frame[_len - 1] = 0; //Clean last probably partial byte - getMultipleBits(_frame + 6, startreg, numregs); + if (data) { + bitsToBool(data, _frame + 6, numregs); + } else { + getMultipleBits(_frame + 6, startreg, numregs); + } _reply = REPLY_NORMAL; return true; } @@ -376,7 +380,40 @@ bool Modbus::writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode f return false; } -void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint8_t* output) { +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, void* output) { uint8_t fcode = frame[0]; _reply = 0; if ((fcode & 0x80) != 0) { @@ -393,27 +430,39 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint8_t* output) { _reply = EX_DATA_MISMACH; break; } - setMultipleWords(frame + 2, HREG(field1), field2); + if (output) { + memcpy(output, frame + 2, 2 * field2); + } else { + setMultipleWords(frame + 2, HREG(field1), field2); + } break; case FC_READ_COILS: //field1 = startreg, field2 = numregs, frame[1] = data length, header len = 2 bytecount_calc = field2 / 8; - if (field2%8) bytecount_calc++; + if (field2 % 8) bytecount_calc++; if (frame[1] != bytecount_calc) { // check if data size matches _reply = EX_DATA_MISMACH; break; } - setMultipleBits(frame + 2, COIL(field1), field2); + if (output) { + bitsToBool((bool*)output, frame + 2, field2); + } else { + setMultipleBits(frame + 2, COIL(field1), field2); + } break; case FC_READ_INPUT_STAT: //field1 = startreg, field2 = numregs, frame[1] = data length, header len = 2 bytecount_calc = field2 / 8; - if (field2%8) bytecount_calc++; + if (field2 % 8) bytecount_calc++; if (frame[1] != bytecount_calc) { // check if data size matches _reply = EX_DATA_MISMACH; break; } - setMultipleBits(frame + 2, ISTS(field1), field2); + if (output) { + bitsToBool((bool*)output, frame + 2, field2); + } else { + setMultipleBits(frame + 2, ISTS(field1), field2); + } break; case FC_READ_INPUT_REGS: //field1 = startreg, field2 = status, frame[1] = data lenght, header len = 2 @@ -421,7 +470,11 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint8_t* output) { _reply = EX_DATA_MISMACH; break; } - setMultipleWords(frame + 2, IREG(field1), field2); + if (output) { + memcpy(output, frame + 2, 2 * field2); + } else { + setMultipleWords(frame + 2, IREG(field1), field2); + } break; case FC_WRITE_REG: break; @@ -438,4 +491,84 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint8_t* output) { void Modbus::cbEnable(bool state) { cbEnabled = state; +} +void Modbus::cbDisable() { + cbEnable(false); +} + +bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return addReg(HREG(offset), value, numregs); +} +bool Modbus::Hreg(uint16_t offset, uint16_t value) { + return Reg(HREG(offset), value); +} +uint16_t Modbus::Hreg(uint16_t offset) { + return Reg(HREG(offset)); +} +uint16_t Modbus::removeHreg(uint16_t offset) { + return removeReg(HREG(offset)); +} +bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { + return addReg(COIL(offset), COIL_VAL(value), numregs); +} +bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { + return addReg(ISTS(offset), ISTS_VAL(value), numregs); +} +bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return addReg(IREG(offset), value, numregs); +} +bool Modbus::Coil(uint16_t offset, bool value) { + return Reg(COIL(offset), COIL_VAL(value)); +} +bool Modbus::Ists(uint16_t offset, bool value) { + return Reg(ISTS(offset), ISTS_VAL(value)); +} +bool Modbus::Ireg(uint16_t offset, uint16_t value) { + return Reg(IREG(offset), value); +} +bool Modbus::Coil(uint16_t offset) { + return COIL_BOOL(Reg(COIL(offset))); +} +bool Modbus::Ists(uint16_t offset) { + return ISTS_BOOL(Reg(ISTS(offset))); +} +uint16_t Modbus::Ireg(uint16_t offset) { + return Reg(IREG(offset)); +} +bool Modbus::removeCoil(uint16_t offset) { + return removeReg(COIL(offset)); +} +bool Modbus::removeIsts(uint16_t offset) { + return removeReg(ISTS(offset)); +} +bool Modbus::removeIreg(uint16_t offset) { + return removeReg(IREG(offset)); +} +bool Modbus::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onGet(COIL(offset), cb, numregs); +} +bool Modbus::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onSet(COIL(offset), cb, numregs); +} +bool Modbus::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onGet(HREG(offset), cb, numregs); +} +bool Modbus::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onSet(HREG(offset), cb, numregs); +} +bool Modbus::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onGet(ISTS(offset), cb, numregs); +} +bool Modbus::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onSet(ISTS(offset), cb, numregs); +} +bool Modbus::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onGet(IREG(offset), cb, numregs); +} +bool Modbus::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return onSet(IREG(offset), cb, numregs); +} + +Modbus::~Modbus() { + free(_frame); } \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index d1a83d1..0136e20 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -99,85 +99,35 @@ class Modbus { EX_TIMEOUT = 0xE4, // Custom. Operation not finished within reasonable time EX_CONNECTION_LOST = 0xE5 // Custom. Connection with device lost }; - - bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { - return addReg(HREG(offset), value, numregs); - } - bool Hreg(uint16_t offset, uint16_t value) { - return Reg(HREG(offset), value); - } - uint16_t Hreg(uint16_t offset) { - return Reg(HREG(offset)); - } - uint16_t removeHreg(uint16_t offset) { - return removeReg(HREG(offset)); - } - bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1) { - return addReg(COIL(offset), COIL_VAL(value), numregs); - } - bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1) { - return addReg(ISTS(offset), ISTS_VAL(value), numregs); - } - bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1) { - return addReg(IREG(offset), value, numregs); - } - bool Coil(uint16_t offset, bool value) { - return Reg(COIL(offset), COIL_VAL(value)); - } - bool Ists(uint16_t offset, bool value) { - return Reg(ISTS(offset), ISTS_VAL(value)); - } - bool Ireg(uint16_t offset, uint16_t value) { - return Reg(IREG(offset), value); - } - bool Coil(uint16_t offset) { - return COIL_BOOL(Reg(COIL(offset))); - } - bool Ists(uint16_t offset) { - return ISTS_BOOL(Reg(ISTS(offset))); - } - uint16_t Ireg(uint16_t offset) { - return Reg(IREG(offset)); - } - bool removeCoil(uint16_t offset) { - return removeReg(COIL(offset)); - } - bool removeIsts(uint16_t offset) { - return removeReg(ISTS(offset)); - } - bool removeIreg(uint16_t offset) { - return removeReg(IREG(offset)); - } + ~Modbus(); + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); + bool Hreg(uint16_t offset, uint16_t value); + uint16_t Hreg(uint16_t offset); + uint16_t removeHreg(uint16_t offset); + 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 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); + bool removeCoil(uint16_t offset); + bool removeIsts(uint16_t offset); + bool removeIreg(uint16_t offset); void cbEnable(bool state = true); - void cbDisable() { - cbEnable(false); - } + void cbDisable(); - bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onGet(COIL(offset), cb, numregs); - } - bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(COIL(offset), cb, numregs); - } - bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onGet(HREG(offset), cb, numregs); - } - bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(HREG(offset), cb, numregs); - } - bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onGet(ISTS(offset), cb, numregs); - } - bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(ISTS(offset), cb, numregs); - } - bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onGet(IREG(offset), cb, numregs); - } - bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1) { - return onSet(IREG(offset), cb, numregs); - } + bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); + bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); private: void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); @@ -188,6 +138,9 @@ class Modbus { void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); void getMultipleWords(uint8_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); + TRegister* searchRegister(TAddress addr); protected: @@ -209,7 +162,7 @@ class Modbus { void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave - void masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint8_t* output= nullptr); //For Master + void masterPDU(uint8_t* frame, uint8_t* sourceFrame, void* output= nullptr); //For Master bool readSlave(TAddress address, uint16_t numregs, FunctionCode fn); bool writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn, bool* data = nullptr); @@ -222,8 +175,4 @@ class Modbus { bool onGet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); - - ~Modbus() { - free(_frame); - } }; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 5fa1a9b..f3ae410 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -218,11 +218,30 @@ int8_t ModbusIP::getSlave(IPAddress ip) { return -1; } +int8_t ModbusIP::getMaster(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == MODBUSIP_PORT) + return i; + return -1; +} + uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); return send(ip, cb); } +uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS, value); + return send(ip, cb); +} + +uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(COIL(offset), numregs, FC_READ_COILS); + return send(ip, cb, value); +} + uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { readSlave(HREG(offset), value, FC_WRITE_REG); return send(ip, cb); @@ -234,9 +253,27 @@ uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uin return send(ip, cb); } +uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(HREG(offset), numregs, FC_READ_REGS); + return send(ip, cb, value); +} + +uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); + return send(ip, cb, value); +} + +uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); + return send(ip, cb, value); +} + uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - //addCoil(offset, numregs); // Should registers requre to be added there or use existing? + //addCoil(offset, numregs); // Should registers requre to be added there or use only existing? if (numregs == 1) { readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); } else { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 626ff5e..0181781 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -27,8 +27,6 @@ // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); -typedef struct TTransaction TTransaction; - typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); typedef struct TTransaction { @@ -66,6 +64,7 @@ class ModbusIP : public Modbus { void cleanup(); // Free clients if not connected and remove timedout transactions int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); + int8_t getMaster(IPAddress ip); uint16_t send(IPAddress ip, cbTransaction cb, void* data = nullptr); public: @@ -86,12 +85,12 @@ class ModbusIP : public Modbus { uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); -// uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); diff --git a/src/ModbusSerial.cpp b/src/ModbusSerial.cpp new file mode 100644 index 0000000..e8066ac --- /dev/null +++ b/src/ModbusSerial.cpp @@ -0,0 +1,157 @@ +/* + ModbusSerial.cpp - Source for Modbus Serial Library + Copyright (C) 2014 André Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#include "ModbusSerial.h" + +uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { + uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; + + Index = CRCHi ^ address; + CRCHi = CRCLo ^ _auchCRCHi[Index]; + CRCLo = _auchCRCLo[Index]; + + while (pduLen--) { + Index = CRCHi ^ *pduFrame++; + CRCHi = CRCLo ^ _auchCRCHi[Index]; + CRCLo = _auchCRCLo[Index]; + } + + return (CRCHi << 8) | CRCLo; +} + +bool ModbusSerial::setSlaveId(uint8_t slaveId){ + _slaveId = slaveId; + return true; +} + +uint8_t ModbusSerial::getSlaveId() { + return _slaveId; +} + +#ifdef MB_SOFTWARE_SERIAL +bool ModbusSerial::slave(SoftwareSerial* port, uint32_t baud, int16_t txPin) { + (*port).begin(baud); +#else +bool ModbusSerial::slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { + (*port).begin(baud, format); +#endif + _port = port; + _txPin = txPin; + + delay(2000); // ??? + + if (txPin >= 0) { + pinMode(txPin, OUTPUT); + digitalWrite(txPin, LOW); + } + + if (baud > 19200) { + _t15 = 750; + _t35 = 1750; + } else { + _t15 = 15000000/baud; // 1T * 1.5 = T1.5 + _t35 = 35000000/baud; // 1T * 3.5 = T3.5 + } + + return true; +} + +bool ModbusSerial::receive(uint8_t* frame) { + //first byte of frame = address + uint8_t address = frame[0]; + //Last two byts = crc + u_int crc = ((frame[_len - 2] << 8) | frame[_len - 1]); + + //Slave Check + if (address != 0xFF && address != getSlaveId()) { + return false; + } + + //CRC Check + if (crc != calcCrc(_frame[0], _frame+1, _len-3)) { + return false; + } + + //PDU starts after first uint8_t + //framesize PDU = framesize - address(1) - crc(2) + slavePDU(frame+1); + //No reply to Broadcasts + if (address == 0xFF) _reply = Modbus::REPLY_OFF; + return true; +} + +bool ModbusSerial::send(uint8_t* frame) { + uint8_t i; + + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + + for (i = 0 ; i < _len ; i++) { + (*_port).write(frame[i]); + } + + (*_port).flush(); + delayMicroseconds(_t35); + + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } +} + +bool ModbusSerial::sendPDU(uint8_t* pduframe) { + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + + //Send slaveId + (*_port).write(_slaveId); + + //Send PDU + uint8_t i; + for (i = 0 ; i < _len ; i++) { + (*_port).write(pduframe[i]); + } + + //Send CRC + uint16_t crc = calcCrc(_slaveId, _frame, _len); + (*_port).write(crc >> 8); + (*_port).write(crc & 0xFF); + + (*_port).flush(); + delayMicroseconds(_t35); + + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } +} + +void ModbusSerial::task() { + _len = 0; + + while ((*_port).available() > _len) { + _len = (*_port).available(); + delayMicroseconds(_t15); + } + + if (_len == 0) return; + + uint8_t i; + _frame = (uint8_t*) malloc(_len); + for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); + + if (receive(_frame)) { + if (_reply == Modbus::REPLY_NORMAL) + sendPDU(_frame); + else + if (_reply == Modbus::REPLY_ECHO) + send(_frame); + } + + free(_frame); + _len = 0; +} \ No newline at end of file diff --git a/src/ModbusSerial.h b/src/ModbusSerial.h new file mode 100644 index 0000000..64bb02d --- /dev/null +++ b/src/ModbusSerial.h @@ -0,0 +1,79 @@ +/* + ModbusSerial.h - Header for ModbusSerial Library + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once + +#include + +//#define MB_SOFTWARE_SERIAL + +#ifdef MB_SOFTWARE_SERIAL +#include +#endif + +uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); + +class ModbusSerial : public Modbus { + private: + Stream* _port; + int _txPin; + unsigned int _t15; // inter character time out + unsigned int _t35; // frame delay + uint8_t _slaveId; + public: + bool setSlaveId(uint8_t slaveId); + uint8_t getSlaveId(); + #ifdef MB_SOFTWARE_SERIAL + bool slave(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); + #else + bool slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); + #endif + void task(); + bool receive(uint8_t* frame); + bool sendPDU(uint8_t* pduframe); + bool send(uint8_t* frame); +}; + +/* Table of CRC values for high�order byte */ +const uint8_t _auchCRCHi[] = { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40}; + +/* Table of CRC values for low�order byte */ +const uint8_t _auchCRCLo[] = { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, + 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, + 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, + 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, + 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, + 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, + 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, + 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, + 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, + 0x40}; \ No newline at end of file From 7f6d72ce569696a209a417ec8b0c99e13d5434aa Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 7 Sep 2018 15:06:23 +0500 Subject: [PATCH 090/288] Remove ModbusSerial.* --- src/ModbusSerial.cpp | 157 ------------------------------------------- src/ModbusSerial.h | 79 ---------------------- 2 files changed, 236 deletions(-) delete mode 100644 src/ModbusSerial.cpp delete mode 100644 src/ModbusSerial.h diff --git a/src/ModbusSerial.cpp b/src/ModbusSerial.cpp deleted file mode 100644 index e8066ac..0000000 --- a/src/ModbusSerial.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - ModbusSerial.cpp - Source for Modbus Serial Library - Copyright (C) 2014 André Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#include "ModbusSerial.h" - -uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { - uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; - - Index = CRCHi ^ address; - CRCHi = CRCLo ^ _auchCRCHi[Index]; - CRCLo = _auchCRCLo[Index]; - - while (pduLen--) { - Index = CRCHi ^ *pduFrame++; - CRCHi = CRCLo ^ _auchCRCHi[Index]; - CRCLo = _auchCRCLo[Index]; - } - - return (CRCHi << 8) | CRCLo; -} - -bool ModbusSerial::setSlaveId(uint8_t slaveId){ - _slaveId = slaveId; - return true; -} - -uint8_t ModbusSerial::getSlaveId() { - return _slaveId; -} - -#ifdef MB_SOFTWARE_SERIAL -bool ModbusSerial::slave(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - (*port).begin(baud); -#else -bool ModbusSerial::slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { - (*port).begin(baud, format); -#endif - _port = port; - _txPin = txPin; - - delay(2000); // ??? - - if (txPin >= 0) { - pinMode(txPin, OUTPUT); - digitalWrite(txPin, LOW); - } - - if (baud > 19200) { - _t15 = 750; - _t35 = 1750; - } else { - _t15 = 15000000/baud; // 1T * 1.5 = T1.5 - _t35 = 35000000/baud; // 1T * 3.5 = T3.5 - } - - return true; -} - -bool ModbusSerial::receive(uint8_t* frame) { - //first byte of frame = address - uint8_t address = frame[0]; - //Last two byts = crc - u_int crc = ((frame[_len - 2] << 8) | frame[_len - 1]); - - //Slave Check - if (address != 0xFF && address != getSlaveId()) { - return false; - } - - //CRC Check - if (crc != calcCrc(_frame[0], _frame+1, _len-3)) { - return false; - } - - //PDU starts after first uint8_t - //framesize PDU = framesize - address(1) - crc(2) - slavePDU(frame+1); - //No reply to Broadcasts - if (address == 0xFF) _reply = Modbus::REPLY_OFF; - return true; -} - -bool ModbusSerial::send(uint8_t* frame) { - uint8_t i; - - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - - for (i = 0 ; i < _len ; i++) { - (*_port).write(frame[i]); - } - - (*_port).flush(); - delayMicroseconds(_t35); - - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); - } -} - -bool ModbusSerial::sendPDU(uint8_t* pduframe) { - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - - //Send slaveId - (*_port).write(_slaveId); - - //Send PDU - uint8_t i; - for (i = 0 ; i < _len ; i++) { - (*_port).write(pduframe[i]); - } - - //Send CRC - uint16_t crc = calcCrc(_slaveId, _frame, _len); - (*_port).write(crc >> 8); - (*_port).write(crc & 0xFF); - - (*_port).flush(); - delayMicroseconds(_t35); - - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); - } -} - -void ModbusSerial::task() { - _len = 0; - - while ((*_port).available() > _len) { - _len = (*_port).available(); - delayMicroseconds(_t15); - } - - if (_len == 0) return; - - uint8_t i; - _frame = (uint8_t*) malloc(_len); - for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); - - if (receive(_frame)) { - if (_reply == Modbus::REPLY_NORMAL) - sendPDU(_frame); - else - if (_reply == Modbus::REPLY_ECHO) - send(_frame); - } - - free(_frame); - _len = 0; -} \ No newline at end of file diff --git a/src/ModbusSerial.h b/src/ModbusSerial.h deleted file mode 100644 index 64bb02d..0000000 --- a/src/ModbusSerial.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - ModbusSerial.h - Header for ModbusSerial Library - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#pragma once - -#include - -//#define MB_SOFTWARE_SERIAL - -#ifdef MB_SOFTWARE_SERIAL -#include -#endif - -uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); - -class ModbusSerial : public Modbus { - private: - Stream* _port; - int _txPin; - unsigned int _t15; // inter character time out - unsigned int _t35; // frame delay - uint8_t _slaveId; - public: - bool setSlaveId(uint8_t slaveId); - uint8_t getSlaveId(); - #ifdef MB_SOFTWARE_SERIAL - bool slave(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); - #else - bool slave(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); - #endif - void task(); - bool receive(uint8_t* frame); - bool sendPDU(uint8_t* pduframe); - bool send(uint8_t* frame); -}; - -/* Table of CRC values for high�order byte */ -const uint8_t _auchCRCHi[] = { - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, - 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40}; - -/* Table of CRC values for low�order byte */ -const uint8_t _auchCRCLo[] = { - 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, - 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, - 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, - 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, - 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, - 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, - 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, - 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, - 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, - 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, - 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, - 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, - 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, - 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, - 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, - 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, - 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, - 0x40}; \ No newline at end of file From 4f25bf399348f2db48bd449c5525c992c2dfbe20 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 10 Sep 2018 23:28:17 +0500 Subject: [PATCH 091/288] Add multiple registers fix, API change (pull/push regs) --- .gitignore | 4 +++- API.md | 12 +++++------ src/Modbus.cpp | 5 +++-- src/Modbus.h | 6 +++--- src/ModbusIP_ESP8266.cpp | 45 ++++++++++++++++++++-------------------- src/ModbusIP_ESP8266.h | 22 +++++++++++++------- 6 files changed, 52 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 73d9cc4..44ca147 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .vscode/arduino.json -.vscode/c_cpp_properties.json \ No newline at end of file +.vscode/c_cpp_properties.json +src/ModbusSerial.cpp +src/ModbusSerial.h diff --git a/API.md b/API.md index 8d77af9..8d94dd3 100644 --- a/API.md +++ b/API.md @@ -41,10 +41,10 @@ bool removeIreg(uint16_t offset); ### Query [multiple] regs from remote slave ```c -uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t nemregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Result is saved to local registers. Method returns corresponding transaction id. @@ -52,8 +52,8 @@ Result is saved to local registers. Method returns corresponding transaction id. ### Send [multiple] regs to remote slave ```c -uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. diff --git a/src/Modbus.cpp b/src/Modbus.cpp index a647ea2..1f54b29 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -413,14 +413,15 @@ void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { } } -void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, void* output) { +//1 void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, void* output) { +void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t field1, void* output) { uint8_t fcode = frame[0]; _reply = 0; if ((fcode & 0x80) != 0) { _reply = _frame[1]; return; } - uint16_t field1 = (uint16_t)sourceFrame[1] << 8 | (uint16_t)sourceFrame[2]; + //1 uint16_t field1 = (uint16_t)sourceFrame[1] << 8 | (uint16_t)sourceFrame[2]; uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; uint8_t bytecount_calc; switch (fcode) { diff --git a/src/Modbus.h b/src/Modbus.h index 0136e20..833af42 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -37,14 +37,14 @@ typedef struct TAddress { } TAddress operator++(int) { // TAddress++ TAddress result(*this); - address++; + ++(*this); return result; } TAddress& operator+=(const int& inc) { // TAddress += integer address += inc; return *this; } - TAddress operator+(const int &inc) { // TAddress + integer + const TAddress operator+(const int& inc) const { // TAddress + integer TAddress result(*this); result.address += inc; return result; @@ -162,7 +162,7 @@ class Modbus { void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave - void masterPDU(uint8_t* frame, uint8_t* sourceFrame, void* output= nullptr); //For Master + void masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t startreg, void* output= nullptr); //For Master bool readSlave(TAddress address, uint16_t numregs, FunctionCode fn); bool writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn, bool* data = nullptr); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index f3ae410..ac52158 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -109,7 +109,7 @@ void ModbusIP::task() { if (trans) { // if valid transaction id if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested // Procass incoming frame as master - masterPDU(_frame, trans->_frame, trans->data); + masterPDU(_frame, trans->_frame, trans->startreg, trans->data); } else { _reply = EX_UNEXPECTED_RESPONSE; } @@ -141,7 +141,7 @@ void ModbusIP::task() { n = -1; } -uint16_t ModbusIP::send(IPAddress ip, cbTransaction cb, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data +uint16_t ModbusIP::send(IPAddress ip, uint16_t startreg, cbTransaction cb, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data #ifdef MODBUSIP_MAX_TRANSACIONS if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; @@ -167,6 +167,7 @@ uint16_t ModbusIP::send(IPAddress ip, cbTransaction cb, void* data) { // Prepare tmp.cb = cb; tmp.data = data; tmp._frame = _frame; + tmp.startreg = startreg; _trans.push_back(tmp); _frame = nullptr; return transactionId; @@ -227,51 +228,51 @@ int8_t ModbusIP::getMaster(IPAddress ip) { uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); - return send(ip, cb); + return send(ip, offset, cb); } uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS, value); - return send(ip, cb); + return send(ip, offset, cb); } uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(COIL(offset), numregs, FC_READ_COILS); - return send(ip, cb, value); + return send(ip, offset, cb, value); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { readSlave(HREG(offset), value, FC_WRITE_REG); - return send(ip, cb); + return send(ip, offset, cb); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS, value); - return send(ip, cb); + return send(ip, offset, cb); } uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(HREG(offset), numregs, FC_READ_REGS); - return send(ip, cb, value); + return send(ip, offset, cb, value); } uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); - return send(ip, cb, value); + return send(ip, offset, cb, value); } uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); - return send(ip, cb, value); + return send(ip, offset, cb, value); } -uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; //addCoil(offset, numregs); // Should registers requre to be added there or use only existing? if (numregs == 1) { @@ -279,24 +280,24 @@ uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbT } else { writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); } - return send(ip, cb); + return send(ip, startreg, cb); } -uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; addCoil(offset, numregs); // Should registers requre to be added there or use existing? readSlave(COIL(offset), numregs, FC_READ_COILS); - return send(ip, cb); + return send(ip, startreg, cb); } -uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; addIsts(offset, numregs); // Should registers requre to be added there or use existing? readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); - return send(ip, cb); + return send(ip, startreg, cb); } -uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; //addCoil(offset, numregs); // Should registers requre to be added there or use existing? if (numregs == 1) { @@ -304,21 +305,21 @@ uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbT } else { writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS); } - return send(ip, cb); + return send(ip, startreg, cb); } -uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; addHreg(offset, numregs); // Should registers requre to be added there or use existing? readSlave(HREG(offset), numregs, FC_READ_REGS); - return send(ip, cb); + return send(ip, startreg, cb); } -uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; addIreg(offset, numregs); // Should registers requre to be added there or use existing? readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); - return send(ip, cb); + return send(ip, startreg, cb); } bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 0181781..b7fecf9 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -35,6 +35,7 @@ typedef struct TTransaction { cbTransaction cb = nullptr; uint8_t* _frame = nullptr; void* data = nullptr; + uint16_t startreg; bool operator ==(const TTransaction &obj) const { return transactionId == obj.transactionId; } @@ -65,11 +66,10 @@ class ModbusIP : public Modbus { int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); - uint16_t send(IPAddress ip, cbTransaction cb, void* data = nullptr); + uint16_t send(IPAddress ip, uint16_t startreg, cbTransaction cb, void* data = nullptr); public: ModbusIP(); - //uint16_t lastTransaction(); bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); bool connect(IPAddress ip); @@ -91,10 +91,16 @@ class ModbusIP : public Modbus { uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t numregs = 1, cbTransaction cb = nullptr); + + uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + +// uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +// uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +// uint16_t pushIstsToCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +// uint16_t pushIregToHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); }; \ No newline at end of file From 3ce4da6fa90db6ad8d30f14afcbaf420be3290f5 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 10 Sep 2018 23:29:23 +0500 Subject: [PATCH 092/288] ESP32 compile error fix --- src/Modbus.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Modbus.h b/src/Modbus.h index 833af42..77a6a94 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -6,6 +6,8 @@ #pragma once #include "Arduino.h" +#include +#include //#define MB_GLOBAL_REGS #define MB_MAX_REGS 32 From ae7cf36f7d265457c36e353da9b801f3617a5754 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 11 Sep 2018 11:58:26 +0500 Subject: [PATCH 093/288] push\pull code cleanup after last API changes --- API.md | 16 ++++----- README.md | 11 ++++-- src/Modbus.cpp | 22 +++++++----- src/Modbus.h | 20 +++++++---- src/ModbusIP_ESP8266.cpp | 76 ++++++++++++++++++++++------------------ src/ModbusIP_ESP8266.h | 13 +++---- 6 files changed, 92 insertions(+), 66 deletions(-) diff --git a/API.md b/API.md index 8d94dd3..600699f 100644 --- a/API.md +++ b/API.md @@ -41,22 +41,22 @@ bool removeIreg(uint16_t offset); ### Query [multiple] regs from remote slave ```c - uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` -Result is saved to local registers. Method returns corresponding transaction id. +Result is saved to local registers. Method returns corresponding transaction id. [ip/from] - slave, [to] - local ### Send [multiple] regs to remote slave ```c -uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` -Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. +Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local ### Write [multiple] values to remote slave reg diff --git a/README.md b/README.md index fd7aa88..86aa0b9 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,14 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf ## Last Changes ```diff -//Internal changes +// Internal changes + Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. + Change object's list implementation to *std::vector* + Modbus class refactoring + ModbusIP networking code refactoring and error reporting + Global registers storage to share between multiple Modbus* instances + Move rest of implementations from Modbus.h -//Public API changes +// Public API changes + Modbus master implementation + Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL + Back to marking private for onSet, onGet, addReg and Reg methods @@ -67,7 +67,12 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + Extend register addressing to 0..65535 + removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) + readCoil, readHreg, readIsts, readIreg -// ToDo ++ push\pullCoil, push\pullHreg, pullIsts, pullIreg +// ToDo for 2.0 +- extend removeCoil/Hreg/... to remove multiple registers +- implement pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg +- optimize code around std::vector processing +// ToDo later - ModbusSerial (over RS-485) - Modbus Read/Write File Records function - Modbus Write Mask Register function diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 1f54b29..e8a2a9e 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -321,27 +321,29 @@ bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::readSlave(TAddress address, uint16_t numregs, FunctionCode fn) { +bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; _frame = (uint8_t*) malloc(_len); _frame[0] = fn; - _frame[1] = address.address >> 8; - _frame[2] = address.address & 0x00FF; + _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 numregs, FunctionCode fn, bool* data) { +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) { _frame[0] = fn; - _frame[1] = startreg.address >> 8; - _frame[2] = startreg.address & 0x00FF; +// _frame[1] = startreg.address >> 8; +// _frame[2] = startreg.address & 0x00FF; + _frame[1] = to >> 8; + _frame[2] = to & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; @@ -358,14 +360,16 @@ bool Modbus::writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn return false; } -bool Modbus::writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn, uint16_t* data) { +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) { _frame[0] = fn; - _frame[1] = startreg.address >> 8; - _frame[2] = startreg.address & 0x00FF; + //_frame[1] = startreg.address >> 8; + //_frame[2] = startreg.address & 0x00FF; + _frame[1] = to >> 8; + _frame[2] = to & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; diff --git a/src/Modbus.h b/src/Modbus.h index 77a6a94..fc87504 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -142,8 +142,6 @@ class Modbus { void bitsToBool(bool* dst, uint8_t* src, uint16_t numregs); void boolToBits(uint8_t* dst, bool* src, uint16_t numregs); - - TRegister* searchRegister(TAddress addr); protected: //Reply Types @@ -161,14 +159,24 @@ class Modbus { uint16_t _len = 0; uint8_t _reply = 0; bool cbEnabled = true; + TRegister* searchRegister(TAddress addr); void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave void masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t startreg, void* output= nullptr); //For Master + // frame - data received form slave + // sourceFrame - data 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(TAddress address, uint16_t numregs, FunctionCode fn); - bool writeSlaveBits(TAddress startreg, uint16_t numregs, FunctionCode fn, bool* data = nullptr); - bool writeSlaveWords(TAddress startreg, uint16_t numregs, FunctionCode fn, uint16_t* data = nullptr); + 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 addReg(TAddress address, uint16_t value = 0, uint16_t numregs = 1); bool Reg(TAddress address, uint16_t value); @@ -177,4 +185,4 @@ class Modbus { bool onGet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); -}; +}; \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index ac52158..fa29722 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -227,99 +227,107 @@ int8_t ModbusIP::getMaster(IPAddress ip) { } uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { - readSlave(COIL(offset), COIL_VAL(value), FC_WRITE_COIL); + readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); return send(ip, offset, cb); } uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS, value); + writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); return send(ip, offset, cb); } uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(COIL(offset), numregs, FC_READ_COILS); + readSlave(offset, numregs, FC_READ_COILS); return send(ip, offset, cb, value); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { - readSlave(HREG(offset), value, FC_WRITE_REG); + readSlave(offset, value, FC_WRITE_REG); return send(ip, offset, cb); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS, value); + writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); return send(ip, offset, cb); } uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(HREG(offset), numregs, FC_READ_REGS); + readSlave(offset, numregs, FC_READ_REGS); return send(ip, offset, cb, value); } uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); + readSlave(offset, numregs, FC_READ_INPUT_STAT); return send(ip, offset, cb, value); } uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); + readSlave(offset, numregs, FC_READ_INPUT_REGS); return send(ip, offset, cb, value); } -uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - //addCoil(offset, numregs); // Should registers requre to be added there or use only existing? + if (!searchRegister(COIL(from))) return false; if (numregs == 1) { - readSlave(COIL(offset), COIL_VAL(Coil(offset)), FC_WRITE_COIL); + readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); } else { - writeSlaveBits(COIL(offset), numregs, FC_WRITE_COILS); + writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); } - return send(ip, startreg, cb); + return send(ip, 0, cb); // Second parameter will be omited } -uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - addCoil(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(COIL(offset), numregs, FC_READ_COILS); - return send(ip, startreg, cb); + #ifdef MODBUSIP_ADD_REG + addCoil(to, numregs); // Should registers requre to be added there or use existing? + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(ip, to, cb); } -uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - addIsts(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(ISTS(offset), numregs, FC_READ_INPUT_STAT); - return send(ip, startreg, cb); + #ifdef MODBUSIP_ADD_REG + addIsts(to, numregs); // Should registers requre to be added there or use existing? + #endif + readSlave(from, numregs, FC_READ_INPUT_STAT); + return send(ip, to, cb); } -uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - //addCoil(offset, numregs); // Should registers requre to be added there or use existing? + if (!searchRegister(HREG(from))) return false; if (numregs == 1) { - readSlave(HREG(offset), Hreg(offset), FC_WRITE_REG); + readSlave(to, Hreg(from), FC_WRITE_REG); } else { - writeSlaveWords(HREG(offset), numregs, FC_WRITE_REGS); + writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); } - return send(ip, startreg, cb); + return send(ip, 0, cb); // second parameter will be omited } -uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - addHreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(HREG(offset), numregs, FC_READ_REGS); - return send(ip, startreg, cb); + #ifdef MODBUSIP_ADD_REG + addHreg(to, numregs); // Should registers requre to be added there or use existing? + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(ip, to, cb); } -uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; - addIreg(offset, numregs); // Should registers requre to be added there or use existing? - readSlave(IREG(offset), numregs, FC_READ_INPUT_REGS); - return send(ip, startreg, cb); + #ifdef MODBUSIP_ADD_REG + addIreg(to, numregs); // Should registers requre to be added there or use existing? + #endif + readSlave(from, numregs, FC_READ_INPUT_REGS); + return send(ip, to, cb); } bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index b7fecf9..c17d1a4 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -23,6 +23,7 @@ #define MODBUSIP_UNIT 255 #define MODBUSIP_MAX_TRANSACIONS 16 #define MODBUSIP_MAX_CLIENTS 4 +#define MODBUSIP_ADD_REG 1 // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); @@ -92,12 +93,12 @@ class ModbusIP : public Modbus { uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); // uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); // uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); From bed2be87c0c9816b4d836219405d0fc60303ce60 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 11 Sep 2018 12:58:33 +0500 Subject: [PATCH 094/288] Implement pullHregToIreg, pullCoilToIsts, pushIstsToCoil, pushIregToHreg --- API.md | 4 +++ src/Modbus.cpp | 15 +++++--- src/Modbus.h | 2 +- src/ModbusIP_ESP8266.cpp | 78 ++++++++++++++++++++++++++++++---------- src/ModbusIP_ESP8266.h | 12 +++---- 5 files changed, 80 insertions(+), 31 deletions(-) diff --git a/API.md b/API.md index 600699f..761f83c 100644 --- a/API.md +++ b/API.md @@ -45,6 +45,8 @@ uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1 uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Result is saved to local registers. Method returns corresponding transaction id. [ip/from] - slave, [to] - local @@ -54,6 +56,8 @@ Result is saved to local registers. Method returns corresponding transaction id. ```c uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e8a2a9e..0c2a70c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -418,7 +418,7 @@ void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { } //1 void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, void* output) { -void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t field1, void* output) { +void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output) { uint8_t fcode = frame[0]; _reply = 0; if ((fcode & 0x80) != 0) { @@ -426,6 +426,7 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t field1, vo return; } //1 uint16_t field1 = (uint16_t)sourceFrame[1] << 8 | (uint16_t)sourceFrame[2]; + //uint16_t field1 = startreg.address; uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; uint8_t bytecount_calc; switch (fcode) { @@ -438,7 +439,8 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t field1, vo if (output) { memcpy(output, frame + 2, 2 * field2); } else { - setMultipleWords(frame + 2, HREG(field1), field2); + //setMultipleWords(frame + 2, HREG(field1), field2); + setMultipleWords(frame + 2, startreg, field2); } break; case FC_READ_COILS: @@ -452,7 +454,8 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t field1, vo if (output) { bitsToBool((bool*)output, frame + 2, field2); } else { - setMultipleBits(frame + 2, COIL(field1), field2); + //setMultipleBits(frame + 2, COIL(field1), field2); + setMultipleBits(frame + 2, startreg, field2); } break; case FC_READ_INPUT_STAT: @@ -466,7 +469,8 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t field1, vo if (output) { bitsToBool((bool*)output, frame + 2, field2); } else { - setMultipleBits(frame + 2, ISTS(field1), field2); + //setMultipleBits(frame + 2, ISTS(field1), field2); + setMultipleBits(frame + 2, startreg, field2); } break; case FC_READ_INPUT_REGS: @@ -478,7 +482,8 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t field1, vo if (output) { memcpy(output, frame + 2, 2 * field2); } else { - setMultipleWords(frame + 2, IREG(field1), field2); + //setMultipleWords(frame + 2, IREG(field1), field2); + setMultipleWords(frame + 2, startreg, field2); } break; case FC_WRITE_REG: diff --git a/src/Modbus.h b/src/Modbus.h index fc87504..9649eb0 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -163,7 +163,7 @@ class Modbus { void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave - void masterPDU(uint8_t* frame, uint8_t* sourceFrame, uint16_t startreg, void* output= nullptr); //For Master + void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output= nullptr); //For Master // frame - data received form slave // sourceFrame - data sent fo slave // startreg - local register to start put data to diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index fa29722..10d1308 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -141,7 +141,7 @@ void ModbusIP::task() { n = -1; } -uint16_t ModbusIP::send(IPAddress ip, uint16_t startreg, cbTransaction cb, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data +uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data #ifdef MODBUSIP_MAX_TRANSACIONS if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; @@ -228,48 +228,48 @@ int8_t ModbusIP::getMaster(IPAddress ip) { uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(ip, offset, cb); + return send(ip, COIL(offset), cb); } uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(ip, offset, cb); + return send(ip, COIL(offset), cb); } uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_COILS); - return send(ip, offset, cb, value); + return send(ip, COIL(offset), cb, value); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { readSlave(offset, value, FC_WRITE_REG); - return send(ip, offset, cb); + return send(ip, HREG(offset), cb); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(ip, offset, cb); + return send(ip, HREG(offset), cb); } uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_REGS); - return send(ip, offset, cb, value); + return send(ip, HREG(offset), cb, value); } uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(ip, offset, cb, value); + return send(ip, ISTS(offset), cb, value); } uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(ip, offset, cb, value); + return send(ip, IREG(offset), cb, value); } uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { @@ -280,25 +280,25 @@ uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t n } else { writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); } - return send(ip, 0, cb); // Second parameter will be omited + return send(ip, COIL(from), cb); } uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG - addCoil(to, numregs); // Should registers requre to be added there or use existing? + addCoil(to, numregs); #endif readSlave(from, numregs, FC_READ_COILS); - return send(ip, to, cb); + return send(ip, COIL(to), cb); } uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG - addIsts(to, numregs); // Should registers requre to be added there or use existing? + addIsts(to, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_STAT); - return send(ip, to, cb); + return send(ip, ISTS(to), cb); } uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { @@ -309,25 +309,65 @@ uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t n } else { writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); } - return send(ip, 0, cb); // second parameter will be omited + return send(ip, HREG(from), cb); } uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG - addHreg(to, numregs); // Should registers requre to be added there or use existing? + addHreg(to, numregs); #endif readSlave(from, numregs, FC_READ_REGS); - return send(ip, to, cb); + return send(ip, HREG(to), cb); } uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG - addIreg(to, numregs); // Should registers requre to be added there or use existing? + addIreg(to, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_REGS); - return send(ip, to, cb); + return send(ip, IREG(to), cb); +} + +uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(IREG(from))) return false; + if (numregs == 1) { + readSlave(to, Ireg(from), FC_WRITE_REG); + } else { + writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); + } + return send(ip, IREG(from), cb); +} + +uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(ISTS(from))) return false; + if (numregs == 1) { + readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); + } else { + writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); + } + return send(ip, ISTS(from), cb); +} + +uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addIreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(ip, IREG(to), cb); +} + +uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addIsts(to, numregs); + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(ip, ISTS(to), cb); } bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index c17d1a4..15f9548 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -36,7 +36,7 @@ typedef struct TTransaction { cbTransaction cb = nullptr; uint8_t* _frame = nullptr; void* data = nullptr; - uint16_t startreg; + TAddress startreg; bool operator ==(const TTransaction &obj) const { return transactionId == obj.transactionId; } @@ -67,7 +67,7 @@ class ModbusIP : public Modbus { int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); - uint16_t send(IPAddress ip, uint16_t startreg, cbTransaction cb, void* data = nullptr); + uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, void* data = nullptr); public: ModbusIP(); @@ -100,8 +100,8 @@ class ModbusIP : public Modbus { uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t pushIstsToCoil(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); -// uint16_t pushIregToHreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); }; \ No newline at end of file From c847929206b81be6ed79aebb54ec8447682da380 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 11 Sep 2018 13:56:28 +0500 Subject: [PATCH 095/288] Optimize std::vector processing --- README.md | 6 ++++-- src/Modbus.cpp | 5 +++-- src/ModbusIP_ESP8266.cpp | 22 ++++++++++++++-------- src/ModbusIP_ESP8266.h | 2 +- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 86aa0b9..c251733 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,12 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) + readCoil, readHreg, readIsts, readIreg + push\pullCoil, push\pullHreg, pullIsts, pullIreg ++ pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg ++ optimize code around std::vector processing // ToDo for 2.0 - extend removeCoil/Hreg/... to remove multiple registers -- implement pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg -- optimize code around std::vector processing +- add examples +- code cleanup // ToDo later - ModbusSerial (over RS-485) - Modbus Read/Write File Records function diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 0c2a70c..e9682e4 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -14,8 +14,9 @@ uint16_t cbDefault(TRegister* reg, uint16_t val) { } TRegister* Modbus::searchRegister(TAddress address) { - const TRegister tmp = {address, 0, cbDefault, cbDefault}; - std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); + //const TRegister tmp = {address, 0, cbDefault, cbDefault}; + //std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); + std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), [address](TRegister& addr){return addr.address == address;}); if (it != _regs.end()) return &*it; return nullptr; } diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 10d1308..9e51057 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -41,12 +41,13 @@ IPAddress ModbusIP::eventSource() { // Returns IP of current processing client } TTransaction* ModbusIP::searchTransaction(uint16_t id) { - TTransaction tmp; - tmp.transactionId = id; - tmp.timestamp = 0; - tmp.cb = nullptr; - tmp._frame = nullptr; - std::vector::iterator it = std::find(_trans.begin(), _trans.end(), tmp); + //TTransaction tmp; + //tmp.transactionId = id; + //tmp.timestamp = 0; + //tmp.cb = nullptr; + //tmp._frame = nullptr; + //std::vector::iterator it = std::find(_trans.begin(), _trans.end(), tmp); + std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); if (it != _trans.end()) return &*it; return nullptr; } @@ -117,8 +118,9 @@ void ModbusIP::task() { trans->cb((ResultCode)_reply, trans->transactionId, nullptr); } free(trans->_frame); - std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); - _trans.erase(it); + //std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); + _trans.erase(std::remove( _trans.begin(), _trans.end(), *trans), _trans.end() ); + //_trans.erase(it); } } client[n]->flush(); // Not sure if we need flush rest of data available @@ -377,3 +379,7 @@ bool ModbusIP::isConnected(IPAddress ip) { int8_t p = getSlave(ip); return p != -1;// && client[p]->connected(); } + +uint16_t ModbusIP::transactions() { + return _trans.capacity(); +} \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 15f9548..11b0a6b 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -79,7 +79,7 @@ class ModbusIP : public Modbus { void master(); void task(); void begin(); // Depricated - + uint16_t transactions(); void onConnect(cbModbusConnect cb = nullptr); void onDisconnect(cbModbusConnect cb = nullptr); IPAddress eventSource(); From 52f6775537802682a63a7d9f4ac33aadf661f73b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 11 Sep 2018 14:49:20 +0500 Subject: [PATCH 096/288] Extend removeCoil/Hreg/... to remove multiple registers --- API.md | 8 ++++---- README.md | 3 ++- src/Modbus.cpp | 33 ++++++++++++++++++--------------- src/Modbus.h | 10 +++++----- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/API.md b/API.md index 761f83c..df38724 100644 --- a/API.md +++ b/API.md @@ -32,10 +32,10 @@ uint16_t Ireg(uint16_t offset); ### Remove reg ```c -bool removeHreg(uint16_t offset); -bool removeCoil(uint16_t offset); -bool removeIsts(uint16_t offset); -bool removeIreg(uint16_t offset); +bool removeHreg(uint16_t offset, uint16_t numregs = 1); +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); ``` ### Query [multiple] regs from remote slave diff --git a/README.md b/README.md index c251733..f641a27 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + push\pullCoil, push\pullHreg, pullIsts, pullIreg + pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg + optimize code around std::vector processing ++ extend removeCoil/Hreg/... to remove multiple registers // ToDo for 2.0 -- extend removeCoil/Hreg/... to remove multiple registers - add examples - code cleanup // ToDo later @@ -79,6 +79,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf - Modbus Read/Write File Records function - Modbus Write Mask Register function - Modbus Serial line-specific functions +- Create destructor for ModbusIP ``` ## Contributions diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e9682e4..c36fcd8 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -60,14 +60,17 @@ uint16_t Modbus::Reg(TAddress address) { return 0; } -bool Modbus::removeReg(TAddress address) { - TRegister* reg = searchRegister(address); - if (reg) { - _regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() ); - return true; +bool Modbus::removeReg(TAddress address, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + for (uint16_t i = 0; i < numregs; i++) { + reg = searchRegister(address + i); + if (reg) { + atLeastOne = true; + _regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() ); + } } - return false; - + return atLeastOne; } void Modbus::slavePDU(uint8_t* frame) { @@ -516,8 +519,8 @@ bool Modbus::Hreg(uint16_t offset, uint16_t value) { uint16_t Modbus::Hreg(uint16_t offset) { return Reg(HREG(offset)); } -uint16_t Modbus::removeHreg(uint16_t offset) { - return removeReg(HREG(offset)); +uint16_t Modbus::removeHreg(uint16_t offset, uint16_t numregs) { + return removeReg(HREG(offset), numregs); } bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { return addReg(COIL(offset), COIL_VAL(value), numregs); @@ -546,14 +549,14 @@ bool Modbus::Ists(uint16_t offset) { uint16_t Modbus::Ireg(uint16_t offset) { return Reg(IREG(offset)); } -bool Modbus::removeCoil(uint16_t offset) { - return removeReg(COIL(offset)); +bool Modbus::removeCoil(uint16_t offset, uint16_t numregs) { + return removeReg(COIL(offset), numregs); } -bool Modbus::removeIsts(uint16_t offset) { - return removeReg(ISTS(offset)); +bool Modbus::removeIsts(uint16_t offset, uint16_t numregs) { + return removeReg(ISTS(offset), numregs); } -bool Modbus::removeIreg(uint16_t offset) { - return removeReg(IREG(offset)); +bool Modbus::removeIreg(uint16_t offset, uint16_t numregs) { + return removeReg(IREG(offset), numregs); } bool Modbus::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { return onGet(COIL(offset), cb, numregs); diff --git a/src/Modbus.h b/src/Modbus.h index 9649eb0..d0d9fea 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -105,7 +105,7 @@ class Modbus { bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); bool Hreg(uint16_t offset, uint16_t value); uint16_t Hreg(uint16_t offset); - uint16_t removeHreg(uint16_t offset); + uint16_t removeHreg(uint16_t offset, 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); @@ -115,9 +115,9 @@ class Modbus { bool Coil(uint16_t offset); bool Ists(uint16_t offset); uint16_t Ireg(uint16_t offset); - bool removeCoil(uint16_t offset); - bool removeIsts(uint16_t offset); - bool removeIreg(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); void cbEnable(bool state = true); void cbDisable(); @@ -181,7 +181,7 @@ class Modbus { 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); + bool removeReg(TAddress address, uint16_t numregs = 1); bool onGet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); bool onSet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); From fbc03dd7a1afe6bb0c1bf8d39c32a1dd929b9a16 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 11 Sep 2018 14:55:10 +0500 Subject: [PATCH 097/288] Add methods --- keywords.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/keywords.txt b/keywords.txt index 147d3c3..80d9f1e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -45,6 +45,14 @@ pullIsts KEYWORD2 pushHreg KEYWORD2 pullHreg KEYWORD2 pullIreg KEYWORD2 +pullHregToIreg KEYWORD2 +pullCoilToIsts KEYWORD2 +pushIstsToCoil KEYWORD2 +pushIregToHreg KEYWORD2 +removeHreg KEYWORD2 +removeIreg KEYWORD2 +removeCoil KEYWORD2 +removeIsts KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 From f571171f6b344a5e68994b5fa728e87a9047efce Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 30 Sep 2018 22:25:28 +0500 Subject: [PATCH 098/288] Quick fix for new API --- examples/Master/Master.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index f73744f..f03859a 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -66,11 +66,11 @@ void setup() { void loop() { if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established - mb.pullCoil(remote, LED_COIL); // Initiate Read Coil from Modbus Slave + mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave } else { mb.connect(remote); // Try to connect if no connection delay(100); // Additional deleay for conection } mb.task(); // Common local Modbus task delay(100); // Pooling interval -} \ No newline at end of file +} From d1a51aefb5f38c95a3c43f3b2bc0a8d631621901 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 1 Oct 2018 16:48:41 +0500 Subject: [PATCH 099/288] Callback API extended to support multiple handlers for event --- API.md | 37 +++++++++++++------- README.md | 2 ++ examples/Master/Master.ino | 4 +-- keywords.txt | 8 +++++ src/Modbus.cpp | 71 +++++++++++++++++++++++++++++++++----- src/Modbus.h | 50 +++++++++++++++++++-------- src/ModbusIP_ESP8266.h | 6 ++-- 7 files changed, 139 insertions(+), 39 deletions(-) diff --git a/API.md b/API.md index df38724..703b5e2 100644 --- a/API.md +++ b/API.md @@ -81,10 +81,10 @@ Write multiple values from array to remote Coil/Hreg. ### Read values from multiple remote slave regs ```c - uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Read values from remote Hreg/Coil/Ireg/Ists to array. @@ -132,24 +132,37 @@ Should be called from onGet/onSet or transaction callback function. Returns IP a *Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. ```c -bool onSetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); -bool onSetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); -bool onSetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); -bool onSetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); ``` Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onSetCoil(regId)` form to disconnect callback. ```c -bool onGetCoil(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); -bool onGetHreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); -bool onGetIsts(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); -bool onGetIreg(uint16_t address, cbModbus cb = cbDefault, uint16_t numregs = 1); +bool onGetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); ``` Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onGet(regId)` form to disconnect callback. +```c +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); +``` + +Disconnect specific callback function or all callbacks of the type if cb=NULL. + ### Macros ```c diff --git a/README.md b/README.md index f641a27..712090a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg + optimize code around std::vector processing + extend removeCoil/Hreg/... to remove multiple registers ++ multiple callbacks => memory usage optimization ++ add removeOnSetCoil\... methods // ToDo for 2.0 - add examples - code cleanup diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index f73744f..2e93aac 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -21,7 +21,7 @@ IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device #ifdef ESP8266 #define USE_LED D4 #else - $define UES_LED TX + #define UES_LED TX #endif ModbusIP mb; //ModbusIP object @@ -66,7 +66,7 @@ void setup() { void loop() { if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established - mb.pullCoil(remote, LED_COIL); // Initiate Read Coil from Modbus Slave + mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave } else { mb.connect(remote); // Try to connect if no connection delay(100); // Additional deleay for conection diff --git a/keywords.txt b/keywords.txt index 80d9f1e..609b1b7 100644 --- a/keywords.txt +++ b/keywords.txt @@ -23,6 +23,14 @@ onGetIreg KEYWORD2 onSetIreg KEYWORD2 onGetIsts KEYWORD2 onSetIsts KEYWORD2 +removeOnGetCoil KEYWORD2 +removeOnSetCoil KEYWORD2 +removeOnGetHreg KEYWORD2 +removeOnSetHreg KEYWORD2 +removeOnGetIsts KEYWORD2 +removeOnSetIsts KEYWORD2 +removeOnGetIreg KEYWORD2 +removeOnSetIreg KEYWORD2 addCoil KEYWORD2 addIsts KEYWORD2 addIreg KEYWORD2 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index c36fcd8..3fde321 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -7,16 +7,21 @@ #ifdef MB_GLOBAL_REGS std::vector _regs; +std::vector _callbacks; #endif -uint16_t cbDefault(TRegister* reg, uint16_t val) { - return val; +uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType t) { + std::vector::iterator it = std::find_if(_callbacks.begin(), _callbacks.end(), + [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}); + if (it != _callbacks.end()) return it->cb(reg, val); + return val; } TRegister* Modbus::searchRegister(TAddress address) { //const TRegister tmp = {address, 0, cbDefault, cbDefault}; //std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); - std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), [address](TRegister& addr){return addr.address == address;}); + std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), + [address](TRegister& addr){return addr.address == address;}); if (it != _regs.end()) return &*it; return nullptr; } @@ -27,7 +32,7 @@ bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) { #endif for (uint16_t i = 0; i < numregs; i++) { if (!searchRegister(address + i)) - _regs.push_back({address + i, value, cbDefault, cbDefault}); + _regs.push_back({address + i, value}); } //std::sort(_regs.begin(), _regs.end()); return true; @@ -38,7 +43,7 @@ bool Modbus::Reg(TAddress address, uint16_t value) { 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 = reg->set(reg, value); + reg->value = callback(reg, value, TCallback::ON_SET); } else { reg->value = value; } @@ -52,7 +57,7 @@ uint16_t Modbus::Reg(TAddress address) { reg = searchRegister(address); if(reg) if (cbEnabled) { - return reg->get(reg, reg->value); + return callback(reg, reg->value, TCallback::ON_GET); } else { return reg->value; } @@ -67,6 +72,8 @@ bool Modbus::removeReg(TAddress address, uint16_t numregs) { reg = searchRegister(address + i); if (reg) { atLeastOne = true; + removeOnSet(address + i); + removeOnGet(address + i); _regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() ); } } @@ -299,10 +306,13 @@ void Modbus::setMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numreg bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; + if (!cb) { + return removeOnGet(address); + } while (numregs > 0) { reg = searchRegister(address); if (reg) { - reg->get = cb; + _callbacks.push_back({TCallback::ON_GET, address, cb}); atLeastOne = true; } address++; @@ -313,10 +323,13 @@ bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; + if (!cb) { + return removeOnGet(address); + } while (numregs > 0) { reg = searchRegister(address); if (reg) { - reg->set = cb; + _callbacks.push_back({TCallback::ON_SET, address, cb}); atLeastOne = true; } address++; @@ -325,6 +338,23 @@ bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { return atLeastOne; } +bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) { + while(numregs--) { + _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), [address, cb](TCallback entry){ + return entry.type == TCallback::ON_SET && entry.address == address && (!cb || entry.cb == cb);} ), _callbacks.end() ); + address++; + } + return false; +} +bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) { + while(numregs--) { + _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), [address, cb](TCallback entry){ + return entry.type == TCallback::ON_GET && entry.address == address && (!cb || entry.cb == cb);} ), _callbacks.end() ); + address++; + } + return false; +} + bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) { free(_frame); _len = 5; @@ -583,6 +613,31 @@ bool Modbus::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return onSet(IREG(offset), cb, numregs); } +bool Modbus::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnGet(COIL(offset), cb, numregs); +} +bool Modbus::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnSet(COIL(offset), cb, numregs); +} +bool Modbus::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnGet(HREG(offset), cb, numregs); +} +bool Modbus::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnSet(HREG(offset), cb, numregs); +} +bool Modbus::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnGet(ISTS(offset), cb, numregs); +} +bool Modbus::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnSet(ISTS(offset), cb, numregs); +} +bool Modbus::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnGet(IREG(offset), cb, numregs); +} +bool Modbus::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return removeOnSet(IREG(offset), cb, numregs); +} + Modbus::~Modbus() { free(_frame); } \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index d0d9fea..b29701b 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -23,6 +23,9 @@ #define ISTS_VAL(v) (v?0xFF00:0x0000) #define ISTS_BOOL(v) (v==0xFF00) +// For depricated (v1.xx) onSet/onGet format compatibility +#define cbDefault nullptr + typedef struct TRegister; typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type @@ -53,18 +56,23 @@ typedef struct TAddress { } }; +typedef struct TCallback { + enum CallbackType {ON_SET, ON_GET}; + CallbackType type; + TAddress address; + cbModbus cb; +}; + typedef struct TRegister { TAddress address; uint16_t value; - cbModbus get; - cbModbus set; + //cbModbus get; + //cbModbus set; bool operator ==(const TRegister &obj) const { return address == obj.address; } }; -uint16_t cbDefault(TRegister* reg, uint16_t val); - class Modbus { public: //Function Codes @@ -122,14 +130,24 @@ class Modbus { void cbEnable(bool state = true); void cbDisable(); - bool onGetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSetCoil(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onGetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSetHreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onGetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSetIsts(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onGetIreg(uint16_t offset, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSetIreg(uint16_t offset, cbModbus cb = cbDefault, 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); + private: void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); @@ -154,11 +172,13 @@ class Modbus { }; #ifndef MB_GLOBAL_REGS std::vector _regs; + std::vector _callbacks; #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); TRegister* searchRegister(TAddress addr); void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); @@ -183,6 +203,8 @@ class Modbus { uint16_t Reg(TAddress address); bool removeReg(TAddress address, uint16_t numregs = 1); - bool onGet(TAddress address, cbModbus cb = cbDefault, uint16_t numregs = 1); - bool onSet(TAddress address, cbModbus cb = cbDefault, 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); }; \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 11b0a6b..e3b814e 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -53,7 +53,7 @@ class ModbusIP : public Modbus { }; uint8_t raw[7]; }; - MBAP_t _MBAP; + MBAP_t _MBAP; cbModbusConnect cbConnect = nullptr; cbModbusConnect cbDisconnect = nullptr; WiFiServer* server = nullptr; @@ -80,9 +80,9 @@ class ModbusIP : public Modbus { void task(); void begin(); // Depricated uint16_t transactions(); - void onConnect(cbModbusConnect cb = nullptr); + void onConnect(cbModbusConnect cb = nullptr); void onDisconnect(cbModbusConnect cb = nullptr); - IPAddress eventSource(); + IPAddress eventSource(); uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); From a7fac3a0b805bdee907795c6018f6b5f4a3e0d79 Mon Sep 17 00:00:00 2001 From: PeterEmbedded Date: Tue, 2 Oct 2018 21:48:44 +0200 Subject: [PATCH 100/288] Update Master.ino Fixed some typos --- examples/Master/Master.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index f03859a..72cbb10 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -1,6 +1,6 @@ /* Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) - Control Led on D4/TX pin by remote Modbus devise using Read Single Coil Modbus Function + Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 @@ -69,8 +69,8 @@ void loop() { mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave } else { mb.connect(remote); // Try to connect if no connection - delay(100); // Additional deleay for conection + delay(100); // Additional delay for connection } mb.task(); // Common local Modbus task - delay(100); // Pooling interval + delay(100); // Polling interval } From 1bac4ffeb89217657ed445ce7872fe34be2a5a51 Mon Sep 17 00:00:00 2001 From: PeterEmbedded Date: Tue, 2 Oct 2018 21:50:44 +0200 Subject: [PATCH 101/288] Update AnalogInput.ino Fixed a typo --- examples/AnalogInput/AnalogInput.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/AnalogInput/AnalogInput.ino b/examples/AnalogInput/AnalogInput.ino index e53ed72..ae77b35 100644 --- a/examples/AnalogInput/AnalogInput.ino +++ b/examples/AnalogInput/AnalogInput.ino @@ -43,7 +43,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.begin(); //Sart Modbus IP + mb.begin(); //Start Modbus IP // Add SENSOR_IREG register - Use addIreg() for analog Inputs mb.addIreg(SENSOR_IREG); From fa7c2732d3e500d23e7bfde350d87c8989235f13 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 3 Oct 2018 11:02:06 +0500 Subject: [PATCH 102/288] Mistype fix --- examples/Master/Master.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 72cbb10..1c6ccec 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -21,7 +21,7 @@ IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device #ifdef ESP8266 #define USE_LED D4 #else - $define UES_LED TX + #define UES_LED TX #endif ModbusIP mb; //ModbusIP object From 59f4a02ad759129c661af48c359b2706700c63bc Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 3 Oct 2018 14:36:47 +0500 Subject: [PATCH 103/288] Finishing with multiple callbacks on event --- API.md | 4 ++-- src/Modbus.cpp | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/API.md b/API.md index 703b5e2..52646d3 100644 --- a/API.md +++ b/API.md @@ -138,7 +138,7 @@ bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); ``` -Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onSetCoil(regId)` form to disconnect callback. +Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. ```c @@ -148,7 +148,7 @@ bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); ``` -Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. Call in `onGet(regId)` form to disconnect callback. +Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. ```c bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 3fde321..a6927fe 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -6,20 +6,25 @@ #include "Modbus.h" #ifdef MB_GLOBAL_REGS -std::vector _regs; -std::vector _callbacks; + std::vector _regs; + std::vector _callbacks; #endif uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType t) { - std::vector::iterator it = std::find_if(_callbacks.begin(), _callbacks.end(), + uint16_t newVal = val; + std::vector::iterator it = _callbacks.begin(); + do { + it = std::find_if(it, _callbacks.end(), [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}); - if (it != _callbacks.end()) return it->cb(reg, val); - return val; + if (it != _callbacks.end()) { + newVal = it->cb(reg, newVal); + it++; + } + } while (it != _callbacks.end()); + return newVal; } TRegister* Modbus::searchRegister(TAddress address) { - //const TRegister tmp = {address, 0, cbDefault, cbDefault}; - //std::vector::iterator it = std::find(_regs.begin(), _regs.end(), tmp); std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), [address](TRegister& addr){return addr.address == address;}); if (it != _regs.end()) return &*it; From 83eefda1d860a046b0f6ab6e6155fd70c8a8b135 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 5 Oct 2018 15:37:08 +0500 Subject: [PATCH 104/288] Add HReg read example --- .../MasterSimpleRead/MasterSimpleRead.ino | 60 +++++++++++++++++++ src/Modbus.cpp | 6 +- 2 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 examples/MasterSimpleRead/MasterSimpleRead.ino diff --git a/examples/MasterSimpleRead/MasterSimpleRead.ino b/examples/MasterSimpleRead/MasterSimpleRead.ino new file mode 100644 index 0000000..131d825 --- /dev/null +++ b/examples/MasterSimpleRead/MasterSimpleRead.ino @@ -0,0 +1,60 @@ +/* + Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) + Read Holding Register from Slave device + + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +const int REG = 528; // Modbus Hreg Offset (0-9999) +IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device +const int LOOP_COUNT = 10; + +ModbusIP mb; //ModbusIP object + +void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif + + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.master(); +} + +uint16_t res = 0; +uint8_t show = LOOP_COUNT; + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + delay(100); // Additional deleay for conection + } + mb.task(); // Common local Modbus task + delay(100); // Pulling interval + if (show--) { // Display Slave register value one time per second (with default settings) + Serial.println(res); + show = LOOP_COUNT; + } +} \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index a6927fe..ef985b0 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -14,8 +14,7 @@ uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType uint16_t newVal = val; std::vector::iterator it = _callbacks.begin(); do { - it = std::find_if(it, _callbacks.end(), - [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}); + it = std::find_if(it, _callbacks.end(), [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}); if (it != _callbacks.end()) { newVal = it->cb(reg, newVal); it++; @@ -25,8 +24,7 @@ uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType } TRegister* Modbus::searchRegister(TAddress address) { - std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), - [address](TRegister& addr){return addr.address == address;}); + std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), [address](TRegister& addr){return addr.address == address;}); if (it != _regs.end()) return &*it; return nullptr; } From 50c8e3a651d61da0ee8e8580e6aabca031ec1089 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 17 Oct 2018 11:38:54 +0500 Subject: [PATCH 105/288] Wrong byte order in read/writeHreg, read/writeIreg fixed --- keywords.txt | 16 ++++++++++++++++ src/Modbus.cpp | 27 ++++++++++++++++++++++++--- src/Modbus.h | 8 ++++++++ src/ModbusIP_ESP8266.cpp | 2 +- src/ModbusIP_ESP8266.h | 5 ----- 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/keywords.txt b/keywords.txt index 609b1b7..302fcc8 100644 --- a/keywords.txt +++ b/keywords.txt @@ -71,3 +71,19 @@ ISTS_VAL LITERAL1 ISTS_BOOL LITERAL1 ResultCode LITERAL1 FunctionCode LITERAL1 +EX_SUCCESS LITERAL1 +EX_ILLEGAL_FUNCTION LITERAL1 +EX_ILLEGAL_ADDRESS LITERAL1 +EX_ILLEGAL_VALUE LITERAL1 +EX_SLAVE_FAILURE LITERAL1 +EX_ACKNOWLEDGE LITERAL1 +EX_SLAVE_DEVICE_BUSY LITERAL1 +EX_MEMORY_PARITY_ERROR LITERAL1 +EX_PATH_UNAVAILABLE LITERAL1 +EX_DEVICE_FAILED_TO_RESPOND LITERAL1 +EX_GENERAL_FAILURE LITERAL1 +EX_DATA_MISMACH LITERAL1 +EX_UNEXPECTED_RESPONSE LITERAL1 +EX_TIMEOUT LITERAL1 +EX_CONNECTION_LOST LITERAL1 + diff --git a/src/Modbus.cpp b/src/Modbus.cpp index ef985b0..8bd78f8 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -411,7 +411,14 @@ bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, F _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; if (data) { - memcpy(_frame + 6, data, numregs * 2); + //memcpy(_frame + 6, data, numregs * 2); + uint16_t* frame = (uint16_t*)(_frame + 6); + while(numregs) { + *frame = __bswap_16(*((uint16_t*)data)); + frame = frame + 2; + data = data + 2; + numregs--; + } } else { getMultipleWords(_frame + 6, startreg, numregs); } @@ -474,7 +481,14 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, break; } if (output) { - memcpy(output, frame + 2, 2 * field2); + //memcpy(output, frame + 2, 2 * field2); + frame = frame + 2; + while(field2) { + *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); + frame = frame + 2; + output = output + 2; + field2--; + } } else { //setMultipleWords(frame + 2, HREG(field1), field2); setMultipleWords(frame + 2, startreg, field2); @@ -517,7 +531,14 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, break; } if (output) { - memcpy(output, frame + 2, 2 * field2); + //memcpy(output, frame + 2, 2 * field2); + frame = frame + 2; + while(field2) { + *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); + frame = frame + 2; + output = output + 2; + field2--; + } } else { //setMultipleWords(frame + 2, IREG(field1), field2); setMultipleWords(frame + 2, startreg, field2); diff --git a/src/Modbus.h b/src/Modbus.h index b29701b..251afbb 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -8,6 +8,14 @@ #include "Arduino.h" #include #include +#ifndef ESP8266 + #include +#endif + +#ifndef __bswap_16 + #define __bswap_16(num) ((uint16_t)num>>8) | ((uint16_t)num<<8) +#endif + //#define MB_GLOBAL_REGS #define MB_MAX_REGS 32 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 9e51057..3900b4b 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -203,7 +203,7 @@ void ModbusIP::cleanup() { // Free clients if not connected and remove timedout cbDisconnect(ip); } } - _trans.erase( remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); + _trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); } int8_t ModbusIP::getFreeClient() { // Returns free slot position diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index e3b814e..d346b2a 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -10,11 +10,6 @@ #include #else #include - #include -#endif - -#ifndef __bswap_16 - #define __bswap_16(num) ((uint16_t)num>>8) | ((uint16_t)num<<8) #endif #define MODBUSIP_PORT 502 From d3227fc3ddc79b9936da0eba01c0aed677e2f251 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 22 Oct 2018 23:38:59 +0500 Subject: [PATCH 106/288] Static client's array --- keywords.txt | 1 + src/ModbusIP_ESP8266.cpp | 105 +++++++++++++++++++++++---------------- src/ModbusIP_ESP8266.h | 2 +- 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/keywords.txt b/keywords.txt index 302fcc8..2e992a3 100644 --- a/keywords.txt +++ b/keywords.txt @@ -5,6 +5,7 @@ ModbusIP KEYWORD1 Modbus KEYWORD1 TRegister KEYWORD1 TTransaction KEYWORD1 +TAddress KEYWORD1 # Methods and Functions (KEYWORD2) master KEYWORD2 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 3900b4b..28f310a 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -6,8 +6,10 @@ #include "ModbusIP_ESP8266.h" ModbusIP::ModbusIP() { + _trans.reserve(MODBUSIP_MAX_TRANSACIONS); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - client[i] = nullptr; + //client[i] = nullptr; + client[i] = WiFiClient(); } void ModbusIP::master() { @@ -30,13 +32,19 @@ bool ModbusIP::connect(IPAddress ip) { int8_t p = getFreeClient(); if (p == -1) return false; - client[p] = new WiFiClient(); - return client[p]->connect(ip, MODBUSIP_PORT); + //client[p] = WiFiClient(); + return client[p].connect(ip, MODBUSIP_PORT); + //WiFiClient cl = WiFiClient(); + //if (cl.connect(ip, MODBUSIP_PORT)) { + // client[p] = new WiFiClient(cl); + // return true; + //} + //return false; } IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) - return client[n]->remoteIP(); + return client[n].remoteIP(); return INADDR_NONE; } @@ -57,10 +65,10 @@ void ModbusIP::task() { cleanup(); if (server) { while (server->hasClient()) { - WiFiClient* currentClient = new WiFiClient(server->available()); - if (currentClient == nullptr && !currentClient->connected()) + WiFiClient currentClient = server->available(); + if (currentClient && !currentClient.connected()) continue; - if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + if (cbConnect == nullptr || cbConnect(currentClient.remoteIP())) { n = getFreeClient(); if (n > -1) { client[n] = currentClient; @@ -68,39 +76,39 @@ void ModbusIP::task() { } } // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached - currentClient->flush(); - currentClient->stop(); - delete currentClient; + currentClient.flush(); + currentClient.stop(); + //delete currentClient; } } for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - if (client[n] == nullptr) + if (!client[n].connected()) continue; // for (n) - if (client[n]->available() < sizeof(_MBAP) + 1) + if (client[n].available() < sizeof(_MBAP) + 1) continue; - client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP + client[n].readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP _len = __bswap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. - client[n]->flush(); + client[n].flush(); continue; // for (n) } if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME - exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->flush(); + exceptionResponse((FunctionCode)client[n].read(), EX_SLAVE_FAILURE); + client[n].flush(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { - exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->flush(); + exceptionResponse((FunctionCode)client[n].read(), EX_SLAVE_FAILURE); + client[n].flush(); } else { - if (client[n]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame + if (client[n].readBytes(_frame, _len) < _len) { //Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - client[n]->flush(); + client[n].flush(); } else { - if (client[n]->localPort() == MODBUSIP_PORT) { + if (client[n].localPort() == MODBUSIP_PORT) { // Process incoming frame as slave slavePDU(_frame); } else { @@ -118,23 +126,24 @@ void ModbusIP::task() { trans->cb((ResultCode)_reply, trans->transactionId, nullptr); } free(trans->_frame); - //std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); - _trans.erase(std::remove( _trans.begin(), _trans.end(), *trans), _trans.end() ); - //_trans.erase(it); + //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); + if (it != _trans.end()) + _trans.erase(it); } } - client[n]->flush(); // Not sure if we need flush rest of data available + client[n].flush(); // Not sure if we need flush rest of data available } } } - if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master + if (client[n].localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master if (_reply != REPLY_OFF) { _MBAP.length = __bswap_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); - client[n]->write(sbuf, send_len); + client[n].write(sbuf, send_len); } free(_frame); _frame = nullptr; @@ -149,7 +158,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, void* return false; #endif int8_t p = getSlave(ip); - if (p == -1 || !client[p]->connected()) + if (p == -1 || !client[p].connected()) return false; transactionId++; if (!transactionId) transactionId = 1; @@ -161,7 +170,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, void* uint8_t sbuf[send_len]; memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); - if (client[p]->write(sbuf, send_len) != send_len) + if (client[p].write(sbuf, send_len) != send_len) return false; TTransaction tmp; tmp.transactionId = transactionId; @@ -186,44 +195,54 @@ void ModbusIP::onDisconnect(cbModbusConnect cb) { bool ifExpired(TTransaction& t) { if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { - if (t.cb) - t.cb(Modbus::EX_TIMEOUT, t.transactionId, nullptr); - free(t._frame); + //if (t.cb) + // t.cb(Modbus::EX_TIMEOUT, t.transactionId, nullptr); + //free(t._frame); return true; } return false; } void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - if (client[i] && !client[i]->connected()) { - IPAddress ip = client[i]->remoteIP(); - delete client[i]; - client[i] = nullptr; - if (cbDisconnect && cbEnabled) - cbDisconnect(ip); + if (!client[i].connected()) { + //IPAddress ip = client[i].remoteIP(); + //client[i].stop(); + //delete client[i]; + //client[i] = nullptr; + //if (cbDisconnect && cbEnabled) + // cbDisconnect(ip); } } - _trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); + //_trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); + std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), ifExpired); + while (it != _trans.end()) { + if (it->cb) + it->cb(Modbus::EX_TIMEOUT, it->transactionId, nullptr); + free(it->_frame); + _trans.erase(it); + it = std::find_if(it, _trans.end(), ifExpired); + } + } int8_t ModbusIP::getFreeClient() { // Returns free slot position //clientsCleanup(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (!client[i]) + if (!client[i].connected()) return i; return -1; } int8_t ModbusIP::getSlave(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) + if (client[i].connected() && client[i].remoteIP() == ip && client[i].localPort() != MODBUSIP_PORT) return i; return -1; } int8_t ModbusIP::getMaster(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == MODBUSIP_PORT) + if (client[i].connected() && client[i].remoteIP() == ip && client[i].localPort() == MODBUSIP_PORT) return i; return -1; } @@ -377,7 +396,7 @@ bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progre } bool ModbusIP::isConnected(IPAddress ip) { int8_t p = getSlave(ip); - return p != -1;// && client[p]->connected(); + return p != -1 && client[p].connected(); } uint16_t ModbusIP::transactions() { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index d346b2a..ce42dbf 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -52,7 +52,7 @@ class ModbusIP : public Modbus { cbModbusConnect cbConnect = nullptr; cbModbusConnect cbDisconnect = nullptr; WiFiServer* server = nullptr; - WiFiClient* client[MODBUSIP_MAX_CLIENTS]; + WiFiClient client[MODBUSIP_MAX_CLIENTS]; std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; From 7c2e57aca310980c66c708c8af688ded0d851997 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 9 Nov 2018 15:38:15 +0500 Subject: [PATCH 107/288] add Modbus Unit parameter to master calls --- API.md | 36 ++++----- README.md | 1 + examples/Master/Master.ino | 1 - .../MasterSimpleRead/MasterSimpleRead.ino | 1 - src/Modbus.h | 22 ++++-- src/ModbusIP_ESP8266.cpp | 76 +++++++++---------- src/ModbusIP_ESP8266.h | 38 +++++----- 7 files changed, 93 insertions(+), 82 deletions(-) diff --git a/API.md b/API.md index 52646d3..13a0013 100644 --- a/API.md +++ b/API.md @@ -41,12 +41,12 @@ bool removeIreg(uint16_t offset, uint16_t numregs = 1); ### Query [multiple] regs from remote slave ```c -uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); ``` Result is saved to local registers. Method returns corresponding transaction id. [ip/from] - slave, [to] - local @@ -54,10 +54,10 @@ Result is saved to local registers. Method returns corresponding transaction id. ### Send [multiple] regs to remote slave ```c -uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); ``` Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local @@ -65,15 +65,15 @@ Write Register/Coil or Write Multiple Registers/Coils Modbus function selected a ### Write [multiple] values to remote slave reg ```c -uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); -uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); ``` Write single value to remote Hreg/Coil. ```c -uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); ``` Write multiple values from array to remote Coil/Hreg. @@ -81,10 +81,10 @@ Write multiple values from array to remote Coil/Hreg. ### Read values from multiple remote slave regs ```c -uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); ``` Read values from remote Hreg/Coil/Ireg/Ists to array. diff --git a/README.md b/README.md index 712090a..de999b3 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + extend removeCoil/Hreg/... to remove multiple registers + multiple callbacks => memory usage optimization + add removeOnSetCoil\... methods ++ add read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id // ToDo for 2.0 - add examples - code cleanup diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 1c6ccec..85ea99f 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -69,7 +69,6 @@ void loop() { mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave } else { mb.connect(remote); // Try to connect if no connection - delay(100); // Additional delay for connection } mb.task(); // Common local Modbus task delay(100); // Polling interval diff --git a/examples/MasterSimpleRead/MasterSimpleRead.ino b/examples/MasterSimpleRead/MasterSimpleRead.ino index 131d825..19822c5 100644 --- a/examples/MasterSimpleRead/MasterSimpleRead.ino +++ b/examples/MasterSimpleRead/MasterSimpleRead.ino @@ -49,7 +49,6 @@ void loop() { mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave } else { mb.connect(remote); // Try to connect if no connection - delay(100); // Additional deleay for conection } mb.task(); // Common local Modbus task delay(100); // Pulling interval diff --git a/src/Modbus.h b/src/Modbus.h index 251afbb..2b3e088 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -34,10 +34,10 @@ // For depricated (v1.xx) onSet/onGet format compatibility #define cbDefault nullptr -typedef struct TRegister; +struct TRegister; typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type -typedef struct TAddress { +struct TAddress { enum RegType {COIL, ISTS, IREG, HREG}; RegType type; uint16_t address; @@ -61,17 +61,29 @@ typedef struct TAddress { 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; + } }; -typedef struct TCallback { +struct TCallback { enum CallbackType {ON_SET, ON_GET}; CallbackType type; TAddress address; cbModbus cb; }; -typedef struct TRegister { +struct TRegister { TAddress address; uint16_t value; //cbModbus get; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 28f310a..e60a159 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -152,7 +152,7 @@ void ModbusIP::task() { n = -1; } -uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data +uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data #ifdef MODBUSIP_MAX_TRANSACIONS if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; @@ -165,7 +165,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, void* _MBAP.transactionId = __bswap_16(transactionId); _MBAP.protocolId = __bswap_16(0); _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP - _MBAP.unitId = MODBUSIP_UNIT; + _MBAP.unitId = unit; size_t send_len = _len + sizeof(_MBAP.raw); uint8_t sbuf[send_len]; memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); @@ -247,53 +247,53 @@ int8_t ModbusIP::getMaster(IPAddress ip) { return -1; } -uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb) { +uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb, uint8_t unit) { readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(ip, COIL(offset), cb); + return send(ip, COIL(offset), cb, unit); } -uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(ip, COIL(offset), cb); + return send(ip, COIL(offset), cb, unit); } -uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_COILS); - return send(ip, COIL(offset), cb, value); + return send(ip, COIL(offset), cb, unit, value); } -uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb) { +uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb, uint8_t unit) { readSlave(offset, value, FC_WRITE_REG); - return send(ip, HREG(offset), cb); + return send(ip, HREG(offset), cb, unit); } -uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(ip, HREG(offset), cb); + return send(ip, HREG(offset), cb, unit); } -uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_REGS); - return send(ip, HREG(offset), cb, value); + return send(ip, HREG(offset), cb, unit, value); } -uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(offset), cb, value); + return send(ip, ISTS(offset), cb, unit, value); } -uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(offset), cb, value); + return send(ip, IREG(offset), cb, unit, value); } -uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; if (!searchRegister(COIL(from))) return false; if (numregs == 1) { @@ -301,28 +301,28 @@ uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t n } else { writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); } - return send(ip, COIL(from), cb); + return send(ip, COIL(from), cb, unit); } -uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG addCoil(to, numregs); #endif readSlave(from, numregs, FC_READ_COILS); - return send(ip, COIL(to), cb); + return send(ip, COIL(to), cb, unit); } -uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG addIsts(to, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(to), cb); + return send(ip, ISTS(to), cb, unit); } -uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; if (!searchRegister(HREG(from))) return false; if (numregs == 1) { @@ -330,28 +330,28 @@ uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t n } else { writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); } - return send(ip, HREG(from), cb); + return send(ip, HREG(from), cb, unit); } -uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG addHreg(to, numregs); #endif readSlave(from, numregs, FC_READ_REGS); - return send(ip, HREG(to), cb); + return send(ip, HREG(to), cb, unit); } -uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG addIreg(to, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(to), cb); + return send(ip, IREG(to), cb, unit); } -uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; if (!searchRegister(IREG(from))) return false; if (numregs == 1) { @@ -359,10 +359,10 @@ uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint } else { writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); } - return send(ip, IREG(from), cb); + return send(ip, IREG(from), cb, unit); } -uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; if (!searchRegister(ISTS(from))) return false; if (numregs == 1) { @@ -370,25 +370,25 @@ uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint } else { writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); } - return send(ip, ISTS(from), cb); + return send(ip, ISTS(from), cb, unit); } -uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG addIreg(to, numregs); #endif readSlave(from, numregs, FC_READ_REGS); - return send(ip, IREG(to), cb); + return send(ip, IREG(to), cb, unit); } -uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { +uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; #ifdef MODBUSIP_ADD_REG addIsts(to, numregs); #endif readSlave(from, numregs, FC_READ_COILS); - return send(ip, ISTS(to), cb); + return send(ip, ISTS(to), cb, unit); } bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index ce42dbf..c52b919 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -62,7 +62,7 @@ class ModbusIP : public Modbus { int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); - uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, void* data = nullptr); + uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr); public: ModbusIP(); @@ -79,24 +79,24 @@ class ModbusIP : public Modbus { void onDisconnect(cbModbusConnect cb = nullptr); IPAddress eventSource(); - uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr); - uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); - uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); }; \ No newline at end of file From 9a0044e955d25d90aa87339bb93b447660b14abf Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 6 Jan 2019 13:29:29 +0500 Subject: [PATCH 108/288] Add Modbus unit parameter to read/write/pull/push. Remove responce wait if no CallBack --- .../MultipleHRegDebug/MultipleHRegDebug.ino | 16 --- src/ModbusIP_ESP8266.cpp | 129 ++++++++---------- src/ModbusIP_ESP8266.h | 4 +- 3 files changed, 61 insertions(+), 88 deletions(-) diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/MultipleHRegDebug/MultipleHRegDebug.ino index 67b4251..0a0650c 100644 --- a/examples/MultipleHRegDebug/MultipleHRegDebug.ino +++ b/examples/MultipleHRegDebug/MultipleHRegDebug.ino @@ -6,11 +6,7 @@ http://github.com/andresarmento/modbus-arduino Current version -<<<<<<< HEAD (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) -======= - (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) ->>>>>>> std-list https://github.com/emelianov/modbus-esp8266 */ @@ -54,15 +50,11 @@ bool cbConn(IPAddress ip) { } void setup() { -<<<<<<< HEAD #ifdef ESP8266 Serial.begin(74880); #else Serial.begin(115200); #endif -======= - Serial.begin(74880); ->>>>>>> std-list WiFi.begin("ssid", "pass"); @@ -79,11 +71,7 @@ void setup() { mb.onConnect(cbConn); // Add callback on connection event mb.begin(); -<<<<<<< HEAD if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs -======= - if (!mb.addHReg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Coils. The same as mb.addCoil(COIL_BASE, false, LEN) ->>>>>>> std-list mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get mb.onSetHreg(0, cbWrite, LEN); } @@ -91,9 +79,5 @@ void setup() { void loop() { //Call once inside loop() - all magic here mb.task(); -<<<<<<< HEAD delay(100); -======= - yield(); ->>>>>>> std-list } diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index e60a159..6015fc0 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -8,8 +8,7 @@ ModbusIP::ModbusIP() { _trans.reserve(MODBUSIP_MAX_TRANSACIONS); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - //client[i] = nullptr; - client[i] = WiFiClient(); + client[i] = nullptr; } void ModbusIP::master() { @@ -32,29 +31,17 @@ bool ModbusIP::connect(IPAddress ip) { int8_t p = getFreeClient(); if (p == -1) return false; - //client[p] = WiFiClient(); - return client[p].connect(ip, MODBUSIP_PORT); - //WiFiClient cl = WiFiClient(); - //if (cl.connect(ip, MODBUSIP_PORT)) { - // client[p] = new WiFiClient(cl); - // return true; - //} - //return false; + client[p] = new WiFiClient(); + return client[p]->connect(ip, MODBUSIP_PORT); } IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) - return client[n].remoteIP(); + return client[n]->remoteIP(); return INADDR_NONE; } TTransaction* ModbusIP::searchTransaction(uint16_t id) { - //TTransaction tmp; - //tmp.transactionId = id; - //tmp.timestamp = 0; - //tmp.cb = nullptr; - //tmp._frame = nullptr; - //std::vector::iterator it = std::find(_trans.begin(), _trans.end(), tmp); std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); if (it != _trans.end()) return &*it; return nullptr; @@ -65,10 +52,10 @@ void ModbusIP::task() { cleanup(); if (server) { while (server->hasClient()) { - WiFiClient currentClient = server->available(); - if (currentClient && !currentClient.connected()) + WiFiClient* currentClient = new WiFiClient(server->available()); + if (!currentClient || !currentClient->connected()) continue; - if (cbConnect == nullptr || cbConnect(currentClient.remoteIP())) { + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { n = getFreeClient(); if (n > -1) { client[n] = currentClient; @@ -76,39 +63,39 @@ void ModbusIP::task() { } } // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached - currentClient.flush(); - currentClient.stop(); - //delete currentClient; + currentClient->flush(); + currentClient->stop(); + delete currentClient; } } for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - if (!client[n].connected()) - continue; // for (n) - if (client[n].available() < sizeof(_MBAP) + 1) - continue; - client[n].readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP + if (!client[n]) continue; + if (!client[n]->connected()) continue; + if (client[n]->available() < sizeof(_MBAP) + 1) continue; + + client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP _len = __bswap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. - client[n].flush(); + client[n]->flush(); continue; // for (n) } if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME - exceptionResponse((FunctionCode)client[n].read(), EX_SLAVE_FAILURE); - client[n].flush(); + exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + client[n]->flush(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { - exceptionResponse((FunctionCode)client[n].read(), EX_SLAVE_FAILURE); - client[n].flush(); + exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + client[n]->flush(); } else { - if (client[n].readBytes(_frame, _len) < _len) { //Try to read MODBUS frame + if (client[n]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - client[n].flush(); + client[n]->flush(); } else { - if (client[n].localPort() == MODBUSIP_PORT) { + if (client[n]->localPort() == MODBUSIP_PORT) { // Process incoming frame as slave slavePDU(_frame); } else { @@ -132,18 +119,18 @@ void ModbusIP::task() { _trans.erase(it); } } - client[n].flush(); // Not sure if we need flush rest of data available + client[n]->flush(); // Not sure if we need flush rest of data available } } } - if (client[n].localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master + if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master if (_reply != REPLY_OFF) { _MBAP.length = __bswap_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); - client[n].write(sbuf, send_len); + client[n]->write(sbuf, send_len); } free(_frame); _frame = nullptr; @@ -152,13 +139,14 @@ void ModbusIP::task() { n = -1; } -uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data) { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data + // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data +uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { #ifdef MODBUSIP_MAX_TRANSACIONS if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; #endif int8_t p = getSlave(ip); - if (p == -1 || !client[p].connected()) + if (p == -1 || !client[p]->connected()) return false; transactionId++; if (!transactionId) transactionId = 1; @@ -170,17 +158,19 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 uint8_t sbuf[send_len]; memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); - if (client[p].write(sbuf, send_len) != send_len) + if (client[p]->write(sbuf, send_len) != send_len) return false; - TTransaction tmp; - tmp.transactionId = transactionId; - tmp.timestamp = millis(); - tmp.cb = cb; - tmp.data = data; - tmp._frame = _frame; - tmp.startreg = startreg; - _trans.push_back(tmp); - _frame = nullptr; + if (waitResponse) { + TTransaction tmp; + tmp.transactionId = transactionId; + tmp.timestamp = millis(); + tmp.cb = cb; + tmp.data = data; + tmp._frame = _frame; + tmp.startreg = startreg; + _trans.push_back(tmp); + _frame = nullptr; + } return transactionId; } @@ -204,13 +194,12 @@ bool ifExpired(TTransaction& t) { } void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - if (!client[i].connected()) { - //IPAddress ip = client[i].remoteIP(); - //client[i].stop(); - //delete client[i]; - //client[i] = nullptr; - //if (cbDisconnect && cbEnabled) - // cbDisconnect(ip); + if (client[i] && !client[i]->connected()) { + IPAddress ip = client[i]->remoteIP(); + delete client[i]; + client[i] = nullptr; + if (cbDisconnect && cbEnabled) + cbDisconnect(ip); } } //_trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); @@ -228,69 +217,69 @@ void ModbusIP::cleanup() { // Free clients if not connected and remove timedout int8_t ModbusIP::getFreeClient() { // Returns free slot position //clientsCleanup(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (!client[i].connected()) + if (!client[i]) return i; return -1; } int8_t ModbusIP::getSlave(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i].connected() && client[i].remoteIP() == ip && client[i].localPort() != MODBUSIP_PORT) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) return i; return -1; } int8_t ModbusIP::getMaster(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i].connected() && client[i].remoteIP() == ip && client[i].localPort() == MODBUSIP_PORT) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == MODBUSIP_PORT) return i; return -1; } uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb, uint8_t unit) { readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(ip, COIL(offset), cb, unit); + return send(ip, COIL(offset), cb, unit, nullptr, cb); } uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(ip, COIL(offset), cb, unit); + return send(ip, COIL(offset), cb, unit, nullptr, cb); } uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_COILS); - return send(ip, COIL(offset), cb, unit, value); + return send(ip, COIL(offset), cb, unit, value, cb); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb, uint8_t unit) { readSlave(offset, value, FC_WRITE_REG); - return send(ip, HREG(offset), cb, unit); + return send(ip, HREG(offset), cb, unit, nullptr, cb); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(ip, HREG(offset), cb, unit); + return send(ip, HREG(offset), cb, unit, nullptr, cb); } uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_REGS); - return send(ip, HREG(offset), cb, unit, value); + return send(ip, HREG(offset), cb, unit, value, cb); } uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(offset), cb, unit, value); + return send(ip, ISTS(offset), cb, unit, value, cb); } uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(offset), cb, unit, value); + return send(ip, IREG(offset), cb, unit, value, cb); } uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { @@ -396,7 +385,7 @@ bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progre } bool ModbusIP::isConnected(IPAddress ip) { int8_t p = getSlave(ip); - return p != -1 && client[p].connected(); + return p != -1 && client[p]->connected(); } uint16_t ModbusIP::transactions() { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index c52b919..a05f4ef 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -52,7 +52,7 @@ class ModbusIP : public Modbus { cbModbusConnect cbConnect = nullptr; cbModbusConnect cbDisconnect = nullptr; WiFiServer* server = nullptr; - WiFiClient client[MODBUSIP_MAX_CLIENTS]; + WiFiClient* client[MODBUSIP_MAX_CLIENTS]; std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; @@ -62,7 +62,7 @@ class ModbusIP : public Modbus { int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); - uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr); + uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); public: ModbusIP(); From a8753768a94fc514e5a4afb83605782a93ba6e53 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 6 Jan 2019 14:07:17 +0500 Subject: [PATCH 109/288] Add autoConnect for read/write/pull/push --- API.md | 6 ++++++ src/ModbusIP_ESP8266.cpp | 6 +++++- src/ModbusIP_ESP8266.h | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 13a0013..9faa69b 100644 --- a/API.md +++ b/API.md @@ -195,6 +195,12 @@ bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); ``` +```c +void autoConnect(bool enabled); +``` + +Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. + ### Callback example ```c diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 6015fc0..2a247dc 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -147,7 +147,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 #endif int8_t p = getSlave(ip); if (p == -1 || !client[p]->connected()) - return false; + return autoConnectMode?connect(ip):false; transactionId++; if (!transactionId) transactionId = 1; _MBAP.transactionId = __bswap_16(transactionId); @@ -390,4 +390,8 @@ bool ModbusIP::isConnected(IPAddress ip) { uint16_t ModbusIP::transactions() { return _trans.capacity(); +} + +void ModbusIP::autoConnect(bool enabled = true) { + autoConnectMode = enabled; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index a05f4ef..101d425 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -56,6 +56,7 @@ class ModbusIP : public Modbus { std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; + bool autoConnectMode = false; TTransaction* searchTransaction(uint16_t id); void cleanup(); // Free clients if not connected and remove timedout transactions @@ -78,6 +79,7 @@ class ModbusIP : public Modbus { void onConnect(cbModbusConnect cb = nullptr); void onDisconnect(cbModbusConnect cb = nullptr); IPAddress eventSource(); + void autoConnect(bool enabled = true); uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); From 8326ade2fd7d72b640469ec0b75191c55b94baaf Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 6 Jan 2019 14:09:47 +0500 Subject: [PATCH 110/288] Add autoConnect for read/write/pull/push --- src/ModbusIP_ESP8266.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 2a247dc..4ad5e9c 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -392,6 +392,6 @@ uint16_t ModbusIP::transactions() { return _trans.capacity(); } -void ModbusIP::autoConnect(bool enabled = true) { +void ModbusIP::autoConnect(bool enabled) { autoConnectMode = enabled; } \ No newline at end of file From 0a877cdaee7dc9773838134a13a305c188b8d091 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Jan 2019 13:11:26 +0500 Subject: [PATCH 111/288] 2.0 --- API.md | 518 ++++++------ README.md | 202 +++-- examples/Master/Master.ino | 150 ++-- .../MasterSimpleRead/MasterSimpleRead.ino | 116 +-- .../MultipleHRegDebug/MultipleHRegDebug.ino | 166 ++-- keywords.txt | 181 ++-- src/Modbus.h | 458 +++++----- src/ModbusIP_ESP8266.cpp | 788 +++++++++--------- src/ModbusIP_ESP8266.h | 205 +++-- 9 files changed, 1391 insertions(+), 1393 deletions(-) diff --git a/API.md b/API.md index 9faa69b..0625017 100644 --- a/API.md +++ b/API.md @@ -1,256 +1,262 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 - -## API - -### Add [multiple] regs - -```c -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 nemregs = 1); -``` - -### Write reg - -```c -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); -``` - -### Read reg - -```c -uint16_t Hreg(uint16_t offset); -bool Coil(uint16_t offset); -bool Ists(uint16_t offset); -uint16_t Ireg(uint16_t offset); -``` - -### Remove reg - -```c -bool removeHreg(uint16_t offset, uint16_t numregs = 1); -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); -``` - -### Query [multiple] regs from remote slave - -```c -uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -``` - -Result is saved to local registers. Method returns corresponding transaction id. [ip/from] - slave, [to] - local - -### Send [multiple] regs to remote slave - -```c -uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -``` - -Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local - -### Write [multiple] values to remote slave reg - -```c -uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -``` - -Write single value to remote Hreg/Coil. - -```c -uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -``` - -Write multiple values from array to remote Coil/Hreg. - -### Read values from multiple remote slave regs - -```c -uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -``` - -Read values from remote Hreg/Coil/Ireg/Ists to array. - -### Callbacks - -```c -void cbEnable(bool state = TRUE); -void cbDisable(); -``` - -Callback generation control. Callback generation is enabled by default. *Has no effect on transactions callbacks.* - -```c -void onConnect(cbModbusConnect cb); -void onDisonnect(cbModbusConnect cb); -``` - -Assign callback function on new incoming connection event. - -```c -typedef bool (*cbModbusConnect)(IPAddress ip); -``` - -Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. - -```c -typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); -``` - -Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. - -```c -typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); -``` - -Transaction end callback function definition. *data* is currently reserved. - -```c -IPAddress eventSource(); -``` - -Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. - -*Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. - -```c -bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -``` - -Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. - - -```c -bool onGetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -bool onGetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); -``` - -Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. - -```c -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); -``` - -Disconnect specific callback function or all callbacks of the type if cb=NULL. - -### Macros - -```c -#define COIL_VAL(v) -#define COIL_BOOL(v) -#define ISTS_VAL(v) -#define ISTS_BOOL(v) -``` - -### ModBus IP specific - -```c -void task(); -``` - -### ModBus IP Slave specific - -```c -void begin(); // Depricated. Use slave() instead. -void slave(); -``` - -### ModBus IP Master specific - -```c -void master(); -bool connect(IPAddress ip); -bool disconnect(IPAddress ip); // Not implemented yet. -bool isTransaction(uint16_t id); -bool isConnected(IPAddress ip); -``` - -```c -void autoConnect(bool enabled); -``` - -Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. - -### Callback example - -```c -ModbusIP mb; -bool coil = false; // Define external variable to get/set value -uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg structure to modify, 'val' is new register value - Serial.print("Set query from "); - Serial.println(mb.eventSource().toString()); - coil = COIL_BOOL(val); // Assign value to external variable - return val; // Returns value to be saved to TRegister structure -} -uint16_t cbCoilGet(TRegister* reg, uint16_t val) { - Serial.print("Get query from "); - Serial.println(mb.eventSource().toString()); - return COIL_VAL(coil); // Override value to be returned to ModBus master as reply for current request -} -bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; // Return 'true' to allow connection or 'false' to drop connection -} -ModbusIP mb; // ModbusIP object -void setup() { -... - mb.onConnect(cbConn); // Add callback on connection event - mb.slave(); // Accept incoming Modbus connections - mb.addCoil(COIL_NR); // Add Coil - mb.onSetCoil(COIL_NR, cbCoilSet); // Add callback on Coil COIL_NR value set - mb.onGetCoil(COIL_NR, cbCoilGet); // Add callback on Coil COIL_NR value get -... -} -void loop() { -... - mb.task(); -... -} -``` - -## Contributions - -https://github.com/emelianov/modbus-esp8266 - -a.m.emelianov@gmail.com - -Original version: - -https://github.com/andresarmento/modbus-esp8266 -https://github.com/andresarmento/modbus-arduino - -prof (at) andresarmento (dot) com - -## License - -The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. +# Modbus Master-Slave Library for ESP8266/ESP32 + +## API + +### Add [multiple] regs + +```c +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 nemregs = 1); +``` + +### Write reg + +```c +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); +``` + +### Read reg + +```c +uint16_t Hreg(uint16_t offset); +bool Coil(uint16_t offset); +bool Ists(uint16_t offset); +uint16_t Ireg(uint16_t offset); +``` + +### Remove reg + +```c +bool removeHreg(uint16_t offset, uint16_t numregs = 1); +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); +``` + +### Query [multiple] regs from remote slave + +```c +uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +Result is saved to local registers. Method returns corresponding transaction id. [ip/from] or [ip/offset] - slave, [to] or [startreg] - local + +### Send [multiple] regs to remote slave + +```c +uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local + +### Write [multiple] values to remote slave reg + +```c +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +Write single value to remote Hreg/Coil. + +```c +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +Write multiple values from array to remote Coil/Hreg. + +### Read values from multiple remote slave regs + +```c +uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +Read values from remote Hreg/Coil/Ireg/Ists to array. + +```c +void autoConnect(bool enabled = true); +``` + +Set mode for automatic connect on read*\write*\push*\pull* calls. Disabled by default. + +### Callbacks + +```c +void cbEnable(bool state = TRUE); +void cbDisable(); +``` + +Callback generation control. Callback generation is enabled by default. *Has no effect on transactions callbacks.* + +```c +void onConnect(cbModbusConnect cb); +void onDisonnect(cbModbusConnect cb); +``` + +Assign callback function on new incoming connection event. + +```c +typedef bool (*cbModbusConnect)(IPAddress ip); +``` + +Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. + +```c +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); +``` + +Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. + +```c +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); +``` + +Transaction end callback function definition. *data* is currently reserved. + +```c +IPAddress eventSource(); +``` + +Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. + +*Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. + +```c +bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. + + +```c +bool onGetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. + +```c +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); +``` + +Disconnect specific callback function or all callbacks of the type if cb=NULL. + +### Macros + +```c +#define COIL_VAL(v) +#define COIL_BOOL(v) +#define ISTS_VAL(v) +#define ISTS_BOOL(v) +``` + +### ModBus IP specific + +```c +void task(); +``` + +### ModBus IP Slave specific + +```c +void begin(); // Depricated. Use slave() instead. +void slave(); +``` + +### ModBus IP Master specific + +```c +void master(); +bool connect(IPAddress ip); +bool disconnect(IPAddress ip); // Not implemented yet. +bool isTransaction(uint16_t id); +bool isConnected(IPAddress ip); +``` + +```c +void autoConnect(bool enabled); +``` + +Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. + +### Callback example + +```c +ModbusIP mb; +bool coil = false; // Define external variable to get/set value +uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg structure to modify, 'val' is new register value + Serial.print("Set query from "); + Serial.println(mb.eventSource().toString()); + coil = COIL_BOOL(val); // Assign value to external variable + return val; // Returns value to be saved to TRegister structure +} +uint16_t cbCoilGet(TRegister* reg, uint16_t val) { + Serial.print("Get query from "); + Serial.println(mb.eventSource().toString()); + return COIL_VAL(coil); // Override value to be returned to ModBus master as reply for current request +} +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; // Return 'true' to allow connection or 'false' to drop connection +} +ModbusIP mb; // ModbusIP object +void setup() { +... + mb.onConnect(cbConn); // Add callback on connection event + mb.slave(); // Accept incoming Modbus connections + mb.addCoil(COIL_NR); // Add Coil + mb.onSetCoil(COIL_NR, cbCoilSet); // Add callback on Coil COIL_NR value set + mb.onGetCoil(COIL_NR, cbCoilGet); // Add callback on Coil COIL_NR value get +... +} +void loop() { +... + mb.task(); +... +} +``` + +## Contributions + +https://github.com/emelianov/modbus-esp8266 + +a.m.emelianov@gmail.com + +Original version: + +https://github.com/andresarmento/modbus-esp8266 +https://github.com/andresarmento/modbus-arduino + +prof (at) andresarmento (dot) com + +## License + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/README.md b/README.md index de999b3..eed49c9 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,99 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 v2.0 - -# Status: Release Candidate - -*Be careful using current version in production. Stable version from 'releases' page is recommended.* - -This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol -used in industrial automation and can be used in other areas, such as home automation. - -The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). - -In the current version the library allows the ESP8266/ESP32 operate async as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: - -http://pt.wikipedia.org/wiki/Modbus -http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf -http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf -http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf - -## Features - -* Supported platforms are - * ESP8266 - * ESP32 -* Operates as - * slave - * master -* Fully async operations. No loop locks. -* Supports Modbus IP (TCP) -* Reply exception messages for all supported functions -* Modbus functions supported: - * 0x01 - Read Coils - * 0x02 - Read Input Status (Read Discrete Inputs) - * 0x03 - Read Holding Registers - * 0x04 - Read Input Registers - * 0x05 - Write Single Coil - * 0x06 - Write Single Register - * 0x0F - Write Multiple Coils - * 0x10 - Write Multiple Registers -* Callbacks for - * Master connect - * Master/Slave disconnect - * Read specific Register - * Write specific Register - * Slave transaction finish - -## Notes: - -1. When using Modbus IP the transport protocol is TCP (port 502). -2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) - -## Last Changes - -```diff -// Internal changes -+ Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. -+ Change object's list implementation to *std::vector* -+ Modbus class refactoring -+ ModbusIP networking code refactoring and error reporting -+ Global registers storage to share between multiple Modbus* instances -+ Move rest of implementations from Modbus.h -// Public API changes -+ Modbus master implementation -+ Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL -+ Back to marking private for onSet, onGet, addReg and Reg methods -+ Added callback-related eventSource method, onDisconnect and transaction result callbacks -+ Extend register addressing to 0..65535 -+ removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) -+ readCoil, readHreg, readIsts, readIreg -+ push\pullCoil, push\pullHreg, pullIsts, pullIreg -+ pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg -+ optimize code around std::vector processing -+ extend removeCoil/Hreg/... to remove multiple registers -+ multiple callbacks => memory usage optimization -+ add removeOnSetCoil\... methods -+ add read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id -// ToDo for 2.0 -- add examples -- code cleanup -// ToDo later -- ModbusSerial (over RS-485) -- Modbus Read/Write File Records function -- Modbus Write Mask Register function -- Modbus Serial line-specific functions -- Create destructor for ModbusIP -``` - -## Contributions - -https://github.com/emelianov/modbus-esp8266 - -a.m.emelianov@gmail.com - -Original version: - -https://github.com/andresarmento/modbus-esp8266 -https://github.com/andresarmento/modbus-arduino - -prof (at) andresarmento (dot) com - -## License - -The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. +# Modbus Master-Slave Library for ESP8266/ESP32 v2.0 + +This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol +used in industrial automation and can be used in other areas, such as home automation. + +The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). + +In the current version the library allows the ESP8266/ESP32 operate async as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: + +http://pt.wikipedia.org/wiki/Modbus +http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf +http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf +http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + +## Features + +* Supported platforms are + * ESP8266 + * ESP32 +* Operates as + * slave + * master +* Supports Modbus IP (TCP) +* Reply exception messages for all supported functions +* Modbus functions supported: + * 0x01 - Read Coils + * 0x02 - Read Input Status (Read Discrete Inputs) + * 0x03 - Read Holding Registers + * 0x04 - Read Input Registers + * 0x05 - Write Single Coil + * 0x06 - Write Single Register + * 0x0F - Write Multiple Coils + * 0x10 - Write Multiple Registers +* Callbacks for + * Master connect + * Master/Slave disconnect + * Read specific Register + * Write specific Register + * Slave transaction finish + +## Notes: + +1. When using Modbus IP the transport protocol is TCP (port 502). +2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. +3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) + +## Last Changes + +```diff +// Internal changes ++ Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. ++ Change object's list implementation to *std::vector* ++ Modbus class refactoring ++ ModbusIP networking code refactoring and error reporting ++ Global registers storage to share between multiple Modbus* instances ++ Move rest of implementations from Modbus.h +// Public API changes ++ Modbus master implementation ++ Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL ++ Back to marking private for onSet, onGet, addReg and Reg methods ++ Added callback-related eventSource method, onDisconnect and transaction result callbacks ++ Extend register addressing to 0..65535 ++ removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) ++ readCoil, readHreg, readIsts, readIreg ++ push\pullCoil, push\pullHreg, pullIsts, pullIreg ++ pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg ++ optimize code around std::vector processing ++ extend removeCoil/Hreg/... to remove multiple registers ++ multiple callbacks => memory usage optimization ++ added removeOnSetCoil\... methods ++ added read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id ++ added ability to auto connect to slave. Setting is global. Disabled by default. +// ToDo for 2.0 +- add examples +- code cleanup +// ToDo later +- ModbusSerial (over RS-485) +- Modbus Read/Write File Records function +- Modbus Write Mask Register function +- Modbus Serial line-specific functions +- Create destructor for ModbusIP +``` + +## Contributions + +https://github.com/emelianov/modbus-esp8266 + +a.m.emelianov@gmail.com + +Original version: + +https://github.com/andresarmento/modbus-esp8266 +https://github.com/andresarmento/modbus-arduino + +prof (at) andresarmento (dot) com + +## License + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 85ea99f..d128578 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -1,75 +1,75 @@ -/* - Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) - Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function - - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else - #include -#endif -#include - - -const int LED_COIL = 1; // Modbus Coil Offset (0-9999) -IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device - -//Used Pins -#ifdef ESP8266 - #define USE_LED D4 - #else - #define UES_LED TX - #endif - -ModbusIP mb; //ModbusIP object - -uint16_t gc(TRegister* r, uint16_t v) { // Callback function - if (r->value != v) { // Check if Coil state is going to be changed - Serial.print("Set reg: "); - Serial.println(v); - if (COIL_BOOL(v)) { - digitalWrite(USE_LED, LOW); - } else { - digitalWrite(USE_LED, HIGH); - } - } - return v; -} - -void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else - Serial.begin(115200); - #endif - - WiFi.begin("SSID", "password"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - - mb.master(); // Initialize local Modbus Master - pinMode(USE_LED, OUTPUT); - mb.addCoil(LED_COIL); // Add Coil - mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil -} - -void loop() { - if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established - mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave - } else { - mb.connect(remote); // Try to connect if no connection - } - mb.task(); // Common local Modbus task - delay(100); // Polling interval -} +/* + Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) + Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function + + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + + +const int LED_COIL = 1; // Modbus Coil Offset (0-9999) +IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device + +//Used Pins +#ifdef ESP8266 + #define USE_LED D4 + #else + #define UES_LED TX + #endif + +ModbusIP mb; //ModbusIP object + +uint16_t gc(TRegister* r, uint16_t v) { // Callback function + if (r->value != v) { // Check if Coil state is going to be changed + Serial.print("Set reg: "); + Serial.println(v); + if (COIL_BOOL(v)) { + digitalWrite(USE_LED, LOW); + } else { + digitalWrite(USE_LED, HIGH); + } + } + return v; +} + +void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif + + WiFi.begin("SSID", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.master(); // Initialize local Modbus Master + pinMode(USE_LED, OUTPUT); + mb.addCoil(LED_COIL); // Add Coil + mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil +} + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + } + mb.task(); // Common local Modbus task + delay(100); // Polling interval +} diff --git a/examples/MasterSimpleRead/MasterSimpleRead.ino b/examples/MasterSimpleRead/MasterSimpleRead.ino index 19822c5..32e4d08 100644 --- a/examples/MasterSimpleRead/MasterSimpleRead.ino +++ b/examples/MasterSimpleRead/MasterSimpleRead.ino @@ -1,59 +1,59 @@ -/* - Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) - Read Holding Register from Slave device - - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else - #include -#endif -#include - -const int REG = 528; // Modbus Hreg Offset (0-9999) -IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device -const int LOOP_COUNT = 10; - -ModbusIP mb; //ModbusIP object - -void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else - Serial.begin(115200); - #endif - - WiFi.begin("SSID", "PASSWORD"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - - mb.master(); -} - -uint16_t res = 0; -uint8_t show = LOOP_COUNT; - -void loop() { - if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established - mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave - } else { - mb.connect(remote); // Try to connect if no connection - } - mb.task(); // Common local Modbus task - delay(100); // Pulling interval - if (show--) { // Display Slave register value one time per second (with default settings) - Serial.println(res); - show = LOOP_COUNT; - } +/* + Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) + Read Holding Register from Slave device + + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +const int REG = 528; // Modbus Hreg Offset (0-9999) +IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device +const int LOOP_COUNT = 10; + +ModbusIP mb; //ModbusIP object + +void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif + + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.master(); +} + +uint16_t res = 0; +uint8_t show = LOOP_COUNT; + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + } + mb.task(); // Common local Modbus task + delay(100); // Pulling interval + if (show--) { // Display Slave register value one time per second (with default settings) + Serial.println(res); + show = LOOP_COUNT; + } } \ No newline at end of file diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/MultipleHRegDebug/MultipleHRegDebug.ino index 0a0650c..90d124e 100644 --- a/examples/MultipleHRegDebug/MultipleHRegDebug.ino +++ b/examples/MultipleHRegDebug/MultipleHRegDebug.ino @@ -1,83 +1,83 @@ -/* - Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) - - Original library - Copyright by André Sarmento Barbosa - http://github.com/andresarmento/modbus-arduino - - Current version - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else //ESP32 - #include -#endif -#include - -#define LEN 10 - -//ModbusIP object -ModbusIP mb; - -// Callback function to read corresponding DI -uint16_t cbRead(TRegister* reg, uint16_t val) { - Serial.print("Read. Reg RAW#: "); - Serial.print(reg->address); - Serial.print(" Old: "); - Serial.print(reg->value); - Serial.print(" New: "); - Serial.println(val); - return val; -} -// Callback function to write-protect DI -uint16_t cbWrite(TRegister* reg, uint16_t val) { - Serial.print("Write. Reg RAW#: "); - Serial.print(reg->address); - Serial.print(" Old: "); - Serial.print(reg->value); - Serial.print(" New: "); - Serial.println(val); - return val; -} - -// Callback function for client connect. Returns true to allow connection. -bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; -} - -void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else - Serial.begin(115200); - #endif - - WiFi.begin("ssid", "pass"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); - - if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs - mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get - mb.onSetHreg(0, cbWrite, LEN); -} - -void loop() { - //Call once inside loop() - all magic here - mb.task(); - delay(100); -} +/* + Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +#define LEN 10 + +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + Serial.print("Read. Reg RAW#: "); + Serial.print(reg->address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + Serial.print("Write. Reg RAW#: "); + Serial.print(reg->address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif + + WiFi.begin("ssid", "pass"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + + if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs + mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get + mb.onSetHreg(0, cbWrite, LEN); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(100); +} diff --git a/keywords.txt b/keywords.txt index 2e992a3..41e0fef 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,90 +1,91 @@ -# Syntax Coloring Map For ModbusIP-ESP8266 - -# Datatypes (KEYWORD1) -ModbusIP KEYWORD1 -Modbus KEYWORD1 -TRegister KEYWORD1 -TTransaction KEYWORD1 -TAddress KEYWORD1 - -# Methods and Functions (KEYWORD2) -master KEYWORD2 -slave KEYWORD2 -task KEYWORD2 -onConnect KEYWORD2 -onDisconnect KEYWORD2 -cbEnable KEYWORD2 -cbDisable KEYWORD2 -eventSource KEYWORD2 -onGetCoil KEYWORD2 -onSetCoil KEYWORD2 -onGetHreg KEYWORD2 -onSetHreg KEYWORD2 -onGetIreg KEYWORD2 -onSetIreg KEYWORD2 -onGetIsts KEYWORD2 -onSetIsts KEYWORD2 -removeOnGetCoil KEYWORD2 -removeOnSetCoil KEYWORD2 -removeOnGetHreg KEYWORD2 -removeOnSetHreg KEYWORD2 -removeOnGetIsts KEYWORD2 -removeOnSetIsts KEYWORD2 -removeOnGetIreg KEYWORD2 -removeOnSetIreg KEYWORD2 -addCoil KEYWORD2 -addIsts KEYWORD2 -addIreg KEYWORD2 -addHreg KEYWORD2 -Coil KEYWORD2 -Ists KEYWORD2 -Ireg KEYWORD2 -Hreg KEYWORD2 -isTransaction KEYWORD2 -isConnected KEYWORD2 -writeCoil KEYWORD2 -readCoil KEYWORD2 -writeHreg KEYWORD2 -readHreg KEYWORD2 -readIsts KEYWORD2 -readIreg KEYWORD2 -pushCoil KEYWORD2 -pullCoil KEYWORD2 -pullIsts KEYWORD2 -pushHreg KEYWORD2 -pullHreg KEYWORD2 -pullIreg KEYWORD2 -pullHregToIreg KEYWORD2 -pullCoilToIsts KEYWORD2 -pushIstsToCoil KEYWORD2 -pushIregToHreg KEYWORD2 -removeHreg KEYWORD2 -removeIreg KEYWORD2 -removeCoil KEYWORD2 -removeIsts KEYWORD2 - -# Constants and Macros (LITERAL1) -BIT_VAL LITERAL1 -BIT_BOOL LITERAL1 -COIL_VAL LITERAL1 -COIL_BOOL LITERAL1 -ISTS_VAL LITERAL1 -ISTS_BOOL LITERAL1 -ResultCode LITERAL1 -FunctionCode LITERAL1 -EX_SUCCESS LITERAL1 -EX_ILLEGAL_FUNCTION LITERAL1 -EX_ILLEGAL_ADDRESS LITERAL1 -EX_ILLEGAL_VALUE LITERAL1 -EX_SLAVE_FAILURE LITERAL1 -EX_ACKNOWLEDGE LITERAL1 -EX_SLAVE_DEVICE_BUSY LITERAL1 -EX_MEMORY_PARITY_ERROR LITERAL1 -EX_PATH_UNAVAILABLE LITERAL1 -EX_DEVICE_FAILED_TO_RESPOND LITERAL1 -EX_GENERAL_FAILURE LITERAL1 -EX_DATA_MISMACH LITERAL1 -EX_UNEXPECTED_RESPONSE LITERAL1 -EX_TIMEOUT LITERAL1 -EX_CONNECTION_LOST LITERAL1 - +# Syntax Coloring Map For ModbusIP-ESP8266 + +# Datatypes (KEYWORD1) +ModbusIP KEYWORD1 +Modbus KEYWORD1 +TRegister KEYWORD1 +TTransaction KEYWORD1 +TAddress KEYWORD1 + +# Methods and Functions (KEYWORD2) +master KEYWORD2 +slave KEYWORD2 +task KEYWORD2 +onConnect KEYWORD2 +onDisconnect KEYWORD2 +cbEnable KEYWORD2 +cbDisable KEYWORD2 +eventSource KEYWORD2 +onGetCoil KEYWORD2 +onSetCoil KEYWORD2 +onGetHreg KEYWORD2 +onSetHreg KEYWORD2 +onGetIreg KEYWORD2 +onSetIreg KEYWORD2 +onGetIsts KEYWORD2 +onSetIsts KEYWORD2 +removeOnGetCoil KEYWORD2 +removeOnSetCoil KEYWORD2 +removeOnGetHreg KEYWORD2 +removeOnSetHreg KEYWORD2 +removeOnGetIsts KEYWORD2 +removeOnSetIsts KEYWORD2 +removeOnGetIreg KEYWORD2 +removeOnSetIreg KEYWORD2 +addCoil KEYWORD2 +addIsts KEYWORD2 +addIreg KEYWORD2 +addHreg KEYWORD2 +Coil KEYWORD2 +Ists KEYWORD2 +Ireg KEYWORD2 +Hreg KEYWORD2 +isTransaction KEYWORD2 +isConnected KEYWORD2 +writeCoil KEYWORD2 +readCoil KEYWORD2 +writeHreg KEYWORD2 +readHreg KEYWORD2 +readIsts KEYWORD2 +readIreg KEYWORD2 +pushCoil KEYWORD2 +pullCoil KEYWORD2 +pullIsts KEYWORD2 +pushHreg KEYWORD2 +pullHreg KEYWORD2 +pullIreg KEYWORD2 +pullHregToIreg KEYWORD2 +pullCoilToIsts KEYWORD2 +pushIstsToCoil KEYWORD2 +pushIregToHreg KEYWORD2 +removeHreg KEYWORD2 +removeIreg KEYWORD2 +removeCoil KEYWORD2 +removeIsts KEYWORD2 +autoConnect KEYWORD2 + +# Constants and Macros (LITERAL1) +BIT_VAL LITERAL1 +BIT_BOOL LITERAL1 +COIL_VAL LITERAL1 +COIL_BOOL LITERAL1 +ISTS_VAL LITERAL1 +ISTS_BOOL LITERAL1 +ResultCode LITERAL1 +FunctionCode LITERAL1 +EX_SUCCESS LITERAL1 +EX_ILLEGAL_FUNCTION LITERAL1 +EX_ILLEGAL_ADDRESS LITERAL1 +EX_ILLEGAL_VALUE LITERAL1 +EX_SLAVE_FAILURE LITERAL1 +EX_ACKNOWLEDGE LITERAL1 +EX_SLAVE_DEVICE_BUSY LITERAL1 +EX_MEMORY_PARITY_ERROR LITERAL1 +EX_PATH_UNAVAILABLE LITERAL1 +EX_DEVICE_FAILED_TO_RESPOND LITERAL1 +EX_GENERAL_FAILURE LITERAL1 +EX_DATA_MISMACH LITERAL1 +EX_UNEXPECTED_RESPONSE LITERAL1 +EX_TIMEOUT LITERAL1 +EX_CONNECTION_LOST LITERAL1 + diff --git a/src/Modbus.h b/src/Modbus.h index 2b3e088..51f0f35 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,230 +1,230 @@ -/* - Modbuc.h - Header for Modbus Base Library - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#pragma once - -#include "Arduino.h" -#include -#include -#ifndef ESP8266 - #include -#endif - -#ifndef __bswap_16 - #define __bswap_16(num) ((uint16_t)num>>8) | ((uint16_t)num<<8) -#endif - - -//#define MB_GLOBAL_REGS -#define MB_MAX_REGS 32 -#define MB_MAX_FRAME 128 -#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 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; - -typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type -struct TAddress { - enum RegType {COIL, ISTS, IREG, HREG}; - RegType type; - uint16_t 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; - //cbModbus get; - //cbModbus set; - 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_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) - FC_WRITE_REGS = 0x10, // Write block of contiguous registers - FC_READ_FILE_REC = 0x14, // Not implemented - FC_WRITE_FILE_REC = 0x15, // Not implemented - FC_MASKWRITE_REG = 0x16, // Not implemented - FC_READWRITE_REGS = 0x17 // Not implemented - }; - //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 Deice 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 - }; - ~Modbus(); - bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); - bool Hreg(uint16_t offset, uint16_t value); - uint16_t Hreg(uint16_t offset); - uint16_t removeHreg(uint16_t offset, 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 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); - 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); - - void cbEnable(bool state = true); - void cbDisable(); - - 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); - - private: - void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); - void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); - - void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); - void setMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numoutputs); - - void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); - void getMultipleWords(uint8_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 - }; - #ifndef MB_GLOBAL_REGS - std::vector _regs; - std::vector _callbacks; - #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); - TRegister* searchRegister(TAddress addr); - void exceptionResponse(FunctionCode fn, ResultCode excode); - void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); - void slavePDU(uint8_t* frame); //For Slave - void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output= nullptr); //For Master - // frame - data received form slave - // sourceFrame - data 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 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 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); +/* + Modbuc.h - Header for Modbus Base Library + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once + +#include "Arduino.h" +#include +#include +#ifndef ESP8266 + #include +#endif + +#ifndef __bswap_16 + #define __bswap_16(num) ((uint16_t)num>>8) | ((uint16_t)num<<8) +#endif + + +//#define MB_GLOBAL_REGS +#define MB_MAX_REGS 32 +#define MB_MAX_FRAME 128 +#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 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; + +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type +struct TAddress { + enum RegType {COIL, ISTS, IREG, HREG}; + RegType type; + uint16_t 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; + //cbModbus get; + //cbModbus set; + 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_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) + FC_WRITE_REGS = 0x10, // Write block of contiguous registers + FC_READ_FILE_REC = 0x14, // Not implemented + FC_WRITE_FILE_REC = 0x15, // Not implemented + FC_MASKWRITE_REG = 0x16, // Not implemented + FC_READWRITE_REGS = 0x17 // Not implemented + }; + //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 Deice 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 + }; + ~Modbus(); + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); + bool Hreg(uint16_t offset, uint16_t value); + uint16_t Hreg(uint16_t offset); + uint16_t removeHreg(uint16_t offset, 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 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); + 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); + + void cbEnable(bool state = true); + void cbDisable(); + + 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); + + private: + void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); + void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); + + void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); + void setMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numoutputs); + + void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); + void getMultipleWords(uint8_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 + }; + #ifndef MB_GLOBAL_REGS + std::vector _regs; + std::vector _callbacks; + #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); + TRegister* searchRegister(TAddress addr); + void exceptionResponse(FunctionCode fn, ResultCode excode); + void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); + void slavePDU(uint8_t* frame); //For Slave + void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output= nullptr); //For Master + // frame - data received form slave + // sourceFrame - data 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 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 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); }; \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 4ad5e9c..c80e947 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -1,397 +1,393 @@ -/* - ModbusIP_ESP8266.cpp - ModbusIP Library Implementation - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#include "ModbusIP_ESP8266.h" - -ModbusIP::ModbusIP() { - _trans.reserve(MODBUSIP_MAX_TRANSACIONS); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - client[i] = nullptr; -} - -void ModbusIP::master() { - -} - -void ModbusIP::slave() { - server = new WiFiServer(MODBUSIP_PORT); - server->begin(); -} - -void ModbusIP::begin() { - slave(); -} - -bool ModbusIP::connect(IPAddress ip) { - //cleanup(); - if(getSlave(ip) != -1) - return true; - int8_t p = getFreeClient(); - if (p == -1) - return false; - client[p] = new WiFiClient(); - return client[p]->connect(ip, MODBUSIP_PORT); -} - -IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query - if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) - return client[n]->remoteIP(); - return INADDR_NONE; -} - -TTransaction* ModbusIP::searchTransaction(uint16_t id) { - std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); - if (it != _trans.end()) return &*it; - return nullptr; -} - - -void ModbusIP::task() { - cleanup(); - if (server) { - while (server->hasClient()) { - WiFiClient* currentClient = new WiFiClient(server->available()); - if (!currentClient || !currentClient->connected()) - continue; - if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { - n = getFreeClient(); - if (n > -1) { - client[n] = currentClient; - continue; // while - } - } - // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached - currentClient->flush(); - currentClient->stop(); - delete currentClient; - } - } - for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - if (!client[n]) continue; - if (!client[n]->connected()) continue; - if (client[n]->available() < sizeof(_MBAP) + 1) continue; - - client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP - _len = __bswap_16(_MBAP.length); - _len--; // Do not count with last byte from MBAP - - if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. - client[n]->flush(); - continue; // for (n) - } - if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME - exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->flush(); - } else { - free(_frame); - _frame = (uint8_t*) malloc(_len); - if (!_frame) { - exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->flush(); - } else { - if (client[n]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame - exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - client[n]->flush(); - } else { - if (client[n]->localPort() == MODBUSIP_PORT) { - // Process incoming frame as slave - slavePDU(_frame); - } else { - // Process reply to master request - _reply = EX_SUCCESS; - TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); - if (trans) { // if valid transaction id - if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested - // Procass incoming frame as master - masterPDU(_frame, trans->_frame, trans->startreg, trans->data); - } else { - _reply = EX_UNEXPECTED_RESPONSE; - } - if (cbEnabled && trans->cb) { - trans->cb((ResultCode)_reply, trans->transactionId, nullptr); - } - free(trans->_frame); - //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); - std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); - if (it != _trans.end()) - _trans.erase(it); - } - } - client[n]->flush(); // Not sure if we need flush rest of data available - } - } - } - if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master - if (_reply != REPLY_OFF) { - _MBAP.length = __bswap_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); - client[n]->write(sbuf, send_len); - } - free(_frame); - _frame = nullptr; - _len = 0; - } - n = -1; -} - - // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data -uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { -#ifdef MODBUSIP_MAX_TRANSACIONS - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) - return false; -#endif - int8_t p = getSlave(ip); - if (p == -1 || !client[p]->connected()) - return autoConnectMode?connect(ip):false; - transactionId++; - if (!transactionId) transactionId = 1; - _MBAP.transactionId = __bswap_16(transactionId); - _MBAP.protocolId = __bswap_16(0); - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP - _MBAP.unitId = unit; - 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); - if (client[p]->write(sbuf, send_len) != send_len) - return false; - if (waitResponse) { - TTransaction tmp; - tmp.transactionId = transactionId; - tmp.timestamp = millis(); - tmp.cb = cb; - tmp.data = data; - tmp._frame = _frame; - tmp.startreg = startreg; - _trans.push_back(tmp); - _frame = nullptr; - } - return transactionId; -} - - -void ModbusIP::onConnect(cbModbusConnect cb) { - cbConnect = cb; -} - -void ModbusIP::onDisconnect(cbModbusConnect cb) { - cbDisconnect = cb; -} - -bool ifExpired(TTransaction& t) { - if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { - //if (t.cb) - // t.cb(Modbus::EX_TIMEOUT, t.transactionId, nullptr); - //free(t._frame); - return true; - } - return false; -} -void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - if (client[i] && !client[i]->connected()) { - IPAddress ip = client[i]->remoteIP(); - delete client[i]; - client[i] = nullptr; - if (cbDisconnect && cbEnabled) - cbDisconnect(ip); - } - } - //_trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); - std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), ifExpired); - while (it != _trans.end()) { - if (it->cb) - it->cb(Modbus::EX_TIMEOUT, it->transactionId, nullptr); - free(it->_frame); - _trans.erase(it); - it = std::find_if(it, _trans.end(), ifExpired); - } - -} - -int8_t ModbusIP::getFreeClient() { // Returns free slot position - //clientsCleanup(); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (!client[i]) - return i; - return -1; -} - -int8_t ModbusIP::getSlave(IPAddress ip) { - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) - return i; - return -1; -} - -int8_t ModbusIP::getMaster(IPAddress ip) { - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == MODBUSIP_PORT) - return i; - return -1; -} - -uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb, uint8_t unit) { - readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(ip, COIL(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(ip, COIL(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(offset, numregs, FC_READ_COILS); - return send(ip, COIL(offset), cb, unit, value, cb); -} - -uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb, uint8_t unit) { - readSlave(offset, value, FC_WRITE_REG); - return send(ip, HREG(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(ip, HREG(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(offset, numregs, FC_READ_REGS); - return send(ip, HREG(offset), cb, unit, value, cb); -} - -uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(offset), cb, unit, value, cb); -} - -uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(offset), cb, unit, value, cb); -} - -uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - if (!searchRegister(COIL(from))) return false; - if (numregs == 1) { - readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); - } - return send(ip, COIL(from), cb, unit); -} - -uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - #ifdef MODBUSIP_ADD_REG - addCoil(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(ip, COIL(to), cb, unit); -} - -uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - #ifdef MODBUSIP_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(to), cb, unit); -} - -uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - if (!searchRegister(HREG(from))) return false; - if (numregs == 1) { - readSlave(to, Hreg(from), FC_WRITE_REG); - } else { - writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); - } - return send(ip, HREG(from), cb, unit); -} - -uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - #ifdef MODBUSIP_ADD_REG - addHreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(ip, HREG(to), cb, unit); -} - -uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - #ifdef MODBUSIP_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(to), cb, unit); -} - -uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - if (!searchRegister(IREG(from))) return false; - if (numregs == 1) { - readSlave(to, Ireg(from), FC_WRITE_REG); - } else { - writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); - } - return send(ip, IREG(from), cb, unit); -} - -uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - if (!searchRegister(ISTS(from))) return false; - if (numregs == 1) { - readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); - } - return send(ip, ISTS(from), cb, unit); -} - -uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - #ifdef MODBUSIP_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(ip, IREG(to), cb, unit); -} - -uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - #ifdef MODBUSIP_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(ip, ISTS(to), cb, unit); -} - -bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) - return searchTransaction(id) != nullptr; -} -bool ModbusIP::isConnected(IPAddress ip) { - int8_t p = getSlave(ip); - return p != -1 && client[p]->connected(); -} - -uint16_t ModbusIP::transactions() { - return _trans.capacity(); -} - -void ModbusIP::autoConnect(bool enabled) { - autoConnectMode = enabled; +/* + ModbusIP_ESP8266.cpp - ModbusIP Library Implementation + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#include "ModbusIP_ESP8266.h" + +ModbusIP::ModbusIP() { + _trans.reserve(MODBUSIP_MAX_TRANSACIONS); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + client[i] = nullptr; +} + +void ModbusIP::master() { + +} + +void ModbusIP::slave() { + server = new WiFiServer(MODBUSIP_PORT); + server->begin(); +} + +void ModbusIP::begin() { + slave(); +} + +bool ModbusIP::connect(IPAddress ip) { + //cleanup(); + if(getSlave(ip) != -1) + return true; + int8_t p = getFreeClient(); + if (p == -1) + return false; + client[p] = new WiFiClient(); + return client[p]->connect(ip, MODBUSIP_PORT); +} + +IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query + if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) + return client[n]->remoteIP(); + return INADDR_NONE; +} + +TTransaction* ModbusIP::searchTransaction(uint16_t id) { + std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); + if (it != _trans.end()) return &*it; + return nullptr; +} + + +void ModbusIP::task() { + cleanup(); + if (server) { + while (server->hasClient()) { + WiFiClient* currentClient = new WiFiClient(server->available()); + if (!currentClient || !currentClient->connected()) + continue; + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + n = getFreeClient(); + if (n > -1) { + client[n] = currentClient; + continue; // while + } + } + // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached + currentClient->flush(); + currentClient->stop(); + delete currentClient; + } + } + for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + if (!client[n]) continue; + if (!client[n]->connected()) continue; + if (client[n]->available() < sizeof(_MBAP) + 1) continue; + + client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP + _len = __bswap_16(_MBAP.length); + _len--; // Do not count with last byte from MBAP + + if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. + client[n]->flush(); + continue; // for (n) + } + if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME + exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + client[n]->flush(); + } else { + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + client[n]->flush(); + } else { + if (client[n]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame + exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); + client[n]->flush(); + } else { + if (client[n]->localPort() == MODBUSIP_PORT) { + // Process incoming frame as slave + slavePDU(_frame); + } else { + // Process reply to master request + _reply = EX_SUCCESS; + TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); + if (trans) { // if valid transaction id + if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + masterPDU(_frame, trans->_frame, trans->startreg, trans->data); + } else { + _reply = EX_UNEXPECTED_RESPONSE; + } + if (cbEnabled && trans->cb) { + trans->cb((ResultCode)_reply, trans->transactionId, nullptr); + } + free(trans->_frame); + //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); + if (it != _trans.end()) + _trans.erase(it); + } + } + client[n]->flush(); // Not sure if we need flush rest of data available + } + } + } + if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master + if (_reply != REPLY_OFF) { + _MBAP.length = __bswap_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); + client[n]->write(sbuf, send_len); + } + free(_frame); + _frame = nullptr; + _len = 0; + } + n = -1; +} + + // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data +uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { +#ifdef MODBUSIP_MAX_TRANSACIONS + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) + return false; +#endif + int8_t p = getSlave(ip); + if (p == -1 || !client[p]->connected()) + return autoConnectMode?connect(ip):false; + transactionId++; + if (!transactionId) transactionId = 1; + _MBAP.transactionId = __bswap_16(transactionId); + _MBAP.protocolId = __bswap_16(0); + _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.unitId = unit; + 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); + if (client[p]->write(sbuf, send_len) != send_len) + return false; + if (waitResponse) { + TTransaction tmp; + tmp.transactionId = transactionId; + tmp.timestamp = millis(); + tmp.cb = cb; + tmp.data = data; + tmp._frame = _frame; + tmp.startreg = startreg; + _trans.push_back(tmp); + _frame = nullptr; + } + return transactionId; +} + + +void ModbusIP::onConnect(cbModbusConnect cb) { + cbConnect = cb; +} + +void ModbusIP::onDisconnect(cbModbusConnect cb) { + cbDisconnect = cb; +} + +bool ifExpired(TTransaction& t) { + if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { + //if (t.cb) + // t.cb(Modbus::EX_TIMEOUT, t.transactionId, nullptr); + //free(t._frame); + return true; + } + return false; +} +void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + if (client[i] && !client[i]->connected()) { + IPAddress ip = client[i]->remoteIP(); + delete client[i]; + client[i] = nullptr; + if (cbDisconnect && cbEnabled) + cbDisconnect(ip); + } + } + //_trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); + std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), ifExpired); + while (it != _trans.end()) { + if (it->cb) + it->cb(Modbus::EX_TIMEOUT, it->transactionId, nullptr); + free(it->_frame); + _trans.erase(it); + it = std::find_if(it, _trans.end(), ifExpired); + } + +} + +int8_t ModbusIP::getFreeClient() { // Returns free slot position + //clientsCleanup(); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (!client[i]) + return i; + return -1; +} + +int8_t ModbusIP::getSlave(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) + return i; + return -1; +} + +int8_t ModbusIP::getMaster(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == MODBUSIP_PORT) + return i; + return -1; +} + +uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb, uint8_t unit) { + readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); + return send(ip, COIL(offset), cb, unit, nullptr, cb); +} + +uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); + return send(ip, COIL(offset), cb, unit, nullptr, cb); +} + +uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_COILS); + return send(ip, COIL(offset), cb, unit, value, cb); +} + +uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb, uint8_t unit) { + readSlave(offset, value, FC_WRITE_REG); + return send(ip, HREG(offset), cb, unit, nullptr, cb); +} + +uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); + return send(ip, HREG(offset), cb, unit, nullptr, cb); +} + +uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_REGS); + return send(ip, HREG(offset), cb, unit, value, cb); +} + +uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_INPUT_STAT); + return send(ip, ISTS(offset), cb, unit, value, cb); +} + +uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_INPUT_REGS); + return send(ip, IREG(offset), cb, unit, value, cb); +} + +uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(COIL(from))) return false; + if (numregs == 1) { + readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); + } else { + writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); + } + return send(ip, COIL(from), cb, unit); +} + +uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addCoil(to, numregs); + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(ip, COIL(to), cb, unit); +} + +uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addIsts(to, numregs); + #endif + readSlave(from, numregs, FC_READ_INPUT_STAT); + return send(ip, ISTS(to), cb, unit); +} + +uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(HREG(from))) return false; + if (numregs == 1) { + readSlave(to, Hreg(from), FC_WRITE_REG); + } else { + writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); + } + return send(ip, HREG(from), cb, unit); +} + +uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addHreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(ip, HREG(to), cb, unit); +} + +uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addIreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_INPUT_REGS); + return send(ip, IREG(to), cb, unit); +} + +uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(IREG(from))) return false; + if (numregs == 1) { + readSlave(to, Ireg(from), FC_WRITE_REG); + } else { + writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); + } + return send(ip, IREG(from), cb, unit); +} + +uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(ISTS(from))) return false; + if (numregs == 1) { + readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); + } else { + writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); + } + return send(ip, ISTS(from), cb, unit); +} + +uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addIreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(ip, IREG(to), cb, unit); +} + +uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSIP_ADD_REG + addIsts(to, numregs); + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(ip, ISTS(to), cb, unit); +} + +bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) + return searchTransaction(id) != nullptr; +} +bool ModbusIP::isConnected(IPAddress ip) { + int8_t p = getSlave(ip); + return p != -1 && client[p]->connected(); +} + +void ModbusIP::autoConnect(bool enabled) { + autoConnectMode = enabled; } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 101d425..7cc91e8 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,104 +1,103 @@ -/* - ModbusIP_ESP8266.h - Header for ModbusIP Library - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#pragma once - -#include -#ifdef ESP8266 - #include -#else - #include -#endif - -#define MODBUSIP_PORT 502 -#define MODBUSIP_MAXFRAME 200 -#define MODBUSIP_TIMEOUT 1000 -#define MODBUSIP_UNIT 255 -#define MODBUSIP_MAX_TRANSACIONS 16 -#define MODBUSIP_MAX_CLIENTS 4 -#define MODBUSIP_ADD_REG 1 - -// Callback function Type -typedef bool (*cbModbusConnect)(IPAddress ip); - -typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); - -typedef struct TTransaction { - uint16_t transactionId; - uint32_t timestamp; - cbTransaction cb = nullptr; - uint8_t* _frame = nullptr; - void* data = nullptr; - TAddress startreg; - bool operator ==(const TTransaction &obj) const { - return transactionId == obj.transactionId; - } -}; - -class ModbusIP : public Modbus { - protected: - typedef union MBAP_t { - struct { - uint16_t transactionId; - uint16_t protocolId; - uint16_t length; - uint8_t unitId; - }; - uint8_t raw[7]; - }; - MBAP_t _MBAP; - cbModbusConnect cbConnect = nullptr; - cbModbusConnect cbDisconnect = nullptr; - WiFiServer* server = nullptr; - WiFiClient* client[MODBUSIP_MAX_CLIENTS]; - std::vector _trans; - int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. - int8_t n = -1; - bool autoConnectMode = false; - - TTransaction* searchTransaction(uint16_t id); - void cleanup(); // Free clients if not connected and remove timedout transactions - int8_t getFreeClient(); // Returns free slot position - int8_t getSlave(IPAddress ip); - int8_t getMaster(IPAddress ip); - uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); - - public: - ModbusIP(); - bool isTransaction(uint16_t id); - bool isConnected(IPAddress ip); - bool connect(IPAddress ip); - bool disconnect(IPAddress addr) {} // Not implemented yet - void slave(); - void master(); - void task(); - void begin(); // Depricated - uint16_t transactions(); - void onConnect(cbModbusConnect cb = nullptr); - void onDisconnect(cbModbusConnect cb = nullptr); - IPAddress eventSource(); - void autoConnect(bool enabled = true); - - uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - - uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - - uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +/* + ModbusIP_ESP8266.h - Header for ModbusIP Library + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once + +#include +#ifdef ESP8266 + #include +#else + #include +#endif + +#define MODBUSIP_PORT 502 +#define MODBUSIP_MAXFRAME 200 +#define MODBUSIP_TIMEOUT 1000 +#define MODBUSIP_UNIT 255 +#define MODBUSIP_MAX_TRANSACIONS 16 +#define MODBUSIP_MAX_CLIENTS 4 +#define MODBUSIP_ADD_REG 1 + +// Callback function Type +typedef bool (*cbModbusConnect)(IPAddress ip); + +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); + +typedef struct TTransaction { + uint16_t transactionId; + uint32_t timestamp; + cbTransaction cb = nullptr; + uint8_t* _frame = nullptr; + void* data = nullptr; + TAddress startreg; + bool operator ==(const TTransaction &obj) const { + return transactionId == obj.transactionId; + } +}; + +class ModbusIP : public Modbus { + protected: + typedef union MBAP_t { + struct { + uint16_t transactionId; + uint16_t protocolId; + uint16_t length; + uint8_t unitId; + }; + uint8_t raw[7]; + }; + MBAP_t _MBAP; + cbModbusConnect cbConnect = nullptr; + cbModbusConnect cbDisconnect = nullptr; + WiFiServer* server = nullptr; + WiFiClient* client[MODBUSIP_MAX_CLIENTS]; + std::vector _trans; + int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. + int8_t n = -1; + bool autoConnectMode = false; + + TTransaction* searchTransaction(uint16_t id); + void cleanup(); // Free clients if not connected and remove timedout transactions + int8_t getFreeClient(); // Returns free slot position + int8_t getSlave(IPAddress ip); + int8_t getMaster(IPAddress ip); + uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); + + public: + ModbusIP(); + bool isTransaction(uint16_t id); + bool isConnected(IPAddress ip); + bool connect(IPAddress ip); + bool disconnect(IPAddress addr) {} // Not implemented yet + void slave(); + void master(); + void task(); + void begin(); // Depricated + void onConnect(cbModbusConnect cb = nullptr); + void onDisconnect(cbModbusConnect cb = nullptr); + IPAddress eventSource(); + void autoConnect(bool enabled = true); + + uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); }; \ No newline at end of file From a691ae9dbdda87413a20c0927ba050753dfbb418 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Jan 2019 15:17:34 +0500 Subject: [PATCH 112/288] Change comments --- examples/AnalogInput/AnalogInput.ino | 2 +- examples/Callback/Callback.ino | 2 +- examples/HoldingReg/HoldingReg.ino | 2 +- examples/Led/Led.ino | 2 +- examples/Master/Master.ino | 2 +- examples/MasterSimpleRead/MasterSimpleRead.ino | 2 +- examples/SwitchStatus/SwitchStatus.ino | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/AnalogInput/AnalogInput.ino b/examples/AnalogInput/AnalogInput.ino index ae77b35..9495683 100644 --- a/examples/AnalogInput/AnalogInput.ino +++ b/examples/AnalogInput/AnalogInput.ino @@ -17,7 +17,7 @@ #endif #include -//Modbus Registers Offsets (0-9999) +//Modbus Registers Offsets const int SENSOR_IREG = 100; //ModbusIP object diff --git a/examples/Callback/Callback.ino b/examples/Callback/Callback.ino index 058337b..9eca24c 100644 --- a/examples/Callback/Callback.ino +++ b/examples/Callback/Callback.ino @@ -17,7 +17,7 @@ #endif #include -//Modbus Registers Offsets (0-65535) +//Modbus Registers Offsets const int LED_COIL = 100; //Used Pins #ifdef ESP8266 diff --git a/examples/HoldingReg/HoldingReg.ino b/examples/HoldingReg/HoldingReg.ino index 93a09c9..6b791b2 100644 --- a/examples/HoldingReg/HoldingReg.ino +++ b/examples/HoldingReg/HoldingReg.ino @@ -18,7 +18,7 @@ #endif #include -// Modbus Registers Offsets (0-9999) +// Modbus Registers Offsets const int TEST_HREG = 100; diff --git a/examples/Led/Led.ino b/examples/Led/Led.ino index 666392a..678fcf0 100644 --- a/examples/Led/Led.ino +++ b/examples/Led/Led.ino @@ -17,7 +17,7 @@ #endif #include -//Modbus Registers Offsets (0-9999) +//Modbus Registers Offsets const int LED_COIL = 100; //Used Pins const int ledPin = 0; //GPIO0 diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index d128578..00396f7 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -14,7 +14,7 @@ #include -const int LED_COIL = 1; // Modbus Coil Offset (0-9999) +const int LED_COIL = 1; // Modbus Coil Offset IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device //Used Pins diff --git a/examples/MasterSimpleRead/MasterSimpleRead.ino b/examples/MasterSimpleRead/MasterSimpleRead.ino index 32e4d08..88ba43c 100644 --- a/examples/MasterSimpleRead/MasterSimpleRead.ino +++ b/examples/MasterSimpleRead/MasterSimpleRead.ino @@ -13,7 +13,7 @@ #endif #include -const int REG = 528; // Modbus Hreg Offset (0-9999) +const int REG = 528; // Modbus Hreg Offset IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device const int LOOP_COUNT = 10; diff --git a/examples/SwitchStatus/SwitchStatus.ino b/examples/SwitchStatus/SwitchStatus.ino index 5d9730a..6fc08ee 100644 --- a/examples/SwitchStatus/SwitchStatus.ino +++ b/examples/SwitchStatus/SwitchStatus.ino @@ -17,7 +17,7 @@ #endif #include -//Modbus Registers Offsets (0-9999) +//Modbus Registers Offsets const int SWITCH_ISTS = 100; //Used Pins const int switchPin = 0; //GPIO0 From da61893df1aa84c9b8571e108e83f6b4b5c6326b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Jan 2019 15:21:08 +0500 Subject: [PATCH 113/288] Docs changes --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eed49c9..9b7e163 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ used in industrial automation and can be used in other areas, such as home autom The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). -In the current version the library allows the ESP8266/ESP32 operate async as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: +In the current version the library allows the ESP8266/ESP32 operate as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf @@ -70,8 +70,8 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + added removeOnSetCoil\... methods + added read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id + added ability to auto connect to slave. Setting is global. Disabled by default. -// ToDo for 2.0 -- add examples +// ToDo for 2.0.1 +- modify examples - code cleanup // ToDo later - ModbusSerial (over RS-485) From c0394b55b1f1d8fce4403c31c95452418a622400 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Jan 2019 15:53:38 +0500 Subject: [PATCH 114/288] Fix examples --- examples/MassOperations/MassOperations.ino | 164 +++++++++--------- .../MultipleHRegDebug/MultipleHRegDebug.ino | 4 +- 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/examples/MassOperations/MassOperations.ino b/examples/MassOperations/MassOperations.ino index 829be2d..2efbb87 100644 --- a/examples/MassOperations/MassOperations.ino +++ b/examples/MassOperations/MassOperations.ino @@ -1,83 +1,83 @@ -/* - Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32) - - Original library - Copyright by André Sarmento Barbosa - http://github.com/andresarmento/modbus-arduino - - Current version - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else //ESP32 - #include -#endif -#include - -//Used Pins -#ifdef ESP8266 - uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; -#else //ESP32 - uint8_t pinList[] = {12, 13, 14, 14, 16, 17, 18, 21, 22, 23}; -#endif -#define LEN sizeof(pinList)/sizeof(uint8_t) - -//ModbusIP object -ModbusIP mb; - -// Callback function to read corresponding DI -uint16_t cbRead(TRegister* reg, uint16_t val) { - if(reg->address < COIL_BASE) - return 0; - uint8_t offset = reg->address - COIL_BASE; - if(offset >= LEN) - return 0; - return COIL_VAL(digitalRead(pinList[offset])); -} -// Callback function to write-protect DI -uint16_t cbWrite(TRegister* reg, uint16_t val) { - return reg->value; -} - -// Callback function for client connect. Returns true to allow connection. -bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; -} - -void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else - Serial.begin(115200); - #endif - - WiFi.begin("ssid", "password"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - for (uint8_t i = 0; i < LEN; i++) - pinMode(pinList[i], INPUT); - mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); - - mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. - mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add callback on Coils value get - mb.onSetCoil(COIL_BASE, cbWrite, LEN); -} - -void loop() { - //Call once inside loop() - all magic here - mb.task(); - delay(100); +/* + Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Used Pins +#ifdef ESP8266 + uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; +#else //ESP32 + uint8_t pinList[] = {12, 13, 14, 14, 16, 17, 18, 21, 22, 23}; +#endif +#define LEN sizeof(pinList)/sizeof(uint8_t) +#define COIL_BASE 0 +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + if(reg->address.address < COIL_BASE) + return 0; + uint8_t offset = reg->address.address - COIL_BASE; + if(offset >= LEN) + return 0; + return COIL_VAL(digitalRead(pinList[offset])); +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + return reg->value; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif + + WiFi.begin("ssid", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + for (uint8_t i = 0; i < LEN; i++) + pinMode(pinList[i], INPUT); + mb.onConnect(cbConn); // Add callback on connection event + mb.begin(); + + mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. + mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add callback on Coils value get + mb.onSetCoil(COIL_BASE, cbWrite, LEN); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(100); } \ No newline at end of file diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/MultipleHRegDebug/MultipleHRegDebug.ino index 90d124e..cff0f48 100644 --- a/examples/MultipleHRegDebug/MultipleHRegDebug.ino +++ b/examples/MultipleHRegDebug/MultipleHRegDebug.ino @@ -25,7 +25,7 @@ ModbusIP mb; // Callback function to read corresponding DI uint16_t cbRead(TRegister* reg, uint16_t val) { Serial.print("Read. Reg RAW#: "); - Serial.print(reg->address); + Serial.print(reg->address.address); Serial.print(" Old: "); Serial.print(reg->value); Serial.print(" New: "); @@ -35,7 +35,7 @@ uint16_t cbRead(TRegister* reg, uint16_t val) { // Callback function to write-protect DI uint16_t cbWrite(TRegister* reg, uint16_t val) { Serial.print("Write. Reg RAW#: "); - Serial.print(reg->address); + Serial.print(reg->address.address); Serial.print(" Old: "); Serial.print(reg->value); Serial.print(" New: "); From 1368f41c65eec1f8bfc413f90f7ed09abc3e4481 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 19 Jan 2019 16:12:40 +0500 Subject: [PATCH 115/288] Fix crash on disconnect (with 2.5.x) --- src/ModbusIP_ESP8266.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index c80e947..028ffec 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -195,11 +195,11 @@ bool ifExpired(TTransaction& t) { void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (client[i] && !client[i]->connected()) { - IPAddress ip = client[i]->remoteIP(); + //IPAddress ip = client[i]->remoteIP(); delete client[i]; client[i] = nullptr; if (cbDisconnect && cbEnabled) - cbDisconnect(ip); + cbDisconnect(IPADDR_NONE); } } //_trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); From 522012ea8d22a3ce7dfaf64fca7ced7324daf44c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 25 Jan 2019 10:36:51 +0500 Subject: [PATCH 116/288] Fix readCoil/Hreg/Ists/Ireg --- src/ModbusIP_ESP8266.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 028ffec..571be34 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -250,7 +250,7 @@ uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_ uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_COILS); - return send(ip, COIL(offset), cb, unit, value, cb); + return send(ip, COIL(offset), cb, unit, value); } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb, uint8_t unit) { @@ -267,19 +267,19 @@ uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uin uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_REGS); - return send(ip, HREG(offset), cb, unit, value, cb); + return send(ip, HREG(offset), cb, unit, value); } uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(offset), cb, unit, value, cb); + return send(ip, ISTS(offset), cb, unit, value); } uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(offset), cb, unit, value, cb); + return send(ip, IREG(offset), cb, unit, value); } uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { From e5c734173bd57434cb8a71eac765c18c8023c962 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 25 Jan 2019 16:35:04 +0500 Subject: [PATCH 117/288] 2.0.1 --- README.md | 5 ++++- library.properties | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b7e163..9cafcd9 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,10 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + added removeOnSetCoil\... methods + added read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id + added ability to auto connect to slave. Setting is global. Disabled by default. -// ToDo for 2.0.1 +// 2.0.1 ++ Fix readCoil\Hreg\Ists\Ireg not read value from slave ++ Fix cresh on disconnect with Arduino Core 2.5.x +// ToDo for 2.0.2 - modify examples - code cleanup // ToDo later diff --git a/library.properties b/library.properties index 74dd8f0..e34fa89 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=2.0 +version=2.0.1 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov sentence=Modbus Master-Slave Library for ESP8266/ESP32 From f601525a7a82e5a02512a199c7117939c279f303 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 2 Feb 2019 20:09:17 +0500 Subject: [PATCH 118/288] Fix master write multile Hregs/Coils. Change TCP flushing --- src/Modbus.cpp | 21 +++++++++++++-------- src/Modbus.h | 4 ++-- src/ModbusIP_ESP8266.cpp | 21 ++++++++++++++------- src/ModbusIP_ESP8266.h | 6 +++++- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 8bd78f8..2222206 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -88,6 +88,7 @@ void Modbus::slavePDU(uint8_t* frame) { 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 bytecount_calc; + uint16_t k; switch (fcode) { case FC_WRITE_REG: //field1 = reg, field2 = value @@ -113,15 +114,17 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, EX_ILLEGAL_VALUE); break; } - for (int k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) + for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) if (!searchRegister(HREG(field1) + k)) { exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); break; } } - setMultipleWords(frame + 6, HREG(field1), field2); - successResponce(HREG(field1), field2, fcode); - _reply = REPLY_NORMAL; + if (k >= field2) { + setMultipleWords(frame + 6, HREG(field1), field2); + successResponce(HREG(field1), field2, fcode); + _reply = REPLY_NORMAL; + } break; case FC_READ_COILS: @@ -164,15 +167,17 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, EX_ILLEGAL_VALUE); break; } - for (int k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) + for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) if (!searchRegister(COIL(field1) + k)) { exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); break; } } - setMultipleBits(frame + 6, COIL(field1), field2); - successResponce(COIL(field1), field2, fcode); - _reply = REPLY_NORMAL; + if (k >= field2) { + setMultipleBits(frame + 6, COIL(field1), field2); + successResponce(COIL(field1), field2, fcode); + _reply = REPLY_NORMAL; + } break; default: diff --git a/src/Modbus.h b/src/Modbus.h index 51f0f35..ccbd964 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -203,9 +203,9 @@ class Modbus { void exceptionResponse(FunctionCode fn, ResultCode excode); void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); void slavePDU(uint8_t* frame); //For Slave - void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output= nullptr); //For Master + void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output = nullptr); //For Master // frame - data received form slave - // sourceFrame - data sent fo 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 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 028ffec..7a0ca99 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -6,7 +6,7 @@ #include "ModbusIP_ESP8266.h" ModbusIP::ModbusIP() { - _trans.reserve(MODBUSIP_MAX_TRANSACIONS); + //_trans.reserve(MODBUSIP_MAX_TRANSACIONS); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) client[i] = nullptr; } @@ -63,7 +63,7 @@ void ModbusIP::task() { } } // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached - currentClient->flush(); + //currentClient->flush(); currentClient->stop(); delete currentClient; } @@ -78,22 +78,26 @@ void ModbusIP::task() { _len--; // Do not count with last byte from MBAP if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. - client[n]->flush(); + client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); + client[n]->flush(); continue; // for (n) } if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->flush(); + client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); + //client[n]->flush(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->flush(); + client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); + //client[n]->flush(); } else { if (client[n]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - client[n]->flush(); + client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); + //client[n]->flush(); } else { if (client[n]->localPort() == MODBUSIP_PORT) { // Process incoming frame as slave @@ -119,7 +123,8 @@ void ModbusIP::task() { _trans.erase(it); } } - client[n]->flush(); // Not sure if we need flush rest of data available + //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); + //client[n]->flush(); // Not sure if we need flush rest of data available } } } @@ -131,7 +136,9 @@ void ModbusIP::task() { memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); client[n]->write(sbuf, send_len); + //client[n]->flush(); } + client[n]->flush(); free(_frame); _frame = nullptr; _len = 0; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 7cc91e8..c70d1a8 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -64,7 +64,11 @@ class ModbusIP : public Modbus { int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); - + // 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: ModbusIP(); bool isTransaction(uint16_t id); From e909c88d4f8703c88db46fa0e64ecb3bd475d205 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 2 Feb 2019 21:53:04 +0500 Subject: [PATCH 119/288] Disconnect initial implementation --- src/Modbus.h | 3 ++- src/ModbusIP_ESP8266.cpp | 16 ++++++++++++++-- src/ModbusIP_ESP8266.h | 7 ++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index 51f0f35..6aaa37a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -127,7 +127,8 @@ class Modbus { 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_CONNECTION_LOST = 0xE5, // Custom. Connection with device lost + EX_CANCEL = 0xE6 // Custom. Transaction/request canceled }; ~Modbus(); bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 571be34..048e1b7 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -142,8 +142,10 @@ void ModbusIP::task() { // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { #ifdef MODBUSIP_MAX_TRANSACIONS - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) { +Serial.println(_trans.size()); return false; +} #endif int8_t p = getSlave(ip); if (p == -1 || !client[p]->connected()) @@ -160,7 +162,8 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); if (client[p]->write(sbuf, send_len) != send_len) return false; - if (waitResponse) { + client[p]->flush(); + if (waitResponse || true) { TTransaction tmp; tmp.transactionId = transactionId; tmp.timestamp = millis(); @@ -390,4 +393,13 @@ bool ModbusIP::isConnected(IPAddress ip) { void ModbusIP::autoConnect(bool enabled) { autoConnectMode = enabled; +} + +bool ModbusIP::disconnect(IPAddress ip) { + int8_t p = getSlave(ip); + if (p != -1) client[p]->stop(); +} + +void ModbusIP::dropTransactions() { + _trans.resize(0); } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 7cc91e8..da980de 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -32,7 +32,7 @@ typedef struct TTransaction { uint8_t* _frame = nullptr; void* data = nullptr; TAddress startreg; - bool operator ==(const TTransaction &obj) const { + bool operator ==(const TTransaction &obj) const { return transactionId == obj.transactionId; } }; @@ -70,7 +70,7 @@ class ModbusIP : public Modbus { bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); bool connect(IPAddress ip); - bool disconnect(IPAddress addr) {} // Not implemented yet + bool disconnect(IPAddress ip); void slave(); void master(); void task(); @@ -79,8 +79,9 @@ class ModbusIP : public Modbus { void onDisconnect(cbModbusConnect cb = nullptr); IPAddress eventSource(); void autoConnect(bool enabled = true); + void dropTransactions(); - uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); From 604d1707b70f4527e97ab285baedc6cf1950d430 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 3 Feb 2019 21:47:02 +0500 Subject: [PATCH 120/288] 2.1 BETA --- README.md | 12 ++++++---- keywords.txt | 3 +++ library.properties | 2 +- src/Modbus.cpp | 2 +- src/ModbusIP_ESP8266.cpp | 49 ++++++++++++++++++++++++++-------------- src/ModbusIP_ESP8266.h | 2 ++ 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 9cafcd9..8a5d022 100644 --- a/README.md +++ b/README.md @@ -73,15 +73,19 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf // 2.0.1 + Fix readCoil\Hreg\Ists\Ireg not read value from slave + Fix cresh on disconnect with Arduino Core 2.5.x -// ToDo for 2.0.2 -- modify examples -- code cleanup +// 2.1.0 ++ dropTransactions() ++ Fix slave error response on write multiple Hreg\Coils ++ Implement disconnect() +- Modify slave task() for high query rate +- Create destructor for ModbusIP +- Modify examples // ToDo later +- code cleanup - ModbusSerial (over RS-485) - Modbus Read/Write File Records function - Modbus Write Mask Register function - Modbus Serial line-specific functions -- Create destructor for ModbusIP ``` ## Contributions diff --git a/keywords.txt b/keywords.txt index 41e0fef..caafaa8 100644 --- a/keywords.txt +++ b/keywords.txt @@ -63,6 +63,8 @@ removeIreg KEYWORD2 removeCoil KEYWORD2 removeIsts KEYWORD2 autoConnect KEYWORD2 +disconnect KEYWORD2 +dropTransactions KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 @@ -88,4 +90,5 @@ EX_DATA_MISMACH LITERAL1 EX_UNEXPECTED_RESPONSE LITERAL1 EX_TIMEOUT LITERAL1 EX_CONNECTION_LOST LITERAL1 +EX_CANCEL LITERAL1 diff --git a/library.properties b/library.properties index e34fa89..4fa70be 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=2.0.1 +version=2.1.0 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov sentence=Modbus Master-Slave Library for ESP8266/ESP32 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 2222206..d0e7ebe 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -469,7 +469,7 @@ void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { //1 void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, void* output) { void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output) { uint8_t fcode = frame[0]; - _reply = 0; + _reply = EX_SUCCESS; if ((fcode & 0x80) != 0) { _reply = _frame[1]; return; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 6eeb85a..5856667 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -73,28 +73,28 @@ void ModbusIP::task() { if (!client[n]->connected()) continue; if (client[n]->available() < sizeof(_MBAP) + 1) continue; - client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); //Get MBAP - _len = __bswap_16(_MBAP.length); - _len--; // Do not count with last byte from MBAP + client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP - if (__bswap_16(_MBAP.protocolId) != 0) { //Check if MODBUSIP packet. __bswap is usless there. + if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); - client[n]->flush(); + //client[n]->flush(); continue; // for (n) } - if (_len > MODBUSIP_MAXFRAME) { //Length is over MODBUSIP_MAXFRAME + _len = __bswap_16(_MBAP.length); + _len--; // Do not count with last byte from MBAP + if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); + client[n]->readBytes((uint8_t*)nullptr, _len); //client[n]->flush(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); + client[n]->readBytes((uint8_t*)nullptr, _len); //client[n]->flush(); } else { - if (client[n]->readBytes(_frame, _len) < _len) { //Try to read MODBUS frame + if (client[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); //client[n]->flush(); @@ -128,9 +128,9 @@ void ModbusIP::task() { } } } - if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was request to master + if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was responce to master if (_reply != REPLY_OFF) { - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.length = __bswap_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)); @@ -142,17 +142,18 @@ void ModbusIP::task() { free(_frame); _frame = nullptr; _len = 0; + //n--; } + //for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) + // if (client[n] && client[n]->connected()) + // client[n]->flush(); n = -1; } - // Prepare and send ModbusIP frame. _frame buffer should be filled with Modbus data uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { #ifdef MODBUSIP_MAX_TRANSACIONS - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) { -Serial.println(_trans.size()); + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; -} #endif int8_t p = getSlave(ip); if (p == -1 || !client[p]->connected()) @@ -390,7 +391,7 @@ uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint return send(ip, ISTS(to), cb, unit); } -bool ModbusIP::isTransaction(uint16_t id) { // Check if transaction is in progress (by ID) +bool ModbusIP::isTransaction(uint16_t id) { return searchTransaction(id) != nullptr; } bool ModbusIP::isConnected(IPAddress ip) { @@ -408,5 +409,19 @@ bool ModbusIP::disconnect(IPAddress ip) { } void ModbusIP::dropTransactions() { - _trans.resize(0); + for (auto &t : _trans) { + if (t.cb) + t.cb(EX_CANCEL, t.transactionId, nullptr); + free(t._frame); + } + _trans.clear(); +} + +ModbusIP::~ModbusIP() { + free(_frame); + dropTransactions(); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + client[i]->stop(); + delete client[i]; + } } \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 935c536..6b44a1c 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -64,6 +64,7 @@ class ModbusIP : public Modbus { int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* 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 @@ -71,6 +72,7 @@ class ModbusIP : public Modbus { // data - if not null use buffer to save returned data instead of local registers public: ModbusIP(); + ~ModbusIP(); bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); bool connect(IPAddress ip); From a09c4ceef1d5e36bb482c0f961f5841a615d349d Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 4 Feb 2019 12:19:55 +0500 Subject: [PATCH 121/288] dropTransaction()/clean() refactoring --- API.md | 5 +++-- keywords.txt | 9 ++++++++ src/ModbusIP_ESP8266.cpp | 46 ++++++++++++++-------------------------- src/ModbusIP_ESP8266.h | 3 ++- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/API.md b/API.md index 0625017..da1bf0b 100644 --- a/API.md +++ b/API.md @@ -196,9 +196,10 @@ void slave(); ```c void master(); bool connect(IPAddress ip); -bool disconnect(IPAddress ip); // Not implemented yet. +bool disconnect(IPAddress ip); bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); +void dropTransactions(); ``` ```c @@ -209,7 +210,7 @@ Select behavior of executing read/write/pull/push. If autoConnect disabled (defa ### Callback example -```c +```arduino ModbusIP mb; bool coil = false; // Define external variable to get/set value uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg structure to modify, 'val' is new register value diff --git a/keywords.txt b/keywords.txt index caafaa8..b1a6ca0 100644 --- a/keywords.txt +++ b/keywords.txt @@ -6,6 +6,7 @@ Modbus KEYWORD1 TRegister KEYWORD1 TTransaction KEYWORD1 TAddress KEYWORD1 +ResultCode KEYWORD1 # Methods and Functions (KEYWORD2) master KEYWORD2 @@ -65,6 +66,10 @@ removeIsts KEYWORD2 autoConnect KEYWORD2 disconnect KEYWORD2 dropTransactions KEYWORD2 +isCoil KEYWORD2 +isHreg KEYWORD2 +isIsts KEYWORD2 +isIreg KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 @@ -91,4 +96,8 @@ EX_UNEXPECTED_RESPONSE LITERAL1 EX_TIMEOUT LITERAL1 EX_CONNECTION_LOST LITERAL1 EX_CANCEL LITERAL1 +COIL LITERAL1 +HREG LITERAL1 +ISTS LITERAL1 +IREG LITERAL1 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 5856667..cb4301a 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -152,8 +152,7 @@ void ModbusIP::task() { uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { #ifdef MODBUSIP_MAX_TRANSACIONS - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) - return false; + if (this->_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; #endif int8_t p = getSlave(ip); if (p == -1 || !client[p]->connected()) @@ -171,7 +170,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 if (client[p]->write(sbuf, send_len) != send_len) return false; client[p]->flush(); - if (waitResponse || true) { + if (waitResponse) { TTransaction tmp; tmp.transactionId = transactionId; tmp.timestamp = millis(); @@ -194,16 +193,8 @@ void ModbusIP::onDisconnect(cbModbusConnect cb) { cbDisconnect = cb; } -bool ifExpired(TTransaction& t) { - if (millis() - t.timestamp > MODBUSIP_TIMEOUT) { - //if (t.cb) - // t.cb(Modbus::EX_TIMEOUT, t.transactionId, nullptr); - //free(t._frame); - return true; - } - return false; -} -void ModbusIP::cleanup() { // Free clients if not connected and remove timedout transactions +void ModbusIP::cleanup() { + // Free clients if not connected for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (client[i] && !client[i]->connected()) { //IPAddress ip = client[i]->remoteIP(); @@ -213,20 +204,19 @@ void ModbusIP::cleanup() { // Free clients if not connected and remove timedout cbDisconnect(IPADDR_NONE); } } - //_trans.erase(remove_if( _trans.begin(), _trans.end(), ifExpired ), _trans.end() ); - std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), ifExpired); - while (it != _trans.end()) { - if (it->cb) - it->cb(Modbus::EX_TIMEOUT, it->transactionId, nullptr); - free(it->_frame); - _trans.erase(it); - it = std::find_if(it, _trans.end(), ifExpired); + // Remove timedout transactions and forced event + 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; + it->cb(res, it->transactionId, nullptr); + free(it->_frame); + it = _trans.erase(it); + } else + it++; } - } -int8_t ModbusIP::getFreeClient() { // Returns free slot position - //clientsCleanup(); +int8_t ModbusIP::getFreeClient() { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) if (!client[i]) return i; @@ -409,17 +399,13 @@ bool ModbusIP::disconnect(IPAddress ip) { } void ModbusIP::dropTransactions() { - for (auto &t : _trans) { - if (t.cb) - t.cb(EX_CANCEL, t.transactionId, nullptr); - free(t._frame); - } - _trans.clear(); + for (auto &t : _trans) t.forcedEvent = EX_CANCEL; } ModbusIP::~ModbusIP() { free(_frame); dropTransactions(); + cleanup(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { client[i]->stop(); delete client[i]; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 6b44a1c..18680da 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -32,6 +32,7 @@ typedef struct TTransaction { uint8_t* _frame = nullptr; void* 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; } @@ -59,7 +60,7 @@ class ModbusIP : public Modbus { bool autoConnectMode = false; TTransaction* searchTransaction(uint16_t id); - void cleanup(); // Free clients if not connected and remove timedout transactions + void cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); From b9079e6740d2b0af028894661449dbd7e630c347 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 4 Feb 2019 14:02:15 +0500 Subject: [PATCH 122/288] Fix writeMiltipleBits for array --- README.md | 21 ++++++++++++--------- src/Modbus.cpp | 3 ++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8a5d022..f9c37aa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 v2.0 +# Modbus Master-Slave Library for ESP8266/ESP32 v2.1 This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. @@ -47,14 +47,13 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf ## Last Changes ```diff -// Internal changes +// 2.0.0 + Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. + Change object's list implementation to *std::vector* + Modbus class refactoring + ModbusIP networking code refactoring and error reporting + Global registers storage to share between multiple Modbus* instances + Move rest of implementations from Modbus.h -// Public API changes + Modbus master implementation + Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL + Back to marking private for onSet, onGet, addReg and Reg methods @@ -74,15 +73,19 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + Fix readCoil\Hreg\Ists\Ireg not read value from slave + Fix cresh on disconnect with Arduino Core 2.5.x // 2.1.0 -+ dropTransactions() + Fix slave error response on write multiple Hreg\Coils -+ Implement disconnect() -- Modify slave task() for high query rate -- Create destructor for ModbusIP ++ Fix writeCoil() for multiple coils ++ dropTransactions() ++ disconnect() ++ ~ModbusIP() ++ task() cleanup - Modify examples -// ToDo later +// 2.2.0 - code cleanup -- ModbusSerial (over RS-485) +- Implement Private Reg/Coil +// 3.0.0 +- ModbusRTU (over RS-485) +// ToDo later - Modbus Read/Write File Records function - Modbus Write Mask Register function - Modbus Serial line-specific functions diff --git a/src/Modbus.cpp b/src/Modbus.cpp index d0e7ebe..ca5971c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -391,7 +391,8 @@ bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, Fu _frame[5] = _len - 6; _frame[_len - 1] = 0; //Clean last probably partial byte if (data) { - bitsToBool(data, _frame + 6, numregs); + //bitsToBool(data, _frame + 6, numregs); + boolToBits(_frame + 6, data, numregs); } else { getMultipleBits(_frame + 6, startreg, numregs); } From fa7598970a3c76e4a438e16f134359a32543f082 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 4 Feb 2019 17:17:43 +0500 Subject: [PATCH 123/288] Update examples --- README.md | 6 +- examples/AnalogInput/AnalogInput.ino | 4 +- examples/Callback/Callback.ino | 4 +- examples/HoldingReg/HoldingReg.ino | 4 +- examples/Led/Led.ino | 4 +- examples/MassOperations/MassOperations.ino | 4 +- examples/Master/Master.ino | 2 +- .../MasterSimpleRead/MasterSimpleRead.ino | 2 +- .../MasterWriteMultiple.ino | 65 +++++++++++++++++++ .../MultipleHRegDebug/MultipleHRegDebug.ino | 4 +- examples/SwitchStatus/SwitchStatus.ino | 4 +- src/Modbus.cpp | 2 +- src/Modbus.h | 4 +- src/ModbusIP_ESP8266.cpp | 4 +- src/ModbusIP_ESP8266.h | 4 +- 15 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 examples/MasterWriteMultiple/MasterWriteMultiple.ino diff --git a/README.md b/README.md index f9c37aa..667526e 100644 --- a/README.md +++ b/README.md @@ -71,15 +71,15 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + added ability to auto connect to slave. Setting is global. Disabled by default. // 2.0.1 + Fix readCoil\Hreg\Ists\Ireg not read value from slave -+ Fix cresh on disconnect with Arduino Core 2.5.x ++ Fix crash on disconnect with Arduino Core 2.5.x // 2.1.0 -+ Fix slave error response on write multiple Hreg\Coils ++ Fix slave error response on write multiple Hregs\Coils + Fix writeCoil() for multiple coils + dropTransactions() + disconnect() + ~ModbusIP() + task() cleanup -- Modify examples ++ Modify examples // 2.2.0 - code cleanup - Implement Private Reg/Coil diff --git a/examples/AnalogInput/AnalogInput.ino b/examples/AnalogInput/AnalogInput.ino index 9495683..55a02a9 100644 --- a/examples/AnalogInput/AnalogInput.ino +++ b/examples/AnalogInput/AnalogInput.ino @@ -43,7 +43,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.begin(); //Start Modbus IP + mb.slave(); //Start Modbus IP // Add SENSOR_IREG register - Use addIreg() for analog Inputs mb.addIreg(SENSOR_IREG); @@ -60,5 +60,5 @@ void loop() { //Setting raw value (0-1024) mb.Ireg(SENSOR_IREG, analogRead(A0)); } - delay(100); + delay(10); } diff --git a/examples/Callback/Callback.ino b/examples/Callback/Callback.ino index 9eca24c..4ccb2a1 100644 --- a/examples/Callback/Callback.ino +++ b/examples/Callback/Callback.ino @@ -60,7 +60,7 @@ void setup() { Serial.println(WiFi.localIP()); mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); + mb.slave(); pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) @@ -70,5 +70,5 @@ void setup() { void loop() { //Call once inside loop() - all magic here mb.task(); - delay(100); + delay(10); } diff --git a/examples/HoldingReg/HoldingReg.ino b/examples/HoldingReg/HoldingReg.ino index 6b791b2..4a183ae 100644 --- a/examples/HoldingReg/HoldingReg.ino +++ b/examples/HoldingReg/HoldingReg.ino @@ -44,12 +44,12 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.begin(); + mb.slave(); mb.addHreg(TEST_HREG, 0xABCD); } void loop() { //Call once inside loop() - all magic here mb.task(); - delay(100); + delay(10); } diff --git a/examples/Led/Led.ino b/examples/Led/Led.ino index 678fcf0..ce6990b 100644 --- a/examples/Led/Led.ino +++ b/examples/Led/Led.ino @@ -44,7 +44,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.begin(); + mb.slave(); pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); @@ -56,5 +56,5 @@ void loop() { //Attach ledPin to LED_COIL register digitalWrite(ledPin, mb.Coil(LED_COIL)); - delay(100); + delay(10); } \ No newline at end of file diff --git a/examples/MassOperations/MassOperations.ino b/examples/MassOperations/MassOperations.ino index 2efbb87..3b0e0a5 100644 --- a/examples/MassOperations/MassOperations.ino +++ b/examples/MassOperations/MassOperations.ino @@ -69,7 +69,7 @@ void setup() { for (uint8_t i = 0; i < LEN; i++) pinMode(pinList[i], INPUT); mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); + mb.slave(); mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add callback on Coils value get @@ -79,5 +79,5 @@ void setup() { void loop() { //Call once inside loop() - all magic here mb.task(); - delay(100); + delay(10); } \ No newline at end of file diff --git a/examples/Master/Master.ino b/examples/Master/Master.ino index 00396f7..ab4ac8b 100644 --- a/examples/Master/Master.ino +++ b/examples/Master/Master.ino @@ -71,5 +71,5 @@ void loop() { mb.connect(remote); // Try to connect if no connection } mb.task(); // Common local Modbus task - delay(100); // Polling interval + delay(10); // Polling interval } diff --git a/examples/MasterSimpleRead/MasterSimpleRead.ino b/examples/MasterSimpleRead/MasterSimpleRead.ino index 88ba43c..47b886d 100644 --- a/examples/MasterSimpleRead/MasterSimpleRead.ino +++ b/examples/MasterSimpleRead/MasterSimpleRead.ino @@ -52,7 +52,7 @@ void loop() { } mb.task(); // Common local Modbus task delay(100); // Pulling interval - if (show--) { // Display Slave register value one time per second (with default settings) + if (!show--) { // Display Slave register value one time per second (with default settings) Serial.println(res); show = LOOP_COUNT; } diff --git a/examples/MasterWriteMultiple/MasterWriteMultiple.ino b/examples/MasterWriteMultiple/MasterWriteMultiple.ino new file mode 100644 index 0000000..2a6c401 --- /dev/null +++ b/examples/MasterWriteMultiple/MasterWriteMultiple.ino @@ -0,0 +1,65 @@ +/* + Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) + Write multiple coils to Slave device + + (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +const int REG = 100; // Modbus Coils Offset +const int COUNT = 5; // Count of Coils +IPAddress remote(192, 168, 20, 102); // Address of Modbus Slave device + +ModbusIP mb; // ModbusIP object + +void setup() { + #ifdef ESP8266 + Serial.begin(74880); + #else + Serial.begin(115200); + #endif + + WiFi.begin(); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.master(); +} + +bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X\n", event); // Display Modbus error code + if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place + mb.disconnect(remote); // Close connection to slave and + mb.dropTransactions(); // Cancel all waiting transactions + } + return true; +} + +bool res[COUNT] = {false, true, false, true, true}; + +void loop() { + if (!mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.connect(remote); // Try to connect if no connection + Serial.print("."); + } + if (!mb.writeCoil(remote, REG, res, COUNT, cb)) // Try to Write array of COUNT of Coils to Modbus Slave + Serial.print("#"); + mb.task(); // Modbus task + delay(50); // Pushing interval +} \ No newline at end of file diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/MultipleHRegDebug/MultipleHRegDebug.ino index cff0f48..3f7b507 100644 --- a/examples/MultipleHRegDebug/MultipleHRegDebug.ino +++ b/examples/MultipleHRegDebug/MultipleHRegDebug.ino @@ -69,7 +69,7 @@ void setup() { Serial.println(WiFi.localIP()); mb.onConnect(cbConn); // Add callback on connection event - mb.begin(); + mb.slave(); if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get @@ -79,5 +79,5 @@ void setup() { void loop() { //Call once inside loop() - all magic here mb.task(); - delay(100); + delay(10); } diff --git a/examples/SwitchStatus/SwitchStatus.ino b/examples/SwitchStatus/SwitchStatus.ino index 6fc08ee..bc3bd16 100644 --- a/examples/SwitchStatus/SwitchStatus.ino +++ b/examples/SwitchStatus/SwitchStatus.ino @@ -38,7 +38,7 @@ void setup() { Serial.print("."); } //Config Modbus IP - mb.begin(); + mb.slave(); //Set ledPin mode pinMode(switchPin, INPUT); // Add SWITCH_ISTS register - Use addIsts() for digital inputs @@ -51,5 +51,5 @@ void loop() { //Attach switchPin to SWITCH_ISTS register mb.Ists(SWITCH_ISTS, digitalRead(switchPin)); - delay(100); + delay(10); } diff --git a/src/Modbus.cpp b/src/Modbus.cpp index ca5971c..48ab8c8 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,7 +1,7 @@ /* Modbus.cpp - Modbus Base Library Implementation Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" diff --git a/src/Modbus.h b/src/Modbus.h index 18ea83c..df1715f 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -201,8 +201,8 @@ class Modbus { bool cbEnabled = true; uint16_t callback(TRegister* reg, uint16_t val, TCallback::CallbackType t); TRegister* searchRegister(TAddress addr); - void exceptionResponse(FunctionCode fn, ResultCode excode); - void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); + 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, void* output = nullptr); //For Master // frame - data received form slave diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index cb4301a..f9d09a7 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -1,7 +1,7 @@ /* ModbusIP_ESP8266.cpp - ModbusIP Library Implementation Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "ModbusIP_ESP8266.h" @@ -49,6 +49,7 @@ TTransaction* ModbusIP::searchTransaction(uint16_t id) { void ModbusIP::task() { + MBAP_t _MBAP; cleanup(); if (server) { while (server->hasClient()) { @@ -151,6 +152,7 @@ void ModbusIP::task() { } uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { + MBAP_t _MBAP; #ifdef MODBUSIP_MAX_TRANSACIONS if (this->_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; #endif diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 18680da..829bab9 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,7 +1,7 @@ /* ModbusIP_ESP8266.h - Header for ModbusIP Library Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once @@ -49,7 +49,7 @@ class ModbusIP : public Modbus { }; uint8_t raw[7]; }; - MBAP_t _MBAP; + //MBAP_t _MBAP; cbModbusConnect cbConnect = nullptr; cbModbusConnect cbDisconnect = nullptr; WiFiServer* server = nullptr; From a0e2ef8d8cea45171b9468cf7d5954a9779cdab3 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 5 Feb 2019 00:31:37 +0500 Subject: [PATCH 124/288] Limit incoming connections to one per IP --- README.md | 11 ++++++----- src/ModbusIP_ESP8266.cpp | 11 ++++++++++- src/ModbusIP_ESP8266.h | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 667526e..40ce4d0 100644 --- a/README.md +++ b/README.md @@ -70,16 +70,17 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + added read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id + added ability to auto connect to slave. Setting is global. Disabled by default. // 2.0.1 -+ Fix readCoil\Hreg\Ists\Ireg not read value from slave ++ Master. Fix readCoil\Hreg\Ists\Ireg not read value from slave + Fix crash on disconnect with Arduino Core 2.5.x // 2.1.0 -+ Fix slave error response on write multiple Hregs\Coils -+ Fix writeCoil() for multiple coils -+ dropTransactions() -+ disconnect() ++ Slave. Fix error response on write multiple Hregs\Coils ++ Slave. Fix writeCoil() for multiple coils ++ Master. dropTransactions() ++ Master. disconnect() + ~ModbusIP() + task() cleanup + Modify examples ++ Slave. Allow only single incoming master connection per IP // 2.2.0 - code cleanup - Implement Private Reg/Coil diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index f9d09a7..61e88c8 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -57,6 +57,15 @@ void ModbusIP::task() { if (!currentClient || !currentClient->connected()) continue; if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + #ifdef MODBUSIP_UNIQUE_CLIENTS + n = getMaster(currentClient->remoteIP()); + if (n != -1) { + client[n]->flush(); + client[n]->stop(); + delete client[n]; + client[n] = nullptr; + } + #endif n = getFreeClient(); if (n > -1) { client[n] = currentClient; @@ -154,7 +163,7 @@ void ModbusIP::task() { uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { MBAP_t _MBAP; #ifdef MODBUSIP_MAX_TRANSACIONS - if (this->_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; #endif int8_t p = getSlave(ip); if (p == -1 || !client[p]->connected()) diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 829bab9..378f61c 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -19,6 +19,7 @@ #define MODBUSIP_MAX_TRANSACIONS 16 #define MODBUSIP_MAX_CLIENTS 4 #define MODBUSIP_ADD_REG 1 +#define MODBUSIP_UNIQUE_CLIENTS // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); From 3b1cbe229be1dcc6b075704ab4446f125c365aa2 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 15 Feb 2019 21:50:35 +0500 Subject: [PATCH 125/288] Switch to leonardo/mega --- src/ModbusIP_ESP8266.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 61e88c8..75fe411 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -61,7 +61,7 @@ void ModbusIP::task() { n = getMaster(currentClient->remoteIP()); if (n != -1) { client[n]->flush(); - client[n]->stop(); + //client[n]->stop(); delete client[n]; client[n] = nullptr; } @@ -74,7 +74,7 @@ void ModbusIP::task() { } // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached //currentClient->flush(); - currentClient->stop(); + //currentClient->stop(); delete currentClient; } } @@ -191,6 +191,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 tmp.startreg = startreg; _trans.push_back(tmp); _frame = nullptr; + _len = 0; } return transactionId; } @@ -209,6 +210,7 @@ void ModbusIP::cleanup() { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (client[i] && !client[i]->connected()) { //IPAddress ip = client[i]->remoteIP(); + //client[i]->stop(); delete client[i]; client[i] = nullptr; if (cbDisconnect && cbEnabled) @@ -406,7 +408,12 @@ void ModbusIP::autoConnect(bool enabled) { bool ModbusIP::disconnect(IPAddress ip) { int8_t p = getSlave(ip); - if (p != -1) client[p]->stop(); + //if (p != -1) client[p]->stop(); + if (p != -1) { + delete client[p]; + client[p] = nullptr; + } + return true; } void ModbusIP::dropTransactions() { @@ -418,7 +425,8 @@ ModbusIP::~ModbusIP() { dropTransactions(); cleanup(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - client[i]->stop(); + //client[i]->stop(); delete client[i]; + client[i] = nullptr; } } \ No newline at end of file From 1514c9baecffb651e81796f3bb7216e6c892078b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 20 Mar 2019 22:51:33 +0500 Subject: [PATCH 126/288] Comments added --- README.md | 1 + src/ModbusIP_ESP8266.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 40ce4d0..9e8f37f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf 1. When using Modbus IP the transport protocol is TCP (port 502). 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) +4. By default library limits incoming connection to one connection per unique IP address. This done to eliminate stale connections that taking place some times. ## Last Changes diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 61e88c8..927425d 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -58,6 +58,7 @@ void ModbusIP::task() { continue; if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { #ifdef MODBUSIP_UNIQUE_CLIENTS + // Disconnect previous connection from same IP if present n = getMaster(currentClient->remoteIP()); if (n != -1) { client[n]->flush(); From 9cadf5dc500e93b7aff1b25d0aae755e89154a19 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 13 May 2019 14:56:36 +0500 Subject: [PATCH 127/288] Remove useless ->stop() lines --- src/ModbusIP_ESP8266.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 75fe411..7b633bb 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -408,7 +408,6 @@ void ModbusIP::autoConnect(bool enabled) { bool ModbusIP::disconnect(IPAddress ip) { int8_t p = getSlave(ip); - //if (p != -1) client[p]->stop(); if (p != -1) { delete client[p]; client[p] = nullptr; @@ -425,7 +424,6 @@ ModbusIP::~ModbusIP() { dropTransactions(); cleanup(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - //client[i]->stop(); delete client[i]; client[i] = nullptr; } From 6f4b2e807156c6bbbe943d69ebc20eddfe30dc11 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 15 May 2019 13:12:36 +0500 Subject: [PATCH 128/288] ModbusRTU slave initial draft --- README.md | 18 +++-- src/Modbus.cpp | 12 --- src/Modbus.h | 4 +- src/ModbusIP_ESP8266.h | 1 - src/ModbusRTU.cpp | 174 +++++++++++++++++++++++++++++++++++++++++ src/ModbusRTU.h | 120 ++++++++++++++++++++++++++++ 6 files changed, 306 insertions(+), 23 deletions(-) create mode 100644 src/ModbusRTU.cpp create mode 100644 src/ModbusRTU.h diff --git a/README.md b/README.md index 40ce4d0..0580c8b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 v2.1 +# Modbus Master-Slave Library for ESP8266/ESP32 v3.0 + +**Relase state: Development** + +Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). -In the current version the library allows the ESP8266/ESP32 operate as a master and/or slave, supporting Modbus IP via wireless network. For more information about Modbus see: +In the current version the library allows the ESP8266/ESP32 operate as a master and/or slave, supporting Modbus IP via wireless network and Modbus RTU over serial. For more information about Modbus see: http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf @@ -20,7 +24,9 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf * Operates as * slave * master -* Supports Modbus IP (TCP) +* Supports + * Modbus IP (TCP) + * Modbus RTU (RS-485) * Reply exception messages for all supported functions * Modbus functions supported: * 0x01 - Read Coils @@ -81,11 +87,9 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + task() cleanup + Modify examples + Slave. Allow only single incoming master connection per IP -// 2.2.0 -- code cleanup -- Implement Private Reg/Coil // 3.0.0 -- ModbusRTU (over RS-485) ++ ModbusRTU Slave +- ModbusRTU Master // ToDo later - Modbus Read/Write File Records function - Modbus Write Mask Register function diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 48ab8c8..9444dc1 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -382,8 +382,6 @@ bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, Fu _frame = (uint8_t*) malloc(_len); if (_frame) { _frame[0] = fn; -// _frame[1] = startreg.address >> 8; -// _frame[2] = startreg.address & 0x00FF; _frame[1] = to >> 8; _frame[2] = to & 0x00FF; _frame[3] = numregs >> 8; @@ -391,7 +389,6 @@ bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, Fu _frame[5] = _len - 6; _frame[_len - 1] = 0; //Clean last probably partial byte if (data) { - //bitsToBool(data, _frame + 6, numregs); boolToBits(_frame + 6, data, numregs); } else { getMultipleBits(_frame + 6, startreg, numregs); @@ -409,15 +406,12 @@ bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, F _frame = (uint8_t*) malloc(_len); if (_frame) { _frame[0] = fn; - //_frame[1] = startreg.address >> 8; - //_frame[2] = startreg.address & 0x00FF; _frame[1] = to >> 8; _frame[2] = to & 0x00FF; _frame[3] = numregs >> 8; _frame[4] = numregs & 0x00FF; _frame[5] = _len - 6; if (data) { - //memcpy(_frame + 6, data, numregs * 2); uint16_t* frame = (uint16_t*)(_frame + 6); while(numregs) { *frame = __bswap_16(*((uint16_t*)data)); @@ -487,7 +481,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, break; } if (output) { - //memcpy(output, frame + 2, 2 * field2); frame = frame + 2; while(field2) { *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); @@ -496,7 +489,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, field2--; } } else { - //setMultipleWords(frame + 2, HREG(field1), field2); setMultipleWords(frame + 2, startreg, field2); } break; @@ -511,7 +503,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, if (output) { bitsToBool((bool*)output, frame + 2, field2); } else { - //setMultipleBits(frame + 2, COIL(field1), field2); setMultipleBits(frame + 2, startreg, field2); } break; @@ -526,7 +517,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, if (output) { bitsToBool((bool*)output, frame + 2, field2); } else { - //setMultipleBits(frame + 2, ISTS(field1), field2); setMultipleBits(frame + 2, startreg, field2); } break; @@ -537,7 +527,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, break; } if (output) { - //memcpy(output, frame + 2, 2 * field2); frame = frame + 2; while(field2) { *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); @@ -546,7 +535,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, field2--; } } else { - //setMultipleWords(frame + 2, IREG(field1), field2); setMultipleWords(frame + 2, startreg, field2); } break; diff --git a/src/Modbus.h b/src/Modbus.h index df1715f..c40a826 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -19,7 +19,7 @@ //#define MB_GLOBAL_REGS #define MB_MAX_REGS 32 -#define MB_MAX_FRAME 128 +#define MB_MAX_FRAME 256 #define COIL(n) (TAddress){TAddress::COIL, n} #define ISTS(n) (TAddress){TAddress::ISTS, n} #define IREG(n) (TAddress){TAddress::IREG, n} @@ -86,8 +86,6 @@ struct TCallback { struct TRegister { TAddress address; uint16_t value; - //cbModbus get; - //cbModbus set; bool operator ==(const TRegister &obj) const { return address == obj.address; } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 378f61c..2790100 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -50,7 +50,6 @@ class ModbusIP : public Modbus { }; uint8_t raw[7]; }; - //MBAP_t _MBAP; cbModbusConnect cbConnect = nullptr; cbModbusConnect cbDisconnect = nullptr; WiFiServer* server = nullptr; diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp new file mode 100644 index 0000000..547119f --- /dev/null +++ b/src/ModbusRTU.cpp @@ -0,0 +1,174 @@ +/* + ModbusSerial.cpp - Source for Modbus Serial Library + Copyright (C) 2014 André Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#include "ModbusRTU.h" + +uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { + uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; + + Index = CRCHi ^ address; + CRCHi = CRCLo ^ _auchCRCHi[Index]; + CRCLo = _auchCRCLo[Index]; + + while (pduLen--) { + Index = CRCHi ^ *pduFrame++; + CRCHi = CRCLo ^ _auchCRCHi[Index]; + CRCLo = _auchCRCLo[Index]; + } + + return (CRCHi << 8) | CRCLo; +} + +bool ModbusRTUSlave::setSlaveId(uint8_t slaveId){ + _slaveId = slaveId; + return true; +} + +uint8_t ModbusRTUSlave::getSlaveId() { + return _slaveId; +} + +#ifdef MB_SOFTWARE_SERIAL +bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { + (*port).begin(baud); +#else +#ifdef ESP8266 +bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { + (*port).begin(baud, format); +#else +bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { + (*port).begin(baud); +#endif +#endif + _port = port; + _txPin = txPin; + + delay(2000); // ??? + + if (txPin >= 0) { + pinMode(txPin, OUTPUT); + digitalWrite(txPin, LOW); + } + + if (baud > 19200) { + _t15 = 750; + _t35 = 1750; + } else { + _t15 = 15000000/baud; // 1T * 1.5 = T1.5 + _t35 = 35000000/baud; // 1T * 3.5 = T3.5 + } + + return true; +} + +bool ModbusRTU::receive(uint8_t* frame, uint8_t slaveId) { + //first byte of frame = address + uint8_t address = frame[0]; + //Last two byts = crc + u_int crc = ((frame[_len - 2] << 8) | frame[_len - 1]); + + //Slave Check + if (address != MB_BROADCAST && address != slaveId) { + return false; + } + + //CRC Check + if (crc != calcCrc(_frame[0], _frame+1, _len-3)) { + return false; + } + + //PDU starts after first uint8_t + //framesize PDU = framesize - address(1) - crc(2) + slavePDU(frame+1); + //No reply to Broadcasts + if (address == MB_BROADCAST) _reply = Modbus::REPLY_OFF; + return true; +} + +bool ModbusRTU::send(uint8_t* frame) { + uint8_t i; + + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + + for (i = 0 ; i < _len ; i++) { + (*_port).write(frame[i]); + } + + (*_port).flush(); + delayMicroseconds(_t35); + + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } +} + +bool ModbusRTU::sendPDU(uint8_t* pduframe, uint8_t slaveId) { + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + + //Send slaveId + (*_port).write(slaveId); + + //Send PDU + uint8_t i; + for (i = 0 ; i < _len ; i++) { + (*_port).write(pduframe[i]); + } + + //Send CRC + uint16_t crc = calcCrc(slaveId, _frame, _len); + (*_port).write(crc >> 8); + (*_port).write(crc & 0xFF); + + (*_port).flush(); + delayMicroseconds(_t35); + + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } +} +/* +task() function is totaly wrong: +1. Serial bufer overfull may happen. +2. Up to ~0.3 sec execution time at 9600. Needs to add some logic to be async at low rates (9600 and may be 19200). +3. No frame size (256 is max) checking +count = MB_MAX_FRAME; +if (.available()) { while(.available() > 0 && count > MB_SERIAL_BUFFER) { read(); count--;}; return(); } +if (_len > 0) { + process(); +} + +*/ +void ModbusRTUSlave::task() { + _len = 0; + + while ((*_port).available() > _len) { + _len = (*_port).available(); + delayMicroseconds(_t15); + } + + if (_len == 0) return; + + uint8_t i; + //_frame = (uint8_t*) malloc(_len); + _frame = (uint8_t*) malloc(MB_MAX_FRAME); + for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); + + if (receive(_frame, _slaveId)) { + if (_reply == Modbus::REPLY_NORMAL) + sendPDU(_frame, _slaveId); + else + if (_reply == Modbus::REPLY_ECHO) + send(_frame); + } + + free(_frame); + _len = 0; +} \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h new file mode 100644 index 0000000..9a8b36d --- /dev/null +++ b/src/ModbusRTU.h @@ -0,0 +1,120 @@ +/* + ModbusSerial.h - Header for ModbusSerial Library + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once + +#include + +#define MB_SOFTWARE_SERIAL + +#ifdef MB_SOFTWARE_SERIAL +#include +#endif + +#define MB_BROADCAST 0 +#define MB_RESERVE 248 +#define MB_SERIAL_BUFFER 128 +//#define MB_STATIC_FRAME 1 + +uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); + +class ModbusRTU : public Modbus { + protected: + Stream* _port; + int _txPin; + unsigned int _t15; // inter character time out + unsigned int _t35; // frame delay + public: + #ifdef MB_SOFTWARE_SERIAL + bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); + #else + #ifdef ESP8266 + bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); + #else + bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); + #endif + #endif + virtual void task() = 0; + bool receive(uint8_t* frame, uint8_t slaveId); + bool sendPDU(uint8_t* pduframe, uint8_t slaveId); + bool send(uint8_t* frame); +}; + +class ModbusRTUSlave : public ModbusRTU { + private: + uint8_t _slaveId; + public: + bool setSlaveId(uint8_t slaveId); + uint8_t getSlaveId(); + void task() override; +}; +/* +class ModbusRTUMaster : public ModbusRTU { + public: + void task(); + + uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); + uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); + uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + + uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + + uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +} +*/ +/* Table of CRC values for high�order byte */ +const uint8_t _auchCRCHi[] = { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40}; + +/* Table of CRC values for low�order byte */ +const uint8_t _auchCRCLo[] = { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, + 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, + 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, + 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, + 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, + 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, + 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, + 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, + 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, + 0x40}; \ No newline at end of file From 8c9be054fc7097ebc295099d9a71713c34695181 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 17 May 2019 11:50:02 +0500 Subject: [PATCH 129/288] Update doc --- API.md | 83 +++++++++++++++++++++++++--------------------- keywords.txt | 2 ++ library.properties | 2 +- src/ModbusRTU.cpp | 6 +++- 4 files changed, 54 insertions(+), 39 deletions(-) diff --git a/API.md b/API.md index da1bf0b..ac063e1 100644 --- a/API.md +++ b/API.md @@ -1,6 +1,6 @@ # Modbus Master-Slave Library for ESP8266/ESP32 -## API +## Common API ### Add [multiple] regs @@ -11,7 +11,7 @@ bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1); ``` -### Write reg +### Write local reg ```c bool Hreg(uint16_t offset, uint16_t value); @@ -20,7 +20,7 @@ bool Ists(uint16_t offset, bool value); bool Ireg(uint16_t offset, uint16_t value); ``` -### Read reg +### Read local reg ```c uint16_t Hreg(uint16_t offset); @@ -38,6 +38,45 @@ bool removeIsts(uint16_t offset, uint16_t numregs = 1); bool removeIreg(uint16_t offset, uint16_t numregs = 1); ``` +```c +void task(); +``` + +Processing routine. Should be periodically called form loop(). + +### Modbus RTU Specific API + +```c +void begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); +void begin(HardwareSerial* port, uint32_t baud, int16_t txPin=-1); +``` + +txPin is not implemented yet. + +### ModBus IP Slave specific API + +```c +void begin(); // Depricated. Use slave() instead. +void slave(); +``` + +### ModBus IP Master specific + +```c +void master(); +bool connect(IPAddress ip); +bool disconnect(IPAddress ip); +bool isTransaction(uint16_t id); +bool isConnected(IPAddress ip); +void dropTransactions(); +``` + +```c +void autoConnect(bool enabled); +``` + +Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. + ### Query [multiple] regs from remote slave ```c @@ -95,7 +134,7 @@ void autoConnect(bool enabled = true); Set mode for automatic connect on read*\write*\push*\pull* calls. Disabled by default. -### Callbacks +## Callbacks API ```c void cbEnable(bool state = TRUE); @@ -109,13 +148,13 @@ void onConnect(cbModbusConnect cb); void onDisonnect(cbModbusConnect cb); ``` -Assign callback function on new incoming connection event. +*Modbus IP Slave* Assign callback function on new incoming connection event. ```c typedef bool (*cbModbusConnect)(IPAddress ip); ``` -Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. +*Modbus IP Slave* Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. ```c typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); @@ -133,7 +172,7 @@ Transaction end callback function definition. *data* is currently reserved. IPAddress eventSource(); ``` -Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. +*Modbus IP Master/Slave* Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. *Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. @@ -178,36 +217,6 @@ Disconnect specific callback function or all callbacks of the type if cb=NULL. #define ISTS_BOOL(v) ``` -### ModBus IP specific - -```c -void task(); -``` - -### ModBus IP Slave specific - -```c -void begin(); // Depricated. Use slave() instead. -void slave(); -``` - -### ModBus IP Master specific - -```c -void master(); -bool connect(IPAddress ip); -bool disconnect(IPAddress ip); -bool isTransaction(uint16_t id); -bool isConnected(IPAddress ip); -void dropTransactions(); -``` - -```c -void autoConnect(bool enabled); -``` - -Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. - ### Callback example ```arduino diff --git a/keywords.txt b/keywords.txt index b1a6ca0..01715c5 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,6 +1,8 @@ # Syntax Coloring Map For ModbusIP-ESP8266 # Datatypes (KEYWORD1) +ModbusRTUSlave KEYWORD1 +ModbusRTUMaster KEYWORD1 ModbusIP KEYWORD1 Modbus KEYWORD1 TRegister KEYWORD1 diff --git a/library.properties b/library.properties index 4fa70be..d10b7ab 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=2.1.0 +version=3.0.0 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov sentence=Modbus Master-Slave Library for ESP8266/ESP32 diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 547119f..0b07e16 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -5,6 +5,10 @@ */ #include "ModbusRTU.h" +/* calcCrc optimisation required: +1. Move inside class code +2. Merge CRC tables to one and move it to PROGMEM +*/ uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; @@ -155,7 +159,7 @@ void ModbusRTUSlave::task() { } if (_len == 0) return; - + Serial.println(_len); uint8_t i; //_frame = (uint8_t*) malloc(_len); _frame = (uint8_t*) malloc(MB_MAX_FRAME); From 0434de9c43bfa993c70eca8753027534888ad404 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 20 May 2019 00:40:59 +0500 Subject: [PATCH 130/288] ModbusRTU task() refactoring --- .gitignore | 1 + README.md | 1 + src/Modbus.h | 15 ++-- src/ModbusIP_ESP8266.h | 11 +++ src/ModbusRTU.cpp | 159 +++++++++++++++-------------------------- src/ModbusRTU.h | 53 ++++++++++++-- 6 files changed, 124 insertions(+), 116 deletions(-) diff --git a/.gitignore b/.gitignore index 44ca147..f0b948a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode/arduino.json .vscode/c_cpp_properties.json +.vscode/* src/ModbusSerial.cpp src/ModbusSerial.h diff --git a/README.md b/README.md index 0580c8b..5ef7863 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf 1. When using Modbus IP the transport protocol is TCP (port 502). 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) +4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Read Multiple HRegs/IRegs for ESP master is limited to 63 per query. ## Last Changes diff --git a/src/Modbus.h b/src/Modbus.h index c40a826..1dee580 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -17,9 +17,9 @@ #endif -//#define MB_GLOBAL_REGS +#define MB_GLOBAL_REGS #define MB_MAX_REGS 32 -#define MB_MAX_FRAME 256 +#define MB_MAX_FRAME 128 #define COIL(n) (TAddress){TAddress::COIL, n} #define ISTS(n) (TAddress){TAddress::ISTS, n} #define IREG(n) (TAddress){TAddress::IREG, n} @@ -101,12 +101,13 @@ class Modbus { 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, // Not implemented - FC_WRITE_FILE_REC = 0x15, // Not implemented - FC_MASKWRITE_REG = 0x16, // Not implemented - FC_READWRITE_REGS = 0x17 // Not implemented + FC_READ_FILE_REC = 0x14, // Not implemented. Read File Record + FC_WRITE_FILE_REC = 0x15, // Not implemented. Write File Record + FC_MASKWRITE_REG = 0x16, // Not implemented. Mask Write Register + FC_READWRITE_REGS = 0x17 // Not implemented. Read/Write Multiple registers }; //Exception Codes //Custom result codes used internally and for callbacks but never used for Modbus responce @@ -115,7 +116,7 @@ class Modbus { 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 Deice Fails to process request + 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 diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 2790100..f90d9af 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -108,4 +108,15 @@ class ModbusIP : public Modbus { uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + /* + uint16_t maskHreg(IPAddress ip, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushPullHreg(IPAddress ip, + uint16_t from, uint16_t to, uint16_t numregs = 1, + uint16_t to, uint16_t from, uint16_t numregs = 1, + cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readWriteHreg(IPAddress ip, + uint16_t readOffset, uint16_t* value, uint16_t numregs = 1, + uint16_t writeOffset, uint16_t* value, uint16_t numregs = 1, + cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + */ }; \ No newline at end of file diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 0b07e16..4111ac7 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -3,11 +3,12 @@ Copyright (C) 2014 André Sarmento Barbosa 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ -#include "ModbusRTU.h" +#include /* calcCrc optimisation required: 1. Move inside class code 2. Merge CRC tables to one and move it to PROGMEM +3. in task() probably don't read crc to buffer? */ uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; @@ -36,14 +37,14 @@ uint8_t ModbusRTUSlave::getSlaveId() { #ifdef MB_SOFTWARE_SERIAL bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - (*port).begin(baud); + port->begin(baud); #else #ifdef ESP8266 bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { - (*port).begin(baud, format); + port->begin(baud, format); #else bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { - (*port).begin(baud); + port->begin(baud); #endif #endif _port = port; @@ -59,120 +60,72 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int1 if (baud > 19200) { _t15 = 750; _t35 = 1750; + _t = 2; } else { _t15 = 15000000/baud; // 1T * 1.5 = T1.5 _t35 = 35000000/baud; // 1T * 3.5 = T3.5 + _t = (_t35 / 1000) + 1; + Serial.print(_t); } return true; } -bool ModbusRTU::receive(uint8_t* frame, uint8_t slaveId) { - //first byte of frame = address - uint8_t address = frame[0]; - //Last two byts = crc - u_int crc = ((frame[_len - 2] << 8) | frame[_len - 1]); - - //Slave Check - if (address != MB_BROADCAST && address != slaveId) { - return false; - } - - //CRC Check - if (crc != calcCrc(_frame[0], _frame+1, _len-3)) { - return false; - } - - //PDU starts after first uint8_t - //framesize PDU = framesize - address(1) - crc(2) - slavePDU(frame+1); - //No reply to Broadcasts - if (address == MB_BROADCAST) _reply = Modbus::REPLY_OFF; - return true; -} - -bool ModbusRTU::send(uint8_t* frame) { - uint8_t i; - - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); +void ModbusRTUSlave::task() { + if (_port->available() > _len) { + _len = _port->available(); + t = millis(); + return; } - - for (i = 0 ; i < _len ; i++) { - (*_port).write(frame[i]); + if (_len == 0) return; // No data + if (millis() - t < _t) return; // Wait data whitespace + + uint8_t address = _port->read(); //first byte of frame = address + _len--; // Decrease by slaveId byte + if (address != MB_BROADCAST && address != _slaveId) { // SlaveId Check + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach + _len = 0; + return; } - (*_port).flush(); - delayMicroseconds(_t35); - - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); + _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; + return; } -} - -bool ModbusRTU::sendPDU(uint8_t* pduframe, uint8_t slaveId) { - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - - //Send slaveId - (*_port).write(slaveId); - - //Send PDU - uint8_t i; - for (i = 0 ; i < _len ; i++) { - (*_port).write(pduframe[i]); + for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc + u_int crc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc + _len = _len - 2; // Decrease by CRC 2 bytes + if (crc != calcCrc(address, _frame, _len)) { // CRC Check + _len = 0; // Cleanup if wrong crc + free(_frame); + _frame = nullptr; + return; } - //Send CRC - uint16_t crc = calcCrc(slaveId, _frame, _len); - (*_port).write(crc >> 8); - (*_port).write(crc & 0xFF); - - (*_port).flush(); - delayMicroseconds(_t35); - - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); + slavePDU(_frame); + + if (address == MB_BROADCAST) _reply = Modbus::REPLY_OFF; //No reply to Broadcasts + if (_reply != Modbus::REPLY_OFF) { + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + _port->write(_slaveId); //Send slaveId + for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU + //Send CRC + uint16_t crc = calcCrc(_slaveId, _frame, _len); + _port->write(crc >> 8); + _port->write(crc & 0xFF); + _port->flush(); + delay(_t); //delayMicroseconds(_t35); + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } } -} -/* -task() function is totaly wrong: -1. Serial bufer overfull may happen. -2. Up to ~0.3 sec execution time at 9600. Needs to add some logic to be async at low rates (9600 and may be 19200). -3. No frame size (256 is max) checking -count = MB_MAX_FRAME; -if (.available()) { while(.available() > 0 && count > MB_SERIAL_BUFFER) { read(); count--;}; return(); } -if (_len > 0) { - process(); -} - -*/ -void ModbusRTUSlave::task() { + // Cleanup _len = 0; - - while ((*_port).available() > _len) { - _len = (*_port).available(); - delayMicroseconds(_t15); - } - - if (_len == 0) return; - Serial.println(_len); - uint8_t i; - //_frame = (uint8_t*) malloc(_len); - _frame = (uint8_t*) malloc(MB_MAX_FRAME); - for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); - - if (receive(_frame, _slaveId)) { - if (_reply == Modbus::REPLY_NORMAL) - sendPDU(_frame, _slaveId); - else - if (_reply == Modbus::REPLY_ECHO) - send(_frame); - } - free(_frame); - _len = 0; + _frame = nullptr; } \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 9a8b36d..978b6f1 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -5,7 +5,7 @@ */ #pragma once -#include +#include "Modbus.h" #define MB_SOFTWARE_SERIAL @@ -16,6 +16,7 @@ #define MB_BROADCAST 0 #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 +#define MB_MAX_TIME 10 //#define MB_STATIC_FRAME 1 uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); @@ -24,8 +25,10 @@ class ModbusRTU : public Modbus { protected: Stream* _port; int _txPin; - unsigned int _t15; // inter character time out - unsigned int _t35; // frame delay + unsigned int _t15; // inter character time out in uS + unsigned int _t35; // frame delay in uS + unsigned int _t; // frame delay in mS + uint32_t t = 0; public: #ifdef MB_SOFTWARE_SERIAL bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); @@ -37,9 +40,6 @@ class ModbusRTU : public Modbus { #endif #endif virtual void task() = 0; - bool receive(uint8_t* frame, uint8_t slaveId); - bool sendPDU(uint8_t* pduframe, uint8_t slaveId); - bool send(uint8_t* frame); }; class ModbusRTUSlave : public ModbusRTU { @@ -76,6 +76,47 @@ class ModbusRTUMaster : public ModbusRTU { uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); } +// Table of CRC values +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 +}; +union bytes_t { + uint32_t wide32; + uint8_t wide8[4]; +} +uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { + bytes_t t; + uint8_t Index = 0xFF ^ address; + t.wide32 = (uint32)_auchCRC[Index]; + uint8_t CRCHi = 0xFF ^ t.wide8[0]; // Hi + uint8_t CRCLo = t.wide8[1]; //Low + + while (pduLen--) { + Index = CRCHi ^ *pduFrame++; + t.wide32 = (uint32)_auchCRC[Index]; + CRCHi = CRCLo ^ t.wide8[0]; // Hi + CRCLo = t.wide8[1]; //Low + } + + return (CRCHi << 8) | CRCLo; +} */ /* Table of CRC values for high�order byte */ const uint8_t _auchCRCHi[] = { From b831d96721f4f7d0e4d3865a72bfde421e9258a4 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 20 May 2019 11:52:32 +0500 Subject: [PATCH 131/288] Merge slave and master to ModbusRTU --- src/ModbusIP_ESP8266.cpp | 2 +- src/ModbusRTU.cpp | 71 ++++++++++++++++++++++++++++++++++------ src/ModbusRTU.h | 42 ++++++++++++++---------- 3 files changed, 87 insertions(+), 28 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 7b633bb..cd32b39 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -186,7 +186,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 tmp.transactionId = transactionId; tmp.timestamp = millis(); tmp.cb = cb; - tmp.data = data; + 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); diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 4111ac7..c6dfd9a 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -1,7 +1,6 @@ /* - ModbusSerial.cpp - Source for Modbus Serial Library - Copyright (C) 2014 André Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) + ModbusRTU.cpp - Source for ModbusRTU Library + Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include @@ -26,12 +25,12 @@ uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } -bool ModbusRTUSlave::setSlaveId(uint8_t slaveId){ +bool ModbusRTU::setSlaveId(uint8_t slaveId){ _slaveId = slaveId; return true; } -uint8_t ModbusRTUSlave::getSlaveId() { +uint8_t ModbusRTU::getSlaveId() { return _slaveId; } @@ -65,13 +64,47 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int1 _t15 = 15000000/baud; // 1T * 1.5 = T1.5 _t35 = 35000000/baud; // 1T * 3.5 = T3.5 _t = (_t35 / 1000) + 1; - Serial.print(_t); } return true; } -void ModbusRTUSlave::task() { +bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { +// Prepare and send ModbusIP 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 + if (_slaveId) return false; // Break if waiting for previous request result + uint16_t crc = calcCrc(slaveId, _frame, _len); + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + _port->write(slaveId); //Send slaveId + for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU + //Send CRC + _port->write(crc >> 8); + _port->write(crc & 0xFF); + _port->flush(); + delay(_t); + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } + if (waitResponse) { + _slaveId = slaveId; + _timestamp = millis(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + _len = 0; + } + return true; +} + +void ModbusRTU::task() { if (_port->available() > _len) { _len = _port->available(); t = millis(); @@ -82,6 +115,11 @@ void ModbusRTUSlave::task() { uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte + if (isMaster && _slaveId == 0) { // Check is slaveId is set + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected + _len = 0; + return; + } if (address != MB_BROADCAST && address != _slaveId) { // SlaveId Check for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach _len = 0; @@ -103,10 +141,23 @@ void ModbusRTUSlave::task() { _frame = nullptr; return; } + if (isMaster) { + _reply = EX_SUCCESS; + if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + masterPDU(_frame, _sentFrame, _sentReg, _data); + } else { + _reply = EX_UNEXPECTED_RESPONSE; + } + if (cbEnabled && _cb) { + _cb((ResultCode)_reply, 0, nullptr); + } + _reply = Modbus::REPLY_OFF; // No reply if master + } else { + slavePDU(_frame); + if (address == MB_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + } - slavePDU(_frame); - - if (address == MB_BROADCAST) _reply = Modbus::REPLY_OFF; //No reply to Broadcasts if (_reply != Modbus::REPLY_OFF) { if (_txPin >= 0) { digitalWrite(_txPin, HIGH); diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 978b6f1..a742a0c 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,7 +1,6 @@ /* - ModbusSerial.h - Header for ModbusSerial Library - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) + ModbusRTU.h - Header for ModbusRTU Library + Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once @@ -21,6 +20,8 @@ uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); + class ModbusRTU : public Modbus { protected: Stream* _port; @@ -29,7 +30,21 @@ class ModbusRTU : public Modbus { unsigned int _t35; // frame delay in uS unsigned int _t; // frame delay in mS uint32_t t = 0; + bool isMaster = false; + uint8_t _slaveId; + uint32_t _timestamp = 0; + cbTransaction _cb = nullptr; + void* _data = nullptr; + uint8_t* _sentFrame = nullptr; + TAddress _sentReg = COIL(0); + bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); public: + /* + Bits per Byte: 1 start bit + 8 data bits, least significant bit sent first + 1 bit for parity completion or stop bit + 1 stop bit + */ #ifdef MB_SOFTWARE_SERIAL bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); #else @@ -39,22 +54,12 @@ class ModbusRTU : public Modbus { bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); #endif #endif - virtual void task() = 0; -}; - -class ModbusRTUSlave : public ModbusRTU { - private: - uint8_t _slaveId; - public: + void task(); + void master() {isMaster = true;}; + void slave(uint8_t slaveId) {}; bool setSlaveId(uint8_t slaveId); uint8_t getSlaveId(); - void task() override; -}; /* -class ModbusRTUMaster : public ModbusRTU { - public: - void task(); - uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); @@ -75,7 +80,10 @@ class ModbusRTUMaster : public ModbusRTU { uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -} +*/ +}; + +/* // Table of CRC values const uint16_t _auchCRC[] PROGMEM = { 0x0000, 0xC1C0, 0x81C1, 0x4001, 0x01C3, 0xC003, 0x8002, 0x41C2, 0x01C6, 0xC006, 0x8007, 0x41C7, 0x0005, 0xC1C5, 0x81C4 From c12473322983cccee43aecc7b0ee409494f1b7ac Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 20 May 2019 15:06:04 +0500 Subject: [PATCH 132/288] Move CRC table to PROGMEM --- src/ModbusRTU.cpp | 55 ++++++++++++----------- src/ModbusRTU.h | 109 ++++++++++++---------------------------------- 2 files changed, 57 insertions(+), 107 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index c6dfd9a..9cac53b 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -6,20 +6,19 @@ /* calcCrc optimisation required: 1. Move inside class code -2. Merge CRC tables to one and move it to PROGMEM -3. in task() probably don't read crc to buffer? +2. in task() probably don't read crc to buffer? */ -uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { - uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; - - Index = CRCHi ^ address; - CRCHi = CRCLo ^ _auchCRCHi[Index]; - CRCLo = _auchCRCLo[Index]; +uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { + uint8_t Index = 0xFF ^ address; + uint16_t val = pgm_read_word(_auchCRC + Index); + uint8_t CRCHi = 0xFF ^ highByte(val); // Hi + uint8_t CRCLo = lowByte(val); //Low while (pduLen--) { Index = CRCHi ^ *pduFrame++; - CRCHi = CRCLo ^ _auchCRCHi[Index]; - CRCLo = _auchCRCLo[Index]; + val = pgm_read_word(_auchCRC + Index); + CRCHi = CRCLo ^ highByte(val); // Hi + CRCLo = lowByte(val); //Low } return (CRCHi << 8) | CRCLo; @@ -57,24 +56,14 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int1 } if (baud > 19200) { - _t15 = 750; - _t35 = 1750; _t = 2; } else { - _t15 = 15000000/baud; // 1T * 1.5 = T1.5 - _t35 = 35000000/baud; // 1T * 3.5 = T3.5 - _t = (_t35 / 1000) + 1; + _t = (35000/baud) + 1; } - return true; } -bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { -// Prepare and send ModbusIP 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 ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse, uint8_t unit) { if (_slaveId) return false; // Break if waiting for previous request result uint16_t crc = calcCrc(slaveId, _frame, _len); if (_txPin >= 0) { @@ -110,7 +99,10 @@ void ModbusRTU::task() { t = millis(); return; } - if (_len == 0) return; // No data + if (_len == 0) { // No data + if (isMaster) cleanup(); + return; + } if (millis() - t < _t) return; // Wait data whitespace uint8_t address = _port->read(); //first byte of frame = address @@ -120,7 +112,7 @@ void ModbusRTU::task() { _len = 0; return; } - if (address != MB_BROADCAST && address != _slaveId) { // SlaveId Check + if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach _len = 0; return; @@ -155,7 +147,7 @@ void ModbusRTU::task() { _reply = Modbus::REPLY_OFF; // No reply if master } else { slavePDU(_frame); - if (address == MB_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts } if (_reply != Modbus::REPLY_OFF) { @@ -179,4 +171,17 @@ void ModbusRTU::task() { _len = 0; free(_frame); _frame = nullptr; +} + +bool ModbusRTU::cleanup() { + // Remove timedouted request and forced event + if (millis() - _timestamp > MODBUSRTU_TIMEOUT) { + _cb(Modbus::EX_TIMEOUT, 0, nullptr); + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + return true; + } + return false; } \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index a742a0c..237a5bf 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -12,10 +12,11 @@ #include #endif -#define MB_BROADCAST 0 +#define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 #define MB_MAX_TIME 10 +#define MODBUSRTU_TIMEOUT 1000 //#define MB_STATIC_FRAME 1 uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); @@ -37,7 +38,13 @@ class ModbusRTU : public Modbus { void* _data = nullptr; uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); - bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); + bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true, uint8_t unit = 0); + // Prepare and send ModbusIP 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 cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events public: /* Bits per Byte: 1 start bit @@ -83,87 +90,25 @@ class ModbusRTU : public Modbus { */ }; -/* + // Table of CRC values 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 + 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 }; -union bytes_t { - uint32_t wide32; - uint8_t wide8[4]; -} -uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { - bytes_t t; - uint8_t Index = 0xFF ^ address; - t.wide32 = (uint32)_auchCRC[Index]; - uint8_t CRCHi = 0xFF ^ t.wide8[0]; // Hi - uint8_t CRCLo = t.wide8[1]; //Low - - while (pduLen--) { - Index = CRCHi ^ *pduFrame++; - t.wide32 = (uint32)_auchCRC[Index]; - CRCHi = CRCLo ^ t.wide8[0]; // Hi - CRCLo = t.wide8[1]; //Low - } - - return (CRCHi << 8) | CRCLo; -} -*/ -/* Table of CRC values for high�order byte */ -const uint8_t _auchCRCHi[] = { - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, - 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, - 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, - 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, - 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, - 0x40}; - -/* Table of CRC values for low�order byte */ -const uint8_t _auchCRCLo[] = { - 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, - 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, - 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, - 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, - 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, - 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, - 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, - 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, - 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, - 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, - 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, - 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, - 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, - 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, - 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, - 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, - 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, - 0x40}; \ No newline at end of file From 45b6bc68c6c5254898cb6777dbfe57abaddecaab Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 20 May 2019 17:28:41 +0500 Subject: [PATCH 133/288] Move crc routine inside ModbusRTU class, test Master methods added, task() fix for Master --- src/ModbusRTU.cpp | 52 ++++++++++++++++++++++++++++------------------- src/ModbusRTU.h | 28 +++++++++++-------------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 9cac53b..d4ad208 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -9,18 +9,17 @@ 2. in task() probably don't read crc to buffer? */ -uint16_t calcCrc(uint8_t address, uint8_t* pduFrame, uint8_t pduLen) { - uint8_t Index = 0xFF ^ address; - uint16_t val = pgm_read_word(_auchCRC + Index); +uint16_t ModbusRTU::crc(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--) { - Index = CRCHi ^ *pduFrame++; - val = pgm_read_word(_auchCRC + Index); + i = CRCHi ^ *frame++; + val = pgm_read_word(_auchCRC + i); CRCHi = CRCLo ^ highByte(val); // Hi CRCLo = lowByte(val); //Low } - return (CRCHi << 8) | CRCLo; } @@ -63,9 +62,9 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int1 return true; } -bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse, uint8_t unit) { +bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { if (_slaveId) return false; // Break if waiting for previous request result - uint16_t crc = calcCrc(slaveId, _frame, _len); + uint16_t newCrc = crc(slaveId, _frame, _len); if (_txPin >= 0) { digitalWrite(_txPin, HIGH); delay(1); @@ -73,8 +72,8 @@ bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* _port->write(slaveId); //Send slaveId for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU //Send CRC - _port->write(crc >> 8); - _port->write(crc & 0xFF); + _port->write(newCrc >> 8); + _port->write(newCrc & 0xFF); _port->flush(); delay(_t); if (_txPin >= 0) { @@ -125,9 +124,9 @@ void ModbusRTU::task() { return; } for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc - u_int crc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc + u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc _len = _len - 2; // Decrease by CRC 2 bytes - if (crc != calcCrc(address, _frame, _len)) { // CRC Check + if (frameCrc != crc(address, _frame, _len)) { // CRC Check _len = 0; // Cleanup if wrong crc free(_frame); _frame = nullptr; @@ -138,11 +137,13 @@ void ModbusRTU::task() { if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested // Procass incoming frame as master masterPDU(_frame, _sentFrame, _sentReg, _data); - } else { - _reply = EX_UNEXPECTED_RESPONSE; - } - if (cbEnabled && _cb) { - _cb((ResultCode)_reply, 0, nullptr); + if (cbEnabled && _cb) { + _cb((ResultCode)_reply, 0, nullptr); + } + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; } _reply = Modbus::REPLY_OFF; // No reply if master } else { @@ -158,9 +159,9 @@ void ModbusRTU::task() { _port->write(_slaveId); //Send slaveId for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU //Send CRC - uint16_t crc = calcCrc(_slaveId, _frame, _len); - _port->write(crc >> 8); - _port->write(crc & 0xFF); + uint16_t newCrc = crc(_slaveId, _frame, _len); + _port->write(newCrc >> 8); + _port->write(newCrc & 0xFF); _port->flush(); delay(_t); //delayMicroseconds(_t35); if (_txPin >= 0) { @@ -175,7 +176,7 @@ void ModbusRTU::task() { bool ModbusRTU::cleanup() { // Remove timedouted request and forced event - if (millis() - _timestamp > MODBUSRTU_TIMEOUT) { + if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { _cb(Modbus::EX_TIMEOUT, 0, nullptr); free(_sentFrame); _sentFrame = nullptr; @@ -184,4 +185,13 @@ bool ModbusRTU::cleanup() { return true; } return false; +} + +uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { + readSlave(offset, value, FC_WRITE_REG); + return send(slaveId, HREG(offset), cb, nullptr, cb); +} +uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { + readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); + return send(slaveId, COIL(offset), cb, nullptr, cb); } \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 237a5bf..e686c34 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -3,7 +3,6 @@ Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once - #include "Modbus.h" #define MB_SOFTWARE_SERIAL @@ -19,16 +18,12 @@ #define MODBUSRTU_TIMEOUT 1000 //#define MB_STATIC_FRAME 1 -uint16_t calcCrc(uint16_t address, uint8_t* pduframe, uint8_t pdulen); - typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); class ModbusRTU : public Modbus { protected: Stream* _port; int _txPin; - unsigned int _t15; // inter character time out in uS - unsigned int _t35; // frame delay in uS unsigned int _t; // frame delay in mS uint32_t t = 0; bool isMaster = false; @@ -38,20 +33,21 @@ class ModbusRTU : public Modbus { void* _data = nullptr; uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); - bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true, uint8_t unit = 0); + bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); // Prepare and send ModbusIP 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 cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events + uint16_t crc(uint8_t address, uint8_t* frame, uint8_t pdulen); public: - /* - Bits per Byte: 1 start bit - 8 data bits, least significant bit sent first - 1 bit for parity completion or stop bit - 1 stop bit - */ + /* + Bits per Byte: 1 start bit + 8 data bits, least significant bit sent first + 1 bit for parity completion or stop bit + 1 stop bit + */ #ifdef MB_SOFTWARE_SERIAL bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); #else @@ -66,9 +62,9 @@ class ModbusRTU : public Modbus { void slave(uint8_t slaveId) {}; bool setSlaveId(uint8_t slaveId); uint8_t getSlaveId(); + uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); + uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); /* - uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); - uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); @@ -92,7 +88,7 @@ class ModbusRTU : public Modbus { // Table of CRC values -const uint16_t _auchCRC[] PROGMEM = { +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, @@ -111,4 +107,4 @@ const uint16_t _auchCRC[] PROGMEM = { 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 -}; +}; \ No newline at end of file From b516b718db4b91178b691cd30c073f67cc572d0c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 20 May 2019 17:56:49 +0500 Subject: [PATCH 134/288] Master readCoil tested --- src/Modbus.h | 5 ++++- src/ModbusIP_ESP8266.h | 2 +- src/ModbusRTU.cpp | 5 +++++ src/ModbusRTU.h | 8 +++++--- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index 1dee580..326b1b1 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -37,6 +37,7 @@ struct TRegister; typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type + struct TAddress { enum RegType {COIL, ISTS, IREG, HREG}; RegType type; @@ -227,4 +228,6 @@ class Modbus { 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); -}; \ No newline at end of file +}; + +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index f90d9af..f9bb85a 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -24,7 +24,7 @@ // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); -typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); +//typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); typedef struct TTransaction { uint16_t transactionId; diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index d4ad208..f882802 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -194,4 +194,9 @@ uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); return send(slaveId, COIL(offset), cb, nullptr, cb); +} +uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_COILS); + return send(slaveId, COIL(offset), cb, value); } \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index e686c34..4f12b01 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -18,7 +18,7 @@ #define MODBUSRTU_TIMEOUT 1000 //#define MB_STATIC_FRAME 1 -typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); +//typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); class ModbusRTU : public Modbus { protected: @@ -58,12 +58,14 @@ class ModbusRTU : public Modbus { #endif #endif void task(); - void master() {isMaster = true;}; - void slave(uint8_t slaveId) {}; + void master() { isMaster = true; }; + void slave(uint8_t slaveId) {_slaveId = slaveId;}; + uint8_t slave() { return _slaveId; } bool setSlaveId(uint8_t slaveId); uint8_t getSlaveId(); uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); + uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); /* uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); From 5ca5e30977002aafd4011868be79ca0be52cb5c8 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 21 May 2019 11:41:08 +0500 Subject: [PATCH 135/288] Switch to templates, add HardwareSerial support --- API.md | 5 +- README.md | 47 +++++---- src/ModbusRTU.cpp | 202 --------------------------------------- src/ModbusRTU.h | 238 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 253 insertions(+), 239 deletions(-) delete mode 100644 src/ModbusRTU.cpp diff --git a/API.md b/API.md index ac063e1..48b6931 100644 --- a/API.md +++ b/API.md @@ -49,6 +49,9 @@ Processing routine. Should be periodically called form loop(). ```c void begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); void begin(HardwareSerial* port, uint32_t baud, int16_t txPin=-1); +void master(); +void slave(uint8_t slaveId); +uint8_t slave(); ``` txPin is not implemented yet. @@ -137,7 +140,7 @@ Set mode for automatic connect on read*\write*\push*\pull* calls. Disabled by de ## Callbacks API ```c -void cbEnable(bool state = TRUE); +void cbEnable(bool state = true); void cbDisable(); ``` diff --git a/README.md b/README.md index 5ef7863..1230c60 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 v3.0 +# Modbus RTU and IP Master-Slave Library for ESP8266/ESP32 v3.0 -**Relase state: Development** +**Release state: DEVELOPMENT** Everything including API is subject to be changed diring stage. Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. @@ -50,10 +50,34 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) 4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Read Multiple HRegs/IRegs for ESP master is limited to 63 per query. +5. ModbusRTU at this moment working on 9600 only for some reason. ## Last Changes ```diff +// 3.0.0 ++ ModbusRTU Slave ++ ModbusRTU Master ++ Tested with ESP8266 +- Test with ESP32 +- Documentation changes +// ToDo later +- Modbus Read/Write File Records function (0x14/0x15) +- Modbus Write Mask Register function (0x16) +- Modbus Read/Write Registers function (0x17) +- Modbus Serial line-specific functions (0x08) +// 2.1.0 ++ Slave. Fix error response on write multiple Hregs\Coils ++ Slave. Fix writeCoil() for multiple coils ++ Master. dropTransactions() ++ Master. disconnect() ++ ~ModbusIP() ++ task() cleanup ++ Modify examples ++ Slave. Allow only single incoming master connection per IP +// 2.0.1 ++ Master. Fix readCoil\Hreg\Ists\Ireg not read value from slave ++ Fix crash on disconnect with Arduino Core 2.5.x // 2.0.0 + Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. + Change object's list implementation to *std::vector* @@ -76,25 +100,6 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + added removeOnSetCoil\... methods + added read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id + added ability to auto connect to slave. Setting is global. Disabled by default. -// 2.0.1 -+ Master. Fix readCoil\Hreg\Ists\Ireg not read value from slave -+ Fix crash on disconnect with Arduino Core 2.5.x -// 2.1.0 -+ Slave. Fix error response on write multiple Hregs\Coils -+ Slave. Fix writeCoil() for multiple coils -+ Master. dropTransactions() -+ Master. disconnect() -+ ~ModbusIP() -+ task() cleanup -+ Modify examples -+ Slave. Allow only single incoming master connection per IP -// 3.0.0 -+ ModbusRTU Slave -- ModbusRTU Master -// ToDo later -- Modbus Read/Write File Records function -- Modbus Write Mask Register function -- Modbus Serial line-specific functions ``` ## Contributions diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp deleted file mode 100644 index f882802..0000000 --- a/src/ModbusRTU.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - ModbusRTU.cpp - Source for ModbusRTU Library - Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#include - -/* calcCrc optimisation required: -1. Move inside class code -2. in task() probably don't read crc to buffer? -*/ - -uint16_t ModbusRTU::crc(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; -} - -bool ModbusRTU::setSlaveId(uint8_t slaveId){ - _slaveId = slaveId; - return true; -} - -uint8_t ModbusRTU::getSlaveId() { - return _slaveId; -} - -#ifdef MB_SOFTWARE_SERIAL -bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - port->begin(baud); -#else -#ifdef ESP8266 -bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { - port->begin(baud, format); -#else -bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { - port->begin(baud); -#endif -#endif - _port = port; - _txPin = txPin; - - delay(2000); // ??? - - if (txPin >= 0) { - pinMode(txPin, OUTPUT); - digitalWrite(txPin, LOW); - } - - if (baud > 19200) { - _t = 2; - } else { - _t = (35000/baud) + 1; - } - return true; -} - -bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { - if (_slaveId) return false; // Break if waiting for previous request result - uint16_t newCrc = crc(slaveId, _frame, _len); - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - _port->write(slaveId); //Send slaveId - for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU - //Send CRC - _port->write(newCrc >> 8); - _port->write(newCrc & 0xFF); - _port->flush(); - delay(_t); - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); - } - if (waitResponse) { - _slaveId = slaveId; - _timestamp = millis(); - _cb = cb; - _data = data; - _sentFrame = _frame; - _sentReg = startreg; - _frame = nullptr; - _len = 0; - } - return true; -} - -void ModbusRTU::task() { - if (_port->available() > _len) { - _len = _port->available(); - t = millis(); - return; - } - if (_len == 0) { // No data - if (isMaster) cleanup(); - return; - } - if (millis() - t < _t) return; // Wait data whitespace - - uint8_t address = _port->read(); //first byte of frame = address - _len--; // Decrease by slaveId byte - if (isMaster && _slaveId == 0) { // Check is slaveId is set - for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected - _len = 0; - return; - } - if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check - for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach - _len = 0; - return; - } - - _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; - return; - } - for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc - u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc - _len = _len - 2; // Decrease by CRC 2 bytes - if (frameCrc != crc(address, _frame, _len)) { // CRC Check - _len = 0; // Cleanup if wrong crc - free(_frame); - _frame = nullptr; - return; - } - if (isMaster) { - _reply = EX_SUCCESS; - if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested - // Procass incoming frame as master - masterPDU(_frame, _sentFrame, _sentReg, _data); - if (cbEnabled && _cb) { - _cb((ResultCode)_reply, 0, nullptr); - } - free(_sentFrame); - _sentFrame = nullptr; - _data = nullptr; - _slaveId = 0; - } - _reply = Modbus::REPLY_OFF; // No reply if master - } else { - slavePDU(_frame); - if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts - } - - if (_reply != Modbus::REPLY_OFF) { - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - _port->write(_slaveId); //Send slaveId - for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU - //Send CRC - uint16_t newCrc = crc(_slaveId, _frame, _len); - _port->write(newCrc >> 8); - _port->write(newCrc & 0xFF); - _port->flush(); - delay(_t); //delayMicroseconds(_t35); - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); - } - } - // Cleanup - _len = 0; - free(_frame); - _frame = nullptr; -} - -bool ModbusRTU::cleanup() { - // Remove timedouted request and forced event - if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { - _cb(Modbus::EX_TIMEOUT, 0, nullptr); - free(_sentFrame); - _sentFrame = nullptr; - _data = nullptr; - _slaveId = 0; - return true; - } - return false; -} - -uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { - readSlave(offset, value, FC_WRITE_REG); - return send(slaveId, HREG(offset), cb, nullptr, cb); -} -uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { - readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(slaveId, COIL(offset), cb, nullptr, cb); -} -uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; - readSlave(offset, numregs, FC_READ_COILS); - return send(slaveId, COIL(offset), cb, value); -} \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 4f12b01..64cdf9c 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,15 +1,11 @@ /* - ModbusRTU.h - Header for ModbusRTU Library + ModbusRTU.h - ModbusRTU Library Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once -#include "Modbus.h" - -#define MB_SOFTWARE_SERIAL - -#ifdef MB_SOFTWARE_SERIAL +#include +#include #include -#endif #define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 @@ -18,12 +14,15 @@ #define MODBUSRTU_TIMEOUT 1000 //#define MB_STATIC_FRAME 1 -//typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); - +template +// Possible declarations are +// ModbusRTU mb(Serial); +// ModbusRTU mb(SoftSerial); class ModbusRTU : public Modbus { protected: - Stream* _port; + S* _port; int _txPin; + uint32_t _t15; unsigned int _t; // frame delay in mS uint32_t t = 0; bool isMaster = false; @@ -48,15 +47,13 @@ class ModbusRTU : public Modbus { 1 bit for parity completion or stop bit 1 stop bit */ - #ifdef MB_SOFTWARE_SERIAL + bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); - #else #ifdef ESP8266 bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); #else bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); #endif - #endif void task(); void master() { isMaster = true; }; void slave(uint8_t slaveId) {_slaveId = slaveId;}; @@ -88,7 +85,6 @@ class ModbusRTU : public Modbus { */ }; - // 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, @@ -109,4 +105,216 @@ static const uint16_t _auchCRC[] PROGMEM = { 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 -}; \ No newline at end of file +}; + +template +uint16_t ModbusRTU::crc(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; +} + +template +bool ModbusRTU::setSlaveId(uint8_t slaveId){ + _slaveId = slaveId; + return true; +} + +template +uint8_t ModbusRTU::getSlaveId() { + return _slaveId; +} + +#ifdef ESP8266 +template <> +bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { + port->begin(baud, format); +#else +template <> +bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { + port->begin(baud); +#endif + port->begin(baud); + _port = port; + _txPin = txPin; + if (txPin >= 0) { + pinMode(txPin, OUTPUT); + digitalWrite(txPin, LOW); + } + if (baud > 19200) { + _t = 2; + } else { + _t = (35000/baud) + 1; + } + return true; +} + +template <> +bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { + port->begin(baud); + _port = port; + _txPin = txPin; + if (txPin >= 0) { + pinMode(txPin, OUTPUT); + digitalWrite(txPin, LOW); + } + if (baud > 19200) { + _t = 2; + } else { + _t = (35000/baud) + 1; + } + return true; +} + +template +bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { + if (_slaveId) return false; // Break if waiting for previous request result + uint16_t newCrc = crc(slaveId, _frame, _len); + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + _port->write(slaveId); //Send slaveId + for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU + //Send CRC + _port->write(newCrc >> 8); + _port->write(newCrc & 0xFF); + _port->flush(); + delay(_t); + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } + if (waitResponse) { + _slaveId = slaveId; + _timestamp = millis(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + _len = 0; + } + return true; +} + +template +void ModbusRTU::task() { + if (_port->available() > _len) { + _len = _port->available(); + t = millis(); + return; + } + if (_len == 0) { // No data + if (isMaster) cleanup(); + return; + } + if (millis() - t < _t) return; // Wait data whitespace + + uint8_t address = _port->read(); //first byte of frame = address + _len--; // Decrease by slaveId byte + if (isMaster && _slaveId == 0) { // Check is slaveId is set + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected + _len = 0; + return; + } + if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach + _len = 0; + return; + } + + _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; + return; + } + for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc + u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc + _len = _len - 2; // Decrease by CRC 2 bytes + if (frameCrc != crc(address, _frame, _len)) { // CRC Check + _len = 0; // Cleanup if wrong crc + free(_frame); + _frame = nullptr; + return; + } + if (isMaster) { + _reply = EX_SUCCESS; + if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + masterPDU(_frame, _sentFrame, _sentReg, _data); + if (cbEnabled && _cb) { + _cb((ResultCode)_reply, 0, nullptr); + } + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + } + _reply = Modbus::REPLY_OFF; // No reply if master + } else { + slavePDU(_frame); + if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + } + + if (_reply != Modbus::REPLY_OFF) { + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + _port->write(_slaveId); //Send slaveId + for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU + //Send CRC + uint16_t newCrc = crc(_slaveId, _frame, _len); + _port->write(newCrc >> 8); + _port->write(newCrc & 0xFF); + _port->flush(); + delay(_t); //delayMicroseconds(_t35); + if (_txPin >= 0) { + digitalWrite(_txPin, LOW); + } + } + // Cleanup + _len = 0; + free(_frame); + _frame = nullptr; +} + +template +bool ModbusRTU::cleanup() { + // Remove timeouted request and forced event + if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { + _cb(Modbus::EX_TIMEOUT, 0, nullptr); + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + return true; + } + return false; +} + +template +uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { + readSlave(offset, value, FC_WRITE_REG); + return send(slaveId, HREG(offset), cb, nullptr, cb); +} +template +uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { + readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); + return send(slaveId, COIL(offset), cb, nullptr, cb); +} +template +uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_COILS); + return send(slaveId, COIL(offset), cb, value); +} \ No newline at end of file From 176560aa046e1f0e17ab8ecf8892679dde6963ad Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 21 May 2019 11:56:54 +0500 Subject: [PATCH 136/288] Add rest of master methods --- src/ModbusRTU.h | 183 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 162 insertions(+), 21 deletions(-) diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 64cdf9c..76bd6c7 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -12,6 +12,7 @@ #define MB_SERIAL_BUFFER 128 #define MB_MAX_TIME 10 #define MODBUSRTU_TIMEOUT 1000 +#define MODBUSRTU_ADD_REG //#define MB_STATIC_FRAME 1 template @@ -33,7 +34,7 @@ class ModbusRTU : public Modbus { uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); - // Prepare and send ModbusIP frame. _frame buffer and _len should be filled with Modbus data + // 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 @@ -63,26 +64,23 @@ class ModbusRTU : public Modbus { uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -/* - uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - - uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - - uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -*/ + uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + + uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); + + uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); }; // Table of CRC values @@ -317,4 +315,147 @@ uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, u if (numregs < 0x0001 || numregs > 0x007B) return false; readSlave(offset, numregs, FC_READ_COILS); return send(slaveId, COIL(offset), cb, value); +} + +template +uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); + return send(slaveId, COIL(offset), cb, nullptr, cb); +} + +template +uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); + return send(slaveId, HREG(offset), cb, nullptr, cb); +} + +template +uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_REGS); + return send(slaveId, HREG(offset), cb, value); +} + +template +uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_INPUT_STAT); + return send(slaveId, ISTS(offset), cb, value); +} + +template +uint16_t ModbusRTU::readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + readSlave(offset, numregs, FC_READ_INPUT_REGS); + return send(slaveId, IREG(offset), cb, value); +} + +template +uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(COIL(from))) return false; + if (numregs == 1) { + readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); + } else { + writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); + } + return send(slaveId, COIL(from), cb); +} + +template +uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSRTU_ADD_REG + addCoil(to, numregs); + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(slaveId, COIL(to), cb); +} + +template +uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSRTU_ADD_REG + addIsts(to, numregs); + #endif + readSlave(from, numregs, FC_READ_INPUT_STAT); + return send(slaveId, ISTS(to), cb); +} + +template +uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(HREG(from))) return false; + if (numregs == 1) { + readSlave(to, Hreg(from), FC_WRITE_REG); + } else { + writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); + } + return send(slaveId, HREG(from), cb); +} + +template +uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSRTU_ADD_REG + addHreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(slaveId, HREG(to), cb); +} + +template +uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSRTU_ADD_REG + addIreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_INPUT_REGS); + return send(slaveId, IREG(to), cb); +} + +template +uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(IREG(from))) return false; + if (numregs == 1) { + readSlave(to, Ireg(from), FC_WRITE_REG); + } else { + writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); + } + return send(slaveId, IREG(from), cb); +} + +template +uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + if (!searchRegister(ISTS(from))) return false; + if (numregs == 1) { + readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); + } else { + writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); + } + return send(slaveId, ISTS(from), cb); +} + +template +uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSRTU_ADD_REG + addIreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(slaveId, IREG(to), cb); +} + +template +uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007B) return false; + #ifdef MODBUSRTU_ADD_REG + addIsts(to, numregs); + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(slaveId, ISTS(to), cb); } \ No newline at end of file From 6d7213275db68a8058af5836576a529493eb46df Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 21 May 2019 12:07:14 +0500 Subject: [PATCH 137/288] Add ModbusRTU examples --- README.md | 2 ++ examples/RTU-master/RTU-Master.ino | 34 ++++++++++++++++++++++++++++++ examples/RTU-slave/RTU-slave.ino | 30 ++++++++++++++++++++++++++ src/ModbusRTU.h | 9 ++++---- 4 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 examples/RTU-master/RTU-Master.ino create mode 100644 examples/RTU-slave/RTU-slave.ino diff --git a/README.md b/README.md index 1230c60..fde3b30 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,10 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + ModbusRTU Slave + ModbusRTU Master + Tested with ESP8266 ++ CRC tables stored in PROGMEM - Test with ESP32 - Documentation changes +- Add examples // ToDo later - Modbus Read/Write File Records function (0x14/0x15) - Modbus Write Mask Register function (0x16) diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU-master/RTU-Master.ino new file mode 100644 index 0000000..4a6641f --- /dev/null +++ b/examples/RTU-master/RTU-Master.ino @@ -0,0 +1,34 @@ +/* + ModbusRTU ESP8266/ESP32 + Read multiple coils from slave device example + + (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#include +#include + +SoftwareSerial S(D1, D2, false, 128); +ModbusRTU mb; + +bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { + Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap()); + return true; +} + +void setup() { + Serial.begin(115200); + mb.begin(&S, 9600); + mb.master(); +} + +bool coils[20]; + +void loop() { + if (!mb.slave()) { + mb.readCoil(1, 1, coils, 20, cbWrite); + } + mb.task(); + yield(); +} \ No newline at end of file diff --git a/examples/RTU-slave/RTU-slave.ino b/examples/RTU-slave/RTU-slave.ino new file mode 100644 index 0000000..6a67b10 --- /dev/null +++ b/examples/RTU-slave/RTU-slave.ino @@ -0,0 +1,30 @@ +/* + ModbusRTU ESP8266/ESP32 + Simple slave example + + (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#include +#include + +#define REGN 10 + +SoftwareSerial S(D1, D2, false, 128); +ModbusRTU mb; + +void setup() { + Serial.begin(115200); + mb.begin(&S, 9600); + mb.slave(1); + mb.addHreg(REGN); + mb.Hreg(REGN, 100); +} + +bool coils[20]; + +void loop() { + mb.task(); + yield(); +} \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 76bd6c7..c30e8f1 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,5 +1,5 @@ /* - ModbusRTU.h - ModbusRTU Library + ModbusRTU Library for ESP8266/ESP32 Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once @@ -17,14 +17,13 @@ template // Possible declarations are -// ModbusRTU mb(Serial); -// ModbusRTU mb(SoftSerial); +// ModbusRTU mb(); +// ModbusRTU mb(); class ModbusRTU : public Modbus { protected: S* _port; int _txPin; - uint32_t _t15; - unsigned int _t; // frame delay in mS + unsigned int _t; // inter-frame delay in mS uint32_t t = 0; bool isMaster = false; uint8_t _slaveId; From 9d4e89d660b96368c84718e51e3c13b3ec9c4c4a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 21 May 2019 12:09:51 +0500 Subject: [PATCH 138/288] Fix mistype --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fde3b30..27914e5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Modbus RTU and IP Master-Slave Library for ESP8266/ESP32 v3.0 -**Release state: DEVELOPMENT** Everything including API is subject to be changed diring stage. +**Release state: DEVELOPMENT** Everything including API is subject to be changed during this stage. Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. From 28b477c41dd16230c7b8b24ba844e4308d595f08 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 21 May 2019 15:37:13 +0500 Subject: [PATCH 139/288] Extract write to rawSend() routine. Use internal TX enable control for SoftwareSerial --- API.md | 7 ++-- README.md | 1 + src/ModbusRTU.h | 96 ++++++++++++++++++++++--------------------------- 3 files changed, 48 insertions(+), 56 deletions(-) diff --git a/API.md b/API.md index 48b6931..d69759b 100644 --- a/API.md +++ b/API.md @@ -47,14 +47,15 @@ Processing routine. Should be periodically called form loop(). ### Modbus RTU Specific API ```c -void begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); -void begin(HardwareSerial* port, uint32_t baud, int16_t txPin=-1); +bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); +bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); +bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); void master(); void slave(uint8_t slaveId); uint8_t slave(); ``` -txPin is not implemented yet. +txPin is not tested yet. ### ModBus IP Slave specific API diff --git a/README.md b/README.md index 27914e5..1e1a780 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) 4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Read Multiple HRegs/IRegs for ESP master is limited to 63 per query. 5. ModbusRTU at this moment working on 9600 only for some reason. +6. Probably it's possible to use ModbusRTU with AVR using from https://github.com/maniacbug/StandardCplusplus ## Last Changes diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index c30e8f1..bba0f69 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -22,7 +22,7 @@ template class ModbusRTU : public Modbus { protected: S* _port; - int _txPin; + int16_t _txPin; unsigned int _t; // inter-frame delay in mS uint32_t t = 0; bool isMaster = false; @@ -32,12 +32,14 @@ class ModbusRTU : public Modbus { void* _data = nullptr; uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); + uint16_t maxRegs = 0x007B; bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* 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 crc(uint8_t address, uint8_t* frame, uint8_t pdulen); public: @@ -46,8 +48,9 @@ class ModbusRTU : public Modbus { 8 data bits, least significant bit sent first 1 bit for parity completion or stop bit 1 stop bit + SERIAL_8N2 + SERIAL_8E1 */ - bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); #ifdef ESP8266 bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); @@ -58,8 +61,6 @@ class ModbusRTU : public Modbus { void master() { isMaster = true; }; void slave(uint8_t slaveId) {_slaveId = slaveId;}; uint8_t slave() { return _slaveId; } - bool setSlaveId(uint8_t slaveId); - uint8_t getSlaveId(); uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); @@ -119,17 +120,6 @@ uint16_t ModbusRTU::crc(uint8_t address, uint8_t* frame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } -template -bool ModbusRTU::setSlaveId(uint8_t slaveId){ - _slaveId = slaveId; - return true; -} - -template -uint8_t ModbusRTU::getSlaveId() { - return _slaveId; -} - #ifdef ESP8266 template <> bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { @@ -139,6 +129,7 @@ template <> bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { port->begin(baud); #endif + maxRegs = port->setRxBufferSize(256) / 2 - 2; port->begin(baud); _port = port; _txPin = txPin; @@ -156,39 +147,54 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint1 template <> bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - port->begin(baud); _port = port; _txPin = txPin; - if (txPin >= 0) { - pinMode(txPin, OUTPUT); - digitalWrite(txPin, LOW); - } + _port->begin(baud); + if (txPin >= 0) + _port->setTransmitEnablePin(txPin); if (baud > 19200) { _t = 2; + //_port->enableIntTx(false); } else { _t = (35000/baud) + 1; } return true; } -template -bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { - if (_slaveId) return false; // Break if waiting for previous request result +template <> +bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { + uint16_t newCrc = crc(slaveId, _frame, _len); + _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(); + delay(_t); + return true; +} + +template <> +bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { uint16_t newCrc = crc(slaveId, _frame, _len); if (_txPin >= 0) { digitalWrite(_txPin, HIGH); delay(1); } - _port->write(slaveId); //Send slaveId - for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU - //Send CRC - _port->write(newCrc >> 8); - _port->write(newCrc & 0xFF); + _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(); delay(_t); - if (_txPin >= 0) { + if (_txPin >= 0) digitalWrite(_txPin, LOW); - } + return true; +} + +template +bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { + if (_slaveId) return false; // Break if waiting for previous request result + rawSend(slaveId, _frame, _len); if (waitResponse) { _slaveId = slaveId; _timestamp = millis(); @@ -261,24 +267,8 @@ void ModbusRTU::task() { slavePDU(_frame); if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts } - - if (_reply != Modbus::REPLY_OFF) { - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - _port->write(_slaveId); //Send slaveId - for (uint8_t i = 0 ; i < _len ; i++) _port->write(_frame[i]); // Send PDU - //Send CRC - uint16_t newCrc = crc(_slaveId, _frame, _len); - _port->write(newCrc >> 8); - _port->write(newCrc & 0xFF); - _port->flush(); - delay(_t); //delayMicroseconds(_t35); - if (_txPin >= 0) { - digitalWrite(_txPin, LOW); - } - } + if (_reply != Modbus::REPLY_OFF) + rawSend(_slaveId, _frame, _len); // Cleanup _len = 0; free(_frame); @@ -332,7 +322,7 @@ uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* val template uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > maxRegs) return false; readSlave(offset, numregs, FC_READ_REGS); return send(slaveId, HREG(offset), cb, value); } @@ -346,7 +336,7 @@ uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, u template uint16_t ModbusRTU::readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > maxRegs) return false; readSlave(offset, numregs, FC_READ_INPUT_REGS); return send(slaveId, IREG(offset), cb, value); } @@ -397,7 +387,7 @@ uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uin template uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > maxRegs) return false; #ifdef MODBUSRTU_ADD_REG addHreg(to, numregs); #endif @@ -407,7 +397,7 @@ uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uin template uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > maxRegs) return false; #ifdef MODBUSRTU_ADD_REG addIreg(to, numregs); #endif @@ -441,7 +431,7 @@ uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t fro template uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > maxRegs) return false; #ifdef MODBUSRTU_ADD_REG addIreg(to, numregs); #endif From cc71d8279086e8ce15a2ef217fd0324661ca6eb3 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 21 May 2019 15:56:42 +0500 Subject: [PATCH 140/288] Fix count of processing regs/coils/etc according to Modbus Spacification --- src/ModbusIP_ESP8266.cpp | 32 ++++++++++++++++---------------- src/ModbusRTU.h | 24 ++++++++++++------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index cd32b39..a4fdd05 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -256,13 +256,13 @@ uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransa } uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); return send(ip, COIL(offset), cb, unit, nullptr, cb); } uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; readSlave(offset, numregs, FC_READ_COILS); return send(ip, COIL(offset), cb, unit, value); } @@ -273,31 +273,31 @@ uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTr } uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); return send(ip, HREG(offset), cb, unit, nullptr, cb); } uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; readSlave(offset, numregs, FC_READ_REGS); return send(ip, HREG(offset), cb, unit, value); } uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; readSlave(offset, numregs, FC_READ_INPUT_STAT); return send(ip, ISTS(offset), cb, unit, value); } uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; readSlave(offset, numregs, FC_READ_INPUT_REGS); return send(ip, IREG(offset), cb, unit, value); } uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; if (!searchRegister(COIL(from))) return false; if (numregs == 1) { readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); @@ -308,7 +308,7 @@ uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t n } uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSIP_ADD_REG addCoil(to, numregs); #endif @@ -317,7 +317,7 @@ uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t n } uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSIP_ADD_REG addIsts(to, numregs); #endif @@ -326,7 +326,7 @@ uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t n } uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; if (!searchRegister(HREG(from))) return false; if (numregs == 1) { readSlave(to, Hreg(from), FC_WRITE_REG); @@ -337,7 +337,7 @@ uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t n } uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; #ifdef MODBUSIP_ADD_REG addHreg(to, numregs); #endif @@ -346,7 +346,7 @@ uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t n } uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; #ifdef MODBUSIP_ADD_REG addIreg(to, numregs); #endif @@ -355,7 +355,7 @@ uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t n } uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; if (!searchRegister(IREG(from))) return false; if (numregs == 1) { readSlave(to, Ireg(from), FC_WRITE_REG); @@ -366,7 +366,7 @@ uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint } uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; if (!searchRegister(ISTS(from))) return false; if (numregs == 1) { readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); @@ -377,7 +377,7 @@ uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint } uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; #ifdef MODBUSIP_ADD_REG addIreg(to, numregs); #endif @@ -386,7 +386,7 @@ uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint } uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSIP_ADD_REG addIsts(to, numregs); #endif diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index bba0f69..2b4bb71 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -32,7 +32,7 @@ class ModbusRTU : public Modbus { void* _data = nullptr; uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); - uint16_t maxRegs = 0x007B; + uint16_t maxRegs = 0x007D; bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); // Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data // slaveId - slave id @@ -301,21 +301,21 @@ uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, c } template uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; readSlave(offset, numregs, FC_READ_COILS); return send(slaveId, COIL(offset), cb, value); } template uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); return send(slaveId, COIL(offset), cb, nullptr, cb); } template uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); return send(slaveId, HREG(offset), cb, nullptr, cb); } @@ -329,7 +329,7 @@ uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* valu template uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; readSlave(offset, numregs, FC_READ_INPUT_STAT); return send(slaveId, ISTS(offset), cb, value); } @@ -343,7 +343,7 @@ uint16_t ModbusRTU::readIreg(uint8_t slaveId, uint16_t offset, uint16_t* valu template uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; if (!searchRegister(COIL(from))) return false; if (numregs == 1) { readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); @@ -355,7 +355,7 @@ uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uin template uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSRTU_ADD_REG addCoil(to, numregs); #endif @@ -365,7 +365,7 @@ uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uin template uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSRTU_ADD_REG addIsts(to, numregs); #endif @@ -375,7 +375,7 @@ uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uin template uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; if (!searchRegister(HREG(from))) return false; if (numregs == 1) { readSlave(to, Hreg(from), FC_WRITE_REG); @@ -407,7 +407,7 @@ uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uin template uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x007D) return false; if (!searchRegister(IREG(from))) return false; if (numregs == 1) { readSlave(to, Ireg(from), FC_WRITE_REG); @@ -419,7 +419,7 @@ uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t fro template uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; if (!searchRegister(ISTS(from))) return false; if (numregs == 1) { readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); @@ -441,7 +441,7 @@ uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t t template uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007B) return false; + if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSRTU_ADD_REG addIsts(to, numregs); #endif From bcb97b5f37313d6d6e1c63be2cb33ac35c9be38c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 21 May 2019 18:09:33 +0500 Subject: [PATCH 141/288] Fix read/pullCoils/Ists number limitation. Cleanup. --- README.md | 25 +++++++++++++++---------- src/Modbus.cpp | 3 --- src/Modbus.h | 7 ++++++- src/ModbusIP_ESP8266.h | 6 ++++-- src/ModbusRTU.h | 15 +++++++-------- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1e1a780..21fb65d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Modbus RTU and IP Master-Slave Library for ESP8266/ESP32 v3.0 +# ModbusRTU and ModbusIP Master-Slave Library for ESP8266/ESP32 v3.0 **Release state: DEVELOPMENT** Everything including API is subject to be changed during this stage. @@ -51,24 +51,29 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) 4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Read Multiple HRegs/IRegs for ESP master is limited to 63 per query. 5. ModbusRTU at this moment working on 9600 only for some reason. -6. Probably it's possible to use ModbusRTU with AVR using from https://github.com/maniacbug/StandardCplusplus +6. Probably it's possible to use ModbusRTU with other AVR boards using from https://github.com/maniacbug/StandardCplusplus ## Last Changes ```diff -// 3.0.0 +// 3.0.0-DEVEL + ModbusRTU Slave + ModbusRTU Master -+ Tested with ESP8266 ++ Test with SoftwareSerial on ESP8266 + CRC tables stored in PROGMEM -- Test with ESP32 ++ Fix functions register count limits to follow Modbus specification (or RX buffer limitations) ++ ModbusRTU examples added +- Test with HardwareSerial on ESP8266 +- Test on ESP32 +- Test TX control pin +- Test multiple Modbus* instances - Documentation changes -- Add examples // ToDo later -- Modbus Read/Write File Records function (0x14/0x15) -- Modbus Write Mask Register function (0x16) -- Modbus Read/Write Registers function (0x17) -- Modbus Serial line-specific functions (0x08) +- 0x14 - Read File Records function +- 0x15 - Write File Records function +- 0x16 - Write Mask Register function +- 0x17 - Read/Write Registers function +- 0x08 - Serial Diagnostics functions // 2.1.0 + Slave. Fix error response on write multiple Hregs\Coils + Slave. Fix writeCoil() for multiple coils diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 9444dc1..636f198 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -461,7 +461,6 @@ void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { } } -//1 void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, void* output) { void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output) { uint8_t fcode = frame[0]; _reply = EX_SUCCESS; @@ -469,8 +468,6 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, _reply = _frame[1]; return; } - //1 uint16_t field1 = (uint16_t)sourceFrame[1] << 8 | (uint16_t)sourceFrame[2]; - //uint16_t field1 = startreg.address; uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; uint8_t bytecount_calc; switch (fcode) { diff --git a/src/Modbus.h b/src/Modbus.h index 326b1b1..034750c 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -147,7 +147,12 @@ class Modbus { 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 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); + */ void cbEnable(bool state = true); void cbDisable(); diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index f9bb85a..4d493f9 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -24,8 +24,6 @@ // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); -//typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); - typedef struct TTransaction { uint16_t transactionId; uint32_t timestamp; @@ -110,6 +108,10 @@ class ModbusIP : public Modbus { uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); /* uint16_t maskHreg(IPAddress ip, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushPullIreg + uint16_t pushPullHreg + uint16_t pushIregPullToHreg + uint16_t pushHregPullToIreg uint16_t pushPullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, uint16_t to, uint16_t from, uint16_t numregs = 1, diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 2b4bb71..3b44f62 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -129,10 +129,9 @@ template <> bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { port->begin(baud); #endif - maxRegs = port->setRxBufferSize(256) / 2 - 2; - port->begin(baud); _port = port; _txPin = txPin; + maxRegs = _port->setRxBufferSize(256) / 2 - 2; if (txPin >= 0) { pinMode(txPin, OUTPUT); digitalWrite(txPin, LOW); @@ -301,7 +300,7 @@ uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, c } template uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; readSlave(offset, numregs, FC_READ_COILS); return send(slaveId, COIL(offset), cb, value); } @@ -329,7 +328,7 @@ uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* valu template uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; readSlave(offset, numregs, FC_READ_INPUT_STAT); return send(slaveId, ISTS(offset), cb, value); } @@ -355,7 +354,7 @@ uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uin template uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; #ifdef MODBUSRTU_ADD_REG addCoil(to, numregs); #endif @@ -365,7 +364,7 @@ uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uin template uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; #ifdef MODBUSRTU_ADD_REG addIsts(to, numregs); #endif @@ -419,7 +418,7 @@ uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t fro template uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; if (!searchRegister(ISTS(from))) return false; if (numregs == 1) { readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); @@ -441,7 +440,7 @@ uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t t template uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; #ifdef MODBUSRTU_ADD_REG addIsts(to, numregs); #endif From 3ae9d124d100ad7817526e9eaa4d5779d4ea6df0 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 22 May 2019 15:59:14 +0500 Subject: [PATCH 142/288] Fix rawSend() --- src/ModbusRTU.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 3b44f62..b27d4fe 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -162,9 +162,9 @@ bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16 template <> bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { - uint16_t newCrc = crc(slaveId, _frame, _len); + uint16_t newCrc = crc(slaveId, frame, len); _port->write(slaveId); //Send slaveId - _port->write(_frame, len); // Send PDU + _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC _port->flush(); @@ -174,13 +174,13 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t template <> bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { - uint16_t newCrc = crc(slaveId, _frame, _len); + uint16_t newCrc = crc(slaveId, frame, len); if (_txPin >= 0) { digitalWrite(_txPin, HIGH); delay(1); } _port->write(slaveId); //Send slaveId - _port->write(_frame, len); // Send PDU + _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC _port->flush(); @@ -240,6 +240,7 @@ void ModbusRTU::task() { return; } for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc + //_port->readBytes(_frame, _len); u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc _len = _len - 2; // Decrease by CRC 2 bytes if (frameCrc != crc(address, _frame, _len)) { // CRC Check From 1b3dcd4bcf49de3a4a098ad5fbca7de8aac182e4 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 09:25:50 +0500 Subject: [PATCH 143/288] Revert to no-template --- README.md | 17 ++- src/ModbusRTU.cpp | 352 ++++++++++++++++++++++++++++++++++++++++++ src/ModbusRTU.h | 382 +--------------------------------------------- 3 files changed, 365 insertions(+), 386 deletions(-) create mode 100644 src/ModbusRTU.cpp diff --git a/README.md b/README.md index 21fb65d..9c6bfe0 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,14 @@ The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called In the current version the library allows the ESP8266/ESP32 operate as a master and/or slave, supporting Modbus IP via wireless network and Modbus RTU over serial. For more information about Modbus see: -http://pt.wikipedia.org/wiki/Modbus -http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf -http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf -http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf +[Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) +[MODBUS APPLICATION PROTOCOL SPECIFICATION +V1.1b](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) +[MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE +V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) +[MODBUS over Serial Line +Specification and Implementation Guide +V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Features @@ -50,8 +54,7 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) 4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Read Multiple HRegs/IRegs for ESP master is limited to 63 per query. -5. ModbusRTU at this moment working on 9600 only for some reason. -6. Probably it's possible to use ModbusRTU with other AVR boards using from https://github.com/maniacbug/StandardCplusplus +5. Probably it's possible to use ModbusRTU with other AVR boards using from [Standard C++ for Arduino (port of uClibc++)](https://github.com/maniacbug/StandardCplusplus). ## Last Changes @@ -63,6 +66,8 @@ http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + CRC tables stored in PROGMEM + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) + ModbusRTU examples added +- Optimize CRC calculation +- Check real Serial buffer size - Test with HardwareSerial on ESP8266 - Test on ESP32 - Test TX control pin diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp new file mode 100644 index 0000000..d4e1637 --- /dev/null +++ b/src/ModbusRTU.cpp @@ -0,0 +1,352 @@ +/* + ModbusRTU Library for ESP8266/ESP32 + Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once +#include + +// 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 ModbusRTU::crc(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; +} + +#ifdef ESP8266 +bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { + port->begin(baud, format); +#else +bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { + port->begin(baud); +#endif + maxRegs = port->setRxBufferSize(256) / 2 - 2; + _port = port; + _txPin = txPin; + if (txPin >= 0) { + pinMode(txPin, OUTPUT); + digitalWrite(txPin, LOW); + } + if (baud > 19200) { + _t = 2; + } else { + _t = (35000/baud) + 1; + } + return true; +} + +bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { + port->begin(baud); + _port = port; + if (txPin >= 0) + port->setTransmitEnablePin(txPin); + if (baud > 19200) { + _t = 2; + //port->enableIntTx(false); + } else { + _t = (35000/baud) + 1; + } + return true; +} + +bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { + uint16_t newCrc = crc(slaveId, frame, len); + if (_txPin >= 0) { + digitalWrite(_txPin, HIGH); + delay(1); + } + _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(); + delay(_t); + if (_txPin >= 0) + digitalWrite(_txPin, LOW); + return true; +} + +bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { + if (_slaveId) return false; // Break if waiting for previous request result + rawSend(slaveId, _frame, _len); + if (waitResponse) { + _slaveId = slaveId; + _timestamp = millis(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + _len = 0; + } + return true; +} + +void ModbusRTU::task() { + if (_port->available() > _len) { + _len = _port->available(); + t = millis(); + return; + } + if (_len == 0) { // No data + if (isMaster) cleanup(); + return; + } + if (millis() - t < _t) return; // Wait data whitespace + + uint8_t address = _port->read(); //first byte of frame = address + _len--; // Decrease by slaveId byte + if (isMaster && _slaveId == 0) { // Check is slaveId is set + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected + _len = 0; + return; + } + if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach + _len = 0; + return; + } + + _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; + return; + } + for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc + //_port->readBytes(_frame, _len); + u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc + _len = _len - 2; // Decrease by CRC 2 bytes + if (frameCrc != crc(address, _frame, _len)) { // CRC Check + _len = 0; // Cleanup if wrong crc + free(_frame); + _frame = nullptr; + return; + } + if (isMaster) { + _reply = EX_SUCCESS; + if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + masterPDU(_frame, _sentFrame, _sentReg, _data); + if (cbEnabled && _cb) { + _cb((ResultCode)_reply, 0, nullptr); + } + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + } + _reply = Modbus::REPLY_OFF; // No reply if master + } else { + slavePDU(_frame); + if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + } + if (_reply != Modbus::REPLY_OFF) + rawSend(_slaveId, _frame, _len); + // Cleanup + _len = 0; + free(_frame); + _frame = nullptr; +} + + +bool ModbusRTU::cleanup() { + // Remove timeouted request and forced event + if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { + _cb(Modbus::EX_TIMEOUT, 0, nullptr); + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + return true; + } + return false; +} + + +uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { + readSlave(offset, value, FC_WRITE_REG); + return send(slaveId, HREG(offset), cb, nullptr, cb); +} + +uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { + readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); + return send(slaveId, COIL(offset), cb, nullptr, cb); +} + +uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; + readSlave(offset, numregs, FC_READ_COILS); + return send(slaveId, COIL(offset), cb, value); +} + + +uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x07D0) return false; + writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); + return send(slaveId, COIL(offset), cb, nullptr, cb); +} + + +uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007D) return false; + writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); + return send(slaveId, HREG(offset), cb, nullptr, cb); +} + + +uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs) return false; + readSlave(offset, numregs, FC_READ_REGS); + return send(slaveId, HREG(offset), cb, value); +} + + +uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; + readSlave(offset, numregs, FC_READ_INPUT_STAT); + return send(slaveId, ISTS(offset), cb, value); +} + + +uint16_t ModbusRTU::readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs) return false; + readSlave(offset, numregs, FC_READ_INPUT_REGS); + return send(slaveId, IREG(offset), cb, value); +} + + +uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x07D0) return false; + if (!searchRegister(COIL(from))) return false; + if (numregs == 1) { + readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); + } else { + writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); + } + return send(slaveId, COIL(from), cb); +} + + +uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; + #ifdef MODBUSRTU_ADD_REG + addCoil(to, numregs); + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(slaveId, COIL(to), cb); +} + + +uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; + #ifdef MODBUSRTU_ADD_REG + addIsts(to, numregs); + #endif + readSlave(from, numregs, FC_READ_INPUT_STAT); + return send(slaveId, ISTS(to), cb); +} + + +uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007D) return false; + if (!searchRegister(HREG(from))) return false; + if (numregs == 1) { + readSlave(to, Hreg(from), FC_WRITE_REG); + } else { + writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); + } + return send(slaveId, HREG(from), cb); +} + + +uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs) return false; + #ifdef MODBUSRTU_ADD_REG + addHreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(slaveId, HREG(to), cb); +} + + +uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs) return false; + #ifdef MODBUSRTU_ADD_REG + addIreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_INPUT_REGS); + return send(slaveId, IREG(to), cb); +} + + +uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > 0x007D) return false; + if (!searchRegister(IREG(from))) return false; + if (numregs == 1) { + readSlave(to, Ireg(from), FC_WRITE_REG); + } else { + writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); + } + return send(slaveId, IREG(from), cb); +} + + +uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; + if (!searchRegister(ISTS(from))) return false; + if (numregs == 1) { + readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); + } else { + writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); + } + return send(slaveId, ISTS(from), cb); +} + + +uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs) return false; + #ifdef MODBUSRTU_ADD_REG + addIreg(to, numregs); + #endif + readSlave(from, numregs, FC_READ_REGS); + return send(slaveId, IREG(to), cb); +} + + +uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { + if (numregs < 0x0001 || numregs > maxRegs << 4) return false; + #ifdef MODBUSRTU_ADD_REG + addIsts(to, numregs); + #endif + readSlave(from, numregs, FC_READ_COILS); + return send(slaveId, ISTS(to), cb); +} \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index b27d4fe..ee03373 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -15,13 +15,9 @@ #define MODBUSRTU_ADD_REG //#define MB_STATIC_FRAME 1 -template -// Possible declarations are -// ModbusRTU mb(); -// ModbusRTU mb(); class ModbusRTU : public Modbus { protected: - S* _port; + Stream* _port; int16_t _txPin; unsigned int _t; // inter-frame delay in mS uint32_t t = 0; @@ -43,14 +39,6 @@ class ModbusRTU : public Modbus { bool cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events uint16_t crc(uint8_t address, uint8_t* frame, uint8_t pdulen); public: - /* - Bits per Byte: 1 start bit - 8 data bits, least significant bit sent first - 1 bit for parity completion or stop bit - 1 stop bit - SERIAL_8N2 - SERIAL_8E1 - */ bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); #ifdef ESP8266 bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); @@ -81,370 +69,4 @@ class ModbusRTU : public Modbus { uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -}; - -// 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 -}; - -template -uint16_t ModbusRTU::crc(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; -} - -#ifdef ESP8266 -template <> -bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { - port->begin(baud, format); -#else -template <> -bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { - port->begin(baud); -#endif - _port = port; - _txPin = txPin; - maxRegs = _port->setRxBufferSize(256) / 2 - 2; - if (txPin >= 0) { - pinMode(txPin, OUTPUT); - digitalWrite(txPin, LOW); - } - if (baud > 19200) { - _t = 2; - } else { - _t = (35000/baud) + 1; - } - return true; -} - -template <> -bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - _port = port; - _txPin = txPin; - _port->begin(baud); - if (txPin >= 0) - _port->setTransmitEnablePin(txPin); - if (baud > 19200) { - _t = 2; - //_port->enableIntTx(false); - } else { - _t = (35000/baud) + 1; - } - return true; -} - -template <> -bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { - uint16_t newCrc = crc(slaveId, frame, len); - _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(); - delay(_t); - return true; -} - -template <> -bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { - uint16_t newCrc = crc(slaveId, frame, len); - if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); - } - _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(); - delay(_t); - if (_txPin >= 0) - digitalWrite(_txPin, LOW); - return true; -} - -template -bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { - if (_slaveId) return false; // Break if waiting for previous request result - rawSend(slaveId, _frame, _len); - if (waitResponse) { - _slaveId = slaveId; - _timestamp = millis(); - _cb = cb; - _data = data; - _sentFrame = _frame; - _sentReg = startreg; - _frame = nullptr; - _len = 0; - } - return true; -} - -template -void ModbusRTU::task() { - if (_port->available() > _len) { - _len = _port->available(); - t = millis(); - return; - } - if (_len == 0) { // No data - if (isMaster) cleanup(); - return; - } - if (millis() - t < _t) return; // Wait data whitespace - - uint8_t address = _port->read(); //first byte of frame = address - _len--; // Decrease by slaveId byte - if (isMaster && _slaveId == 0) { // Check is slaveId is set - for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected - _len = 0; - return; - } - if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check - for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach - _len = 0; - return; - } - - _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; - return; - } - for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc - //_port->readBytes(_frame, _len); - u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc - _len = _len - 2; // Decrease by CRC 2 bytes - if (frameCrc != crc(address, _frame, _len)) { // CRC Check - _len = 0; // Cleanup if wrong crc - free(_frame); - _frame = nullptr; - return; - } - if (isMaster) { - _reply = EX_SUCCESS; - if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested - // Procass incoming frame as master - masterPDU(_frame, _sentFrame, _sentReg, _data); - if (cbEnabled && _cb) { - _cb((ResultCode)_reply, 0, nullptr); - } - free(_sentFrame); - _sentFrame = nullptr; - _data = nullptr; - _slaveId = 0; - } - _reply = Modbus::REPLY_OFF; // No reply if master - } else { - slavePDU(_frame); - if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts - } - if (_reply != Modbus::REPLY_OFF) - rawSend(_slaveId, _frame, _len); - // Cleanup - _len = 0; - free(_frame); - _frame = nullptr; -} - -template -bool ModbusRTU::cleanup() { - // Remove timeouted request and forced event - if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { - _cb(Modbus::EX_TIMEOUT, 0, nullptr); - free(_sentFrame); - _sentFrame = nullptr; - _data = nullptr; - _slaveId = 0; - return true; - } - return false; -} - -template -uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { - readSlave(offset, value, FC_WRITE_REG); - return send(slaveId, HREG(offset), cb, nullptr, cb); -} -template -uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { - readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(slaveId, COIL(offset), cb, nullptr, cb); -} -template -uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - readSlave(offset, numregs, FC_READ_COILS); - return send(slaveId, COIL(offset), cb, value); -} - -template -uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(slaveId, COIL(offset), cb, nullptr, cb); -} - -template -uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(slaveId, HREG(offset), cb, nullptr, cb); -} - -template -uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - readSlave(offset, numregs, FC_READ_REGS); - return send(slaveId, HREG(offset), cb, value); -} - -template -uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(slaveId, ISTS(offset), cb, value); -} - -template -uint16_t ModbusRTU::readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(slaveId, IREG(offset), cb, value); -} - -template -uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - if (!searchRegister(COIL(from))) return false; - if (numregs == 1) { - readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); - } - return send(slaveId, COIL(from), cb); -} - -template -uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - #ifdef MODBUSRTU_ADD_REG - addCoil(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(slaveId, COIL(to), cb); -} - -template -uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - #ifdef MODBUSRTU_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_STAT); - return send(slaveId, ISTS(to), cb); -} - -template -uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - if (!searchRegister(HREG(from))) return false; - if (numregs == 1) { - readSlave(to, Hreg(from), FC_WRITE_REG); - } else { - writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); - } - return send(slaveId, HREG(from), cb); -} - -template -uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - #ifdef MODBUSRTU_ADD_REG - addHreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(slaveId, HREG(to), cb); -} - -template -uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - #ifdef MODBUSRTU_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_REGS); - return send(slaveId, IREG(to), cb); -} - -template -uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - if (!searchRegister(IREG(from))) return false; - if (numregs == 1) { - readSlave(to, Ireg(from), FC_WRITE_REG); - } else { - writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); - } - return send(slaveId, IREG(from), cb); -} - -template -uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - if (!searchRegister(ISTS(from))) return false; - if (numregs == 1) { - readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); - } - return send(slaveId, ISTS(from), cb); -} - -template -uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - #ifdef MODBUSRTU_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(slaveId, IREG(to), cb); -} - -template -uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - #ifdef MODBUSRTU_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(slaveId, ISTS(to), cb); -} \ No newline at end of file +}; \ No newline at end of file From def5cf3dbedd52db9d876509393837c594f3ce89 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 09:28:58 +0500 Subject: [PATCH 144/288] ReadMe update --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c6bfe0..2306e21 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,13 @@ The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called In the current version the library allows the ESP8266/ESP32 operate as a master and/or slave, supporting Modbus IP via wireless network and Modbus RTU over serial. For more information about Modbus see: [Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) + [MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) + [MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) + [MODBUS over Serial Line Specification and Implementation Guide V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) @@ -62,13 +65,12 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) // 3.0.0-DEVEL + ModbusRTU Slave + ModbusRTU Master -+ Test with SoftwareSerial on ESP8266 ++ Test on ESP8266 + CRC tables stored in PROGMEM + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) + ModbusRTU examples added - Optimize CRC calculation - Check real Serial buffer size -- Test with HardwareSerial on ESP8266 - Test on ESP32 - Test TX control pin - Test multiple Modbus* instances From 98e4cd362c674d0ad04836124f771892784e0e28 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 10:12:20 +0500 Subject: [PATCH 145/288] Update RTU examples --- examples/RTU-master/RTU-Master.ino | 2 +- examples/RTU-slave/RTU-slave.ino | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU-master/RTU-Master.ino index 4a6641f..9124f13 100644 --- a/examples/RTU-master/RTU-Master.ino +++ b/examples/RTU-master/RTU-Master.ino @@ -10,7 +10,7 @@ #include SoftwareSerial S(D1, D2, false, 128); -ModbusRTU mb; +ModbusRTU mb; bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap()); diff --git a/examples/RTU-slave/RTU-slave.ino b/examples/RTU-slave/RTU-slave.ino index 6a67b10..364fbea 100644 --- a/examples/RTU-slave/RTU-slave.ino +++ b/examples/RTU-slave/RTU-slave.ino @@ -7,23 +7,19 @@ */ #include -#include #define REGN 10 +#define SLAVE_ID 1 -SoftwareSerial S(D1, D2, false, 128); -ModbusRTU mb; +ModbusRTU mb; void setup() { - Serial.begin(115200); - mb.begin(&S, 9600); - mb.slave(1); + mb.begin(&Serial, 9600, SERIAL_8N1); + mb.slave(SLAVE_ID); mb.addHreg(REGN); mb.Hreg(REGN, 100); } -bool coils[20]; - void loop() { mb.task(); yield(); From 470d75d6b1498bbe464d2c6832282ee82626cde1 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 10:33:15 +0500 Subject: [PATCH 146/288] Remove SoftwareSerial for ESP32 --- README.md | 8 +++++--- src/ModbusRTU.cpp | 8 +++++--- src/ModbusRTU.h | 10 ++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2306e21..df890e2 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,13 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) // 3.0.0-DEVEL + ModbusRTU Slave + ModbusRTU Master -+ Test on ESP8266 -+ CRC tables stored in PROGMEM ++ Registers are now shared between Modbus* instances by default. + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) + ModbusRTU examples added -- Optimize CRC calculation ++ CRC tables stored in PROGMEM ++ Test on ESP8266 +- Optimize CRC calculation for ESP8266 +- Use in-rom CRC calculation function for ESP32 - Check real Serial buffer size - Test on ESP32 - Test TX control pin diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index d4e1637..5adf06e 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -27,7 +27,7 @@ static const uint16_t _auchCRC[] PROGMEM = { 0x4040, 0x0000 }; -uint16_t ModbusRTU::crc(uint8_t address, uint8_t* frame, uint8_t pduLen) { +uint16_t ModbusRTU::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 @@ -63,6 +63,7 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int1 return true; } +#if defined(ESP8266) bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { port->begin(baud); _port = port; @@ -76,9 +77,10 @@ bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { } return true; } +#endif bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { - uint16_t newCrc = crc(slaveId, frame, len); + uint16_t newCrc = crc16(slaveId, frame, len); if (_txPin >= 0) { digitalWrite(_txPin, HIGH); delay(1); @@ -145,7 +147,7 @@ void ModbusRTU::task() { //_port->readBytes(_frame, _len); u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc _len = _len - 2; // Decrease by CRC 2 bytes - if (frameCrc != crc(address, _frame, _len)) { // CRC Check + if (frameCrc != crc16(address, _frame, _len)) { // CRC Check _len = 0; // Cleanup if wrong crc free(_frame); _frame = nullptr; diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index ee03373..2f418b5 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -5,7 +5,9 @@ #pragma once #include #include -#include +#if defined(ESP8266) + #include +#endif #define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 @@ -37,10 +39,10 @@ class ModbusRTU : public Modbus { // 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 crc(uint8_t address, uint8_t* frame, uint8_t pdulen); + uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen); public: - bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); - #ifdef ESP8266 + #if defined(ESP8266) + bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); #else bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); From bc33337e32ea174ac8c8e336a79ffa38ca3db3b9 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 11:19:43 +0500 Subject: [PATCH 147/288] API documentation update --- API.md | 52 ++++++++++++++++++++++++++++++++++++++++------------ README.md | 22 ++++++++++------------ 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/API.md b/API.md index d69759b..32639dd 100644 --- a/API.md +++ b/API.md @@ -48,14 +48,24 @@ Processing routine. Should be periodically called form loop(). ```c bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); -bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); -bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); +bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); // for ESP32 +bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); // for ESP8266 +``` + +Initialize Serial port only. txPin support is not tested yet. + +```c void master(); void slave(uint8_t slaveId); +``` + +Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictible. + +```c uint8_t slave(); ``` -txPin is not tested yet. +Slave mode. Returns configured slave id. Master mode. Returns slave id for active request or 0 if no request in-progress. ### ModBus IP Slave specific API @@ -79,7 +89,7 @@ void dropTransactions(); void autoConnect(bool enabled); ``` -Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. +Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. Disabled by default. ### Query [multiple] regs from remote slave @@ -90,6 +100,13 @@ uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1 uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Result is saved to local registers. Method returns corresponding transaction id. [ip/from] or [ip/offset] - slave, [to] or [startreg] - local @@ -100,7 +117,13 @@ Result is saved to local registers. Method returns corresponding transaction id. uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = +MODBUSIP_UNIT); + +uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local @@ -110,6 +133,9 @@ Write Register/Coil or Write Multiple Registers/Coils Modbus function selected a ```c uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); ``` Write single value to remote Hreg/Coil. @@ -117,6 +143,9 @@ Write single value to remote Hreg/Coil. ```c uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Write multiple values from array to remote Coil/Hreg. @@ -128,15 +157,14 @@ uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -``` -Read values from remote Hreg/Coil/Ireg/Ists to array. - -```c -void autoConnect(bool enabled = true); +uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` -Set mode for automatic connect on read*\write*\push*\pull* calls. Disabled by default. +Read values from remote Hreg/Coil/Ireg/Ists to array. ## Callbacks API @@ -170,7 +198,7 @@ Get/Set register callback function definition. Pointer to TRegister structure (s typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); ``` -Transaction end callback function definition. *data* is currently reserved. +Transaction end callback function definition. For ModbusIP *data* is currently reserved. For ModbusRTU *transactionId* is also reserved. ```c IPAddress eventSource(); diff --git a/README.md b/README.md index df890e2..9a861c6 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,12 @@ The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called In the current version the library allows the ESP8266/ESP32 operate as a master and/or slave, supporting Modbus IP via wireless network and Modbus RTU over serial. For more information about Modbus see: -[Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) - -[MODBUS APPLICATION PROTOCOL SPECIFICATION +* [Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) +* [MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) - -[MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE +* [MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) - -[MODBUS over Serial Line +* [MODBUS over Serial Line Specification and Implementation Guide V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) @@ -45,8 +42,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * 0x0F - Write Multiple Coils * 0x10 - Write Multiple Registers * Callbacks for - * Master connect - * Master/Slave disconnect + * Master connect (ModbusIP) + * Master/Slave disconnect (ModbusIP) * Read specific Register * Write specific Register * Slave transaction finish @@ -56,7 +53,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) 1. When using Modbus IP the transport protocol is TCP (port 502). 2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) -4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Read Multiple HRegs/IRegs for ESP master is limited to 63 per query. +4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Coils - to 1008, Read Multiple HRegs/IRegs for ESP master is limited to 63, Coils/Istss - to 1008 per query. 5. Probably it's possible to use ModbusRTU with other AVR boards using from [Standard C++ for Arduino (port of uClibc++)](https://github.com/maniacbug/StandardCplusplus). ## Last Changes @@ -65,7 +62,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) // 3.0.0-DEVEL + ModbusRTU Slave + ModbusRTU Master -+ Registers are now shared between Modbus* instances by default. ++ Registers are now shared between Modbus* instances by default + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) + ModbusRTU examples added + CRC tables stored in PROGMEM @@ -76,7 +73,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) - Test on ESP32 - Test TX control pin - Test multiple Modbus* instances -- Documentation changes +- Implement eventSource() for ModbusRTU ++ Documentation changes // ToDo later - 0x14 - Read File Records function - 0x15 - Write File Records function From 254495c74912fbbd3a03c6e9502c9bdf2011ff02 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 12:23:26 +0500 Subject: [PATCH 148/288] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9a861c6..d953aa9 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. +|If this project is helpfull you can give me a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CCBLZ8VT7XQSC&source=url)| +|---|---| + This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. From 88f9d0df4f70fbc4a918bb18206d517a73a3a764 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 12:25:45 +0500 Subject: [PATCH 149/288] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d953aa9..b83441a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # ModbusRTU and ModbusIP Master-Slave Library for ESP8266/ESP32 v3.0 +|If this project is helpfull for you projects you can give me a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CCBLZ8VT7XQSC&source=url)| +|---|---| + + **Release state: DEVELOPMENT** Everything including API is subject to be changed during this stage. Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. -|If this project is helpfull you can give me a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CCBLZ8VT7XQSC&source=url)| -|---|---| - This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. From acdd0cdffc174d7a2cb28746b63f6fa665cacb17 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 23 May 2019 13:08:09 +0500 Subject: [PATCH 150/288] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b83441a..97b5b80 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ModbusRTU and ModbusIP Master-Slave Library for ESP8266/ESP32 v3.0 -|If this project is helpfull for you projects you can give me a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CCBLZ8VT7XQSC&source=url)| +|If this project is helpfull for you projects you can give me a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| From 0a8c57d289d8510819b2ab248f6395a0e903d83b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 24 May 2019 15:24:00 +0500 Subject: [PATCH 151/288] Allow use just begin(&Serial1) on already initialized port --- README.md | 6 +++--- src/ModbusRTU.cpp | 11 ++++++----- src/ModbusRTU.h | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b83441a..9f157af 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. +--- + This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. @@ -71,10 +73,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + ModbusRTU examples added + CRC tables stored in PROGMEM + Test on ESP8266 -- Optimize CRC calculation for ESP8266 -- Use in-rom CRC calculation function for ESP32 - Check real Serial buffer size -- Test on ESP32 ++ Test on ESP32 - Test TX control pin - Test multiple Modbus* instances - Implement eventSource() for ModbusRTU diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 5adf06e..625cc2a 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -41,14 +41,14 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } -#ifdef ESP8266 +#if defined(ESP8266) bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { - port->begin(baud, format); #else bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { - port->begin(baud); #endif - maxRegs = port->setRxBufferSize(256) / 2 - 2; + if (baud) port->begin(baud, format); + else baud = port->baudRate(); + maxRegs = port->setRxBufferSize(256) / 2 - 3; _port = port; _txPin = txPin; if (txPin >= 0) { @@ -65,7 +65,8 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int1 #if defined(ESP8266) bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - port->begin(baud); + if (baud) port->begin(baud); + else baud = port->baudRate(); _port = port; if (txPin >= 0) port->setTransmitEnablePin(txPin); diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 2f418b5..48942aa 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -42,10 +42,10 @@ class ModbusRTU : public Modbus { uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen); public: #if defined(ESP8266) - bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); - bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); + bool begin(SoftwareSerial* port, uint32_t baud=0, int16_t txPin=-1); + bool begin(HardwareSerial* port, uint32_t baud=0, SerialConfig format=SERIAL_8N1, int16_t txPin=-1); #else - bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); + bool begin(HardwareSerial* port, uint32_t baud=0, uint16_t format=SERIAL_8N1, int16_t txPin=-1); #endif void task(); void master() { isMaster = true; }; From caae81b42a4d2c32dee83225c58027e40431c028 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 26 May 2019 21:18:31 +0500 Subject: [PATCH 152/288] Update serial init in examples --- README.md | 1 + examples/RTU-master/RTU-Master.ino | 15 ++++++++++++--- src/ModbusRTU.cpp | 2 ++ src/ModbusRTU.h | 2 ++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 070dad1..75bd90d 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ a.m.emelianov@gmail.com Original version: https://github.com/andresarmento/modbus-esp8266 + https://github.com/andresarmento/modbus-arduino prof (at) andresarmento (dot) com diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU-master/RTU-Master.ino index 9124f13..1f2881f 100644 --- a/examples/RTU-master/RTU-Master.ino +++ b/examples/RTU-master/RTU-Master.ino @@ -4,12 +4,15 @@ (c)2019 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 -#include +#ifdef ESP8266 + #include + SoftwareSerial S(D1, D2, false, 256); +#endif -SoftwareSerial S(D1, D2, false, 128); ModbusRTU mb; bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { @@ -19,7 +22,13 @@ bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { void setup() { Serial.begin(115200); - mb.begin(&S, 9600); + #ifdef ESP8266 + S.begin(9600, SWSERIAL_8N1); + mb.begin(&S); + #else + Serial1.begin(9600, SERIAL_8N1, 17, 18); + mb.begin(&Serial1) + #endif mb.master(); } diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 625cc2a..2ac9073 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -1,6 +1,8 @@ /* ModbusRTU Library for ESP8266/ESP32 Copyright (C) 2019 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 diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 48942aa..7908a1a 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,6 +1,8 @@ /* ModbusRTU Library for ESP8266/ESP32 Copyright (C) 2019 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 From 767bed165135714de38b4437eb59122ec1f83d06 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 29 May 2019 16:32:57 +0500 Subject: [PATCH 153/288] Generalize eventSource() and implement it for RTU. --- API.md | 8 ++++++-- src/Modbus.h | 2 ++ src/ModbusIP_ESP8266.cpp | 6 +++--- src/ModbusIP_ESP8266.h | 2 +- src/ModbusRTU.h | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/API.md b/API.md index 32639dd..fd74e28 100644 --- a/API.md +++ b/API.md @@ -201,13 +201,17 @@ typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, Transaction end callback function definition. For ModbusIP *data* is currently reserved. For ModbusRTU *transactionId* is also reserved. ```c -IPAddress eventSource(); +uint32_t eventSource(); ``` -*Modbus IP Master/Slave* Should be called from onGet/onSet or transaction callback function. Returns IP address of remote requesting operation or INADDR_NONE for local. +Should be called from onGet/onSet or transaction callback function. + +*Modbus IP Master/Slave* Returns IP address of remote requesting operation or INADDR_NONE for local. Use IPAddress(eventSource) to operate result as IPAddress type. *Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. +*Modbus RTU Master/Slave* Returns slave id. + ```c bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); diff --git a/src/Modbus.h b/src/Modbus.h index 034750c..ef3a2ed 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -233,6 +233,8 @@ class Modbus { 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;} }; typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index a4fdd05..04f76b2 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -35,10 +35,10 @@ bool ModbusIP::connect(IPAddress ip) { return client[p]->connect(ip, MODBUSIP_PORT); } -IPAddress ModbusIP::eventSource() { // Returns IP of current processing client query +uint32_t ModbusIP::eventSource() { // Returns IP of current processing client query if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) - return client[n]->remoteIP(); - return INADDR_NONE; + return (uint32_t)client[n]->remoteIP(); + return (uint32_t)INADDR_NONE; } TTransaction* ModbusIP::searchTransaction(uint16_t id) { diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 4d493f9..703afca 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -82,7 +82,7 @@ class ModbusIP : public Modbus { void begin(); // Depricated void onConnect(cbModbusConnect cb = nullptr); void onDisconnect(cbModbusConnect cb = nullptr); - IPAddress eventSource(); + uint32_t eventSource() override; void autoConnect(bool enabled = true); void dropTransactions(); diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 7908a1a..f0b105c 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -53,6 +53,7 @@ class ModbusRTU : public Modbus { void master() { isMaster = true; }; void slave(uint8_t slaveId) {_slaveId = slaveId;}; uint8_t slave() { return _slaveId; } + uint32_t eventSource() override {return _slaveId;} uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); From b2ff7d103067225c95ba00f97a8ce7dfedd09ed3 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 29 May 2019 16:35:09 +0500 Subject: [PATCH 154/288] Generalize eventSource() and implement it for RTU. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75bd90d..71d7522 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + Test on ESP32 - Test TX control pin - Test multiple Modbus* instances -- Implement eventSource() for ModbusRTU ++ Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both. + Documentation changes // ToDo later - 0x14 - Read File Records function From 184c24656368e6ec04ed3a6ad8d40a7fcefe8c63 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 29 May 2019 16:50:32 +0500 Subject: [PATCH 155/288] sendRaw(): Move delyay() after txPin LOW --- src/ModbusRTU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 2ac9073..270731c 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -93,9 +93,9 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC _port->flush(); - delay(_t); if (_txPin >= 0) digitalWrite(_txPin, LOW); + delay(_t); return true; } From d4fcd1213e5486a21bf99af9b22efa77df579c40 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 30 May 2019 09:41:50 +0500 Subject: [PATCH 156/288] Change .begin() API call --- API.md | 7 +++---- README.md | 7 ++++--- examples/RTU-slave/RTU-slave.ino | 3 ++- src/ModbusRTU.cpp | 15 +++++---------- src/ModbusRTU.h | 6 ++---- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/API.md b/API.md index fd74e28..cb07971 100644 --- a/API.md +++ b/API.md @@ -47,12 +47,11 @@ Processing routine. Should be periodically called form loop(). ### Modbus RTU Specific API ```c -bool begin(SoftwareSerial* port, uint32_t baud, int16_t txPin=-1); -bool begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin=-1); // for ESP32 -bool begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin=-1); // for ESP8266 +bool begin(SoftwareSerial* port, int16_t txPin=-1); // For ESP8266 only +bool begin(HardwareSerial* port, int16_t txPin=-1); ``` -Initialize Serial port only. txPin support is not tested yet. +Assing Serial port. txPin support is not tested on ESP8266 yet. ```c void master(); diff --git a/README.md b/README.md index 71d7522..d78c0e4 100644 --- a/README.md +++ b/README.md @@ -72,10 +72,11 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) + ModbusRTU examples added + CRC tables stored in PROGMEM -+ Test on ESP8266 ++ ESP8266. Tested - Check real Serial buffer size -+ Test on ESP32 -- Test TX control pin ++ ESP32. Tested ++ ESP32. Tested TX control pin with MAX-485 +- ESP8266. Test TX control pin - Test multiple Modbus* instances + Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both. + Documentation changes diff --git a/examples/RTU-slave/RTU-slave.ino b/examples/RTU-slave/RTU-slave.ino index 364fbea..ad1a326 100644 --- a/examples/RTU-slave/RTU-slave.ino +++ b/examples/RTU-slave/RTU-slave.ino @@ -14,7 +14,8 @@ ModbusRTU mb; void setup() { - mb.begin(&Serial, 9600, SERIAL_8N1); + Serial.begin(9600, SERIAL_8N1) + mb.begin(&Serial); mb.slave(SLAVE_ID); mb.addHreg(REGN); mb.Hreg(REGN, 100); diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 270731c..b1f9327 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -43,13 +43,9 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } -#if defined(ESP8266) -bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, SerialConfig format, int16_t txPin) { -#else -bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int16_t txPin) { -#endif - if (baud) port->begin(baud, format); - else baud = port->baudRate(); + +bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { + uint32_t baud = port->baudRate(); maxRegs = port->setRxBufferSize(256) / 2 - 3; _port = port; _txPin = txPin; @@ -66,9 +62,8 @@ bool ModbusRTU::begin(HardwareSerial* port, uint32_t baud, uint16_t format, int1 } #if defined(ESP8266) -bool ModbusRTU::begin(SoftwareSerial* port, uint32_t baud, int16_t txPin) { - if (baud) port->begin(baud); - else baud = port->baudRate(); +bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { + uint32_t baud = port->baudRate(); _port = port; if (txPin >= 0) port->setTransmitEnablePin(txPin); diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index f0b105c..6b3153a 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -44,11 +44,9 @@ class ModbusRTU : public Modbus { uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen); public: #if defined(ESP8266) - bool begin(SoftwareSerial* port, uint32_t baud=0, int16_t txPin=-1); - bool begin(HardwareSerial* port, uint32_t baud=0, SerialConfig format=SERIAL_8N1, int16_t txPin=-1); - #else - bool begin(HardwareSerial* port, uint32_t baud=0, uint16_t format=SERIAL_8N1, int16_t txPin=-1); + bool begin(SoftwareSerial* port, int16_t txPin=-1); #endif + bool begin(HardwareSerial* port, int16_t txPin=-1); void task(); void master() { isMaster = true; }; void slave(uint8_t slaveId) {_slaveId = slaveId;}; From 628b4ba5a6e8a93e6583317da80a64e2225753ca Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 30 May 2019 09:50:36 +0500 Subject: [PATCH 157/288] Notes added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d78c0e4..5e01c5a 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) 3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) 4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Coils - to 1008, Read Multiple HRegs/IRegs for ESP master is limited to 63, Coils/Istss - to 1008 per query. 5. Probably it's possible to use ModbusRTU with other AVR boards using from [Standard C++ for Arduino (port of uClibc++)](https://github.com/maniacbug/StandardCplusplus). +6. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017 only up to 9600 for some reason. ## Last Changes From bd14f46e07332c053caabe3e82e1db17bd4d5537 Mon Sep 17 00:00:00 2001 From: per1234 Date: Thu, 30 May 2019 02:28:22 -0700 Subject: [PATCH 158/288] Use correct field separator in keywords.txt The Arduino IDE requires the use of a single true tab separator between the keyword name and identifier. When spaces are used rather than a true tab, the keyword is not highlighted. Reference: https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#keywords --- keywords.txt | 94 ++++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/keywords.txt b/keywords.txt index 01715c5..5566dc8 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,14 +1,14 @@ # Syntax Coloring Map For ModbusIP-ESP8266 # Datatypes (KEYWORD1) -ModbusRTUSlave KEYWORD1 -ModbusRTUMaster KEYWORD1 +ModbusRTUSlave KEYWORD1 +ModbusRTUMaster KEYWORD1 ModbusIP KEYWORD1 Modbus KEYWORD1 TRegister KEYWORD1 TTransaction KEYWORD1 -TAddress KEYWORD1 -ResultCode KEYWORD1 +TAddress KEYWORD1 +ResultCode KEYWORD1 # Methods and Functions (KEYWORD2) master KEYWORD2 @@ -27,14 +27,14 @@ onGetIreg KEYWORD2 onSetIreg KEYWORD2 onGetIsts KEYWORD2 onSetIsts KEYWORD2 -removeOnGetCoil KEYWORD2 -removeOnSetCoil KEYWORD2 -removeOnGetHreg KEYWORD2 -removeOnSetHreg KEYWORD2 -removeOnGetIsts KEYWORD2 -removeOnSetIsts KEYWORD2 -removeOnGetIreg KEYWORD2 -removeOnSetIreg KEYWORD2 +removeOnGetCoil KEYWORD2 +removeOnSetCoil KEYWORD2 +removeOnGetHreg KEYWORD2 +removeOnSetHreg KEYWORD2 +removeOnGetIsts KEYWORD2 +removeOnSetIsts KEYWORD2 +removeOnGetIreg KEYWORD2 +removeOnSetIreg KEYWORD2 addCoil KEYWORD2 addIsts KEYWORD2 addIreg KEYWORD2 @@ -57,21 +57,21 @@ pullIsts KEYWORD2 pushHreg KEYWORD2 pullHreg KEYWORD2 pullIreg KEYWORD2 -pullHregToIreg KEYWORD2 -pullCoilToIsts KEYWORD2 -pushIstsToCoil KEYWORD2 -pushIregToHreg KEYWORD2 -removeHreg KEYWORD2 -removeIreg KEYWORD2 -removeCoil KEYWORD2 -removeIsts KEYWORD2 -autoConnect KEYWORD2 -disconnect KEYWORD2 -dropTransactions KEYWORD2 -isCoil KEYWORD2 -isHreg KEYWORD2 -isIsts KEYWORD2 -isIreg KEYWORD2 +pullHregToIreg KEYWORD2 +pullCoilToIsts KEYWORD2 +pushIstsToCoil KEYWORD2 +pushIregToHreg KEYWORD2 +removeHreg KEYWORD2 +removeIreg KEYWORD2 +removeCoil KEYWORD2 +removeIsts KEYWORD2 +autoConnect KEYWORD2 +disconnect KEYWORD2 +dropTransactions KEYWORD2 +isCoil KEYWORD2 +isHreg KEYWORD2 +isIsts KEYWORD2 +isIreg KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 @@ -82,24 +82,24 @@ ISTS_VAL LITERAL1 ISTS_BOOL LITERAL1 ResultCode LITERAL1 FunctionCode LITERAL1 -EX_SUCCESS LITERAL1 -EX_ILLEGAL_FUNCTION LITERAL1 -EX_ILLEGAL_ADDRESS LITERAL1 -EX_ILLEGAL_VALUE LITERAL1 -EX_SLAVE_FAILURE LITERAL1 -EX_ACKNOWLEDGE LITERAL1 -EX_SLAVE_DEVICE_BUSY LITERAL1 -EX_MEMORY_PARITY_ERROR LITERAL1 -EX_PATH_UNAVAILABLE LITERAL1 -EX_DEVICE_FAILED_TO_RESPOND LITERAL1 -EX_GENERAL_FAILURE LITERAL1 -EX_DATA_MISMACH LITERAL1 -EX_UNEXPECTED_RESPONSE LITERAL1 -EX_TIMEOUT LITERAL1 -EX_CONNECTION_LOST LITERAL1 -EX_CANCEL LITERAL1 -COIL LITERAL1 -HREG LITERAL1 -ISTS LITERAL1 -IREG LITERAL1 +EX_SUCCESS LITERAL1 +EX_ILLEGAL_FUNCTION LITERAL1 +EX_ILLEGAL_ADDRESS LITERAL1 +EX_ILLEGAL_VALUE LITERAL1 +EX_SLAVE_FAILURE LITERAL1 +EX_ACKNOWLEDGE LITERAL1 +EX_SLAVE_DEVICE_BUSY LITERAL1 +EX_MEMORY_PARITY_ERROR LITERAL1 +EX_PATH_UNAVAILABLE LITERAL1 +EX_DEVICE_FAILED_TO_RESPOND LITERAL1 +EX_GENERAL_FAILURE LITERAL1 +EX_DATA_MISMACH LITERAL1 +EX_UNEXPECTED_RESPONSE LITERAL1 +EX_TIMEOUT LITERAL1 +EX_CONNECTION_LOST LITERAL1 +EX_CANCEL LITERAL1 +COIL LITERAL1 +HREG LITERAL1 +ISTS LITERAL1 +IREG LITERAL1 From 36ac832d9e6f7afe5e29492d3dbbda931c376622 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 6 Jun 2019 13:17:00 +0500 Subject: [PATCH 159/288] Text update --- API.md | 2 +- README.md | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/API.md b/API.md index cb07971..d443da0 100644 --- a/API.md +++ b/API.md @@ -51,7 +51,7 @@ bool begin(SoftwareSerial* port, int16_t txPin=-1); // For ESP8266 only bool begin(HardwareSerial* port, int16_t txPin=-1); ``` -Assing Serial port. txPin support is not tested on ESP8266 yet. +Assing Serial port. txPin controls transmit enable for MAX-485. ```c void master(); diff --git a/README.md b/README.md index 5e01c5a..e7581f1 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,11 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Notes: -1. When using Modbus IP the transport protocol is TCP (port 502). -2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -3. For API specefication refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) -4. Modbus RTU maximum incoming frame size is limited by Serial buffer size (128 bytes for ESP8266 HardwareSerial, user-specified for SoftwareSerial). That is HardwareSerial limits Write Multiple HRegs for ESP slave device is limited to 63 registers, Coils - to 1008, Read Multiple HRegs/IRegs for ESP master is limited to 63, Coils/Istss - to 1008 per query. -5. Probably it's possible to use ModbusRTU with other AVR boards using from [Standard C++ for Arduino (port of uClibc++)](https://github.com/maniacbug/StandardCplusplus). -6. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017 only up to 9600 for some reason. +1. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. +2. For API refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) +3. Modbus RTU maximum incoming frame size is determinated by HardwareSerial buffer size. For SoftwareSerial buffer must be set to 256 bytes. +4. Probably it's possible to use ModbusRTU with other AVR boards using from [Standard C++ for Arduino (port of uClibc++)](https://github.com/maniacbug/StandardCplusplus). +5. RS-485 transivers based on MAX-485 is working on at least up to 57600. XY-017/XY-485 working only up to 9600 for some reason. ## Last Changes @@ -73,11 +72,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) + ModbusRTU examples added + CRC tables stored in PROGMEM -+ ESP8266. Tested -- Check real Serial buffer size -+ ESP32. Tested -+ ESP32. Tested TX control pin with MAX-485 -- ESP8266. Test TX control pin ++ TX control pin support to work with MAX-485 - Test multiple Modbus* instances + Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both. + Documentation changes From 1c5eced218ca3ce1683199b9f87d158aa4d0d5cd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 5 Jul 2019 09:57:11 +0500 Subject: [PATCH 160/288] Replace magin number for buffer size --- src/Modbus.h | 2 +- src/ModbusRTU.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index ef3a2ed..7c341c5 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -19,7 +19,7 @@ #define MB_GLOBAL_REGS #define MB_MAX_REGS 32 -#define MB_MAX_FRAME 128 +#define MODBUS_MAX_FRAME 256 #define COIL(n) (TAddress){TAddress::COIL, n} #define ISTS(n) (TAddress){TAddress::ISTS, n} #define IREG(n) (TAddress){TAddress::IREG, n} diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index b1f9327..6400fd4 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -46,7 +46,7 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { uint32_t baud = port->baudRate(); - maxRegs = port->setRxBufferSize(256) / 2 - 3; + maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; _port = port; _txPin = txPin; if (txPin >= 0) { From 3ae7f9e425c644d42d90f32a5ab61806d287e235 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 8 Jul 2019 12:11:00 +0500 Subject: [PATCH 161/288] Allow to change TCP port for master/slave --- API.md | 4 ++-- README.md | 2 ++ src/ModbusIP_ESP8266.cpp | 17 +++++++++-------- src/ModbusIP_ESP8266.h | 5 +++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/API.md b/API.md index d443da0..69b4628 100644 --- a/API.md +++ b/API.md @@ -70,14 +70,14 @@ Slave mode. Returns configured slave id. Master mode. Returns slave id for activ ```c void begin(); // Depricated. Use slave() instead. -void slave(); +void slave(uint16_t port = MODBUSIP_PORT); ``` ### ModBus IP Master specific ```c void master(); -bool connect(IPAddress ip); +bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); bool disconnect(IPAddress ip); bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); diff --git a/README.md b/README.md index e7581f1..2da1c9e 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) - Test multiple Modbus* instances + Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both. + Documentation changes ++ Allow to specify local TCP port for Slave (default is 502) ++ Allow to specify TCP port for remote Slave connection (default is 502) // ToDo later - 0x14 - Read File Records function - 0x15 - Write File Records function diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 04f76b2..c9ea924 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -15,8 +15,9 @@ void ModbusIP::master() { } -void ModbusIP::slave() { - server = new WiFiServer(MODBUSIP_PORT); +void ModbusIP::slave(uint16_t port) { + slavePort = port; + server = new WiFiServer(slavePort); server->begin(); } @@ -24,7 +25,7 @@ void ModbusIP::begin() { slave(); } -bool ModbusIP::connect(IPAddress ip) { +bool ModbusIP::connect(IPAddress ip, uint16_t port) { //cleanup(); if(getSlave(ip) != -1) return true; @@ -32,7 +33,7 @@ bool ModbusIP::connect(IPAddress ip) { if (p == -1) return false; client[p] = new WiFiClient(); - return client[p]->connect(ip, MODBUSIP_PORT); + return client[p]->connect(ip, port); } uint32_t ModbusIP::eventSource() { // Returns IP of current processing client query @@ -109,7 +110,7 @@ void ModbusIP::task() { client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); //client[n]->flush(); } else { - if (client[n]->localPort() == MODBUSIP_PORT) { + if (client[n]->localPort() == slavePort) { // Process incoming frame as slave slavePDU(_frame); } else { @@ -138,7 +139,7 @@ void ModbusIP::task() { } } } - if (client[n]->localPort() != MODBUSIP_PORT) _reply = REPLY_OFF; // No replay if it was responce to master + if (client[n]->localPort() != slavePort) _reply = REPLY_OFF; // No replay if it was responce to master if (_reply != REPLY_OFF) { _MBAP.length = __bswap_16(_len+1); // _len+1 for last byte from MBAP size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); @@ -238,14 +239,14 @@ int8_t ModbusIP::getFreeClient() { int8_t ModbusIP::getSlave(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != MODBUSIP_PORT) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != slavePort) return i; return -1; } int8_t ModbusIP::getMaster(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == MODBUSIP_PORT) + if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == slavePort) return i; return -1; } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 703afca..377f153 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -56,6 +56,7 @@ class ModbusIP : public Modbus { int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; bool autoConnectMode = false; + uint16_t slavePort = 0; TTransaction* searchTransaction(uint16_t id); void cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events @@ -74,9 +75,9 @@ class ModbusIP : public Modbus { ~ModbusIP(); bool isTransaction(uint16_t id); bool isConnected(IPAddress ip); - bool connect(IPAddress ip); + bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); bool disconnect(IPAddress ip); - void slave(); + void slave(uint16_t port = MODBUSIP_PORT); void master(); void task(); void begin(); // Depricated From b38a94603f2bba1690ba1953644763a455350432 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 3 Sep 2019 23:10:35 +0500 Subject: [PATCH 162/288] Include byteswap.h only for ESP32 --- src/Modbus.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index 7c341c5..6f5f5d6 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -8,7 +8,7 @@ #include "Arduino.h" #include #include -#ifndef ESP8266 +#ifdef ARDUINO_ARCH_ESP32 #include #endif @@ -237,4 +237,4 @@ class Modbus { virtual uint32_t eventSource() {return 0;} }; -typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests \ No newline at end of file +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests From 3fd2be8c7e2c95c18ec652c8c51bcbb196185055 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 6 Sep 2019 11:14:25 +0500 Subject: [PATCH 163/288] task() error processing fixes --- src/ModbusIP_ESP8266.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 8978634..6da28f5 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -97,6 +97,7 @@ void ModbusIP::task() { _len--; // Do not count with last byte from MBAP if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + _len--; //client[n]->readBytes((uint8_t*)nullptr, _len); for (uint8_t i = 0; i < _len; i++) client[n]->read(); @@ -114,7 +115,7 @@ void ModbusIP::task() { if (client[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); - for (uint8_t i = 0; i < _len; i++) + while (client[n]->available()) client[n]->read(); //client[n]->flush(); } else { @@ -132,7 +133,7 @@ void ModbusIP::task() { } else { _reply = EX_UNEXPECTED_RESPONSE; } - if (cbEnabled && trans->cb) { + if (trans->cb) { trans->cb((ResultCode)_reply, trans->transactionId, nullptr); } free(trans->_frame); @@ -155,7 +156,7 @@ void ModbusIP::task() { memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); client[n]->write(sbuf, send_len); - //client[n]->flush(); + client[n]->flush(); } //client[n]->flush(); free(_frame); From 34e238685535d25ebe28904a77d69ce0515bd5ee Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 6 Sep 2019 20:00:52 +0500 Subject: [PATCH 164/288] task() cleanup --- src/ModbusIP_ESP8266.cpp | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 6da28f5..ef4cc63 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -62,7 +62,6 @@ void ModbusIP::task() { n = getMaster(currentClient->remoteIP()); if (n != -1) { client[n]->flush(); - //client[n]->stop(); delete client[n]; client[n] = nullptr; } @@ -74,8 +73,6 @@ void ModbusIP::task() { } } // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached - //currentClient->flush(); - //currentClient->stop(); delete currentClient; } } @@ -87,37 +84,29 @@ void ModbusIP::task() { client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. - //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); - while (client[n]->available()) + while (client[n]->available()) // Drop all incoming if wrong packet client[n]->read(); - //client[n]->flush(); continue; // for (n) } _len = __bswap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - _len--; - //client[n]->readBytes((uint8_t*)nullptr, _len); - for (uint8_t i = 0; i < _len; i++) + _len--; // Subtract for read byte + for (uint8_t i = 0; i < _len; i++) // Drop rest of packet client[n]->read(); - //client[n]->flush(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - //client[n]->readBytes((uint8_t*)nullptr, _len); - for (uint8_t i = 0; i < _len; i++) + for (uint8_t i = 0; i < _len; i++) // Drop packet client[n]->read(); - //client[n]->flush(); } else { if (client[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); - while (client[n]->available()) + while (client[n]->available()) // Drop all incoming (if any) client[n]->read(); - //client[n]->flush(); } else { if (client[n]->localPort() == slavePort) { // Process incoming frame as slave @@ -143,8 +132,6 @@ void ModbusIP::task() { _trans.erase(it); } } - //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); - //client[n]->flush(); // Not sure if we need flush rest of data available } } } @@ -158,15 +145,10 @@ void ModbusIP::task() { client[n]->write(sbuf, send_len); client[n]->flush(); } - //client[n]->flush(); free(_frame); _frame = nullptr; _len = 0; - //n--; } - //for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) - // if (client[n] && client[n]->connected()) - // client[n]->flush(); n = -1; } From 51c09d48238157a3939a89356254ce15babcfd48 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 6 Sep 2019 20:01:53 +0500 Subject: [PATCH 165/288] Misc --- src/ModbusIP_ESP8266.cpp | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 8978634..27e3491 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -87,36 +87,28 @@ void ModbusIP::task() { client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. - //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); while (client[n]->available()) client[n]->read(); - //client[n]->flush(); continue; // for (n) } _len = __bswap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - //client[n]->readBytes((uint8_t*)nullptr, _len); for (uint8_t i = 0; i < _len; i++) client[n]->read(); - //client[n]->flush(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - //client[n]->readBytes((uint8_t*)nullptr, _len); for (uint8_t i = 0; i < _len; i++) client[n]->read(); - //client[n]->flush(); } else { if (client[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); - for (uint8_t i = 0; i < _len; i++) + while (client[n]->available()) client[n]->read(); - //client[n]->flush(); } else { if (client[n]->localPort() == slavePort) { // Process incoming frame as slave @@ -142,8 +134,6 @@ void ModbusIP::task() { _trans.erase(it); } } - //client[n]->readBytes((uint8_t*)nullptr, client[n]->available()); - //client[n]->flush(); // Not sure if we need flush rest of data available } } } @@ -157,15 +147,10 @@ void ModbusIP::task() { client[n]->write(sbuf, send_len); //client[n]->flush(); } - //client[n]->flush(); free(_frame); _frame = nullptr; _len = 0; - //n--; } - //for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) - // if (client[n] && client[n]->connected()) - // client[n]->flush(); n = -1; } From 0bfb9da36c04033c3f61aa3bbf7615f6167f9397 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 14 Oct 2019 20:48:12 +0500 Subject: [PATCH 166/288] ESP32-related build fixes --- examples/RTU-master/RTU-Master.ino | 2 +- src/ModbusRTU.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU-master/RTU-Master.ino index 1f2881f..25ae0cb 100644 --- a/examples/RTU-master/RTU-Master.ino +++ b/examples/RTU-master/RTU-Master.ino @@ -27,7 +27,7 @@ void setup() { mb.begin(&S); #else Serial1.begin(9600, SERIAL_8N1, 17, 18); - mb.begin(&Serial1) + mb.begin(&Serial1); #endif mb.master(); } diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 6400fd4..2f360c2 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -46,7 +46,9 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { uint32_t baud = port->baudRate(); + #if defined(ESP8266) maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; + #endif _port = port; _txPin = txPin; if (txPin >= 0) { From 5820a063ff64a1ccb43bca71fb2194c1cc16bf78 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 15 Oct 2019 18:13:55 +0500 Subject: [PATCH 167/288] ModbusIP task(): Limit execution time & fix possible deadlock --- src/ModbusIP_ESP8266.cpp | 12 ++++++------ src/ModbusIP_ESP8266.h | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 1163dcf..eca33d0 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -79,8 +79,8 @@ void ModbusIP::task() { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (!client[n]) continue; if (!client[n]->connected()) continue; - - while (client[n]->available() > sizeof(_MBAP)) { + unt32_t readStart = millis(); + while (millis() - readStart < MODBUSIP_MAX_READMS && client[n]->available() > sizeof(_MBAP)) { client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. @@ -93,20 +93,20 @@ void ModbusIP::task() { if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); _len--; // Subtract for read byte - for (uint8_t i = 0; i < _len; i++) // Drop rest of packet + for (uint8_t i = 0; client[n]->available() && i < _len; i++) // Drop rest of packet client[n]->read(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - for (uint8_t i = 0; i < _len; i++) // Drop packet + for (uint8_t i = 0; client[n]->available() && i < _len; i++) // Drop packet client[n]->read(); } else { if (client[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - while (client[n]->available()) // Drop all incoming (if any) - client[n]->read(); + //while (client[n]->available()) // Drop all incoming (if any) + // client[n]->read(); } else { if (client[n]->localPort() == slavePort) { // Process incoming frame as slave diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 377f153..fe4618f 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -20,6 +20,7 @@ #define MODBUSIP_MAX_CLIENTS 4 #define MODBUSIP_ADD_REG 1 #define MODBUSIP_UNIQUE_CLIENTS +#define MODBUSIP_MAX_READMS 100 // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); From 6daf1ccbad3e57db33ee3f4416638c461701f71d Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 25 Nov 2019 08:07:07 +0500 Subject: [PATCH 168/288] Fix write multiple Hregs. Refactor internal *MultipleWords() --- src/Modbus.cpp | 42 ++++++++++++++-------------------------- src/Modbus.h | 4 ++-- src/ModbusIP_ESP8266.cpp | 8 +++++--- src/ModbusRTU.cpp | 1 + 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 636f198..7587a7a 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -121,7 +121,7 @@ void Modbus::slavePDU(uint8_t* frame) { } } if (k >= field2) { - setMultipleWords(frame + 6, HREG(field1), field2); + setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2); successResponce(HREG(field1), field2, fcode); _reply = REPLY_NORMAL; } @@ -222,15 +222,10 @@ void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs } } -void Modbus::getMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numregs) { - uint16_t val; - uint16_t i = 0; - while(numregs--) { - val = Reg(startreg + i); //retrieve the value from the register bank for the current register - frame[i * 2] = val >> 8; //write the high byte of the register value - frame[i * 2 + 1] = val & 0xFF; //write the low byte of the register value - i++; - } +void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { + for (uint8_t i = 0; i < numregs; i++) { + frame[i] = __bswap_16(Reg(startreg + i)); + } } void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { @@ -283,7 +278,7 @@ void Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { } _frame[0] = fn; _frame[1] = _len - 2; //byte count - getMultipleWords(_frame + 2, startreg, numregs); + getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs); _reply = REPLY_NORMAL; } @@ -301,14 +296,10 @@ void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutp } } -void Modbus::setMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numregs) { - uint16_t val; - uint16_t i = 0; - while(numregs--) { - val = (uint16_t)frame[i*2] << 8 | (uint16_t)frame[i*2 + 1]; - Reg(startreg + i, val); - i++; - } +void Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { + for (uint8_t i = 0; i < numregs; i++) { + Reg(startreg + i, __bswap_16(frame[i])); + } } bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { @@ -413,14 +404,11 @@ bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, F _frame[5] = _len - 6; if (data) { uint16_t* frame = (uint16_t*)(_frame + 6); - while(numregs) { - *frame = __bswap_16(*((uint16_t*)data)); - frame = frame + 2; - data = data + 2; - numregs--; + for (uint8_t i = 0; i < numregs; i++) { + frame[i] = __bswap_16(data[i]); } } else { - getMultipleWords(_frame + 6, startreg, numregs); + getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs); } return true; } @@ -486,7 +474,7 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, field2--; } } else { - setMultipleWords(frame + 2, startreg, field2); + setMultipleWords((uint16_t*)(frame + 2), startreg, field2); } break; case FC_READ_COILS: @@ -532,7 +520,7 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, field2--; } } else { - setMultipleWords(frame + 2, startreg, field2); + setMultipleWords((uint16_t*)(frame + 2), startreg, field2); } break; case FC_WRITE_REG: diff --git a/src/Modbus.h b/src/Modbus.h index c989e3a..a8d3959 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -179,10 +179,10 @@ class Modbus { void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); - void setMultipleWords(uint8_t* frame, TAddress startreg, uint16_t numoutputs); + void setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs); void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); - void getMultipleWords(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); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index eca33d0..fbd2a85 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -79,7 +79,7 @@ void ModbusIP::task() { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (!client[n]) continue; if (!client[n]->connected()) continue; - unt32_t readStart = millis(); + uint32_t readStart = millis(); while (millis() - readStart < MODBUSIP_MAX_READMS && client[n]->available() > sizeof(_MBAP)) { client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP @@ -145,8 +145,10 @@ void ModbusIP::task() { client[n]->write(sbuf, send_len); client[n]->flush(); } - free(_frame); - _frame = nullptr; + if (_frame) { + free(_frame); + _frame = nullptr; + } _len = 0; } } diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 2f360c2..0fee522 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -137,6 +137,7 @@ void ModbusRTU::task() { 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 From 24f34469fe11602b738ab5e68d5d91eb490a66cc Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 2 Dec 2019 11:06:57 +0300 Subject: [PATCH 169/288] Fix crash on no callback function on read\write remote. Thanks to Krutarth Trivedi --- README.md | 10 ++++++---- src/ModbusRTU.cpp | 17 ++++++++++++----- src/ModbusRTU.h | 3 ++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7515038..b35e712 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # ModbusRTU and ModbusIP Master-Slave Library for ESP8266/ESP32 v3.0 -|If this project is helpfull for you projects you can give me a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| +|If this project is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| -**Release state: DEVELOPMENT** Everything including API is subject to be changed during this stage. +**Release state: DEVELOPMENT** Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. @@ -73,17 +73,19 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + ModbusRTU examples added + CRC tables stored in PROGMEM + TX control pin support to work with MAX-485 -- Test multiple Modbus* instances ++ Test multiple Modbus* instances + Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both. + Documentation changes + Allow to specify local TCP port for Slave (default is 502) + Allow to specify TCP port for remote Slave connection (default is 502) ++ Master\Client. Fix crash on Write Multiple Hregs ++ Master\Client. Fix crash on no callback function on read\write remote. +- Add tests. // ToDo later - 0x14 - Read File Records function - 0x15 - Write File Records function - 0x16 - Write Mask Register function - 0x17 - Read/Write Registers function -- 0x08 - Serial Diagnostics functions // 2.1.0 + Slave. Fix error response on write multiple Hregs\Coils + Slave. Fix writeCoil() for multiple coils diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 0fee522..d089b64 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -43,6 +43,11 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } +bool ModbusRTU::begin(Stream* port) { + _port = port; + _t = 2; + return true; +} bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { uint32_t baud = port->baudRate(); @@ -126,7 +131,7 @@ void ModbusRTU::task() { uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte - if (isMaster && _slaveId == 0) { // Check is slaveId is set + if (isMaster && _slaveId == 0) { // Check if slaveId is set for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected _len = 0; return; @@ -144,14 +149,15 @@ void ModbusRTU::task() { _len = 0; return; } - for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc + for (uint8_t i=0 ; i < _len ; i++) + _frame[i] = _port->read(); // read data + crc //_port->readBytes(_frame, _len); u_int 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 - _len = 0; // Cleanup if wrong crc free(_frame); _frame = nullptr; + _len = 0; // Cleanup if wrong crc return; } if (isMaster) { @@ -175,16 +181,17 @@ void ModbusRTU::task() { if (_reply != Modbus::REPLY_OFF) rawSend(_slaveId, _frame, _len); // Cleanup - _len = 0; free(_frame); _frame = nullptr; + _len = 0; } bool ModbusRTU::cleanup() { // Remove timeouted request and forced event if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { - _cb(Modbus::EX_TIMEOUT, 0, nullptr); + if (_cb) + _cb(Modbus::EX_TIMEOUT, 0, nullptr); free(_sentFrame); _sentFrame = nullptr; _data = nullptr; diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 6b3153a..19c0caa 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -22,7 +22,7 @@ class ModbusRTU : public Modbus { protected: Stream* _port; - int16_t _txPin; + int16_t _txPin = -1; unsigned int _t; // inter-frame delay in mS uint32_t t = 0; bool isMaster = false; @@ -47,6 +47,7 @@ class ModbusRTU : public Modbus { bool begin(SoftwareSerial* port, int16_t txPin=-1); #endif bool begin(HardwareSerial* port, int16_t txPin=-1); + bool begin(Stream* port); void task(); void master() { isMaster = true; }; void slave(uint8_t slaveId) {_slaveId = slaveId;}; From 8051f004630e196f297238b2e2035a9229def143 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 2 Dec 2019 15:26:49 +0300 Subject: [PATCH 170/288] Write Coil(s)/Hreg(s) tests added --- src/Modbus.h | 2 +- src/ModbusRTU.cpp | 5 +- tests/README.md | 6 ++ tests/common.h | 46 ++++++++++++ tests/reg.h | 187 ++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.ino | 78 +++++++++++++++++++ 6 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/common.h create mode 100644 tests/reg.h create mode 100644 tests/tests.ino diff --git a/src/Modbus.h b/src/Modbus.h index a8d3959..845fadd 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,5 +1,5 @@ /* - Modbuc.h - Header for Modbus Base Library + Modbus.h - Header for Modbus Base Library Copyright (C) 2014 Andr� Sarmento Barbosa 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) */ diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index d089b64..3327ca5 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -165,7 +165,7 @@ void ModbusRTU::task() { if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested // Procass incoming frame as master masterPDU(_frame, _sentFrame, _sentReg, _data); - if (cbEnabled && _cb) { + if (_cb) { _cb((ResultCode)_reply, 0, nullptr); } free(_sentFrame); @@ -176,7 +176,8 @@ void ModbusRTU::task() { _reply = Modbus::REPLY_OFF; // No reply if master } else { slavePDU(_frame); - if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + if (address == MODBUSRTU_BROADCAST) + _reply = Modbus::REPLY_OFF; // No reply for Broadcasts } if (_reply != Modbus::REPLY_OFF) rawSend(_slaveId, _frame, _len); diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..0fa7368 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,6 @@ +# Modbus RTU tests + +There are not autotests. Just sketch executing Master and Slave on single ESP device and run Modbus calls with checking results. + +## Required libraryes +[StreamBuf](https://github.com/emelianov/StreamBuf) \ No newline at end of file diff --git a/tests/common.h b/tests/common.h new file mode 100644 index 0000000..693c358 --- /dev/null +++ b/tests/common.h @@ -0,0 +1,46 @@ +/* + Modbus Library for ESP8266/ESP32 + Functional tests + Copyright (C) 2019 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 + +#define BSIZE 1024 + +uint8_t buf1[BSIZE]; +uint8_t buf2[BSIZE]; + +StreamBuf S1(buf1, BSIZE); +StreamBuf S2(buf2, BSIZE); +DuplexBuf D1(&S1, &S2); +DuplexBuf D2(&S2, &S1); + +ModbusRTU master; +ModbusRTU slave; + +bool result; +uint8_t code ; + +bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { + //Serial.printf_P(" 0x%02X ", event); + //if (event == 0x00) { + code = event; + result = true; + return true; +} + +uint8_t wait() { + result = false; + code = 0; + while (!result) { + master.task(); + slave.task(); + yield(); + } + Serial.printf_P(" 0x%02X", code); + return code; +} diff --git a/tests/reg.h b/tests/reg.h new file mode 100644 index 0000000..c1f46d7 --- /dev/null +++ b/tests/reg.h @@ -0,0 +1,187 @@ +/* + Modbus Library for ESP8266/ESP32 + Functional tests + Copyright (C) 2019 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 "common.h" + +// Single Hreg write +void writeSingle(uint8_t sl, TAddress reg, uint16_t value) { + Serial.print("Write Single "); + switch (reg.type) { + case TAddress::HREG: + slave.addHreg(reg.address); + Serial.print("HREG: "); + break; + case TAddress::IREG: + slave.addIreg(reg.address); + Serial.print("IREG: "); + break; + case TAddress::COIL: + slave.addCoil(reg.address); + Serial.print("COIL: "); + break; + case TAddress::ISTS: + slave.addIsts(reg.address); + Serial.print("ISTS: "); + break; + default: + Serial.println("UNKNOWN"); + return; + } + if (!master.slave()) { + bool res = false; + switch (reg.type) { + case TAddress::HREG: + res = master.writeHreg(sl, reg.address, value, cbWrite); + break; + case TAddress::IREG: + //res = master.writeIreg(sl, reg.address, value, cbWrite); + break; + case TAddress::COIL: + res = master.writeCoil(sl, reg.address, value, cbWrite); + break; + case TAddress::ISTS: + //res = master.writeIsts(sl, reg.address, value, cbWrite); + break; + } + if (res) { + Serial.print(" SENT "); + if (wait() == Modbus::EX_SUCCESS) { + uint16_t val = 0; + switch (reg.type) { + case TAddress::HREG: + val = slave.Hreg(reg.address); + break; + case TAddress::IREG: + val = slave.Ireg(reg.address); + break; + case TAddress::COIL: + val = slave.Coil(reg.address); + break; + case TAddress::ISTS: + val = slave.Ists(reg.address); + break; + } + if (val = value) { + Serial.println(" PASSED"); + } else { + Serial.print(" INCORRECT"); + } + } else { + Serial.println(); + } + } else { + Serial.println(" FAILED"); + } + } else { + Serial.println(" BUSY"); + } +} + +// Multiple write +void writeMultiple(uint8_t sl, TAddress reg, uint16_t count = 1, void* value = nullptr) { + Serial.print("Write Multiple "); + bool mem = false; + if (!value) { + if (reg.isHreg() || reg.isIreg()) { + value = malloc(count * sizeof(uint16_t)); + if (!value) + return; + for (uint8_t i = 0; i < count; i++) { + ((uint16_t*)value)[i] = i; + } + } else { + value = malloc(count * sizeof(bool)); + if (!value) + return; + for (uint8_t i = 0; i < count; i++) { + ((bool*)value)[i] = i % 2; + } + } + mem = true; + } + switch (reg.type) { + case TAddress::HREG: + slave.addHreg(reg.address, 0, count); + Serial.print("HREG: "); + break; + case TAddress::IREG: + slave.addIreg(reg.address, 0, count); + Serial.print("IREG: "); + break; + case TAddress::COIL: + slave.addCoil(reg.address, false, count); + Serial.print("COIL: "); + break; + case TAddress::ISTS: + slave.addIsts(reg.address, false, count); + Serial.print("ISTS: "); + break; + default: + Serial.println("UNKNOWN"); + return; + } + if (!master.slave()) { + bool res = false; + switch (reg.type) { + case TAddress::HREG: + res = master.writeHreg(sl, reg.address, (uint16_t*)value, count, cbWrite); + break; + case TAddress::IREG: + //res = master.writeIreg(sl, reg.address, value, count, cbWrite); + break; + case TAddress::COIL: + res = master.writeCoil(sl, reg.address, (bool*)value, count, cbWrite); + break; + case TAddress::ISTS: + //res = master.writeIsts(sl, reg.address, value, count, cbWrite); + break; + } + if (res) { + Serial.print(" SENT "); + if (wait() == Modbus::EX_SUCCESS) { + bool res = true; + switch (reg.type) { + case TAddress::HREG: + for (uint8_t i = 0; i < count; i++) { + if (slave.Hreg(reg.address + i) != ((uint16_t*)value)[i]) res = false; + } + break; + case TAddress::IREG: + for (uint8_t i = 0; i < count; i++) { + //if (slave.Ireg(reg.address + i) != value[i]) res = false; + } + break; + case TAddress::COIL: + for (uint8_t i = 0; i < count; i++) { + if (slave.Coil(reg.address + i) != ((bool*)value)[i]) res = false; + } + break; + case TAddress::ISTS: + for (uint8_t i = 0; i < count; i++) { + //if (slave.Ists(reg.address + i) != value[i]) res = false; + } + break; + } + if (res) { + Serial.println(" PASSED"); + } else { + Serial.print(" INCORRECT"); + } + } else { + Serial.println(); + } + } else { + Serial.println(" FAILED"); + } + } else { + Serial.println(" BUSY"); + } + if (mem) + free(value); +} \ No newline at end of file diff --git a/tests/tests.ino b/tests/tests.ino new file mode 100644 index 0000000..96543d3 --- /dev/null +++ b/tests/tests.ino @@ -0,0 +1,78 @@ +/* + Modbus Library for ESP8266/ESP32 + Functional tests + Copyright (C) 2019 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 +#include +#include "common.h" +#include "reg.h" + +#define SLAVE_ID 1 +#define HREG_ID 10 +#define HREG_VALUE 100 + +#define HREGS_ID 20 +#define HREGS_COUNT 20 + +void setup() { + Serial.begin(115200); + master.begin(&D1); + master.master(); + slave.begin(&D2); + slave.slave(SLAVE_ID); + slave.addHreg(HREG_ID); + +writeSingle(SLAVE_ID, HREG(HREG_ID), HREG_VALUE); +writeSingle(SLAVE_ID, COIL(HREG_ID), true); + +writeMultiple(SLAVE_ID, HREG(HREG_ID), 10); +writeMultiple(SLAVE_ID, COIL(HREG_ID), 10); + +// Garbage read + { + bool Node_1_ackStatus = false; + bool Node_2_ackStatus = false; + slave.addIsts(100, true); + slave.addIsts(101, true); + Serial.print("Write garbage: "); + if (!master.slave()) { + master.readIsts(2, 100, &Node_1_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 100, &Node_1_ackStatus, NULL); + while (master.slave()) { + master.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + while(D2.available()) + D2.write(D2.read()); + //slave.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, NULL); + while (master.slave()) { + master.task(); + delay(1); + } + } + if (Node_1_ackStatus && Node_2_ackStatus) { + Serial.println(" PASSED"); + } else { + Serial.println(" FAILED"); + } + } +} + +void loop() { + yield(); +} From f8677ae8ebccc01165b55d152b4e150dc39235d8 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 3 Dec 2019 09:43:24 +0300 Subject: [PATCH 171/288] Read Coils/Hregs/Iregs/Istss tests added --- tests/README.md | 2 +- tests/read.h | 134 +++++++++++++++++++++++++++++++++++++++ tests/tests.ino | 26 ++++---- tests/{reg.h => write.h} | 0 4 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 tests/read.h rename tests/{reg.h => write.h} (100%) diff --git a/tests/README.md b/tests/README.md index 0fa7368..239ac1e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,5 +2,5 @@ There are not autotests. Just sketch executing Master and Slave on single ESP device and run Modbus calls with checking results. -## Required libraryes +## Required libraries [StreamBuf](https://github.com/emelianov/StreamBuf) \ No newline at end of file diff --git a/tests/read.h b/tests/read.h new file mode 100644 index 0000000..21b8495 --- /dev/null +++ b/tests/read.h @@ -0,0 +1,134 @@ +#pragma once +#include "common.h" + +// Single Hreg write +// Multiple read +void readMultiple(uint8_t sl, TAddress reg, uint16_t count = 1, void* value = nullptr) { + Serial.print("Read Multiple "); + bool mem = false; + if (!value) { + if (reg.isHreg() || reg.isIreg()) { + value = malloc(count * sizeof(uint16_t)); + if (!value) { + Serial.println(" FAILED"); + return; + } + for (uint8_t i = 0; i < count; i++) { + ((uint16_t*)value)[i] = i; + } + } else { + value = malloc(count * sizeof(bool)); + if (!value) { + Serial.println(" FAILED"); + return; + } + for (uint8_t i = 0; i < count; i++) { + ((bool*)value)[i] = i % 2; + } + } + mem = true; + } + bool addRes = true; + switch (reg.type) { + case TAddress::HREG: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addHreg(reg.address + i, ((uint16_t*)value)[i]); + } + Serial.print("HREG: "); + break; + case TAddress::IREG: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addIreg(reg.address + i, ((uint16_t*)value)[i]); + //Serial.print(slave.Ireg(reg.address + i)); Serial.print(" "); + } + Serial.print("IREG: "); + break; + case TAddress::COIL: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addCoil(reg.address + i, ((bool*)value)[i]); + } + Serial.print("COIL: "); + break; + case TAddress::ISTS: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addIsts(reg.address + i, ((bool*)value)[i]); + } + Serial.print("ISTS: "); + break; + default: + addRes = false; + Serial.println("UNKNOWN"); + return; + } + if (!addRes) { + Serial.println(" SLAVE FAILED"); + return; + } + if (reg.isHreg() || reg.isIreg()) { + for (uint8_t i = 0; i < count; i++) { + ((uint16_t*)value)[i] = 0; + } + } else { + for (uint8_t i = 0; i < count; i++) { + ((bool*)value)[i] = false; + } + } + if (!master.slave()) { + bool res = false; + switch (reg.type) { + case TAddress::HREG: + res = master.readHreg(sl, reg.address, (uint16_t*)value, count, cbWrite); + break; + case TAddress::IREG: + res = master.readIreg(sl, reg.address, (uint16_t*)value, count, cbWrite); + break; + case TAddress::COIL: + res = master.readCoil(sl, reg.address, (bool*)value, count, cbWrite); + break; + case TAddress::ISTS: + res = master.readIsts(sl, reg.address, (bool*)value, count, cbWrite); + break; + } + if (res) { + Serial.print(" SENT "); + if (wait() == Modbus::EX_SUCCESS) { + bool res = true; + switch (reg.type) { + case TAddress::HREG: + for (uint8_t i = 0; i < count; i++) { + if (slave.Hreg(reg.address + i) != ((uint16_t*)value)[i]) res = false; + } + break; + case TAddress::IREG: + for (uint8_t i = 0; i < count; i++) { + if (slave.Ireg(reg.address + i) != ((uint16_t*)value)[i]) res = false; + } + break; + case TAddress::COIL: + for (uint8_t i = 0; i < count; i++) { + if (slave.Coil(reg.address + i) != ((bool*)value)[i]) res = false; + } + break; + case TAddress::ISTS: + for (uint8_t i = 0; i < count; i++) { + if (slave.Ists(reg.address + i) != ((bool*)value)[i]) res = false; + } + break; + } + if (res) { + Serial.println(" PASSED"); + } else { + Serial.print(" INCORRECT"); + } + } else { + Serial.println(); + } + } else { + Serial.println(" FAILED"); + } + } else { + Serial.println(" BUSY"); + } + if (mem) + free(value); +} \ No newline at end of file diff --git a/tests/tests.ino b/tests/tests.ino index 96543d3..d69bd3f 100644 --- a/tests/tests.ino +++ b/tests/tests.ino @@ -1,15 +1,12 @@ -/* - Modbus Library for ESP8266/ESP32 - Functional tests - Copyright (C) 2019 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 #include #include "common.h" -#include "reg.h" +#include "write.h" +#include "read.h" + + +uint8_t stage = 0; +uint16_t readHreg = 0; #define SLAVE_ID 1 #define HREG_ID 10 @@ -32,6 +29,11 @@ writeSingle(SLAVE_ID, COIL(HREG_ID), true); writeMultiple(SLAVE_ID, HREG(HREG_ID), 10); writeMultiple(SLAVE_ID, COIL(HREG_ID), 10); +readMultiple(SLAVE_ID, HREG(HREG_ID), 10); +readMultiple(SLAVE_ID, COIL(HREG_ID), 10); +readMultiple(SLAVE_ID, IREG(HREG_ID), 10); +readMultiple(SLAVE_ID, ISTS(HREG_ID), 10); + // Garbage read { bool Node_1_ackStatus = false; @@ -46,9 +48,10 @@ writeMultiple(SLAVE_ID, COIL(HREG_ID), 10); slave.task(); delay(1); } - master.readIsts(SLAVE_ID, 100, &Node_1_ackStatus, NULL); + master.readIsts(SLAVE_ID, 100, &Node_1_ackStatus, 1, NULL); while (master.slave()) { master.task(); + slave.task(); delay(1); } master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); @@ -59,9 +62,10 @@ writeMultiple(SLAVE_ID, COIL(HREG_ID), 10); //slave.task(); delay(1); } - master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, NULL); + master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); while (master.slave()) { master.task(); + slave.task(); delay(1); } } diff --git a/tests/reg.h b/tests/write.h similarity index 100% rename from tests/reg.h rename to tests/write.h From caaa3adbb6914b2a80cf873f6e45dd10cb0fdd9a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 5 Dec 2019 07:18:56 +0300 Subject: [PATCH 172/288] 3.0.0 --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b35e712..5714719 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ |---|---| -**Release state: DEVELOPMENT** - Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. --- @@ -65,22 +63,19 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff -// 3.0.0-DEVEL +// 3.0.0 + ModbusRTU Slave + ModbusRTU Master + Registers are now shared between Modbus* instances by default + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) + ModbusRTU examples added -+ CRC tables stored in PROGMEM -+ TX control pin support to work with MAX-485 + Test multiple Modbus* instances -+ Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both. -+ Documentation changes ++ Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both + Allow to specify local TCP port for Slave (default is 502) + Allow to specify TCP port for remote Slave connection (default is 502) + Master\Client. Fix crash on Write Multiple Hregs -+ Master\Client. Fix crash on no callback function on read\write remote. -- Add tests. ++ Master\Client. Fix crash on no callback function on read\write remote ++ Tests added // ToDo later - 0x14 - Read File Records function - 0x15 - Write File Records function From 9b8ddf2dbac0595c58cbbdc2c02a2d5d9d863370 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 16 Mar 2020 14:59:35 +0300 Subject: [PATCH 173/288] ESP32: Add critical section to escape possible timouts --- src/ModbusRTU.cpp | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 3327ca5..18e4c96 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -5,7 +5,7 @@ This code is licensed under the BSD New License. See LICENSE.txt for more info. */ #pragma once -#include +#include "ModbusRTU.h" // Table of CRC values static const uint16_t _auchCRC[] PROGMEM = { @@ -56,9 +56,9 @@ bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { #endif _port = port; _txPin = txPin; - if (txPin >= 0) { - pinMode(txPin, OUTPUT); - digitalWrite(txPin, LOW); + if (_txPin >= 0) { + pinMode(_txPin, OUTPUT); + digitalWrite(_txPin, LOW); } if (baud > 19200) { _t = 2; @@ -90,14 +90,21 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { digitalWrite(_txPin, HIGH); delay(1); } + #ifdef ESP32 + portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + portENTER_CRITICAL(&mux); + #endif _port->write(slaveId); //Send slaveId _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif _port->flush(); if (_txPin >= 0) digitalWrite(_txPin, LOW); - delay(_t); + //delay(_t); return true; } @@ -118,16 +125,24 @@ bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* } void ModbusRTU::task() { + #ifdef ESP32 + portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + portENTER_CRITICAL(&mux); + #endif if (_port->available() > _len) { _len = _port->available(); t = millis(); - return; - } - if (_len == 0) { // No data - if (isMaster) cleanup(); - return; } - if (millis() - t < _t) return; // Wait data whitespace + if (isMaster) cleanup(); + if (_len == 0 || millis() - t < _t) { // No data or not end of frame + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif + return; // Wait data whitespace + } + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte From 1a142ea51a307e353f430417fd38bf44f548bdb5 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 17 May 2020 22:06:10 +0500 Subject: [PATCH 174/288] TAddress != operator added --- src/Modbus.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Modbus.h b/src/Modbus.h index 845fadd..73a8e71 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -45,6 +45,9 @@ struct TAddress { 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; From c177eed771d1b1cd3171ee8c814088107ba2735d Mon Sep 17 00:00:00 2001 From: Jones Date: Sun, 17 May 2020 19:28:22 +0200 Subject: [PATCH 175/288] Support non ESP devices (#40) * may handover baudrate to begin serial communication, use baudRate() only on ESP32 or ESP8266 * include ModbusIP_ESP8266 only if board is ESP32 or ESP8266 * setBaudrate function added, fixed PR remarks * fixed begin function in header * small fix for setBaudrate function * updated RTU examples for ESP32, ESP8266, STM32F103 --- examples/RTU-master/RTU-Master.ino | 29 ++++++++++++++++++++++++----- examples/RTU-slave/RTU-slave.ino | 14 ++++++++++++-- src/ModbusIP_ESP8266.cpp | 6 +++++- src/ModbusIP_ESP8266.h | 6 +++++- src/ModbusRTU.cpp | 26 +++++++++++++++++++++----- src/ModbusRTU.h | 4 +++- 6 files changed, 70 insertions(+), 15 deletions(-) diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU-master/RTU-Master.ino index 25ae0cb..fb4be75 100644 --- a/examples/RTU-master/RTU-Master.ino +++ b/examples/RTU-master/RTU-Master.ino @@ -1,32 +1,51 @@ /* ModbusRTU ESP8266/ESP32 Read multiple coils from slave device example - + (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 + + modified 13 May 2020 + by brainelectronics + This code is licensed under the BSD New License. See LICENSE.txt for more info. */ #include -#ifdef ESP8266 +#if defined(ESP8266) #include - SoftwareSerial S(D1, D2, false, 256); + // SoftwareSerial S(D1, D2, false, 256); + + // receivePin, transmitPin, inverse_logic, bufSize, isrBufSize + // connect RX to D2 (GPIO4, Arduino pin 4), TX to D1 (GPIO5, Arduino pin 4) + SoftwareSerial S(4, 5); #endif ModbusRTU mb; bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { +#ifdef ESP8266 + Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap()); +#elif ESP32 Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap()); +#else + Serial.print("Request result: 0x"); + Serial.print(event, HEX); +#endif return true; } void setup() { Serial.begin(115200); - #ifdef ESP8266 + #if defined(ESP8266) S.begin(9600, SWSERIAL_8N1); mb.begin(&S); + #elif defined(ESP32) + Serial1.begin(9600, SERIAL_8N1); + mb.begin(&Serial1); #else - Serial1.begin(9600, SERIAL_8N1, 17, 18); + Serial1.begin(9600, SERIAL_8N1); + mb.setBaudrate(9600); mb.begin(&Serial1); #endif mb.master(); diff --git a/examples/RTU-slave/RTU-slave.ino b/examples/RTU-slave/RTU-slave.ino index ad1a326..73e7794 100644 --- a/examples/RTU-slave/RTU-slave.ino +++ b/examples/RTU-slave/RTU-slave.ino @@ -1,9 +1,14 @@ /* ModbusRTU ESP8266/ESP32 Simple slave example - + (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 + + modified 13 May 2020 + by brainelectronics + + This code is licensed under the BSD New License. See LICENSE.txt for more info. */ #include @@ -14,8 +19,13 @@ ModbusRTU mb; void setup() { - Serial.begin(9600, SERIAL_8N1) + Serial.begin(9600, SERIAL_8N1); +#if defined(ESP32) || defined(ESP8266) + mb.begin(&Serial); +#else + mb.setBaudrate(9600); mb.begin(&Serial); +#endif mb.slave(SLAVE_ID); mb.addHreg(REGN); mb.Hreg(REGN, 100); diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index fbd2a85..c1962a7 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -3,6 +3,8 @@ Copyright (C) 2014 Andr� Sarmento Barbosa 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) */ +#if defined(ESP32) || defined(ESP8266) + #include "ModbusIP_ESP8266.h" ModbusIP::ModbusIP() { @@ -423,4 +425,6 @@ ModbusIP::~ModbusIP() { delete client[i]; client[i] = nullptr; } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index fe4618f..0ed2f76 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -5,6 +5,8 @@ */ #pragma once +#if defined(ESP32) || defined(ESP8266) + #include #ifdef ESP8266 #include @@ -123,4 +125,6 @@ class ModbusIP : public Modbus { uint16_t writeOffset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); */ -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 18e4c96..a0a4602 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -43,6 +43,10 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } +void ModbusRTU::setBaudrate(uint32_t baud) { + _baudrate = baud; +} + bool ModbusRTU::begin(Stream* port) { _port = port; _t = 2; @@ -50,10 +54,22 @@ bool ModbusRTU::begin(Stream* port) { } bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { - uint32_t baud = port->baudRate(); - #if defined(ESP8266) - maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; - #endif + uint32_t baud = 0; + if (_baudrate > 0) { + baud = _baudrate; + } + else { + #if defined(ESP32) || defined(ESP8266) + // baudRate() only available with ESP32+ESP8266 + baud = port->baudRate(); + #else + #warning "Using default 9600 baud as not specified with setBaudrate()" + baud = 9600; + #endif + } + #if defined(ESP8266) + maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; + #endif _port = port; _txPin = txPin; if (_txPin >= 0) { @@ -96,7 +112,7 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { #endif _port->write(slaveId); //Send slaveId _port->write(frame, len); // Send PDU - _port->write(newCrc >> 8); //Send CRC + _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC #ifdef ESP32 portEXIT_CRITICAL(&mux); diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 19c0caa..5b323df 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -23,6 +23,7 @@ class ModbusRTU : public Modbus { protected: Stream* _port; int16_t _txPin = -1; + uint32_t _baudrate = -1; unsigned int _t; // inter-frame delay in mS uint32_t t = 0; bool isMaster = false; @@ -43,10 +44,11 @@ class ModbusRTU : public Modbus { 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); public: + void setBaudrate(uint32_t baud = -1); #if defined(ESP8266) bool begin(SoftwareSerial* port, int16_t txPin=-1); #endif - bool begin(HardwareSerial* port, int16_t txPin=-1); + bool begin(HardwareSerial* port, int16_t txPin=-1); bool begin(Stream* port); void task(); void master() { isMaster = true; }; From f19f7d9793a1c4af5182aef5158dff7e7e99cc40 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 18 May 2020 08:46:21 +0300 Subject: [PATCH 176/288] ModburRTU: setBaudrate() rewrite --- API.md | 6 ++++ README.md | 56 +++++++----------------------- examples/RTU-master/RTU-Master.ino | 2 +- examples/RTU-slave/RTU-slave.ino | 2 +- keywords.txt | 2 ++ src/Modbus.cpp | 2 +- src/Modbus.h | 4 +-- src/ModbusIP_ESP8266.h | 2 +- src/ModbusRTU.cpp | 33 ++++++------------ src/ModbusRTU.h | 3 +- 10 files changed, 39 insertions(+), 73 deletions(-) diff --git a/API.md b/API.md index 69b4628..26bf670 100644 --- a/API.md +++ b/API.md @@ -53,6 +53,12 @@ bool begin(HardwareSerial* port, int16_t txPin=-1); Assing Serial port. txPin controls transmit enable for MAX-485. +```c +void setBaudrte(uint32 baud); +``` + +Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. + ```c void master(); void slave(uint8_t slaveId); diff --git a/README.md b/README.md index 5714719..7c9ac81 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * ESP8266 * ESP32 * Operates as - * slave - * master + * Slave/Server + * Master/Client * Supports * Modbus IP (TCP) * Modbus RTU (RS-485) @@ -46,8 +46,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * 0x0F - Write Multiple Coils * 0x10 - Write Multiple Registers * Callbacks for - * Master connect (ModbusIP) - * Master/Slave disconnect (ModbusIP) + * Client connect (ModbusIP) + * Server/Client disconnect (ModbusIP) * Read specific Register * Write specific Register * Slave transaction finish @@ -63,58 +63,28 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 3.0.1 ++ ModbusRTU: ESP32 possible send failure fix ++ ModbusRTU: Non-ESP devices support ++ Restriction to registers count removed // 3.0.0 + ModbusRTU Slave + ModbusRTU Master + Registers are now shared between Modbus* instances by default + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) -+ ModbusRTU examples added ++ ModbusRTU: Examples added + Test multiple Modbus* instances + Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both -+ Allow to specify local TCP port for Slave (default is 502) -+ Allow to specify TCP port for remote Slave connection (default is 502) -+ Master\Client. Fix crash on Write Multiple Hregs -+ Master\Client. Fix crash on no callback function on read\write remote ++ Client: Allow to specify local TCP port (default is 502) ++ Server: Allow to specify TCP remote port for connection (default is 502) ++ Master\Client: Fix crash on Write Multiple Hregs ++ Master\Client: Fix crash on no callback function on read\write remote + Tests added // ToDo later - 0x14 - Read File Records function - 0x15 - Write File Records function - 0x16 - Write Mask Register function - 0x17 - Read/Write Registers function -// 2.1.0 -+ Slave. Fix error response on write multiple Hregs\Coils -+ Slave. Fix writeCoil() for multiple coils -+ Master. dropTransactions() -+ Master. disconnect() -+ ~ModbusIP() -+ task() cleanup -+ Modify examples -+ Slave. Allow only single incoming master connection per IP -// 2.0.1 -+ Master. Fix readCoil\Hreg\Ists\Ireg not read value from slave -+ Fix crash on disconnect with Arduino Core 2.5.x -// 2.0.0 -+ Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. -+ Change object's list implementation to *std::vector* -+ Modbus class refactoring -+ ModbusIP networking code refactoring and error reporting -+ Global registers storage to share between multiple Modbus* instances -+ Move rest of implementations from Modbus.h -+ Modbus master implementation -+ Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL -+ Back to marking private for onSet, onGet, addReg and Reg methods -+ Added callback-related eventSource method, onDisconnect and transaction result callbacks -+ Extend register addressing to 0..65535 -+ removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) -+ readCoil, readHreg, readIsts, readIreg -+ push\pullCoil, push\pullHreg, pullIsts, pullIreg -+ pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg -+ optimize code around std::vector processing -+ extend removeCoil/Hreg/... to remove multiple registers -+ multiple callbacks => memory usage optimization -+ added removeOnSetCoil\... methods -+ added read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id -+ added ability to auto connect to slave. Setting is global. Disabled by default. ``` ## Contributions diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU-master/RTU-Master.ino index fb4be75..d4d8c23 100644 --- a/examples/RTU-master/RTU-Master.ino +++ b/examples/RTU-master/RTU-Master.ino @@ -45,8 +45,8 @@ void setup() { mb.begin(&Serial1); #else Serial1.begin(9600, SERIAL_8N1); - mb.setBaudrate(9600); mb.begin(&Serial1); + mb.setBaudrate(9600); #endif mb.master(); } diff --git a/examples/RTU-slave/RTU-slave.ino b/examples/RTU-slave/RTU-slave.ino index 73e7794..f5f0a64 100644 --- a/examples/RTU-slave/RTU-slave.ino +++ b/examples/RTU-slave/RTU-slave.ino @@ -23,8 +23,8 @@ void setup() { #if defined(ESP32) || defined(ESP8266) mb.begin(&Serial); #else - mb.setBaudrate(9600); mb.begin(&Serial); + mb.setBaudrate(9600); #endif mb.slave(SLAVE_ID); mb.addHreg(REGN); diff --git a/keywords.txt b/keywords.txt index 5566dc8..6b035ea 100644 --- a/keywords.txt +++ b/keywords.txt @@ -72,6 +72,8 @@ isCoil KEYWORD2 isHreg KEYWORD2 isIsts KEYWORD2 isIreg KEYWORD2 +begin KEYWORD2 +setBaudrate KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 7587a7a..e94e64c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,7 +1,7 @@ /* Modbus.cpp - Modbus Base Library Implementation Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" diff --git a/src/Modbus.h b/src/Modbus.h index 73a8e71..5bcf26d 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,7 +1,7 @@ /* Modbus.h - Header for Modbus Base Library Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once @@ -18,7 +18,7 @@ #define MB_GLOBAL_REGS -#define MB_MAX_REGS 32 +//#define MB_MAX_REGS 32 #define MODBUS_MAX_FRAME 256 #define COIL(n) (TAddress){TAddress::COIL, n} #define ISTS(n) (TAddress){TAddress::ISTS, n} diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 0ed2f76..a50024f 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,7 +1,7 @@ /* ModbusIP_ESP8266.h - Header for ModbusIP Library Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index a0a4602..3d17ce9 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -1,6 +1,6 @@ /* ModbusRTU Library for ESP8266/ESP32 - Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) + Copyright (C) 2019-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. */ @@ -44,7 +44,11 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { } void ModbusRTU::setBaudrate(uint32_t baud) { - _baudrate = baud; + if (baud > 19200) { + _t = 2; + } else { + _t = (35000/baud) + 1; + } } bool ModbusRTU::begin(Stream* port) { @@ -55,18 +59,13 @@ bool ModbusRTU::begin(Stream* port) { bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { uint32_t baud = 0; - if (_baudrate > 0) { - baud = _baudrate; - } - else { #if defined(ESP32) || defined(ESP8266) - // baudRate() only available with ESP32+ESP8266 - baud = port->baudRate(); + // baudRate() only available with ESP32+ESP8266 + baud = port->baudRate(); #else - #warning "Using default 9600 baud as not specified with setBaudrate()" - baud = 9600; + baud = 9600; #endif - } + setBaudrate(baud); #if defined(ESP8266) maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; #endif @@ -76,11 +75,6 @@ bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { pinMode(_txPin, OUTPUT); digitalWrite(_txPin, LOW); } - if (baud > 19200) { - _t = 2; - } else { - _t = (35000/baud) + 1; - } return true; } @@ -90,12 +84,7 @@ bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { _port = port; if (txPin >= 0) port->setTransmitEnablePin(txPin); - if (baud > 19200) { - _t = 2; - //port->enableIntTx(false); - } else { - _t = (35000/baud) + 1; - } + setBaudrate(baud); return true; } #endif diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 5b323df..f649e35 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,6 +1,6 @@ /* ModbusRTU Library for ESP8266/ESP32 - Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) + Copyright (C) 2019-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. */ @@ -23,7 +23,6 @@ class ModbusRTU : public Modbus { protected: Stream* _port; int16_t _txPin = -1; - uint32_t _baudrate = -1; unsigned int _t; // inter-frame delay in mS uint32_t t = 0; bool isMaster = false; From 696b6f728bfd9154658559008d4304062226a34f Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 8 Jun 2020 14:04:14 +0500 Subject: [PATCH 177/288] Add incoming packet output to serial for debug --- README.md | 3 ++- library.properties | 4 ++-- src/ModbusRTU.cpp | 9 ++++++++- src/ModbusRTU.h | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5714719..03c4946 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ModbusRTU and ModbusIP Master-Slave Library for ESP8266/ESP32 v3.0 +# ModbusRTU and ModbusIP Library for ESP8266/ESP32 v3.0 |If this project is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| @@ -29,6 +29,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * Supported platforms are * ESP8266 * ESP32 + * STM32F103 and probably other (Modbus RTU only) * Operates as * slave * master diff --git a/library.properties b/library.properties index d10b7ab..8f34736 100644 --- a/library.properties +++ b/library.properties @@ -1,8 +1,8 @@ name=modbus-esp8266 -version=3.0.0 +version=3.0.1 author=Andre Sarmento Barbosa maintainer=Alexander Emelianov -sentence=Modbus Master-Slave Library for ESP8266/ESP32 +sentence=ModbusRTU and ModbusIP Library for ESP8266/ESP32 paragraph=This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index a0a4602..cdd8338 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -180,8 +180,15 @@ void ModbusRTU::task() { _len = 0; return; } - for (uint8_t i=0 ; i < _len ; i++) + for (uint8_t i=0 ; i < _len ; i++) { _frame[i] = _port->read(); // read data + crc + #if defined(MODBUSRTU_DEBUG) + Serial.printf("%02X ", _frame[i]); + #endif + } + #if defined(MODBUSRTU_DEBUG) + Serial.println(); + #endif //_port->readBytes(_frame, _len); u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc _len = _len - 2; // Decrease by CRC 2 bytes diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 5b323df..eece580 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -11,6 +11,7 @@ #include #endif +#define MODBUSRTU_DEBUG #define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 From b4b6b2d037864c105a1eb2de08234606cc709b0f Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 8 Jun 2020 14:15:29 +0500 Subject: [PATCH 178/288] README update --- README.md | 49 +++++++++------------------------------------- library.properties | 2 +- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 03c4946..6e5e3cd 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,11 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * ESP8266 * ESP32 * STM32F103 and probably other (Modbus RTU only) -* Operates as - * slave - * master -* Supports - * Modbus IP (TCP) - * Modbus RTU (RS-485) +* Operates in any combination of multiple instances of + * Modbus RTU slave + * Modbus RTU master + * Modbus IP server + * Modbus IP client * Reply exception messages for all supported functions * Modbus functions supported: * 0x01 - Read Coils @@ -64,6 +63,10 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 3.0.1 ++ ModbusRTU: ESP32 possible send failure fix ++ ModbusRTU: Non-ESP devices support ++ Restriction to registers count removed // 3.0.0 + ModbusRTU Slave + ModbusRTU Master @@ -82,40 +85,6 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) - 0x15 - Write File Records function - 0x16 - Write Mask Register function - 0x17 - Read/Write Registers function -// 2.1.0 -+ Slave. Fix error response on write multiple Hregs\Coils -+ Slave. Fix writeCoil() for multiple coils -+ Master. dropTransactions() -+ Master. disconnect() -+ ~ModbusIP() -+ task() cleanup -+ Modify examples -+ Slave. Allow only single incoming master connection per IP -// 2.0.1 -+ Master. Fix readCoil\Hreg\Ists\Ireg not read value from slave -+ Fix crash on disconnect with Arduino Core 2.5.x -// 2.0.0 -+ Remove memory allocation checking for small blocks as anyway firmware will fail if so low memory available. -+ Change object's list implementation to *std::vector* -+ Modbus class refactoring -+ ModbusIP networking code refactoring and error reporting -+ Global registers storage to share between multiple Modbus* instances -+ Move rest of implementations from Modbus.h -+ Modbus master implementation -+ Move enum constants. E.g. MB_FC_READ_COIL => Modbus::FC_READ_COIL -+ Back to marking private for onSet, onGet, addReg and Reg methods -+ Added callback-related eventSource method, onDisconnect and transaction result callbacks -+ Extend register addressing to 0..65535 -+ removeCoil, removeIsts, removeIreg, removeHreg, (removeReg) -+ readCoil, readHreg, readIsts, readIreg -+ push\pullCoil, push\pullHreg, pullIsts, pullIreg -+ pullCoilToIsts, pullHregToIreg, pushIstsToCoil, pushIregToHreg -+ optimize code around std::vector processing -+ extend removeCoil/Hreg/... to remove multiple registers -+ multiple callbacks => memory usage optimization -+ added removeOnSetCoil\... methods -+ added read/write/push/pullCoil/Hreg/Ireg/Ists() parameter to specify Modbus unit id -+ added ability to auto connect to slave. Setting is global. Disabled by default. ``` ## Contributions diff --git a/library.properties b/library.properties index 8f34736..9d66fdc 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=modbus-esp8266 version=3.0.1 -author=Andre Sarmento Barbosa +author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=ModbusRTU and ModbusIP Library for ESP8266/ESP32 paragraph=This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. From b02629611129b9888bf413e0acb766cded07a6fc Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 14 Jun 2020 22:26:28 +0300 Subject: [PATCH 179/288] Remove code duplication in masterPDU --- src/Modbus.cpp | 49 ++++++++----------------------------------------- 1 file changed, 8 insertions(+), 41 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e94e64c..08801cd 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -453,24 +453,25 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t fcode = frame[0]; _reply = EX_SUCCESS; if ((fcode & 0x80) != 0) { - _reply = _frame[1]; + _reply = frame[1]; return; } uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; uint8_t bytecount_calc; switch (fcode) { case FC_READ_REGS: - //field1 = startreg, field2 = numregs, frame[1] = data lenght, header len = 2 + case FC_READ_INPUT_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) { - frame = frame + 2; + frame += 2; while(field2) { *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); - frame = frame + 2; - output = output + 2; + frame += 2; + output += 2; field2--; } } else { @@ -478,21 +479,8 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, } break; case FC_READ_COILS: - //field1 = startreg, 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; case FC_READ_INPUT_STAT: - //field1 = startreg, field2 = numregs, frame[1] = data length, header len = 2 + //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 @@ -505,34 +493,13 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, setMultipleBits(frame + 2, startreg, field2); } break; - case FC_READ_INPUT_REGS: - //field1 = startreg, field2 = status, frame[1] = data lenght, header len = 2 - if (frame[1] != 2 * field2) { //Check if data size matches - _reply = EX_DATA_MISMACH; - break; - } - if (output) { - frame = frame + 2; - while(field2) { - *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); - frame = frame + 2; - output = output + 2; - field2--; - } - } else { - setMultipleWords((uint16_t*)(frame + 2), startreg, field2); - } - break; case FC_WRITE_REG: - break; case FC_WRITE_REGS: - break; case FC_WRITE_COIL: - break; case FC_WRITE_COILS: break; default: - _reply = EX_GENERAL_FAILURE; + _reply = EX_GENERAL_FAILURE; } } From 95b26c84634523e3191d288e2314f1ad9f931d5b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 17 Jun 2020 11:46:50 +0300 Subject: [PATCH 180/288] ModbusIP: master/slave => client/server --- API.md | 14 ++- README.md | 2 + examples/IP-bridge/IP-bridge.ino | 34 ++++++ .../IP-client-Pull.ino} | 8 +- .../IP-client-SimpleRead.ino} | 10 +- .../IP-client-WriteMultiple.ino} | 8 +- .../IP-server-AnalogInput.ino} | 6 +- .../IP-server-Callback.ino} | 7 +- .../IP-server-HoldingReg.ino} | 6 +- .../IP-server-Led.ino} | 6 +- .../IP-server-MassOperations.ino} | 12 +- .../IP-server-MultipleHRegDebug.ino} | 6 +- .../IP-server-SwitchStatus.ino} | 6 +- keywords.txt | 6 +- src/Modbus.cpp | 2 +- src/Modbus.h | 2 +- src/ModbusIP_ESP8266.cpp | 108 +++++++++--------- src/ModbusIP_ESP8266.h | 14 ++- src/ModbusRTU.h | 4 +- 19 files changed, 131 insertions(+), 130 deletions(-) create mode 100644 examples/IP-bridge/IP-bridge.ino rename examples/{Master/Master.ino => IP-client-Pull/IP-client-Pull.ino} (86%) rename examples/{MasterSimpleRead/MasterSimpleRead.ino => IP-client-SimpleRead/IP-client-SimpleRead.ino} (84%) rename examples/{MasterWriteMultiple/MasterWriteMultiple.ino => IP-client-WriteMultiple/IP-client-WriteMultiple.ino} (93%) rename examples/{AnalogInput/AnalogInput.ino => IP-server-AnalogInput/IP-server-AnalogInput.ino} (93%) rename examples/{Callback/Callback.ino => IP-server-Callback/IP-server-Callback.ino} (96%) rename examples/{HoldingReg/HoldingReg.ino => IP-server-HoldingReg/IP-server-HoldingReg.ino} (93%) rename examples/{Led/Led.ino => IP-server-Led/IP-server-Led.ino} (94%) rename examples/{MassOperations/MassOperations.ino => IP-server-MassOperations/IP-server-MassOperations.ino} (80%) rename examples/{MultipleHRegDebug/MultipleHRegDebug.ino => IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino} (92%) rename examples/{SwitchStatus/SwitchStatus.ino => IP-server-SwitchStatus/IP-server-SwitchStatus.ino} (93%) diff --git a/API.md b/API.md index 26bf670..c8a6072 100644 --- a/API.md +++ b/API.md @@ -49,6 +49,7 @@ Processing routine. Should be periodically called form loop(). ```c bool begin(SoftwareSerial* port, int16_t txPin=-1); // For ESP8266 only bool begin(HardwareSerial* port, int16_t txPin=-1); +bool begin(Stream* port); ``` Assing Serial port. txPin controls transmit enable for MAX-485. @@ -70,19 +71,21 @@ Select and initialize master or slave mode to work. Switching between modes is n uint8_t slave(); ``` -Slave mode. Returns configured slave id. Master mode. Returns slave id for active request or 0 if no request in-progress. +Slave mode: Returns configured slave id. Master mode: Returns slave id for active request or 0 if no request in-progress. ### ModBus IP Slave specific API ```c -void begin(); // Depricated. Use slave() instead. -void slave(uint16_t port = MODBUSIP_PORT); +void begin(); // Depricated. Use server() instead. +void slave(uint16_t port = MODBUSIP_PORT); // For compatibility with ModbusRTU calls. Typically may be replaced with server() call. +void server(uint16_t port = MODBUSIP_PORT); ``` ### ModBus IP Master specific ```c -void master(); +void master(); // For compatibility with ModbusRTU calls. Typically may be replaced with client() call. +void client(); bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); bool disconnect(IPAddress ip); bool isTransaction(uint16_t id); @@ -122,8 +125,7 @@ Result is saved to local registers. Method returns corresponding transaction id. uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = -MODBUSIP_UNIT); +uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); diff --git a/README.md b/README.md index 04874ba..bb499f1 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + ModbusRTU: ESP32 possible send failure fix + ModbusRTU: Non-ESP devices support + Restriction to registers count removed ++ Added bridge example // 3.0.0 + ModbusRTU Slave + ModbusRTU Master @@ -85,6 +86,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) - 0x15 - Write File Records function - 0x16 - Write Mask Register function - 0x17 - Read/Write Registers function +- ModbusIP: Support for non-ESP boards (using W5x00) ``` ## Contributions diff --git a/examples/IP-bridge/IP-bridge.ino b/examples/IP-bridge/IP-bridge.ino new file mode 100644 index 0000000..bfea948 --- /dev/null +++ b/examples/IP-bridge/IP-bridge.ino @@ -0,0 +1,34 @@ +/* + Modbus ESP8266/ESP32 + Simple ModbesRTU to ModbusIP bridge + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#include +#include + +#define TO_REG 10 +#define SLAVE_ID 1 +#define PULL_ID 2 +#define FROM_REG 20 + +ModbusRTU mb1; +ModbusIP mb2; + +void setup() { + Serial1.begin(9600, SERIAL_8N1); + mb1.begin(&Serial1); + mb1.master(); + mb2.server(); + mb2.addHreg(TO_REG); +} + +void loop() { + if(!mb1.slave()) + mb1.pullHreg(PULL_ID, FROM_REG, TO_REG); + mb1.task(); + mb2.task(); + delay(50); +} \ No newline at end of file diff --git a/examples/Master/Master.ino b/examples/IP-client-Pull/IP-client-Pull.ino similarity index 86% rename from examples/Master/Master.ino rename to examples/IP-client-Pull/IP-client-Pull.ino index ab4ac8b..0a28835 100644 --- a/examples/Master/Master.ino +++ b/examples/IP-client-Pull/IP-client-Pull.ino @@ -1,5 +1,5 @@ /* - Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) + Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) @@ -40,11 +40,7 @@ uint16_t gc(TRegister* r, uint16_t v) { // Callback function } void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("SSID", "password"); @@ -58,7 +54,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.master(); // Initialize local Modbus Master + mb.client(); // Initialize local Modbus Client pinMode(USE_LED, OUTPUT); mb.addCoil(LED_COIL); // Add Coil mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil diff --git a/examples/MasterSimpleRead/MasterSimpleRead.ino b/examples/IP-client-SimpleRead/IP-client-SimpleRead.ino similarity index 84% rename from examples/MasterSimpleRead/MasterSimpleRead.ino rename to examples/IP-client-SimpleRead/IP-client-SimpleRead.ino index 47b886d..a7f3d5c 100644 --- a/examples/MasterSimpleRead/MasterSimpleRead.ino +++ b/examples/IP-client-SimpleRead/IP-client-SimpleRead.ino @@ -1,6 +1,6 @@ /* - Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) - Read Holding Register from Slave device + Modbus-Arduino Example - Master Modbus IP Client (ESP8266/ESP32) + Read Holding Register from Server device (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 @@ -20,11 +20,7 @@ const int LOOP_COUNT = 10; ModbusIP mb; //ModbusIP object void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("SSID", "PASSWORD"); @@ -38,7 +34,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.master(); + mb.client(); } uint16_t res = 0; diff --git a/examples/MasterWriteMultiple/MasterWriteMultiple.ino b/examples/IP-client-WriteMultiple/IP-client-WriteMultiple.ino similarity index 93% rename from examples/MasterWriteMultiple/MasterWriteMultiple.ino rename to examples/IP-client-WriteMultiple/IP-client-WriteMultiple.ino index 2a6c401..68fd532 100644 --- a/examples/MasterWriteMultiple/MasterWriteMultiple.ino +++ b/examples/IP-client-WriteMultiple/IP-client-WriteMultiple.ino @@ -1,5 +1,5 @@ /* - Modbus-Arduino Example - Master (Modbus IP ESP8266/ESP32) + Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) Write multiple coils to Slave device (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) @@ -20,11 +20,7 @@ IPAddress remote(192, 168, 20, 102); // Address of Modbus Slave device ModbusIP mb; // ModbusIP object void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin(); @@ -38,7 +34,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.master(); + mb.client(); } bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback diff --git a/examples/AnalogInput/AnalogInput.ino b/examples/IP-server-AnalogInput/IP-server-AnalogInput.ino similarity index 93% rename from examples/AnalogInput/AnalogInput.ino rename to examples/IP-server-AnalogInput/IP-server-AnalogInput.ino index 55a02a9..0e39a5f 100644 --- a/examples/AnalogInput/AnalogInput.ino +++ b/examples/IP-server-AnalogInput/IP-server-AnalogInput.ino @@ -26,11 +26,7 @@ ModbusIP mb; long ts; void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { @@ -43,7 +39,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.slave(); //Start Modbus IP + mb.server(); //Start Modbus IP // Add SENSOR_IREG register - Use addIreg() for analog Inputs mb.addIreg(SENSOR_IREG); diff --git a/examples/Callback/Callback.ino b/examples/IP-server-Callback/IP-server-Callback.ino similarity index 96% rename from examples/Callback/Callback.ino rename to examples/IP-server-Callback/IP-server-Callback.ino index 4ccb2a1..d604f49 100644 --- a/examples/Callback/Callback.ino +++ b/examples/IP-server-Callback/IP-server-Callback.ino @@ -42,11 +42,8 @@ bool cbConn(IPAddress ip) { } void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif + WiFi.begin("SID", "PASSWORD"); while (WiFi.status() != WL_CONNECTED) { @@ -60,7 +57,7 @@ void setup() { Serial.println(WiFi.localIP()); mb.onConnect(cbConn); // Add callback on connection event - mb.slave(); + mb.server(); pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) diff --git a/examples/HoldingReg/HoldingReg.ino b/examples/IP-server-HoldingReg/IP-server-HoldingReg.ino similarity index 93% rename from examples/HoldingReg/HoldingReg.ino rename to examples/IP-server-HoldingReg/IP-server-HoldingReg.ino index 4a183ae..1cf0977 100644 --- a/examples/HoldingReg/HoldingReg.ino +++ b/examples/IP-server-HoldingReg/IP-server-HoldingReg.ino @@ -26,11 +26,7 @@ const int TEST_HREG = 100; ModbusIP mb; void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("your_ssid", "your_password"); @@ -44,7 +40,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.slave(); + mb.server(); mb.addHreg(TEST_HREG, 0xABCD); } diff --git a/examples/Led/Led.ino b/examples/IP-server-Led/IP-server-Led.ino similarity index 94% rename from examples/Led/Led.ino rename to examples/IP-server-Led/IP-server-Led.ino index ce6990b..5155711 100644 --- a/examples/Led/Led.ino +++ b/examples/IP-server-Led/IP-server-Led.ino @@ -26,11 +26,7 @@ const int ledPin = 0; //GPIO0 ModbusIP mb; void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("your_ssid", "your_password"); @@ -44,7 +40,7 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); - mb.slave(); + mb.server(); pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); diff --git a/examples/MassOperations/MassOperations.ino b/examples/IP-server-MassOperations/IP-server-MassOperations.ino similarity index 80% rename from examples/MassOperations/MassOperations.ino rename to examples/IP-server-MassOperations/IP-server-MassOperations.ino index 3b0e0a5..12cf3bb 100644 --- a/examples/MassOperations/MassOperations.ino +++ b/examples/IP-server-MassOperations/IP-server-MassOperations.ino @@ -30,6 +30,8 @@ ModbusIP mb; // Callback function to read corresponding DI uint16_t cbRead(TRegister* reg, uint16_t val) { + // Checking value of register address which callback is called on. + // See Modbus.h for TRegister and TAddress definition if(reg->address.address < COIL_BASE) return 0; uint8_t offset = reg->address.address - COIL_BASE; @@ -49,11 +51,7 @@ bool cbConn(IPAddress ip) { } void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("ssid", "password"); @@ -69,11 +67,11 @@ void setup() { for (uint8_t i = 0; i < LEN; i++) pinMode(pinList[i], INPUT); mb.onConnect(cbConn); // Add callback on connection event - mb.slave(); + mb.server(); mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. - mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add callback on Coils value get - mb.onSetCoil(COIL_BASE, cbWrite, LEN); + mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add single callback for multiple Coils. It will be called for each of these coils value get + mb.onSetCoil(COIL_BASE, cbWrite, LEN); // The same as above just for set value } void loop() { diff --git a/examples/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino similarity index 92% rename from examples/MultipleHRegDebug/MultipleHRegDebug.ino rename to examples/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino index 3f7b507..cdf68af 100644 --- a/examples/MultipleHRegDebug/MultipleHRegDebug.ino +++ b/examples/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino @@ -50,11 +50,7 @@ bool cbConn(IPAddress ip) { } void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("ssid", "pass"); @@ -69,7 +65,7 @@ void setup() { Serial.println(WiFi.localIP()); mb.onConnect(cbConn); // Add callback on connection event - mb.slave(); + mb.server(); if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get diff --git a/examples/SwitchStatus/SwitchStatus.ino b/examples/IP-server-SwitchStatus/IP-server-SwitchStatus.ino similarity index 93% rename from examples/SwitchStatus/SwitchStatus.ino rename to examples/IP-server-SwitchStatus/IP-server-SwitchStatus.ino index bc3bd16..6a7b339 100644 --- a/examples/SwitchStatus/SwitchStatus.ino +++ b/examples/IP-server-SwitchStatus/IP-server-SwitchStatus.ino @@ -26,11 +26,7 @@ const int switchPin = 0; //GPIO0 ModbusIP mb; void setup() { - #ifdef ESP8266 - Serial.begin(74880); - #else Serial.begin(115200); - #endif WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { @@ -38,7 +34,7 @@ void setup() { Serial.print("."); } //Config Modbus IP - mb.slave(); + mb.server(); //Set ledPin mode pinMode(switchPin, INPUT); // Add SWITCH_ISTS register - Use addIsts() for digital inputs diff --git a/keywords.txt b/keywords.txt index 6b035ea..8701a0f 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,9 +1,9 @@ # Syntax Coloring Map For ModbusIP-ESP8266 # Datatypes (KEYWORD1) -ModbusRTUSlave KEYWORD1 -ModbusRTUMaster KEYWORD1 +ModbusRTU KEYWORD1 ModbusIP KEYWORD1 +ModbusIP_ESP8266 KEYWORD1 Modbus KEYWORD1 TRegister KEYWORD1 TTransaction KEYWORD1 @@ -13,6 +13,8 @@ ResultCode KEYWORD1 # Methods and Functions (KEYWORD2) master KEYWORD2 slave KEYWORD2 +client KEYWORD2 +server KEYWORD2 task KEYWORD2 onConnect KEYWORD2 onDisconnect KEYWORD2 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 08801cd..e53d872 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,5 +1,5 @@ /* - Modbus.cpp - Modbus Base Library Implementation + Modbus.cpp - Modbus Core Library Implementation Copyright (C) 2014 Andr� Sarmento Barbosa 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) */ diff --git a/src/Modbus.h b/src/Modbus.h index 5bcf26d..1fe9d80 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,5 +1,5 @@ /* - Modbus.h - Header for Modbus Base Library + Modbus.h - Header for Modbus Core Library Copyright (C) 2014 Andr� Sarmento Barbosa 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) */ diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index c1962a7..da9021e 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -10,21 +10,17 @@ ModbusIP::ModbusIP() { //_trans.reserve(MODBUSIP_MAX_TRANSACIONS); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - client[i] = nullptr; + tcpclient[i] = nullptr; } -void ModbusIP::master() { +void ModbusIP::client() { } -void ModbusIP::slave(uint16_t port) { - slavePort = port; - server = new WiFiServer(slavePort); - server->begin(); -} - -void ModbusIP::begin() { - slave(); +void ModbusIP::server(uint16_t port) { + serverPort = port; + tcpserver = new WiFiServer(serverPort); + tcpserver->begin(); } bool ModbusIP::connect(IPAddress ip, uint16_t port) { @@ -34,13 +30,13 @@ bool ModbusIP::connect(IPAddress ip, uint16_t port) { int8_t p = getFreeClient(); if (p == -1) return false; - client[p] = new WiFiClient(); - return client[p]->connect(ip, port); + tcpclient[p] = new WiFiClient(); + return tcpclient[p]->connect(ip, port); } uint32_t ModbusIP::eventSource() { // Returns IP of current processing client query - if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && client[n]) - return (uint32_t)client[n]->remoteIP(); + if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && tcpclient[n]) + return (uint32_t)tcpclient[n]->remoteIP(); return (uint32_t)INADDR_NONE; } @@ -53,9 +49,9 @@ TTransaction* ModbusIP::searchTransaction(uint16_t id) { void ModbusIP::task() { MBAP_t _MBAP; cleanup(); - if (server) { - while (server->hasClient()) { - WiFiClient* currentClient = new WiFiClient(server->available()); + if (tcpserver) { + while (tcpserver->hasClient()) { + WiFiClient* currentClient = new WiFiClient(tcpserver->available()); if (!currentClient || !currentClient->connected()) continue; if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { @@ -63,14 +59,14 @@ void ModbusIP::task() { // Disconnect previous connection from same IP if present n = getMaster(currentClient->remoteIP()); if (n != -1) { - client[n]->flush(); - delete client[n]; - client[n] = nullptr; + tcpclient[n]->flush(); + delete tcpclient[n]; + tcpclient[n] = nullptr; } #endif n = getFreeClient(); if (n > -1) { - client[n] = currentClient; + tcpclient[n] = currentClient; continue; // while } } @@ -79,38 +75,38 @@ void ModbusIP::task() { } } for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { - if (!client[n]) continue; - if (!client[n]->connected()) continue; + if (!tcpclient[n]) continue; + if (!tcpclient[n]->connected()) continue; uint32_t readStart = millis(); - while (millis() - readStart < MODBUSIP_MAX_READMS && client[n]->available() > sizeof(_MBAP)) { - client[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP + while (millis() - readStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { + tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. - while (client[n]->available()) // Drop all incoming if wrong packet - client[n]->read(); + while (tcpclient[n]->available()) // Drop all incoming if wrong packet + tcpclient[n]->read(); continue; } _len = __bswap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME - exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); + exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); _len--; // Subtract for read byte - for (uint8_t i = 0; client[n]->available() && i < _len; i++) // Drop rest of packet - client[n]->read(); + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of packet + tcpclient[n]->read(); } else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { - exceptionResponse((FunctionCode)client[n]->read(), EX_SLAVE_FAILURE); - for (uint8_t i = 0; client[n]->available() && i < _len; i++) // Drop packet - client[n]->read(); + exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop packet + tcpclient[n]->read(); } else { - if (client[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame + if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - //while (client[n]->available()) // Drop all incoming (if any) - // client[n]->read(); + //while (tcpclient[n]->available()) // Drop all incoming (if any) + // tcpclient[n]->read(); } else { - if (client[n]->localPort() == slavePort) { + if (tcpclient[n]->localPort() == serverPort) { // Process incoming frame as slave slavePDU(_frame); } else { @@ -137,15 +133,15 @@ void ModbusIP::task() { } } } - if (client[n]->localPort() != slavePort) _reply = REPLY_OFF; // No replay if it was responce to master + if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master if (_reply != REPLY_OFF) { _MBAP.length = __bswap_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); - client[n]->write(sbuf, send_len); - client[n]->flush(); + tcpclient[n]->write(sbuf, send_len); + tcpclient[n]->flush(); } if (_frame) { free(_frame); @@ -163,7 +159,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; #endif int8_t p = getSlave(ip); - if (p == -1 || !client[p]->connected()) + if (p == -1 || !tcpclient[p]->connected()) return autoConnectMode?connect(ip):false; transactionId++; if (!transactionId) transactionId = 1; @@ -175,9 +171,9 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 uint8_t sbuf[send_len]; memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); - if (client[p]->write(sbuf, send_len) != send_len) + if (tcpclient[p]->write(sbuf, send_len) != send_len) return false; - client[p]->flush(); + tcpclient[p]->flush(); if (waitResponse) { TTransaction tmp; tmp.transactionId = transactionId; @@ -205,11 +201,11 @@ void ModbusIP::onDisconnect(cbModbusConnect cb) { void ModbusIP::cleanup() { // Free clients if not connected for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - if (client[i] && !client[i]->connected()) { - //IPAddress ip = client[i]->remoteIP(); - //client[i]->stop(); - delete client[i]; - client[i] = nullptr; + 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); } @@ -229,21 +225,21 @@ void ModbusIP::cleanup() { int8_t ModbusIP::getFreeClient() { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (!client[i]) + if (!tcpclient[i]) return i; return -1; } int8_t ModbusIP::getSlave(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() != slavePort) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() != serverPort) return i; return -1; } int8_t ModbusIP::getMaster(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (client[i] && client[i]->connected() && client[i]->remoteIP() == ip && client[i]->localPort() == slavePort) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() == serverPort) return i; return -1; } @@ -397,7 +393,7 @@ bool ModbusIP::isTransaction(uint16_t id) { } bool ModbusIP::isConnected(IPAddress ip) { int8_t p = getSlave(ip); - return p != -1 && client[p]->connected(); + return p != -1 && tcpclient[p]->connected(); } void ModbusIP::autoConnect(bool enabled) { @@ -407,8 +403,8 @@ void ModbusIP::autoConnect(bool enabled) { bool ModbusIP::disconnect(IPAddress ip) { int8_t p = getSlave(ip); if (p != -1) { - delete client[p]; - client[p] = nullptr; + delete tcpclient[p]; + tcpclient[p] = nullptr; } return true; } @@ -422,8 +418,8 @@ ModbusIP::~ModbusIP() { dropTransactions(); cleanup(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - delete client[i]; - client[i] = nullptr; + delete tcpclient[i]; + tcpclient[i] = nullptr; } } diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index a50024f..15b27e3 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -53,13 +53,13 @@ class ModbusIP : public Modbus { }; cbModbusConnect cbConnect = nullptr; cbModbusConnect cbDisconnect = nullptr; - WiFiServer* server = nullptr; - WiFiClient* client[MODBUSIP_MAX_CLIENTS]; + WiFiServer* tcpserver = nullptr; + WiFiClient* tcpclient[MODBUSIP_MAX_CLIENTS]; std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; bool autoConnectMode = false; - uint16_t slavePort = 0; + uint16_t serverPort = 0; TTransaction* searchTransaction(uint16_t id); void cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events @@ -80,10 +80,12 @@ class ModbusIP : public Modbus { bool isConnected(IPAddress ip); bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); bool disconnect(IPAddress ip); - void slave(uint16_t port = MODBUSIP_PORT); - void master(); + void server(uint16_t port = MODBUSIP_PORT); + inline void slave(uint16_t port = MODBUSIP_PORT) { server(port); } // Depricated + void client(); + inline void master() { client(); } // Depricated void task(); - void begin(); // Depricated + inline void begin() { server(); }; // Depricated void onConnect(cbModbusConnect cb = nullptr); void onDisconnect(cbModbusConnect cb = nullptr); uint32_t eventSource() override; diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 03467c7..0648c35 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -11,7 +11,7 @@ #include #endif -#define MODBUSRTU_DEBUG +//#define MODBUSRTU_DEBUG #define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 @@ -25,7 +25,7 @@ class ModbusRTU : public Modbus { Stream* _port; int16_t _txPin = -1; unsigned int _t; // inter-frame delay in mS - uint32_t t = 0; + uint32_t t = 0; // time sience last data byte arrived bool isMaster = false; uint8_t _slaveId; uint32_t _timestamp = 0; From e735a3bdab5c91e6df43571ba8a8c5436e9107b8 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 17 Jun 2020 14:05:07 +0300 Subject: [PATCH 181/288] ReadMe update --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bb499f1..9b99d65 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ModbusRTU and ModbusIP Library for ESP8266/ESP32 v3.0 +# ModbusRTU and ModbusIP Library for ESP8266/ESP32 -|If this project is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| +|If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| @@ -13,7 +13,7 @@ used in industrial automation and can be used in other areas, such as home autom The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). -In the current version the library allows the ESP8266/ESP32 operate as a master and/or slave, supporting Modbus IP via wireless network and Modbus RTU over serial. For more information about Modbus see: +For more information about Modbus see: * [Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) * [MODBUS APPLICATION PROTOCOL SPECIFICATION @@ -29,7 +29,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * Supported platforms are * ESP8266 * ESP32 - * STM32F103 and probably other (Modbus RTU only) + * STM32F103 and probably others (Modbus RTU only) * Operates in any combination of multiple instances of * Modbus RTU slave * Modbus RTU master From 615b401f6c1f1d36984b5acd091706f11e95bd43 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 18 Jun 2020 08:41:36 +0300 Subject: [PATCH 182/288] ModbusRTU: ESP32 possible recive fix --- README.md | 2 +- README_pt_BR.md | 83 ----------------------------------------------- src/Modbus.h | 2 +- src/ModbusRTU.cpp | 18 +++++----- src/ModbusRTU.h | 3 ++ 5 files changed, 13 insertions(+), 95 deletions(-) delete mode 100644 README_pt_BR.md diff --git a/README.md b/README.md index 9b99d65..fdf3e57 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ```diff // 3.0.1 -+ ModbusRTU: ESP32 possible send failure fix ++ ModbusRTU: ESP32 possible send\receive failure fix + ModbusRTU: Non-ESP devices support + Restriction to registers count removed + Added bridge example diff --git a/README_pt_BR.md b/README_pt_BR.md deleted file mode 100644 index 28efc59..0000000 --- a/README_pt_BR.md +++ /dev/null @@ -1,83 +0,0 @@ -Biblioteca Modbus para ESP8266 -============================== - -Esta biblioteca permite que seu ESP8266 se comunique através do protocolo Modbus. -O Modbus é um protocolo do tipo mestre-escravo, utilizado em automação industrial, -podendo ser utilizado em outras áreas, como por exemplo, na automação residencial. - -O Modbus geralmente utiliza como meio físico as interfaces seriais RS-232 ou RS-485 -(quando é chamado Modbus Serial) e TCP/IP via Ethernet ou WiFi (Modbus IP). - -Na versão atual a biblioteca permite que o arduino opere como escravo, suportando -o Modbus IP via rede wireless. Para mais informações sobre o Modbus consulte: - -http://pt.wikipedia.org/wiki/Modbus -http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf -http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf - -Características -=============== - -
        -
      • Opera como escravo
      • -
      • Suporta Modbus IP (TCP não keep-alive)
      • -
      • Responde todos os tipos de exceção para as funções suportadas
      • -
      • Suporta as seguintes funções Modbus:
      • -
          -
        • 0x01 - Read Coils
        • -
        • 0x02 - Read Input Status (Read Discrete Inputs)
        • -
        • 0x03 - Read Holding Registers
        • -
        • 0x04 - Read Input Registers
        • -
        • 0x05 - Write Single Coil
        • -
        • 0x06 - Write Single Register
        • -
        • 0x0F - Write Multiple Coils
        • -
        • 0x10 - Write Multiple Registers
        • -
        -
      - -Observações: - -1. Quando se usa Modbus IP o protocolo de transporte é o TCP (porta 502) e a conexão -é finalizada a cada mensagem transmitida, ou seja, não é do tipo keep-alive. - -2. Os offsets para acesso aos registradores são baseados em 0. Assim, tenha cuidado -ao configurar seu seu supervisório ou utilitário de teste. Por exempo, no ScadaBR -(http://www.scadabr.com.br) os offsets são baseados em 0, então, um registrador -configurado como 100 na biblioteca será configurado como 100 no ScadaBR. Por outro -lado, no software de teste CAS Modbus Scanner (http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) -os offsets são baseados em 1, logo, um registrador configurado como 100 na biblioteca -deverá ser 101 neste software. - -3. No início do arquivo Modbus.h da biblioteca há uma opção para limitar o funcionamento -da mesma às funções de Holding Registers, salvando espaço na memória de programa. -Basta retirar o comentário da seguinte linha: - -``` -#define USE_HOLDING_REGISTERS_ONLY -``` -Dessa forma, somente as seguintes funções são suportadas: -
        -
      • 0x03 - Read Holding Registers
      • -
      • 0x06 - Write Single Register
      • -
      • 0x10 - Write Multiple Registers
      • -
      - -Como utilizar -============= - -``` -Este README está em desenvolvimento, por enquanto, consulte os exemplos da biblioteca. -``` - -Contribuições -============= -http://github.com/andresarmento/modbus-esp8266
      -prof (at) andresarmento (dot) com - -Licença -======= - -O código neste repositório é licenciado pela BSD New License. -Veja [LICENSE.txt](LICENSE.txt) para mais informações. - - diff --git a/src/Modbus.h b/src/Modbus.h index 1fe9d80..c9143d0 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -17,7 +17,7 @@ #endif -#define MB_GLOBAL_REGS +//#define MB_GLOBAL_REGS //#define MB_MAX_REGS 32 #define MODBUS_MAX_FRAME 256 #define COIL(n) (TAddress){TAddress::COIL, n} diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index b2cb932..d27b038 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -96,7 +96,6 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { delay(1); } #ifdef ESP32 - portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; portENTER_CRITICAL(&mux); #endif _port->write(slaveId); //Send slaveId @@ -131,23 +130,22 @@ bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* void ModbusRTU::task() { #ifdef ESP32 - portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; - portENTER_CRITICAL(&mux); + if (_len == 0) + portENTER_CRITICAL(&mux); #endif if (_port->available() > _len) { _len = _port->available(); t = millis(); + return; } - if (isMaster) cleanup(); - if (_len == 0 || millis() - t < _t) { // No data or not end of frame - #ifdef ESP32 - portEXIT_CRITICAL(&mux); - #endif - return; // Wait data whitespace - } + if (_len != 0 && millis() - t < _t) // Wait data whitespace if there is data + return; #ifdef ESP32 portEXIT_CRITICAL(&mux); #endif + if (isMaster) cleanup(); + if (_len == 0) + return; uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 0648c35..23845a7 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -34,6 +34,9 @@ class ModbusRTU : public Modbus { uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); uint16_t maxRegs = 0x007D; + #ifdef ESP32 + portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + #endif bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); // Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data // slaveId - slave id From 82c23e14a589a351b1608fd4c79ce130ab6d5013 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 25 Jun 2020 10:18:45 +0300 Subject: [PATCH 183/288] ESP32: Drop mutex in task as it were resulting stability problems --- src/ModbusRTU.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index d27b038..1822c7a 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -75,6 +75,7 @@ bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { pinMode(_txPin, OUTPUT); digitalWrite(_txPin, LOW); } + Serial.println(_t); return true; } @@ -129,10 +130,6 @@ bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* } void ModbusRTU::task() { - #ifdef ESP32 - if (_len == 0) - portENTER_CRITICAL(&mux); - #endif if (_port->available() > _len) { _len = _port->available(); t = millis(); @@ -140,9 +137,6 @@ void ModbusRTU::task() { } if (_len != 0 && millis() - t < _t) // Wait data whitespace if there is data return; - #ifdef ESP32 - portEXIT_CRITICAL(&mux); - #endif if (isMaster) cleanup(); if (_len == 0) return; From 69597f44b47b41d7f0ff9b305005deeedc7172bc Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 3 Jul 2020 08:13:15 +0300 Subject: [PATCH 184/288] ModbusRTU task(): reduce timeout possibility due to rare task() calls --- src/ModbusRTU.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 1822c7a..fbba6f3 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -130,27 +130,43 @@ bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* } void ModbusRTU::task() { - if (_port->available() > _len) { + #ifdef ESP32 + portENTER_CRITICAL(&mux); + #endif + if (_port->available() > _len) { _len = _port->available(); t = millis(); + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif return; } - if (_len != 0 && millis() - t < _t) // Wait data whitespace if there is data - return; - if (isMaster) cleanup(); - if (_len == 0) + if (_len != 0 && millis() - t < _t) { // Wait data whitespace if there is data + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif return; + } + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif + if (_len == 0) { + if (isMaster) cleanup(); + return; + } uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte if (isMaster && _slaveId == 0) { // Check if slaveId is set for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected _len = 0; + if (isMaster) cleanup(); return; } if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach _len = 0; + if (isMaster) cleanup(); return; } @@ -159,6 +175,7 @@ void ModbusRTU::task() { 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++) { @@ -176,7 +193,8 @@ void ModbusRTU::task() { if (frameCrc != crc16(address, _frame, _len)) { // CRC Check free(_frame); _frame = nullptr; - _len = 0; // Cleanup if wrong crc + _len = 0; + if (isMaster) cleanup(); return; } if (isMaster) { @@ -204,6 +222,7 @@ void ModbusRTU::task() { free(_frame); _frame = nullptr; _len = 0; + if (isMaster) cleanup(); } From c2c3eee21f6c0fb796eb7f979a7cf80a62f177dd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 3 Jul 2020 08:56:29 +0300 Subject: [PATCH 185/288] ModbusIP task(): reduce timeout possibility due to rare task() calls --- src/ModbusIP_ESP8266.cpp | 15 +++++++++------ src/ModbusIP_ESP8266.h | 4 +++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index da9021e..c3002a4 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -24,7 +24,7 @@ void ModbusIP::server(uint16_t port) { } bool ModbusIP::connect(IPAddress ip, uint16_t port) { - //cleanup(); + //cleanupConnections(); if(getSlave(ip) != -1) return true; int8_t p = getFreeClient(); @@ -48,7 +48,7 @@ TTransaction* ModbusIP::searchTransaction(uint16_t id) { void ModbusIP::task() { MBAP_t _MBAP; - cleanup(); + cleanupConnections(); if (tcpserver) { while (tcpserver->hasClient()) { WiFiClient* currentClient = new WiFiClient(tcpserver->available()); @@ -151,6 +151,7 @@ void ModbusIP::task() { } } n = -1; + cleanupTransactions(); } uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { @@ -198,8 +199,7 @@ void ModbusIP::onDisconnect(cbModbusConnect cb) { cbDisconnect = cb; } -void ModbusIP::cleanup() { - // Free clients if not connected +void ModbusIP::cleanupConnections() { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (tcpclient[i] && !tcpclient[i]->connected()) { //IPAddress ip = tcpclient[i]->remoteIP(); @@ -210,7 +210,9 @@ void ModbusIP::cleanup() { cbDisconnect(IPADDR_NONE); } } - // Remove timedout transactions and forced event +} + +void ModbusIP::cleanupTransactions() { 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; @@ -416,7 +418,8 @@ void ModbusIP::dropTransactions() { ModbusIP::~ModbusIP() { free(_frame); dropTransactions(); - cleanup(); + cleanupConnections(); + cleanupTransactions(); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { delete tcpclient[i]; tcpclient[i] = nullptr; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 15b27e3..c2e8d49 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -62,7 +62,9 @@ class ModbusIP : public Modbus { uint16_t serverPort = 0; TTransaction* searchTransaction(uint16_t id); - void cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events + 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); From b760f74f7250f20ae5f5c631c650c02ca56fb10f Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 3 Jul 2020 12:25:07 +0300 Subject: [PATCH 186/288] Put back global registers removed by mistake --- src/Modbus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modbus.h b/src/Modbus.h index c9143d0..1fe9d80 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -17,7 +17,7 @@ #endif -//#define MB_GLOBAL_REGS +#define MB_GLOBAL_REGS //#define MB_MAX_REGS 32 #define MODBUS_MAX_FRAME 256 #define COIL(n) (TAddress){TAddress::COIL, n} From b52b1ec8503b4b54e3d68c3a4572e3ee7577a234 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 13 Jul 2020 08:25:58 +0300 Subject: [PATCH 187/288] 3.0.1 --- API.md | 30 +++++++++++++++--------------- README.md | 10 +++++----- library.properties | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/API.md b/API.md index c8a6072..78b723c 100644 --- a/API.md +++ b/API.md @@ -1,4 +1,4 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 +# Modbus RTU and Modbus TCP Library for ESP8266/ESP32 ## Common API @@ -73,7 +73,7 @@ uint8_t slave(); Slave mode: Returns configured slave id. Master mode: Returns slave id for active request or 0 if no request in-progress. -### ModBus IP Slave specific API +### Modbus TCP Server specific API ```c void begin(); // Depricated. Use server() instead. @@ -81,7 +81,7 @@ void slave(uint16_t port = MODBUSIP_PORT); // For compatibility with ModbusRTU c void server(uint16_t port = MODBUSIP_PORT); ``` -### ModBus IP Master specific +### Modbus TCP Client specific ```c void master(); // For compatibility with ModbusRTU calls. Typically may be replaced with client() call. @@ -99,7 +99,7 @@ void autoConnect(bool enabled); Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. Disabled by default. -### Query [multiple] regs from remote slave +### Query [multiple] regs from remote slave/server ```c uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); @@ -119,7 +119,7 @@ uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uin Result is saved to local registers. Method returns corresponding transaction id. [ip/from] or [ip/offset] - slave, [to] or [startreg] - local -### Send [multiple] regs to remote slave +### Send [multiple] regs to remote slave/server ```c uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); @@ -135,7 +135,7 @@ uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t nu Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local -### Write [multiple] values to remote slave reg +### Write [multiple] values to remote slave/servr reg[s] ```c uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); @@ -145,7 +145,7 @@ uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransacti uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); ``` -Write single value to remote Hreg/Coil. +Writes single value to remote Hreg/Coil. ```c uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); @@ -155,9 +155,9 @@ uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numre uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` -Write multiple values from array to remote Coil/Hreg. +Writes multiple values from array to remote Coil/Hreg. -### Read values from multiple remote slave regs +### Read values from multiple remote slave/server regs ```c uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); @@ -171,7 +171,7 @@ uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t nu uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` -Read values from remote Hreg/Coil/Ireg/Ists to array. +Reads values from remote Hreg/Coil/Ireg/Ists to array. ## Callbacks API @@ -187,13 +187,13 @@ void onConnect(cbModbusConnect cb); void onDisonnect(cbModbusConnect cb); ``` -*Modbus IP Slave* Assign callback function on new incoming connection event. +*Modbus TCP Sserver* Assign callback function on new incoming connection event. ```c typedef bool (*cbModbusConnect)(IPAddress ip); ``` -*Modbus IP Slave* Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. +*Modbus TCP Sserver* Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. ```c typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); @@ -205,7 +205,7 @@ Get/Set register callback function definition. Pointer to TRegister structure (s typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); ``` -Transaction end callback function definition. For ModbusIP *data* is currently reserved. For ModbusRTU *transactionId* is also reserved. +Transaction end callback function definition. For ModbusIP *data* is currently reserved. For Modbus RTU *transactionId* is also reserved. ```c uint32_t eventSource(); @@ -213,7 +213,7 @@ uint32_t eventSource(); Should be called from onGet/onSet or transaction callback function. -*Modbus IP Master/Slave* Returns IP address of remote requesting operation or INADDR_NONE for local. Use IPAddress(eventSource) to operate result as IPAddress type. +*Modbus TCP Client/Server* Returns IP address of remote requesting operation or INADDR_NONE for local. Use IPAddress(eventSource) to operate result as IPAddress type. *Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. @@ -274,7 +274,7 @@ uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg st uint16_t cbCoilGet(TRegister* reg, uint16_t val) { Serial.print("Get query from "); Serial.println(mb.eventSource().toString()); - return COIL_VAL(coil); // Override value to be returned to ModBus master as reply for current request + return COIL_VAL(coil); // Override value to be returned to Modbus client as reply for current request } bool cbConn(IPAddress ip) { Serial.println(ip); diff --git a/README.md b/README.md index fdf3e57..cc50ac7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ModbusRTU and ModbusIP Library for ESP8266/ESP32 +# Modbus RTU and Modbus TCP Library for ESP8266/ESP32 |If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| @@ -33,8 +33,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * Operates in any combination of multiple instances of * Modbus RTU slave * Modbus RTU master - * Modbus IP server - * Modbus IP client + * Modbus TCP server + * Modbus TCP client * Reply exception messages for all supported functions * Modbus functions supported: * 0x01 - Read Coils @@ -46,8 +46,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * 0x0F - Write Multiple Coils * 0x10 - Write Multiple Registers * Callbacks for - * Client connect (ModbusIP) - * Server/Client disconnect (ModbusIP) + * Client connect (Modbus TCP) + * Server/Client disconnect (Modbus TCP) * Read specific Register * Write specific Register * Slave transaction finish diff --git a/library.properties b/library.properties index 9d66fdc..83b12f6 100644 --- a/library.properties +++ b/library.properties @@ -1,8 +1,8 @@ name=modbus-esp8266 version=3.0.1 author=Andre Sarmento Barbosa, Alexander Emelianov -maintainer=Alexander Emelianov -sentence=ModbusRTU and ModbusIP Library for ESP8266/ESP32 +maintainer=Alexander Emelianov +sentence=Modbus RTU and Modbus TCP Library for ESP8266/ESP32 paragraph=This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 From 34e4e7346bb893b8aa4637ad5eacab139d3e9caa Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 15 Jul 2020 14:30:37 +0300 Subject: [PATCH 188/288] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cc50ac7..252fd33 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Modbus RTU and Modbus TCP Library for ESP8266/ESP32 +# ModbusRTU and ModbusTCP Library for ESP8266/ESP32 |If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| @@ -31,10 +31,10 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * ESP32 * STM32F103 and probably others (Modbus RTU only) * Operates in any combination of multiple instances of - * Modbus RTU slave - * Modbus RTU master - * Modbus TCP server - * Modbus TCP client + * ModbusRTU slave + * ModbusRTU master + * ModbusTCP server + * ModbusTCP client * Reply exception messages for all supported functions * Modbus functions supported: * 0x01 - Read Coils @@ -46,8 +46,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * 0x0F - Write Multiple Coils * 0x10 - Write Multiple Registers * Callbacks for - * Client connect (Modbus TCP) - * Server/Client disconnect (Modbus TCP) + * Client connect (ModbusTCP) + * Server/Client disconnect (ModbusTCP) * Read specific Register * Write specific Register * Slave transaction finish @@ -56,7 +56,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) 1. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 2. For API refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) -3. Modbus RTU maximum incoming frame size is determinated by HardwareSerial buffer size. For SoftwareSerial buffer must be set to 256 bytes. +3. ModbusRTU maximum incoming frame size is determinated by HardwareSerial buffer size. For SoftwareSerial buffer must be set to 256 bytes. 4. Probably it's possible to use ModbusRTU with other AVR boards using from [ArduinoSTL](https://github.com/mike-matera/ArduinoSTL). 5. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. From d524a537261a81836448838c2366f053de9536a3 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 16 Aug 2020 16:39:55 +0300 Subject: [PATCH 189/288] ModbusTCP Client: ESP32 fix unexpected transaction timeout --- README.md | 2 ++ library.properties | 2 +- src/ModbusIP_ESP8266.cpp | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9b99d65..13dd14d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 3.0.2 ++ ModbusTCP Client: ESP32 fix unexpected transaction timeout // 3.0.1 + ModbusRTU: ESP32 possible send failure fix + ModbusRTU: Non-ESP devices support diff --git a/library.properties b/library.properties index 9d66fdc..6b2a90b 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=3.0.1 +version=3.0.2 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=ModbusRTU and ModbusIP Library for ESP8266/ESP32 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index da9021e..de6a3dd 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -141,7 +141,7 @@ void ModbusIP::task() { memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); tcpclient[n]->write(sbuf, send_len); - tcpclient[n]->flush(); + //tcpclient[n]->flush(); } if (_frame) { free(_frame); @@ -173,7 +173,7 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); if (tcpclient[p]->write(sbuf, send_len) != send_len) return false; - tcpclient[p]->flush(); + //tcpclient[p]->flush(); if (waitResponse) { TTransaction tmp; tmp.transactionId = transactionId; From 9dd737996a1513086632b6762c83071866c85a8a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 17 Aug 2020 13:33:05 +0300 Subject: [PATCH 190/288] 4.0.0.DEVEL - Based on 3.0.2 - Merge API implementation for RTU and TCP - Split TCP implementation from specific TCP Stack libraryes - Templates for TLS and W5x00 support - New flexible API preparations --- README.md | 27 ++- src/Modbus.cpp | 112 +--------- src/Modbus.h | 51 +---- src/ModbusAPI.h | 283 ++++++++++++++++++++++++++ src/ModbusEthernet.h | 12 ++ src/ModbusIP_ESP8266.cpp | 429 --------------------------------------- src/ModbusIP_ESP8266.h | 145 +------------ src/ModbusRTU.cpp | 182 +---------------- src/ModbusRTU.h | 41 +--- src/ModbusSettings.h | 41 ++++ src/ModbusTCP.h | 13 ++ src/ModbusTCPTemplate.h | 379 ++++++++++++++++++++++++++++++++++ src/ModbusTLS.h | 17 ++ 13 files changed, 801 insertions(+), 931 deletions(-) create mode 100644 src/ModbusAPI.h create mode 100644 src/ModbusEthernet.h delete mode 100644 src/ModbusIP_ESP8266.cpp create mode 100644 src/ModbusSettings.h create mode 100644 src/ModbusTCP.h create mode 100644 src/ModbusTCPTemplate.h create mode 100644 src/ModbusTLS.h diff --git a/README.md b/README.md index cc50ac7..c0de5aa 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,15 @@ Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. -The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). +The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus TCP). For more information about Modbus see: * [Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) -* [MODBUS APPLICATION PROTOCOL SPECIFICATION -V1.1b](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) -* [MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE -V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) -* [MODBUS over Serial Line -Specification and Implementation Guide -V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) +* [MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) +* [MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) +* [MODBUS over Serial Line Specification and Implementation Guide V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) +* [MODBUS/TCP Security Protocol Specification](https://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf) ## Features @@ -63,6 +60,14 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 4.0.0 +- Modbus TCP Security Server +- Modbus TCP Security Client +- STL dependency remove +- Ethernet library support ++ API implementation code merge ++ ModbusIP => ModbusTCP +- Examples revising // 3.0.1 + ModbusRTU: ESP32 possible send\receive failure fix + ModbusRTU: Non-ESP devices support @@ -81,12 +86,6 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + Master\Client: Fix crash on Write Multiple Hregs + Master\Client: Fix crash on no callback function on read\write remote + Tests added -// ToDo later -- 0x14 - Read File Records function -- 0x15 - Write File Records function -- 0x16 - Write Mask Register function -- 0x17 - Read/Write Registers function -- ModbusIP: Support for non-ESP boards (using W5x00) ``` ## Contributions diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e53d872..a0c6a59 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,5 +1,6 @@ /* - Modbus.cpp - Modbus Core Library Implementation + Modbus Library for Arduino + Core functions Copyright (C) 2014 Andr� Sarmento Barbosa 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) */ @@ -92,11 +93,11 @@ void Modbus::slavePDU(uint8_t* frame) { switch (fcode) { case FC_WRITE_REG: //field1 = reg, field2 = value - if (!Hreg(field1, field2)) { //Check Address and execute (reg exists?) + if (!Reg(HREG(field1), field2)) { //Check Address and execute (reg exists?) exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); break; } - if (Hreg(field1) != field2) { //Check for failure + if (Reg(HREG(field1)) != field2) { //Check for failure exceptionResponse(fcode, EX_SLAVE_FAILURE); break; } @@ -148,11 +149,13 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, EX_ILLEGAL_VALUE); break; } - if (!Coil(field1, COIL_BOOL(field2))) { //Check Address and execute (reg exists?) + //if (!Coil(field1, COIL_BOOL(field2))) { //Check Address and execute (reg exists?) + if (!Reg(COIL(field1), field2)) { //Check Address and execute (reg exists?) exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); break; } - if (Coil(field1) != COIL_BOOL(field2)) { //Check for failure + //if (Coil(field1) != COIL_BOOL(field2)) { //Check for failure + if (Reg(COIL(field1)) != field2) { //Check for failure exceptionResponse(fcode, EX_SLAVE_FAILURE); break; } @@ -509,105 +512,6 @@ void Modbus::cbEnable(bool state) { void Modbus::cbDisable() { cbEnable(false); } - -bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { - return addReg(HREG(offset), value, numregs); -} -bool Modbus::Hreg(uint16_t offset, uint16_t value) { - return Reg(HREG(offset), value); -} -uint16_t Modbus::Hreg(uint16_t offset) { - return Reg(HREG(offset)); -} -uint16_t Modbus::removeHreg(uint16_t offset, uint16_t numregs) { - return removeReg(HREG(offset), numregs); -} -bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { - return addReg(COIL(offset), COIL_VAL(value), numregs); -} -bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { - return addReg(ISTS(offset), ISTS_VAL(value), numregs); -} -bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { - return addReg(IREG(offset), value, numregs); -} -bool Modbus::Coil(uint16_t offset, bool value) { - return Reg(COIL(offset), COIL_VAL(value)); -} -bool Modbus::Ists(uint16_t offset, bool value) { - return Reg(ISTS(offset), ISTS_VAL(value)); -} -bool Modbus::Ireg(uint16_t offset, uint16_t value) { - return Reg(IREG(offset), value); -} -bool Modbus::Coil(uint16_t offset) { - return COIL_BOOL(Reg(COIL(offset))); -} -bool Modbus::Ists(uint16_t offset) { - return ISTS_BOOL(Reg(ISTS(offset))); -} -uint16_t Modbus::Ireg(uint16_t offset) { - return Reg(IREG(offset)); -} -bool Modbus::removeCoil(uint16_t offset, uint16_t numregs) { - return removeReg(COIL(offset), numregs); -} -bool Modbus::removeIsts(uint16_t offset, uint16_t numregs) { - return removeReg(ISTS(offset), numregs); -} -bool Modbus::removeIreg(uint16_t offset, uint16_t numregs) { - return removeReg(IREG(offset), numregs); -} -bool Modbus::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onGet(COIL(offset), cb, numregs); -} -bool Modbus::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onSet(COIL(offset), cb, numregs); -} -bool Modbus::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onGet(HREG(offset), cb, numregs); -} -bool Modbus::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onSet(HREG(offset), cb, numregs); -} -bool Modbus::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onGet(ISTS(offset), cb, numregs); -} -bool Modbus::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onSet(ISTS(offset), cb, numregs); -} -bool Modbus::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onGet(IREG(offset), cb, numregs); -} -bool Modbus::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return onSet(IREG(offset), cb, numregs); -} - -bool Modbus::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnGet(COIL(offset), cb, numregs); -} -bool Modbus::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnSet(COIL(offset), cb, numregs); -} -bool Modbus::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnGet(HREG(offset), cb, numregs); -} -bool Modbus::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnSet(HREG(offset), cb, numregs); -} -bool Modbus::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnGet(ISTS(offset), cb, numregs); -} -bool Modbus::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnSet(ISTS(offset), cb, numregs); -} -bool Modbus::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnGet(IREG(offset), cb, numregs); -} -bool Modbus::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnSet(IREG(offset), cb, numregs); -} - Modbus::~Modbus() { free(_frame); } \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index 1fe9d80..cd29e0e 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,10 +1,11 @@ /* - Modbus.h - Header for Modbus Core Library + Modbus Library for Arduino + Core functions Copyright (C) 2014 Andr� Sarmento Barbosa 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once - +#include "ModbusSettings.h" #include "Arduino.h" #include #include @@ -16,10 +17,6 @@ #define __bswap_16(num) (((uint16_t)num>>8) | ((uint16_t)num<<8)) #endif - -#define MB_GLOBAL_REGS -//#define MB_MAX_REGS 32 -#define MODBUS_MAX_FRAME 256 #define COIL(n) (TAddress){TAddress::COIL, n} #define ISTS(n) (TAddress){TAddress::ISTS, n} #define IREG(n) (TAddress){TAddress::IREG, n} @@ -134,48 +131,9 @@ class Modbus { EX_CANCEL = 0xE6 // Custom. Transaction/request canceled }; ~Modbus(); - bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); - bool Hreg(uint16_t offset, uint16_t value); - uint16_t Hreg(uint16_t offset); - uint16_t removeHreg(uint16_t offset, 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 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); - 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 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); - */ + void cbEnable(bool state = true); void cbDisable(); - - 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); private: void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); @@ -227,6 +185,7 @@ class Modbus { // fn - Modbus function // data - if null use local registers. Otherwise use data from array to erite to slave + 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); diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h new file mode 100644 index 0000000..0cfad16 --- /dev/null +++ b/src/ModbusAPI.h @@ -0,0 +1,283 @@ +/* + Modbus Library for Arduino + Modbus public API implementation + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once +#include "Modbus.h" + +template +class ModbusAPI : public T { + public: +/* // New API + uint16_t write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t write(TYPEID id, TAddress reg, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t write(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + uint16_t push(TYPEID id, TAddress to, TAddress from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +*/ + // Legacy API +/* + 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 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); + uint16_t 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); + + uint16_t writeCoil(TYPEID id, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readIsts(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t readIreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + uint16_t pushCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullCoil(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullIsts(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullHreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullIreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + uint16_t pullHregToIreg(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pullCoilToIsts(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushIstsToCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t pushIregToHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, 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 \ +uint16_t ModbusAPI::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, nullptr, cb); \ +} +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 \ +uint16_t ModbusAPI::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, nullptr, cb); \ +} +IMPLEMENT_WRITEREGS(writeCoil, COIL, FC_WRITE_COILS, writeSlaveBits, 0x07D0, bool) +IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, 0x007D, uint16_t) + +#define IMPLEMENT_READREGS(FNAME, REG, FUNC, MAXNUM, VALTYPE) \ +template \ +uint16_t ModbusAPI::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, value); \ +} +IMPLEMENT_READREGS(readCoil, COIL, FC_READ_COILS, 0x07D0, bool) +IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, 0x007D, uint16_t) +IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0, bool) +IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, 0x007D, uint16_t) + +#define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \ +template \ +uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ + if (numregs < 0x0001 || numregs > MAXNUM) return false; \ + this->addCoil(to, numregs); \ + this->readSlave(from, numregs, Modbus::FUNC); \ + return this->send(ip, REG(to), cb, unit); \ +} +IMPLEMENT_PULL(pullCoil, COIL, FC_READ_COILS, 0x07D0) +IMPLEMENT_PULL(pullIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0) +IMPLEMENT_PULL(pullHreg, HREG, FC_READ_REGS, 0x007D) +IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, 0x007D) +IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, 0x007D) +IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, 0x07D0) + +#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM) \ +template \ +uint16_t ModbusAPI::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->writeSlaveWords(REG(from), to, numregs, Modbus::FUNC); \ + return this->send(ip, REG(from), cb, unit); \ +} +IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, 0x7D0) +IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, 0x007D) +IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, 0x007D) +IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, 0x07D0) + +template \ +bool ModbusAPI::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return this->addReg(HREG(offset), value, numregs); +} +template \ +bool ModbusAPI::Hreg(uint16_t offset, uint16_t value) { + return this->Reg(HREG(offset), value); +} +template \ +uint16_t ModbusAPI::Hreg(uint16_t offset) { + return this->Reg(HREG(offset)); +} +template \ +uint16_t ModbusAPI::removeHreg(uint16_t offset, uint16_t numregs) { + return this->removeReg(HREG(offset), numregs); +} +template \ +bool ModbusAPI::addCoil(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(COIL(offset), COIL_VAL(value), numregs); +} +template \ +bool ModbusAPI::addIsts(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); +} +template \ +bool ModbusAPI::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return this->addReg(IREG(offset), value, numregs); +} +template \ +bool ModbusAPI::Coil(uint16_t offset, bool value) { + return this->Reg(COIL(offset), COIL_VAL(value)); +} +template \ +bool ModbusAPI::Ists(uint16_t offset, bool value) { + return this->Reg(ISTS(offset), ISTS_VAL(value)); +} +template \ +bool ModbusAPI::Ireg(uint16_t offset, uint16_t value) { + return this->Reg(IREG(offset), value); +} +template \ +bool ModbusAPI::Coil(uint16_t offset) { + return COIL_BOOL(this->Reg(COIL(offset))); +} +template \ +bool ModbusAPI::Ists(uint16_t offset) { + return ISTS_BOOL(this->Reg(ISTS(offset))); +} +template \ +uint16_t ModbusAPI::Ireg(uint16_t offset) { + return this->Reg(IREG(offset)); +} +template \ +bool ModbusAPI::removeCoil(uint16_t offset, uint16_t numregs) { + return this->removeReg(COIL(offset), numregs); +} +template \ +bool ModbusAPI::removeIsts(uint16_t offset, uint16_t numregs) { + return this->removeReg(ISTS(offset), numregs); +} +template \ +bool ModbusAPI::removeIreg(uint16_t offset, uint16_t numregs) { + return this->removeReg(IREG(offset), numregs); +} +template \ +bool ModbusAPI::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(IREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(IREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(IREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(IREG(offset), cb, numregs); +} \ No newline at end of file diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h new file mode 100644 index 0000000..b31bb6f --- /dev/null +++ b/src/ModbusEthernet.h @@ -0,0 +1,12 @@ +/* + Modbus Library for Arduino + ModbusTCP for W5x00 Ethernet + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#include +#include "ModbusAPI.h" +#include "ModbusTCPTemplate.h" + +class ModbusEthernet : public ModbusAPI> {}; \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp deleted file mode 100644 index c3002a4..0000000 --- a/src/ModbusIP_ESP8266.cpp +++ /dev/null @@ -1,429 +0,0 @@ -/* - ModbusIP_ESP8266.cpp - ModbusIP Library Implementation - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#if defined(ESP32) || defined(ESP8266) - -#include "ModbusIP_ESP8266.h" - -ModbusIP::ModbusIP() { - //_trans.reserve(MODBUSIP_MAX_TRANSACIONS); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - tcpclient[i] = nullptr; -} - -void ModbusIP::client() { - -} - -void ModbusIP::server(uint16_t port) { - serverPort = port; - tcpserver = new WiFiServer(serverPort); - tcpserver->begin(); -} - -bool ModbusIP::connect(IPAddress ip, uint16_t port) { - //cleanupConnections(); - if(getSlave(ip) != -1) - return true; - int8_t p = getFreeClient(); - if (p == -1) - return false; - tcpclient[p] = new WiFiClient(); - return tcpclient[p]->connect(ip, port); -} - -uint32_t ModbusIP::eventSource() { // Returns IP of current processing client query - if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && tcpclient[n]) - return (uint32_t)tcpclient[n]->remoteIP(); - return (uint32_t)INADDR_NONE; -} - -TTransaction* ModbusIP::searchTransaction(uint16_t id) { - std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); - if (it != _trans.end()) return &*it; - return nullptr; -} - -void ModbusIP::task() { - MBAP_t _MBAP; - cleanupConnections(); - if (tcpserver) { - while (tcpserver->hasClient()) { - WiFiClient* currentClient = new WiFiClient(tcpserver->available()); - if (!currentClient || !currentClient->connected()) - continue; - if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { - #ifdef 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; - continue; // while - } - } - // 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; - uint32_t readStart = millis(); - while (millis() - readStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { - tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP - - if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. - while (tcpclient[n]->available()) // Drop all incoming if wrong packet - tcpclient[n]->read(); - continue; - } - _len = __bswap_16(_MBAP.length); - _len--; // Do not count with last byte from MBAP - if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME - exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); - _len--; // Subtract for read byte - for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of packet - tcpclient[n]->read(); - } else { - free(_frame); - _frame = (uint8_t*) malloc(_len); - if (!_frame) { - exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); - for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop packet - tcpclient[n]->read(); - } else { - if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame - exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); - //while (tcpclient[n]->available()) // Drop all incoming (if any) - // tcpclient[n]->read(); - } else { - if (tcpclient[n]->localPort() == serverPort) { - // Process incoming frame as slave - slavePDU(_frame); - } else { - // Process reply to master request - _reply = EX_SUCCESS; - TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); - if (trans) { // if valid transaction id - if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested - // Procass incoming frame as master - masterPDU(_frame, trans->_frame, trans->startreg, trans->data); - } else { - _reply = EX_UNEXPECTED_RESPONSE; - } - if (trans->cb) { - trans->cb((ResultCode)_reply, trans->transactionId, nullptr); - } - free(trans->_frame); - //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); - std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); - if (it != _trans.end()) - _trans.erase(it); - } - } - } - } - } - if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master - if (_reply != REPLY_OFF) { - _MBAP.length = __bswap_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(); -} - -uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { - MBAP_t _MBAP; -#ifdef MODBUSIP_MAX_TRANSACIONS - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; -#endif - int8_t p = getSlave(ip); - if (p == -1 || !tcpclient[p]->connected()) - return autoConnectMode?connect(ip):false; - transactionId++; - if (!transactionId) transactionId = 1; - _MBAP.transactionId = __bswap_16(transactionId); - _MBAP.protocolId = __bswap_16(0); - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP - _MBAP.unitId = unit; - 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); - if (tcpclient[p]->write(sbuf, send_len) != send_len) - return false; - 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; - _len = 0; - } - return transactionId; -} - - -void ModbusIP::onConnect(cbModbusConnect cb) { - cbConnect = cb; -} - -void ModbusIP::onDisconnect(cbModbusConnect cb) { - cbDisconnect = cb; -} - -void ModbusIP::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); - } - } -} - -void ModbusIP::cleanupTransactions() { - 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++; - } -} - -int8_t ModbusIP::getFreeClient() { - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (!tcpclient[i]) - return i; - return -1; -} - -int8_t ModbusIP::getSlave(IPAddress ip) { - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() != serverPort) - return i; - return -1; -} - -int8_t ModbusIP::getMaster(IPAddress ip) { - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() == serverPort) - return i; - return -1; -} - -uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb, uint8_t unit) { - readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(ip, COIL(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(ip, COIL(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - readSlave(offset, numregs, FC_READ_COILS); - return send(ip, COIL(offset), cb, unit, value); -} - -uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb, uint8_t unit) { - readSlave(offset, value, FC_WRITE_REG); - return send(ip, HREG(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(ip, HREG(offset), cb, unit, nullptr, cb); -} - -uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - readSlave(offset, numregs, FC_READ_REGS); - return send(ip, HREG(offset), cb, unit, value); -} - -uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(offset), cb, unit, value); -} - -uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(offset), cb, unit, value); -} - -uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - if (!searchRegister(COIL(from))) return false; - if (numregs == 1) { - readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); - } - return send(ip, COIL(from), cb, unit); -} - -uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - #ifdef MODBUSIP_ADD_REG - addCoil(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(ip, COIL(to), cb, unit); -} - -uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - #ifdef MODBUSIP_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_STAT); - return send(ip, ISTS(to), cb, unit); -} - -uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - if (!searchRegister(HREG(from))) return false; - if (numregs == 1) { - readSlave(to, Hreg(from), FC_WRITE_REG); - } else { - writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); - } - return send(ip, HREG(from), cb, unit); -} - -uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - #ifdef MODBUSIP_ADD_REG - addHreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(ip, HREG(to), cb, unit); -} - -uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - #ifdef MODBUSIP_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_REGS); - return send(ip, IREG(to), cb, unit); -} - -uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - if (!searchRegister(IREG(from))) return false; - if (numregs == 1) { - readSlave(to, Ireg(from), FC_WRITE_REG); - } else { - writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); - } - return send(ip, IREG(from), cb, unit); -} - -uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - if (!searchRegister(ISTS(from))) return false; - if (numregs == 1) { - readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); - } - return send(ip, ISTS(from), cb, unit); -} - -uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - #ifdef MODBUSIP_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(ip, IREG(to), cb, unit); -} - -uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - #ifdef MODBUSIP_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(ip, ISTS(to), cb, unit); -} - -bool ModbusIP::isTransaction(uint16_t id) { - return searchTransaction(id) != nullptr; -} -bool ModbusIP::isConnected(IPAddress ip) { - int8_t p = getSlave(ip); - return p != -1 && tcpclient[p]->connected(); -} - -void ModbusIP::autoConnect(bool enabled) { - autoConnectMode = enabled; -} - -bool ModbusIP::disconnect(IPAddress ip) { - int8_t p = getSlave(ip); - if (p != -1) { - delete tcpclient[p]; - tcpclient[p] = nullptr; - } - return true; -} - -void ModbusIP::dropTransactions() { - for (auto &t : _trans) t.forcedEvent = EX_CANCEL; -} - -ModbusIP::~ModbusIP() { - free(_frame); - dropTransactions(); - cleanupConnections(); - cleanupTransactions(); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - delete tcpclient[i]; - tcpclient[i] = nullptr; - } -} - -#endif \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index c2e8d49..2c83146 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,134 +1,11 @@ -/* - ModbusIP_ESP8266.h - Header for ModbusIP Library - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ -#pragma once - -#if defined(ESP32) || defined(ESP8266) - -#include -#ifdef ESP8266 - #include -#else - #include -#endif - -#define MODBUSIP_PORT 502 -#define MODBUSIP_MAXFRAME 200 -#define MODBUSIP_TIMEOUT 1000 -#define MODBUSIP_UNIT 255 -#define MODBUSIP_MAX_TRANSACIONS 16 -#define MODBUSIP_MAX_CLIENTS 4 -#define MODBUSIP_ADD_REG 1 -#define MODBUSIP_UNIQUE_CLIENTS -#define MODBUSIP_MAX_READMS 100 - -// Callback function Type -typedef bool (*cbModbusConnect)(IPAddress ip); - -typedef struct TTransaction { - uint16_t transactionId; - uint32_t timestamp; - cbTransaction cb = nullptr; - uint8_t* _frame = nullptr; - void* 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; - } -}; - -class ModbusIP : public Modbus { - protected: - typedef 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; - WiFiServer* tcpserver = nullptr; - WiFiClient* tcpclient[MODBUSIP_MAX_CLIENTS]; - std::vector _trans; - int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. - int8_t n = -1; - bool autoConnectMode = false; - uint16_t serverPort = 0; - - 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); - uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* 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: - ModbusIP(); - ~ModbusIP(); - bool isTransaction(uint16_t id); - bool isConnected(IPAddress ip); - bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); - bool disconnect(IPAddress ip); - void server(uint16_t port = MODBUSIP_PORT); - inline void slave(uint16_t port = MODBUSIP_PORT) { server(port); } // Depricated - void client(); - inline void master() { client(); } // Depricated - void task(); - inline void begin() { server(); }; // Depricated - void onConnect(cbModbusConnect cb = nullptr); - void onDisconnect(cbModbusConnect cb = nullptr); - uint32_t eventSource() override; - void autoConnect(bool enabled = true); - void dropTransactions(); - - uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - - uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - - uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - /* - uint16_t maskHreg(IPAddress ip, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t pushPullIreg - uint16_t pushPullHreg - uint16_t pushIregPullToHreg - uint16_t pushHregPullToIreg - uint16_t pushPullHreg(IPAddress ip, - uint16_t from, uint16_t to, uint16_t numregs = 1, - uint16_t to, uint16_t from, uint16_t numregs = 1, - cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - uint16_t readWriteHreg(IPAddress ip, - uint16_t readOffset, uint16_t* value, uint16_t numregs = 1, - uint16_t writeOffset, uint16_t* value, uint16_t numregs = 1, - cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - */ -}; - -#endif \ No newline at end of file +/* + Modbus Library for Arduino + ModbusIP class compatibility wrapper + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#include "ModbusTCP.h" + +class ModbusIP : public ModbusTCP {}; \ No newline at end of file diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index fbba6f3..efdcb45 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -1,5 +1,6 @@ /* - ModbusRTU Library for ESP8266/ESP32 + Modbus Library for Arduino + ModbusRTU implementation Copyright (C) 2019-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. @@ -29,7 +30,7 @@ static const uint16_t _auchCRC[] PROGMEM = { 0x4040, 0x0000 }; -uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { +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 @@ -43,7 +44,7 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { return (CRCHi << 8) | CRCLo; } -void ModbusRTU::setBaudrate(uint32_t baud) { +void ModbusRTUTemplate::setBaudrate(uint32_t baud) { if (baud > 19200) { _t = 2; } else { @@ -51,13 +52,13 @@ void ModbusRTU::setBaudrate(uint32_t baud) { } } -bool ModbusRTU::begin(Stream* port) { +bool ModbusRTUTemplate::begin(Stream* port) { _port = port; _t = 2; return true; } -bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { +bool ModbusRTUTemplate::begin(HardwareSerial* port, int16_t txPin) { uint32_t baud = 0; #if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266 @@ -80,7 +81,7 @@ bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { } #if defined(ESP8266) -bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { +bool ModbusRTUTemplate::begin(SoftwareSerial* port, int16_t txPin) { uint32_t baud = port->baudRate(); _port = port; if (txPin >= 0) @@ -90,7 +91,7 @@ bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { } #endif -bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { +bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { uint16_t newCrc = crc16(slaveId, frame, len); if (_txPin >= 0) { digitalWrite(_txPin, HIGH); @@ -113,7 +114,7 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { return true; } -bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { +uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { if (_slaveId) return false; // Break if waiting for previous request result rawSend(slaveId, _frame, _len); if (waitResponse) { @@ -129,7 +130,7 @@ bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* return true; } -void ModbusRTU::task() { +void ModbusRTUTemplate::task() { #ifdef ESP32 portENTER_CRITICAL(&mux); #endif @@ -225,8 +226,7 @@ void ModbusRTU::task() { if (isMaster) cleanup(); } - -bool ModbusRTU::cleanup() { +bool ModbusRTUTemplate::cleanup() { // Remove timeouted request and forced event if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { if (_cb) @@ -238,164 +238,4 @@ bool ModbusRTU::cleanup() { return true; } return false; -} - - -uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { - readSlave(offset, value, FC_WRITE_REG); - return send(slaveId, HREG(offset), cb, nullptr, cb); -} - -uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { - readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(slaveId, COIL(offset), cb, nullptr, cb); -} - -uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - readSlave(offset, numregs, FC_READ_COILS); - return send(slaveId, COIL(offset), cb, value); -} - - -uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(slaveId, COIL(offset), cb, nullptr, cb); -} - - -uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(slaveId, HREG(offset), cb, nullptr, cb); -} - - -uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - readSlave(offset, numregs, FC_READ_REGS); - return send(slaveId, HREG(offset), cb, value); -} - - -uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - readSlave(offset, numregs, FC_READ_INPUT_STAT); - return send(slaveId, ISTS(offset), cb, value); -} - - -uint16_t ModbusRTU::readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - readSlave(offset, numregs, FC_READ_INPUT_REGS); - return send(slaveId, IREG(offset), cb, value); -} - - -uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x07D0) return false; - if (!searchRegister(COIL(from))) return false; - if (numregs == 1) { - readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); - } - return send(slaveId, COIL(from), cb); -} - - -uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - #ifdef MODBUSRTU_ADD_REG - addCoil(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(slaveId, COIL(to), cb); -} - - -uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - #ifdef MODBUSRTU_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_STAT); - return send(slaveId, ISTS(to), cb); -} - - -uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - if (!searchRegister(HREG(from))) return false; - if (numregs == 1) { - readSlave(to, Hreg(from), FC_WRITE_REG); - } else { - writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); - } - return send(slaveId, HREG(from), cb); -} - - -uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - #ifdef MODBUSRTU_ADD_REG - addHreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(slaveId, HREG(to), cb); -} - - -uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - #ifdef MODBUSRTU_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_INPUT_REGS); - return send(slaveId, IREG(to), cb); -} - - -uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > 0x007D) return false; - if (!searchRegister(IREG(from))) return false; - if (numregs == 1) { - readSlave(to, Ireg(from), FC_WRITE_REG); - } else { - writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); - } - return send(slaveId, IREG(from), cb); -} - - -uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - if (!searchRegister(ISTS(from))) return false; - if (numregs == 1) { - readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); - } else { - writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); - } - return send(slaveId, ISTS(from), cb); -} - - -uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs) return false; - #ifdef MODBUSRTU_ADD_REG - addIreg(to, numregs); - #endif - readSlave(from, numregs, FC_READ_REGS); - return send(slaveId, IREG(to), cb); -} - - -uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { - if (numregs < 0x0001 || numregs > maxRegs << 4) return false; - #ifdef MODBUSRTU_ADD_REG - addIsts(to, numregs); - #endif - readSlave(from, numregs, FC_READ_COILS); - return send(slaveId, ISTS(to), cb); } \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 23845a7..7465a3e 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,26 +1,18 @@ /* - ModbusRTU Library for ESP8266/ESP32 + Modbus Library for Arduino + ModbusRTU Copyright (C) 2019-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. */ #pragma once -#include #include #if defined(ESP8266) #include #endif +#include "ModbusAPI.h" -//#define MODBUSRTU_DEBUG -#define MODBUSRTU_BROADCAST 0 -#define MB_RESERVE 248 -#define MB_SERIAL_BUFFER 128 -#define MB_MAX_TIME 10 -#define MODBUSRTU_TIMEOUT 1000 -#define MODBUSRTU_ADD_REG -//#define MB_STATIC_FRAME 1 - -class ModbusRTU : public Modbus { +class ModbusRTUTemplate : public Modbus { protected: Stream* _port; int16_t _txPin = -1; @@ -37,7 +29,8 @@ class ModbusRTU : public Modbus { #ifdef ESP32 portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; #endif - bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); + + uint16_t send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* 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) @@ -58,24 +51,6 @@ class ModbusRTU : public Modbus { void slave(uint8_t slaveId) {_slaveId = slaveId;}; uint8_t slave() { return _slaveId; } uint32_t eventSource() override {return _slaveId;} - uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); - uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); - uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); - - uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +}; - uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); - uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -}; \ No newline at end of file +class ModbusRTU : public ModbusAPI {}; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h new file mode 100644 index 0000000..7d697d7 --- /dev/null +++ b/src/ModbusSettings.h @@ -0,0 +1,41 @@ + +/* + Modbus Library for Arduino + + Copyright (C) 2019-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. + +Prefixes: +MODBUS_ Global library settings +MODBUSIP_ Settings for TCP and TLS both +MODBUSTCP_ Settings for TCP +MODBUSTLS_ Settings for TLS +MODBUSRTU_ Settings for RTU +*/ +#pragma once + +#define MODBUS_GLOBAL_REGS +//#define MODBUS_MAX_REGS 32 +#define MODBUS_ADD_REG +#define MODBUS_MAX_FRAME 256 +//#define MODBUS_STATIC_FRAME +#define MODBUS_MAX_WORDS 0x007D +#define MODBUS_MAX_BITS 0x07D0 + +#define MODBUSTCP_PORT 502 +#define MODBUSTLS_PORT 802 +#define MODBUSIP_MAXFRAME 200 +#define MODBUSIP_TIMEOUT 1000 +#define MODBUSIP_UNIT 255 +#define MODBUSIP_MAX_TRANSACIONS 16 +#define MODBUSIP_MAX_CLIENTS 4 +#define MODBUSIP_UNIQUE_CLIENTS +#define MODBUSIP_MAX_READMS 100 + +//#define MODBUSRTU_DEBUG +#define MODBUSRTU_BROADCAST 0 +#define MB_RESERVE 248 +#define MB_SERIAL_BUFFER 128 +#define MB_MAX_TIME 10 +#define MODBUSRTU_TIMEOUT 1000 \ No newline at end of file diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h new file mode 100644 index 0000000..b637034 --- /dev/null +++ b/src/ModbusTCP.h @@ -0,0 +1,13 @@ +/* + Modbus Library for Arduino + ModbusTCP for ESP8266/ESP32 + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#include +#include +#include "ModbusAPI.h" +#include "ModbusTCPTemplate.h" + +class ModbusTCP : public ModbusTemplate> {}; diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h new file mode 100644 index 0000000..249f607 --- /dev/null +++ b/src/ModbusTCPTemplate.h @@ -0,0 +1,379 @@ +/* + Modbus Library for Arduino + ModbusTCP general implementation + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#include "Modbus.h" + +// Callback function Type +typedef bool (*cbModbusConnect)(IPAddress ip); + +typedef struct TTransaction { + uint16_t transactionId; + uint32_t timestamp; + cbTransaction cb = nullptr; + uint8_t* _frame = nullptr; + void* 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 ModbusTCPTemplate : public Modbus { + protected: + typedef 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]; + std::vector _trans; + int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. + int8_t n = -1; + bool autoConnectMode = false; + uint16_t serverPort = 0; + + 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); + uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* 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); + bool isConnected(IPAddress ip); + bool connect(IPAddress ip, uint16_t port = PORT); + bool disconnect(IPAddress ip); + // ModbusTCP + void server(uint16_t port = PORT); + // ModbusTCP depricated + inline void slave(uint16_t port = PORT) { 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(); +}; + +template +ModbusTCPTemplate::ModbusTCPTemplate() { + //_trans.reserve(MODBUSIP_MAX_TRANSACIONS); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + tcpclient[i] = nullptr; +} + +template +void ModbusTCPTemplate::client() { + +} + +template +void ModbusTCPTemplate::server(uint16_t port) { + serverPort = port; + tcpserver = new SERVER(serverPort); + tcpserver->begin(); +} + +template +bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { + //cleanupConnections(); + if(getSlave(ip) != -1) + return true; + int8_t p = getFreeClient(); + if (p == -1) + return false; + tcpclient[p] = new CLIENT(); + return tcpclient[p]->connect(ip, port); +} + +template +uint32_t ModbusTCPTemplate::eventSource() { // Returns IP of current processing client query + if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && tcpclient[n]) + return (uint32_t)tcpclient[n]->remoteIP(); + return (uint32_t)INADDR_NONE; +} + +template +TTransaction* ModbusTCPTemplate::searchTransaction(uint16_t id) { + std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); + if (it != _trans.end()) return &*it; + return nullptr; +} + +template +void ModbusTCPTemplate::task() { + MBAP_t _MBAP; + cleanupConnections(); + if (tcpserver) { + while (tcpserver->hasClient()) { + CLIENT* currentClient = new CLIENT(tcpserver->available()); + if (!currentClient || !currentClient->connected()) + continue; + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + #ifdef 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; + continue; // while + } + } + // 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; + uint32_t readStart = millis(); + while (millis() - readStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { + tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP + + if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. + while (tcpclient[n]->available()) // Drop all incoming if wrong packet + tcpclient[n]->read(); + continue; + } + _len = __bswap_16(_MBAP.length); + _len--; // Do not count with last byte from MBAP + if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME + exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); + _len--; // Subtract for read byte + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of packet + tcpclient[n]->read(); + } else { + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop packet + tcpclient[n]->read(); + } else { + if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame + exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); + //while (tcpclient[n]->available()) // Drop all incoming (if any) + // tcpclient[n]->read(); + } else { + if (tcpclient[n]->localPort() == serverPort) { + // Process incoming frame as slave + slavePDU(_frame); + } else { + // Process reply to master request + _reply = EX_SUCCESS; + TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); + if (trans) { // if valid transaction id + if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + masterPDU(_frame, trans->_frame, trans->startreg, trans->data); + } else { + _reply = EX_UNEXPECTED_RESPONSE; + } + if (trans->cb) { + trans->cb((ResultCode)_reply, trans->transactionId, nullptr); + } + free(trans->_frame); + //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); + if (it != _trans.end()) + _trans.erase(it); + } + } + } + } + } + if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master + if (_reply != REPLY_OFF) { + _MBAP.length = __bswap_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 +uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { + MBAP_t _MBAP; +#ifdef MODBUSIP_MAX_TRANSACIONS + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; +#endif + int8_t p = getSlave(ip); + if (p == -1 || !tcpclient[p]->connected()) + return autoConnectMode?connect(ip):false; + transactionId++; + if (!transactionId) transactionId = 1; + _MBAP.transactionId = __bswap_16(transactionId); + _MBAP.protocolId = __bswap_16(0); + _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.unitId = unit; + 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); + if (tcpclient[p]->write(sbuf, send_len) != send_len) + return false; + 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; + _len = 0; + } + return transactionId; +} + +template +void ModbusTCPTemplate::onConnect(cbModbusConnect cb) { + cbConnect = cb; +} + +template +void ModbusTCPTemplate::onDisconnect(cbModbusConnect cb) { + cbDisconnect = cb; +} + +template +void ModbusTCPTemplate::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 +void ModbusTCPTemplate::cleanupTransactions() { + 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++; + } +} + +template +int8_t ModbusTCPTemplate::getFreeClient() { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (!tcpclient[i]) + return i; + return -1; +} + +template +int8_t ModbusTCPTemplate::getSlave(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() != serverPort) + return i; + return -1; +} + +template +int8_t ModbusTCPTemplate::getMaster(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() == serverPort) + return i; + return -1; +} + +template +bool ModbusTCPTemplate::isTransaction(uint16_t id) { + return searchTransaction(id) != nullptr; +} + +template +bool ModbusTCPTemplate::isConnected(IPAddress ip) { + int8_t p = getSlave(ip); + return p != -1 && tcpclient[p]->connected(); +} + +template +void ModbusTCPTemplate::autoConnect(bool enabled) { + autoConnectMode = enabled; +} + +template +bool ModbusTCPTemplate::disconnect(IPAddress ip) { + int8_t p = getSlave(ip); + if (p != -1) { + delete tcpclient[p]; + tcpclient[p] = nullptr; + } + return true; +} + +template +void ModbusTCPTemplate::dropTransactions() { + for (auto &t : _trans) t.forcedEvent = EX_CANCEL; +} + +template +ModbusTCPTemplate::~ModbusTCPTemplate() { + free(_frame); + dropTransactions(); + cleanupConnections(); + cleanupTransactions(); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + delete tcpclient[i]; + tcpclient[i] = nullptr; + } +} \ No newline at end of file diff --git a/src/ModbusTLS.h b/src/ModbusTLS.h new file mode 100644 index 0000000..8476b53 --- /dev/null +++ b/src/ModbusTLS.h @@ -0,0 +1,17 @@ +/* + Modbus Library for Arduino + ModbusTLS - ModbusTCP Security for ESP8266 + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#include +#include + +#include "ModbusTCPTemplate.h" +#include "ModbusAPI.h" +// ModbusTLS +class ModbusTLS : public ModbusAPI> { + void server(uint16_t port, const char* server_cert = nullptr, const char* private_key = nullptr, const char* ca = nullptr) {}; + //bool connect(IPAddress ip, uint16_t port); Certificate ? + //bool setCerteficats() {}; ??? +}; From 609164e390b5051fde0ac623001bfda38cc839f4 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 17 Aug 2020 18:20:38 +0300 Subject: [PATCH 191/288] Add Modbus read/write file block support - 0x14 + test - 0x15 + test - 0x17 (Mask register) --- README.md | 30 +++++-- src/Modbus.cpp | 193 ++++++++++++++++++++++++++++++++++++++++++- src/Modbus.h | 30 +++++++ src/ModbusAPI.h | 41 ++++++++- src/ModbusSettings.h | 2 +- src/ModbusTCP.h | 2 +- tests/files.h | 62 ++++++++++++++ tests/tests.ino | 18 ++-- 8 files changed, 363 insertions(+), 15 deletions(-) create mode 100644 tests/files.h diff --git a/README.md b/README.md index c0de5aa..7e4812f 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ For more information about Modbus see: * 0x06 - Write Single Register * 0x0F - Write Multiple Coils * 0x10 - Write Multiple Registers + * 0x14 - Read File Record + * 0x15 - Write File Record + * 0x16 - Mask Write Register * Callbacks for * Client connect (Modbus TCP) * Server/Client disconnect (Modbus TCP) @@ -54,20 +57,37 @@ For more information about Modbus see: 1. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 2. For API refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) 3. Modbus RTU maximum incoming frame size is determinated by HardwareSerial buffer size. For SoftwareSerial buffer must be set to 256 bytes. -4. Probably it's possible to use ModbusRTU with other AVR boards using from [ArduinoSTL](https://github.com/mike-matera/ArduinoSTL). -5. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. +4. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. ## Last Changes ```diff -// 4.0.0 +// 4.0.0.DEVEL - Modbus TCP Security Server - Modbus TCP Security Client - STL dependency remove - Ethernet library support -+ API implementation code merge ++ API: Implementation code merge + ModbusIP => ModbusTCP -- Examples revising ++ 0x14 - Read File Records function ++ Test: 0x14 ++ 0x15 - Write File Records function ++ Test: 0x15 +- Examples: Basic file operations +- Examples: FW update ++ 0x16 - Write Mask Register function +- Test: 0x16 +- 0x17 - Read/Write Registers function +- Test: 0x17 +- Slave/Server: slavePDU use early exit by return where possible +- Master/Client: Check frame size against header data where possible +- Master/Client: Additional responce data validation +- Test: push/pull functions +- Test: Frame accuracy to specefication +- Documentation: Update +- Examples: Revising +// 3.0.2 ++ ModbusTCP Client: ESP32 fix unexpected transaction timeout // 3.0.1 + ModbusRTU: ESP32 possible send\receive failure fix + ModbusRTU: Non-ESP devices support diff --git a/src/Modbus.cpp b/src/Modbus.cpp index a0c6a59..fc5eeab 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -9,6 +9,7 @@ #ifdef MB_GLOBAL_REGS std::vector _regs; std::vector _callbacks; + cbModbusFileOp _onFile; #endif uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType t) { @@ -182,6 +183,109 @@ void Modbus::slavePDU(uint8_t* frame) { _reply = REPLY_NORMAL; } break; + #if defined(MODBUS_MAX_FRAME) + 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 + // Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) + uint16_t orMask = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; + uint16_t val = Reg(HREG(field1)); + val = (val && field2) || (orMask && !field2); + if (!Reg(HREG(field1), val)) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + if (Reg(HREG(field1)) != val) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + } + _reply = REPLY_ECHO; + return; default: exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); @@ -496,11 +600,38 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, 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; } @@ -514,4 +645,64 @@ void Modbus::cbDisable() { } Modbus::~Modbus() { free(_frame); -} \ No newline at end of file +} + +#if defined(MODBUS_FILES) +bool Modbus::onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)) { + _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 diff --git a/src/Modbus.h b/src/Modbus.h index cd29e0e..05d190f 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -160,6 +160,10 @@ class Modbus { #ifndef MB_GLOBAL_REGS std::vector _regs; std::vector _callbacks; + #if defined(MODBUS_FILES) + Modbus::ResultCode (*_onFile)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*) = nullptr; + #endif + #endif uint8_t* _frame = nullptr; uint16_t _len = 0; @@ -197,6 +201,32 @@ class Modbus { bool removeOnGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); virtual uint32_t eventSource() {return 0;} + + #if defined(MODBUS_FILES) + public: + bool onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)); + 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 + }; typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests +#if defined(MODBUS_FILES) +// Callback skeleton for file read/write +typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame); +#endif \ No newline at end of file diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 0cfad16..8318816 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -86,6 +86,12 @@ class ModbusAPI : public T { uint16_t pullCoilToIsts(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t pushIstsToCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t pushIregToHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + 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); + 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); + + uint16_t maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + //uint16_t readWriteHreg(uint8_t slaveId, uint16_t readOffset, uint16_t* value, uint16_t numregs, uint16_t writeOffset, uint16_t* value, uint16_t numregs, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); }; // FNAME writeCoil, writeIsts, writeHreg, writeIreg @@ -280,4 +286,37 @@ bool ModbusAPI::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_ template \ bool ModbusAPI::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnSet(IREG(offset), cb, numregs); -} \ No newline at end of file +} +template \ +uint16_t ModbusAPI::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { + if (startRec > 0x270F) return 0; + if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0; + return this->send(slaveId, HREG(0), cb, unit, data); // HREG(0) - just dummy value + }; +template \ +uint16_t ModbusAPI::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { + if (startRec > 0x270F) return 0; + if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0; + return this->send(slaveId, HREG(0), cb, unit); // HREG(0) - just dummy value + }; +template \ +uint16_t ModbusAPI::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, nullptr, cb); + }; +/* +template \ +uint16_t ModbusAPI::readWriteHreg(TYPEID slaveId, + uint16_t readOffset, uint16_t* value, uint16_t numregs, + uint16_t writeOffset, uint16_t* value, uint16_t numregs, + cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +*/ \ No newline at end of file diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 7d697d7..8f00a1d 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -22,7 +22,7 @@ MODBUSRTU_ Settings for RTU //#define MODBUS_STATIC_FRAME #define MODBUS_MAX_WORDS 0x007D #define MODBUS_MAX_BITS 0x07D0 - +#define MODBUS_FILES #define MODBUSTCP_PORT 502 #define MODBUSTLS_PORT 802 #define MODBUSIP_MAXFRAME 200 diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h index b637034..6bf0bf9 100644 --- a/src/ModbusTCP.h +++ b/src/ModbusTCP.h @@ -10,4 +10,4 @@ #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" -class ModbusTCP : public ModbusTemplate> {}; +class ModbusTCP : public ModbusAPI> {}; diff --git a/tests/files.h b/tests/files.h new file mode 100644 index 0000000..25ec080 --- /dev/null +++ b/tests/files.h @@ -0,0 +1,62 @@ +#pragma once +#include "common.h" + +#define FILE_LEN 100 +uint8_t block[FILE_LEN*2]; +uint8_t src[FILE_LEN*2]; + +Modbus::ResultCode handleFile(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame) { + switch (func) { + case Modbus::FC_READ_FILE_REC: + memcpy(frame, src, recLength * 2); + return Modbus::EX_SUCCESS; + break; + case Modbus::FC_WRITE_FILE_REC: + memcpy(src, frame, recLength * 2); + return Modbus::EX_SUCCESS; + break; + default: + return Modbus::EX_ILLEGAL_FUNCTION; + } +} + +void initFile() { + master.onFile(handleFile); + slave.onFile(handleFile); +} + +void testFile() { + Serial.print("FILE READ:"); + if (master.readFileRec(1, 0, 0, FILE_LEN, block, cbWrite)) { + Serial.print(" SENT"); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + Serial.printf(" 0x%02X ", code); + if (memcmp(block, src, FILE_LEN * 2) == 0) { + Serial.println("PASSED"); + } else { + Serial.println("FAILED"); + } + } + + memset(block, 0xFF, FILE_LEN * 2); + + Serial.print("FILE WRITE:"); + if (master.writeFileRec(1, 0, 0, FILE_LEN, block, cbWrite)) { + Serial.print(" SENT"); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + Serial.printf(" 0x%02X ", code); + if (memcmp(block, src, FILE_LEN * 2) == 0) { + Serial.println("PASSED"); + } else { + Serial.println("FAILED"); + } + } +} \ No newline at end of file diff --git a/tests/tests.ino b/tests/tests.ino index d69bd3f..c303b2c 100644 --- a/tests/tests.ino +++ b/tests/tests.ino @@ -3,6 +3,7 @@ #include "common.h" #include "write.h" #include "read.h" +#include "files.h" uint8_t stage = 0; @@ -17,9 +18,11 @@ uint16_t readHreg = 0; void setup() { Serial.begin(115200); - master.begin(&D1); + Serial.println("ModbusRTU API test"); + delay(100); + master.begin(&P1); master.master(); - slave.begin(&D2); + slave.begin(&P2); slave.slave(SLAVE_ID); slave.addHreg(HREG_ID); @@ -57,8 +60,8 @@ readMultiple(SLAVE_ID, ISTS(HREG_ID), 10); master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); while (master.slave()) { master.task(); - while(D2.available()) - D2.write(D2.read()); + while(P2.available()) + P2.write(P2.read()); //slave.task(); delay(1); } @@ -75,8 +78,11 @@ readMultiple(SLAVE_ID, ISTS(HREG_ID), 10); Serial.println(" FAILED"); } } + { + initFile(); + testFile(); + } } - void loop() { yield(); -} +} \ No newline at end of file From 68ea8c4a9ce0d8f70acccabdcc78604c949ce04a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 18 Aug 2020 14:25:54 +0300 Subject: [PATCH 192/288] W5x00 Ethernet support (not tested) - Added missing 3.0.2 changes - ModbusTCPTemplate changes to support Ethernet 1.x --- README.md | 5 ++++- src/Modbus.cpp | 2 +- src/ModbusSettings.h | 1 + src/ModbusTCPTemplate.h | 40 ++++++++++++++++++++++++++++++++-------- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7e4812f..d4a43ac 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,12 @@ For more information about Modbus see: ```diff // 4.0.0.DEVEL - Modbus TCP Security Server +- Test: TLS Server - Modbus TCP Security Client +- Test: TLS Client - STL dependency remove -- Ethernet library support ++ W5x00 Ethernet library support +- Test: W5x00 support + API: Implementation code merge + ModbusIP => ModbusTCP + 0x14 - Read File Records function diff --git a/src/Modbus.cpp b/src/Modbus.cpp index fc5eeab..77a2fdc 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -183,7 +183,7 @@ void Modbus::slavePDU(uint8_t* frame) { _reply = REPLY_NORMAL; } break; - #if defined(MODBUS_MAX_FRAME) + #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); diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 8f00a1d..69983a4 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -32,6 +32,7 @@ MODBUSRTU_ Settings for RTU #define MODBUSIP_MAX_CLIENTS 4 #define MODBUSIP_UNIQUE_CLIENTS #define MODBUSIP_MAX_READMS 100 +#define MODBUSIP_FULL //#define MODBUSRTU_DEBUG #define MODBUSRTU_BROADCAST 0 diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 249f607..e091de1 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -8,6 +8,10 @@ #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 + // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); @@ -40,6 +44,13 @@ class ModbusTCPTemplate : public Modbus { 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 std::vector _trans; int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; @@ -110,13 +121,18 @@ bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t por if (p == -1) return false; tcpclient[p] = new CLIENT(); + BIT_CLEAR(tcpServerConnection, p); return tcpclient[p]->connect(ip, port); } template uint32_t ModbusTCPTemplate::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; } @@ -132,10 +148,12 @@ void ModbusTCPTemplate::task() { MBAP_t _MBAP; cleanupConnections(); if (tcpserver) { - while (tcpserver->hasClient()) { - CLIENT* currentClient = new CLIENT(tcpserver->available()); + //while (tcpserver->hasClient()) { + //CLIENT* currentClient = new CLIENT(tcpserver->available()); + while (CLIENT* currentClient = new CLIENT(tcpserver->available())) { if (!currentClient || !currentClient->connected()) continue; + #if !defined(ethernet_h) if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { #ifdef MODBUSIP_UNIQUE_CLIENTS // Disconnect previous connection from same IP if present @@ -146,12 +164,16 @@ void ModbusTCPTemplate::task() { tcpclient[n] = nullptr; } #endif + #endif n = getFreeClient(); if (n > -1) { tcpclient[n] = currentClient; + BIT_SET(tcpServerConnection, n); continue; // while } + #if !defined(ethernet_h) } + #endif // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached delete currentClient; } @@ -188,7 +210,8 @@ void ModbusTCPTemplate::task() { //while (tcpclient[n]->available()) // Drop all incoming (if any) // tcpclient[n]->read(); } else { - if (tcpclient[n]->localPort() == serverPort) { + //if (tcpclient[n]->localPort() == serverPort) { + if (BIT_CHECK(tcpServerConnection, n)) { // Process incoming frame as slave slavePDU(_frame); } else { @@ -215,7 +238,8 @@ void ModbusTCPTemplate::task() { } } } - if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master + //if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master + if (!BIT_CHECK(tcpServerConnection, n)) _reply = REPLY_OFF; // No replay if it was responce to master if (_reply != REPLY_OFF) { _MBAP.length = __bswap_16(_len+1); // _len+1 for last byte from MBAP size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); @@ -223,7 +247,7 @@ void ModbusTCPTemplate::task() { memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); tcpclient[n]->write(sbuf, send_len); - tcpclient[n]->flush(); + //tcpclient[n]->flush(); } if (_frame) { free(_frame); @@ -257,7 +281,7 @@ uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress st memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); if (tcpclient[p]->write(sbuf, send_len) != send_len) return false; - tcpclient[p]->flush(); + //tcpclient[p]->flush(); if (waitResponse) { TTransaction tmp; tmp.transactionId = transactionId; @@ -322,7 +346,7 @@ int8_t ModbusTCPTemplate::getFreeClient() { template int8_t ModbusTCPTemplate::getSlave(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() != serverPort) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && !BIT_CHECK(tcpServerConnection, i)) return i; return -1; } @@ -330,7 +354,7 @@ int8_t ModbusTCPTemplate::getSlave(IPAddress ip) { template int8_t ModbusTCPTemplate::getMaster(IPAddress ip) { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) - if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() == serverPort) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && BIT_CHECK(tcpServerConnection, i)) return i; return -1; } From dbd1f70c3989565f86458e5267a26685d89d7868 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 20 Aug 2020 09:45:05 +0300 Subject: [PATCH 193/288] no-STL build support (not completly tested) - Add basic class to replace std::vector - Changed way to W5x0 Ethernet library support --- README.md | 5 +- .../RTU-Master.ino => RTU/master/master.ino} | 0 .../RTU-slave.ino => RTU/slave/slave.ino} | 0 src/Modbus.cpp | 56 +++++++++++++-- src/Modbus.h | 13 +++- src/ModbusEthernet.h | 16 ++++- src/ModbusRTU.cpp | 2 +- src/ModbusSettings.h | 1 + src/ModbusTCPTemplate.h | 50 ++++++++++--- src/darray.h | 70 +++++++++++++++++++ 10 files changed, 192 insertions(+), 21 deletions(-) rename examples/{RTU-master/RTU-Master.ino => RTU/master/master.ino} (100%) rename examples/{RTU-slave/RTU-slave.ino => RTU/slave/slave.ino} (100%) create mode 100644 src/darray.h diff --git a/README.md b/README.md index d4a43ac..9873039 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Modbus RTU and Modbus TCP Library for ESP8266/ESP32 +# Modbus RTU and Modbus TCP Library for Arduino |If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| @@ -67,7 +67,8 @@ For more information about Modbus see: - Test: TLS Server - Modbus TCP Security Client - Test: TLS Client -- STL dependency remove ++ STL dependency remove +- Test: No-STL mode + W5x00 Ethernet library support - Test: W5x00 support + API: Implementation code merge diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU/master/master.ino similarity index 100% rename from examples/RTU-master/RTU-Master.ino rename to examples/RTU/master/master.ino diff --git a/examples/RTU-slave/RTU-slave.ino b/examples/RTU/slave/slave.ino similarity index 100% rename from examples/RTU-slave/RTU-slave.ino rename to examples/RTU/slave/slave.ino diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 77a2fdc..af1f56a 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -7,32 +7,54 @@ #include "Modbus.h" #ifdef MB_GLOBAL_REGS +#if defined(MODBUS_USE_STL) std::vector _regs; std::vector _callbacks; +#else + DArray _regs; + DArrat _callbacks; +#endif cbModbusFileOp _onFile; #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::iterator it = _callbacks.begin(); do { - it = std::find_if(it, _callbacks.end(), [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}); + 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) { - std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), [address](TRegister& addr){return addr.address == address;}); +#define MODBUS_COMPARE_REG [address](TRegister& addr){return (addr.address == address);} +#if defined(MODBUS_USE_STL) + std::vector::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) { - #ifdef MB_MAX_REGS + #ifdef MODBUS_MAX_REGS if (_regs.size() + numregs > MB_MAX_REGS) return false; #endif for (uint16_t i = 0; i < numregs; i++) { @@ -79,7 +101,11 @@ bool Modbus::removeReg(TAddress address, uint16_t numregs) { 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; @@ -445,17 +471,33 @@ bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { } bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) { +#define MODBUS_COMPARE_ONSET [address, cb](TCallback entry){ return entry.type == TCallback::ON_SET && entry.address == address && (!cb || entry.cb == cb);} while(numregs--) { - _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), [address, cb](TCallback entry){ - return entry.type == TCallback::ON_SET && entry.address == address && (!cb || entry.cb == cb);} ), _callbacks.end() ); + #if defined(MODBUS_USE_STL) + _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ONSET), _callbacks.end()); + #else + size_t r = 0; + do { + r = _callbacks.find(MODBUS_COMPARE_ONSET, r); + _callbacks.remove(r); + } while (r < _callbacks.size()); + #endif address++; } return false; } bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) { +#define MODBUS_COMPARE_ONGET [address, cb](TCallback entry){ return entry.type == TCallback::ON_GET && entry.address == address && (!cb || entry.cb == cb);} while(numregs--) { - _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), [address, cb](TCallback entry){ - return entry.type == TCallback::ON_GET && entry.address == address && (!cb || entry.cb == cb);} ), _callbacks.end() ); + #if defined(MODBUS_USE_STL) + _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ONGET), _callbacks.end()); + #else + size_t r = 0; + do { + r = _callbacks.find(MODBUS_COMPARE_ONGET, r); + _callbacks.remove(r); + } while (r < _callbacks.size()); + #endif address++; } return false; diff --git a/src/Modbus.h b/src/Modbus.h index 05d190f..92ea59e 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -7,8 +7,12 @@ #pragma once #include "ModbusSettings.h" #include "Arduino.h" -#include -#include +#if defined(MODBUS_USE_STL) + #include + #include +#else + #include "darray.h" +#endif #ifdef ARDUINO_ARCH_ESP32 #include #endif @@ -158,8 +162,13 @@ class Modbus { REPLY_UNEXPECTED = 0x05 }; #ifndef MB_GLOBAL_REGS + #if defined(MODBUS_USE_STL) std::vector _regs; std::vector _callbacks; + #else + DArray _regs; + DArray _callbacks; + #endif #if defined(MODBUS_FILES) Modbus::ResultCode (*_onFile)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*) = nullptr; #endif diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h index b31bb6f..be8adba 100644 --- a/src/ModbusEthernet.h +++ b/src/ModbusEthernet.h @@ -9,4 +9,18 @@ #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" -class ModbusEthernet : public ModbusAPI> {}; \ No newline at end of file +#if defined(ethernet_h) +class EthernetClientWrapper : public EthernetClient { + public: + EthernetClientWrapper(EthernetClient c) : EthernetClient(c) {}; + IPAddress remoteIP() { + if (connected()) + return IPAddress(0,0,0,1); + return IPADDR_NONE; + } +}; +#undef MODBUSIP_UNIQUE_CLIENTS +class ModbusEthernet : public ModbusAPI> {}; +#else +class ModbusEthernet : public ModbusAPI> {}; +#endif \ No newline at end of file diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index efdcb45..5775f45 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -189,7 +189,7 @@ void ModbusRTUTemplate::task() { Serial.println(); #endif //_port->readBytes(_frame, _len); - u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc + 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 free(_frame); diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 69983a4..4dc2e72 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -16,6 +16,7 @@ MODBUSRTU_ Settings for RTU #pragma once #define MODBUS_GLOBAL_REGS +//#define MODBUS_USE_STL //#define MODBUS_MAX_REGS 32 #define MODBUS_ADD_REG #define MODBUS_MAX_FRAME 256 diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index e091de1..0b475a6 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -11,7 +11,9 @@ #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 (IPAddress(0,0,0,0)) +#endif // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); @@ -51,7 +53,11 @@ class ModbusTCPTemplate : public Modbus { #else uint32_t tcpServerConnection = 0; #endif + #if defined(MODBUS_USE_STL) std::vector _trans; + #else + DArray _trans; + #endif int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; bool autoConnectMode = false; @@ -138,9 +144,14 @@ uint32_t ModbusTCPTemplate::eventSource() { // Returns IP template TTransaction* ModbusTCPTemplate::searchTransaction(uint16_t id) { - std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); +#define MODBUSIP_COMPARE_TRANS [id](TTransaction& trans){return trans.transactionId == id;} + #if defined(MODBUS_USE_STL) + std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), MODBUSIP_COMPARE_TRANS); if (it != _trans.end()) return &*it; - return nullptr; + return nullptr; + #else + return _trans.entry(_trans.find(MODBUSIP_COMPARE_TRANS)); + #endif } template @@ -150,10 +161,11 @@ void ModbusTCPTemplate::task() { if (tcpserver) { //while (tcpserver->hasClient()) { //CLIENT* currentClient = new CLIENT(tcpserver->available()); - while (CLIENT* currentClient = new CLIENT(tcpserver->available())) { + //while (CLIENT* currentClient = new CLIENT(tcpserver->available())) { + while (CLIENT c = tcpserver->available()) { + CLIENT* currentClient = new CLIENT(c); if (!currentClient || !currentClient->connected()) continue; - #if !defined(ethernet_h) if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { #ifdef MODBUSIP_UNIQUE_CLIENTS // Disconnect previous connection from same IP if present @@ -164,16 +176,13 @@ void ModbusTCPTemplate::task() { tcpclient[n] = nullptr; } #endif - #endif n = getFreeClient(); if (n > -1) { tcpclient[n] = currentClient; BIT_SET(tcpServerConnection, n); continue; // while } - #if !defined(ethernet_h) } - #endif // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached delete currentClient; } @@ -229,10 +238,15 @@ void ModbusTCPTemplate::task() { 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::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 } } } @@ -323,6 +337,7 @@ void ModbusTCPTemplate::cleanupConnections() { template void ModbusTCPTemplate::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; @@ -333,6 +348,20 @@ void ModbusTCPTemplate::cleanupTransactions() { } 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 @@ -387,7 +416,12 @@ bool ModbusTCPTemplate::disconnect(IPAddress ip) { template void ModbusTCPTemplate::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 diff --git a/src/darray.h b/src/darray.h new file mode 100644 index 0000000..cc71dad --- /dev/null +++ b/src/darray.h @@ -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 +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 + 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 = 0; + return; + } + memcpy(&data[i], &data[i + 1], (last - i - 1) * sizeof(T)); + if (last) last --; + else isEmpty = true; + } + T operator[](int i) { + return data[i]; + } + T* entry(size_t i) { + if (i > last) return nullptr; + return &data[i]; + } +}; \ No newline at end of file From e7c1299d4311b75e9862f94105cfc94044fb4711 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 28 Aug 2020 08:59:50 +0300 Subject: [PATCH 194/288] Modbus/TCP Security implemantation and API funcs - Client/Server for ESP8266 - Client for ESP32 (not tested) - TLS exmample (including demo cerificates) - API extended to allow connect to TCP/TLS server by name (for ESP and Ethernet both) - non-STL mode appeas to be broken --- README.md | 19 ++- examples/TLS/README.md | 3 + examples/TLS/certs/ca.conf | 11 ++ examples/TLS/certs/ca_cer.pem | 17 +++ examples/TLS/certs/ca_cer.srl | 1 + examples/TLS/certs/ca_key.pem | 27 ++++ examples/TLS/certs/cert.cmd | 17 +++ examples/TLS/certs/client.cmd | 1 + examples/TLS/certs/client.conf | 7 + examples/TLS/certs/client1_cer.pem | 17 +++ examples/TLS/certs/client1_key.pem | 27 ++++ examples/TLS/certs/client1_req.csr | 15 ++ examples/TLS/certs/server.cmd | 1 + examples/TLS/certs/server.conf | 7 + examples/TLS/certs/server_cer.pem | 17 +++ examples/TLS/certs/server_key.pem | 27 ++++ examples/TLS/certs/server_pubkey.pem | 9 ++ examples/TLS/certs/server_req.csr | 15 ++ examples/TLS/client/client.ino | 161 +++++++++++++++++++++ examples/TLS/server/server.ino | 142 ++++++++++++++++++ src/Modbus.cpp | 4 +- src/ModbusAPI.h | 207 ++++++++++++++++----------- src/ModbusEthernet.h | 23 ++- src/ModbusRTU.h | 2 +- src/ModbusSettings.h | 2 +- src/ModbusTCP.h | 14 +- src/ModbusTCPTemplate.h | 168 +++++++++++++++------- src/ModbusTLS.h | 108 +++++++++++++- 28 files changed, 911 insertions(+), 158 deletions(-) create mode 100644 examples/TLS/README.md create mode 100644 examples/TLS/certs/ca.conf create mode 100644 examples/TLS/certs/ca_cer.pem create mode 100644 examples/TLS/certs/ca_cer.srl create mode 100644 examples/TLS/certs/ca_key.pem create mode 100644 examples/TLS/certs/cert.cmd create mode 100644 examples/TLS/certs/client.cmd create mode 100644 examples/TLS/certs/client.conf create mode 100644 examples/TLS/certs/client1_cer.pem create mode 100644 examples/TLS/certs/client1_key.pem create mode 100644 examples/TLS/certs/client1_req.csr create mode 100644 examples/TLS/certs/server.cmd create mode 100644 examples/TLS/certs/server.conf create mode 100644 examples/TLS/certs/server_cer.pem create mode 100644 examples/TLS/certs/server_key.pem create mode 100644 examples/TLS/certs/server_pubkey.pem create mode 100644 examples/TLS/certs/server_req.csr create mode 100644 examples/TLS/client/client.ino create mode 100644 examples/TLS/server/server.ino diff --git a/README.md b/README.md index 9873039..0f9d1e6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Modbus RTU and Modbus TCP Library for Arduino +# Modbus RTU and Modbus TCP Library for ESP8266/ESP32 |If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| @@ -63,15 +63,20 @@ For more information about Modbus see: ```diff // 4.0.0.DEVEL -- Modbus TCP Security Server -- Test: TLS Server -- Modbus TCP Security Client -- Test: TLS Client -+ STL dependency remove ++ ModbusTLS: Modbus TCP Security Client/Server ++ ModbusTLS: ESP8266 Client/Server ++ Test: TLS ESP8266 Client/Server ++ Examples: TLS added +- Examples: TLS Certificate test Role extension and Alt-Name +- Examples: TLS Add example explanation +- ModbusTLS: ESP32 Client +- Test: TLS ESP32 Client +- Build with no STL dependency - Test: No-STL mode -+ W5x00 Ethernet library support ++ ModbusTCP: ModbusEthernet - W5x00 Ethernet library support - Test: W5x00 support + API: Implementation code merge ++ API: Access ModbusTCP server by name + ModbusIP => ModbusTCP + 0x14 - Read File Records function + Test: 0x14 diff --git a/examples/TLS/README.md b/examples/TLS/README.md new file mode 100644 index 0000000..259913c --- /dev/null +++ b/examples/TLS/README.md @@ -0,0 +1,3 @@ +# Modbus\TCP Security for ESP8266 Example + +*This is just a draft* \ No newline at end of file diff --git a/examples/TLS/certs/ca.conf b/examples/TLS/certs/ca.conf new file mode 100644 index 0000000..c48c6ab --- /dev/null +++ b/examples/TLS/certs/ca.conf @@ -0,0 +1,11 @@ +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_dn +x509_extensions = v3_req + +[ req_dn ] +CN = root_ca + +[v3_req] +basicConstraints=CA:TRUE diff --git a/examples/TLS/certs/ca_cer.pem b/examples/TLS/certs/ca_cer.pem new file mode 100644 index 0000000..14a4793 --- /dev/null +++ b/examples/TLS/certs/ca_cer.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwjCCAaqgAwIBAgIUTz9NFtf8JkdIkrDroXVB/ANtqlYwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHcm9vdF9jYTAeFw0yMDA4MjcxMDI4MzFaFw0zMTExMTQx +MDI4MzFaMBIxEDAOBgNVBAMMB3Jvb3RfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCpYdVI2PjW+Pdw5bMqxFz0s3jgYgTHyt51NJGlImgJpmjmj16T +rwcqAe70BtsSjOQeWRoF/rk46ZO/ntDbVkP8ZA40Vf8F8Yft64f1OOBf93rTR0sH +oUk+HmE3Iu+bWYSewNMw/LJyF2r95V2xNeX50Y+BhQskBoWYR7C671ifFlsQHI+a +/BpALEi7qt6kGenlhrmRAjweNxVNILHTPH7Fr/TYXWfAb69TzXWTUFy0bdwZfPIP +b2HXyGINGiD6EtZDkybPk17zZgJKMdxpEG5XA/O+daVh3Prlar+amqb30zntOVga +AcyREcmzYFFBWQmuKNw9mz9x09GWLWjBaYP9AgMBAAGjEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBAIFtNowXu8wfahpKF5MoNKrqA9AG/aGzlmbD +pBKr5Cvvo7NdC6oZMdXlS0VkRmAyw9mEJgwspUvqsLx0/lgN+sE381MUovWL9BIs +o4IOax5473q6ZwV87jwpsrlNHBiolw+WCDKVYuDktaThCaxqxmPKCPMbgYPdqWB0 +l1gYDJJ+MwNH/CRsynpM8Hppf88BwwbM6JYegg5/DLxRl5z3HjCAVD8vBoqkWLRD +b9tIER4WDJhZG4tzgMW+lbMJyDoQA1cw4BGag4Ir1er32+w1519UR/VK0ltk9BK9 +yHObfUNN6saco1/f4OM4tzaQOKa+6U1iXVBTBjE2IHPchGqctBk= +-----END CERTIFICATE----- diff --git a/examples/TLS/certs/ca_cer.srl b/examples/TLS/certs/ca_cer.srl new file mode 100644 index 0000000..bc42790 --- /dev/null +++ b/examples/TLS/certs/ca_cer.srl @@ -0,0 +1 @@ +4221D52EC27B0A950D9F41EFC7D20A43100E437D diff --git a/examples/TLS/certs/ca_key.pem b/examples/TLS/certs/ca_key.pem new file mode 100644 index 0000000..d9ad72b --- /dev/null +++ b/examples/TLS/certs/ca_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqWHVSNj41vj3cOWzKsRc9LN44GIEx8redTSRpSJoCaZo5o9e +k68HKgHu9AbbEozkHlkaBf65OOmTv57Q21ZD/GQONFX/BfGH7euH9TjgX/d600dL +B6FJPh5hNyLvm1mEnsDTMPyychdq/eVdsTXl+dGPgYULJAaFmEewuu9YnxZbEByP +mvwaQCxIu6repBnp5Ya5kQI8HjcVTSCx0zx+xa/02F1nwG+vU811k1BctG3cGXzy +D29h18hiDRog+hLWQ5Mmz5Ne82YCSjHcaRBuVwPzvnWlYdz65Wq/mpqm99M57TlY +GgHMkRHJs2BRQVkJrijcPZs/cdPRli1owWmD/QIDAQABAoIBAFn2/argm2LK/9o2 +FrC7dUf/X0+GoFVh+kA0eLtGCA5AFe2H7srwJxT3y+xPC+LRdIRt/PV8MvL4lSIs +/2/QZPHUTvsbRgXpILKM7DyiRgKS1ukLL93Qm69jwWzgoHVZ2afccQ/O2BTjPU+3 +mMj8ALdsyBUaDi3HTQPx5/uSDvcHsoIneIrecX0/I5Yi5+BoVQIkwOkZZsFtHvg6 +44Hf4sqhbB0f7PSSEFjdWceANjMoMZ/upBa6KgvYgYc2gBaU+aCsBC2g2zUY9waG +pbVGMl61gSaD4IqcVCYSFWZkzpeIw2YHwmyFO1H5PCRzgVRYaE72alHmtDrP2cx+ +Ftc+naECgYEA0zQo8VVDWEvcUTIb5igIOGjTJZMBCpR2Y0M7Kh/qx9XfBguEKGiS +KBYWDolweCeZ0tdFW8GTb3RwLLcLBnl/sge7ouvKUlk2iFNbAFipjwYUkAtcfX9a +sPwR+JXbZF2LhBMZsw7dSwhWWjStGaAiXEFFeWqTdV/vwwVdtaiPrgkCgYEAzU7d +VirIVsm7ps5L71zkZtf211o4LRysQS64oh6JUQPzcvC85yU3HtS2VNSCz2GiA1R/ +jqzGAL5q6k0UPBT4xzAwDmBJvtRjFr38FCCcl3Txeqjy7zkG/H0UpmQNMA1jjf/9 +iBRrR1vwjj79xyyl4PojpZmi6GVN2IIWrpf9o1UCgYBEbhf96XRCfYHKxQOJFNtk ++4G+IN0rgmLBUp0uztyRFtiF6uFM/mSsnEtVNm68X4hVae5NBnEwoXde5Yeq917K +XfsLlH4fJEyo6ukHObLmZj/vU98JwmOuCF4CPvuwjyaPCmk/PMeycecYnwyeyuWX +IobSChfw5b6XX3u3SgATkQKBgQCXq35J7LspmkhdlyNzxh0ZeMvrFcRQV1FNqhVN +9t8ckZ2kuQHkhJKu3ReBnaixSYAlk6PUJAD2hbV4N88N/7Q1enzV8f4o0sANCfcS +a3EjVooaQnuNjISDvGen8FvptspoGcgTYnpKMjqI6zIRlQNKK6Bv8wrtQgF7Q8c7 +3h7LLQKBgHQjFqVmM0a3WhgXGTWIhDALWXDgLcsVTQwGnUCafLgdP8wrjF1+tZf1 +UIw04p35FE2xlpMVkYRItYLIuQ4S8Q323rk2yIpTYYZApKYjT5BuNFFUinE5QsqV +iXeCigYuKwOJ6Gi8c+lgFkxZnA3+rZJg9vdzp/yz4Xgy7gJ6QVBc +-----END RSA PRIVATE KEY----- diff --git a/examples/TLS/certs/cert.cmd b/examples/TLS/certs/cert.cmd new file mode 100644 index 0000000..65e3567 --- /dev/null +++ b/examples/TLS/certs/cert.cmd @@ -0,0 +1,17 @@ +set OPATH=C:\Program Files\OpenSSL-Win64\bin + +rem CA +"%OPATH%\openssl" genrsa -out ca_key.pem 2048 +"%OPATH%\openssl" req -x509 -new -nodes -key ca_key.pem -days 4096 -config ca.conf -out ca_cer.pem + +rem SERVER +"%OPATH%\openssl" genrsa -out server_key.pem 2048 +"%OPATH%\openssl" req -out server_req.csr -key server_key.pem -new -config server.conf +"%OPATH%\openssl" x509 -req -in server_req.csr -out server_cer.pem -sha256 -CAcreateserial -days 4000 -CA ca_cer.pem -CAkey ca_key.pem +"%OPATH%\openssl" rsa -in server_key.pem -pubout -out server_pubkey.pem + +rem CLIENT +"%OPATH%\openssl" genrsa -out client1_key.pem 2048 +"%OPATH%\openssl" req -out client1_req.csr -key client1_key.pem -new -config client.conf +"%OPATH%\openssl" x509 -req -in client1_req.csr -out client1_cer.pem -sha256 -CAcreateserial -days 4000 -CA ca_cer.pem -CAkey ca_key.pem + diff --git a/examples/TLS/certs/client.cmd b/examples/TLS/certs/client.cmd new file mode 100644 index 0000000..719b5f4 --- /dev/null +++ b/examples/TLS/certs/client.cmd @@ -0,0 +1 @@ +openssl s_client -showcerts -connect 192.168.30.123:802 -cert client1_cer.pem -key client1_key.pem -verifyCAfile ca_cer.pem \ No newline at end of file diff --git a/examples/TLS/certs/client.conf b/examples/TLS/certs/client.conf new file mode 100644 index 0000000..522e43c --- /dev/null +++ b/examples/TLS/certs/client.conf @@ -0,0 +1,7 @@ +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_dn + +[ req_dn ] +CN = client diff --git a/examples/TLS/certs/client1_cer.pem b/examples/TLS/certs/client1_cer.pem new file mode 100644 index 0000000..39a0460 --- /dev/null +++ b/examples/TLS/certs/client1_cer.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqjCCAZICFEIh1S7CewqVDZ9B78fSCkMQDkN9MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjARMQ8wDQYDVQQDDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDIU4Czr45Jgj7TiUbF0MSs4ydC41R/rB2dSlwcbrmjmZHlJn2fifcVCBuQ +H4a/SCbUNDOXz23p2NZHeLpcHV8TeuvDocmplJIuKNoN8BPbeZ+IS5yrvHBzC0S2 +bXs1gOkcWwmdD87NqQD8v7m+hZjBIBDvPAHPBXsEzkNNlqnye2mRYI8G0sGTqMWV +zCt+m2mwJLAIwVCWYLVEn3sY/ksU6SrwyNKfnoCCw+0hfaMVdUq2u9wpDD2i9fKT +yehSW727r5dOtGUbp3hoxiFWzAlaodvIk0eZ/12EMboc3y4WLax7W2vefNc9sKeM +6jgYRoqz4YRgJLbZzk1tJk292521AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAeY +SL7wIYQONK2uqhqb9MmbfOZznlaGz6kybB0GtVmZpvBaqZtCmTSOSbs/0YVF3OSv ++L9+kWTGsaWx/6t1fdiDG8DlZCqF3dwbmd0YmV2GYbpRF53rYSUETSsdO2g1Fs0a +lvSVrQvhUj/cXvlTqtvjSVBELwFmlu0qhUHqN8Ap3dgy1YUZvRQcJS1GZ46iZLae +SQYAANvfYXC4gBy1vfgKeDkZD4Qs+NnV6J+aFpXTYsmMMOS/lfLTpWP2tEfuaexW +dGPlQ5dw7JZHcPrD9EdVIvDozACS0Y8B7oP4xvKFJnsqE7RmOsnukO0D7CQkxkBy +hJmblVkcv6VRNS9JHDQ= +-----END CERTIFICATE----- diff --git a/examples/TLS/certs/client1_key.pem b/examples/TLS/certs/client1_key.pem new file mode 100644 index 0000000..2427ca8 --- /dev/null +++ b/examples/TLS/certs/client1_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAyFOAs6+OSYI+04lGxdDErOMnQuNUf6wdnUpcHG65o5mR5SZ9 +n4n3FQgbkB+Gv0gm1DQzl89t6djWR3i6XB1fE3rrw6HJqZSSLijaDfAT23mfiEuc +q7xwcwtEtm17NYDpHFsJnQ/OzakA/L+5voWYwSAQ7zwBzwV7BM5DTZap8ntpkWCP +BtLBk6jFlcwrfptpsCSwCMFQlmC1RJ97GP5LFOkq8MjSn56AgsPtIX2jFXVKtrvc +KQw9ovXyk8noUlu9u6+XTrRlG6d4aMYhVswJWqHbyJNHmf9dhDG6HN8uFi2se1tr +3nzXPbCnjOo4GEaKs+GEYCS22c5NbSZNvdudtQIDAQABAoIBAQCQQeGag795G/vW +JTL73JzkyydIuZ/t2KnyzMuMBghU0ZAIbjFko9t0H8SJgspsEK81fOnyVoOWNHoK +Odwp3VTMGGaTGHy6S60A5JYyF0KVd/30Dk8iNK7dia3PmQNywgQcUUqY+fs4io2V +dRNzKY2Y9Vh8jr/WruGp0kcRJn/3hrR7S10UyWDbQYE7R3Hir7V0YMFWEbzgwhRE +6MO6H5obFdZFxy7V+RJLeeq+dKHrvOmtd6F6hWSQUVX9YOVjh820IhhhC3F20EQw +FTiVO9UfpmOzhtBp0vOBCWIHa5Yu+AXufrytfT//DClyiex+kfXrmS+OhZS/zqPf +YjadqQF5AoGBAPXWUUD/jzkE82TPpwtRuIhZtF0kLedpBkzSg0e+Fgbdw4osRbMs +13cXMucWW9wK0TikHeoCcq1N2xDRWGreNqolbj9KEqWG0D2LcTBm0pKXuhAT+bWQ +hJmsiNEQYsM9hJLByLWNp3mwgzDLVjXDAxJgirP1L6Qw65SbQoYMt06bAoGBANCb +i3T0A/YP6ounu2iGiEqrJTU/11zh+ykVSvHd4MpV+szex7pBRlXpkFE2iqElAoja +xVrGsQCTebtJIz58Fy7tJQlTRqilRHCTRR60x+0ab7768OHZNKcSRXFDLVTdEyzv +dKTIZh0IJfbz/DpwyNqTM0GYLhDXJfyJxu7YmeHvAoGAC8N1n+aas9/Ixcop9CC0 +89FXEB3rFGeyJXrtTUGLTEjQUoxLyYcbyFcT2Hr5ak4aNNulks0LL7/J+8QItxRr +CTlBTUX+Hm2VCVziza4d5WXdQWezSzzfG3tmEJr4Ht+SuHMNZ6KfoPMRVARm26u5 +OefkuzfAT9sHatUDGecB3oECgYEAgWaLOlAHiQJUfq7cTLlvH8pMOVzRrfcsAk8H +/0KgJ0LwYVcsU7gb9jz83bPUiKNZkCUM2QN5Vp8kmu2CZEc7ZkuKdt9mbESgUKi5 +7pM7lTOZ78Df3WkMBTsLQnfmTccZFv2uwGzjEs00J50vb9z4asV2vRC2OpILKT0Z +3p0Tz5cCgYEAnFJ5HAVKlzi4k8l7v1SuDb9vCLIG+XQYBNrhW/3BLQftOyEKgK5O +6fkNQT4u7Bgmzu3kpljq7jKBlPdW+l009L0gEEO5QBQLy64IquyydB1kiBIY5jGZ +x7M6SvzBWzC7gzH/P94LxtqM22zzU0LszocV2j1UkxqBVXv0EYVjPB0= +-----END RSA PRIVATE KEY----- diff --git a/examples/TLS/certs/client1_req.csr b/examples/TLS/certs/client1_req.csr new file mode 100644 index 0000000..cc79f2d --- /dev/null +++ b/examples/TLS/certs/client1_req.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICVjCCAT4CAQAwETEPMA0GA1UEAwwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAyFOAs6+OSYI+04lGxdDErOMnQuNUf6wdnUpcHG65o5mR +5SZ9n4n3FQgbkB+Gv0gm1DQzl89t6djWR3i6XB1fE3rrw6HJqZSSLijaDfAT23mf +iEucq7xwcwtEtm17NYDpHFsJnQ/OzakA/L+5voWYwSAQ7zwBzwV7BM5DTZap8ntp +kWCPBtLBk6jFlcwrfptpsCSwCMFQlmC1RJ97GP5LFOkq8MjSn56AgsPtIX2jFXVK +trvcKQw9ovXyk8noUlu9u6+XTrRlG6d4aMYhVswJWqHbyJNHmf9dhDG6HN8uFi2s +e1tr3nzXPbCnjOo4GEaKs+GEYCS22c5NbSZNvdudtQIDAQABoAAwDQYJKoZIhvcN +AQELBQADggEBAIvHh0usZM0QBJvUO1e/LRFrKrS/6dNQ7lkbqEIQxl9NRddak5ad +QYy4aBSA1yR/T2TSqK8Tq3W1eRdvH62KZn0VqumzgtGfRoI6Xtp5pUrCWw1Bv3eX +L1lGicBQezVmXn1vUyNn/+U8FUbPoSWIqPAjetoBEnxPDLEy72OYs5dnC5AC7bXN +jT+pdYqQncy+ghtPgpc1WrHWVIcamhhyUuUcy69sa2eigBwfxh8lfOknUjUE69mI +BafDkwu4DzpayDe/C/TatTan30jur2Dpr+fKiEtfag+15E+BXphdAOodhWLMzJKz +xEvxo4UH/Uo/5dEmwSe7wdYZYIa5OFvTLhY= +-----END CERTIFICATE REQUEST----- diff --git a/examples/TLS/certs/server.cmd b/examples/TLS/certs/server.cmd new file mode 100644 index 0000000..fe80478 --- /dev/null +++ b/examples/TLS/certs/server.cmd @@ -0,0 +1 @@ +openssl s_server -showcerts -accept 802 -cert server_cer.pem -key server_key.pem -verifyCAfile ca_cer.pem \ No newline at end of file diff --git a/examples/TLS/certs/server.conf b/examples/TLS/certs/server.conf new file mode 100644 index 0000000..59a07c8 --- /dev/null +++ b/examples/TLS/certs/server.conf @@ -0,0 +1,7 @@ +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_dn + +[ req_dn ] +CN = modbustls diff --git a/examples/TLS/certs/server_cer.pem b/examples/TLS/certs/server_cer.pem new file mode 100644 index 0000000..c6efd5e --- /dev/null +++ b/examples/TLS/certs/server_cer.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICrTCCAZUCFEIh1S7CewqVDZ9B78fSCkMQDkN8MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjAUMRIwEAYDVQQDDAltb2RidXN0bHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDdKlwKryxSEBsHO1z5fr0f6aJX8PHD1Iftfka3PT285wRoNCcPa5eT +o5dBSyJM9JgKpqGsdm2M7UBAJZAFBSgQi++pRuNsssza1uUre28T3PHV463Oma57 +mFfpIlKGfL/rVuUlqu4igNIgQT/wQJmxJO8tDrWaTjMz4VgCNkG4y1veeIpz7/Cy +9S5CxEKBbibQncpUXyV4tTT9O37qze0Gr+d7frnyyTOtr80AwMMg1Pn61hZbku9E +L/VE13oWVBBSXz0exHVG8X9Ne4uyuyG3HAWheglhQ7m2RkBxTkDVolSf6ec1Xgn3 +15BMCG1eBTAZKNdRRSr4+x60p47ReaWJAgMBAAEwDQYJKoZIhvcNAQELBQADggEB +AGqz1benN8ygveD3F/XxCMgEPfI8WhYS3PQ6sPBE850TuQ+9OrHvue8q87/RfJBW +Yllkyi2JHGuY2muMBJWGWTDHK72JwI69hIIwE9bGrvFUwAZjCbK9+gF6UIUDznNN +bSHHSWTfkCMLFz0Q6XbhJvF2AX5dtmYL+AWqD8+G5ZIrbHgd/o4neA21DTYRfUt2 +0UQ0RAxKxf/BenJWr8IzvOxo0MHxi7JivgreCfxna1REZYxIwVFlh7O9KhB1QQBJ +oezb4CS0Um9sQowFTZ3AtRxpv1u60ZjTeJwSIL00YEyZ0sEO3K4h3PTbh2VxSoQD +D2fcSb1t+gWsvLVZCmNMbVo= +-----END CERTIFICATE----- diff --git a/examples/TLS/certs/server_key.pem b/examples/TLS/certs/server_key.pem new file mode 100644 index 0000000..73eccb2 --- /dev/null +++ b/examples/TLS/certs/server_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3SpcCq8sUhAbBztc+X69H+miV/Dxw9SH7X5Gtz09vOcEaDQn +D2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUoEIvvqUbjbLLM2tblK3tvE9zx1eOt +zpmue5hX6SJShny/61blJaruIoDSIEE/8ECZsSTvLQ61mk4zM+FYAjZBuMtb3niK +c+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3tBq/ne3658skzra/NAMDDINT5+tYW +W5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrshtxwFoXoJYUO5tkZAcU5A1aJUn+nn +NV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0XmliQIDAQABAoIBAAEFxB0siCjs+CMF +bD2fD2LJYr3DWGrOXb6EWfFY8CMickvFCfUxSyccl4NuxH7Ulqtd79trRMBlDGn/ +gnXzeybwbrA6qqyC+x175t1XmcDewaN6hQAyh7L8llN2nCkRBJYi9bZB3w37yHzr +sE79DXjbMdvkeIR5HhV8UjrYY19mVxbJsTokbrDXJEuDvR1kIkYM10UnEqMdDRiV +pSVFS3XkwNfCZHn/f2slcv1Piah8qfTseb5QPNouCZBW+PU5E1BuoFTH2ZtkzlZk +L6lcg42Ameyn5G9w8Walz89zwUIe3sycdJJ8+tRCWA2a2Aj8MeFiLfLxXnTjPNLU +nsVkuH0CgYEA/vbYyef3nzlmV4H+Rk9Rn3nGOeSTQj00avCUQS298bztEvFwcibM +84GOg0j68n/IW6gd4qBJyGpjBYl9ggcSTYBXKEK5FgtO6fM+Rnvnds3/WO7cMz3z +SzGIYranzcpaphj8H2xihW1OLsOUlb0+M71+6Y8P8Yui/mQIRRxHzMsCgYEA3hBc +/lzxs0A/TiwLlCpMIBuadkAxc4zgFY1LYHMveCwCa1cp9lujL5NRVOd8VhmYfpDg +MWAN3q7N/3dOu+hdMDoArXCXS5g5My3c6Ki87aSL1rmHyt9nYIBWZzjnwAii/zqs +kcKaFoEYv1vCuIMDFg+S1f+dpwuYlNn2pcnmwHsCgYEAlm/1+CQbsmI+5ZE5BClX +At7qPEyHKwVMAXFUOKURtyn/RDcbXu9P7Lnb6dDM6PrGsHYgtBBZmJxVMvYuDOO5 +Q+tfAc1kwgIIHPg+HX6MU0g2yzWczctW214tl/koR7+G/wws7ymXdBzLjcIu0K9p +nUPJN2wHP0Fh+fHyAz0tjEMCgYB4R9C3Dkz01LX1d7IF3StCsPDnYDno5sNxqQjN +A1cQ9nWRArN994DagicpoAEe+do5o+trkyWwGmsGFu+UpHXla2V2jGfG0HsbF5py +gwNijSAZfIDrCDsMcDdczdvpjkQLjxJuGUQxMFfhPqioHH6Ncn4MX9pa4tMQvUb1 +4fiVBQKBgAOtTdRqX66vvcOsjJmr2f+fw5SRXc09hxLaCDNjC6jVbCUw0aj6WKlx +sBfP8fvlHqJ6wA9/W2l+YiIf3G2jY2Z8OlOINs3hdXpH0JBzoeEiFwfcfZPAMFb1 +M4JURmEGAriH2lw/5iMQ/YqB9+NoE8t8lBLrhjwXWxN3qxoSruwe +-----END RSA PRIVATE KEY----- diff --git a/examples/TLS/certs/server_pubkey.pem b/examples/TLS/certs/server_pubkey.pem new file mode 100644 index 0000000..06548c2 --- /dev/null +++ b/examples/TLS/certs/server_pubkey.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3SpcCq8sUhAbBztc+X69 +H+miV/Dxw9SH7X5Gtz09vOcEaDQnD2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUo +EIvvqUbjbLLM2tblK3tvE9zx1eOtzpmue5hX6SJShny/61blJaruIoDSIEE/8ECZ +sSTvLQ61mk4zM+FYAjZBuMtb3niKc+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3t +Bq/ne3658skzra/NAMDDINT5+tYWW5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrsh +txwFoXoJYUO5tkZAcU5A1aJUn+nnNV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0Xml +iQIDAQAB +-----END PUBLIC KEY----- diff --git a/examples/TLS/certs/server_req.csr b/examples/TLS/certs/server_req.csr new file mode 100644 index 0000000..42b13d8 --- /dev/null +++ b/examples/TLS/certs/server_req.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbW9kYnVzdGxzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA3SpcCq8sUhAbBztc+X69H+miV/Dxw9SH7X5Gtz09 +vOcEaDQnD2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUoEIvvqUbjbLLM2tblK3tv +E9zx1eOtzpmue5hX6SJShny/61blJaruIoDSIEE/8ECZsSTvLQ61mk4zM+FYAjZB +uMtb3niKc+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3tBq/ne3658skzra/NAMDD +INT5+tYWW5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrshtxwFoXoJYUO5tkZAcU5A +1aJUn+nnNV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0XmliQIDAQABoAAwDQYJKoZI +hvcNAQELBQADggEBAHNXxhHJZt64+Ot1ekZ/VaQcitt/MwOW3kpN+yPIN6iTFSb2 +fXZkrlRG+TIU4hTnJ85HgsoK1hB/9GqEvJ2zerxeMeXH3QFm9jy+bzVJ6vR/hWPH +e4UI2u78w1kY1Z51xNBhIQQ4FJKb+iV1IsijE2sp+mpGbSQKQihG/FBOrxKUAV1q +GomcPTE40XXm6O9TsnnK9AnQCb3AsiZPC/Dm4+xoLwebf92wUPAzPbP74e/AL2ti +PDt3NTb6JCNMoXlhRnroGzOtigvRHF54GAEyLdwpSi1gLfQV1z6uBlU6vZIDOJFm +l0LJMHWDCxewfU+IjDYa77W+/8ApwGcEOF01e3g= +-----END CERTIFICATE REQUEST----- diff --git a/examples/TLS/client/client.ino b/examples/TLS/client/client.ino new file mode 100644 index 0000000..1186a66 --- /dev/null +++ b/examples/TLS/client/client.ino @@ -0,0 +1,161 @@ +/* + Modbus-Uni - Most complete Modbus Library for Arduino + + Modbus/TCP Security Client for ESP8266 Example + + (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. +*/ + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#else +#error Platform is not supported +#endif +#include +#include + +// The hardcoded certificate authority for this example. +// Don't use it on your own apps!!!!! +const char ca_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICwjCCAaqgAwIBAgIUTz9NFtf8JkdIkrDroXVB/ANtqlYwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHcm9vdF9jYTAeFw0yMDA4MjcxMDI4MzFaFw0zMTExMTQx +MDI4MzFaMBIxEDAOBgNVBAMMB3Jvb3RfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCpYdVI2PjW+Pdw5bMqxFz0s3jgYgTHyt51NJGlImgJpmjmj16T +rwcqAe70BtsSjOQeWRoF/rk46ZO/ntDbVkP8ZA40Vf8F8Yft64f1OOBf93rTR0sH +oUk+HmE3Iu+bWYSewNMw/LJyF2r95V2xNeX50Y+BhQskBoWYR7C671ifFlsQHI+a +/BpALEi7qt6kGenlhrmRAjweNxVNILHTPH7Fr/TYXWfAb69TzXWTUFy0bdwZfPIP +b2HXyGINGiD6EtZDkybPk17zZgJKMdxpEG5XA/O+daVh3Prlar+amqb30zntOVga +AcyREcmzYFFBWQmuKNw9mz9x09GWLWjBaYP9AgMBAAGjEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBAIFtNowXu8wfahpKF5MoNKrqA9AG/aGzlmbD +pBKr5Cvvo7NdC6oZMdXlS0VkRmAyw9mEJgwspUvqsLx0/lgN+sE381MUovWL9BIs +o4IOax5473q6ZwV87jwpsrlNHBiolw+WCDKVYuDktaThCaxqxmPKCPMbgYPdqWB0 +l1gYDJJ+MwNH/CRsynpM8Hppf88BwwbM6JYegg5/DLxRl5z3HjCAVD8vBoqkWLRD +b9tIER4WDJhZG4tzgMW+lbMJyDoQA1cw4BGag4Ir1er32+w1519UR/VK0ltk9BK9 +yHObfUNN6saco1/f4OM4tzaQOKa+6U1iXVBTBjE2IHPchGqctBk= +-----END CERTIFICATE----- +)EOF"; + +// The client's private key which must be kept secret +const char client_private_key[] PROGMEM = R"EOF( +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAyFOAs6+OSYI+04lGxdDErOMnQuNUf6wdnUpcHG65o5mR5SZ9 +n4n3FQgbkB+Gv0gm1DQzl89t6djWR3i6XB1fE3rrw6HJqZSSLijaDfAT23mfiEuc +q7xwcwtEtm17NYDpHFsJnQ/OzakA/L+5voWYwSAQ7zwBzwV7BM5DTZap8ntpkWCP +BtLBk6jFlcwrfptpsCSwCMFQlmC1RJ97GP5LFOkq8MjSn56AgsPtIX2jFXVKtrvc +KQw9ovXyk8noUlu9u6+XTrRlG6d4aMYhVswJWqHbyJNHmf9dhDG6HN8uFi2se1tr +3nzXPbCnjOo4GEaKs+GEYCS22c5NbSZNvdudtQIDAQABAoIBAQCQQeGag795G/vW +JTL73JzkyydIuZ/t2KnyzMuMBghU0ZAIbjFko9t0H8SJgspsEK81fOnyVoOWNHoK +Odwp3VTMGGaTGHy6S60A5JYyF0KVd/30Dk8iNK7dia3PmQNywgQcUUqY+fs4io2V +dRNzKY2Y9Vh8jr/WruGp0kcRJn/3hrR7S10UyWDbQYE7R3Hir7V0YMFWEbzgwhRE +6MO6H5obFdZFxy7V+RJLeeq+dKHrvOmtd6F6hWSQUVX9YOVjh820IhhhC3F20EQw +FTiVO9UfpmOzhtBp0vOBCWIHa5Yu+AXufrytfT//DClyiex+kfXrmS+OhZS/zqPf +YjadqQF5AoGBAPXWUUD/jzkE82TPpwtRuIhZtF0kLedpBkzSg0e+Fgbdw4osRbMs +13cXMucWW9wK0TikHeoCcq1N2xDRWGreNqolbj9KEqWG0D2LcTBm0pKXuhAT+bWQ +hJmsiNEQYsM9hJLByLWNp3mwgzDLVjXDAxJgirP1L6Qw65SbQoYMt06bAoGBANCb +i3T0A/YP6ounu2iGiEqrJTU/11zh+ykVSvHd4MpV+szex7pBRlXpkFE2iqElAoja +xVrGsQCTebtJIz58Fy7tJQlTRqilRHCTRR60x+0ab7768OHZNKcSRXFDLVTdEyzv +dKTIZh0IJfbz/DpwyNqTM0GYLhDXJfyJxu7YmeHvAoGAC8N1n+aas9/Ixcop9CC0 +89FXEB3rFGeyJXrtTUGLTEjQUoxLyYcbyFcT2Hr5ak4aNNulks0LL7/J+8QItxRr +CTlBTUX+Hm2VCVziza4d5WXdQWezSzzfG3tmEJr4Ht+SuHMNZ6KfoPMRVARm26u5 +OefkuzfAT9sHatUDGecB3oECgYEAgWaLOlAHiQJUfq7cTLlvH8pMOVzRrfcsAk8H +/0KgJ0LwYVcsU7gb9jz83bPUiKNZkCUM2QN5Vp8kmu2CZEc7ZkuKdt9mbESgUKi5 +7pM7lTOZ78Df3WkMBTsLQnfmTccZFv2uwGzjEs00J50vb9z4asV2vRC2OpILKT0Z +3p0Tz5cCgYEAnFJ5HAVKlzi4k8l7v1SuDb9vCLIG+XQYBNrhW/3BLQftOyEKgK5O +6fkNQT4u7Bgmzu3kpljq7jKBlPdW+l009L0gEEO5QBQLy64IquyydB1kiBIY5jGZ +x7M6SvzBWzC7gzH/P94LxtqM22zzU0LszocV2j1UkxqBVXv0EYVjPB0= +-----END RSA PRIVATE KEY----- +)EOF"; + +// The server's public certificate which must be shared +const char client_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICqjCCAZICFEIh1S7CewqVDZ9B78fSCkMQDkN9MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjARMQ8wDQYDVQQDDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDIU4Czr45Jgj7TiUbF0MSs4ydC41R/rB2dSlwcbrmjmZHlJn2fifcVCBuQ +H4a/SCbUNDOXz23p2NZHeLpcHV8TeuvDocmplJIuKNoN8BPbeZ+IS5yrvHBzC0S2 +bXs1gOkcWwmdD87NqQD8v7m+hZjBIBDvPAHPBXsEzkNNlqnye2mRYI8G0sGTqMWV +zCt+m2mwJLAIwVCWYLVEn3sY/ksU6SrwyNKfnoCCw+0hfaMVdUq2u9wpDD2i9fKT +yehSW727r5dOtGUbp3hoxiFWzAlaodvIk0eZ/12EMboc3y4WLax7W2vefNc9sKeM +6jgYRoqz4YRgJLbZzk1tJk292521AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAeY +SL7wIYQONK2uqhqb9MmbfOZznlaGz6kybB0GtVmZpvBaqZtCmTSOSbs/0YVF3OSv ++L9+kWTGsaWx/6t1fdiDG8DlZCqF3dwbmd0YmV2GYbpRF53rYSUETSsdO2g1Fs0a +lvSVrQvhUj/cXvlTqtvjSVBELwFmlu0qhUHqN8Ap3dgy1YUZvRQcJS1GZ46iZLae +SQYAANvfYXC4gBy1vfgKeDkZD4Qs+NnV6J+aFpXTYsmMMOS/lfLTpWP2tEfuaexW +dGPlQ5dw7JZHcPrD9EdVIvDozACS0Y8B7oP4xvKFJnsqE7RmOsnukO0D7CQkxkBy +hJmblVkcv6VRNS9JHDQ= +-----END CERTIFICATE----- +)EOF"; + +// The server's public certificate which must be shared +const char server_pk[] PROGMEM = R"EOF( +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3SpcCq8sUhAbBztc+X69 +H+miV/Dxw9SH7X5Gtz09vOcEaDQnD2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUo +EIvvqUbjbLLM2tblK3tvE9zx1eOtzpmue5hX6SJShny/61blJaruIoDSIEE/8ECZ +sSTvLQ61mk4zM+FYAjZBuMtb3niKc+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3t +Bq/ne3658skzra/NAMDDINT5+tYWW5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrsh +txwFoXoJYUO5tkZAcU5A1aJUn+nnNV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0Xml +iQIDAQAB +-----END PUBLIC KEY----- +)EOF"; + +//Modbus Registers Offsets +const int LED_COIL = 100; + +//ModbusIP object +ModbusTLS mb; +// Set time via NTP, as required for x.509 validation +void setClock() +{ + configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + delay(500); + Serial.print("."); + now = time(nullptr); + } + Serial.println(""); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print("Current time: "); + Serial.print(asctime(&timeinfo)); +} +//IPAddress remote(192,168,30,127); +char* remote = "modbustls"; +void setup() { + Serial.begin(115200); + + WiFi.begin("E2", "fOlissio92"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + setClock(); + mb.client(); +} +uint16_t v; +void loop() { + if (!mb.isConnected(remote)) { + delay(1000); + //mb.connectWithKnownKey(remote, MODBUSTLS_PORT, client_cert, client_private_key, server_pk); + mb.connect(remote, MODBUSTLS_PORT, client_cert, client_private_key, ca_cert); + Serial.print("."); + } else { + mb.readHreg(remote, 0, &v); + mb.task(); + delay(100); + } +} \ No newline at end of file diff --git a/examples/TLS/server/server.ino b/examples/TLS/server/server.ino new file mode 100644 index 0000000..226d3cb --- /dev/null +++ b/examples/TLS/server/server.ino @@ -0,0 +1,142 @@ +/* + Modbus-Uni - Most complete Modbus Library for Arduino + + Modbus/TCP Security Server for ESP8266 Example + + (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. +*/ + +#include +#include +#include + +// The hardcoded certificate authority for this example. +// Don't use it on your own apps!!!!! + +const char ca_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICwjCCAaqgAwIBAgIUTz9NFtf8JkdIkrDroXVB/ANtqlYwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHcm9vdF9jYTAeFw0yMDA4MjcxMDI4MzFaFw0zMTExMTQx +MDI4MzFaMBIxEDAOBgNVBAMMB3Jvb3RfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCpYdVI2PjW+Pdw5bMqxFz0s3jgYgTHyt51NJGlImgJpmjmj16T +rwcqAe70BtsSjOQeWRoF/rk46ZO/ntDbVkP8ZA40Vf8F8Yft64f1OOBf93rTR0sH +oUk+HmE3Iu+bWYSewNMw/LJyF2r95V2xNeX50Y+BhQskBoWYR7C671ifFlsQHI+a +/BpALEi7qt6kGenlhrmRAjweNxVNILHTPH7Fr/TYXWfAb69TzXWTUFy0bdwZfPIP +b2HXyGINGiD6EtZDkybPk17zZgJKMdxpEG5XA/O+daVh3Prlar+amqb30zntOVga +AcyREcmzYFFBWQmuKNw9mz9x09GWLWjBaYP9AgMBAAGjEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBAIFtNowXu8wfahpKF5MoNKrqA9AG/aGzlmbD +pBKr5Cvvo7NdC6oZMdXlS0VkRmAyw9mEJgwspUvqsLx0/lgN+sE381MUovWL9BIs +o4IOax5473q6ZwV87jwpsrlNHBiolw+WCDKVYuDktaThCaxqxmPKCPMbgYPdqWB0 +l1gYDJJ+MwNH/CRsynpM8Hppf88BwwbM6JYegg5/DLxRl5z3HjCAVD8vBoqkWLRD +b9tIER4WDJhZG4tzgMW+lbMJyDoQA1cw4BGag4Ir1er32+w1519UR/VK0ltk9BK9 +yHObfUNN6saco1/f4OM4tzaQOKa+6U1iXVBTBjE2IHPchGqctBk= +-----END CERTIFICATE----- +)EOF"; + +// The server's private key which must be kept secret +const char server_private_key[] PROGMEM = R"EOF( +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3SpcCq8sUhAbBztc+X69H+miV/Dxw9SH7X5Gtz09vOcEaDQn +D2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUoEIvvqUbjbLLM2tblK3tvE9zx1eOt +zpmue5hX6SJShny/61blJaruIoDSIEE/8ECZsSTvLQ61mk4zM+FYAjZBuMtb3niK +c+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3tBq/ne3658skzra/NAMDDINT5+tYW +W5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrshtxwFoXoJYUO5tkZAcU5A1aJUn+nn +NV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0XmliQIDAQABAoIBAAEFxB0siCjs+CMF +bD2fD2LJYr3DWGrOXb6EWfFY8CMickvFCfUxSyccl4NuxH7Ulqtd79trRMBlDGn/ +gnXzeybwbrA6qqyC+x175t1XmcDewaN6hQAyh7L8llN2nCkRBJYi9bZB3w37yHzr +sE79DXjbMdvkeIR5HhV8UjrYY19mVxbJsTokbrDXJEuDvR1kIkYM10UnEqMdDRiV +pSVFS3XkwNfCZHn/f2slcv1Piah8qfTseb5QPNouCZBW+PU5E1BuoFTH2ZtkzlZk +L6lcg42Ameyn5G9w8Walz89zwUIe3sycdJJ8+tRCWA2a2Aj8MeFiLfLxXnTjPNLU +nsVkuH0CgYEA/vbYyef3nzlmV4H+Rk9Rn3nGOeSTQj00avCUQS298bztEvFwcibM +84GOg0j68n/IW6gd4qBJyGpjBYl9ggcSTYBXKEK5FgtO6fM+Rnvnds3/WO7cMz3z +SzGIYranzcpaphj8H2xihW1OLsOUlb0+M71+6Y8P8Yui/mQIRRxHzMsCgYEA3hBc +/lzxs0A/TiwLlCpMIBuadkAxc4zgFY1LYHMveCwCa1cp9lujL5NRVOd8VhmYfpDg +MWAN3q7N/3dOu+hdMDoArXCXS5g5My3c6Ki87aSL1rmHyt9nYIBWZzjnwAii/zqs +kcKaFoEYv1vCuIMDFg+S1f+dpwuYlNn2pcnmwHsCgYEAlm/1+CQbsmI+5ZE5BClX +At7qPEyHKwVMAXFUOKURtyn/RDcbXu9P7Lnb6dDM6PrGsHYgtBBZmJxVMvYuDOO5 +Q+tfAc1kwgIIHPg+HX6MU0g2yzWczctW214tl/koR7+G/wws7ymXdBzLjcIu0K9p +nUPJN2wHP0Fh+fHyAz0tjEMCgYB4R9C3Dkz01LX1d7IF3StCsPDnYDno5sNxqQjN +A1cQ9nWRArN994DagicpoAEe+do5o+trkyWwGmsGFu+UpHXla2V2jGfG0HsbF5py +gwNijSAZfIDrCDsMcDdczdvpjkQLjxJuGUQxMFfhPqioHH6Ncn4MX9pa4tMQvUb1 +4fiVBQKBgAOtTdRqX66vvcOsjJmr2f+fw5SRXc09hxLaCDNjC6jVbCUw0aj6WKlx +sBfP8fvlHqJ6wA9/W2l+YiIf3G2jY2Z8OlOINs3hdXpH0JBzoeEiFwfcfZPAMFb1 +M4JURmEGAriH2lw/5iMQ/YqB9+NoE8t8lBLrhjwXWxN3qxoSruwe +-----END RSA PRIVATE KEY----- +)EOF"; + +// The server's public certificate which must be shared +const char server_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICrTCCAZUCFEIh1S7CewqVDZ9B78fSCkMQDkN8MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjAUMRIwEAYDVQQDDAltb2RidXN0bHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDdKlwKryxSEBsHO1z5fr0f6aJX8PHD1Iftfka3PT285wRoNCcPa5eT +o5dBSyJM9JgKpqGsdm2M7UBAJZAFBSgQi++pRuNsssza1uUre28T3PHV463Oma57 +mFfpIlKGfL/rVuUlqu4igNIgQT/wQJmxJO8tDrWaTjMz4VgCNkG4y1veeIpz7/Cy +9S5CxEKBbibQncpUXyV4tTT9O37qze0Gr+d7frnyyTOtr80AwMMg1Pn61hZbku9E +L/VE13oWVBBSXz0exHVG8X9Ne4uyuyG3HAWheglhQ7m2RkBxTkDVolSf6ec1Xgn3 +15BMCG1eBTAZKNdRRSr4+x60p47ReaWJAgMBAAEwDQYJKoZIhvcNAQELBQADggEB +AGqz1benN8ygveD3F/XxCMgEPfI8WhYS3PQ6sPBE850TuQ+9OrHvue8q87/RfJBW +Yllkyi2JHGuY2muMBJWGWTDHK72JwI69hIIwE9bGrvFUwAZjCbK9+gF6UIUDznNN +bSHHSWTfkCMLFz0Q6XbhJvF2AX5dtmYL+AWqD8+G5ZIrbHgd/o4neA21DTYRfUt2 +0UQ0RAxKxf/BenJWr8IzvOxo0MHxi7JivgreCfxna1REZYxIwVFlh7O9KhB1QQBJ +oezb4CS0Um9sQowFTZ3AtRxpv1u60ZjTeJwSIL00YEyZ0sEO3K4h3PTbh2VxSoQD +D2fcSb1t+gWsvLVZCmNMbVo= +-----END CERTIFICATE----- +)EOF"; + + +//Modbus Registers Offsets +const int LED_COIL = 100; + +//ModbusIP object +ModbusTLS mb; +// Set time via NTP, as required for x.509 validation +void setClock() +{ + + configTime(5 * 3600, 0, "pool.ntp.org", "time.nist.gov"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + delay(500); + Serial.print("."); + now = time(nullptr); + } + + Serial.println(""); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print("Current time: "); + Serial.print(asctime(&timeinfo)); +} + bool c(IPAddress ip) { + Serial.println(ip); + return true; + } +void setup() { + Serial.begin(115200); + + WiFi.begin("E2", "fOlissio92"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + setClock(); + mb.server(MODBUSTLS_PORT, server_cert, server_private_key, ca_cert); + + mb.addHreg(0); +} + +void loop() { + mb.task(); + delay(100); +} \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index af1f56a..bfe7270 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -6,13 +6,13 @@ */ #include "Modbus.h" -#ifdef MB_GLOBAL_REGS +#ifdef MODBUS_GLOBAL_REGS #if defined(MODBUS_USE_STL) std::vector _regs; std::vector _callbacks; #else DArray _regs; - DArrat _callbacks; + DArray _callbacks; #endif cbModbusFileOp _onFile; #endif diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 8318816..16d375e 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -7,18 +7,26 @@ #pragma once #include "Modbus.h" -template +template class ModbusAPI : public T { public: /* // New API + template uint16_t write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t write(TYPEID id, TAddress reg, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t write(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t push(TYPEID id, TAddress to, TAddress from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); */ // Legacy API @@ -66,31 +74,53 @@ class ModbusAPI : public T { 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 uint16_t writeCoil(TYPEID id, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t writeCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t readCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t readIsts(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t readHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t readIreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pushCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pullCoil(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pullIsts(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pushHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pullHreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pullIreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pullHregToIreg(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pullCoilToIsts(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pushIstsToCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template uint16_t pushIregToHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - 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 + 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 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 uint16_t maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + //template //uint16_t readWriteHreg(uint8_t slaveId, uint16_t readOffset, uint16_t* value, uint16_t numregs, uint16_t writeOffset, uint16_t* value, uint16_t numregs, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); }; @@ -101,8 +131,9 @@ class ModbusAPI : public T { // VALTYPE bool, uint16_t // VALUE #define IMPLEMENT_WRITEREG(FNAME, REG, FUNC, VALUE, VALTYPE) \ -template \ -uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE value, cbTransaction cb, uint8_t unit) { \ +template \ +template \ +uint16_t ModbusAPI::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, nullptr, cb); \ } @@ -110,8 +141,9 @@ 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 \ -uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ +template \ +template \ +uint16_t ModbusAPI::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, nullptr, cb); \ @@ -120,8 +152,9 @@ IMPLEMENT_WRITEREGS(writeCoil, COIL, FC_WRITE_COILS, writeSlaveBits, 0x07D0, boo IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, 0x007D, uint16_t) #define IMPLEMENT_READREGS(FNAME, REG, FUNC, MAXNUM, VALTYPE) \ -template \ -uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ +template \ +template \ +uint16_t ModbusAPI::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, value); \ @@ -132,8 +165,9 @@ IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0, bool) IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, 0x007D, uint16_t) #define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \ -template \ -uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ +template \ +template \ +uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ if (numregs < 0x0001 || numregs > MAXNUM) return false; \ this->addCoil(to, numregs); \ this->readSlave(from, numregs, Modbus::FUNC); \ @@ -147,8 +181,9 @@ IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, 0x007D) IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, 0x07D0) #define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM) \ -template \ -uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ +template \ +template \ +uint16_t ModbusAPI::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->writeSlaveWords(REG(from), to, numregs, Modbus::FUNC); \ @@ -159,148 +194,151 @@ IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, 0x007D) IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, 0x007D) IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, 0x07D0) -template \ -bool ModbusAPI::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { +template \ +bool ModbusAPI::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { return this->addReg(HREG(offset), value, numregs); } -template \ -bool ModbusAPI::Hreg(uint16_t offset, uint16_t value) { +template \ +bool ModbusAPI::Hreg(uint16_t offset, uint16_t value) { return this->Reg(HREG(offset), value); } -template \ -uint16_t ModbusAPI::Hreg(uint16_t offset) { +template \ +uint16_t ModbusAPI::Hreg(uint16_t offset) { return this->Reg(HREG(offset)); } -template \ -uint16_t ModbusAPI::removeHreg(uint16_t offset, uint16_t numregs) { +template \ +uint16_t ModbusAPI::removeHreg(uint16_t offset, uint16_t numregs) { return this->removeReg(HREG(offset), numregs); } -template \ -bool ModbusAPI::addCoil(uint16_t offset, bool value, uint16_t numregs) { +template \ +bool ModbusAPI::addCoil(uint16_t offset, bool value, uint16_t numregs) { return this->addReg(COIL(offset), COIL_VAL(value), numregs); } -template \ -bool ModbusAPI::addIsts(uint16_t offset, bool value, uint16_t numregs) { +template \ +bool ModbusAPI::addIsts(uint16_t offset, bool value, uint16_t numregs) { return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); } -template \ -bool ModbusAPI::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { +template \ +bool ModbusAPI::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { return this->addReg(IREG(offset), value, numregs); } -template \ -bool ModbusAPI::Coil(uint16_t offset, bool value) { +template \ +bool ModbusAPI::Coil(uint16_t offset, bool value) { return this->Reg(COIL(offset), COIL_VAL(value)); } -template \ -bool ModbusAPI::Ists(uint16_t offset, bool value) { +template \ +bool ModbusAPI::Ists(uint16_t offset, bool value) { return this->Reg(ISTS(offset), ISTS_VAL(value)); } -template \ -bool ModbusAPI::Ireg(uint16_t offset, uint16_t value) { +template \ +bool ModbusAPI::Ireg(uint16_t offset, uint16_t value) { return this->Reg(IREG(offset), value); } -template \ -bool ModbusAPI::Coil(uint16_t offset) { +template \ +bool ModbusAPI::Coil(uint16_t offset) { return COIL_BOOL(this->Reg(COIL(offset))); } -template \ -bool ModbusAPI::Ists(uint16_t offset) { +template \ +bool ModbusAPI::Ists(uint16_t offset) { return ISTS_BOOL(this->Reg(ISTS(offset))); } -template \ -uint16_t ModbusAPI::Ireg(uint16_t offset) { +template \ +uint16_t ModbusAPI::Ireg(uint16_t offset) { return this->Reg(IREG(offset)); } -template \ -bool ModbusAPI::removeCoil(uint16_t offset, uint16_t numregs) { +template \ +bool ModbusAPI::removeCoil(uint16_t offset, uint16_t numregs) { return this->removeReg(COIL(offset), numregs); } -template \ -bool ModbusAPI::removeIsts(uint16_t offset, uint16_t numregs) { +template \ +bool ModbusAPI::removeIsts(uint16_t offset, uint16_t numregs) { return this->removeReg(ISTS(offset), numregs); } -template \ -bool ModbusAPI::removeIreg(uint16_t offset, uint16_t numregs) { +template \ +bool ModbusAPI::removeIreg(uint16_t offset, uint16_t numregs) { return this->removeReg(IREG(offset), numregs); } -template \ -bool ModbusAPI::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onGet(COIL(offset), cb, numregs); } -template \ -bool ModbusAPI::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onSet(COIL(offset), cb, numregs); } -template \ -bool ModbusAPI::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onGet(HREG(offset), cb, numregs); } -template \ -bool ModbusAPI::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onSet(HREG(offset), cb, numregs); } -template \ -bool ModbusAPI::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onGet(ISTS(offset), cb, numregs); } -template \ -bool ModbusAPI::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onSet(ISTS(offset), cb, numregs); } -template \ -bool ModbusAPI::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onGet(IREG(offset), cb, numregs); } -template \ -bool ModbusAPI::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->onSet(IREG(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnGet(COIL(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnSet(COIL(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnGet(HREG(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnSet(HREG(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnGet(ISTS(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnSet(ISTS(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnGet(IREG(offset), cb, numregs); } -template \ -bool ModbusAPI::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { +template \ +bool ModbusAPI::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { return this->removeOnSet(IREG(offset), cb, numregs); } -template \ -uint16_t ModbusAPI::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { +template \ +template \ +uint16_t ModbusAPI::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { if (startRec > 0x270F) return 0; if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0; return this->send(slaveId, HREG(0), cb, unit, data); // HREG(0) - just dummy value }; -template \ -uint16_t ModbusAPI::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { +template \ +template \ +uint16_t ModbusAPI::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { if (startRec > 0x270F) return 0; if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0; return this->send(slaveId, HREG(0), cb, unit); // HREG(0) - just dummy value }; -template \ -uint16_t ModbusAPI::maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb, uint8_t unit) { +template \ +template \ +uint16_t ModbusAPI::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); @@ -314,8 +352,9 @@ uint16_t ModbusAPI::maskHreg(TYPEID slaveId, uint16_t offset, uint16_ return this->send(slaveId, HREG(offset), cb, unit, nullptr, cb); }; /* -template \ -uint16_t ModbusAPI::readWriteHreg(TYPEID slaveId, +template \ +template \ +uint16_t ModbusAPI::readWriteHreg(TYPEID slaveId, uint16_t readOffset, uint16_t* value, uint16_t numregs, uint16_t writeOffset, uint16_t* value, uint16_t numregs, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h index be8adba..1d4e0d2 100644 --- a/src/ModbusEthernet.h +++ b/src/ModbusEthernet.h @@ -6,6 +6,7 @@ #pragma once #include +#include #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" @@ -20,7 +21,23 @@ class EthernetClientWrapper : public EthernetClient { } }; #undef MODBUSIP_UNIQUE_CLIENTS -class ModbusEthernet : public ModbusAPI> {}; +class ModbusEthernet : public ModbusAPI> { #else -class ModbusEthernet : public ModbusAPI> {}; -#endif \ No newline at end of file +class ModbusEthernet : public ModbusAPI> { +#endif + private: + static IPAddress resolver (const char* host) { + DNSClient dns; + IPAddress ip; + + dns.begin(Ethernet.dnsServerIP()); + if (dns.getHostByName(host, remote_addr) == 1) + return ip; + else + return IPADDR_NONE; + } + public: + ModbusEthernet() : ModbusAPI() { + resolve = resolver; + } +}; diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 7465a3e..3aeb2b5 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -53,4 +53,4 @@ class ModbusRTUTemplate : public Modbus { uint32_t eventSource() override {return _slaveId;} }; -class ModbusRTU : public ModbusAPI {}; +class ModbusRTU : public ModbusAPI {}; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 4dc2e72..fdfb309 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -16,7 +16,7 @@ MODBUSRTU_ Settings for RTU #pragma once #define MODBUS_GLOBAL_REGS -//#define MODBUS_USE_STL +#define MODBUS_USE_STL //#define MODBUS_MAX_REGS 32 #define MODBUS_ADD_REG #define MODBUS_MAX_FRAME 256 diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h index 6bf0bf9..82fdad7 100644 --- a/src/ModbusTCP.h +++ b/src/ModbusTCP.h @@ -10,4 +10,16 @@ #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" -class ModbusTCP : public ModbusAPI> {}; +class ModbusTCP : public ModbusAPI> { + 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; + } +}; diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 0b475a6..0afc14b 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -16,6 +16,7 @@ #endif // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); +typedef IPAddress (*cbModbusResolver)(const char*); typedef struct TTransaction { uint16_t transactionId; @@ -30,7 +31,7 @@ typedef struct TTransaction { } }; -template +template class ModbusTCPTemplate : public Modbus { protected: typedef union MBAP_t { @@ -62,7 +63,8 @@ class ModbusTCPTemplate : public Modbus { 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 @@ -70,6 +72,8 @@ class ModbusTCPTemplate : public Modbus { int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); + uint16_t send(String host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); + uint16_t send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); // Prepare and send ModbusIP frame. _frame buffer and _len should be filled with Modbus data // ip - slave ip address @@ -81,13 +85,19 @@ class ModbusTCPTemplate : public Modbus { ModbusTCPTemplate(); ~ModbusTCPTemplate(); bool isTransaction(uint16_t id); + bool isConnected(String host); + bool isConnected(const char* host); bool isConnected(IPAddress ip); - bool connect(IPAddress ip, uint16_t port = PORT); + bool connect(String host, uint16_t port = 0); + bool connect(const char* host, uint16_t port = 0); + bool connect(IPAddress ip, uint16_t port = 0); + bool disconnect(String host); + bool disconnect(const char* host); bool disconnect(IPAddress ip); // ModbusTCP - void server(uint16_t port = PORT); + void server(uint16_t port = 0); // ModbusTCP depricated - inline void slave(uint16_t port = PORT) { server(port); } // Depricated + inline void slave(uint16_t port = 0) { server(port); } // Depricated inline void master() { client(); } // Depricated inline void begin() { server(); }; // Depricated void client(); @@ -97,30 +107,47 @@ class ModbusTCPTemplate : public Modbus { uint32_t eventSource() override; void autoConnect(bool enabled = true); void dropTransactions(); + static IPAddress defaultResolver(const char*) {return IPADDR_NONE;} }; -template -ModbusTCPTemplate::ModbusTCPTemplate() { +template +ModbusTCPTemplate::ModbusTCPTemplate() { //_trans.reserve(MODBUSIP_MAX_TRANSACIONS); for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) tcpclient[i] = nullptr; + resolve = defaultResolver; } -template -void ModbusTCPTemplate::client() { +template +void ModbusTCPTemplate::client() { } -template -void ModbusTCPTemplate::server(uint16_t port) { - serverPort = port; +template +void ModbusTCPTemplate::server(uint16_t port) { + if (port) + serverPort = port; + else + serverPort = defaultPort; tcpserver = new SERVER(serverPort); tcpserver->begin(); } -template -bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { +template +bool ModbusTCPTemplate::connect(String host, uint16_t port) { + return connect(resolve(host.c_str()), port); +} + +template +bool ModbusTCPTemplate::connect(const char* host, uint16_t port) { + return connect(resolve(host), port); +} + +template +bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { //cleanupConnections(); + if (!ip) + return false; if(getSlave(ip) != -1) return true; int8_t p = getFreeClient(); @@ -128,11 +155,11 @@ bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t por return false; tcpclient[p] = new CLIENT(); BIT_CLEAR(tcpServerConnection, p); - return tcpclient[p]->connect(ip, port); + return tcpclient[p]->connect(ip, port?port:defaultPort); } -template -uint32_t ModbusTCPTemplate::eventSource() { // Returns IP of current processing client query +template +uint32_t ModbusTCPTemplate::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(); @@ -142,8 +169,8 @@ uint32_t ModbusTCPTemplate::eventSource() { // Returns IP return (uint32_t)INADDR_NONE; } -template -TTransaction* ModbusTCPTemplate::searchTransaction(uint16_t id) { +template +TTransaction* ModbusTCPTemplate::searchTransaction(uint16_t id) { #define MODBUSIP_COMPARE_TRANS [id](TTransaction& trans){return trans.transactionId == id;} #if defined(MODBUS_USE_STL) std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), MODBUSIP_COMPARE_TRANS); @@ -154,8 +181,8 @@ TTransaction* ModbusTCPTemplate::searchTransaction(uint16_ #endif } -template -void ModbusTCPTemplate::task() { +template +void ModbusTCPTemplate::task() { MBAP_t _MBAP; cleanupConnections(); if (tcpserver) { @@ -274,15 +301,27 @@ void ModbusTCPTemplate::task() { cleanupTransactions(); } -template -uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { +template +uint16_t ModbusTCPTemplate::send(String host, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { + return send(resolve(host.c_str()), startreg, cb, unit, data, waitResponse); +} + +template +uint16_t ModbusTCPTemplate::send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { + return send(resolve(host), startreg, cb, unit, data, waitResponse); +} + +template +uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { MBAP_t _MBAP; #ifdef MODBUSIP_MAX_TRANSACIONS - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; + if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return 0; #endif + if (!ip) + return 0; int8_t p = getSlave(ip); if (p == -1 || !tcpclient[p]->connected()) - return autoConnectMode?connect(ip):false; + return autoConnectMode?connect(ip):0; transactionId++; if (!transactionId) transactionId = 1; _MBAP.transactionId = __bswap_16(transactionId); @@ -311,18 +350,18 @@ uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress st return transactionId; } -template -void ModbusTCPTemplate::onConnect(cbModbusConnect cb) { +template +void ModbusTCPTemplate::onConnect(cbModbusConnect cb) { cbConnect = cb; } -template -void ModbusTCPTemplate::onDisconnect(cbModbusConnect cb) { +template +void ModbusTCPTemplate::onDisconnect(cbModbusConnect cb) { cbDisconnect = cb; } -template -void ModbusTCPTemplate::cleanupConnections() { +template +void ModbusTCPTemplate::cleanupConnections() { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { if (tcpclient[i] && !tcpclient[i]->connected()) { //IPAddress ip = tcpclient[i]->remoteIP(); @@ -335,8 +374,8 @@ void ModbusTCPTemplate::cleanupConnections() { } } -template -void ModbusTCPTemplate::cleanupTransactions() { +template +void ModbusTCPTemplate::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) { @@ -364,58 +403,83 @@ void ModbusTCPTemplate::cleanupTransactions() { #endif } -template -int8_t ModbusTCPTemplate::getFreeClient() { +template +int8_t ModbusTCPTemplate::getFreeClient() { for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) if (!tcpclient[i]) return i; return -1; } -template -int8_t ModbusTCPTemplate::getSlave(IPAddress ip) { +template +int8_t ModbusTCPTemplate::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 -int8_t ModbusTCPTemplate::getMaster(IPAddress ip) { +template +int8_t ModbusTCPTemplate::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 -bool ModbusTCPTemplate::isTransaction(uint16_t id) { +template +bool ModbusTCPTemplate::isTransaction(uint16_t id) { return searchTransaction(id) != nullptr; } -template -bool ModbusTCPTemplate::isConnected(IPAddress ip) { +template +bool ModbusTCPTemplate::isConnected(String host) { + return isConnected(resolve(host.c_str())); +} + +template +bool ModbusTCPTemplate::isConnected(const char* host) { + return isConnected(resolve(host)); +} + +template +bool ModbusTCPTemplate::isConnected(IPAddress ip) { + if (!ip) + return false; int8_t p = getSlave(ip); return p != -1 && tcpclient[p]->connected(); } -template -void ModbusTCPTemplate::autoConnect(bool enabled) { +template +void ModbusTCPTemplate::autoConnect(bool enabled) { autoConnectMode = enabled; } -template -bool ModbusTCPTemplate::disconnect(IPAddress ip) { +template +bool ModbusTCPTemplate::disconnect(String host) { + return disconnect(resolve(host.c_str())); +} + +template +bool ModbusTCPTemplate::disconnect(const char* host) { + return disconnect(resolve(host)); +} + +template +bool ModbusTCPTemplate::disconnect(IPAddress ip) { + if (!ip) + return false; int8_t p = getSlave(ip); if (p != -1) { delete tcpclient[p]; tcpclient[p] = nullptr; + return true; } - return true; + return false; } -template -void ModbusTCPTemplate::dropTransactions() { +template +void ModbusTCPTemplate::dropTransactions() { #if defined(MODBUS_USE_STL) for (auto &t : _trans) t.forcedEvent = EX_CANCEL; #else @@ -424,8 +488,8 @@ void ModbusTCPTemplate::dropTransactions() { #endif } -template -ModbusTCPTemplate::~ModbusTCPTemplate() { +template +ModbusTCPTemplate::~ModbusTCPTemplate() { free(_frame); dropTransactions(); cleanupConnections(); diff --git a/src/ModbusTLS.h b/src/ModbusTLS.h index 8476b53..517ccd0 100644 --- a/src/ModbusTLS.h +++ b/src/ModbusTLS.h @@ -3,15 +3,109 @@ 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 +#if defined(ESP8266) #include - +#else +// Just emty stub +class WiFiServerSecure { +public: + WiFiServerSecure(uint16_t){} + WiFiClientSecure available(){} + void begin(); +}; +#endif #include "ModbusTCPTemplate.h" #include "ModbusAPI.h" -// ModbusTLS -class ModbusTLS : public ModbusAPI> { - void server(uint16_t port, const char* server_cert = nullptr, const char* private_key = nullptr, const char* ca = nullptr) {}; - //bool connect(IPAddress ip, uint16_t port); Certificate ? - //bool setCerteficats() {}; ??? + +class ModbusTLS : public ModbusAPI> { + 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; + } + static IPAddress resolver (const char* host) { + IPAddress remote_addr; + if (WiFi.hostByName(host, remote_addr)) + return remote_addr; + return IPADDR_NONE; + } + public: + ModbusTLS() : ModbusAPI() { + defaultPort = MODBUSTLS_PORT; + resolve = resolver; + } + #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 + 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); + } + 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; + } }; From cd31f7b17581790e73e72f0e0a587d2807b52378 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 4 Sep 2020 22:02:28 +0300 Subject: [PATCH 195/288] ModbusRTU: Add inverse logic support for transmit control --- API.md | 6 +++--- README.md | 2 ++ library.properties | 2 +- src/Modbus.cpp | 2 +- src/Modbus.h | 2 +- src/ModbusRTU.cpp | 27 ++++++++++++++++++--------- src/ModbusRTU.h | 7 ++++--- 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/API.md b/API.md index 78b723c..01c1f30 100644 --- a/API.md +++ b/API.md @@ -47,12 +47,12 @@ Processing routine. Should be periodically called form loop(). ### Modbus RTU Specific API ```c -bool begin(SoftwareSerial* port, int16_t txPin=-1); // For ESP8266 only -bool begin(HardwareSerial* port, int16_t txPin=-1); +bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); // For ESP8266 only +bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true); bool begin(Stream* port); ``` -Assing Serial port. txPin controls transmit enable for MAX-485. +Assing Serial port. txPin controls transmit enable for MAX-485. Pass direct=false if txPin uses inverse logic. ```c void setBaudrte(uint32 baud); diff --git a/README.md b/README.md index f286100..ba14700 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 3.0.3 ++ ModbusRTU: Add inverse logic support for transmit control // 3.0.2 + ModbusTCP Client: ESP32 fix unexpected transaction timeout // 3.0.1 diff --git a/library.properties b/library.properties index 045a0aa..ac36c18 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=3.0.2 +version=3.0.3 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus RTU and Modbus TCP Library for ESP8266/ESP32 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e53d872..cd8872b 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -519,7 +519,7 @@ bool Modbus::Hreg(uint16_t offset, uint16_t value) { uint16_t Modbus::Hreg(uint16_t offset) { return Reg(HREG(offset)); } -uint16_t Modbus::removeHreg(uint16_t offset, uint16_t numregs) { +bool Modbus::removeHreg(uint16_t offset, uint16_t numregs) { return removeReg(HREG(offset), numregs); } bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { diff --git a/src/Modbus.h b/src/Modbus.h index 1fe9d80..a639dca 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -137,7 +137,7 @@ class Modbus { bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); bool Hreg(uint16_t offset, uint16_t value); uint16_t Hreg(uint16_t offset); - uint16_t removeHreg(uint16_t offset, uint16_t numregs = 1); + bool removeHreg(uint16_t offset, 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); diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index fbba6f3..3a544a6 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -57,7 +57,7 @@ bool ModbusRTU::begin(Stream* port) { return true; } -bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { +bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin, bool direct) { uint32_t baud = 0; #if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266 @@ -70,21 +70,30 @@ bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; #endif _port = port; - _txPin = txPin; - if (_txPin >= 0) { + if (txPin >= 0) { + _txPin = txPin; + _direct = direct; pinMode(_txPin, OUTPUT); - digitalWrite(_txPin, LOW); + digitalWrite(_txPin, _direct?LOW:HIGH); } Serial.println(_t); return true; } #if defined(ESP8266) -bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { +bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin, bool direct) { uint32_t baud = port->baudRate(); _port = port; - if (txPin >= 0) - port->setTransmitEnablePin(txPin); + if (txPin >= 0) { + if (direct) // If direct logic use SoftwareSerial transmit control + port->setTransmitEnablePin(txPin); + else { + _txPin = txPin; + _direct = direct; + pinMode(_txPin, OUTPUT); + digitalWrite(_txPin, _direct?LOW:HIGH); + } + } setBaudrate(baud); return true; } @@ -93,7 +102,7 @@ bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { uint16_t newCrc = crc16(slaveId, frame, len); if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); + digitalWrite(_txPin, _direct?HIGH:LOW); delay(1); } #ifdef ESP32 @@ -108,7 +117,7 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { #endif _port->flush(); if (_txPin >= 0) - digitalWrite(_txPin, LOW); + digitalWrite(_txPin, _direct?LOW:HIGH); //delay(_t); return true; } diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 23845a7..9909ab3 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -23,7 +23,8 @@ class ModbusRTU : public Modbus { protected: Stream* _port; - int16_t _txPin = -1; + int16_t _txPin = -1; // Transmic control GPIO + bool _direct = true; // Transmit control logic (true=direct, false=inverse) unsigned int _t; // inter-frame delay in mS uint32_t t = 0; // time sience last data byte arrived bool isMaster = false; @@ -49,9 +50,9 @@ class ModbusRTU : public Modbus { public: void setBaudrate(uint32_t baud = -1); #if defined(ESP8266) - bool begin(SoftwareSerial* port, int16_t txPin=-1); + bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); #endif - bool begin(HardwareSerial* port, int16_t txPin=-1); + bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true); bool begin(Stream* port); void task(); void master() { isMaster = true; }; From 526b9746fe10b796eec8cce48c91871a9770d0bd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 1 Oct 2020 10:16:21 +0300 Subject: [PATCH 196/288] Add request callback - Callback prior operation. Can be used for acess control. - Callback at operation successfuly finished. (Optional API) - Example of using this kind of callback - ModbusTCP with Ethernet library example - Documentation and examples refactoring started --- API.md | 57 +++++++---- README.md | 10 +- examples/Callback/README.md | 22 +++++ examples/Callback/Request/Request.ino | 66 +++++++++++++ examples/RTU/README.MD | 42 +++++++++ examples/TCP-Ethernet/client/client.ino | 35 +++++++ examples/TLS/README.md | 43 ++++++++- examples/TLS/certs/server.conf | 9 ++ library.properties | 6 +- src/Modbus.cpp | 120 +++++++++++++++++++----- src/Modbus.h | 21 ++++- src/ModbusAPI.h | 4 +- src/ModbusSettings.h | 6 +- src/ModbusTCP.h | 10 +- src/darray.h | 4 +- 15 files changed, 395 insertions(+), 60 deletions(-) create mode 100644 examples/Callback/README.md create mode 100644 examples/Callback/Request/Request.ino create mode 100644 examples/RTU/README.MD create mode 100644 examples/TCP-Ethernet/client/client.ino diff --git a/API.md b/API.md index 78b723c..92f3d55 100644 --- a/API.md +++ b/API.md @@ -99,22 +99,52 @@ void autoConnect(bool enabled); Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. Disabled by default. -### Query [multiple] regs from remote slave/server +### Read Coils (0x01) from slave/server ```c +uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(const char* host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(String host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +### Write single Coil to slave/server + +```c +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + +uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +### Read Ists (0x02) from slave/server + +```c +uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +```c uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); - -uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` Result is saved to local registers. Method returns corresponding transaction id. [ip/from] or [ip/offset] - slave, [to] or [startreg] - local @@ -122,14 +152,10 @@ Result is saved to local registers. Method returns corresponding transaction id. ### Send [multiple] regs to remote slave/server ```c -uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` @@ -138,20 +164,14 @@ Write Register/Coil or Write Multiple Registers/Coils Modbus function selected a ### Write [multiple] values to remote slave/servr reg[s] ```c -uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); - uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); -uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); ``` Writes single value to remote Hreg/Coil. ```c -uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); - -uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` @@ -160,13 +180,10 @@ Writes multiple values from array to remote Coil/Hreg. ### Read values from multiple remote slave/server regs ```c -uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); -uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); -uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); ``` diff --git a/README.md b/README.md index 0f9d1e6..7f2e944 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# Modbus RTU and Modbus TCP Library for ESP8266/ESP32 +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security |If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| @@ -28,10 +29,12 @@ For more information about Modbus see: * ESP32 * STM32F103 and probably others (Modbus RTU only) * Operates in any combination of multiple instances of - * Modbus RTU slave - * Modbus RTU master + * [Modbus RTU slave](examples/RTU) + * [Modbus RTU master](examples/RTU) * Modbus TCP server * Modbus TCP client + * [MODBUS/TCP Security server (ESP8266)](examples/TLS) + * [MODBUS/TCP Security client (ESP8266/ESP32)](examples/TLS) * Reply exception messages for all supported functions * Modbus functions supported: * 0x01 - Read Coils @@ -88,6 +91,7 @@ For more information about Modbus see: - Test: 0x16 - 0x17 - Read/Write Registers function - Test: 0x17 +- Access control callback for individual function - Slave/Server: slavePDU use early exit by return where possible - Master/Client: Check frame size against header data where possible - Master/Client: Additional responce data validation diff --git a/examples/Callback/README.md b/examples/Callback/README.md new file mode 100644 index 0000000..f8f14cd --- /dev/null +++ b/examples/Callback/README.md @@ -0,0 +1,22 @@ +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +# Callbacks + +There are three types of calbacks are available + +## Register read/write callback + +## Incoming request callback (applicable to server/slave) + +```c +typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount); +bool onRequest(cbRequest cb = _onRequestDefault); +bool onRequestSuccess(cbRequest cb = _onRequestDefault); +``` + +Callback function receives Modbus function code, register type and offset (TAddress structure) and count of registers requested. The function should return Modbus::EX_SUCCESS to allow request processing or Modbus error code to block processing. This code will be returned to client/master. + +[Example](Request/Request.ino) + +## Incoming connection callback (ModbusTCP/TLS only) \ No newline at end of file diff --git a/examples/Callback/Request/Request.ino b/examples/Callback/Request/Request.ino new file mode 100644 index 0000000..967caab --- /dev/null +++ b/examples/Callback/Request/Request.ino @@ -0,0 +1,66 @@ +/* + Modbus-Arduino Example - Using request processing callback to control access to Hreg write operations + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +const uint16_t RO_FLAG = 100; // Coil register to allow/disallow Hregs write +const uint16_t RW_HREG = 200; // Sample Hreg + +//ModbusIP object +ModbusTCP mb; + +Modbus::ResultCode cbPreRequest(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount) { + Serial.printf("PRE Function: %02X\n", fc); + if ((fc == Modbus::FC_WRITE_REG || fc == Modbus::FC_WRITE_REGS) && mb.Coil(RO_FLAG)) + return Modbus::EX_ILLEGAL_FUNCTION; + return Modbus::EX_SUCCESS; +} + +Modbus::ResultCode cbPostRequest(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount) { + Serial.printf("POST Function: %02X\n", fc); + return Modbus::EX_SUCCESS; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("SID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.onRequest(cbPreRequest); + mb.onRequestSuccess(cbPostRequest); + mb.server(); + + mb.addCoil(RO_FLAG); + mb.addHreg(RW_HREG, 100); +} + +void loop() { + mb.task(); + delay(10); +} \ No newline at end of file diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD new file mode 100644 index 0000000..d391b1a --- /dev/null +++ b/examples/RTU/README.MD @@ -0,0 +1,42 @@ +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +This exampel is introduces how to use the library for ModbusRTU (typicaly over RS-485). +Key API functions for slave device + +### Modbus RTU Specific API + +```c +bool begin(SoftwareSerial* port, int16_t txPin=-1); +bool begin(HardwareSerial* port, int16_t txPin=-1); +bool begin(Stream* port); +``` + +Assing Serial port. txPin controls transmit enable for MAX-485. + +```c +void setBaudrte(uint32 baud); +``` + +Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. + +```c +void master(); +void slave(uint8_t slaveId); +``` + +Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictible. + +```c +uint8_t slave(); +``` + +Slave mode: Returns configured slave id. Master mode: Returns slave id for active request or 0 if no request in-progress. + +### Master code structure + +```c +void loop() { + +} +``` \ No newline at end of file diff --git a/examples/TCP-Ethernet/client/client.ino b/examples/TCP-Ethernet/client/client.ino new file mode 100644 index 0000000..abdf51e --- /dev/null +++ b/examples/TCP-Ethernet/client/client.ino @@ -0,0 +1,35 @@ + +/* + ModbusTCP for W5x00 Ethernet library + Basic Server code example + + (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. +*/ + +#include +#include +#include + +// Enter a MAC address and IP address for your controller below. +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED +}; +IPAddress ip(192, 168, 1, 177); // The IP address will be dependent on your local network: +ModbusEthernet mb; // Declare ModbusTCP instance + +void setup() { + Serial.begin(115200); // Open serial communications and wait for port to open + while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only + Ethernet.begin(mac, ip); // start the Ethernet connection + delay(1000); // give the Ethernet shield a second to initialize + mb.server(); // Act as Modbus TCP server + mb.addReg(HREG(100)); // Add Holding register #100 +} + +void loop() { + mb.task(); // Server Modbus TCP queries + delay(50); +} \ No newline at end of file diff --git a/examples/TLS/README.md b/examples/TLS/README.md index 259913c..9a97d71 100644 --- a/examples/TLS/README.md +++ b/examples/TLS/README.md @@ -1,3 +1,42 @@ -# Modbus\TCP Security for ESP8266 Example +# Modbus\TCP Security Example -*This is just a draft* \ No newline at end of file +*Target Platforms:* +- *ESP8266 (CLient/Server)* +- *ESP32 (Client only)* + +## [Sample certificates](certs) + +[cert.cmd](certs/cert.cmd) Script to recreate all the certificates in the catalog. Requires OpenSSL installed. + +[Good issue explanation to read](https://github.com/esp8266/Arduino/issues/6128) + +## [Client](client/client.ino) + +```c +bool connect(const char* host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr); +bool connectWithKnownKey(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* key = nullptr); +``` + +- `const char* host` Host name to connect to +- `uint16_t port` Host port +- `const char* client_cert` Client's certificate +- `const char* client_private_key` Client's private key +- `const char* ca_cert` Certificate of CA. Can be omitted (or set NULL) to escape certificate chain verifying. +- `IPAddress ip` Host IP address to connect to +- `const char* key` Server's public key + +All certificates must be in PEM format and can be stored in PROGMEM. + +## [Server](server/server.ino) + +Method to observe + +```c +void server(uint16_t port, const char* server_cert = nullptr, const char* server_private_key = nullptr, const char* ca_cert = nullptr); +``` +- `uint16_t port` Port to bind to +- `const char* server_cert` Server certificate in PEM format. +- `const char* server_private_key` Server private key in PEM format. +- `const char* ca_cert` Certificate of CA. + +All certificates must be in PEM format and can be stored in PROGMEM. diff --git a/examples/TLS/certs/server.conf b/examples/TLS/certs/server.conf index 59a07c8..d8a56b8 100644 --- a/examples/TLS/certs/server.conf +++ b/examples/TLS/certs/server.conf @@ -2,6 +2,15 @@ prompt = no default_bits = 2048 distinguished_name = req_dn +req_extensions = v3_req +x509_extensions = v3_req [ req_dn ] CN = modbustls + +[v3_req] +# The extentions to add to a self-signed cert +subjectKeyIdentifier = hash +basicConstraints = critical,CA:false +subjectAltName = IP:192.168.30.127 +keyUsage = critical,digitalSignature,keyEncipherment \ No newline at end of file diff --git a/library.properties b/library.properties index 83b12f6..9762257 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=modbus-esp8266 -version=3.0.1 +version=4.0.0.DEV author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus RTU and Modbus TCP Library for ESP8266/ESP32 -paragraph=This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. +paragraph=Most complete Modbus protocol implementation for Arduino. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 -architectures=esp8266,esp32 +architectures=* diff --git a/src/Modbus.cpp b/src/Modbus.cpp index bfe7270..54fc74c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -117,35 +117,54 @@ void Modbus::slavePDU(uint8_t* frame) { uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; 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); - break; + return; } if (Reg(HREG(field1)) != field2) { //Check for failure exceptionResponse(fcode, EX_SLAVE_FAILURE); - break; + return; } _reply = REPLY_ECHO; + _onRequestSuccess(fcode, HREG(field1), field2); break; case FC_READ_REGS: //field1 = startreg, field2 = numregs, header len = 3 - readWords(HREG(field1), field2, fcode); + ex = _onRequest(fcode, HREG(field1), field2); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (!readWords(HREG(field1), field2, fcode)) + 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 > 0x007B || frame[5] != 2 * field2) { //Check value exceptionResponse(fcode, EX_ILLEGAL_VALUE); - break; + return; } for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) if (!searchRegister(HREG(field1) + k)) { exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); - break; + return; } } if (k >= field2) { @@ -153,54 +172,87 @@ void Modbus::slavePDU(uint8_t* frame) { successResponce(HREG(field1), field2, fcode); _reply = REPLY_NORMAL; } + _onRequestSuccess(fcode, HREG(field1), field2); break; case FC_READ_COILS: //field1 = startreg, field2 = numregs - readBits(COIL(field1), field2, fcode); + ex = _onRequest(fcode, COIL(field1), field2); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (!readBits(COIL(field1), field2, fcode)) + return; + _onRequestSuccess(fcode, COIL(field1), field2); break; case FC_READ_INPUT_STAT: //field1 = startreg, field2 = numregs - readBits(ISTS(field1), field2, fcode); + ex = _onRequest(fcode, ISTS(field1), field2); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (!readBits(ISTS(field1), field2, fcode)) + return; + _onRequestSuccess(fcode, ISTS(field1), field2); break; case FC_READ_INPUT_REGS: //field1 = startreg, field2 = numregs - readWords(IREG(field1), field2, fcode); + ex = _onRequest(fcode, IREG(field1), field2); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (!readWords(IREG(field1), field2, fcode)) + 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); - break; + return; } //if (!Coil(field1, COIL_BOOL(field2))) { //Check Address and execute (reg exists?) if (!Reg(COIL(field1), field2)) { //Check Address and execute (reg exists?) exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); - break; + return; } //if (Coil(field1) != COIL_BOOL(field2)) { //Check for failure if (Reg(COIL(field1)) != field2) { //Check for failure exceptionResponse(fcode, EX_SLAVE_FAILURE); - break; + 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 > 0x07B0 || frame[5] != bytecount_calc) { //Check registers range and data size maches exceptionResponse(fcode, EX_ILLEGAL_VALUE); - break; + return; } for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) if (!searchRegister(COIL(field1) + k)) { exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); - break; + return; } } if (k >= field2) { @@ -208,6 +260,7 @@ void Modbus::slavePDU(uint8_t* frame) { successResponce(COIL(field1), field2, fcode); _reply = REPLY_NORMAL; } + _onRequestSuccess(fcode, COIL(field1), field2); break; #if defined(MODBUS_FILES) case FC_READ_FILE_REC: @@ -298,6 +351,11 @@ void Modbus::slavePDU(uint8_t* frame) { case FC_MASKWRITE_REG: { //field1 = reg, field2 = AND mask // Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) + ex = _onRequest(fcode, HREG(field1), field2); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } uint16_t orMask = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; uint16_t val = Reg(HREG(field1)); val = (val && field2) || (orMask && !field2); @@ -311,10 +369,12 @@ void Modbus::slavePDU(uint8_t* frame) { } } _reply = REPLY_ECHO; - return; + _onRequestSuccess(fcode, HREG(field1), field2); + break; default: exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + return; } } @@ -361,10 +421,10 @@ void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numre } } -void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { if (numregs < 0x0001 || numregs > 0x07D0) { //Check value (numregs) exceptionResponse(fn, EX_ILLEGAL_VALUE); - return; + return false; } //Check Address //Check only startreg. Is this correct? @@ -373,7 +433,7 @@ void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { //when you have more then one datapoint configured from same type. if (!searchRegister(startreg)) { exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; + return false; } free(_frame); //Determine the message length = function type, byte count and @@ -383,36 +443,38 @@ void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse(fn, EX_SLAVE_FAILURE); - return; + return false; } _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 true; } -void Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { +bool Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { exceptionResponse(fn, EX_ILLEGAL_VALUE); - return; + return false; } if (!searchRegister(startreg)) { //Check Address exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; + return false; } 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) { exceptionResponse(fn, EX_SLAVE_FAILURE); - return; + return false; } _frame[0] = fn; _frame[1] = _len - 2; //byte count getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs); _reply = REPLY_NORMAL; + return true; } void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) { @@ -748,3 +810,17 @@ Modbus::ResultCode Modbus::fileOp(Modbus::FunctionCode fc, uint16_t fileNum, uin return true; } #endif + +Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount) { + 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 diff --git a/src/Modbus.h b/src/Modbus.h index 92ea59e..77560df 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -13,7 +13,7 @@ #else #include "darray.h" #endif -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ESP32) #include #endif @@ -140,8 +140,8 @@ class Modbus { void cbDisable(); private: - void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); - void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); + bool readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); + bool readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); void setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs); @@ -211,6 +211,20 @@ class Modbus { virtual uint32_t eventSource() {return 0;} + typedef ResultCode (*cbRequest)(FunctionCode fc, TAddress reg, uint16_t regCount); // Callback function Type + + protected: + static ResultCode _onRequestDefault(FunctionCode fc, TAddress reg, uint16_t regCount); + cbRequest _onRequestSuccess = _onRequestDefault; + public: + bool onRequest(cbRequest cb = _onRequestDefault); + #if defined (MODBUSAPI_OPTIONAL) + protected: + cbRequest _onRequest = _onRequestDefault; + public: + bool onRequestSuccess(cbRequest cb = _onRequestDefault); + #endif + #if defined(MODBUS_FILES) public: bool onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)); @@ -235,6 +249,7 @@ class Modbus { }; typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests +//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 typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame); diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 16d375e..199bb82 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -54,7 +54,7 @@ class ModbusAPI : public T { 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); - uint16_t removeHreg(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); @@ -207,7 +207,7 @@ uint16_t ModbusAPI::Hreg(uint16_t offset) { return this->Reg(HREG(offset)); } template \ -uint16_t ModbusAPI::removeHreg(uint16_t offset, uint16_t numregs) { +bool ModbusAPI::removeHreg(uint16_t offset, uint16_t numregs) { return this->removeReg(HREG(offset), numregs); } template \ diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index fdfb309..187fe9f 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -12,6 +12,7 @@ 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 @@ -40,4 +41,7 @@ MODBUSRTU_ Settings for RTU #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 #define MB_MAX_TIME 10 -#define MODBUSRTU_TIMEOUT 1000 \ No newline at end of file +#define MODBUSRTU_TIMEOUT 1000 + +#define MODBUSAPI_LEGACY +#define MODBUSAPI_OPTIONAL \ No newline at end of file diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h index 82fdad7..856be52 100644 --- a/src/ModbusTCP.h +++ b/src/ModbusTCP.h @@ -5,8 +5,14 @@ */ #pragma once -#include -#include +#if defined(ESP8266) +//#include +#elif defined(ESP32) +//#include +#endif + +//#include +//#include #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" diff --git a/src/darray.h b/src/darray.h index cc71dad..24dd21b 100644 --- a/src/darray.h +++ b/src/darray.h @@ -51,9 +51,9 @@ class DArray { void remove(size_t i) { if (isEmpty) return; - if (i >= last) return; + if (i > last) return; if (last == 0) { - isEmpty = 0; + isEmpty = true; return; } memcpy(&data[i], &data[i + 1], (last - i - 1) * sizeof(T)); From 6905a0e73aa346c6981a6188995da4115e08f1e4 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 3 Oct 2020 09:05:50 +0300 Subject: [PATCH 197/288] ModbusRTU class slight refactoring - Add RX/TX pin inverse (form 3.0.3) - Extended ESP32 critical section over .flush() to escape potential data loss (from 3.0.4 ?) - Generalized ModbusRTUTemplate class to allow SoftwareSerial under ESP32 --- src/ModbusRTU.cpp | 47 +++++++---------------------------------------- src/ModbusRTU.h | 30 ++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 48 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 5775f45..f77b3f5 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -58,58 +58,25 @@ bool ModbusRTUTemplate::begin(Stream* port) { return true; } -bool ModbusRTUTemplate::begin(HardwareSerial* port, int16_t txPin) { - uint32_t baud = 0; - #if defined(ESP32) || defined(ESP8266) - // baudRate() only available with ESP32+ESP8266 - baud = port->baudRate(); - #else - baud = 9600; - #endif - setBaudrate(baud); - #if defined(ESP8266) - maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; - #endif - _port = port; - _txPin = txPin; - if (_txPin >= 0) { - pinMode(_txPin, OUTPUT); - digitalWrite(_txPin, LOW); - } - Serial.println(_t); - return true; -} - -#if defined(ESP8266) -bool ModbusRTUTemplate::begin(SoftwareSerial* port, int16_t txPin) { - uint32_t baud = port->baudRate(); - _port = port; - if (txPin >= 0) - port->setTransmitEnablePin(txPin); - setBaudrate(baud); - return true; -} -#endif - bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { uint16_t newCrc = crc16(slaveId, frame, len); if (_txPin >= 0) { - digitalWrite(_txPin, HIGH); - delay(1); + digitalWrite(_txPin, _direct?HIGH:LOW); + delayMicroseconds(1000); } - #ifdef ESP32 + #if defined(ESP32) portENTER_CRITICAL(&mux); #endif _port->write(slaveId); //Send slaveId _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC - #ifdef ESP32 - portEXIT_CRITICAL(&mux); - #endif _port->flush(); if (_txPin >= 0) - digitalWrite(_txPin, LOW); + digitalWrite(_txPin, _direct?LOW:HIGH); + #if defined(ESP32) + portEXIT_CRITICAL(&mux); + #endif //delay(_t); return true; } diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 3aeb2b5..fa836fa 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -6,16 +6,13 @@ This code is licensed under the BSD New License. See LICENSE.txt for more info. */ #pragma once -#include -#if defined(ESP8266) - #include -#endif #include "ModbusAPI.h" class ModbusRTUTemplate : public Modbus { protected: Stream* _port; int16_t _txPin = -1; + bool _direct = true; // Transmit control logic (true=direct, false=inverse) unsigned int _t; // inter-frame delay in mS uint32_t t = 0; // time sience last data byte arrived bool isMaster = false; @@ -41,10 +38,8 @@ class ModbusRTUTemplate : public Modbus { uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen); public: void setBaudrate(uint32_t baud = -1); - #if defined(ESP8266) - bool begin(SoftwareSerial* port, int16_t txPin=-1); - #endif - bool begin(HardwareSerial* port, int16_t txPin=-1); + template + bool begin(T* port, int16_t txPin = -1, bool direct = true); bool begin(Stream* port); void task(); void master() { isMaster = true; }; @@ -53,4 +48,23 @@ class ModbusRTUTemplate : public Modbus { uint32_t eventSource() override {return _slaveId;} }; +template +bool ModbusRTUTemplate::begin(T* port, int16_t txPin, bool direct) { + uint32_t baud = 0; + #if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266 + baud = port->baudRate(); + #else + baud = 9600; + #endif + setBaudrate(baud); + _port = port; + if (txPin >= 0) { + _txPin = txPin; + _direct = direct; + pinMode(_txPin, OUTPUT); + digitalWrite(_txPin, _direct?LOW:HIGH); + } + return true; +} + class ModbusRTU : public ModbusAPI {}; From 544562ea1ed3872f1fbd1017b25e5f46169ac4fd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 6 Oct 2020 08:55:17 +0300 Subject: [PATCH 198/288] Cleanup code style - Remove compile-time warning in mastedPDU() - #include code style change - Remove compile-time warning in structs --- src/Modbus.cpp | 14 ++++++-------- src/Modbus.h | 7 +------ src/ModbusRTU.cpp | 8 ++++---- src/ModbusRTU.h | 2 +- src/ModbusTCP.h | 15 +++++++-------- src/ModbusTCPTemplate.h | 8 ++++---- 6 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 54fc74c..27ad5f6 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -6,7 +6,7 @@ */ #include "Modbus.h" -#ifdef MODBUS_GLOBAL_REGS +#if defined(MODBUS_GLOBAL_REGS) #if defined(MODBUS_USE_STL) std::vector _regs; std::vector _callbacks; @@ -54,7 +54,7 @@ TRegister* Modbus::searchRegister(TAddress address) { } bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) { - #ifdef MODBUS_MAX_REGS + #if defined(MODBUS_MAX_REGS) if (_regs.size() + numregs > MB_MAX_REGS) return false; #endif for (uint16_t i = 0; i < numregs; i++) { @@ -678,12 +678,10 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, break; } if (output) { - frame += 2; - while(field2) { - *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); - frame += 2; - output += 2; - field2--; + uint16_t* from = (uint16_t*)(frame + 2); + uint16_t* to = (uint16_t*)output; + while(field2--) { + *(to++) = __bswap_16(*(from++)); } } else { setMultipleWords((uint16_t*)(frame + 2), startreg, field2); diff --git a/src/Modbus.h b/src/Modbus.h index 77560df..080a30d 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -13,13 +13,8 @@ #else #include "darray.h" #endif -#if defined(ESP32) - #include -#endif -#ifndef __bswap_16 - #define __bswap_16(num) (((uint16_t)num>>8) | ((uint16_t)num<<8)) -#endif +inline uint16_t __bswap_16(uint16_t num) { return (num >> 8) | (num << 8); } #define COIL(n) (TAddress){TAddress::COIL, n} #define ISTS(n) (TAddress){TAddress::ISTS, n} diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index f77b3f5..9aacc99 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -98,24 +98,24 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti } void ModbusRTUTemplate::task() { - #ifdef ESP32 + #if defined(ESP32) portENTER_CRITICAL(&mux); #endif if (_port->available() > _len) { _len = _port->available(); t = millis(); - #ifdef ESP32 + #if defined(ESP32) portEXIT_CRITICAL(&mux); #endif return; } if (_len != 0 && millis() - t < _t) { // Wait data whitespace if there is data - #ifdef ESP32 + #if defined(ESP32) portEXIT_CRITICAL(&mux); #endif return; } - #ifdef ESP32 + #if defined(ESP32) portEXIT_CRITICAL(&mux); #endif diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index fa836fa..34a8891 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -23,7 +23,7 @@ class ModbusRTUTemplate : public Modbus { uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); uint16_t maxRegs = 0x007D; - #ifdef ESP32 + #if defined(ESP32) portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; #endif diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h index 856be52..be55b12 100644 --- a/src/ModbusTCP.h +++ b/src/ModbusTCP.h @@ -6,25 +6,24 @@ #pragma once #if defined(ESP8266) -//#include +#include #elif defined(ESP32) -//#include +#include #endif -//#include -//#include #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" class ModbusTCP : public ModbusAPI> { - private: - static IPAddress resolver (const char* host) { + private: + static IPAddress resolver(const char *host) { IPAddress remote_addr; if (WiFi.hostByName(host, remote_addr)) - return remote_addr; + return remote_addr; return IPADDR_NONE; } - public: + + public: ModbusTCP() : ModbusAPI() { resolve = resolver; } diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 0afc14b..bd052c8 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -18,7 +18,7 @@ typedef bool (*cbModbusConnect)(IPAddress ip); typedef IPAddress (*cbModbusResolver)(const char*); -typedef struct TTransaction { +struct TTransaction { uint16_t transactionId; uint32_t timestamp; cbTransaction cb = nullptr; @@ -34,7 +34,7 @@ typedef struct TTransaction { template class ModbusTCPTemplate : public Modbus { protected: - typedef union MBAP_t { + union MBAP_t { struct { uint16_t transactionId; uint16_t protocolId; @@ -194,7 +194,7 @@ void ModbusTCPTemplate::task() { if (!currentClient || !currentClient->connected()) continue; if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { - #ifdef MODBUSIP_UNIQUE_CLIENTS + #if defined(MODBUSIP_UNIQUE_CLIENTS) // Disconnect previous connection from same IP if present n = getMaster(currentClient->remoteIP()); if (n != -1) { @@ -314,7 +314,7 @@ uint16_t ModbusTCPTemplate::send(const char* host, TAddress star template uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { MBAP_t _MBAP; -#ifdef MODBUSIP_MAX_TRANSACIONS +#if defined(MODBUSIP_MAX_TRANSACIONS) if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return 0; #endif if (!ip) From 9520b2ddeea6c8b12f64bf68822cf5e6466360f6 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 10 Oct 2020 22:04:44 +0300 Subject: [PATCH 199/288] Cleanup code style and fix bugs - Remove compile-time warning in mastedPDU() - #include code style change - Remove compile-time warning in structs - Generic Arduino support finally added - Examples and documentation refactoring (in progress) --- examples/Callback/README.md | 65 ++++++++++- .../Transactional/Transactional.ino} | 0 .../onSet/onSet.ino} | 0 examples/README.md | 12 ++ .../client/client.ino} | 108 +++++++++--------- .../server/server.ino} | 0 .../{client/client.ino => server/server.ino} | 0 library.properties | 2 +- src/Modbus.cpp | 2 +- src/Modbus.h | 2 +- src/ModbusAPI.h | 12 +- src/ModbusEthernet.h | 1 - src/ModbusRTU.cpp | 2 +- src/ModbusRTU.h | 4 +- src/ModbusSettings.h | 18 +++ src/ModbusTCPTemplate.h | 14 +-- src/darray.h | 6 +- 17 files changed, 167 insertions(+), 81 deletions(-) rename examples/{IP-client-WriteMultiple/IP-client-WriteMultiple.ino => Callback/Transactional/Transactional.ino} (100%) rename examples/{IP-server-Callback/IP-server-Callback.ino => Callback/onSet/onSet.ino} (100%) create mode 100644 examples/README.md rename examples/{IP-client-SimpleRead/IP-client-SimpleRead.ino => TCP-ESP/client/client.ino} (96%) rename examples/{IP-server-HoldingReg/IP-server-HoldingReg.ino => TCP-ESP/server/server.ino} (100%) rename examples/TCP-Ethernet/{client/client.ino => server/server.ino} (100%) diff --git a/examples/Callback/README.md b/examples/Callback/README.md index f8f14cd..4f08945 100644 --- a/examples/Callback/README.md +++ b/examples/Callback/README.md @@ -3,11 +3,52 @@ # Callbacks -There are three types of calbacks are available +[## Register read/write callback](onSet/onSet.ino) -## Register read/write callback +```c +bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +`address` Address of register assign callback on +`cb` Callback function +`numregs` Count of sequental segisters assign this callback to + +Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. + + +```c +bool onGetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +`address` Address of register assign callback on +`cb` Callback function +`numregs` Count of sequental segisters assign this callback to -## Incoming request callback (applicable to server/slave) +Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. + +```c +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); +``` + +`address` Address of register assign callback on +`cb` Callback function or NULL to remove all the callbacks. +`numregs` Count of sequental segisters remove this callback to. +Disconnect specific callback function or all callbacks of the type if cb=NULL. + +[## Incoming request callback (applicable to server/slave)](Request/Request.ino) ```c typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount); @@ -17,6 +58,20 @@ bool onRequestSuccess(cbRequest cb = _onRequestDefault); Callback function receives Modbus function code, register type and offset (TAddress structure) and count of registers requested. The function should return Modbus::EX_SUCCESS to allow request processing or Modbus error code to block processing. This code will be returned to client/master. -[Example](Request/Request.ino) +[## Modbus TCP/TLS Incoming connection callback](onSet/onSet.ino) + +```c +void onConnect(cbModbusConnect cb); +void onDisonnect(cbModbusConnect cb); +``` + +*Modbus TCP Sserver* Assign callback function on new incoming connection event. + +```c +typedef bool (*cbModbusConnect)(IPAddress ip); +``` + +*Modbus TCP Sserver* Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. + +[## Modbus TCP/TLS Transaction result](Transactional/Transactional.ino) -## Incoming connection callback (ModbusTCP/TLS only) \ No newline at end of file diff --git a/examples/IP-client-WriteMultiple/IP-client-WriteMultiple.ino b/examples/Callback/Transactional/Transactional.ino similarity index 100% rename from examples/IP-client-WriteMultiple/IP-client-WriteMultiple.ino rename to examples/Callback/Transactional/Transactional.ino diff --git a/examples/IP-server-Callback/IP-server-Callback.ino b/examples/Callback/onSet/onSet.ino similarity index 100% rename from examples/IP-server-Callback/IP-server-Callback.ino rename to examples/Callback/onSet/onSet.ino diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..5d526a6 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,12 @@ +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +# Examples + +[## RTU](RTU) + +[##TCP ESP8266/ESP32](TCP) + +[##TCP Ethernet W5x00](TCP-Ethernet) + +[##TLS](TLS) diff --git a/examples/IP-client-SimpleRead/IP-client-SimpleRead.ino b/examples/TCP-ESP/client/client.ino similarity index 96% rename from examples/IP-client-SimpleRead/IP-client-SimpleRead.ino rename to examples/TCP-ESP/client/client.ino index a7f3d5c..22d3de7 100644 --- a/examples/IP-client-SimpleRead/IP-client-SimpleRead.ino +++ b/examples/TCP-ESP/client/client.ino @@ -1,55 +1,55 @@ -/* - Modbus-Arduino Example - Master Modbus IP Client (ESP8266/ESP32) - Read Holding Register from Server device - - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else - #include -#endif -#include - -const int REG = 528; // Modbus Hreg Offset -IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device -const int LOOP_COUNT = 10; - -ModbusIP mb; //ModbusIP object - -void setup() { - Serial.begin(115200); - - WiFi.begin("SSID", "PASSWORD"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - - mb.client(); -} - -uint16_t res = 0; -uint8_t show = LOOP_COUNT; - -void loop() { - if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established - mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave - } else { - mb.connect(remote); // Try to connect if no connection - } - mb.task(); // Common local Modbus task - delay(100); // Pulling interval - if (!show--) { // Display Slave register value one time per second (with default settings) - Serial.println(res); - show = LOOP_COUNT; - } +/* + Modbus-Arduino Example - Master Modbus IP Client (ESP8266/ESP32) + Read Holding Register from Server device + + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +const int REG = 528; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device +const int LOOP_COUNT = 10; + +ModbusIP mb; //ModbusIP object + +void setup() { + Serial.begin(115200); + + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.client(); +} + +uint16_t res = 0; +uint8_t show = LOOP_COUNT; + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + } + mb.task(); // Common local Modbus task + delay(100); // Pulling interval + if (!show--) { // Display Slave register value one time per second (with default settings) + Serial.println(res); + show = LOOP_COUNT; + } } \ No newline at end of file diff --git a/examples/IP-server-HoldingReg/IP-server-HoldingReg.ino b/examples/TCP-ESP/server/server.ino similarity index 100% rename from examples/IP-server-HoldingReg/IP-server-HoldingReg.ino rename to examples/TCP-ESP/server/server.ino diff --git a/examples/TCP-Ethernet/client/client.ino b/examples/TCP-Ethernet/server/server.ino similarity index 100% rename from examples/TCP-Ethernet/client/client.ino rename to examples/TCP-Ethernet/server/server.ino diff --git a/library.properties b/library.properties index 9762257..31b9b10 100644 --- a/library.properties +++ b/library.properties @@ -2,7 +2,7 @@ name=modbus-esp8266 version=4.0.0.DEV author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov -sentence=Modbus RTU and Modbus TCP Library for ESP8266/ESP32 +sentence=Modbus Library for Arduino. ModbusRTU, ModbusTCP and ModbusTCP Security paragraph=Most complete Modbus protocol implementation for Arduino. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 27ad5f6..9ae978d 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -660,7 +660,7 @@ void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { } } -void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output) { +void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output) { uint8_t fcode = frame[0]; _reply = EX_SUCCESS; if ((fcode & 0x80) != 0) { diff --git a/src/Modbus.h b/src/Modbus.h index 080a30d..a4efccf 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -178,7 +178,7 @@ class Modbus { 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, void* output = nullptr); //For Master + 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 diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 199bb82..f248c8e 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -31,10 +31,12 @@ class ModbusAPI : public T { */ // Legacy API /* - 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 addReg(TAddress address, uint16_t* value = 0, uint16_t numregs = 1); + bool Reg(TAddress address, uint16_t* value, uint16_t numregs = 1); + bool Hreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1); + bool Coil(uint16_t offset, bool* value, uint16_t numregs = 1); + bool Ists(uint16_t offset, bool* value, uint16_t numregs = 1); + bool Ireg(uint16_t offset, uint16_t* value, uint16_t numregs = 1); */ 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); @@ -157,7 +159,7 @@ template \ uint16_t ModbusAPI::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, value); \ + return this->send(ip, REG(offset), cb, unit, (uint8_t*)value); \ } IMPLEMENT_READREGS(readCoil, COIL, FC_READ_COILS, 0x07D0, bool) IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, 0x007D, uint16_t) diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h index 1d4e0d2..03a47a1 100644 --- a/src/ModbusEthernet.h +++ b/src/ModbusEthernet.h @@ -5,7 +5,6 @@ */ #pragma once -#include #include #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 9aacc99..17beb2e 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -81,7 +81,7 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { return true; } -uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { +uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { if (_slaveId) return false; // Break if waiting for previous request result rawSend(slaveId, _frame, _len); if (waitResponse) { diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 34a8891..0d4c2ed 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -19,7 +19,7 @@ class ModbusRTUTemplate : public Modbus { uint8_t _slaveId; uint32_t _timestamp = 0; cbTransaction _cb = nullptr; - void* _data = nullptr; + uint8_t* _data = nullptr; uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); uint16_t maxRegs = 0x007D; @@ -27,7 +27,7 @@ class ModbusRTUTemplate : public Modbus { portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; #endif - uint16_t send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); + 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) diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 187fe9f..618218e 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -16,9 +16,27 @@ 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_USE_STL +If defined C STL will be used. +*/ +#if defined(ESP8266) || defined(ESP32) +#define MODBUS_USE_STL +#endif + +/* +#define MODBUS_MAX_REGS 32 +If defined regisers count will be limited. +*/ //#define MODBUS_MAX_REGS 32 + #define MODBUS_ADD_REG #define MODBUS_MAX_FRAME 256 //#define MODBUS_STATIC_FRAME diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index bd052c8..6c68e43 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -23,7 +23,7 @@ struct TTransaction { uint32_t timestamp; cbTransaction cb = nullptr; uint8_t* _frame = nullptr; - void* data = 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 { @@ -72,9 +72,9 @@ class ModbusTCPTemplate : public Modbus { int8_t getFreeClient(); // Returns free slot position int8_t getSlave(IPAddress ip); int8_t getMaster(IPAddress ip); - uint16_t send(String host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); - uint16_t send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); - uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); + 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) @@ -302,17 +302,17 @@ void ModbusTCPTemplate::task() { } template -uint16_t ModbusTCPTemplate::send(String host, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { +uint16_t ModbusTCPTemplate::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 -uint16_t ModbusTCPTemplate::send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { +uint16_t ModbusTCPTemplate::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 -uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { +uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { MBAP_t _MBAP; #if defined(MODBUSIP_MAX_TRANSACIONS) if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return 0; diff --git a/src/darray.h b/src/darray.h index 24dd21b..0b204fb 100644 --- a/src/darray.h +++ b/src/darray.h @@ -56,9 +56,9 @@ class DArray { isEmpty = true; return; } - memcpy(&data[i], &data[i + 1], (last - i - 1) * sizeof(T)); - if (last) last --; - else isEmpty = true; + if (i < last) + memcpy(&data[i], &data[i + 1], (last - i) * sizeof(T)); + last --; } T operator[](int i) { return data[i]; From 70ca04f77ac6d852253e0b07a6960f73c2680965 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 12 Oct 2020 07:51:39 +0300 Subject: [PATCH 200/288] W5x00 @ESP8266 - Split v1 and v2 derivated libraries classes - Update Ethernet server example --- examples/TCP-Ethernet/server/server.ino | 6 ++-- src/ModbusEthernet-v1.h | 40 +++++++++++++++++++++++++ src/ModbusEthernet.h | 16 +--------- 3 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 src/ModbusEthernet-v1.h diff --git a/examples/TCP-Ethernet/server/server.ino b/examples/TCP-Ethernet/server/server.ino index abdf51e..a7e2582 100644 --- a/examples/TCP-Ethernet/server/server.ino +++ b/examples/TCP-Ethernet/server/server.ino @@ -1,4 +1,3 @@ - /* ModbusTCP for W5x00 Ethernet library Basic Server code example @@ -10,7 +9,7 @@ */ #include -#include +#include // Ethernet library v2 is required #include // Enter a MAC address and IP address for your controller below. @@ -22,7 +21,10 @@ ModbusEthernet mb; // Declare ModbusTCP instance void setup() { Serial.begin(115200); // Open serial communications and wait for port to open + #if defined(AVR_LEONARDO) while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only + #endif + Ethernet.init(15); // SS pin Ethernet.begin(mac, ip); // start the Ethernet connection delay(1000); // give the Ethernet shield a second to initialize mb.server(); // Act as Modbus TCP server diff --git a/src/ModbusEthernet-v1.h b/src/ModbusEthernet-v1.h new file mode 100644 index 0000000..630d9a6 --- /dev/null +++ b/src/ModbusEthernet-v1.h @@ -0,0 +1,40 @@ +/* + Modbus Library for Arduino + ModbusTCP for W5x00 Ethernet + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#include +#include "ModbusAPI.h" +#include "ModbusTCPTemplate.h" + +#undef MODBUSIP_UNIQUE_CLIENTS + +class EthernetClientWrapper : public EthernetClient { + public: + EthernetClientWrapper(EthernetClient c) : EthernetClient(c) {}; + IPAddress remoteIP() { + if (connected()) + return IPAddress(0,0,0,1); + return IPADDR_NONE; + } +}; + +class ModbusEthernet : public ModbusAPI> { + 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; + } +}; diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h index 03a47a1..f1c5ca1 100644 --- a/src/ModbusEthernet.h +++ b/src/ModbusEthernet.h @@ -9,28 +9,14 @@ #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" -#if defined(ethernet_h) -class EthernetClientWrapper : public EthernetClient { - public: - EthernetClientWrapper(EthernetClient c) : EthernetClient(c) {}; - IPAddress remoteIP() { - if (connected()) - return IPAddress(0,0,0,1); - return IPADDR_NONE; - } -}; -#undef MODBUSIP_UNIQUE_CLIENTS -class ModbusEthernet : public ModbusAPI> { -#else class ModbusEthernet : public ModbusAPI> { -#endif private: static IPAddress resolver (const char* host) { DNSClient dns; IPAddress ip; dns.begin(Ethernet.dnsServerIP()); - if (dns.getHostByName(host, remote_addr) == 1) + if (dns.getHostByName(host, ip) == 1) return ip; else return IPADDR_NONE; From 0895bb861e045fcd09ab89105b64141a5a68d6cd Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 12 Oct 2020 09:16:51 +0300 Subject: [PATCH 201/288] API extension - Set multiple local registers value from array - Add multiple registers and fill values from array --- src/Modbus.cpp | 6 ++++++ src/Modbus.h | 2 ++ src/ModbusAPI.h | 18 ++++++++++-------- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 9ae978d..c69037c 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -111,6 +111,12 @@ bool Modbus::removeReg(TAddress address, uint16_t numregs) { return atLeastOne; } +bool Modbus::addReg(TAddress address, uint16_t* value, uint16_t numregs) { + 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]; diff --git a/src/Modbus.h b/src/Modbus.h index a4efccf..0a4b8f6 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -198,6 +198,8 @@ class Modbus { 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); diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index f248c8e..9eb65c1 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -30,14 +30,16 @@ class ModbusAPI : public T { uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); */ // Legacy API -/* - bool addReg(TAddress address, uint16_t* value = 0, uint16_t numregs = 1); - bool Reg(TAddress address, uint16_t* value, uint16_t numregs = 1); - bool Hreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1); - bool Coil(uint16_t offset, bool* value, uint16_t numregs = 1); - bool Ists(uint16_t offset, bool* value, uint16_t numregs = 1); - bool Ireg(uint16_t offset, uint16_t* value, uint16_t numregs = 1); -*/ + bool Hreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(HREG(offset), value);} + bool Coil(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(COIL(offset), value);} + bool Ists(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(ISTS(offset), value);} + bool Ireg(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); From 54d2887338b0dfdb52a4e308a80fe453a47ea4f4 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 13 Oct 2020 07:53:11 +0300 Subject: [PATCH 202/288] Fixed compatibility with Ethernet library - Usage of accept() instead available() for Ethernet - Added wrapper accept() => available() for ESP - Removed possible deadlock in task() - Documentation and examples spet-by-step changes --- README.md | 8 ++-- examples/Callback/README.md | 16 ++++--- examples/RTU/slave/slave.ino | 1 + examples/TCP-ESP/README.md | 42 +++++++++++++++++ examples/TCP-ESP/clientSync/clientSync.ino | 54 ++++++++++++++++++++++ src/ModbusTCP.h | 10 +++- src/ModbusTCPTemplate.h | 12 ++--- 7 files changed, 125 insertions(+), 18 deletions(-) create mode 100644 examples/TCP-ESP/README.md create mode 100644 examples/TCP-ESP/clientSync/clientSync.ino diff --git a/README.md b/README.md index 7f2e944..4fb8f36 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ For more information about Modbus see: * 0x14 - Read File Record * 0x15 - Write File Record * 0x16 - Mask Write Register -* Callbacks for +* [Callbacks](examples/callback) for * Client connect (Modbus TCP) * Server/Client disconnect (Modbus TCP) * Read specific Register @@ -72,9 +72,9 @@ For more information about Modbus see: + Examples: TLS added - Examples: TLS Certificate test Role extension and Alt-Name - Examples: TLS Add example explanation -- ModbusTLS: ESP32 Client ++ ModbusTLS: ESP32 Client - Test: TLS ESP32 Client -- Build with no STL dependency ++ Build with no STL dependency - Test: No-STL mode + ModbusTCP: ModbusEthernet - W5x00 Ethernet library support - Test: W5x00 support @@ -91,7 +91,7 @@ For more information about Modbus see: - Test: 0x16 - 0x17 - Read/Write Registers function - Test: 0x17 -- Access control callback for individual function ++ Access control callback for individual function - Slave/Server: slavePDU use early exit by return where possible - Master/Client: Check frame size against header data where possible - Master/Client: Additional responce data validation diff --git a/examples/Callback/README.md b/examples/Callback/README.md index 4f08945..8aa277e 100644 --- a/examples/Callback/README.md +++ b/examples/Callback/README.md @@ -3,7 +3,7 @@ # Callbacks -[## Register read/write callback](onSet/onSet.ino) +## [Register read/write callback](onSet/onSet.ino) ```c bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); @@ -48,7 +48,7 @@ bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = `numregs` Count of sequental segisters remove this callback to. Disconnect specific callback function or all callbacks of the type if cb=NULL. -[## Incoming request callback (applicable to server/slave)](Request/Request.ino) +## [Incoming request callback (applicable to server/slave)](Request/Request.ino) ```c typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount); @@ -56,22 +56,24 @@ bool onRequest(cbRequest cb = _onRequestDefault); bool onRequestSuccess(cbRequest cb = _onRequestDefault); ``` -Callback function receives Modbus function code, register type and offset (TAddress structure) and count of registers requested. The function should return Modbus::EX_SUCCESS to allow request processing or Modbus error code to block processing. This code will be returned to client/master. +Callback function receives Modbus function code, register type and offset (`TAddress` structure) and count of registers requested. The function should return `Modbus::EX_SUCCESS` to allow request processing or Modbus error code to block processing. This code will be returned to client/master. -[## Modbus TCP/TLS Incoming connection callback](onSet/onSet.ino) +## [Modbus TCP/TLS Incoming connection callback](onSet/onSet.ino) ```c void onConnect(cbModbusConnect cb); void onDisonnect(cbModbusConnect cb); ``` -*Modbus TCP Sserver* Assign callback function on new incoming connection event. +Modbus TCP Server Assign callback function on new incoming connection event. ```c typedef bool (*cbModbusConnect)(IPAddress ip); ``` -*Modbus TCP Sserver* Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. +- `ip` Client's address of incomig connection source. `INADDR_NONE` for on disconnect callback. -[## Modbus TCP/TLS Transaction result](Transactional/Transactional.ino) +## [Modbus TCP/TLS Transaction result](Transactional/Transactional.ino) + +## Other related functions and defenitions \ No newline at end of file diff --git a/examples/RTU/slave/slave.ino b/examples/RTU/slave/slave.ino index f5f0a64..4a96808 100644 --- a/examples/RTU/slave/slave.ino +++ b/examples/RTU/slave/slave.ino @@ -24,6 +24,7 @@ void setup() { mb.begin(&Serial); #else mb.begin(&Serial); + //mb.begin(&Serial, RXTX_PIN); //or use RX/TX direction control pin (if required) mb.setBaudrate(9600); #endif mb.slave(SLAVE_ID); diff --git a/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md new file mode 100644 index 0000000..833f3b9 --- /dev/null +++ b/examples/TCP-ESP/README.md @@ -0,0 +1,42 @@ +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +# ESP8266/ESP32 TCP Examples + +## [Basic client](client.ino) + +```c +void client(); +bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); +bool disconnect(IPAddress ip); +``` + +```c +uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(const char* host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(String host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +- `ip` IP Address of Modbus server to get registers from +- `host` Hostname fo Modbus server to get registers from +- `offset` Address of first Modbus register to read/write + +## [Client with blocking read operation](clientSync.ino) + +```c +bool isTransaction(uint16_t id); +bool isConnected(IPAddress ip); +void dropTransactions(); +``` + +## [Server]](server.ino) + +```c +``` \ No newline at end of file diff --git a/examples/TCP-ESP/clientSync/clientSync.ino b/examples/TCP-ESP/clientSync/clientSync.ino new file mode 100644 index 0000000..7525068 --- /dev/null +++ b/examples/TCP-ESP/clientSync/clientSync.ino @@ -0,0 +1,54 @@ +/* + Modbus Library for Arduino Example - Modbus IP Client (ESP8266/ESP32) + Read Holding Register from Modbus Server in blocking way + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#else +#error "Unsupported platform" +#endif +#include + +const int REG = 528; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device + +ModbusIP mb; //ModbusTCP object + +void setup() { + Serial.begin(115200); + + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.client(); +} + +void loop() { + uint16_t res = 0; + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + uint16_t trans = mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Server + while(mb.isTransaction(trans)) { // Check if transaction is active + mb.task(); + delay(10); + } + Serial.println(res); // At this point res is filled with responce value + } else { + mb.connect(remote); // Try to connect if no connection + } + delay(100); +} \ No newline at end of file diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h index be55b12..dbe2c22 100644 --- a/src/ModbusTCP.h +++ b/src/ModbusTCP.h @@ -14,7 +14,15 @@ #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" -class ModbusTCP : public ModbusAPI> { +class WiFiServerESPWrapper : public WiFiServer { + public: + WiFiServerESPWrapper(uint16_t port) : WiFiServer(port) {} + inline WiFiClient accept() { + return available(); + } +}; + +class ModbusTCP : public ModbusAPI> { private: static IPAddress resolver(const char *host) { IPAddress remote_addr; diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 6c68e43..44e3b1a 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -184,12 +184,13 @@ TTransaction* ModbusTCPTemplate::searchTransaction(uint16_t id) template void ModbusTCPTemplate::task() { MBAP_t _MBAP; + uint32_t taskStart = millis(); cleanupConnections(); if (tcpserver) { - //while (tcpserver->hasClient()) { - //CLIENT* currentClient = new CLIENT(tcpserver->available()); - //while (CLIENT* currentClient = new CLIENT(tcpserver->available())) { - while (CLIENT c = tcpserver->available()) { + CLIENT c; + // WiFiServer.available() == Ethernet.accept() and should wrapped to get code to be compatible with Ethernet library (See ModbusTCP.h code). + // WiFiServer.accept() != Ethernet.accept() internally + while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) { CLIENT* currentClient = new CLIENT(c); if (!currentClient || !currentClient->connected()) continue; @@ -217,8 +218,7 @@ void ModbusTCPTemplate::task() { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (!tcpclient[n]) continue; if (!tcpclient[n]->connected()) continue; - uint32_t readStart = millis(); - while (millis() - readStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { + while (millis() - taskStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. From 1d8541acbf3ba7f49b4605f14f2de68702d11a68 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 25 Oct 2020 13:14:15 +0300 Subject: [PATCH 203/288] ModbusRTU stability improvment - .task() refactoring - Fixed possible wrong transactional callback call - Examples rearranged - Documentation update (in-progress) --- README.md | 21 ++- .../IP-server-MultipleHRegDebug.ino | 158 ++++++++--------- examples/Callback/README.md | 51 ++++-- .../onGetShared/onGetShared.ino} | 160 +++++++++--------- examples/README.md | 24 ++- examples/RTU/README.MD | 6 +- .../IP-server-AnalogInput.ino | 0 .../IP-server-Led/IP-server-Led.ino | 0 .../IP-server-SwitchStatus.ino | 0 examples/TCP-ESP/README.md | 77 ++++++++- .../clientPull/clientPull.ino} | 142 ++++++++-------- examples/TLS/README.md | 2 +- .../IP-bridge.ino => bridge/bridge.ino} | 0 src/ModbusAPI.h | 8 +- src/ModbusRTU.cpp | 29 ++-- src/ModbusSettings.h | 1 + src/ModbusTCPTemplate.h | 4 +- 17 files changed, 402 insertions(+), 281 deletions(-) rename examples/{ => Callback}/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino (95%) rename examples/{IP-server-MassOperations/IP-server-MassOperations.ino => Callback/onGetShared/onGetShared.ino} (96%) rename examples/{ => TCP-ESP}/IP-server-AnalogInput/IP-server-AnalogInput.ino (100%) rename examples/{ => TCP-ESP}/IP-server-Led/IP-server-Led.ino (100%) rename examples/{ => TCP-ESP}/IP-server-SwitchStatus/IP-server-SwitchStatus.ino (100%) rename examples/{IP-client-Pull/IP-client-Pull.ino => TCP-ESP/clientPull/clientPull.ino} (96%) rename examples/{IP-bridge/IP-bridge.ino => bridge/bridge.ino} (100%) diff --git a/README.md b/README.md index 4fb8f36..b99452d 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,12 @@ For more information about Modbus see: ## Features -* Supported platforms are - * ESP8266 - * ESP32 - * STM32F103 and probably others (Modbus RTU only) +* Supports all Arduino platforms * Operates in any combination of multiple instances of * [Modbus RTU slave](examples/RTU) * [Modbus RTU master](examples/RTU) - * Modbus TCP server - * Modbus TCP client + * Modbus TCP server for [ESP8266/ESP32](examples/TCP) and [Ethernet library](examples/TCP-Ethernet) + * Modbus TCP client for [ESP8266/ESP32](examples/TCP) and [Ethernet library](examples/TCP-Ethernet) * [MODBUS/TCP Security server (ESP8266)](examples/TLS) * [MODBUS/TCP Security client (ESP8266/ESP32)](examples/TLS) * Reply exception messages for all supported functions @@ -77,9 +74,11 @@ For more information about Modbus see: + Build with no STL dependency - Test: No-STL mode + ModbusTCP: ModbusEthernet - W5x00 Ethernet library support -- Test: W5x00 support ++ Test: W5x00 support +- Test: W5x00 with Ethernet library v1 + API: Implementation code merge -+ API: Access ModbusTCP server by name ++ API: Access ModbusTCP server by name ++ API: Set local multiple registers from an array + ModbusIP => ModbusTCP + 0x14 - Read File Records function + Test: 0x14 @@ -91,7 +90,7 @@ For more information about Modbus see: - Test: 0x16 - 0x17 - Read/Write Registers function - Test: 0x17 -+ Access control callback for individual function ++ API: Access control callback for individual function - Slave/Server: slavePDU use early exit by return where possible - Master/Client: Check frame size against header data where possible - Master/Client: Additional responce data validation @@ -99,6 +98,10 @@ For more information about Modbus see: - Test: Frame accuracy to specefication - Documentation: Update - Examples: Revising ++ SoftwareSerial support for ESP32 +// 4.1.0 +- ModbusTLS: ESP32 Server +- Test: TLS ESP32 Server // 3.0.2 + ModbusTCP Client: ESP32 fix unexpected transaction timeout // 3.0.1 diff --git a/examples/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino b/examples/Callback/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino similarity index 95% rename from examples/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino rename to examples/Callback/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino index cdf68af..7f717b1 100644 --- a/examples/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino +++ b/examples/Callback/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino @@ -1,79 +1,79 @@ -/* - Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) - - Original library - Copyright by André Sarmento Barbosa - http://github.com/andresarmento/modbus-arduino - - Current version - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else //ESP32 - #include -#endif -#include - -#define LEN 10 - -//ModbusIP object -ModbusIP mb; - -// Callback function to read corresponding DI -uint16_t cbRead(TRegister* reg, uint16_t val) { - Serial.print("Read. Reg RAW#: "); - Serial.print(reg->address.address); - Serial.print(" Old: "); - Serial.print(reg->value); - Serial.print(" New: "); - Serial.println(val); - return val; -} -// Callback function to write-protect DI -uint16_t cbWrite(TRegister* reg, uint16_t val) { - Serial.print("Write. Reg RAW#: "); - Serial.print(reg->address.address); - Serial.print(" Old: "); - Serial.print(reg->value); - Serial.print(" New: "); - Serial.println(val); - return val; -} - -// Callback function for client connect. Returns true to allow connection. -bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; -} - -void setup() { - Serial.begin(115200); - - WiFi.begin("ssid", "pass"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - mb.onConnect(cbConn); // Add callback on connection event - mb.server(); - - if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs - mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get - mb.onSetHreg(0, cbWrite, LEN); -} - -void loop() { - //Call once inside loop() - all magic here - mb.task(); - delay(10); -} +/* + Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +#define LEN 10 + +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + Serial.print("Read. Reg RAW#: "); + Serial.print(reg->address.address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + Serial.print("Write. Reg RAW#: "); + Serial.print(reg->address.address); + Serial.print(" Old: "); + Serial.print(reg->value); + Serial.print(" New: "); + Serial.println(val); + return val; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("ssid", "pass"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.server(); + + if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs + mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get + mb.onSetHreg(0, cbWrite, LEN); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(10); +} diff --git a/examples/Callback/README.md b/examples/Callback/README.md index 8aa277e..cb4a89e 100644 --- a/examples/Callback/README.md +++ b/examples/Callback/README.md @@ -1,6 +1,3 @@ -# Modbus Library for Arduino -### ModbusRTU, ModbusTCP and ModbusTCP Security - # Callbacks ## [Register read/write callback](onSet/onSet.ino) @@ -12,9 +9,9 @@ bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); ``` -`address` Address of register assign callback on -`cb` Callback function -`numregs` Count of sequental segisters assign this callback to +- `address` Address of register assign callback on +- `cb` Callback function +- `numregs` Count of sequental segisters assign this callback to Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. @@ -26,9 +23,9 @@ bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); ``` -`address` Address of register assign callback on -`cb` Callback function -`numregs` Count of sequental segisters assign this callback to +- `address` Address of register assign callback on +- `cb` Callback function +- `numregs` Count of sequental segisters assign this callback to Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. @@ -43,11 +40,14 @@ bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); ``` -`address` Address of register assign callback on -`cb` Callback function or NULL to remove all the callbacks. -`numregs` Count of sequental segisters remove this callback to. +- `address` Address of register assign callback on +- `cb` Callback function or NULL to remove all the callbacks. +- `numregs` Count of sequental segisters remove this callback to. + Disconnect specific callback function or all callbacks of the type if cb=NULL. +## [Use one callback function for multiple registers](onGetShared/onGetShared.ino) + ## [Incoming request callback (applicable to server/slave)](Request/Request.ino) ```c @@ -56,7 +56,7 @@ bool onRequest(cbRequest cb = _onRequestDefault); bool onRequestSuccess(cbRequest cb = _onRequestDefault); ``` -Callback function receives Modbus function code, register type and offset (`TAddress` structure) and count of registers requested. The function should return `Modbus::EX_SUCCESS` to allow request processing or Modbus error code to block processing. This code will be returned to client/master. +Callback function receives Modbus function code, register type and offset (`TAddress` structure) and count of registers requested. The function should return [result code](#Result codes *Modbus::ResultCode*) `Modbus::EX_SUCCESS` to allow request processing or Modbus error code to block processing. This code will be returned to client/master. ## [Modbus TCP/TLS Incoming connection callback](onSet/onSet.ino) @@ -65,7 +65,7 @@ void onConnect(cbModbusConnect cb); void onDisonnect(cbModbusConnect cb); ``` -Modbus TCP Server Assign callback function on new incoming connection event. +Assign callback function on incoming connection event. ```c typedef bool (*cbModbusConnect)(IPAddress ip); @@ -75,5 +75,26 @@ typedef bool (*cbModbusConnect)(IPAddress ip); ## [Modbus TCP/TLS Transaction result](Transactional/Transactional.ino) +## Result codes *Modbus::ResultCode* + +|Value|Hex|Definition|Decription| +|---|---|---|---| +|Modbus::EX_SUCCESS|0x00|Custom|No error| +|Modbus::EX_ILLEGAL_FUNCTION|0x01|Modbus|Function Code not Supported| +|Modbus::EX_ILLEGAL_ADDRESS|0x02|Modbus|Output Address not exists| +|Modbus::EX_ILLEGAL_VALUE|0x03|Modbus|Output Value not in Range| +|Modbus::EX_SLAVE_FAILURE|0x04|Modbus|Slave or Master Device Fails to process request +|Modbus::EX_ACKNOWLEDGE|0x05|Modbus|Not used| +|Modbus::EX_SLAVE_DEVICE_BUSY|0x06|Modbus|Not used| +|Modbus::EX_MEMORY_PARITY_ERROR|0x08|Modbus|Not used| +|Modbus::EX_PATH_UNAVAILABLE|0x0A|Modbus|Not used| +|Modbus::EX_DEVICE_FAILED_TO_RESPOND|0x0B|Modbus|Not used| +|Modbus::EX_GENERAL_FAILURE|0xE1|Custom|Unexpected master error| +|Modbus::EX_DATA_MISMACH|0xE2|Custom|Inpud data size mismach| +|Modbus::EX_UNEXPECTED_RESPONSE|0xE3|Custom|Returned result doesn't mach transaction| +|Modbus::EX_TIMEOUT|0xE4|Custom|Operation not finished within reasonable time| +|Modbus::EX_CONNECTION_LOST|0xE5|Custom|Connection with device lost| +|Modbus::EX_CANCEL|0xE6|Custom|Transaction/request canceled| -## Other related functions and defenitions \ No newline at end of file +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security \ No newline at end of file diff --git a/examples/IP-server-MassOperations/IP-server-MassOperations.ino b/examples/Callback/onGetShared/onGetShared.ino similarity index 96% rename from examples/IP-server-MassOperations/IP-server-MassOperations.ino rename to examples/Callback/onGetShared/onGetShared.ino index 12cf3bb..554e114 100644 --- a/examples/IP-server-MassOperations/IP-server-MassOperations.ino +++ b/examples/Callback/onGetShared/onGetShared.ino @@ -1,81 +1,81 @@ -/* - Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32) - - Original library - Copyright by André Sarmento Barbosa - http://github.com/andresarmento/modbus-arduino - - Current version - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else //ESP32 - #include -#endif -#include - -//Used Pins -#ifdef ESP8266 - uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; -#else //ESP32 - uint8_t pinList[] = {12, 13, 14, 14, 16, 17, 18, 21, 22, 23}; -#endif -#define LEN sizeof(pinList)/sizeof(uint8_t) -#define COIL_BASE 0 -//ModbusIP object -ModbusIP mb; - -// Callback function to read corresponding DI -uint16_t cbRead(TRegister* reg, uint16_t val) { - // Checking value of register address which callback is called on. - // See Modbus.h for TRegister and TAddress definition - if(reg->address.address < COIL_BASE) - return 0; - uint8_t offset = reg->address.address - COIL_BASE; - if(offset >= LEN) - return 0; - return COIL_VAL(digitalRead(pinList[offset])); -} -// Callback function to write-protect DI -uint16_t cbWrite(TRegister* reg, uint16_t val) { - return reg->value; -} - -// Callback function for client connect. Returns true to allow connection. -bool cbConn(IPAddress ip) { - Serial.println(ip); - return true; -} - -void setup() { - Serial.begin(115200); - - WiFi.begin("ssid", "password"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - for (uint8_t i = 0; i < LEN; i++) - pinMode(pinList[i], INPUT); - mb.onConnect(cbConn); // Add callback on connection event - mb.server(); - - mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. - mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add single callback for multiple Coils. It will be called for each of these coils value get - mb.onSetCoil(COIL_BASE, cbWrite, LEN); // The same as above just for set value -} - -void loop() { - //Call once inside loop() - all magic here - mb.task(); - delay(10); +/* + Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Used Pins +#ifdef ESP8266 + uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; +#else //ESP32 + uint8_t pinList[] = {12, 13, 14, 14, 16, 17, 18, 21, 22, 23}; +#endif +#define LEN sizeof(pinList)/sizeof(uint8_t) +#define COIL_BASE 0 +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + // Checking value of register address which callback is called on. + // See Modbus.h for TRegister and TAddress definition + if(reg->address.address < COIL_BASE) + return 0; + uint8_t offset = reg->address.address - COIL_BASE; + if(offset >= LEN) + return 0; + return COIL_VAL(digitalRead(pinList[offset])); +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + return reg->value; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("ssid", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + for (uint8_t i = 0; i < LEN; i++) + pinMode(pinList[i], INPUT); + mb.onConnect(cbConn); // Add callback on connection event + mb.server(); + + mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. + mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add single callback for multiple Coils. It will be called for each of these coils value get + mb.onSetCoil(COIL_BASE, cbWrite, LEN); // The same as above just for set value +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(10); } \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 5d526a6..92b0e7a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1,22 @@ -# Modbus Library for Arduino -### ModbusRTU, ModbusTCP and ModbusTCP Security - # Examples -[## RTU](RTU) +## [RTU](RTU) + +## [TCP ESP8266/ESP32](TCP-ESP) + +## [TCP Ethernet W5x00](TCP-Ethernet) + +## [TLS ESP8266/ESP32](TLS) -[##TCP ESP8266/ESP32](TCP) +## [Callbacks usage](Callback) + +## [Files operations](Files) + +## [Basic Mosbus RTU to Modbus TCP bridge](bridge) + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security -[##TCP Ethernet W5x00](TCP-Ethernet) +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) -[##TLS](TLS) +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index d391b1a..7d08643 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -39,4 +39,8 @@ Slave mode: Returns configured slave id. Master mode: Returns slave id for activ void loop() { } -``` \ No newline at end of file +``` + +## ESP32 Slave notes + +For achive stable results it's not recommended to use high pooling rate \ No newline at end of file diff --git a/examples/IP-server-AnalogInput/IP-server-AnalogInput.ino b/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino similarity index 100% rename from examples/IP-server-AnalogInput/IP-server-AnalogInput.ino rename to examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino diff --git a/examples/IP-server-Led/IP-server-Led.ino b/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino similarity index 100% rename from examples/IP-server-Led/IP-server-Led.ino rename to examples/TCP-ESP/IP-server-Led/IP-server-Led.ino diff --git a/examples/IP-server-SwitchStatus/IP-server-SwitchStatus.ino b/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino similarity index 100% rename from examples/IP-server-SwitchStatus/IP-server-SwitchStatus.ino rename to examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino diff --git a/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md index 833f3b9..f5f8f56 100644 --- a/examples/TCP-ESP/README.md +++ b/examples/TCP-ESP/README.md @@ -7,6 +7,9 @@ ```c void client(); +``` + +```c bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); bool disconnect(IPAddress ip); ``` @@ -27,16 +30,88 @@ uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numre - `ip` IP Address of Modbus server to get registers from - `host` Hostname fo Modbus server to get registers from - `offset` Address of first Modbus register to read/write +- `numregs` Count of registers to read/write +- `cb` Transaction callback function (see [Calback examples](../calback) for details). `NULL` if not used +- `unit` Modbus unit + +Sends corresponding Modbus read request to Modbus server at `ip`. Connection with server shoud be already established by connect(ip). +Returns transaction `id` or `0` on failure. ## [Client with blocking read operation](clientSync.ino) ```c bool isTransaction(uint16_t id); +``` + +Returns `true` if transaction with `id` is active. + +```c bool isConnected(IPAddress ip); +``` + +Returns `true` is connection with Modbus server at `ip` is established. + +```c void dropTransactions(); ``` +Cancel all active transactions. Callback with result code `Modbus::EX_CANCEL` will be called for each transaction (if assigned). + ## [Server]](server.ino) +### Add local register +```c +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 nemregs = 1); +``` + +- `offset` Address of the first register to add +- `value` Initial value to be assigned to register(s) +- `numregs` Count of registers to be created + +Adding new register(s) and assigning value(s). If [some] registers already exists value will be updated. +Returns `true` on success. `false` if operation is failed for some reason. + +### Write local reg + ```c -``` \ No newline at end of file +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); +``` + +- `offset` Address of the register +- `value` Value to be assigned to register + +Returns `true` on success. `false` if register not previousely added or other error. + +### Read local reg + +```c +uint16_t Hreg(uint16_t offset); +bool Coil(uint16_t offset); +bool Ists(uint16_t offset); +uint16_t Ireg(uint16_t offset); +``` + +- `offset` Address of the first register to add + +Returns current value of the register. + +### Remove reg(s) + +```c +bool removeHreg(uint16_t offset, uint16_t numregs = 1); +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); +``` + +- `offset` Address of the first register to remove +- `numregs` Count of registers to be created + +Function trying to remove `numregs` registers starting from `offset`. If some of registers within the range are not exists removal continues execution. +Returns `true` if atleast one register in the range was removed. \ No newline at end of file diff --git a/examples/IP-client-Pull/IP-client-Pull.ino b/examples/TCP-ESP/clientPull/clientPull.ino similarity index 96% rename from examples/IP-client-Pull/IP-client-Pull.ino rename to examples/TCP-ESP/clientPull/clientPull.ino index 0a28835..2098004 100644 --- a/examples/IP-client-Pull/IP-client-Pull.ino +++ b/examples/TCP-ESP/clientPull/clientPull.ino @@ -1,71 +1,71 @@ -/* - Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) - Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function - - (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) - https://github.com/emelianov/modbus-esp8266 -*/ - -#ifdef ESP8266 - #include -#else - #include -#endif -#include - - -const int LED_COIL = 1; // Modbus Coil Offset -IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device - -//Used Pins -#ifdef ESP8266 - #define USE_LED D4 - #else - #define UES_LED TX - #endif - -ModbusIP mb; //ModbusIP object - -uint16_t gc(TRegister* r, uint16_t v) { // Callback function - if (r->value != v) { // Check if Coil state is going to be changed - Serial.print("Set reg: "); - Serial.println(v); - if (COIL_BOOL(v)) { - digitalWrite(USE_LED, LOW); - } else { - digitalWrite(USE_LED, HIGH); - } - } - return v; -} - -void setup() { - Serial.begin(115200); - - WiFi.begin("SSID", "password"); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - - mb.client(); // Initialize local Modbus Client - pinMode(USE_LED, OUTPUT); - mb.addCoil(LED_COIL); // Add Coil - mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil -} - -void loop() { - if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established - mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave - } else { - mb.connect(remote); // Try to connect if no connection - } - mb.task(); // Common local Modbus task - delay(10); // Polling interval -} +/* + Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) + Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function + + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + + +const int LED_COIL = 1; // Modbus Coil Offset +IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device + +//Used Pins +#ifdef ESP8266 + #define USE_LED D4 + #else + #define UES_LED TX + #endif + +ModbusIP mb; //ModbusIP object + +uint16_t gc(TRegister* r, uint16_t v) { // Callback function + if (r->value != v) { // Check if Coil state is going to be changed + Serial.print("Set reg: "); + Serial.println(v); + if (COIL_BOOL(v)) { + digitalWrite(USE_LED, LOW); + } else { + digitalWrite(USE_LED, HIGH); + } + } + return v; +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("SSID", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.client(); // Initialize local Modbus Client + pinMode(USE_LED, OUTPUT); + mb.addCoil(LED_COIL); // Add Coil + mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil +} + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + } + mb.task(); // Common local Modbus task + delay(10); // Polling interval +} diff --git a/examples/TLS/README.md b/examples/TLS/README.md index 9a97d71..7c90ce0 100644 --- a/examples/TLS/README.md +++ b/examples/TLS/README.md @@ -1,6 +1,6 @@ # Modbus\TCP Security Example -*Target Platforms:* +### *Target Platforms:* - *ESP8266 (CLient/Server)* - *ESP32 (Client only)* diff --git a/examples/IP-bridge/IP-bridge.ino b/examples/bridge/bridge.ino similarity index 100% rename from examples/IP-bridge/IP-bridge.ino rename to examples/bridge/bridge.ino diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 9eb65c1..ef50721 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -30,10 +30,10 @@ class ModbusAPI : public T { uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); */ // Legacy API - bool Hreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(HREG(offset), value);} - bool Coil(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(COIL(offset), value);} - bool Ists(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(ISTS(offset), value);} - bool Ireg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(IREG(offset), value);} + 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);} diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 17beb2e..62c5092 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -104,25 +104,31 @@ void ModbusRTUTemplate::task() { if (_port->available() > _len) { _len = _port->available(); t = millis(); - #if defined(ESP32) - portEXIT_CRITICAL(&mux); - #endif - return; } - if (_len != 0 && millis() - t < _t) { // Wait data whitespace if there is data + if (_len == 0) { #if defined(ESP32) portEXIT_CRITICAL(&mux); #endif + if (isMaster) cleanup(); return; } + uint32_t taskStart = millis(); + while (millis() - t < _t) { // Wait data whitespace + if (_port->available() > _len) { + _len = _port->available(); + t = millis(); + } + if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long + #if defined(ESP32) + portEXIT_CRITICAL(&mux); + #endif + return; + } + } #if defined(ESP32) portEXIT_CRITICAL(&mux); #endif - if (_len == 0) { - if (isMaster) cleanup(); - return; - } uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte if (isMaster && _slaveId == 0) { // Check if slaveId is set @@ -172,6 +178,7 @@ void ModbusRTUTemplate::task() { masterPDU(_frame, _sentFrame, _sentReg, _data); if (_cb) { _cb((ResultCode)_reply, 0, nullptr); + _cb = nullptr; } free(_sentFrame); _sentFrame = nullptr; @@ -183,9 +190,9 @@ void ModbusRTUTemplate::task() { slavePDU(_frame); if (address == MODBUSRTU_BROADCAST) _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + if (_reply != Modbus::REPLY_OFF) + rawSend(_slaveId, _frame, _len); } - if (_reply != Modbus::REPLY_OFF) - rawSend(_slaveId, _frame, _len); // Cleanup free(_frame); _frame = nullptr; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 618218e..2971a5a 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -60,6 +60,7 @@ If defined regisers count will be limited. #define MB_SERIAL_BUFFER 128 #define MB_MAX_TIME 10 #define MODBUSRTU_TIMEOUT 1000 +#define MODBUSRTU_MAX_READMS 100 #define MODBUSAPI_LEGACY #define MODBUSAPI_OPTIONAL \ No newline at end of file diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 44e3b1a..5de8561 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -12,7 +12,7 @@ #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 (IPAddress(0,0,0,0)) +#define IPADDR_NONE ((u32_t)0xffffffffUL) #endif // Callback function Type typedef bool (*cbModbusConnect)(IPAddress ip); @@ -189,7 +189,7 @@ void ModbusTCPTemplate::task() { 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.accept() != Ethernet.accept() internally + // WiFiServer.available() != Ethernet.available() internally while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) { CLIENT* currentClient = new CLIENT(c); if (!currentClient || !currentClient->connected()) From d5f34442afbe38ad2ff8aef982c5839c977f6106 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 26 Oct 2020 15:39:14 +0300 Subject: [PATCH 204/288] Critical fixes - ModbusIP: Fixed missed server connection free in destructor - Fixed addition of missing registers in .poll*() calls - ModbusRTU: Fixed callback remains assigned after transaction ends --- README.md | 4 ++++ src/ModbusIP_ESP8266.cpp | 14 ++++++++------ src/ModbusRTU.cpp | 17 ++++++++++------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ba14700..1500724 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 3.0.4 ++ ModbusIP: Fixed missed server connection free in destructor ++ Fixed addition of missing registers in .poll*() calls ++ ModbusRTU: Fixed callback remains assigned after transaction ends // 3.0.3 + ModbusRTU: Add inverse logic support for transmit control // 3.0.2 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 6f80d3d..8a6065e 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -306,7 +306,7 @@ uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t n uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSIP_ADD_REG - addCoil(to, numregs); + addCoil(to, false, numregs); #endif readSlave(from, numregs, FC_READ_COILS); return send(ip, COIL(to), cb, unit); @@ -315,7 +315,7 @@ uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t n uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSIP_ADD_REG - addIsts(to, numregs); + addIsts(to, false, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_STAT); return send(ip, ISTS(to), cb, unit); @@ -335,7 +335,7 @@ uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t n uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007D) return false; #ifdef MODBUSIP_ADD_REG - addHreg(to, numregs); + addHreg(to, 0, numregs); #endif readSlave(from, numregs, FC_READ_REGS); return send(ip, HREG(to), cb, unit); @@ -344,7 +344,7 @@ uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t n uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007D) return false; #ifdef MODBUSIP_ADD_REG - addIreg(to, numregs); + addIreg(to, 0, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_REGS); return send(ip, IREG(to), cb, unit); @@ -375,7 +375,7 @@ uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x007D) return false; #ifdef MODBUSIP_ADD_REG - addIreg(to, numregs); + addIreg(to, 0, numregs); #endif readSlave(from, numregs, FC_READ_REGS); return send(ip, IREG(to), cb, unit); @@ -384,7 +384,7 @@ uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { if (numregs < 0x0001 || numregs > 0x07D0) return false; #ifdef MODBUSIP_ADD_REG - addIsts(to, numregs); + addIsts(to, false, numregs); #endif readSlave(from, numregs, FC_READ_COILS); return send(ip, ISTS(to), cb, unit); @@ -420,6 +420,8 @@ ModbusIP::~ModbusIP() { dropTransactions(); cleanupConnections(); cleanupTransactions(); + delete tcpserver; + tcpserver = nullptr; for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { delete tcpclient[i]; tcpclient[i] = nullptr; diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 3a544a6..a494dd3 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -213,6 +213,7 @@ void ModbusRTU::task() { masterPDU(_frame, _sentFrame, _sentReg, _data); if (_cb) { _cb((ResultCode)_reply, 0, nullptr); + _cb = nullptr; } free(_sentFrame); _sentFrame = nullptr; @@ -238,8 +239,10 @@ void ModbusRTU::task() { bool ModbusRTU::cleanup() { // Remove timeouted request and forced event if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { - if (_cb) + if (_cb) { _cb(Modbus::EX_TIMEOUT, 0, nullptr); + _cb = nullptr; + } free(_sentFrame); _sentFrame = nullptr; _data = nullptr; @@ -317,7 +320,7 @@ uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16 uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > maxRegs << 4) return false; #ifdef MODBUSRTU_ADD_REG - addCoil(to, numregs); + addCoil(to, false, numregs); #endif readSlave(from, numregs, FC_READ_COILS); return send(slaveId, COIL(to), cb); @@ -327,7 +330,7 @@ uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16 uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > maxRegs << 4) return false; #ifdef MODBUSRTU_ADD_REG - addIsts(to, numregs); + addIsts(to, false, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_STAT); return send(slaveId, ISTS(to), cb); @@ -349,7 +352,7 @@ uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16 uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > maxRegs) return false; #ifdef MODBUSRTU_ADD_REG - addHreg(to, numregs); + addHreg(to, 0, numregs); #endif readSlave(from, numregs, FC_READ_REGS); return send(slaveId, HREG(to), cb); @@ -359,7 +362,7 @@ uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16 uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > maxRegs) return false; #ifdef MODBUSRTU_ADD_REG - addIreg(to, numregs); + addIreg(to, 0, numregs); #endif readSlave(from, numregs, FC_READ_INPUT_REGS); return send(slaveId, IREG(to), cb); @@ -393,7 +396,7 @@ uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > maxRegs) return false; #ifdef MODBUSRTU_ADD_REG - addIreg(to, numregs); + addIreg(to, 0, numregs); #endif readSlave(from, numregs, FC_READ_REGS); return send(slaveId, IREG(to), cb); @@ -403,7 +406,7 @@ uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > maxRegs << 4) return false; #ifdef MODBUSRTU_ADD_REG - addIsts(to, numregs); + addIsts(to, false, numregs); #endif readSlave(from, numregs, FC_READ_COILS); return send(slaveId, ISTS(to), cb); From dc53a694f63d810a272115380f3b32a69ed5b96c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 27 Oct 2020 19:20:33 +0300 Subject: [PATCH 205/288] Fixes - ModbusRTU: Fix transaction callback remains assigned after request end - ModbusTCP: Free server connection in destructor - Declare global registers and callbacks as ststic members - ModbusRTU: Fixed possible packet lost because of wrong cleanup after send - ModbusRTU: Slight optimezation .task() for Master --- README.md | 5 +++++ src/Modbus.cpp | 8 ++++---- src/Modbus.h | 14 ++++++++++++-- src/ModbusAPI.h | 7 ++++++- src/ModbusRTU.cpp | 41 ++++++++++++++++++++++++++++------------- src/ModbusTCPTemplate.h | 2 ++ 6 files changed, 57 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b99452d..0567ab7 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,11 @@ For more information about Modbus see: - Documentation: Update - Examples: Revising + SoftwareSerial support for ESP32 ++ ModbusRTU: Fix transaction callback remains assigned after request end ++ ModbusTCP: Free server connection in destructor ++ Declare global registers and callbacks as ststic members +- Free global registers and callbacks on remove last Modbus instance ++ ModbusRTU: Refactor .task() for relaibe processing of incoming data // 4.1.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server diff --git a/src/Modbus.cpp b/src/Modbus.cpp index c69037c..6d28b02 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -8,11 +8,11 @@ #if defined(MODBUS_GLOBAL_REGS) #if defined(MODBUS_USE_STL) - std::vector _regs; - std::vector _callbacks; + std::vector Modbus::_regs; + std::vector Modbus::_callbacks; #else - DArray _regs; - DArray _callbacks; + DArray Modbus::_regs; + DArray Modbus::_callbacks; #endif cbModbusFileOp _onFile; #endif diff --git a/src/Modbus.h b/src/Modbus.h index 0a4b8f6..feab2f5 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -156,19 +156,29 @@ class Modbus { REPLY_ERROR = 0x04, REPLY_UNEXPECTED = 0x05 }; - #ifndef MB_GLOBAL_REGS #if defined(MODBUS_USE_STL) + #if defined(MODBUS_GLOBAL_REGS) + static + #endif std::vector _regs; + #if defined(MODBUS_GLOBAL_REGS) + static + #endif std::vector _callbacks; #else + #if defined(MODBUS_GLOBAL_REGS) + static + #endif DArray _regs; + #if defined(MODBUS_GLOBAL_REGS) + static + #endif DArray _callbacks; #endif #if defined(MODBUS_FILES) Modbus::ResultCode (*_onFile)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*) = nullptr; #endif - #endif uint8_t* _frame = nullptr; uint16_t _len = 0; uint8_t _reply = 0; diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index ef50721..bd0ae73 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -168,12 +168,17 @@ IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, 0x007D, uint16_t) IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0, bool) IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, 0x007D, uint16_t) +#if defined(MODBUS_ADD_REG) +#define ADDREG this->addReg(REG(to), 0, numregs); \ +#else +#define ADDREG +#endif #define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \ template \ template \ uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ if (numregs < 0x0001 || numregs > MAXNUM) return false; \ - this->addCoil(to, numregs); \ + ADDREG \ this->readSlave(from, numregs, Modbus::FUNC); \ return this->send(ip, REG(to), cb, unit); \ } diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 62c5092..d0c5aac 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -92,14 +92,17 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti _sentFrame = _frame; _sentReg = startreg; _frame = nullptr; - _len = 0; + //_len = 0; } + free(_frame); + _frame = nullptr; + _len = 0; return true; } void ModbusRTUTemplate::task() { #if defined(ESP32) - portENTER_CRITICAL(&mux); + taskENTER_CRITICAL(&mux); #endif if (_port->available() > _len) { _len = _port->available(); @@ -107,26 +110,36 @@ void ModbusRTUTemplate::task() { } if (_len == 0) { #if defined(ESP32) - portEXIT_CRITICAL(&mux); + taskEXIT_CRITICAL(&mux); #endif if (isMaster) cleanup(); return; } - uint32_t taskStart = millis(); - while (millis() - t < _t) { // Wait data whitespace - if (_port->available() > _len) { - _len = _port->available(); - t = millis(); - } - if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long + if (isMaster) { + if (millis() - t < _t) { #if defined(ESP32) - portEXIT_CRITICAL(&mux); + taskEXIT_CRITICAL(&mux); #endif return; } } + else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached) + uint32_t taskStart = millis(); + while (millis() - t < _t) { // Wait data whitespace + if (_port->available() > _len) { + _len = _port->available(); + t = millis(); + } + if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long + #if defined(ESP32) + taskEXIT_CRITICAL(&mux); + #endif + return; + } + } + } #if defined(ESP32) - portEXIT_CRITICAL(&mux); + taskEXIT_CRITICAL(&mux); #endif uint8_t address = _port->read(); //first byte of frame = address @@ -203,8 +216,10 @@ void ModbusRTUTemplate::task() { bool ModbusRTUTemplate::cleanup() { // Remove timeouted request and forced event if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { - if (_cb) + if (_cb) { _cb(Modbus::EX_TIMEOUT, 0, nullptr); + _cb = nullptr; + } free(_sentFrame); _sentFrame = nullptr; _data = nullptr; diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 5de8561..c722009 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -494,6 +494,8 @@ ModbusTCPTemplate::~ModbusTCPTemplate() { dropTransactions(); cleanupConnections(); cleanupTransactions(); + delete tcpserver; + tcpserver = nullptr; for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { delete tcpclient[i]; tcpclient[i] = nullptr; From 8504e0124e6cc7a5e0108ccc48be659ce5752b83 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 27 Oct 2020 20:54:48 +0300 Subject: [PATCH 206/288] ModbusRTU: Fix possible data loss and slight .task() optimezation It were no cleanup of _len and _frame if .rawSend() is called with no callback --- README.md | 1 + src/ModbusRTU.cpp | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1500724..226a2bc 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) + ModbusIP: Fixed missed server connection free in destructor + Fixed addition of missing registers in .poll*() calls + ModbusRTU: Fixed callback remains assigned after transaction ends ++ ModbusRTU: Fixed possible packet loss because of wrong cleanup after data sent. // 3.0.3 + ModbusRTU: Add inverse logic support for transmit control // 3.0.2 diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index a494dd3..db5a1cb 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -76,7 +76,6 @@ bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin, bool direct) { pinMode(_txPin, OUTPUT); digitalWrite(_txPin, _direct?LOW:HIGH); } - Serial.println(_t); return true; } @@ -135,6 +134,9 @@ bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* _frame = nullptr; _len = 0; } + free(_frame); + _frame = nullptr; + _len = 0; return true; } @@ -150,7 +152,16 @@ void ModbusRTU::task() { #endif return; } - if (_len != 0 && millis() - t < _t) { // Wait data whitespace if there is data + + if (_len == 0) { + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif + if (isMaster) cleanup(); + return; + } + + if (millis() - t < _t) { // Wait data whitespace if there is data #ifdef ESP32 portEXIT_CRITICAL(&mux); #endif @@ -160,10 +171,6 @@ void ModbusRTU::task() { portEXIT_CRITICAL(&mux); #endif - if (_len == 0) { - if (isMaster) cleanup(); - return; - } uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte if (isMaster && _slaveId == 0) { // Check if slaveId is set From c145238c6ae841555ff7632ca368b79310b8cd40 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 29 Oct 2020 07:52:39 +0300 Subject: [PATCH 207/288] Fix bridge example --- examples/IP-bridge/IP-bridge.ino | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/examples/IP-bridge/IP-bridge.ino b/examples/IP-bridge/IP-bridge.ino index bfea948..acf71c0 100644 --- a/examples/IP-bridge/IP-bridge.ino +++ b/examples/IP-bridge/IP-bridge.ino @@ -5,21 +5,40 @@ (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 */ - +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif #include #include #define TO_REG 10 #define SLAVE_ID 1 -#define PULL_ID 2 +#define PULL_ID 1 #define FROM_REG 20 ModbusRTU mb1; ModbusIP mb2; void setup() { - Serial1.begin(9600, SERIAL_8N1); + Serial.begin(115200); + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + Serial1.begin(9600, SERIAL_8N1); // Init Serial on default pins + //Serial2.begin(19200, SERIAL_8N1, 19, 18); // Override default pins for ESP32 mb1.begin(&Serial1); + //mb1.begin(&Serial2, 17); // Specify RE_DE control pin mb1.master(); mb2.server(); mb2.addHreg(TO_REG); From 980cbb4ab6f33190ae90946b56eb0ed355e5d5f7 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 29 Oct 2020 08:45:13 +0300 Subject: [PATCH 208/288] Fix bridge example --- README.md | 22 +--------------------- examples/README.md | 18 ++++++++++++++++-- examples/RTU/README.MD | 29 +++++++++++++---------------- examples/TCP-ESP/README.md | 14 +++++++++++++- examples/TLS/README.md | 2 -- examples/bridge/bridge.ino | 25 ++++++++++++++++++++++--- 6 files changed, 65 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 0567ab7..0251aa4 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ For more information about Modbus see: ## Last Changes ```diff -// 4.0.0.DEVEL +// 4.0.0.DEV + ModbusTLS: Modbus TCP Security Client/Server + ModbusTLS: ESP8266 Client/Server + Test: TLS ESP8266 Client/Server @@ -107,26 +107,6 @@ For more information about Modbus see: // 4.1.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server -// 3.0.2 -+ ModbusTCP Client: ESP32 fix unexpected transaction timeout -// 3.0.1 -+ ModbusRTU: ESP32 possible send\receive failure fix -+ ModbusRTU: Non-ESP devices support -+ Restriction to registers count removed -+ Added bridge example -// 3.0.0 -+ ModbusRTU Slave -+ ModbusRTU Master -+ Registers are now shared between Modbus* instances by default -+ Fix functions register count limits to follow Modbus specification (or RX buffer limitations) -+ ModbusRTU: Examples added -+ Test multiple Modbus* instances -+ Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both -+ Client: Allow to specify local TCP port (default is 502) -+ Server: Allow to specify TCP remote port for connection (default is 502) -+ Master\Client: Fix crash on Write Multiple Hregs -+ Master\Client: Fix crash on no callback function on read\write remote -+ Tests added ``` ## Contributions diff --git a/examples/README.md b/examples/README.md index 92b0e7a..febf0d4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,18 +1,32 @@ -# Examples +# Examples and API explanation ## [RTU](RTU) +ModbusRTU master and slave examples + ## [TCP ESP8266/ESP32](TCP-ESP) +ModbusTCP for ESP8266/ESP32 client and server examples + ## [TCP Ethernet W5x00](TCP-Ethernet) +ModbusTCP for W5x00 Ethernet library client and server examples (for all Arduino). + ## [TLS ESP8266/ESP32](TLS) +ModbusTCP Security for ESP8266 and ESP32 (client only) examples. + ## [Callbacks usage](Callback) +Examples of using callback functions. + ## [Files operations](Files) -## [Basic Mosbus RTU to Modbus TCP bridge](bridge) +Modbus file operations examples. + +## [Basic Modbus RTU to Modbus TCP bridge](bridge) + +Very basic example of accessing ModbusRTU slave device connected to ESP8266/ESP32 via ModbusTCP server. # Modbus Library for Arduino ### ModbusRTU, ModbusTCP and ModbusTCP Security diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index 7d08643..5dda88e 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,23 +1,29 @@ # Modbus Library for Arduino ### ModbusRTU, ModbusTCP and ModbusTCP Security -This exampel is introduces how to use the library for ModbusRTU (typicaly over RS-485). +This example is introduces how to use the library for ModbusRTU (typicaly over RS-485). Key API functions for slave device ### Modbus RTU Specific API ```c -bool begin(SoftwareSerial* port, int16_t txPin=-1); -bool begin(HardwareSerial* port, int16_t txPin=-1); +bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); +bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true); bool begin(Stream* port); ``` +- `port` Pointer to Serial port +- `txPin` RX/TX control pin. Not assigned (assume auto RX/TX) by default +- `direct` Direct (true, default) or inverse (false) RX/TX pin control. + Assing Serial port. txPin controls transmit enable for MAX-485. ```c void setBaudrte(uint32 baud); ``` +- `baud` New baudrate. + Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. ```c @@ -25,22 +31,13 @@ void master(); void slave(uint8_t slaveId); ``` +- `slaveId` Modbus slave id to associate to. + Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictible. ```c uint8_t slave(); ``` -Slave mode: Returns configured slave id. Master mode: Returns slave id for active request or 0 if no request in-progress. - -### Master code structure - -```c -void loop() { - -} -``` - -## ESP32 Slave notes - -For achive stable results it's not recommended to use high pooling rate \ No newline at end of file +- Slave mode: Returns configured slave id. +- Master mode: Returns slave id for active request or 0 if no request in-progress. \ No newline at end of file diff --git a/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md index f5f8f56..5fbba68 100644 --- a/examples/TCP-ESP/README.md +++ b/examples/TCP-ESP/README.md @@ -9,11 +9,18 @@ void client(); ``` +Initialize internal structures to act as a Modbus client. + ```c bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); bool disconnect(IPAddress ip); ``` +- `ip` IP address of the remote Modbus server +- `port` TCP port of remote Modbus server (standard value is 502) + +Note: Just one connection to the specific address is supported. That is the library is unable to simultaniousaly connect to Modbus servers that has same IP address but diffrent ports. + ```c uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); uint16_t readCoil(const char* host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); @@ -35,7 +42,8 @@ uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numre - `unit` Modbus unit Sends corresponding Modbus read request to Modbus server at `ip`. Connection with server shoud be already established by connect(ip). -Returns transaction `id` or `0` on failure. +Returns transaction `id` or `0` on failure. Failure maens that client unable to send the request bacause of no connection to the Modbus server is established or other internal error. +Note: read/write functions just sending requests to remote Modbus server. The functions returns immediate after request sent and doesn't waiting for result. That is `value` contains no result data on the function exit. `value` will be filled as responce arrive and processed by .task() function. ## [Client with blocking read operation](clientSync.ino) @@ -43,12 +51,16 @@ Returns transaction `id` or `0` on failure. bool isTransaction(uint16_t id); ``` +- `id` Transaction id. + Returns `true` if transaction with `id` is active. ```c bool isConnected(IPAddress ip); ``` +- `ip` Remote Modbus server IP address + Returns `true` is connection with Modbus server at `ip` is established. ```c diff --git a/examples/TLS/README.md b/examples/TLS/README.md index 7c90ce0..7878a7a 100644 --- a/examples/TLS/README.md +++ b/examples/TLS/README.md @@ -29,8 +29,6 @@ All certificates must be in PEM format and can be stored in PROGMEM. ## [Server](server/server.ino) -Method to observe - ```c void server(uint16_t port, const char* server_cert = nullptr, const char* server_private_key = nullptr, const char* ca_cert = nullptr); ``` diff --git a/examples/bridge/bridge.ino b/examples/bridge/bridge.ino index bfea948..acf71c0 100644 --- a/examples/bridge/bridge.ino +++ b/examples/bridge/bridge.ino @@ -5,21 +5,40 @@ (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 */ - +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif #include #include #define TO_REG 10 #define SLAVE_ID 1 -#define PULL_ID 2 +#define PULL_ID 1 #define FROM_REG 20 ModbusRTU mb1; ModbusIP mb2; void setup() { - Serial1.begin(9600, SERIAL_8N1); + Serial.begin(115200); + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + Serial1.begin(9600, SERIAL_8N1); // Init Serial on default pins + //Serial2.begin(19200, SERIAL_8N1, 19, 18); // Override default pins for ESP32 mb1.begin(&Serial1); + //mb1.begin(&Serial2, 17); // Specify RE_DE control pin mb1.master(); mb2.server(); mb2.addHreg(TO_REG); From 4bad4354a9da969ebeef64d6850df16d117c1d11 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 29 Oct 2020 09:01:44 +0300 Subject: [PATCH 209/288] ModbusRTU: Fix packet loss after sending attempt on busy bus --- src/ModbusRTU.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index d0c5aac..5c91aae 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -82,22 +82,24 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { } uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { - if (_slaveId) return false; // Break if waiting for previous request result - rawSend(slaveId, _frame, _len); - if (waitResponse) { - _slaveId = slaveId; - _timestamp = millis(); - _cb = cb; - _data = data; - _sentFrame = _frame; - _sentReg = startreg; - _frame = nullptr; - //_len = 0; + bool result = false; + if (!_slaveId) { // Check if waiting for previous request result + rawSend(slaveId, _frame, _len); + if (waitResponse) { + _slaveId = slaveId; + _timestamp = millis(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + } + result = true; } free(_frame); _frame = nullptr; _len = 0; - return true; + return result; } void ModbusRTUTemplate::task() { From fca1dbcd959385cc8d6e77994fcea9522f0a6238 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 29 Oct 2020 09:02:51 +0300 Subject: [PATCH 210/288] ModbusRTU: Fix packet loss after sending attempt on busy bus --- src/ModbusRTU.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index db5a1cb..f8cef26 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -122,22 +122,24 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { } bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { - if (_slaveId) return false; // Break if waiting for previous request result - rawSend(slaveId, _frame, _len); - if (waitResponse) { - _slaveId = slaveId; - _timestamp = millis(); - _cb = cb; - _data = data; - _sentFrame = _frame; - _sentReg = startreg; - _frame = nullptr; - _len = 0; + bool result = false; + if (!_slaveId) { // Check if waiting for previous request result + rawSend(slaveId, _frame, _len); + if (waitResponse) { + _slaveId = slaveId; + _timestamp = millis(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + } + result = true; } free(_frame); _frame = nullptr; _len = 0; - return true; + return result; } void ModbusRTU::task() { From e247944e2ee073a42f6e19c97d3aad4f93ceb7b8 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 29 Oct 2020 09:44:19 +0300 Subject: [PATCH 211/288] ModbusTCP: Free buffers after request have sent --- src/ModbusSettings.h | 2 +- src/ModbusTCPTemplate.h | 41 ++++++++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 2971a5a..29200ab 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -48,7 +48,7 @@ If defined regisers count will be limited. #define MODBUSIP_MAXFRAME 200 #define MODBUSIP_TIMEOUT 1000 #define MODBUSIP_UNIT 255 -#define MODBUSIP_MAX_TRANSACIONS 16 +#define MODBUSIP_MAX_TRANSACTIONS 16 #define MODBUSIP_MAX_CLIENTS 4 #define MODBUSIP_UNIQUE_CLIENTS #define MODBUSIP_MAX_READMS 100 diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index c722009..427cdb8 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -314,26 +314,37 @@ uint16_t ModbusTCPTemplate::send(const char* host, TAddress star template uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { MBAP_t _MBAP; -#if defined(MODBUSIP_MAX_TRANSACIONS) - if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return 0; + 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; - int8_t p = getSlave(ip); - if (p == -1 || !tcpclient[p]->connected()) - return autoConnectMode?connect(ip):0; + p = getSlave(ip); + if (p == -1 || !tcpclient[p]->connected()) { + if (!autoConnectMode) + goto cleanup; + if (!connect(ip)) + goto cleanup; + } transactionId++; if (!transactionId) transactionId = 1; _MBAP.transactionId = __bswap_16(transactionId); _MBAP.protocolId = __bswap_16(0); _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP _MBAP.unitId = unit; - 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); - if (tcpclient[p]->write(sbuf, send_len) != send_len) - return false; + 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; @@ -345,9 +356,13 @@ uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg tmp.startreg = startreg; _trans.push_back(tmp); _frame = nullptr; - _len = 0; } - return transactionId; + result = transactionId; + cleanup: + free(_frame); + _frame = nullptr; + _len = 0; + return result; } template From cd2d9e469d3005b5bda39ee94fc4b4fcf7b185d0 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 8 Nov 2020 16:34:24 +0300 Subject: [PATCH 212/288] Rewrite all callbacks to be std::function --- README.md | 7 +++--- library.properties | 2 +- src/Modbus.cpp | 53 ++++++++++++++++++++++++----------------- src/Modbus.h | 51 ++++++++++++++++++++++++++++----------- src/ModbusAPI.h | 4 ++-- src/ModbusRTU.cpp | 2 +- src/ModbusTCPTemplate.h | 9 +++++++ 7 files changed, 85 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 0251aa4..ee72059 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ For more information about Modbus see: ## Last Changes ```diff -// 4.0.0.DEV +// 4.0.0-DEV + ModbusTLS: Modbus TCP Security Client/Server + ModbusTLS: ESP8266 Client/Server + Test: TLS ESP8266 Client/Server @@ -90,7 +90,7 @@ For more information about Modbus see: - Test: 0x16 - 0x17 - Read/Write Registers function - Test: 0x17 -+ API: Access control callback for individual function ++ API: Access control callback for individual Modbus function - Slave/Server: slavePDU use early exit by return where possible - Master/Client: Check frame size against header data where possible - Master/Client: Additional responce data validation @@ -98,12 +98,13 @@ For more information about Modbus see: - Test: Frame accuracy to specefication - Documentation: Update - Examples: Revising -+ SoftwareSerial support for ESP32 ++ ModbusRTU: ESP32 SoftwareSerial support + ModbusRTU: Fix transaction callback remains assigned after request end + ModbusTCP: Free server connection in destructor + Declare global registers and callbacks as ststic members - Free global registers and callbacks on remove last Modbus instance + ModbusRTU: Refactor .task() for relaibe processing of incoming data ++ API: Declare all callbacks as std::function (for STL) // 4.1.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server diff --git a/library.properties b/library.properties index 31b9b10..2a8bca1 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=4.0.0.DEV +version=4.0.0-DEV author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus Library for Arduino. ModbusRTU, ModbusTCP and ModbusTCP Security diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 6d28b02..03d5eb2 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -10,11 +10,16 @@ #if defined(MODBUS_USE_STL) std::vector Modbus::_regs; std::vector Modbus::_callbacks; + #if defined(MODBUS_FILES) + std::function Modbus::_onFile; + #endif #else DArray Modbus::_regs; DArray Modbus::_callbacks; + #if defined(MODBUS_FILES) + cbModbusFileOp Modbus::_onFile = nullptr; + #endif #endif - cbModbusFileOp _onFile; #endif uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType t) { @@ -538,37 +543,37 @@ bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) { -#define MODBUS_COMPARE_ONSET [address, cb](TCallback entry){ return entry.type == TCallback::ON_SET && entry.address == address && (!cb || entry.cb == cb);} +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--) { - #if defined(MODBUS_USE_STL) - _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ONSET), _callbacks.end()); - #else - size_t r = 0; - do { - r = _callbacks.find(MODBUS_COMPARE_ONSET, r); - _callbacks.remove(r); - } while (r < _callbacks.size()); - #endif + _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ON), _callbacks.end()); address++; } - return false; -} -bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) { -#define MODBUS_COMPARE_ONGET [address, cb](TCallback entry){ return entry.type == TCallback::ON_GET && entry.address == address && (!cb || entry.cb == cb);} + #else + #define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){ \ + return entry.type == t && entry.address == address \ + && (!cb || entry.cb == cb);} while(numregs--) { - #if defined(MODBUS_USE_STL) - _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ONGET), _callbacks.end()); - #else size_t r = 0; do { - r = _callbacks.find(MODBUS_COMPARE_ONGET, r); + r = _callbacks.find(MODBUS_COMPARE_ON); _callbacks.remove(r); } while (r < _callbacks.size()); - #endif address++; } - return false; + #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) { @@ -756,7 +761,11 @@ Modbus::~Modbus() { } #if defined(MODBUS_FILES) +#if defined(MODBUS_USE_STL) +bool Modbus::onFile(std::function cb) { +#else bool Modbus::onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)) { +#endif _onFile = cb; return true; } diff --git a/src/Modbus.h b/src/Modbus.h index feab2f5..27d62f8 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -10,6 +10,8 @@ #if defined(MODBUS_USE_STL) #include #include + #include + #include #else #include "darray.h" #endif @@ -31,8 +33,11 @@ inline uint16_t __bswap_16(uint16_t num) { return (num >> 8) | (num << 8); } #define cbDefault nullptr struct TRegister; - +#if defined(MODBUS_USE_STL) +typedef std::function 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}; @@ -158,25 +163,32 @@ class Modbus { }; #if defined(MODBUS_USE_STL) #if defined(MODBUS_GLOBAL_REGS) - static + static std::vector _regs; + static std::vector _callbacks; + #if defined(MODBUS_FILES) + static std::function _onFile; #endif + #else std::vector _regs; - #if defined(MODBUS_GLOBAL_REGS) - static - #endif std::vector _callbacks; + #if defined(MODBUS_FILES) + std::function _onFile; + #endif + #endif #else #if defined(MODBUS_GLOBAL_REGS) - static + static DArray _regs; + static DArray _callbacks; + #if defined(MODBUS_FILES) + static ResultCode (*_onFile)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*); #endif + #else DArray _regs; - #if defined(MODBUS_GLOBAL_REGS) - static - #endif DArray _callbacks; - #endif #if defined(MODBUS_FILES) - Modbus::ResultCode (*_onFile)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*) = nullptr; + ResultCode (*_onFile)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)= nullptr; + #endif + #endif #endif uint8_t* _frame = nullptr; @@ -202,7 +214,7 @@ class Modbus { // 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); @@ -217,8 +229,11 @@ class Modbus { 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 cbRequest; // Callback function Type + #else typedef ResultCode (*cbRequest)(FunctionCode fc, TAddress reg, uint16_t regCount); // Callback function Type + #endif protected: static ResultCode _onRequestDefault(FunctionCode fc, TAddress reg, uint16_t regCount); @@ -234,7 +249,11 @@ class Modbus { #if defined(MODBUS_FILES) public: - bool onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)); + #if defined(MODBUS_USE_STL) + bool onFile(std::function); + #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: @@ -255,7 +274,11 @@ class Modbus { }; +#if defined(MODBUS_USE_STL) +typedef std::function 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 diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index bd0ae73..ba3ffff 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -139,7 +139,7 @@ template \ template \ uint16_t ModbusAPI::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, nullptr, cb); \ + return this->send(ip, REG(offset), cb, unit, nullptr, cb != nullptr); \ } IMPLEMENT_WRITEREG(writeCoil, COIL, FC_WRITE_COIL, COIL_VAL, bool) IMPLEMENT_WRITEREG(writeHreg, HREG, FC_WRITE_REG, , uint16_t) @@ -150,7 +150,7 @@ template \ uint16_t ModbusAPI::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, nullptr, cb); \ + return this->send(ip, REG(offset), cb, unit, nullptr, cb != nullptr); \ } IMPLEMENT_WRITEREGS(writeCoil, COIL, FC_WRITE_COILS, writeSlaveBits, 0x07D0, bool) IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, 0x007D, uint16_t) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 5c91aae..7b9df59 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -149,7 +149,7 @@ void ModbusRTUTemplate::task() { if (isMaster && _slaveId == 0) { // Check if slaveId is set for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected _len = 0; - if (isMaster) cleanup(); + //if (isMaster) cleanup(); return; } if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 427cdb8..cbb4064 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -15,8 +15,13 @@ #define IPADDR_NONE ((u32_t)0xffffffffUL) #endif // Callback function Type +#if defined(MODBUS_USE_STL) +typedef std::function cbModbusConnect; +typedef std::function cbModbusResolver; +#else typedef bool (*cbModbusConnect)(IPAddress ip); typedef IPAddress (*cbModbusResolver)(const char*); +#endif struct TTransaction { uint16_t transactionId; @@ -107,7 +112,11 @@ class ModbusTCPTemplate : public Modbus { uint32_t eventSource() override; void autoConnect(bool enabled = true); void dropTransactions(); + #if defined(MODBUS_USE_STL) static IPAddress defaultResolver(const char*) {return IPADDR_NONE;} + #else + static IPAddress defaultResolver(const char*) {return IPADDR_NONE;} + #endif }; template From f3f05675f8f876f448f120551b1ad065a84d8da2 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 8 Nov 2020 17:43:30 +0300 Subject: [PATCH 213/288] 3.0.4 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index ac36c18..e953513 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=3.0.3 +version=3.0.4 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus RTU and Modbus TCP Library for ESP8266/ESP32 From 499c230a001b0465ba8f6e5713c936e66274f09e Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 13 Nov 2020 14:54:36 +0300 Subject: [PATCH 214/288] Fixed wrong onRequest/onRequestSuccess location --- src/Modbus.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modbus.h b/src/Modbus.h index 27d62f8..dcb2fde 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -237,12 +237,12 @@ class Modbus { protected: static ResultCode _onRequestDefault(FunctionCode fc, TAddress reg, uint16_t regCount); - cbRequest _onRequestSuccess = _onRequestDefault; + cbRequest _onRequest = _onRequestDefault; public: bool onRequest(cbRequest cb = _onRequestDefault); #if defined (MODBUSAPI_OPTIONAL) protected: - cbRequest _onRequest = _onRequestDefault; + cbRequest _onRequestSuccess = _onRequestDefault; public: bool onRequestSuccess(cbRequest cb = _onRequestDefault); #endif From ac78bff5fea540545e11e42cbdf6cd6c6bd4ad33 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 29 Nov 2020 14:34:24 +0500 Subject: [PATCH 215/288] ModbusRTU Stability fixes - Fixed early bus release if no transactional callback assigned for read/write Hreg/Coil - Fixed possible responce loss for ESP32 master --- README.md | 3 +++ library.properties | 2 +- src/ModbusRTU.cpp | 15 +++++++-------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 226a2bc..10bf0b1 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,9 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 3.0.5 ++ ModbusRTU: Fix early bus release if no callback ++ ModbusRTU ESP32: Fix potential responce loss // 3.0.4 + ModbusIP: Fixed missed server connection free in destructor + Fixed addition of missing registers in .poll*() calls diff --git a/library.properties b/library.properties index e953513..adea178 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=3.0.4 +version=3.0.5 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus RTU and Modbus TCP Library for ESP8266/ESP32 diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index f8cef26..128145f 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -111,13 +111,12 @@ bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC - #ifdef ESP32 - portEXIT_CRITICAL(&mux); - #endif _port->flush(); if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); - //delay(_t); + #ifdef ESP32 + portEXIT_CRITICAL(&mux); + #endif return true; } @@ -264,12 +263,12 @@ bool ModbusRTU::cleanup() { uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { readSlave(offset, value, FC_WRITE_REG); - return send(slaveId, HREG(offset), cb, nullptr, cb); + return send(slaveId, HREG(offset), cb, nullptr); } uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); - return send(slaveId, COIL(offset), cb, nullptr, cb); + return send(slaveId, COIL(offset), cb, nullptr); } uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { @@ -282,14 +281,14 @@ uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x07D0) return false; writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); - return send(slaveId, COIL(offset), cb, nullptr, cb); + return send(slaveId, COIL(offset), cb, nullptr); } uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { if (numregs < 0x0001 || numregs > 0x007D) return false; writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); - return send(slaveId, HREG(offset), cb, nullptr, cb); + return send(slaveId, HREG(offset), cb, nullptr); } From 7273f820fcbb0120358c3216782f12ae8db8cc39 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 5 Dec 2020 17:59:07 +0300 Subject: [PATCH 216/288] Server: Fix callback called twice on register read --- README.md | 2 ++ library.properties | 2 +- src/Modbus.cpp | 8 ++++---- src/Modbus.h | 5 +---- src/ModbusIP_ESP8266.cpp | 14 +++++++------- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 10bf0b1..4ca9bb3 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) ## Last Changes ```diff +// 3.0.6 ++ Slave/Server: Fix callback called twice on register read // 3.0.5 + ModbusRTU: Fix early bus release if no callback + ModbusRTU ESP32: Fix potential responce loss diff --git a/library.properties b/library.properties index adea178..c8849c9 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=3.0.5 +version=3.0.6 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus RTU and Modbus TCP Library for ESP8266/ESP32 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index cd8872b..b9ac80a 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -224,7 +224,7 @@ void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { for (uint8_t i = 0; i < numregs; i++) { - frame[i] = __bswap_16(Reg(startreg + i)); + frame[i] = __swap_16(Reg(startreg + i)); } } @@ -298,7 +298,7 @@ void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutp void Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { for (uint8_t i = 0; i < numregs; i++) { - Reg(startreg + i, __bswap_16(frame[i])); + Reg(startreg + i, __swap_16(frame[i])); } } @@ -405,7 +405,7 @@ bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, F if (data) { uint16_t* frame = (uint16_t*)(_frame + 6); for (uint8_t i = 0; i < numregs; i++) { - frame[i] = __bswap_16(data[i]); + frame[i] = __swap_16(data[i]); } } else { getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs); @@ -469,7 +469,7 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, if (output) { frame += 2; while(field2) { - *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); + *((uint16_t*)output) = __swap_16(*((uint16_t*)frame)); frame += 2; output += 2; field2--; diff --git a/src/Modbus.h b/src/Modbus.h index a639dca..74c81fc 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -12,10 +12,7 @@ #include #endif -#ifndef __bswap_16 - #define __bswap_16(num) (((uint16_t)num>>8) | ((uint16_t)num<<8)) -#endif - +static inline uint16_t __swap_16(uint16_t num) { return (num >> 8) | (num << 8); } #define MB_GLOBAL_REGS //#define MB_MAX_REGS 32 diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp index 8a6065e..6da7718 100644 --- a/src/ModbusIP_ESP8266.cpp +++ b/src/ModbusIP_ESP8266.cpp @@ -81,12 +81,12 @@ void ModbusIP::task() { while (millis() - readStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP - if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. + if (__swap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. while (tcpclient[n]->available()) // Drop all incoming if wrong packet tcpclient[n]->read(); continue; } - _len = __bswap_16(_MBAP.length); + _len = __swap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); @@ -112,7 +112,7 @@ void ModbusIP::task() { } else { // Process reply to master request _reply = EX_SUCCESS; - TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); + 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 // Procass incoming frame as master @@ -135,7 +135,7 @@ void ModbusIP::task() { } if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master if (_reply != REPLY_OFF) { - _MBAP.length = __bswap_16(_len+1); // _len+1 for last byte from MBAP + _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)); @@ -164,9 +164,9 @@ uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8 return autoConnectMode?connect(ip):false; transactionId++; if (!transactionId) transactionId = 1; - _MBAP.transactionId = __bswap_16(transactionId); - _MBAP.protocolId = __bswap_16(0); - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _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; size_t send_len = _len + sizeof(_MBAP.raw); uint8_t sbuf[send_len]; From 5f1feef2e1b657bd20eb9c3feca745978adf28b1 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 6 Dec 2020 11:06:19 +0300 Subject: [PATCH 217/288] Port changes from 3.0.6 - ModbusRTU: Fix easly bus release if no callback - Server: Fix onGet callback called twice - Documentation light update --- README.md | 29 +++++++++++++++-------------- library.properties | 2 +- src/Modbus.h | 2 +- src/ModbusAPI.h | 4 ++-- src/ModbusRTU.h | 7 +++++-- src/ModbusSettings.h | 2 +- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ee72059..ab8b3e6 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,14 @@ |---|---| -Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. +The library is under active development. Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. --- -This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol -used in industrial automation and can be used in other areas, such as home automation. +This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a protocol +used in industrial automation and also can be used in other areas, such as home automation. -The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus TCP). - -For more information about Modbus see: - -* [Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) -* [MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) -* [MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) -* [MODBUS over Serial Line Specification and Implementation Guide V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) -* [MODBUS/TCP Security Protocol Specification](https://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf) +The Modbus generally uses serial RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus TCP). ## Features @@ -59,6 +51,14 @@ For more information about Modbus see: 3. Modbus RTU maximum incoming frame size is determinated by HardwareSerial buffer size. For SoftwareSerial buffer must be set to 256 bytes. 4. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. +For more information about Modbus see: + +* [Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) +* [MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b3](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf) +* [MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) +* [MODBUS over Serial Line Specification and Implementation Guide V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) +* [MODBUS/TCP Security Protocol Specification](https://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf) + ## Last Changes ```diff @@ -101,10 +101,11 @@ For more information about Modbus see: + ModbusRTU: ESP32 SoftwareSerial support + ModbusRTU: Fix transaction callback remains assigned after request end + ModbusTCP: Free server connection in destructor -+ Declare global registers and callbacks as ststic members ++ Declare global registers and callbacks as static members - Free global registers and callbacks on remove last Modbus instance -+ ModbusRTU: Refactor .task() for relaibe processing of incoming data ++ ModbusRTU: Refactor .task() for more relaibe processing of incoming data + API: Declare all callbacks as std::function (for STL) +- API: Msater/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) // 4.1.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server diff --git a/library.properties b/library.properties index 2a8bca1..7d0ceb5 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=4.0.0-DEV author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus Library for Arduino. ModbusRTU, ModbusTCP and ModbusTCP Security -paragraph=Most complete Modbus protocol implementation for Arduino. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. +paragraph=Most complete Modbus protocol implementation for Arduino. The Modbus is a master-slave protocol used in industrial automation and also can be used in other areas, such as home automation. category=Communication url=https://github.com/emelianov/modbus-esp8266 architectures=* diff --git a/src/Modbus.h b/src/Modbus.h index dcb2fde..8cf1a2e 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -16,7 +16,7 @@ #include "darray.h" #endif -inline uint16_t __bswap_16(uint16_t num) { return (num >> 8) | (num << 8); } +static inline uint16_t __bswap_16(uint16_t num) { return (num >> 8) | (num << 8); } #define COIL(n) (TAddress){TAddress::COIL, n} #define ISTS(n) (TAddress){TAddress::ISTS, n} diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index ba3ffff..bde6df8 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -139,7 +139,7 @@ template \ template \ uint16_t ModbusAPI::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, nullptr, cb != nullptr); \ + 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) @@ -150,7 +150,7 @@ template \ uint16_t ModbusAPI::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, nullptr, cb != nullptr); \ + return this->send(ip, REG(offset), cb, unit); \ } IMPLEMENT_WRITEREGS(writeCoil, COIL, FC_WRITE_COILS, writeSlaveBits, 0x07D0, bool) IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, 0x007D, uint16_t) diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 0d4c2ed..76d0f8f 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -42,8 +42,11 @@ class ModbusRTUTemplate : public Modbus { bool begin(T* port, int16_t txPin = -1, bool direct = true); bool begin(Stream* port); void task(); - void master() { isMaster = true; }; - void slave(uint8_t slaveId) {_slaveId = slaveId;}; + 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 slave() { return _slaveId; } uint32_t eventSource() override {return _slaveId;} }; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 29200ab..4f7e84c 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -27,7 +27,7 @@ If not defined each Modbus object will have own registers set. #define MODBUS_USE_STL If defined C STL will be used. */ -#if defined(ESP8266) || defined(ESP32) +#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) #define MODBUS_USE_STL #endif From ba4ecac90e6c54deb09ac5b3b938a4cb41425da4 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Dec 2020 13:00:52 +0300 Subject: [PATCH 218/288] W5x00: Support for ESP32, Client example --- README.md | 5 +-- examples/TCP-Ethernet/client/client.ino | 52 +++++++++++++++++++++++++ examples/TCP-Ethernet/server/server.ino | 4 +- src/ModbusEthernet.h | 13 ++++++- src/ModbusTCPTemplate.h | 2 +- 5 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 examples/TCP-Ethernet/client/client.ino diff --git a/README.md b/README.md index ab8b3e6..c4cdf20 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The library is under active development. Visit [Releases](https://github.com/eme --- -This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a protocol +This library allows your Arduino board to communicate via Modbus protocol. The Modbus is a protocol used in industrial automation and also can be used in other areas, such as home automation. The Modbus generally uses serial RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus TCP). @@ -48,8 +48,7 @@ The Modbus generally uses serial RS-485 as physical layer (then called Modbus Se 1. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. 2. For API refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) -3. Modbus RTU maximum incoming frame size is determinated by HardwareSerial buffer size. For SoftwareSerial buffer must be set to 256 bytes. -4. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. +3. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. For more information about Modbus see: diff --git a/examples/TCP-Ethernet/client/client.ino b/examples/TCP-Ethernet/client/client.ino new file mode 100644 index 0000000..74d4660 --- /dev/null +++ b/examples/TCP-Ethernet/client/client.ino @@ -0,0 +1,52 @@ +/* + ModbusTCP for W5x00 Ethernet library + Basic Client code example + + (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. +*/ + +#include +#include // Ethernet library v2 is required +#include + +const uint16_t REG = 512; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 12); // Address of Modbus Slave device +const int32_t showDelay = 5000; // Show result every n'th mellisecond + +// Enter a MAC address and IP address for your controller below. +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE +}; +IPAddress ip(192, 168, 30, 178); // The IP address will be dependent on your local network: +ModbusEthernet mb; // Declare ModbusTCP instance + +void setup() { + Serial.begin(115200); // Open serial communications and wait for port to open + #if defined(AVR_LEONARDO) + while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only + #endif + Ethernet.init(5); // SS pin + Ethernet.begin(mac, ip); // start the Ethernet connection + delay(1000); // give the Ethernet shield a second to initialize + mb.client(); // Act as Modbus TCP server +} + +uint16_t res = 0; +uint32_t showLast = 0; + +void loop() { +if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Slave + } else { + mb.connect(remote); // Try to connect if not connected + } + delay(100); // Pulling interval + mb.task(); // Common local Modbus task + if (millis() - showLast > showDelay) { // Display register value every 5 seconds (with default settings) + showLast = millis(); + Serial.println(res); + } +} \ No newline at end of file diff --git a/examples/TCP-Ethernet/server/server.ino b/examples/TCP-Ethernet/server/server.ino index a7e2582..769e7cd 100644 --- a/examples/TCP-Ethernet/server/server.ino +++ b/examples/TCP-Ethernet/server/server.ino @@ -16,7 +16,7 @@ byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); // The IP address will be dependent on your local network: +IPAddress ip(192, 168, 30, 177); // The IP address will be dependent on your local network: ModbusEthernet mb; // Declare ModbusTCP instance void setup() { @@ -24,7 +24,7 @@ void setup() { #if defined(AVR_LEONARDO) while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only #endif - Ethernet.init(15); // SS pin + Ethernet.init(5); // SS pin Ethernet.begin(mac, ip); // start the Ethernet connection delay(1000); // give the Ethernet shield a second to initialize mb.server(); // Act as Modbus TCP server diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h index f1c5ca1..71543a8 100644 --- a/src/ModbusEthernet.h +++ b/src/ModbusEthernet.h @@ -9,7 +9,18 @@ #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" -class ModbusEthernet : public ModbusAPI> { +// Ethernet class wrapper to be able to compile for ESP32 +class EthernetServerWrapper : public EthernetServer { + public: + EthernetServerWrapper(uint16_t port) : EthernetServer(port) { + + } + void begin(uint16_t port=0) { + + } +}; + +class ModbusEthernet : public ModbusAPI> { private: static IPAddress resolver (const char* host) { DNSClient dns; diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index cbb4064..7ba2d52 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -12,7 +12,7 @@ #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 ((u32_t)0xffffffffUL) +#define IPADDR_NONE ((uint32_t)0xffffffffUL) #endif // Callback function Type #if defined(MODBUS_USE_STL) From 452a368aad750ad13e6aea91097aaa806247cd49 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Dec 2020 15:50:53 +0500 Subject: [PATCH 219/288] Create README.md --- examples/TCP-Ethernet/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 examples/TCP-Ethernet/README.md diff --git a/examples/TCP-Ethernet/README.md b/examples/TCP-Ethernet/README.md new file mode 100644 index 0000000..65dcf95 --- /dev/null +++ b/examples/TCP-Ethernet/README.md @@ -0,0 +1,8 @@ +# W5x00 Example + +## Physical connection between ESP32 and W5500 + +* GPIO23 <--> MOSI +* GPIO19 <--> MISO +* GPIO18 <--> SCLK +* GPIO5 <--> SCS From 644f727f871f132be14a777d7669380444103a89 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Dec 2020 18:05:02 +0300 Subject: [PATCH 220/288] Fix __bswap_16 rename --- src/ModbusTCPTemplate.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 7ba2d52..9162d28 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -230,12 +230,12 @@ void ModbusTCPTemplate::task() { while (millis() - taskStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP - if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. + 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 = __bswap_16(_MBAP.length); + _len = __swap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); @@ -262,7 +262,7 @@ void ModbusTCPTemplate::task() { } else { // Process reply to master request _reply = EX_SUCCESS; - TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); + 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 // Procass incoming frame as master @@ -291,7 +291,7 @@ void ModbusTCPTemplate::task() { //if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master if (!BIT_CHECK(tcpServerConnection, n)) _reply = REPLY_OFF; // No replay if it was responce to master if (_reply != REPLY_OFF) { - _MBAP.length = __bswap_16(_len+1); // _len+1 for last byte from MBAP + _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)); @@ -340,9 +340,9 @@ uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg } transactionId++; if (!transactionId) transactionId = 1; - _MBAP.transactionId = __bswap_16(transactionId); - _MBAP.protocolId = __bswap_16(0); - _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP + _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 From 91f88c4972d6f50fa731344534e25b36140ffe0c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 16 Dec 2020 14:56:27 +0300 Subject: [PATCH 221/288] ESP32: Correct task switch blocking. - Add multitask example --- .../RTU/ESP32-Concurent/ESP32-Concurent.ino | 89 +++++++++++++++++++ examples/RTU/README.MD | 3 +- examples/TCP-ESP/README.md | 3 +- src/ModbusRTU.cpp | 15 ++-- 4 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 examples/RTU/ESP32-Concurent/ESP32-Concurent.ino diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino new file mode 100644 index 0000000..93dc748 --- /dev/null +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -0,0 +1,89 @@ +/* + ModbusRTU ESP32 + Concurent thread example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#include + +#define REG1 1 +#define REG2 2 +#define SLAVE_ID 1 + +ModbusRTU mb; + +xSemaphoreHandle xMutex; +Modbus::ResultCode err; + +bool resCallback(Modbus::ResultCode event, uint16_t, void*) { + err = event; +} + +Modbus::ResultCode readSync(uint16_t Address, uint16_t start, uint16_t num, uint16_t* buf) { + xSemaphoreTake(xMutex, portMAX_DELAY); + if (mb.slave()){Serial.println(mb.slave());xSemaphoreGive(xMutex); return Modbus::EX_GENERAL_FAILURE;} + Serial.printf("SlaveID: %d Hreg %d\n", Address, start); + mb.readHreg(Address, start, buf, num, resCallback); + while (mb.slave()) { + vTaskDelay(1); + mb.task(); + } + Modbus::ResultCode res = err; + xSemaphoreGive(xMutex); + return res; +} + +void loop1( void * pvParameters ); +void loop2( void * pvParameters ); + +void setup() { + Serial.begin(115200); + Serial1.begin(9600, SERIAL_8N1, 19, 18); + mb.begin(&Serial1, 17); + mb.master(); + vSemaphoreCreateBinary( xMutex ); + xTaskCreatePinnedToCore( + loop1, /* Task function. */ + "Task1", /* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 10, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 0); /* pin task to core 1 */ + + xTaskCreatePinnedToCore( + loop2, /* Task function. */ + "Task1", /* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 1); /* pin task to core 1 */ + +} + +uint16_t hregs1[10]; +void loop1( void * pvParameters ){ + while(true) { + delay(10); + if (readSync(1, 1, 10, hregs1) != Modbus::EX_SUCCESS) + Serial.print("Error 1"); + } +} + +uint16_t hregs2[10]; +void loop2( void * pvParameters ){ + while(true) { + delay(100); + if (readSync(1, 2, 10, hregs2) == Modbus::EX_SUCCESS) + Serial.println("OK 2"); + else + Serial.print("Error 2"); + } +} + +void loop() { + delay(100); +} \ No newline at end of file diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index 5dda88e..c73b1c6 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,8 +1,7 @@ # Modbus Library for Arduino ### ModbusRTU, ModbusTCP and ModbusTCP Security -This example is introduces how to use the library for ModbusRTU (typicaly over RS-485). -Key API functions for slave device +This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally thre is [example of master](WSP32-Concurent) device for multithread usage with ESP32. ### Modbus RTU Specific API diff --git a/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md index 5fbba68..c8ab9e4 100644 --- a/examples/TCP-ESP/README.md +++ b/examples/TCP-ESP/README.md @@ -109,7 +109,8 @@ bool Ists(uint16_t offset); uint16_t Ireg(uint16_t offset); ``` -- `offset` Address of the first register to add +- `offset` Address of the register to read + Returns current value of the register. diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 7b9df59..8f1972d 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -104,7 +104,8 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti void ModbusRTUTemplate::task() { #if defined(ESP32) - taskENTER_CRITICAL(&mux); + //taskENTER_CRITICAL(&mux); + vTaskSuspendAll(); #endif if (_port->available() > _len) { _len = _port->available(); @@ -112,7 +113,8 @@ void ModbusRTUTemplate::task() { } if (_len == 0) { #if defined(ESP32) - taskEXIT_CRITICAL(&mux); + //taskEXIT_CRITICAL(&mux); + xTaskResumeAll(); #endif if (isMaster) cleanup(); return; @@ -120,7 +122,8 @@ void ModbusRTUTemplate::task() { if (isMaster) { if (millis() - t < _t) { #if defined(ESP32) - taskEXIT_CRITICAL(&mux); + //taskEXIT_CRITICAL(&mux); + xTaskResumeAll(); #endif return; } @@ -134,14 +137,16 @@ void ModbusRTUTemplate::task() { } if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long #if defined(ESP32) - taskEXIT_CRITICAL(&mux); + //taskEXIT_CRITICAL(&mux); + xTaskResumeAll(); #endif return; } } } #if defined(ESP32) - taskEXIT_CRITICAL(&mux); + //taskEXIT_CRITICAL(&mux); + xTaskResumeAll(); #endif uint8_t address = _port->read(); //first byte of frame = address From ce752933c01eed17ecc37127d2cf461a1c3ae0ac Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 16 Dec 2020 18:15:26 +0300 Subject: [PATCH 222/288] ESP32 RTU-Concurent Example: Change Binary to Mutex --- .../RTU/ESP32-Concurent/ESP32-Concurent.ino | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino index 93dc748..b75557a 100644 --- a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -23,7 +23,10 @@ bool resCallback(Modbus::ResultCode event, uint16_t, void*) { Modbus::ResultCode readSync(uint16_t Address, uint16_t start, uint16_t num, uint16_t* buf) { xSemaphoreTake(xMutex, portMAX_DELAY); - if (mb.slave()){Serial.println(mb.slave());xSemaphoreGive(xMutex); return Modbus::EX_GENERAL_FAILURE;} + if (mb.slave()){ + xSemaphoreGive(xMutex); + return Modbus::EX_GENERAL_FAILURE; + } Serial.printf("SlaveID: %d Hreg %d\n", Address, start); mb.readHreg(Address, start, buf, num, resCallback); while (mb.slave()) { @@ -43,23 +46,23 @@ void setup() { Serial1.begin(9600, SERIAL_8N1, 19, 18); mb.begin(&Serial1, 17); mb.master(); - vSemaphoreCreateBinary( xMutex ); + xMutex = xSemaphoreCreateMutex(); xTaskCreatePinnedToCore( - loop1, /* Task function. */ + loop1, /* Task function. */ "Task1", /* name of task. */ 10000, /* Stack size of task */ NULL, /* parameter of the task */ - 10, /* priority of the task */ - NULL, /* Task handle to keep track of created task */ + 10, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ 0); /* pin task to core 1 */ xTaskCreatePinnedToCore( - loop2, /* Task function. */ + loop2, /* Task function. */ "Task1", /* name of task. */ 10000, /* Stack size of task */ NULL, /* parameter of the task */ 1, /* priority of the task */ - NULL, /* Task handle to keep track of created task */ + NULL, /* Task handle to keep track of created task */ 1); /* pin task to core 1 */ } From 2e16b3683a81c6e1d27ec5ee67667fe4872a7893 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 27 Dec 2020 18:47:35 +0300 Subject: [PATCH 223/288] ModbusTCP Client: Fix persistent connection error in some situations ModbusRTU Client: Add sync read example --- examples/RTU/README.MD | 20 +++++++++-- examples/RTU/masterSync/masterSync.ino | 47 ++++++++++++++++++++++++++ src/ModbusTCPTemplate.h | 8 ++++- 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 examples/RTU/masterSync/masterSync.ino diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index c73b1c6..3a6c259 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,8 +1,15 @@ -# Modbus Library for Arduino -### ModbusRTU, ModbusTCP and ModbusTCP Security +## (Concurent thread-safe access to Modbus object)[ESP32-Concurent] This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally thre is [example of master](WSP32-Concurent) device for multithread usage with ESP32. +## (Simple ModbusRTU master)[master] + +## (Simple ModbusRTU slave)[slave] + +## (Sync ModbusRTU master)[masterSync] + + + ### Modbus RTU Specific API ```c @@ -39,4 +46,11 @@ uint8_t slave(); ``` - Slave mode: Returns configured slave id. -- Master mode: Returns slave id for active request or 0 if no request in-progress. \ No newline at end of file +- Master mode: Returns slave id for active request or 0 if no request in-progress. + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. \ No newline at end of file diff --git a/examples/RTU/masterSync/masterSync.ino b/examples/RTU/masterSync/masterSync.ino new file mode 100644 index 0000000..75a4614 --- /dev/null +++ b/examples/RTU/masterSync/masterSync.ino @@ -0,0 +1,47 @@ +/* + Modbus Library for Arduino Example - Modbus RTU Client + Read Holding Registers from Modbus RTU Server in blocking way + ESP8266 Example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#include +#include + +#define SLAVE_ID 1 +#define FIRST_REG 0 +#define REG_COUNT 2 + +SoftwareSerial S(D2, D1); +ModbusRTU mb; + +bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Callback to monitor errors + if (event != Modbus::EX_SUCCESS) { + Serial.print("Request result: 0x"); + Serial.print(event, HEX); + } + return true; +} + +void setup() { + Serial.begin(115200); + S.begin(9600, SWSERIAL_8N1); + mb.begin(&S); + mb.master(); +} + +void loop() { + uint16_t res[REG_COUNT]; + if (!mb.slave()) { // Check if no transaction in progress + mb.readHreg(SLAVE_ID, FIRST_REG, res, REG_COUNT, cb); // Send Read Hreg from Modbus Server + while(mb.slave()) { // Check if transaction is active + mb.task(); + delay(10); + } + Serial.println(res[0]); + Serial.println(res[1]); + } + delay(1000); +} \ No newline at end of file diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 9162d28..b25e33a 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -164,7 +164,12 @@ bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { return false; tcpclient[p] = new CLIENT(); BIT_CLEAR(tcpServerConnection, p); - return tcpclient[p]->connect(ip, port?port:defaultPort); + if (!tcpclient[p]->connect(ip, port?port:defaultPort)) { + delete(tcpclient[p]); + tcpclient[p] = nullptr; + return false; + } + return true; } template @@ -495,6 +500,7 @@ bool ModbusTCPTemplate::disconnect(IPAddress ip) { return false; int8_t p = getSlave(ip); if (p != -1) { + tcpclient[p]->stop(); delete tcpclient[p]; tcpclient[p] = nullptr; return true; From 096fab39e917efe9da24525e381228335f3ac8c5 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 6 Jan 2021 19:20:35 +0300 Subject: [PATCH 224/288] Full Bridge helping changes (stage #1) --- src/Modbus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modbus.h b/src/Modbus.h index 760f829..3dd8299 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -196,7 +196,7 @@ class Modbus { uint8_t _reply = 0; bool cbEnabled = true; uint16_t callback(TRegister* reg, uint16_t val, TCallback::CallbackType t); - TRegister* searchRegister(TAddress addr); + 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 From 83e8fd5691be7b1264c0b0deaa0ff729ad192bf7 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 12 Feb 2021 17:58:48 +0500 Subject: [PATCH 225/288] Fix add register on pull. Fix TLS. --- src/ModbusAPI.h | 6 +++--- src/ModbusSettings.h | 2 +- src/ModbusTLS.h | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index bde6df8..7a9a23f 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -169,16 +169,16 @@ IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0, bool) IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, 0x007D, uint16_t) #if defined(MODBUS_ADD_REG) -#define ADDREG this->addReg(REG(to), 0, numregs); \ +#define ADDREG(R) this->addReg(R(to), 0, numregs); #else -#define ADDREG +#define ADDREG(R) ; #endif #define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \ template \ template \ uint16_t ModbusAPI::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 \ + ADDREG(REG) \ this->readSlave(from, numregs, Modbus::FUNC); \ return this->send(ip, REG(to), cb, unit); \ } diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 4f7e84c..30d290e 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -27,7 +27,7 @@ If not defined each Modbus object will have own registers set. #define MODBUS_USE_STL If defined C STL will be used. */ -#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) +#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) //|| defined(ARDUINO_SAM_DUE)clear #define MODBUS_USE_STL #endif diff --git a/src/ModbusTLS.h b/src/ModbusTLS.h index 517ccd0..92cef86 100644 --- a/src/ModbusTLS.h +++ b/src/ModbusTLS.h @@ -17,6 +17,9 @@ class WiFiServerSecure { WiFiServerSecure(uint16_t){} WiFiClientSecure available(){} void begin(); + inline WiFiClientSecure accept() { + return available(); + } }; #endif #include "ModbusTCPTemplate.h" From b018f8ac52b49edb46c1f0d9032e462650097ddc Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 13 Feb 2021 16:13:36 +0500 Subject: [PATCH 226/288] Move Adruino Due to STL --- src/Modbus.cpp | 9 +++++++++ src/Modbus.h | 7 +++++++ src/ModbusSettings.h | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index f0f4ee4..d4e5006 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -837,3 +837,12 @@ bool Modbus::onRequestSuccess(cbRequest cb) { return true; } #endif + +#if defined(ARDUINO_SAM_DUE) +namespace std { + void __throw_bad_function_call() { + Serial.println(F("STL ERROR - __throw_bad_function_call")); + __builtin_unreachable(); + } +} +#endif \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index 760f829..d20bb23 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -283,4 +283,11 @@ typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, #if defined(MODBUS_FILES) // Callback skeleton for file read/write typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame); +#endif + +#if defined(ARDUINO_SAM_DUE) +// Arduino Due STL workaround +namespace std { + void __throw_bad_function_call(); +} #endif \ No newline at end of file diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 30d290e..aae170e 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -27,7 +27,7 @@ If not defined each Modbus object will have own registers set. #define MODBUS_USE_STL If defined C STL will be used. */ -#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) //|| defined(ARDUINO_SAM_DUE)clear +#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_SAM_DUE) #define MODBUS_USE_STL #endif From 6ced153e895867c4975a9d8309f07cd8c75299d2 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 13 Feb 2021 17:05:24 +0500 Subject: [PATCH 227/288] Temprary disable Arduino Due STL due to PlatformIO incompatibility --- src/Modbus.cpp | 2 +- src/Modbus.h | 2 +- src/ModbusRTU.cpp | 1 - src/ModbusSettings.h | 11 ++++++++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index d4e5006..ead48ab 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -838,7 +838,7 @@ bool Modbus::onRequestSuccess(cbRequest cb) { } #endif -#if defined(ARDUINO_SAM_DUE) +#if defined(ARDUINO_SAM_DUE_STL) namespace std { void __throw_bad_function_call() { Serial.println(F("STL ERROR - __throw_bad_function_call")); diff --git a/src/Modbus.h b/src/Modbus.h index 7c8ddf1..29ffd7b 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -285,7 +285,7 @@ typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame); #endif -#if defined(ARDUINO_SAM_DUE) +#if defined(ARDUINO_SAM_DUE_STL) // Arduino Due STL workaround namespace std { void __throw_bad_function_call(); diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 8f1972d..f1cabb1 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -5,7 +5,6 @@ https://github.com/emelianov/modbus-esp8266 This code is licensed under the BSD New License. See LICENSE.txt for more info. */ -#pragma once #include "ModbusRTU.h" // Table of CRC values diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index aae170e..24cf3df 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -23,11 +23,20 @@ If not defined each Modbus object will have own registers set. */ #define MODBUS_GLOBAL_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) +#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_SAM_DUE_STL) #define MODBUS_USE_STL #endif From c65b059971400e1462144cc1ab7a02d6934b3c7d Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 25 Mar 2021 16:37:37 +0500 Subject: [PATCH 228/288] Fix compile error issue #112 --- src/ModbusAPI.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 7a9a23f..94c2d8d 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -169,7 +169,7 @@ IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0, bool) IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, 0x007D, uint16_t) #if defined(MODBUS_ADD_REG) -#define ADDREG(R) this->addReg(R(to), 0, numregs); +#define ADDREG(R) this->addReg(R(to), (uint16_t)0, numregs); #else #define ADDREG(R) ; #endif From 89f9a5e5ca2d30e8a2eaa21e9c869b78d3be9b32 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 13 Apr 2021 18:06:43 +0300 Subject: [PATCH 229/288] Add EX_SLAVE_FAILURE on multiplr registers write error --- src/Modbus.cpp | 30 ++++++++++++++++++++++-------- src/Modbus.h | 4 ++-- src/ModbusSettings.h | 2 +- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index ead48ab..a2d4bf9 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -179,10 +179,13 @@ void Modbus::slavePDU(uint8_t* frame) { } } if (k >= field2) { - setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2); - successResponce(HREG(field1), field2, fcode); - _reply = REPLY_NORMAL; + 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; @@ -267,10 +270,13 @@ void Modbus::slavePDU(uint8_t* frame) { } } if (k >= field2) { - setMultipleBits(frame + 6, COIL(field1), field2); - successResponce(COIL(field1), field2, fcode); - _reply = REPLY_NORMAL; + 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) @@ -488,11 +494,14 @@ bool Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { return true; } -void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) { +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++; @@ -500,12 +509,17 @@ void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutp } startreg++; //increment the register } + return result; } -void Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { +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) { diff --git a/src/Modbus.h b/src/Modbus.h index 29ffd7b..6846e84 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -143,8 +143,8 @@ class Modbus { bool readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); bool readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); - void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); - void setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs); + 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); diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 24cf3df..483910d 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -37,7 +37,7 @@ Also note STL issue workaround code in Modbus.cpp 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 +//#define MODBUS_USE_STL #endif /* From 18a9bf5051c003d4c8bb8ed7665cf09093cb9f7b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 9 Jun 2021 14:15:16 +0300 Subject: [PATCH 230/288] Change on request callback API --- examples/Callback/Request/Request.ino | 4 +-- src/Modbus.cpp | 38 +++++++++++++-------------- src/Modbus.h | 22 +++++++++++++--- src/ModbusRTU.cpp | 9 +++++-- src/ModbusSettings.h | 2 +- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/examples/Callback/Request/Request.ino b/examples/Callback/Request/Request.ino index 967caab..fea3dea 100644 --- a/examples/Callback/Request/Request.ino +++ b/examples/Callback/Request/Request.ino @@ -18,14 +18,14 @@ const uint16_t RW_HREG = 200; // Sample Hreg //ModbusIP object ModbusTCP mb; -Modbus::ResultCode cbPreRequest(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount) { +Modbus::ResultCode cbPreRequest(Modbus::FunctionCode fc, const Modbus::RequestData data) { Serial.printf("PRE Function: %02X\n", fc); if ((fc == Modbus::FC_WRITE_REG || fc == Modbus::FC_WRITE_REGS) && mb.Coil(RO_FLAG)) return Modbus::EX_ILLEGAL_FUNCTION; return Modbus::EX_SUCCESS; } -Modbus::ResultCode cbPostRequest(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount) { +Modbus::ResultCode cbPostRequest(Modbus::FunctionCode fc, const Modbus::RequestData data) { Serial.printf("POST Function: %02X\n", fc); return Modbus::EX_SUCCESS; } diff --git a/src/Modbus.cpp b/src/Modbus.cpp index a2d4bf9..7e29415 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -132,7 +132,7 @@ void Modbus::slavePDU(uint8_t* frame) { switch (fcode) { case FC_WRITE_REG: //field1 = reg, field2 = value - ex = _onRequest(fcode, HREG(field1), field2); + ex = _onRequest(fcode, {HREG(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; @@ -146,24 +146,24 @@ void Modbus::slavePDU(uint8_t* frame) { return; } _reply = REPLY_ECHO; - _onRequestSuccess(fcode, HREG(field1), field2); + _onRequestSuccess(fcode, {HREG(field1), field2}); break; case FC_READ_REGS: //field1 = startreg, field2 = numregs, header len = 3 - ex = _onRequest(fcode, HREG(field1), field2); + ex = _onRequest(fcode, {HREG(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; } if (!readWords(HREG(field1), field2, fcode)) return; - _onRequestSuccess(fcode, HREG(field1), field2); + _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); + ex = _onRequest(fcode, {HREG(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; @@ -186,48 +186,48 @@ void Modbus::slavePDU(uint8_t* frame) { } successResponce(HREG(field1), field2, fcode); _reply = REPLY_NORMAL; - _onRequestSuccess(fcode, HREG(field1), field2); + _onRequestSuccess(fcode, {HREG(field1), field2}); break; case FC_READ_COILS: //field1 = startreg, field2 = numregs - ex = _onRequest(fcode, COIL(field1), field2); + ex = _onRequest(fcode, {COIL(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; } if (!readBits(COIL(field1), field2, fcode)) return; - _onRequestSuccess(fcode, COIL(field1), field2); + _onRequestSuccess(fcode, {COIL(field1), field2}); break; case FC_READ_INPUT_STAT: //field1 = startreg, field2 = numregs - ex = _onRequest(fcode, ISTS(field1), field2); + ex = _onRequest(fcode, {ISTS(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; } if (!readBits(ISTS(field1), field2, fcode)) return; - _onRequestSuccess(fcode, ISTS(field1), field2); + _onRequestSuccess(fcode, {ISTS(field1), field2}); break; case FC_READ_INPUT_REGS: //field1 = startreg, field2 = numregs - ex = _onRequest(fcode, IREG(field1), field2); + ex = _onRequest(fcode, {IREG(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; } if (!readWords(IREG(field1), field2, fcode)) return; - _onRequestSuccess(fcode, IREG(field1), field2); + _onRequestSuccess(fcode, {IREG(field1), field2}); break; case FC_WRITE_COIL: //field1 = reg, field2 = status, header len = 3 - ex = _onRequest(fcode, COIL(field1), field2); + ex = _onRequest(fcode, {COIL(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; @@ -247,12 +247,12 @@ void Modbus::slavePDU(uint8_t* frame) { return; } _reply = REPLY_ECHO; - _onRequestSuccess(fcode, COIL(field1), field2); + _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); + ex = _onRequest(fcode, {COIL(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; @@ -277,7 +277,7 @@ void Modbus::slavePDU(uint8_t* frame) { } successResponce(COIL(field1), field2, fcode); _reply = REPLY_NORMAL; - _onRequestSuccess(fcode, COIL(field1), field2); + _onRequestSuccess(fcode, {COIL(field1), field2}); break; #if defined(MODBUS_FILES) case FC_READ_FILE_REC: @@ -368,7 +368,7 @@ void Modbus::slavePDU(uint8_t* frame) { case FC_MASKWRITE_REG: { //field1 = reg, field2 = AND mask // Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) - ex = _onRequest(fcode, HREG(field1), field2); + ex = _onRequest(fcode, {HREG(field1), field2}); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; @@ -386,7 +386,7 @@ void Modbus::slavePDU(uint8_t* frame) { } } _reply = REPLY_ECHO; - _onRequestSuccess(fcode, HREG(field1), field2); + _onRequestSuccess(fcode, {HREG(field1), field2}); break; default: @@ -838,7 +838,7 @@ Modbus::ResultCode Modbus::fileOp(Modbus::FunctionCode fc, uint16_t fileNum, uin } #endif -Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount) { +Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, const RequestData data) { return EX_SUCCESS; } bool Modbus::onRequest(cbRequest cb) { diff --git a/src/Modbus.h b/src/Modbus.h index 6846e84..a3f2125 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -134,6 +134,22 @@ class Modbus { EX_CONNECTION_LOST = 0xE5, // Custom. Connection with device lost EX_CANCEL = 0xE6 // Custom. Transaction/request canceled }; + union RequestData { + struct { + TAddress reg; + uint16_t regCount; + }; + struct { + TAddress regRead; + uint16_t regReadCount; + TAddress regWrite; + uint16_t regWriteCount; + }; + struct { + uint16_t fileNum; + }; + }; + ~Modbus(); void cbEnable(bool state = true); @@ -230,13 +246,13 @@ class Modbus { virtual uint32_t eventSource() {return 0;} #if defined(MODBUS_USE_STL) - typedef std::function cbRequest; // Callback function Type + typedef std::function cbRequest; // Callback function Type #else - typedef ResultCode (*cbRequest)(FunctionCode fc, TAddress reg, uint16_t regCount); // Callback function Type + typedef ResultCode (*cbRequest)(FunctionCode fc, const RequestData data); // Callback function Type #endif protected: - static ResultCode _onRequestDefault(FunctionCode fc, TAddress reg, uint16_t regCount); + static ResultCode _onRequestDefault(FunctionCode fc, const RequestData data); cbRequest _onRequest = _onRequestDefault; public: bool onRequest(cbRequest cb = _onRequestDefault); diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index f1cabb1..9f623b2 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -64,17 +64,22 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { delayMicroseconds(1000); } #if defined(ESP32) - portENTER_CRITICAL(&mux); + //portENTER_CRITICAL(&mux); + vTaskDelay(1); #endif _port->write(slaveId); //Send slaveId _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC + #if defined(ESP32) + vTaskSuspendAll(); + #endif _port->flush(); if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); #if defined(ESP32) - portEXIT_CRITICAL(&mux); + xTaskResumeAll(); + //portEXIT_CRITICAL(&mux); #endif //delay(_t); return true; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 483910d..24cf3df 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -37,7 +37,7 @@ Also note STL issue workaround code in Modbus.cpp 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 +#define MODBUS_USE_STL #endif /* From 735f020bb97ceb4f1558394229abcc0f84c6af59 Mon Sep 17 00:00:00 2001 From: TienHuyIoTLab Date: Sun, 27 Jun 2021 15:52:30 +0700 Subject: [PATCH 231/288] Fix Esp32 crash and master broadcast ID failure Details: - _slaveId must be difference 0 value to cleanup function run and return true. - Not using vTaskSuspendAll() and xTaskResumeAll() by redefine ESP32 to ESP32_REMOVE. - Update example ESP32-Concurent.ino and using Tool Modbus Slave on PC for test. Refs: - Tool Modbus Slave link: https://www.modbustools.com/download.html - ESP32 Arduino Release 1.0.6 based on ESP-IDF v3.3.5 https://github.com/espressif/arduino-esp32/releases/tag/1.0.6 --- .../RTU/ESP32-Concurent/ESP32-Concurent.ino | 43 +++++++++++-------- src/ModbusRTU.cpp | 18 ++++---- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino index b75557a..c94bc01 100644 --- a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -4,31 +4,38 @@ (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 + + Tool Modbus Slave on PC for test + https://www.modbustools.com/download.html */ #include -#define REG1 1 -#define REG2 2 -#define SLAVE_ID 1 +#define REG 0 +#define REG_NUM 32 +#define SLAVE_ID1 51 +#define SLAVE_ID2 52 + +#define MBUS_HW_SERIAL Serial1 +#define MBUS_TXD_PIN 16 +#define MBUS_RXD_PIN 35 ModbusRTU mb; xSemaphoreHandle xMutex; Modbus::ResultCode err; -bool resCallback(Modbus::ResultCode event, uint16_t, void*) { - err = event; -} - Modbus::ResultCode readSync(uint16_t Address, uint16_t start, uint16_t num, uint16_t* buf) { xSemaphoreTake(xMutex, portMAX_DELAY); if (mb.slave()){ xSemaphoreGive(xMutex); return Modbus::EX_GENERAL_FAILURE; } - Serial.printf("SlaveID: %d Hreg %d\n", Address, start); - mb.readHreg(Address, start, buf, num, resCallback); + Serial.printf("SlaveID: %d Hreg %d\r\n", Address, start); + mb.readIreg(Address, start, buf, num, [](Modbus::ResultCode event, uint16_t, void*) { + err = event; + return true; + }); while (mb.slave()) { vTaskDelay(1); mb.task(); @@ -43,8 +50,8 @@ void loop2( void * pvParameters ); void setup() { Serial.begin(115200); - Serial1.begin(9600, SERIAL_8N1, 19, 18); - mb.begin(&Serial1, 17); + MBUS_HW_SERIAL.begin(9600, SERIAL_8N1, MBUS_RXD_PIN, MBUS_TXD_PIN); + mb.begin(&MBUS_HW_SERIAL); mb.master(); xMutex = xSemaphoreCreateMutex(); xTaskCreatePinnedToCore( @@ -67,23 +74,25 @@ void setup() { } -uint16_t hregs1[10]; +uint16_t hregs1[REG_NUM]; void loop1( void * pvParameters ){ while(true) { delay(10); - if (readSync(1, 1, 10, hregs1) != Modbus::EX_SUCCESS) - Serial.print("Error 1"); + if (readSync(SLAVE_ID1, REG, REG_NUM, hregs1) == Modbus::EX_SUCCESS) + Serial.println("OK 2"); + else + Serial.println("Error 2"); } } -uint16_t hregs2[10]; +uint16_t hregs2[REG_NUM]; void loop2( void * pvParameters ){ while(true) { delay(100); - if (readSync(1, 2, 10, hregs2) == Modbus::EX_SUCCESS) + if (readSync(SLAVE_ID2, REG, REG_NUM, hregs2) == Modbus::EX_SUCCESS) Serial.println("OK 2"); else - Serial.print("Error 2"); + Serial.println("Error 2"); } } diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 9f623b2..36c5e47 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -63,7 +63,7 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { digitalWrite(_txPin, _direct?HIGH:LOW); delayMicroseconds(1000); } - #if defined(ESP32) + #if defined(ESP32_REMOVE) //portENTER_CRITICAL(&mux); vTaskDelay(1); #endif @@ -71,13 +71,13 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC - #if defined(ESP32) + #if defined(ESP32_REMOVE) vTaskSuspendAll(); #endif _port->flush(); if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); - #if defined(ESP32) + #if defined(ESP32_REMOVE) xTaskResumeAll(); //portEXIT_CRITICAL(&mux); #endif @@ -90,7 +90,7 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti if (!_slaveId) { // Check if waiting for previous request result rawSend(slaveId, _frame, _len); if (waitResponse) { - _slaveId = slaveId; + _slaveId = std::max(slaveId, (uint8_t)1); _timestamp = millis(); _cb = cb; _data = data; @@ -107,7 +107,7 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti } void ModbusRTUTemplate::task() { - #if defined(ESP32) + #if defined(ESP32_REMOVE) //taskENTER_CRITICAL(&mux); vTaskSuspendAll(); #endif @@ -116,7 +116,7 @@ void ModbusRTUTemplate::task() { t = millis(); } if (_len == 0) { - #if defined(ESP32) + #if defined(ESP32_REMOVE) //taskEXIT_CRITICAL(&mux); xTaskResumeAll(); #endif @@ -125,7 +125,7 @@ void ModbusRTUTemplate::task() { } if (isMaster) { if (millis() - t < _t) { - #if defined(ESP32) + #if defined(ESP32_REMOVE) //taskEXIT_CRITICAL(&mux); xTaskResumeAll(); #endif @@ -140,7 +140,7 @@ void ModbusRTUTemplate::task() { t = millis(); } if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long - #if defined(ESP32) + #if defined(ESP32_REMOVE) //taskEXIT_CRITICAL(&mux); xTaskResumeAll(); #endif @@ -148,7 +148,7 @@ void ModbusRTUTemplate::task() { } } } - #if defined(ESP32) + #if defined(ESP32_REMOVE) //taskEXIT_CRITICAL(&mux); xTaskResumeAll(); #endif From 8bd78406ebeb3534c9d967e373fe58e05ccfe07e Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 3 Jul 2021 16:09:34 +0300 Subject: [PATCH 232/288] Fix memory leak on broadcast with transactional callback --- src/ModbusRTU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 9f623b2..6214b27 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -89,7 +89,7 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti bool result = false; if (!_slaveId) { // Check if waiting for previous request result rawSend(slaveId, _frame, _len); - if (waitResponse) { + if (waitResponse && slaveId) { _slaveId = slaveId; _timestamp = millis(); _cb = cb; From 02511107cdefeab4a5560fee8f885031257c9a2d Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 18 Jul 2021 18:27:16 +0500 Subject: [PATCH 233/288] Fix ESP32 RTU - Remove vSuspendTask commited by excident - Fix potential crash in low memory conditions - Misc examples changes --- examples/Callback/README.md | 20 ++++++++++++++++++-- examples/RTU/README.MD | 8 ++++---- src/ModbusRTU.cpp | 17 +++++++---------- tests/common.h | 10 ++++++++-- tests/tests.ino | 4 ++++ 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/examples/Callback/README.md b/examples/Callback/README.md index cb4a89e..2ca7fae 100644 --- a/examples/Callback/README.md +++ b/examples/Callback/README.md @@ -51,12 +51,28 @@ Disconnect specific callback function or all callbacks of the type if cb=NULL. ## [Incoming request callback (applicable to server/slave)](Request/Request.ino) ```c -typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode fc, TAddress reg, uint16_t regCount); +typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode fc, const Modbus::RequestData data); bool onRequest(cbRequest cb = _onRequestDefault); bool onRequestSuccess(cbRequest cb = _onRequestDefault); + +union Modbus::RequestData { + struct { + TAddress reg; + uint16_t regCount; + }; + struct { + TAddress regRead; + uint16_t regReadCount; + TAddress regWrite; + uint16_t regWriteCount; + }; + struct { + uint16_t fileNum; + }; + }; ``` -Callback function receives Modbus function code, register type and offset (`TAddress` structure) and count of registers requested. The function should return [result code](#Result codes *Modbus::ResultCode*) `Modbus::EX_SUCCESS` to allow request processing or Modbus error code to block processing. This code will be returned to client/master. +Callback function receives Modbus function code, structure `Modbus::RequestData` containing register type and offset (`TAddress` structure) and count of registers requested. The function should return [result code](#Result codes *Modbus::ResultCode*) `Modbus::EX_SUCCESS` to allow request processing or Modbus error code to block processing. This code will be returned to client/master. ## [Modbus TCP/TLS Incoming connection callback](onSet/onSet.ino) diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index 3a6c259..cdd8d56 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,12 +1,12 @@ -## (Concurent thread-safe access to Modbus object)[ESP32-Concurent] +## [Concurent thread-safe access to Modbus object](ESP32-Concurent) This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally thre is [example of master](WSP32-Concurent) device for multithread usage with ESP32. -## (Simple ModbusRTU master)[master] +## [Simple ModbusRTU master](master) -## (Simple ModbusRTU slave)[slave] +## [Simple ModbusRTU slave](slave) -## (Sync ModbusRTU master)[masterSync] +## [Sync ModbusRTU master](masterSync) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 9f623b2..77c6f3c 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -64,22 +64,18 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { delayMicroseconds(1000); } #if defined(ESP32) - //portENTER_CRITICAL(&mux); - vTaskDelay(1); + //vTaskDelay(1); + portENTER_CRITICAL(&mux); #endif _port->write(slaveId); //Send slaveId _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC - #if defined(ESP32) - vTaskSuspendAll(); - #endif _port->flush(); if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); #if defined(ESP32) - xTaskResumeAll(); - //portEXIT_CRITICAL(&mux); + portEXIT_CRITICAL(&mux); #endif //delay(_t); return true; @@ -87,9 +83,9 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { bool result = false; - if (!_slaveId) { // Check if waiting for previous request result + if (!_slaveId && _len && _frame) { // Check if waiting for previous request result and _frame filled rawSend(slaveId, _frame, _len); - if (waitResponse) { + if (waitResponse && slaveId) { _slaveId = slaveId; _timestamp = millis(); _cb = cb; @@ -179,7 +175,8 @@ void ModbusRTUTemplate::task() { for (uint8_t i=0 ; i < _len ; i++) { _frame[i] = _port->read(); // read data + crc #if defined(MODBUSRTU_DEBUG) - Serial.printf("%02X ", _frame[i]); + Serial.print(_frame[i], HEX); + Serial.print(" "); #endif } #if defined(MODBUSRTU_DEBUG) diff --git a/tests/common.h b/tests/common.h index 693c358..8ce91cb 100644 --- a/tests/common.h +++ b/tests/common.h @@ -8,16 +8,22 @@ #pragma once #include +#define HW_SERIAL #define BSIZE 1024 +#if defined(HW_SERIAL) +#define P1 Serial1 +#define P2 Serial2 +#else uint8_t buf1[BSIZE]; uint8_t buf2[BSIZE]; StreamBuf S1(buf1, BSIZE); StreamBuf S2(buf2, BSIZE); -DuplexBuf D1(&S1, &S2); -DuplexBuf D2(&S2, &S1); +DuplexBuf P1(&S1, &S2); +DuplexBuf P2(&S2, &S1); +#endif ModbusRTU master; ModbusRTU slave; diff --git a/tests/tests.ino b/tests/tests.ino index c303b2c..495a084 100644 --- a/tests/tests.ino +++ b/tests/tests.ino @@ -19,6 +19,10 @@ uint16_t readHreg = 0; void setup() { Serial.begin(115200); Serial.println("ModbusRTU API test"); +#if defiend(HW_SERIAL) + Serial1.begin(115200, SERIAL_8N1, 18, 19); + Serial2.begin(115200, SERIAL_8N1, 22, 23); +#endif delay(100); master.begin(&P1); master.master(); From 803e76233c482d92375efa6bf48410ec283b0f1d Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 19 Jul 2021 07:54:41 +0500 Subject: [PATCH 234/288] FC_READWRITE_REGS implementation --- README.md | 16 +++++---- src/Modbus.cpp | 29 +++++++++++++++- src/Modbus.h | 13 +++++++ src/ModbusAPI.h | 83 +++++++++++++++++++++++++++++--------------- src/ModbusRTU.cpp | 7 ++++ src/ModbusSettings.h | 2 +- tests/tests.ino | 8 +++++ 7 files changed, 122 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index c4cdf20..bee4635 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ The Modbus generally uses serial RS-485 as physical layer (then called Modbus Se * Supports all Arduino platforms * Operates in any combination of multiple instances of - * [Modbus RTU slave](examples/RTU) - * [Modbus RTU master](examples/RTU) + * [Modbus RTU server](examples/RTU) + * [Modbus RTU client](examples/RTU) * Modbus TCP server for [ESP8266/ESP32](examples/TCP) and [Ethernet library](examples/TCP-Ethernet) * Modbus TCP client for [ESP8266/ESP32](examples/TCP) and [Ethernet library](examples/TCP-Ethernet) * [MODBUS/TCP Security server (ESP8266)](examples/TLS) @@ -37,12 +37,14 @@ The Modbus generally uses serial RS-485 as physical layer (then called Modbus Se * 0x14 - Read File Record * 0x15 - Write File Record * 0x16 - Mask Write Register + * 0x17 - Read/Write multiple registers * [Callbacks](examples/callback) for * Client connect (Modbus TCP) * Server/Client disconnect (Modbus TCP) * Read specific Register * Write specific Register - * Slave transaction finish + * Transaction result (Client side) + * Transaction start/end (Server side) ## Notes @@ -71,7 +73,7 @@ For more information about Modbus see: + ModbusTLS: ESP32 Client - Test: TLS ESP32 Client + Build with no STL dependency -- Test: No-STL mode ++ Test: No-STL mode + ModbusTCP: ModbusEthernet - W5x00 Ethernet library support + Test: W5x00 support - Test: W5x00 with Ethernet library v1 @@ -87,7 +89,7 @@ For more information about Modbus see: - Examples: FW update + 0x16 - Write Mask Register function - Test: 0x16 -- 0x17 - Read/Write Registers function ++ 0x17 - Read/Write Registers function - Test: 0x17 + API: Access control callback for individual Modbus function - Slave/Server: slavePDU use early exit by return where possible @@ -97,6 +99,8 @@ For more information about Modbus see: - Test: Frame accuracy to specefication - Documentation: Update - Examples: Revising +- Remove unneeded register count check in private functions +- Check startreg + numreg < 65535 + ModbusRTU: ESP32 SoftwareSerial support + ModbusRTU: Fix transaction callback remains assigned after request end + ModbusTCP: Free server connection in destructor @@ -104,7 +108,7 @@ For more information about Modbus see: - Free global registers and callbacks on remove last Modbus instance + ModbusRTU: Refactor .task() for more relaibe processing of incoming data + API: Declare all callbacks as std::function (for STL) -- API: Msater/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) +- API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) // 4.1.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 7e29415..f9fb247 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -126,6 +126,8 @@ 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; @@ -375,7 +377,7 @@ void Modbus::slavePDU(uint8_t* frame) { } uint16_t orMask = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; uint16_t val = Reg(HREG(field1)); - val = (val && field2) || (orMask && !field2); + val = (val & field2) | (orMask & !field2); if (!Reg(HREG(field1), val)) { //Check Address and execute (reg exists?) exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); return; @@ -388,6 +390,30 @@ void Modbus::slavePDU(uint8_t* frame) { _reply = REPLY_ECHO; _onRequestSuccess(fcode, {HREG(field1), field2}); 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 || frame[9] != 2 * field4) { //Check value + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + if (!setMultipleWords((uint16_t*)(frame + 10), HREG(field1), field2)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + + if (!readWords(HREG(field3), field4, fcode)) + return; + + _onRequestSuccess(fcode, {HREG(field1), field2, HREG(field3), field4}); + break; default: exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); @@ -697,6 +723,7 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, 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; diff --git a/src/Modbus.h b/src/Modbus.h index a3f2125..c80788c 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -148,6 +148,19 @@ class Modbus { struct { uint16_t fileNum; }; + 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(uint16_t f) { + fileNum = f; + }; }; ~Modbus(); diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 94c2d8d..252763d 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -124,8 +124,8 @@ class ModbusAPI : public T { template uint16_t maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - //template - //uint16_t readWriteHreg(uint8_t slaveId, uint16_t readOffset, uint16_t* value, uint16_t numregs, uint16_t writeOffset, uint16_t* value, uint16_t numregs, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + 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); }; // FNAME writeCoil, writeIsts, writeHreg, writeIreg @@ -334,37 +334,64 @@ bool ModbusAPI::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numreg template \ template \ uint16_t ModbusAPI::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { - if (startRec > 0x270F) return 0; - if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0; - return this->send(slaveId, HREG(0), cb, unit, data); // HREG(0) - just dummy value - }; + if (startRec > 0x270F) return 0; + if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0; + return this->send(slaveId, HREG(0), cb, unit, data); // HREG(0) - just dummy value +}; template \ template \ uint16_t ModbusAPI::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { - if (startRec > 0x270F) return 0; - if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0; - return this->send(slaveId, HREG(0), cb, unit); // HREG(0) - just dummy value - }; + if (startRec > 0x270F) return 0; + if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0; + return this->send(slaveId, HREG(0), cb, unit); // HREG(0) - just dummy value +}; template \ template \ uint16_t ModbusAPI::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, nullptr, cb); - }; -/* + 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, nullptr, cb); +}; + template \ template \ -uint16_t ModbusAPI::readWriteHreg(TYPEID slaveId, - uint16_t readOffset, uint16_t* value, uint16_t numregs, - uint16_t writeOffset, uint16_t* value, uint16_t numregs, - cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); -*/ \ No newline at end of file +uint16_t ModbusAPI::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); +}; diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 77c6f3c..b526461 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -59,6 +59,13 @@ bool ModbusRTUTemplate::begin(Stream* port) { 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 (_txPin >= 0) { digitalWrite(_txPin, _direct?HIGH:LOW); delayMicroseconds(1000); diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 24cf3df..f5879e5 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -63,7 +63,7 @@ If defined regisers count will be limited. #define MODBUSIP_MAX_READMS 100 #define MODBUSIP_FULL -//#define MODBUSRTU_DEBUG +#define MODBUSRTU_DEBUG #define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 diff --git a/tests/tests.ino b/tests/tests.ino index 495a084..0d284a9 100644 --- a/tests/tests.ino +++ b/tests/tests.ino @@ -41,6 +41,14 @@ readMultiple(SLAVE_ID, COIL(HREG_ID), 10); readMultiple(SLAVE_ID, IREG(HREG_ID), 10); readMultiple(SLAVE_ID, ISTS(HREG_ID), 10); +{ + uint16_t rd[10]; + uint16_t wr[10]; + slave.addHreg(110, 10); + slave.addHreg(120, 20); + master.readWriteHreg(SLAVE_ID, 110, rd, 10, 120, wr, 10, cbWrite); + wait(); +} // Garbage read { bool Node_1_ackStatus = false; From e100eef4b944e58f5134417e1b4d7112317c741d Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 21 Jul 2021 22:18:48 +0500 Subject: [PATCH 235/288] Slave core refactoring - "Magic" returns from internal readWords/readBits eliminated - Check out of range on incoming requests - Fix possible out of range effects on adding registers --- README.md | 6 +- keywords.txt | 27 ++++- src/Modbus.cpp | 235 +++++++++++++++++++++++++------------------ src/Modbus.h | 6 +- src/ModbusAPI.h | 2 +- src/ModbusRTU.cpp | 2 +- src/ModbusRTU.h | 2 +- src/ModbusSettings.h | 6 +- 8 files changed, 174 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index bee4635..51876e4 100644 --- a/README.md +++ b/README.md @@ -99,14 +99,16 @@ For more information about Modbus see: - Test: Frame accuracy to specefication - Documentation: Update - Examples: Revising -- Remove unneeded register count check in private functions -- Check startreg + numreg < 65535 ++ Remove unneeded register count check in private functions ++ Check startreg + numreg < 65535 + ModbusRTU: ESP32 SoftwareSerial support + ModbusRTU: Fix transaction callback remains assigned after request end + ModbusTCP: Free server connection in destructor + Declare global registers and callbacks as static members - Free global registers and callbacks on remove last Modbus instance + ModbusRTU: Refactor .task() for more relaibe processing of incoming data +- ModbusRTU: Static buffer allocation. +- Buffer/packet size limitation support + API: Declare all callbacks as std::function (for STL) - API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) // 4.1.0 diff --git a/keywords.txt b/keywords.txt index 8701a0f..67686fb 100644 --- a/keywords.txt +++ b/keywords.txt @@ -3,6 +3,7 @@ # Datatypes (KEYWORD1) ModbusRTU KEYWORD1 ModbusIP KEYWORD1 +ModbusTCP KEYWORD1 ModbusIP_ESP8266 KEYWORD1 Modbus KEYWORD1 TRegister KEYWORD1 @@ -11,8 +12,6 @@ TAddress KEYWORD1 ResultCode KEYWORD1 # Methods and Functions (KEYWORD2) -master KEYWORD2 -slave KEYWORD2 client KEYWORD2 server KEYWORD2 task KEYWORD2 @@ -76,6 +75,17 @@ isIsts KEYWORD2 isIreg KEYWORD2 begin KEYWORD2 setBaudrate KEYWORD2 +readFileRec KEYWORD2 +writeFileRec KEYWORD2 +maskHreg KEYWORD2 +readWriteHreg KEYWORD2 +onFile KEYWORD2 +onRequest KEYWORD2 +onRequestSuccess KEYWORD2 +onSet KEYWORD2 +onGet KEYWORD2 +removeOnSet KEYWORD2 +removeOnGet KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 @@ -106,4 +116,17 @@ COIL LITERAL1 HREG LITERAL1 ISTS LITERAL1 IREG LITERAL1 +FC_READ_COILS LITERAL1 +FC_READ_INPUT_STAT LITERAL1 +FC_READ_REGS LITERAL1 +FC_READ_INPUT_REGS LITERAL1 +FC_WRITE_COIL LITERAL1 +FC_WRITE_REG LITERAL1 +FC_DIAGNOSTICS LITERAL1 +FC_WRITE_COILS LITERAL1 +FC_WRITE_REGS LITERAL1 +FC_READ_FILE_REC LITERAL1 +FC_WRITE_FILE_REC LITERAL1 +FC_MASKWRITE_REG LITERAL1 +FC_READWRITE_REGS LITERAL1 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index f9fb247..f88fef5 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -2,7 +2,7 @@ Modbus Library for Arduino Core functions Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" @@ -60,8 +60,10 @@ TRegister* Modbus::searchRegister(TAddress address) { bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) { #if defined(MODBUS_MAX_REGS) - if (_regs.size() + numregs > MB_MAX_REGS) return false; + 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}); @@ -100,6 +102,8 @@ uint16_t Modbus::Reg(TAddress address) { 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) { @@ -117,6 +121,8 @@ bool Modbus::removeReg(TAddress address, uint16_t numregs) { } 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; @@ -158,8 +164,11 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, ex); return; } - if (!readWords(HREG(field1), field2, fcode)) + ex = readWords(HREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); return; + } _onRequestSuccess(fcode, {HREG(field1), field2}); break; @@ -170,7 +179,7 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, ex); return; } - if (field2 < 0x0001 || field2 > 0x007B || frame[5] != 2 * field2) { //Check value + if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || 0xFFFF - field1 < field2 || frame[5] != 2 * field2) { //Check constrains exceptionResponse(fcode, EX_ILLEGAL_VALUE); return; } @@ -180,11 +189,9 @@ void Modbus::slavePDU(uint8_t* frame) { return; } } - if (k >= field2) { - if (!setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2)) { - exceptionResponse(fcode, EX_SLAVE_FAILURE); - return; - } + if (!setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; } successResponce(HREG(field1), field2, fcode); _reply = REPLY_NORMAL; @@ -198,8 +205,11 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, ex); return; } - if (!readBits(COIL(field1), field2, fcode)) + ex = readBits(COIL(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); return; + } _onRequestSuccess(fcode, {COIL(field1), field2}); break; @@ -210,8 +220,11 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, ex); return; } - if (!readBits(ISTS(field1), field2, fcode)) + ex = readBits(ISTS(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); return; + } _onRequestSuccess(fcode, {ISTS(field1), field2}); break; @@ -222,8 +235,11 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, ex); return; } - if (!readWords(IREG(field1), field2, fcode)) + ex = readWords(IREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); return; + } _onRequestSuccess(fcode, {IREG(field1), field2}); break; @@ -238,12 +254,10 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, EX_ILLEGAL_VALUE); return; } - //if (!Coil(field1, COIL_BOOL(field2))) { //Check Address and execute (reg exists?) if (!Reg(COIL(field1), field2)) { //Check Address and execute (reg exists?) exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); return; } - //if (Coil(field1) != COIL_BOOL(field2)) { //Check for failure if (Reg(COIL(field1)) != field2) { //Check for failure exceptionResponse(fcode, EX_SLAVE_FAILURE); return; @@ -261,7 +275,7 @@ void Modbus::slavePDU(uint8_t* frame) { } bytecount_calc = field2 / 8; if (field2%8) bytecount_calc++; - if (field2 < 0x0001 || field2 > 0x07B0 || frame[5] != bytecount_calc) { //Check registers range and data size maches + 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; } @@ -271,11 +285,9 @@ void Modbus::slavePDU(uint8_t* frame) { return; } } - if (k >= field2) { - if (!setMultipleBits(frame + 6, COIL(field1), field2)) { - exceptionResponse(fcode, EX_SLAVE_FAILURE); - return; - } + if (!setMultipleBits(frame + 6, COIL(field1), field2)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; } successResponce(COIL(field1), field2, fcode); _reply = REPLY_NORMAL; @@ -367,7 +379,7 @@ void Modbus::slavePDU(uint8_t* frame) { _reply = REPLY_ECHO; break; #endif - case FC_MASKWRITE_REG: { + case FC_MASKWRITE_REG: //field1 = reg, field2 = AND mask // Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) ex = _onRequest(fcode, {HREG(field1), field2}); @@ -375,20 +387,21 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, ex); return; } - uint16_t orMask = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; - uint16_t val = Reg(HREG(field1)); - val = (val & field2) | (orMask & !field2); - if (!Reg(HREG(field1), val)) { //Check Address and execute (reg exists?) - exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); - return; - } - if (Reg(HREG(field1)) != val) { //Check for failure - exceptionResponse(fcode, EX_SLAVE_FAILURE); - return; + { + uint16_t orMask = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; + uint16_t val = Reg(HREG(field1)); + val = (val & field2) | (orMask & !field2); + if (!Reg(HREG(field1), val)) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + if (Reg(HREG(field1)) != val) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } } - } - _reply = REPLY_ECHO; - _onRequestSuccess(fcode, {HREG(field1), field2}); + _reply = REPLY_ECHO; + _onRequestSuccess(fcode, {HREG(field1), field2}); break; case FC_READWRITE_REGS: //field1 = readreg, field2 = read count, frame[9] = data lenght, header len = 10 @@ -400,7 +413,10 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, ex); return; } - if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || field4 < 0x0001 || field4 > MODBUS_MAX_WORDS || frame[9] != 2 * field4) { //Check value + 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; } @@ -408,10 +424,11 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, EX_SLAVE_FAILURE); return; } - - if (!readWords(HREG(field3), field4, fcode)) + ex = readWords(HREG(field3), field4, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); return; - + } _onRequestSuccess(fcode, {HREG(field1), field2, HREG(field3), field4}); break; @@ -425,6 +442,10 @@ void Modbus::successResponce(TAddress startreg, uint16_t numoutputs, FunctionCod 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; @@ -436,6 +457,10 @@ 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; @@ -464,60 +489,62 @@ void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numre } } -bool Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { - if (numregs < 0x0001 || numregs > 0x07D0) { //Check value (numregs) - exceptionResponse(fn, EX_ILLEGAL_VALUE); - return false; - } +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_VALUE; //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 (!searchRegister(startreg)) { - exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return false; +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_VALUE; } +#else + if (!searchRegister(startreg)) + return EX_ILLEGAL_VALUE; +#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) { - exceptionResponse(fn, EX_SLAVE_FAILURE); - return false; - } + 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 true; + return EX_SUCCESS; } -bool Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { +Modbus::ResultCode Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x007D) { - exceptionResponse(fn, EX_ILLEGAL_VALUE); - return false; - } - if (!searchRegister(startreg)) { //Check Address - exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return false; + if (numregs < 0x0001 || numregs > MODBUS_MAX_WORDS || 0xFFFF - startreg.address < numregs) + return EX_ILLEGAL_VALUE; +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_VALUE; } +#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) { - exceptionResponse(fn, EX_SLAVE_FAILURE); - return false; - } + 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 true; + return EX_SUCCESS; } bool Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) { @@ -620,6 +647,10 @@ 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; @@ -633,49 +664,49 @@ bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, Fu _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) { - _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; + 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_OFF; - return false; + _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) { - _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); + 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]); } - return true; + } else { + getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs); } - _reply = REPLY_OFF; - return false; + return true; } void Modbus::boolToBits(uint8_t* dst, bool* src, uint16_t numregs) { @@ -713,11 +744,15 @@ void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output) { uint8_t fcode = frame[0]; - _reply = EX_SUCCESS; - if ((fcode & 0x80) != 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) { diff --git a/src/Modbus.h b/src/Modbus.h index c80788c..7d77dad 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -2,7 +2,7 @@ Modbus Library for Arduino Core functions Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once #include "ModbusSettings.h" @@ -169,8 +169,8 @@ class Modbus { void cbDisable(); private: - bool readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); - bool readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); + 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); diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 252763d..e175089 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -2,7 +2,7 @@ Modbus Library for Arduino Modbus public API implementation Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once #include "Modbus.h" diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index b526461..bd914b2 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -1,7 +1,7 @@ /* Modbus Library for Arduino ModbusRTU implementation - Copyright (C) 2019-2020 Alexander Emelianov (a.m.emelianov@gmail.com) + Copyright (C) 2019-2021 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. */ diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 76d0f8f..028ef9f 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,7 +1,7 @@ /* Modbus Library for Arduino ModbusRTU - Copyright (C) 2019-2020 Alexander Emelianov (a.m.emelianov@gmail.com) + Copyright (C) 2019-2021 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. */ diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index f5879e5..03d516d 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -2,7 +2,7 @@ /* Modbus Library for Arduino - Copyright (C) 2019-2020 Alexander Emelianov (a.m.emelianov@gmail.com) + Copyright (C) 2019-2021 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. @@ -22,6 +22,7 @@ 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 @@ -47,6 +48,7 @@ If defined regisers count will be limited. //#define MODBUS_MAX_REGS 32 #define MODBUS_ADD_REG +//#define MODBUS_STRICT_REG #define MODBUS_MAX_FRAME 256 //#define MODBUS_STATIC_FRAME #define MODBUS_MAX_WORDS 0x007D @@ -63,7 +65,7 @@ If defined regisers count will be limited. #define MODBUSIP_MAX_READMS 100 #define MODBUSIP_FULL -#define MODBUSRTU_DEBUG +//#define MODBUSRTU_DEBUG #define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 From 6066bbf47dab9afee9590f27360d5e489ac84a12 Mon Sep 17 00:00:00 2001 From: escherstair Date: Fri, 30 Jul 2021 18:54:48 +0200 Subject: [PATCH 236/288] Fix links in README.md (#137) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51876e4..d35caff 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ The Modbus generally uses serial RS-485 as physical layer (then called Modbus Se * Operates in any combination of multiple instances of * [Modbus RTU server](examples/RTU) * [Modbus RTU client](examples/RTU) - * Modbus TCP server for [ESP8266/ESP32](examples/TCP) and [Ethernet library](examples/TCP-Ethernet) - * Modbus TCP client for [ESP8266/ESP32](examples/TCP) and [Ethernet library](examples/TCP-Ethernet) + * Modbus TCP server for [ESP8266/ESP32](examples/TCP-ESP) and [Ethernet library](examples/TCP-Ethernet) + * Modbus TCP client for [ESP8266/ESP32](examples/TCP-ESP) and [Ethernet library](examples/TCP-Ethernet) * [MODBUS/TCP Security server (ESP8266)](examples/TLS) * [MODBUS/TCP Security client (ESP8266/ESP32)](examples/TLS) * Reply exception messages for all supported functions @@ -38,7 +38,7 @@ The Modbus generally uses serial RS-485 as physical layer (then called Modbus Se * 0x15 - Write File Record * 0x16 - Mask Write Register * 0x17 - Read/Write multiple registers -* [Callbacks](examples/callback) for +* [Callbacks](examples/Callback) for * Client connect (Modbus TCP) * Server/Client disconnect (Modbus TCP) * Read specific Register From ecd31675d9e3352382b7ce29a95c96ddcb571475 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 1 Aug 2021 09:31:57 +0500 Subject: [PATCH 237/288] Delete ModbusRTU.cpp --- src/ModbusRTU.cpp | 241 ---------------------------------------------- 1 file changed, 241 deletions(-) delete mode 100644 src/ModbusRTU.cpp diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp deleted file mode 100644 index 36c5e47..0000000 --- a/src/ModbusRTU.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* - Modbus Library for Arduino - ModbusRTU implementation - Copyright (C) 2019-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. -*/ -#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; -} - -void ModbusRTUTemplate::setBaudrate(uint32_t baud) { - if (baud > 19200) { - _t = 2; - } else { - _t = (35000/baud) + 1; - } -} - -bool ModbusRTUTemplate::begin(Stream* port) { - _port = port; - _t = 2; - return true; -} - -bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { - uint16_t newCrc = crc16(slaveId, frame, len); - if (_txPin >= 0) { - digitalWrite(_txPin, _direct?HIGH:LOW); - delayMicroseconds(1000); - } - #if defined(ESP32_REMOVE) - //portENTER_CRITICAL(&mux); - vTaskDelay(1); - #endif - _port->write(slaveId); //Send slaveId - _port->write(frame, len); // Send PDU - _port->write(newCrc >> 8); //Send CRC - _port->write(newCrc & 0xFF);//Send CRC - #if defined(ESP32_REMOVE) - vTaskSuspendAll(); - #endif - _port->flush(); - if (_txPin >= 0) - digitalWrite(_txPin, _direct?LOW:HIGH); - #if defined(ESP32_REMOVE) - xTaskResumeAll(); - //portEXIT_CRITICAL(&mux); - #endif - //delay(_t); - 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 (!_slaveId) { // Check if waiting for previous request result - rawSend(slaveId, _frame, _len); - if (waitResponse) { - _slaveId = std::max(slaveId, (uint8_t)1); - _timestamp = millis(); - _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_REMOVE) - //taskENTER_CRITICAL(&mux); - vTaskSuspendAll(); - #endif - if (_port->available() > _len) { - _len = _port->available(); - t = millis(); - } - if (_len == 0) { - #if defined(ESP32_REMOVE) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif - if (isMaster) cleanup(); - return; - } - if (isMaster) { - if (millis() - t < _t) { - #if defined(ESP32_REMOVE) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif - return; - } - } - else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached) - uint32_t taskStart = millis(); - while (millis() - t < _t) { // Wait data whitespace - if (_port->available() > _len) { - _len = _port->available(); - t = millis(); - } - if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long - #if defined(ESP32_REMOVE) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif - return; - } - } - } - #if defined(ESP32_REMOVE) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif - - uint8_t address = _port->read(); //first byte of frame = address - _len--; // Decrease by slaveId byte - if (isMaster && _slaveId == 0) { // Check if slaveId is set - for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected - _len = 0; - //if (isMaster) cleanup(); - return; - } - if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check - 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.printf("%02X ", _frame[i]); - #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 - free(_frame); - _frame = nullptr; - _len = 0; - if (isMaster) cleanup(); - return; - } - if (isMaster) { - _reply = EX_SUCCESS; - if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested - // Procass incoming frame as master - 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 { - slavePDU(_frame); - if (address == MODBUSRTU_BROADCAST) - _reply = Modbus::REPLY_OFF; // No reply for Broadcasts - if (_reply != Modbus::REPLY_OFF) - rawSend(_slaveId, _frame, _len); - } - // Cleanup - free(_frame); - _frame = nullptr; - _len = 0; - if (isMaster) cleanup(); -} - -bool ModbusRTUTemplate::cleanup() { - // Remove timeouted request and forced event - if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { - if (_cb) { - _cb(Modbus::EX_TIMEOUT, 0, nullptr); - _cb = nullptr; - } - free(_sentFrame); - _sentFrame = nullptr; - _data = nullptr; - _slaveId = 0; - return true; - } - return false; -} \ No newline at end of file From 4d49276aba9bf5281bf9e5f4aeb7a9c81faea25c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 1 Aug 2021 09:41:08 +0500 Subject: [PATCH 238/288] Documentation changes --- README.md | 27 ++++++++++--------- .../RTU/ESP32-Concurent/ESP32-Concurent.ino | 6 ++--- examples/RTU/README.MD | 13 +++++---- examples/TCP-ESP/README.md | 2 +- src/Modbus.h | 8 +++--- 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 51876e4..7aef6d2 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,7 @@ For more information about Modbus see: + ModbusTLS: ESP8266 Client/Server + Test: TLS ESP8266 Client/Server + Examples: TLS added -- Examples: TLS Certificate test Role extension and Alt-Name -- Examples: TLS Add example explanation + ModbusTLS: ESP32 Client -- Test: TLS ESP32 Client + Build with no STL dependency + Test: No-STL mode + ModbusTCP: ModbusEthernet - W5x00 Ethernet library support @@ -85,18 +82,13 @@ For more information about Modbus see: + Test: 0x14 + 0x15 - Write File Records function + Test: 0x15 -- Examples: Basic file operations -- Examples: FW update ++ Examples: FW update +- Test: FW update + 0x16 - Write Mask Register function - Test: 0x16 + 0x17 - Read/Write Registers function - Test: 0x17 + API: Access control callback for individual Modbus function -- Slave/Server: slavePDU use early exit by return where possible -- Master/Client: Check frame size against header data where possible -- Master/Client: Additional responce data validation -- Test: push/pull functions -- Test: Frame accuracy to specefication - Documentation: Update - Examples: Revising + Remove unneeded register count check in private functions @@ -105,15 +97,24 @@ For more information about Modbus see: + ModbusRTU: Fix transaction callback remains assigned after request end + ModbusTCP: Free server connection in destructor + Declare global registers and callbacks as static members -- Free global registers and callbacks on remove last Modbus instance + ModbusRTU: Refactor .task() for more relaibe processing of incoming data -- ModbusRTU: Static buffer allocation. -- Buffer/packet size limitation support + API: Declare all callbacks as std::function (for STL) - API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) // 4.1.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server +- Test: TLS ESP32 Client +- Examples: TLS Certificate test Role extension and Alt-Name +- Examples: TLS Add example explanation +- ModbusRTU: Static buffer allocation. +- Buffer/packet size limitation support +- Test: Frame accuracy to specefication +- Slave/Server: slavePDU use early exit by return where possible +- Master/Client: Check frame size against header data where possible +- Master/Client: Additional responce data validation +- Free global registers and callbacks on remove last Modbus instance +- Test: push/pull functions +- Examples: Basic file operations ``` ## Contributions diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino index b75557a..04a0da1 100644 --- a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -21,14 +21,14 @@ bool resCallback(Modbus::ResultCode event, uint16_t, void*) { err = event; } -Modbus::ResultCode readSync(uint16_t Address, uint16_t start, uint16_t num, uint16_t* buf) { +Modbus::ResultCode readSync(uint8_t address, uint16_t start, uint16_t num, uint16_t* buf) { xSemaphoreTake(xMutex, portMAX_DELAY); if (mb.slave()){ xSemaphoreGive(xMutex); return Modbus::EX_GENERAL_FAILURE; } - Serial.printf("SlaveID: %d Hreg %d\n", Address, start); - mb.readHreg(Address, start, buf, num, resCallback); + Serial.printf("SlaveID: %d Hreg %d\n", address, start); + mb.readHreg(address, start, buf, num, resCallback); while (mb.slave()) { vTaskDelay(1); mb.task(); diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index cdd8d56..b2e9825 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,7 +1,7 @@ -## [Concurent thread-safe access to Modbus object](ESP32-Concurent) - This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally thre is [example of master](WSP32-Concurent) device for multithread usage with ESP32. +## [Concurent thread-safe access to Modbus object](ESP32-Concurent) + ## [Simple ModbusRTU master](master) ## [Simple ModbusRTU slave](slave) @@ -33,8 +33,10 @@ void setBaudrte(uint32 baud); Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. ```c -void master(); -void slave(uint8_t slaveId); +void client(); +void server(uint8_t slaveId); +void slave(); // Depricated +void master(uint8_t slaveId); // Depricated ``` - `slaveId` Modbus slave id to associate to. @@ -42,7 +44,8 @@ void slave(uint8_t slaveId); Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictible. ```c -uint8_t slave(); +uint8_t client(); +uint8_t slave(); // Depricated ``` - Slave mode: Returns configured slave id. diff --git a/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md index c8ab9e4..a9fa05c 100644 --- a/examples/TCP-ESP/README.md +++ b/examples/TCP-ESP/README.md @@ -69,7 +69,7 @@ void dropTransactions(); Cancel all active transactions. Callback with result code `Modbus::EX_CANCEL` will be called for each transaction (if assigned). -## [Server]](server.ino) +## [Server](server.ino) ### Add local register ```c diff --git a/src/Modbus.h b/src/Modbus.h index 7d77dad..b126f5a 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -109,10 +109,10 @@ class Modbus { 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, // Not implemented. Read File Record - FC_WRITE_FILE_REC = 0x15, // Not implemented. Write File Record - FC_MASKWRITE_REG = 0x16, // Not implemented. Mask Write Register - FC_READWRITE_REGS = 0x17 // Not implemented. Read/Write Multiple 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 From 4558b58be39596678d30531c803449cec47f768d Mon Sep 17 00:00:00 2001 From: TienHuyIoT Date: Sun, 1 Aug 2021 19:51:37 +0700 Subject: [PATCH 239/288] ESP32-Concurent.ino Modifications (#132) Details: - Update example ESP32-Concurent.ino and using Tool Modbus Slave on PC for test. --- .../RTU/ESP32-Concurent/ESP32-Concurent.ino | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino index b75557a..c94bc01 100644 --- a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -4,31 +4,38 @@ (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 + + Tool Modbus Slave on PC for test + https://www.modbustools.com/download.html */ #include -#define REG1 1 -#define REG2 2 -#define SLAVE_ID 1 +#define REG 0 +#define REG_NUM 32 +#define SLAVE_ID1 51 +#define SLAVE_ID2 52 + +#define MBUS_HW_SERIAL Serial1 +#define MBUS_TXD_PIN 16 +#define MBUS_RXD_PIN 35 ModbusRTU mb; xSemaphoreHandle xMutex; Modbus::ResultCode err; -bool resCallback(Modbus::ResultCode event, uint16_t, void*) { - err = event; -} - Modbus::ResultCode readSync(uint16_t Address, uint16_t start, uint16_t num, uint16_t* buf) { xSemaphoreTake(xMutex, portMAX_DELAY); if (mb.slave()){ xSemaphoreGive(xMutex); return Modbus::EX_GENERAL_FAILURE; } - Serial.printf("SlaveID: %d Hreg %d\n", Address, start); - mb.readHreg(Address, start, buf, num, resCallback); + Serial.printf("SlaveID: %d Hreg %d\r\n", Address, start); + mb.readIreg(Address, start, buf, num, [](Modbus::ResultCode event, uint16_t, void*) { + err = event; + return true; + }); while (mb.slave()) { vTaskDelay(1); mb.task(); @@ -43,8 +50,8 @@ void loop2( void * pvParameters ); void setup() { Serial.begin(115200); - Serial1.begin(9600, SERIAL_8N1, 19, 18); - mb.begin(&Serial1, 17); + MBUS_HW_SERIAL.begin(9600, SERIAL_8N1, MBUS_RXD_PIN, MBUS_TXD_PIN); + mb.begin(&MBUS_HW_SERIAL); mb.master(); xMutex = xSemaphoreCreateMutex(); xTaskCreatePinnedToCore( @@ -67,23 +74,25 @@ void setup() { } -uint16_t hregs1[10]; +uint16_t hregs1[REG_NUM]; void loop1( void * pvParameters ){ while(true) { delay(10); - if (readSync(1, 1, 10, hregs1) != Modbus::EX_SUCCESS) - Serial.print("Error 1"); + if (readSync(SLAVE_ID1, REG, REG_NUM, hregs1) == Modbus::EX_SUCCESS) + Serial.println("OK 2"); + else + Serial.println("Error 2"); } } -uint16_t hregs2[10]; +uint16_t hregs2[REG_NUM]; void loop2( void * pvParameters ){ while(true) { delay(100); - if (readSync(1, 2, 10, hregs2) == Modbus::EX_SUCCESS) + if (readSync(SLAVE_ID2, REG, REG_NUM, hregs2) == Modbus::EX_SUCCESS) Serial.println("OK 2"); else - Serial.print("Error 2"); + Serial.println("Error 2"); } } From b6b765df6318dad7ff6d02e21c870b1c5669c3ae Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 3 Aug 2021 10:17:36 +0500 Subject: [PATCH 240/288] Update README.md --- documentation/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/README.md b/documentation/README.md index 4f1b683..333d9eb 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -4,11 +4,11 @@ ## Where to get documentation for the library? -[API](API.md) -- [ModbusTCP](https://github.com/emelianov/modbus-esp8266/blob/master/examples/ESP-TCP) -- [ModbusRTU](https://github.com/emelianov/modbus-esp8266/blob/master/examples/RTU/#Modbus-RTU-Specific-API -- [Callbacks](https://github.com/emelianov/modbus-esp8266/blob/master/examples/Callback) -- [Modbus Security](https://github.com/emelianov/modbus-esp8266/blob/master/examples/TLS) +- [API](API.md) +- [ModbusTCP](https://github.com/emelianov/modbus-esp8266/tree/master/examples/TCP-ESP#API) +- [ModbusRTU](https://github.com/emelianov/modbus-esp8266/tree/master/examples/RTU#Modbus-RTU-Specific-API) +- [Callbacks](https://github.com/emelianov/modbus-esp8266/tree/master/examples/Callback/#Callback-API) +- [Modbus Security](https://github.com/emelianov/modbus-esp8266/tree/master/examples/TLS) --- @@ -54,4 +54,4 @@ Very limited implementation is available in [examples](https://github.com/emelia (c)2021 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) -The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. \ No newline at end of file +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. From 1bcd3b3a2437ca3fef676d919b1bda5ca6809b9d Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 4 Aug 2021 23:09:03 +0500 Subject: [PATCH 241/288] Prapare for release - Test and Fix ReadWrite Hregs - Test and Fix Mask Hreg - Add Firmware Update example - Fix onRequest arguments - Update documentation --- README.md | 27 ++-- documentation/README.md | 42 ++++- examples/Callback/README.md | 30 ++-- .../FW-Update-Source/FW-Update-Source.ino | 143 ++++++++++++++++++ .../FW-Update-Target/FW-Update-Target.ino | 90 +++++++++++ examples/Files/README.md | 54 +++++++ examples/RTU/README.MD | 4 +- keywords.txt | 19 ++- src/Modbus.cpp | 32 ++-- src/Modbus.h | 14 +- src/ModbusAPI.h | 2 +- src/ModbusRTU.h | 4 +- src/ModbusSettings.h | 13 +- tests/common.h | 4 +- tests/tests.ino | 52 ++++++- tests/write.h | 8 - 16 files changed, 448 insertions(+), 90 deletions(-) create mode 100644 examples/Files/FW-Update-Source/FW-Update-Source.ino create mode 100644 examples/Files/FW-Update-Target/FW-Update-Target.ino create mode 100644 examples/Files/README.md diff --git a/README.md b/README.md index f2be9a3..fcad3f2 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,7 @@ |If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| |---|---| - -The library is under active development. Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. - ---- - -This library allows your Arduino board to communicate via Modbus protocol. The Modbus is a protocol -used in industrial automation and also can be used in other areas, such as home automation. - -The Modbus generally uses serial RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus TCP and Modbus TCP Security). +For detailes on the library usage visit [documentation](documentation) section. ## Features @@ -49,8 +41,7 @@ The Modbus generally uses serial RS-485 as physical layer (then called Modbus Se ## Notes 1. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in [ScadaBR](http://www.scadabr.com.br) offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the [CAS Modbus Scanner](http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. -2. For API refer [documentation](documentation) -3. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. +2. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. For more information about Modbus see: @@ -73,7 +64,6 @@ For more information about Modbus see: + Test: No-STL mode + ModbusTCP: ModbusEthernet - W5x00 Ethernet library support + Test: W5x00 support -- Test: W5x00 with Ethernet library v1 + API: Implementation code merge + API: Access ModbusTCP server by name + API: Set local multiple registers from an array @@ -85,12 +75,11 @@ For more information about Modbus see: + Examples: FW update - Test: FW update + 0x16 - Write Mask Register function -- Test: 0x16 ++ Test: 0x16 + 0x17 - Read/Write Registers function -- Test: 0x17 ++ Test: 0x17 + API: Access control callback for individual Modbus function -- Documentation: Update -- Examples: Revising ++ Documentation: Update + Remove unneeded register count check in private functions + Check startreg + numreg < 65535 + ModbusRTU: ESP32 SoftwareSerial support @@ -99,7 +88,7 @@ For more information about Modbus see: + Declare global registers and callbacks as static members + ModbusRTU: Refactor .task() for more relaibe processing of incoming data + API: Declare all callbacks as std::function (for STL) -- API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) ++ API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) // 4.1.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server @@ -108,13 +97,15 @@ For more information about Modbus see: - Examples: TLS Add example explanation - ModbusRTU: Static buffer allocation. - Buffer/packet size limitation support -- Test: Frame accuracy to specefication - Slave/Server: slavePDU use early exit by return where possible - Master/Client: Check frame size against header data where possible - Master/Client: Additional responce data validation - Free global registers and callbacks on remove last Modbus instance +- Test: Frame accuracy to specefication - Test: push/pull functions +- Test: W5x00 with Ethernet library v1 - Examples: Basic file operations +- Examples: Revising ``` ## Contributions diff --git a/documentation/README.md b/documentation/README.md index 4f1b683..67e37ae 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,14 +1,21 @@ # FAQ +This library allows your Arduino board to communicate via Modbus protocol. The Modbus is a protocol +used in industrial automation and also can be used in other areas, such as home automation. + +The Modbus generally uses serial RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus TCP and Modbus TCP Security). + --- ## Where to get documentation for the library? -[API](API.md) -- [ModbusTCP](https://github.com/emelianov/modbus-esp8266/blob/master/examples/ESP-TCP) -- [ModbusRTU](https://github.com/emelianov/modbus-esp8266/blob/master/examples/RTU/#Modbus-RTU-Specific-API -- [Callbacks](https://github.com/emelianov/modbus-esp8266/blob/master/examples/Callback) -- [Modbus Security](https://github.com/emelianov/modbus-esp8266/blob/master/examples/TLS) +- [API](API.md) +- [ModbusTCP](https://github.com/emelianov/modbus-esp8266/tree/master/examples/TCP-ESP#API) +- [ModbusRTU](https://github.com/emelianov/modbus-esp8266/tree/master/examples/RTU#Modbus-RTU-Specific-API) +- [Callbacks](https://github.com/emelianov/modbus-esp8266/tree/master/examples/Callback/#Callback-API) +- [Modbus Security](https://github.com/emelianov/modbus-esp8266/tree/master/examples/TLS) +- [Modbus File operations](https://github.com/emelianov/modbus-esp8266/tree/master/examples/Files#File-block-API) +- [Compile time settings](https://github.com/emelianov/modbus-esp8266/tree/master/src/ModbusSettings.h)) --- @@ -24,8 +31,12 @@ --- +## How to send signed value (`int16_t`)? + ## How to send `float` or `uint32_t` values? +Modbus standard defines only two types of data: bit value and 16-bit value. All other datatypes should be sent as multiple 16-bit values. + --- ## Value not read after `readCoil`/`readHreg`/etc @@ -40,12 +51,29 @@ The library is designed to execute calls async way. That is `readHreg()` functio ## Transactional callback returns *0xE4* error +It's timeout error. Suggestions below are applicable to persistent errors or frequently errors. Rare timeout errors may be normal in some considerations. + +### ModbusRTU + +Typically is indicates some kind of wiring or hardware problems. + +- Check wiring. +- Check that baudrate settings are identical for client and server. +- Try to reduce it to 9600bps. +- Try to use different power source for Arduino device. +- Try to replace RS-485 tranceiver. +- If using Modbus simulator software on PC check the result with alternative software. + +### ModbusTCP + +It maybe network problems. Use standard procedures as `ping` and firewall settings checks for diagnostics. + --- -## If it's possible to create ModbusTCP to ModbusRTU pass through brodge? +## If it's possible to create ModbusTCP to ModbusRTU pass through bridge? Some ideas to implement full functional brodge may be taken from [this code](https://github.com/emelianov/modbus-esp8266/issues/101#issuecomment-755419095). -Very limited implementation is available in [examples](https://github.com/emelianov/modbus-esp8266/examples/bridge). +Very limited implementation is available in [example](https://github.com/emelianov/modbus-esp8266/examples/bridge). --- diff --git a/examples/Callback/README.md b/examples/Callback/README.md index 402510d..6a7e0d9 100644 --- a/examples/Callback/README.md +++ b/examples/Callback/README.md @@ -62,20 +62,22 @@ bool onRequest(cbRequest cb = _onRequestDefault); bool onRequestSuccess(cbRequest cb = _onRequestDefault); union Modbus::RequestData { - struct { - TAddress reg; - uint16_t regCount; - }; - struct { - TAddress regRead; - uint16_t regReadCount; - TAddress regWrite; - uint16_t regWriteCount; - }; - struct { - uint16_t fileNum; - }; - }; + 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; + }; +}; ``` Callback function receives Modbus function code, structure `Modbus::RequestData` containing register type and offset (`TAddress` structure) and count of registers requested. The function should return [result code](#Result codes *Modbus::ResultCode*) `Modbus::EX_SUCCESS` to allow request processing or Modbus error code to block processing. This code will be returned to client/master. diff --git a/examples/Files/FW-Update-Source/FW-Update-Source.ino b/examples/Files/FW-Update-Source/FW-Update-Source.ino new file mode 100644 index 0000000..9c7ff1c --- /dev/null +++ b/examples/Files/FW-Update-Source/FW-Update-Source.ino @@ -0,0 +1,143 @@ +/* + Modbus Library for Arduino Example - Modbus RTU Firmware Update - ESP8266/ESP32 + Update main node to update from. + Hosts simple web-server to upload firmware file and writes it to update target. + + (c)2021 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ +#include +ModbusRTU rtu; + +#if defined(ESP8266) + +#else +#include +#include +#endif +WebServer web(80); + +#if defined(ESP8266) + #include + // SoftwareSerial S(D1, D2, false, 256); + + // receivePin, transmitPin, inverse_logic, bufSize, isrBufSize + // connect RX to D2 (GPIO4, Arduino pin 4), TX to D1 (GPIO5, Arduino pin 4) + SoftwareSerial S(4, 5); +#endif + +#define UPDATE_ENABLE 301 +#define UPDATE_FILE 301 +#define SLAVE_ID 1 +#define BLOCK_SIZE 256 + +bool updating = false; +Modbus::ResultCode result = Modbus::EX_GENERAL_FAILURE; +bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X\n", event); // Display Modbus error code + result = event; + return true; +} + +void handlePage() { + String output = F(R"EOF( +\ +
      \ + Update firmware:
      \ + \ +
      \ +\ +)EOF"); + web.sendHeader("Connection", "close"); + web.sendHeader("Cache-Control", "no-store, must-revalidate"); + web.sendHeader("Access-Control-Allow-Origin", "*"); + web.send(200, "text/html; charset=utf-8", output); +} + +void updateHandle() { + web.sendHeader("Connection", "close"); + web.sendHeader("Refresh", "10; url=/"); + web.send(200, "text/plain", (Update.hasError())?"FAIL":"OK"); +} + +void updateUploadHandle() { + uint8_t* data = nullptr; + uint16_t remaining; + HTTPUpload& upload = web.upload(); + switch (upload.status) { + case UPLOAD_FILE_START: + result = Modbus::EX_GENERAL_FAILURE; + rtu.writeCoil(SLAVE_ID, UPDATE_ENABLE, true, cb); + while (rtu.server()) { + rtu.task(); + yield(); + } + updating = (result == Modbus::EX_SUCCESS); + Serial.print("O"); + break; + case UPLOAD_FILE_WRITE: + if (!updating) + break; + Serial.print("o"); + data = upload.buf; + remaining = upload.currentSize >> 2; + while (remaining) { + uint16_t amount = (remaining > BLOCK_SIZE)?256:remaining; + result = Modbus::EX_GENERAL_FAILURE; + rtu.writeFileRec(SLAVE_ID, UPDATE_FILE, 0, amount, data, cb); + while (rtu.server()) { + rtu.task(); + yield(); + } + remaining -= amount; + if (result != Modbus::EX_SUCCESS) { + updating = false; + break; + } + Serial.print("."); + } + break; + case UPLOAD_FILE_END: + if (!updating) + break; + rtu.writeCoil(SLAVE_ID, UPDATE_ENABLE, false); + while (rtu.server()) { + rtu.task(); + yield(); + } + updating = false; + Serial.println("!"); + break; + default: + if (updating) { + rtu.writeCoil(SLAVE_ID, UPDATE_ENABLE, false); + while (rtu.server()) { + rtu.task(); + yield(); + } + updating = false; + } + Serial.print("X"); + } +} + +void setup() { + Serial.begin(115200); +#if defined(ESP8266) + S.begin(9600, SWSERIAL_8N1); + rtu.begin(&S); +#else + Serial1.begin(9600, SERIAL_8N1); + rtu.begin(&Serial1); + rtu.client(); + #endif + web.on("/update", HTTP_POST, updateHandle, updateUploadHandle); + web.on("/", HTTP_GET, handlePage); +} + +void loop() { + web.handleClient(); + rtu.task(); + yield(); +} \ No newline at end of file diff --git a/examples/Files/FW-Update-Target/FW-Update-Target.ino b/examples/Files/FW-Update-Target/FW-Update-Target.ino new file mode 100644 index 0000000..1b04ca4 --- /dev/null +++ b/examples/Files/FW-Update-Target/FW-Update-Target.ino @@ -0,0 +1,90 @@ +/* + Modbus Library for Arduino Example - Modbus RTU Firmware Update - ESP8266/ESP32 + Update target node to update. + Receives firmware to upload and flashes it. + + (c)2021 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ +#include +ModbusRTU rtu; +#if defined (ESP32) + #include + #define ESP32_SKETCH_SIZE 1310720 +#endif +#if defined(ESP8266) + #include + // SoftwareSerial S(D1, D2, false, 256); + + // receivePin, transmitPin, inverse_logic, bufSize, isrBufSize + // connect RX to D2 (GPIO4, Arduino pin 4), TX to D1 (GPIO5, Arduino pin 4) + SoftwareSerial S(4, 5); +#endif + +#define UPDATE_ENABLE 301 +#define UPDATE_FILE 301 +#define SLAVE_ID 1 + +uint16_t update_enable(TRegister* reg, uint16_t val) { + uint32_t sketchSpace; + if (rtu.Reg(reg->address)) { + if (COIL_BOOL(val)) + return BIT_VAL(true); + Serial.print("O"); + if(Update.end(true)){ //true to set the size to the current progress + Serial.println("Update Success. \nRebooting..."); + } + return BIT_VAL(false); + } + #ifdef ESP8266 + sketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + #else + sketchSpace = ESP32_SKETCH_SIZE; + #endif + Serial.printf("Starting update. FW max sise: %ld\n", sketchSpace); + if(!Update.begin(sketchSpace)) { + Update.printError(Serial); + return BIT_VAL(false); + } + return BIT_VAL(true); +} + +// Expected arguments are: +// func = EX_WRITE_FILE_REC +// fileNum = UPDATE_FILE +// recNumber ignored +// recLength = data size (words) +// frame = data to write ptr +Modbus::ResultCode handle_file(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame) { + if (func != Modbus::FC_WRITE_FILE_REC) + return Modbus::EX_ILLEGAL_FUNCTION; + if (!rtu.Reg(COIL(UPDATE_ENABLE))) + return Modbus::EX_ILLEGAL_VALUE; + if (fileNum != UPDATE_FILE) + return Modbus::EX_ILLEGAL_VALUE; + Serial.print("."); + if(Update.write(frame, recLength * 2) != recLength * 2){ + Update.printError(Serial); + } + return Modbus::EX_SUCCESS; +} + +void setup() { + Serial.begin(115200); +#if defined(ESP8266) + S.begin(9600, SWSERIAL_8N1); + rtu.begin(&S); +#else + Serial1.begin(9600, SERIAL_8N1); + rtu.begin(&Serial1); + #endif + rtu.server(SLAVE_ID); + rtu.onFile(handle_file); + rtu.addReg(COIL(UPDATE_ENABLE)); + rtu.onSet(COIL(UPDATE_ENABLE), update_enable); +} + +void loop() { + rtu.task(); + yield(); +} \ No newline at end of file diff --git a/examples/Files/README.md b/examples/Files/README.md new file mode 100644 index 0000000..a791a6b --- /dev/null +++ b/examples/Files/README.md @@ -0,0 +1,54 @@ +# Files operations + +## [Firmware update over ModbusRTU - Update source node](FW-Update-Source/FW-Update-Source.ino) + +ModbusRTU client that pushes firmware to server node. + +## [Firmware update over ModbusRTU - Update target node](FW-Update-Source/FW-Update-Source.ino) + +ModbusRTU server that receives and flashes new firmware. + +## File block API + +### Client side + +```c +uint16_t readFileRec(uint8_t slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb); +uint16_t writeFileRec(uint8_t slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb); + +uint16_t readFileRec(IPAddress slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit); +uint16_t writeFileRec(IPAddress slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit); +``` + +- `slaveId` server id or IP Address +- `fileNum` File number to access +- `startRec` Start offset in file (words) +- `len` Length of data (words) +- `*data` Pointer to data. In case of `readFileRec` must be at least `len` * 2 bytes. +- `cb` Transactional callback function +- `unit` ModbusTCP unit id + +### Server side + +```c +typedef std::function cbModbusFileOp; // ST: +typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame); // no-STL + +bool onFile(std::function); // STL +bool onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)); // no-STL +``` + +- `func` function code to process (FC_READ_FILE_REC or FC_WRITE_FILE_REC) +- `fileNum` file # to read/write +- `recNumber` record number in file (record size is word = 2 bytes) +- `recLength` number of records to read/write +- `*frame` pointer to data buffer + +`onFile` sets file operations handler function. + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2021 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index b2e9825..35d3735 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -8,9 +8,7 @@ This example is introduces how to use the library for ModbusRTU (typicaly over R ## [Sync ModbusRTU master](masterSync) - - -### Modbus RTU Specific API +## Modbus RTU Specific API ```c bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); diff --git a/keywords.txt b/keywords.txt index 67686fb..38bc2f4 100644 --- a/keywords.txt +++ b/keywords.txt @@ -76,16 +76,19 @@ isIreg KEYWORD2 begin KEYWORD2 setBaudrate KEYWORD2 readFileRec KEYWORD2 -writeFileRec KEYWORD2 -maskHreg KEYWORD2 -readWriteHreg KEYWORD2 -onFile KEYWORD2 -onRequest KEYWORD2 -onRequestSuccess KEYWORD2 -onSet KEYWORD2 -onGet KEYWORD2 +writeFileRec KEYWORD2 +maskHreg KEYWORD2 +readWriteHreg KEYWORD2 +onFile KEYWORD2 +onRequest KEYWORD2 +onRequestSuccess KEYWORD2 +onSet KEYWORD2 +onGet KEYWORD2 removeOnSet KEYWORD2 removeOnGet KEYWORD2 +Reg KEYWORD2 +addReg KEYWORD2 +removeReg KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 diff --git a/src/Modbus.cpp b/src/Modbus.cpp index f88fef5..9405353 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -380,28 +380,26 @@ void Modbus::slavePDU(uint8_t* frame) { break; #endif case FC_MASKWRITE_REG: - //field1 = reg, field2 = AND mask + //field1 = reg, field2 = AND mask, field3 = OR mask // Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) - ex = _onRequest(fcode, {HREG(field1), field2}); + 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; } - { - uint16_t orMask = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; - uint16_t val = Reg(HREG(field1)); - val = (val & field2) | (orMask & !field2); - if (!Reg(HREG(field1), val)) { //Check Address and execute (reg exists?) - exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); - return; - } - if (Reg(HREG(field1)) != val) { //Check for failure - exceptionResponse(fcode, EX_SLAVE_FAILURE); - 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}); + _onRequestSuccess(fcode, {HREG(field1), field2, field3}); break; case FC_READWRITE_REGS: //field1 = readreg, field2 = read count, frame[9] = data lenght, header len = 10 @@ -420,11 +418,11 @@ void Modbus::slavePDU(uint8_t* frame) { exceptionResponse(fcode, EX_ILLEGAL_VALUE); return; } - if (!setMultipleWords((uint16_t*)(frame + 10), HREG(field1), field2)) { + if (!setMultipleWords((uint16_t*)(frame + 10), HREG(field3), field4)) { exceptionResponse(fcode, EX_SLAVE_FAILURE); return; } - ex = readWords(HREG(field3), field4, fcode); + ex = readWords(HREG(field1), field2, fcode); if (ex != EX_SUCCESS) { exceptionResponse(fcode, ex); return; diff --git a/src/Modbus.h b/src/Modbus.h index b126f5a..90e1026 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -146,7 +146,9 @@ class Modbus { uint16_t regWriteCount; }; struct { - uint16_t fileNum; + TAddress regMask; + uint16_t andMask; + uint16_t orMask; }; RequestData(TAddress r1, uint16_t c1) { reg = r1; @@ -158,8 +160,10 @@ class Modbus { regWrite = r2; regWriteCount = c2; }; - RequestData(uint16_t f) { - fileNum = f; + RequestData(TAddress r1, uint16_t m1, uint16_t m2) { + regMask = r1; + andMask = m1; + orMask = m2; }; }; @@ -311,8 +315,12 @@ typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, //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 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 diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index e175089..afd1096 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -358,7 +358,7 @@ uint16_t ModbusAPI::maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMas this->_frame[4] = andMask & 0x00FF; this->_frame[5] = orMask >> 8; this->_frame[6] = orMask & 0x00FF; - return this->send(slaveId, HREG(offset), cb, unit, nullptr, cb); + return this->send(slaveId, HREG(offset), cb, unit); }; template \ diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 028ef9f..34221e1 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -46,8 +46,8 @@ class ModbusRTUTemplate : public Modbus { inline void master() {client();} void server(uint8_t serverId) {_slaveId = serverId;}; inline void slave(uint8_t slaveId) {server(slaveId);} - - uint8_t slave() { return _slaveId; } + uint8_t server() { return _slaveId; } + inline uint8_t slave() { return server(); } uint32_t eventSource() override {return _slaveId;} }; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 03d516d..5e0e727 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -74,4 +74,15 @@ If defined regisers count will be limited. #define MODBUSRTU_MAX_READMS 100 #define MODBUSAPI_LEGACY -#define MODBUSAPI_OPTIONAL \ No newline at end of file +#define MODBUSAPI_OPTIONAL + +#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 \ No newline at end of file diff --git a/tests/common.h b/tests/common.h index 8ce91cb..efd98e4 100644 --- a/tests/common.h +++ b/tests/common.h @@ -8,7 +8,7 @@ #pragma once #include -#define HW_SERIAL +//#define HW_SERIAL #define BSIZE 1024 @@ -49,4 +49,4 @@ uint8_t wait() { } Serial.printf_P(" 0x%02X", code); return code; -} +} \ No newline at end of file diff --git a/tests/tests.ino b/tests/tests.ino index 0d284a9..5ab2673 100644 --- a/tests/tests.ino +++ b/tests/tests.ino @@ -1,3 +1,11 @@ +/* + Modbus Library for ESP8266/ESP32 + Functional tests + Copyright (C) 2019 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 #include #include "common.h" @@ -19,14 +27,14 @@ uint16_t readHreg = 0; void setup() { Serial.begin(115200); Serial.println("ModbusRTU API test"); -#if defiend(HW_SERIAL) +#if defined(HW_SERIAL) Serial1.begin(115200, SERIAL_8N1, 18, 19); Serial2.begin(115200, SERIAL_8N1, 22, 23); #endif delay(100); - master.begin(&P1); + master.begin((Stream*)&P1); master.master(); - slave.begin(&P2); + slave.begin((Stream*)&P2); slave.slave(SLAVE_ID); slave.addHreg(HREG_ID); @@ -41,13 +49,45 @@ readMultiple(SLAVE_ID, COIL(HREG_ID), 10); readMultiple(SLAVE_ID, IREG(HREG_ID), 10); readMultiple(SLAVE_ID, ISTS(HREG_ID), 10); +// Read-Write Hreg { + Serial.print("Read-Write Hreg: "); + #define RD 0x10 + #define WR 0x20 + #define RW_COUNT 10 uint16_t rd[10]; uint16_t wr[10]; - slave.addHreg(110, 10); - slave.addHreg(120, 20); - master.readWriteHreg(SLAVE_ID, 110, rd, 10, 120, wr, 10, cbWrite); + for (uint8_t i = 0; i < RW_COUNT; i++) + wr[i] = WR; + slave.addHreg(110, RD, RW_COUNT); + slave.addHreg(120, !WR, RW_COUNT); + master.readWriteHreg(SLAVE_ID, 110, rd, RW_COUNT, 120, wr, RW_COUNT, cbWrite); wait(); + uint8_t i,j; + for (i = 0; i < RW_COUNT; i++) + if (rd[i] != RD) + break; + for (j = 0; j < RW_COUNT; j++) + if (slave.Hreg(120 + j) != WR) + break; + if (i < RW_COUNT || j < RW_COUNT) + Serial.println(" FAILED"); + else + Serial.println(" PASSED"); +} +// Mask Hreg +{ + Serial.print("Mask Hreg: "); + slave.addReg(HREG(130), 0xF0F0); + slave.addReg(HREG(131), 0x1212); + master.maskHreg(SLAVE_ID, 130, 0xF0F0, 0x0000, cbWrite); + wait(); + master.maskHreg(SLAVE_ID, 131, 0xF2F2, 0x2525, cbWrite); + wait(); + if (slave.Reg(HREG(130)) != 0xF0F0 || slave.Reg(HREG(131)) != 0x1717) + Serial.println(" FAILED"); + else + Serial.println(" PASSED"); } // Garbage read { diff --git a/tests/write.h b/tests/write.h index c1f46d7..627ce91 100644 --- a/tests/write.h +++ b/tests/write.h @@ -1,11 +1,3 @@ -/* - Modbus Library for ESP8266/ESP32 - Functional tests - Copyright (C) 2019 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 "common.h" From f7c1fa3650038ec54a5c2b53429fa6319b959782 Mon Sep 17 00:00:00 2001 From: emerout Date: Thu, 5 Aug 2021 18:43:04 +0200 Subject: [PATCH 242/288] Fix broken link and typo in example/RTU/Readme (#138) Co-authored-by: Emeric Chardiny --- examples/RTU/README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index 35d3735..6f4d1b7 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,4 +1,4 @@ -This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally thre is [example of master](WSP32-Concurent) device for multithread usage with ESP32. +This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally there is [example of master](ESP32-Concurent) device for multithread usage with ESP32. ## [Concurent thread-safe access to Modbus object](ESP32-Concurent) From fe5d4a83a15a3d7a5dbf516c9c207a56eb17231f Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 16 Aug 2021 21:13:22 +0500 Subject: [PATCH 243/288] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7242f03 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://paypal.me/modbus?locale.x=en_US From f14d0e2bcd376e24f325ae67774aa857430ca742 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 17 Aug 2021 09:35:19 +0300 Subject: [PATCH 244/288] Finalize FW-Update example --- README.md | 16 ++---- .../FW-Update-Source/FW-Update-Source.ino | 50 ++++++++++++++----- .../FW-Update-Target/FW-Update-Target.ino | 30 +++++++---- examples/Files/README.md | 8 +++ library.properties | 2 +- 5 files changed, 71 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index fcad3f2..acf1ea4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ For detailes on the library usage visit [documentation](documentation) section. * Modbus TCP client for [ESP8266/ESP32](examples/TCP-ESP) and [Ethernet library](examples/TCP-Ethernet) * [MODBUS/TCP Security server (ESP8266)](examples/TLS) * [MODBUS/TCP Security client (ESP8266/ESP32)](examples/TLS) -* Reply exception messages for all supported functions * Modbus functions supported: * 0x01 - Read Coils * 0x02 - Read Input Status (Read Discrete Inputs) @@ -30,13 +29,7 @@ For detailes on the library usage visit [documentation](documentation) section. * 0x15 - Write File Record * 0x16 - Mask Write Register * 0x17 - Read/Write multiple registers -* [Callbacks](examples/Callback) for - * Client connect (Modbus TCP) - * Server/Client disconnect (Modbus TCP) - * Read specific Register - * Write specific Register - * Transaction result (Client side) - * Transaction start/end (Server side) +* [Callbacks](examples/Callback) driven design ## Notes @@ -54,7 +47,7 @@ For more information about Modbus see: ## Last Changes ```diff -// 4.0.0-DEV +// 4.0.0 + ModbusTLS: Modbus TCP Security Client/Server + ModbusTLS: ESP8266 Client/Server + Test: TLS ESP8266 Client/Server @@ -73,7 +66,7 @@ For more information about Modbus see: + 0x15 - Write File Records function + Test: 0x15 + Examples: FW update -- Test: FW update ++ Test: FW update + 0x16 - Write Mask Register function + Test: 0x16 + 0x17 - Read/Write Registers function @@ -89,12 +82,13 @@ For more information about Modbus see: + ModbusRTU: Refactor .task() for more relaibe processing of incoming data + API: Declare all callbacks as std::function (for STL) + API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) -// 4.1.0 +// 4.1.0-DEV - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server - Test: TLS ESP32 Client - Examples: TLS Certificate test Role extension and Alt-Name - Examples: TLS Add example explanation +- ModbusRTU: Add direction control pin for Stream - ModbusRTU: Static buffer allocation. - Buffer/packet size limitation support - Slave/Server: slavePDU use early exit by return where possible diff --git a/examples/Files/FW-Update-Source/FW-Update-Source.ino b/examples/Files/FW-Update-Source/FW-Update-Source.ino index 9c7ff1c..1effff8 100644 --- a/examples/Files/FW-Update-Source/FW-Update-Source.ino +++ b/examples/Files/FW-Update-Source/FW-Update-Source.ino @@ -10,8 +10,10 @@ ModbusRTU rtu; #if defined(ESP8266) - + #include + #include #else + #include #include #include #endif @@ -29,8 +31,9 @@ WebServer web(80); #define UPDATE_ENABLE 301 #define UPDATE_FILE 301 #define SLAVE_ID 1 -#define BLOCK_SIZE 256 +#define BLOCK_SIZE 64 +uint32_t written = 0; bool updating = false; Modbus::ResultCode result = Modbus::EX_GENERAL_FAILURE; bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback @@ -42,12 +45,12 @@ bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbu void handlePage() { String output = F(R"EOF( -\ -
      \ - Update firmware:
      \ - \ -
      \ -\ + +
      + Update firmware:
      + +
      + )EOF"); web.sendHeader("Connection", "close"); web.sendHeader("Cache-Control", "no-store, must-revalidate"); @@ -74,6 +77,7 @@ void updateUploadHandle() { yield(); } updating = (result == Modbus::EX_SUCCESS); + written = 0; Serial.print("O"); break; case UPLOAD_FILE_WRITE: @@ -81,18 +85,25 @@ void updateUploadHandle() { break; Serial.print("o"); data = upload.buf; - remaining = upload.currentSize >> 2; + remaining = upload.currentSize / 2; while (remaining) { - uint16_t amount = (remaining > BLOCK_SIZE)?256:remaining; + uint16_t amount = (remaining > BLOCK_SIZE)?BLOCK_SIZE:remaining; result = Modbus::EX_GENERAL_FAILURE; - rtu.writeFileRec(SLAVE_ID, UPDATE_FILE, 0, amount, data, cb); + if (!rtu.writeFileRec(SLAVE_ID, UPDATE_FILE, 0, amount, data, cb)) { + updating = false; + Serial.println("X:send"); + break; + } while (rtu.server()) { rtu.task(); yield(); } remaining -= amount; + data += amount * 2; + written += amount; if (result != Modbus::EX_SUCCESS) { updating = false; + Serial.println("X"); break; } Serial.print("."); @@ -108,6 +119,8 @@ void updateUploadHandle() { } updating = false; Serial.println("!"); + Serial.print("Written: "); + Serial.println(written * 2); break; default: if (updating) { @@ -124,16 +137,27 @@ void updateUploadHandle() { void setup() { Serial.begin(115200); + + WiFi.begin("E2", "fOlissio92"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + #if defined(ESP8266) - S.begin(9600, SWSERIAL_8N1); + S.begin(19200, SWSERIAL_8N1); rtu.begin(&S); #else - Serial1.begin(9600, SERIAL_8N1); + Serial1.begin(19200, SERIAL_8N1, 18, 19); rtu.begin(&Serial1); rtu.client(); #endif web.on("/update", HTTP_POST, updateHandle, updateUploadHandle); web.on("/", HTTP_GET, handlePage); + web.begin(); } void loop() { diff --git a/examples/Files/FW-Update-Target/FW-Update-Target.ino b/examples/Files/FW-Update-Target/FW-Update-Target.ino index 1b04ca4..5864929 100644 --- a/examples/Files/FW-Update-Target/FW-Update-Target.ino +++ b/examples/Files/FW-Update-Target/FW-Update-Target.ino @@ -24,13 +24,15 @@ ModbusRTU rtu; #define UPDATE_ENABLE 301 #define UPDATE_FILE 301 #define SLAVE_ID 1 - +uint32_t written = 0; uint16_t update_enable(TRegister* reg, uint16_t val) { uint32_t sketchSpace; if (rtu.Reg(reg->address)) { if (COIL_BOOL(val)) return BIT_VAL(true); - Serial.print("O"); + Serial.println("!"); + Serial.print("Written: "); + Serial.println(written * 2); if(Update.end(true)){ //true to set the size to the current progress Serial.println("Update Success. \nRebooting..."); } @@ -46,6 +48,7 @@ uint16_t update_enable(TRegister* reg, uint16_t val) { Update.printError(Serial); return BIT_VAL(false); } + written = 0; return BIT_VAL(true); } @@ -56,26 +59,33 @@ uint16_t update_enable(TRegister* reg, uint16_t val) { // recLength = data size (words) // frame = data to write ptr Modbus::ResultCode handle_file(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame) { - if (func != Modbus::FC_WRITE_FILE_REC) - return Modbus::EX_ILLEGAL_FUNCTION; - if (!rtu.Reg(COIL(UPDATE_ENABLE))) - return Modbus::EX_ILLEGAL_VALUE; - if (fileNum != UPDATE_FILE) - return Modbus::EX_ILLEGAL_VALUE; + if (func != Modbus::FC_WRITE_FILE_REC) { + Serial.println("X:func"); + return Modbus::EX_ILLEGAL_FUNCTION; + } + if (!rtu.Reg(COIL(UPDATE_ENABLE))) { + Serial.println("X:idle"); + return Modbus::EX_ILLEGAL_VALUE; + } + if (fileNum != UPDATE_FILE) { + Serial.println("X:file"); + return Modbus::EX_ILLEGAL_VALUE; + } Serial.print("."); if(Update.write(frame, recLength * 2) != recLength * 2){ Update.printError(Serial); } + written += recLength; return Modbus::EX_SUCCESS; } void setup() { Serial.begin(115200); #if defined(ESP8266) - S.begin(9600, SWSERIAL_8N1); + S.begin(19200, SWSERIAL_8N1); rtu.begin(&S); #else - Serial1.begin(9600, SERIAL_8N1); + Serial1.begin(19200, SERIAL_8N1, 18, 19); rtu.begin(&Serial1); #endif rtu.server(SLAVE_ID); diff --git a/examples/Files/README.md b/examples/Files/README.md index a791a6b..078d7b3 100644 --- a/examples/Files/README.md +++ b/examples/Files/README.md @@ -4,6 +4,14 @@ ModbusRTU client that pushes firmware to server node. +How to use: +* Connect to target node +* Prapare binary image (Sketch - Export compiled binary) +* Open http:/// in browser +* Choose firmware file +* Press **Update firmware** +* Debug information on update pregress is available in debug console + ## [Firmware update over ModbusRTU - Update target node](FW-Update-Source/FW-Update-Source.ino) ModbusRTU server that receives and flashes new firmware. diff --git a/library.properties b/library.properties index 7d0ceb5..838fdf8 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=4.0.0-DEV +version=4.0.0-RC1 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus Library for Arduino. ModbusRTU, ModbusTCP and ModbusTCP Security From f9c99428c17697a082a5ce842dbb9cb759b6c19e Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 30 Sep 2021 07:15:27 +0500 Subject: [PATCH 245/288] ESP32: Removed taskENTER_CRITICAL and vTaskSuspendAll() --- .../FW-Update-Source/FW-Update-Source.ino | 2 +- examples/TCP-ESP/clientSync/clientSync.ino | 3 +- src/ModbusRTU.cpp | 35 +++++-------------- src/ModbusRTU.h | 3 -- 4 files changed, 11 insertions(+), 32 deletions(-) diff --git a/examples/Files/FW-Update-Source/FW-Update-Source.ino b/examples/Files/FW-Update-Source/FW-Update-Source.ino index 1effff8..5be2231 100644 --- a/examples/Files/FW-Update-Source/FW-Update-Source.ino +++ b/examples/Files/FW-Update-Source/FW-Update-Source.ino @@ -138,7 +138,7 @@ void updateUploadHandle() { void setup() { Serial.begin(115200); - WiFi.begin("E2", "fOlissio92"); + WiFi.begin("SSID", "PASSWORD"); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); diff --git a/examples/TCP-ESP/clientSync/clientSync.ino b/examples/TCP-ESP/clientSync/clientSync.ino index 7525068..03c2213 100644 --- a/examples/TCP-ESP/clientSync/clientSync.ino +++ b/examples/TCP-ESP/clientSync/clientSync.ino @@ -38,8 +38,9 @@ void setup() { mb.client(); } +uint16_t res = 0; + void loop() { - uint16_t res = 0; if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established uint16_t trans = mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Server while(mb.isTransaction(trans)) { // Check if transaction is active diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index bd914b2..31a3355 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -68,12 +68,13 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { #endif if (_txPin >= 0) { digitalWrite(_txPin, _direct?HIGH:LOW); +#if !defined(ESP32) delayMicroseconds(1000); +#endif } - #if defined(ESP32) - //vTaskDelay(1); - portENTER_CRITICAL(&mux); - #endif +#if defined(ESP32) + vTaskDelay(0); +#endif _port->write(slaveId); //Send slaveId _port->write(frame, len); // Send PDU _port->write(newCrc >> 8); //Send CRC @@ -81,9 +82,6 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->flush(); if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); - #if defined(ESP32) - portEXIT_CRITICAL(&mux); - #endif //delay(_t); return true; } @@ -110,28 +108,19 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti } void ModbusRTUTemplate::task() { - #if defined(ESP32) - //taskENTER_CRITICAL(&mux); - vTaskSuspendAll(); - #endif +#if defined(ESP32) + vTaskDelay(0) +#endif if (_port->available() > _len) { _len = _port->available(); t = millis(); } if (_len == 0) { - #if defined(ESP32) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif if (isMaster) cleanup(); return; } if (isMaster) { if (millis() - t < _t) { - #if defined(ESP32) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif return; } } @@ -143,18 +132,10 @@ void ModbusRTUTemplate::task() { t = millis(); } if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long - #if defined(ESP32) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif return; } } } - #if defined(ESP32) - //taskEXIT_CRITICAL(&mux); - xTaskResumeAll(); - #endif uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 34221e1..e4f11a5 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -23,9 +23,6 @@ class ModbusRTUTemplate : public Modbus { uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); uint16_t maxRegs = 0x007D; - #if defined(ESP32) - portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; - #endif 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 From 4a4456e477fe7eee87b61e4367a1bc8fb4e8de74 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 2 Oct 2021 09:04:13 +0500 Subject: [PATCH 246/288] ESP32: Removed taskENTER_CRITICAL and vTaskSuspendAll() --- src/ModbusRTU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 31a3355..4bdb07d 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -109,7 +109,7 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti void ModbusRTUTemplate::task() { #if defined(ESP32) - vTaskDelay(0) + vTaskDelay(0); #endif if (_port->available() > _len) { _len = _port->available(); From 68409fe97cb0cd9154a2e1c62b26e6f16ddf6ec4 Mon Sep 17 00:00:00 2001 From: Andy Hempel Date: Fri, 29 Oct 2021 20:04:50 +0200 Subject: [PATCH 247/288] fix warning (#161) * fix warning comparison of int and unsigned int * convert tabs to spaces to remove warnings -Wmisleading-indentation --- src/Modbus.cpp | 6 +++--- src/ModbusRTU.cpp | 4 ++-- src/ModbusTCPTemplate.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 9405353..3d6c6f6 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -123,9 +123,9 @@ bool Modbus::removeReg(TAddress address, uint16_t numregs) { 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; + for (uint16_t k = 0; k < numregs; k++) + addReg(address + k, value[k]); + return true; } void Modbus::slavePDU(uint8_t* frame) { diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 4bdb07d..b1d233a 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -82,8 +82,8 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->flush(); if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); - //delay(_t); - return true; + //delay(_t); + return true; } uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index b25e33a..d0f4a56 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -232,7 +232,7 @@ void ModbusTCPTemplate::task() { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (!tcpclient[n]) continue; if (!tcpclient[n]->connected()) continue; - while (millis() - taskStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { + while (millis() - taskStart < MODBUSIP_MAX_READMS && (size_t)tcpclient[n]->available() > sizeof(_MBAP)) { tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP if (__swap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __swap is usless there. From 518756441d71c8fb6726653c8b1ceee28d58049e Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 2 Nov 2021 21:37:59 +0500 Subject: [PATCH 248/288] 4.0.0 --- README.md | 38 +++++++++----------------------------- library.properties | 2 +- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index acf1ea4..8533be5 100644 --- a/README.md +++ b/README.md @@ -48,40 +48,20 @@ For more information about Modbus see: ```diff // 4.0.0 -+ ModbusTLS: Modbus TCP Security Client/Server -+ ModbusTLS: ESP8266 Client/Server -+ Test: TLS ESP8266 Client/Server -+ Examples: TLS added -+ ModbusTLS: ESP32 Client -+ Build with no STL dependency -+ Test: No-STL mode -+ ModbusTCP: ModbusEthernet - W5x00 Ethernet library support -+ Test: W5x00 support -+ API: Implementation code merge -+ API: Access ModbusTCP server by name -+ API: Set local multiple registers from an array -+ ModbusIP => ModbusTCP ++ Support of all Arduino boards ++ ModbusTLS: ESP8266 Client/Server and ESP32 Client ++ ModbusTCP: ModbusEthernet - WizNet W5x00, ENC28J60 Ethernet library support + 0x14 - Read File Records function -+ Test: 0x14 + 0x15 - Write File Records function -+ Test: 0x15 -+ Examples: FW update -+ Test: FW update -+ 0x16 - Write Mask Register function -+ Test: 0x16 ++ Examples: FW update over Modbus fullfunctional example ++ 0x16 - Write Mask Register function+ Test: 0x16 + 0x17 - Read/Write Registers function -+ Test: 0x17 -+ API: Access control callback for individual Modbus function -+ Documentation: Update -+ Remove unneeded register count check in private functions -+ Check startreg + numreg < 65535 + ModbusRTU: ESP32 SoftwareSerial support -+ ModbusRTU: Fix transaction callback remains assigned after request end -+ ModbusTCP: Free server connection in destructor -+ Declare global registers and callbacks as static members -+ ModbusRTU: Refactor .task() for more relaibe processing of incoming data -+ API: Declare all callbacks as std::function (for STL) ++ Build with no STL dependency (switchable) ++ API: ModbusIP => ModbusTCP ++ API: Access control callback for individual Modbus function + API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) ++ Lot of code refacting and small fixes // 4.1.0-DEV - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server diff --git a/library.properties b/library.properties index 838fdf8..bc98ad3 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=4.0.0-RC1 +version=4.0.0 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus Library for Arduino. ModbusRTU, ModbusTCP and ModbusTCP Security From e48d76c36c99a23a23fd066e6ed44466afe56b55 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 4 Nov 2021 23:02:20 +0500 Subject: [PATCH 249/288] Fix pushCoil and pushIstsToCoil (#162) --- src/ModbusAPI.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index afd1096..bd4852c 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -189,19 +189,19 @@ IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, 0x007D) IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, 0x007D) IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, 0x07D0) -#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM) \ +#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM, FINT) \ template \ template \ uint16_t ModbusAPI::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->writeSlaveWords(REG(from), to, numregs, Modbus::FUNC); \ + this->FINT(REG(from), to, numregs, Modbus::FUNC); \ return this->send(ip, REG(from), cb, unit); \ } -IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, 0x7D0) -IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, 0x007D) -IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, 0x007D) -IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, 0x07D0) +IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, 0x7D0, writeSlaveBits) +IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, 0x007D, writeSlaveWords) +IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, 0x007D, writeSlaveWords) +IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, 0x07D0, writeSlaveBits) template \ bool ModbusAPI::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { From 0cef4f5f2d78d938b0536503b96a7b88d7383f1e Mon Sep 17 00:00:00 2001 From: Gonzalo Brusco Date: Tue, 4 Jan 2022 04:02:32 -0300 Subject: [PATCH 250/288] Millis to Micros in RTU (#170) * Millis to Micros in RTU * Replace setBaudrate with calculateMinimumInterFrameDelay * Change the word Delay for Time * Changes suggested applied Co-authored-by: Gonzalo Brusco --- src/ModbusRTU.cpp | 54 ++++++++++++++++++++++++++++++++++++-------- src/ModbusRTU.h | 6 +++-- src/ModbusSettings.h | 5 +++- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index b1d233a..1ad55ec 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -43,6 +43,32 @@ uint16_t ModbusRTUTemplate::crc16(uint8_t address, uint8_t* frame, uint8_t pduLe return (CRCHi << 8) | CRCLo; } +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 * (uint32_t)char_bits * 1000000UL) / baud; + } +} + +// Kept for backward compatibility void ModbusRTUTemplate::setBaudrate(uint32_t baud) { if (baud > 19200) { _t = 2; @@ -51,9 +77,19 @@ void ModbusRTUTemplate::setBaudrate(uint32_t 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) { _port = port; - _t = 2; + _t = 1750UL; return true; } @@ -92,7 +128,7 @@ uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransacti rawSend(slaveId, _frame, _len); if (waitResponse && slaveId) { _slaveId = slaveId; - _timestamp = millis(); + _timestamp = micros(); _cb = cb; _data = data; _sentFrame = _frame; @@ -113,25 +149,25 @@ void ModbusRTUTemplate::task() { #endif if (_port->available() > _len) { _len = _port->available(); - t = millis(); + t = micros(); } if (_len == 0) { if (isMaster) cleanup(); return; } if (isMaster) { - if (millis() - t < _t) { + if (micros() - t < _t) { return; } } else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached) - uint32_t taskStart = millis(); - while (millis() - t < _t) { // Wait data whitespace + uint32_t taskStart = micros(); + while (micros() - t < _t) { // Wait data whitespace if (_port->available() > _len) { _len = _port->available(); - t = millis(); + t = micros(); } - if (millis() - taskStart > MODBUSRTU_MAX_READMS) { // Prevent from task() executed too long + if (micros() - taskStart > MODBUSRTU_MAX_READ_US) { // Prevent from task() executed too long return; } } @@ -211,7 +247,7 @@ void ModbusRTUTemplate::task() { bool ModbusRTUTemplate::cleanup() { // Remove timeouted request and forced event - if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { + if (_slaveId && (micros() - _timestamp > MODBUSRTU_TIMEOUT_US)) { if (_cb) { _cb(Modbus::EX_TIMEOUT, 0, nullptr); _cb = nullptr; diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index e4f11a5..74bad5e 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -13,7 +13,7 @@ class ModbusRTUTemplate : public Modbus { Stream* _port; int16_t _txPin = -1; bool _direct = true; // Transmit control logic (true=direct, false=inverse) - unsigned int _t; // inter-frame delay in mS + uint32_t _t; // inter-frame delay in uS uint32_t t = 0; // time sience last data byte arrived bool isMaster = false; uint8_t _slaveId; @@ -35,6 +35,8 @@ class ModbusRTUTemplate : public Modbus { uint16_t crc16(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); template bool begin(T* port, int16_t txPin = -1, bool direct = true); bool begin(Stream* port); @@ -56,7 +58,7 @@ bool ModbusRTUTemplate::begin(T* port, int16_t txPin, bool direct) { #else baud = 9600; #endif - setBaudrate(baud); + setInterFrameTime(calculateMinimumInterFrameTime(baud)); _port = port; if (txPin >= 0) { _txPin = txPin; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 5e0e727..87f9a07 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -69,10 +69,13 @@ If defined regisers count will be limited. #define MODBUSRTU_BROADCAST 0 #define MB_RESERVE 248 #define MB_SERIAL_BUFFER 128 -#define MB_MAX_TIME 10 #define MODBUSRTU_TIMEOUT 1000 #define MODBUSRTU_MAX_READMS 100 +// Define for internal use. Do not change. +#define MODBUSRTU_TIMEOUT_US 1000UL * MODBUSRTU_TIMEOUT +#define MODBUSRTU_MAX_READ_US 1000UL * MODBUSRTU_MAX_READMS + #define MODBUSAPI_LEGACY #define MODBUSAPI_OPTIONAL From 375a006aa31932a4f529f857363ea3d7aadc4466 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 4 Jan 2022 12:14:18 +0500 Subject: [PATCH 251/288] Fix example mistype --- examples/RTU/ESP32-Concurent/ESP32-Concurent.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino index 219807d..bc6f858 100644 --- a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -32,7 +32,7 @@ Modbus::ResultCode readSync(uint8_t address, uint16_t start, uint16_t num, uint1 return Modbus::EX_GENERAL_FAILURE; } Serial.printf("SlaveID: %d Hreg %d\r\n", address, start); - mb.readIreg(Address, start, buf, num, [](Modbus::ResultCode event, uint16_t, void*) { + mb.readIreg(address, start, buf, num, [](Modbus::ResultCode event, uint16_t, void*) { err = event; return true; }); From 0ae379f4968cdc77b1215898b246b14a78c995fe Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 4 Jan 2022 20:45:44 +0500 Subject: [PATCH 252/288] 4.1.0.DEV (#174) * ModbusRTU: Separate pins for RE/DE support * ModbusRTU: RE/DE receive fix * Examples: Added ClearCode ModbusTCP server/client * Fix CleadCore example client/server was swapped * ClearCore example: remove unneeded lines * ClearCore example: fix * MODBUSIP_DEBUG added * Fix example mistype --- README.md | 37 +++++----- examples/ClearCore/README.md | 3 + examples/ClearCore/TCP-Client/TCP-Client.ino | 72 +++++++++++++++++++ examples/ClearCore/TCP-Server/TCP-Server.ino | 67 +++++++++++++++++ .../RTU/ESP32-Concurent/ESP32-Concurent.ino | 2 +- src/ModbusRTU.cpp | 18 +++++ src/ModbusRTU.h | 19 ++++- src/ModbusSettings.h | 6 ++ src/ModbusTCPTemplate.h | 15 ++++ 9 files changed, 220 insertions(+), 19 deletions(-) create mode 100644 examples/ClearCore/README.md create mode 100644 examples/ClearCore/TCP-Client/TCP-Client.ino create mode 100644 examples/ClearCore/TCP-Server/TCP-Server.ino diff --git a/README.md b/README.md index 8533be5..fc184ca 100644 --- a/README.md +++ b/README.md @@ -47,29 +47,17 @@ For more information about Modbus see: ## Last Changes ```diff -// 4.0.0 -+ Support of all Arduino boards -+ ModbusTLS: ESP8266 Client/Server and ESP32 Client -+ ModbusTCP: ModbusEthernet - WizNet W5x00, ENC28J60 Ethernet library support -+ 0x14 - Read File Records function -+ 0x15 - Write File Records function -+ Examples: FW update over Modbus fullfunctional example -+ 0x16 - Write Mask Register function+ Test: 0x16 -+ 0x17 - Read/Write Registers function -+ ModbusRTU: ESP32 SoftwareSerial support -+ Build with no STL dependency (switchable) -+ API: ModbusIP => ModbusTCP -+ API: Access control callback for individual Modbus function -+ API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) -+ Lot of code refacting and small fixes // 4.1.0-DEV ++ ModbusRTU: Add separate RE/DE pins control +- ModbusRTU: Add direction control pin for Stream +- ModbusRTU: Static buffer allocation. +- API: Extend API to allow custom Modbus commands +- STL: Add Reg count limitation to vector limit of 4000 for ESP (re-check first) - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server - Test: TLS ESP32 Client - Examples: TLS Certificate test Role extension and Alt-Name - Examples: TLS Add example explanation -- ModbusRTU: Add direction control pin for Stream -- ModbusRTU: Static buffer allocation. - Buffer/packet size limitation support - Slave/Server: slavePDU use early exit by return where possible - Master/Client: Check frame size against header data where possible @@ -80,6 +68,21 @@ For more information about Modbus see: - Test: W5x00 with Ethernet library v1 - Examples: Basic file operations - Examples: Revising +// 4.0.0 ++ Support of all Arduino boards ++ ModbusTLS: ESP8266 Client/Server and ESP32 Client ++ ModbusTCP: ModbusEthernet - WizNet W5x00, ENC28J60 Ethernet library support ++ 0x14 - Read File Records function ++ 0x15 - Write File Records function ++ Examples: FW update over Modbus fullfunctional example ++ 0x16 - Write Mask Register function+ Test: 0x16 ++ 0x17 - Read/Write Registers function ++ ModbusRTU: ESP32 SoftwareSerial support ++ Build with no STL dependency (switchable) ++ API: ModbusIP => ModbusTCP ++ API: Access control callback for individual Modbus function ++ API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) ++ Lot of code refacting and small fixes ``` ## Contributions diff --git a/examples/ClearCore/README.md b/examples/ClearCore/README.md new file mode 100644 index 0000000..f2a5f21 --- /dev/null +++ b/examples/ClearCore/README.md @@ -0,0 +1,3 @@ +# Examples for [Teknic ClearCore ArduinoWrapper](https://github.com/Teknic-Inc/ClearCore-Arduino-wrapper) + +Not tested on real hardware just based on documentation. \ No newline at end of file diff --git a/examples/ClearCore/TCP-Client/TCP-Client.ino b/examples/ClearCore/TCP-Client/TCP-Client.ino new file mode 100644 index 0000000..a93862f --- /dev/null +++ b/examples/ClearCore/TCP-Client/TCP-Client.ino @@ -0,0 +1,72 @@ +/* + ModbusTCP Client for ClearCode Arduino wrapper + + (c)2021 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 // Ethernet library v2 is required + +#include +#include + +class ModbusEthernet : public ModbusAPI> {}; + +const uint16_t REG = 512; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 12); // Address of Modbus Slave device +const int32_t showDelay = 5000; // Show result every n'th mellisecond + +bool usingDhcp = true; +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE }; // MAC address for your controller +IPAddress ip(192, 168, 30, 178); // The IP address will be dependent on your local network +ModbusEthernet mb; // Declare ModbusTCP instance + +void setup() { + Serial.begin(9600); + uint32_t timeout = 5000; + uint32_t startTime = millis(); + while (!Serial && millis() - startTime < timeout) + continue; + + // Get the Ethernet module up and running. + if (usingDhcp) { + int dhcpSuccess = Ethernet.begin(mac); + if (dhcpSuccess) + Serial.println("DHCP configuration was successful."); + else { + Serial.println("DHCP configuration was unsuccessful!"); + Serial.println("Try again using a manual configuration..."); + while (true) + continue; + } + } + else { + Ethernet.begin(mac, ip); + } + + // Make sure the physical link is up before continuing. + while (Ethernet.linkStatus() == LinkOFF) { + Serial.println("The Ethernet cable is unplugged..."); + delay(1000); + } + mb.client(); // Act as Modbus TCP server +} + +uint16_t res = 0; +uint32_t showLast = 0; + +void loop() { +if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Slave + } else { + mb.connect(remote); // Try to connect if not connected + } + delay(100); // Pulling interval + mb.task(); // Common local Modbus task + if (millis() - showLast > showDelay) { // Display register value every 5 seconds (with default settings) + showLast = millis(); + Serial.println(res); + } +} \ No newline at end of file diff --git a/examples/ClearCore/TCP-Server/TCP-Server.ino b/examples/ClearCore/TCP-Server/TCP-Server.ino new file mode 100644 index 0000000..abf844c --- /dev/null +++ b/examples/ClearCore/TCP-Server/TCP-Server.ino @@ -0,0 +1,67 @@ +/* + ModbusTCP Server for ClearCore Arduino wrapper + + (c)2021 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 // Ethernet library v2 is required + +#include +#include + +class ModbusEthernet : public ModbusAPI> {}; + +const uint16_t REG = 512; // Modbus Hreg Offset +const int32_t showDelay = 5000; // Show result every n'th mellisecond + +bool usingDhcp = true; +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE }; // MAC address for your controller +IPAddress ip(192, 168, 30, 178); // The IP address will be dependent on your local network +ModbusEthernet mb; // Declare ModbusTCP instance + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(9600); + uint32_t timeout = 5000; + uint32_t startTime = millis(); + while (!Serial && millis() - startTime < timeout) + continue; + + // Get the Ethernet module up and running. + if (usingDhcp) { + int dhcpSuccess = Ethernet.begin(mac); + if (dhcpSuccess) + Serial.println("DHCP configuration was successful."); + else { + Serial.println("DHCP configuration was unsuccessful!"); + Serial.println("Try again using a manual configuration..."); + while (true) + continue; + } + } + else { + Ethernet.begin(mac, ip); + } + + // Make sure the physical link is up before continuing. + while (Ethernet.linkStatus() == LinkOFF) { + Serial.println("The Ethernet cable is unplugged..."); + delay(1000); + } + mb.server(); // Act as Modbus TCP server + mb.onConnect(cbConn); + mb.addHreg(100); // Expose Holding Register #100 +} + +void loop() { + mb.task(); // Common local Modbus task + delay(10); +} \ No newline at end of file diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino index 219807d..bc6f858 100644 --- a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -32,7 +32,7 @@ Modbus::ResultCode readSync(uint8_t address, uint16_t start, uint16_t num, uint1 return Modbus::EX_GENERAL_FAILURE; } Serial.printf("SlaveID: %d Hreg %d\r\n", address, start); - mb.readIreg(Address, start, buf, num, [](Modbus::ResultCode event, uint16_t, void*) { + mb.readIreg(address, start, buf, num, [](Modbus::ResultCode event, uint16_t, void*) { err = event; return true; }); diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 1ad55ec..c9effaf 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -102,6 +102,15 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { } Serial.println(); #endif +#if defined(MODBUSRTU_REDE) + if (_txPin >= 0 || _rxPin >= 0) { + if (_txPin >= 0) + digitalWrite(_txPin, _direct?HIGH:LOW); + if (_rxPin >= 0) + digitalWrite(_rxPin, _direct?HIGH:LOW); + delayMicroseconds(1000); + } +#else if (_txPin >= 0) { digitalWrite(_txPin, _direct?HIGH:LOW); #if !defined(ESP32) @@ -116,6 +125,15 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC _port->flush(); +#if defined(MODBUSRTU_REDE) + if (_txPin >= 0 || _rxPin >= 0) { + if (_txPin >= 0) + digitalWrite(_txPin, _direct?LOW:HIGH); + if (_rxPin >= 0) + digitalWrite(_rxPin, _direct?LOW:HIGH); + delayMicroseconds(1000); + } +#else if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); //delay(_t); diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 74bad5e..425f1e3 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -12,6 +12,9 @@ class ModbusRTUTemplate : public Modbus { protected: Stream* _port; int16_t _txPin = -1; +#if defined(MODBUSRTU_REDE) + int16_t _rxPin = -1; +#endif bool _direct = true; // Transmit control logic (true=direct, false=inverse) uint32_t _t; // inter-frame delay in uS uint32_t t = 0; // time sience last data byte arrived @@ -39,6 +42,10 @@ class ModbusRTUTemplate : public Modbus { void setInterFrameTime(uint32_t t_us); template bool begin(T* port, int16_t txPin = -1, bool direct = true); +#if defined(MODBUSRTU_REDE) + template + bool begin(T* port, int16_t txPin, int16_t rxPin, bool direct); +#endif bool begin(Stream* port); void task(); void client() { isMaster = true; }; @@ -68,5 +75,15 @@ bool ModbusRTUTemplate::begin(T* port, int16_t txPin, bool direct) { } return true; } - +#if defined(MODBUSRTU_REDE) +template +bool ModbusRTUTemplate::begin(T* port, int16_t txPin, int16_t rxPin, bool direct) { + begin(port, txPin, direct); + if (rxPin > 0) { + _rxPin = rxPin; + pinMode(_rxPin, OUTPUT); + digitalWrite(_rxPin, _direct?LOW:HIGH); + } +} +#endif class ModbusRTU : public ModbusAPI {}; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 87f9a07..c7cf889 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -64,6 +64,7 @@ If defined regisers count will be limited. #define MODBUSIP_UNIQUE_CLIENTS #define MODBUSIP_MAX_READMS 100 #define MODBUSIP_FULL +#define MODBUSIP_DEBUG //#define MODBUSRTU_DEBUG #define MODBUSRTU_BROADCAST 0 @@ -71,6 +72,11 @@ If defined regisers count will be limited. #define MB_SERIAL_BUFFER 128 #define MODBUSRTU_TIMEOUT 1000 #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 diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index d0f4a56..ce36336 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -205,9 +205,15 @@ void ModbusTCPTemplate::task() { // 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 while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) { +#if defined(MODBUSIP_DEBUG) + Serial.println("IP: Accepted"); +#endif CLIENT* currentClient = new CLIENT(c); if (!currentClient || !currentClient->connected()) continue; +#if defined(MODBUSRTU_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 @@ -222,6 +228,10 @@ void ModbusTCPTemplate::task() { if (n > -1) { tcpclient[n] = currentClient; BIT_SET(tcpServerConnection, n); +#if defined(MODBUSIP_DEBUG) + Serial.print("IP: Conn "); + Serial.println(n); +#endif continue; // while } } @@ -233,6 +243,11 @@ void ModbusTCPTemplate::task() { if (!tcpclient[n]) continue; if (!tcpclient[n]->connected()) continue; while (millis() - taskStart < MODBUSIP_MAX_READMS && (size_t)tcpclient[n]->available() > sizeof(_MBAP)) { +#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. From e71a4acb75322808a2b414aa8a6a12fe4ef6b1af Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 4 Jan 2022 20:48:25 +0500 Subject: [PATCH 253/288] Raw callback (#175) * True bridge core implementation test * True bridge example * Make works: Raw callbacks and True bridge example. * Add debuglog output to True Bridge example * Fix mistipe * Refactor raw callback flow in task() * Update True Bridge example * Set MODBUSIP_CONNECT_TIMEOUT optional * Fix example mistype --- README.md | 8 ++ examples/Bridge/README.md | 48 +++++++ .../bridge.ino => Bridge/basic/basic.ino} | 0 examples/Bridge/true/true.ino | 121 ++++++++++++++++++ examples/README.md | 2 +- src/Modbus.cpp | 4 + src/Modbus.h | 29 ++++- src/ModbusAPI.h | 41 ++++++ src/ModbusRTU.cpp | 39 ++++-- src/ModbusSettings.h | 16 ++- src/ModbusTCPTemplate.h | 40 ++++-- 11 files changed, 320 insertions(+), 28 deletions(-) create mode 100644 examples/Bridge/README.md rename examples/{bridge/bridge.ino => Bridge/basic/basic.ino} (100%) create mode 100644 examples/Bridge/true/true.ino diff --git a/README.md b/README.md index fc184ca..60ce54e 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,14 @@ For more information about Modbus see: - Test: W5x00 with Ethernet library v1 - Examples: Basic file operations - Examples: Revising ++ Settings: Added MODBUSIP_CONNECTION_TIMEOUT (ESP32 only) ++ Settings: Set MODBUSIP_MAX_CLIENTS = 8 for ESP32 ++ API: Raw Modbus frame processing functionality ++ Examples: True ModbusRTU to ModbusTCP Server bridge +- ModbusTCP: ModbusAsyncTCP +- Refactor connect by dns name (using native implementation for ESP32 etc) +- ESP32: Fix dns resolver conflict when using Ethernet.h and WiFi.h together +======= // 4.0.0 + Support of all Arduino boards + ModbusTLS: ESP8266 Client/Server and ESP32 Client diff --git a/examples/Bridge/README.md b/examples/Bridge/README.md new file mode 100644 index 0000000..9cc8edd --- /dev/null +++ b/examples/Bridge/README.md @@ -0,0 +1,48 @@ +# Bridge functions + +## Basic + +Basic 'Bridge'. Indeed this sample pulling data from Modbus Server and stores it to local registers. Local registers can be accessed via Modbus Client instance that running aside. + +## True + +Fullfunctional ModbusRTU to ModbusTCP bridge. + +```c +uint16_t rawRequest(id_ip, uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +uint16_t rawResponce(id_ip, uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); +uint16_t errorResponce(id_ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT); +``` +- `id_ip` SlaveId (`uint8_t`) or server IP address (`IPAddress`) +- `data` Pointer to data buffer to send +- `len` Byte count to send +- `unit` UnitId (ModbusTCP/TLS only) +- `fn` function code in responce +- `excode` Exceprion code in responce + +```c +union frame_arg_t { +struct frame_arg_t { + bool to_server; // true if frame is responce for local Modbus server/slave + union { + // For ModbusRTU + uint8_t slaveId; + // For ModbusTCP/TLS + struct { + uint8_t unitId; // UnitId as passed in Modbus header + uint32_t ipaddr; // IP address from which frame is received + uint16_t transactionId; // TransactionId as passed im Modbus header + }; + }; +}; +typedef std::function cbRaw; // Callback function Type for STL +typedef ResultCode (*cbRaw)(uint8_t* frame, uint8 len, void* data); // Callback function Type +bool onRaw(cbRaw cb = nullptr); +``` +- `frame` Modbus payload frame with stripped MBAP, slaveid and crc +- `len` frame size in bytes +- `data` Pointer to frame_arg_t filled with frame header information +*Returns:* +- If a special error code `Modbus::EX_PASSTHROU` returned frame will be processed normally +- Any other return code disables normal frame processing. Only transactional callback will be executed (if any and transaction data is correct) +The callback is executed only on Modbus frame with valid header and CRC. \ No newline at end of file diff --git a/examples/bridge/bridge.ino b/examples/Bridge/basic/basic.ino similarity index 100% rename from examples/bridge/bridge.ino rename to examples/Bridge/basic/basic.ino diff --git a/examples/Bridge/true/true.ino b/examples/Bridge/true/true.ino new file mode 100644 index 0000000..b4d3236 --- /dev/null +++ b/examples/Bridge/true/true.ino @@ -0,0 +1,121 @@ +/* + ModbusRTU ESP8266/ESP32 + True RTU-TCP bridge example + + (c)2021 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 +#include +#include +#include + +ModbusRTU rtu; +ModbusTCP tcp; + +// ModbusRTU(SlaveID) => ModbusTCP(IP) mapping table +struct slave_map_t { + uint8_t slaveId; // Slave id in incoming request + IPAddress ip; // IP address of MosbusTCP Server map request to + uint8_t unitId = MODBUSIP_UNIT; // UnitId on target server + slave_map_t(uint8_t s, IPAddress i, uint8_t u = MODBUSIP_UNIT) { + slaveId = s; + ip = i; + unitId = u; + }; +}; +std::vector mapping; // Slave => IP mappings +uint16_t transRunning = 0; // Currently executed ModbusTCP transaction +uint8_t slaveRunning = 0; // Current request slave + +bool cbTcpTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place + tcp.disconnect(tcp.eventSource()); // Close connection + } + return true; +} + +// Callback receives raw data from ModbusTCP and sends it on behalf of slave (slaveRunning) to master +Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + Serial.print("TCP IP: "); + Serial.print(IPAddress(src->ipaddr)); + Serial.printf(" Fn: %02X, len: %d \n", data[0], len); + if (!src->to_server && transRunning == src->transactionId) { // Check if transaction id is match + rtu.rawResponce(slaveRunning, data, len); + } else + return Modbus::EX_PASSTHROUGH; // Allow frame to be processed by generic ModbusTCP routines + transRunning = 0; + slaveRunning = 0; + return Modbus::EX_SUCCESS; // Stop other processing +} + + +// Callback receives raw data +Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); + auto it = std::find_if(mapping.begin(), mapping.end(), [src](slave_map_t& item){return (item.slaveId == src->slaveId);}); // Find mapping + if (it != mapping.end()) { + if (!tcp.isConnected(it->ip)) { // Check if connection established + if (!tcp.connect(it->ip)) { // Try to connect if not + Serial.printf("error: Connection timeout\n"); + + rtu.errorResponce(it->slaveId, (Modbus::FunctionCode)data[0], Modbus::EX_DEVICE_FAILED_TO_RESPOND); // Send exceprional responce to master if no connection established + // Note: + // Indeed if both sides is build with the Modbus library _default settings_ RTU master side initiating requests to bridge will respond EX_TIMEOUT not EX_DEVICE_FAILED_TO_RESPOND. + // That's because connection timeout and RTU responce timeout are the same (1 second). That case EX_TIMEOUT on reached prior getting EX_DEVICE_FAILED_TO_RESPOND frame. + return Modbus::EX_DEVICE_FAILED_TO_RESPOND; // Stop processing the frame + } + } + // Save transaction ans slave it for responce processing + transRunning = tcp.rawRequest(it->ip, data, len, cbTcpTrans, it->unitId); + if (!transRunning) { // rawRequest returns 0 is unable to send data for some reason + tcp.disconnect(it->ip); // Close TCP connection that case + Serial.printf("send failed\n"); + rtu.errorResponce(it->slaveId, (Modbus::FunctionCode)data[0], Modbus::EX_DEVICE_FAILED_TO_RESPOND); // Send exceprional responce to master if request bridging failed + return Modbus::EX_DEVICE_FAILED_TO_RESPOND; // Stop processing the frame + } + Serial.printf("transaction: %d\n", transRunning); + slaveRunning = it->slaveId; + return Modbus::EX_SUCCESS; // Stop procesing the frame + } + Serial.printf("ignored: No mapping\n"); + return Modbus::EX_PASSTHROUGH; // Process by generic ModbusRTU routines if no mapping found +} + + +void setup() { + Serial.begin(115000); + WiFi.begin("SSID", "PASSWORD"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + tcp.client(); // Initialize ModbusTCP to pracess as client + tcp.onRaw(cbTcpRaw); // Assign raw data processing callback + + Serial1.begin(9600, SERIAL_8N1, 18, 19); + rtu.begin(&Serial1); + rtu.slave(3); // Initialize ModbusRTU as slave + rtu.onRaw(cbRtuRaw); // Assign raw data processing callback + +// Assign mappings + mapping.push_back({1, IPAddress(192,168,30,18)}); + mapping.push_back({2, IPAddress(192,168,30,19)}); +} + +void loop() { + rtu.task(); + tcp.task(); + yield(); +} \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index febf0d4..716e793 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,7 @@ Examples of using callback functions. Modbus file operations examples. -## [Basic Modbus RTU to Modbus TCP bridge](bridge) +## [Basic Modbus RTU to Modbus TCP bridge](Bridge) Very basic example of accessing ModbusRTU slave device connected to ESP8266/ESP32 via ModbusTCP server. diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 3d6c6f6..e724c0b 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -898,6 +898,10 @@ Modbus::ResultCode Modbus::fileOp(Modbus::FunctionCode fc, uint16_t fileNum, uin } #endif +bool Modbus::onRaw(cbRaw cb) { + _cbRaw = cb; + return true; +} Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, const RequestData data) { return EX_SUCCESS; } diff --git a/src/Modbus.h b/src/Modbus.h index 90e1026..bb93871 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -132,7 +132,8 @@ class Modbus { 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_CANCEL = 0xE6, // Custom. Transaction/request canceled + EX_PASSTHROUGH = 0xE7 // Custom. Indicate to continue processing on callback exit }; union RequestData { struct { @@ -167,6 +168,28 @@ class Modbus { }; }; + 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(); void cbEnable(bool state = true); @@ -264,14 +287,18 @@ class Modbus { virtual uint32_t eventSource() {return 0;} #if defined(MODBUS_USE_STL) typedef std::function cbRequest; // Callback function Type + typedef std::function 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: diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index bd4852c..ff65f8d 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -126,6 +126,13 @@ class ModbusAPI : public T { uint16_t maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); template 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 + uint16_t rawRequest(TYPEID ip, uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t rawResponce(TYPEID ip, uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT); }; // FNAME writeCoil, writeIsts, writeHreg, writeIreg @@ -395,3 +402,37 @@ uint16_t ModbusAPI::readWriteHreg(TYPEID ip, \ } return this->send(ip, HREG(readOffset), cb, unit, (uint8_t*)readValue); }; + +template +template +uint16_t ModbusAPI::rawRequest(TYPEID ip, \ + 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, HREG(0), cb, unit); +}; + +template +template +uint16_t ModbusAPI::rawResponce(TYPEID ip, \ + 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, HREG(0), nullptr, unit, nullptr, false); +}; + +template +template +uint16_t ModbusAPI::errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit) { + this->exceptionResponse(fn, excode); + return this->send(ip, HREG(0), nullptr, unit, nullptr, false); +} diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index c9effaf..f99c380 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -142,7 +142,8 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { bool result = false; - if (!_slaveId && _len && _frame) { // Check if waiting for previous request result and _frame filled + 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; @@ -191,20 +192,21 @@ void ModbusRTUTemplate::task() { } } + bool valid_frame = true; uint8_t address = _port->read(); //first byte of frame = address _len--; // Decrease by slaveId byte if (isMaster && _slaveId == 0) { // Check if slaveId is set - for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected - _len = 0; - //if (isMaster) cleanup(); - return; + 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); @@ -234,11 +236,19 @@ void ModbusRTUTemplate::task() { if (isMaster) cleanup(); return; } + _reply = EX_PASSTHROUGH; + if (_cbRaw) { + frame_arg_t header_data = { address, !isMaster }; + _reply = _cbRaw(_frame, _len, (void*)&header_data); + } + if (!valid_frame) { + goto cleanup; + } if (isMaster) { - _reply = EX_SUCCESS; if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested // Procass incoming frame as master - masterPDU(_frame, _sentFrame, _sentReg, _data); + if (_reply == EX_PASSTHROUGH) + masterPDU(_frame, _sentFrame, _sentReg, _data); if (_cb) { _cb((ResultCode)_reply, 0, nullptr); _cb = nullptr; @@ -250,13 +260,16 @@ void ModbusRTUTemplate::task() { } _reply = Modbus::REPLY_OFF; // No reply if master } else { - slavePDU(_frame); - if (address == MODBUSRTU_BROADCAST) - _reply = Modbus::REPLY_OFF; // No reply for Broadcasts - if (_reply != Modbus::REPLY_OFF) - rawSend(_slaveId, _frame, _len); + if (_reply == EX_PASSTHROUGH) { + slavePDU(_frame); + if (address == MODBUSRTU_BROADCAST) + _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + if (_reply != Modbus::REPLY_OFF) + rawSend(_slaveId, _frame, _len); + } } // Cleanup +cleanup: free(_frame); _frame = nullptr; _len = 0; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index c7cf889..8793f1d 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -57,10 +57,24 @@ If defined regisers count will be limited. #define MODBUSTCP_PORT 502 #define MODBUSTLS_PORT 802 #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 -#define MODBUSIP_MAX_CLIENTS 4 +#if defined(ESP32) +#define MODBUSIP_MAX_CLIENTS 8 +#else +#define MODBUSIP_MAX_CLIENTS 4 +#endif #define MODBUSIP_UNIQUE_CLIENTS #define MODBUSIP_MAX_READMS 100 #define MODBUSIP_FULL diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index ce36336..20a9dd3 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -77,6 +77,7 @@ class ModbusTCPTemplate : public Modbus { 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); @@ -164,7 +165,11 @@ bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { 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; @@ -262,32 +267,44 @@ void ModbusTCPTemplate::task() { _len--; // Subtract for read byte for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of packet tcpclient[n]->read(); - } else { + } + else { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop packet tcpclient[n]->read(); - } else { + } + else { if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); //while (tcpclient[n]->available()) // Drop all incoming (if any) // tcpclient[n]->read(); - } else { - //if (tcpclient[n]->localPort() == serverPort) { + } + 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)) { - // Process incoming frame as slave - slavePDU(_frame); - } else { + if (_reply == EX_PASSTHROUGH) + slavePDU(_frame); // Process incoming frame as slave + else + _reply = REPLY_OFF; + } + else { // Process reply to master request - _reply = EX_SUCCESS; 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 - // Procass incoming frame as master - masterPDU(_frame, trans->_frame, trans->startreg, trans->data); - } else { + if (_reply == EX_PASSTHROUGH) + masterPDU(_frame, trans->_frame, trans->startreg, trans->data); // Procass incoming frame as master + } + else { _reply = EX_UNEXPECTED_RESPONSE; } if (trans->cb) { @@ -308,7 +325,6 @@ void ModbusTCPTemplate::task() { } } } - //if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master 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 From 6cd22d093b23a8a18847a61555d1f8f0758bd33b Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 5 Jan 2022 21:22:12 +0500 Subject: [PATCH 254/288] Fix merge defects --- README.md | 45 ++++++++++++++++++++++++----------------- src/ModbusEthernet-v1.h | 40 ------------------------------------ src/ModbusRTU.cpp | 44 +++++++++++++++++++++++++++++++--------- src/ModbusRTU.h | 3 ++- src/ModbusSettings.h | 10 ++++++--- 5 files changed, 70 insertions(+), 72 deletions(-) delete mode 100644 src/ModbusEthernet-v1.h diff --git a/README.md b/README.md index 60ce54e..4cb7b23 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ For detailes on the library usage visit [documentation](documentation) section. * 0x16 - Mask Write Register * 0x17 - Read/Write multiple registers * [Callbacks](examples/Callback) driven design +* Real life complex examples: + * [ESP8266/ESP32 firmware over Modbus](examples/Files) + * [ModbusRTU to ModbusTCP bridge](examples/bridge) ## Notes @@ -49,33 +52,37 @@ For more information about Modbus see: ```diff // 4.1.0-DEV + ModbusRTU: Add separate RE/DE pins control -- ModbusRTU: Add direction control pin for Stream -- ModbusRTU: Static buffer allocation. -- API: Extend API to allow custom Modbus commands -- STL: Add Reg count limitation to vector limit of 4000 for ESP (re-check first) -- ModbusTLS: ESP32 Server -- Test: TLS ESP32 Server -- Test: TLS ESP32 Client -- Examples: TLS Certificate test Role extension and Alt-Name -- Examples: TLS Add example explanation ++ ModbusRTU: Add direction control pin for Stream ++ STL: Add Reg count limitation to vector limit of 4000 for ESP ++ Settings: Added MODBUSIP_CONNECTION_TIMEOUT (ESP32 only) ++ Settings: Set MODBUSIP_MAX_CLIENTS = 8 for ESP32 ++ API: Raw Modbus frame processing functionality +- API: Alternative CRC calulation (reduced memory footprint) ++ Examples: True ModbusRTU to ModbusTCP Server bridge +- ModbusTCP: Refactor connect by dns name (using native implementation for ESP32 etc) +- ESP32: Fix dns resolver conflict when using Ethernet.h and WiFi.h together ++ ModbusRTU: Precise inter-frame interval control ++ API: Drop support of Ethernet library v1 ++ Examples: Teknic ClearCore ArduinoWrapper examples added +// 4.2.0-DEV +- ModbusRTU: Static buffer allocation +- Test: Frame accuracy to specefication - Buffer/packet size limitation support - Slave/Server: slavePDU use early exit by return where possible - Master/Client: Check frame size against header data where possible - Master/Client: Additional responce data validation - Free global registers and callbacks on remove last Modbus instance -- Test: Frame accuracy to specefication - Test: push/pull functions -- Test: W5x00 with Ethernet library v1 +// 4.3.0-DEV +- ModbusTLS: ESP32 Server +- Test: TLS ESP32 Server +- Test: TLS ESP32 Client +- Examples: TLS Certificate test Role extension and Alt-Name +- Examples: TLS Add example explanation +- ModbusTCP: ModbusAsyncTCP +- API: Extend API to allow custom Modbus commands - Examples: Basic file operations - Examples: Revising -+ Settings: Added MODBUSIP_CONNECTION_TIMEOUT (ESP32 only) -+ Settings: Set MODBUSIP_MAX_CLIENTS = 8 for ESP32 -+ API: Raw Modbus frame processing functionality -+ Examples: True ModbusRTU to ModbusTCP Server bridge -- ModbusTCP: ModbusAsyncTCP -- Refactor connect by dns name (using native implementation for ESP32 etc) -- ESP32: Fix dns resolver conflict when using Ethernet.h and WiFi.h together -======= // 4.0.0 + Support of all Arduino boards + ModbusTLS: ESP8266 Client/Server and ESP32 Client diff --git a/src/ModbusEthernet-v1.h b/src/ModbusEthernet-v1.h deleted file mode 100644 index 630d9a6..0000000 --- a/src/ModbusEthernet-v1.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Modbus Library for Arduino - ModbusTCP for W5x00 Ethernet - Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) -*/ - -#pragma once -#include -#include "ModbusAPI.h" -#include "ModbusTCPTemplate.h" - -#undef MODBUSIP_UNIQUE_CLIENTS - -class EthernetClientWrapper : public EthernetClient { - public: - EthernetClientWrapper(EthernetClient c) : EthernetClient(c) {}; - IPAddress remoteIP() { - if (connected()) - return IPAddress(0,0,0,1); - return IPADDR_NONE; - } -}; - -class ModbusEthernet : public ModbusAPI> { - 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; - } -}; diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index f99c380..da6bbbb 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -42,7 +42,28 @@ uint16_t ModbusRTUTemplate::crc16(uint8_t address, uint8_t* frame, uint8_t pduLe } 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::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) @@ -70,11 +91,7 @@ uint32_t ModbusRTUTemplate::calculateMinimumInterFrameTime(uint32_t baud, uint8_ // Kept for backward compatibility void ModbusRTUTemplate::setBaudrate(uint32_t baud) { - if (baud > 19200) { - _t = 2; - } else { - _t = (35000/baud) + 1; - } + setInterFrameTime(calculateMinimumInterFrameTime(baud)); } void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) { @@ -87,9 +104,15 @@ void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) { _t = t_us; } -bool ModbusRTUTemplate::begin(Stream* port) { +bool ModbusRTUTemplate::begin(Stream* port, int16_t txPin, bool direct) { _port = port; _t = 1750UL; + if (txPin >= 0) { + _txPin = txPin; + _direct = direct; + pinMode(_txPin, OUTPUT); + digitalWrite(_txPin, _direct?LOW:HIGH); + } return true; } @@ -108,7 +131,9 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { digitalWrite(_txPin, _direct?HIGH:LOW); if (_rxPin >= 0) digitalWrite(_rxPin, _direct?HIGH:LOW); +#if !defined(ESP32) delayMicroseconds(1000); +#endif } #else if (_txPin >= 0) { @@ -116,7 +141,8 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { #if !defined(ESP32) delayMicroseconds(1000); #endif - } + } +#endif #if defined(ESP32) vTaskDelay(0); #endif @@ -131,11 +157,11 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { digitalWrite(_txPin, _direct?LOW:HIGH); if (_rxPin >= 0) digitalWrite(_rxPin, _direct?LOW:HIGH); - delayMicroseconds(1000); } #else if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); +#endif //delay(_t); return true; } diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 425f1e3..671a567 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -36,6 +36,7 @@ class ModbusRTUTemplate : public Modbus { 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); @@ -46,7 +47,7 @@ class ModbusRTUTemplate : public Modbus { template bool begin(T* port, int16_t txPin, int16_t rxPin, bool direct); #endif - bool begin(Stream* port); + bool begin(Stream* port, int16_t txPin = -1, bool direct = true); void task(); void client() { isMaster = true; }; inline void master() {client();} diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 8793f1d..4652a28 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -44,8 +44,12 @@ If defined C STL will be used. /* #define MODBUS_MAX_REGS 32 If defined regisers count will be limited. -*/ -//#define MODBUS_MAX_REGS 32 +*/ +// 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 @@ -90,7 +94,7 @@ ESP32 only. Outgoing connection attempt timeout #define MODBUSRTU_REDE Enable using separate pins for RE DE */ -#define MODBUSRTU_REDE +//#define MODBUSRTU_REDE // Define for internal use. Do not change. #define MODBUSRTU_TIMEOUT_US 1000UL * MODBUSRTU_TIMEOUT From 4c5581807aeddbe07071b556ac2e4d189ab49b41 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Wed, 12 Jan 2022 22:38:01 +0500 Subject: [PATCH 255/288] '-' => '_' in filenames --- README.md | 6 +++--- .../IP_server_MultipleHRegDebug.ino} | 0 examples/README.md | 4 ++-- .../IP_server_AnalogInput/IP_server_AnalogInput.ino} | 0 .../IP_server_Led/IP_server_Led.ino} | 0 .../IP_server_SwitchStatus/IP_server_SwitchStatus.ino} | 0 examples/{TCP-ESP => TCP_ESP}/README.md | 0 examples/{TCP-ESP => TCP_ESP}/client/client.ino | 0 examples/{TCP-ESP => TCP_ESP}/clientPull/clientPull.ino | 0 examples/{TCP-ESP => TCP_ESP}/clientSync/clientSync.ino | 0 examples/{TCP-ESP => TCP_ESP}/server/server.ino | 0 examples/{TCP-Ethernet => TCP_Ethernet}/README.md | 0 examples/{TCP-Ethernet => TCP_Ethernet}/client/client.ino | 0 examples/{TCP-Ethernet => TCP_Ethernet}/server/server.ino | 0 src/ModbusSettings.h | 2 +- 15 files changed, 6 insertions(+), 6 deletions(-) rename examples/Callback/{IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino => IP_server_MultipleHRegDebug/IP_server_MultipleHRegDebug.ino} (100%) rename examples/{TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino => TCP_ESP/IP_server_AnalogInput/IP_server_AnalogInput.ino} (100%) rename examples/{TCP-ESP/IP-server-Led/IP-server-Led.ino => TCP_ESP/IP_server_Led/IP_server_Led.ino} (100%) rename examples/{TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino => TCP_ESP/IP_server_SwitchStatus/IP_server_SwitchStatus.ino} (100%) rename examples/{TCP-ESP => TCP_ESP}/README.md (100%) rename examples/{TCP-ESP => TCP_ESP}/client/client.ino (100%) rename examples/{TCP-ESP => TCP_ESP}/clientPull/clientPull.ino (100%) rename examples/{TCP-ESP => TCP_ESP}/clientSync/clientSync.ino (100%) rename examples/{TCP-ESP => TCP_ESP}/server/server.ino (100%) rename examples/{TCP-Ethernet => TCP_Ethernet}/README.md (100%) rename examples/{TCP-Ethernet => TCP_Ethernet}/client/client.ino (100%) rename examples/{TCP-Ethernet => TCP_Ethernet}/server/server.ino (100%) diff --git a/README.md b/README.md index 4cb7b23..8fa7172 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ For detailes on the library usage visit [documentation](documentation) section. * Operates in any combination of multiple instances of * [Modbus RTU server](examples/RTU) * [Modbus RTU client](examples/RTU) - * Modbus TCP server for [ESP8266/ESP32](examples/TCP-ESP) and [Ethernet library](examples/TCP-Ethernet) - * Modbus TCP client for [ESP8266/ESP32](examples/TCP-ESP) and [Ethernet library](examples/TCP-Ethernet) + * Modbus TCP server for [ESP8266/ESP32](examples/TCP_ESP) and [Ethernet library](examples/TCP_Ethernet) + * Modbus TCP client for [ESP8266/ESP32](examples/TCP_ESP) and [Ethernet library](examples/TCP_Ethernet) * [MODBUS/TCP Security server (ESP8266)](examples/TLS) * [MODBUS/TCP Security client (ESP8266/ESP32)](examples/TLS) * Modbus functions supported: @@ -31,7 +31,7 @@ For detailes on the library usage visit [documentation](documentation) section. * 0x17 - Read/Write multiple registers * [Callbacks](examples/Callback) driven design * Real life complex examples: - * [ESP8266/ESP32 firmware over Modbus](examples/Files) + * [ESP8266/ESP32 firmware update over Modbus](examples/Files) * [ModbusRTU to ModbusTCP bridge](examples/bridge) ## Notes diff --git a/examples/Callback/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino b/examples/Callback/IP_server_MultipleHRegDebug/IP_server_MultipleHRegDebug.ino similarity index 100% rename from examples/Callback/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino rename to examples/Callback/IP_server_MultipleHRegDebug/IP_server_MultipleHRegDebug.ino diff --git a/examples/README.md b/examples/README.md index 716e793..4e843e6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,11 +4,11 @@ ModbusRTU master and slave examples -## [TCP ESP8266/ESP32](TCP-ESP) +## [TCP ESP8266/ESP32](TCP_ESP) ModbusTCP for ESP8266/ESP32 client and server examples -## [TCP Ethernet W5x00](TCP-Ethernet) +## [TCP Ethernet W5x00](TCP_Ethernet) ModbusTCP for W5x00 Ethernet library client and server examples (for all Arduino). diff --git a/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino b/examples/TCP_ESP/IP_server_AnalogInput/IP_server_AnalogInput.ino similarity index 100% rename from examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino rename to examples/TCP_ESP/IP_server_AnalogInput/IP_server_AnalogInput.ino diff --git a/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino b/examples/TCP_ESP/IP_server_Led/IP_server_Led.ino similarity index 100% rename from examples/TCP-ESP/IP-server-Led/IP-server-Led.ino rename to examples/TCP_ESP/IP_server_Led/IP_server_Led.ino diff --git a/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino b/examples/TCP_ESP/IP_server_SwitchStatus/IP_server_SwitchStatus.ino similarity index 100% rename from examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino rename to examples/TCP_ESP/IP_server_SwitchStatus/IP_server_SwitchStatus.ino diff --git a/examples/TCP-ESP/README.md b/examples/TCP_ESP/README.md similarity index 100% rename from examples/TCP-ESP/README.md rename to examples/TCP_ESP/README.md diff --git a/examples/TCP-ESP/client/client.ino b/examples/TCP_ESP/client/client.ino similarity index 100% rename from examples/TCP-ESP/client/client.ino rename to examples/TCP_ESP/client/client.ino diff --git a/examples/TCP-ESP/clientPull/clientPull.ino b/examples/TCP_ESP/clientPull/clientPull.ino similarity index 100% rename from examples/TCP-ESP/clientPull/clientPull.ino rename to examples/TCP_ESP/clientPull/clientPull.ino diff --git a/examples/TCP-ESP/clientSync/clientSync.ino b/examples/TCP_ESP/clientSync/clientSync.ino similarity index 100% rename from examples/TCP-ESP/clientSync/clientSync.ino rename to examples/TCP_ESP/clientSync/clientSync.ino diff --git a/examples/TCP-ESP/server/server.ino b/examples/TCP_ESP/server/server.ino similarity index 100% rename from examples/TCP-ESP/server/server.ino rename to examples/TCP_ESP/server/server.ino diff --git a/examples/TCP-Ethernet/README.md b/examples/TCP_Ethernet/README.md similarity index 100% rename from examples/TCP-Ethernet/README.md rename to examples/TCP_Ethernet/README.md diff --git a/examples/TCP-Ethernet/client/client.ino b/examples/TCP_Ethernet/client/client.ino similarity index 100% rename from examples/TCP-Ethernet/client/client.ino rename to examples/TCP_Ethernet/client/client.ino diff --git a/examples/TCP-Ethernet/server/server.ino b/examples/TCP_Ethernet/server/server.ino similarity index 100% rename from examples/TCP-Ethernet/server/server.ino rename to examples/TCP_Ethernet/server/server.ino diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 4652a28..56277d1 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -21,7 +21,7 @@ MODBUSAPI_ Settings for API 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_GLOBAL_REGS //#define MODBUS_FREE_REGS /* From dfb1ce85eb42f791f2a6492745b36b64c645906f Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 8 Mar 2022 16:03:38 +0500 Subject: [PATCH 256/288] Minor changes: ModbusTCP: Make DNS as Modbus target optional ModbusRTU: Add extra deleay for RD DE control API-RawCallback: Add force packet processing --- README.md | 19 ++++++++++--------- src/Modbus.h | 5 +++-- src/ModbusEthernet.h | 6 +++++- src/ModbusRTU.cpp | 42 +++++++++++++++++++++++++---------------- src/ModbusRTU.h | 12 ++++++++++-- src/ModbusSettings.h | 19 +++++++++++++++++-- src/ModbusTCP.h | 2 ++ src/ModbusTCPTemplate.h | 13 ++++++++++--- src/ModbusTLS.h | 6 ++++++ 9 files changed, 89 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 8fa7172..f364fff 100644 --- a/README.md +++ b/README.md @@ -50,21 +50,21 @@ For more information about Modbus see: ## Last Changes ```diff -// 4.1.0-DEV -+ ModbusRTU: Add separate RE/DE pins control +// 4.1.0-RC1 ++ API: Raw Modbus frame processing functionality ++ ModbusRTU: Precise inter-frame interval control ++ Examples: True ModbusRTU to ModbusTCP Server bridge + ModbusRTU: Add direction control pin for Stream -+ STL: Add Reg count limitation to vector limit of 4000 for ESP ++ STL: Add Reg count limitation to vector limit of 4000 (for ESP8266 and ESP32) + Settings: Added MODBUSIP_CONNECTION_TIMEOUT (ESP32 only) + Settings: Set MODBUSIP_MAX_CLIENTS = 8 for ESP32 -+ API: Raw Modbus frame processing functionality -- API: Alternative CRC calulation (reduced memory footprint) -+ Examples: True ModbusRTU to ModbusTCP Server bridge -- ModbusTCP: Refactor connect by dns name (using native implementation for ESP32 etc) -- ESP32: Fix dns resolver conflict when using Ethernet.h and WiFi.h together -+ ModbusRTU: Precise inter-frame interval control ++ ModbusTCP: Make using DNS names optional feature ++ ModbusRTU: Add separate RE/DE pins control optional feature + API: Drop support of Ethernet library v1 + Examples: Teknic ClearCore ArduinoWrapper examples added ++ ModbusRTU: Flush extra delay optional feature // 4.2.0-DEV +- API: Alternative CRC calulation (reduced memory footprint) - ModbusRTU: Static buffer allocation - Test: Frame accuracy to specefication - Buffer/packet size limitation support @@ -73,6 +73,7 @@ For more information about Modbus see: - Master/Client: Additional responce data validation - Free global registers and callbacks on remove last Modbus instance - Test: push/pull functions +- ModbusTCP: Refactor connect by dns name (using native implementation for ESP32 etc) // 4.3.0-DEV - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server diff --git a/src/Modbus.h b/src/Modbus.h index bb93871..018cc54 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -2,7 +2,7 @@ Modbus Library for Arduino Core functions Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2022 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once #include "ModbusSettings.h" @@ -133,7 +133,8 @@ class Modbus { 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. Indicate to continue processing on callback exit + 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 { diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h index 71543a8..54c02ff 100644 --- a/src/ModbusEthernet.h +++ b/src/ModbusEthernet.h @@ -1,11 +1,13 @@ /* Modbus Library for Arduino ModbusTCP for W5x00 Ethernet - Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) + Copyright (C) 2022 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once +#if defined(MODBUSIP_USE_DNS) #include +#endif #include "ModbusAPI.h" #include "ModbusTCPTemplate.h" @@ -21,6 +23,7 @@ class EthernetServerWrapper : public EthernetServer { }; class ModbusEthernet : public ModbusAPI> { +#if defined(MODBUSIP_USE_DNS) private: static IPAddress resolver (const char* host) { DNSClient dns; @@ -36,4 +39,5 @@ class ModbusEthernet : public ModbusAPI 19200) { return 1750UL; } else { - return (3.5 * (uint32_t)char_bits * 1000000UL) / baud; + return 3.5 * charSendTime(baud, char_bits); } } @@ -107,6 +112,9 @@ void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) { bool ModbusRTUTemplate::begin(Stream* port, int16_t txPin, bool direct) { _port = port; _t = 1750UL; +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(0); +#endif if (txPin >= 0) { _txPin = txPin; _direct = direct; @@ -132,14 +140,14 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { if (_rxPin >= 0) digitalWrite(_rxPin, _direct?HIGH:LOW); #if !defined(ESP32) - delayMicroseconds(1000); + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); #endif } #else if (_txPin >= 0) { digitalWrite(_txPin, _direct?HIGH:LOW); #if !defined(ESP32) - delayMicroseconds(1000); + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); #endif } #endif @@ -153,16 +161,22 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->flush(); #if defined(MODBUSRTU_REDE) if (_txPin >= 0 || _rxPin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); if (_rxPin >= 0) digitalWrite(_rxPin, _direct?LOW:HIGH); } #else - if (_txPin >= 0) + if (_txPin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif digitalWrite(_txPin, _direct?LOW:HIGH); + } #endif - //delay(_t); return true; } @@ -219,7 +233,7 @@ void ModbusRTUTemplate::task() { } bool valid_frame = true; - uint8_t address = _port->read(); //first byte of frame = address + 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; @@ -256,24 +270,20 @@ void ModbusRTUTemplate::task() { 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 - free(_frame); - _frame = nullptr; - _len = 0; - if (isMaster) cleanup(); - return; + goto cleanup; } _reply = EX_PASSTHROUGH; if (_cbRaw) { frame_arg_t header_data = { address, !isMaster }; _reply = _cbRaw(_frame, _len, (void*)&header_data); } - if (!valid_frame) { + 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) + if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) masterPDU(_frame, _sentFrame, _sentReg, _data); if (_cb) { _cb((ResultCode)_reply, 0, nullptr); @@ -286,12 +296,12 @@ void ModbusRTUTemplate::task() { } _reply = Modbus::REPLY_OFF; // No reply if master } else { - if (_reply == EX_PASSTHROUGH) { + 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(_slaveId, _frame, _len); + rawSend(address, _frame, _len); } } // Cleanup diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 671a567..6691346 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,7 +1,7 @@ /* Modbus Library for Arduino ModbusRTU - Copyright (C) 2019-2021 Alexander Emelianov (a.m.emelianov@gmail.com) + 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. */ @@ -17,6 +17,9 @@ class ModbusRTUTemplate : public Modbus { #endif bool _direct = true; // Transmit control logic (true=direct, 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; @@ -26,6 +29,7 @@ class ModbusRTUTemplate : public Modbus { uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); uint16_t maxRegs = 0x007D; + 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 @@ -41,6 +45,7 @@ class ModbusRTUTemplate : public Modbus { 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 bool begin(T* port, int16_t txPin = -1, bool direct = true); #if defined(MODBUSRTU_REDE) @@ -55,7 +60,7 @@ class ModbusRTUTemplate : public Modbus { inline void slave(uint8_t slaveId) {server(slaveId);} uint8_t server() { return _slaveId; } inline uint8_t slave() { return server(); } - uint32_t eventSource() override {return _slaveId;} + uint32_t eventSource() override {return address;} }; template @@ -67,6 +72,9 @@ bool ModbusRTUTemplate::begin(T* port, int16_t txPin, bool direct) { baud = 9600; #endif setInterFrameTime(calculateMinimumInterFrameTime(baud)); +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(baud); +#endif _port = port; if (txPin >= 0) { _txPin = txPin; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 56277d1..de60a76 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -2,7 +2,7 @@ /* Modbus Library for Arduino - Copyright (C) 2019-2021 Alexander Emelianov (a.m.emelianov@gmail.com) + 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. @@ -21,7 +21,7 @@ MODBUSAPI_ Settings for API 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_GLOBAL_REGS //#define MODBUS_FREE_REGS /* @@ -83,6 +83,12 @@ ESP32 only. Outgoing connection attempt timeout #define MODBUSIP_MAX_READMS 100 #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 @@ -100,6 +106,15 @@ Enable using separate pins for RE DE #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 diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h index dbe2c22..fed76c3 100644 --- a/src/ModbusTCP.h +++ b/src/ModbusTCP.h @@ -23,6 +23,7 @@ class WiFiServerESPWrapper : public WiFiServer { }; class ModbusTCP : public ModbusAPI> { +#if defined(MODBUSIP_USE_DNS) private: static IPAddress resolver(const char *host) { IPAddress remote_addr; @@ -35,4 +36,5 @@ class ModbusTCP : public ModbusAPI::server(uint16_t port) { tcpserver->begin(); } +#if defined(MODBUSIP_USE_DNS) template bool ModbusTCPTemplate::connect(String host, uint16_t port) { return connect(resolve(host.c_str()), port); @@ -152,6 +155,7 @@ template bool ModbusTCPTemplate::connect(const char* host, uint16_t port) { return connect(resolve(host), port); } +#endif template bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { @@ -491,7 +495,7 @@ template bool ModbusTCPTemplate::isTransaction(uint16_t id) { return searchTransaction(id) != nullptr; } - +#if defined(MODBUSIP_USE_DNS) template bool ModbusTCPTemplate::isConnected(String host) { return isConnected(resolve(host.c_str())); @@ -501,6 +505,7 @@ template bool ModbusTCPTemplate::isConnected(const char* host) { return isConnected(resolve(host)); } +#endif template bool ModbusTCPTemplate::isConnected(IPAddress ip) { @@ -515,6 +520,7 @@ void ModbusTCPTemplate::autoConnect(bool enabled) { autoConnectMode = enabled; } +#if defined(MODBUSIP_USE_DNS) template bool ModbusTCPTemplate::disconnect(String host) { return disconnect(resolve(host.c_str())); @@ -524,6 +530,7 @@ template bool ModbusTCPTemplate::disconnect(const char* host) { return disconnect(resolve(host)); } +#endif template bool ModbusTCPTemplate::disconnect(IPAddress ip) { diff --git a/src/ModbusTLS.h b/src/ModbusTLS.h index 92cef86..7f0fdf8 100644 --- a/src/ModbusTLS.h +++ b/src/ModbusTLS.h @@ -44,16 +44,20 @@ class ModbusTLS : public ModbusAPI Date: Tue, 8 Mar 2022 17:01:00 +0500 Subject: [PATCH 257/288] Add Multipe ID response example --- README.md | 4 +- .../MultipleServerID/MultipleServerID.ino | 51 +++++++++++++++++++ examples/Bridge/README.md | 5 ++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 examples/Bridge/MultipleServerID/MultipleServerID.ino diff --git a/README.md b/README.md index f364fff..a22bf47 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # Modbus Library for Arduino ### ModbusRTU, ModbusTCP and ModbusTCP Security -|If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| -|---|---| - For detailes on the library usage visit [documentation](documentation) section. ## Features @@ -54,6 +51,7 @@ For more information about Modbus see: + API: Raw Modbus frame processing functionality + ModbusRTU: Precise inter-frame interval control + Examples: True ModbusRTU to ModbusTCP Server bridge ++ Examples: ModbusRTU respond to multiple ID from single device + ModbusRTU: Add direction control pin for Stream + STL: Add Reg count limitation to vector limit of 4000 (for ESP8266 and ESP32) + Settings: Added MODBUSIP_CONNECTION_TIMEOUT (ESP32 only) diff --git a/examples/Bridge/MultipleServerID/MultipleServerID.ino b/examples/Bridge/MultipleServerID/MultipleServerID.ino new file mode 100644 index 0000000..278a99b --- /dev/null +++ b/examples/Bridge/MultipleServerID/MultipleServerID.ino @@ -0,0 +1,51 @@ +/* + ModbusRTU ESP32 + Minimalistic example of server responding for multiple IDs. That is the server looks as multiple devices on bus. + + (c)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 + +#define REGN 10 +#define PRIMARY_ID 1 +#define PRIMERT_VALUE 100 +#define SECONDARY_ID 2 +#define SECONDARY_VALUE 200 + +ModbusRTU mb; + +Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; // argument contains some data on incoming packet + Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); + if (src->slaveId == SECONDARY_ID) // Check if incoming packet is addresses to server with ID + return Modbus::EX_FORCE_PROCESS; // Instruct the library to force the packet processing + // It's required as otherwise packet will be not processed as not addressed + // to the server + + return Modbus::EX_PASSTHROUGH; // or process packet normally +} + +uint16_t cbRead(TRegister* reg, uint16_t val) { + if (mb.eventSource() == SECONDARY_ID) + return SECONDARY_VALUE; + return val; +} + +void setup() { + Serial.begin(115200); + Serial1.begin(9600); + mb.begin(&Serial1); + mb.slave(PRIMARY_ID); // Set Modbus to work as a server with ID + mb.onRaw(cbRtuRaw); // Assign raw packet callback + mb.addHreg(REGN, PRIMARY_VALUE); + mb.onGet(cbReadHreg) +} + +void loop() { + mb.task(); + yield(); +} \ No newline at end of file diff --git a/examples/Bridge/README.md b/examples/Bridge/README.md index 9cc8edd..e03a7c9 100644 --- a/examples/Bridge/README.md +++ b/examples/Bridge/README.md @@ -8,6 +8,10 @@ Basic 'Bridge'. Indeed this sample pulling data from Modbus Server and stores it Fullfunctional ModbusRTU to ModbusTCP bridge. +## MultipeServerID + +Respond for multiple ModbusRTU IDs from single device + ```c uint16_t rawRequest(id_ip, uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t rawResponce(id_ip, uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); @@ -44,5 +48,6 @@ bool onRaw(cbRaw cb = nullptr); - `data` Pointer to frame_arg_t filled with frame header information *Returns:* - If a special error code `Modbus::EX_PASSTHROU` returned frame will be processed normally +- If a special error code `Modbus::EX_FORCE_PROCESS` returned frame will be processed even if addressed to another Modbus unit - Any other return code disables normal frame processing. Only transactional callback will be executed (if any and transaction data is correct) The callback is executed only on Modbus frame with valid header and CRC. \ No newline at end of file From 7a213b3857cb56faaf29300ba342b3f21801028a Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 8 Mar 2022 17:11:07 +0500 Subject: [PATCH 258/288] _ to - in examples --- README.md | 4 ++-- .../IPserver-MultipleHRegDebug.ino} | 0 examples/README.md | 4 ++-- .../IP-server-AnalogInput/IP-server-AnalogInput.ino} | 0 .../IP-server-Led/IP-server-Led.ino} | 0 .../IP-server-SwitchStatus/IP-server-SwitchStatus.ino} | 0 examples/{TCP_ESP => TCP-ESP}/README.md | 0 examples/{TCP_ESP => TCP-ESP}/client/client.ino | 0 examples/{TCP_ESP => TCP-ESP}/clientPull/clientPull.ino | 0 examples/{TCP_ESP => TCP-ESP}/clientSync/clientSync.ino | 0 examples/{TCP_ESP => TCP-ESP}/server/server.ino | 0 examples/{TCP_Ethernet => TCP-Ethernet}/README.md | 0 examples/{TCP_Ethernet => TCP-Ethernet}/client/client.ino | 0 examples/{TCP_Ethernet => TCP-Ethernet}/server/server.ino | 0 14 files changed, 4 insertions(+), 4 deletions(-) rename examples/Callback/{IP_server_MultipleHRegDebug/IP_server_MultipleHRegDebug.ino => IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino} (100%) rename examples/{TCP_ESP/IP_server_AnalogInput/IP_server_AnalogInput.ino => TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino} (100%) rename examples/{TCP_ESP/IP_server_Led/IP_server_Led.ino => TCP-ESP/IP-server-Led/IP-server-Led.ino} (100%) rename examples/{TCP_ESP/IP_server_SwitchStatus/IP_server_SwitchStatus.ino => TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino} (100%) rename examples/{TCP_ESP => TCP-ESP}/README.md (100%) rename examples/{TCP_ESP => TCP-ESP}/client/client.ino (100%) rename examples/{TCP_ESP => TCP-ESP}/clientPull/clientPull.ino (100%) rename examples/{TCP_ESP => TCP-ESP}/clientSync/clientSync.ino (100%) rename examples/{TCP_ESP => TCP-ESP}/server/server.ino (100%) rename examples/{TCP_Ethernet => TCP-Ethernet}/README.md (100%) rename examples/{TCP_Ethernet => TCP-Ethernet}/client/client.ino (100%) rename examples/{TCP_Ethernet => TCP-Ethernet}/server/server.ino (100%) diff --git a/README.md b/README.md index a22bf47..c9378fd 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ For detailes on the library usage visit [documentation](documentation) section. * Operates in any combination of multiple instances of * [Modbus RTU server](examples/RTU) * [Modbus RTU client](examples/RTU) - * Modbus TCP server for [ESP8266/ESP32](examples/TCP_ESP) and [Ethernet library](examples/TCP_Ethernet) - * Modbus TCP client for [ESP8266/ESP32](examples/TCP_ESP) and [Ethernet library](examples/TCP_Ethernet) + * Modbus TCP server for [ESP8266/ESP32](examples/TCP-ESP) and [Ethernet library](examples/TCP-Ethernet) + * Modbus TCP client for [ESP8266/ESP32](examples/TCP-ESP) and [Ethernet library](examples/TCP-Ethernet) * [MODBUS/TCP Security server (ESP8266)](examples/TLS) * [MODBUS/TCP Security client (ESP8266/ESP32)](examples/TLS) * Modbus functions supported: diff --git a/examples/Callback/IP_server_MultipleHRegDebug/IP_server_MultipleHRegDebug.ino b/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino similarity index 100% rename from examples/Callback/IP_server_MultipleHRegDebug/IP_server_MultipleHRegDebug.ino rename to examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino diff --git a/examples/README.md b/examples/README.md index 4e843e6..716e793 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,11 +4,11 @@ ModbusRTU master and slave examples -## [TCP ESP8266/ESP32](TCP_ESP) +## [TCP ESP8266/ESP32](TCP-ESP) ModbusTCP for ESP8266/ESP32 client and server examples -## [TCP Ethernet W5x00](TCP_Ethernet) +## [TCP Ethernet W5x00](TCP-Ethernet) ModbusTCP for W5x00 Ethernet library client and server examples (for all Arduino). diff --git a/examples/TCP_ESP/IP_server_AnalogInput/IP_server_AnalogInput.ino b/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino similarity index 100% rename from examples/TCP_ESP/IP_server_AnalogInput/IP_server_AnalogInput.ino rename to examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino diff --git a/examples/TCP_ESP/IP_server_Led/IP_server_Led.ino b/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino similarity index 100% rename from examples/TCP_ESP/IP_server_Led/IP_server_Led.ino rename to examples/TCP-ESP/IP-server-Led/IP-server-Led.ino diff --git a/examples/TCP_ESP/IP_server_SwitchStatus/IP_server_SwitchStatus.ino b/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino similarity index 100% rename from examples/TCP_ESP/IP_server_SwitchStatus/IP_server_SwitchStatus.ino rename to examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino diff --git a/examples/TCP_ESP/README.md b/examples/TCP-ESP/README.md similarity index 100% rename from examples/TCP_ESP/README.md rename to examples/TCP-ESP/README.md diff --git a/examples/TCP_ESP/client/client.ino b/examples/TCP-ESP/client/client.ino similarity index 100% rename from examples/TCP_ESP/client/client.ino rename to examples/TCP-ESP/client/client.ino diff --git a/examples/TCP_ESP/clientPull/clientPull.ino b/examples/TCP-ESP/clientPull/clientPull.ino similarity index 100% rename from examples/TCP_ESP/clientPull/clientPull.ino rename to examples/TCP-ESP/clientPull/clientPull.ino diff --git a/examples/TCP_ESP/clientSync/clientSync.ino b/examples/TCP-ESP/clientSync/clientSync.ino similarity index 100% rename from examples/TCP_ESP/clientSync/clientSync.ino rename to examples/TCP-ESP/clientSync/clientSync.ino diff --git a/examples/TCP_ESP/server/server.ino b/examples/TCP-ESP/server/server.ino similarity index 100% rename from examples/TCP_ESP/server/server.ino rename to examples/TCP-ESP/server/server.ino diff --git a/examples/TCP_Ethernet/README.md b/examples/TCP-Ethernet/README.md similarity index 100% rename from examples/TCP_Ethernet/README.md rename to examples/TCP-Ethernet/README.md diff --git a/examples/TCP_Ethernet/client/client.ino b/examples/TCP-Ethernet/client/client.ino similarity index 100% rename from examples/TCP_Ethernet/client/client.ino rename to examples/TCP-Ethernet/client/client.ino diff --git a/examples/TCP_Ethernet/server/server.ino b/examples/TCP-Ethernet/server/server.ino similarity index 100% rename from examples/TCP_Ethernet/server/server.ino rename to examples/TCP-Ethernet/server/server.ino From 8ee89359867fd7abef37d8173e243f01372b27af Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 11 Mar 2022 14:11:32 +0500 Subject: [PATCH 259/288] 4.1.0-RC1 --- examples/README.md | 2 +- library.properties | 2 +- src/ModbusSettings.h | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index 716e793..c398860 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,7 @@ Examples of using callback functions. Modbus file operations examples. -## [Basic Modbus RTU to Modbus TCP bridge](Bridge) +## [Modbus RTU to Modbus TCP bridge](bridge) Very basic example of accessing ModbusRTU slave device connected to ESP8266/ESP32 via ModbusTCP server. diff --git a/library.properties b/library.properties index bc98ad3..50eade5 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=4.0.0 +version=4.1.0-RC1 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus Library for Arduino. ModbusRTU, ModbusTCP and ModbusTCP Security diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index de60a76..3fcbb1e 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -118,6 +118,12 @@ Specified in chars. That is 1 is means to add delay enough to send 1 char at cur #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 From 7b4b42ab043ecbce630f90a4892c791cb8fb628e Mon Sep 17 00:00:00 2001 From: mako777 <24919651+mako777@users.noreply.github.com> Date: Sun, 13 Mar 2022 12:25:27 +0100 Subject: [PATCH 260/288] Correct indentation (#190) Compilation warning: 265 | continue; --- src/ModbusTCPTemplate.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index d761bbc..1a8d9dc 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -262,7 +262,7 @@ void ModbusTCPTemplate::task() { 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; + continue; } _len = __swap_16(_MBAP.length); _len--; // Do not count with last byte from MBAP @@ -568,4 +568,4 @@ ModbusTCPTemplate::~ModbusTCPTemplate() { delete tcpclient[i]; tcpclient[i] = nullptr; } -} \ No newline at end of file +} From 861f0d9172f60347b0549704b909d77f12bd1992 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 27 Mar 2022 08:03:05 +0500 Subject: [PATCH 261/288] ModbusTCP: Fix Raw callback processing --- src/ModbusSettings.h | 2 +- src/ModbusTCPTemplate.h | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 3fcbb1e..c5b1d54 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -82,7 +82,7 @@ ESP32 only. Outgoing connection attempt timeout #define MODBUSIP_UNIQUE_CLIENTS #define MODBUSIP_MAX_READMS 100 #define MODBUSIP_FULL -#define MODBUSIP_DEBUG +//#define MODBUSIP_DEBUG /* Allows to use DNS names as target Otherwise IP addresses only must be used diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 1a8d9dc..5b436dc 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -64,7 +64,7 @@ class ModbusTCPTemplate : public Modbus { #else DArray _trans; #endif - int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. + int16_t transactionId = 1; // Last started transaction. Increments on unsuccessful transaction start too. int8_t n = -1; bool autoConnectMode = false; uint16_t serverPort = 0; @@ -115,6 +115,7 @@ class ModbusTCPTemplate : public Modbus { 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 @@ -371,19 +372,24 @@ uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg #endif if (!ip) return 0; - p = getSlave(ip); + if (tcpserver) { + p = getMaster(ip); + } else { + p = getSlave(ip); + } if (p == -1 || !tcpclient[p]->connected()) { if (!autoConnectMode) goto cleanup; if (!connect(ip)) goto cleanup; } - transactionId++; - if (!transactionId) transactionId = 1; _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; + transactionId++; + if (!transactionId) + transactionId = 1; bool writeResult; { // for sbuf isolation size_t send_len = _len + sizeof(_MBAP.raw); @@ -569,3 +575,12 @@ ModbusTCPTemplate::~ModbusTCPTemplate() { tcpclient[i] = nullptr; } } + +template +uint16_t ModbusTCPTemplate::setTransactionId(uint16_t t) { + transactionId = t; + if (!transactionId) + transactionId = 1; + return transactionId; +} + From b7aeef01d38adfeb738f78ddaeed44e8068ec464 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 10 Apr 2022 18:04:17 +0500 Subject: [PATCH 262/288] ModbusTCP to ModbusRTU example added --- examples/Bridge/README.md | 17 ++- .../TCP-to-RTU-Simulator.ino | 134 ++++++++++++++++++ examples/README.md | 2 +- 3 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino diff --git a/examples/Bridge/README.md b/examples/Bridge/README.md index e03a7c9..9ed49c5 100644 --- a/examples/Bridge/README.md +++ b/examples/Bridge/README.md @@ -1,17 +1,21 @@ # Bridge functions -## Basic +## [Basic](basic/basic.ino) Basic 'Bridge'. Indeed this sample pulling data from Modbus Server and stores it to local registers. Local registers can be accessed via Modbus Client instance that running aside. -## True +## [ModbusRTU to ModbusTCP bridge](true/true.ino) Fullfunctional ModbusRTU to ModbusTCP bridge. -## MultipeServerID +## [Multiple Server ID](MultipeServerID/MultipeServerID.ino) Respond for multiple ModbusRTU IDs from single device +## [ModbusTCP to Modbus RTU Simulator](TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino) + +Fullfunctional ModbusTCP to ModbusRTU bridge with on-device ModbusRTU simulator + ```c uint16_t rawRequest(id_ip, uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); uint16_t rawResponce(id_ip, uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); @@ -22,7 +26,12 @@ uint16_t errorResponce(id_ip, Modbus::FunctionCode fn, Modbus::ResultCode excode - `len` Byte count to send - `unit` UnitId (ModbusTCP/TLS only) - `fn` function code in responce -- `excode` Exceprion code in responce +- `excode` Exception code in responce + +```c +uint16_t setTransactionId(uint16_t id); +``` +- `id` Value to replace transaction id sequence (ModbusTCP/TLS only) ```c union frame_arg_t { diff --git a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino new file mode 100644 index 0000000..5b94947 --- /dev/null +++ b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -0,0 +1,134 @@ +/* + ModbusRTU ESP8266/ESP32 + ModbusTCP to ModbusRTU bridge with on-device ModbusRTU simulator +*/ +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include +#include +//#include +//SoftwareSerial S(13, 15); +#include +#define BSIZE 1024 +uint8_t buf1[BSIZE]; +uint8_t buf2[BSIZE]; +StreamBuf S1(buf1, BSIZE); +StreamBuf S2(buf2, BSIZE); +DuplexBuf P1(&S1, &S2); +DuplexBuf P2(&S2, &S1); +ModbusRTU sym; + +int DE_RE = 2; + +ModbusRTU rtu; +ModbusTCP tcp; + +IPAddress srcIp; + + +uint16_t transRunning = 0; // Currently executed ModbusTCP transaction +uint8_t slaveRunning = 0; // Current request slave + +bool cbTcpTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place + tcp.disconnect(tcp.eventSource()); // Close connection + } + return true; +} + +bool cbRtuTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + return true; +} + + +// Callback receives raw data +Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + + Serial.print("TCP IP: "); + Serial.print(IPAddress(src->ipaddr)); + Serial.printf(" Fn: %02X, len: %d \n\r", data[0], len); + + if (transRunning) { // Note that we can't process new requests from TCP-side while waiting for responce from RTU-side. + tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + tcp.errorResponce(src->ipaddr, (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); + return Modbus::EX_SLAVE_DEVICE_BUSY; + } + + rtu.rawRequest(slaveRunning, data, len, cbRtuTrans); + + if (src->unitId) { + tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + + //uint16_t succeed = tcp.rawResponce(src->ipaddr, data, len, slaveRunning); + + tcp.errorResponce(src->ipaddr, (Modbus::FunctionCode)data[0], Modbus::EX_ACKNOWLEDGE); + return Modbus::EX_ACKNOWLEDGE; + } + + srcIp = src->ipaddr; + + slaveRunning = src->slaveId; + + transRunning = src->transactionId; + + return Modbus::EX_SUCCESS; + +} + + +// Callback receives raw data from ModbusTCP and sends it on behalf of slave (slaveRunning) to master +Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + uint16_t succeed = tcp.rawResponce(srcIp, data, len, slaveRunning); + if (!succeed){ + Serial.print("fail"); + } + Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); + Serial.print("Response TCP IP: "); + Serial.println(srcIp); + + transRunning = 0; + slaveRunning = 0; + return Modbus::EX_PASSTHROUGH; +} + + +void setup() { + Serial.begin(115000); + WiFi.begin("E2", "*****"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + tcp.server(); // Initialize ModbusTCP to pracess as server + tcp.onRaw(cbTcpRaw); // Assign raw data processing callback + + //S.begin(19200, SWSERIAL_8E1); + //rtu.begin(&S, DE_RE); // Specify RE_DE control pin + sym.begin((Stream*)&P2); + sym.slave(1); + sym.addHreg(1, 100); + rtu.begin((Stream*)&P1); // Specify RE_DE control pin + rtu.master(); // Initialize ModbusRTU as master + rtu.onRaw(cbRtuRaw); // Assign raw data processing callback +} + +void loop() { + sym.task(); + rtu.task(); + tcp.task(); + yield(); +} \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index c398860..b779609 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,7 @@ Examples of using callback functions. Modbus file operations examples. -## [Modbus RTU to Modbus TCP bridge](bridge) +## [ModbusRTU to ModbusTCP bridge and related functions](bridge) Very basic example of accessing ModbusRTU slave device connected to ESP8266/ESP32 via ModbusTCP server. From 6600ea5187ab657be450e975fff29fbacefc28eb Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 17 Apr 2022 08:50:32 +0500 Subject: [PATCH 263/288] 4.1.0 release - Fix TransactionId - Misc documentation fixes --- README.md | 39 ++++++++++++++++++++++----------------- examples/Bridge/README.md | 2 +- examples/README.md | 2 +- library.properties | 2 +- src/ModbusTCPTemplate.h | 6 +++--- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index c9378fd..6d5cf56 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ For more information about Modbus see: ## Last Changes ```diff -// 4.1.0-RC1 +// 4.1.0 + API: Raw Modbus frame processing functionality + ModbusRTU: Precise inter-frame interval control + Examples: True ModbusRTU to ModbusTCP Server bridge @@ -60,7 +60,28 @@ For more information about Modbus see: + ModbusRTU: Add separate RE/DE pins control optional feature + API: Drop support of Ethernet library v1 + Examples: Teknic ClearCore ArduinoWrapper examples added ++ Examples: ModbusTCP to ModbusRTU example added + ModbusRTU: Flush extra delay optional feature +// 4.0.0 ++ Support of all Arduino boards ++ ModbusTLS: ESP8266 Client/Server and ESP32 Client ++ ModbusTCP: ModbusEthernet - WizNet W5x00, ENC28J60 Ethernet library support ++ 0x14 - Read File Records function ++ 0x15 - Write File Records function ++ Examples: FW update over Modbus fullfunctional example ++ 0x16 - Write Mask Register function+ Test: 0x16 ++ 0x17 - Read/Write Registers function ++ ModbusRTU: ESP32 SoftwareSerial support ++ Build with no STL dependency (switchable) ++ API: ModbusIP => ModbusTCP ++ API: Access control callback for individual Modbus function ++ API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) ++ Lot of code refacting and small fixes +``` + +## Roadmap + +```diff // 4.2.0-DEV - API: Alternative CRC calulation (reduced memory footprint) - ModbusRTU: Static buffer allocation @@ -82,23 +103,7 @@ For more information about Modbus see: - API: Extend API to allow custom Modbus commands - Examples: Basic file operations - Examples: Revising -// 4.0.0 -+ Support of all Arduino boards -+ ModbusTLS: ESP8266 Client/Server and ESP32 Client -+ ModbusTCP: ModbusEthernet - WizNet W5x00, ENC28J60 Ethernet library support -+ 0x14 - Read File Records function -+ 0x15 - Write File Records function -+ Examples: FW update over Modbus fullfunctional example -+ 0x16 - Write Mask Register function+ Test: 0x16 -+ 0x17 - Read/Write Registers function -+ ModbusRTU: ESP32 SoftwareSerial support -+ Build with no STL dependency (switchable) -+ API: ModbusIP => ModbusTCP -+ API: Access control callback for individual Modbus function -+ API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) -+ Lot of code refacting and small fixes ``` - ## Contributions https://github.com/emelianov/modbus-esp8266 diff --git a/examples/Bridge/README.md b/examples/Bridge/README.md index 9ed49c5..ad763d3 100644 --- a/examples/Bridge/README.md +++ b/examples/Bridge/README.md @@ -8,7 +8,7 @@ Basic 'Bridge'. Indeed this sample pulling data from Modbus Server and stores it Fullfunctional ModbusRTU to ModbusTCP bridge. -## [Multiple Server ID](MultipeServerID/MultipeServerID.ino) +## [Multiple Server ID](MultipleServerID/MultipleServerID.ino) Respond for multiple ModbusRTU IDs from single device diff --git a/examples/README.md b/examples/README.md index b779609..c82ba51 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,7 @@ Examples of using callback functions. Modbus file operations examples. -## [ModbusRTU to ModbusTCP bridge and related functions](bridge) +## [ModbusRTU to ModbusTCP bridge and related functions](Bridge) Very basic example of accessing ModbusRTU slave device connected to ESP8266/ESP32 via ModbusTCP server. diff --git a/library.properties b/library.properties index 50eade5..765f44a 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=modbus-esp8266 -version=4.1.0-RC1 +version=4.1.0 author=Andre Sarmento Barbosa, Alexander Emelianov maintainer=Alexander Emelianov sentence=Modbus Library for Arduino. ModbusRTU, ModbusTCP and ModbusTCP Security diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 5b436dc..c7aacb0 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -387,9 +387,6 @@ uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg _MBAP.protocolId = __swap_16(0); _MBAP.length = __swap_16(_len+1); //_len+1 for last byte from MBAP _MBAP.unitId = unit; - transactionId++; - if (!transactionId) - transactionId = 1; bool writeResult; { // for sbuf isolation size_t send_len = _len + sizeof(_MBAP.raw); @@ -413,6 +410,9 @@ uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg _frame = nullptr; } result = transactionId; + transactionId++; + if (!transactionId) + transactionId = 1; cleanup: free(_frame); _frame = nullptr; From 2f14138d34542d91e736de62d85d7aa85e700dfe Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 9 May 2022 07:36:29 +0500 Subject: [PATCH 264/288] Memory leak, examples and API fixes - Fix memory leak (issue #208) - Extend cbEnable/cbDisable API - Fix TCP-to-RTU example --- CMakeLists.txt | 6 ++++++ examples/Bridge/README.md | 9 +++++---- .../TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino | 10 +++++----- src/Modbus.cpp | 8 +++++--- src/Modbus.h | 4 ++-- src/ModbusTCPTemplate.h | 7 +++++-- 6 files changed, 28 insertions(+), 16 deletions(-) create mode 100755 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..01b31a6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_ADD_INCLUDEDIRS src) +set(COMPONENT_SRCS "src/Modbus.cpp" "src/ModbusRTU.cpp") +set(COMPONENT_PRIV_REQUIRES arduino) +register_component() + + diff --git a/examples/Bridge/README.md b/examples/Bridge/README.md index ad763d3..598fe48 100644 --- a/examples/Bridge/README.md +++ b/examples/Bridge/README.md @@ -42,9 +42,9 @@ struct frame_arg_t { uint8_t slaveId; // For ModbusTCP/TLS struct { - uint8_t unitId; // UnitId as passed in Modbus header + uint8_t unitId; // UnitId as passed in MBAP header uint32_t ipaddr; // IP address from which frame is received - uint16_t transactionId; // TransactionId as passed im Modbus header + uint16_t transactionId; // TransactionId as passed in MBAP header }; }; }; @@ -52,11 +52,12 @@ typedef std::function cbRaw; // Callback f typedef ResultCode (*cbRaw)(uint8_t* frame, uint8 len, void* data); // Callback function Type bool onRaw(cbRaw cb = nullptr); ``` -- `frame` Modbus payload frame with stripped MBAP, slaveid and crc +- `frame` Modbus payload frame with stripped MBAP/slaveid and crc - `len` frame size in bytes - `data` Pointer to frame_arg_t filled with frame header information + *Returns:* -- If a special error code `Modbus::EX_PASSTHROU` returned frame will be processed normally +- If a special error code `Modbus::EX_PASSTHROUGH` returned frame will be processed normally - If a special error code `Modbus::EX_FORCE_PROCESS` returned frame will be processed even if addressed to another Modbus unit - Any other return code disables normal frame processing. Only transactional callback will be executed (if any and transaction data is correct) The callback is executed only on Modbus frame with valid header and CRC. \ No newline at end of file diff --git a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino index 5b94947..bb31ded 100644 --- a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino +++ b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -58,24 +58,24 @@ Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { if (transRunning) { // Note that we can't process new requests from TCP-side while waiting for responce from RTU-side. tcp.setTransactionId(transRunning); // Set transaction id as per incoming request - tcp.errorResponce(src->ipaddr, (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); + tcp.errorResponce(IPAddress((src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); return Modbus::EX_SLAVE_DEVICE_BUSY; } - rtu.rawRequest(slaveRunning, data, len, cbRtuTrans); + rtu.rawRequest(src->unitId, data, len, cbRtuTrans); if (src->unitId) { tcp.setTransactionId(transRunning); // Set transaction id as per incoming request //uint16_t succeed = tcp.rawResponce(src->ipaddr, data, len, slaveRunning); - tcp.errorResponce(src->ipaddr, (Modbus::FunctionCode)data[0], Modbus::EX_ACKNOWLEDGE); + tcp.errorResponce(IPAddress(src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_ACKNOWLEDGE); return Modbus::EX_ACKNOWLEDGE; } - srcIp = src->ipaddr; + srcIp = IPAddress(src->ipaddr); - slaveRunning = src->slaveId; + slaveRunning = src->unitId; transRunning = src->transactionId; diff --git a/src/Modbus.cpp b/src/Modbus.cpp index e724c0b..c27eba2 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -824,11 +824,13 @@ void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, } } -void Modbus::cbEnable(bool state) { +bool Modbus::cbEnable(const bool state) { + const bool old_state = state; cbEnabled = state; + return old_state; } -void Modbus::cbDisable() { - cbEnable(false); +bool Modbus::cbDisable() { + return cbEnable(false); } Modbus::~Modbus() { free(_frame); diff --git a/src/Modbus.h b/src/Modbus.h index 018cc54..e4f8064 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -193,8 +193,8 @@ class Modbus { ~Modbus(); - void cbEnable(bool state = true); - void cbDisable(); + bool cbEnable(const bool state = true); + bool cbDisable(); private: ResultCode readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index c7aacb0..5b189d6 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -219,8 +219,10 @@ void ModbusTCPTemplate::task() { Serial.println("IP: Accepted"); #endif CLIENT* currentClient = new CLIENT(c); - if (!currentClient || !currentClient->connected()) + if (!currentClient || !currentClient->connected()) { + delete currentClient; continue; + } #if defined(MODBUSRTU_DEBUG) Serial.println("IP: Connected"); #endif @@ -544,7 +546,7 @@ bool ModbusTCPTemplate::disconnect(IPAddress ip) { return false; int8_t p = getSlave(ip); if (p != -1) { - tcpclient[p]->stop(); + //tcpclient[p]->stop(); delete tcpclient[p]; tcpclient[p] = nullptr; return true; @@ -565,6 +567,7 @@ void ModbusTCPTemplate::dropTransactions() { template ModbusTCPTemplate::~ModbusTCPTemplate() { free(_frame); + _frame = nullptr; dropTransactions(); cleanupConnections(); cleanupTransactions(); From 69f3d071579a8845c87b61c349efd2001418b3c2 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 22 May 2022 08:18:50 +0500 Subject: [PATCH 265/288] Replace magic number with constants - MODBUS_MAX_WORDS/BITS - NULLREG - Alternative API read/write functions implemented --- README.md | 5 + src/Modbus.h | 3 +- src/ModbusAPI.h | 203 ++++++++++++++++++++++++++-------------- src/ModbusRTU.h | 2 +- src/ModbusSettings.h | 1 + src/ModbusTCPTemplate.h | 3 +- 6 files changed, 146 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 6d5cf56..9e1b4cb 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,11 @@ For more information about Modbus see: ## Last Changes ```diff +// 4.1.1 ++ ModbusTCP: Fix potential memory leak ++ API: cbEnable/cbDisable functionality extended ++ ESP-IDF: CMakeList.txt added ++ Examples: TCP-to-RTU fixed // 4.1.0 + API: Raw Modbus frame processing functionality + ModbusRTU: Precise inter-frame interval control diff --git a/src/Modbus.h b/src/Modbus.h index e4f8064..93f4220 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -22,6 +22,7 @@ static inline uint16_t __swap_16(uint16_t num) { return (num >> 8) | (num << 8); #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) @@ -40,7 +41,7 @@ typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function #endif struct TAddress { - enum RegType {COIL, ISTS, IREG, HREG}; + enum RegType {COIL, ISTS, IREG, HREG, NONE = 0xFF}; RegType type; uint16_t address; bool operator==(const TAddress &obj) const { // TAddress == TAddress diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index ff65f8d..d0e9322 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -10,7 +10,11 @@ template class ModbusAPI : public T { public: -/* // New API + // Alternative API + template + uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); template uint16_t write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); template @@ -19,64 +23,60 @@ class ModbusAPI : public T { uint16_t write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); template uint16_t write(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - template - uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - template - uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - +/* template uint16_t push(TYPEID id, TAddress to, TAddress from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); template uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); */ - // Legacy API + // 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 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 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 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 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 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 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 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); + 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 uint16_t writeCoil(TYPEID id, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); @@ -117,7 +117,7 @@ class ModbusAPI : public T { template uint16_t pushIregToHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); - template + template 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 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); @@ -159,8 +159,8 @@ uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_ 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, 0x07D0, bool) -IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, 0x007D, uint16_t) +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 \ @@ -170,10 +170,10 @@ uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_ this->readSlave(offset, numregs, Modbus::FUNC); \ return this->send(ip, REG(offset), cb, unit, (uint8_t*)value); \ } -IMPLEMENT_READREGS(readCoil, COIL, FC_READ_COILS, 0x07D0, bool) -IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, 0x007D, uint16_t) -IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0, bool) -IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, 0x007D, uint16_t) +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); @@ -189,12 +189,12 @@ uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t num this->readSlave(from, numregs, Modbus::FUNC); \ return this->send(ip, REG(to), cb, unit); \ } -IMPLEMENT_PULL(pullCoil, COIL, FC_READ_COILS, 0x07D0) -IMPLEMENT_PULL(pullIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0) -IMPLEMENT_PULL(pullHreg, HREG, FC_READ_REGS, 0x007D) -IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, 0x007D) -IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, 0x007D) -IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, 0x07D0) +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 \ @@ -205,10 +205,77 @@ uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t to, uint16_t from, uint16_t num this->FINT(REG(from), to, numregs, Modbus::FUNC); \ return this->send(ip, REG(from), cb, unit); \ } -IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, 0x7D0, writeSlaveBits) -IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, 0x007D, writeSlaveWords) -IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, 0x007D, writeSlaveWords) -IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, 0x07D0, writeSlaveBits) +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 +template +uint16_t ModbusAPI::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 +template +uint16_t ModbusAPI::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 +template +uint16_t ModbusAPI::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 +template +uint16_t ModbusAPI::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; + } +} +uint16_t ModbusAPI::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 +template +uint16_t ModbusAPI::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 \ bool ModbusAPI::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { @@ -341,16 +408,16 @@ bool ModbusAPI::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numreg template \ template \ uint16_t ModbusAPI::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { - if (startRec > 0x270F) return 0; + 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, HREG(0), cb, unit, data); // HREG(0) - just dummy value + return this->send(slaveId, NULLREG, cb, unit, data); }; template \ template \ uint16_t ModbusAPI::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { - if (startRec > 0x270F) return 0; + 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, HREG(0), cb, unit); // HREG(0) - just dummy value + return this->send(slaveId, NULLREG, cb, unit); }; template \ template \ @@ -414,7 +481,7 @@ uint16_t ModbusAPI::rawRequest(TYPEID ip, \ return 0; this->_len = len; memcpy(this->_frame, data, len); - return this->send(ip, HREG(0), cb, unit); + return this->send(ip, NULLREG, cb, unit); }; template @@ -427,12 +494,12 @@ uint16_t ModbusAPI::rawResponce(TYPEID ip, \ return 0; this->_len = len; memcpy(this->_frame, data, len); - return this->send(ip, HREG(0), nullptr, unit, nullptr, false); + return this->send(ip, NULLREG, nullptr, unit, nullptr, false); }; template template uint16_t ModbusAPI::errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit) { this->exceptionResponse(fn, excode); - return this->send(ip, HREG(0), nullptr, unit, nullptr, false); + return this->send(ip, NULLREG, nullptr, unit, nullptr, false); } diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 6691346..49b9312 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -28,7 +28,7 @@ class ModbusRTUTemplate : public Modbus { uint8_t* _data = nullptr; uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); - uint16_t maxRegs = 0x007D; + 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); diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index c5b1d54..13dbd80 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -58,6 +58,7 @@ If defined regisers count will be limited. #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_MAXFRAME 200 diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 5b189d6..728d09e 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -259,6 +259,7 @@ void ModbusTCPTemplate::task() { Serial.print(n); Serial.print(": Bytes available "); Serial.println(tcpclient[n]->available()); +#endif #endif tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP @@ -309,7 +310,7 @@ void ModbusTCPTemplate::task() { 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); // Procass incoming frame as master + masterPDU(_frame, trans->_frame, trans->startreg, trans->data); // Process incoming frame as master } else { _reply = EX_UNEXPECTED_RESPONSE; From e80ecf5d1268399313bbf5e1380399c1498c70e6 Mon Sep 17 00:00:00 2001 From: dukea <23308942+alepiva@users.noreply.github.com> Date: Sat, 25 Jun 2022 15:03:36 +0200 Subject: [PATCH 266/288] Update ESP32-Concurent.ino (#217) typo fix --- examples/RTU/ESP32-Concurent/ESP32-Concurent.ino | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino index bc6f858..2cee07c 100644 --- a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -65,7 +65,7 @@ void setup() { xTaskCreatePinnedToCore( loop2, /* Task function. */ - "Task1", /* name of task. */ + "Task2", /* name of task. */ 10000, /* Stack size of task */ NULL, /* parameter of the task */ 1, /* priority of the task */ @@ -79,9 +79,9 @@ void loop1( void * pvParameters ){ while(true) { delay(10); if (readSync(SLAVE_ID1, REG, REG_NUM, hregs1) == Modbus::EX_SUCCESS) - Serial.println("OK 2"); + Serial.println("OK 1"); else - Serial.println("Error 2"); + Serial.println("Error 1"); } } @@ -98,4 +98,4 @@ void loop2( void * pvParameters ){ void loop() { delay(100); -} \ No newline at end of file +} From b9c5a25d510bacd54915cec66d2a22848ccfc20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E8=89=B2=E5=8F=8C=E8=91=89IsshikiFutaba?= Date: Tue, 28 Jun 2022 09:31:55 +0800 Subject: [PATCH 267/288] Fix typo (#218) --- documentation/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/API.md b/documentation/API.md index 30ecb5b..948d23a 100644 --- a/documentation/API.md +++ b/documentation/API.md @@ -14,7 +14,7 @@ Processing routine. Should be periodically called form loop(). 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 nemregs = 1); +bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); ``` ### Write local reg From 6d98023487199c84fe71ceb322d42da976b44465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E8=89=B2=E5=8F=8C=E8=91=89IsshikiFutaba?= Date: Tue, 28 Jun 2022 09:32:03 +0800 Subject: [PATCH 268/288] Fix typo (#219) --- documentation/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/API.md b/documentation/API.md index 948d23a..1e49a18 100644 --- a/documentation/API.md +++ b/documentation/API.md @@ -203,7 +203,7 @@ Callback generation control. Callback generation is enabled by default. *Has no ```c void onConnect(cbModbusConnect cb); -void onDisonnect(cbModbusConnect cb); +void onDisconnect(cbModbusConnect cb); ``` *Modbus TCP Server* Assign callback function on new incoming connection event. From 336d56ca3f444e3508272a48c9b3201b81694c39 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Thu, 30 Jun 2022 08:07:41 +0500 Subject: [PATCH 269/288] ModbusEthernet: Portenta and ESP32 boards build fix --- src/ModbusAPI.h | 2 ++ src/ModbusEthernet.h | 20 ++++++++++++++++++-- src/ModbusTCPTemplate.h | 1 - 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index d0e9322..600a55a 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -256,6 +256,8 @@ uint16_t ModbusAPI::write(TYPEID id, TAddress reg, bool value, cbTransaction return 0; } } +template +template uint16_t ModbusAPI::write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { switch (reg.type) { case TAddress::COIL: diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h index 54c02ff..301432f 100644 --- a/src/ModbusEthernet.h +++ b/src/ModbusEthernet.h @@ -10,16 +10,32 @@ #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> { diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 728d09e..80ae2f9 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -259,7 +259,6 @@ void ModbusTCPTemplate::task() { Serial.print(n); Serial.print(": Bytes available "); Serial.println(tcpclient[n]->available()); -#endif #endif tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP From dc1a79ad43a64ed27ec84e129112fdaebca9da88 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Fri, 22 Jul 2022 07:05:37 +0500 Subject: [PATCH 270/288] TCP-to-RTU example: Fix cbTcpRaw --- .../TCP-to-RTU-Simulator.ino | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino index bb31ded..7431c03 100644 --- a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino +++ b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -52,24 +52,24 @@ bool cbRtuTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { auto src = (Modbus::frame_arg_t*) custom; - Serial.print("TCP IP: "); + Serial.print("TCP IP in - "); Serial.print(IPAddress(src->ipaddr)); Serial.printf(" Fn: %02X, len: %d \n\r", data[0], len); if (transRunning) { // Note that we can't process new requests from TCP-side while waiting for responce from RTU-side. - tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + tcp.setTransactionId(src->transactionId); // Set transaction id as per incoming request tcp.errorResponce(IPAddress((src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); return Modbus::EX_SLAVE_DEVICE_BUSY; } rtu.rawRequest(src->unitId, data, len, cbRtuTrans); - if (src->unitId) { - tcp.setTransactionId(transRunning); // Set transaction id as per incoming request - - //uint16_t succeed = tcp.rawResponce(src->ipaddr, data, len, slaveRunning); - + if (!src->unitId) { // If broadcast request (no responce from slave is expected) + tcp.setTransactionId(src->transactionId); // Set transaction id as per incoming request tcp.errorResponce(IPAddress(src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_ACKNOWLEDGE); + + transRunning = 0; + slaveRunning = 0; return Modbus::EX_ACKNOWLEDGE; } @@ -87,10 +87,12 @@ Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { // Callback receives raw data from ModbusTCP and sends it on behalf of slave (slaveRunning) to master Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { auto src = (Modbus::frame_arg_t*) custom; + if (!transRunning) // Unexpected incoming data + return Modbus::EX_PASSTHROUGH; tcp.setTransactionId(transRunning); // Set transaction id as per incoming request uint16_t succeed = tcp.rawResponce(srcIp, data, len, slaveRunning); if (!succeed){ - Serial.print("fail"); + Serial.print("TCP IP out - failed"); } Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); Serial.print("Response TCP IP: "); From e0aa79b686f636b2d43c104fe3ebd27a63dfe96c Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 23 Jul 2022 06:39:44 +0500 Subject: [PATCH 271/288] TCP-to-RTU example: Fix timeout processing --- examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino index 7431c03..fce0a38 100644 --- a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino +++ b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -37,6 +37,8 @@ bool cbTcpTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place tcp.disconnect(tcp.eventSource()); // Close connection + transRunning = 0; + slaveRunning = 0; } return true; } From b79129fee52756609e375e4f3eb376e9a1f371d9 Mon Sep 17 00:00:00 2001 From: Alex Barcelo Date: Wed, 12 Oct 2022 14:08:21 +0200 Subject: [PATCH 272/288] Minor grammar / typos (#249) --- examples/RTU/README.MD | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index 6f4d1b7..4117541 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,6 +1,6 @@ -This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally there is [example of master](ESP32-Concurent) device for multithread usage with ESP32. +This example introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally there is [example of master](ESP32-Concurent) device for multithread usage with ESP32. -## [Concurent thread-safe access to Modbus object](ESP32-Concurent) +## [Concurrent thread-safe access to Modbus object](ESP32-Concurent) ## [Simple ModbusRTU master](master) @@ -20,10 +20,10 @@ bool begin(Stream* port); - `txPin` RX/TX control pin. Not assigned (assume auto RX/TX) by default - `direct` Direct (true, default) or inverse (false) RX/TX pin control. -Assing Serial port. txPin controls transmit enable for MAX-485. +Assign Serial port. txPin controls transmit enable for MAX-485. ```c -void setBaudrte(uint32 baud); +void setBaudrate(uint32 baud); ``` - `baud` New baudrate. @@ -33,25 +33,25 @@ Set or override Serial baudrate. Must be called after .begin() for Non-ESP devic ```c void client(); void server(uint8_t slaveId); -void slave(); // Depricated -void master(uint8_t slaveId); // Depricated +void slave(); // Deprecated +void master(uint8_t slaveId); // Deprecated ``` - `slaveId` Modbus slave id to associate to. -Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictible. +Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictable. ```c uint8_t client(); -uint8_t slave(); // Depricated +uint8_t slave(); // Deprecated ``` - Slave mode: Returns configured slave id. -- Master mode: Returns slave id for active request or 0 if no request in-progress. +- Master mode: Returns slave id for active request or 0 if no request in progress. # Modbus Library for Arduino ### ModbusRTU, ModbusTCP and ModbusTCP Security (c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) -The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. \ No newline at end of file +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. From d79deb2f3aae1b07b3c5c0614ae4d5ce3ae3aa79 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 1 Jan 2023 14:43:27 +0500 Subject: [PATCH 273/288] Respond EX_ILLEGAL_ADDRESS on non-existent reg --- src/Modbus.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index c27eba2..526e89b 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -2,7 +2,7 @@ Modbus Library for Arduino Core functions Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2023 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" @@ -489,7 +489,7 @@ void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numre 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_VALUE; + return EX_ILLEGAL_ADDRESS; //Check Address //Check only startreg. Is this correct? //When I check all registers in range I got errors in ScadaBR @@ -498,11 +498,11 @@ Modbus::ResultCode Modbus::readBits(TAddress startreg, uint16_t numregs, Functio #if defined(MODBUS_STRICT_REG) for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) if (!searchRegister(startreg + k)) - return EX_ILLEGAL_VALUE; + return EX_ILLEGAL_ADDRESS; } #else if (!searchRegister(startreg)) - return EX_ILLEGAL_VALUE; + return EX_ILLEGAL_ADDRESS; #endif free(_frame); //Determine the message length = function type, byte count and @@ -523,11 +523,11 @@ Modbus::ResultCode Modbus::readBits(TAddress startreg, uint16_t numregs, Functio 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_VALUE; + 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_VALUE; + return EX_ILLEGAL_ADDRESS; } #else if (!searchRegister(startreg)) From 299666307a052df52f757f3131e292c2513bb57f Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Tue, 3 Jan 2023 08:36:20 +0500 Subject: [PATCH 274/288] README update --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e1b4cb..e419e69 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ For detailes on the library usage visit [documentation](documentation) section. * [Callbacks](examples/Callback) driven design * Real life complex examples: * [ESP8266/ESP32 firmware update over Modbus](examples/Files) - * [ModbusRTU to ModbusTCP bridge](examples/bridge) + * [ModbusRTU to ModbusTCP bridge](examples/Bridge) ## Notes @@ -48,6 +48,7 @@ For more information about Modbus see: ```diff // 4.1.1 ++ Protocol: Fix wrong error code responce on non-existent register + ModbusTCP: Fix potential memory leak + API: cbEnable/cbDisable functionality extended + ESP-IDF: CMakeList.txt added @@ -87,7 +88,7 @@ For more information about Modbus see: ## Roadmap ```diff -// 4.2.0-DEV +// 4.2.0 - API: Alternative CRC calulation (reduced memory footprint) - ModbusRTU: Static buffer allocation - Test: Frame accuracy to specefication @@ -98,7 +99,7 @@ For more information about Modbus see: - Free global registers and callbacks on remove last Modbus instance - Test: push/pull functions - ModbusTCP: Refactor connect by dns name (using native implementation for ESP32 etc) -// 4.3.0-DEV +// 4.3.0 - ModbusTLS: ESP32 Server - Test: TLS ESP32 Server - Test: TLS ESP32 Client From 4383e1ab587adb5ca6dc363f4fadeb7cc63087a0 Mon Sep 17 00:00:00 2001 From: rob040 <72281835+rob040@users.noreply.github.com> Date: Fri, 31 Mar 2023 18:21:09 +0200 Subject: [PATCH 275/288] Fix deadlinks in example docs #272 (#275) --- examples/RTU/README.MD | 8 ++++---- examples/TCP-ESP/README.md | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index 4117541..efa78bf 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -1,12 +1,12 @@ This example introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally there is [example of master](ESP32-Concurent) device for multithread usage with ESP32. -## [Concurrent thread-safe access to Modbus object](ESP32-Concurent) +## [Concurrent thread-safe access to Modbus object](ESP32-Concurent/ESP32-Concurent.ino) -## [Simple ModbusRTU master](master) +## [Simple ModbusRTU master](master/master.ino) -## [Simple ModbusRTU slave](slave) +## [Simple ModbusRTU slave](slave/slave.ino) -## [Sync ModbusRTU master](masterSync) +## [Sync ModbusRTU master](masterSync/masterSync.ino) ## Modbus RTU Specific API diff --git a/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md index 429cc83..70ae29f 100644 --- a/examples/TCP-ESP/README.md +++ b/examples/TCP-ESP/README.md @@ -1,10 +1,10 @@ # ESP8266/ESP32 TCP Examples -## [Basic client](client.ino) +## [Basic client](client/client.ino) -## [Client with blocking read operation](clientSync.ino) +## [Client with blocking read operation](clientSync/clientSync.ino) -## [Server](server.ino) +## [Server](server/server.ino) ### API From 644c3245cc2ef3f2e515c4fa538e9c2e3573b232 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 1 Apr 2023 07:17:43 +0500 Subject: [PATCH 276/288] Fix Slave answer with malformed packet #268 --- src/ModbusTCPTemplate.h | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 80ae2f9..f33b4cd 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -254,7 +254,7 @@ void ModbusTCPTemplate::task() { for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { if (!tcpclient[n]) continue; if (!tcpclient[n]->connected()) continue; - while (millis() - taskStart < MODBUSIP_MAX_READMS && (size_t)tcpclient[n]->available() > sizeof(_MBAP)) { + while ((size_t)tcpclient[n]->available() > sizeof(_MBAP) && millis() - taskStart < MODBUSIP_MAX_READMS) { #if defined(MODBUSIP_DEBUG) Serial.print(n); Serial.print(": Bytes available "); @@ -268,20 +268,28 @@ void ModbusTCPTemplate::task() { continue; } _len = __swap_16(_MBAP.length); + if (_len < MODBUSIP_MINFRAME) { // Length is over MODBUSIP_MAXFRAME + 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 - exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); + Modbus::FunctionCode fc = tcpclient[n]->read(); _len--; // Subtract for read byte - for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of packet + 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) { - exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); - for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop packet + Modbus::FunctionCode fc = 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 From 4ae0f7d77cbf869408dddbd0ec0061adbf8478ab Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 1 Apr 2023 08:52:21 +0500 Subject: [PATCH 277/288] STM32 Ethernet and simular workaround #278 --- src/ModbusSettings.h | 9 +++++++++ src/ModbusTCPTemplate.h | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 13dbd80..6f52ebc 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -61,6 +61,7 @@ If defined regisers count will be limited. #define MODBUS_MAX_FILES 0x270F #define MODBUSTCP_PORT 502 #define MODBUSTLS_PORT 802 +#define MODBUSIP_MINFRAME 2 #define MODBUSIP_MAXFRAME 200 /* @@ -82,6 +83,14 @@ ESP32 only. Outgoing connection attempt timeout #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 /* diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index f33b4cd..5b532bc 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -214,7 +214,11 @@ void ModbusTCPTemplate::task() { 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 @@ -244,7 +248,11 @@ void ModbusTCPTemplate::task() { 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 @@ -268,14 +276,15 @@ void ModbusTCPTemplate::task() { continue; } _len = __swap_16(_MBAP.length); - if (_len < MODBUSIP_MINFRAME) { // Length is over MODBUSIP_MAXFRAME + 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 = tcpclient[n]->read(); + 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(); @@ -285,7 +294,7 @@ void ModbusTCPTemplate::task() { free(_frame); _frame = (uint8_t*) malloc(_len); if (!_frame) { - Modbus::FunctionCode fc = tcpclient[n]->read(); + 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(); @@ -293,7 +302,7 @@ void ModbusTCPTemplate::task() { } else { if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame - exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); + exceptionResponse((Modbus::FunctionCode)_frame[0], EX_ILLEGAL_VALUE); //while (tcpclient[n]->available()) // Drop all incoming (if any) // tcpclient[n]->read(); } From 0bc538d1eb43b2416733154c2fbc546c15b68858 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 1 Apr 2023 09:07:05 +0500 Subject: [PATCH 278/288] txPin to txEnablePin, direct to txEnableDirect #266 --- documentation/API.md | 6 +++--- examples/RTU/README.MD | 10 +++++----- src/ModbusRTU.cpp | 32 ++++++++++++++++---------------- src/ModbusRTU.h | 30 +++++++++++++++--------------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/documentation/API.md b/documentation/API.md index 1e49a18..a69d94b 100644 --- a/documentation/API.md +++ b/documentation/API.md @@ -47,12 +47,12 @@ bool removeIreg(uint16_t offset, uint16_t numregs = 1); ### Modbus RTU Specific API ```c -bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); -bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true); +bool begin(SoftwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); +bool begin(HardwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); bool begin(Stream* port); ``` -Assing Serial port. txPin controls transmit enable for MAX-485. Pass direct=false if txPin uses inverse logic. +Assing Serial port. txEnablePin controls transmit enable for MAX-485. Pass txEnableDirect=false if txEnablePin uses inverse logic. ```c void setBaudrte(uint32 baud); diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD index efa78bf..1f4e4b9 100644 --- a/examples/RTU/README.MD +++ b/examples/RTU/README.MD @@ -11,16 +11,16 @@ This example introduces how to use the library for ModbusRTU (typicaly over RS-4 ## Modbus RTU Specific API ```c -bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); -bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true); +bool begin(SoftwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); +bool begin(HardwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); bool begin(Stream* port); ``` - `port` Pointer to Serial port -- `txPin` RX/TX control pin. Not assigned (assume auto RX/TX) by default -- `direct` Direct (true, default) or inverse (false) RX/TX pin control. +- `txEnablePin` RX/TX control pin. Not assigned (assume auto RX/TX) by default +- `txEnableDirect` Direct (true, default) or inverse (false) RX/TX pin control. -Assign Serial port. txPin controls transmit enable for MAX-485. +Assign Serial port. txEnablePin controls transmit enable for MAX-485. ```c void setBaudrate(uint32 baud); diff --git a/src/ModbusRTU.cpp b/src/ModbusRTU.cpp index 1f2d495..a00d609 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -109,17 +109,17 @@ void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) { _t = t_us; } -bool ModbusRTUTemplate::begin(Stream* port, int16_t txPin, bool direct) { +bool ModbusRTUTemplate::begin(Stream* port, int16_t txEnablePin, bool txEnableDirect) { _port = port; _t = 1750UL; #if defined(MODBUSRTU_FLUSH_DELAY) _t1 = charSendTime(0); #endif - if (txPin >= 0) { - _txPin = txPin; - _direct = direct; - pinMode(_txPin, OUTPUT); - digitalWrite(_txPin, _direct?LOW:HIGH); + if (txEnablePin >= 0) { + _txEnablePin = txEnablePin; + _direct = txEnableDirect; + pinMode(_txEnablePin, OUTPUT); + digitalWrite(_txEnablePin, _direct?LOW:HIGH); } return true; } @@ -134,9 +134,9 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { Serial.println(); #endif #if defined(MODBUSRTU_REDE) - if (_txPin >= 0 || _rxPin >= 0) { - if (_txPin >= 0) - digitalWrite(_txPin, _direct?HIGH:LOW); + if (_txEnablePin >= 0 || _rxPin >= 0) { + if (_txEnablePin >= 0) + digitalWrite(_txEnablePin, _direct?HIGH:LOW); if (_rxPin >= 0) digitalWrite(_rxPin, _direct?HIGH:LOW); #if !defined(ESP32) @@ -144,8 +144,8 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { #endif } #else - if (_txPin >= 0) { - digitalWrite(_txPin, _direct?HIGH:LOW); + if (_txEnablePin >= 0) { + digitalWrite(_txEnablePin, _direct?HIGH:LOW); #if !defined(ESP32) delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); #endif @@ -160,21 +160,21 @@ bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { _port->write(newCrc & 0xFF);//Send CRC _port->flush(); #if defined(MODBUSRTU_REDE) - if (_txPin >= 0 || _rxPin >= 0) { + if (_txEnablePin >= 0 || _rxPin >= 0) { #if defined(MODBUSRTU_FLUSH_DELAY) delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); #endif - if (_txPin >= 0) - digitalWrite(_txPin, _direct?LOW:HIGH); + if (_txEnablePin >= 0) + digitalWrite(_txEnablePin, _direct?LOW:HIGH); if (_rxPin >= 0) digitalWrite(_rxPin, _direct?LOW:HIGH); } #else - if (_txPin >= 0) { + if (_txEnablePin >= 0) { #if defined(MODBUSRTU_FLUSH_DELAY) delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); #endif - digitalWrite(_txPin, _direct?LOW:HIGH); + digitalWrite(_txEnablePin, _direct?LOW:HIGH); } #endif return true; diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index 49b9312..ce54348 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -11,11 +11,11 @@ class ModbusRTUTemplate : public Modbus { protected: Stream* _port; - int16_t _txPin = -1; + int16_t _txEnablePin = -1; #if defined(MODBUSRTU_REDE) int16_t _rxPin = -1; #endif - bool _direct = true; // Transmit control logic (true=direct, false=inverse) + 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 @@ -47,12 +47,12 @@ class ModbusRTUTemplate : public Modbus { void setInterFrameTime(uint32_t t_us); uint32_t charSendTime(uint32_t baud, uint8_t char_bits = 11); template - bool begin(T* port, int16_t txPin = -1, bool direct = true); + bool begin(T* port, int16_t txEnablePin = -1, bool txEnableDirect = true); #if defined(MODBUSRTU_REDE) template - bool begin(T* port, int16_t txPin, int16_t rxPin, bool direct); + bool begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect); #endif - bool begin(Stream* port, int16_t txPin = -1, bool direct = true); + bool begin(Stream* port, int16_t txEnablePin = -1, bool txEnableDirect = true); void task(); void client() { isMaster = true; }; inline void master() {client();} @@ -64,7 +64,7 @@ class ModbusRTUTemplate : public Modbus { }; template -bool ModbusRTUTemplate::begin(T* port, int16_t txPin, bool direct) { +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(); @@ -76,20 +76,20 @@ bool ModbusRTUTemplate::begin(T* port, int16_t txPin, bool direct) { _t1 = charSendTime(baud); #endif _port = port; - if (txPin >= 0) { - _txPin = txPin; - _direct = direct; - pinMode(_txPin, OUTPUT); - digitalWrite(_txPin, _direct?LOW:HIGH); + if (txEnablePin >= 0) { + _txEnablePin = txEnablePin; + _direct = txEnableDirect; + pinMode(_txEnablePin, OUTPUT); + digitalWrite(_txEnablePin, _direct?LOW:HIGH); } return true; } #if defined(MODBUSRTU_REDE) template -bool ModbusRTUTemplate::begin(T* port, int16_t txPin, int16_t rxPin, bool direct) { - begin(port, txPin, direct); - if (rxPin > 0) { - _rxPin = rxPin; +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); } From ea2bd0e506b2c32755ca86edcd2ea95acc1b0855 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Mon, 3 Apr 2023 09:10:19 +0500 Subject: [PATCH 279/288] Uncomment the explicit stop() method call to allow sockets cleanups (#281) This should allow w5xxx-based ethernet to clean up their sockets' states upon disconnection, preventing the server implementation to experience sockets starvation Co-authored-by: Marco Fagiolini --- src/ModbusTCPTemplate.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 5b532bc..0f435d8 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -454,7 +454,7 @@ void ModbusTCPTemplate::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(); + tcpclient[i]->stop(); delete tcpclient[i]; tcpclient[i] = nullptr; if (cbDisconnect && cbEnabled) @@ -563,7 +563,7 @@ bool ModbusTCPTemplate::disconnect(IPAddress ip) { return false; int8_t p = getSlave(ip); if (p != -1) { - //tcpclient[p]->stop(); + tcpclient[p]->stop(); delete tcpclient[p]; tcpclient[p] = nullptr; return true; From df666c6a6b4e355a1b6036adb91ccf1c8e71e85e Mon Sep 17 00:00:00 2001 From: ArekKubacki Date: Sat, 14 Oct 2023 04:28:42 +0200 Subject: [PATCH 280/288] Fixed error: modbus-esp8266/src/Modbus.cpp:318:25: error: comparison is always false due to limited range of data type [-Werror=type-limits] (#292) --- src/Modbus.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 526e89b..b2007a2 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -315,10 +315,10 @@ void Modbus::slavePDU(uint8_t* frame) { 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; - } +// 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) { From 5d7fe2d70957ecab99d38a35df124aa760782489 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sun, 29 Oct 2023 08:51:27 +0500 Subject: [PATCH 281/288] RTU: Add _onRequest on illegal function --- src/Modbus.cpp | 5 ++++- src/Modbus.h | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 526e89b..4d58b07 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -431,7 +431,10 @@ void Modbus::slavePDU(uint8_t* frame) { break; default: - exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + ex = _onRequest(fcode, {frame + 1}); + if (ex != EX_PASSTHROUGH) { + exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + } return; } } diff --git a/src/Modbus.h b/src/Modbus.h index 93f4220..97fd340 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -153,6 +153,7 @@ class Modbus { uint16_t andMask; uint16_t orMask; }; + uint8_t* data; RequestData(TAddress r1, uint16_t c1) { reg = r1; regCount = c1; @@ -168,6 +169,9 @@ class Modbus { andMask = m1; orMask = m2; }; + RequestData(uint8_t* d) { + data = d; + }; }; struct frame_arg_t { From 16a4094e2888ded3553d2f775a65ec9c7229035c Mon Sep 17 00:00:00 2001 From: Stefan Date: Thu, 2 May 2024 04:36:16 +0200 Subject: [PATCH 282/288] chore: correct comment in modbus client example (#342) the esp was used as modbus client, but the code comment said modbus server. --- examples/TCP-Ethernet/client/client.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/TCP-Ethernet/client/client.ino b/examples/TCP-Ethernet/client/client.ino index 74d4660..c477adc 100644 --- a/examples/TCP-Ethernet/client/client.ino +++ b/examples/TCP-Ethernet/client/client.ino @@ -31,7 +31,7 @@ void setup() { Ethernet.init(5); // SS pin Ethernet.begin(mac, ip); // start the Ethernet connection delay(1000); // give the Ethernet shield a second to initialize - mb.client(); // Act as Modbus TCP server + mb.client(); // Act as Modbus TCP client } uint16_t res = 0; @@ -49,4 +49,4 @@ if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is establ showLast = millis(); Serial.println(res); } -} \ No newline at end of file +} From 304912a67c5ecd661fe949f123e1a5d48b57c48f Mon Sep 17 00:00:00 2001 From: dukea <23308942+alepiva@users.noreply.github.com> Date: Thu, 2 May 2024 04:36:48 +0200 Subject: [PATCH 283/288] correction of TCP debug (#326) used MODBUSRTU_DEBUG insted of MODBUSIP_DEBUG --- src/ModbusTCPTemplate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h index 0f435d8..c78a43d 100644 --- a/src/ModbusTCPTemplate.h +++ b/src/ModbusTCPTemplate.h @@ -227,7 +227,7 @@ void ModbusTCPTemplate::task() { delete currentClient; continue; } -#if defined(MODBUSRTU_DEBUG) +#if defined(MODBUSIP_DEBUG) Serial.println("IP: Connected"); #endif if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { From 2eb5abe3ed7ead0d5198f43b095033ee9267afee Mon Sep 17 00:00:00 2001 From: Luciano Martorella Date: Thu, 2 May 2024 04:45:11 +0200 Subject: [PATCH 284/288] - Added const (#313) # Conflicts: # src/ModbusAPI.h --- src/ModbusAPI.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h index 600a55a..8c31a93 100644 --- a/src/ModbusAPI.h +++ b/src/ModbusAPI.h @@ -128,9 +128,9 @@ class ModbusAPI : public T { 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 - uint16_t rawRequest(TYPEID ip, uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + uint16_t rawRequest(TYPEID ip, const uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); template - uint16_t rawResponce(TYPEID ip, uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); + uint16_t rawResponce(TYPEID ip, const uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); template uint16_t errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT); }; @@ -475,7 +475,7 @@ uint16_t ModbusAPI::readWriteHreg(TYPEID ip, \ template template uint16_t ModbusAPI::rawRequest(TYPEID ip, \ - uint8_t* data, uint16_t len, + const uint8_t* data, uint16_t len, cbTransaction cb, uint8_t unit) { free(this->_frame); this->_frame = (uint8_t*)malloc(len); @@ -489,7 +489,7 @@ uint16_t ModbusAPI::rawRequest(TYPEID ip, \ template template uint16_t ModbusAPI::rawResponce(TYPEID ip, \ - uint8_t* data, uint16_t len, uint8_t unit) { + const uint8_t* data, uint16_t len, uint8_t unit) { free(this->_frame); this->_frame = (uint8_t*)malloc(len); if (!this->_frame) From ad1c39f580f94e5da95825fd16d88da3b1ccf4c0 Mon Sep 17 00:00:00 2001 From: Luciano Martorella Date: Thu, 2 May 2024 04:46:18 +0200 Subject: [PATCH 285/288] - Allow to change MODBUSRTU_TIMEOUT from project (#315) --- src/ModbusSettings.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h index 6f52ebc..0a26644 100644 --- a/src/ModbusSettings.h +++ b/src/ModbusSettings.h @@ -104,7 +104,9 @@ Otherwise IP addresses only must be used #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 From 500dbbaea233df4f51756c064bf2382764f5bf6e Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 11 May 2024 14:45:39 +0500 Subject: [PATCH 286/288] onGetRemove/onSetRemove fix --- src/Modbus.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 6a090bb..66f9f98 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -580,7 +580,7 @@ bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; if (!cb) { - return removeOnGet(address); + return removeOnGet(address, nullptr, numregs); } while (numregs > 0) { reg = searchRegister(address); @@ -597,7 +597,7 @@ bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; if (!cb) { - return removeOnGet(address); + return removeOnSet(address, nullptr, numregs); } while (numregs > 0) { reg = searchRegister(address); From a5872af7250e03e9a5cd578366b0c2cdc097fcc9 Mon Sep 17 00:00:00 2001 From: Alexander Emelianov Date: Sat, 11 May 2024 14:53:40 +0500 Subject: [PATCH 287/288] begin with re/de warning fix --- src/ModbusRTU.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h index ce54348..5d27562 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -93,6 +93,7 @@ bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, pinMode(_rxPin, OUTPUT); digitalWrite(_rxPin, _direct?LOW:HIGH); } + return true; } #endif class ModbusRTU : public ModbusAPI {}; From 73f19119a4f92b092d395eed0ce29d18499611fe Mon Sep 17 00:00:00 2001 From: smilexs4 Date: Sun, 27 Oct 2024 15:03:36 +0200 Subject: [PATCH 288/288] Update TCP-to-RTU-Simulator.ino (#361) Fixed typo --- examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino index fce0a38..cacf05d 100644 --- a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino +++ b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -60,7 +60,7 @@ Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { if (transRunning) { // Note that we can't process new requests from TCP-side while waiting for responce from RTU-side. tcp.setTransactionId(src->transactionId); // Set transaction id as per incoming request - tcp.errorResponce(IPAddress((src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); + tcp.errorResponce(IPAddress(src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); return Modbus::EX_SLAVE_DEVICE_BUSY; } @@ -135,4 +135,4 @@ void loop() { rtu.task(); tcp.task(); yield(); -} \ No newline at end of file +}