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 diff --git a/API.md b/API.md index 69b4628..bcfb971 100644 --- a/API.md +++ b/API.md @@ -1,307 +1 @@ -# Modbus Master-Slave Library for ESP8266/ESP32 - -## Common 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 local 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 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); -``` - -### 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); -``` - -```c -void task(); -``` - -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); -``` - -Assing Serial port. txPin controls transmit enable for MAX-485. - -```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. - -### ModBus IP Slave specific API - -```c -void begin(); // Depricated. Use slave() instead. -void slave(uint16_t port = MODBUSIP_PORT); -``` - -### ModBus IP Master specific - -```c -void master(); -bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); -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. Disabled by default. - -### 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); - -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 - -### 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); - -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 - -### 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); - -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. - -```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. - -### 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); - -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); -``` - -Read values from remote Hreg/Coil/Ireg/Ists to array. - -## Callbacks API - -```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); -``` - -*Modbus IP Slave* 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. - -```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. For ModbusIP *data* is currently reserved. For ModbusRTU *transactionId* is also reserved. - -```c -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. - -*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); -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) -``` - -### Callback example - -```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 - 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. +Moved to [documentation](documentation) \ No newline at end of file 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/README.md b/README.md index 7515038..e419e69 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,18 @@ -# ModbusRTU and ModbusIP Master-Slave Library for ESP8266/ESP32 v3.0 +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security -|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)| -|---|---| - - -**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. - ---- - -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 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) +For detailes on the library usage visit [documentation](documentation) section. ## Features -* Supported platforms are - * ESP8266 - * ESP32 -* Operates as - * slave - * master -* Supports - * Modbus IP (TCP) - * Modbus RTU (RS-485) -* Reply exception messages for all supported functions +* Supports all Arduino platforms +* 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 Security server (ESP8266)](examples/TLS) + * [MODBUS/TCP Security client (ESP8266/ESP32)](examples/TLS) * Modbus functions supported: * 0x01 - Read Coils * 0x02 - Read Input Status (Read Discrete Inputs) @@ -47,79 +22,94 @@ V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) * 0x06 - Write Single Register * 0x0F - Write Multiple Coils * 0x10 - Write Multiple Registers -* Callbacks for - * Master connect (ModbusIP) - * Master/Slave disconnect (ModbusIP) - * Read specific Register - * Write specific Register - * Slave transaction finish + * 0x14 - Read File Record + * 0x15 - Write File Record + * 0x16 - Mask Write Register + * 0x17 - Read/Write multiple registers +* [Callbacks](examples/Callback) driven design +* Real life complex examples: + * [ESP8266/ESP32 firmware update over Modbus](examples/Files) + * [ModbusRTU to ModbusTCP bridge](examples/Bridge) ## 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 [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. +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: + +* [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 -// 3.0.0-DEVEL -+ 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 -+ 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 -- 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 -+ 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. +// 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 ++ Examples: TCP-to-RTU fixed +// 4.1.0 ++ 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) ++ Settings: Set MODBUSIP_MAX_CLIENTS = 8 for ESP32 ++ 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 ++ 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 +- API: Alternative CRC calulation (reduced memory footprint) +- 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: push/pull functions +- ModbusTCP: Refactor connect by dns name (using native implementation for ESP32 etc) +// 4.3.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 +- ModbusTCP: ModbusAsyncTCP +- API: Extend API to allow custom Modbus commands +- Examples: Basic file operations +- Examples: Revising +``` ## Contributions https://github.com/emelianov/modbus-esp8266 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 -=============== - - - -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: - - -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/documentation/API.md b/documentation/API.md new file mode 100644 index 0000000..a69d94b --- /dev/null +++ b/documentation/API.md @@ -0,0 +1,296 @@ +## Common API + +```c +void task(); +``` + +Processing routine. Should be periodically called form loop(). + +## Server API + +### Add registers + +```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 numregs = 1); +``` + +### Write local 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 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); +``` + +### 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); +``` + +### Modbus RTU Specific API + +```c +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. txEnablePin controls transmit enable for MAX-485. Pass txEnableDirect=false if txEnablePin uses inverse logic. + +```c +void setBaudrte(uint32 baud); +``` + +Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. + +```c +void server(uint8_t slaveId); +void slave(uint8_t slaveId); //Depricated +``` + +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 server(); +uint8_t slave(); //Depricated +``` + +Slave mode: Returns configured slave id. Master mode: Returns slave id for active request or 0 if no request in-progress. + +## Modbus TCP Server specific API + +```c +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 TCP Client specific + +```c +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); +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. Disabled by default. + +## Client API + +### 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 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); +``` + +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/server + +```c +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 pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t pushHreg(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 + +### Write [multiple] values to remote slave/servr reg[s] + +```c +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); +``` + +Writes single value to remote Hreg/Coil. + +```c +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 writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +``` + +Writes multiple values from array to remote Coil/Hreg. + +### Read values from multiple remote slave/server regs + +```c +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 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); +``` + +Reads values from remote Hreg/Coil/Ireg/Ists to array. + +## Callbacks API + +```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 onDisconnect(cbModbusConnect cb); +``` + +*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. + +```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. For ModbusIP *data* is currently reserved. For Modbus RTU *transactionId* is also reserved. + +```c +uint32_t eventSource(); +``` + +Should be called from onGet/onSet or transaction callback function. + +*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. + +*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); +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) +``` + +## 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/documentation/README.md b/documentation/README.md new file mode 100644 index 0000000..d493b93 --- /dev/null +++ b/documentation/README.md @@ -0,0 +1,85 @@ +# 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/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)) + +--- + +## Client work cycle diagram + +![Client diagram](https://github.com/emelianov/modbus-esp8266/blob/master/resources/client.png) + +--- + +## Server work cycle diagram + +![Server diagram](https://github.com/emelianov/modbus-esp8266/blob/master/resources/server.png) + +--- + +## 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 + +The library is designed to execute calls async way. That is `readHreg()` function just sends read request to Modbus server device and exits. Responce is processed (as suun as it's arrive) by `task()`. `task()` is also async and exits if data hasn't arrive yet. + +--- + +## When calling `readCoil`/`readHreg`/`writeHreg`/etc multiple times only first of them executed + +--- + +## 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 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 [example](https://github.com/emelianov/modbus-esp8266/examples/bridge). + +--- + +# 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/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 new file mode 100644 index 0000000..598fe48 --- /dev/null +++ b/examples/Bridge/README.md @@ -0,0 +1,63 @@ +# Bridge functions + +## [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. + +## [ModbusRTU to ModbusTCP bridge](true/true.ino) + +Fullfunctional ModbusRTU to ModbusTCP bridge. + +## [Multiple Server ID](MultipleServerID/MultipleServerID.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); +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` 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 { +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 MBAP header + uint32_t ipaddr; // IP address from which frame is received + uint16_t transactionId; // TransactionId as passed in MBAP 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_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 new file mode 100644 index 0000000..cacf05d --- /dev/null +++ b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -0,0 +1,138 @@ +/* + 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 + transRunning = 0; + slaveRunning = 0; + } + 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 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(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) { // 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; + } + + srcIp = IPAddress(src->ipaddr); + + slaveRunning = src->unitId; + + 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; + 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("TCP IP out - failed"); + } + 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(); +} diff --git a/examples/Bridge/basic/basic.ino b/examples/Bridge/basic/basic.ino new file mode 100644 index 0000000..acf71c0 --- /dev/null +++ b/examples/Bridge/basic/basic.ino @@ -0,0 +1,53 @@ +/* + Modbus ESP8266/ESP32 + Simple ModbesRTU to ModbusIP bridge + + (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 1 +#define FROM_REG 20 + +ModbusRTU mb1; +ModbusIP mb2; + +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()); + + 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); +} + +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/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/MultipleHRegDebug/MultipleHRegDebug.ino b/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino similarity index 92% rename from examples/MultipleHRegDebug/MultipleHRegDebug.ino rename to examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino index 3f7b507..7f717b1 100644 --- a/examples/MultipleHRegDebug/MultipleHRegDebug.ino +++ b/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino @@ -1,83 +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() { - #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.slave(); - - 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 new file mode 100644 index 0000000..6a7e0d9 --- /dev/null +++ b/examples/Callback/README.md @@ -0,0 +1,124 @@ +# Callbacks + +## [Register read/write callback](onSet/onSet.ino) + +## [Use one callback function for multiple registers](onGetShared/onGetShared.ino) + +## [Incoming request callback (applicable to server/slave)](Request/Request.ino) + +## [Modbus TCP/TLS Incoming connection callback](onSet/onSet.ino) + +## [Modbus TCP/TLS Transaction result](Transactional/Transactional.ino) + +### Callback API + +```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 + +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. + +```c +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 { + 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. + +```c +void onConnect(cbModbusConnect cb); +void onDisonnect(cbModbusConnect cb); +``` + +Assign callback function on incoming connection event. + +```c +typedef bool (*cbModbusConnect)(IPAddress ip); +``` + +- `ip` Client's address of incomig connection source. `INADDR_NONE` for on disconnect callback. + +## 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| + +# 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/Callback/Request/Request.ino b/examples/Callback/Request/Request.ino new file mode 100644 index 0000000..fea3dea --- /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, 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, const Modbus::RequestData data) { + 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/MasterWriteMultiple/MasterWriteMultiple.ino b/examples/Callback/Transactional/Transactional.ino similarity index 93% rename from examples/MasterWriteMultiple/MasterWriteMultiple.ino rename to examples/Callback/Transactional/Transactional.ino index 2a6c401..68fd532 100644 --- a/examples/MasterWriteMultiple/MasterWriteMultiple.ino +++ b/examples/Callback/Transactional/Transactional.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/MassOperations/MassOperations.ino b/examples/Callback/onGetShared/onGetShared.ino similarity index 83% rename from examples/MassOperations/MassOperations.ino rename to examples/Callback/onGetShared/onGetShared.ino index 3b0e0a5..554e114 100644 --- a/examples/MassOperations/MassOperations.ino +++ b/examples/Callback/onGetShared/onGetShared.ino @@ -1,83 +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) { - 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.slave(); - - 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(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/Callback/Callback.ino b/examples/Callback/onSet/onSet.ino similarity index 96% rename from examples/Callback/Callback.ino rename to examples/Callback/onSet/onSet.ino index 4ccb2a1..d604f49 100644 --- a/examples/Callback/Callback.ino +++ b/examples/Callback/onSet/onSet.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/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/Files/FW-Update-Source/FW-Update-Source.ino b/examples/Files/FW-Update-Source/FW-Update-Source.ino new file mode 100644 index 0000000..5be2231 --- /dev/null +++ b/examples/Files/FW-Update-Source/FW-Update-Source.ino @@ -0,0 +1,167 @@ +/* + 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) + #include + #include +#else + #include +#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 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 + 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); + written = 0; + 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)?BLOCK_SIZE:remaining; + result = Modbus::EX_GENERAL_FAILURE; + 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("."); + } + 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("!"); + Serial.print("Written: "); + Serial.println(written * 2); + 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); + + WiFi.begin("SSID", "PASSWORD"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + +#if defined(ESP8266) + S.begin(19200, SWSERIAL_8N1); + rtu.begin(&S); +#else + 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() { + 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..5864929 --- /dev/null +++ b/examples/Files/FW-Update-Target/FW-Update-Target.ino @@ -0,0 +1,100 @@ +/* + 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 +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.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..."); + } + 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); + } + written = 0; + 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) { + 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(19200, SWSERIAL_8N1); + rtu.begin(&S); +#else + Serial1.begin(19200, SERIAL_8N1, 18, 19); + 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..078d7b3 --- /dev/null +++ b/examples/Files/README.md @@ -0,0 +1,62 @@ +# Files operations + +## [Firmware update over ModbusRTU - Update source node](FW-Update-Source/FW-Update-Source.ino) + +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. + +## 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/README.md b/examples/README.md new file mode 100644 index 0000000..c82ba51 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,36 @@ +# 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) + +Modbus file operations examples. + +## [ModbusRTU to ModbusTCP bridge and related functions](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 + +(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. diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino new file mode 100644 index 0000000..2cee07c --- /dev/null +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -0,0 +1,101 @@ +/* + ModbusRTU ESP32 + Concurent thread example + + (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 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; + +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\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(); + } + Modbus::ResultCode res = err; + xSemaphoreGive(xMutex); + return res; +} + +void loop1( void * pvParameters ); +void loop2( void * pvParameters ); + +void setup() { + Serial.begin(115200); + MBUS_HW_SERIAL.begin(9600, SERIAL_8N1, MBUS_RXD_PIN, MBUS_TXD_PIN); + mb.begin(&MBUS_HW_SERIAL); + mb.master(); + xMutex = xSemaphoreCreateMutex(); + 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. */ + "Task2", /* 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[REG_NUM]; +void loop1( void * pvParameters ){ + while(true) { + delay(10); + if (readSync(SLAVE_ID1, REG, REG_NUM, hregs1) == Modbus::EX_SUCCESS) + Serial.println("OK 1"); + else + Serial.println("Error 1"); + } +} + +uint16_t hregs2[REG_NUM]; +void loop2( void * pvParameters ){ + while(true) { + delay(100); + if (readSync(SLAVE_ID2, REG, REG_NUM, hregs2) == Modbus::EX_SUCCESS) + Serial.println("OK 2"); + else + Serial.println("Error 2"); + } +} + +void loop() { + delay(100); +} diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD new file mode 100644 index 0000000..1f4e4b9 --- /dev/null +++ b/examples/RTU/README.MD @@ -0,0 +1,57 @@ +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/ESP32-Concurent.ino) + +## [Simple ModbusRTU master](master/master.ino) + +## [Simple ModbusRTU slave](slave/slave.ino) + +## [Sync ModbusRTU master](masterSync/masterSync.ino) + +## Modbus RTU Specific API + +```c +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 +- `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. txEnablePin controls transmit enable for MAX-485. + +```c +void setBaudrate(uint32 baud); +``` + +- `baud` New baudrate. + +Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. + +```c +void client(); +void server(uint8_t slaveId); +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 unpredictable. + +```c +uint8_t client(); +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. + +# 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. diff --git a/examples/RTU-master/RTU-Master.ino b/examples/RTU/master/master.ino similarity index 55% rename from examples/RTU-master/RTU-Master.ino rename to examples/RTU/master/master.ino index 1f2881f..d4d8c23 100644 --- a/examples/RTU-master/RTU-Master.ino +++ b/examples/RTU/master/master.ino @@ -1,33 +1,52 @@ /* 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); - mb.begin(&Serial1) + Serial1.begin(9600, SERIAL_8N1); + mb.begin(&Serial1); + mb.setBaudrate(9600); #endif mb.master(); } 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/examples/RTU-slave/RTU-slave.ino b/examples/RTU/slave/slave.ino similarity index 52% rename from examples/RTU-slave/RTU-slave.ino rename to examples/RTU/slave/slave.ino index ad1a326..4a96808 100644 --- a/examples/RTU-slave/RTU-slave.ino +++ b/examples/RTU/slave/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,14 @@ ModbusRTU mb; void setup() { - Serial.begin(9600, SERIAL_8N1) + Serial.begin(9600, SERIAL_8N1); +#if defined(ESP32) || defined(ESP8266) + 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); mb.addHreg(REGN); mb.Hreg(REGN, 100); diff --git a/examples/AnalogInput/AnalogInput.ino b/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino similarity index 93% rename from examples/AnalogInput/AnalogInput.ino rename to examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino index 55a02a9..0e39a5f 100644 --- a/examples/AnalogInput/AnalogInput.ino +++ b/examples/TCP-ESP/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/Led/Led.ino b/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino similarity index 94% rename from examples/Led/Led.ino rename to examples/TCP-ESP/IP-server-Led/IP-server-Led.ino index ce6990b..5155711 100644 --- a/examples/Led/Led.ino +++ b/examples/TCP-ESP/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/SwitchStatus/SwitchStatus.ino b/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino similarity index 93% rename from examples/SwitchStatus/SwitchStatus.ino rename to examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino index bc3bd16..6a7b339 100644 --- a/examples/SwitchStatus/SwitchStatus.ino +++ b/examples/TCP-ESP/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/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md new file mode 100644 index 0000000..70ae29f --- /dev/null +++ b/examples/TCP-ESP/README.md @@ -0,0 +1,136 @@ +# ESP8266/ESP32 TCP Examples + +## [Basic client](client/client.ino) + +## [Client with blocking read operation](clientSync/clientSync.ino) + +## [Server](server/server.ino) + +### API + +```c +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); +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 +- `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. 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. + +```c +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 +void dropTransactions(); +``` + +Cancel all active transactions. Callback with result code `Modbus::EX_CANCEL` will be called for each transaction (if assigned). + +### 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 +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 register to read + + +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. + +# 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/MasterSimpleRead/MasterSimpleRead.ino b/examples/TCP-ESP/client/client.ino similarity index 84% rename from examples/MasterSimpleRead/MasterSimpleRead.ino rename to examples/TCP-ESP/client/client.ino index 47b886d..22d3de7 100644 --- a/examples/MasterSimpleRead/MasterSimpleRead.ino +++ b/examples/TCP-ESP/client/client.ino @@ -1,59 +1,55 @@ -/* - 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 -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 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/Master/Master.ino b/examples/TCP-ESP/clientPull/clientPull.ino similarity index 86% rename from examples/Master/Master.ino rename to examples/TCP-ESP/clientPull/clientPull.ino index ab4ac8b..2098004 100644 --- a/examples/Master/Master.ino +++ b/examples/TCP-ESP/clientPull/clientPull.ino @@ -1,75 +1,71 @@ -/* - 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 -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(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/TCP-ESP/clientSync/clientSync.ino b/examples/TCP-ESP/clientSync/clientSync.ino new file mode 100644 index 0000000..03c2213 --- /dev/null +++ b/examples/TCP-ESP/clientSync/clientSync.ino @@ -0,0 +1,55 @@ +/* + 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(); +} + +uint16_t res = 0; + +void loop() { + 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/examples/HoldingReg/HoldingReg.ino b/examples/TCP-ESP/server/server.ino similarity index 93% rename from examples/HoldingReg/HoldingReg.ino rename to examples/TCP-ESP/server/server.ino index 4a183ae..1cf0977 100644 --- a/examples/HoldingReg/HoldingReg.ino +++ b/examples/TCP-ESP/server/server.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/TCP-Ethernet/README.md b/examples/TCP-Ethernet/README.md new file mode 100644 index 0000000..8857af7 --- /dev/null +++ b/examples/TCP-Ethernet/README.md @@ -0,0 +1,15 @@ +# W5x00 Example + +## Physical connection between ESP32 and W5500 + +* GPIO23 <--> MOSI +* GPIO19 <--> MISO +* GPIO18 <--> SCLK +* GPIO5 <--> SCS + +# 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. diff --git a/examples/TCP-Ethernet/client/client.ino b/examples/TCP-Ethernet/client/client.ino new file mode 100644 index 0000000..c477adc --- /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 client +} + +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); + } +} diff --git a/examples/TCP-Ethernet/server/server.ino b/examples/TCP-Ethernet/server/server.ino new file mode 100644 index 0000000..769e7cd --- /dev/null +++ b/examples/TCP-Ethernet/server/server.ino @@ -0,0 +1,37 @@ +/* + 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 // Ethernet library v2 is required +#include + +// Enter a MAC address and IP address for your controller below. +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED +}; +IPAddress ip(192, 168, 30, 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 + #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.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 new file mode 100644 index 0000000..ede543f --- /dev/null +++ b/examples/TLS/README.md @@ -0,0 +1,47 @@ +# Modbus\TCP Security Example + +### *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) + +```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. + +# 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. 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..d8a56b8 --- /dev/null +++ b/examples/TLS/certs/server.conf @@ -0,0 +1,16 @@ +[ req ] +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/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/keywords.txt b/keywords.txt index 5566dc8..38bc2f4 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,9 +1,10 @@ # Syntax Coloring Map For ModbusIP-ESP8266 # Datatypes (KEYWORD1) -ModbusRTUSlave KEYWORD1 -ModbusRTUMaster KEYWORD1 +ModbusRTU KEYWORD1 ModbusIP KEYWORD1 +ModbusTCP KEYWORD1 +ModbusIP_ESP8266 KEYWORD1 Modbus KEYWORD1 TRegister KEYWORD1 TTransaction KEYWORD1 @@ -11,8 +12,8 @@ TAddress KEYWORD1 ResultCode KEYWORD1 # Methods and Functions (KEYWORD2) -master KEYWORD2 -slave KEYWORD2 +client KEYWORD2 +server KEYWORD2 task KEYWORD2 onConnect KEYWORD2 onDisconnect KEYWORD2 @@ -72,6 +73,22 @@ isCoil KEYWORD2 isHreg KEYWORD2 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 +Reg KEYWORD2 +addReg KEYWORD2 +removeReg KEYWORD2 # Constants and Macros (LITERAL1) BIT_VAL LITERAL1 @@ -102,4 +119,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/library.properties b/library.properties index d10b7ab..765f44a 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=modbus-esp8266 -version=3.0.0 -author=Andre Sarmento Barbosa -maintainer=Alexander Emelianov -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. +version=4.1.0 +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 also 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/resources/client.png b/resources/client.png new file mode 100644 index 0000000..5d6b825 Binary files /dev/null and b/resources/client.png differ diff --git a/resources/client.uml b/resources/client.uml new file mode 100644 index 0000000..3f0b538 --- /dev/null +++ b/resources/client.uml @@ -0,0 +1,21 @@ +@startuml +!pragma useVerticalIf on +start +partition readHreg() { +if (no request is alreaty running) then (yes) + :readHreg; +endif +} +while (request is active) +partition task() { +if (responce arrived) then (yes) + #palegreen:Execute Transactional Callback; + :fill result data; +endif +if (responce timeout) then (yes) + #palegreen:Execute Transactional Callback; +endif +} +endwhile (no request) +stop +@enduml \ No newline at end of file diff --git a/resources/server.png b/resources/server.png new file mode 100644 index 0000000..f8082e9 Binary files /dev/null and b/resources/server.png differ diff --git a/resources/server.uml b/resources/server.uml new file mode 100644 index 0000000..d4c03a7 --- /dev/null +++ b/resources/server.uml @@ -0,0 +1,26 @@ +@startuml +!pragma useVerticalIf on +start +partition task() { +if (valid request arrived) then (yes) + #palegreen:Execute onRequest Callback; + if (EX_SUCCESS) then (yes) + if (correct request parameters) then (yes) + :Preapre responce; + #palegreen:Execute onGet/onSet Callback for each register in request; + #palegreen:Execute onRequestSuccess Callback; + else (no) + #pink:Return error code; + stop + endif + else (no) + #pink:Return error code; + stop + endif +else + #pink:Return error code; + stop +endif +} +stop +@enduml \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp index 636f198..66f9f98 100644 --- a/src/Modbus.cpp +++ b/src/Modbus.cpp @@ -1,38 +1,69 @@ /* - Modbus.cpp - Modbus Base Library Implementation + Modbus Library for Arduino + Core functions Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2023 Alexander Emelianov (a.m.emelianov@gmail.com) */ #include "Modbus.h" -#ifdef MB_GLOBAL_REGS - std::vector _regs; - std::vector _callbacks; +#if defined(MODBUS_GLOBAL_REGS) +#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 #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 - if (_regs.size() + numregs > MB_MAX_REGS) return false; + #if defined(MODBUS_MAX_REGS) + if (_regs.size() + numregs > MODBUS_MAX_REGS) return false; #endif + if (0xFFFF - address.address < numregs) + numregs = 0xFFFF - address.address; for (uint16_t i = 0; i < numregs; i++) { if (!searchRegister(address + i)) _regs.push_back({address + i, value}); @@ -71,117 +102,340 @@ 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) { atLeastOne = true; removeOnSet(address + i); removeOnGet(address + i); + #if defined(MODBUS_USE_STL) _regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() ); + #else + _regs.remove(_regs.find(MODBUS_COMPARE_REG)); + #endif } } return atLeastOne; } +bool Modbus::addReg(TAddress address, uint16_t* value, uint16_t numregs) { + if (0xFFFF - address.address < numregs) + numregs = 0xFFFF - address.address; + for (uint16_t k = 0; k < numregs; k++) + addReg(address + k, value[k]); + return true; +} + void Modbus::slavePDU(uint8_t* frame) { FunctionCode fcode = (FunctionCode)frame[0]; uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; + uint16_t field3 = 0; + uint16_t field4 = 0; uint16_t bytecount_calc; uint16_t k; + ResultCode ex; switch (fcode) { case FC_WRITE_REG: //field1 = reg, field2 = value - if (!Hreg(field1, field2)) { //Check Address and execute (reg exists?) + 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 (Hreg(field1) != field2) { //Check for failure + 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; + } + ex = readWords(HREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {HREG(field1), field2}); break; case FC_WRITE_REGS: //field1 = startreg, field2 = numregs, frame[5] = data lenght, header len = 6 - if (field2 < 0x0001 || field2 > 0x007B || frame[5] != 2 * field2) { //Check value + ex = _onRequest(fcode, {HREG(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || 0xFFFF - field1 < field2 || frame[5] != 2 * field2) { //Check constrains exceptionResponse(fcode, EX_ILLEGAL_VALUE); - 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) { - setMultipleWords(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; 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; + } + ex = readBits(COIL(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {COIL(field1), field2}); break; case FC_READ_INPUT_STAT: //field1 = startreg, field2 = numregs - readBits(ISTS(field1), field2, fcode); + ex = _onRequest(fcode, {ISTS(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + ex = readBits(ISTS(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {ISTS(field1), field2}); break; case FC_READ_INPUT_REGS: //field1 = startreg, field2 = numregs - readWords(IREG(field1), field2, fcode); + ex = _onRequest(fcode, {IREG(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + ex = readWords(IREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {IREG(field1), field2}); break; case FC_WRITE_COIL: //field1 = reg, field2 = status, header len = 3 + ex = _onRequest(fcode, {COIL(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } if (field2 != 0xFF00 && field2 != 0x0000) { //Check value (status) exceptionResponse(fcode, EX_ILLEGAL_VALUE); - 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 + 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); - 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 (!setMultipleBits(frame + 6, COIL(field1), field2)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + successResponce(COIL(field1), field2, fcode); + _reply = REPLY_NORMAL; + _onRequestSuccess(fcode, {COIL(field1), field2}); + break; + #if defined(MODBUS_FILES) + case FC_READ_FILE_REC: + if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + { + uint8_t bufSize = 2; // 2 bytes for frame header + uint8_t* recs = frame + 2; // Begin of sub-recs blocks + uint8_t recsCount = frame[1] / 7; // Count of sub-rec blocks + for (uint8_t p = 0; p < recsCount; p++) { // Calc output buffer size required + //uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2]; + uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4]; + uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6]; + //Serial.printf("%d, %d, %d\n", fileNum, recNum, recLen); + if (recs[0] != 0x06 || recNum > 0x270F) { // Wrong ref type or count of records + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + bufSize += recLen * 2 + 2; // 4 bytes for header + data + recs += 7; + } +// if (bufSize > MODBUS_MAX_FRAME) { // Frame to return too large +// exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); +// return; +// } + uint8_t* srcFrame = _frame; + _frame = (uint8_t*)malloc(bufSize); + if (!_frame) { + free(srcFrame); + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + _len = bufSize; + recs = frame + 2; // Begin of sub-recs blocks + uint8_t* data = _frame + 2; + for (uint8_t p = 0; p < recsCount; p++) { + uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2]; + uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4]; + uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6]; + ResultCode res = fileOp(fcode, fileNum, recNum, recLen, data + 2); + if (res != EX_SUCCESS) { // File read failed + free(srcFrame); + exceptionResponse(fcode, res); + return; } + data[0] = recLen * 2 + 1; + data[1] = 0x06; + data += recLen * 2 + 2; + recs += 7; } - if (k >= field2) { - setMultipleBits(frame + 6, COIL(field1), field2); - successResponce(COIL(field1), field2, fcode); - _reply = REPLY_NORMAL; + _frame[0] = fcode; + _frame[1] = bufSize; + _reply = REPLY_NORMAL; + free(srcFrame); } break; + case FC_WRITE_FILE_REC: { + if (frame[1] < 0x09 || frame[1] > 0xFB) { // Wrong request data size + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + uint8_t* recs = frame + 2; // Begin of sub-recs blocks + while (recs < frame + frame[1]) { + if (recs[0] != 0x06) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2]; + uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4]; + uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6]; + if (recs + recLen * 2 > frame + frame[1]) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + ResultCode res = fileOp(fcode, fileNum, recNum, recLen, recs + 7); + if (res != EX_SUCCESS) { // File write failed + exceptionResponse(fcode, res); + return; + } + recs += 7 + recLen * 2; + } + } + _reply = REPLY_ECHO; + break; + #endif + case FC_MASKWRITE_REG: + //field1 = reg, field2 = AND mask, field3 = OR mask + // Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) + field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; + ex = _onRequest(fcode, {HREG(field1), field2, field3}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + field4 = Reg(HREG(field1)); + field4 = (field4 & field2) | (field3 & ~field2); + if (!Reg(HREG(field1), field4)) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + if (Reg(HREG(field1)) != field4) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + _reply = REPLY_ECHO; + _onRequestSuccess(fcode, {HREG(field1), field2, field3}); + break; + case FC_READWRITE_REGS: + //field1 = readreg, field2 = read count, frame[9] = data lenght, header len = 10 + //field3 = wtitereg, field4 = write count + field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; + field4 = (uint16_t)frame[7] << 8 | (uint16_t)frame[8]; + ex = _onRequest(fcode, {HREG(field1), field2, HREG(field3), field4}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || + field4 < 0x0001 || field4 > MODBUS_MAX_WORDS || + 0xFFFF - field1 < field2 || 0xFFFF - field1 < field2 || + frame[9] != 2 * field4) { //Check value + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + if (!setMultipleWords((uint16_t*)(frame + 10), HREG(field3), field4)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + ex = readWords(HREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {HREG(field1), field2, HREG(field3), field4}); + break; default: - exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + ex = _onRequest(fcode, {frame + 1}); + if (ex != EX_PASSTHROUGH) { + exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + } + return; } } @@ -189,6 +443,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; @@ -200,6 +458,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; @@ -222,76 +484,78 @@ 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] = __swap_16(Reg(startreg + i)); + } } -void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { - if (numregs < 0x0001 || numregs > 0x07D0) { //Check value (numregs) - exceptionResponse(fn, EX_ILLEGAL_VALUE); - return; - } +Modbus::ResultCode Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { + if (numregs < 0x0001 || numregs > MODBUS_MAX_BITS || (0xFFFF - startreg.address) < numregs) + return EX_ILLEGAL_ADDRESS; //Check Address //Check only startreg. Is this correct? //When I check all registers in range I got errors in ScadaBR //I think that ScadaBR request more than one in the single request //when you have more then one datapoint configured from same type. - if (!searchRegister(startreg)) { - exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_ADDRESS; } +#else + if (!searchRegister(startreg)) + return EX_ILLEGAL_ADDRESS; +#endif free(_frame); //Determine the message length = function type, byte count and //for each group of 8 registers the message length increases by 1 _len = 2 + numregs/8; if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. _frame = (uint8_t*) malloc(_len); - if (!_frame) { - exceptionResponse(fn, EX_SLAVE_FAILURE); - return; - } + if (!_frame) + return EX_SLAVE_FAILURE; _frame[0] = fn; _frame[1] = _len - 2; //byte count (_len - function code and byte count) _frame[_len - 1] = 0; //Clean last probably partial byte getMultipleBits(_frame+2, startreg, numregs); _reply = REPLY_NORMAL; + return EX_SUCCESS; } -void 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; - } - if (!searchRegister(startreg)) { //Check Address - exceptionResponse(fn, EX_ILLEGAL_ADDRESS); - return; + if (numregs < 0x0001 || numregs > MODBUS_MAX_WORDS || 0xFFFF - startreg.address < numregs) + return EX_ILLEGAL_ADDRESS; +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_ADDRESS; } +#else + if (!searchRegister(startreg)) + return EX_ILLEGAL_ADDRESS; +#endif free(_frame); _len = 2 + numregs * 2; //calculate the query reply message length. 2 bytes per register + 2 bytes for header _frame = (uint8_t*) malloc(_len); - if (!_frame) { - exceptionResponse(fn, EX_SLAVE_FAILURE); - return; - } + if (!_frame) + return EX_SLAVE_FAILURE; _frame[0] = fn; _frame[1] = _len - 2; //byte count - getMultipleWords(_frame + 2, startreg, numregs); + getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs); _reply = REPLY_NORMAL; + return EX_SUCCESS; } -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++; @@ -299,23 +563,24 @@ void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutp } startreg++; //increment the register } + return result; } -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++; - } +bool Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { + bool result = true; + for (uint8_t i = 0; i < numregs; i++) { + Reg(startreg + i, __swap_16(frame[i])); + if (Reg(startreg + i) != __swap_16(frame[i])) + result = false; + } + return result; } bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { TRegister* reg; bool atLeastOne = false; if (!cb) { - return removeOnGet(address); + return removeOnGet(address, nullptr, numregs); } while (numregs > 0) { reg = searchRegister(address); @@ -332,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); @@ -346,27 +611,47 @@ bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { return atLeastOne; } -bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) { +bool Modbus::removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb, uint16_t numregs) { + size_t s = _callbacks.size(); + #if defined(MODBUS_USE_STL) + #define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){\ + return entry.type == t && entry.address == address \ + && (!cb || std::addressof(cb) == std::addressof(entry.cb));} while(numregs--) { - _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), [address, cb](TCallback entry){ - return entry.type == TCallback::ON_SET && entry.address == address && (!cb || entry.cb == cb);} ), _callbacks.end() ); + _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) { + #else + #define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){ \ + return entry.type == t && 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() ); + size_t r = 0; + do { + r = _callbacks.find(MODBUS_COMPARE_ON); + _callbacks.remove(r); + } while (r < _callbacks.size()); 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) { 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; @@ -380,52 +665,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; } - _reply = REPLY_OFF; - return false; + _frame[0] = fn; + _frame[1] = to >> 8; + _frame[2] = to & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + _frame[5] = _len - 6; + _frame[_len - 1] = 0; //Clean last probably partial byte + if (data) { + boolToBits(_frame + 6, data, numregs); + } else { + getMultipleBits(_frame + 6, startreg, numregs); + } + _reply = REPLY_NORMAL; + return true; } bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data) { free(_frame); _len = 6 + 2 * numregs; _frame = (uint8_t*) malloc(_len); - if (_frame) { - _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); - while(numregs) { - *frame = __bswap_16(*((uint16_t*)data)); - frame = frame + 2; - data = data + 2; - numregs--; - } - } else { - getMultipleWords(_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) { @@ -461,50 +743,41 @@ 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) { - _reply = _frame[1]; + if ((fcode & 0x80) != 0) { // Check if error responce + _reply = frame[1]; return; } + if (fcode != sourceFrame[0]) { // Check if responce matches the request + _reply = EX_DATA_MISMACH; + return; + } + _reply = EX_SUCCESS; uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; uint8_t bytecount_calc; switch (fcode) { case FC_READ_REGS: - //field1 = startreg, field2 = numregs, frame[1] = data lenght, header len = 2 + case FC_READ_INPUT_REGS: + case FC_READWRITE_REGS: + //field2 = numregs, frame[1] = data lenght, header len = 2 if (frame[1] != 2 * field2) { //Check if data size matches _reply = EX_DATA_MISMACH; break; } if (output) { - frame = frame + 2; - while(field2) { - *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); - frame = frame + 2; - output = output + 2; - field2--; + uint16_t* from = (uint16_t*)(frame + 2); + uint16_t* to = (uint16_t*)output; + while(field2--) { + *(to++) = __swap_16(*(from++)); } } else { - setMultipleWords(frame + 2, startreg, field2); + setMultipleWords((uint16_t*)(frame + 2), startreg, 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 (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 @@ -517,142 +790,142 @@ 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 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; } - if (output) { - frame = frame + 2; - while(field2) { - *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); - frame = frame + 2; - output = output + 2; - field2--; + { + 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; } - } else { - setMultipleWords(frame + 2, startreg, field2); + 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: - break; case FC_WRITE_REGS: - break; case FC_WRITE_COIL: - break; case FC_WRITE_COILS: + case FC_MASKWRITE_REG: break; + default: - _reply = EX_GENERAL_FAILURE; + _reply = EX_GENERAL_FAILURE; } } -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); } - -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); +Modbus::~Modbus() { + free(_frame); } -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); +#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; } -bool Modbus::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnSet(HREG(offset), cb, numregs); +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::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnGet(ISTS(offset), cb, numregs); + + bool Modbus::readSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn) { + _len = count * 7 + 2; + if (_len > MODBUS_MAX_FRAME) return false; + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) return false; + _frame[0] = fn; + _frame[1] = _len - 2; + uint8_t* subReq = _frame + 2; + for (uint8_t i = 0; i < count; i++) { + subReq[0] = 0x06; + subReq[1] = fileNum[i] >> 8; + subReq[2] = fileNum[i] & 0x00FF; + subReq[3] = startRec[i] >> 8; + subReq[4] = startRec[i] & 0x00FF; + subReq[5] = len[i] >> 8; + subReq[6] = len[i] & 0x00FF; + subReq += 7; + } + return true; + } + bool Modbus::writeSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn, uint8_t* data) { + _len = 2; + for (uint8_t i = 0; i < count; i++) { + _len += len[i] * 2 + 7; + } + if (_len > MODBUS_MAX_FRAME) return false; + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) return false; + _frame[0] = fn; + _frame[1] = _len - 2; + uint8_t* subReq = _frame + 2; + for (uint8_t i = 0; i < count; i++) { + subReq[0] = 0x06; + subReq[1] = fileNum[i] >> 8; + subReq[2] = fileNum[i] & 0x00FF; + subReq[3] = startRec[i] >> 8; + subReq[4] = startRec[i] & 0x00FF; + subReq[5] = len[i] >> 8; + subReq[6] = len[i] & 0x00FF; + uint8_t clen = len[i] * 2; + memcpy(subReq + 7, data, clen); + subReq += 7 + clen; + data += clen; + } + return true; + } + #endif + +bool Modbus::onRaw(cbRaw cb) { + _cbRaw = cb; + return true; } -bool Modbus::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnSet(ISTS(offset), cb, numregs); +Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, const RequestData data) { + return EX_SUCCESS; } -bool Modbus::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnGet(IREG(offset), cb, numregs); +bool Modbus::onRequest(cbRequest cb) { + _onRequest = cb; + return true; } -bool Modbus::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { - return removeOnSet(IREG(offset), cb, numregs); +#if defined (MODBUSAPI_OPTIONAL) +bool Modbus::onRequestSuccess(cbRequest cb) { + _onRequestSuccess = cb; + return true; } +#endif -Modbus::~Modbus() { - free(_frame); -} \ No newline at end of file +#if defined(ARDUINO_SAM_DUE_STL) +namespace std { + void __throw_bad_function_call() { + Serial.println(F("STL ERROR - __throw_bad_function_call")); + __builtin_unreachable(); + } +} +#endif \ No newline at end of file diff --git a/src/Modbus.h b/src/Modbus.h index c989e3a..97fd340 100644 --- a/src/Modbus.h +++ b/src/Modbus.h @@ -1,29 +1,28 @@ /* - Modbuc.h - Header for Modbus Base Library + Modbus Library for Arduino + Core functions Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2018 Alexander Emelianov (a.m.emelianov@gmail.com) + 2017-2022 Alexander Emelianov (a.m.emelianov@gmail.com) */ #pragma once - +#include "ModbusSettings.h" #include "Arduino.h" -#include -#include -#ifdef ARDUINO_ARCH_ESP32 - #include -#endif - -#ifndef __bswap_16 - #define __bswap_16(num) (((uint16_t)num>>8) | ((uint16_t)num<<8)) +#if defined(MODBUS_USE_STL) + #include + #include + #include + #include +#else + #include "darray.h" #endif +static inline uint16_t __swap_16(uint16_t num) { return (num >> 8) | (num << 8); } -#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} #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) @@ -35,16 +34,22 @@ #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}; + enum RegType {COIL, ISTS, IREG, HREG, NONE = 0xFF}; RegType type; uint16_t address; bool operator==(const TAddress &obj) const { // TAddress == TAddress return type == obj.type && address == obj.address; } + bool operator!=(const TAddress &obj) const { // TAddress != TAddress + return type != obj.type || address != obj.address; + } TAddress& operator++() { // ++TAddress address++; return *this; @@ -105,10 +110,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 @@ -128,61 +133,83 @@ 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. Raw callback. Indicate to normal processing on callback exit + EX_FORCE_PROCESS = 0xE8 // Custom. Raw callback. Indicate to force processing on callback exit }; + union RequestData { + struct { + TAddress reg; + uint16_t regCount; + }; + struct { + TAddress regRead; + uint16_t regReadCount; + TAddress regWrite; + uint16_t regWriteCount; + }; + struct { + TAddress regMask; + uint16_t andMask; + uint16_t orMask; + }; + uint8_t* data; + RequestData(TAddress r1, uint16_t c1) { + reg = r1; + regCount = c1; + }; + RequestData(TAddress r1, uint16_t c1, TAddress r2, uint16_t c2) { + regRead = r1; + regReadCount = c1; + regWrite = r2; + regWriteCount = c2; + }; + RequestData(TAddress r1, uint16_t m1, uint16_t m2) { + regMask = r1; + andMask = m1; + orMask = m2; + }; + RequestData(uint8_t* d) { + data = d; + }; + }; + + struct frame_arg_t { + bool to_server; + union { + uint8_t slaveId; + struct { + uint8_t unitId; + uint32_t ipaddr; + uint16_t transactionId; + }; + }; + frame_arg_t(uint8_t s, bool m = false) { + slaveId = s; + to_server = m; + }; + frame_arg_t(uint8_t u, uint32_t a, uint16_t t, bool m = false) { + unitId = u; + ipaddr = a; + transactionId = t; + to_server = m; + }; + }; + ~Modbus(); - bool 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); + + bool cbEnable(const bool state = true); + bool cbDisable(); private: - void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); - void 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); - void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); - void setMultipleWords(uint8_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(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); @@ -196,20 +223,46 @@ class Modbus { REPLY_ERROR = 0x04, REPLY_UNEXPECTED = 0x05 }; - #ifndef MB_GLOBAL_REGS + #if defined(MODBUS_USE_STL) + #if defined(MODBUS_GLOBAL_REGS) + static std::vector _regs; + static std::vector _callbacks; + #if defined(MODBUS_FILES) + static std::function _onFile; + #endif + #else std::vector _regs; std::vector _callbacks; - #endif + #if defined(MODBUS_FILES) + std::function _onFile; + #endif + #endif + #else + #if defined(MODBUS_GLOBAL_REGS) + 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; + DArray _callbacks; + #if defined(MODBUS_FILES) + ResultCode (*_onFile)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)= nullptr; + #endif + #endif + #endif + uint8_t* _frame = nullptr; uint16_t _len = 0; uint8_t _reply = 0; bool cbEnabled = true; uint16_t callback(TRegister* reg, uint16_t val, TCallback::CallbackType t); - 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 - 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 @@ -223,11 +276,14 @@ 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); 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); @@ -235,6 +291,73 @@ 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 + 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: + cbRequest _onRequestSuccess = _onRequestDefault; + public: + bool onRequestSuccess(cbRequest cb = _onRequestDefault); + #endif + + #if defined(MODBUS_FILES) + public: + #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: + bool readSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn); + // fileNum - sequental array of files numbers to read + // startRec - array of strart records for each file + // len - array of counts of records to read in terms of register size (2 bytes) for each file + // count - count of records to be compose in the single request + // fn - Modbus function. Assumed to be 0x14 + bool writeSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn, uint8_t* data); + // fileNum - sequental array of files numbers to read + // startRec - array of strart records for each file + // len - array of counts of records to read in terms of register size (2 bytes) for each file + // count - count of records to be compose in the single request + // fn - Modbus function. Assumed to be 0x15 + // data - sequental set of data records + #endif + }; +#if defined(MODBUS_USE_STL) +typedef std::function cbTransaction; // Callback skeleton for requests +#else typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests +#endif +//typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode func, TRegister* reg, uint16_t regCount); // Callback function Type +#if defined(MODBUS_FILES) +// Callback skeleton for file read/write +#if defined(MODBUS_USE_STL) +typedef std::function cbModbusFileOp; +#else +typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame); +#endif +#endif + +#if defined(ARDUINO_SAM_DUE_STL) +// Arduino Due STL workaround +namespace std { + void __throw_bad_function_call(); +} +#endif \ No newline at end of file diff --git a/src/ModbusAPI.h b/src/ModbusAPI.h new file mode 100644 index 0000000..8c31a93 --- /dev/null +++ b/src/ModbusAPI.h @@ -0,0 +1,507 @@ +/* + Modbus Library for Arduino + Modbus public API implementation + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once +#include "Modbus.h" + +template +class ModbusAPI : public T { + public: + // 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 + 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 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); +*/ + // Classic API + bool Hregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(HREG(offset), value);} + bool Coils(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(COIL(offset), value);} + bool Istss(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(ISTS(offset), value);} + bool Iregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(IREG(offset), value);} + + //bool addHreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(HREG(offset), value);} + //bool addCoil(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(COIL(offset), value);} + //bool addIsts(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(ISTS(offset), value);} + //bool addIreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(IREG(offset), value);} + + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); + bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1); + bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); + bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); + + bool Hreg(uint16_t offset, uint16_t value); + bool Coil(uint16_t offset, bool value); + bool Ists(uint16_t offset, bool value); + bool Ireg(uint16_t offset, uint16_t value); + + bool Coil(uint16_t offset); + bool Ists(uint16_t offset); + uint16_t Ireg(uint16_t offset); + uint16_t Hreg(uint16_t offset); + + bool removeCoil(uint16_t offset, uint16_t numregs = 1); + bool removeIsts(uint16_t offset, uint16_t numregs = 1); + bool removeIreg(uint16_t offset, uint16_t numregs = 1); + bool removeHreg(uint16_t offset, uint16_t numregs = 1); + + bool onGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + + bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + + template + 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); + + 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(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, const uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + 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); +}; + +// 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 \ +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); \ +} +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 \ +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); \ +} +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 \ +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, (uint8_t*)value); \ +} +IMPLEMENT_READREGS(readCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS, bool) +IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS, uint16_t) +IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS, bool) +IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS, uint16_t) + +#if defined(MODBUS_ADD_REG) +#define ADDREG(R) this->addReg(R(to), (uint16_t)0, numregs); +#else +#define ADDREG(R) ; +#endif +#define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \ +template \ +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(REG) \ + this->readSlave(from, numregs, Modbus::FUNC); \ + return this->send(ip, REG(to), cb, unit); \ +} +IMPLEMENT_PULL(pullCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS) +IMPLEMENT_PULL(pullIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS) +IMPLEMENT_PULL(pullHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS) +IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS) +IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, MODBUS_MAX_WORDS) +IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, MODBUS_MAX_BITS) + +#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM, FINT) \ +template \ +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->FINT(REG(from), to, numregs, Modbus::FUNC); \ + return this->send(ip, REG(from), cb, unit); \ +} +IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits) +IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords) +IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords) +IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits) + +template +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; + } +} +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: + 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) { + 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 \ +bool 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); +} +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 > MODBUS_MAX_FILES) return 0; + if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0; + return this->send(slaveId, NULLREG, cb, unit, data); +}; +template \ +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 > MODBUS_MAX_FILES) return 0; + if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0; + return this->send(slaveId, NULLREG, cb, unit); +}; +template \ +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); +}; + +template \ +template \ +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); +}; + +template +template +uint16_t ModbusAPI::rawRequest(TYPEID ip, \ + const uint8_t* data, uint16_t len, + cbTransaction cb, uint8_t unit) { + free(this->_frame); + this->_frame = (uint8_t*)malloc(len); + if (!this->_frame) + return 0; + this->_len = len; + memcpy(this->_frame, data, len); + return this->send(ip, NULLREG, cb, unit); +}; + +template +template +uint16_t ModbusAPI::rawResponce(TYPEID ip, \ + const uint8_t* data, uint16_t len, uint8_t unit) { + free(this->_frame); + this->_frame = (uint8_t*)malloc(len); + if (!this->_frame) + return 0; + this->_len = len; + memcpy(this->_frame, data, len); + return this->send(ip, NULLREG, nullptr, unit, nullptr, false); +}; + +template +template +uint16_t ModbusAPI::errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit) { + this->exceptionResponse(fn, excode); + return this->send(ip, NULLREG, nullptr, unit, nullptr, false); +} diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h new file mode 100644 index 0000000..301432f --- /dev/null +++ b/src/ModbusEthernet.h @@ -0,0 +1,59 @@ +/* + Modbus Library for Arduino + ModbusTCP for W5x00 Ethernet + Copyright (C) 2022 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#if defined(MODBUSIP_USE_DNS) +#include +#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> { +#if defined(MODBUSIP_USE_DNS) + private: + static IPAddress resolver (const char* host) { + DNSClient dns; + IPAddress ip; + + dns.begin(Ethernet.dnsServerIP()); + if (dns.getHostByName(host, ip) == 1) + return ip; + else + return IPADDR_NONE; + } + public: + ModbusEthernet() : ModbusAPI() { + resolve = resolver; + } +#endif +}; diff --git a/src/ModbusIP_ESP8266.cpp b/src/ModbusIP_ESP8266.cpp deleted file mode 100644 index 1163dcf..0000000 --- a/src/ModbusIP_ESP8266.cpp +++ /dev/null @@ -1,424 +0,0 @@ -/* - ModbusIP_ESP8266.cpp - ModbusIP Library Implementation - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2019 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(uint16_t port) { - slavePort = port; - server = new WiFiServer(slavePort); - server->begin(); -} - -void ModbusIP::begin() { - slave(); -} - -bool ModbusIP::connect(IPAddress ip, uint16_t port) { - //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, 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(); - 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; - cleanup(); - if (server) { - while (server->hasClient()) { - WiFiClient* currentClient = new WiFiClient(server->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) { - client[n]->flush(); - delete client[n]; - client[n] = nullptr; - } - #endif - n = getFreeClient(); - if (n > -1) { - client[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 (!client[n]) continue; - if (!client[n]->connected()) continue; - - while (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. - while (client[n]->available()) // Drop all incoming if wrong packet - client[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); - _len--; // Subtract for read byte - for (uint8_t i = 0; 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 - 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(); - } else { - if (client[n]->localPort() == slavePort) { - // 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 (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); - 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(); - } - free(_frame); - _frame = nullptr; - _len = 0; - } - } - n = -1; -} - -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 || !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; - client[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::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 (cbDisconnect && cbEnabled) - cbDisconnect(IPADDR_NONE); - } - } - // 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; - 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 (!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() != 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() == slavePort) - 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 && client[p]->connected(); -} - -void ModbusIP::autoConnect(bool enabled) { - autoConnectMode = enabled; -} - -bool ModbusIP::disconnect(IPAddress ip) { - int8_t p = getSlave(ip); - if (p != -1) { - delete client[p]; - client[p] = nullptr; - } - return true; -} - -void ModbusIP::dropTransactions() { - for (auto &t : _trans) t.forcedEvent = EX_CANCEL; -} - -ModbusIP::~ModbusIP() { - free(_frame); - dropTransactions(); - cleanup(); - for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { - delete client[i]; - client[i] = nullptr; - } -} \ No newline at end of file diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h index 377f153..2c83146 100644 --- a/src/ModbusIP_ESP8266.h +++ b/src/ModbusIP_ESP8266.h @@ -1,125 +1,11 @@ -/* - ModbusIP_ESP8266.h - Header for ModbusIP Library - Copyright (C) 2014 Andr� Sarmento Barbosa - 2017-2019 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 -#define MODBUSIP_UNIQUE_CLIENTS - -// 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* 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; - 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 - 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 slave(uint16_t port = MODBUSIP_PORT); - void master(); - void task(); - void begin(); // 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); - */ -}; \ 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 6400fd4..a00d609 100644 --- a/src/ModbusRTU.cpp +++ b/src/ModbusRTU.cpp @@ -1,11 +1,11 @@ /* - ModbusRTU Library for ESP8266/ESP32 - Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) + Modbus Library for Arduino + ModbusRTU implementation + Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 This code is licensed under the BSD New License. See LICENSE.txt for more info. */ -#pragma once -#include +#include "ModbusRTU.h" // Table of CRC values static const uint16_t _auchCRC[] PROGMEM = { @@ -29,7 +29,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 @@ -42,122 +42,252 @@ uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { } return (CRCHi << 8) | CRCLo; } +/* +uint16_t ModbusRTUTemplate::crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen) { + uint16_t temp, temp2, flag; + temp = 0xFFFF ^ address; + for (uint8_t i = 0; i < pduLen; i++) + { + temp = temp ^ frame[i]; + for (uint8_t j = 1; j <= 8; j++) + { + flag = temp & 0x0001; + temp >>= 1; + if (flag) + temp ^= 0xA001; + } + } + // Reverse byte order. + temp2 = temp >> 8; + temp = (temp << 8) | temp2; + temp &= 0xFFFF; + return temp; +} +*/ +uint32_t ModbusRTUTemplate::charSendTime(uint32_t baud, uint8_t char_bits) { + return (uint32_t)char_bits * 1000000UL / baud; +} -bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { - uint32_t baud = port->baudRate(); - maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; - _port = port; - _txPin = txPin; - if (txPin >= 0) { - pinMode(txPin, OUTPUT); - digitalWrite(txPin, LOW); - } - if (baud > 19200) { - _t = 2; +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 { - _t = (35000/baud) + 1; + return 3.5 * charSendTime(baud, char_bits); } - return true; } -#if defined(ESP8266) -bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { - uint32_t baud = port->baudRate(); +// Kept for backward compatibility +void ModbusRTUTemplate::setBaudrate(uint32_t baud) { + setInterFrameTime(calculateMinimumInterFrameTime(baud)); +} + +void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) { + // This function sets the inter frame time. This time is the time that task() waits before considering that the frame being transmitted on the RS485 bus has finished. + // If the interframe calculated by calculateMinimumInterFrameTime() is not enough, you can set the interframe time manually with this function. + // The time must be set in micro seconds. + // This is useful when you are receiving data as a slave and you notice that the slave is dividing a frame in two or more pieces (and obviously the CRC is failing on all pieces). + // This is because it is detecting an interframe time inbetween bytes of the frame and thus it interprets one single frame as two or more frames. + // In that case it is useful to be able to set a more "permissive" interframe time. + _t = t_us; +} + +bool ModbusRTUTemplate::begin(Stream* port, int16_t txEnablePin, bool txEnableDirect) { _port = port; - if (txPin >= 0) - port->setTransmitEnablePin(txPin); - if (baud > 19200) { - _t = 2; - //port->enableIntTx(false); - } else { - _t = (35000/baud) + 1; + _t = 1750UL; +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(0); +#endif + if (txEnablePin >= 0) { + _txEnablePin = txEnablePin; + _direct = txEnableDirect; + pinMode(_txEnablePin, OUTPUT); + digitalWrite(_txEnablePin, _direct?LOW:HIGH); } return true; } -#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); - delay(1); - } +#if defined(MODBUSRTU_DEBUG) + for (uint8_t i=0 ; i < _len ; i++) { + Serial.print(_frame[i], HEX); + Serial.print(" "); + } + Serial.println(); +#endif +#if defined(MODBUSRTU_REDE) + if (_txEnablePin >= 0 || _rxPin >= 0) { + if (_txEnablePin >= 0) + digitalWrite(_txEnablePin, _direct?HIGH:LOW); + if (_rxPin >= 0) + digitalWrite(_rxPin, _direct?HIGH:LOW); +#if !defined(ESP32) + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); +#endif + } +#else + if (_txEnablePin >= 0) { + digitalWrite(_txEnablePin, _direct?HIGH:LOW); +#if !defined(ESP32) + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); +#endif + } +#endif +#if defined(ESP32) + vTaskDelay(0); +#endif _port->write(slaveId); //Send slaveId _port->write(frame, len); // Send PDU - _port->write(newCrc >> 8); //Send CRC + _port->write(newCrc >> 8); //Send CRC _port->write(newCrc & 0xFF);//Send CRC _port->flush(); - if (_txPin >= 0) - digitalWrite(_txPin, LOW); - delay(_t); - return true; +#if defined(MODBUSRTU_REDE) + if (_txEnablePin >= 0 || _rxPin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif + if (_txEnablePin >= 0) + digitalWrite(_txEnablePin, _direct?LOW:HIGH); + if (_rxPin >= 0) + digitalWrite(_rxPin, _direct?LOW:HIGH); + } +#else + if (_txEnablePin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif + digitalWrite(_txEnablePin, _direct?LOW:HIGH); + } +#endif + return true; } -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; +uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { + bool result = false; + if ((!isMaster || !_slaveId) && _len && _frame) { // Check if waiting for previous request result and _frame filled + //if (_len && _frame) { // Check if waiting for previous request result and _frame filled + rawSend(slaveId, _frame, _len); + if (waitResponse && slaveId) { + _slaveId = slaveId; + _timestamp = micros(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + } + result = true; } - return true; + free(_frame); + _frame = nullptr; + _len = 0; + return result; } -void ModbusRTU::task() { - if (_port->available() > _len) { +void ModbusRTUTemplate::task() { +#if defined(ESP32) + vTaskDelay(0); +#endif + if (_port->available() > _len) { _len = _port->available(); - t = millis(); - return; + t = micros(); } - if (_len == 0) { // No data - if (isMaster) cleanup(); - return; - } - if (millis() - t < _t) return; // Wait data whitespace + if (_len == 0) { + if (isMaster) cleanup(); + return; + } + if (isMaster) { + if (micros() - t < _t) { + return; + } + } + else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached) + uint32_t taskStart = micros(); + while (micros() - t < _t) { // Wait data whitespace + if (_port->available() > _len) { + _len = _port->available(); + t = micros(); + } + if (micros() - taskStart > MODBUSRTU_MAX_READ_US) { // Prevent from task() executed too long + return; + } + } + } - uint8_t address = _port->read(); //first byte of frame = address + bool valid_frame = true; + 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 (isMaster && _slaveId == 0) { // Check if slaveId is set + valid_frame = false; } if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check + valid_frame = false; + } + if (!valid_frame && !_cbRaw) { for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach _len = 0; + if (isMaster) cleanup(); return; - } + } + free(_frame); //Just in case _frame = (uint8_t*) malloc(_len); if (!_frame) { // Fail to allocate buffer for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if can't allocate buffer _len = 0; + if (isMaster) cleanup(); return; } - for (uint8_t i=0 ; i < _len ; i++) _frame[i] = _port->read(); // read data + crc + for (uint8_t i=0 ; i < _len ; i++) { + _frame[i] = _port->read(); // read data + crc + #if defined(MODBUSRTU_DEBUG) + Serial.print(_frame[i], HEX); + Serial.print(" "); + #endif + } + #if defined(MODBUSRTU_DEBUG) + Serial.println(); + #endif //_port->readBytes(_frame, _len); - 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 - _len = 0; // Cleanup if wrong crc - free(_frame); - _frame = nullptr; - 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 && _reply != EX_FORCE_PROCESS) { + 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 (cbEnabled && _cb) { + if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) + masterPDU(_frame, _sentFrame, _sentReg, _data); + if (_cb) { _cb((ResultCode)_reply, 0, nullptr); + _cb = nullptr; } free(_sentFrame); _sentFrame = nullptr; @@ -166,22 +296,29 @@ 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 (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) { + slavePDU(_frame); + if (address == MODBUSRTU_BROADCAST) + _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + if (_reply != Modbus::REPLY_OFF) + rawSend(address, _frame, _len); + } } - if (_reply != Modbus::REPLY_OFF) - rawSend(_slaveId, _frame, _len); // Cleanup - _len = 0; +cleanup: free(_frame); _frame = nullptr; + _len = 0; + if (isMaster) cleanup(); } - -bool ModbusRTU::cleanup() { +bool ModbusRTUTemplate::cleanup() { // Remove timeouted request and forced event - if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { - _cb(Modbus::EX_TIMEOUT, 0, nullptr); + if (_slaveId && (micros() - _timestamp > MODBUSRTU_TIMEOUT_US)) { + if (_cb) { + _cb(Modbus::EX_TIMEOUT, 0, nullptr); + _cb = nullptr; + } free(_sentFrame); _sentFrame = nullptr; _data = nullptr; @@ -189,164 +326,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 6b3153a..5d27562 100644 --- a/src/ModbusRTU.h +++ b/src/ModbusRTU.h @@ -1,39 +1,37 @@ /* - ModbusRTU Library for ESP8266/ESP32 - Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) + Modbus Library for Arduino + ModbusRTU + Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com) https://github.com/emelianov/modbus-esp8266 This code is licensed under the BSD New License. See LICENSE.txt for more info. */ #pragma once -#include -#include -#if defined(ESP8266) - #include -#endif - -#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 +#include "ModbusAPI.h" -class ModbusRTU : public Modbus { +class ModbusRTUTemplate : public Modbus { protected: Stream* _port; - int16_t _txPin; - unsigned int _t; // inter-frame delay in mS - uint32_t t = 0; + int16_t _txEnablePin = -1; +#if defined(MODBUSRTU_REDE) + int16_t _rxPin = -1; +#endif + bool _direct = true; // Transmit control logic (true=txEnableDirect, false=inverse) + uint32_t _t; // inter-frame delay in uS +#if defined(MODBUSRTU_FLUSH_DELAY) + uint32_t _t1; // char send time +#endif + uint32_t t = 0; // time sience last data byte arrived bool isMaster = false; uint8_t _slaveId; uint32_t _timestamp = 0; cbTransaction _cb = nullptr; - void* _data = nullptr; + uint8_t* _data = nullptr; uint8_t* _sentFrame = nullptr; TAddress _sentReg = COIL(0); - uint16_t maxRegs = 0x007D; - bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); + uint16_t maxRegs = MODBUS_MAX_WORDS; + uint8_t address = 0; + + uint16_t send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); // Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data // slaveId - slave id // startreg - first local register to save returned data to (miningless for write to slave operations) @@ -42,34 +40,60 @@ class ModbusRTU : 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: - #if defined(ESP8266) - bool begin(SoftwareSerial* port, int16_t txPin=-1); - #endif - bool begin(HardwareSerial* port, int16_t txPin=-1); + 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 txEnablePin = -1, bool txEnableDirect = true); +#if defined(MODBUSRTU_REDE) + template + bool begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect); +#endif + bool begin(Stream* port, int16_t txEnablePin = -1, bool txEnableDirect = true); void task(); - void 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); - 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); + void client() { isMaster = true; }; + inline void master() {client();} + void server(uint8_t serverId) {_slaveId = serverId;}; + inline void slave(uint8_t slaveId) {server(slaveId);} + uint8_t server() { return _slaveId; } + inline uint8_t slave() { return server(); } + uint32_t eventSource() override {return address;} +}; - 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 +template +bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, bool txEnableDirect) { + uint32_t baud = 0; + #if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266 + baud = port->baudRate(); + #else + baud = 9600; + #endif + setInterFrameTime(calculateMinimumInterFrameTime(baud)); +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(baud); +#endif + _port = port; + if (txEnablePin >= 0) { + _txEnablePin = txEnablePin; + _direct = txEnableDirect; + pinMode(_txEnablePin, OUTPUT); + digitalWrite(_txEnablePin, _direct?LOW:HIGH); + } + return true; +} +#if defined(MODBUSRTU_REDE) +template +bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect) { + begin(port, txEnablePin, txEnableDirect); + if (rxEnablePin > 0) { + _rxPin = rxEnablePin; + pinMode(_rxPin, OUTPUT); + digitalWrite(_rxPin, _direct?LOW:HIGH); + } + return true; +} +#endif +class ModbusRTU : public ModbusAPI {}; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h new file mode 100644 index 0000000..0a26644 --- /dev/null +++ b/src/ModbusSettings.h @@ -0,0 +1,148 @@ + +/* + Modbus Library for Arduino + + Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. + +Prefixes: +MODBUS_ Global library settings +MODBUSIP_ Settings for TCP and TLS both +MODBUSTCP_ Settings for TCP +MODBUSTLS_ Settings for TLS +MODBUSRTU_ Settings for RTU +MODBUSAPI_ Settings for API +*/ +#pragma once + +/* +#define MODBUS_GLOBAL_REGS +If defined Modbus registers will be shared across all Modbus* instances. +If not defined each Modbus object will have own registers set. +*/ +#define MODBUS_GLOBAL_REGS +//#define MODBUS_FREE_REGS + +/* +#define ARDUINO_SAM_DUE_STL +Use STL with Arduino Due. Was able to use with Arduino IDE but not with PlatformIO +Also note STL issue workaround code in Modbus.cpp +*/ +#if defined(ARDUINO_SAM_DUE) +//#define ARDUINO_SAM_DUE_STL +#endif + +/* +#define MODBUS_USE_STL +If defined C STL will be used. +*/ +#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_SAM_DUE_STL) +#define MODBUS_USE_STL +#endif + +/* +#define MODBUS_MAX_REGS 32 +If defined regisers count will be limited. +*/ +// Add limitation for specific STL implementation +#if defined(MODBUS_USE_STL) && (defined(ESP8266) || defined(ESP32)) +#undef MODBUS_MAX_REGS +#define MODBUS_MAX_REGS 4000 +#endif + +#define MODBUS_ADD_REG +//#define MODBUS_STRICT_REG +#define MODBUS_MAX_FRAME 256 +//#define MODBUS_STATIC_FRAME +#define MODBUS_MAX_WORDS 0x007D +#define MODBUS_MAX_BITS 0x07D0 +#define MODBUS_FILES +#define MODBUS_MAX_FILES 0x270F +#define MODBUSTCP_PORT 502 +#define MODBUSTLS_PORT 802 +#define MODBUSIP_MINFRAME 2 +#define MODBUSIP_MAXFRAME 200 + +/* +ModbusTCP and ModbusTLS timeouts +#define MODBUSIP_TIMEOUT 1000 +Outgoing request timeout +#define MODBUSIP_CONNECT_TIMEOUT 1000 +ESP32 only. Outgoing connection attempt timeout +*/ +#define MODBUSIP_TIMEOUT 1000 +//#define MODBUSIP_CONNECT_TIMEOUT 1000 + +#define MODBUSIP_UNIT 255 +#define MODBUSIP_MAX_TRANSACTIONS 16 +#if defined(ESP32) +#define MODBUSIP_MAX_CLIENTS 8 +#else +#define MODBUSIP_MAX_CLIENTS 4 +#endif +#define MODBUSIP_UNIQUE_CLIENTS +#define MODBUSIP_MAX_READMS 100 + +/* +Use available() instead of accept() to get TCP client +#define MODBUSIP_USE_AVAILABLE +Used to wrap variation in Ethernet/WiFi client API implementations +*/ +//#define MODBUSIP_USE_AVAILABLE + +#define MODBUSIP_FULL +//#define MODBUSIP_DEBUG +/* +Allows to use DNS names as target +Otherwise IP addresses only must be used +#define MODBUS_IP_USE_DNS +*/ +//#define MODBUS_IP_USE_DNS + +//#define MODBUSRTU_DEBUG +#define MODBUSRTU_BROADCAST 0 +#define MB_RESERVE 248 +#define MB_SERIAL_BUFFER 128 +#ifndef MODBUSRTU_TIMEOUT +#define MODBUSRTU_TIMEOUT 1000 +#endif +#define MODBUSRTU_MAX_READMS 100 +/* +#define MODBUSRTU_REDE +Enable using separate pins for RE DE +*/ +//#define MODBUSRTU_REDE + +// Define for internal use. Do not change. +#define MODBUSRTU_TIMEOUT_US 1000UL * MODBUSRTU_TIMEOUT +#define MODBUSRTU_MAX_READ_US 1000UL * MODBUSRTU_MAX_READMS + +/* +#defone MODBUSRTU_FLUSH_DELAY 1 +Set extraa delay after serial buffer flush before changing RE/DE pin state. +Specified in chars. That is 1 is means to add delay enough to send 1 char at current port baudrate +*/ +//#define MODBUSRTU_FLUSH_DELAY 1 + +#define MODBUSRTU_REDE_SWITCH_US 1000 + +#define MODBUSAPI_LEGACY +#define MODBUSAPI_OPTIONAL + +// Workaround for RP2040 flush() bug +#if defined(ARDUINO_ARCH_RP2040) +#define MODBUSRTU_FLUSH_DELAY 1 +#endif + +// Limit resources usage for entry level boards +#if defined(ARDUINO_UNO) || defined(ARDUINO_LEONARDO) +#undef MODBUS_MAX_REGS +#undef MODBUSIP_MAX_TRANSACTIONS +#undef MODBUS_MAX_WORDS +#undef MODBUS_MAX_BITS +#define MODBUS_MAX_REGS 32 +#define MODBUSIP_MAX_TRANSACTIONS 4 +#define MODBUS_MAX_WORDS 0x0020 +#define MODBUS_MAX_BITS 0x0200 +#endif \ No newline at end of file diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h new file mode 100644 index 0000000..fed76c3 --- /dev/null +++ b/src/ModbusTCP.h @@ -0,0 +1,40 @@ +/* + Modbus Library for Arduino + ModbusTCP for ESP8266/ESP32 + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#endif + +#include "ModbusAPI.h" +#include "ModbusTCPTemplate.h" + +class WiFiServerESPWrapper : public WiFiServer { + public: + WiFiServerESPWrapper(uint16_t port) : WiFiServer(port) {} + inline WiFiClient accept() { + return available(); + } +}; + +class ModbusTCP : public ModbusAPI> { +#if defined(MODBUSIP_USE_DNS) + private: + static IPAddress resolver(const char *host) { + IPAddress remote_addr; + if (WiFi.hostByName(host, remote_addr)) + return remote_addr; + return IPADDR_NONE; + } + + public: + ModbusTCP() : ModbusAPI() { + resolve = resolver; + } +#endif +}; diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h new file mode 100644 index 0000000..c78a43d --- /dev/null +++ b/src/ModbusTCPTemplate.h @@ -0,0 +1,606 @@ +/* + 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" + +#define BIT_SET(a,b) ((a) |= (1ULL<<(b))) +#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b))) +#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b)))) // '!!' to make sure this returns 0 or 1 +#ifndef IPADDR_NONE +#define IPADDR_NONE ((uint32_t)0xffffffffUL) +#endif +// Callback function Type +#if defined(MODBUS_USE_STL) +typedef std::function cbModbusConnect; +typedef std::function cbModbusResolver; +#else +typedef bool (*cbModbusConnect)(IPAddress ip); +typedef IPAddress (*cbModbusResolver)(const char*); +#endif + +struct TTransaction { + uint16_t transactionId; + uint32_t timestamp; + cbTransaction cb = nullptr; + uint8_t* _frame = nullptr; + uint8_t* data = nullptr; + TAddress startreg; + Modbus::ResultCode forcedEvent = Modbus::EX_SUCCESS; // EX_SUCCESS means no forced event here. Forced EX_SUCCESS is not possible. + bool operator ==(const TTransaction &obj) const { + return transactionId == obj.transactionId; + } +}; + +template +class ModbusTCPTemplate : public Modbus { + protected: + union MBAP_t { + struct { + uint16_t transactionId; + uint16_t protocolId; + uint16_t length; + uint8_t unitId; + }; + uint8_t raw[7]; + }; + cbModbusConnect cbConnect = nullptr; + cbModbusConnect cbDisconnect = nullptr; + SERVER* tcpserver = nullptr; + CLIENT* tcpclient[MODBUSIP_MAX_CLIENTS]; + #if MODBUSIP_MAX_CLIENTS <= 8 + uint8_t tcpServerConnection = 0; + #elif MODBUSIP_MAX_CLIENTS <= 16 + uint16_t tcpServerConnection = 0; + #else + uint32_t tcpServerConnection = 0; + #endif + #if defined(MODBUS_USE_STL) + std::vector _trans; + #else + DArray _trans; + #endif + int16_t transactionId = 1; // Last started transaction. Increments on unsuccessful transaction start too. + int8_t n = -1; + bool autoConnectMode = false; + uint16_t serverPort = 0; + uint16_t defaultPort = MODBUSTCP_PORT; + cbModbusResolver resolve = nullptr; + TTransaction* searchTransaction(uint16_t id); + void cleanupConnections(); // Free clients if not connected + void cleanupTransactions(); // Remove timedout transactions and forced event + + int8_t getFreeClient(); // Returns free slot position + int8_t getSlave(IPAddress ip); + int8_t getMaster(IPAddress ip); + public: + uint16_t send(String host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + uint16_t send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + // Prepare and send ModbusIP frame. _frame buffer and _len should be filled with Modbus data + // ip - slave ip address + // startreg - first local register to save returned data to (miningless for write to slave operations) + // cb - transaction callback function + // unit - slave modbus unit id + // data - if not null use buffer to save returned data instead of local registers + public: + ModbusTCPTemplate(); + ~ModbusTCPTemplate(); + bool isTransaction(uint16_t id); +#if defined(MODBUSIP_USE_DNS) + bool isConnected(String host); + bool isConnected(const char* host); + bool connect(String host, uint16_t port = 0); + bool connect(const char* host, uint16_t port = 0); + bool disconnect(String host); + bool disconnect(const char* host); +#endif + bool isConnected(IPAddress ip); + bool connect(IPAddress ip, uint16_t port = 0); + bool disconnect(IPAddress ip); + // ModbusTCP + void server(uint16_t port = 0); + // ModbusTCP depricated + inline void slave(uint16_t port = 0) { server(port); } // Depricated + inline void master() { client(); } // Depricated + inline void begin() { server(); }; // Depricated + void client(); + void task(); + void onConnect(cbModbusConnect cb = nullptr); + void onDisconnect(cbModbusConnect cb = nullptr); + uint32_t eventSource() override; + void autoConnect(bool enabled = true); + void dropTransactions(); + uint16_t setTransactionId(uint16_t); + #if defined(MODBUS_USE_STL) + static IPAddress defaultResolver(const char*) {return IPADDR_NONE;} + #else + static IPAddress defaultResolver(const char*) {return IPADDR_NONE;} + #endif +}; + +template +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::server(uint16_t port) { + if (port) + serverPort = port; + else + serverPort = defaultPort; + tcpserver = new SERVER(serverPort); + tcpserver->begin(); +} + +#if defined(MODBUSIP_USE_DNS) +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); +} +#endif + +template +bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { + //cleanupConnections(); + if (!ip) + return false; + if(getSlave(ip) != -1) + return true; + int8_t p = getFreeClient(); + if (p == -1) + return false; + tcpclient[p] = new CLIENT(); + BIT_CLEAR(tcpServerConnection, p); +#if defined(ESP32) && defined(MODBUSIP_CONNECT_TIMEOUT) + if (!tcpclient[p]->connect(ip, port?port:defaultPort, MODBUSIP_CONNECT_TIMEOUT)) { +#else + if (!tcpclient[p]->connect(ip, port?port:defaultPort)) { +#endif + delete(tcpclient[p]); + tcpclient[p] = nullptr; + return false; + } + return true; +} + +template +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; +} + +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); + if (it != _trans.end()) return &*it; + return nullptr; + #else + return _trans.entry(_trans.find(MODBUSIP_COMPARE_TRANS)); + #endif +} + +template +void ModbusTCPTemplate::task() { + MBAP_t _MBAP; + uint32_t taskStart = millis(); + cleanupConnections(); + if (tcpserver) { + CLIENT c; + // WiFiServer.available() == Ethernet.accept() and should wrapped to get code to be compatible with Ethernet library (See ModbusTCP.h code). + // WiFiServer.available() != Ethernet.available() internally +#if defined(MODBUSIP_USE_AVAILABLE) + while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->available())) { +#else + while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) { +#endif +#if defined(MODBUSIP_DEBUG) + Serial.println("IP: Accepted"); +#endif + CLIENT* currentClient = new CLIENT(c); + if (!currentClient || !currentClient->connected()) { + delete currentClient; + continue; + } +#if defined(MODBUSIP_DEBUG) + Serial.println("IP: Connected"); +#endif + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + #if defined(MODBUSIP_UNIQUE_CLIENTS) + // Disconnect previous connection from same IP if present + n = getMaster(currentClient->remoteIP()); + if (n != -1) { + tcpclient[n]->flush(); + delete tcpclient[n]; + tcpclient[n] = nullptr; + } + #endif + n = getFreeClient(); + if (n > -1) { + tcpclient[n] = currentClient; + BIT_SET(tcpServerConnection, n); +#if defined(MODBUSIP_DEBUG) + Serial.print("IP: Conn "); + Serial.println(n); +#endif +#if defined(MODBUSIP_USE_AVAILABLE) + break; // while +#else + continue; // while +#endif + } + } + // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached + delete currentClient; + } + } + for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + if (!tcpclient[n]) continue; + if (!tcpclient[n]->connected()) continue; + while ((size_t)tcpclient[n]->available() > sizeof(_MBAP) && millis() - taskStart < MODBUSIP_MAX_READMS) { +#if defined(MODBUSIP_DEBUG) + Serial.print(n); + Serial.print(": Bytes available "); + Serial.println(tcpclient[n]->available()); +#endif + tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP + + if (__swap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __swap is usless there. + while (tcpclient[n]->available()) // Drop all incoming if wrong packet + tcpclient[n]->read(); + continue; + } + _len = __swap_16(_MBAP.length); + if (_len < MODBUSIP_MINFRAME) { // Length is shorter than MODBUSIP_MINFRAME + Modbus::FunctionCode fc = FC_READ_COILS; // Just placeholder + while (tcpclient[n]->available()) // Drop rest of the packet + tcpclient[n]->read(); + exceptionResponse(fc, EX_ILLEGAL_VALUE); + } + _len--; // Do not count with last byte from MBAP + if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME + Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read(); + _len--; // Subtract for read byte + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet + tcpclient[n]->read(); + exceptionResponse(fc, EX_SLAVE_FAILURE); + } + else { + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read(); + _len--; // Subtract for read byte + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet + tcpclient[n]->read(); + exceptionResponse(fc, EX_SLAVE_FAILURE); + } + else { + if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame + exceptionResponse((Modbus::FunctionCode)_frame[0], EX_ILLEGAL_VALUE); + //while (tcpclient[n]->available()) // Drop all incoming (if any) + // tcpclient[n]->read(); + } + else { + _reply = EX_PASSTHROUGH; + // Note on _reply usage + // it's used and set as ReplyCode by slavePDU and as exceptionCode by masterPDU + if (_cbRaw) { + frame_arg_t transData = { _MBAP.unitId, tcpclient[n]->remoteIP(), __swap_16(_MBAP.transactionId), BIT_CHECK(tcpServerConnection, n) }; + _reply = _cbRaw(_frame, _len, &transData); + } + if (BIT_CHECK(tcpServerConnection, n)) { + if (_reply == EX_PASSTHROUGH) + slavePDU(_frame); // Process incoming frame as slave + else + _reply = REPLY_OFF; + } + else { + // Process reply to master request + TTransaction* trans = searchTransaction(__swap_16(_MBAP.transactionId)); + if (trans) { // if valid transaction id + if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested + if (_reply == EX_PASSTHROUGH) + masterPDU(_frame, trans->_frame, trans->startreg, trans->data); // Process incoming frame as master + } + else { + _reply = EX_UNEXPECTED_RESPONSE; + } + if (trans->cb) { + trans->cb((ResultCode)_reply, trans->transactionId, nullptr); + } + free(trans->_frame); + #if defined(MODBUS_USE_STL) + //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); + if (it != _trans.end()) + _trans.erase(it); + #else + size_t r = _trans.find([trans](TTransaction& t){return *trans == t;}); + _trans.remove(r); + #endif + } + } + } + } + } + if (!BIT_CHECK(tcpServerConnection, n)) _reply = REPLY_OFF; // No replay if it was responce to master + if (_reply != REPLY_OFF) { + _MBAP.length = __swap_16(_len+1); // _len+1 for last byte from MBAP + size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); + uint8_t sbuf[send_len]; + memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); + memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); + tcpclient[n]->write(sbuf, send_len); + //tcpclient[n]->flush(); + } + if (_frame) { + free(_frame); + _frame = nullptr; + } + _len = 0; + } + } + n = -1; + cleanupTransactions(); +} + +template +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, 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, uint8_t* data, bool waitResponse) { + MBAP_t _MBAP; + uint16_t result = 0; + int8_t p; +#if defined(MODBUSIP_MAX_TRANSACTIONS) + if (_trans.size() >= MODBUSIP_MAX_TRANSACTIONS) + goto cleanup; +#endif + if (!ip) + return 0; + if (tcpserver) { + p = getMaster(ip); + } else { + p = getSlave(ip); + } + if (p == -1 || !tcpclient[p]->connected()) { + if (!autoConnectMode) + goto cleanup; + if (!connect(ip)) + goto cleanup; + } + _MBAP.transactionId = __swap_16(transactionId); + _MBAP.protocolId = __swap_16(0); + _MBAP.length = __swap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.unitId = unit; + bool writeResult; + { // for sbuf isolation + size_t send_len = _len + sizeof(_MBAP.raw); + uint8_t sbuf[send_len]; + memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); + memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); + writeResult = (tcpclient[p]->write(sbuf, send_len) == send_len); + } + if (!writeResult) + goto cleanup; + //tcpclient[p]->flush(); + if (waitResponse) { + TTransaction tmp; + tmp.transactionId = transactionId; + tmp.timestamp = millis(); + tmp.cb = cb; + tmp.data = data; // BUG: Should data be saved? It may lead to memory leak or double free. + tmp._frame = _frame; + tmp.startreg = startreg; + _trans.push_back(tmp); + _frame = nullptr; + } + result = transactionId; + transactionId++; + if (!transactionId) + transactionId = 1; + cleanup: + free(_frame); + _frame = nullptr; + _len = 0; + return result; +} + +template +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() { + #if defined(MODBUS_USE_STL) + for (auto it = _trans.begin(); it != _trans.end();) { + if (millis() - it->timestamp > MODBUSIP_TIMEOUT || it->forcedEvent != Modbus::EX_SUCCESS) { + Modbus::ResultCode res = (it->forcedEvent != Modbus::EX_SUCCESS)?it->forcedEvent:Modbus::EX_TIMEOUT; + if (it->cb) + it->cb(res, it->transactionId, nullptr); + free(it->_frame); + it = _trans.erase(it); + } else + it++; + } + #else + size_t i = 0; + while (i < _trans.size()) { + TTransaction t = _trans[i]; + if (millis() - t.timestamp > MODBUSIP_TIMEOUT || t.forcedEvent != Modbus::EX_SUCCESS) { + Modbus::ResultCode res = (t.forcedEvent != Modbus::EX_SUCCESS)?t.forcedEvent:Modbus::EX_TIMEOUT; + if (t.cb) + t.cb(res, t.transactionId, nullptr); + free(t._frame); + _trans.remove(i); + } else + i++; + } + #endif +} + +template +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 && !BIT_CHECK(tcpServerConnection, i)) + 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 && BIT_CHECK(tcpServerConnection, i)) + return i; + return -1; +} + +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())); +} + +template +bool ModbusTCPTemplate::isConnected(const char* host) { + return isConnected(resolve(host)); +} +#endif + +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) { + autoConnectMode = enabled; +} + +#if defined(MODBUSIP_USE_DNS) +template +bool ModbusTCPTemplate::disconnect(String host) { + return disconnect(resolve(host.c_str())); +} + +template +bool ModbusTCPTemplate::disconnect(const char* host) { + return disconnect(resolve(host)); +} +#endif + +template +bool ModbusTCPTemplate::disconnect(IPAddress ip) { + if (!ip) + return false; + int8_t p = getSlave(ip); + if (p != -1) { + tcpclient[p]->stop(); + delete tcpclient[p]; + tcpclient[p] = nullptr; + return true; + } + return false; +} + +template +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 +ModbusTCPTemplate::~ModbusTCPTemplate() { + free(_frame); + _frame = nullptr; + dropTransactions(); + cleanupConnections(); + cleanupTransactions(); + delete tcpserver; + tcpserver = nullptr; + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + delete tcpclient[i]; + tcpclient[i] = nullptr; + } +} + +template +uint16_t ModbusTCPTemplate::setTransactionId(uint16_t t) { + transactionId = t; + if (!transactionId) + transactionId = 1; + return transactionId; +} + diff --git a/src/ModbusTLS.h b/src/ModbusTLS.h new file mode 100644 index 0000000..7f0fdf8 --- /dev/null +++ b/src/ModbusTLS.h @@ -0,0 +1,120 @@ +/* + Modbus Library for Arduino + ModbusTLS - ModbusTCP Security for ESP8266 + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once +#if !defined(ESP8266) && !defined(ESP32) +#error Unsupported architecture +#endif +#include +#if defined(ESP8266) +#include +#else +// Just emty stub +class WiFiServerSecure { +public: + WiFiServerSecure(uint16_t){} + WiFiClientSecure available(){} + void begin(); + inline WiFiClientSecure accept() { + return available(); + } +}; +#endif +#include "ModbusTCPTemplate.h" +#include "ModbusAPI.h" + +class ModbusTLS : public ModbusAPI> { + private: + int8_t _connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr) { + int8_t p = getFreeClient(); + if (p < 0) + return p; + tcpclient[p] = new WiFiClientSecure(); + BIT_CLEAR(tcpServerConnection, p); + #if defined(ESP8266) + BearSSL::X509List *clientCertList = new BearSSL::X509List(client_cert); + BearSSL::PrivateKey *clientPrivKey = new BearSSL::PrivateKey(client_private_key); + tcpclient[p]->setClientRSACert(clientCertList, clientPrivKey); + tcpclient[p]->setBufferSizes(512, 512); + #else + tcpclient[p]->setCertificate(client_cert); + tcpclient[p]->setPrivateKey(client_private_key); + #endif + return p; + } +#if defined(MODBUSIP_USE_DNS) + static IPAddress resolver (const char* host) { + IPAddress remote_addr; + if (WiFi.hostByName(host, remote_addr)) + return remote_addr; + return IPADDR_NONE; + } +#endif + public: + ModbusTLS() : ModbusAPI() { + defaultPort = MODBUSTLS_PORT; +#if defined(MODBUSIP_USE_DNS) + resolve = resolver; +#endif + } + #if defined(ESP8266) + void server(uint16_t port, const char* server_cert = nullptr, const char* server_private_key = nullptr, const char* ca_cert = nullptr) { + serverPort = port; + tcpserver = new WiFiServerSecure(serverPort); + BearSSL::X509List *serverCertList = new BearSSL::X509List(server_cert); + BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(server_private_key); + tcpserver->setRSACert(serverCertList, serverPrivKey); + if (ca_cert) { + BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert); + tcpserver->setClientTrustAnchor(trustedCA); + } + //tcpserver->setBufferSizes(512, 512); + tcpserver->begin(); + } + + bool connectWithKnownKey(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* key = nullptr) { + if(getSlave(ip) >= 0) + return true; + int8_t p = _connect(ip, port, client_cert, client_private_key); + BearSSL::PublicKey *clientPublicKey = new BearSSL::PublicKey(key); + tcpclient[p]->setKnownKey(clientPublicKey); + return tcpclient[p]->connect(ip, port); + } + + #endif +#if defined(MODBUSIP_USE_DNS) + bool connect(String host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) { + return connect(resolver(host.c_str()), port, client_cert, client_private_key, ca_cert); + } + bool connect(const char* host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) { + return connect(resolver(host), port, client_cert, client_private_key, ca_cert); + } +#endif + bool connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) { + if (!ip) + return false; + if(getSlave(ip) >= 0) + return false; + int8_t p = _connect(ip, port, client_cert, client_private_key); + if (p < 0) + return false; + #if defined(ESP8266) + if (ca_cert) { + BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert); + tcpclient[p]->setTrustAnchors(trustedCA); + } else { + tcpclient[p]->setInsecure(); + } + #else + if (ca_cert) { + tcpclient[p]->setCACert(ca_cert); + } + #endif + //return tcpclient[p]->connect(ip, port); + if (!tcpclient[p]->connect(ip, port)) + return false; + return true; + } +}; diff --git a/src/darray.h b/src/darray.h new file mode 100644 index 0000000..0b204fb --- /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 = true; + return; + } + if (i < last) + memcpy(&data[i], &data[i + 1], (last - i) * sizeof(T)); + last --; + } + T operator[](int i) { + return data[i]; + } + T* entry(size_t i) { + if (i > last) return nullptr; + return &data[i]; + } +}; \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..239ac1e --- /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 libraries +[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..efd98e4 --- /dev/null +++ b/tests/common.h @@ -0,0 +1,52 @@ +/* + 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 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 P1(&S1, &S2); +DuplexBuf P2(&S2, &S1); +#endif + +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; +} \ No newline at end of file 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/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 new file mode 100644 index 0000000..5ab2673 --- /dev/null +++ b/tests/tests.ino @@ -0,0 +1,140 @@ +/* + 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 "write.h" +#include "read.h" +#include "files.h" + + +uint8_t stage = 0; +uint16_t readHreg = 0; + +#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); + Serial.println("ModbusRTU API test"); +#if defined(HW_SERIAL) + Serial1.begin(115200, SERIAL_8N1, 18, 19); + Serial2.begin(115200, SERIAL_8N1, 22, 23); +#endif + delay(100); + master.begin((Stream*)&P1); + master.master(); + slave.begin((Stream*)&P2); + 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); + +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); + +// 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]; + 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 + { + 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, 1, NULL); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + while(P2.available()) + P2.write(P2.read()); + //slave.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + } + if (Node_1_ackStatus && Node_2_ackStatus) { + Serial.println(" PASSED"); + } else { + Serial.println(" FAILED"); + } + } + { + initFile(); + testFile(); + } +} +void loop() { + yield(); +} \ No newline at end of file diff --git a/tests/write.h b/tests/write.h new file mode 100644 index 0000000..627ce91 --- /dev/null +++ b/tests/write.h @@ -0,0 +1,179 @@ +#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