├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── Modbus_Application_Protocol_V1_1b3.pdf └── Modbus_over_serial_line_V1_02.pdf ├── examples └── SDM630 │ └── SDM630.ino ├── keywords.txt ├── library.json ├── library.properties ├── scripts ├── build_examples.sh └── platformio.ini ├── src ├── ModbusMessage.cpp ├── ModbusMessage.h ├── esp32ModbusRTU.cpp ├── esp32ModbusRTU.h └── esp32ModbusTypeDefs.h └── tests ├── Includes ├── CheckArray.h └── catch.hpp ├── Test_ModbusMessage.cpp ├── main.cpp └── platformio.ini /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: bertmelis 2 | custom: "https://paypal.me/bertmelis" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .vscode 3 | lib 4 | build 5 | .vscode/.browse.c_cpp.db* 6 | .vscode/c_cpp_properties.json 7 | .vscode/launch.json 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | os: linux 3 | dist: trusty 4 | 5 | before_install: 6 | - sudo apt-get update 7 | 8 | jobs: 9 | include: 10 | - name: "cpplint" 11 | script: 12 | - pip install -U cpplint 13 | - cpplint --repository=. --recursive --linelength=200 --filter=-build/include,-build/header_guard ./src 14 | - name: "cppcheck" 15 | addons: 16 | apt: 17 | packages: 18 | - cppcheck 19 | script: 20 | - cppcheck --error-exitcode=1 --enable=warning,style,performance,portability -DARDUINO_ARCH_ESP32=1 ./src 21 | - name: "Test" 22 | script: 23 | - pip install -U platformio 24 | - platformio update 25 | - rm -rf build 26 | - mkdir build 27 | - platformio ci --lib="./src" --project-conf="scripts/platformio.ini" --build-dir=build --keep-build-dir tests 28 | - ./build/.pio/build/catch2/program 29 | - name: "Memcheck" 30 | addons: 31 | apt: 32 | packages: 33 | - valgrind 34 | script: 35 | - pip install -U platformio 36 | - platformio update 37 | - rm -rf build 38 | - mkdir build 39 | - platformio ci --lib="./src" --project-conf="scripts/platformio.ini" --build-dir=build --keep-build-dir tests 40 | - valgrind --leak-check=full ./build/.pio/build/catch2/program 41 | - name: "Build examples" 42 | before_install: 43 | - chmod +x ./scripts/build_examples.sh 44 | script: 45 | - ./scripts/build_examples.sh 46 | 47 | notifications: 48 | email: 49 | on_success: change 50 | on_failure: change -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ARCHIVED 2 | 3 | This repo is archived no longer actively maintained. 4 | 5 | For a complete Modbus library for ESP32, I invite you to https://github.com/eModbus/eModbus 6 | 7 | # esp32ModbusRTU 8 | 9 | [![Build Status](https://travis-ci.com/bertmelis/esp32ModbusRTU.svg?branch=master)](https://travis-ci.com/bertmelis/esp32ModbusRTU) 10 | 11 | This is a non blocking Modbus client (master) for ESP32. 12 | 13 | - Modbus Client aka Master for ESP32 14 | - built for the [Arduino framework for ESP32](https://github.com/espressif/arduino-esp32) 15 | - non blocking API. Blocking code is in a seperate task 16 | - only RS485 half duplex (optionally using a GPIO as RTS (DE/RS)) is implemented 17 | - function codes implemented: 18 | - read discrete inputs (02) 19 | - read holding registers (03) 20 | - read input registers (04) 21 | - similar API as my [esp32ModbusTCP](https://github.com/bertmelis/esp32ModbusTCP) implementation 22 | 23 | ## Developement status 24 | 25 | I have this library in use myself with quite some uptime (only using FC3 -read holding registers- though). 26 | 27 | Things to do, ranked: 28 | 29 | - add debug info 30 | - unit testing for ModbusMessage 31 | - implement missing function codes (no priority, pull requests happily accepted) 32 | 33 | ## Installation 34 | 35 | The easiest, and my preferred one, method to install this library is to use Platformio. 36 | [https://platformio.org/lib/show/5902/esp32ModbusTCP/installation](https://platformio.org/lib/show/5902/esp32ModbusTCP/installation) 37 | 38 | Alternatively, you can use Arduino IDE for developement. 39 | [https://www.arduino.cc/en/guide/libraries](https://www.arduino.cc/en/guide/libraries) 40 | 41 | Arduino framework for ESP32 v1.0.1 (January 2019) or higher is needed. Previous versions contain a bug where the last byte of the messages are truncated when using DE/RE 42 | Arduino framework for ESP32 ([https://github.com/espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)) 43 | 44 | ## Example hardware: 45 | 46 | ```ASCII 47 | 3.3V --------------+-----/\/\/\/\---+ 48 | | 680 | 49 | +-------x-------+ | 50 | 17 <------| RO | | 51 | | B|--------+------------------------- 52 | 16 --+--->| DE MAX3485 | | \ / 53 | | | | +-/\/\/\/\-+ RS-485 side 54 | +--->| /RE | 120 | / \ 55 | | A|-------------------+--------------- 56 | 4 -------| DI | | 57 | +-------x-------+ | 58 | | | 59 | +-----/\/\/\/\--------------+ 60 | | 680 61 | +----------------/\/\/\/\------------------ GND 62 | | 100 63 | --- 64 | ``` 65 | 66 | The biasing resistors may not be neccesary for your setup. The GND connection 67 | is connected via a 100 Ohms resistor to limit possible ground loop currents. 68 | 69 | ## Usage 70 | 71 | The API is quite lightweight. It takes minimal 3 steps to get going. 72 | 73 | First create the ModbusRTU object. The constructor takes two arguments: HardwareSerial object and pin number of DE/RS. 74 | 75 | ```C++ 76 | esp32ModbusRTU myModbus(&Serial, DE_PIN); 77 | ``` 78 | 79 | Next add a onData callback. This can be any function object. Here it uses a simple function pointer. 80 | 81 | ```C++ 82 | 83 | void handleData(uint8_t serverAddress, esp32Modbus::FunctionCode fc, uint8_t* data, size_t length) { 84 | Serial.printf("received data: id: %u, fc %u\ndata: 0x", serverAddress, fc); 85 | for (uint8_t i = 0; i < length; ++i) { 86 | Serial.printf("%02x", data[i]); 87 | } 88 | Serial.print("\n"); 89 | } 90 | 91 | // in setup() 92 | myModbus.onData(handleData); 93 | ``` 94 | 95 | Optionally you can attach an onError callback. Again, any function object is possible. 96 | 97 | ```C++ 98 | // in setup() 99 | myModbus.onError([](esp32Modbus::Error error) { 100 | Serial.printf("error: 0x%02x\n\n", static_cast(error)); 101 | }); 102 | ``` 103 | 104 | After setup, start the modbusmaster: 105 | 106 | ```C++ 107 | // in setup() 108 | myModbus.begin(); 109 | ``` 110 | 111 | Now ModbusRTU is setup, you can start reading or writing. The arguments depend on the function used. Obviously, serverID, address and length are always required. The length is specified in words, not in bytes! 112 | 113 | ```C++ 114 | myModbus.readInputRegisters(0x01, 52, 2); // serverId, address + length 115 | ``` 116 | 117 | The requests are places in a queue. The function returns immediately and doesn't wait for the server to respond. 118 | Communication methods return a boolean value so you can check if the command was successful. 119 | 120 | ## Configuration 121 | 122 | The request queue holds maximum 20 items. So a 21st request will fail until the queue has an empty spot. You can change the queue size in the header file or by using a compiler flag: 123 | 124 | ```C++ 125 | #define QUEUE_SIZE 20 126 | ``` 127 | 128 | The waiting time before a timeout error is returned can also be changed by a `#define` variable: 129 | 130 | ```C++ 131 | #define TIMEOUT_MS 5000 132 | ``` 133 | 134 | ## Issues 135 | 136 | Please file a Github issue ~~if~~ when you find a bug. You can also use the issue tracker for feature requests. 137 | 138 | ## Extra 139 | 140 | For modbus-TCP, check out [esp32ModbusTCP](https://github.com/bertmelis/esp32ModbusTCP) 141 | -------------------------------------------------------------------------------- /docs/Modbus_Application_Protocol_V1_1b3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertmelis/esp32ModbusRTU/b5e74bf09d702375eec271f4305180cacc66df7c/docs/Modbus_Application_Protocol_V1_1b3.pdf -------------------------------------------------------------------------------- /docs/Modbus_over_serial_line_V1_02.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertmelis/esp32ModbusRTU/b5e74bf09d702375eec271f4305180cacc66df7c/docs/Modbus_over_serial_line_V1_02.pdf -------------------------------------------------------------------------------- /examples/SDM630/SDM630.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | This example reads 2 words (4 bytes) from address 52 of a server with id 1. 6 | address 52 = register 30053 (Eastron SDM630 Total system power) 7 | The ESP is connected to a max3485 with pins 17 (RX), 4 (TX) and 16 as RTS. 8 | 9 | */ 10 | 11 | #include 12 | #include 13 | #include // for std::reverse 14 | 15 | esp32ModbusRTU modbus(&Serial1, 16); // use Serial1 and pin 16 as RTS 16 | 17 | void setup() { 18 | Serial.begin(115200); // Serial output 19 | Serial1.begin(9600, SERIAL_8N1, 17, 4, true); // Modbus connection 20 | 21 | modbus.onData([](uint8_t serverAddress, esp32Modbus::FunctionCode fc, uint16_t address, uint8_t* data, size_t length) { 22 | Serial.printf("id 0x%02x fc 0x%02x len %u: 0x", serverAddress, fc, length); 23 | for (size_t i = 0; i < length; ++i) { 24 | Serial.printf("%02x", data[i]); 25 | } 26 | std::reverse(data, data + 4); // fix endianness 27 | Serial.printf("\nval: %.2f", *reinterpret_cast(data)); 28 | Serial.print("\n\n"); 29 | }); 30 | modbus.onError([](esp32Modbus::Error error) { 31 | Serial.printf("error: 0x%02x\n\n", static_cast(error)); 32 | }); 33 | modbus.begin(); 34 | 35 | } 36 | 37 | void loop() { 38 | static uint32_t lastMillis = 0; 39 | if (millis() - lastMillis > 30000) { 40 | lastMillis = millis(); 41 | Serial.print("sending Modbus request...\n"); 42 | modbus.readInputRegisters(0x01, 52, 2); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Datatypes (KEYWORD1) 3 | ####################################### 4 | 5 | MBFunctionCode KEYWORD1 6 | MBError KEYWORD1 7 | MB_PDU KEYWORD1 8 | MBOnData KEYWORD1 9 | MBOnError KEYWORD1 10 | esp32ModbusRTU KEYWORD1 11 | 12 | 13 | ####################################### 14 | # Methods and Functions (KEYWORD2) 15 | ####################################### 16 | 17 | begin KEYWORD2 18 | request KEYWORD2 19 | onData KEYWORD2 20 | onError KEYWORD2 21 | 22 | ####################################### 23 | # Constants (LITERAL1) 24 | ####################################### 25 | 26 | READ_COIL LITERAL1 27 | READ_DISC_INPUT LITERAL1 28 | READ_HOLD_REGISTER LITERAL1 29 | READ_INPUT_REGISTER LITERAL1 30 | WRITE_COIL LITERAL1 31 | WRITE_HOLD_REGISTER LITERAL1 32 | WRITE_MULT_COILS LITERAL1 33 | WRITE_MULT_REGISTERS LITERAL1 34 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esp32ModbusRTU", 3 | "version": "0.0.2", 4 | "keywords": "Arduino, ESP32, Modbus, RTU", 5 | "description": "async Modbus-RTU client for ESP32", 6 | "homepage": "https://github.com/bertmelis/esp32ModbusRTU", 7 | "license": "MIT", 8 | "authors": { 9 | "name": "Bert Melis", 10 | "url": "https://github.com/bertmelis", 11 | "maintainer": true 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/bertmelis/esp32ModbusRTU.git", 16 | "branch": "master" 17 | }, 18 | "frameworks": "arduino", 19 | "platforms": [ 20 | "espressif32" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=esp32ModbusRTU 2 | version=0.0.1 3 | author=Bert Melis 4 | maintainer=Bert Melis 5 | sentence=async Modbus-RTU client for ESP32 6 | paragraph= 7 | category=Communication 8 | url=https://github.com/bertmelis/esp32ModbusRTU 9 | architectures=esp32 10 | -------------------------------------------------------------------------------- /scripts/build_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pip install -U platformio 4 | platformio update 5 | platformio lib -g install 415 # Blynk 6 | platformio lib -g install https://github.com/homieiot/homie-esp8266.git#develop-v3 # Homie 7 | 8 | lines=$(find ./examples/ -maxdepth 1 -mindepth 1 -type d) 9 | retval=0 10 | while read line; do 11 | if [[ -e "$line/platformio.ini" ]] 12 | then 13 | platformio ci --lib="." --project-conf="$line/platformio.ini" $line 14 | if [ $? -ne 0 ]; then 15 | retval=1 16 | fi 17 | else 18 | platformio ci --lib="." --board=lolin32 $line 19 | if [ $? -ne 0 ]; then 20 | retval=1 21 | fi 22 | fi 23 | done <<< "$lines" 24 | exit $retval 25 | -------------------------------------------------------------------------------- /scripts/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | [env:catch2] 12 | platform = native 13 | build_flags = 14 | -DESP32MODBUSRTU_TEST=1 15 | -Wall 16 | -Os 17 | -std=c++11 18 | -ggdb3 19 | -------------------------------------------------------------------------------- /src/ModbusMessage.cpp: -------------------------------------------------------------------------------- 1 | /* ModbusMessage 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | 25 | CRC16 calculation 26 | 27 | copyright 2006 Modbus.org 28 | MODBUS over serial line specification and implementation guide V1.02 29 | 30 | */ 31 | 32 | #include "ModbusMessage.h" 33 | 34 | using namespace esp32ModbusRTUInternals; // NOLINT 35 | 36 | static uint8_t crcHiTable[] = { 37 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 38 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 39 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 40 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 41 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 42 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 43 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 44 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 45 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 46 | 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 47 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 48 | 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 49 | 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 50 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 51 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 52 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 53 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 54 | 0x40 55 | }; 56 | 57 | static uint8_t crcLoTable[] = { 58 | 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 59 | 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 60 | 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 61 | 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 62 | 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 63 | 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 64 | 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 65 | 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 66 | 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 67 | 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 68 | 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 69 | 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 70 | 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 71 | 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 72 | 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 73 | 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 74 | 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 75 | 0x40 76 | }; 77 | 78 | uint16_t CRC16(uint8_t* msg, size_t len) { 79 | uint8_t crcHi = 0xFF; 80 | uint8_t crcLo = 0xFF; 81 | uint8_t index; 82 | 83 | while (len--) { 84 | index = crcLo ^ *msg++; 85 | crcLo = crcHi ^ crcHiTable[index]; 86 | crcHi = crcLoTable[index]; 87 | } 88 | return (crcHi << 8 | crcLo); 89 | } 90 | 91 | uint8_t low(uint16_t in) { 92 | return (in & 0xff); 93 | } 94 | 95 | uint8_t high(uint16_t in) { 96 | return ((in >> 8) & 0xff); 97 | } 98 | 99 | uint16_t make_word(uint8_t high, uint8_t low) { 100 | return ((high << 8) | low); 101 | } 102 | 103 | ModbusMessage::ModbusMessage(uint8_t length) : 104 | _buffer(nullptr), 105 | _length(length), 106 | _index(0) { 107 | if (length < 5) _length = 5; // minimum for Modbus Exception codes 108 | _buffer = new uint8_t[_length]; 109 | for (uint8_t i = 0; i < _length; ++i) { 110 | _buffer[i] = 0; 111 | } 112 | } 113 | 114 | ModbusMessage::~ModbusMessage() { 115 | delete[] _buffer; 116 | } 117 | 118 | uint8_t* ModbusMessage::getMessage() { 119 | return _buffer; 120 | } 121 | 122 | uint8_t ModbusMessage::getSize() { 123 | return _index; 124 | } 125 | 126 | void ModbusMessage::add(uint8_t value) { 127 | if (_index < _length) _buffer[_index++] = value; 128 | } 129 | 130 | ModbusRequest::ModbusRequest(uint8_t length) : 131 | ModbusMessage(length), 132 | _slaveAddress(0), 133 | _functionCode(0), 134 | _address(0), 135 | _byteCount(0) {} 136 | 137 | uint16_t ModbusRequest::getAddress() { 138 | return _address; 139 | } 140 | 141 | ModbusRequest02::ModbusRequest02(uint8_t slaveAddress, uint16_t address, uint16_t numberCoils) : 142 | ModbusRequest(8) { 143 | _slaveAddress = slaveAddress; 144 | _functionCode = esp32Modbus::READ_DISCR_INPUT; 145 | _address = address; 146 | _byteCount = numberCoils / 8 + 1; 147 | add(_slaveAddress); 148 | add(_functionCode); 149 | add(high(_address)); 150 | add(low(_address)); 151 | add(high(numberCoils)); 152 | add(low(numberCoils)); 153 | uint16_t CRC = CRC16(_buffer, 6); 154 | add(low(CRC)); 155 | add(high(CRC)); 156 | } 157 | 158 | size_t ModbusRequest02::responseLength() { 159 | return 5 + _byteCount; 160 | } 161 | 162 | ModbusRequest03::ModbusRequest03(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters) : 163 | ModbusRequest(8) { 164 | _slaveAddress = slaveAddress; 165 | _functionCode = esp32Modbus::READ_HOLD_REGISTER; 166 | _address = address; 167 | _byteCount = numberRegisters * 2; // register is 2 bytes wide 168 | add(_slaveAddress); 169 | add(_functionCode); 170 | add(high(_address)); 171 | add(low(_address)); 172 | add(high(numberRegisters)); 173 | add(low(numberRegisters)); 174 | uint16_t CRC = CRC16(_buffer, 6); 175 | add(low(CRC)); 176 | add(high(CRC)); 177 | } 178 | 179 | size_t ModbusRequest03::responseLength() { 180 | return 5 + _byteCount; 181 | } 182 | 183 | ModbusRequest04::ModbusRequest04(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters) : 184 | ModbusRequest(8) { 185 | _slaveAddress = slaveAddress; 186 | _functionCode = esp32Modbus::READ_INPUT_REGISTER; 187 | _address = address; 188 | _byteCount = numberRegisters * 2; // register is 2 bytes wide 189 | add(_slaveAddress); 190 | add(_functionCode); 191 | add(high(_address)); 192 | add(low(_address)); 193 | add(high(numberRegisters)); 194 | add(low(numberRegisters)); 195 | uint16_t CRC = CRC16(_buffer, 6); 196 | add(low(CRC)); 197 | add(high(CRC)); 198 | } 199 | 200 | size_t ModbusRequest04::responseLength() { 201 | // slaveAddress (1) + functionCode (1) + byteCount (1) + length x 2 + CRC (2) 202 | return 5 + _byteCount; 203 | } 204 | 205 | ModbusRequest06::ModbusRequest06(uint8_t slaveAddress, uint16_t address, uint16_t data) : 206 | ModbusRequest(8) { 207 | _slaveAddress = slaveAddress; 208 | _functionCode = esp32Modbus::WRITE_HOLD_REGISTER; 209 | _address = address; 210 | _byteCount = 2; // 1 register is 2 bytes wide 211 | add(_slaveAddress); 212 | add(_functionCode); 213 | add(high(_address)); 214 | add(low(_address)); 215 | add(high(data)); 216 | add(low(data)); 217 | uint16_t CRC = CRC16(_buffer, 6); 218 | add(low(CRC)); 219 | add(high(CRC)); 220 | } 221 | 222 | size_t ModbusRequest06::responseLength() { 223 | return 8; 224 | } 225 | 226 | ModbusRequest16::ModbusRequest16(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters, uint8_t* data) : 227 | ModbusRequest(9 + (numberRegisters * 2)) { 228 | _slaveAddress = slaveAddress; 229 | _functionCode = esp32Modbus::WRITE_MULT_REGISTERS; 230 | _address = address; 231 | _byteCount = numberRegisters * 2; // register is 2 bytes wide 232 | add(_slaveAddress); 233 | add(_functionCode); 234 | add(high(_address)); 235 | add(low(_address)); 236 | add(high(numberRegisters)); 237 | add(low(numberRegisters)); 238 | add(_byteCount); 239 | for (int i = 0; i < _byteCount; i++) { 240 | add(data[i]); 241 | } 242 | uint16_t CRC = CRC16(_buffer, 7 + _byteCount); 243 | add(low(CRC)); 244 | add(high(CRC)); 245 | } 246 | 247 | size_t ModbusRequest16::responseLength() { 248 | return 8; 249 | } 250 | 251 | ModbusResponse::ModbusResponse(uint8_t length, ModbusRequest* request) : 252 | ModbusMessage(length), 253 | _request(request), 254 | _error(esp32Modbus::SUCCES) {} 255 | 256 | bool ModbusResponse::isComplete() { 257 | if (_buffer[1] > 0x80 && _index == 5) { // 5: slaveAddress(1), errorCode(1), CRC(2) + indexed 258 | return true; 259 | } 260 | if (_index == _request->responseLength()) return true; 261 | return false; 262 | } 263 | 264 | bool ModbusResponse::isSucces() { 265 | if (!isComplete()) { 266 | _error = esp32Modbus::TIMEOUT; 267 | } else if (_buffer[1] > 0x80) { 268 | _error = static_cast(_buffer[2]); 269 | } else if (!checkCRC()) { 270 | _error = esp32Modbus::CRC_ERROR; 271 | // TODO(bertmelis): add other checks 272 | } else { 273 | _error = esp32Modbus::SUCCES; 274 | } 275 | if (_error == esp32Modbus::SUCCES) { 276 | return true; 277 | } else { 278 | return false; 279 | } 280 | } 281 | 282 | bool ModbusResponse::checkCRC() { 283 | uint16_t CRC = CRC16(_buffer, _length - 2); 284 | if (low(CRC) == _buffer[_length - 2] && high(CRC) == _buffer[_length -1]) { 285 | return true; 286 | } else { 287 | return false; 288 | } 289 | } 290 | 291 | esp32Modbus::Error ModbusResponse::getError() const { 292 | return _error; 293 | } 294 | 295 | uint8_t ModbusResponse::getSlaveAddress() { 296 | return _buffer[0]; 297 | } 298 | 299 | esp32Modbus::FunctionCode ModbusResponse::getFunctionCode() { 300 | return static_cast(_buffer[1]); 301 | } 302 | 303 | uint8_t* ModbusResponse::getData() { 304 | return &_buffer[3]; 305 | } 306 | 307 | uint8_t ModbusResponse::getByteCount() { 308 | return _buffer[2]; 309 | } 310 | -------------------------------------------------------------------------------- /src/ModbusMessage.h: -------------------------------------------------------------------------------- 1 | /* ModbusMessage 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef esp32ModbusRTUInternals_ModbusMessage_h 26 | #define esp32ModbusRTUInternals_ModbusMessage_h 27 | 28 | #include // for uint*_t 29 | #include // for size_t 30 | 31 | #include "esp32ModbusTypeDefs.h" 32 | 33 | namespace esp32ModbusRTUInternals { 34 | 35 | class ModbusMessage { 36 | public: 37 | virtual ~ModbusMessage(); 38 | uint8_t* getMessage(); 39 | uint8_t getSize(); 40 | void add(uint8_t value); 41 | 42 | protected: 43 | explicit ModbusMessage(uint8_t length); 44 | uint8_t* _buffer; 45 | uint8_t _length; 46 | uint8_t _index; 47 | }; 48 | 49 | class ModbusResponse; // forward declare for use in ModbusRequest 50 | 51 | class ModbusRequest : public ModbusMessage { 52 | public: 53 | virtual size_t responseLength() = 0; 54 | uint16_t getAddress(); 55 | 56 | protected: 57 | explicit ModbusRequest(uint8_t length); 58 | uint8_t _slaveAddress; 59 | uint8_t _functionCode; 60 | uint16_t _address; 61 | uint16_t _byteCount; 62 | }; 63 | 64 | // read discrete coils 65 | class ModbusRequest02 : public ModbusRequest { 66 | public: 67 | explicit ModbusRequest02(uint8_t slaveAddress, uint16_t address, uint16_t numberCoils); 68 | size_t responseLength(); 69 | }; 70 | 71 | // read holding registers 72 | class ModbusRequest03 : public ModbusRequest { 73 | public: 74 | explicit ModbusRequest03(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters); 75 | size_t responseLength(); 76 | }; 77 | 78 | // read input registers 79 | class ModbusRequest04 : public ModbusRequest { 80 | public: 81 | explicit ModbusRequest04(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters); 82 | size_t responseLength(); 83 | }; 84 | 85 | // write single holding registers 86 | class ModbusRequest06 : public ModbusRequest { 87 | public: 88 | explicit ModbusRequest06(uint8_t slaveAddress, uint16_t address, uint16_t data); 89 | size_t responseLength(); 90 | }; 91 | 92 | // write multiple holding registers 93 | class ModbusRequest16 : public ModbusRequest { 94 | public: 95 | explicit ModbusRequest16(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters, uint8_t* data); 96 | size_t responseLength(); 97 | }; 98 | 99 | class ModbusResponse : public ModbusMessage { 100 | public: 101 | explicit ModbusResponse(uint8_t length, ModbusRequest* request); 102 | bool isComplete(); 103 | bool isSucces(); 104 | bool checkCRC(); 105 | esp32Modbus::Error getError() const; 106 | 107 | uint8_t getSlaveAddress(); 108 | esp32Modbus::FunctionCode getFunctionCode(); 109 | uint8_t* getData(); 110 | uint8_t getByteCount(); 111 | 112 | private: 113 | ModbusRequest* _request; 114 | esp32Modbus::Error _error; 115 | }; 116 | 117 | } // namespace esp32ModbusRTUInternals 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /src/esp32ModbusRTU.cpp: -------------------------------------------------------------------------------- 1 | /* esp32ModbusRTU 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #include "esp32ModbusRTU.h" 26 | 27 | #if defined ARDUINO_ARCH_ESP32 28 | 29 | using namespace esp32ModbusRTUInternals; // NOLINT 30 | 31 | esp32ModbusRTU::esp32ModbusRTU(HardwareSerial* serial, int8_t rtsPin) : 32 | TimeOutValue(TIMEOUT_MS), 33 | _serial(serial), 34 | _lastMillis(0), 35 | _interval(0), 36 | _rtsPin(rtsPin), 37 | _task(nullptr), 38 | _queue(nullptr) { 39 | _queue = xQueueCreate(QUEUE_SIZE, sizeof(ModbusRequest*)); 40 | } 41 | 42 | esp32ModbusRTU::~esp32ModbusRTU() { 43 | // TODO(bertmelis): kill task and cleanup 44 | } 45 | 46 | void esp32ModbusRTU::begin(int coreID /* = -1 */) { 47 | // If rtsPin is >=0, the RS485 adapter needs send/receive toggle 48 | if (_rtsPin >= 0) { 49 | pinMode(_rtsPin, OUTPUT); 50 | digitalWrite(_rtsPin, LOW); 51 | } 52 | xTaskCreatePinnedToCore((TaskFunction_t)&_handleConnection, "esp32ModbusRTU", 4096, this, 5, &_task, coreID >= 0 ? coreID : NULL); 53 | // silent interval is at least 3.5x character time 54 | _interval = 40000 / _serial->baudRate(); // 4 * 1000 * 10 / baud 55 | if (_interval == 0) _interval = 1; // minimum of 1msec interval 56 | } 57 | 58 | bool esp32ModbusRTU::readDiscreteInputs(uint8_t slaveAddress, uint16_t address, uint16_t numberCoils) { 59 | ModbusRequest* request = new ModbusRequest02(slaveAddress, address, numberCoils); 60 | return _addToQueue(request); 61 | } 62 | bool esp32ModbusRTU::readHoldingRegisters(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters) { 63 | ModbusRequest* request = new ModbusRequest03(slaveAddress, address, numberRegisters); 64 | return _addToQueue(request); 65 | } 66 | 67 | bool esp32ModbusRTU::readInputRegisters(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters) { 68 | ModbusRequest* request = new ModbusRequest04(slaveAddress, address, numberRegisters); 69 | return _addToQueue(request); 70 | } 71 | 72 | bool esp32ModbusRTU::writeSingleHoldingRegister(uint8_t slaveAddress, uint16_t address, uint16_t data) { 73 | ModbusRequest* request = new ModbusRequest06(slaveAddress, address, data); 74 | return _addToQueue(request); 75 | } 76 | 77 | bool esp32ModbusRTU::writeMultHoldingRegisters(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters, uint8_t* data) { 78 | ModbusRequest* request = new ModbusRequest16(slaveAddress, address, numberRegisters, data); 79 | return _addToQueue(request); 80 | } 81 | 82 | void esp32ModbusRTU::onData(esp32Modbus::MBRTUOnData handler) { 83 | _onData = handler; 84 | } 85 | 86 | void esp32ModbusRTU::onError(esp32Modbus::MBRTUOnError handler) { 87 | _onError = handler; 88 | } 89 | 90 | bool esp32ModbusRTU::_addToQueue(ModbusRequest* request) { 91 | if (!request) { 92 | return false; 93 | } else if (xQueueSend(_queue, reinterpret_cast(&request), (TickType_t)0) != pdPASS) { 94 | delete request; 95 | return false; 96 | } else { 97 | return true; 98 | } 99 | } 100 | 101 | void esp32ModbusRTU::_handleConnection(esp32ModbusRTU* instance) { 102 | while (1) { 103 | ModbusRequest* request; 104 | if (pdTRUE == xQueueReceive(instance->_queue, &request, portMAX_DELAY)) { // block and wait for queued item 105 | instance->_send(request->getMessage(), request->getSize()); 106 | ModbusResponse* response = instance->_receive(request); 107 | if (response->isSucces()) { 108 | if (instance->_onData) instance->_onData(response->getSlaveAddress(), response->getFunctionCode(), request->getAddress(), response->getData(), response->getByteCount()); 109 | } else { 110 | if (instance->_onError) instance->_onError(response->getError()); 111 | } 112 | delete request; // object created in public methods 113 | delete response; // object created in _receive() 114 | } 115 | } 116 | } 117 | 118 | void esp32ModbusRTU::_send(uint8_t* data, uint8_t length) { 119 | while (millis() - _lastMillis < _interval) delay(1); // respect _interval 120 | // Toggle rtsPin, if necessary 121 | if (_rtsPin >= 0) digitalWrite(_rtsPin, HIGH); 122 | _serial->write(data, length); 123 | _serial->flush(); 124 | // Toggle rtsPin, if necessary 125 | if (_rtsPin >= 0) digitalWrite(_rtsPin, LOW); 126 | _lastMillis = millis(); 127 | } 128 | 129 | // Adjust timeout on MODBUS - some slaves require longer/allow for shorter times 130 | void esp32ModbusRTU::setTimeOutValue(uint32_t tov) { 131 | if (tov) TimeOutValue = tov; 132 | } 133 | 134 | ModbusResponse* esp32ModbusRTU::_receive(ModbusRequest* request) { 135 | ModbusResponse* response = new ModbusResponse(request->responseLength(), request); 136 | while (true) { 137 | while (_serial->available()) { 138 | response->add(_serial->read()); 139 | } 140 | if (response->isComplete()) { 141 | _lastMillis = millis(); 142 | break; 143 | } 144 | if (millis() - _lastMillis > TimeOutValue) { 145 | break; 146 | } 147 | delay(1); // take care of watchdog 148 | } 149 | return response; 150 | } 151 | 152 | #elif defined ESP32MODBUSRTU_TEST 153 | 154 | #else 155 | 156 | #pragma message "no suitable platform" 157 | 158 | #endif 159 | -------------------------------------------------------------------------------- /src/esp32ModbusRTU.h: -------------------------------------------------------------------------------- 1 | /* esp32ModbusRTU 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef esp32ModbusRTU_h 26 | #define esp32ModbusRTU_h 27 | 28 | #if defined ARDUINO_ARCH_ESP32 29 | 30 | #ifndef QUEUE_SIZE 31 | #define QUEUE_SIZE 20 32 | #endif 33 | #ifndef TIMEOUT_MS 34 | #define TIMEOUT_MS 5000 35 | #endif 36 | 37 | #include 38 | 39 | extern "C" { 40 | #include 41 | #include 42 | #include 43 | } 44 | #include 45 | #include 46 | 47 | #include "esp32ModbusTypeDefs.h" 48 | #include "ModbusMessage.h" 49 | 50 | class esp32ModbusRTU { 51 | public: 52 | explicit esp32ModbusRTU(HardwareSerial* serial, int8_t rtsPin = -1); 53 | ~esp32ModbusRTU(); 54 | void begin(int coreID = -1); 55 | bool readDiscreteInputs(uint8_t slaveAddress, uint16_t address, uint16_t numberCoils); 56 | bool readHoldingRegisters(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters); 57 | bool readInputRegisters(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters); 58 | bool writeSingleHoldingRegister(uint8_t slaveAddress, uint16_t address, uint16_t data); 59 | bool writeMultHoldingRegisters(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters, uint8_t* data); 60 | void onData(esp32Modbus::MBRTUOnData handler); 61 | void onError(esp32Modbus::MBRTUOnError handler); 62 | void setTimeOutValue(uint32_t tov); 63 | 64 | private: 65 | bool _addToQueue(esp32ModbusRTUInternals::ModbusRequest* request); 66 | static void _handleConnection(esp32ModbusRTU* instance); 67 | void _send(uint8_t* data, uint8_t length); 68 | esp32ModbusRTUInternals::ModbusResponse* _receive(esp32ModbusRTUInternals::ModbusRequest* request); 69 | 70 | private: 71 | uint32_t TimeOutValue; 72 | HardwareSerial* _serial; 73 | uint32_t _lastMillis; 74 | uint32_t _interval; 75 | int8_t _rtsPin; 76 | TaskHandle_t _task; 77 | QueueHandle_t _queue; 78 | esp32Modbus::MBRTUOnData _onData; 79 | esp32Modbus::MBRTUOnError _onError; 80 | }; 81 | 82 | #endif 83 | 84 | #elif defined VITOWIFI_TEST 85 | 86 | #else 87 | 88 | #pragma message "no suitable platform" 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/esp32ModbusTypeDefs.h: -------------------------------------------------------------------------------- 1 | /* esp32ModbusTypedefs 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #ifndef esp32Modbus_esp32ModbusTypeDefs_h 28 | #define esp32Modbus_esp32ModbusTypeDefs_h 29 | 30 | #include // for uint*_t 31 | #include // for std::function 32 | 33 | namespace esp32Modbus { 34 | 35 | enum FunctionCode : uint8_t { 36 | READ_COIL = 0x01, 37 | READ_DISCR_INPUT = 0x02, 38 | READ_HOLD_REGISTER = 0x03, 39 | READ_INPUT_REGISTER = 0x04, 40 | WRITE_COIL = 0x05, 41 | WRITE_HOLD_REGISTER = 0x06, 42 | WRITE_MULT_COILS = 0x0F, 43 | WRITE_MULT_REGISTERS = 0x10 44 | }; 45 | 46 | enum Error : uint8_t { 47 | SUCCES = 0x00, 48 | ILLEGAL_FUNCTION = 0x01, 49 | ILLEGAL_DATA_ADDRESS = 0x02, 50 | ILLEGAL_DATA_VALUE = 0x03, 51 | SERVER_DEVICE_FAILURE = 0x04, 52 | ACKNOWLEDGE = 0x05, 53 | SERVER_DEVICE_BUSY = 0x06, 54 | NEGATIVE_ACKNOWLEDGE = 0x07, 55 | MEMORY_PARITY_ERROR = 0x08, 56 | TIMEOUT = 0xE0, 57 | INVALID_SLAVE = 0xE1, 58 | INVALID_FUNCTION = 0xE2, 59 | CRC_ERROR = 0xE3, // only for Modbus-RTU 60 | COMM_ERROR = 0xE4 // general communication error 61 | }; 62 | 63 | typedef std::function MBTCPOnData; 64 | typedef std::function MBRTUOnData; 65 | typedef std::function MBTCPOnError; 66 | typedef std::function MBRTUOnError; 67 | 68 | } // namespace esp32Modbus 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /tests/Includes/CheckArray.h: -------------------------------------------------------------------------------- 1 | /* copyright 2019 Bert Melis */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "catch.hpp" 7 | 8 | class ByteArrayEqualsBegin : public Catch::MatcherBase { 9 | public: 10 | ByteArrayEqualsBegin(const uint8_t* standard, size_t size) : 11 | _standard(standard), 12 | _size(size) {} 13 | 14 | // Performs the test for this matcher 15 | bool match(const uint8_t* const& test) const override { 16 | for (size_t i = 0; i < _size; ++i) { 17 | if (test[i] != _standard[i]) return false; 18 | } 19 | return true; 20 | } 21 | 22 | // Produces a string describing what this matcher does. It should 23 | // include any provided data (the begin/ end in this case) and 24 | // be written as if it were stating a fact (in the output it will be 25 | // preceded by the value under test). 26 | std::string describe() const override { 27 | std::ostringstream ss; 28 | ss << "starts with 0x"; 29 | for (size_t i = 0; i < _size; ++i) { 30 | ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << unsigned(_standard[i]); 31 | } 32 | return ss.str(); 33 | } 34 | 35 | private: 36 | const uint8_t* _standard; 37 | const size_t _size; 38 | }; 39 | 40 | // The builder function 41 | inline ByteArrayEqualsBegin ByteArrayEqual(const uint8_t* arr, const size_t size) { 42 | return ByteArrayEqualsBegin(arr, size); 43 | } 44 | -------------------------------------------------------------------------------- /tests/Test_ModbusMessage.cpp: -------------------------------------------------------------------------------- 1 | /* copyright 2019 Bert Melis */ 2 | 3 | #include 4 | 5 | #include "Includes/catch.hpp" 6 | #include "Includes/CheckArray.h" 7 | #include 8 | 9 | using Catch::Matchers::WithinAbs; 10 | 11 | TEST_CASE("Read input registers", "[FC04]") { 12 | esp32ModbusRTUInternals::ModbusRequest* request = new esp32ModbusRTUInternals::ModbusRequest04(0x11, 0x0008, 0x0001); 13 | uint8_t stdMessage[] = {0x11, 0x04, 0x00, 0x08, 0x00, 0x01, 0xB2, 0x98}; 14 | uint8_t stdResponse[] = {0x11, 0x04, 0x02, 0x00, 0x0A, 0xF8, 0xF4}; 15 | uint8_t stdErrorResponse[] = {0x11, 0x84, 0x02, 0x00, 0x00}; // TODO(bertmelis): adjust CRC 16 | 17 | REQUIRE(request->getSize() == sizeof(stdMessage)); 18 | REQUIRE_THAT(request->getMessage(), ByteArrayEqual(stdMessage, sizeof(stdMessage))); 19 | 20 | esp32ModbusRTUInternals::ModbusResponse* response = new esp32ModbusRTUInternals::ModbusResponse(request->responseLength(), request); 21 | 22 | SECTION("normal response") { 23 | for (uint8_t i = 0; i < sizeof(stdResponse); ++i) { 24 | response->add(stdResponse[i]); 25 | } 26 | CHECK(response->isSucces()); 27 | } 28 | 29 | SECTION("error response") { 30 | for (uint8_t i = 0; i < sizeof(stdErrorResponse); ++i) { 31 | response->add(stdErrorResponse[i]); 32 | } 33 | CHECK_FALSE(response->isSucces()); 34 | CHECK(response->getError() == esp32Modbus::ILLEGAL_DATA_ADDRESS); 35 | } 36 | 37 | delete request; 38 | delete response; 39 | } 40 | 41 | TEST_CASE("Write multiple holding registers", "[FC16]") { 42 | uint8_t data[] = {0x00, 0x0A, 0x01, 0x02}; 43 | esp32ModbusRTUInternals::ModbusRequest* request = new esp32ModbusRTUInternals::ModbusRequest16(0x11, 0x0001, 0x0002, data); 44 | uint8_t stdMessage[] = {0x11, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x00, 0x0A, 0x01, 0x02, 0xC6, 0xF0}; 45 | uint8_t stdResponse[] = {0x11, 0x10, 0x00, 0x01, 0x00, 0x02, 0x12, 0x98}; 46 | uint8_t stdErrorResponse[] = {0x11, 0x90, 0x02, 0x00, 0x00}; // TODO(bertmelis): adjust CRC 47 | 48 | REQUIRE(request->getSize() == sizeof(stdMessage)); 49 | REQUIRE_THAT(request->getMessage(), ByteArrayEqual(stdMessage, sizeof(stdMessage))); 50 | 51 | esp32ModbusRTUInternals::ModbusResponse* response = new esp32ModbusRTUInternals::ModbusResponse(request->responseLength(), request); 52 | 53 | SECTION("normal response") { 54 | for (uint8_t i = 0; i < sizeof(stdResponse); ++i) { 55 | response->add(stdResponse[i]); 56 | } 57 | CHECK(response->isSucces()); 58 | } 59 | 60 | SECTION("error response") { 61 | for (uint8_t i = 0; i < sizeof(stdErrorResponse); ++i) { 62 | response->add(stdErrorResponse[i]); 63 | } 64 | CHECK_FALSE(response->isSucces()); 65 | CHECK(response->getError() == esp32Modbus::ILLEGAL_DATA_ADDRESS); 66 | } 67 | 68 | delete request; 69 | delete response; 70 | } 71 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | /* copyright 2019 Bert Melis */ 2 | 3 | #define CATCH_CONFIG_MAIN 4 | 5 | #include "Includes/catch.hpp" 6 | 7 | TEST_CASE("All test cases reside in other .cpp files (empty)", "[multi-file:1]") {} 8 | -------------------------------------------------------------------------------- /tests/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | [env:test_queue] 12 | platform = native 13 | build_flags = 14 | -DESP32MODBUSRTU_TEST=1 15 | -Wall 16 | -Os 17 | -std=c++11 18 | --------------------------------------------------------------------------------