diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7242f03 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://paypal.me/modbus?locale.x=en_US diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0b948a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode/arduino.json +.vscode/c_cpp_properties.json +.vscode/* +src/ModbusSerial.cpp +src/ModbusSerial.h diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/API.md b/API.md new file mode 100644 index 0000000..bcfb971 --- /dev/null +++ b/API.md @@ -0,0 +1 @@ +Moved to [documentation](documentation) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..01b31a6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_ADD_INCLUDEDIRS src) +set(COMPONENT_SRCS "src/Modbus.cpp" "src/ModbusRTU.cpp") +set(COMPONENT_PRIV_REQUIRES arduino) +register_component() + + diff --git a/LICENSE.txt b/LICENSE.txt index 5810ccb..f7aa0ba 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,8 @@ Copyright (c) 2015, André Sarmento Barbosa + 2017, Alexander Emelianov (a.m.emelianov@gmail.com) All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index dc26c87..e419e69 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,129 @@ -Modbus Library for ESP8266 -========================== - -This library allows your ESP8266 to communicate via Modbus protocol. The Modbus is a master-slave protocol -used in industrial automation and can be used in other areas, such as home automation. - -The Modbus generally uses serial RS-232 or RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus IP). - -In the current version the library allows the ESP8266 operate as a slave, supporting Modbus IP via wireless network. For more information about Modbus see: - -http://pt.wikipedia.org/wiki/Modbus http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf -http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf - -Features -======== - - - -Notes: - -1. When using Modbus IP the transport protocol is TCP (port 502) and the connection is terminated to each transmitted message, that is, is not a keep-alive type connection. - -2. The offsets for registers are 0-based. So be careful when setting your supervisory system or your testing software. For example, in ScadaBR (http://www.scadabr.com.br) -offsets are 0-based, then, a register configured as 100 in the library is set to 100 in ScadaBR. On the other hand, in the CAS Modbus Scanner -(http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) offsets are 1-based, so a register configured as 100 in library should be 101 in this software. - -3. Early in the library Modbus.h file there is an option to limit the operation -to the functions of Holding Registers, saving space in the program memory. -Just comment out the following line: - -``` -#define USE_HOLDING_REGISTERS_ONLY -``` -Thus, only the following functions are supported: - - - -How to -====== - -``` -This README is under development, for now, see the examples of the library. -``` - -Contributions -============= -http://github.com/andresarmento/modbus-esp8266
-prof (at) andresarmento (dot) com - -License -======= -The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. - +# 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.1 ++ Protocol: Fix wrong error code responce on non-existent register ++ ModbusTCP: Fix potential memory leak ++ API: cbEnable/cbDisable functionality extended ++ ESP-IDF: CMakeList.txt added ++ Examples: TCP-to-RTU fixed +// 4.1.0 ++ API: Raw Modbus frame processing functionality ++ ModbusRTU: Precise inter-frame interval control ++ Examples: True ModbusRTU to ModbusTCP Server bridge ++ Examples: ModbusRTU respond to multiple ID from single device ++ ModbusRTU: Add direction control pin for Stream ++ STL: Add Reg count limitation to vector limit of 4000 (for ESP8266 and ESP32) ++ Settings: Added MODBUSIP_CONNECTION_TIMEOUT (ESP32 only) ++ Settings: Set MODBUSIP_MAX_CLIENTS = 8 for ESP32 ++ ModbusTCP: Make using DNS names optional feature ++ ModbusRTU: Add separate RE/DE pins control optional feature ++ API: Drop support of Ethernet library v1 ++ Examples: Teknic ClearCore ArduinoWrapper examples added ++ Examples: ModbusTCP to ModbusRTU example added ++ ModbusRTU: Flush extra delay optional feature +// 4.0.0 ++ Support of all Arduino boards ++ ModbusTLS: ESP8266 Client/Server and ESP32 Client ++ ModbusTCP: ModbusEthernet - WizNet W5x00, ENC28J60 Ethernet library support ++ 0x14 - Read File Records function ++ 0x15 - Write File Records function ++ Examples: FW update over Modbus fullfunctional example ++ 0x16 - Write Mask Register function+ Test: 0x16 ++ 0x17 - Read/Write Registers function ++ ModbusRTU: ESP32 SoftwareSerial support ++ Build with no STL dependency (switchable) ++ API: ModbusIP => ModbusTCP ++ API: Access control callback for individual Modbus function ++ API: Master/Slave => Client/Server according to [PRESS RELEASE](https://modbus.org/docs/Client-ServerPR-07-2020-final.docx.pdf) ++ Lot of code refacting and small fixes +``` + +## Roadmap + +```diff +// 4.2.0 +- API: Alternative CRC calulation (reduced memory footprint) +- ModbusRTU: Static buffer allocation +- Test: Frame accuracy to specefication +- Buffer/packet size limitation support +- Slave/Server: slavePDU use early exit by return where possible +- Master/Client: Check frame size against header data where possible +- Master/Client: Additional responce data validation +- Free global registers and callbacks on remove last Modbus instance +- Test: push/pull functions +- ModbusTCP: Refactor connect by dns name (using native implementation for ESP32 etc) +// 4.3.0 +- ModbusTLS: ESP32 Server +- Test: TLS ESP32 Server +- Test: TLS ESP32 Client +- Examples: TLS Certificate test Role extension and Alt-Name +- Examples: TLS Add example explanation +- ModbusTCP: ModbusAsyncTCP +- API: Extend API to allow custom Modbus commands +- Examples: Basic file operations +- Examples: Revising +``` +## Contributions + +https://github.com/emelianov/modbus-esp8266 + +a.m.emelianov@gmail.com + +Original version: + +https://github.com/andresarmento/modbus-esp8266 + +https://github.com/andresarmento/modbus-arduino + +prof (at) andresarmento (dot) com + +## License + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/README_pt_BR.md b/README_pt_BR.md deleted file mode 100644 index 28efc59..0000000 --- a/README_pt_BR.md +++ /dev/null @@ -1,83 +0,0 @@ -Biblioteca Modbus para ESP8266 -============================== - -Esta biblioteca permite que seu ESP8266 se comunique através do protocolo Modbus. -O Modbus é um protocolo do tipo mestre-escravo, utilizado em automação industrial, -podendo ser utilizado em outras áreas, como por exemplo, na automação residencial. - -O Modbus geralmente utiliza como meio físico as interfaces seriais RS-232 ou RS-485 -(quando é chamado Modbus Serial) e TCP/IP via Ethernet ou WiFi (Modbus IP). - -Na versão atual a biblioteca permite que o arduino opere como escravo, suportando -o Modbus IP via rede wireless. Para mais informações sobre o Modbus consulte: - -http://pt.wikipedia.org/wiki/Modbus -http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf -http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf - -Características -=============== - - - -Observações: - -1. Quando se usa Modbus IP o protocolo de transporte é o TCP (porta 502) e a conexão -é finalizada a cada mensagem transmitida, ou seja, não é do tipo keep-alive. - -2. Os offsets para acesso aos registradores são baseados em 0. Assim, tenha cuidado -ao configurar seu seu supervisório ou utilitário de teste. Por exempo, no ScadaBR -(http://www.scadabr.com.br) os offsets são baseados em 0, então, um registrador -configurado como 100 na biblioteca será configurado como 100 no ScadaBR. Por outro -lado, no software de teste CAS Modbus Scanner (http://www.chipkin.com/products/software/modbus-software/cas-modbus-scanner/) -os offsets são baseados em 1, logo, um registrador configurado como 100 na biblioteca -deverá ser 101 neste software. - -3. No início do arquivo Modbus.h da biblioteca há uma opção para limitar o funcionamento -da mesma às funções de Holding Registers, salvando espaço na memória de programa. -Basta retirar o comentário da seguinte linha: - -``` -#define USE_HOLDING_REGISTERS_ONLY -``` -Dessa forma, somente as seguintes funções são suportadas: - - -Como utilizar -============= - -``` -Este README está em desenvolvimento, por enquanto, consulte os exemplos da biblioteca. -``` - -Contribuições -============= -http://github.com/andresarmento/modbus-esp8266
-prof (at) andresarmento (dot) com - -Licença -======= - -O código neste repositório é licenciado pela BSD New License. -Veja [LICENSE.txt](LICENSE.txt) para mais informações. - - diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 5cd9a53..0000000 --- a/TODO.txt +++ /dev/null @@ -1,8 +0,0 @@ -TODO -==== -. Modbus for Expressif SDK (in development) - - - - - diff --git a/arduinoIDE/Modbus/Modbus.cpp b/arduinoIDE/Modbus/Modbus.cpp deleted file mode 100644 index 6e9c7da..0000000 --- a/arduinoIDE/Modbus/Modbus.cpp +++ /dev/null @@ -1,514 +0,0 @@ -/* - Modbus.cpp - Source for Modbus Base Library - Copyright (C) 2014 André Sarmento Barbosa -*/ -#include "Modbus.h" - -Modbus::Modbus() { - _regs_head = 0; - _regs_last = 0; -} - -TRegister* Modbus::searchRegister(word address) { - TRegister *reg = _regs_head; - //if there is no register configured, bail - if(reg == 0) return(0); - //scan through the linked list until the end of the list or the register is found. - //return the pointer. - do { - if (reg->address == address) return(reg); - reg = reg->next; - } while(reg); - return(0); -} - -void Modbus::addReg(word address, word value) { - TRegister *newreg; - - newreg = (TRegister *) malloc(sizeof(TRegister)); - newreg->address = address; - newreg->value = value; - newreg->next = 0; - - if(_regs_head == 0) { - _regs_head = newreg; - _regs_last = _regs_head; - } else { - //Assign the last register's next pointer to newreg. - _regs_last->next = newreg; - //then make temp the last register in the list. - _regs_last = newreg; - } -} - -bool Modbus::Reg(word address, word value) { - TRegister *reg; - //search for the register address - reg = this->searchRegister(address); - //if found then assign the register value to the new value. - if (reg) { - reg->value = value; - return true; - } else - return false; -} - -word Modbus::Reg(word address) { - TRegister *reg; - reg = this->searchRegister(address); - if(reg) - return(reg->value); - else - return(0); -} - -void Modbus::addHreg(word offset, word value) { - this->addReg(offset + 40001, value); -} - -bool Modbus::Hreg(word offset, word value) { - return Reg(offset + 40001, value); -} - -word Modbus::Hreg(word offset) { - return Reg(offset + 40001); -} - -#ifndef USE_HOLDING_REGISTERS_ONLY - void Modbus::addCoil(word offset, bool value) { - this->addReg(offset + 1, value?0xFF00:0x0000); - } - - void Modbus::addIsts(word offset, bool value) { - this->addReg(offset + 10001, value?0xFF00:0x0000); - } - - void Modbus::addIreg(word offset, word value) { - this->addReg(offset + 30001, value); - } - - bool Modbus::Coil(word offset, bool value) { - return Reg(offset + 1, value?0xFF00:0x0000); - } - - bool Modbus::Ists(word offset, bool value) { - return Reg(offset + 10001, value?0xFF00:0x0000); - } - - bool Modbus::Ireg(word offset, word value) { - return Reg(offset + 30001, value); - } - - bool Modbus::Coil(word offset) { - if (Reg(offset + 1) == 0xFF00) { - return true; - } else return false; - } - - bool Modbus::Ists(word offset) { - if (Reg(offset + 10001) == 0xFF00) { - return true; - } else return false; - } - - word Modbus::Ireg(word offset) { - return Reg(offset + 30001); - } -#endif - - -void Modbus::receivePDU(byte* frame) { - byte fcode = frame[0]; - word field1 = (word)frame[1] << 8 | (word)frame[2]; - word field2 = (word)frame[3] << 8 | (word)frame[4]; - - switch (fcode) { - - case MB_FC_WRITE_REG: - //field1 = reg, field2 = value - this->writeSingleRegister(field1, field2); - break; - - case MB_FC_READ_REGS: - //field1 = startreg, field2 = numregs - this->readRegisters(field1, field2); - break; - - case MB_FC_WRITE_REGS: - //field1 = startreg, field2 = status - this->writeMultipleRegisters(frame,field1, field2, frame[5]); - break; - - #ifndef USE_HOLDING_REGISTERS_ONLY - case MB_FC_READ_COILS: - //field1 = startreg, field2 = numregs - this->readCoils(field1, field2); - break; - - case MB_FC_READ_INPUT_STAT: - //field1 = startreg, field2 = numregs - this->readInputStatus(field1, field2); - break; - - case MB_FC_READ_INPUT_REGS: - //field1 = startreg, field2 = numregs - this->readInputRegisters(field1, field2); - break; - - case MB_FC_WRITE_COIL: - //field1 = reg, field2 = status - this->writeSingleCoil(field1, field2); - break; - - case MB_FC_WRITE_COILS: - //field1 = startreg, field2 = numoutputs - this->writeMultipleCoils(frame,field1, field2, frame[5]); - break; - - #endif - default: - this->exceptionResponse(fcode, MB_EX_ILLEGAL_FUNCTION); - } -} - -void Modbus::exceptionResponse(byte fcode, byte excode) { - //Clean frame buffer - free(_frame); - _len = 2; - _frame = (byte *) malloc(_len); - _frame[0] = fcode + 0x80; - _frame[1] = excode; - - _reply = MB_REPLY_NORMAL; -} - -void Modbus::readRegisters(word startreg, word numregs) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x007D) { - this->exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address - //*** See comments on readCoils method. - if (!this->searchRegister(startreg + 40001)) { - this->exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_ADDRESS); - return; - } - - - //Clean frame buffer - free(_frame); - _len = 0; - - //calculate the query reply message length - //for each register queried add 2 bytes - _len = 2 + numregs * 2; - - _frame = (byte *) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_READ_REGS, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_READ_REGS; - _frame[1] = _len - 2; //byte count - - word val; - word i = 0; - while(numregs--) { - //retrieve the value from the register bank for the current register - val = this->Hreg(startreg + i); - //write the high byte of the register value - _frame[2 + i * 2] = val >> 8; - //write the low byte of the register value - _frame[3 + i * 2] = val & 0xFF; - i++; - } - - _reply = MB_REPLY_NORMAL; -} - -void Modbus::writeSingleRegister(word reg, word value) { - //No necessary verify illegal value (EX_ILLEGAL_VALUE) - because using word (0x0000 - 0x0FFFF) - //Check Address and execute (reg exists?) - if (!this->Hreg(reg, value)) { - this->exceptionResponse(MB_FC_WRITE_REG, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Check for failure - if (this->Hreg(reg) != value) { - this->exceptionResponse(MB_FC_WRITE_REG, MB_EX_SLAVE_FAILURE); - return; - } - - _reply = MB_REPLY_ECHO; -} - -void Modbus::writeMultipleRegisters(byte* frame,word startreg, word numoutputs, byte bytecount) { - //Check value - if (numoutputs < 0x0001 || numoutputs > 0x007B || bytecount != 2 * numoutputs) { - this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address (startreg...startreg + numregs) - for (int k = 0; k < numoutputs; k++) { - if (!this->searchRegister(startreg + 40001 + k)) { - this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_ADDRESS); - return; - } - } - - //Clean frame buffer - free(_frame); - _len = 5; - _frame = (byte *) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_WRITE_REGS, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_WRITE_REGS; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; - _frame[3] = numoutputs >> 8; - _frame[4] = numoutputs & 0x00FF; - - word val; - word i = 0; - while(numoutputs--) { - val = (word)frame[6+i*2] << 8 | (word)frame[7+i*2]; - this->Hreg(startreg + i, val); - i++; - } - - _reply = MB_REPLY_NORMAL; -} - -#ifndef USE_HOLDING_REGISTERS_ONLY -void Modbus::readCoils(word startreg, word numregs) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x07D0) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address - //Check only startreg. Is this correct? - //When I check all registers in range I got errors in ScadaBR - //I think that ScadaBR request more than one in the single request - //when you have more then one datapoint configured from same type. - if (!this->searchRegister(startreg + 1)) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Clean frame buffer - free(_frame); - _len = 0; - - //Determine the message length = function type, byte count and - //for each group of 8 registers the message length increases by 1 - _len = 2 + numregs/8; - if (numregs%8) _len++; //Add 1 to the message length for the partial byte. - - _frame = (byte *) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_READ_COILS; - _frame[1] = _len - 2; //byte count (_len - function code and byte count) - - byte bitn = 0; - word totregs = numregs; - word i; - while (numregs--) { - i = (totregs - numregs) / 8; - if (this->Coil(startreg)) - bitSet(_frame[2+i], bitn); - else - bitClear(_frame[2+i], bitn); - //increment the bit index - bitn++; - if (bitn == 8) bitn = 0; - //increment the register - startreg++; - } - - _reply = MB_REPLY_NORMAL; -} - -void Modbus::readInputStatus(word startreg, word numregs) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x07D0) { - this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address - //*** See comments on readCoils method. - if (!this->searchRegister(startreg + 10001)) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Clean frame buffer - free(_frame); - _len = 0; - - //Determine the message length = function type, byte count and - //for each group of 8 registers the message length increases by 1 - _len = 2 + numregs/8; - if (numregs%8) _len++; //Add 1 to the message length for the partial byte. - - _frame = (byte *) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_READ_INPUT_STAT; - _frame[1] = _len - 2; - - byte bitn = 0; - word totregs = numregs; - word i; - while (numregs--) { - i = (totregs - numregs) / 8; - if (this->Ists(startreg)) - bitSet(_frame[2+i], bitn); - else - bitClear(_frame[2+i], bitn); - //increment the bit index - bitn++; - if (bitn == 8) bitn = 0; - //increment the register - startreg++; - } - - _reply = MB_REPLY_NORMAL; -} - -void Modbus::readInputRegisters(word startreg, word numregs) { - //Check value (numregs) - if (numregs < 0x0001 || numregs > 0x007D) { - this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address - //*** See comments on readCoils method. - if (!this->searchRegister(startreg + 30001)) { - this->exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Clean frame buffer - free(_frame); - _len = 0; - - //calculate the query reply message length - //for each register queried add 2 bytes - _len = 2 + numregs * 2; - - _frame = (byte *) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_READ_INPUT_REGS; - _frame[1] = _len - 2; - - word val; - word i = 0; - while(numregs--) { - //retrieve the value from the register bank for the current register - val = this->Ireg(startreg + i); - //write the high byte of the register value - _frame[2 + i * 2] = val >> 8; - //write the low byte of the register value - _frame[3 + i * 2] = val & 0xFF; - i++; - } - - _reply = MB_REPLY_NORMAL; -} - -void Modbus::writeSingleCoil(word reg, word status) { - //Check value (status) - if (status != 0xFF00 && status != 0x0000) { - this->exceptionResponse(MB_FC_WRITE_COIL, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address and execute (reg exists?) - if (!this->Coil(reg, (bool)status)) { - this->exceptionResponse(MB_FC_WRITE_COIL, MB_EX_ILLEGAL_ADDRESS); - return; - } - - //Check for failure - if (this->Coil(reg) != (bool)status) { - this->exceptionResponse(MB_FC_WRITE_COIL, MB_EX_SLAVE_FAILURE); - return; - } - - _reply = MB_REPLY_ECHO; -} - -void Modbus::writeMultipleCoils(byte* frame,word startreg, word numoutputs, byte bytecount) { - //Check value - word bytecount_calc = numoutputs / 8; - if (numoutputs%8) bytecount_calc++; - if (numoutputs < 0x0001 || numoutputs > 0x07B0 || bytecount != bytecount_calc) { - this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_VALUE); - return; - } - - //Check Address (startreg...startreg + numregs) - for (int k = 0; k < numoutputs; k++) { - if (!this->searchRegister(startreg + 1 + k)) { - this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_ADDRESS); - return; - } - } - - //Clean frame buffer - free(_frame); - _len = 5; - _frame = (byte *) malloc(_len); - if (!_frame) { - this->exceptionResponse(MB_FC_WRITE_COILS, MB_EX_SLAVE_FAILURE); - return; - } - - _frame[0] = MB_FC_WRITE_COILS; - _frame[1] = startreg >> 8; - _frame[2] = startreg & 0x00FF; - _frame[3] = numoutputs >> 8; - _frame[4] = numoutputs & 0x00FF; - - byte bitn = 0; - word totoutputs = numoutputs; - word i; - while (numoutputs--) { - i = (totoutputs - numoutputs) / 8; - this->Coil(startreg, bitRead(frame[6+i], bitn)); - //increment the bit index - bitn++; - if (bitn == 8) bitn = 0; - //increment the register - startreg++; - } - - _reply = MB_REPLY_NORMAL; -} -#endif - - - diff --git a/arduinoIDE/Modbus/Modbus.h b/arduinoIDE/Modbus/Modbus.h deleted file mode 100644 index a52c875..0000000 --- a/arduinoIDE/Modbus/Modbus.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - Modbus.h - Header for Modbus Base Library - Copyright (C) 2014 André Sarmento Barbosa -*/ -#include "Arduino.h" - -#ifndef MODBUS_H -#define MODBUS_H - -#define MAX_REGS 32 -#define MAX_FRAME 128 -//#define USE_HOLDING_REGISTERS_ONLY - -typedef unsigned int u_int; - -//Function Codes -enum { - MB_FC_READ_COILS = 0x01, // Read Coils (Output) Status 0xxxx - MB_FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 1xxxx - MB_FC_READ_REGS = 0x03, // Read Holding Registers 4xxxx - MB_FC_READ_INPUT_REGS = 0x04, // Read Input Registers 3xxxx - MB_FC_WRITE_COIL = 0x05, // Write Single Coil (Output) 0xxxx - MB_FC_WRITE_REG = 0x06, // Preset Single Register 4xxxx - MB_FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 0xxxx - MB_FC_WRITE_REGS = 0x10, // Write block of contiguous registers 4xxxx -}; - -//Exception Codes -enum { - MB_EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported - MB_EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists - MB_EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range - MB_EX_SLAVE_FAILURE = 0x04, // Slave Deive Fails to process request -}; - -//Reply Types -enum { - MB_REPLY_OFF = 0x01, - MB_REPLY_ECHO = 0x02, - MB_REPLY_NORMAL = 0x03, -}; - -typedef struct TRegister { - word address; - word value; - struct TRegister* next; -} TRegister; - -class Modbus { - private: - TRegister *_regs_head; - TRegister *_regs_last; - - void readRegisters(word startreg, word numregs); - void writeSingleRegister(word reg, word value); - void writeMultipleRegisters(byte* frame,word startreg, word numoutputs, byte bytecount); - void exceptionResponse(byte fcode, byte excode); - #ifndef USE_HOLDING_REGISTERS_ONLY - void readCoils(word startreg, word numregs); - void readInputStatus(word startreg, word numregs); - void readInputRegisters(word startreg, word numregs); - void writeSingleCoil(word reg, word status); - void writeMultipleCoils(byte* frame,word startreg, word numoutputs, byte bytecount); - #endif - - TRegister* searchRegister(word addr); - - void addReg(word address, word value = 0); - bool Reg(word address, word value); - word Reg(word address); - - protected: - byte *_frame; - byte _len; - byte _reply; - void receivePDU(byte* frame); - - public: - Modbus(); - - void addHreg(word offset, word value = 0); - bool Hreg(word offset, word value); - word Hreg(word offset); - - #ifndef USE_HOLDING_REGISTERS_ONLY - void addCoil(word offset, bool value = false); - void addIsts(word offset, bool value = false); - void addIreg(word offset, word value = 0); - - bool Coil(word offset, bool value); - bool Ists(word offset, bool value); - bool Ireg(word offset, word value); - - bool Coil(word offset); - bool Ists(word offset); - word Ireg(word offset); - #endif -}; - -#endif //MODBUS_H diff --git a/arduinoIDE/Modbus/keywords.txt b/arduinoIDE/Modbus/keywords.txt deleted file mode 100644 index 4c183a2..0000000 --- a/arduinoIDE/Modbus/keywords.txt +++ /dev/null @@ -1,41 +0,0 @@ -# Syntax Coloring Map For Modbus - -# Datatypes (KEYWORD1) -Modbus KEYWORD1 -u_int KEYWORD1 -TRegister KEYWORD1 - -# Methods and Functions (KEYWORD2) -readCoils KEYWORD2 -readInputStatus KEYWORD2 -readRegisters KEYWORD2 -readInputRegisters KEYWORD2 -writeSingleCoil KEYWORD2 -writeSingleRegister KEYWORD2 -writeMultipleCoils KEYWORD2 -writeMultipleRegisters KEYWORD2 -searchRegister KEYWORD2 -receivePDU KEYWORD2 -addReg KEYWORD2 -addCoil KEYWORD2 -addIsts KEYWORD2 -addIreg KEYWORD2 -addHreg KEYWORD2 -Reg KEYWORD2 -Coil KEYWORD2 -Ists KEYWORD2 -Ireg KEYWORD2 -Hreg KEYWORD2 - -# Constants (LITERAL1) -MB_FC_READ_COILS LITERAL1 -MB_FC_READ_INPUTS LITERAL1 -MB_FC_READ_REGS LITERAL1 -MB_FC_READ_INPUT_STAT LITERAL1 -MB_FC_WRITE_COIL LITERAL1 -MB_FC_WRITE_REG LITERAL1 -MB_FC_WRITE_COILS LITERAL1 -MB_FC_WRITE_REGS LITERAL1 -MB_REPLY_OFF LITERAL1 -MB_REPLY_ECHO LITERAL1 -MB_REPLY_NORMAL LITERAL1 diff --git a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.cpp b/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.cpp deleted file mode 100644 index 70c103f..0000000 --- a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - ModbusIP_ESP8266.cpp - Source for Modbus IP ESP8266 Library - Copyright (C) 2015 André Sarmento Barbosa -*/ -#include "ModbusIP_ESP8266.h" - -WiFiServer server(MODBUSIP_PORT); - -ModbusIP::ModbusIP() { - -} - -void ModbusIP::config(const char* ssid, const char* password) { - WiFi.begin(ssid, password); - server.begin(); -} - -void ModbusIP::task() { - WiFiClient client = server.available(); - - int raw_len = 0; - - if (client) { - if (client.connected()) { - for (int x = 0; x < 300; x++) { // Time to have data available - if (client.available()) { - while (client.available() > raw_len) { //Computes data length - raw_len = client.available(); - delay(1); - } - break; - } - delay(10); - } - } - - if (raw_len > 7) { - for (int i=0; i<7; i++) _MBAP[i] = client.read(); //Get MBAP - - _len = _MBAP[4] << 8 | _MBAP[5]; - _len--; // Do not count with last byte from MBAP - if (_MBAP[2] !=0 || _MBAP[3] !=0) return; //Not a MODBUSIP packet - if (_len > MODBUSIP_MAXFRAME) return; //Length is over MODBUSIP_MAXFRAME - _frame = (byte*) malloc(_len); - - raw_len = raw_len - 7; - for (int i=0; i< raw_len; i++) _frame[i] = client.read(); //Get Modbus PDU - - this->receivePDU(_frame); - client.flush(); - - if (_reply != MB_REPLY_OFF) { - //MBAP - _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP - _MBAP[5] = (_len+1) & 0x00FF; - - size_t send_len = (unsigned int)_len + 7; - uint8_t sbuf[send_len]; - - for (int i=0; i<7; i++) sbuf[i] = _MBAP[i]; - for (int i=0; i<_len; i++) sbuf[i+7] = _frame[i]; - - client.write(sbuf, send_len); - } - - client.stop(); - free(_frame); - _len = 0; - } - } -} - /* - uint8_t buffer[128] = {0}; - uint8_t mux_id; - uint32_t len = _wifi->recv(&mux_id, buffer, sizeof(buffer), 100); - - if (len > 0) { - int i = 0; - while (i < 7) { - _MBAP[i] = buffer[i]; - i++; - } - - _len = _MBAP[4] << 8 | _MBAP[5]; - _len--; // Do not count with last byte from MBAP - if (_MBAP[2] !=0 || _MBAP[3] !=0) return; //Not a MODBUSIP packet - if (_len > MODBUSIP_MAXFRAME) return; //Length is over MODBUSIP_MAXFRAME - - _frame = (byte*) malloc(_len); - i = 0; - while (i < _len){ - _frame[i] = buffer[7+i]; //Forget MBAP and take just modbus pdu - i++; - } - - this->receivePDU(_frame); - - if (_reply != MB_REPLY_OFF) { - //MBAP - _MBAP[4] = _len >> 8; - _MBAP[5] = _len | 0x00FF; - buffer[4] = _MBAP[4]; - buffer[5] = _MBAP[5]; - - i = 0; - while (i < _len){ - buffer[i+7] = _frame[i]; - i++; - } - _wifi->send(mux_id, buffer, _len + 7); - _wifi->releaseTCP(mux_id); - } - - free(_frame); - _len = 0; - } - -} -*/ diff --git a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.h b/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.h deleted file mode 100644 index 907b0f2..0000000 --- a/arduinoIDE/ModbusIP_ESP8266/ModbusIP_ESP8266.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - ModbusIP_ESP8266.h - Header for Modbus IP ESP8266 Library - Copyright (C) 2015 André Sarmento Barbosa -*/ -#include -#include - -#ifndef MODBUSIP_ESP8266_H -#define MODBUSIP_ESP8266_H - -#define MODBUSIP_PORT 502 -#define MODBUSIP_MAXFRAME 200 -#define MODBUSIP_TIMEOUT 10 - -class ModbusIP : public Modbus { - private: - byte _MBAP[7]; - public: - ModbusIP(); - void config(const char* ssid, const char* password); - void task(); -}; - -#endif //MODBUSIP_ESP8266_H - diff --git a/arduinoIDE/ModbusIP_ESP8266/keywords.txt b/arduinoIDE/ModbusIP_ESP8266/keywords.txt deleted file mode 100644 index f8960e6..0000000 --- a/arduinoIDE/ModbusIP_ESP8266/keywords.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Syntax Coloring Map For ModbusIP - -# Datatypes (KEYWORD1) -ModbusIP KEYWORD1 -ModbusIP_ESP8266 KEYWORD1 - -# Methods and Functions (KEYWORD2) -config KEYWORD2 -task KEYWORD2 - -# Constants (LITERAL1) diff --git a/documentation/API.md b/documentation/API.md new file mode 100644 index 0000000..a69d94b --- /dev/null +++ b/documentation/API.md @@ -0,0 +1,296 @@ +## Common API + +```c +void task(); +``` + +Processing routine. Should be periodically called form loop(). + +## Server API + +### Add registers + +```c +bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); +bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1); +bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); +bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); +``` + +### Write local reg + +```c +bool Hreg(uint16_t offset, uint16_t value); +bool Coil(uint16_t offset, bool value); +bool Ists(uint16_t offset, bool value); +bool Ireg(uint16_t offset, uint16_t value); +``` + +### Read local reg + +```c +uint16_t Hreg(uint16_t offset); +bool Coil(uint16_t offset); +bool Ists(uint16_t offset); +uint16_t Ireg(uint16_t offset); +``` + +### Remove reg + +```c +bool removeHreg(uint16_t offset, uint16_t numregs = 1); +bool removeCoil(uint16_t offset, uint16_t numregs = 1); +bool removeIsts(uint16_t offset, uint16_t numregs = 1); +bool removeIreg(uint16_t offset, uint16_t numregs = 1); +``` + +### Modbus RTU Specific API + +```c +bool begin(SoftwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); +bool begin(HardwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); +bool begin(Stream* port); +``` + +Assing Serial port. txEnablePin controls transmit enable for MAX-485. Pass txEnableDirect=false if txEnablePin uses inverse logic. + +```c +void setBaudrte(uint32 baud); +``` + +Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. + +```c +void server(uint8_t slaveId); +void slave(uint8_t slaveId); //Depricated +``` + +Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictible. + +```c +uint8_t server(); +uint8_t slave(); //Depricated +``` + +Slave mode: Returns configured slave id. Master mode: Returns slave id for active request or 0 if no request in-progress. + +## Modbus TCP Server specific API + +```c +void begin(); // Depricated. Use server() instead. +void slave(uint16_t port = MODBUSIP_PORT); // For compatibility with ModbusRTU calls. Typically may be replaced with server() call. +void server(uint16_t port = MODBUSIP_PORT); +``` + +## Modbus TCP Client specific + +```c +void master(); // For compatibility with ModbusRTU calls. Typically may be replaced with client() call. +void client(); +bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); +bool disconnect(IPAddress ip); +bool isTransaction(uint16_t id); +bool isConnected(IPAddress ip); +void dropTransactions(); +``` + +```c +void autoConnect(bool enabled); +``` + +Select behavior of executing read/write/pull/push. If autoConnect disabled (default) execution returns error if connection to slave is not already established. If autoConnect is enabled trying to establish connection during read/write/pull/push function call. Disabled by default. + +## Client API + +### Read Coils (0x01) from slave/server + +```c +uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(const char* host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(String host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +### Write single Coil to slave/server + +```c +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); + +uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +### Read Ists (0x02) from slave/server + +```c +uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); + +uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +```c +uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); +``` + +Result is saved to local registers. Method returns corresponding transaction id. [ip/from] or [ip/offset] - slave, [to] or [startreg] - local + +### Send [multiple] regs to remote slave/server + +```c +uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); +``` + +Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local + +### Write [multiple] values to remote slave/servr reg[s] + +```c +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); +``` + +Writes single value to remote Hreg/Coil. + +```c +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +``` + +Writes multiple values from array to remote Coil/Hreg. + +### Read values from multiple remote slave/server regs + +```c +uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); + +uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); +``` + +Reads values from remote Hreg/Coil/Ireg/Ists to array. + +## Callbacks API + +```c +void cbEnable(bool state = true); +void cbDisable(); +``` + +Callback generation control. Callback generation is enabled by default. *Has no effect on transactions callbacks.* + +```c +void onConnect(cbModbusConnect cb); +void onDisconnect(cbModbusConnect cb); +``` + +*Modbus TCP Server* Assign callback function on new incoming connection event. + +```c +typedef bool (*cbModbusConnect)(IPAddress ip); +``` + +*Modbus TCP Sserver* Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. + +```c +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); +``` + +Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. + +```c +typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); +``` + +Transaction end callback function definition. For ModbusIP *data* is currently reserved. For Modbus RTU *transactionId* is also reserved. + +```c +uint32_t eventSource(); +``` + +Should be called from onGet/onSet or transaction callback function. + +*Modbus TCP Client/Server* Returns IP address of remote requesting operation or INADDR_NONE for local. Use IPAddress(eventSource) to operate result as IPAddress type. + +*Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. + +*Modbus RTU Master/Slave* Returns slave id. + +```c +bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. + +```c +bool onGetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. + +```c +bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +Disconnect specific callback function or all callbacks of the type if cb=NULL. + +### Macros + +```c +#define COIL_VAL(v) +#define COIL_BOOL(v) +#define ISTS_VAL(v) +#define ISTS_BOOL(v) +``` + +## Contributions + +https://github.com/emelianov/modbus-esp8266 + +a.m.emelianov@gmail.com + +Original version: + +https://github.com/andresarmento/modbus-esp8266 +https://github.com/andresarmento/modbus-arduino + +prof (at) andresarmento (dot) com + +## License + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/documentation/README.md b/documentation/README.md new file mode 100644 index 0000000..d493b93 --- /dev/null +++ b/documentation/README.md @@ -0,0 +1,85 @@ +# FAQ + +This library allows your Arduino board to communicate via Modbus protocol. The Modbus is a protocol +used in industrial automation and also can be used in other areas, such as home automation. + +The Modbus generally uses serial RS-485 as physical layer (then called Modbus Serial) and TCP/IP via Ethernet or WiFi (Modbus TCP and Modbus TCP Security). + +--- + +## Where to get documentation for the library? + +- [API](API.md) +- [ModbusTCP](https://github.com/emelianov/modbus-esp8266/tree/master/examples/TCP-ESP#API) +- [ModbusRTU](https://github.com/emelianov/modbus-esp8266/tree/master/examples/RTU#Modbus-RTU-Specific-API) +- [Callbacks](https://github.com/emelianov/modbus-esp8266/tree/master/examples/Callback/#Callback-API) +- [Modbus Security](https://github.com/emelianov/modbus-esp8266/tree/master/examples/TLS) +- [Modbus File operations](https://github.com/emelianov/modbus-esp8266/tree/master/examples/Files#File-block-API) +- [Compile time settings](https://github.com/emelianov/modbus-esp8266/tree/master/src/ModbusSettings.h)) + +--- + +## Client work cycle diagram + +![Client diagram](https://github.com/emelianov/modbus-esp8266/blob/master/resources/client.png) + +--- + +## Server work cycle diagram + +![Server diagram](https://github.com/emelianov/modbus-esp8266/blob/master/resources/server.png) + +--- + +## How to send signed value (`int16_t`)? + +## How to send `float` or `uint32_t` values? + +Modbus standard defines only two types of data: bit value and 16-bit value. All other datatypes should be sent as multiple 16-bit values. + +--- + +## Value not read after `readCoil`/`readHreg`/etc + +The library is designed to execute calls async way. That is `readHreg()` function just sends read request to Modbus server device and exits. Responce is processed (as suun as it's arrive) by `task()`. `task()` is also async and exits if data hasn't arrive yet. + +--- + +## When calling `readCoil`/`readHreg`/`writeHreg`/etc multiple times only first of them executed + +--- + +## Transactional callback returns *0xE4* error + +It's timeout error. Suggestions below are applicable to persistent errors or frequently errors. Rare timeout errors may be normal in some considerations. + +### ModbusRTU + +Typically is indicates some kind of wiring or hardware problems. + +- Check wiring. +- Check that baudrate settings are identical for client and server. +- Try to reduce it to 9600bps. +- Try to use different power source for Arduino device. +- Try to replace RS-485 tranceiver. +- If using Modbus simulator software on PC check the result with alternative software. + +### ModbusTCP + +It maybe network problems. Use standard procedures as `ping` and firewall settings checks for diagnostics. + +--- + +## If it's possible to create ModbusTCP to ModbusRTU pass through bridge? + +Some ideas to implement full functional brodge may be taken from [this code](https://github.com/emelianov/modbus-esp8266/issues/101#issuecomment-755419095). +Very limited implementation is available in [example](https://github.com/emelianov/modbus-esp8266/examples/bridge). + +--- + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2021 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/Bridge/MultipleServerID/MultipleServerID.ino b/examples/Bridge/MultipleServerID/MultipleServerID.ino new file mode 100644 index 0000000..278a99b --- /dev/null +++ b/examples/Bridge/MultipleServerID/MultipleServerID.ino @@ -0,0 +1,51 @@ +/* + ModbusRTU ESP32 + Minimalistic example of server responding for multiple IDs. That is the server looks as multiple devices on bus. + + (c)2022 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include + +#define REGN 10 +#define PRIMARY_ID 1 +#define PRIMERT_VALUE 100 +#define SECONDARY_ID 2 +#define SECONDARY_VALUE 200 + +ModbusRTU mb; + +Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; // argument contains some data on incoming packet + Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); + if (src->slaveId == SECONDARY_ID) // Check if incoming packet is addresses to server with ID + return Modbus::EX_FORCE_PROCESS; // Instruct the library to force the packet processing + // It's required as otherwise packet will be not processed as not addressed + // to the server + + return Modbus::EX_PASSTHROUGH; // or process packet normally +} + +uint16_t cbRead(TRegister* reg, uint16_t val) { + if (mb.eventSource() == SECONDARY_ID) + return SECONDARY_VALUE; + return val; +} + +void setup() { + Serial.begin(115200); + Serial1.begin(9600); + mb.begin(&Serial1); + mb.slave(PRIMARY_ID); // Set Modbus to work as a server with ID + mb.onRaw(cbRtuRaw); // Assign raw packet callback + mb.addHreg(REGN, PRIMARY_VALUE); + mb.onGet(cbReadHreg) +} + +void loop() { + mb.task(); + yield(); +} \ No newline at end of file diff --git a/examples/Bridge/README.md b/examples/Bridge/README.md new file mode 100644 index 0000000..598fe48 --- /dev/null +++ b/examples/Bridge/README.md @@ -0,0 +1,63 @@ +# Bridge functions + +## [Basic](basic/basic.ino) + +Basic 'Bridge'. Indeed this sample pulling data from Modbus Server and stores it to local registers. Local registers can be accessed via Modbus Client instance that running aside. + +## [ModbusRTU to ModbusTCP bridge](true/true.ino) + +Fullfunctional ModbusRTU to ModbusTCP bridge. + +## [Multiple Server ID](MultipleServerID/MultipleServerID.ino) + +Respond for multiple ModbusRTU IDs from single device + +## [ModbusTCP to Modbus RTU Simulator](TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino) + +Fullfunctional ModbusTCP to ModbusRTU bridge with on-device ModbusRTU simulator + +```c +uint16_t rawRequest(id_ip, uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +uint16_t rawResponce(id_ip, uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); +uint16_t errorResponce(id_ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT); +``` +- `id_ip` SlaveId (`uint8_t`) or server IP address (`IPAddress`) +- `data` Pointer to data buffer to send +- `len` Byte count to send +- `unit` UnitId (ModbusTCP/TLS only) +- `fn` function code in responce +- `excode` Exception code in responce + +```c +uint16_t setTransactionId(uint16_t id); +``` +- `id` Value to replace transaction id sequence (ModbusTCP/TLS only) + +```c +union frame_arg_t { +struct frame_arg_t { + bool to_server; // true if frame is responce for local Modbus server/slave + union { + // For ModbusRTU + uint8_t slaveId; + // For ModbusTCP/TLS + struct { + uint8_t unitId; // UnitId as passed in MBAP header + uint32_t ipaddr; // IP address from which frame is received + uint16_t transactionId; // TransactionId as passed in MBAP header + }; + }; +}; +typedef std::function cbRaw; // Callback function Type for STL +typedef ResultCode (*cbRaw)(uint8_t* frame, uint8 len, void* data); // Callback function Type +bool onRaw(cbRaw cb = nullptr); +``` +- `frame` Modbus payload frame with stripped MBAP/slaveid and crc +- `len` frame size in bytes +- `data` Pointer to frame_arg_t filled with frame header information + +*Returns:* +- If a special error code `Modbus::EX_PASSTHROUGH` returned frame will be processed normally +- If a special error code `Modbus::EX_FORCE_PROCESS` returned frame will be processed even if addressed to another Modbus unit +- Any other return code disables normal frame processing. Only transactional callback will be executed (if any and transaction data is correct) +The callback is executed only on Modbus frame with valid header and CRC. \ No newline at end of file diff --git a/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino new file mode 100644 index 0000000..cacf05d --- /dev/null +++ b/examples/Bridge/TCP-to-RTU-Simulator/TCP-to-RTU-Simulator.ino @@ -0,0 +1,138 @@ +/* + ModbusRTU ESP8266/ESP32 + ModbusTCP to ModbusRTU bridge with on-device ModbusRTU simulator +*/ +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include +#include +//#include +//SoftwareSerial S(13, 15); +#include +#define BSIZE 1024 +uint8_t buf1[BSIZE]; +uint8_t buf2[BSIZE]; +StreamBuf S1(buf1, BSIZE); +StreamBuf S2(buf2, BSIZE); +DuplexBuf P1(&S1, &S2); +DuplexBuf P2(&S2, &S1); +ModbusRTU sym; + +int DE_RE = 2; + +ModbusRTU rtu; +ModbusTCP tcp; + +IPAddress srcIp; + + +uint16_t transRunning = 0; // Currently executed ModbusTCP transaction +uint8_t slaveRunning = 0; // Current request slave + +bool cbTcpTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place + tcp.disconnect(tcp.eventSource()); // Close connection + transRunning = 0; + slaveRunning = 0; + } + return true; +} + +bool cbRtuTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + return true; +} + + +// Callback receives raw data +Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + + Serial.print("TCP IP in - "); + Serial.print(IPAddress(src->ipaddr)); + Serial.printf(" Fn: %02X, len: %d \n\r", data[0], len); + + if (transRunning) { // Note that we can't process new requests from TCP-side while waiting for responce from RTU-side. + tcp.setTransactionId(src->transactionId); // Set transaction id as per incoming request + tcp.errorResponce(IPAddress(src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_SLAVE_DEVICE_BUSY); + return Modbus::EX_SLAVE_DEVICE_BUSY; + } + + rtu.rawRequest(src->unitId, data, len, cbRtuTrans); + + if (!src->unitId) { // If broadcast request (no responce from slave is expected) + tcp.setTransactionId(src->transactionId); // Set transaction id as per incoming request + tcp.errorResponce(IPAddress(src->ipaddr), (Modbus::FunctionCode)data[0], Modbus::EX_ACKNOWLEDGE); + + transRunning = 0; + slaveRunning = 0; + return Modbus::EX_ACKNOWLEDGE; + } + + srcIp = IPAddress(src->ipaddr); + + slaveRunning = src->unitId; + + transRunning = src->transactionId; + + return Modbus::EX_SUCCESS; + +} + + +// Callback receives raw data from ModbusTCP and sends it on behalf of slave (slaveRunning) to master +Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + if (!transRunning) // Unexpected incoming data + return Modbus::EX_PASSTHROUGH; + tcp.setTransactionId(transRunning); // Set transaction id as per incoming request + uint16_t succeed = tcp.rawResponce(srcIp, data, len, slaveRunning); + if (!succeed){ + Serial.print("TCP IP out - failed"); + } + Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); + Serial.print("Response TCP IP: "); + Serial.println(srcIp); + + transRunning = 0; + slaveRunning = 0; + return Modbus::EX_PASSTHROUGH; +} + + +void setup() { + Serial.begin(115000); + WiFi.begin("E2", "*****"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + tcp.server(); // Initialize ModbusTCP to pracess as server + tcp.onRaw(cbTcpRaw); // Assign raw data processing callback + + //S.begin(19200, SWSERIAL_8E1); + //rtu.begin(&S, DE_RE); // Specify RE_DE control pin + sym.begin((Stream*)&P2); + sym.slave(1); + sym.addHreg(1, 100); + rtu.begin((Stream*)&P1); // Specify RE_DE control pin + rtu.master(); // Initialize ModbusRTU as master + rtu.onRaw(cbRtuRaw); // Assign raw data processing callback +} + +void loop() { + sym.task(); + rtu.task(); + tcp.task(); + yield(); +} diff --git a/examples/Bridge/basic/basic.ino b/examples/Bridge/basic/basic.ino new file mode 100644 index 0000000..acf71c0 --- /dev/null +++ b/examples/Bridge/basic/basic.ino @@ -0,0 +1,53 @@ +/* + Modbus ESP8266/ESP32 + Simple ModbesRTU to ModbusIP bridge + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include +#include + +#define TO_REG 10 +#define SLAVE_ID 1 +#define PULL_ID 1 +#define FROM_REG 20 + +ModbusRTU mb1; +ModbusIP mb2; + +void setup() { + Serial.begin(115200); + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + Serial1.begin(9600, SERIAL_8N1); // Init Serial on default pins + //Serial2.begin(19200, SERIAL_8N1, 19, 18); // Override default pins for ESP32 + mb1.begin(&Serial1); + //mb1.begin(&Serial2, 17); // Specify RE_DE control pin + mb1.master(); + mb2.server(); + mb2.addHreg(TO_REG); +} + +void loop() { + if(!mb1.slave()) + mb1.pullHreg(PULL_ID, FROM_REG, TO_REG); + mb1.task(); + mb2.task(); + delay(50); +} \ No newline at end of file diff --git a/examples/Bridge/true/true.ino b/examples/Bridge/true/true.ino new file mode 100644 index 0000000..b4d3236 --- /dev/null +++ b/examples/Bridge/true/true.ino @@ -0,0 +1,121 @@ +/* + ModbusRTU ESP8266/ESP32 + True RTU-TCP bridge example + + (c)2021 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include +#include +#include +#include + +ModbusRTU rtu; +ModbusTCP tcp; + +// ModbusRTU(SlaveID) => ModbusTCP(IP) mapping table +struct slave_map_t { + uint8_t slaveId; // Slave id in incoming request + IPAddress ip; // IP address of MosbusTCP Server map request to + uint8_t unitId = MODBUSIP_UNIT; // UnitId on target server + slave_map_t(uint8_t s, IPAddress i, uint8_t u = MODBUSIP_UNIT) { + slaveId = s; + ip = i; + unitId = u; + }; +}; +std::vector mapping; // Slave => IP mappings +uint16_t transRunning = 0; // Currently executed ModbusTCP transaction +uint8_t slaveRunning = 0; // Current request slave + +bool cbTcpTrans(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X, Mem: %d\n", event, ESP.getFreeHeap()); // Display Modbus error code (222527) + if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place + tcp.disconnect(tcp.eventSource()); // Close connection + } + return true; +} + +// Callback receives raw data from ModbusTCP and sends it on behalf of slave (slaveRunning) to master +Modbus::ResultCode cbTcpRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + Serial.print("TCP IP: "); + Serial.print(IPAddress(src->ipaddr)); + Serial.printf(" Fn: %02X, len: %d \n", data[0], len); + if (!src->to_server && transRunning == src->transactionId) { // Check if transaction id is match + rtu.rawResponce(slaveRunning, data, len); + } else + return Modbus::EX_PASSTHROUGH; // Allow frame to be processed by generic ModbusTCP routines + transRunning = 0; + slaveRunning = 0; + return Modbus::EX_SUCCESS; // Stop other processing +} + + +// Callback receives raw data +Modbus::ResultCode cbRtuRaw(uint8_t* data, uint8_t len, void* custom) { + auto src = (Modbus::frame_arg_t*) custom; + Serial.printf("RTU Slave: %d, Fn: %02X, len: %d, ", src->slaveId, data[0], len); + auto it = std::find_if(mapping.begin(), mapping.end(), [src](slave_map_t& item){return (item.slaveId == src->slaveId);}); // Find mapping + if (it != mapping.end()) { + if (!tcp.isConnected(it->ip)) { // Check if connection established + if (!tcp.connect(it->ip)) { // Try to connect if not + Serial.printf("error: Connection timeout\n"); + + rtu.errorResponce(it->slaveId, (Modbus::FunctionCode)data[0], Modbus::EX_DEVICE_FAILED_TO_RESPOND); // Send exceprional responce to master if no connection established + // Note: + // Indeed if both sides is build with the Modbus library _default settings_ RTU master side initiating requests to bridge will respond EX_TIMEOUT not EX_DEVICE_FAILED_TO_RESPOND. + // That's because connection timeout and RTU responce timeout are the same (1 second). That case EX_TIMEOUT on reached prior getting EX_DEVICE_FAILED_TO_RESPOND frame. + return Modbus::EX_DEVICE_FAILED_TO_RESPOND; // Stop processing the frame + } + } + // Save transaction ans slave it for responce processing + transRunning = tcp.rawRequest(it->ip, data, len, cbTcpTrans, it->unitId); + if (!transRunning) { // rawRequest returns 0 is unable to send data for some reason + tcp.disconnect(it->ip); // Close TCP connection that case + Serial.printf("send failed\n"); + rtu.errorResponce(it->slaveId, (Modbus::FunctionCode)data[0], Modbus::EX_DEVICE_FAILED_TO_RESPOND); // Send exceprional responce to master if request bridging failed + return Modbus::EX_DEVICE_FAILED_TO_RESPOND; // Stop processing the frame + } + Serial.printf("transaction: %d\n", transRunning); + slaveRunning = it->slaveId; + return Modbus::EX_SUCCESS; // Stop procesing the frame + } + Serial.printf("ignored: No mapping\n"); + return Modbus::EX_PASSTHROUGH; // Process by generic ModbusRTU routines if no mapping found +} + + +void setup() { + Serial.begin(115000); + WiFi.begin("SSID", "PASSWORD"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + tcp.client(); // Initialize ModbusTCP to pracess as client + tcp.onRaw(cbTcpRaw); // Assign raw data processing callback + + Serial1.begin(9600, SERIAL_8N1, 18, 19); + rtu.begin(&Serial1); + rtu.slave(3); // Initialize ModbusRTU as slave + rtu.onRaw(cbRtuRaw); // Assign raw data processing callback + +// Assign mappings + mapping.push_back({1, IPAddress(192,168,30,18)}); + mapping.push_back({2, IPAddress(192,168,30,19)}); +} + +void loop() { + rtu.task(); + tcp.task(); + yield(); +} \ No newline at end of file diff --git a/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino b/examples/Callback/IP-server-MultipleHRegDebug/IPserver-MultipleHRegDebug.ino new file mode 100644 index 0000000..7f717b1 --- /dev/null +++ b/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/examples/Callback/README.md b/examples/Callback/README.md new file mode 100644 index 0000000..6a7e0d9 --- /dev/null +++ b/examples/Callback/README.md @@ -0,0 +1,124 @@ +# Callbacks + +## [Register read/write callback](onSet/onSet.ino) + +## [Use one callback function for multiple registers](onGetShared/onGetShared.ino) + +## [Incoming request callback (applicable to server/slave)](Request/Request.ino) + +## [Modbus TCP/TLS Incoming connection callback](onSet/onSet.ino) + +## [Modbus TCP/TLS Transaction result](Transactional/Transactional.ino) + +### Callback API + +```c +bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +- `address` Address of register assign callback on +- `cb` Callback function +- `numregs` Count of sequental segisters assign this callback to + +Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. + + +```c +bool onGetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +- `address` Address of register assign callback on +- `cb` Callback function +- `numregs` Count of sequental segisters assign this callback to + +Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. + +```c +bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); +``` + +- `address` Address of register assign callback on +- `cb` Callback function or NULL to remove all the callbacks. +- `numregs` Count of sequental segisters remove this callback to. + +Disconnect specific callback function or all callbacks of the type if cb=NULL. + +```c +typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode fc, const Modbus::RequestData data); +bool onRequest(cbRequest cb = _onRequestDefault); +bool onRequestSuccess(cbRequest cb = _onRequestDefault); + +union Modbus::RequestData { + struct { + TAddress reg; + uint16_t regCount; + }; + struct { + TAddress regRead; + uint16_t regReadCount; + TAddress regWrite; + uint16_t regWriteCount; + }; + struct { + TAddress regMask; + uint16_t andMask; + uint16_t orMask; + }; +}; +``` + +Callback function receives Modbus function code, structure `Modbus::RequestData` containing register type and offset (`TAddress` structure) and count of registers requested. The function should return [result code](#Result codes *Modbus::ResultCode*) `Modbus::EX_SUCCESS` to allow request processing or Modbus error code to block processing. This code will be returned to client/master. + +```c +void onConnect(cbModbusConnect cb); +void onDisonnect(cbModbusConnect cb); +``` + +Assign callback function on incoming connection event. + +```c +typedef bool (*cbModbusConnect)(IPAddress ip); +``` + +- `ip` Client's address of incomig connection source. `INADDR_NONE` for on disconnect callback. + +## Result codes *Modbus::ResultCode* + +|Value|Hex|Definition|Decription| +|---|---|---|---| +|Modbus::EX_SUCCESS|0x00|Custom|No error| +|Modbus::EX_ILLEGAL_FUNCTION|0x01|Modbus|Function Code not Supported| +|Modbus::EX_ILLEGAL_ADDRESS|0x02|Modbus|Output Address not exists| +|Modbus::EX_ILLEGAL_VALUE|0x03|Modbus|Output Value not in Range| +|Modbus::EX_SLAVE_FAILURE|0x04|Modbus|Slave or Master Device Fails to process request +|Modbus::EX_ACKNOWLEDGE|0x05|Modbus|Not used| +|Modbus::EX_SLAVE_DEVICE_BUSY|0x06|Modbus|Not used| +|Modbus::EX_MEMORY_PARITY_ERROR|0x08|Modbus|Not used| +|Modbus::EX_PATH_UNAVAILABLE|0x0A|Modbus|Not used| +|Modbus::EX_DEVICE_FAILED_TO_RESPOND|0x0B|Modbus|Not used| +|Modbus::EX_GENERAL_FAILURE|0xE1|Custom|Unexpected master error| +|Modbus::EX_DATA_MISMACH|0xE2|Custom|Inpud data size mismach| +|Modbus::EX_UNEXPECTED_RESPONSE|0xE3|Custom|Returned result doesn't mach transaction| +|Modbus::EX_TIMEOUT|0xE4|Custom|Operation not finished within reasonable time| +|Modbus::EX_CONNECTION_LOST|0xE5|Custom|Connection with device lost| +|Modbus::EX_CANCEL|0xE6|Custom|Transaction/request canceled| + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. \ No newline at end of file diff --git a/examples/Callback/Request/Request.ino b/examples/Callback/Request/Request.ino new file mode 100644 index 0000000..fea3dea --- /dev/null +++ b/examples/Callback/Request/Request.ino @@ -0,0 +1,66 @@ +/* + Modbus-Arduino Example - Using request processing callback to control access to Hreg write operations + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include + +const uint16_t RO_FLAG = 100; // Coil register to allow/disallow Hregs write +const uint16_t RW_HREG = 200; // Sample Hreg + +//ModbusIP object +ModbusTCP mb; + +Modbus::ResultCode cbPreRequest(Modbus::FunctionCode fc, const Modbus::RequestData data) { + Serial.printf("PRE Function: %02X\n", fc); + if ((fc == Modbus::FC_WRITE_REG || fc == Modbus::FC_WRITE_REGS) && mb.Coil(RO_FLAG)) + return Modbus::EX_ILLEGAL_FUNCTION; + return Modbus::EX_SUCCESS; +} + +Modbus::ResultCode cbPostRequest(Modbus::FunctionCode fc, const Modbus::RequestData data) { + Serial.printf("POST Function: %02X\n", fc); + return Modbus::EX_SUCCESS; +} + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("SID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + mb.onConnect(cbConn); // Add callback on connection event + mb.onRequest(cbPreRequest); + mb.onRequestSuccess(cbPostRequest); + mb.server(); + + mb.addCoil(RO_FLAG); + mb.addHreg(RW_HREG, 100); +} + +void loop() { + mb.task(); + delay(10); +} \ No newline at end of file diff --git a/examples/Callback/Transactional/Transactional.ino b/examples/Callback/Transactional/Transactional.ino new file mode 100644 index 0000000..68fd532 --- /dev/null +++ b/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/examples/Callback/onGetShared/onGetShared.ino b/examples/Callback/onGetShared/onGetShared.ino new file mode 100644 index 0000000..554e114 --- /dev/null +++ b/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/examples/Callback/onSet/onSet.ino b/examples/Callback/onSet/onSet.ino new file mode 100644 index 0000000..d604f49 --- /dev/null +++ b/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/examples/ClearCore/README.md b/examples/ClearCore/README.md new file mode 100644 index 0000000..f2a5f21 --- /dev/null +++ b/examples/ClearCore/README.md @@ -0,0 +1,3 @@ +# Examples for [Teknic ClearCore ArduinoWrapper](https://github.com/Teknic-Inc/ClearCore-Arduino-wrapper) + +Not tested on real hardware just based on documentation. \ No newline at end of file diff --git a/examples/ClearCore/TCP-Client/TCP-Client.ino b/examples/ClearCore/TCP-Client/TCP-Client.ino new file mode 100644 index 0000000..a93862f --- /dev/null +++ b/examples/ClearCore/TCP-Client/TCP-Client.ino @@ -0,0 +1,72 @@ +/* + ModbusTCP Client for ClearCode Arduino wrapper + + (c)2021 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include // Ethernet library v2 is required + +#include +#include + +class ModbusEthernet : public ModbusAPI> {}; + +const uint16_t REG = 512; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 12); // Address of Modbus Slave device +const int32_t showDelay = 5000; // Show result every n'th mellisecond + +bool usingDhcp = true; +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE }; // MAC address for your controller +IPAddress ip(192, 168, 30, 178); // The IP address will be dependent on your local network +ModbusEthernet mb; // Declare ModbusTCP instance + +void setup() { + Serial.begin(9600); + uint32_t timeout = 5000; + uint32_t startTime = millis(); + while (!Serial && millis() - startTime < timeout) + continue; + + // Get the Ethernet module up and running. + if (usingDhcp) { + int dhcpSuccess = Ethernet.begin(mac); + if (dhcpSuccess) + Serial.println("DHCP configuration was successful."); + else { + Serial.println("DHCP configuration was unsuccessful!"); + Serial.println("Try again using a manual configuration..."); + while (true) + continue; + } + } + else { + Ethernet.begin(mac, ip); + } + + // Make sure the physical link is up before continuing. + while (Ethernet.linkStatus() == LinkOFF) { + Serial.println("The Ethernet cable is unplugged..."); + delay(1000); + } + mb.client(); // Act as Modbus TCP server +} + +uint16_t res = 0; +uint32_t showLast = 0; + +void loop() { +if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Slave + } else { + mb.connect(remote); // Try to connect if not connected + } + delay(100); // Pulling interval + mb.task(); // Common local Modbus task + if (millis() - showLast > showDelay) { // Display register value every 5 seconds (with default settings) + showLast = millis(); + Serial.println(res); + } +} \ No newline at end of file diff --git a/examples/ClearCore/TCP-Server/TCP-Server.ino b/examples/ClearCore/TCP-Server/TCP-Server.ino new file mode 100644 index 0000000..abf844c --- /dev/null +++ b/examples/ClearCore/TCP-Server/TCP-Server.ino @@ -0,0 +1,67 @@ +/* + ModbusTCP Server for ClearCore Arduino wrapper + + (c)2021 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include // Ethernet library v2 is required + +#include +#include + +class ModbusEthernet : public ModbusAPI> {}; + +const uint16_t REG = 512; // Modbus Hreg Offset +const int32_t showDelay = 5000; // Show result every n'th mellisecond + +bool usingDhcp = true; +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE }; // MAC address for your controller +IPAddress ip(192, 168, 30, 178); // The IP address will be dependent on your local network +ModbusEthernet mb; // Declare ModbusTCP instance + +// Callback function for client connect. Returns true to allow connection. +bool cbConn(IPAddress ip) { + Serial.println(ip); + return true; +} + +void setup() { + Serial.begin(9600); + uint32_t timeout = 5000; + uint32_t startTime = millis(); + while (!Serial && millis() - startTime < timeout) + continue; + + // Get the Ethernet module up and running. + if (usingDhcp) { + int dhcpSuccess = Ethernet.begin(mac); + if (dhcpSuccess) + Serial.println("DHCP configuration was successful."); + else { + Serial.println("DHCP configuration was unsuccessful!"); + Serial.println("Try again using a manual configuration..."); + while (true) + continue; + } + } + else { + Ethernet.begin(mac, ip); + } + + // Make sure the physical link is up before continuing. + while (Ethernet.linkStatus() == LinkOFF) { + Serial.println("The Ethernet cable is unplugged..."); + delay(1000); + } + mb.server(); // Act as Modbus TCP server + mb.onConnect(cbConn); + mb.addHreg(100); // Expose Holding Register #100 +} + +void loop() { + mb.task(); // Common local Modbus task + delay(10); +} \ No newline at end of file diff --git a/examples/Files/FW-Update-Source/FW-Update-Source.ino b/examples/Files/FW-Update-Source/FW-Update-Source.ino new file mode 100644 index 0000000..5be2231 --- /dev/null +++ b/examples/Files/FW-Update-Source/FW-Update-Source.ino @@ -0,0 +1,167 @@ +/* + Modbus Library for Arduino Example - Modbus RTU Firmware Update - ESP8266/ESP32 + Update main node to update from. + Hosts simple web-server to upload firmware file and writes it to update target. + + (c)2021 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ +#include +ModbusRTU rtu; + +#if defined(ESP8266) + #include + #include +#else + #include +#include +#include +#endif +WebServer web(80); + +#if defined(ESP8266) + #include + // SoftwareSerial S(D1, D2, false, 256); + + // receivePin, transmitPin, inverse_logic, bufSize, isrBufSize + // connect RX to D2 (GPIO4, Arduino pin 4), TX to D1 (GPIO5, Arduino pin 4) + SoftwareSerial S(4, 5); +#endif + +#define UPDATE_ENABLE 301 +#define UPDATE_FILE 301 +#define SLAVE_ID 1 +#define BLOCK_SIZE 64 + +uint32_t written = 0; +bool updating = false; +Modbus::ResultCode result = Modbus::EX_GENERAL_FAILURE; +bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback + if (event != Modbus::EX_SUCCESS) // If transaction got an error + Serial.printf("Modbus result: %02X\n", event); // Display Modbus error code + result = event; + return true; +} + +void handlePage() { + String output = F(R"EOF( + +
+ Update firmware:
+ +
+ +)EOF"); + web.sendHeader("Connection", "close"); + web.sendHeader("Cache-Control", "no-store, must-revalidate"); + web.sendHeader("Access-Control-Allow-Origin", "*"); + web.send(200, "text/html; charset=utf-8", output); +} + +void updateHandle() { + web.sendHeader("Connection", "close"); + web.sendHeader("Refresh", "10; url=/"); + web.send(200, "text/plain", (Update.hasError())?"FAIL":"OK"); +} + +void updateUploadHandle() { + uint8_t* data = nullptr; + uint16_t remaining; + HTTPUpload& upload = web.upload(); + switch (upload.status) { + case UPLOAD_FILE_START: + result = Modbus::EX_GENERAL_FAILURE; + rtu.writeCoil(SLAVE_ID, UPDATE_ENABLE, true, cb); + while (rtu.server()) { + rtu.task(); + yield(); + } + updating = (result == Modbus::EX_SUCCESS); + written = 0; + Serial.print("O"); + break; + case UPLOAD_FILE_WRITE: + if (!updating) + break; + Serial.print("o"); + data = upload.buf; + remaining = upload.currentSize / 2; + while (remaining) { + uint16_t amount = (remaining > BLOCK_SIZE)?BLOCK_SIZE:remaining; + result = Modbus::EX_GENERAL_FAILURE; + if (!rtu.writeFileRec(SLAVE_ID, UPDATE_FILE, 0, amount, data, cb)) { + updating = false; + Serial.println("X:send"); + break; + } + while (rtu.server()) { + rtu.task(); + yield(); + } + remaining -= amount; + data += amount * 2; + written += amount; + if (result != Modbus::EX_SUCCESS) { + updating = false; + Serial.println("X"); + break; + } + Serial.print("."); + } + break; + case UPLOAD_FILE_END: + if (!updating) + break; + rtu.writeCoil(SLAVE_ID, UPDATE_ENABLE, false); + while (rtu.server()) { + rtu.task(); + yield(); + } + updating = false; + Serial.println("!"); + Serial.print("Written: "); + Serial.println(written * 2); + break; + default: + if (updating) { + rtu.writeCoil(SLAVE_ID, UPDATE_ENABLE, false); + while (rtu.server()) { + rtu.task(); + yield(); + } + updating = false; + } + Serial.print("X"); + } +} + +void setup() { + Serial.begin(115200); + + WiFi.begin("SSID", "PASSWORD"); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.println(""); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + +#if defined(ESP8266) + S.begin(19200, SWSERIAL_8N1); + rtu.begin(&S); +#else + Serial1.begin(19200, SERIAL_8N1, 18, 19); + rtu.begin(&Serial1); + rtu.client(); + #endif + web.on("/update", HTTP_POST, updateHandle, updateUploadHandle); + web.on("/", HTTP_GET, handlePage); + web.begin(); +} + +void loop() { + web.handleClient(); + rtu.task(); + yield(); +} \ No newline at end of file diff --git a/examples/Files/FW-Update-Target/FW-Update-Target.ino b/examples/Files/FW-Update-Target/FW-Update-Target.ino new file mode 100644 index 0000000..5864929 --- /dev/null +++ b/examples/Files/FW-Update-Target/FW-Update-Target.ino @@ -0,0 +1,100 @@ +/* + Modbus Library for Arduino Example - Modbus RTU Firmware Update - ESP8266/ESP32 + Update target node to update. + Receives firmware to upload and flashes it. + + (c)2021 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ +#include +ModbusRTU rtu; +#if defined (ESP32) + #include + #define ESP32_SKETCH_SIZE 1310720 +#endif +#if defined(ESP8266) + #include + // SoftwareSerial S(D1, D2, false, 256); + + // receivePin, transmitPin, inverse_logic, bufSize, isrBufSize + // connect RX to D2 (GPIO4, Arduino pin 4), TX to D1 (GPIO5, Arduino pin 4) + SoftwareSerial S(4, 5); +#endif + +#define UPDATE_ENABLE 301 +#define UPDATE_FILE 301 +#define SLAVE_ID 1 +uint32_t written = 0; +uint16_t update_enable(TRegister* reg, uint16_t val) { + uint32_t sketchSpace; + if (rtu.Reg(reg->address)) { + if (COIL_BOOL(val)) + return BIT_VAL(true); + Serial.println("!"); + Serial.print("Written: "); + Serial.println(written * 2); + if(Update.end(true)){ //true to set the size to the current progress + Serial.println("Update Success. \nRebooting..."); + } + return BIT_VAL(false); + } + #ifdef ESP8266 + sketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + #else + sketchSpace = ESP32_SKETCH_SIZE; + #endif + Serial.printf("Starting update. FW max sise: %ld\n", sketchSpace); + if(!Update.begin(sketchSpace)) { + Update.printError(Serial); + return BIT_VAL(false); + } + written = 0; + return BIT_VAL(true); +} + +// Expected arguments are: +// func = EX_WRITE_FILE_REC +// fileNum = UPDATE_FILE +// recNumber ignored +// recLength = data size (words) +// frame = data to write ptr +Modbus::ResultCode handle_file(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame) { + if (func != Modbus::FC_WRITE_FILE_REC) { + Serial.println("X:func"); + return Modbus::EX_ILLEGAL_FUNCTION; + } + if (!rtu.Reg(COIL(UPDATE_ENABLE))) { + Serial.println("X:idle"); + return Modbus::EX_ILLEGAL_VALUE; + } + if (fileNum != UPDATE_FILE) { + Serial.println("X:file"); + return Modbus::EX_ILLEGAL_VALUE; + } + Serial.print("."); + if(Update.write(frame, recLength * 2) != recLength * 2){ + Update.printError(Serial); + } + written += recLength; + return Modbus::EX_SUCCESS; +} + +void setup() { + Serial.begin(115200); +#if defined(ESP8266) + S.begin(19200, SWSERIAL_8N1); + rtu.begin(&S); +#else + Serial1.begin(19200, SERIAL_8N1, 18, 19); + rtu.begin(&Serial1); + #endif + rtu.server(SLAVE_ID); + rtu.onFile(handle_file); + rtu.addReg(COIL(UPDATE_ENABLE)); + rtu.onSet(COIL(UPDATE_ENABLE), update_enable); +} + +void loop() { + rtu.task(); + yield(); +} \ No newline at end of file diff --git a/examples/Files/README.md b/examples/Files/README.md new file mode 100644 index 0000000..078d7b3 --- /dev/null +++ b/examples/Files/README.md @@ -0,0 +1,62 @@ +# Files operations + +## [Firmware update over ModbusRTU - Update source node](FW-Update-Source/FW-Update-Source.ino) + +ModbusRTU client that pushes firmware to server node. + +How to use: +* Connect to target node +* Prapare binary image (Sketch - Export compiled binary) +* Open http:/// in browser +* Choose firmware file +* Press **Update firmware** +* Debug information on update pregress is available in debug console + +## [Firmware update over ModbusRTU - Update target node](FW-Update-Source/FW-Update-Source.ino) + +ModbusRTU server that receives and flashes new firmware. + +## File block API + +### Client side + +```c +uint16_t readFileRec(uint8_t slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb); +uint16_t writeFileRec(uint8_t slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb); + +uint16_t readFileRec(IPAddress slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit); +uint16_t writeFileRec(IPAddress slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit); +``` + +- `slaveId` server id or IP Address +- `fileNum` File number to access +- `startRec` Start offset in file (words) +- `len` Length of data (words) +- `*data` Pointer to data. In case of `readFileRec` must be at least `len` * 2 bytes. +- `cb` Transactional callback function +- `unit` ModbusTCP unit id + +### Server side + +```c +typedef std::function cbModbusFileOp; // ST: +typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame); // no-STL + +bool onFile(std::function); // STL +bool onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)); // no-STL +``` + +- `func` function code to process (FC_READ_FILE_REC or FC_WRITE_FILE_REC) +- `fileNum` file # to read/write +- `recNumber` record number in file (record size is word = 2 bytes) +- `recLength` number of records to read/write +- `*frame` pointer to data buffer + +`onFile` sets file operations handler function. + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2021 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..c82ba51 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,36 @@ +# Examples and API explanation + +## [RTU](RTU) + +ModbusRTU master and slave examples + +## [TCP ESP8266/ESP32](TCP-ESP) + +ModbusTCP for ESP8266/ESP32 client and server examples + +## [TCP Ethernet W5x00](TCP-Ethernet) + +ModbusTCP for W5x00 Ethernet library client and server examples (for all Arduino). + +## [TLS ESP8266/ESP32](TLS) + +ModbusTCP Security for ESP8266 and ESP32 (client only) examples. + +## [Callbacks usage](Callback) + +Examples of using callback functions. + +## [Files operations](Files) + +Modbus file operations examples. + +## [ModbusRTU to ModbusTCP bridge and related functions](Bridge) + +Very basic example of accessing ModbusRTU slave device connected to ESP8266/ESP32 via ModbusTCP server. + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino new file mode 100644 index 0000000..2cee07c --- /dev/null +++ b/examples/RTU/ESP32-Concurent/ESP32-Concurent.ino @@ -0,0 +1,101 @@ +/* + ModbusRTU ESP32 + Concurent thread example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + Tool Modbus Slave on PC for test + https://www.modbustools.com/download.html +*/ + +#include + +#define REG 0 +#define REG_NUM 32 +#define SLAVE_ID1 51 +#define SLAVE_ID2 52 + +#define MBUS_HW_SERIAL Serial1 +#define MBUS_TXD_PIN 16 +#define MBUS_RXD_PIN 35 + +ModbusRTU mb; + +xSemaphoreHandle xMutex; +Modbus::ResultCode err; + +Modbus::ResultCode readSync(uint8_t address, uint16_t start, uint16_t num, uint16_t* buf) { + xSemaphoreTake(xMutex, portMAX_DELAY); + if (mb.slave()) { + xSemaphoreGive(xMutex); + return Modbus::EX_GENERAL_FAILURE; + } + Serial.printf("SlaveID: %d Hreg %d\r\n", address, start); + mb.readIreg(address, start, buf, num, [](Modbus::ResultCode event, uint16_t, void*) { + err = event; + return true; + }); + while (mb.slave()) { + vTaskDelay(1); + mb.task(); + } + Modbus::ResultCode res = err; + xSemaphoreGive(xMutex); + return res; +} + +void loop1( void * pvParameters ); +void loop2( void * pvParameters ); + +void setup() { + Serial.begin(115200); + MBUS_HW_SERIAL.begin(9600, SERIAL_8N1, MBUS_RXD_PIN, MBUS_TXD_PIN); + mb.begin(&MBUS_HW_SERIAL); + mb.master(); + xMutex = xSemaphoreCreateMutex(); + xTaskCreatePinnedToCore( + loop1, /* Task function. */ + "Task1", /* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 10, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 0); /* pin task to core 1 */ + + xTaskCreatePinnedToCore( + loop2, /* Task function. */ + "Task2", /* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 1); /* pin task to core 1 */ + +} + +uint16_t hregs1[REG_NUM]; +void loop1( void * pvParameters ){ + while(true) { + delay(10); + if (readSync(SLAVE_ID1, REG, REG_NUM, hregs1) == Modbus::EX_SUCCESS) + Serial.println("OK 1"); + else + Serial.println("Error 1"); + } +} + +uint16_t hregs2[REG_NUM]; +void loop2( void * pvParameters ){ + while(true) { + delay(100); + if (readSync(SLAVE_ID2, REG, REG_NUM, hregs2) == Modbus::EX_SUCCESS) + Serial.println("OK 2"); + else + Serial.println("Error 2"); + } +} + +void loop() { + delay(100); +} diff --git a/examples/RTU/README.MD b/examples/RTU/README.MD new file mode 100644 index 0000000..1f4e4b9 --- /dev/null +++ b/examples/RTU/README.MD @@ -0,0 +1,57 @@ +This example introduces how to use the library for ModbusRTU (typicaly over RS-485) to act as [master](master) or [slave](slave). Additionally there is [example of master](ESP32-Concurent) device for multithread usage with ESP32. + +## [Concurrent thread-safe access to Modbus object](ESP32-Concurent/ESP32-Concurent.ino) + +## [Simple ModbusRTU master](master/master.ino) + +## [Simple ModbusRTU slave](slave/slave.ino) + +## [Sync ModbusRTU master](masterSync/masterSync.ino) + +## Modbus RTU Specific API + +```c +bool begin(SoftwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); +bool begin(HardwareSerial* port, int16_t txEnablePin=-1, bool txEnableDirect=true); +bool begin(Stream* port); +``` + +- `port` Pointer to Serial port +- `txEnablePin` RX/TX control pin. Not assigned (assume auto RX/TX) by default +- `txEnableDirect` Direct (true, default) or inverse (false) RX/TX pin control. + +Assign Serial port. txEnablePin controls transmit enable for MAX-485. + +```c +void setBaudrate(uint32 baud); +``` + +- `baud` New baudrate. + +Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. + +```c +void client(); +void server(uint8_t slaveId); +void slave(); // Deprecated +void master(uint8_t slaveId); // Deprecated +``` + +- `slaveId` Modbus slave id to associate to. + +Select and initialize master or slave mode to work. Switching between modes is not supported. Call is not returning error in this case but behaviour is unpredictable. + +```c +uint8_t client(); +uint8_t slave(); // Deprecated +``` + +- Slave mode: Returns configured slave id. +- Master mode: Returns slave id for active request or 0 if no request in progress. + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/RTU/master/master.ino b/examples/RTU/master/master.ino new file mode 100644 index 0000000..d4d8c23 --- /dev/null +++ b/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/examples/RTU/masterSync/masterSync.ino b/examples/RTU/masterSync/masterSync.ino new file mode 100644 index 0000000..75a4614 --- /dev/null +++ b/examples/RTU/masterSync/masterSync.ino @@ -0,0 +1,47 @@ +/* + Modbus Library for Arduino Example - Modbus RTU Client + Read Holding Registers from Modbus RTU Server in blocking way + ESP8266 Example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#include +#include + +#define SLAVE_ID 1 +#define FIRST_REG 0 +#define REG_COUNT 2 + +SoftwareSerial S(D2, D1); +ModbusRTU mb; + +bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Callback to monitor errors + if (event != Modbus::EX_SUCCESS) { + Serial.print("Request result: 0x"); + Serial.print(event, HEX); + } + return true; +} + +void setup() { + Serial.begin(115200); + S.begin(9600, SWSERIAL_8N1); + mb.begin(&S); + mb.master(); +} + +void loop() { + uint16_t res[REG_COUNT]; + if (!mb.slave()) { // Check if no transaction in progress + mb.readHreg(SLAVE_ID, FIRST_REG, res, REG_COUNT, cb); // Send Read Hreg from Modbus Server + while(mb.slave()) { // Check if transaction is active + mb.task(); + delay(10); + } + Serial.println(res[0]); + Serial.println(res[1]); + } + delay(1000); +} \ No newline at end of file diff --git a/examples/RTU/slave/slave.ino b/examples/RTU/slave/slave.ino new file mode 100644 index 0000000..4a96808 --- /dev/null +++ b/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/arduinoIDE/ModbusIP_ESP8266/examples/TestAnalogInput/TestAnalogInput.ino b/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino similarity index 68% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestAnalogInput/TestAnalogInput.ino rename to examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino index 1a29954..0e39a5f 100644 --- a/arduinoIDE/ModbusIP_ESP8266/examples/TestAnalogInput/TestAnalogInput.ino +++ b/examples/TCP-ESP/IP-server-AnalogInput/IP-server-AnalogInput.ino @@ -1,15 +1,23 @@ /* - Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) + Modbus-Arduino Example - Test Analog Input (Modbus IP ESP8266) Read Analog sensor on Pin ADC (ADC input between 0 ... 1V) + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ -#include -#include +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif #include -//Modbus Registers Offsets (0-9999) +//Modbus Registers Offsets const int SENSOR_IREG = 100; //ModbusIP object @@ -20,19 +28,18 @@ long ts; void setup() { Serial.begin(115200); - //Config Modbus IP - mb.config("your_ssid", "your_password"); - + WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } - + Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); + mb.server(); //Start Modbus IP // Add SENSOR_IREG register - Use addIreg() for analog Inputs mb.addIreg(SENSOR_IREG); @@ -49,4 +56,5 @@ void loop() { //Setting raw value (0-1024) mb.Ireg(SENSOR_IREG, analogRead(A0)); } + delay(10); } diff --git a/arduinoIDE/ModbusIP_ESP8266/examples/TestLed/TestLed.ino b/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino similarity index 72% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestLed/TestLed.ino rename to examples/TCP-ESP/IP-server-Led/IP-server-Led.ino index c314041..5155711 100644 --- a/arduinoIDE/ModbusIP_ESP8266/examples/TestLed/TestLed.ino +++ b/examples/TCP-ESP/IP-server-Led/IP-server-Led.ino @@ -1,15 +1,23 @@ /* Modbus-Arduino Example - Test Led (Modbus IP ESP8266) Control a Led on GPIO0 pin using Write Single Coil Modbus Function + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ -#include -#include +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif #include -//Modbus Registers Offsets (0-9999) +//Modbus Registers Offsets const int LED_COIL = 100; //Used Pins const int ledPin = 0; //GPIO0 @@ -20,7 +28,7 @@ ModbusIP mb; void setup() { Serial.begin(115200); - mb.config("your_ssid", "your_password"); + WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -32,6 +40,8 @@ void setup() { Serial.println("IP address: "); Serial.println(WiFi.localIP()); + mb.server(); + pinMode(ledPin, OUTPUT); mb.addCoil(LED_COIL); } @@ -42,4 +52,5 @@ void loop() { //Attach ledPin to LED_COIL register digitalWrite(ledPin, mb.Coil(LED_COIL)); + delay(10); } \ No newline at end of file diff --git a/arduinoIDE/ModbusIP_ESP8266/examples/TestSwitchStatus/TestSwitchStatus.ino b/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino similarity index 61% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestSwitchStatus/TestSwitchStatus.ino rename to examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino index b156ece..6a7b339 100644 --- a/arduinoIDE/ModbusIP_ESP8266/examples/TestSwitchStatus/TestSwitchStatus.ino +++ b/examples/TCP-ESP/IP-server-SwitchStatus/IP-server-SwitchStatus.ino @@ -1,15 +1,23 @@ /* Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) Read Switch Status on pin GPIO0 + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ -#include -#include +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif #include -//Modbus Registers Offsets (0-9999) +//Modbus Registers Offsets const int SWITCH_ISTS = 100; //Used Pins const int switchPin = 0; //GPIO0 @@ -18,8 +26,15 @@ const int switchPin = 0; //GPIO0 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.config("your_ssid", "your_password"); + mb.server(); //Set ledPin mode pinMode(switchPin, INPUT); // Add SWITCH_ISTS register - Use addIsts() for digital inputs @@ -32,4 +47,5 @@ void loop() { //Attach switchPin to SWITCH_ISTS register mb.Ists(SWITCH_ISTS, digitalRead(switchPin)); + delay(10); } diff --git a/examples/TCP-ESP/README.md b/examples/TCP-ESP/README.md new file mode 100644 index 0000000..70ae29f --- /dev/null +++ b/examples/TCP-ESP/README.md @@ -0,0 +1,136 @@ +# ESP8266/ESP32 TCP Examples + +## [Basic client](client/client.ino) + +## [Client with blocking read operation](clientSync/clientSync.ino) + +## [Server](server/server.ino) + +### API + +```c +void client(); +``` + +Initialize internal structures to act as a Modbus client. + +```c +bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); +bool disconnect(IPAddress ip); +``` + +- `ip` IP address of the remote Modbus server +- `port` TCP port of remote Modbus server (standard value is 502) + +Note: Just one connection to the specific address is supported. That is the library is unable to simultaniousaly connect to Modbus servers that has same IP address but diffrent ports. + +```c +uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(const char* host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readCoil(String host, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); +``` + +- `ip` IP Address of Modbus server to get registers from +- `host` Hostname fo Modbus server to get registers from +- `offset` Address of first Modbus register to read/write +- `numregs` Count of registers to read/write +- `cb` Transaction callback function (see [Calback examples](../calback) for details). `NULL` if not used +- `unit` Modbus unit + +Sends corresponding Modbus read request to Modbus server at `ip`. Connection with server shoud be already established by connect(ip). +Returns transaction `id` or `0` on failure. Failure maens that client unable to send the request bacause of no connection to the Modbus server is established or other internal error. +Note: read/write functions just sending requests to remote Modbus server. The functions returns immediate after request sent and doesn't waiting for result. That is `value` contains no result data on the function exit. `value` will be filled as responce arrive and processed by .task() function. + +```c +bool isTransaction(uint16_t id); +``` + +- `id` Transaction id. + +Returns `true` if transaction with `id` is active. + +```c +bool isConnected(IPAddress ip); +``` + +- `ip` Remote Modbus server IP address + +Returns `true` is connection with Modbus server at `ip` is established. + +```c +void dropTransactions(); +``` + +Cancel all active transactions. Callback with result code `Modbus::EX_CANCEL` will be called for each transaction (if assigned). + +### Add local register +```c +bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); +bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1); +bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); +bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1); +``` + +- `offset` Address of the first register to add +- `value` Initial value to be assigned to register(s) +- `numregs` Count of registers to be created + +Adding new register(s) and assigning value(s). If [some] registers already exists value will be updated. +Returns `true` on success. `false` if operation is failed for some reason. + +### Write local reg + +```c +bool Hreg(uint16_t offset, uint16_t value); +bool Coil(uint16_t offset, bool value); +bool Ists(uint16_t offset, bool value); +bool Ireg(uint16_t offset, uint16_t value); +``` + +- `offset` Address of the register +- `value` Value to be assigned to register + +Returns `true` on success. `false` if register not previousely added or other error. + +### Read local reg + +```c +uint16_t Hreg(uint16_t offset); +bool Coil(uint16_t offset); +bool Ists(uint16_t offset); +uint16_t Ireg(uint16_t offset); +``` + +- `offset` Address of the register to read + + +Returns current value of the register. + +### Remove reg(s) + +```c +bool removeHreg(uint16_t offset, uint16_t numregs = 1); +bool removeCoil(uint16_t offset, uint16_t numregs = 1); +bool removeIsts(uint16_t offset, uint16_t numregs = 1); +bool removeIreg(uint16_t offset, uint16_t numregs = 1); +``` + +- `offset` Address of the first register to remove +- `numregs` Count of registers to be created + +Function trying to remove `numregs` registers starting from `offset`. If some of registers within the range are not exists removal continues execution. +Returns `true` if atleast one register in the range was removed. + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. \ No newline at end of file diff --git a/examples/TCP-ESP/client/client.ino b/examples/TCP-ESP/client/client.ino new file mode 100644 index 0000000..22d3de7 --- /dev/null +++ b/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/examples/TCP-ESP/clientPull/clientPull.ino b/examples/TCP-ESP/clientPull/clientPull.ino new file mode 100644 index 0000000..2098004 --- /dev/null +++ b/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/examples/TCP-ESP/clientSync/clientSync.ino b/examples/TCP-ESP/clientSync/clientSync.ino new file mode 100644 index 0000000..03c2213 --- /dev/null +++ b/examples/TCP-ESP/clientSync/clientSync.ino @@ -0,0 +1,55 @@ +/* + Modbus Library for Arduino Example - Modbus IP Client (ESP8266/ESP32) + Read Holding Register from Modbus Server in blocking way + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 +*/ + +#ifdef ESP8266 + #include +#else + #include +#else +#error "Unsupported platform" +#endif +#include + +const int REG = 528; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device + +ModbusIP mb; //ModbusTCP object + +void setup() { + Serial.begin(115200); + + WiFi.begin("SSID", "PASSWORD"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mb.client(); +} + +uint16_t res = 0; + +void loop() { + if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + uint16_t trans = mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Server + while(mb.isTransaction(trans)) { // Check if transaction is active + mb.task(); + delay(10); + } + Serial.println(res); // At this point res is filled with responce value + } else { + mb.connect(remote); // Try to connect if no connection + } + delay(100); +} \ No newline at end of file diff --git a/arduinoIDE/ModbusIP_ESP8266/examples/TestHoldingReg/TestHoldingReg.ino b/examples/TCP-ESP/server/server.ino similarity index 69% rename from arduinoIDE/ModbusIP_ESP8266/examples/TestHoldingReg/TestHoldingReg.ino rename to examples/TCP-ESP/server/server.ino index 982fadc..1cf0977 100644 --- a/arduinoIDE/ModbusIP_ESP8266/examples/TestHoldingReg/TestHoldingReg.ino +++ b/examples/TCP-ESP/server/server.ino @@ -2,15 +2,23 @@ Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) Configure Holding Register (offset 100) with initial value 0xABCD You can get or set this holding register + Original library Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino + + Current version + (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 */ -#include -#include +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif #include -// Modbus Registers Offsets (0-9999) +// Modbus Registers Offsets const int TEST_HREG = 100; @@ -20,7 +28,7 @@ ModbusIP mb; void setup() { Serial.begin(115200); - mb.config("your_ssid", "your_password"); + WiFi.begin("your_ssid", "your_password"); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -32,11 +40,12 @@ void setup() { 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/examples/TCP-Ethernet/README.md b/examples/TCP-Ethernet/README.md new file mode 100644 index 0000000..8857af7 --- /dev/null +++ b/examples/TCP-Ethernet/README.md @@ -0,0 +1,15 @@ +# W5x00 Example + +## Physical connection between ESP32 and W5500 + +* GPIO23 <--> MOSI +* GPIO19 <--> MISO +* GPIO18 <--> SCLK +* GPIO5 <--> SCS + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/TCP-Ethernet/client/client.ino b/examples/TCP-Ethernet/client/client.ino new file mode 100644 index 0000000..c477adc --- /dev/null +++ b/examples/TCP-Ethernet/client/client.ino @@ -0,0 +1,52 @@ +/* + ModbusTCP for W5x00 Ethernet library + Basic Client code example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include +#include // Ethernet library v2 is required +#include + +const uint16_t REG = 512; // Modbus Hreg Offset +IPAddress remote(192, 168, 30, 12); // Address of Modbus Slave device +const int32_t showDelay = 5000; // Show result every n'th mellisecond + +// Enter a MAC address and IP address for your controller below. +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE +}; +IPAddress ip(192, 168, 30, 178); // The IP address will be dependent on your local network: +ModbusEthernet mb; // Declare ModbusTCP instance + +void setup() { + Serial.begin(115200); // Open serial communications and wait for port to open + #if defined(AVR_LEONARDO) + while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only + #endif + Ethernet.init(5); // SS pin + Ethernet.begin(mac, ip); // start the Ethernet connection + delay(1000); // give the Ethernet shield a second to initialize + mb.client(); // Act as Modbus TCP client +} + +uint16_t res = 0; +uint32_t showLast = 0; + +void loop() { +if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established + mb.readHreg(remote, REG, &res); // Initiate Read Hreg from Modbus Slave + } else { + mb.connect(remote); // Try to connect if not connected + } + delay(100); // Pulling interval + mb.task(); // Common local Modbus task + if (millis() - showLast > showDelay) { // Display register value every 5 seconds (with default settings) + showLast = millis(); + Serial.println(res); + } +} diff --git a/examples/TCP-Ethernet/server/server.ino b/examples/TCP-Ethernet/server/server.ino new file mode 100644 index 0000000..769e7cd --- /dev/null +++ b/examples/TCP-Ethernet/server/server.ino @@ -0,0 +1,37 @@ +/* + ModbusTCP for W5x00 Ethernet library + Basic Server code example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include +#include // Ethernet library v2 is required +#include + +// Enter a MAC address and IP address for your controller below. +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED +}; +IPAddress ip(192, 168, 30, 177); // The IP address will be dependent on your local network: +ModbusEthernet mb; // Declare ModbusTCP instance + +void setup() { + Serial.begin(115200); // Open serial communications and wait for port to open + #if defined(AVR_LEONARDO) + while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only + #endif + Ethernet.init(5); // SS pin + Ethernet.begin(mac, ip); // start the Ethernet connection + delay(1000); // give the Ethernet shield a second to initialize + mb.server(); // Act as Modbus TCP server + mb.addReg(HREG(100)); // Add Holding register #100 +} + +void loop() { + mb.task(); // Server Modbus TCP queries + delay(50); +} \ No newline at end of file diff --git a/examples/TLS/README.md b/examples/TLS/README.md new file mode 100644 index 0000000..ede543f --- /dev/null +++ b/examples/TLS/README.md @@ -0,0 +1,47 @@ +# Modbus\TCP Security Example + +### *Target Platforms:* +- *ESP8266 (CLient/Server)* +- *ESP32 (Client only)* + +## [Sample certificates](certs) + +[cert.cmd](certs/cert.cmd) Script to recreate all the certificates in the catalog. Requires OpenSSL installed. + +[Good issue explanation to read](https://github.com/esp8266/Arduino/issues/6128) + +## [Client](client/client.ino) + +```c +bool connect(const char* host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr); +bool connectWithKnownKey(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* key = nullptr); +``` + +- `const char* host` Host name to connect to +- `uint16_t port` Host port +- `const char* client_cert` Client's certificate +- `const char* client_private_key` Client's private key +- `const char* ca_cert` Certificate of CA. Can be omitted (or set NULL) to escape certificate chain verifying. +- `IPAddress ip` Host IP address to connect to +- `const char* key` Server's public key + +All certificates must be in PEM format and can be stored in PROGMEM. + +## [Server](server/server.ino) + +```c +void server(uint16_t port, const char* server_cert = nullptr, const char* server_private_key = nullptr, const char* ca_cert = nullptr); +``` +- `uint16_t port` Port to bind to +- `const char* server_cert` Server certificate in PEM format. +- `const char* server_private_key` Server private key in PEM format. +- `const char* ca_cert` Certificate of CA. + +All certificates must be in PEM format and can be stored in PROGMEM. + +# Modbus Library for Arduino +### ModbusRTU, ModbusTCP and ModbusTCP Security + +(c)2020 [Alexander Emelianov](mailto:a.m.emelianov@gmail.com) + +The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. diff --git a/examples/TLS/certs/ca.conf b/examples/TLS/certs/ca.conf new file mode 100644 index 0000000..c48c6ab --- /dev/null +++ b/examples/TLS/certs/ca.conf @@ -0,0 +1,11 @@ +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_dn +x509_extensions = v3_req + +[ req_dn ] +CN = root_ca + +[v3_req] +basicConstraints=CA:TRUE diff --git a/examples/TLS/certs/ca_cer.pem b/examples/TLS/certs/ca_cer.pem new file mode 100644 index 0000000..14a4793 --- /dev/null +++ b/examples/TLS/certs/ca_cer.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwjCCAaqgAwIBAgIUTz9NFtf8JkdIkrDroXVB/ANtqlYwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHcm9vdF9jYTAeFw0yMDA4MjcxMDI4MzFaFw0zMTExMTQx +MDI4MzFaMBIxEDAOBgNVBAMMB3Jvb3RfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCpYdVI2PjW+Pdw5bMqxFz0s3jgYgTHyt51NJGlImgJpmjmj16T +rwcqAe70BtsSjOQeWRoF/rk46ZO/ntDbVkP8ZA40Vf8F8Yft64f1OOBf93rTR0sH +oUk+HmE3Iu+bWYSewNMw/LJyF2r95V2xNeX50Y+BhQskBoWYR7C671ifFlsQHI+a +/BpALEi7qt6kGenlhrmRAjweNxVNILHTPH7Fr/TYXWfAb69TzXWTUFy0bdwZfPIP +b2HXyGINGiD6EtZDkybPk17zZgJKMdxpEG5XA/O+daVh3Prlar+amqb30zntOVga +AcyREcmzYFFBWQmuKNw9mz9x09GWLWjBaYP9AgMBAAGjEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBAIFtNowXu8wfahpKF5MoNKrqA9AG/aGzlmbD +pBKr5Cvvo7NdC6oZMdXlS0VkRmAyw9mEJgwspUvqsLx0/lgN+sE381MUovWL9BIs +o4IOax5473q6ZwV87jwpsrlNHBiolw+WCDKVYuDktaThCaxqxmPKCPMbgYPdqWB0 +l1gYDJJ+MwNH/CRsynpM8Hppf88BwwbM6JYegg5/DLxRl5z3HjCAVD8vBoqkWLRD +b9tIER4WDJhZG4tzgMW+lbMJyDoQA1cw4BGag4Ir1er32+w1519UR/VK0ltk9BK9 +yHObfUNN6saco1/f4OM4tzaQOKa+6U1iXVBTBjE2IHPchGqctBk= +-----END CERTIFICATE----- diff --git a/examples/TLS/certs/ca_cer.srl b/examples/TLS/certs/ca_cer.srl new file mode 100644 index 0000000..bc42790 --- /dev/null +++ b/examples/TLS/certs/ca_cer.srl @@ -0,0 +1 @@ +4221D52EC27B0A950D9F41EFC7D20A43100E437D diff --git a/examples/TLS/certs/ca_key.pem b/examples/TLS/certs/ca_key.pem new file mode 100644 index 0000000..d9ad72b --- /dev/null +++ b/examples/TLS/certs/ca_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqWHVSNj41vj3cOWzKsRc9LN44GIEx8redTSRpSJoCaZo5o9e +k68HKgHu9AbbEozkHlkaBf65OOmTv57Q21ZD/GQONFX/BfGH7euH9TjgX/d600dL +B6FJPh5hNyLvm1mEnsDTMPyychdq/eVdsTXl+dGPgYULJAaFmEewuu9YnxZbEByP +mvwaQCxIu6repBnp5Ya5kQI8HjcVTSCx0zx+xa/02F1nwG+vU811k1BctG3cGXzy +D29h18hiDRog+hLWQ5Mmz5Ne82YCSjHcaRBuVwPzvnWlYdz65Wq/mpqm99M57TlY +GgHMkRHJs2BRQVkJrijcPZs/cdPRli1owWmD/QIDAQABAoIBAFn2/argm2LK/9o2 +FrC7dUf/X0+GoFVh+kA0eLtGCA5AFe2H7srwJxT3y+xPC+LRdIRt/PV8MvL4lSIs +/2/QZPHUTvsbRgXpILKM7DyiRgKS1ukLL93Qm69jwWzgoHVZ2afccQ/O2BTjPU+3 +mMj8ALdsyBUaDi3HTQPx5/uSDvcHsoIneIrecX0/I5Yi5+BoVQIkwOkZZsFtHvg6 +44Hf4sqhbB0f7PSSEFjdWceANjMoMZ/upBa6KgvYgYc2gBaU+aCsBC2g2zUY9waG +pbVGMl61gSaD4IqcVCYSFWZkzpeIw2YHwmyFO1H5PCRzgVRYaE72alHmtDrP2cx+ +Ftc+naECgYEA0zQo8VVDWEvcUTIb5igIOGjTJZMBCpR2Y0M7Kh/qx9XfBguEKGiS +KBYWDolweCeZ0tdFW8GTb3RwLLcLBnl/sge7ouvKUlk2iFNbAFipjwYUkAtcfX9a +sPwR+JXbZF2LhBMZsw7dSwhWWjStGaAiXEFFeWqTdV/vwwVdtaiPrgkCgYEAzU7d +VirIVsm7ps5L71zkZtf211o4LRysQS64oh6JUQPzcvC85yU3HtS2VNSCz2GiA1R/ +jqzGAL5q6k0UPBT4xzAwDmBJvtRjFr38FCCcl3Txeqjy7zkG/H0UpmQNMA1jjf/9 +iBRrR1vwjj79xyyl4PojpZmi6GVN2IIWrpf9o1UCgYBEbhf96XRCfYHKxQOJFNtk ++4G+IN0rgmLBUp0uztyRFtiF6uFM/mSsnEtVNm68X4hVae5NBnEwoXde5Yeq917K +XfsLlH4fJEyo6ukHObLmZj/vU98JwmOuCF4CPvuwjyaPCmk/PMeycecYnwyeyuWX +IobSChfw5b6XX3u3SgATkQKBgQCXq35J7LspmkhdlyNzxh0ZeMvrFcRQV1FNqhVN +9t8ckZ2kuQHkhJKu3ReBnaixSYAlk6PUJAD2hbV4N88N/7Q1enzV8f4o0sANCfcS +a3EjVooaQnuNjISDvGen8FvptspoGcgTYnpKMjqI6zIRlQNKK6Bv8wrtQgF7Q8c7 +3h7LLQKBgHQjFqVmM0a3WhgXGTWIhDALWXDgLcsVTQwGnUCafLgdP8wrjF1+tZf1 +UIw04p35FE2xlpMVkYRItYLIuQ4S8Q323rk2yIpTYYZApKYjT5BuNFFUinE5QsqV +iXeCigYuKwOJ6Gi8c+lgFkxZnA3+rZJg9vdzp/yz4Xgy7gJ6QVBc +-----END RSA PRIVATE KEY----- diff --git a/examples/TLS/certs/cert.cmd b/examples/TLS/certs/cert.cmd new file mode 100644 index 0000000..65e3567 --- /dev/null +++ b/examples/TLS/certs/cert.cmd @@ -0,0 +1,17 @@ +set OPATH=C:\Program Files\OpenSSL-Win64\bin + +rem CA +"%OPATH%\openssl" genrsa -out ca_key.pem 2048 +"%OPATH%\openssl" req -x509 -new -nodes -key ca_key.pem -days 4096 -config ca.conf -out ca_cer.pem + +rem SERVER +"%OPATH%\openssl" genrsa -out server_key.pem 2048 +"%OPATH%\openssl" req -out server_req.csr -key server_key.pem -new -config server.conf +"%OPATH%\openssl" x509 -req -in server_req.csr -out server_cer.pem -sha256 -CAcreateserial -days 4000 -CA ca_cer.pem -CAkey ca_key.pem +"%OPATH%\openssl" rsa -in server_key.pem -pubout -out server_pubkey.pem + +rem CLIENT +"%OPATH%\openssl" genrsa -out client1_key.pem 2048 +"%OPATH%\openssl" req -out client1_req.csr -key client1_key.pem -new -config client.conf +"%OPATH%\openssl" x509 -req -in client1_req.csr -out client1_cer.pem -sha256 -CAcreateserial -days 4000 -CA ca_cer.pem -CAkey ca_key.pem + diff --git a/examples/TLS/certs/client.cmd b/examples/TLS/certs/client.cmd new file mode 100644 index 0000000..719b5f4 --- /dev/null +++ b/examples/TLS/certs/client.cmd @@ -0,0 +1 @@ +openssl s_client -showcerts -connect 192.168.30.123:802 -cert client1_cer.pem -key client1_key.pem -verifyCAfile ca_cer.pem \ No newline at end of file diff --git a/examples/TLS/certs/client.conf b/examples/TLS/certs/client.conf new file mode 100644 index 0000000..522e43c --- /dev/null +++ b/examples/TLS/certs/client.conf @@ -0,0 +1,7 @@ +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_dn + +[ req_dn ] +CN = client diff --git a/examples/TLS/certs/client1_cer.pem b/examples/TLS/certs/client1_cer.pem new file mode 100644 index 0000000..39a0460 --- /dev/null +++ b/examples/TLS/certs/client1_cer.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqjCCAZICFEIh1S7CewqVDZ9B78fSCkMQDkN9MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjARMQ8wDQYDVQQDDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDIU4Czr45Jgj7TiUbF0MSs4ydC41R/rB2dSlwcbrmjmZHlJn2fifcVCBuQ +H4a/SCbUNDOXz23p2NZHeLpcHV8TeuvDocmplJIuKNoN8BPbeZ+IS5yrvHBzC0S2 +bXs1gOkcWwmdD87NqQD8v7m+hZjBIBDvPAHPBXsEzkNNlqnye2mRYI8G0sGTqMWV +zCt+m2mwJLAIwVCWYLVEn3sY/ksU6SrwyNKfnoCCw+0hfaMVdUq2u9wpDD2i9fKT +yehSW727r5dOtGUbp3hoxiFWzAlaodvIk0eZ/12EMboc3y4WLax7W2vefNc9sKeM +6jgYRoqz4YRgJLbZzk1tJk292521AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAeY +SL7wIYQONK2uqhqb9MmbfOZznlaGz6kybB0GtVmZpvBaqZtCmTSOSbs/0YVF3OSv ++L9+kWTGsaWx/6t1fdiDG8DlZCqF3dwbmd0YmV2GYbpRF53rYSUETSsdO2g1Fs0a +lvSVrQvhUj/cXvlTqtvjSVBELwFmlu0qhUHqN8Ap3dgy1YUZvRQcJS1GZ46iZLae +SQYAANvfYXC4gBy1vfgKeDkZD4Qs+NnV6J+aFpXTYsmMMOS/lfLTpWP2tEfuaexW +dGPlQ5dw7JZHcPrD9EdVIvDozACS0Y8B7oP4xvKFJnsqE7RmOsnukO0D7CQkxkBy +hJmblVkcv6VRNS9JHDQ= +-----END CERTIFICATE----- diff --git a/examples/TLS/certs/client1_key.pem b/examples/TLS/certs/client1_key.pem new file mode 100644 index 0000000..2427ca8 --- /dev/null +++ b/examples/TLS/certs/client1_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAyFOAs6+OSYI+04lGxdDErOMnQuNUf6wdnUpcHG65o5mR5SZ9 +n4n3FQgbkB+Gv0gm1DQzl89t6djWR3i6XB1fE3rrw6HJqZSSLijaDfAT23mfiEuc +q7xwcwtEtm17NYDpHFsJnQ/OzakA/L+5voWYwSAQ7zwBzwV7BM5DTZap8ntpkWCP +BtLBk6jFlcwrfptpsCSwCMFQlmC1RJ97GP5LFOkq8MjSn56AgsPtIX2jFXVKtrvc +KQw9ovXyk8noUlu9u6+XTrRlG6d4aMYhVswJWqHbyJNHmf9dhDG6HN8uFi2se1tr +3nzXPbCnjOo4GEaKs+GEYCS22c5NbSZNvdudtQIDAQABAoIBAQCQQeGag795G/vW +JTL73JzkyydIuZ/t2KnyzMuMBghU0ZAIbjFko9t0H8SJgspsEK81fOnyVoOWNHoK +Odwp3VTMGGaTGHy6S60A5JYyF0KVd/30Dk8iNK7dia3PmQNywgQcUUqY+fs4io2V +dRNzKY2Y9Vh8jr/WruGp0kcRJn/3hrR7S10UyWDbQYE7R3Hir7V0YMFWEbzgwhRE +6MO6H5obFdZFxy7V+RJLeeq+dKHrvOmtd6F6hWSQUVX9YOVjh820IhhhC3F20EQw +FTiVO9UfpmOzhtBp0vOBCWIHa5Yu+AXufrytfT//DClyiex+kfXrmS+OhZS/zqPf +YjadqQF5AoGBAPXWUUD/jzkE82TPpwtRuIhZtF0kLedpBkzSg0e+Fgbdw4osRbMs +13cXMucWW9wK0TikHeoCcq1N2xDRWGreNqolbj9KEqWG0D2LcTBm0pKXuhAT+bWQ +hJmsiNEQYsM9hJLByLWNp3mwgzDLVjXDAxJgirP1L6Qw65SbQoYMt06bAoGBANCb +i3T0A/YP6ounu2iGiEqrJTU/11zh+ykVSvHd4MpV+szex7pBRlXpkFE2iqElAoja +xVrGsQCTebtJIz58Fy7tJQlTRqilRHCTRR60x+0ab7768OHZNKcSRXFDLVTdEyzv +dKTIZh0IJfbz/DpwyNqTM0GYLhDXJfyJxu7YmeHvAoGAC8N1n+aas9/Ixcop9CC0 +89FXEB3rFGeyJXrtTUGLTEjQUoxLyYcbyFcT2Hr5ak4aNNulks0LL7/J+8QItxRr +CTlBTUX+Hm2VCVziza4d5WXdQWezSzzfG3tmEJr4Ht+SuHMNZ6KfoPMRVARm26u5 +OefkuzfAT9sHatUDGecB3oECgYEAgWaLOlAHiQJUfq7cTLlvH8pMOVzRrfcsAk8H +/0KgJ0LwYVcsU7gb9jz83bPUiKNZkCUM2QN5Vp8kmu2CZEc7ZkuKdt9mbESgUKi5 +7pM7lTOZ78Df3WkMBTsLQnfmTccZFv2uwGzjEs00J50vb9z4asV2vRC2OpILKT0Z +3p0Tz5cCgYEAnFJ5HAVKlzi4k8l7v1SuDb9vCLIG+XQYBNrhW/3BLQftOyEKgK5O +6fkNQT4u7Bgmzu3kpljq7jKBlPdW+l009L0gEEO5QBQLy64IquyydB1kiBIY5jGZ +x7M6SvzBWzC7gzH/P94LxtqM22zzU0LszocV2j1UkxqBVXv0EYVjPB0= +-----END RSA PRIVATE KEY----- diff --git a/examples/TLS/certs/client1_req.csr b/examples/TLS/certs/client1_req.csr new file mode 100644 index 0000000..cc79f2d --- /dev/null +++ b/examples/TLS/certs/client1_req.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICVjCCAT4CAQAwETEPMA0GA1UEAwwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAyFOAs6+OSYI+04lGxdDErOMnQuNUf6wdnUpcHG65o5mR +5SZ9n4n3FQgbkB+Gv0gm1DQzl89t6djWR3i6XB1fE3rrw6HJqZSSLijaDfAT23mf +iEucq7xwcwtEtm17NYDpHFsJnQ/OzakA/L+5voWYwSAQ7zwBzwV7BM5DTZap8ntp +kWCPBtLBk6jFlcwrfptpsCSwCMFQlmC1RJ97GP5LFOkq8MjSn56AgsPtIX2jFXVK +trvcKQw9ovXyk8noUlu9u6+XTrRlG6d4aMYhVswJWqHbyJNHmf9dhDG6HN8uFi2s +e1tr3nzXPbCnjOo4GEaKs+GEYCS22c5NbSZNvdudtQIDAQABoAAwDQYJKoZIhvcN +AQELBQADggEBAIvHh0usZM0QBJvUO1e/LRFrKrS/6dNQ7lkbqEIQxl9NRddak5ad +QYy4aBSA1yR/T2TSqK8Tq3W1eRdvH62KZn0VqumzgtGfRoI6Xtp5pUrCWw1Bv3eX +L1lGicBQezVmXn1vUyNn/+U8FUbPoSWIqPAjetoBEnxPDLEy72OYs5dnC5AC7bXN +jT+pdYqQncy+ghtPgpc1WrHWVIcamhhyUuUcy69sa2eigBwfxh8lfOknUjUE69mI +BafDkwu4DzpayDe/C/TatTan30jur2Dpr+fKiEtfag+15E+BXphdAOodhWLMzJKz +xEvxo4UH/Uo/5dEmwSe7wdYZYIa5OFvTLhY= +-----END CERTIFICATE REQUEST----- diff --git a/examples/TLS/certs/server.cmd b/examples/TLS/certs/server.cmd new file mode 100644 index 0000000..fe80478 --- /dev/null +++ b/examples/TLS/certs/server.cmd @@ -0,0 +1 @@ +openssl s_server -showcerts -accept 802 -cert server_cer.pem -key server_key.pem -verifyCAfile ca_cer.pem \ No newline at end of file diff --git a/examples/TLS/certs/server.conf b/examples/TLS/certs/server.conf new file mode 100644 index 0000000..d8a56b8 --- /dev/null +++ b/examples/TLS/certs/server.conf @@ -0,0 +1,16 @@ +[ req ] +prompt = no +default_bits = 2048 +distinguished_name = req_dn +req_extensions = v3_req +x509_extensions = v3_req + +[ req_dn ] +CN = modbustls + +[v3_req] +# The extentions to add to a self-signed cert +subjectKeyIdentifier = hash +basicConstraints = critical,CA:false +subjectAltName = IP:192.168.30.127 +keyUsage = critical,digitalSignature,keyEncipherment \ No newline at end of file diff --git a/examples/TLS/certs/server_cer.pem b/examples/TLS/certs/server_cer.pem new file mode 100644 index 0000000..c6efd5e --- /dev/null +++ b/examples/TLS/certs/server_cer.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICrTCCAZUCFEIh1S7CewqVDZ9B78fSCkMQDkN8MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjAUMRIwEAYDVQQDDAltb2RidXN0bHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDdKlwKryxSEBsHO1z5fr0f6aJX8PHD1Iftfka3PT285wRoNCcPa5eT +o5dBSyJM9JgKpqGsdm2M7UBAJZAFBSgQi++pRuNsssza1uUre28T3PHV463Oma57 +mFfpIlKGfL/rVuUlqu4igNIgQT/wQJmxJO8tDrWaTjMz4VgCNkG4y1veeIpz7/Cy +9S5CxEKBbibQncpUXyV4tTT9O37qze0Gr+d7frnyyTOtr80AwMMg1Pn61hZbku9E +L/VE13oWVBBSXz0exHVG8X9Ne4uyuyG3HAWheglhQ7m2RkBxTkDVolSf6ec1Xgn3 +15BMCG1eBTAZKNdRRSr4+x60p47ReaWJAgMBAAEwDQYJKoZIhvcNAQELBQADggEB +AGqz1benN8ygveD3F/XxCMgEPfI8WhYS3PQ6sPBE850TuQ+9OrHvue8q87/RfJBW +Yllkyi2JHGuY2muMBJWGWTDHK72JwI69hIIwE9bGrvFUwAZjCbK9+gF6UIUDznNN +bSHHSWTfkCMLFz0Q6XbhJvF2AX5dtmYL+AWqD8+G5ZIrbHgd/o4neA21DTYRfUt2 +0UQ0RAxKxf/BenJWr8IzvOxo0MHxi7JivgreCfxna1REZYxIwVFlh7O9KhB1QQBJ +oezb4CS0Um9sQowFTZ3AtRxpv1u60ZjTeJwSIL00YEyZ0sEO3K4h3PTbh2VxSoQD +D2fcSb1t+gWsvLVZCmNMbVo= +-----END CERTIFICATE----- diff --git a/examples/TLS/certs/server_key.pem b/examples/TLS/certs/server_key.pem new file mode 100644 index 0000000..73eccb2 --- /dev/null +++ b/examples/TLS/certs/server_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3SpcCq8sUhAbBztc+X69H+miV/Dxw9SH7X5Gtz09vOcEaDQn +D2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUoEIvvqUbjbLLM2tblK3tvE9zx1eOt +zpmue5hX6SJShny/61blJaruIoDSIEE/8ECZsSTvLQ61mk4zM+FYAjZBuMtb3niK +c+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3tBq/ne3658skzra/NAMDDINT5+tYW +W5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrshtxwFoXoJYUO5tkZAcU5A1aJUn+nn +NV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0XmliQIDAQABAoIBAAEFxB0siCjs+CMF +bD2fD2LJYr3DWGrOXb6EWfFY8CMickvFCfUxSyccl4NuxH7Ulqtd79trRMBlDGn/ +gnXzeybwbrA6qqyC+x175t1XmcDewaN6hQAyh7L8llN2nCkRBJYi9bZB3w37yHzr +sE79DXjbMdvkeIR5HhV8UjrYY19mVxbJsTokbrDXJEuDvR1kIkYM10UnEqMdDRiV +pSVFS3XkwNfCZHn/f2slcv1Piah8qfTseb5QPNouCZBW+PU5E1BuoFTH2ZtkzlZk +L6lcg42Ameyn5G9w8Walz89zwUIe3sycdJJ8+tRCWA2a2Aj8MeFiLfLxXnTjPNLU +nsVkuH0CgYEA/vbYyef3nzlmV4H+Rk9Rn3nGOeSTQj00avCUQS298bztEvFwcibM +84GOg0j68n/IW6gd4qBJyGpjBYl9ggcSTYBXKEK5FgtO6fM+Rnvnds3/WO7cMz3z +SzGIYranzcpaphj8H2xihW1OLsOUlb0+M71+6Y8P8Yui/mQIRRxHzMsCgYEA3hBc +/lzxs0A/TiwLlCpMIBuadkAxc4zgFY1LYHMveCwCa1cp9lujL5NRVOd8VhmYfpDg +MWAN3q7N/3dOu+hdMDoArXCXS5g5My3c6Ki87aSL1rmHyt9nYIBWZzjnwAii/zqs +kcKaFoEYv1vCuIMDFg+S1f+dpwuYlNn2pcnmwHsCgYEAlm/1+CQbsmI+5ZE5BClX +At7qPEyHKwVMAXFUOKURtyn/RDcbXu9P7Lnb6dDM6PrGsHYgtBBZmJxVMvYuDOO5 +Q+tfAc1kwgIIHPg+HX6MU0g2yzWczctW214tl/koR7+G/wws7ymXdBzLjcIu0K9p +nUPJN2wHP0Fh+fHyAz0tjEMCgYB4R9C3Dkz01LX1d7IF3StCsPDnYDno5sNxqQjN +A1cQ9nWRArN994DagicpoAEe+do5o+trkyWwGmsGFu+UpHXla2V2jGfG0HsbF5py +gwNijSAZfIDrCDsMcDdczdvpjkQLjxJuGUQxMFfhPqioHH6Ncn4MX9pa4tMQvUb1 +4fiVBQKBgAOtTdRqX66vvcOsjJmr2f+fw5SRXc09hxLaCDNjC6jVbCUw0aj6WKlx +sBfP8fvlHqJ6wA9/W2l+YiIf3G2jY2Z8OlOINs3hdXpH0JBzoeEiFwfcfZPAMFb1 +M4JURmEGAriH2lw/5iMQ/YqB9+NoE8t8lBLrhjwXWxN3qxoSruwe +-----END RSA PRIVATE KEY----- diff --git a/examples/TLS/certs/server_pubkey.pem b/examples/TLS/certs/server_pubkey.pem new file mode 100644 index 0000000..06548c2 --- /dev/null +++ b/examples/TLS/certs/server_pubkey.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3SpcCq8sUhAbBztc+X69 +H+miV/Dxw9SH7X5Gtz09vOcEaDQnD2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUo +EIvvqUbjbLLM2tblK3tvE9zx1eOtzpmue5hX6SJShny/61blJaruIoDSIEE/8ECZ +sSTvLQ61mk4zM+FYAjZBuMtb3niKc+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3t +Bq/ne3658skzra/NAMDDINT5+tYWW5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrsh +txwFoXoJYUO5tkZAcU5A1aJUn+nnNV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0Xml +iQIDAQAB +-----END PUBLIC KEY----- diff --git a/examples/TLS/certs/server_req.csr b/examples/TLS/certs/server_req.csr new file mode 100644 index 0000000..42b13d8 --- /dev/null +++ b/examples/TLS/certs/server_req.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbW9kYnVzdGxzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA3SpcCq8sUhAbBztc+X69H+miV/Dxw9SH7X5Gtz09 +vOcEaDQnD2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUoEIvvqUbjbLLM2tblK3tv +E9zx1eOtzpmue5hX6SJShny/61blJaruIoDSIEE/8ECZsSTvLQ61mk4zM+FYAjZB +uMtb3niKc+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3tBq/ne3658skzra/NAMDD +INT5+tYWW5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrshtxwFoXoJYUO5tkZAcU5A +1aJUn+nnNV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0XmliQIDAQABoAAwDQYJKoZI +hvcNAQELBQADggEBAHNXxhHJZt64+Ot1ekZ/VaQcitt/MwOW3kpN+yPIN6iTFSb2 +fXZkrlRG+TIU4hTnJ85HgsoK1hB/9GqEvJ2zerxeMeXH3QFm9jy+bzVJ6vR/hWPH +e4UI2u78w1kY1Z51xNBhIQQ4FJKb+iV1IsijE2sp+mpGbSQKQihG/FBOrxKUAV1q +GomcPTE40XXm6O9TsnnK9AnQCb3AsiZPC/Dm4+xoLwebf92wUPAzPbP74e/AL2ti +PDt3NTb6JCNMoXlhRnroGzOtigvRHF54GAEyLdwpSi1gLfQV1z6uBlU6vZIDOJFm +l0LJMHWDCxewfU+IjDYa77W+/8ApwGcEOF01e3g= +-----END CERTIFICATE REQUEST----- diff --git a/examples/TLS/client/client.ino b/examples/TLS/client/client.ino new file mode 100644 index 0000000..1186a66 --- /dev/null +++ b/examples/TLS/client/client.ino @@ -0,0 +1,161 @@ +/* + Modbus-Uni - Most complete Modbus Library for Arduino + + Modbus/TCP Security Client for ESP8266 Example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#else +#error Platform is not supported +#endif +#include +#include + +// The hardcoded certificate authority for this example. +// Don't use it on your own apps!!!!! +const char ca_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICwjCCAaqgAwIBAgIUTz9NFtf8JkdIkrDroXVB/ANtqlYwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHcm9vdF9jYTAeFw0yMDA4MjcxMDI4MzFaFw0zMTExMTQx +MDI4MzFaMBIxEDAOBgNVBAMMB3Jvb3RfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCpYdVI2PjW+Pdw5bMqxFz0s3jgYgTHyt51NJGlImgJpmjmj16T +rwcqAe70BtsSjOQeWRoF/rk46ZO/ntDbVkP8ZA40Vf8F8Yft64f1OOBf93rTR0sH +oUk+HmE3Iu+bWYSewNMw/LJyF2r95V2xNeX50Y+BhQskBoWYR7C671ifFlsQHI+a +/BpALEi7qt6kGenlhrmRAjweNxVNILHTPH7Fr/TYXWfAb69TzXWTUFy0bdwZfPIP +b2HXyGINGiD6EtZDkybPk17zZgJKMdxpEG5XA/O+daVh3Prlar+amqb30zntOVga +AcyREcmzYFFBWQmuKNw9mz9x09GWLWjBaYP9AgMBAAGjEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBAIFtNowXu8wfahpKF5MoNKrqA9AG/aGzlmbD +pBKr5Cvvo7NdC6oZMdXlS0VkRmAyw9mEJgwspUvqsLx0/lgN+sE381MUovWL9BIs +o4IOax5473q6ZwV87jwpsrlNHBiolw+WCDKVYuDktaThCaxqxmPKCPMbgYPdqWB0 +l1gYDJJ+MwNH/CRsynpM8Hppf88BwwbM6JYegg5/DLxRl5z3HjCAVD8vBoqkWLRD +b9tIER4WDJhZG4tzgMW+lbMJyDoQA1cw4BGag4Ir1er32+w1519UR/VK0ltk9BK9 +yHObfUNN6saco1/f4OM4tzaQOKa+6U1iXVBTBjE2IHPchGqctBk= +-----END CERTIFICATE----- +)EOF"; + +// The client's private key which must be kept secret +const char client_private_key[] PROGMEM = R"EOF( +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAyFOAs6+OSYI+04lGxdDErOMnQuNUf6wdnUpcHG65o5mR5SZ9 +n4n3FQgbkB+Gv0gm1DQzl89t6djWR3i6XB1fE3rrw6HJqZSSLijaDfAT23mfiEuc +q7xwcwtEtm17NYDpHFsJnQ/OzakA/L+5voWYwSAQ7zwBzwV7BM5DTZap8ntpkWCP +BtLBk6jFlcwrfptpsCSwCMFQlmC1RJ97GP5LFOkq8MjSn56AgsPtIX2jFXVKtrvc +KQw9ovXyk8noUlu9u6+XTrRlG6d4aMYhVswJWqHbyJNHmf9dhDG6HN8uFi2se1tr +3nzXPbCnjOo4GEaKs+GEYCS22c5NbSZNvdudtQIDAQABAoIBAQCQQeGag795G/vW +JTL73JzkyydIuZ/t2KnyzMuMBghU0ZAIbjFko9t0H8SJgspsEK81fOnyVoOWNHoK +Odwp3VTMGGaTGHy6S60A5JYyF0KVd/30Dk8iNK7dia3PmQNywgQcUUqY+fs4io2V +dRNzKY2Y9Vh8jr/WruGp0kcRJn/3hrR7S10UyWDbQYE7R3Hir7V0YMFWEbzgwhRE +6MO6H5obFdZFxy7V+RJLeeq+dKHrvOmtd6F6hWSQUVX9YOVjh820IhhhC3F20EQw +FTiVO9UfpmOzhtBp0vOBCWIHa5Yu+AXufrytfT//DClyiex+kfXrmS+OhZS/zqPf +YjadqQF5AoGBAPXWUUD/jzkE82TPpwtRuIhZtF0kLedpBkzSg0e+Fgbdw4osRbMs +13cXMucWW9wK0TikHeoCcq1N2xDRWGreNqolbj9KEqWG0D2LcTBm0pKXuhAT+bWQ +hJmsiNEQYsM9hJLByLWNp3mwgzDLVjXDAxJgirP1L6Qw65SbQoYMt06bAoGBANCb +i3T0A/YP6ounu2iGiEqrJTU/11zh+ykVSvHd4MpV+szex7pBRlXpkFE2iqElAoja +xVrGsQCTebtJIz58Fy7tJQlTRqilRHCTRR60x+0ab7768OHZNKcSRXFDLVTdEyzv +dKTIZh0IJfbz/DpwyNqTM0GYLhDXJfyJxu7YmeHvAoGAC8N1n+aas9/Ixcop9CC0 +89FXEB3rFGeyJXrtTUGLTEjQUoxLyYcbyFcT2Hr5ak4aNNulks0LL7/J+8QItxRr +CTlBTUX+Hm2VCVziza4d5WXdQWezSzzfG3tmEJr4Ht+SuHMNZ6KfoPMRVARm26u5 +OefkuzfAT9sHatUDGecB3oECgYEAgWaLOlAHiQJUfq7cTLlvH8pMOVzRrfcsAk8H +/0KgJ0LwYVcsU7gb9jz83bPUiKNZkCUM2QN5Vp8kmu2CZEc7ZkuKdt9mbESgUKi5 +7pM7lTOZ78Df3WkMBTsLQnfmTccZFv2uwGzjEs00J50vb9z4asV2vRC2OpILKT0Z +3p0Tz5cCgYEAnFJ5HAVKlzi4k8l7v1SuDb9vCLIG+XQYBNrhW/3BLQftOyEKgK5O +6fkNQT4u7Bgmzu3kpljq7jKBlPdW+l009L0gEEO5QBQLy64IquyydB1kiBIY5jGZ +x7M6SvzBWzC7gzH/P94LxtqM22zzU0LszocV2j1UkxqBVXv0EYVjPB0= +-----END RSA PRIVATE KEY----- +)EOF"; + +// The server's public certificate which must be shared +const char client_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICqjCCAZICFEIh1S7CewqVDZ9B78fSCkMQDkN9MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjARMQ8wDQYDVQQDDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDIU4Czr45Jgj7TiUbF0MSs4ydC41R/rB2dSlwcbrmjmZHlJn2fifcVCBuQ +H4a/SCbUNDOXz23p2NZHeLpcHV8TeuvDocmplJIuKNoN8BPbeZ+IS5yrvHBzC0S2 +bXs1gOkcWwmdD87NqQD8v7m+hZjBIBDvPAHPBXsEzkNNlqnye2mRYI8G0sGTqMWV +zCt+m2mwJLAIwVCWYLVEn3sY/ksU6SrwyNKfnoCCw+0hfaMVdUq2u9wpDD2i9fKT +yehSW727r5dOtGUbp3hoxiFWzAlaodvIk0eZ/12EMboc3y4WLax7W2vefNc9sKeM +6jgYRoqz4YRgJLbZzk1tJk292521AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAeY +SL7wIYQONK2uqhqb9MmbfOZznlaGz6kybB0GtVmZpvBaqZtCmTSOSbs/0YVF3OSv ++L9+kWTGsaWx/6t1fdiDG8DlZCqF3dwbmd0YmV2GYbpRF53rYSUETSsdO2g1Fs0a +lvSVrQvhUj/cXvlTqtvjSVBELwFmlu0qhUHqN8Ap3dgy1YUZvRQcJS1GZ46iZLae +SQYAANvfYXC4gBy1vfgKeDkZD4Qs+NnV6J+aFpXTYsmMMOS/lfLTpWP2tEfuaexW +dGPlQ5dw7JZHcPrD9EdVIvDozACS0Y8B7oP4xvKFJnsqE7RmOsnukO0D7CQkxkBy +hJmblVkcv6VRNS9JHDQ= +-----END CERTIFICATE----- +)EOF"; + +// The server's public certificate which must be shared +const char server_pk[] PROGMEM = R"EOF( +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3SpcCq8sUhAbBztc+X69 +H+miV/Dxw9SH7X5Gtz09vOcEaDQnD2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUo +EIvvqUbjbLLM2tblK3tvE9zx1eOtzpmue5hX6SJShny/61blJaruIoDSIEE/8ECZ +sSTvLQ61mk4zM+FYAjZBuMtb3niKc+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3t +Bq/ne3658skzra/NAMDDINT5+tYWW5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrsh +txwFoXoJYUO5tkZAcU5A1aJUn+nnNV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0Xml +iQIDAQAB +-----END PUBLIC KEY----- +)EOF"; + +//Modbus Registers Offsets +const int LED_COIL = 100; + +//ModbusIP object +ModbusTLS mb; +// Set time via NTP, as required for x.509 validation +void setClock() +{ + configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + delay(500); + Serial.print("."); + now = time(nullptr); + } + Serial.println(""); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print("Current time: "); + Serial.print(asctime(&timeinfo)); +} +//IPAddress remote(192,168,30,127); +char* remote = "modbustls"; +void setup() { + Serial.begin(115200); + + WiFi.begin("E2", "fOlissio92"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + setClock(); + mb.client(); +} +uint16_t v; +void loop() { + if (!mb.isConnected(remote)) { + delay(1000); + //mb.connectWithKnownKey(remote, MODBUSTLS_PORT, client_cert, client_private_key, server_pk); + mb.connect(remote, MODBUSTLS_PORT, client_cert, client_private_key, ca_cert); + Serial.print("."); + } else { + mb.readHreg(remote, 0, &v); + mb.task(); + delay(100); + } +} \ No newline at end of file diff --git a/examples/TLS/server/server.ino b/examples/TLS/server/server.ino new file mode 100644 index 0000000..226d3cb --- /dev/null +++ b/examples/TLS/server/server.ino @@ -0,0 +1,142 @@ +/* + Modbus-Uni - Most complete Modbus Library for Arduino + + Modbus/TCP Security Server for ESP8266 Example + + (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include +#include +#include + +// The hardcoded certificate authority for this example. +// Don't use it on your own apps!!!!! + +const char ca_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICwjCCAaqgAwIBAgIUTz9NFtf8JkdIkrDroXVB/ANtqlYwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHcm9vdF9jYTAeFw0yMDA4MjcxMDI4MzFaFw0zMTExMTQx +MDI4MzFaMBIxEDAOBgNVBAMMB3Jvb3RfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCpYdVI2PjW+Pdw5bMqxFz0s3jgYgTHyt51NJGlImgJpmjmj16T +rwcqAe70BtsSjOQeWRoF/rk46ZO/ntDbVkP8ZA40Vf8F8Yft64f1OOBf93rTR0sH +oUk+HmE3Iu+bWYSewNMw/LJyF2r95V2xNeX50Y+BhQskBoWYR7C671ifFlsQHI+a +/BpALEi7qt6kGenlhrmRAjweNxVNILHTPH7Fr/TYXWfAb69TzXWTUFy0bdwZfPIP +b2HXyGINGiD6EtZDkybPk17zZgJKMdxpEG5XA/O+daVh3Prlar+amqb30zntOVga +AcyREcmzYFFBWQmuKNw9mz9x09GWLWjBaYP9AgMBAAGjEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBAIFtNowXu8wfahpKF5MoNKrqA9AG/aGzlmbD +pBKr5Cvvo7NdC6oZMdXlS0VkRmAyw9mEJgwspUvqsLx0/lgN+sE381MUovWL9BIs +o4IOax5473q6ZwV87jwpsrlNHBiolw+WCDKVYuDktaThCaxqxmPKCPMbgYPdqWB0 +l1gYDJJ+MwNH/CRsynpM8Hppf88BwwbM6JYegg5/DLxRl5z3HjCAVD8vBoqkWLRD +b9tIER4WDJhZG4tzgMW+lbMJyDoQA1cw4BGag4Ir1er32+w1519UR/VK0ltk9BK9 +yHObfUNN6saco1/f4OM4tzaQOKa+6U1iXVBTBjE2IHPchGqctBk= +-----END CERTIFICATE----- +)EOF"; + +// The server's private key which must be kept secret +const char server_private_key[] PROGMEM = R"EOF( +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3SpcCq8sUhAbBztc+X69H+miV/Dxw9SH7X5Gtz09vOcEaDQn +D2uXk6OXQUsiTPSYCqahrHZtjO1AQCWQBQUoEIvvqUbjbLLM2tblK3tvE9zx1eOt +zpmue5hX6SJShny/61blJaruIoDSIEE/8ECZsSTvLQ61mk4zM+FYAjZBuMtb3niK +c+/wsvUuQsRCgW4m0J3KVF8leLU0/Tt+6s3tBq/ne3658skzra/NAMDDINT5+tYW +W5LvRC/1RNd6FlQQUl89HsR1RvF/TXuLsrshtxwFoXoJYUO5tkZAcU5A1aJUn+nn +NV4J99eQTAhtXgUwGSjXUUUq+PsetKeO0XmliQIDAQABAoIBAAEFxB0siCjs+CMF +bD2fD2LJYr3DWGrOXb6EWfFY8CMickvFCfUxSyccl4NuxH7Ulqtd79trRMBlDGn/ +gnXzeybwbrA6qqyC+x175t1XmcDewaN6hQAyh7L8llN2nCkRBJYi9bZB3w37yHzr +sE79DXjbMdvkeIR5HhV8UjrYY19mVxbJsTokbrDXJEuDvR1kIkYM10UnEqMdDRiV +pSVFS3XkwNfCZHn/f2slcv1Piah8qfTseb5QPNouCZBW+PU5E1BuoFTH2ZtkzlZk +L6lcg42Ameyn5G9w8Walz89zwUIe3sycdJJ8+tRCWA2a2Aj8MeFiLfLxXnTjPNLU +nsVkuH0CgYEA/vbYyef3nzlmV4H+Rk9Rn3nGOeSTQj00avCUQS298bztEvFwcibM +84GOg0j68n/IW6gd4qBJyGpjBYl9ggcSTYBXKEK5FgtO6fM+Rnvnds3/WO7cMz3z +SzGIYranzcpaphj8H2xihW1OLsOUlb0+M71+6Y8P8Yui/mQIRRxHzMsCgYEA3hBc +/lzxs0A/TiwLlCpMIBuadkAxc4zgFY1LYHMveCwCa1cp9lujL5NRVOd8VhmYfpDg +MWAN3q7N/3dOu+hdMDoArXCXS5g5My3c6Ki87aSL1rmHyt9nYIBWZzjnwAii/zqs +kcKaFoEYv1vCuIMDFg+S1f+dpwuYlNn2pcnmwHsCgYEAlm/1+CQbsmI+5ZE5BClX +At7qPEyHKwVMAXFUOKURtyn/RDcbXu9P7Lnb6dDM6PrGsHYgtBBZmJxVMvYuDOO5 +Q+tfAc1kwgIIHPg+HX6MU0g2yzWczctW214tl/koR7+G/wws7ymXdBzLjcIu0K9p +nUPJN2wHP0Fh+fHyAz0tjEMCgYB4R9C3Dkz01LX1d7IF3StCsPDnYDno5sNxqQjN +A1cQ9nWRArN994DagicpoAEe+do5o+trkyWwGmsGFu+UpHXla2V2jGfG0HsbF5py +gwNijSAZfIDrCDsMcDdczdvpjkQLjxJuGUQxMFfhPqioHH6Ncn4MX9pa4tMQvUb1 +4fiVBQKBgAOtTdRqX66vvcOsjJmr2f+fw5SRXc09hxLaCDNjC6jVbCUw0aj6WKlx +sBfP8fvlHqJ6wA9/W2l+YiIf3G2jY2Z8OlOINs3hdXpH0JBzoeEiFwfcfZPAMFb1 +M4JURmEGAriH2lw/5iMQ/YqB9+NoE8t8lBLrhjwXWxN3qxoSruwe +-----END RSA PRIVATE KEY----- +)EOF"; + +// The server's public certificate which must be shared +const char server_cert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +MIICrTCCAZUCFEIh1S7CewqVDZ9B78fSCkMQDkN8MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3Jvb3RfY2EwHhcNMjAwODI3MTAyODMxWhcNMzEwODEwMTAyODMx +WjAUMRIwEAYDVQQDDAltb2RidXN0bHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDdKlwKryxSEBsHO1z5fr0f6aJX8PHD1Iftfka3PT285wRoNCcPa5eT +o5dBSyJM9JgKpqGsdm2M7UBAJZAFBSgQi++pRuNsssza1uUre28T3PHV463Oma57 +mFfpIlKGfL/rVuUlqu4igNIgQT/wQJmxJO8tDrWaTjMz4VgCNkG4y1veeIpz7/Cy +9S5CxEKBbibQncpUXyV4tTT9O37qze0Gr+d7frnyyTOtr80AwMMg1Pn61hZbku9E +L/VE13oWVBBSXz0exHVG8X9Ne4uyuyG3HAWheglhQ7m2RkBxTkDVolSf6ec1Xgn3 +15BMCG1eBTAZKNdRRSr4+x60p47ReaWJAgMBAAEwDQYJKoZIhvcNAQELBQADggEB +AGqz1benN8ygveD3F/XxCMgEPfI8WhYS3PQ6sPBE850TuQ+9OrHvue8q87/RfJBW +Yllkyi2JHGuY2muMBJWGWTDHK72JwI69hIIwE9bGrvFUwAZjCbK9+gF6UIUDznNN +bSHHSWTfkCMLFz0Q6XbhJvF2AX5dtmYL+AWqD8+G5ZIrbHgd/o4neA21DTYRfUt2 +0UQ0RAxKxf/BenJWr8IzvOxo0MHxi7JivgreCfxna1REZYxIwVFlh7O9KhB1QQBJ +oezb4CS0Um9sQowFTZ3AtRxpv1u60ZjTeJwSIL00YEyZ0sEO3K4h3PTbh2VxSoQD +D2fcSb1t+gWsvLVZCmNMbVo= +-----END CERTIFICATE----- +)EOF"; + + +//Modbus Registers Offsets +const int LED_COIL = 100; + +//ModbusIP object +ModbusTLS mb; +// Set time via NTP, as required for x.509 validation +void setClock() +{ + + configTime(5 * 3600, 0, "pool.ntp.org", "time.nist.gov"); + + Serial.print("Waiting for NTP time sync: "); + time_t now = time(nullptr); + while (now < 8 * 3600 * 2) { + delay(500); + Serial.print("."); + now = time(nullptr); + } + + Serial.println(""); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + Serial.print("Current time: "); + Serial.print(asctime(&timeinfo)); +} + bool c(IPAddress ip) { + Serial.println(ip); + return true; + } +void setup() { + Serial.begin(115200); + + WiFi.begin("E2", "fOlissio92"); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + setClock(); + mb.server(MODBUSTLS_PORT, server_cert, server_private_key, ca_cert); + + mb.addHreg(0); +} + +void loop() { + mb.task(); + delay(100); +} \ No newline at end of file diff --git a/expressifSDK/In development.txt b/expressifSDK/In development.txt deleted file mode 100644 index e69de29..0000000 diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..38bc2f4 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,135 @@ +# Syntax Coloring Map For ModbusIP-ESP8266 + +# Datatypes (KEYWORD1) +ModbusRTU KEYWORD1 +ModbusIP KEYWORD1 +ModbusTCP KEYWORD1 +ModbusIP_ESP8266 KEYWORD1 +Modbus KEYWORD1 +TRegister KEYWORD1 +TTransaction KEYWORD1 +TAddress KEYWORD1 +ResultCode KEYWORD1 + +# Methods and Functions (KEYWORD2) +client KEYWORD2 +server KEYWORD2 +task KEYWORD2 +onConnect KEYWORD2 +onDisconnect KEYWORD2 +cbEnable KEYWORD2 +cbDisable KEYWORD2 +eventSource KEYWORD2 +onGetCoil KEYWORD2 +onSetCoil KEYWORD2 +onGetHreg KEYWORD2 +onSetHreg KEYWORD2 +onGetIreg KEYWORD2 +onSetIreg KEYWORD2 +onGetIsts KEYWORD2 +onSetIsts KEYWORD2 +removeOnGetCoil KEYWORD2 +removeOnSetCoil KEYWORD2 +removeOnGetHreg KEYWORD2 +removeOnSetHreg KEYWORD2 +removeOnGetIsts KEYWORD2 +removeOnSetIsts KEYWORD2 +removeOnGetIreg KEYWORD2 +removeOnSetIreg KEYWORD2 +addCoil KEYWORD2 +addIsts KEYWORD2 +addIreg KEYWORD2 +addHreg KEYWORD2 +Coil KEYWORD2 +Ists KEYWORD2 +Ireg KEYWORD2 +Hreg KEYWORD2 +isTransaction KEYWORD2 +isConnected KEYWORD2 +writeCoil KEYWORD2 +readCoil KEYWORD2 +writeHreg KEYWORD2 +readHreg KEYWORD2 +readIsts KEYWORD2 +readIreg KEYWORD2 +pushCoil KEYWORD2 +pullCoil KEYWORD2 +pullIsts KEYWORD2 +pushHreg KEYWORD2 +pullHreg KEYWORD2 +pullIreg KEYWORD2 +pullHregToIreg KEYWORD2 +pullCoilToIsts KEYWORD2 +pushIstsToCoil KEYWORD2 +pushIregToHreg KEYWORD2 +removeHreg KEYWORD2 +removeIreg KEYWORD2 +removeCoil KEYWORD2 +removeIsts KEYWORD2 +autoConnect KEYWORD2 +disconnect KEYWORD2 +dropTransactions KEYWORD2 +isCoil KEYWORD2 +isHreg KEYWORD2 +isIsts KEYWORD2 +isIreg KEYWORD2 +begin KEYWORD2 +setBaudrate KEYWORD2 +readFileRec KEYWORD2 +writeFileRec KEYWORD2 +maskHreg KEYWORD2 +readWriteHreg KEYWORD2 +onFile KEYWORD2 +onRequest KEYWORD2 +onRequestSuccess KEYWORD2 +onSet KEYWORD2 +onGet KEYWORD2 +removeOnSet KEYWORD2 +removeOnGet KEYWORD2 +Reg KEYWORD2 +addReg KEYWORD2 +removeReg KEYWORD2 + +# Constants and Macros (LITERAL1) +BIT_VAL LITERAL1 +BIT_BOOL LITERAL1 +COIL_VAL LITERAL1 +COIL_BOOL LITERAL1 +ISTS_VAL LITERAL1 +ISTS_BOOL LITERAL1 +ResultCode LITERAL1 +FunctionCode LITERAL1 +EX_SUCCESS LITERAL1 +EX_ILLEGAL_FUNCTION LITERAL1 +EX_ILLEGAL_ADDRESS LITERAL1 +EX_ILLEGAL_VALUE LITERAL1 +EX_SLAVE_FAILURE LITERAL1 +EX_ACKNOWLEDGE LITERAL1 +EX_SLAVE_DEVICE_BUSY LITERAL1 +EX_MEMORY_PARITY_ERROR LITERAL1 +EX_PATH_UNAVAILABLE LITERAL1 +EX_DEVICE_FAILED_TO_RESPOND LITERAL1 +EX_GENERAL_FAILURE LITERAL1 +EX_DATA_MISMACH LITERAL1 +EX_UNEXPECTED_RESPONSE LITERAL1 +EX_TIMEOUT LITERAL1 +EX_CONNECTION_LOST LITERAL1 +EX_CANCEL LITERAL1 +COIL LITERAL1 +HREG LITERAL1 +ISTS LITERAL1 +IREG LITERAL1 +FC_READ_COILS LITERAL1 +FC_READ_INPUT_STAT LITERAL1 +FC_READ_REGS LITERAL1 +FC_READ_INPUT_REGS LITERAL1 +FC_WRITE_COIL LITERAL1 +FC_WRITE_REG LITERAL1 +FC_DIAGNOSTICS LITERAL1 +FC_WRITE_COILS LITERAL1 +FC_WRITE_REGS LITERAL1 +FC_READ_FILE_REC LITERAL1 +FC_WRITE_FILE_REC LITERAL1 +FC_MASKWRITE_REG LITERAL1 +FC_READWRITE_REGS LITERAL1 + diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..765f44a --- /dev/null +++ b/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/resources/client.png b/resources/client.png new file mode 100644 index 0000000..5d6b825 Binary files /dev/null and b/resources/client.png differ diff --git a/resources/client.uml b/resources/client.uml new file mode 100644 index 0000000..3f0b538 --- /dev/null +++ b/resources/client.uml @@ -0,0 +1,21 @@ +@startuml +!pragma useVerticalIf on +start +partition readHreg() { +if (no request is alreaty running) then (yes) + :readHreg; +endif +} +while (request is active) +partition task() { +if (responce arrived) then (yes) + #palegreen:Execute Transactional Callback; + :fill result data; +endif +if (responce timeout) then (yes) + #palegreen:Execute Transactional Callback; +endif +} +endwhile (no request) +stop +@enduml \ No newline at end of file diff --git a/resources/server.png b/resources/server.png new file mode 100644 index 0000000..f8082e9 Binary files /dev/null and b/resources/server.png differ diff --git a/resources/server.uml b/resources/server.uml new file mode 100644 index 0000000..d4c03a7 --- /dev/null +++ b/resources/server.uml @@ -0,0 +1,26 @@ +@startuml +!pragma useVerticalIf on +start +partition task() { +if (valid request arrived) then (yes) + #palegreen:Execute onRequest Callback; + if (EX_SUCCESS) then (yes) + if (correct request parameters) then (yes) + :Preapre responce; + #palegreen:Execute onGet/onSet Callback for each register in request; + #palegreen:Execute onRequestSuccess Callback; + else (no) + #pink:Return error code; + stop + endif + else (no) + #pink:Return error code; + stop + endif +else + #pink:Return error code; + stop +endif +} +stop +@enduml \ No newline at end of file diff --git a/src/Modbus.cpp b/src/Modbus.cpp new file mode 100644 index 0000000..66f9f98 --- /dev/null +++ b/src/Modbus.cpp @@ -0,0 +1,931 @@ +/* + Modbus Library for Arduino + Core functions + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2023 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#include "Modbus.h" + +#if defined(MODBUS_GLOBAL_REGS) +#if defined(MODBUS_USE_STL) + std::vector 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: + ex = _onRequest(fcode, {frame + 1}); + if (ex != EX_PASSTHROUGH) { + exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); + } + return; + } +} + +void Modbus::successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return; + } + _frame[0] = fn; + _frame[1] = startreg.address >> 8; + _frame[2] = startreg.address & 0x00FF; + _frame[3] = numoutputs >> 8; + _frame[4] = numoutputs & 0x00FF; +} + +void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) { + free(_frame); + _len = 2; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return; + } + _frame[0] = fn + 0x80; + _frame[1] = excode; + _reply = REPLY_NORMAL; +} + +void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs) { + uint8_t bitn = 0; + uint16_t i = 0; + while (numregs--) { + if (BIT_BOOL(Reg(startreg))) + bitSet(frame[i], bitn); + else + bitClear(frame[i], bitn); + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + startreg++; //increment the register + } +} + +void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { + for (uint8_t i = 0; i < numregs; i++) { + frame[i] = __swap_16(Reg(startreg + i)); + } +} + +Modbus::ResultCode Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { + if (numregs < 0x0001 || numregs > MODBUS_MAX_BITS || (0xFFFF - startreg.address) < numregs) + return EX_ILLEGAL_ADDRESS; + //Check Address + //Check only startreg. Is this correct? + //When I check all registers in range I got errors in ScadaBR + //I think that ScadaBR request more than one in the single request + //when you have more then one datapoint configured from same type. +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_ADDRESS; + } +#else + if (!searchRegister(startreg)) + return EX_ILLEGAL_ADDRESS; +#endif + free(_frame); + //Determine the message length = function type, byte count and + //for each group of 8 registers the message length increases by 1 + _len = 2 + numregs/8; + if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. + _frame = (uint8_t*) malloc(_len); + if (!_frame) + return EX_SLAVE_FAILURE; + _frame[0] = fn; + _frame[1] = _len - 2; //byte count (_len - function code and byte count) + _frame[_len - 1] = 0; //Clean last probably partial byte + getMultipleBits(_frame+2, startreg, numregs); + _reply = REPLY_NORMAL; + return EX_SUCCESS; +} + +Modbus::ResultCode Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { + //Check value (numregs) + if (numregs < 0x0001 || numregs > MODBUS_MAX_WORDS || 0xFFFF - startreg.address < numregs) + return EX_ILLEGAL_ADDRESS; +#if defined(MODBUS_STRICT_REG) + for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs) + if (!searchRegister(startreg + k)) + return EX_ILLEGAL_ADDRESS; + } +#else + if (!searchRegister(startreg)) + return EX_ILLEGAL_ADDRESS; +#endif + free(_frame); + _len = 2 + numregs * 2; //calculate the query reply message length. 2 bytes per register + 2 bytes for header + _frame = (uint8_t*) malloc(_len); + if (!_frame) + return EX_SLAVE_FAILURE; + _frame[0] = fn; + _frame[1] = _len - 2; //byte count + getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs); + _reply = REPLY_NORMAL; + return EX_SUCCESS; +} + +bool Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) { + uint8_t bitn = 0; + uint16_t i = 0; + bool result = true; + while (numoutputs--) { + Reg(startreg, BIT_VAL(bitRead(frame[i], bitn))); + if (Reg(startreg) != BIT_VAL(bitRead(frame[i], bitn))) + result = false; + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + startreg++; //increment the register + } + return result; +} + +bool Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { + bool result = true; + for (uint8_t i = 0; i < numregs; i++) { + Reg(startreg + i, __swap_16(frame[i])); + if (Reg(startreg + i) != __swap_16(frame[i])) + result = false; + } + return result; +} + +bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + if (!cb) { + return removeOnGet(address, nullptr, numregs); + } + while (numregs > 0) { + reg = searchRegister(address); + if (reg) { + _callbacks.push_back({TCallback::ON_GET, address, cb}); + atLeastOne = true; + } + address++; + numregs--; + } + return atLeastOne; +} +bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { + TRegister* reg; + bool atLeastOne = false; + if (!cb) { + return removeOnSet(address, nullptr, numregs); + } + while (numregs > 0) { + reg = searchRegister(address); + if (reg) { + _callbacks.push_back({TCallback::ON_SET, address, cb}); + atLeastOne = true; + } + address++; + numregs--; + } + return atLeastOne; +} + +bool Modbus::removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb, uint16_t numregs) { + size_t s = _callbacks.size(); + #if defined(MODBUS_USE_STL) + #define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){\ + return entry.type == t && entry.address == address \ + && (!cb || std::addressof(cb) == std::addressof(entry.cb));} + while(numregs--) { + _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ON), _callbacks.end()); + address++; + } + #else + #define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){ \ + return entry.type == t && entry.address == address \ + && (!cb || entry.cb == cb);} + while(numregs--) { + size_t r = 0; + do { + r = _callbacks.find(MODBUS_COMPARE_ON); + _callbacks.remove(r); + } while (r < _callbacks.size()); + address++; + } + #endif + return s == _callbacks.size(); +} +bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) { + return removeOn(TCallback::ON_SET, address, cb, numregs); +} + +bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) { + return removeOn(TCallback::ON_GET, address, cb, numregs); +} + +bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) { + free(_frame); + _len = 5; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return false; + } + _frame[0] = fn; + _frame[1] = address >> 8; + _frame[2] = address & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + return true; +} + +bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data) { + free(_frame); + _len = 6 + numregs/8; + if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return false; + } + _frame[0] = fn; + _frame[1] = to >> 8; + _frame[2] = to & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + _frame[5] = _len - 6; + _frame[_len - 1] = 0; //Clean last probably partial byte + if (data) { + boolToBits(_frame + 6, data, numregs); + } else { + getMultipleBits(_frame + 6, startreg, numregs); + } + _reply = REPLY_NORMAL; + return true; +} + +bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data) { + free(_frame); + _len = 6 + 2 * numregs; + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + _reply = REPLY_OFF; + return false; + } + _frame[0] = fn; + _frame[1] = to >> 8; + _frame[2] = to & 0x00FF; + _frame[3] = numregs >> 8; + _frame[4] = numregs & 0x00FF; + _frame[5] = _len - 6; + if (data) { + uint16_t* frame = (uint16_t*)(_frame + 6); + for (uint8_t i = 0; i < numregs; i++) { + frame[i] = __swap_16(data[i]); + } + } else { + getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs); + } + return true; +} + +void Modbus::boolToBits(uint8_t* dst, bool* src, uint16_t numregs) { + uint8_t bitn = 0; + uint16_t i = 0; + uint16_t j = 0; + while (numregs--) { + if (src[j]) + bitSet(dst[i], bitn); + else + bitClear(dst[i], bitn); + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + j++; //increment the register + } +} + +void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { + uint8_t bitn = 0; + uint16_t i = 0; + uint16_t j = 0; + while (numregs--) { + dst[j] = bitRead(src[i], bitn); + bitn++; //increment the bit index + if (bitn == 8) { + i++; + bitn = 0; + } + j++; //increment the register + } +} + +void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output) { + uint8_t fcode = frame[0]; + if ((fcode & 0x80) != 0) { // Check if error responce + _reply = frame[1]; + return; + } + if (fcode != sourceFrame[0]) { // Check if responce matches the request + _reply = EX_DATA_MISMACH; + return; + } + _reply = EX_SUCCESS; + uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; + uint8_t bytecount_calc; + switch (fcode) { + case FC_READ_REGS: + case FC_READ_INPUT_REGS: + case FC_READWRITE_REGS: + //field2 = numregs, frame[1] = data lenght, header len = 2 + if (frame[1] != 2 * field2) { //Check if data size matches + _reply = EX_DATA_MISMACH; + break; + } + if (output) { + uint16_t* from = (uint16_t*)(frame + 2); + uint16_t* to = (uint16_t*)output; + while(field2--) { + *(to++) = __swap_16(*(from++)); + } + } else { + setMultipleWords((uint16_t*)(frame + 2), startreg, field2); + } + break; + case FC_READ_COILS: + case FC_READ_INPUT_STAT: + //field2 = numregs, frame[1] = data length, header len = 2 + bytecount_calc = field2 / 8; + if (field2 % 8) bytecount_calc++; + if (frame[1] != bytecount_calc) { // check if data size matches + _reply = EX_DATA_MISMACH; + break; + } + if (output) { + bitsToBool((bool*)output, frame + 2, field2); + } else { + setMultipleBits(frame + 2, startreg, field2); + } + break; + #if defined(MODBUS_FILES) + case FC_READ_FILE_REC: + // Should check if byte order swap needed + if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size + _reply = EX_ILLEGAL_VALUE; + return; + } + { + uint8_t* data = frame + 2; + uint8_t* eoFrame = frame + frame[1]; + while (data < eoFrame) { + //data[0] - sub-req length + //data[1] = 0x06 + if (data[1] != 0x06 || data[0] < 0x07 || data[0] > 0xF5 || data + data[0] > eoFrame) { // Wrong request data size + _reply = EX_ILLEGAL_VALUE; + return; + } + memcpy(output, data + 2, data[0]); + data += data[0] + 1; + output += data[0] - 1; + } + } + break; + case FC_WRITE_FILE_REC: + #endif + case FC_WRITE_REG: + case FC_WRITE_REGS: + case FC_WRITE_COIL: + case FC_WRITE_COILS: + case FC_MASKWRITE_REG: + break; + + default: + _reply = EX_GENERAL_FAILURE; + } +} + +bool Modbus::cbEnable(const bool state) { + const bool old_state = state; + cbEnabled = state; + return old_state; +} +bool Modbus::cbDisable() { + return cbEnable(false); +} +Modbus::~Modbus() { + free(_frame); +} + +#if defined(MODBUS_FILES) +#if defined(MODBUS_USE_STL) +bool Modbus::onFile(std::function 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/src/Modbus.h b/src/Modbus.h new file mode 100644 index 0000000..97fd340 --- /dev/null +++ b/src/Modbus.h @@ -0,0 +1,363 @@ +/* + 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 NULLREG (TAddress){TAddress::NONE, 0xFFFF} +#define BIT_VAL(v) (v?0xFF00:0x0000) +#define BIT_BOOL(v) (v==0xFF00) +#define COIL_VAL(v) (v?0xFF00:0x0000) +#define COIL_BOOL(v) (v==0xFF00) +#define ISTS_VAL(v) (v?0xFF00:0x0000) +#define ISTS_BOOL(v) (v==0xFF00) + +// For depricated (v1.xx) onSet/onGet format compatibility +#define cbDefault nullptr + +struct TRegister; +#if defined(MODBUS_USE_STL) +typedef std::function cbModbus; // Callback function Type +#else +typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type +#endif + +struct TAddress { + enum RegType {COIL, ISTS, IREG, HREG, NONE = 0xFF}; + RegType type; + uint16_t address; + bool operator==(const TAddress &obj) const { // TAddress == TAddress + return type == obj.type && address == obj.address; + } + bool operator!=(const TAddress &obj) const { // TAddress != TAddress + return type != obj.type || address != obj.address; + } + TAddress& operator++() { // ++TAddress + address++; + return *this; + } + TAddress operator++(int) { // TAddress++ + TAddress result(*this); + ++(*this); + return result; + } + TAddress& operator+=(const int& inc) { // TAddress += integer + address += inc; + return *this; + } + const TAddress operator+(const int& inc) const { // TAddress + integer + TAddress result(*this); + result.address += inc; + return result; + } + bool isCoil() { + return type == COIL; + } + bool isIsts() { + return type == ISTS; + } + bool isIreg() { + return type == IREG; + } + bool isHreg() { + return type == HREG; + } +}; + +struct TCallback { + enum CallbackType {ON_SET, ON_GET}; + CallbackType type; + TAddress address; + cbModbus cb; +}; + +struct TRegister { + TAddress address; + uint16_t value; + bool operator ==(const TRegister &obj) const { + return address == obj.address; + } +}; + +class Modbus { + public: + //Function Codes + enum FunctionCode { + FC_READ_COILS = 0x01, // Read Coils (Output) Status + FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) + FC_READ_REGS = 0x03, // Read Holding Registers + FC_READ_INPUT_REGS = 0x04, // Read Input Registers + FC_WRITE_COIL = 0x05, // Write Single Coil (Output) + FC_WRITE_REG = 0x06, // Preset Single Register + FC_DIAGNOSTICS = 0x08, // Not implemented. Diagnostics (Serial Line only) + FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) + FC_WRITE_REGS = 0x10, // Write block of contiguous registers + FC_READ_FILE_REC = 0x14, // Read File Record + FC_WRITE_FILE_REC = 0x15, // Write File Record + FC_MASKWRITE_REG = 0x16, // Mask Write Register + FC_READWRITE_REGS = 0x17 // Read/Write Multiple registers + }; + //Exception Codes + //Custom result codes used internally and for callbacks but never used for Modbus responce + enum ResultCode { + EX_SUCCESS = 0x00, // Custom. No error + EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported + EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists + EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range + EX_SLAVE_FAILURE = 0x04, // Slave or Master Device Fails to process request + EX_ACKNOWLEDGE = 0x05, // Not used + EX_SLAVE_DEVICE_BUSY = 0x06, // Not used + EX_MEMORY_PARITY_ERROR = 0x08, // Not used + EX_PATH_UNAVAILABLE = 0x0A, // Not used + EX_DEVICE_FAILED_TO_RESPOND = 0x0B, // Not used + EX_GENERAL_FAILURE = 0xE1, // Custom. Unexpected master error + EX_DATA_MISMACH = 0xE2, // Custom. Inpud data size mismach + EX_UNEXPECTED_RESPONSE = 0xE3, // Custom. Returned result doesn't mach transaction + EX_TIMEOUT = 0xE4, // Custom. Operation not finished within reasonable time + EX_CONNECTION_LOST = 0xE5, // Custom. Connection with device lost + EX_CANCEL = 0xE6, // Custom. Transaction/request canceled + EX_PASSTHROUGH = 0xE7, // Custom. Raw callback. Indicate to normal processing on callback exit + EX_FORCE_PROCESS = 0xE8 // Custom. Raw callback. Indicate to force processing on callback exit + }; + union RequestData { + struct { + TAddress reg; + uint16_t regCount; + }; + struct { + TAddress regRead; + uint16_t regReadCount; + TAddress regWrite; + uint16_t regWriteCount; + }; + struct { + TAddress regMask; + uint16_t andMask; + uint16_t orMask; + }; + uint8_t* data; + RequestData(TAddress r1, uint16_t c1) { + reg = r1; + regCount = c1; + }; + RequestData(TAddress r1, uint16_t c1, TAddress r2, uint16_t c2) { + regRead = r1; + regReadCount = c1; + regWrite = r2; + regWriteCount = c2; + }; + RequestData(TAddress r1, uint16_t m1, uint16_t m2) { + regMask = r1; + andMask = m1; + orMask = m2; + }; + RequestData(uint8_t* d) { + data = d; + }; + }; + + struct frame_arg_t { + bool to_server; + union { + uint8_t slaveId; + struct { + uint8_t unitId; + uint32_t ipaddr; + uint16_t transactionId; + }; + }; + frame_arg_t(uint8_t s, bool m = false) { + slaveId = s; + to_server = m; + }; + frame_arg_t(uint8_t u, uint32_t a, uint16_t t, bool m = false) { + unitId = u; + ipaddr = a; + transactionId = t; + to_server = m; + }; + }; + + ~Modbus(); + + bool cbEnable(const bool state = true); + bool cbDisable(); + + private: + ResultCode readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); + ResultCode readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); + + bool setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); + bool setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs); + + void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); + void getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs); + + void bitsToBool(bool* dst, uint8_t* src, uint16_t numregs); + void boolToBits(uint8_t* dst, bool* src, uint16_t numregs); + + protected: + //Reply Types + enum ReplyCode { + REPLY_OFF = 0x01, + REPLY_ECHO = 0x02, + REPLY_NORMAL = 0x03, + REPLY_ERROR = 0x04, + REPLY_UNEXPECTED = 0x05 + }; + #if defined(MODBUS_USE_STL) + #if defined(MODBUS_GLOBAL_REGS) + static std::vector _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/src/ModbusAPI.h b/src/ModbusAPI.h new file mode 100644 index 0000000..8c31a93 --- /dev/null +++ b/src/ModbusAPI.h @@ -0,0 +1,507 @@ +/* + Modbus Library for Arduino + Modbus public API implementation + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once +#include "Modbus.h" + +template +class ModbusAPI : public T { + public: + // Alternative API + template + uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t write(TYPEID id, TAddress reg, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t write(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +/* + template + uint16_t push(TYPEID id, TAddress to, TAddress from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); +*/ + // Classic API + bool Hregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(HREG(offset), value);} + bool Coils(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(COIL(offset), value);} + bool Istss(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(ISTS(offset), value);} + bool Iregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(IREG(offset), value);} + + //bool addHreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(HREG(offset), value);} + //bool addCoil(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(COIL(offset), value);} + //bool addIsts(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(ISTS(offset), value);} + //bool addIreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(IREG(offset), value);} + + bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); + bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1); + bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); + bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); + + bool Hreg(uint16_t offset, uint16_t value); + bool Coil(uint16_t offset, bool value); + bool Ists(uint16_t offset, bool value); + bool Ireg(uint16_t offset, uint16_t value); + + bool Coil(uint16_t offset); + bool Ists(uint16_t offset); + uint16_t Ireg(uint16_t offset); + uint16_t Hreg(uint16_t offset); + + bool removeCoil(uint16_t offset, uint16_t numregs = 1); + bool removeIsts(uint16_t offset, uint16_t numregs = 1); + bool removeIreg(uint16_t offset, uint16_t numregs = 1); + bool removeHreg(uint16_t offset, uint16_t numregs = 1); + + bool onGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool onSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + + bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); + + template + uint16_t writeCoil(TYPEID id, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t writeCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t readCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t readIsts(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t readHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t readIreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + template + uint16_t pushCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pullCoil(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pullIsts(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pushHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pullHreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pullIreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + template + uint16_t pullHregToIreg(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pullCoilToIsts(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pushIstsToCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t pushIregToHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + template + uint16_t readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + template + uint16_t maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t readWriteHreg(TYPEID slaveId, uint16_t readOffset, uint16_t* readValue, uint16_t readNumregs, uint16_t writeOffset, uint16_t* writeValue, uint16_t writeNumregs, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + + template + uint16_t rawRequest(TYPEID ip, const uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t rawResponce(TYPEID ip, const uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT); + template + uint16_t errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT); +}; + +// FNAME writeCoil, writeIsts, writeHreg, writeIreg +// REG COIL, ISTS, HREG, IREG +// FUNC Modbus function +// MAXNUM Register count limit +// VALTYPE bool, uint16_t +// VALUE +#define IMPLEMENT_WRITEREG(FNAME, REG, FUNC, VALUE, VALTYPE) \ +template \ +template \ +uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE value, cbTransaction cb, uint8_t unit) { \ + this->readSlave(offset, VALUE(value), Modbus::FUNC); \ + return this->send(ip, REG(offset), cb, unit); \ +} +IMPLEMENT_WRITEREG(writeCoil, COIL, FC_WRITE_COIL, COIL_VAL, bool) +IMPLEMENT_WRITEREG(writeHreg, HREG, FC_WRITE_REG, , uint16_t) + +#define IMPLEMENT_WRITEREGS(FNAME, REG, FUNC, VALUE, MAXNUM, VALTYPE) \ +template \ +template \ +uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ + if (numregs < 0x0001 || numregs > MAXNUM) return false; \ + this->VALUE(REG(offset), offset, numregs, Modbus::FUNC, value); \ + return this->send(ip, REG(offset), cb, unit); \ +} +IMPLEMENT_WRITEREGS(writeCoil, COIL, FC_WRITE_COILS, writeSlaveBits, MODBUS_MAX_BITS, bool) +IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, MODBUS_MAX_WORDS, uint16_t) + +#define IMPLEMENT_READREGS(FNAME, REG, FUNC, MAXNUM, VALTYPE) \ +template \ +template \ +uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ + if (numregs < 0x0001 || numregs > MAXNUM) return false; \ + this->readSlave(offset, numregs, Modbus::FUNC); \ + return this->send(ip, REG(offset), cb, unit, (uint8_t*)value); \ +} +IMPLEMENT_READREGS(readCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS, bool) +IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS, uint16_t) +IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS, bool) +IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS, uint16_t) + +#if defined(MODBUS_ADD_REG) +#define ADDREG(R) this->addReg(R(to), (uint16_t)0, numregs); +#else +#define ADDREG(R) ; +#endif +#define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \ +template \ +template \ +uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ + if (numregs < 0x0001 || numregs > MAXNUM) return false; \ + ADDREG(REG) \ + this->readSlave(from, numregs, Modbus::FUNC); \ + return this->send(ip, REG(to), cb, unit); \ +} +IMPLEMENT_PULL(pullCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS) +IMPLEMENT_PULL(pullIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS) +IMPLEMENT_PULL(pullHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS) +IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS) +IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, MODBUS_MAX_WORDS) +IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, MODBUS_MAX_BITS) + +#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM, FINT) \ +template \ +template \ +uint16_t ModbusAPI::FNAME(TYPEID ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { \ + if (numregs < 0x0001 || numregs > MAXNUM) return false; \ + if (!this->searchRegister(REG(from))) return false; \ + this->FINT(REG(from), to, numregs, Modbus::FUNC); \ + return this->send(ip, REG(from), cb, unit); \ +} +IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits) +IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords) +IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords) +IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits) + +template +template +uint16_t ModbusAPI::read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + switch (reg.type) { + case TAddress::HREG: + return readHreg(id, reg.address, value, numregs, cb, unit); + case TAddress::IREG: + return readIreg(id, reg.address, value, numregs, cb, unit); + default: + return 0; + } +} +template +template +uint16_t ModbusAPI::read(TYPEID id, TAddress reg, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + switch (reg.type) { + case TAddress::COIL: + return readCoil(id, reg.address, value, numregs, cb, unit); + case TAddress::ISTS: + return readIsts(id, reg.address, value, numregs, cb, unit); + default: + return 0; + } +} +template +template +uint16_t ModbusAPI::write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb, uint8_t unit) { + switch (reg.type) { + case TAddress::COIL: + return writeCoil(id, reg.address, value, cb, unit); + case TAddress::HREG: + return writeHreg(id, reg.address, value, cb, unit); + default: + return 0; + } +} +template +template +uint16_t ModbusAPI::write(TYPEID id, TAddress reg, bool value, cbTransaction cb, uint8_t unit) { + switch (reg.type) { + case TAddress::COIL: + return writeCoil(id, reg.address, value, cb, unit); + default: + return 0; + } +} +template +template +uint16_t ModbusAPI::write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + switch (reg.type) { + case TAddress::COIL: + return writeCoil(id, reg.address, value, numregs, cb, unit); + case TAddress::HREG: + return writeHreg(id, reg.address, value, numregs, cb, unit); + default: + return 0; + } +} +template +template +uint16_t ModbusAPI::write(TYPEID id, TAddress reg, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { + switch (reg.type) { + case TAddress::COIL: + return writeCoil(id, reg.address, value, cb, numregs, unit); + default: + return 0; + } +} + +template \ +bool ModbusAPI::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return this->addReg(HREG(offset), value, numregs); +} +template \ +bool ModbusAPI::Hreg(uint16_t offset, uint16_t value) { + return this->Reg(HREG(offset), value); +} +template \ +uint16_t ModbusAPI::Hreg(uint16_t offset) { + return this->Reg(HREG(offset)); +} +template \ +bool ModbusAPI::removeHreg(uint16_t offset, uint16_t numregs) { + return this->removeReg(HREG(offset), numregs); +} +template \ +bool ModbusAPI::addCoil(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(COIL(offset), COIL_VAL(value), numregs); +} +template \ +bool ModbusAPI::addIsts(uint16_t offset, bool value, uint16_t numregs) { + return this->addReg(ISTS(offset), ISTS_VAL(value), numregs); +} +template \ +bool ModbusAPI::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { + return this->addReg(IREG(offset), value, numregs); +} +template \ +bool ModbusAPI::Coil(uint16_t offset, bool value) { + return this->Reg(COIL(offset), COIL_VAL(value)); +} +template \ +bool ModbusAPI::Ists(uint16_t offset, bool value) { + return this->Reg(ISTS(offset), ISTS_VAL(value)); +} +template \ +bool ModbusAPI::Ireg(uint16_t offset, uint16_t value) { + return this->Reg(IREG(offset), value); +} +template \ +bool ModbusAPI::Coil(uint16_t offset) { + return COIL_BOOL(this->Reg(COIL(offset))); +} +template \ +bool ModbusAPI::Ists(uint16_t offset) { + return ISTS_BOOL(this->Reg(ISTS(offset))); +} +template \ +uint16_t ModbusAPI::Ireg(uint16_t offset) { + return this->Reg(IREG(offset)); +} +template \ +bool ModbusAPI::removeCoil(uint16_t offset, uint16_t numregs) { + return this->removeReg(COIL(offset), numregs); +} +template \ +bool ModbusAPI::removeIsts(uint16_t offset, uint16_t numregs) { + return this->removeReg(ISTS(offset), numregs); +} +template \ +bool ModbusAPI::removeIreg(uint16_t offset, uint16_t numregs) { + return this->removeReg(IREG(offset), numregs); +} +template \ +bool ModbusAPI::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onGet(IREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->onSet(IREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(COIL(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(HREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(ISTS(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnGet(IREG(offset), cb, numregs); +} +template \ +bool ModbusAPI::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { + return this->removeOnSet(IREG(offset), cb, numregs); +} +template \ +template \ +uint16_t ModbusAPI::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { + if (startRec > MODBUS_MAX_FILES) return 0; + if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0; + return this->send(slaveId, NULLREG, cb, unit, data); +}; +template \ +template \ +uint16_t ModbusAPI::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) { + if (startRec > MODBUS_MAX_FILES) return 0; + if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0; + return this->send(slaveId, NULLREG, cb, unit); +}; +template \ +template \ +uint16_t ModbusAPI::maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb, uint8_t unit) { + free(this->_frame); + this->_len = 7; + this->_frame = (uint8_t*) malloc(this->_len); + this->_frame[0] = Modbus::FC_MASKWRITE_REG; + this->_frame[1] = offset >> 8; + this->_frame[2] = offset & 0x00FF; + this->_frame[3] = andMask >> 8; + this->_frame[4] = andMask & 0x00FF; + this->_frame[5] = orMask >> 8; + this->_frame[6] = orMask & 0x00FF; + return this->send(slaveId, HREG(offset), cb, unit); +}; + +template \ +template \ +uint16_t ModbusAPI::readWriteHreg(TYPEID ip, \ + uint16_t readOffset, uint16_t* readValue, uint16_t readNumregs, \ + uint16_t writeOffset, uint16_t* writeValue, uint16_t writeNumregs, \ + cbTransaction cb, uint8_t unit) { + const uint8_t _header = 10; + if (readNumregs < 0x0001 || readNumregs > MODBUS_MAX_WORDS || writeNumregs < 0x0001 || writeNumregs > 0X0079 || !readValue || !writeValue) return 0; + + free(this->_frame); + this->_len = _header + 2 * writeNumregs; + this->_frame = (uint8_t*) malloc(this->_len); + if (!this->_frame) { + this->_reply = Modbus::REPLY_OFF; + return 0; + } + this->_frame[0] = Modbus::FC_READWRITE_REGS; + this->_frame[1] = readOffset >> 8; + this->_frame[2] = readOffset & 0x00FF; + this->_frame[3] = readNumregs >> 8; + this->_frame[4] = readNumregs & 0x00FF; + + this->_frame[5] = writeOffset >> 8; + this->_frame[6] = writeOffset & 0x00FF; + this->_frame[7] = writeNumregs >> 8; + this->_frame[8] = writeNumregs & 0x00FF; + this->_frame[9] = this->_len - _header; + + uint16_t* frame = (uint16_t*)(this->_frame + _header); + for (uint8_t i = 0; i < writeNumregs; i++) { + frame[i] = __swap_16(writeValue[i]); + } + return this->send(ip, HREG(readOffset), cb, unit, (uint8_t*)readValue); +}; + +template +template +uint16_t ModbusAPI::rawRequest(TYPEID ip, \ + const uint8_t* data, uint16_t len, + cbTransaction cb, uint8_t unit) { + free(this->_frame); + this->_frame = (uint8_t*)malloc(len); + if (!this->_frame) + return 0; + this->_len = len; + memcpy(this->_frame, data, len); + return this->send(ip, NULLREG, cb, unit); +}; + +template +template +uint16_t ModbusAPI::rawResponce(TYPEID ip, \ + const uint8_t* data, uint16_t len, uint8_t unit) { + free(this->_frame); + this->_frame = (uint8_t*)malloc(len); + if (!this->_frame) + return 0; + this->_len = len; + memcpy(this->_frame, data, len); + return this->send(ip, NULLREG, nullptr, unit, nullptr, false); +}; + +template +template +uint16_t ModbusAPI::errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit) { + this->exceptionResponse(fn, excode); + return this->send(ip, NULLREG, nullptr, unit, nullptr, false); +} diff --git a/src/ModbusEthernet.h b/src/ModbusEthernet.h new file mode 100644 index 0000000..301432f --- /dev/null +++ b/src/ModbusEthernet.h @@ -0,0 +1,59 @@ +/* + Modbus Library for Arduino + ModbusTCP for W5x00 Ethernet + Copyright (C) 2022 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#if defined(MODBUSIP_USE_DNS) +#include +#endif +#include "ModbusAPI.h" +#include "ModbusTCPTemplate.h" +#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_X8) +#define MODBUS_ETH_WRAP_ACCEPT +#undef MODBUS_ETH_WRAP_BEGIN +#elif defined(ESP32) +#undef MODBUS_ETH_WRAP_ACCEPT +#define MODBUS_ETH_WRAP_BEGIN +#else +#undef MODBUS_ETH_WRAP_ACCEPT +#undef MODBUS_ETH_WRAP_BEGIN +#endif +// Ethernet class wrapper to be able to compile for ESP32 +class EthernetServerWrapper : public EthernetServer { + public: + EthernetServerWrapper(uint16_t port) : EthernetServer(port) { + + } +#if defined(MODBUS_ETH_WRAP_BEGIN) + void begin(uint16_t port=0) { + EthernetServer::begin(); + } +#endif +#if defined(MODBUS_ETH_WRAP_ACCEPT) + inline EthernetClient accept() { + return available(); + } +#endif +}; + +class ModbusEthernet : public ModbusAPI> { +#if defined(MODBUSIP_USE_DNS) + private: + static IPAddress resolver (const char* host) { + DNSClient dns; + IPAddress ip; + + dns.begin(Ethernet.dnsServerIP()); + if (dns.getHostByName(host, ip) == 1) + return ip; + else + return IPADDR_NONE; + } + public: + ModbusEthernet() : ModbusAPI() { + resolve = resolver; + } +#endif +}; diff --git a/src/ModbusIP_ESP8266.h b/src/ModbusIP_ESP8266.h new file mode 100644 index 0000000..2c83146 --- /dev/null +++ b/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/src/ModbusRTU.cpp b/src/ModbusRTU.cpp new file mode 100644 index 0000000..a00d609 --- /dev/null +++ b/src/ModbusRTU.cpp @@ -0,0 +1,329 @@ +/* + Modbus Library for Arduino + ModbusRTU implementation + Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ +#include "ModbusRTU.h" + +// Table of CRC values +static const uint16_t _auchCRC[] PROGMEM = { + 0x0000, 0xC1C0, 0x81C1, 0x4001, 0x01C3, 0xC003, 0x8002, 0x41C2, 0x01C6, 0xC006, 0x8007, 0x41C7, 0x0005, 0xC1C5, 0x81C4, + 0x4004, 0x01CC, 0xC00C, 0x800D, 0x41CD, 0x000F, 0xC1CF, 0x81CE, 0x400E, 0x000A, 0xC1CA, 0x81CB, 0x400B, 0x01C9, 0xC009, + 0x8008, 0x41C8, 0x01D8, 0xC018, 0x8019, 0x41D9, 0x001B, 0xC1DB, 0x81DA, 0x401A, 0x001E, 0xC1DE, 0x81DF, 0x401F, 0x01DD, + 0xC01D, 0x801C, 0x41DC, 0x0014, 0xC1D4, 0x81D5, 0x4015, 0x01D7, 0xC017, 0x8016, 0x41D6, 0x01D2, 0xC012, 0x8013, 0x41D3, + 0x0011, 0xC1D1, 0x81D0, 0x4010, 0x01F0, 0xC030, 0x8031, 0x41F1, 0x0033, 0xC1F3, 0x81F2, 0x4032, 0x0036, 0xC1F6, 0x81F7, + 0x4037, 0x01F5, 0xC035, 0x8034, 0x41F4, 0x003C, 0xC1FC, 0x81FD, 0x403D, 0x01FF, 0xC03F, 0x803E, 0x41FE, 0x01FA, 0xC03A, + 0x803B, 0x41FB, 0x0039, 0xC1F9, 0x81F8, 0x4038, 0x0028, 0xC1E8, 0x81E9, 0x4029, 0x01EB, 0xC02B, 0x802A, 0x41EA, 0x01EE, + 0xC02E, 0x802F, 0x41EF, 0x002D, 0xC1ED, 0x81EC, 0x402C, 0x01E4, 0xC024, 0x8025, 0x41E5, 0x0027, 0xC1E7, 0x81E6, 0x4026, + 0x0022, 0xC1E2, 0x81E3, 0x4023, 0x01E1, 0xC021, 0x8020, 0x41E0, 0x01A0, 0xC060, 0x8061, 0x41A1, 0x0063, 0xC1A3, 0x81A2, + 0x4062, 0x0066, 0xC1A6, 0x81A7, 0x4067, 0x01A5, 0xC065, 0x8064, 0x41A4, 0x006C, 0xC1AC, 0x81AD, 0x406D, 0x01AF, 0xC06F, + 0x806E, 0x41AE, 0x01AA, 0xC06A, 0x806B, 0x41AB, 0x0069, 0xC1A9, 0x81A8, 0x4068, 0x0078, 0xC1B8, 0x81B9, 0x4079, 0x01BB, + 0xC07B, 0x807A, 0x41BA, 0x01BE, 0xC07E, 0x807F, 0x41BF, 0x007D, 0xC1BD, 0x81BC, 0x407C, 0x01B4, 0xC074, 0x8075, 0x41B5, + 0x0077, 0xC1B7, 0x81B6, 0x4076, 0x0072, 0xC1B2, 0x81B3, 0x4073, 0x01B1, 0xC071, 0x8070, 0x41B0, 0x0050, 0xC190, 0x8191, + 0x4051, 0x0193, 0xC053, 0x8052, 0x4192, 0x0196, 0xC056, 0x8057, 0x4197, 0x0055, 0xC195, 0x8194, 0x4054, 0x019C, 0xC05C, + 0x805D, 0x419D, 0x005F, 0xC19F, 0x819E, 0x405E, 0x005A, 0xC19A, 0x819B, 0x405B, 0x0199, 0xC059, 0x8058, 0x4198, 0x0188, + 0xC048, 0x8049, 0x4189, 0x004B, 0xC18B, 0x818A, 0x404A, 0x004E, 0xC18E, 0x818F, 0x404F, 0x018D, 0xC04D, 0x804C, 0x418C, + 0x0044, 0xC184, 0x8185, 0x4045, 0x0187, 0xC047, 0x8046, 0x4186, 0x0182, 0xC042, 0x8043, 0x4183, 0x0041, 0xC181, 0x8180, + 0x4040, 0x0000 +}; + +uint16_t ModbusRTUTemplate::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { + uint8_t i = 0xFF ^ address; + uint16_t val = pgm_read_word(_auchCRC + i); + uint8_t CRCHi = 0xFF ^ highByte(val); // Hi + uint8_t CRCLo = lowByte(val); //Low + while (pduLen--) { + i = CRCHi ^ *frame++; + val = pgm_read_word(_auchCRC + i); + CRCHi = CRCLo ^ highByte(val); // Hi + CRCLo = lowByte(val); //Low + } + return (CRCHi << 8) | CRCLo; +} +/* +uint16_t ModbusRTUTemplate::crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen) { + uint16_t temp, temp2, flag; + temp = 0xFFFF ^ address; + for (uint8_t i = 0; i < pduLen; i++) + { + temp = temp ^ frame[i]; + for (uint8_t j = 1; j <= 8; j++) + { + flag = temp & 0x0001; + temp >>= 1; + if (flag) + temp ^= 0xA001; + } + } + // Reverse byte order. + temp2 = temp >> 8; + temp = (temp << 8) | temp2; + temp &= 0xFFFF; + return temp; +} +*/ + +uint32_t ModbusRTUTemplate::charSendTime(uint32_t baud, uint8_t char_bits) { + return (uint32_t)char_bits * 1000000UL / baud; +} + +uint32_t ModbusRTUTemplate::calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits) { + // baud = baudrate of the serial port + // char_bits = size of 1 modbus character (defined a 11 bits in modbus specificacion) + // Returns: The minimum time between frames (defined as 3.5 characters time in modbus specification) + + // According to standard, the Modbus frame is always 11 bits long: + // 1 start + 8 data + 1 parity + 1 stop + // 1 start + 8 data + 2 stops + // And the minimum time between frames is defined as 3.5 characters time in modbus specification. + // This means the time between frames (in microseconds) should be calculated as follows: + // _t = 3.5 x 11 x 1000000 / baudrate = 38500000 / baudrate + + // Eg: For 9600 baudrate _t = 38500000 / 9600 = 4010 us + // For baudrates grater than 19200 the _t should be fixed at 1750 us. + + // If the used modbus frame length is 10 bits (out of standard - 1 start + 8 data + 1 stop), then + // it can be set using char_bits = 10. + + if (baud > 19200) { + return 1750UL; + } else { + return 3.5 * charSendTime(baud, char_bits); + } +} + +// Kept for backward compatibility +void ModbusRTUTemplate::setBaudrate(uint32_t baud) { + setInterFrameTime(calculateMinimumInterFrameTime(baud)); +} + +void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) { + // This function sets the inter frame time. This time is the time that task() waits before considering that the frame being transmitted on the RS485 bus has finished. + // If the interframe calculated by calculateMinimumInterFrameTime() is not enough, you can set the interframe time manually with this function. + // The time must be set in micro seconds. + // This is useful when you are receiving data as a slave and you notice that the slave is dividing a frame in two or more pieces (and obviously the CRC is failing on all pieces). + // This is because it is detecting an interframe time inbetween bytes of the frame and thus it interprets one single frame as two or more frames. + // In that case it is useful to be able to set a more "permissive" interframe time. + _t = t_us; +} + +bool ModbusRTUTemplate::begin(Stream* port, int16_t txEnablePin, bool txEnableDirect) { + _port = port; + _t = 1750UL; +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(0); +#endif + if (txEnablePin >= 0) { + _txEnablePin = txEnablePin; + _direct = txEnableDirect; + pinMode(_txEnablePin, OUTPUT); + digitalWrite(_txEnablePin, _direct?LOW:HIGH); + } + return true; +} + +bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { + uint16_t newCrc = crc16(slaveId, frame, len); +#if defined(MODBUSRTU_DEBUG) + for (uint8_t i=0 ; i < _len ; i++) { + Serial.print(_frame[i], HEX); + Serial.print(" "); + } + Serial.println(); +#endif +#if defined(MODBUSRTU_REDE) + if (_txEnablePin >= 0 || _rxPin >= 0) { + if (_txEnablePin >= 0) + digitalWrite(_txEnablePin, _direct?HIGH:LOW); + if (_rxPin >= 0) + digitalWrite(_rxPin, _direct?HIGH:LOW); +#if !defined(ESP32) + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); +#endif + } +#else + if (_txEnablePin >= 0) { + digitalWrite(_txEnablePin, _direct?HIGH:LOW); +#if !defined(ESP32) + delayMicroseconds(MODBUSRTU_REDE_SWITCH_US); +#endif + } +#endif +#if defined(ESP32) + vTaskDelay(0); +#endif + _port->write(slaveId); //Send slaveId + _port->write(frame, len); // Send PDU + _port->write(newCrc >> 8); //Send CRC + _port->write(newCrc & 0xFF);//Send CRC + _port->flush(); +#if defined(MODBUSRTU_REDE) + if (_txEnablePin >= 0 || _rxPin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif + if (_txEnablePin >= 0) + digitalWrite(_txEnablePin, _direct?LOW:HIGH); + if (_rxPin >= 0) + digitalWrite(_rxPin, _direct?LOW:HIGH); + } +#else + if (_txEnablePin >= 0) { +#if defined(MODBUSRTU_FLUSH_DELAY) + delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY); +#endif + digitalWrite(_txEnablePin, _direct?LOW:HIGH); + } +#endif + return true; +} + +uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { + bool result = false; + if ((!isMaster || !_slaveId) && _len && _frame) { // Check if waiting for previous request result and _frame filled + //if (_len && _frame) { // Check if waiting for previous request result and _frame filled + rawSend(slaveId, _frame, _len); + if (waitResponse && slaveId) { + _slaveId = slaveId; + _timestamp = micros(); + _cb = cb; + _data = data; + _sentFrame = _frame; + _sentReg = startreg; + _frame = nullptr; + } + result = true; + } + free(_frame); + _frame = nullptr; + _len = 0; + return result; +} + +void ModbusRTUTemplate::task() { +#if defined(ESP32) + vTaskDelay(0); +#endif + if (_port->available() > _len) { + _len = _port->available(); + t = micros(); + } + if (_len == 0) { + if (isMaster) cleanup(); + return; + } + if (isMaster) { + if (micros() - t < _t) { + return; + } + } + else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached) + uint32_t taskStart = micros(); + while (micros() - t < _t) { // Wait data whitespace + if (_port->available() > _len) { + _len = _port->available(); + t = micros(); + } + if (micros() - taskStart > MODBUSRTU_MAX_READ_US) { // Prevent from task() executed too long + return; + } + } + } + + bool valid_frame = true; + address = _port->read(); //first byte of frame = address + _len--; // Decrease by slaveId byte + if (isMaster && _slaveId == 0) { // Check if slaveId is set + valid_frame = false; + } + if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check + valid_frame = false; + } + if (!valid_frame && !_cbRaw) { + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach + _len = 0; + if (isMaster) cleanup(); + return; + } + + free(_frame); //Just in case + _frame = (uint8_t*) malloc(_len); + if (!_frame) { // Fail to allocate buffer + for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if can't allocate buffer + _len = 0; + if (isMaster) cleanup(); + return; + } + for (uint8_t i=0 ; i < _len ; i++) { + _frame[i] = _port->read(); // read data + crc + #if defined(MODBUSRTU_DEBUG) + Serial.print(_frame[i], HEX); + Serial.print(" "); + #endif + } + #if defined(MODBUSRTU_DEBUG) + Serial.println(); + #endif + //_port->readBytes(_frame, _len); + uint16_t frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc + _len = _len - 2; // Decrease by CRC 2 bytes + if (frameCrc != crc16(address, _frame, _len)) { // CRC Check + goto cleanup; + } + _reply = EX_PASSTHROUGH; + if (_cbRaw) { + frame_arg_t header_data = { address, !isMaster }; + _reply = _cbRaw(_frame, _len, (void*)&header_data); + } + if (!valid_frame && _reply != EX_FORCE_PROCESS) { + goto cleanup; + } + if (isMaster) { + if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested + // Procass incoming frame as master + if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) + masterPDU(_frame, _sentFrame, _sentReg, _data); + if (_cb) { + _cb((ResultCode)_reply, 0, nullptr); + _cb = nullptr; + } + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + } + _reply = Modbus::REPLY_OFF; // No reply if master + } else { + if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) { + slavePDU(_frame); + if (address == MODBUSRTU_BROADCAST) + _reply = Modbus::REPLY_OFF; // No reply for Broadcasts + if (_reply != Modbus::REPLY_OFF) + rawSend(address, _frame, _len); + } + } + // Cleanup +cleanup: + free(_frame); + _frame = nullptr; + _len = 0; + if (isMaster) cleanup(); +} + +bool ModbusRTUTemplate::cleanup() { + // Remove timeouted request and forced event + if (_slaveId && (micros() - _timestamp > MODBUSRTU_TIMEOUT_US)) { + if (_cb) { + _cb(Modbus::EX_TIMEOUT, 0, nullptr); + _cb = nullptr; + } + free(_sentFrame); + _sentFrame = nullptr; + _data = nullptr; + _slaveId = 0; + return true; + } + return false; +} \ No newline at end of file diff --git a/src/ModbusRTU.h b/src/ModbusRTU.h new file mode 100644 index 0000000..5d27562 --- /dev/null +++ b/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 _txEnablePin = -1; +#if defined(MODBUSRTU_REDE) + int16_t _rxPin = -1; +#endif + bool _direct = true; // Transmit control logic (true=txEnableDirect, false=inverse) + uint32_t _t; // inter-frame delay in uS +#if defined(MODBUSRTU_FLUSH_DELAY) + uint32_t _t1; // char send time +#endif + uint32_t t = 0; // time sience last data byte arrived + bool isMaster = false; + uint8_t _slaveId; + uint32_t _timestamp = 0; + cbTransaction _cb = nullptr; + uint8_t* _data = nullptr; + uint8_t* _sentFrame = nullptr; + TAddress _sentReg = COIL(0); + uint16_t maxRegs = MODBUS_MAX_WORDS; + uint8_t address = 0; + + uint16_t send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + // Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data + // slaveId - slave id + // startreg - first local register to save returned data to (miningless for write to slave operations) + // cb - transaction callback function + // data - if not null use buffer to save returned data instead of local registers + bool rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len); + bool cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events + uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen); + uint16_t crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen); + public: + void setBaudrate(uint32_t baud = -1); + uint32_t calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits = 11); + void setInterFrameTime(uint32_t t_us); + uint32_t charSendTime(uint32_t baud, uint8_t char_bits = 11); + template + bool begin(T* port, int16_t txEnablePin = -1, bool txEnableDirect = true); +#if defined(MODBUSRTU_REDE) + template + bool begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect); +#endif + bool begin(Stream* port, int16_t txEnablePin = -1, bool txEnableDirect = true); + void task(); + void 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 txEnablePin, bool txEnableDirect) { + uint32_t baud = 0; + #if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266 + baud = port->baudRate(); + #else + baud = 9600; + #endif + setInterFrameTime(calculateMinimumInterFrameTime(baud)); +#if defined(MODBUSRTU_FLUSH_DELAY) + _t1 = charSendTime(baud); +#endif + _port = port; + if (txEnablePin >= 0) { + _txEnablePin = txEnablePin; + _direct = txEnableDirect; + pinMode(_txEnablePin, OUTPUT); + digitalWrite(_txEnablePin, _direct?LOW:HIGH); + } + return true; +} +#if defined(MODBUSRTU_REDE) +template +bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect) { + begin(port, txEnablePin, txEnableDirect); + if (rxEnablePin > 0) { + _rxPin = rxEnablePin; + pinMode(_rxPin, OUTPUT); + digitalWrite(_rxPin, _direct?LOW:HIGH); + } + return true; +} +#endif +class ModbusRTU : public ModbusAPI {}; diff --git a/src/ModbusSettings.h b/src/ModbusSettings.h new file mode 100644 index 0000000..0a26644 --- /dev/null +++ b/src/ModbusSettings.h @@ -0,0 +1,148 @@ + +/* + Modbus Library for Arduino + + Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. + +Prefixes: +MODBUS_ Global library settings +MODBUSIP_ Settings for TCP and TLS both +MODBUSTCP_ Settings for TCP +MODBUSTLS_ Settings for TLS +MODBUSRTU_ Settings for RTU +MODBUSAPI_ Settings for API +*/ +#pragma once + +/* +#define MODBUS_GLOBAL_REGS +If defined Modbus registers will be shared across all Modbus* instances. +If not defined each Modbus object will have own registers set. +*/ +#define MODBUS_GLOBAL_REGS +//#define MODBUS_FREE_REGS + +/* +#define ARDUINO_SAM_DUE_STL +Use STL with Arduino Due. Was able to use with Arduino IDE but not with PlatformIO +Also note STL issue workaround code in Modbus.cpp +*/ +#if defined(ARDUINO_SAM_DUE) +//#define ARDUINO_SAM_DUE_STL +#endif + +/* +#define MODBUS_USE_STL +If defined C STL will be used. +*/ +#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_SAM_DUE_STL) +#define MODBUS_USE_STL +#endif + +/* +#define MODBUS_MAX_REGS 32 +If defined regisers count will be limited. +*/ +// Add limitation for specific STL implementation +#if defined(MODBUS_USE_STL) && (defined(ESP8266) || defined(ESP32)) +#undef MODBUS_MAX_REGS +#define MODBUS_MAX_REGS 4000 +#endif + +#define MODBUS_ADD_REG +//#define MODBUS_STRICT_REG +#define MODBUS_MAX_FRAME 256 +//#define MODBUS_STATIC_FRAME +#define MODBUS_MAX_WORDS 0x007D +#define MODBUS_MAX_BITS 0x07D0 +#define MODBUS_FILES +#define MODBUS_MAX_FILES 0x270F +#define MODBUSTCP_PORT 502 +#define MODBUSTLS_PORT 802 +#define MODBUSIP_MINFRAME 2 +#define MODBUSIP_MAXFRAME 200 + +/* +ModbusTCP and ModbusTLS timeouts +#define MODBUSIP_TIMEOUT 1000 +Outgoing request timeout +#define MODBUSIP_CONNECT_TIMEOUT 1000 +ESP32 only. Outgoing connection attempt timeout +*/ +#define MODBUSIP_TIMEOUT 1000 +//#define MODBUSIP_CONNECT_TIMEOUT 1000 + +#define MODBUSIP_UNIT 255 +#define MODBUSIP_MAX_TRANSACTIONS 16 +#if defined(ESP32) +#define MODBUSIP_MAX_CLIENTS 8 +#else +#define MODBUSIP_MAX_CLIENTS 4 +#endif +#define MODBUSIP_UNIQUE_CLIENTS +#define MODBUSIP_MAX_READMS 100 + +/* +Use available() instead of accept() to get TCP client +#define MODBUSIP_USE_AVAILABLE +Used to wrap variation in Ethernet/WiFi client API implementations +*/ +//#define MODBUSIP_USE_AVAILABLE + +#define MODBUSIP_FULL +//#define MODBUSIP_DEBUG +/* +Allows to use DNS names as target +Otherwise IP addresses only must be used +#define MODBUS_IP_USE_DNS +*/ +//#define MODBUS_IP_USE_DNS + +//#define MODBUSRTU_DEBUG +#define MODBUSRTU_BROADCAST 0 +#define MB_RESERVE 248 +#define MB_SERIAL_BUFFER 128 +#ifndef MODBUSRTU_TIMEOUT +#define MODBUSRTU_TIMEOUT 1000 +#endif +#define MODBUSRTU_MAX_READMS 100 +/* +#define MODBUSRTU_REDE +Enable using separate pins for RE DE +*/ +//#define MODBUSRTU_REDE + +// Define for internal use. Do not change. +#define MODBUSRTU_TIMEOUT_US 1000UL * MODBUSRTU_TIMEOUT +#define MODBUSRTU_MAX_READ_US 1000UL * MODBUSRTU_MAX_READMS + +/* +#defone MODBUSRTU_FLUSH_DELAY 1 +Set extraa delay after serial buffer flush before changing RE/DE pin state. +Specified in chars. That is 1 is means to add delay enough to send 1 char at current port baudrate +*/ +//#define MODBUSRTU_FLUSH_DELAY 1 + +#define MODBUSRTU_REDE_SWITCH_US 1000 + +#define MODBUSAPI_LEGACY +#define MODBUSAPI_OPTIONAL + +// Workaround for RP2040 flush() bug +#if defined(ARDUINO_ARCH_RP2040) +#define MODBUSRTU_FLUSH_DELAY 1 +#endif + +// Limit resources usage for entry level boards +#if defined(ARDUINO_UNO) || defined(ARDUINO_LEONARDO) +#undef MODBUS_MAX_REGS +#undef MODBUSIP_MAX_TRANSACTIONS +#undef MODBUS_MAX_WORDS +#undef MODBUS_MAX_BITS +#define MODBUS_MAX_REGS 32 +#define MODBUSIP_MAX_TRANSACTIONS 4 +#define MODBUS_MAX_WORDS 0x0020 +#define MODBUS_MAX_BITS 0x0200 +#endif \ No newline at end of file diff --git a/src/ModbusTCP.h b/src/ModbusTCP.h new file mode 100644 index 0000000..fed76c3 --- /dev/null +++ b/src/ModbusTCP.h @@ -0,0 +1,40 @@ +/* + Modbus Library for Arduino + ModbusTCP for ESP8266/ESP32 + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#endif + +#include "ModbusAPI.h" +#include "ModbusTCPTemplate.h" + +class WiFiServerESPWrapper : public WiFiServer { + public: + WiFiServerESPWrapper(uint16_t port) : WiFiServer(port) {} + inline WiFiClient accept() { + return available(); + } +}; + +class ModbusTCP : public ModbusAPI> { +#if defined(MODBUSIP_USE_DNS) + private: + static IPAddress resolver(const char *host) { + IPAddress remote_addr; + if (WiFi.hostByName(host, remote_addr)) + return remote_addr; + return IPADDR_NONE; + } + + public: + ModbusTCP() : ModbusAPI() { + resolve = resolver; + } +#endif +}; diff --git a/src/ModbusTCPTemplate.h b/src/ModbusTCPTemplate.h new file mode 100644 index 0000000..c78a43d --- /dev/null +++ b/src/ModbusTCPTemplate.h @@ -0,0 +1,606 @@ +/* + Modbus Library for Arduino + ModbusTCP general implementation + Copyright (C) 2014 Andr� Sarmento Barbosa + 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ + +#pragma once +#include "Modbus.h" + +#define BIT_SET(a,b) ((a) |= (1ULL<<(b))) +#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b))) +#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b)))) // '!!' to make sure this returns 0 or 1 +#ifndef IPADDR_NONE +#define IPADDR_NONE ((uint32_t)0xffffffffUL) +#endif +// Callback function Type +#if defined(MODBUS_USE_STL) +typedef std::function cbModbusConnect; +typedef std::function cbModbusResolver; +#else +typedef bool (*cbModbusConnect)(IPAddress ip); +typedef IPAddress (*cbModbusResolver)(const char*); +#endif + +struct TTransaction { + uint16_t transactionId; + uint32_t timestamp; + cbTransaction cb = nullptr; + uint8_t* _frame = nullptr; + uint8_t* data = nullptr; + TAddress startreg; + Modbus::ResultCode forcedEvent = Modbus::EX_SUCCESS; // EX_SUCCESS means no forced event here. Forced EX_SUCCESS is not possible. + bool operator ==(const TTransaction &obj) const { + return transactionId == obj.transactionId; + } +}; + +template +class ModbusTCPTemplate : public Modbus { + protected: + union MBAP_t { + struct { + uint16_t transactionId; + uint16_t protocolId; + uint16_t length; + uint8_t unitId; + }; + uint8_t raw[7]; + }; + cbModbusConnect cbConnect = nullptr; + cbModbusConnect cbDisconnect = nullptr; + SERVER* tcpserver = nullptr; + CLIENT* tcpclient[MODBUSIP_MAX_CLIENTS]; + #if MODBUSIP_MAX_CLIENTS <= 8 + uint8_t tcpServerConnection = 0; + #elif MODBUSIP_MAX_CLIENTS <= 16 + uint16_t tcpServerConnection = 0; + #else + uint32_t tcpServerConnection = 0; + #endif + #if defined(MODBUS_USE_STL) + std::vector _trans; + #else + DArray _trans; + #endif + int16_t transactionId = 1; // Last started transaction. Increments on unsuccessful transaction start too. + int8_t n = -1; + bool autoConnectMode = false; + uint16_t serverPort = 0; + uint16_t defaultPort = MODBUSTCP_PORT; + cbModbusResolver resolve = nullptr; + TTransaction* searchTransaction(uint16_t id); + void cleanupConnections(); // Free clients if not connected + void cleanupTransactions(); // Remove timedout transactions and forced event + + int8_t getFreeClient(); // Returns free slot position + int8_t getSlave(IPAddress ip); + int8_t getMaster(IPAddress ip); + public: + uint16_t send(String host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + uint16_t send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true); + // Prepare and send ModbusIP frame. _frame buffer and _len should be filled with Modbus data + // ip - slave ip address + // startreg - first local register to save returned data to (miningless for write to slave operations) + // cb - transaction callback function + // unit - slave modbus unit id + // data - if not null use buffer to save returned data instead of local registers + public: + ModbusTCPTemplate(); + ~ModbusTCPTemplate(); + bool isTransaction(uint16_t id); +#if defined(MODBUSIP_USE_DNS) + bool isConnected(String host); + bool isConnected(const char* host); + bool connect(String host, uint16_t port = 0); + bool connect(const char* host, uint16_t port = 0); + bool disconnect(String host); + bool disconnect(const char* host); +#endif + bool isConnected(IPAddress ip); + bool connect(IPAddress ip, uint16_t port = 0); + bool disconnect(IPAddress ip); + // ModbusTCP + void server(uint16_t port = 0); + // ModbusTCP depricated + inline void slave(uint16_t port = 0) { server(port); } // Depricated + inline void master() { client(); } // Depricated + inline void begin() { server(); }; // Depricated + void client(); + void task(); + void onConnect(cbModbusConnect cb = nullptr); + void onDisconnect(cbModbusConnect cb = nullptr); + uint32_t eventSource() override; + void autoConnect(bool enabled = true); + void dropTransactions(); + uint16_t setTransactionId(uint16_t); + #if defined(MODBUS_USE_STL) + static IPAddress defaultResolver(const char*) {return IPADDR_NONE;} + #else + static IPAddress defaultResolver(const char*) {return IPADDR_NONE;} + #endif +}; + +template +ModbusTCPTemplate::ModbusTCPTemplate() { + //_trans.reserve(MODBUSIP_MAX_TRANSACIONS); + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + tcpclient[i] = nullptr; + resolve = defaultResolver; +} + +template +void ModbusTCPTemplate::client() { + +} + +template +void ModbusTCPTemplate::server(uint16_t port) { + if (port) + serverPort = port; + else + serverPort = defaultPort; + tcpserver = new SERVER(serverPort); + tcpserver->begin(); +} + +#if defined(MODBUSIP_USE_DNS) +template +bool ModbusTCPTemplate::connect(String host, uint16_t port) { + return connect(resolve(host.c_str()), port); +} + +template +bool ModbusTCPTemplate::connect(const char* host, uint16_t port) { + return connect(resolve(host), port); +} +#endif + +template +bool ModbusTCPTemplate::connect(IPAddress ip, uint16_t port) { + //cleanupConnections(); + if (!ip) + return false; + if(getSlave(ip) != -1) + return true; + int8_t p = getFreeClient(); + if (p == -1) + return false; + tcpclient[p] = new CLIENT(); + BIT_CLEAR(tcpServerConnection, p); +#if defined(ESP32) && defined(MODBUSIP_CONNECT_TIMEOUT) + if (!tcpclient[p]->connect(ip, port?port:defaultPort, MODBUSIP_CONNECT_TIMEOUT)) { +#else + if (!tcpclient[p]->connect(ip, port?port:defaultPort)) { +#endif + delete(tcpclient[p]); + tcpclient[p] = nullptr; + return false; + } + return true; +} + +template +uint32_t ModbusTCPTemplate::eventSource() { // Returns IP of current processing client query + if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && tcpclient[n]) + #if !defined(ethernet_h) + return (uint32_t)tcpclient[n]->remoteIP(); + #else + return 1; + #endif + return (uint32_t)INADDR_NONE; +} + +template +TTransaction* ModbusTCPTemplate::searchTransaction(uint16_t id) { +#define MODBUSIP_COMPARE_TRANS [id](TTransaction& trans){return trans.transactionId == id;} + #if defined(MODBUS_USE_STL) + std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), MODBUSIP_COMPARE_TRANS); + if (it != _trans.end()) return &*it; + return nullptr; + #else + return _trans.entry(_trans.find(MODBUSIP_COMPARE_TRANS)); + #endif +} + +template +void ModbusTCPTemplate::task() { + MBAP_t _MBAP; + uint32_t taskStart = millis(); + cleanupConnections(); + if (tcpserver) { + CLIENT c; + // WiFiServer.available() == Ethernet.accept() and should wrapped to get code to be compatible with Ethernet library (See ModbusTCP.h code). + // WiFiServer.available() != Ethernet.available() internally +#if defined(MODBUSIP_USE_AVAILABLE) + while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->available())) { +#else + while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) { +#endif +#if defined(MODBUSIP_DEBUG) + Serial.println("IP: Accepted"); +#endif + CLIENT* currentClient = new CLIENT(c); + if (!currentClient || !currentClient->connected()) { + delete currentClient; + continue; + } +#if defined(MODBUSIP_DEBUG) + Serial.println("IP: Connected"); +#endif + if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { + #if defined(MODBUSIP_UNIQUE_CLIENTS) + // Disconnect previous connection from same IP if present + n = getMaster(currentClient->remoteIP()); + if (n != -1) { + tcpclient[n]->flush(); + delete tcpclient[n]; + tcpclient[n] = nullptr; + } + #endif + n = getFreeClient(); + if (n > -1) { + tcpclient[n] = currentClient; + BIT_SET(tcpServerConnection, n); +#if defined(MODBUSIP_DEBUG) + Serial.print("IP: Conn "); + Serial.println(n); +#endif +#if defined(MODBUSIP_USE_AVAILABLE) + break; // while +#else + continue; // while +#endif + } + } + // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached + delete currentClient; + } + } + for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { + if (!tcpclient[n]) continue; + if (!tcpclient[n]->connected()) continue; + while ((size_t)tcpclient[n]->available() > sizeof(_MBAP) && millis() - taskStart < MODBUSIP_MAX_READMS) { +#if defined(MODBUSIP_DEBUG) + Serial.print(n); + Serial.print(": Bytes available "); + Serial.println(tcpclient[n]->available()); +#endif + tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP + + if (__swap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __swap is usless there. + while (tcpclient[n]->available()) // Drop all incoming if wrong packet + tcpclient[n]->read(); + continue; + } + _len = __swap_16(_MBAP.length); + if (_len < MODBUSIP_MINFRAME) { // Length is shorter than MODBUSIP_MINFRAME + Modbus::FunctionCode fc = FC_READ_COILS; // Just placeholder + while (tcpclient[n]->available()) // Drop rest of the packet + tcpclient[n]->read(); + exceptionResponse(fc, EX_ILLEGAL_VALUE); + } + _len--; // Do not count with last byte from MBAP + if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME + Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read(); + _len--; // Subtract for read byte + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet + tcpclient[n]->read(); + exceptionResponse(fc, EX_SLAVE_FAILURE); + } + else { + free(_frame); + _frame = (uint8_t*) malloc(_len); + if (!_frame) { + Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read(); + _len--; // Subtract for read byte + for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet + tcpclient[n]->read(); + exceptionResponse(fc, EX_SLAVE_FAILURE); + } + else { + if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame + exceptionResponse((Modbus::FunctionCode)_frame[0], EX_ILLEGAL_VALUE); + //while (tcpclient[n]->available()) // Drop all incoming (if any) + // tcpclient[n]->read(); + } + else { + _reply = EX_PASSTHROUGH; + // Note on _reply usage + // it's used and set as ReplyCode by slavePDU and as exceptionCode by masterPDU + if (_cbRaw) { + frame_arg_t transData = { _MBAP.unitId, tcpclient[n]->remoteIP(), __swap_16(_MBAP.transactionId), BIT_CHECK(tcpServerConnection, n) }; + _reply = _cbRaw(_frame, _len, &transData); + } + if (BIT_CHECK(tcpServerConnection, n)) { + if (_reply == EX_PASSTHROUGH) + slavePDU(_frame); // Process incoming frame as slave + else + _reply = REPLY_OFF; + } + else { + // Process reply to master request + TTransaction* trans = searchTransaction(__swap_16(_MBAP.transactionId)); + if (trans) { // if valid transaction id + if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested + if (_reply == EX_PASSTHROUGH) + masterPDU(_frame, trans->_frame, trans->startreg, trans->data); // Process incoming frame as master + } + else { + _reply = EX_UNEXPECTED_RESPONSE; + } + if (trans->cb) { + trans->cb((ResultCode)_reply, trans->transactionId, nullptr); + } + free(trans->_frame); + #if defined(MODBUS_USE_STL) + //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); + std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); + if (it != _trans.end()) + _trans.erase(it); + #else + size_t r = _trans.find([trans](TTransaction& t){return *trans == t;}); + _trans.remove(r); + #endif + } + } + } + } + } + if (!BIT_CHECK(tcpServerConnection, n)) _reply = REPLY_OFF; // No replay if it was responce to master + if (_reply != REPLY_OFF) { + _MBAP.length = __swap_16(_len+1); // _len+1 for last byte from MBAP + size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); + uint8_t sbuf[send_len]; + memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); + memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); + tcpclient[n]->write(sbuf, send_len); + //tcpclient[n]->flush(); + } + if (_frame) { + free(_frame); + _frame = nullptr; + } + _len = 0; + } + } + n = -1; + cleanupTransactions(); +} + +template +uint16_t ModbusTCPTemplate::send(String host, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { + return send(resolve(host.c_str()), startreg, cb, unit, data, waitResponse); +} + +template +uint16_t ModbusTCPTemplate::send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { + return send(resolve(host), startreg, cb, unit, data, waitResponse); +} + +template +uint16_t ModbusTCPTemplate::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) { + MBAP_t _MBAP; + uint16_t result = 0; + int8_t p; +#if defined(MODBUSIP_MAX_TRANSACTIONS) + if (_trans.size() >= MODBUSIP_MAX_TRANSACTIONS) + goto cleanup; +#endif + if (!ip) + return 0; + if (tcpserver) { + p = getMaster(ip); + } else { + p = getSlave(ip); + } + if (p == -1 || !tcpclient[p]->connected()) { + if (!autoConnectMode) + goto cleanup; + if (!connect(ip)) + goto cleanup; + } + _MBAP.transactionId = __swap_16(transactionId); + _MBAP.protocolId = __swap_16(0); + _MBAP.length = __swap_16(_len+1); //_len+1 for last byte from MBAP + _MBAP.unitId = unit; + bool writeResult; + { // for sbuf isolation + size_t send_len = _len + sizeof(_MBAP.raw); + uint8_t sbuf[send_len]; + memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); + memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); + writeResult = (tcpclient[p]->write(sbuf, send_len) == send_len); + } + if (!writeResult) + goto cleanup; + //tcpclient[p]->flush(); + if (waitResponse) { + TTransaction tmp; + tmp.transactionId = transactionId; + tmp.timestamp = millis(); + tmp.cb = cb; + tmp.data = data; // BUG: Should data be saved? It may lead to memory leak or double free. + tmp._frame = _frame; + tmp.startreg = startreg; + _trans.push_back(tmp); + _frame = nullptr; + } + result = transactionId; + transactionId++; + if (!transactionId) + transactionId = 1; + cleanup: + free(_frame); + _frame = nullptr; + _len = 0; + return result; +} + +template +void ModbusTCPTemplate::onConnect(cbModbusConnect cb) { + cbConnect = cb; +} + +template +void ModbusTCPTemplate::onDisconnect(cbModbusConnect cb) { + cbDisconnect = cb; +} + +template +void ModbusTCPTemplate::cleanupConnections() { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + if (tcpclient[i] && !tcpclient[i]->connected()) { + //IPAddress ip = tcpclient[i]->remoteIP(); + tcpclient[i]->stop(); + delete tcpclient[i]; + tcpclient[i] = nullptr; + if (cbDisconnect && cbEnabled) + cbDisconnect(IPADDR_NONE); + } + } +} + +template +void ModbusTCPTemplate::cleanupTransactions() { + #if defined(MODBUS_USE_STL) + for (auto it = _trans.begin(); it != _trans.end();) { + if (millis() - it->timestamp > MODBUSIP_TIMEOUT || it->forcedEvent != Modbus::EX_SUCCESS) { + Modbus::ResultCode res = (it->forcedEvent != Modbus::EX_SUCCESS)?it->forcedEvent:Modbus::EX_TIMEOUT; + if (it->cb) + it->cb(res, it->transactionId, nullptr); + free(it->_frame); + it = _trans.erase(it); + } else + it++; + } + #else + size_t i = 0; + while (i < _trans.size()) { + TTransaction t = _trans[i]; + if (millis() - t.timestamp > MODBUSIP_TIMEOUT || t.forcedEvent != Modbus::EX_SUCCESS) { + Modbus::ResultCode res = (t.forcedEvent != Modbus::EX_SUCCESS)?t.forcedEvent:Modbus::EX_TIMEOUT; + if (t.cb) + t.cb(res, t.transactionId, nullptr); + free(t._frame); + _trans.remove(i); + } else + i++; + } + #endif +} + +template +int8_t ModbusTCPTemplate::getFreeClient() { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (!tcpclient[i]) + return i; + return -1; +} + +template +int8_t ModbusTCPTemplate::getSlave(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && !BIT_CHECK(tcpServerConnection, i)) + return i; + return -1; +} + +template +int8_t ModbusTCPTemplate::getMaster(IPAddress ip) { + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) + if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && BIT_CHECK(tcpServerConnection, i)) + return i; + return -1; +} + +template +bool ModbusTCPTemplate::isTransaction(uint16_t id) { + return searchTransaction(id) != nullptr; +} +#if defined(MODBUSIP_USE_DNS) +template +bool ModbusTCPTemplate::isConnected(String host) { + return isConnected(resolve(host.c_str())); +} + +template +bool ModbusTCPTemplate::isConnected(const char* host) { + return isConnected(resolve(host)); +} +#endif + +template +bool ModbusTCPTemplate::isConnected(IPAddress ip) { + if (!ip) + return false; + int8_t p = getSlave(ip); + return p != -1 && tcpclient[p]->connected(); +} + +template +void ModbusTCPTemplate::autoConnect(bool enabled) { + autoConnectMode = enabled; +} + +#if defined(MODBUSIP_USE_DNS) +template +bool ModbusTCPTemplate::disconnect(String host) { + return disconnect(resolve(host.c_str())); +} + +template +bool ModbusTCPTemplate::disconnect(const char* host) { + return disconnect(resolve(host)); +} +#endif + +template +bool ModbusTCPTemplate::disconnect(IPAddress ip) { + if (!ip) + return false; + int8_t p = getSlave(ip); + if (p != -1) { + tcpclient[p]->stop(); + delete tcpclient[p]; + tcpclient[p] = nullptr; + return true; + } + return false; +} + +template +void ModbusTCPTemplate::dropTransactions() { + #if defined(MODBUS_USE_STL) + for (auto &t : _trans) t.forcedEvent = EX_CANCEL; + #else + for (size_t i = 0; i < _trans.size(); i++) + _trans.entry(i)->forcedEvent = EX_CANCEL; + #endif +} + +template +ModbusTCPTemplate::~ModbusTCPTemplate() { + free(_frame); + _frame = nullptr; + dropTransactions(); + cleanupConnections(); + cleanupTransactions(); + delete tcpserver; + tcpserver = nullptr; + for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { + delete tcpclient[i]; + tcpclient[i] = nullptr; + } +} + +template +uint16_t ModbusTCPTemplate::setTransactionId(uint16_t t) { + transactionId = t; + if (!transactionId) + transactionId = 1; + return transactionId; +} + diff --git a/src/ModbusTLS.h b/src/ModbusTLS.h new file mode 100644 index 0000000..7f0fdf8 --- /dev/null +++ b/src/ModbusTLS.h @@ -0,0 +1,120 @@ +/* + Modbus Library for Arduino + ModbusTLS - ModbusTCP Security for ESP8266 + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) +*/ +#pragma once +#if !defined(ESP8266) && !defined(ESP32) +#error Unsupported architecture +#endif +#include +#if defined(ESP8266) +#include +#else +// Just emty stub +class WiFiServerSecure { +public: + WiFiServerSecure(uint16_t){} + WiFiClientSecure available(){} + void begin(); + inline WiFiClientSecure accept() { + return available(); + } +}; +#endif +#include "ModbusTCPTemplate.h" +#include "ModbusAPI.h" + +class ModbusTLS : public ModbusAPI> { + private: + int8_t _connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr) { + int8_t p = getFreeClient(); + if (p < 0) + return p; + tcpclient[p] = new WiFiClientSecure(); + BIT_CLEAR(tcpServerConnection, p); + #if defined(ESP8266) + BearSSL::X509List *clientCertList = new BearSSL::X509List(client_cert); + BearSSL::PrivateKey *clientPrivKey = new BearSSL::PrivateKey(client_private_key); + tcpclient[p]->setClientRSACert(clientCertList, clientPrivKey); + tcpclient[p]->setBufferSizes(512, 512); + #else + tcpclient[p]->setCertificate(client_cert); + tcpclient[p]->setPrivateKey(client_private_key); + #endif + return p; + } +#if defined(MODBUSIP_USE_DNS) + static IPAddress resolver (const char* host) { + IPAddress remote_addr; + if (WiFi.hostByName(host, remote_addr)) + return remote_addr; + return IPADDR_NONE; + } +#endif + public: + ModbusTLS() : ModbusAPI() { + defaultPort = MODBUSTLS_PORT; +#if defined(MODBUSIP_USE_DNS) + resolve = resolver; +#endif + } + #if defined(ESP8266) + void server(uint16_t port, const char* server_cert = nullptr, const char* server_private_key = nullptr, const char* ca_cert = nullptr) { + serverPort = port; + tcpserver = new WiFiServerSecure(serverPort); + BearSSL::X509List *serverCertList = new BearSSL::X509List(server_cert); + BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(server_private_key); + tcpserver->setRSACert(serverCertList, serverPrivKey); + if (ca_cert) { + BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert); + tcpserver->setClientTrustAnchor(trustedCA); + } + //tcpserver->setBufferSizes(512, 512); + tcpserver->begin(); + } + + bool connectWithKnownKey(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* key = nullptr) { + if(getSlave(ip) >= 0) + return true; + int8_t p = _connect(ip, port, client_cert, client_private_key); + BearSSL::PublicKey *clientPublicKey = new BearSSL::PublicKey(key); + tcpclient[p]->setKnownKey(clientPublicKey); + return tcpclient[p]->connect(ip, port); + } + + #endif +#if defined(MODBUSIP_USE_DNS) + bool connect(String host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) { + return connect(resolver(host.c_str()), port, client_cert, client_private_key, ca_cert); + } + bool connect(const char* host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) { + return connect(resolver(host), port, client_cert, client_private_key, ca_cert); + } +#endif + bool connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) { + if (!ip) + return false; + if(getSlave(ip) >= 0) + return false; + int8_t p = _connect(ip, port, client_cert, client_private_key); + if (p < 0) + return false; + #if defined(ESP8266) + if (ca_cert) { + BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert); + tcpclient[p]->setTrustAnchors(trustedCA); + } else { + tcpclient[p]->setInsecure(); + } + #else + if (ca_cert) { + tcpclient[p]->setCACert(ca_cert); + } + #endif + //return tcpclient[p]->connect(ip, port); + if (!tcpclient[p]->connect(ip, port)) + return false; + return true; + } +}; diff --git a/src/darray.h b/src/darray.h new file mode 100644 index 0000000..0b204fb --- /dev/null +++ b/src/darray.h @@ -0,0 +1,70 @@ + +/* + Very Basic Dynamic Array + + Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ +template +class DArray { + public: + typedef bool (*Compare)(T); + T* data = nullptr; + size_t resSize = 0; + size_t last = 0; + bool isEmpty = true; + DArray(size_t i = SIZE) { + data = (T*)malloc(i * sizeof(T)); + if (data) resSize = i; + } + size_t push_back(const T& v) { + if (!data) { + data = (T*)malloc(resSize * sizeof(T)); + if (!data) return 1; + } + if (last >= resSize - 1) { + if (INCREMENT == 0) return last + 1; + void* tmp = realloc(data, (resSize + INCREMENT) * sizeof(T)); + if (!tmp) return last + 1; + resSize += INCREMENT; + data = (T*)tmp; + } + if (!isEmpty) + last++; + else + isEmpty = false; + data[last] = v; + return last; + } + size_t size() { + if (isEmpty) return 0; + return last + 1; + } + template + size_t find(UnaryPredicate func, size_t i = 0) { + if (isEmpty) return 1; + for (; i <= last; i++) + if (func(data[i])) break; + return i; + } + + void remove(size_t i) { + if (isEmpty) return; + if (i > last) return; + if (last == 0) { + isEmpty = true; + return; + } + if (i < last) + memcpy(&data[i], &data[i + 1], (last - i) * sizeof(T)); + last --; + } + T operator[](int i) { + return data[i]; + } + T* entry(size_t i) { + if (i > last) return nullptr; + return &data[i]; + } +}; \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..239ac1e --- /dev/null +++ b/tests/README.md @@ -0,0 +1,6 @@ +# Modbus RTU tests + +There are not autotests. Just sketch executing Master and Slave on single ESP device and run Modbus calls with checking results. + +## Required libraries +[StreamBuf](https://github.com/emelianov/StreamBuf) \ No newline at end of file diff --git a/tests/common.h b/tests/common.h new file mode 100644 index 0000000..efd98e4 --- /dev/null +++ b/tests/common.h @@ -0,0 +1,52 @@ +/* + Modbus Library for ESP8266/ESP32 + Functional tests + Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#pragma once +#include +//#define HW_SERIAL + +#define BSIZE 1024 + +#if defined(HW_SERIAL) +#define P1 Serial1 +#define P2 Serial2 +#else +uint8_t buf1[BSIZE]; +uint8_t buf2[BSIZE]; + +StreamBuf S1(buf1, BSIZE); +StreamBuf S2(buf2, BSIZE); +DuplexBuf P1(&S1, &S2); +DuplexBuf P2(&S2, &S1); +#endif + +ModbusRTU master; +ModbusRTU slave; + +bool result; +uint8_t code ; + +bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { + //Serial.printf_P(" 0x%02X ", event); + //if (event == 0x00) { + code = event; + result = true; + return true; +} + +uint8_t wait() { + result = false; + code = 0; + while (!result) { + master.task(); + slave.task(); + yield(); + } + Serial.printf_P(" 0x%02X", code); + return code; +} \ No newline at end of file diff --git a/tests/files.h b/tests/files.h new file mode 100644 index 0000000..25ec080 --- /dev/null +++ b/tests/files.h @@ -0,0 +1,62 @@ +#pragma once +#include "common.h" + +#define FILE_LEN 100 +uint8_t block[FILE_LEN*2]; +uint8_t src[FILE_LEN*2]; + +Modbus::ResultCode handleFile(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame) { + switch (func) { + case Modbus::FC_READ_FILE_REC: + memcpy(frame, src, recLength * 2); + return Modbus::EX_SUCCESS; + break; + case Modbus::FC_WRITE_FILE_REC: + memcpy(src, frame, recLength * 2); + return Modbus::EX_SUCCESS; + break; + default: + return Modbus::EX_ILLEGAL_FUNCTION; + } +} + +void initFile() { + master.onFile(handleFile); + slave.onFile(handleFile); +} + +void testFile() { + Serial.print("FILE READ:"); + if (master.readFileRec(1, 0, 0, FILE_LEN, block, cbWrite)) { + Serial.print(" SENT"); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + Serial.printf(" 0x%02X ", code); + if (memcmp(block, src, FILE_LEN * 2) == 0) { + Serial.println("PASSED"); + } else { + Serial.println("FAILED"); + } + } + + memset(block, 0xFF, FILE_LEN * 2); + + Serial.print("FILE WRITE:"); + if (master.writeFileRec(1, 0, 0, FILE_LEN, block, cbWrite)) { + Serial.print(" SENT"); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + Serial.printf(" 0x%02X ", code); + if (memcmp(block, src, FILE_LEN * 2) == 0) { + Serial.println("PASSED"); + } else { + Serial.println("FAILED"); + } + } +} \ No newline at end of file diff --git a/tests/read.h b/tests/read.h new file mode 100644 index 0000000..21b8495 --- /dev/null +++ b/tests/read.h @@ -0,0 +1,134 @@ +#pragma once +#include "common.h" + +// Single Hreg write +// Multiple read +void readMultiple(uint8_t sl, TAddress reg, uint16_t count = 1, void* value = nullptr) { + Serial.print("Read Multiple "); + bool mem = false; + if (!value) { + if (reg.isHreg() || reg.isIreg()) { + value = malloc(count * sizeof(uint16_t)); + if (!value) { + Serial.println(" FAILED"); + return; + } + for (uint8_t i = 0; i < count; i++) { + ((uint16_t*)value)[i] = i; + } + } else { + value = malloc(count * sizeof(bool)); + if (!value) { + Serial.println(" FAILED"); + return; + } + for (uint8_t i = 0; i < count; i++) { + ((bool*)value)[i] = i % 2; + } + } + mem = true; + } + bool addRes = true; + switch (reg.type) { + case TAddress::HREG: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addHreg(reg.address + i, ((uint16_t*)value)[i]); + } + Serial.print("HREG: "); + break; + case TAddress::IREG: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addIreg(reg.address + i, ((uint16_t*)value)[i]); + //Serial.print(slave.Ireg(reg.address + i)); Serial.print(" "); + } + Serial.print("IREG: "); + break; + case TAddress::COIL: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addCoil(reg.address + i, ((bool*)value)[i]); + } + Serial.print("COIL: "); + break; + case TAddress::ISTS: + for (uint8_t i = 0; i < count; i++) { + addRes = addRes && slave.addIsts(reg.address + i, ((bool*)value)[i]); + } + Serial.print("ISTS: "); + break; + default: + addRes = false; + Serial.println("UNKNOWN"); + return; + } + if (!addRes) { + Serial.println(" SLAVE FAILED"); + return; + } + if (reg.isHreg() || reg.isIreg()) { + for (uint8_t i = 0; i < count; i++) { + ((uint16_t*)value)[i] = 0; + } + } else { + for (uint8_t i = 0; i < count; i++) { + ((bool*)value)[i] = false; + } + } + if (!master.slave()) { + bool res = false; + switch (reg.type) { + case TAddress::HREG: + res = master.readHreg(sl, reg.address, (uint16_t*)value, count, cbWrite); + break; + case TAddress::IREG: + res = master.readIreg(sl, reg.address, (uint16_t*)value, count, cbWrite); + break; + case TAddress::COIL: + res = master.readCoil(sl, reg.address, (bool*)value, count, cbWrite); + break; + case TAddress::ISTS: + res = master.readIsts(sl, reg.address, (bool*)value, count, cbWrite); + break; + } + if (res) { + Serial.print(" SENT "); + if (wait() == Modbus::EX_SUCCESS) { + bool res = true; + switch (reg.type) { + case TAddress::HREG: + for (uint8_t i = 0; i < count; i++) { + if (slave.Hreg(reg.address + i) != ((uint16_t*)value)[i]) res = false; + } + break; + case TAddress::IREG: + for (uint8_t i = 0; i < count; i++) { + if (slave.Ireg(reg.address + i) != ((uint16_t*)value)[i]) res = false; + } + break; + case TAddress::COIL: + for (uint8_t i = 0; i < count; i++) { + if (slave.Coil(reg.address + i) != ((bool*)value)[i]) res = false; + } + break; + case TAddress::ISTS: + for (uint8_t i = 0; i < count; i++) { + if (slave.Ists(reg.address + i) != ((bool*)value)[i]) res = false; + } + break; + } + if (res) { + Serial.println(" PASSED"); + } else { + Serial.print(" INCORRECT"); + } + } else { + Serial.println(); + } + } else { + Serial.println(" FAILED"); + } + } else { + Serial.println(" BUSY"); + } + if (mem) + free(value); +} \ No newline at end of file diff --git a/tests/tests.ino b/tests/tests.ino new file mode 100644 index 0000000..5ab2673 --- /dev/null +++ b/tests/tests.ino @@ -0,0 +1,140 @@ +/* + Modbus Library for ESP8266/ESP32 + Functional tests + Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) + https://github.com/emelianov/modbus-esp8266 + This code is licensed under the BSD New License. See LICENSE.txt for more info. +*/ + +#include +#include +#include "common.h" +#include "write.h" +#include "read.h" +#include "files.h" + + +uint8_t stage = 0; +uint16_t readHreg = 0; + +#define SLAVE_ID 1 +#define HREG_ID 10 +#define HREG_VALUE 100 + +#define HREGS_ID 20 +#define HREGS_COUNT 20 + +void setup() { + Serial.begin(115200); + Serial.println("ModbusRTU API test"); +#if defined(HW_SERIAL) + Serial1.begin(115200, SERIAL_8N1, 18, 19); + Serial2.begin(115200, SERIAL_8N1, 22, 23); +#endif + delay(100); + master.begin((Stream*)&P1); + master.master(); + slave.begin((Stream*)&P2); + slave.slave(SLAVE_ID); + slave.addHreg(HREG_ID); + +writeSingle(SLAVE_ID, HREG(HREG_ID), HREG_VALUE); +writeSingle(SLAVE_ID, COIL(HREG_ID), true); + +writeMultiple(SLAVE_ID, HREG(HREG_ID), 10); +writeMultiple(SLAVE_ID, COIL(HREG_ID), 10); + +readMultiple(SLAVE_ID, HREG(HREG_ID), 10); +readMultiple(SLAVE_ID, COIL(HREG_ID), 10); +readMultiple(SLAVE_ID, IREG(HREG_ID), 10); +readMultiple(SLAVE_ID, ISTS(HREG_ID), 10); + +// Read-Write Hreg +{ + Serial.print("Read-Write Hreg: "); + #define RD 0x10 + #define WR 0x20 + #define RW_COUNT 10 + uint16_t rd[10]; + uint16_t wr[10]; + for (uint8_t i = 0; i < RW_COUNT; i++) + wr[i] = WR; + slave.addHreg(110, RD, RW_COUNT); + slave.addHreg(120, !WR, RW_COUNT); + master.readWriteHreg(SLAVE_ID, 110, rd, RW_COUNT, 120, wr, RW_COUNT, cbWrite); + wait(); + uint8_t i,j; + for (i = 0; i < RW_COUNT; i++) + if (rd[i] != RD) + break; + for (j = 0; j < RW_COUNT; j++) + if (slave.Hreg(120 + j) != WR) + break; + if (i < RW_COUNT || j < RW_COUNT) + Serial.println(" FAILED"); + else + Serial.println(" PASSED"); +} +// Mask Hreg +{ + Serial.print("Mask Hreg: "); + slave.addReg(HREG(130), 0xF0F0); + slave.addReg(HREG(131), 0x1212); + master.maskHreg(SLAVE_ID, 130, 0xF0F0, 0x0000, cbWrite); + wait(); + master.maskHreg(SLAVE_ID, 131, 0xF2F2, 0x2525, cbWrite); + wait(); + if (slave.Reg(HREG(130)) != 0xF0F0 || slave.Reg(HREG(131)) != 0x1717) + Serial.println(" FAILED"); + else + Serial.println(" PASSED"); +} +// Garbage read + { + bool Node_1_ackStatus = false; + bool Node_2_ackStatus = false; + slave.addIsts(100, true); + slave.addIsts(101, true); + Serial.print("Write garbage: "); + if (!master.slave()) { + master.readIsts(2, 100, &Node_1_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 100, &Node_1_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + while(P2.available()) + P2.write(P2.read()); + //slave.task(); + delay(1); + } + master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); + while (master.slave()) { + master.task(); + slave.task(); + delay(1); + } + } + if (Node_1_ackStatus && Node_2_ackStatus) { + Serial.println(" PASSED"); + } else { + Serial.println(" FAILED"); + } + } + { + initFile(); + testFile(); + } +} +void loop() { + yield(); +} \ No newline at end of file diff --git a/tests/write.h b/tests/write.h new file mode 100644 index 0000000..627ce91 --- /dev/null +++ b/tests/write.h @@ -0,0 +1,179 @@ +#pragma once +#include "common.h" + +// Single Hreg write +void writeSingle(uint8_t sl, TAddress reg, uint16_t value) { + Serial.print("Write Single "); + switch (reg.type) { + case TAddress::HREG: + slave.addHreg(reg.address); + Serial.print("HREG: "); + break; + case TAddress::IREG: + slave.addIreg(reg.address); + Serial.print("IREG: "); + break; + case TAddress::COIL: + slave.addCoil(reg.address); + Serial.print("COIL: "); + break; + case TAddress::ISTS: + slave.addIsts(reg.address); + Serial.print("ISTS: "); + break; + default: + Serial.println("UNKNOWN"); + return; + } + if (!master.slave()) { + bool res = false; + switch (reg.type) { + case TAddress::HREG: + res = master.writeHreg(sl, reg.address, value, cbWrite); + break; + case TAddress::IREG: + //res = master.writeIreg(sl, reg.address, value, cbWrite); + break; + case TAddress::COIL: + res = master.writeCoil(sl, reg.address, value, cbWrite); + break; + case TAddress::ISTS: + //res = master.writeIsts(sl, reg.address, value, cbWrite); + break; + } + if (res) { + Serial.print(" SENT "); + if (wait() == Modbus::EX_SUCCESS) { + uint16_t val = 0; + switch (reg.type) { + case TAddress::HREG: + val = slave.Hreg(reg.address); + break; + case TAddress::IREG: + val = slave.Ireg(reg.address); + break; + case TAddress::COIL: + val = slave.Coil(reg.address); + break; + case TAddress::ISTS: + val = slave.Ists(reg.address); + break; + } + if (val = value) { + Serial.println(" PASSED"); + } else { + Serial.print(" INCORRECT"); + } + } else { + Serial.println(); + } + } else { + Serial.println(" FAILED"); + } + } else { + Serial.println(" BUSY"); + } +} + +// Multiple write +void writeMultiple(uint8_t sl, TAddress reg, uint16_t count = 1, void* value = nullptr) { + Serial.print("Write Multiple "); + bool mem = false; + if (!value) { + if (reg.isHreg() || reg.isIreg()) { + value = malloc(count * sizeof(uint16_t)); + if (!value) + return; + for (uint8_t i = 0; i < count; i++) { + ((uint16_t*)value)[i] = i; + } + } else { + value = malloc(count * sizeof(bool)); + if (!value) + return; + for (uint8_t i = 0; i < count; i++) { + ((bool*)value)[i] = i % 2; + } + } + mem = true; + } + switch (reg.type) { + case TAddress::HREG: + slave.addHreg(reg.address, 0, count); + Serial.print("HREG: "); + break; + case TAddress::IREG: + slave.addIreg(reg.address, 0, count); + Serial.print("IREG: "); + break; + case TAddress::COIL: + slave.addCoil(reg.address, false, count); + Serial.print("COIL: "); + break; + case TAddress::ISTS: + slave.addIsts(reg.address, false, count); + Serial.print("ISTS: "); + break; + default: + Serial.println("UNKNOWN"); + return; + } + if (!master.slave()) { + bool res = false; + switch (reg.type) { + case TAddress::HREG: + res = master.writeHreg(sl, reg.address, (uint16_t*)value, count, cbWrite); + break; + case TAddress::IREG: + //res = master.writeIreg(sl, reg.address, value, count, cbWrite); + break; + case TAddress::COIL: + res = master.writeCoil(sl, reg.address, (bool*)value, count, cbWrite); + break; + case TAddress::ISTS: + //res = master.writeIsts(sl, reg.address, value, count, cbWrite); + break; + } + if (res) { + Serial.print(" SENT "); + if (wait() == Modbus::EX_SUCCESS) { + bool res = true; + switch (reg.type) { + case TAddress::HREG: + for (uint8_t i = 0; i < count; i++) { + if (slave.Hreg(reg.address + i) != ((uint16_t*)value)[i]) res = false; + } + break; + case TAddress::IREG: + for (uint8_t i = 0; i < count; i++) { + //if (slave.Ireg(reg.address + i) != value[i]) res = false; + } + break; + case TAddress::COIL: + for (uint8_t i = 0; i < count; i++) { + if (slave.Coil(reg.address + i) != ((bool*)value)[i]) res = false; + } + break; + case TAddress::ISTS: + for (uint8_t i = 0; i < count; i++) { + //if (slave.Ists(reg.address + i) != value[i]) res = false; + } + break; + } + if (res) { + Serial.println(" PASSED"); + } else { + Serial.print(" INCORRECT"); + } + } else { + Serial.println(); + } + } else { + Serial.println(" FAILED"); + } + } else { + Serial.println(" BUSY"); + } + if (mem) + free(value); +} \ No newline at end of file