├── src ├── CIBuildSettings.h └── main.cpp ├── .gitmodules ├── docs └── CASModbusScanner.png ├── .gitignore ├── platformio.ini ├── test └── README ├── lib └── README ├── include └── README ├── .travis.yml ├── .gitlab-ci.yml └── README.md /src/CIBuildSettings.h: -------------------------------------------------------------------------------- 1 | // This file is overwritten in the Gitlab CI build proccess. 2 | #define CI_PIPELINE_IID 0 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cas-modbus-stack"] 2 | path = cas-modbus-stack 3 | url = ../../chipkin/cas-modbus-stack.git 4 | -------------------------------------------------------------------------------- /docs/CASModbusScanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chipkin/ESP32-ModbusServerExample/HEAD/docs/CASModbusScanner.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | 8 | lib/casmodbusstack/ 9 | 10 | \.vscode/ 11 | -------------------------------------------------------------------------------- /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 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:featheresp32] 12 | platform = espressif32 13 | board = featheresp32 14 | framework = arduino 15 | monitor_speed = 115200 -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://docs.platformio.org/en/latest/ci/gitlab.html 2 | 3 | variables: 4 | GIT_SUBMODULE_STRATEGY: recursive 5 | 6 | stages: 7 | - build 8 | 9 | cache: 10 | key: ${CI_JOB_NAME} 11 | 12 | Build ESP32 ModbusServerExample: 13 | image: python:2.7 14 | stage: build 15 | tags: 16 | - docker 17 | before_script: 18 | # Update the build number 19 | - echo -e "#define CI_PIPELINE_IID $CI_PIPELINE_IID\n" >src/CIBuildSettings.h 20 | - "cat src/CIBuildSettings.h" 21 | 22 | # Install platformio. (Requires Python 2.7, see image above) 23 | - "pip install -U platformio" 24 | script: 25 | # Debug 26 | - ls 27 | - ls cas-modbus-stack/ 28 | - ls cas-modbus-stack/source/ 29 | - ls cas-modbus-stack/submodules/cas-common/source/ 30 | 31 | # Move the files 32 | # The way that platforum.io works is that it will compile every file in the lib folder. 33 | # and the entire cas-modbus-stack has many dependencies, including unit testing frameworks. 34 | # so we can't just put the entire cas-modbus-stack folder into the lib folder. We need to 35 | # move the files that are needed into lib folder for the build. 36 | - mkdir lib/cas-modbus-stack/ 37 | - cp cas-modbus-stack/source/* lib/cas-modbus-stack/ 38 | - cp cas-modbus-stack/adapters/cpp/* lib/cas-modbus-stack/ 39 | - cp cas-modbus-stack/submodules/cas-common/source/ChipkinEndianness*.* lib/cas-modbus-stack/ 40 | 41 | # Debug 42 | - ls lib/cas-modbus-stack/ 43 | 44 | # Build 45 | # More info about platformio https://docs.platformio.org/en/latest/userguide/cmd_ci.html 46 | - "platformio ci --lib='lib/cas-modbus-stack' --board=featheresp32 src" 47 | 48 | # After the project builds I could not find the .pioenvs in the docker image. 49 | # this folder does exist on my local computer during testing just not in the 50 | # docker image. The artifacts are not required because this is just to ensure 51 | # that changes can be built without error. There is no unit tests, or production 52 | # tests happening on this binary. 53 | # Still strange that I could not get the binary. 54 | # 55 | # artifacts: 56 | # paths: 57 | # - .pioenvs/featheresp32/firmware*.* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Modbus Server Example 2 | 3 | In this project we are using the CAS Modbus stack (https://store.chipkin.com) to generate a simple Modbus TCP server with three registers 40001-40003. Depening on the state of the registers the built in LED wil on/off/blink. A Modbus Client/Master application (such as [CAS Modbus Scanner](https://store.chipkin.com/products/tools/cas-modbus-scanner)) can be used to read/write to these registers and to change the values. 4 | 5 | | Register | Description | Default | 6 | | :------- | ------------------------------------- | ------: | 7 | | 40001 | LED Mode (1 = Off, 2 = On, 3 = Blink) | 3 | 8 | | 40002 | LED Blink Speed in milliseconds | 200 | 9 | | 40003 | LED Current state (0 = OFF, 1 = ON) | 0 | 10 | | 40004 | Running Count. This value incurments once a second and loops every ~18 hours. | 0 | 11 | | 40005 | Spare | 0 | 12 | | 40006 | Spare | 0 | 13 | | 40007 | Spare | 0 | 14 | | 40008 | Spare | 0 | 15 | | 40009 | Spare | 0 | 16 | 17 | ## Quick start 18 | 19 | 1. Download and install [Platform/io](https://platformio.org/) for [Visual studios code](https://code.visualstudio.com/) 20 | 2. Add the ESP32 board to Platform/io 21 | 3. Add the [CAS Modbus stack](https://store.chipkin.com/) libary to the */lib/* folder. 22 | 4. Use Platform/io to *Build* the project. 23 | 5. Use Platform/io to *Upload and Monitor*. 24 | 25 | ## Supported Modbus functions 26 | 27 | The CAS Modbus stack supports many other Modbus functions, this minumial example only supports the following: 28 | 29 | - 03 (0x03) **Read Holding Registers** 30 | - 06 (0x06) **Write Single Register** 31 | - 16 (0x10) **Write Multiple registers** 32 | 33 | ## Example output from CAS Modbus Scanner 34 | 35 | The following output was produced using the CAS Modbus Scanner when reading values from the ESP32-ModbusServerExample. 36 | 37 | ```txt 38 | Poll: FF 03 00 00 00 09 39 | Response: 01 03 12 00 03 00 FA 00 01 90 5A 00 00 00 00 00 00 00 00 00 00 40 | ``` 41 | 42 | | Register | Value (Hex) | Value (Uint16) | 43 | | :------- | ----------: | -------------: | 44 | | 40001 | 0x0003 | 3 | 45 | | 40002 | 0x00FA | 250 | 46 | | 40003 | 0x0001 | 1 | 47 | | 40004 | 0x905A | 36954 | 48 | | 40005 | 0x0000 | 0 | 49 | | 40006 | 0x0000 | 0 | 50 | | 40007 | 0x0000 | 0 | 51 | | 40008 | 0x0000 | 0 | 52 | | 40009 | 0x0000 | 0 | 53 | 54 | ## Example in the CAS Modbus Scanner 55 | 56 | ![Preview of the CAS Modbus Scanner](/docs/CASModbusScanner.png?raw=true "Preview of the CAS Modbus Scanner") 57 | 58 | ## Example output 59 | 60 | The example output from the serial terminal of the ESP32 61 | 62 | ```txt 63 | FYI: ESP32 CAS Modbus Stack example version: 0.0.1 64 | FYI: ESP32 Chip ID: 30C6, (08A4AE30) 65 | FYI: Connecting to wifi... 66 | FYI: Connected to XXXXXXXXXXXX 67 | FYI: IP address: 192.168.1.66 68 | FYI: Subnet mask: 255.255.255.0 69 | FYI: CAS Modbus Stack version: 2.3.11 70 | FYI: FreeHeap: 280012 / 343144 (81.60 %) 71 | FYI: New TCP connection 72 | FYI: RemoteIP: 192.168.1.77, Port: 27484 73 | FYI: RecvModbusMessage bytes=[12] 74 | FYI: GetModbusValue slaveAddress=[1], function=[3], startingAddress=[0], length=[9] 75 | 40001 = 3 (0x0003) 76 | 40002 = 250 (0x00FA) 77 | 40003 = 1 (0x0001) 78 | 40004 = 36954 (0x905A) 79 | 40005 = 0 (0x0000) 80 | 40006 = 0 (0x0000) 81 | 40007 = 0 (0x0000) 82 | 40008 = 0 (0x0000) 83 | 40009 = 0 (0x0000) 84 | FYI: sendModbusMessage bytes=[27] 85 | FYI: FreeHeap: 277328 / 342900 (80.88 %) 86 | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 87 | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 88 | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 89 | ``` 90 | 91 | ## Tested hardware 92 | 93 | - [Adafruit HUZZAH32 – ESP32 Feather Board](https://www.adafruit.com/product/3405) 94 | 95 | ## FAQ 96 | 97 | ### Can't build the project. *CASModbusAdapter.h: No such file or directory* 98 | 99 | Please see this issue [CASBACnetStackAdapter.h: No such file or directory](https://github.com/chipkin/ESP32-ModbusServerExample/issues/1) -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ESP32 Modbus Server Example 3 | * -------------------------------------- 4 | * In this project we are using the CAS Modbus stack (https://store.chipkin.com) to generate a 5 | * simple Modbus TCP server with three registers 40001-40003. Depening on the state of the registers 6 | * the built in LED wil on/off/blink. A Modbus Client/Master application (such as CAS Modbus Scanner) 7 | * can be used to read/write to these registers and to change the values. 8 | * 9 | * 40001 = LED Mode (1 = Off, 2 = On, 3 = Blink) 10 | * 40002 = LED Blink Speed in milliseconds (Default: 200) 11 | * 40003 = LED Current state (0 = OFF, 1 = ON) 12 | * 40004 = Count. This value incurments once a second and loops every ~18 hours. 13 | * 14 | * Created by: Steven Smethurst 15 | * Created on: May 9, 2019 16 | * Last updated: May 9, 2019 17 | * 18 | */ 19 | #include 20 | #include 21 | 22 | // Missing file 23 | // This file is part of the CAS Modbus stack and is not included in this repo 24 | // More information about the CAS Modbus stack can be found here http://store.chipkin.com 25 | // See this issue for more information https://github.com/chipkin/ESP32-ModbusServerExample/issues/1 26 | #include // Loads all the Modbus functions. 27 | #include // Helps with OS depenent Endianness 28 | 29 | // Application Version 30 | // ========================================== 31 | const uint32_t APPLICATION_VERSION_MAJOR = 0; 32 | const uint32_t APPLICATION_VERSION_MINOR = 0; 33 | const uint32_t APPLICATION_VERSION_PATCH = 1; 34 | 35 | // Application settings 36 | // ========================================== 37 | // ToDo: Update these prameters with your local wifi SSID and password. 38 | const char* APPLICATION_WIFI_SSID = "---YOUR SSID---"; 39 | const char* APPLICATION_WIFI_PASSWORD = "---YOUR PASSWORD---"; 40 | 41 | const uint32_t APPLICATION_LED_PIN = LED_BUILTIN; 42 | const uint16_t APPLICATION_LED_BLINK_RATE_MS = 250; // interval at which to blink (milliseconds) 43 | const uint32_t APPLICATION_SERIAL_BAUD_RATE = 115200; 44 | const uint8_t APPLICATION_MODBUS_SLAVE_ADDRESS = 1; 45 | const uint16_t APPLICATION_MODBUS_TCP_PORT = 502; 46 | 47 | // Modbus Constance 48 | const uint32_t MODBUS_TYPE_TCP = 2; 49 | const uint8_t MODBUS_FUNCTION_03_HOLDING_REGISTERS = 3; 50 | const uint8_t MODBUS_FUNCTION_06_WRITE_SINGLE_REGISTERS = 6; 51 | 52 | // Database 53 | // ========================================== 54 | const uint16_t DATABASE_SIZE = 10; 55 | uint16_t gDatabase[DATABASE_SIZE]; 56 | 57 | const uint16_t DATABASE_OFFSET_LED_MODE = 0; 58 | const uint16_t DATABASE_OFFSET_LED_BLINK_SPEED = 1; 59 | const uint16_t DATABASE_OFFSET_LED_STATE = 2; 60 | const uint16_t DATABASE_OFFSET_COUNT = 3; 61 | 62 | // LED 63 | // ========================================== 64 | // LED Mode 65 | const uint16_t LED_MODE_OFF = 1; 66 | const uint16_t LED_MODE_ON = 2; 67 | const uint16_t LED_MODE_BLINK = 3; 68 | const uint16_t LED_MODE_STATE_COUNT = LED_MODE_BLINK; 69 | 70 | // Globls 71 | // ========================================== 72 | WiFiClient gClient; 73 | WiFiServer gServer; 74 | 75 | // Callback functions 76 | // ========================================== 77 | bool sendModbusMessage(const unsigned short connectionId, const unsigned char* payload, const unsigned short payloadSize); 78 | unsigned int recvModbusMessage(unsigned short& connectionId, unsigned char* payload, unsigned short maxPayloadSize); 79 | unsigned long currentTime(); 80 | bool setModbusValue(const unsigned char slaveAddress, const unsigned char function, const unsigned short startingAddress, const unsigned short length, const unsigned char* data, const unsigned short dataSize, unsigned char* errorCode); 81 | bool getModbusValue(const unsigned char slaveAddress, const unsigned char function, const unsigned short startingAddress, const unsigned short length, unsigned char* data, const unsigned short maxPayloadSize, unsigned char* errorCode); 82 | 83 | void setup() 84 | { 85 | // Hardware setup 86 | // ========================================== 87 | // Set the digital pin as output: 88 | pinMode(APPLICATION_LED_PIN, OUTPUT); 89 | Serial.begin(APPLICATION_SERIAL_BAUD_RATE); 90 | delay(3000); // Let the Serial port connect, and give us some time to reprogram if needed. 91 | 92 | Serial.print("FYI: ESP32 CAS Modbus Stack example version: "); 93 | Serial.print(APPLICATION_VERSION_MAJOR); 94 | Serial.print("."); 95 | Serial.print(APPLICATION_VERSION_MINOR); 96 | Serial.print("."); 97 | Serial.println(APPLICATION_VERSION_PATCH); 98 | uint64_t chipid = ESP.getEfuseMac(); // The chip ID is essentially its MAC address(length: 6 bytes). 99 | Serial.printf("FYI: ESP32 Chip ID: %04X, (%08X)\n", (uint16_t)(chipid >> 32), (uint32_t)chipid); 100 | 101 | // WiFi connection 102 | // ========================================== 103 | Serial.println("FYI: Connecting to wifi..."); 104 | WiFi.begin(APPLICATION_WIFI_SSID, APPLICATION_WIFI_PASSWORD); 105 | 106 | // Wait for connection 107 | while (WiFi.status() != WL_CONNECTED) { 108 | delay(500); 109 | Serial.print("."); 110 | 111 | if (millis() > 1000 * 30) { 112 | Serial.print("\n\nError: Can not connect to the WiFi. Restarting and trying again\n\n"); 113 | ESP.restart(); 114 | return; 115 | } 116 | } 117 | Serial.println(""); 118 | Serial.print("FYI: Connected to "); 119 | Serial.println(APPLICATION_WIFI_SSID); 120 | Serial.print("IP address: "); 121 | Serial.println(WiFi.localIP()); 122 | Serial.print("Subnet mask: "); 123 | Serial.println(WiFi.subnetMask()); 124 | 125 | // Set up the TCP stack 126 | gServer = WiFiServer(APPLICATION_MODBUS_TCP_PORT); 127 | gServer.begin(); 128 | delay(1000); // Wait a second for the port to be fully setup. 129 | 130 | // Database defaults 131 | gDatabase[DATABASE_OFFSET_LED_MODE] = LED_MODE_BLINK; 132 | gDatabase[DATABASE_OFFSET_LED_BLINK_SPEED] = APPLICATION_LED_BLINK_RATE_MS; 133 | gDatabase[DATABASE_OFFSET_LED_STATE] = LOW; 134 | gDatabase[DATABASE_OFFSET_COUNT] = 0; 135 | 136 | // Set up the CAS BACnet stack. 137 | // ========================================== 138 | LoadModbusFunctions(); 139 | Serial.print("FYI: CAS Modbus Stack version: "); 140 | Serial.print(fpModbusStack_GetAPIMajorVersion()); 141 | Serial.print("."); 142 | Serial.print(fpModbusStack_GetAPIMinorVersion()); 143 | Serial.print("."); 144 | Serial.println(fpModbusStack_GetAPIPatchVersion()); 145 | Serial.print("."); 146 | Serial.println(fpModbusStack_GetAPIBuildVersion()); 147 | 148 | fpModbusStack_Init(MODBUS_TYPE_TCP, sendModbusMessage, recvModbusMessage, currentTime); 149 | fpModbusStack_SetSlaveId(APPLICATION_MODBUS_SLAVE_ADDRESS); 150 | fpModbusStack_RegisterGetValue(getModbusValue); 151 | fpModbusStack_RegisterSetValue(setModbusValue); 152 | } 153 | 154 | void loop() 155 | { 156 | // Modbus stack loop 157 | int modbusStackStatus = fpModbusStack_Loop(); 158 | if (modbusStackStatus != 1) { 159 | Serial.printf("modbusStackStatus=%d\n", modbusStackStatus); 160 | } 161 | 162 | unsigned long currentMillis = millis(); 163 | 164 | static unsigned long nextMemoryCheck = 0; 165 | if (nextMemoryCheck < currentMillis) { 166 | nextMemoryCheck = currentMillis + 30000; 167 | Serial.print("FYI: FreeHeap: "); 168 | Serial.print(ESP.getFreeHeap()); 169 | Serial.print(" / "); 170 | Serial.print(ESP.getHeapSize()); 171 | Serial.print(" ("); 172 | Serial.print(((float)ESP.getFreeHeap() / (float)ESP.getHeapSize()) * 100.0f); 173 | Serial.println(" %)"); 174 | } 175 | 176 | static unsigned long nextCountUpdate = 0; 177 | if (nextCountUpdate < currentMillis) { 178 | nextCountUpdate = currentMillis + 1000; 179 | gDatabase[DATABASE_OFFSET_COUNT]++; 180 | } 181 | 182 | switch (gDatabase[DATABASE_OFFSET_LED_MODE]) { 183 | default: 184 | case LED_MODE_OFF: 185 | if (gDatabase[DATABASE_OFFSET_LED_STATE] != LOW) { 186 | gDatabase[DATABASE_OFFSET_LED_STATE] = LOW; 187 | digitalWrite(APPLICATION_LED_PIN, gDatabase[DATABASE_OFFSET_LED_STATE]); 188 | } 189 | break; 190 | case LED_MODE_ON: 191 | if (gDatabase[DATABASE_OFFSET_LED_STATE] != HIGH) { 192 | gDatabase[DATABASE_OFFSET_LED_STATE] = HIGH; 193 | digitalWrite(APPLICATION_LED_PIN, gDatabase[DATABASE_OFFSET_LED_STATE]); 194 | } 195 | break; 196 | case LED_MODE_BLINK: 197 | // check to see if it's time to blink the LED; that is, if the difference 198 | // between the current time and last time you blinked the LED is bigger than 199 | // the interval at which you want to blink the LED. 200 | static long nextBlink = 0; 201 | if (nextBlink < currentMillis) { 202 | // save the last time you blinked the LED 203 | nextBlink = currentMillis + gDatabase[DATABASE_OFFSET_LED_BLINK_SPEED]; 204 | 205 | // if the LED is off turn it on and vice-versa: 206 | if (gDatabase[DATABASE_OFFSET_LED_STATE] == LOW) { 207 | gDatabase[DATABASE_OFFSET_LED_STATE] = HIGH; 208 | Serial.print("+"); 209 | } else { 210 | gDatabase[DATABASE_OFFSET_LED_STATE] = LOW; 211 | Serial.print("-"); 212 | } 213 | 214 | // set the LED with the ledState of the variable: 215 | digitalWrite(APPLICATION_LED_PIN, gDatabase[DATABASE_OFFSET_LED_STATE]); 216 | } 217 | break; 218 | } 219 | } 220 | 221 | // Callback functions 222 | // =========================== 223 | bool sendModbusMessage(const unsigned short connectionId, const unsigned char* payload, const unsigned short payloadSize) 224 | { 225 | if (payload == NULL || payloadSize <= 0) { 226 | return false; // Nothing to do. 227 | } 228 | 229 | if (!gClient) { 230 | Serial.println("Error: No client, Can not send message"); 231 | return false; 232 | } 233 | if (!gClient.connected()) { 234 | Serial.println("Error: Not connected to a client, Can not send message"); 235 | return false; 236 | } 237 | 238 | gClient.write(payload, payloadSize); 239 | Serial.printf("FYI: sendModbusMessage bytes=[%d] \n", payloadSize); 240 | // Serial.printf("Message first 12 bytes: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X ... \n", payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6], payload[7], payload[8], payload[9], payload[10], payload[11]); 241 | return true; 242 | } 243 | unsigned int recvModbusMessage(unsigned short& connectionId, unsigned char* payload, unsigned short maxPayloadSize) 244 | { 245 | if (payload == NULL || maxPayloadSize <= 0) { 246 | return 0; // Nothing to do. 247 | } 248 | 249 | if (!gClient) { 250 | gClient = gServer.available(); 251 | if (!gClient.connected()) { 252 | return 0; 253 | } 254 | Serial.printf("FYI: New TCP connection\n"); 255 | Serial.print("FYI: RemoteIP: "); 256 | Serial.print(gClient.remoteIP()); 257 | Serial.print(", Port: "); 258 | Serial.println(gClient.remotePort()); 259 | } 260 | if (!gClient.connected()) { 261 | Serial.printf("FYI: Disconnected TCP connection\n"); 262 | return 0; 263 | } 264 | 265 | if (gClient.available() > maxPayloadSize) { 266 | // Too many bytes for our buffer 267 | gClient.flush(); 268 | return false; 269 | } 270 | 271 | size_t recved = gClient.readBytes(payload, maxPayloadSize); 272 | if (recved > 0) { 273 | Serial.printf("FYI: recvModbusMessage bytes=[%d] \n", recved); 274 | // Serial.printf("Message first 12 bytes: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X ... \n", payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6], payload[7], payload[8], payload[9], payload[10], payload[11]); 275 | } 276 | return recved; 277 | } 278 | unsigned long currentTime() 279 | { 280 | return millis() / 1000; 281 | } 282 | bool setModbusValue(const unsigned char slaveAddress, const unsigned char function, const unsigned short startingAddress, const unsigned short length, const unsigned char* data, const unsigned short dataSize, unsigned char* errorCode) 283 | { 284 | Serial.printf("FYI: setModbusValue slaveAddress=[%d], function=[%d], startingAddress=[%d], length=[%d]\n", slaveAddress, function, startingAddress, length); 285 | 286 | if (function == MODBUS_FUNCTION_06_WRITE_SINGLE_REGISTERS && startingAddress + length < DATABASE_SIZE) { 287 | memcpy(((uint8_t*)gDatabase) + startingAddress * sizeof(uint16_t), data, length * sizeof(uint16_t)); 288 | for (uint16_t offset = 0; offset < length; offset++) { 289 | Serial.printf(" %d = %d (0x%04X)\n", 40001 + startingAddress + offset, gDatabase[startingAddress + offset], gDatabase[startingAddress + offset]); 290 | } 291 | return true; 292 | } 293 | 294 | Serial.println("Error: Value not written. Malformed request"); 295 | return false; 296 | } 297 | bool getModbusValue(const unsigned char slaveAddress, const unsigned char function, const unsigned short startingAddress, const unsigned short length, unsigned char* data, const unsigned short maxPayloadSize, unsigned char* errorCode) 298 | { 299 | Serial.printf("FYI: getModbusValue slaveAddress=[%d], function=[%d], startingAddress=[%d], length=[%d]\n", slaveAddress, function, startingAddress, length); 300 | 301 | if (function == MODBUS_FUNCTION_03_HOLDING_REGISTERS && startingAddress + length < DATABASE_SIZE) { 302 | memcpy(data, ((uint8_t*)gDatabase) + startingAddress * sizeof(uint16_t), length * sizeof(uint16_t)); 303 | for (uint16_t offset = 0; offset < length; offset++) { 304 | Serial.printf(" %d = %d (0x%04X)\n", 40001 + startingAddress + offset, gDatabase[startingAddress + offset], gDatabase[startingAddress + offset]); 305 | } 306 | return true; 307 | } 308 | 309 | Serial.println("Error: Read request not supported. Malformed request"); 310 | return false; 311 | } --------------------------------------------------------------------------------