From 6bb9d8a60cdfb8c485d7a62527cba0170f88e292 Mon Sep 17 00:00:00 2001 From: fredlcore Date: Mon, 11 Dec 2023 02:03:33 +0800 Subject: [PATCH] Added Modbus library with some bugfixes --- BSB_LAN/src/modbus-esp8266/API.md | 1 + BSB_LAN/src/modbus-esp8266/README.md | 123 +++ .../src/modbus-esp8266/documentation/API.md | 296 ++++++ .../modbus-esp8266/documentation/README.md | 85 ++ .../MultipleServerID/MultipleServerID.ino | 51 + .../modbus-esp8266/examples/Bridge/README.md | 62 ++ .../TCP-to-RTU-Simulator.ino | 134 +++ .../examples/Bridge/basic/basic.ino | 53 + .../examples/Bridge/true/true.ino | 121 +++ .../IPserver-MultipleHRegDebug.ino | 79 ++ .../examples/Callback/README.md | 124 +++ .../examples/Callback/Request/Request.ino | 66 ++ .../Callback/Transactional/Transactional.ino | 61 ++ .../Callback/onGetShared/onGetShared.ino | 81 ++ .../examples/Callback/onSet/onSet.ino | 71 ++ .../examples/ClearCore/README.md | 3 + .../ClearCore/TCP-Client/TCP-Client.ino | 72 ++ .../ClearCore/TCP-Server/TCP-Server.ino | 67 ++ .../FW-Update-Source/FW-Update-Source.ino | 167 ++++ .../FW-Update-Target/FW-Update-Target.ino | 100 ++ .../modbus-esp8266/examples/Files/README.md | 62 ++ BSB_LAN/src/modbus-esp8266/examples/README.md | 36 + .../RTU/ESP32-Concurent/ESP32-Concurent.ino | 101 ++ .../src/modbus-esp8266/examples/RTU/README.MD | 57 ++ .../examples/RTU/master/master.ino | 62 ++ .../examples/RTU/masterSync/masterSync.ino | 47 + .../examples/RTU/slave/slave.ino | 38 + .../IP-server-AnalogInput.ino | 60 ++ .../TCP-ESP/IP-server-Led/IP-server-Led.ino | 56 ++ .../IP-server-SwitchStatus.ino | 51 + .../modbus-esp8266/examples/TCP-ESP/README.md | 136 +++ .../examples/TCP-ESP/client/client.ino | 55 ++ .../TCP-ESP/clientPull/clientPull.ino | 71 ++ .../TCP-ESP/clientSync/clientSync.ino | 55 ++ .../examples/TCP-ESP/server/server.ino | 51 + .../examples/TCP-Ethernet/README.md | 15 + .../examples/TCP-Ethernet/client/client.ino | 52 + .../examples/TCP-Ethernet/server/server.ino | 37 + .../src/modbus-esp8266/examples/TLS/README.md | 47 + .../modbus-esp8266/examples/TLS/certs/ca.conf | 11 + .../examples/TLS/certs/ca_cer.pem | 17 + .../examples/TLS/certs/ca_cer.srl | 1 + .../examples/TLS/certs/ca_key.pem | 27 + .../examples/TLS/certs/cert.cmd | 17 + .../examples/TLS/certs/client.cmd | 1 + .../examples/TLS/certs/client.conf | 7 + .../examples/TLS/certs/client1_cer.pem | 17 + .../examples/TLS/certs/client1_key.pem | 27 + .../examples/TLS/certs/client1_req.csr | 15 + .../examples/TLS/certs/server.cmd | 1 + .../examples/TLS/certs/server.conf | 16 + .../examples/TLS/certs/server_cer.pem | 17 + .../examples/TLS/certs/server_key.pem | 27 + .../examples/TLS/certs/server_pubkey.pem | 9 + .../examples/TLS/certs/server_req.csr | 15 + .../examples/TLS/client/client.ino | 161 +++ .../examples/TLS/server/server.ino | 142 +++ BSB_LAN/src/modbus-esp8266/library.properties | 9 + .../src/modbus-esp8266/resources/client.png | Bin 0 -> 30688 bytes .../src/modbus-esp8266/resources/client.uml | 21 + .../src/modbus-esp8266/resources/server.png | Bin 0 -> 36036 bytes .../src/modbus-esp8266/resources/server.uml | 26 + BSB_LAN/src/modbus-esp8266/src/Modbus.cpp | 926 ++++++++++++++++++ BSB_LAN/src/modbus-esp8266/src/Modbus.h | 358 +++++++ BSB_LAN/src/modbus-esp8266/src/ModbusAPI.h | 438 +++++++++ .../src/modbus-esp8266/src/ModbusEthernet.h | 43 + .../src/modbus-esp8266/src/ModbusIP_ESP8266.h | 11 + BSB_LAN/src/modbus-esp8266/src/ModbusRTU.cpp | 325 ++++++ BSB_LAN/src/modbus-esp8266/src/ModbusRTU.h | 99 ++ .../src/modbus-esp8266/src/ModbusSettings.h | 136 +++ BSB_LAN/src/modbus-esp8266/src/ModbusTCP.h | 40 + .../modbus-esp8266/src/ModbusTCPTemplate.h | 586 +++++++++++ BSB_LAN/src/modbus-esp8266/src/ModbusTLS.h | 120 +++ BSB_LAN/src/modbus-esp8266/src/darray.h | 70 ++ BSB_LAN/src/modbus-esp8266/tests/README.md | 6 + BSB_LAN/src/modbus-esp8266/tests/common.h | 52 + BSB_LAN/src/modbus-esp8266/tests/files.h | 62 ++ BSB_LAN/src/modbus-esp8266/tests/read.h | 134 +++ BSB_LAN/src/modbus-esp8266/tests/tests.ino | 140 +++ BSB_LAN/src/modbus-esp8266/tests/write.h | 179 ++++ 80 files changed, 7215 insertions(+) create mode 100644 BSB_LAN/src/modbus-esp8266/API.md create mode 100644 BSB_LAN/src/modbus-esp8266/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/documentation/API.md create mode 100644 BSB_LAN/src/modbus-esp8266/documentation/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Bridge/MultipleServerID/MultipleServerID.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Bridge/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Bridge/basic/basic.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Bridge/true/true.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Callback/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Callback/Request/Request.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Callback/Transactional/Transactional.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Callback/onGetShared/onGetShared.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Callback/onSet/onSet.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/ClearCore/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/ClearCore/TCP-Client/TCP-Client.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/ClearCore/TCP-Server/TCP-Server.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Files/FW-Update-Source/FW-Update-Source.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Files/FW-Update-Target/FW-Update-Target.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/Files/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/RTU/README.MD create mode 100644 BSB_LAN/src/modbus-esp8266/examples/RTU/master/master.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/RTU/masterSync/masterSync.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/RTU/slave/slave.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/client/client.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/clientPull/clientPull.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/clientSync/clientSync.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/server/server.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/client/client.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/server/server.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca.conf create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_cer.pem create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_cer.srl create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_key.pem create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/cert.cmd create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client.cmd create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client.conf create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_cer.pem create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_key.pem create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_req.csr create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server.cmd create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server.conf create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_cer.pem create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_key.pem create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_pubkey.pem create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_req.csr create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/client/client.ino create mode 100644 BSB_LAN/src/modbus-esp8266/examples/TLS/server/server.ino create mode 100644 BSB_LAN/src/modbus-esp8266/library.properties create mode 100644 BSB_LAN/src/modbus-esp8266/resources/client.png create mode 100644 BSB_LAN/src/modbus-esp8266/resources/client.uml create mode 100644 BSB_LAN/src/modbus-esp8266/resources/server.png create mode 100644 BSB_LAN/src/modbus-esp8266/resources/server.uml create mode 100644 BSB_LAN/src/modbus-esp8266/src/Modbus.cpp create mode 100644 BSB_LAN/src/modbus-esp8266/src/Modbus.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusAPI.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusEthernet.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusIP_ESP8266.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusRTU.cpp create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusRTU.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusSettings.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusTCP.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusTCPTemplate.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/ModbusTLS.h create mode 100644 BSB_LAN/src/modbus-esp8266/src/darray.h create mode 100644 BSB_LAN/src/modbus-esp8266/tests/README.md create mode 100644 BSB_LAN/src/modbus-esp8266/tests/common.h create mode 100644 BSB_LAN/src/modbus-esp8266/tests/files.h create mode 100644 BSB_LAN/src/modbus-esp8266/tests/read.h create mode 100644 BSB_LAN/src/modbus-esp8266/tests/tests.ino create mode 100644 BSB_LAN/src/modbus-esp8266/tests/write.h diff --git a/BSB_LAN/src/modbus-esp8266/API.md b/BSB_LAN/src/modbus-esp8266/API.md new file mode 100644 index 00000000..bcfb971a --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/API.md @@ -0,0 +1 @@ +Moved to [documentation](documentation) \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/README.md b/BSB_LAN/src/modbus-esp8266/README.md new file mode 100644 index 00000000..6d5cf568 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/README.md @@ -0,0 +1,123 @@ +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +For detailes on the library usage visit [documentation](documentation) section. + +## Features + +* 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) + * 0x03 - Read Holding Registers + * 0x04 - Read Input Registers + * 0x05 - Write Single Coil + * 0x06 - Write Single Register + * 0x0F - Write Multiple Coils + * 0x10 - Write Multiple Registers + * 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. 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 +// 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-DEV +- 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-DEV +- ModbusTLS: ESP32 Server +- Test: TLS ESP32 Server +- Test: TLS ESP32 Client +- Examples: TLS Certificate test Role extension and Alt-Name +- Examples: TLS Add example explanation +- ModbusTCP: ModbusAsyncTCP +- API: Extend API to allow custom Modbus commands +- Examples: Basic file operations +- Examples: Revising +``` +## 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/BSB_LAN/src/modbus-esp8266/documentation/API.md b/BSB_LAN/src/modbus-esp8266/documentation/API.md new file mode 100644 index 00000000..30ecb5be --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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 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); +``` + +### Modbus RTU Specific API + +```c +bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); +bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true); +bool begin(Stream* port); +``` + +Assing Serial port. txPin controls transmit enable for MAX-485. Pass direct=false if txPin 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 onDisonnect(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/BSB_LAN/src/modbus-esp8266/documentation/README.md b/BSB_LAN/src/modbus-esp8266/documentation/README.md new file mode 100644 index 00000000..d493b930 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Bridge/MultipleServerID/MultipleServerID.ino b/BSB_LAN/src/modbus-esp8266/examples/Bridge/MultipleServerID/MultipleServerID.ino new file mode 100644 index 00000000..278a99b6 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Bridge/README.md b/BSB_LAN/src/modbus-esp8266/examples/Bridge/README.md new file mode 100644 index 00000000..ad763d3e --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/Bridge/README.md @@ -0,0 +1,62 @@ +# 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 Modbus header + uint32_t ipaddr; // IP address from which frame is received + uint16_t transactionId; // TransactionId as passed im Modbus header + }; + }; +}; +typedef std::function cbRaw; // Callback function Type for STL +typedef ResultCode (*cbRaw)(uint8_t* frame, uint8 len, void* data); // Callback function Type +bool onRaw(cbRaw cb = nullptr); +``` +- `frame` Modbus payload frame with stripped MBAP, slaveid and crc +- `len` frame size in bytes +- `data` Pointer to frame_arg_t filled with frame header information +*Returns:* +- If a special error code `Modbus::EX_PASSTHROU` returned frame will be processed normally +- 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/BSB_LAN/src/modbus-esp8266/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino b/BSB_LAN/src/modbus-esp8266/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino new file mode 100644 index 00000000..5b949479 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -0,0 +1,134 @@ +/* + ModbusRTU ESP8266/ESP32 + ModbusTCP to ModbusRTU bridge with on-device ModbusRTU simulator +*/ +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include +#include +//#include +//SoftwareSerial S(13, 15); +#include +#define BSIZE 1024 +uint8_t buf1[BSIZE]; +uint8_t buf2[BSIZE]; +StreamBuf S1(buf1, BSIZE); +StreamBuf S2(buf2, BSIZE); +DuplexBuf P1(&S1, &S2); +DuplexBuf P2(&S2, &S1); +ModbusRTU sym; + +int DE_RE = 2; + +ModbusRTU rtu; +ModbusTCP tcp; + +IPAddress srcIp; + + +uint16_t transRunning = 0; // Currently executed ModbusTCP transaction +uint8_t slaveRunning = 0; // Current request slave + +bool cbTcpTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place + tcp.disconnect(tcp.eventSource()); // Close connection + } + return true; +} + +bool cbRtuTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + return true; +} + + +// Callback receives raw data +Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + + Serial.print("TCP IP: "); + Serial.print(IPAddress(src->ipaddr)); + Serial.printf(" Fn: %02X, len: %d \n\r", data[0], len); + + if (transRunning) { // Note that we can't process new requests from TCP-side while waiting for responce from RTU-side. + tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + tcp.errorResponce(src->ipaddr, (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); + return Modbus::EX_SLAVE_DEVICE_BUSY; + } + + rtu.rawRequest(slaveRunning, data, len, cbRtuTrans); + + if (src->unitId) { + tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + + //uint16_t succeed = tcp.rawResponce(src->ipaddr, data, len, slaveRunning); + + tcp.errorResponce(src->ipaddr, (Modbus::FunctionCode)data[0], Modbus::EX_ACKNOWLEDGE); + return Modbus::EX_ACKNOWLEDGE; + } + + srcIp = src->ipaddr; + + slaveRunning = src->slaveId; + + transRunning = src->transactionId; + + return Modbus::EX_SUCCESS; + +} + + +// Callback receives raw data from ModbusTCP and sends it on behalf of slave (slaveRunning) to master +Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + uint16_t succeed = tcp.rawResponce(srcIp, data, len, slaveRunning); + if (!succeed){ + Serial.print("fail"); + } + Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); + Serial.print("Response TCP IP: "); + Serial.println(srcIp); + + transRunning = 0; + slaveRunning = 0; + return Modbus::EX_PASSTHROUGH; +} + + +void setup() { + Serial.begin(115000); + WiFi.begin("E2", "*****"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + tcp.server(); // Initialize ModbusTCP to pracess as server + tcp.onRaw(cbTcpRaw); // Assign raw data processing callback + + //S.begin(19200, SWSERIAL_8E1); + //rtu.begin(&S, DE_RE); // Specify RE_DE control pin + sym.begin((Stream*)&P2); + sym.slave(1); + sym.addHreg(1, 100); + rtu.begin((Stream*)&P1); // Specify RE_DE control pin + rtu.master(); // Initialize ModbusRTU as master + rtu.onRaw(cbRtuRaw); // Assign raw data processing callback +} + +void loop() { + sym.task(); + rtu.task(); + tcp.task(); + yield(); +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/Bridge/basic/basic.ino b/BSB_LAN/src/modbus-esp8266/examples/Bridge/basic/basic.ino new file mode 100644 index 00000000..acf71c00 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Bridge/true/true.ino b/BSB_LAN/src/modbus-esp8266/examples/Bridge/true/true.ino new file mode 100644 index 00000000..b4d32368 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino b/BSB_LAN/src/modbus-esp8266/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino new file mode 100644 index 00000000..7f717b15 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino @@ -0,0 +1,79 @@ +/* + Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)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/BSB_LAN/src/modbus-esp8266/examples/Callback/README.md b/BSB_LAN/src/modbus-esp8266/examples/Callback/README.md new file mode 100644 index 00000000..6a7e0d92 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Callback/Request/Request.ino b/BSB_LAN/src/modbus-esp8266/examples/Callback/Request/Request.ino new file mode 100644 index 00000000..fea3deaa --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Callback/Transactional/Transactional.ino b/BSB_LAN/src/modbus-esp8266/examples/Callback/Transactional/Transactional.ino new file mode 100644 index 00000000..68fd532f --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/Callback/Transactional/Transactional.ino @@ -0,0 +1,61 @@ +/* + Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) + Write multiple coils to Slave device + + (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +const int REG = 100; // Modbus Coils Offset +const int COUNT = 5; // Count of Coils +IPAddress remote(192, 168, 20, 102); // Address of Modbus Slave device + +ModbusIP mb; // ModbusIP object + +void setup() { + Serial.begin(115200); + + WiFi.begin(); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.client(); +} + +bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X\n", event); // Display Modbus error code + if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place + mb.disconnect(remote); // Close connection to slave and + mb.dropTransactions(); // Cancel all waiting transactions + } + return true; +} + +bool res[COUNT] = {false, true, false, true, true}; + +void loop() { + if (!mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.connect(remote); // Try to connect if no connection + Serial.print("."); + } + if (!mb.writeCoil(remote, REG, res, COUNT, cb)) // Try to Write array of COUNT of Coils to Modbus Slave + Serial.print("#"); + mb.task(); // Modbus task + delay(50); // Pushing interval +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/Callback/onGetShared/onGetShared.ino b/BSB_LAN/src/modbus-esp8266/examples/Callback/onGetShared/onGetShared.ino new file mode 100644 index 00000000..554e114c --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/Callback/onGetShared/onGetShared.ino @@ -0,0 +1,81 @@ +/* + Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32) + + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Used Pins +#ifdef ESP8266 + uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; +#else //ESP32 + uint8_t pinList[] = {12, 13, 14, 14, 16, 17, 18, 21, 22, 23}; +#endif +#define LEN sizeof(pinList)/sizeof(uint8_t) +#define COIL_BASE 0 +//ModbusIP object +ModbusIP mb; + +// Callback function to read corresponding DI +uint16_t cbRead(TRegister* reg, uint16_t val) { + // Checking value of register address which callback is called on. + // See Modbus.h for TRegister and TAddress definition + if(reg->address.address < COIL_BASE) + return 0; + uint8_t offset = reg->address.address - COIL_BASE; + if(offset >= LEN) + return 0; + return COIL_VAL(digitalRead(pinList[offset])); +} +// Callback function to write-protect DI +uint16_t cbWrite(TRegister* reg, uint16_t val) { + return reg->value; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("ssid", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + for (uint8_t i = 0; i < LEN; i++) + pinMode(pinList[i], INPUT); + mb.onConnect(cbConn); // Add callback on connection event + mb.server(); + + mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. + mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add single callback for multiple Coils. It will be called for each of these coils value get + mb.onSetCoil(COIL_BASE, cbWrite, LEN); // The same as above just for set value +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(10); +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/Callback/onSet/onSet.ino b/BSB_LAN/src/modbus-esp8266/examples/Callback/onSet/onSet.ino new file mode 100644 index 00000000..d604f498 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/Callback/onSet/onSet.ino @@ -0,0 +1,71 @@ +/* + Modbus-Arduino Example - Test Led using callback (Modbus IP ESP8266/ESP32) + Control a Led on D4 pin using Write Single Coil Modbus Function + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Modbus Registers Offsets +const int LED_COIL = 100; +//Used Pins +#ifdef ESP8266 + const int ledPin = D4; // Builtin ESP8266 LED +#else + const int ledPin = TX; // ESP32 TX LED +#endif +//ModbusIP object +ModbusIP mb; + +// Callback function for write (set) Coil. Returns value to store. +uint16_t cbLed(TRegister* reg, uint16_t val) { + //Attach ledPin to LED_COIL register + digitalWrite(ledPin, COIL_BOOL(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("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.server(); + + pinMode(ledPin, OUTPUT); + mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) + mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(10); +} diff --git a/BSB_LAN/src/modbus-esp8266/examples/ClearCore/README.md b/BSB_LAN/src/modbus-esp8266/examples/ClearCore/README.md new file mode 100644 index 00000000..f2a5f214 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/ClearCore/TCP-Client/TCP-Client.ino b/BSB_LAN/src/modbus-esp8266/examples/ClearCore/TCP-Client/TCP-Client.ino new file mode 100644 index 00000000..a93862f6 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/ClearCore/TCP-Server/TCP-Server.ino b/BSB_LAN/src/modbus-esp8266/examples/ClearCore/TCP-Server/TCP-Server.ino new file mode 100644 index 00000000..abf844c2 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Files/FW-Update-Source/FW-Update-Source.ino b/BSB_LAN/src/modbus-esp8266/examples/Files/FW-Update-Source/FW-Update-Source.ino new file mode 100644 index 00000000..5be22313 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Files/FW-Update-Target/FW-Update-Target.ino b/BSB_LAN/src/modbus-esp8266/examples/Files/FW-Update-Target/FW-Update-Target.ino new file mode 100644 index 00000000..58649290 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/Files/README.md b/BSB_LAN/src/modbus-esp8266/examples/Files/README.md new file mode 100644 index 00000000..078d7b3e --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/README.md b/BSB_LAN/src/modbus-esp8266/examples/README.md new file mode 100644 index 00000000..c82ba512 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/BSB_LAN/src/modbus-esp8266/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino new file mode 100644 index 00000000..bc6f858f --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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. */ + "Task1", /* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 1); /* pin task to core 1 */ + +} + +uint16_t hregs1[REG_NUM]; +void loop1( void * pvParameters ){ + while(true) { + delay(10); + if (readSync(SLAVE_ID1, REG, REG_NUM, hregs1) == Modbus::EX_SUCCESS) + Serial.println("OK 2"); + else + Serial.println("Error 2"); + } +} + +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); +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/RTU/README.MD b/BSB_LAN/src/modbus-esp8266/examples/RTU/README.MD new file mode 100644 index 00000000..6f4d1b7d --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/RTU/README.MD @@ -0,0 +1,57 @@ +This example is introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally there is [example of master](ESP32-Concurent) device for multithread usage with ESP32. + +## [Concurent thread-safe access to Modbus object](ESP32-Concurent) + +## [Simple ModbusRTU master](master) + +## [Simple ModbusRTU slave](slave) + +## [Sync ModbusRTU master](masterSync) + +## Modbus RTU Specific API + +```c +bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true); +bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true); +bool begin(Stream* port); +``` + +- `port` Pointer to Serial port +- `txPin` RX/TX control pin. Not assigned (assume auto RX/TX) by default +- `direct` Direct (true, default) or inverse (false) RX/TX pin control. + +Assing Serial port. txPin controls transmit enable for MAX-485. + +```c +void setBaudrte(uint32 baud); +``` + +- `baud` New baudrate. + +Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. + +```c +void client(); +void server(uint8_t slaveId); +void slave(); // Depricated +void master(uint8_t slaveId); // Depricated +``` + +- `slaveId` Modbus slave id to associate to. + +Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictible. + +```c +uint8_t client(); +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 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/BSB_LAN/src/modbus-esp8266/examples/RTU/master/master.ino b/BSB_LAN/src/modbus-esp8266/examples/RTU/master/master.ino new file mode 100644 index 00000000..d4d8c236 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/RTU/master/master.ino @@ -0,0 +1,62 @@ +/* + 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 +#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 + +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); + #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); + mb.begin(&Serial1); + mb.setBaudrate(9600); + #endif + mb.master(); +} + +bool coils[20]; + +void loop() { + if (!mb.slave()) { + mb.readCoil(1, 1, coils, 20, cbWrite); + } + mb.task(); + yield(); +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/RTU/masterSync/masterSync.ino b/BSB_LAN/src/modbus-esp8266/examples/RTU/masterSync/masterSync.ino new file mode 100644 index 00000000..75a46142 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/RTU/slave/slave.ino b/BSB_LAN/src/modbus-esp8266/examples/RTU/slave/slave.ino new file mode 100644 index 00000000..4a968080 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/RTU/slave/slave.ino @@ -0,0 +1,38 @@ +/* + 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 + +#define REGN 10 +#define SLAVE_ID 1 + +ModbusRTU mb; + +void setup() { + 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); +} + +void loop() { + mb.task(); + yield(); +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino new file mode 100644 index 00000000..0e39a5f0 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino @@ -0,0 +1,60 @@ +/* + Modbus-Arduino Example - Test Analog Input (Modbus IP ESP8266) + Read Analog sensor on Pin ADC (ADC input between 0 ... 1V) + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Modbus Registers Offsets +const int SENSOR_IREG = 100; + +//ModbusIP object +ModbusIP mb; + +long ts; + +void setup() { + Serial.begin(115200); + + WiFi.begin("your_ssid", "your_password"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.server(); //Start Modbus IP + // Add SENSOR_IREG register - Use addIreg() for analog Inputs + mb.addIreg(SENSOR_IREG); + + ts = millis(); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + + //Read each two seconds + if (millis() > ts + 2000) { + ts = millis(); + //Setting raw value (0-1024) + mb.Ireg(SENSOR_IREG, analogRead(A0)); + } + delay(10); +} diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino new file mode 100644 index 00000000..5155711c --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino @@ -0,0 +1,56 @@ +/* + Modbus-Arduino Example - Test Led (Modbus IP ESP8266) + Control a Led on GPIO0 pin using Write Single Coil Modbus Function + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Modbus Registers Offsets +const int LED_COIL = 100; +//Used Pins +const int ledPin = 0; //GPIO0 + +//ModbusIP object +ModbusIP mb; + +void setup() { + Serial.begin(115200); + + WiFi.begin("your_ssid", "your_password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.server(); + + pinMode(ledPin, OUTPUT); + mb.addCoil(LED_COIL); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + + //Attach ledPin to LED_COIL register + digitalWrite(ledPin, mb.Coil(LED_COIL)); + delay(10); +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino new file mode 100644 index 00000000..6a7b3392 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino @@ -0,0 +1,51 @@ +/* + Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) + Read Switch Status on pin GPIO0 + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +//Modbus Registers Offsets +const int SWITCH_ISTS = 100; +//Used Pins +const int switchPin = 0; //GPIO0 + +//ModbusIP object +ModbusIP mb; + +void setup() { + Serial.begin(115200); + + WiFi.begin("your_ssid", "your_password"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + //Config Modbus IP + mb.server(); + //Set ledPin mode + pinMode(switchPin, INPUT); + // Add SWITCH_ISTS register - Use addIsts() for digital inputs + mb.addIsts(SWITCH_ISTS); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + + //Attach switchPin to SWITCH_ISTS register + mb.Ists(SWITCH_ISTS, digitalRead(switchPin)); + delay(10); +} diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/README.md b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/README.md new file mode 100644 index 00000000..429cc836 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/README.md @@ -0,0 +1,136 @@ +# ESP8266/ESP32 TCP Examples + +## [Basic client](client.ino) + +## [Client with blocking read operation](clientSync.ino) + +## [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/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/client/client.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/client/client.ino new file mode 100644 index 00000000..22d3de79 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/client/client.ino @@ -0,0 +1,55 @@ +/* + Modbus-Arduino Example - Master Modbus IP Client (ESP8266/ESP32) + Read Holding Register from Server device + + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + +const int REG = 528; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device +const int LOOP_COUNT = 10; + +ModbusIP mb; //ModbusIP object + +void setup() { + Serial.begin(115200); + + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.client(); +} + +uint16_t res = 0; +uint8_t show = LOOP_COUNT; + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + } + mb.task(); // Common local Modbus task + delay(100); // Pulling interval + if (!show--) { // Display Slave register value one time per second (with default settings) + Serial.println(res); + show = LOOP_COUNT; + } +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/clientPull/clientPull.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/clientPull/clientPull.ino new file mode 100644 index 00000000..2098004c --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/clientPull/clientPull.ino @@ -0,0 +1,71 @@ +/* + Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) + Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function + + (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#endif +#include + + +const int LED_COIL = 1; // Modbus Coil Offset +IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device + +//Used Pins +#ifdef ESP8266 + #define USE_LED D4 + #else + #define UES_LED TX + #endif + +ModbusIP mb; //ModbusIP object + +uint16_t gc(TRegister* r, uint16_t v) { // Callback function + if (r->value != v) { // Check if Coil state is going to be changed + Serial.print("Set reg: "); + Serial.println(v); + if (COIL_BOOL(v)) { + digitalWrite(USE_LED, LOW); + } else { + digitalWrite(USE_LED, HIGH); + } + } + return v; +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("SSID", "password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.client(); // Initialize local Modbus Client + pinMode(USE_LED, OUTPUT); + mb.addCoil(LED_COIL); // Add Coil + mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil +} + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave + } else { + mb.connect(remote); // Try to connect if no connection + } + mb.task(); // Common local Modbus task + delay(10); // Polling interval +} diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/clientSync/clientSync.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/clientSync/clientSync.ino new file mode 100644 index 00000000..03c22139 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/server/server.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/server/server.ino new file mode 100644 index 00000000..1cf09776 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-ESP/server/server.ino @@ -0,0 +1,51 @@ +/* + Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) + Configure Holding Register (offset 100) with initial value 0xABCD + You can get or set this holding register + Original library + Copyright by André Sarmento Barbosa + http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +// Modbus Registers Offsets +const int TEST_HREG = 100; + + +//ModbusIP object +ModbusIP mb; + +void setup() { + Serial.begin(115200); + + WiFi.begin("your_ssid", "your_password"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.server(); + mb.addHreg(TEST_HREG, 0xABCD); +} + +void loop() { + //Call once inside loop() - all magic here + mb.task(); + delay(10); +} diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/README.md b/BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/README.md new file mode 100644 index 00000000..8857af7f --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/client/client.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/client/client.ino new file mode 100644 index 00000000..74d46601 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/client/client.ino @@ -0,0 +1,52 @@ +/* + ModbusTCP for W5x00 Ethernet library + Basic Client code example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include +#include // Ethernet library v2 is required +#include + +const uint16_t REG = 512; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 12); // Address of Modbus Slave device +const int32_t showDelay = 5000; // Show result every n'th mellisecond + +// Enter a MAC address and IP address for your controller below. +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE +}; +IPAddress ip(192, 168, 30, 178); // The IP address will be dependent on your local network: +ModbusEthernet mb; // Declare ModbusTCP instance + +void setup() { + Serial.begin(115200); // Open serial communications and wait for port to open + #if defined(AVR_LEONARDO) + while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only + #endif + Ethernet.init(5); // SS pin + Ethernet.begin(mac, ip); // start the Ethernet connection + delay(1000); // give the Ethernet shield a second to initialize + mb.client(); // Act as Modbus TCP server +} + +uint16_t res = 0; +uint32_t showLast = 0; + +void loop() { +if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Slave + } else { + mb.connect(remote); // Try to connect if not connected + } + delay(100); // Pulling interval + mb.task(); // Common local Modbus task + if (millis() - showLast > showDelay) { // Display register value every 5 seconds (with default settings) + showLast = millis(); + Serial.println(res); + } +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/server/server.ino b/BSB_LAN/src/modbus-esp8266/examples/TCP-Ethernet/server/server.ino new file mode 100644 index 00000000..769e7cd7 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/README.md b/BSB_LAN/src/modbus-esp8266/examples/TLS/README.md new file mode 100644 index 00000000..ede543f8 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca.conf b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca.conf new file mode 100644 index 00000000..c48c6ab5 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_cer.pem b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_cer.pem new file mode 100644 index 00000000..14a4793f --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_cer.srl b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_cer.srl new file mode 100644 index 00000000..bc42790b --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_cer.srl @@ -0,0 +1 @@ +4221D52EC27B0A950D9F41EFC7D20A43100E437D diff --git a/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_key.pem b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/ca_key.pem new file mode 100644 index 00000000..d9ad72ba --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/cert.cmd b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/cert.cmd new file mode 100644 index 00000000..65e35674 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client.cmd b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client.cmd new file mode 100644 index 00000000..719b5f49 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client.conf b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client.conf new file mode 100644 index 00000000..522e43c4 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_cer.pem b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_cer.pem new file mode 100644 index 00000000..39a04604 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_key.pem b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_key.pem new file mode 100644 index 00000000..2427ca82 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_req.csr b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/client1_req.csr new file mode 100644 index 00000000..cc79f2d9 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server.cmd b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server.cmd new file mode 100644 index 00000000..fe80478b --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server.conf b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server.conf new file mode 100644 index 00000000..d8a56b8b --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_cer.pem b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_cer.pem new file mode 100644 index 00000000..c6efd5e1 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_key.pem b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_key.pem new file mode 100644 index 00000000..73eccb20 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_pubkey.pem b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_pubkey.pem new file mode 100644 index 00000000..06548c24 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_req.csr b/BSB_LAN/src/modbus-esp8266/examples/TLS/certs/server_req.csr new file mode 100644 index 00000000..42b13d8f --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/client/client.ino b/BSB_LAN/src/modbus-esp8266/examples/TLS/client/client.ino new file mode 100644 index 00000000..1186a66d --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/examples/TLS/server/server.ino b/BSB_LAN/src/modbus-esp8266/examples/TLS/server/server.ino new file mode 100644 index 00000000..226d3cb5 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/library.properties b/BSB_LAN/src/modbus-esp8266/library.properties new file mode 100644 index 00000000..765f44a4 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/library.properties @@ -0,0 +1,9 @@ +name=modbus-esp8266 +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=* diff --git a/BSB_LAN/src/modbus-esp8266/resources/client.png b/BSB_LAN/src/modbus-esp8266/resources/client.png new file mode 100644 index 0000000000000000000000000000000000000000..5d6b825ed391083ea638a99d915a521c7b8ff9db GIT binary patch literal 30688 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 G^#5OTUX5h{ literal 0 HcmV?d00001 diff --git a/BSB_LAN/src/modbus-esp8266/resources/client.uml b/BSB_LAN/src/modbus-esp8266/resources/client.uml new file mode 100644 index 00000000..3f0b5381 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/resources/server.png b/BSB_LAN/src/modbus-esp8266/resources/server.png new file mode 100644 index 0000000000000000000000000000000000000000..f8082e9004e515d1201cdc6185c642c022ee6e5e GIT binary patch literal 36036 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 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(), MODBUS_COMPARE_CB); + if (it != _callbacks.end()) { + newVal = it->cb(reg, newVal); + it++; + } + } while (it != _callbacks.end()); +#else + size_t r = 0; + do { + r = _callbacks.find(MODBUS_COMPARE_CB, r); + if (r < _callbacks.size()) + newVal = _callbacks[r].cb(reg, newVal); + r++; + } while (r < _callbacks.size()); +#endif + return newVal; +} + +TRegister* Modbus::searchRegister(TAddress address) { +#define MODBUS_COMPARE_REG [address](TRegister& addr){return (addr.address == address);} +#if defined(MODBUS_USE_STL) + std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), MODBUS_COMPARE_REG); + if (it != _regs.end()) return &*it; +#else + size_t r = _regs.find(MODBUS_COMPARE_REG); + if (r < _regs.size()) return _regs.entry(r); +#endif + return nullptr; +} + +bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) { + #if defined(MODBUS_MAX_REGS) + if (_regs.size() + numregs > MODBUS_MAX_REGS) return false; + #endif + if (0xFFFF - address.address < numregs) + numregs = 0xFFFF - address.address; + for (uint16_t i = 0; i < numregs; i++) { + if (!searchRegister(address + i)) + _regs.push_back({address + i, value}); + } + //std::sort(_regs.begin(), _regs.end()); + return true; +} + +bool Modbus::Reg(TAddress address, uint16_t value) { + TRegister* reg; + reg = searchRegister(address); //search for the register address + if (reg) { //if found then assign the register value to the new value. + if (cbEnabled) { + reg->value = callback(reg, value, TCallback::ON_SET); + } else { + reg->value = value; + } + return true; + } else + return false; +} + +uint16_t Modbus::Reg(TAddress address) { + TRegister* reg; + reg = searchRegister(address); + if(reg) + if (cbEnabled) { + return callback(reg, reg->value, TCallback::ON_GET); + } else { + return reg->value; + } + else + return 0; +} + +bool Modbus::removeReg(TAddress address, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + if (0xFFFF - address.address < numregs) + numregs = 0xFFFF - address.address; + for (uint16_t i = 0; i < numregs; i++) { + reg = searchRegister(address + i); + if (reg) { + atLeastOne = true; + removeOnSet(address + i); + removeOnGet(address + i); + #if defined(MODBUS_USE_STL) + _regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() ); + #else + _regs.remove(_regs.find(MODBUS_COMPARE_REG)); + #endif + } + } + return atLeastOne; +} + +bool Modbus::addReg(TAddress address, uint16_t* value, uint16_t numregs) { + if (0xFFFF - address.address < numregs) + numregs = 0xFFFF - address.address; + for (uint16_t k = 0; k < numregs; k++) + addReg(address + k, value[k]); + return true; +} + +void Modbus::slavePDU(uint8_t* frame) { + FunctionCode fcode = (FunctionCode)frame[0]; + uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; + uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; + uint16_t field3 = 0; + uint16_t field4 = 0; + uint16_t bytecount_calc; + uint16_t k; + ResultCode ex; + switch (fcode) { + case FC_WRITE_REG: + //field1 = reg, field2 = value + ex = _onRequest(fcode, {HREG(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (!Reg(HREG(field1), field2)) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + if (Reg(HREG(field1)) != field2) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + _reply = REPLY_ECHO; + _onRequestSuccess(fcode, {HREG(field1), field2}); + break; + + case FC_READ_REGS: + //field1 = startreg, field2 = numregs, header len = 3 + ex = _onRequest(fcode, {HREG(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + ex = readWords(HREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {HREG(field1), field2}); + break; + + case FC_WRITE_REGS: + //field1 = startreg, field2 = numregs, frame[5] = data lenght, header len = 6 + ex = _onRequest(fcode, {HREG(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || 0xFFFF - field1 < field2 || frame[5] != 2 * field2) { //Check constrains + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(HREG(field1) + k)) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + } + if (!setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + successResponce(HREG(field1), field2, fcode); + _reply = REPLY_NORMAL; + _onRequestSuccess(fcode, {HREG(field1), field2}); + break; + + case FC_READ_COILS: + //field1 = startreg, field2 = numregs + ex = _onRequest(fcode, {COIL(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + ex = readBits(COIL(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {COIL(field1), field2}); + break; + + case FC_READ_INPUT_STAT: + //field1 = startreg, field2 = numregs + ex = _onRequest(fcode, {ISTS(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + ex = readBits(ISTS(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {ISTS(field1), field2}); + break; + + case FC_READ_INPUT_REGS: + //field1 = startreg, field2 = numregs + ex = _onRequest(fcode, {IREG(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + ex = readWords(IREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {IREG(field1), field2}); + break; + + case FC_WRITE_COIL: + //field1 = reg, field2 = status, header len = 3 + ex = _onRequest(fcode, {COIL(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (field2 != 0xFF00 && field2 != 0x0000) { //Check value (status) + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + if (!Reg(COIL(field1), field2)) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + if (Reg(COIL(field1)) != field2) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + _reply = REPLY_ECHO; + _onRequestSuccess(fcode, {COIL(field1), field2}); + break; + + case FC_WRITE_COILS: + //field1 = startreg, field2 = numregs, frame[5] = bytecount, header len = 6 + ex = _onRequest(fcode, {COIL(field1), field2}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + bytecount_calc = field2 / 8; + if (field2%8) bytecount_calc++; + if (field2 < 0x0001 || field2 > MODBUS_MAX_BITS || 0xFFFF - field1 < field2 || frame[5] != bytecount_calc) { //Check registers range and data size maches + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(COIL(field1) + k)) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + } + if (!setMultipleBits(frame + 6, COIL(field1), field2)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + successResponce(COIL(field1), field2, fcode); + _reply = REPLY_NORMAL; + _onRequestSuccess(fcode, {COIL(field1), field2}); + break; + #if defined(MODBUS_FILES) + case FC_READ_FILE_REC: + if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + { + uint8_t bufSize = 2; // 2 bytes for frame header + uint8_t* recs = frame + 2; // Begin of sub-recs blocks + uint8_t recsCount = frame[1] / 7; // Count of sub-rec blocks + for (uint8_t p = 0; p < recsCount; p++) { // Calc output buffer size required + //uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2]; + uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4]; + uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6]; + //Serial.printf("%d, %d, %d\n", fileNum, recNum, recLen); + if (recs[0] != 0x06 || recNum > 0x270F) { // Wrong ref type or count of records + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + bufSize += recLen * 2 + 2; // 4 bytes for header + data + recs += 7; + } + if (bufSize > MODBUS_MAX_FRAME) { // Frame to return too large + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + uint8_t* srcFrame = _frame; + _frame = (uint8_t*)malloc(bufSize); + if (!_frame) { + free(srcFrame); + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + _len = bufSize; + recs = frame + 2; // Begin of sub-recs blocks + uint8_t* data = _frame + 2; + for (uint8_t p = 0; p < recsCount; p++) { + uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2]; + uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4]; + uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6]; + ResultCode res = fileOp(fcode, fileNum, recNum, recLen, data + 2); + if (res != EX_SUCCESS) { // File read failed + free(srcFrame); + exceptionResponse(fcode, res); + return; + } + data[0] = recLen * 2 + 1; + data[1] = 0x06; + data += recLen * 2 + 2; + recs += 7; + } + _frame[0] = fcode; + _frame[1] = bufSize; + _reply = REPLY_NORMAL; + free(srcFrame); + } + break; + case FC_WRITE_FILE_REC: { + if (frame[1] < 0x09 || frame[1] > 0xFB) { // Wrong request data size + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + uint8_t* recs = frame + 2; // Begin of sub-recs blocks + while (recs < frame + frame[1]) { + if (recs[0] != 0x06) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2]; + uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4]; + uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6]; + if (recs + recLen * 2 > frame + frame[1]) { + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + ResultCode res = fileOp(fcode, fileNum, recNum, recLen, recs + 7); + if (res != EX_SUCCESS) { // File write failed + exceptionResponse(fcode, res); + return; + } + recs += 7 + recLen * 2; + } + } + _reply = REPLY_ECHO; + break; + #endif + case FC_MASKWRITE_REG: + //field1 = reg, field2 = AND mask, field3 = OR mask + // Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) + field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; + ex = _onRequest(fcode, {HREG(field1), field2, field3}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + field4 = Reg(HREG(field1)); + field4 = (field4 & field2) | (field3 & ~field2); + if (!Reg(HREG(field1), field4)) { //Check Address and execute (reg exists?) + exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); + return; + } + if (Reg(HREG(field1)) != field4) { //Check for failure + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + _reply = REPLY_ECHO; + _onRequestSuccess(fcode, {HREG(field1), field2, field3}); + break; + case FC_READWRITE_REGS: + //field1 = readreg, field2 = read count, frame[9] = data lenght, header len = 10 + //field3 = wtitereg, field4 = write count + field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6]; + field4 = (uint16_t)frame[7] << 8 | (uint16_t)frame[8]; + ex = _onRequest(fcode, {HREG(field1), field2, HREG(field3), field4}); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || + field4 < 0x0001 || field4 > MODBUS_MAX_WORDS || + 0xFFFF - field1 < field2 || 0xFFFF - field1 < field2 || + frame[9] != 2 * field4) { //Check value + exceptionResponse(fcode, EX_ILLEGAL_VALUE); + return; + } + if (!setMultipleWords((uint16_t*)(frame + 10), HREG(field3), field4)) { + exceptionResponse(fcode, EX_SLAVE_FAILURE); + return; + } + ex = readWords(HREG(field1), field2, fcode); + if (ex != EX_SUCCESS) { + exceptionResponse(fcode, ex); + return; + } + _onRequestSuccess(fcode, {HREG(field1), field2, HREG(field3), field4}); + break; + + default: + exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + return; + } +} + +void Modbus::successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return; + } + _frame[0] = fn; + _frame[1] = startreg.address >> 8; + _frame[2] = startreg.address & 0x00FF; + _frame[3] = numoutputs >> 8; + _frame[4] = numoutputs & 0x00FF; +} + +void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) { + free(_frame); + _len = 2; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return; + } + _frame[0] = fn + 0x80; + _frame[1] = excode; + _reply = REPLY_NORMAL; +} + +void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs) { + uint8_t bitn = 0; + uint16_t i = 0; + while (numregs--) { + if (BIT_BOOL(Reg(startreg))) + bitSet(frame[i], bitn); + else + bitClear(frame[i], bitn); + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + startreg++; //increment the register + } +} + +void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { + for (uint8_t i = 0; i < numregs; i++) { + frame[i] = __swap_16(Reg(startreg + i)); + } +} + +Modbus::ResultCode Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { + if (numregs < 0x0001 || numregs > MODBUS_MAX_BITS || (0xFFFF - startreg.address) < numregs) + return EX_ILLEGAL_VALUE; + //Check Address + //Check only startreg. Is this correct? + //When I check all registers in range I got errors in ScadaBR + //I think that ScadaBR request more than one in the single request + //when you have more then one datapoint configured from same type. +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_VALUE; + } +#else + if (!searchRegister(startreg)) + return EX_ILLEGAL_VALUE; +#endif + free(_frame); + //Determine the message length = function type, byte count and + //for each group of 8 registers the message length increases by 1 + _len = 2 + numregs/8; + if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. + _frame = (uint8_t*) malloc(_len); + if (!_frame) + return EX_SLAVE_FAILURE; + _frame[0] = fn; + _frame[1] = _len - 2; //byte count (_len - function code and byte count) + _frame[_len - 1] = 0; //Clean last probably partial byte + getMultipleBits(_frame+2, startreg, numregs); + _reply = REPLY_NORMAL; + return EX_SUCCESS; +} + +Modbus::ResultCode Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { + //Check value (numregs) + if (numregs < 0x0001 || numregs > MODBUS_MAX_WORDS || 0xFFFF - startreg.address < numregs) + return EX_ILLEGAL_VALUE; +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_VALUE; + } +#else + if (!searchRegister(startreg)) + return EX_ILLEGAL_ADDRESS; +#endif + free(_frame); + _len = 2 + numregs * 2; //calculate the query reply message length. 2 bytes per register + 2 bytes for header + _frame = (uint8_t*) malloc(_len); + if (!_frame) + return EX_SLAVE_FAILURE; + _frame[0] = fn; + _frame[1] = _len - 2; //byte count + getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs); + _reply = REPLY_NORMAL; + return EX_SUCCESS; +} + +bool Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) { + uint8_t bitn = 0; + uint16_t i = 0; + bool result = true; + while (numoutputs--) { + Reg(startreg, BIT_VAL(bitRead(frame[i], bitn))); + if (Reg(startreg) != BIT_VAL(bitRead(frame[i], bitn))) + result = false; + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + startreg++; //increment the register + } + return result; +} + +bool Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { + bool result = true; + for (uint8_t i = 0; i < numregs; i++) { + Reg(startreg + i, __swap_16(frame[i])); + if (Reg(startreg + i) != __swap_16(frame[i])) + result = false; + } + return result; +} + +bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + if (!cb) { + return removeOnGet(address); + } + while (numregs > 0) { + reg = searchRegister(address); + if (reg) { + _callbacks.push_back({TCallback::ON_GET, address, cb}); + atLeastOne = true; + } + address++; + numregs--; + } + return atLeastOne; +} +bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + if (!cb) { + return removeOnGet(address); + } + while (numregs > 0) { + reg = searchRegister(address); + if (reg) { + _callbacks.push_back({TCallback::ON_SET, address, cb}); + atLeastOne = true; + } + address++; + numregs--; + } + return atLeastOne; +} + +bool Modbus::removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb, uint16_t numregs) { + size_t s = _callbacks.size(); + #if defined(MODBUS_USE_STL) + #define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){\ + return entry.type == t && entry.address == address \ + && (!cb || std::addressof(cb) == std::addressof(entry.cb));} + while(numregs--) { + _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ON), _callbacks.end()); + address++; + } + #else + #define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){ \ + return entry.type == t && entry.address == address \ + && (!cb || entry.cb == cb);} + while(numregs--) { + size_t r = 0; + do { + r = _callbacks.find(MODBUS_COMPARE_ON); + _callbacks.remove(r); + } while (r < _callbacks.size()); + address++; + } + #endif + return s == _callbacks.size(); +} +bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) { + return removeOn(TCallback::ON_SET, address, cb, numregs); +} + +bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) { + return removeOn(TCallback::ON_GET, address, cb, numregs); +} + +bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return false; + } + _frame[0] = fn; + _frame[1] = address >> 8; + _frame[2] = address & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + return true; +} + +bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data) { + free(_frame); + _len = 6 + numregs/8; + if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return false; + } + _frame[0] = fn; + _frame[1] = to >> 8; + _frame[2] = to & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + _frame[5] = _len - 6; + _frame[_len - 1] = 0; //Clean last probably partial byte + if (data) { + boolToBits(_frame + 6, data, numregs); + } else { + getMultipleBits(_frame + 6, startreg, numregs); + } + _reply = REPLY_NORMAL; + return true; +} + +bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data) { + free(_frame); + _len = 6 + 2 * numregs; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return false; + } + _frame[0] = fn; + _frame[1] = to >> 8; + _frame[2] = to & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + _frame[5] = _len - 6; + if (data) { + uint16_t* frame = (uint16_t*)(_frame + 6); + for (uint8_t i = 0; i < numregs; i++) { + frame[i] = __swap_16(data[i]); + } + } else { + getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs); + } + return true; +} + +void Modbus::boolToBits(uint8_t* dst, bool* src, uint16_t numregs) { + uint8_t bitn = 0; + uint16_t i = 0; + uint16_t j = 0; + while (numregs--) { + if (src[j]) + bitSet(dst[i], bitn); + else + bitClear(dst[i], bitn); + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + j++; //increment the register + } +} + +void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { + uint8_t bitn = 0; + uint16_t i = 0; + uint16_t j = 0; + while (numregs--) { + dst[j] = bitRead(src[i], bitn); + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + j++; //increment the register + } +} + +void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output) { + uint8_t fcode = frame[0]; + if ((fcode & 0x80) != 0) { // Check if error responce + _reply = frame[1]; + return; + } + if (fcode != sourceFrame[0]) { // Check if responce matches the request + _reply = EX_DATA_MISMACH; + return; + } + _reply = EX_SUCCESS; + uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; + uint8_t bytecount_calc; + switch (fcode) { + case FC_READ_REGS: + case FC_READ_INPUT_REGS: + case FC_READWRITE_REGS: + //field2 = numregs, frame[1] = data lenght, header len = 2 + if (frame[1] != 2 * field2) { //Check if data size matches + _reply = EX_DATA_MISMACH; + break; + } + if (output) { + uint16_t* from = (uint16_t*)(frame + 2); + uint16_t* to = (uint16_t*)output; + while(field2--) { + *(to++) = __swap_16(*(from++)); + } + } else { + setMultipleWords((uint16_t*)(frame + 2), startreg, field2); + } + break; + case FC_READ_COILS: + case FC_READ_INPUT_STAT: + //field2 = numregs, frame[1] = data length, header len = 2 + bytecount_calc = field2 / 8; + if (field2 % 8) bytecount_calc++; + if (frame[1] != bytecount_calc) { // check if data size matches + _reply = EX_DATA_MISMACH; + break; + } + if (output) { + bitsToBool((bool*)output, frame + 2, field2); + } else { + setMultipleBits(frame + 2, startreg, field2); + } + break; + #if defined(MODBUS_FILES) + case FC_READ_FILE_REC: + // Should check if byte order swap needed + if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size + _reply = EX_ILLEGAL_VALUE; + return; + } + { + uint8_t* data = frame + 2; + uint8_t* eoFrame = frame + frame[1]; + while (data < eoFrame) { + //data[0] - sub-req length + //data[1] = 0x06 + if (data[1] != 0x06 || data[0] < 0x07 || data[0] > 0xF5 || data + data[0] > eoFrame) { // Wrong request data size + _reply = EX_ILLEGAL_VALUE; + return; + } + memcpy(output, data + 2, data[0]); + data += data[0] + 1; + output += data[0] - 1; + } + } + break; + case FC_WRITE_FILE_REC: + #endif + case FC_WRITE_REG: + case FC_WRITE_REGS: + case FC_WRITE_COIL: + case FC_WRITE_COILS: + case FC_MASKWRITE_REG: + break; + + default: + _reply = EX_GENERAL_FAILURE; + } +} + +void Modbus::cbEnable(bool state) { + cbEnabled = state; +} +void Modbus::cbDisable() { + cbEnable(false); +} +Modbus::~Modbus() { + free(_frame); +} + +#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; +} +Modbus::ResultCode Modbus::fileOp(Modbus::FunctionCode fc, uint16_t fileNum, uint16_t recNum, uint16_t recLen, uint8_t* frame) { + if (!_onFile) return EX_ILLEGAL_ADDRESS; + return _onFile(fc, fileNum, recNum, recLen, frame); +} + + bool Modbus::readSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn) { + _len = count * 7 + 2; + if (_len > MODBUS_MAX_FRAME) return false; + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) return false; + _frame[0] = fn; + _frame[1] = _len - 2; + uint8_t* subReq = _frame + 2; + for (uint8_t i = 0; i < count; i++) { + subReq[0] = 0x06; + subReq[1] = fileNum[i] >> 8; + subReq[2] = fileNum[i] & 0x00FF; + subReq[3] = startRec[i] >> 8; + subReq[4] = startRec[i] & 0x00FF; + subReq[5] = len[i] >> 8; + subReq[6] = len[i] & 0x00FF; + subReq += 7; + } + return true; + } + bool Modbus::writeSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn, uint8_t* data) { + _len = 2; + for (uint8_t i = 0; i < count; i++) { + _len += len[i] * 2 + 7; + } + if (_len > MODBUS_MAX_FRAME) return false; + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) return false; + _frame[0] = fn; + _frame[1] = _len - 2; + uint8_t* subReq = _frame + 2; + for (uint8_t i = 0; i < count; i++) { + subReq[0] = 0x06; + subReq[1] = fileNum[i] >> 8; + subReq[2] = fileNum[i] & 0x00FF; + subReq[3] = startRec[i] >> 8; + subReq[4] = startRec[i] & 0x00FF; + subReq[5] = len[i] >> 8; + subReq[6] = len[i] & 0x00FF; + uint8_t clen = len[i] * 2; + memcpy(subReq + 7, data, clen); + subReq += 7 + clen; + data += clen; + } + return true; + } + #endif + +bool Modbus::onRaw(cbRaw cb) { + _cbRaw = cb; + return true; +} +Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, const RequestData data) { + return EX_SUCCESS; +} +bool Modbus::onRequest(cbRequest cb) { + _onRequest = cb; + return true; +} +#if defined (MODBUSAPI_OPTIONAL) +bool Modbus::onRequestSuccess(cbRequest cb) { + _onRequestSuccess = cb; + return true; +} +#endif + +#if defined(ARDUINO_SAM_DUE_STL) +namespace std { + void __throw_bad_function_call() { + Serial.println(F("STL ERROR - __throw_bad_function_call")); + __builtin_unreachable(); + } +} +#endif \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/src/Modbus.h b/BSB_LAN/src/modbus-esp8266/src/Modbus.h new file mode 100644 index 00000000..018cc54c --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/Modbus.h @@ -0,0 +1,358 @@ +/* + Modbus Library for Arduino + Core functions + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2022 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once +#include "ModbusSettings.h" +#include "Arduino.h" +#if defined(MODBUS_USE_STL) + #include + #include + #include + #include +#else + #include "darray.h" +#endif + +static inline uint16_t __swap_16(uint16_t num) { return (num >> 8) | (num << 8); } + +#define COIL(n) (TAddress){TAddress::COIL, n} +#define ISTS(n) (TAddress){TAddress::ISTS, n} +#define IREG(n) (TAddress){TAddress::IREG, n} +#define HREG(n) (TAddress){TAddress::HREG, n} +#define BIT_VAL(v) (v?0xFF00:0x0000) +#define BIT_BOOL(v) (v==0xFF00) +#define COIL_VAL(v) (v?0xFF00:0x0000) +#define COIL_BOOL(v) (v==0xFF00) +#define ISTS_VAL(v) (v?0xFF00:0x0000) +#define ISTS_BOOL(v) (v==0xFF00) + +// For depricated (v1.xx) onSet/onGet format compatibility +#define cbDefault nullptr + +struct TRegister; +#if defined(MODBUS_USE_STL) +typedef std::function 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}; + RegType type; + uint16_t address; + bool operator==(const TAddress &obj) const { // TAddress == TAddress + return type == obj.type && address == obj.address; + } + bool operator!=(const TAddress &obj) const { // TAddress != TAddress + return type != obj.type || address != obj.address; + } + TAddress& operator++() { // ++TAddress + address++; + return *this; + } + TAddress operator++(int) { // TAddress++ + TAddress result(*this); + ++(*this); + return result; + } + TAddress& operator+=(const int& inc) { // TAddress += integer + address += inc; + return *this; + } + const TAddress operator+(const int& inc) const { // TAddress + integer + TAddress result(*this); + result.address += inc; + return result; + } + bool isCoil() { + return type == COIL; + } + bool isIsts() { + return type == ISTS; + } + bool isIreg() { + return type == IREG; + } + bool isHreg() { + return type == HREG; + } +}; + +struct TCallback { + enum CallbackType {ON_SET, ON_GET}; + CallbackType type; + TAddress address; + cbModbus cb; +}; + +struct TRegister { + TAddress address; + uint16_t value; + bool operator ==(const TRegister &obj) const { + return address == obj.address; + } +}; + +class Modbus { + public: + //Function Codes + enum FunctionCode { + FC_READ_COILS = 0x01, // Read Coils (Output) Status + FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) + FC_READ_REGS = 0x03, // Read Holding Registers + FC_READ_INPUT_REGS = 0x04, // Read Input Registers + FC_WRITE_COIL = 0x05, // Write Single Coil (Output) + FC_WRITE_REG = 0x06, // Preset Single Register + FC_DIAGNOSTICS = 0x08, // Not implemented. Diagnostics (Serial Line only) + FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) + FC_WRITE_REGS = 0x10, // Write block of contiguous registers + FC_READ_FILE_REC = 0x14, // Read File Record + FC_WRITE_FILE_REC = 0x15, // Write File Record + FC_MASKWRITE_REG = 0x16, // Mask Write Register + FC_READWRITE_REGS = 0x17 // Read/Write Multiple registers + }; + //Exception Codes + //Custom result codes used internally and for callbacks but never used for Modbus responce + enum ResultCode { + EX_SUCCESS = 0x00, // Custom. No error + EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported + EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists + EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range + EX_SLAVE_FAILURE = 0x04, // Slave or Master Device Fails to process request + EX_ACKNOWLEDGE = 0x05, // Not used + EX_SLAVE_DEVICE_BUSY = 0x06, // Not used + EX_MEMORY_PARITY_ERROR = 0x08, // Not used + EX_PATH_UNAVAILABLE = 0x0A, // Not used + EX_DEVICE_FAILED_TO_RESPOND = 0x0B, // Not used + EX_GENERAL_FAILURE = 0xE1, // Custom. Unexpected master error + EX_DATA_MISMACH = 0xE2, // Custom. Inpud data size mismach + EX_UNEXPECTED_RESPONSE = 0xE3, // Custom. Returned result doesn't mach transaction + EX_TIMEOUT = 0xE4, // Custom. Operation not finished within reasonable time + EX_CONNECTION_LOST = 0xE5, // Custom. Connection with device lost + EX_CANCEL = 0xE6, // Custom. Transaction/request canceled + EX_PASSTHROUGH = 0xE7, // Custom. Raw callback. Indicate to normal processing on callback exit + EX_FORCE_PROCESS = 0xE8 // Custom. Raw callback. Indicate to force processing on callback exit + }; + union RequestData { + struct { + TAddress reg; + uint16_t regCount; + }; + struct { + TAddress regRead; + uint16_t regReadCount; + TAddress regWrite; + uint16_t regWriteCount; + }; + struct { + TAddress regMask; + uint16_t andMask; + uint16_t orMask; + }; + 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; + }; + }; + + struct frame_arg_t { + bool to_server; + union { + uint8_t slaveId; + struct { + uint8_t unitId; + uint32_t ipaddr; + uint16_t transactionId; + }; + }; + frame_arg_t(uint8_t s, bool m = false) { + slaveId = s; + to_server = m; + }; + frame_arg_t(uint8_t u, uint32_t a, uint16_t t, bool m = false) { + unitId = u; + ipaddr = a; + transactionId = t; + to_server = m; + }; + }; + + ~Modbus(); + + void cbEnable(bool state = true); + void cbDisable(); + + private: + ResultCode readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); + ResultCode readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); + + bool setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); + bool setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs); + + void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); + void getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs); + + void bitsToBool(bool* dst, uint8_t* src, uint16_t numregs); + void boolToBits(uint8_t* dst, bool* src, uint16_t numregs); + + protected: + //Reply Types + enum ReplyCode { + REPLY_OFF = 0x01, + REPLY_ECHO = 0x02, + REPLY_NORMAL = 0x03, + REPLY_ERROR = 0x04, + REPLY_UNEXPECTED = 0x05 + }; + #if defined(MODBUS_USE_STL) + #if defined(MODBUS_GLOBAL_REGS) + static std::vector _regs; + static std::vector _callbacks; + #if defined(MODBUS_FILES) + static std::function _onFile; + #endif + #else + std::vector _regs; + std::vector _callbacks; + #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); + virtual TRegister* searchRegister(TAddress addr); + void exceptionResponse(FunctionCode fn, ResultCode excode); // Fills _frame with response + void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); // Fills frame with response + void slavePDU(uint8_t* frame); //For Slave + void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output = nullptr); //For Master + // frame - data received form slave + // sourceFrame - data have sent fo slave + // startreg - local register to start put data to + // output - if not null put data to the buffer insted local registers. output assumed to by array of uint16_t or boolean + + bool readSlave(uint16_t address, uint16_t numregs, FunctionCode fn); + bool writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data = nullptr); + bool writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data = nullptr); + // startreg - local register to get data from + // to - slave register to write data to + // numregs - number of registers + // fn - Modbus function + // data - if null use local registers. Otherwise use data from array to erite to slave + bool removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); + public: + bool addReg(TAddress address, uint16_t value = 0, uint16_t numregs = 1); + bool Reg(TAddress address, uint16_t value); + uint16_t Reg(TAddress address); + bool removeReg(TAddress address, uint16_t numregs = 1); + bool addReg(TAddress address, uint16_t* value, uint16_t numregs = 1); + bool Reg(TAddress address, uint16_t* value, uint16_t numregs = 1); + + bool onGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); + + virtual uint32_t eventSource() {return 0;} + #if defined(MODBUS_USE_STL) + typedef std::function 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/BSB_LAN/src/modbus-esp8266/src/ModbusAPI.h b/BSB_LAN/src/modbus-esp8266/src/ModbusAPI.h new file mode 100644 index 00000000..ff65f8d9 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/ModbusAPI.h @@ -0,0 +1,438 @@ +/* + 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: +/* // New API + template + uint16_t write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t write(TYPEID id, TAddress reg, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t write(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + template + uint16_t push(TYPEID id, TAddress to, TAddress from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +*/ + // Legacy API + 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, uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t rawResponce(TYPEID ip, uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT); +}; + +// FNAME writeCoil, writeIsts, writeHreg, writeIreg +// 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, 0x07D0, bool) +IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, 0x007D, 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, 0x07D0, bool) +IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, 0x007D, uint16_t) +IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0, bool) +IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, 0x007D, uint16_t) + +#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, 0x07D0) +IMPLEMENT_PULL(pullIsts, ISTS, FC_READ_INPUT_STAT, 0x07D0) +IMPLEMENT_PULL(pullHreg, HREG, FC_READ_REGS, 0x007D) +IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, 0x007D) +IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, 0x007D) +IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, 0x07D0) + +#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM, 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, 0x7D0, writeSlaveBits) +IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, 0x007D, writeSlaveWords) +IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, 0x007D, writeSlaveWords) +IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, 0x07D0, writeSlaveBits) + +template \ +bool ModbusAPI::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { + 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 > 0x270F) return 0; + if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0; + return this->send(slaveId, HREG(0), cb, unit, data); // HREG(0) - just dummy value +}; +template \ +template \ +uint16_t ModbusAPI::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { + if (startRec > 0x270F) return 0; + if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0; + return this->send(slaveId, HREG(0), cb, unit); // HREG(0) - just dummy value +}; +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, \ + uint8_t* data, uint16_t len, + cbTransaction cb, uint8_t unit) { + free(this->_frame); + this->_frame = (uint8_t*)malloc(len); + if (!this->_frame) + return 0; + this->_len = len; + memcpy(this->_frame, data, len); + return this->send(ip, HREG(0), cb, unit); +}; + +template +template +uint16_t ModbusAPI::rawResponce(TYPEID ip, \ + uint8_t* data, uint16_t len, uint8_t unit) { + free(this->_frame); + this->_frame = (uint8_t*)malloc(len); + if (!this->_frame) + return 0; + this->_len = len; + memcpy(this->_frame, data, len); + return this->send(ip, HREG(0), nullptr, unit, nullptr, false); +}; + +template +template +uint16_t ModbusAPI::errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit) { + this->exceptionResponse(fn, excode); + return this->send(ip, HREG(0), nullptr, unit, nullptr, false); +} diff --git a/BSB_LAN/src/modbus-esp8266/src/ModbusEthernet.h b/BSB_LAN/src/modbus-esp8266/src/ModbusEthernet.h new file mode 100644 index 00000000..54c02ffe --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/ModbusEthernet.h @@ -0,0 +1,43 @@ +/* + 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" + +// Ethernet class wrapper to be able to compile for ESP32 +class EthernetServerWrapper : public EthernetServer { + public: + EthernetServerWrapper(uint16_t port) : EthernetServer(port) { + + } + void begin(uint16_t port=0) { + + } +}; + +class ModbusEthernet : public ModbusAPI> { +#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/BSB_LAN/src/modbus-esp8266/src/ModbusIP_ESP8266.h b/BSB_LAN/src/modbus-esp8266/src/ModbusIP_ESP8266.h new file mode 100644 index 00000000..2c831461 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/ModbusIP_ESP8266.h @@ -0,0 +1,11 @@ +/* + 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/BSB_LAN/src/modbus-esp8266/src/ModbusRTU.cpp b/BSB_LAN/src/modbus-esp8266/src/ModbusRTU.cpp new file mode 100644 index 00000000..92af99ee --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/ModbusRTU.cpp @@ -0,0 +1,325 @@ +/* + Modbus Library for Arduino + ModbusRTU implementation + Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ +#include "ModbusRTU.h" + +// Table of CRC values +static const uint16_t _auchCRC[] PROGMEM = { + 0x0000, 0xC1C0, 0x81C1, 0x4001, 0x01C3, 0xC003, 0x8002, 0x41C2, 0x01C6, 0xC006, 0x8007, 0x41C7, 0x0005, 0xC1C5, 0x81C4, + 0x4004, 0x01CC, 0xC00C, 0x800D, 0x41CD, 0x000F, 0xC1CF, 0x81CE, 0x400E, 0x000A, 0xC1CA, 0x81CB, 0x400B, 0x01C9, 0xC009, + 0x8008, 0x41C8, 0x01D8, 0xC018, 0x8019, 0x41D9, 0x001B, 0xC1DB, 0x81DA, 0x401A, 0x001E, 0xC1DE, 0x81DF, 0x401F, 0x01DD, + 0xC01D, 0x801C, 0x41DC, 0x0014, 0xC1D4, 0x81D5, 0x4015, 0x01D7, 0xC017, 0x8016, 0x41D6, 0x01D2, 0xC012, 0x8013, 0x41D3, + 0x0011, 0xC1D1, 0x81D0, 0x4010, 0x01F0, 0xC030, 0x8031, 0x41F1, 0x0033, 0xC1F3, 0x81F2, 0x4032, 0x0036, 0xC1F6, 0x81F7, + 0x4037, 0x01F5, 0xC035, 0x8034, 0x41F4, 0x003C, 0xC1FC, 0x81FD, 0x403D, 0x01FF, 0xC03F, 0x803E, 0x41FE, 0x01FA, 0xC03A, + 0x803B, 0x41FB, 0x0039, 0xC1F9, 0x81F8, 0x4038, 0x0028, 0xC1E8, 0x81E9, 0x4029, 0x01EB, 0xC02B, 0x802A, 0x41EA, 0x01EE, + 0xC02E, 0x802F, 0x41EF, 0x002D, 0xC1ED, 0x81EC, 0x402C, 0x01E4, 0xC024, 0x8025, 0x41E5, 0x0027, 0xC1E7, 0x81E6, 0x4026, + 0x0022, 0xC1E2, 0x81E3, 0x4023, 0x01E1, 0xC021, 0x8020, 0x41E0, 0x01A0, 0xC060, 0x8061, 0x41A1, 0x0063, 0xC1A3, 0x81A2, + 0x4062, 0x0066, 0xC1A6, 0x81A7, 0x4067, 0x01A5, 0xC065, 0x8064, 0x41A4, 0x006C, 0xC1AC, 0x81AD, 0x406D, 0x01AF, 0xC06F, + 0x806E, 0x41AE, 0x01AA, 0xC06A, 0x806B, 0x41AB, 0x0069, 0xC1A9, 0x81A8, 0x4068, 0x0078, 0xC1B8, 0x81B9, 0x4079, 0x01BB, + 0xC07B, 0x807A, 0x41BA, 0x01BE, 0xC07E, 0x807F, 0x41BF, 0x007D, 0xC1BD, 0x81BC, 0x407C, 0x01B4, 0xC074, 0x8075, 0x41B5, + 0x0077, 0xC1B7, 0x81B6, 0x4076, 0x0072, 0xC1B2, 0x81B3, 0x4073, 0x01B1, 0xC071, 0x8070, 0x41B0, 0x0050, 0xC190, 0x8191, + 0x4051, 0x0193, 0xC053, 0x8052, 0x4192, 0x0196, 0xC056, 0x8057, 0x4197, 0x0055, 0xC195, 0x8194, 0x4054, 0x019C, 0xC05C, + 0x805D, 0x419D, 0x005F, 0xC19F, 0x819E, 0x405E, 0x005A, 0xC19A, 0x819B, 0x405B, 0x0199, 0xC059, 0x8058, 0x4198, 0x0188, + 0xC048, 0x8049, 0x4189, 0x004B, 0xC18B, 0x818A, 0x404A, 0x004E, 0xC18E, 0x818F, 0x404F, 0x018D, 0xC04D, 0x804C, 0x418C, + 0x0044, 0xC184, 0x8185, 0x4045, 0x0187, 0xC047, 0x8046, 0x4186, 0x0182, 0xC042, 0x8043, 0x4183, 0x0041, 0xC181, 0x8180, + 0x4040, 0x0000 +}; + +uint16_t ModbusRTUTemplate::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { + uint8_t i = 0xFF ^ address; + uint16_t val = pgm_read_word(_auchCRC + i); + uint8_t CRCHi = 0xFF ^ highByte(val); // Hi + uint8_t CRCLo = lowByte(val); //Low + while (pduLen--) { + i = CRCHi ^ *frame++; + val = pgm_read_word(_auchCRC + i); + CRCHi = CRCLo ^ highByte(val); // Hi + CRCLo = lowByte(val); //Low + } + return (CRCHi << 8) | CRCLo; +} +/* +uint16_t ModbusRTUTemplate::crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen) { + uint16_t temp, temp2, flag; + temp = 0xFFFF ^ address; + for (uint8_t i = 0; i < pduLen; i++) + { + temp = temp ^ frame[i]; + for (uint8_t j = 1; j <= 8; j++) + { + flag = temp & 0x0001; + temp >>= 1; + if (flag) + temp ^= 0xA001; + } + } + // Reverse byte order. + temp2 = temp >> 8; + temp = (temp << 8) | temp2; + temp &= 0xFFFF; + return temp; +} +*/ + +uint32_t ModbusRTUTemplate::charSendTime(uint32_t baud, uint8_t char_bits) { + return (uint32_t)char_bits * 1000000UL / baud; +} + +uint32_t ModbusRTUTemplate::calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits) { + // baud = baudrate of the serial port + // char_bits = size of 1 modbus character (defined a 11 bits in modbus specificacion) + // Returns: The minimum time between frames (defined as 3.5 characters time in modbus specification) + + // According to standard, the Modbus frame is always 11 bits long: + // 1 start + 8 data + 1 parity + 1 stop + // 1 start + 8 data + 2 stops + // And the minimum time between frames is defined as 3.5 characters time in modbus specification. + // This means the time between frames (in microseconds) should be calculated as follows: + // _t = 3.5 x 11 x 1000000 / baudrate = 38500000 / baudrate + + // Eg: For 9600 baudrate _t = 38500000 / 9600 = 4010 us + // For baudrates grater than 19200 the _t should be fixed at 1750 us. + + // If the used modbus frame length is 10 bits (out of standard - 1 start + 8 data + 1 stop), then + // it can be set using char_bits = 10. + + if (baud > 19200) { + return 1750UL; + } else { + return 3.5 * charSendTime(baud, char_bits); + } +} + +// Kept for backward compatibility +void ModbusRTUTemplate::setBaudrate(uint32_t baud) { + setInterFrameTime(calculateMinimumInterFrameTime(baud)); +} + +void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) { + // This function sets the inter frame time. This time is the time that task() waits before considering that the frame being transmitted on the RS485 bus has finished. + // If the interframe calculated by calculateMinimumInterFrameTime() is not enough, you can set the interframe time manually with this function. + // The time must be set in micro seconds. + // This is useful when you are receiving data as a slave and you notice that the slave is dividing a frame in two or more pieces (and obviously the CRC is failing on all pieces). + // This is because it is detecting an interframe time inbetween bytes of the frame and thus it interprets one single frame as two or more frames. + // In that case it is useful to be able to set a more "permissive" interframe time. + _t = t_us; +} + +bool ModbusRTUTemplate::begin(Stream* port, int16_t txPin, bool direct) { + _port = port; + _t = 1750UL; +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(0); +#endif + if (txPin >= 0) { + _txPin = txPin; + _direct = direct; + pinMode(_txPin, OUTPUT); + digitalWrite(_txPin, _direct?LOW:HIGH); + } + return true; +} + +bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { + uint16_t newCrc = crc16(slaveId, frame, len); +#if defined(MODBUSRTU_DEBUG) + for (uint8_t i=0 ; i < _len ; i++) { + Serial.print(_frame[i], HEX); + Serial.print(" "); + } + Serial.println(); +#endif +#if defined(MODBUSRTU_REDE) + if (_txPin >= 0 || _rxPin >= 0) { + if (_txPin >= 0) digitalWrite(_txPin, _direct?HIGH:LOW); + if (_rxPin >= 0) digitalWrite(_rxPin, _direct?HIGH:LOW); +#if !defined(ESP32) + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); +#endif + } +#else + if (_txPin >= 0) { + digitalWrite(_txPin, _direct?HIGH:LOW); +#if !defined(ESP32) + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); +#endif + } +#endif +#if defined(ESP32) + vTaskDelay(0); +#endif + _port->write(slaveId); //Send slaveId + _port->write(frame, len); // Send PDU + _port->write(newCrc >> 8); //Send CRC + _port->write(newCrc & 0xFF);//Send CRC + _port->flush(); +#if defined(MODBUSRTU_REDE) + if (_txPin >= 0 || _rxPin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif + if (_txPin >= 0) digitalWrite(_txPin, _direct?LOW:HIGH); + if (_rxPin >= 0) digitalWrite(_rxPin, _direct?LOW:HIGH); + } +#else + if (_txPin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif + digitalWrite(_txPin, _direct?LOW:HIGH); + } +#endif + return true; +} + +uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { + bool result = false; + if ((!isMaster || !_slaveId) && _len && _frame) { // Check if waiting for previous request result and _frame filled + //if (_len && _frame) { // Check if waiting for previous request result and _frame filled + rawSend(slaveId, _frame, _len); + if (waitResponse && slaveId) { + _slaveId = slaveId; + _timestamp = micros(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + } + result = true; + } + free(_frame); + _frame = nullptr; + _len = 0; + return result; +} + +void ModbusRTUTemplate::task() { +#if defined(ESP32) + vTaskDelay(0); +#endif + if (_port->available() > _len) { + _len = _port->available(); + t = micros(); + } + if (_len == 0) { + if (isMaster) cleanup(); + return; + } + if (isMaster) { + if (micros() - t < _t) { + return; + } + } + else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached) + uint32_t taskStart = micros(); + while (micros() - t < _t) { // Wait data whitespace + if (_port->available() > _len) { + _len = _port->available(); + t = micros(); + } + if (micros() - taskStart > MODBUSRTU_MAX_READ_US) { // Prevent from task() executed too long + return; + } + } + } + + bool valid_frame = true; + address = _port->read(); //first byte of frame = address + _len--; // Decrease by slaveId byte + if (isMaster && _slaveId == 0) { // Check if slaveId is set + valid_frame = false; + } + if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check + valid_frame = false; + } + if (!valid_frame && !_cbRaw) { + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach + _len = 0; + if (isMaster) cleanup(); + return; + } + + free(_frame); //Just in case + _frame = (uint8_t*) malloc(_len); + if (!_frame) { // Fail to allocate buffer + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if can't allocate buffer + _len = 0; + if (isMaster) cleanup(); + return; + } + for (uint8_t i=0 ; i < _len ; i++) { + _frame[i] = _port->read(); // read data + crc + #if defined(MODBUSRTU_DEBUG) + Serial.print(_frame[i], HEX); + Serial.print(" "); + #endif + } + #if defined(MODBUSRTU_DEBUG) + Serial.println(); + #endif + //_port->readBytes(_frame, _len); + uint16_t frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc + _len = _len - 2; // Decrease by CRC 2 bytes + if (frameCrc != crc16(address, _frame, _len)) { // CRC Check + goto cleanup; + } + _reply = EX_PASSTHROUGH; + if (_cbRaw) { + frame_arg_t header_data = { address, !isMaster }; + _reply = _cbRaw(_frame, _len, (void*)&header_data); + } + if (!valid_frame && _reply != EX_FORCE_PROCESS) { + goto cleanup; + } + if (isMaster) { + if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) + masterPDU(_frame, _sentFrame, _sentReg, _data); + if (_cb) { + _cb((ResultCode)_reply, 0, nullptr); + _cb = nullptr; + } + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + } + _reply = Modbus::REPLY_OFF; // No reply if master + } else { + if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) { + slavePDU(_frame); + if (address == MODBUSRTU_BROADCAST) + _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + if (_reply != Modbus::REPLY_OFF) + rawSend(address, _frame, _len); + } + } + // Cleanup +cleanup: + free(_frame); + _frame = nullptr; + _len = 0; + if (isMaster) cleanup(); +} + +bool ModbusRTUTemplate::cleanup() { + // Remove timeouted request and forced event + if (_slaveId && (micros() - _timestamp > MODBUSRTU_TIMEOUT_US)) { + if (_cb) { + _cb(Modbus::EX_TIMEOUT, 0, nullptr); + _cb = nullptr; + } + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + return true; + } + return false; +} \ No newline at end of file diff --git a/BSB_LAN/src/modbus-esp8266/src/ModbusRTU.h b/BSB_LAN/src/modbus-esp8266/src/ModbusRTU.h new file mode 100644 index 00000000..dcf4b047 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/ModbusRTU.h @@ -0,0 +1,99 @@ +/* + Modbus Library for Arduino + ModbusRTU + Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ +#pragma once +#include "ModbusAPI.h" + +class ModbusRTUTemplate : public Modbus { + protected: + Stream* _port; + int16_t _txPin = -1; +#if defined(MODBUSRTU_REDE) + int16_t _rxPin = -1; +#endif + bool _direct = true; // Transmit control logic (true=direct, false=inverse) + uint32_t _t; // inter-frame delay in uS +#if defined(MODBUSRTU_FLUSH_DELAY) + uint32_t _t1; // char send time +#endif + uint32_t t = 0; // time sience last data byte arrived + bool isMaster = false; + uint8_t _slaveId; + uint32_t _timestamp = 0; + cbTransaction _cb = nullptr; + uint8_t* _data = nullptr; + uint8_t* _sentFrame = nullptr; + TAddress _sentReg = COIL(0); + uint16_t maxRegs = 0x007D; + uint8_t address = 0; + + uint16_t send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + // Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data + // slaveId - slave id + // startreg - first local register to save returned data to (miningless for write to slave operations) + // cb - transaction callback function + // data - if not null use buffer to save returned data instead of local registers + bool rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len); + bool cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events + uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen); + uint16_t crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen); + public: + void setBaudrate(uint32_t baud = -1); + uint32_t calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits = 11); + void setInterFrameTime(uint32_t t_us); + uint32_t charSendTime(uint32_t baud, uint8_t char_bits = 11); + template + bool begin(T* port, int16_t txPin = -1, bool direct = true); +#if defined(MODBUSRTU_REDE) + template + bool begin(T* port, int16_t txPin, int16_t rxPin, bool direct); +#endif + bool begin(Stream* port, int16_t txPin = -1, bool direct = true); + void task(); + void client() { isMaster = true; }; + inline void master() {client();} + void server(uint8_t serverId) {_slaveId = serverId;}; + inline void slave(uint8_t slaveId) {server(slaveId);} + uint8_t server() { return _slaveId; } + inline uint8_t slave() { return server(); } + uint32_t eventSource() override {return address;} +}; + +template +bool ModbusRTUTemplate::begin(T* port, int16_t txPin, bool direct) { + uint32_t baud = 0; + #if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266 + baud = port->baudRate(); + #else + baud = 9600; + #endif + setInterFrameTime(calculateMinimumInterFrameTime(baud)); +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(baud); +#endif + _port = port; + if (txPin >= 0) { + _txPin = txPin; + _direct = direct; + pinMode(_txPin, OUTPUT); + digitalWrite(_txPin, _direct?LOW:HIGH); + } + return true; +} +#if defined(MODBUSRTU_REDE) +template +bool ModbusRTUTemplate::begin(T* port, int16_t txPin, int16_t rxPin, bool direct) { + begin(port, txPin, direct); + if (rxPin > 0) { + _rxPin = rxPin; + pinMode(_rxPin, OUTPUT); + digitalWrite(_rxPin, _direct?LOW:HIGH); + } + return true; +} +#endif +class ModbusRTU : public ModbusAPI {}; diff --git a/BSB_LAN/src/modbus-esp8266/src/ModbusSettings.h b/BSB_LAN/src/modbus-esp8266/src/ModbusSettings.h new file mode 100644 index 00000000..919ec79a --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/ModbusSettings.h @@ -0,0 +1,136 @@ + +/* + 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 11000 +#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 MODBUSTCP_PORT 502 +#define MODBUSTLS_PORT 802 +#define MODBUSIP_MAXFRAME 200 + +/* +ModbusTCP and ModbusTLS timeouts +#define MODBUSIP_TIMEOUT 1000 +Outgoing request timeout +#define MODBUSIP_CONNECT_TIMEOUT 1000 +ESP32 only. Outgoing connection attempt timeout +*/ +#define MODBUSIP_TIMEOUT 1000 +//#define MODBUSIP_CONNECT_TIMEOUT 1000 + +#define MODBUSIP_UNIT 255 +#define MODBUSIP_MAX_TRANSACTIONS 16 +#if defined(ESP32) +#define MODBUSIP_MAX_CLIENTS 8 +#else +#define MODBUSIP_MAX_CLIENTS 4 +#endif +#define MODBUSIP_UNIQUE_CLIENTS +#define MODBUSIP_MAX_READMS 100 +#define MODBUSIP_FULL +//#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 +#define MODBUSRTU_TIMEOUT 1000 +#define MODBUSRTU_MAX_READMS 100 +/* +#define MODBUSRTU_REDE +Enable using separate pins for RE DE +*/ +#define MODBUSRTU_REDE + +// Define for internal use. Do not change. +#define MODBUSRTU_TIMEOUT_US 1000UL * MODBUSRTU_TIMEOUT +#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/BSB_LAN/src/modbus-esp8266/src/ModbusTCP.h b/BSB_LAN/src/modbus-esp8266/src/ModbusTCP.h new file mode 100644 index 00000000..fed76c3c --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/src/ModbusTCPTemplate.h b/BSB_LAN/src/modbus-esp8266/src/ModbusTCPTemplate.h new file mode 100644 index 00000000..c7aacb07 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/src/ModbusTCPTemplate.h @@ -0,0 +1,586 @@ +/* + 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 + while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) { +#if defined(MODBUSIP_DEBUG) + Serial.println("IP: Accepted"); +#endif + CLIENT* currentClient = new CLIENT(c); + if (!currentClient || !currentClient->connected()) + continue; +#if defined(MODBUSRTU_DEBUG) + Serial.println("IP: Connected"); +#endif + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + #if defined(MODBUSIP_UNIQUE_CLIENTS) + // Disconnect previous connection from same IP if present + 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 + continue; // while + } + } + // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached + delete currentClient; + } + } + for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + if (!tcpclient[n]) continue; + if (!tcpclient[n]->connected()) continue; + while (millis() - taskStart < MODBUSIP_MAX_READMS && (size_t)tcpclient[n]->available() > sizeof(_MBAP)) { +#if defined(MODBUSIP_DEBUG) + Serial.print(n); + Serial.print(": Bytes available "); + Serial.println(tcpclient[n]->available()); +#endif + tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP + + if (__swap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __swap is usless there. + while (tcpclient[n]->available()) // Drop all incoming if wrong packet + tcpclient[n]->read(); + continue; + } + _len = __swap_16(_MBAP.length); + _len--; // Do not count with last byte from MBAP + if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME + exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); + _len--; // Subtract for read byte + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of packet + tcpclient[n]->read(); + } + else { + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop packet + tcpclient[n]->read(); + } + else { + if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame + exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); + //while (tcpclient[n]->available()) // Drop all incoming (if any) + // tcpclient[n]->read(); + } + else { + _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); // Procass 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); + 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/BSB_LAN/src/modbus-esp8266/src/ModbusTLS.h b/BSB_LAN/src/modbus-esp8266/src/ModbusTLS.h new file mode 100644 index 00000000..7f0fdf8c --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/src/darray.h b/BSB_LAN/src/modbus-esp8266/src/darray.h new file mode 100644 index 00000000..0b204fbf --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/tests/README.md b/BSB_LAN/src/modbus-esp8266/tests/README.md new file mode 100644 index 00000000..239ac1e0 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/tests/common.h b/BSB_LAN/src/modbus-esp8266/tests/common.h new file mode 100644 index 00000000..efd98e42 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/tests/files.h b/BSB_LAN/src/modbus-esp8266/tests/files.h new file mode 100644 index 00000000..25ec0800 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/tests/read.h b/BSB_LAN/src/modbus-esp8266/tests/read.h new file mode 100644 index 00000000..21b84950 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/tests/tests.ino b/BSB_LAN/src/modbus-esp8266/tests/tests.ino new file mode 100644 index 00000000..5ab26733 --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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/BSB_LAN/src/modbus-esp8266/tests/write.h b/BSB_LAN/src/modbus-esp8266/tests/write.h new file mode 100644 index 00000000..627ce91d --- /dev/null +++ b/BSB_LAN/src/modbus-esp8266/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