├── LICENSE.txt ├── .gitignore ├── tests ├── README.md ├── common.h ├── tests.ino ├── read.h └── write.h ├── library.properties ├── examples ├── IP-bridge │ └── IP-bridge.ino ├── RTU-slave │ └── RTU-slave.ino ├── IP-server-HoldingReg │ └── IP-server-HoldingReg.ino ├── IP-server-SwitchStatus │ └── IP-server-SwitchStatus.ino ├── IP-server-Led │ └── IP-server-Led.ino ├── IP-server-AnalogInput │ └── IP-server-AnalogInput.ino ├── RTU-master │ └── RTU-Master.ino ├── IP-client-SimpleRead │ └── IP-client-SimpleRead.ino ├── IP-server-Callback │ └── IP-server-Callback.ino ├── IP-client-WriteMultiple │ └── IP-client-WriteMultiple.ino ├── IP-client-Pull │ └── IP-client-Pull.ino ├── IP-server-MultipleHRegDebug │ └── IP-server-MultipleHRegDebug.ino └── IP-server-MassOperations │ └── IP-server-MassOperations.ino ├── keywords.txt ├── src ├── ModbusRTU.h ├── ModbusIP_ESP.h ├── Modbus.h ├── ModbusRTU.cpp ├── ModbusIP_ESP.cpp └── Modbus.cpp ├── README.md └── API.md /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdtechstep/modbus-esp/HEAD/LICENSE.txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/arduino.json 2 | .vscode/c_cpp_properties.json 3 | .vscode/* 4 | src/ModbusSerial.cpp 5 | src/ModbusSerial.h 6 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Modbus RTU tests 2 | 3 | There are not autotests. Just sketch executing Master and Slave on single ESP device and run Modbus calls with checking results. 4 | 5 | ## Required libraries 6 | [StreamBuf](https://github.com/emelianov/StreamBuf) -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=modbus-esp 2 | version=3.0.1 3 | author=Andre Sarmento Barbosa, Alexander Emelianov 4 | maintainer=Alexander Emelianov 5 | sentence=ModbusRTU and ModbusIP Library for ESP8266/ESP32 6 | paragraph=This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol used in industrial automation and can be used in other areas, such as home automation. 7 | category=Communication 8 | url=https://github.com/emelianov/modbus-esp8266 9 | architectures=esp8266,esp32 10 | -------------------------------------------------------------------------------- /examples/IP-bridge/IP-bridge.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus ESP8266/ESP32 3 | Simple ModbesRTU to ModbusIP bridge 4 | 5 | (c)2020 Alexander Emelianov (a.m.emelianov@gmail.com) 6 | https://github.com/emelianov/modbus-esp8266 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #define TO_REG 10 13 | #define SLAVE_ID 1 14 | #define PULL_ID 2 15 | #define FROM_REG 20 16 | 17 | ModbusRTU mb1; 18 | ModbusIP mb2; 19 | 20 | void setup() { 21 | Serial1.begin(9600, SERIAL_8N1); 22 | mb1.begin(&Serial1); 23 | mb1.master(); 24 | mb2.server(); 25 | mb2.addHreg(TO_REG); 26 | } 27 | 28 | void loop() { 29 | if(!mb1.slave()) 30 | mb1.pullHreg(PULL_ID, FROM_REG, TO_REG); 31 | mb1.task(); 32 | mb2.task(); 33 | delay(50); 34 | } 35 | -------------------------------------------------------------------------------- /examples/RTU-slave/RTU-slave.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusRTU ESP8266/ESP32 3 | Simple slave example 4 | 5 | (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) 6 | https://github.com/emelianov/modbus-esp8266 7 | 8 | modified 13 May 2020 9 | by brainelectronics 10 | 11 | This code is licensed under the BSD New License. See LICENSE.txt for more info. 12 | */ 13 | 14 | #include 15 | 16 | #define REGN 10 17 | #define SLAVE_ID 1 18 | 19 | ModbusRTU mb; 20 | 21 | void setup() { 22 | Serial.begin(9600, SERIAL_8N1); 23 | #if defined(ESP32) || defined(ESP8266) 24 | mb.begin(&Serial); 25 | #else 26 | mb.begin(&Serial); 27 | mb.setBaudrate(9600); 28 | #endif 29 | mb.slave(SLAVE_ID); 30 | mb.addHreg(REGN); 31 | mb.Hreg(REGN, 100); 32 | } 33 | 34 | void loop() { 35 | mb.task(); 36 | yield(); 37 | } -------------------------------------------------------------------------------- /tests/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus Library for ESP8266/ESP32 3 | Functional tests 4 | Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) 5 | https://github.com/emelianov/modbus-esp8266 6 | This code is licensed under the BSD New License. See LICENSE.txt for more info. 7 | */ 8 | 9 | #pragma once 10 | #include 11 | 12 | #define BSIZE 1024 13 | 14 | uint8_t buf1[BSIZE]; 15 | uint8_t buf2[BSIZE]; 16 | 17 | StreamBuf S1(buf1, BSIZE); 18 | StreamBuf S2(buf2, BSIZE); 19 | DuplexBuf D1(&S1, &S2); 20 | DuplexBuf D2(&S2, &S1); 21 | 22 | ModbusRTU master; 23 | ModbusRTU slave; 24 | 25 | bool result; 26 | uint8_t code ; 27 | 28 | bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { 29 | //Serial.printf_P(" 0x%02X ", event); 30 | //if (event == 0x00) { 31 | code = event; 32 | result = true; 33 | return true; 34 | } 35 | 36 | uint8_t wait() { 37 | result = false; 38 | code = 0; 39 | while (!result) { 40 | master.task(); 41 | slave.task(); 42 | yield(); 43 | } 44 | Serial.printf_P(" 0x%02X", code); 45 | return code; 46 | } 47 | -------------------------------------------------------------------------------- /examples/IP-server-HoldingReg/IP-server-HoldingReg.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) 3 | Configure Holding Register (offset 100) with initial value 0xABCD 4 | You can get or set this holding register 5 | Original library 6 | Copyright by André Sarmento Barbosa 7 | http://github.com/andresarmento/modbus-arduino 8 | 9 | Current version 10 | (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) 11 | https://github.com/emelianov/modbus-esp8266 12 | */ 13 | 14 | #ifdef ESP8266 15 | #include 16 | #else //ESP32 17 | #include 18 | #endif 19 | #include 20 | 21 | // Modbus Registers Offsets 22 | const int TEST_HREG = 100; 23 | 24 | 25 | //ModbusIP object 26 | ModbusIP mb; 27 | 28 | void setup() { 29 | Serial.begin(115200); 30 | 31 | WiFi.begin("your_ssid", "your_password"); 32 | 33 | while (WiFi.status() != WL_CONNECTED) { 34 | delay(500); 35 | Serial.print("."); 36 | } 37 | 38 | Serial.println(""); 39 | Serial.println("WiFi connected"); 40 | Serial.println("IP address: "); 41 | Serial.println(WiFi.localIP()); 42 | 43 | mb.server(); 44 | mb.addHreg(TEST_HREG, 0xABCD); 45 | } 46 | 47 | void loop() { 48 | //Call once inside loop() - all magic here 49 | mb.task(); 50 | delay(10); 51 | } 52 | -------------------------------------------------------------------------------- /examples/IP-server-SwitchStatus/IP-server-SwitchStatus.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266) 3 | Read Switch Status on pin GPIO0 4 | Original library 5 | Copyright by André Sarmento Barbosa 6 | http://github.com/andresarmento/modbus-arduino 7 | 8 | Current version 9 | (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) 10 | https://github.com/emelianov/modbus-esp8266 11 | */ 12 | 13 | #ifdef ESP8266 14 | #include 15 | #else //ESP32 16 | #include 17 | #endif 18 | #include 19 | 20 | //Modbus Registers Offsets 21 | const int SWITCH_ISTS = 100; 22 | //Used Pins 23 | const int switchPin = 0; //GPIO0 24 | 25 | //ModbusIP object 26 | ModbusIP mb; 27 | 28 | void setup() { 29 | Serial.begin(115200); 30 | 31 | WiFi.begin("your_ssid", "your_password"); 32 | while (WiFi.status() != WL_CONNECTED) { 33 | delay(500); 34 | Serial.print("."); 35 | } 36 | //Config Modbus IP 37 | mb.server(); 38 | //Set ledPin mode 39 | pinMode(switchPin, INPUT); 40 | // Add SWITCH_ISTS register - Use addIsts() for digital inputs 41 | mb.addIsts(SWITCH_ISTS); 42 | } 43 | 44 | void loop() { 45 | //Call once inside loop() - all magic here 46 | mb.task(); 47 | 48 | //Attach switchPin to SWITCH_ISTS register 49 | mb.Ists(SWITCH_ISTS, digitalRead(switchPin)); 50 | delay(10); 51 | } 52 | -------------------------------------------------------------------------------- /examples/IP-server-Led/IP-server-Led.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Test Led (Modbus IP ESP8266) 3 | Control a Led on GPIO0 pin using Write Single Coil Modbus Function 4 | Original library 5 | Copyright by André Sarmento Barbosa 6 | http://github.com/andresarmento/modbus-arduino 7 | 8 | Current version 9 | (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) 10 | https://github.com/emelianov/modbus-esp8266 11 | */ 12 | 13 | #ifdef ESP8266 14 | #include 15 | #else //ESP32 16 | #include 17 | #endif 18 | #include 19 | 20 | //Modbus Registers Offsets 21 | const int LED_COIL = 100; 22 | //Used Pins 23 | const int ledPin = 0; //GPIO0 24 | 25 | //ModbusIP object 26 | ModbusIP mb; 27 | 28 | void setup() { 29 | Serial.begin(115200); 30 | 31 | WiFi.begin("your_ssid", "your_password"); 32 | 33 | while (WiFi.status() != WL_CONNECTED) { 34 | delay(500); 35 | Serial.print("."); 36 | } 37 | 38 | Serial.println(""); 39 | Serial.println("WiFi connected"); 40 | Serial.println("IP address: "); 41 | Serial.println(WiFi.localIP()); 42 | 43 | mb.server(); 44 | 45 | pinMode(ledPin, OUTPUT); 46 | mb.addCoil(LED_COIL); 47 | } 48 | 49 | void loop() { 50 | //Call once inside loop() - all magic here 51 | mb.task(); 52 | 53 | //Attach ledPin to LED_COIL register 54 | digitalWrite(ledPin, mb.Coil(LED_COIL)); 55 | delay(10); 56 | } 57 | -------------------------------------------------------------------------------- /examples/IP-server-AnalogInput/IP-server-AnalogInput.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Test Analog Input (Modbus IP ESP8266) 3 | Read Analog sensor on Pin ADC (ADC input between 0 ... 1V) 4 | Original library 5 | Copyright by André Sarmento Barbosa 6 | http://github.com/andresarmento/modbus-arduino 7 | 8 | Current version 9 | (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) 10 | https://github.com/emelianov/modbus-esp8266 11 | */ 12 | 13 | #ifdef ESP8266 14 | #include 15 | #else //ESP32 16 | #include 17 | #endif 18 | #include 19 | 20 | //Modbus Registers Offsets 21 | const int SENSOR_IREG = 100; 22 | 23 | //ModbusIP object 24 | ModbusIP mb; 25 | 26 | long ts; 27 | 28 | void setup() { 29 | Serial.begin(115200); 30 | 31 | WiFi.begin("your_ssid", "your_password"); 32 | while (WiFi.status() != WL_CONNECTED) { 33 | delay(500); 34 | Serial.print("."); 35 | } 36 | 37 | Serial.println(""); 38 | Serial.println("WiFi connected"); 39 | Serial.println("IP address: "); 40 | Serial.println(WiFi.localIP()); 41 | 42 | mb.server(); //Start Modbus IP 43 | // Add SENSOR_IREG register - Use addIreg() for analog Inputs 44 | mb.addIreg(SENSOR_IREG); 45 | 46 | ts = millis(); 47 | } 48 | 49 | void loop() { 50 | //Call once inside loop() - all magic here 51 | mb.task(); 52 | 53 | //Read each two seconds 54 | if (millis() > ts + 2000) { 55 | ts = millis(); 56 | //Setting raw value (0-1024) 57 | mb.Ireg(SENSOR_IREG, analogRead(A0)); 58 | } 59 | delay(10); 60 | } 61 | -------------------------------------------------------------------------------- /examples/RTU-master/RTU-Master.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusRTU ESP8266/ESP32 3 | Read multiple coils from slave device example 4 | 5 | (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) 6 | https://github.com/emelianov/modbus-esp8266 7 | 8 | modified 13 May 2020 9 | by brainelectronics 10 | 11 | This code is licensed under the BSD New License. See LICENSE.txt for more info. 12 | */ 13 | 14 | #include 15 | #if defined(ESP8266) 16 | #include 17 | // SoftwareSerial S(D1, D2, false, 256); 18 | 19 | // receivePin, transmitPin, inverse_logic, bufSize, isrBufSize 20 | // connect RX to D2 (GPIO4, Arduino pin 4), TX to D1 (GPIO5, Arduino pin 4) 21 | SoftwareSerial S(4, 5); 22 | #endif 23 | 24 | ModbusRTU mb; 25 | 26 | bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) { 27 | #ifdef ESP8266 28 | Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap()); 29 | #elif ESP32 30 | Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap()); 31 | #else 32 | Serial.print("Request result: 0x"); 33 | Serial.print(event, HEX); 34 | #endif 35 | return true; 36 | } 37 | 38 | void setup() { 39 | Serial.begin(115200); 40 | #if defined(ESP8266) 41 | S.begin(9600, SWSERIAL_8N1); 42 | mb.begin(&S); 43 | #elif defined(ESP32) 44 | Serial1.begin(9600, SERIAL_8N1); 45 | mb.begin(&Serial1); 46 | #else 47 | Serial1.begin(9600, SERIAL_8N1); 48 | mb.begin(&Serial1); 49 | mb.setBaudrate(9600); 50 | #endif 51 | mb.master(); 52 | } 53 | 54 | bool coils[20]; 55 | 56 | void loop() { 57 | if (!mb.slave()) { 58 | mb.readCoil(1, 1, coils, 20, cbWrite); 59 | } 60 | mb.task(); 61 | yield(); 62 | } -------------------------------------------------------------------------------- /examples/IP-client-SimpleRead/IP-client-SimpleRead.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Master Modbus IP Client (ESP8266/ESP32) 3 | Read Holding Register from Server device 4 | 5 | (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) 6 | https://github.com/emelianov/modbus-esp8266 7 | */ 8 | 9 | #ifdef ESP8266 10 | #include 11 | #else 12 | #include 13 | #endif 14 | #include 15 | 16 | const int REG = 528; // Modbus Hreg Offset 17 | IPAddress remote(192, 168, 30, 13); // Address of Modbus Slave device 18 | const int LOOP_COUNT = 10; 19 | 20 | ModbusIP mb; //ModbusIP object 21 | 22 | void setup() { 23 | Serial.begin(115200); 24 | 25 | WiFi.begin("SSID", "PASSWORD"); 26 | 27 | while (WiFi.status() != WL_CONNECTED) { 28 | delay(500); 29 | Serial.print("."); 30 | } 31 | 32 | Serial.println(""); 33 | Serial.println("WiFi connected"); 34 | Serial.println("IP address: "); 35 | Serial.println(WiFi.localIP()); 36 | 37 | mb.client(); 38 | } 39 | 40 | uint16_t res = 0; 41 | uint8_t show = LOOP_COUNT; 42 | 43 | void loop() { 44 | if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established 45 | mb.readHreg(remote, REG, &res); // Initiate Read Coil from Modbus Slave 46 | } else { 47 | mb.connect(remote); // Try to connect if no connection 48 | } 49 | mb.task(); // Common local Modbus task 50 | delay(100); // Pulling interval 51 | if (!show--) { // Display Slave register value one time per second (with default settings) 52 | Serial.println(res); 53 | show = LOOP_COUNT; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/IP-server-Callback/IP-server-Callback.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Test Led using callback (Modbus IP ESP8266/ESP32) 3 | Control a Led on D4 pin using Write Single Coil Modbus Function 4 | Original library 5 | Copyright by André Sarmento Barbosa 6 | http://github.com/andresarmento/modbus-arduino 7 | 8 | Current version 9 | (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com) 10 | https://github.com/emelianov/modbus-esp8266 11 | */ 12 | 13 | #ifdef ESP8266 14 | #include 15 | #else //ESP32 16 | #include 17 | #endif 18 | #include 19 | 20 | //Modbus Registers Offsets 21 | const int LED_COIL = 100; 22 | //Used Pins 23 | #ifdef ESP8266 24 | const int ledPin = D4; // Builtin ESP8266 LED 25 | #else 26 | const int ledPin = TX; // ESP32 TX LED 27 | #endif 28 | //ModbusIP object 29 | ModbusIP mb; 30 | 31 | // Callback function for write (set) Coil. Returns value to store. 32 | uint16_t cbLed(TRegister* reg, uint16_t val) { 33 | //Attach ledPin to LED_COIL register 34 | digitalWrite(ledPin, COIL_BOOL(val)); 35 | return val; 36 | } 37 | 38 | // Callback function for client connect. Returns true to allow connection. 39 | bool cbConn(IPAddress ip) { 40 | Serial.println(ip); 41 | return true; 42 | } 43 | 44 | void setup() { 45 | Serial.begin(115200); 46 | 47 | WiFi.begin("SID", "PASSWORD"); 48 | 49 | while (WiFi.status() != WL_CONNECTED) { 50 | delay(500); 51 | Serial.print("."); 52 | } 53 | 54 | Serial.println(""); 55 | Serial.println("WiFi connected"); 56 | Serial.print("IP address: "); 57 | Serial.println(WiFi.localIP()); 58 | 59 | mb.onConnect(cbConn); // Add callback on connection event 60 | mb.server(); 61 | 62 | pinMode(ledPin, OUTPUT); 63 | mb.addCoil(LED_COIL); // Add Coil. The same as mb.addCoil(COIL_BASE, false, LEN) 64 | mb.onSetCoil(LED_COIL, cbLed); // Add callback on Coil LED_COIL value set 65 | } 66 | 67 | void loop() { 68 | //Call once inside loop() - all magic here 69 | mb.task(); 70 | delay(10); 71 | } 72 | -------------------------------------------------------------------------------- /examples/IP-client-WriteMultiple/IP-client-WriteMultiple.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) 3 | Write multiple coils to Slave device 4 | 5 | (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com) 6 | https://github.com/emelianov/modbus-esp8266 7 | */ 8 | 9 | #ifdef ESP8266 10 | #include 11 | #else 12 | #include 13 | #endif 14 | #include 15 | 16 | const int REG = 100; // Modbus Coils Offset 17 | const int COUNT = 5; // Count of Coils 18 | IPAddress remote(192, 168, 20, 102); // Address of Modbus Slave device 19 | 20 | ModbusIP mb; // ModbusIP object 21 | 22 | void setup() { 23 | Serial.begin(115200); 24 | 25 | WiFi.begin(); 26 | 27 | while (WiFi.status() != WL_CONNECTED) { 28 | delay(500); 29 | Serial.print("."); 30 | } 31 | 32 | Serial.println(""); 33 | Serial.println("WiFi connected"); 34 | Serial.println("IP address: "); 35 | Serial.println(WiFi.localIP()); 36 | 37 | mb.client(); 38 | } 39 | 40 | bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback 41 | if (event != Modbus::EX_SUCCESS) // If transaction got an error 42 | Serial.printf("Modbus result: %02X\n", event); // Display Modbus error code 43 | if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place 44 | mb.disconnect(remote); // Close connection to slave and 45 | mb.dropTransactions(); // Cancel all waiting transactions 46 | } 47 | return true; 48 | } 49 | 50 | bool res[COUNT] = {false, true, false, true, true}; 51 | 52 | void loop() { 53 | if (!mb.isConnected(remote)) { // Check if connection to Modbus Slave is established 54 | mb.connect(remote); // Try to connect if no connection 55 | Serial.print("."); 56 | } 57 | if (!mb.writeCoil(remote, REG, res, COUNT, cb)) // Try to Write array of COUNT of Coils to Modbus Slave 58 | Serial.print("#"); 59 | mb.task(); // Modbus task 60 | delay(50); // Pushing interval 61 | } 62 | -------------------------------------------------------------------------------- /examples/IP-client-Pull/IP-client-Pull.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Modbus IP Client (ESP8266/ESP32) 3 | Control Led on D4/TX pin by remote Modbus device using Read Single Coil Modbus Function 4 | 5 | (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) 6 | https://github.com/emelianov/modbus-esp8266 7 | */ 8 | 9 | #ifdef ESP8266 10 | #include 11 | #else 12 | #include 13 | #endif 14 | #include 15 | 16 | 17 | const int LED_COIL = 1; // Modbus Coil Offset 18 | IPAddress remote(192, 168, 30, 116); // Address of Modbus Slave device 19 | 20 | //Used Pins 21 | #ifdef ESP8266 22 | #define USE_LED D4 23 | #else 24 | #define UES_LED TX 25 | #endif 26 | 27 | ModbusIP mb; //ModbusIP object 28 | 29 | uint16_t gc(TRegister* r, uint16_t v) { // Callback function 30 | if (r->value != v) { // Check if Coil state is going to be changed 31 | Serial.print("Set reg: "); 32 | Serial.println(v); 33 | if (COIL_BOOL(v)) { 34 | digitalWrite(USE_LED, LOW); 35 | } else { 36 | digitalWrite(USE_LED, HIGH); 37 | } 38 | } 39 | return v; 40 | } 41 | 42 | void setup() { 43 | Serial.begin(115200); 44 | 45 | WiFi.begin("SSID", "password"); 46 | 47 | while (WiFi.status() != WL_CONNECTED) { 48 | delay(500); 49 | Serial.print("."); 50 | } 51 | 52 | Serial.println(""); 53 | Serial.println("WiFi connected"); 54 | Serial.println("IP address: "); 55 | Serial.println(WiFi.localIP()); 56 | 57 | mb.client(); // Initialize local Modbus Client 58 | pinMode(USE_LED, OUTPUT); 59 | mb.addCoil(LED_COIL); // Add Coil 60 | mb.onSetCoil(LED_COIL, gc); // Assign Callback on set the Coil 61 | } 62 | 63 | void loop() { 64 | if (mb.isConnected(remote)) { // Check if connection to Modbus Slave is established 65 | mb.pullCoil(remote, LED_COIL, LED_COIL); // Initiate Read Coil from Modbus Slave 66 | } else { 67 | mb.connect(remote); // Try to connect if no connection 68 | } 69 | mb.task(); // Common local Modbus task 70 | delay(10); // Polling interval 71 | } 72 | -------------------------------------------------------------------------------- /tests/tests.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common.h" 4 | #include "write.h" 5 | #include "read.h" 6 | 7 | 8 | uint8_t stage = 0; 9 | uint16_t readHreg = 0; 10 | 11 | #define SLAVE_ID 1 12 | #define HREG_ID 10 13 | #define HREG_VALUE 100 14 | 15 | #define HREGS_ID 20 16 | #define HREGS_COUNT 20 17 | 18 | void setup() { 19 | Serial.begin(115200); 20 | master.begin(&D1); 21 | master.master(); 22 | slave.begin(&D2); 23 | slave.slave(SLAVE_ID); 24 | slave.addHreg(HREG_ID); 25 | 26 | writeSingle(SLAVE_ID, HREG(HREG_ID), HREG_VALUE); 27 | writeSingle(SLAVE_ID, COIL(HREG_ID), true); 28 | 29 | writeMultiple(SLAVE_ID, HREG(HREG_ID), 10); 30 | writeMultiple(SLAVE_ID, COIL(HREG_ID), 10); 31 | 32 | readMultiple(SLAVE_ID, HREG(HREG_ID), 10); 33 | readMultiple(SLAVE_ID, COIL(HREG_ID), 10); 34 | readMultiple(SLAVE_ID, IREG(HREG_ID), 10); 35 | readMultiple(SLAVE_ID, ISTS(HREG_ID), 10); 36 | 37 | // Garbage read 38 | { 39 | bool Node_1_ackStatus = false; 40 | bool Node_2_ackStatus = false; 41 | slave.addIsts(100, true); 42 | slave.addIsts(101, true); 43 | Serial.print("Write garbage: "); 44 | if (!master.slave()) { 45 | master.readIsts(2, 100, &Node_1_ackStatus, 1, NULL); 46 | while (master.slave()) { 47 | master.task(); 48 | slave.task(); 49 | delay(1); 50 | } 51 | master.readIsts(SLAVE_ID, 100, &Node_1_ackStatus, 1, NULL); 52 | while (master.slave()) { 53 | master.task(); 54 | slave.task(); 55 | delay(1); 56 | } 57 | master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); 58 | while (master.slave()) { 59 | master.task(); 60 | while(D2.available()) 61 | D2.write(D2.read()); 62 | //slave.task(); 63 | delay(1); 64 | } 65 | master.readIsts(SLAVE_ID, 101, &Node_2_ackStatus, 1, NULL); 66 | while (master.slave()) { 67 | master.task(); 68 | slave.task(); 69 | delay(1); 70 | } 71 | } 72 | if (Node_1_ackStatus && Node_2_ackStatus) { 73 | Serial.println(" PASSED"); 74 | } else { 75 | Serial.println(" FAILED"); 76 | } 77 | } 78 | } 79 | 80 | void loop() { 81 | yield(); 82 | } 83 | -------------------------------------------------------------------------------- /examples/IP-server-MultipleHRegDebug/IP-server-MultipleHRegDebug.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Hreg multiple Holding register debug code (Modbus IP ESP8266/ESP32) 3 | 4 | Original library 5 | Copyright by André Sarmento Barbosa 6 | http://github.com/andresarmento/modbus-arduino 7 | 8 | Current version 9 | (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) 10 | https://github.com/emelianov/modbus-esp8266 11 | */ 12 | 13 | #ifdef ESP8266 14 | #include 15 | #else //ESP32 16 | #include 17 | #endif 18 | #include 19 | 20 | #define LEN 10 21 | 22 | //ModbusIP object 23 | ModbusIP mb; 24 | 25 | // Callback function to read corresponding DI 26 | uint16_t cbRead(TRegister* reg, uint16_t val) { 27 | Serial.print("Read. Reg RAW#: "); 28 | Serial.print(reg->address.address); 29 | Serial.print(" Old: "); 30 | Serial.print(reg->value); 31 | Serial.print(" New: "); 32 | Serial.println(val); 33 | return val; 34 | } 35 | // Callback function to write-protect DI 36 | uint16_t cbWrite(TRegister* reg, uint16_t val) { 37 | Serial.print("Write. Reg RAW#: "); 38 | Serial.print(reg->address.address); 39 | Serial.print(" Old: "); 40 | Serial.print(reg->value); 41 | Serial.print(" New: "); 42 | Serial.println(val); 43 | return val; 44 | } 45 | 46 | // Callback function for client connect. Returns true to allow connection. 47 | bool cbConn(IPAddress ip) { 48 | Serial.println(ip); 49 | return true; 50 | } 51 | 52 | void setup() { 53 | Serial.begin(115200); 54 | 55 | WiFi.begin("ssid", "pass"); 56 | 57 | while (WiFi.status() != WL_CONNECTED) { 58 | delay(500); 59 | Serial.print("."); 60 | } 61 | 62 | Serial.println(""); 63 | Serial.println("WiFi connected"); 64 | Serial.print("IP address: "); 65 | Serial.println(WiFi.localIP()); 66 | 67 | mb.onConnect(cbConn); // Add callback on connection event 68 | mb.server(); 69 | 70 | if (!mb.addHreg(0, 0xF0F0, LEN)) Serial.println("Error"); // Add Hregs 71 | mb.onGetHreg(0, cbRead, LEN); // Add callback on Coils value get 72 | mb.onSetHreg(0, cbWrite, LEN); 73 | } 74 | 75 | void loop() { 76 | //Call once inside loop() - all magic here 77 | mb.task(); 78 | delay(10); 79 | } 80 | -------------------------------------------------------------------------------- /examples/IP-server-MassOperations/IP-server-MassOperations.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus-Arduino Example - Publish multiple DI as coils (Modbus IP ESP8266/ESP32) 3 | 4 | Original library 5 | Copyright by André Sarmento Barbosa 6 | http://github.com/andresarmento/modbus-arduino 7 | 8 | Current version 9 | (c)2018 Alexander Emelianov (a.m.emelianov@gmail.com) 10 | https://github.com/emelianov/modbus-esp8266 11 | */ 12 | 13 | #ifdef ESP8266 14 | #include 15 | #else //ESP32 16 | #include 17 | #endif 18 | #include 19 | 20 | //Used Pins 21 | #ifdef ESP8266 22 | uint8_t pinList[] = {D0, D1, D2, D3, D4, D5, D6, D7, D8}; 23 | #else //ESP32 24 | uint8_t pinList[] = {12, 13, 14, 14, 16, 17, 18, 21, 22, 23}; 25 | #endif 26 | #define LEN sizeof(pinList)/sizeof(uint8_t) 27 | #define COIL_BASE 0 28 | //ModbusIP object 29 | ModbusIP mb; 30 | 31 | // Callback function to read corresponding DI 32 | uint16_t cbRead(TRegister* reg, uint16_t val) { 33 | // Checking value of register address which callback is called on. 34 | // See Modbus.h for TRegister and TAddress definition 35 | if(reg->address.address < COIL_BASE) 36 | return 0; 37 | uint8_t offset = reg->address.address - COIL_BASE; 38 | if(offset >= LEN) 39 | return 0; 40 | return COIL_VAL(digitalRead(pinList[offset])); 41 | } 42 | // Callback function to write-protect DI 43 | uint16_t cbWrite(TRegister* reg, uint16_t val) { 44 | return reg->value; 45 | } 46 | 47 | // Callback function for client connect. Returns true to allow connection. 48 | bool cbConn(IPAddress ip) { 49 | Serial.println(ip); 50 | return true; 51 | } 52 | 53 | void setup() { 54 | Serial.begin(115200); 55 | 56 | WiFi.begin("ssid", "password"); 57 | 58 | while (WiFi.status() != WL_CONNECTED) { 59 | delay(500); 60 | Serial.print("."); 61 | } 62 | 63 | Serial.println(""); 64 | Serial.println("WiFi connected"); 65 | Serial.print("IP address: "); 66 | Serial.println(WiFi.localIP()); 67 | for (uint8_t i = 0; i < LEN; i++) 68 | pinMode(pinList[i], INPUT); 69 | mb.onConnect(cbConn); // Add callback on connection event 70 | mb.server(); 71 | 72 | mb.addCoil(COIL_BASE, COIL_VAL(false), LEN); // Add Coils. 73 | mb.onGetCoil(COIL_BASE, cbRead, LEN); // Add single callback for multiple Coils. It will be called for each of these coils value get 74 | mb.onSetCoil(COIL_BASE, cbWrite, LEN); // The same as above just for set value 75 | } 76 | 77 | void loop() { 78 | //Call once inside loop() - all magic here 79 | mb.task(); 80 | delay(10); 81 | } 82 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # Syntax Coloring Map For ModbusIP-ESP 2 | 3 | # Datatypes (KEYWORD1) 4 | ModbusRTU KEYWORD1 5 | ModbusIP KEYWORD1 6 | ModbusIP_ESP8266 KEYWORD1 7 | Modbus KEYWORD1 8 | TRegister KEYWORD1 9 | TTransaction KEYWORD1 10 | TAddress KEYWORD1 11 | ResultCode KEYWORD1 12 | 13 | # Methods and Functions (KEYWORD2) 14 | master KEYWORD2 15 | slave KEYWORD2 16 | client KEYWORD2 17 | server KEYWORD2 18 | task KEYWORD2 19 | onConnect KEYWORD2 20 | onDisconnect KEYWORD2 21 | cbEnable KEYWORD2 22 | cbDisable KEYWORD2 23 | eventSource KEYWORD2 24 | onGetCoil KEYWORD2 25 | onSetCoil KEYWORD2 26 | onGetHreg KEYWORD2 27 | onSetHreg KEYWORD2 28 | onGetIreg KEYWORD2 29 | onSetIreg KEYWORD2 30 | onGetIsts KEYWORD2 31 | onSetIsts KEYWORD2 32 | removeOnGetCoil KEYWORD2 33 | removeOnSetCoil KEYWORD2 34 | removeOnGetHreg KEYWORD2 35 | removeOnSetHreg KEYWORD2 36 | removeOnGetIsts KEYWORD2 37 | removeOnSetIsts KEYWORD2 38 | removeOnGetIreg KEYWORD2 39 | removeOnSetIreg KEYWORD2 40 | addCoil KEYWORD2 41 | addIsts KEYWORD2 42 | addIreg KEYWORD2 43 | addHreg KEYWORD2 44 | Coil KEYWORD2 45 | Ists KEYWORD2 46 | Ireg KEYWORD2 47 | Hreg KEYWORD2 48 | isTransaction KEYWORD2 49 | isConnected KEYWORD2 50 | writeCoil KEYWORD2 51 | readCoil KEYWORD2 52 | writeHreg KEYWORD2 53 | readHreg KEYWORD2 54 | readIsts KEYWORD2 55 | readIreg KEYWORD2 56 | pushCoil KEYWORD2 57 | pullCoil KEYWORD2 58 | pullIsts KEYWORD2 59 | pushHreg KEYWORD2 60 | pullHreg KEYWORD2 61 | pullIreg KEYWORD2 62 | pullHregToIreg KEYWORD2 63 | pullCoilToIsts KEYWORD2 64 | pushIstsToCoil KEYWORD2 65 | pushIregToHreg KEYWORD2 66 | removeHreg KEYWORD2 67 | removeIreg KEYWORD2 68 | removeCoil KEYWORD2 69 | removeIsts KEYWORD2 70 | autoConnect KEYWORD2 71 | disconnect KEYWORD2 72 | dropTransactions KEYWORD2 73 | isCoil KEYWORD2 74 | isHreg KEYWORD2 75 | isIsts KEYWORD2 76 | isIreg KEYWORD2 77 | begin KEYWORD2 78 | setBaudrate KEYWORD2 79 | 80 | # Constants and Macros (LITERAL1) 81 | BIT_VAL LITERAL1 82 | BIT_BOOL LITERAL1 83 | COIL_VAL LITERAL1 84 | COIL_BOOL LITERAL1 85 | ISTS_VAL LITERAL1 86 | ISTS_BOOL LITERAL1 87 | ResultCode LITERAL1 88 | FunctionCode LITERAL1 89 | EX_SUCCESS LITERAL1 90 | EX_ILLEGAL_FUNCTION LITERAL1 91 | EX_ILLEGAL_ADDRESS LITERAL1 92 | EX_ILLEGAL_VALUE LITERAL1 93 | EX_SLAVE_FAILURE LITERAL1 94 | EX_ACKNOWLEDGE LITERAL1 95 | EX_SLAVE_DEVICE_BUSY LITERAL1 96 | EX_MEMORY_PARITY_ERROR LITERAL1 97 | EX_PATH_UNAVAILABLE LITERAL1 98 | EX_DEVICE_FAILED_TO_RESPOND LITERAL1 99 | EX_GENERAL_FAILURE LITERAL1 100 | EX_DATA_MISMACH LITERAL1 101 | EX_UNEXPECTED_RESPONSE LITERAL1 102 | EX_TIMEOUT LITERAL1 103 | EX_CONNECTION_LOST LITERAL1 104 | EX_CANCEL LITERAL1 105 | COIL LITERAL1 106 | HREG LITERAL1 107 | ISTS LITERAL1 108 | IREG LITERAL1 109 | 110 | -------------------------------------------------------------------------------- /tests/read.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | // Single Hreg write 5 | // Multiple read 6 | void readMultiple(uint8_t sl, TAddress reg, uint16_t count = 1, void* value = nullptr) { 7 | Serial.print("Read Multiple "); 8 | bool mem = false; 9 | if (!value) { 10 | if (reg.isHreg() || reg.isIreg()) { 11 | value = malloc(count * sizeof(uint16_t)); 12 | if (!value) { 13 | Serial.println(" FAILED"); 14 | return; 15 | } 16 | for (uint8_t i = 0; i < count; i++) { 17 | ((uint16_t*)value)[i] = i; 18 | } 19 | } else { 20 | value = malloc(count * sizeof(bool)); 21 | if (!value) { 22 | Serial.println(" FAILED"); 23 | return; 24 | } 25 | for (uint8_t i = 0; i < count; i++) { 26 | ((bool*)value)[i] = i % 2; 27 | } 28 | } 29 | mem = true; 30 | } 31 | bool addRes = true; 32 | switch (reg.type) { 33 | case TAddress::HREG: 34 | for (uint8_t i = 0; i < count; i++) { 35 | addRes = addRes && slave.addHreg(reg.address + i, ((uint16_t*)value)[i]); 36 | } 37 | Serial.print("HREG: "); 38 | break; 39 | case TAddress::IREG: 40 | for (uint8_t i = 0; i < count; i++) { 41 | addRes = addRes && slave.addIreg(reg.address + i, ((uint16_t*)value)[i]); 42 | //Serial.print(slave.Ireg(reg.address + i)); Serial.print(" "); 43 | } 44 | Serial.print("IREG: "); 45 | break; 46 | case TAddress::COIL: 47 | for (uint8_t i = 0; i < count; i++) { 48 | addRes = addRes && slave.addCoil(reg.address + i, ((bool*)value)[i]); 49 | } 50 | Serial.print("COIL: "); 51 | break; 52 | case TAddress::ISTS: 53 | for (uint8_t i = 0; i < count; i++) { 54 | addRes = addRes && slave.addIsts(reg.address + i, ((bool*)value)[i]); 55 | } 56 | Serial.print("ISTS: "); 57 | break; 58 | default: 59 | addRes = false; 60 | Serial.println("UNKNOWN"); 61 | return; 62 | } 63 | if (!addRes) { 64 | Serial.println(" SLAVE FAILED"); 65 | return; 66 | } 67 | if (reg.isHreg() || reg.isIreg()) { 68 | for (uint8_t i = 0; i < count; i++) { 69 | ((uint16_t*)value)[i] = 0; 70 | } 71 | } else { 72 | for (uint8_t i = 0; i < count; i++) { 73 | ((bool*)value)[i] = false; 74 | } 75 | } 76 | if (!master.slave()) { 77 | bool res = false; 78 | switch (reg.type) { 79 | case TAddress::HREG: 80 | res = master.readHreg(sl, reg.address, (uint16_t*)value, count, cbWrite); 81 | break; 82 | case TAddress::IREG: 83 | res = master.readIreg(sl, reg.address, (uint16_t*)value, count, cbWrite); 84 | break; 85 | case TAddress::COIL: 86 | res = master.readCoil(sl, reg.address, (bool*)value, count, cbWrite); 87 | break; 88 | case TAddress::ISTS: 89 | res = master.readIsts(sl, reg.address, (bool*)value, count, cbWrite); 90 | break; 91 | } 92 | if (res) { 93 | Serial.print(" SENT "); 94 | if (wait() == Modbus::EX_SUCCESS) { 95 | bool res = true; 96 | switch (reg.type) { 97 | case TAddress::HREG: 98 | for (uint8_t i = 0; i < count; i++) { 99 | if (slave.Hreg(reg.address + i) != ((uint16_t*)value)[i]) res = false; 100 | } 101 | break; 102 | case TAddress::IREG: 103 | for (uint8_t i = 0; i < count; i++) { 104 | if (slave.Ireg(reg.address + i) != ((uint16_t*)value)[i]) res = false; 105 | } 106 | break; 107 | case TAddress::COIL: 108 | for (uint8_t i = 0; i < count; i++) { 109 | if (slave.Coil(reg.address + i) != ((bool*)value)[i]) res = false; 110 | } 111 | break; 112 | case TAddress::ISTS: 113 | for (uint8_t i = 0; i < count; i++) { 114 | if (slave.Ists(reg.address + i) != ((bool*)value)[i]) res = false; 115 | } 116 | break; 117 | } 118 | if (res) { 119 | Serial.println(" PASSED"); 120 | } else { 121 | Serial.print(" INCORRECT"); 122 | } 123 | } else { 124 | Serial.println(); 125 | } 126 | } else { 127 | Serial.println(" FAILED"); 128 | } 129 | } else { 130 | Serial.println(" BUSY"); 131 | } 132 | if (mem) 133 | free(value); 134 | } -------------------------------------------------------------------------------- /src/ModbusRTU.h: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusRTU Library for ESP8266/ESP32 3 | Copyright (C) 2019-2020 Alexander Emelianov (a.m.emelianov@gmail.com) 4 | https://github.com/emelianov/modbus-esp8266 5 | This code is licensed under the BSD New License. See LICENSE.txt for more info. 6 | */ 7 | #pragma once 8 | #include 9 | #include 10 | #if defined(ESP8266) 11 | #include 12 | #endif 13 | 14 | //#define MODBUSRTU_DEBUG 15 | #define MODBUSRTU_BROADCAST 0 16 | #define MB_RESERVE 248 17 | #define MB_SERIAL_BUFFER 128 18 | #define MB_MAX_TIME 10 19 | #define MODBUSRTU_TIMEOUT 1000 20 | #define MODBUSRTU_ADD_REG 21 | //#define MB_STATIC_FRAME 1 22 | 23 | class ModbusRTU : public Modbus { 24 | protected: 25 | Stream* _port; 26 | int16_t _txPin = -1; 27 | unsigned int _t; // inter-frame delay in mS 28 | uint32_t t = 0; // time sience last data byte arrived 29 | bool isMaster = false; 30 | uint8_t _slaveId; 31 | uint32_t _timestamp = 0; 32 | cbTransaction _cb = nullptr; 33 | void* _data = nullptr; 34 | uint8_t* _sentFrame = nullptr; 35 | TAddress _sentReg = COIL(0); 36 | uint16_t maxRegs = 0x007D; 37 | #ifdef ESP32 38 | portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; 39 | #endif 40 | bool send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data = nullptr, bool waitResponse = true); 41 | // Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data 42 | // slaveId - slave id 43 | // startreg - first local register to save returned data to (miningless for write to slave operations) 44 | // cb - transaction callback function 45 | // data - if not null use buffer to save returned data instead of local registers 46 | bool rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len); 47 | bool cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events 48 | uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen); 49 | public: 50 | void setBaudrate(uint32_t baud = -1); 51 | #if defined(ESP8266) 52 | bool begin(SoftwareSerial* port, int16_t txPin=-1); 53 | #endif 54 | bool begin(HardwareSerial* port, int16_t txPin=-1); 55 | bool begin(Stream* port); 56 | void task(); 57 | void master() { isMaster = true; }; 58 | void slave(uint8_t slaveId) {_slaveId = slaveId;}; 59 | uint8_t slave() { return _slaveId; } 60 | uint32_t eventSource() override {return _slaveId;} 61 | uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); 62 | uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); 63 | uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 64 | uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 65 | uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 66 | uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 67 | uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 68 | uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 69 | 70 | uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 71 | uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 72 | uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 73 | uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 74 | uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 75 | uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 76 | 77 | uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); 78 | uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); 79 | uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 80 | uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 81 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModbusRTU and ModbusIP Library for ESP8266/ESP32 2 | 3 | |If the library is helpful for your projects you can support it by a glass of beer|[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z38SLGAKGM93S&source=url)| 4 | |---|---| 5 | 6 | 7 | Visit [Releases](https://github.com/emelianov/modbus-esp8266/releases) page for stable one. 8 | 9 | --- 10 | 11 | This library allows your ESP8266/ESP32 to communicate via Modbus protocol. The Modbus is a master-slave protocol 12 | used in industrial automation and can be used in other areas, such as home automation. 13 | 14 | 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). 15 | 16 | For more information about Modbus see: 17 | 18 | * [Modbus (From Wikipedia, the free encyclopedia)](http://pt.wikipedia.org/wiki/Modbus) 19 | * [MODBUS APPLICATION PROTOCOL SPECIFICATION 20 | V1.1b](http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf) 21 | * [MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE 22 | V1.0b](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) 23 | * [MODBUS over Serial Line 24 | Specification and Implementation Guide 25 | V1.02](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) 26 | 27 | ## Features 28 | 29 | * Supported platforms are 30 | * ESP8266 31 | * ESP32 32 | * STM32F103 and probably others (Modbus RTU only) 33 | * Operates in any combination of multiple instances of 34 | * Modbus RTU slave 35 | * Modbus RTU master 36 | * Modbus IP server 37 | * Modbus IP client 38 | * Reply exception messages for all supported functions 39 | * Modbus functions supported: 40 | * 0x01 - Read Coils 41 | * 0x02 - Read Input Status (Read Discrete Inputs) 42 | * 0x03 - Read Holding Registers 43 | * 0x04 - Read Input Registers 44 | * 0x05 - Write Single Coil 45 | * 0x06 - Write Single Register 46 | * 0x0F - Write Multiple Coils 47 | * 0x10 - Write Multiple Registers 48 | * Callbacks for 49 | * Client connect (ModbusIP) 50 | * Server/Client disconnect (ModbusIP) 51 | * Read specific Register 52 | * Write specific Register 53 | * Slave transaction finish 54 | 55 | ## Notes 56 | 57 | 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. 58 | 2. For API refer [API.md](https://github.com/emelianov/modbus-esp8266/blob/master/API.md) 59 | 3. Modbus RTU maximum incoming frame size is determinated by HardwareSerial buffer size. For SoftwareSerial buffer must be set to 256 bytes. 60 | 4. Probably it's possible to use ModbusRTU with other AVR boards using from [ArduinoSTL](https://github.com/mike-matera/ArduinoSTL). 61 | 5. RS-485 transivers based on MAX-485 is working on at least up to 115200. XY-017/XY-485 working only up to 9600 for some reason. 62 | 63 | ## Last Changes 64 | 65 | ```diff 66 | // 3.0.1 67 | + ModbusRTU: ESP32 possible send\receive failure fix 68 | + ModbusRTU: Non-ESP devices support 69 | + Restriction to registers count removed 70 | + Added bridge example 71 | // 3.0.0 72 | + ModbusRTU Slave 73 | + ModbusRTU Master 74 | + Registers are now shared between Modbus* instances by default 75 | + Fix functions register count limits to follow Modbus specification (or RX buffer limitations) 76 | + ModbusRTU: Examples added 77 | + Test multiple Modbus* instances 78 | + Change to 'uint32_t eventSource()'. Implemented for ModbusRTU and ModbusIP both 79 | + Client: Allow to specify local TCP port (default is 502) 80 | + Server: Allow to specify TCP remote port for connection (default is 502) 81 | + Master\Client: Fix crash on Write Multiple Hregs 82 | + Master\Client: Fix crash on no callback function on read\write remote 83 | + Tests added 84 | // ToDo later 85 | - 0x14 - Read File Records function 86 | - 0x15 - Write File Records function 87 | - 0x16 - Write Mask Register function 88 | - 0x17 - Read/Write Registers function 89 | - ModbusIP: Support for non-ESP boards (using W5x00) 90 | ``` 91 | 92 | ## Contributions 93 | 94 | https://github.com/emelianov/modbus-esp8266 95 | 96 | a.m.emelianov@gmail.com 97 | 98 | Original version: 99 | 100 | https://github.com/andresarmento/modbus-esp8266 101 | 102 | https://github.com/andresarmento/modbus-arduino 103 | 104 | prof (at) andresarmento (dot) com 105 | 106 | ## License 107 | 108 | The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. 109 | -------------------------------------------------------------------------------- /tests/write.h: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus Library for ESP8266/ESP32 3 | Functional tests 4 | Copyright (C) 2019 Alexander Emelianov (a.m.emelianov@gmail.com) 5 | https://github.com/emelianov/modbus-esp8266 6 | This code is licensed under the BSD New License. See LICENSE.txt for more info. 7 | */ 8 | 9 | #pragma once 10 | #include "common.h" 11 | 12 | // Single Hreg write 13 | void writeSingle(uint8_t sl, TAddress reg, uint16_t value) { 14 | Serial.print("Write Single "); 15 | switch (reg.type) { 16 | case TAddress::HREG: 17 | slave.addHreg(reg.address); 18 | Serial.print("HREG: "); 19 | break; 20 | case TAddress::IREG: 21 | slave.addIreg(reg.address); 22 | Serial.print("IREG: "); 23 | break; 24 | case TAddress::COIL: 25 | slave.addCoil(reg.address); 26 | Serial.print("COIL: "); 27 | break; 28 | case TAddress::ISTS: 29 | slave.addIsts(reg.address); 30 | Serial.print("ISTS: "); 31 | break; 32 | default: 33 | Serial.println("UNKNOWN"); 34 | return; 35 | } 36 | if (!master.slave()) { 37 | bool res = false; 38 | switch (reg.type) { 39 | case TAddress::HREG: 40 | res = master.writeHreg(sl, reg.address, value, cbWrite); 41 | break; 42 | case TAddress::IREG: 43 | //res = master.writeIreg(sl, reg.address, value, cbWrite); 44 | break; 45 | case TAddress::COIL: 46 | res = master.writeCoil(sl, reg.address, value, cbWrite); 47 | break; 48 | case TAddress::ISTS: 49 | //res = master.writeIsts(sl, reg.address, value, cbWrite); 50 | break; 51 | } 52 | if (res) { 53 | Serial.print(" SENT "); 54 | if (wait() == Modbus::EX_SUCCESS) { 55 | uint16_t val = 0; 56 | switch (reg.type) { 57 | case TAddress::HREG: 58 | val = slave.Hreg(reg.address); 59 | break; 60 | case TAddress::IREG: 61 | val = slave.Ireg(reg.address); 62 | break; 63 | case TAddress::COIL: 64 | val = slave.Coil(reg.address); 65 | break; 66 | case TAddress::ISTS: 67 | val = slave.Ists(reg.address); 68 | break; 69 | } 70 | if (val = value) { 71 | Serial.println(" PASSED"); 72 | } else { 73 | Serial.print(" INCORRECT"); 74 | } 75 | } else { 76 | Serial.println(); 77 | } 78 | } else { 79 | Serial.println(" FAILED"); 80 | } 81 | } else { 82 | Serial.println(" BUSY"); 83 | } 84 | } 85 | 86 | // Multiple write 87 | void writeMultiple(uint8_t sl, TAddress reg, uint16_t count = 1, void* value = nullptr) { 88 | Serial.print("Write Multiple "); 89 | bool mem = false; 90 | if (!value) { 91 | if (reg.isHreg() || reg.isIreg()) { 92 | value = malloc(count * sizeof(uint16_t)); 93 | if (!value) 94 | return; 95 | for (uint8_t i = 0; i < count; i++) { 96 | ((uint16_t*)value)[i] = i; 97 | } 98 | } else { 99 | value = malloc(count * sizeof(bool)); 100 | if (!value) 101 | return; 102 | for (uint8_t i = 0; i < count; i++) { 103 | ((bool*)value)[i] = i % 2; 104 | } 105 | } 106 | mem = true; 107 | } 108 | switch (reg.type) { 109 | case TAddress::HREG: 110 | slave.addHreg(reg.address, 0, count); 111 | Serial.print("HREG: "); 112 | break; 113 | case TAddress::IREG: 114 | slave.addIreg(reg.address, 0, count); 115 | Serial.print("IREG: "); 116 | break; 117 | case TAddress::COIL: 118 | slave.addCoil(reg.address, false, count); 119 | Serial.print("COIL: "); 120 | break; 121 | case TAddress::ISTS: 122 | slave.addIsts(reg.address, false, count); 123 | Serial.print("ISTS: "); 124 | break; 125 | default: 126 | Serial.println("UNKNOWN"); 127 | return; 128 | } 129 | if (!master.slave()) { 130 | bool res = false; 131 | switch (reg.type) { 132 | case TAddress::HREG: 133 | res = master.writeHreg(sl, reg.address, (uint16_t*)value, count, cbWrite); 134 | break; 135 | case TAddress::IREG: 136 | //res = master.writeIreg(sl, reg.address, value, count, cbWrite); 137 | break; 138 | case TAddress::COIL: 139 | res = master.writeCoil(sl, reg.address, (bool*)value, count, cbWrite); 140 | break; 141 | case TAddress::ISTS: 142 | //res = master.writeIsts(sl, reg.address, value, count, cbWrite); 143 | break; 144 | } 145 | if (res) { 146 | Serial.print(" SENT "); 147 | if (wait() == Modbus::EX_SUCCESS) { 148 | bool res = true; 149 | switch (reg.type) { 150 | case TAddress::HREG: 151 | for (uint8_t i = 0; i < count; i++) { 152 | if (slave.Hreg(reg.address + i) != ((uint16_t*)value)[i]) res = false; 153 | } 154 | break; 155 | case TAddress::IREG: 156 | for (uint8_t i = 0; i < count; i++) { 157 | //if (slave.Ireg(reg.address + i) != value[i]) res = false; 158 | } 159 | break; 160 | case TAddress::COIL: 161 | for (uint8_t i = 0; i < count; i++) { 162 | if (slave.Coil(reg.address + i) != ((bool*)value)[i]) res = false; 163 | } 164 | break; 165 | case TAddress::ISTS: 166 | for (uint8_t i = 0; i < count; i++) { 167 | //if (slave.Ists(reg.address + i) != value[i]) res = false; 168 | } 169 | break; 170 | } 171 | if (res) { 172 | Serial.println(" PASSED"); 173 | } else { 174 | Serial.print(" INCORRECT"); 175 | } 176 | } else { 177 | Serial.println(); 178 | } 179 | } else { 180 | Serial.println(" FAILED"); 181 | } 182 | } else { 183 | Serial.println(" BUSY"); 184 | } 185 | if (mem) 186 | free(value); 187 | } -------------------------------------------------------------------------------- /src/ModbusIP_ESP.h: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusIP_ESP.h - Header for ModbusIP Library 3 | Copyright (C) 2014 Andr� Sarmento Barbosa 4 | 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) 5 | */ 6 | #pragma once 7 | 8 | #if defined(ESP32) || defined(ESP8266) 9 | 10 | #include 11 | #ifdef ESP8266 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | #define MODBUSIP_PORT 502 18 | #define MODBUSIP_MAXFRAME 200 19 | #define MODBUSIP_TIMEOUT 1000 20 | #define MODBUSIP_UNIT 255 21 | #define MODBUSIP_MAX_TRANSACIONS 16 22 | #define MODBUSIP_MAX_CLIENTS 4 23 | #define MODBUSIP_ADD_REG 1 24 | #define MODBUSIP_UNIQUE_CLIENTS 25 | #define MODBUSIP_MAX_READMS 100 26 | 27 | // Callback function Type 28 | typedef bool (*cbModbusConnect)(IPAddress ip); 29 | 30 | typedef struct TTransaction { 31 | uint16_t transactionId; 32 | uint32_t timestamp; 33 | cbTransaction cb = nullptr; 34 | uint8_t* _frame = nullptr; 35 | void* data = nullptr; 36 | TAddress startreg; 37 | Modbus::ResultCode forcedEvent = Modbus::EX_SUCCESS; // EX_SUCCESS means no forced event here. Forced EX_SUCCESS is not possible. 38 | bool operator ==(const TTransaction &obj) const { 39 | return transactionId == obj.transactionId; 40 | } 41 | }; 42 | 43 | class ModbusIP : public Modbus { 44 | protected: 45 | typedef union MBAP_t { 46 | struct { 47 | uint16_t transactionId; 48 | uint16_t protocolId; 49 | uint16_t length; 50 | uint8_t unitId; 51 | }; 52 | uint8_t raw[7]; 53 | }; 54 | cbModbusConnect cbConnect = nullptr; 55 | cbModbusConnect cbDisconnect = nullptr; 56 | WiFiServer* tcpserver = nullptr; 57 | WiFiClient* tcpclient[MODBUSIP_MAX_CLIENTS]; 58 | std::vector _trans; 59 | int16_t transactionId = 0; // Last started transaction. Increments on unsuccessful transaction start too. 60 | int8_t n = -1; 61 | bool autoConnectMode = false; 62 | uint16_t serverPort = 0; 63 | 64 | TTransaction* searchTransaction(uint16_t id); 65 | void cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events 66 | int8_t getFreeClient(); // Returns free slot position 67 | int8_t getSlave(IPAddress ip); 68 | int8_t getMaster(IPAddress ip); 69 | uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, void* data = nullptr, bool waitResponse = true); 70 | // Prepare and send ModbusIP frame. _frame buffer and _len should be filled with Modbus data 71 | // ip - slave ip address 72 | // startreg - first local register to save returned data to (miningless for write to slave operations) 73 | // cb - transaction callback function 74 | // unit - slave modbus unit id 75 | // data - if not null use buffer to save returned data instead of local registers 76 | public: 77 | ModbusIP(); 78 | ~ModbusIP(); 79 | bool isTransaction(uint16_t id); 80 | bool isConnected(IPAddress ip); 81 | bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); 82 | bool disconnect(IPAddress ip); 83 | void server(uint16_t port = MODBUSIP_PORT); 84 | inline void slave(uint16_t port = MODBUSIP_PORT) { server(port); } // Depricated 85 | void client(); 86 | inline void master() { client(); } // Depricated 87 | void task(); 88 | inline void begin() { server(); }; // Depricated 89 | void onConnect(cbModbusConnect cb = nullptr); 90 | void onDisconnect(cbModbusConnect cb = nullptr); 91 | uint32_t eventSource() override; 92 | void autoConnect(bool enabled = true); 93 | void dropTransactions(); 94 | 95 | uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 96 | uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 97 | uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 98 | uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 99 | uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 100 | uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 101 | uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 102 | uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 103 | 104 | uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 105 | uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 106 | uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 107 | uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 108 | uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 109 | uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 110 | 111 | uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 112 | uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 113 | uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 114 | uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 115 | /* 116 | uint16_t maskHreg(IPAddress ip, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 117 | uint16_t pushPullIreg 118 | uint16_t pushPullHreg 119 | uint16_t pushIregPullToHreg 120 | uint16_t pushHregPullToIreg 121 | uint16_t pushPullHreg(IPAddress ip, 122 | uint16_t from, uint16_t to, uint16_t numregs = 1, 123 | uint16_t to, uint16_t from, uint16_t numregs = 1, 124 | cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 125 | uint16_t readWriteHreg(IPAddress ip, 126 | uint16_t readOffset, uint16_t* value, uint16_t numregs = 1, 127 | uint16_t writeOffset, uint16_t* value, uint16_t numregs = 1, 128 | cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT); 129 | */ 130 | }; 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /src/Modbus.h: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus.h - Header for Modbus Core Library 3 | Copyright (C) 2014 Andr� Sarmento Barbosa 4 | 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) 5 | */ 6 | #pragma once 7 | 8 | #include "Arduino.h" 9 | #include 10 | #include 11 | #ifdef ARDUINO_ARCH_ESP32 12 | #include 13 | #endif 14 | 15 | #ifndef __bswap_16 16 | #define __bswap_16(num) (((uint16_t)num>>8) | ((uint16_t)num<<8)) 17 | #endif 18 | 19 | 20 | //#define MB_GLOBAL_REGS 21 | //#define MB_MAX_REGS 32 22 | #define MODBUS_MAX_FRAME 256 23 | #define COIL(n) (TAddress){TAddress::COIL, n} 24 | #define ISTS(n) (TAddress){TAddress::ISTS, n} 25 | #define IREG(n) (TAddress){TAddress::IREG, n} 26 | #define HREG(n) (TAddress){TAddress::HREG, n} 27 | #define BIT_VAL(v) (v?0xFF00:0x0000) 28 | #define BIT_BOOL(v) (v==0xFF00) 29 | #define COIL_VAL(v) (v?0xFF00:0x0000) 30 | #define COIL_BOOL(v) (v==0xFF00) 31 | #define ISTS_VAL(v) (v?0xFF00:0x0000) 32 | #define ISTS_BOOL(v) (v==0xFF00) 33 | 34 | // For depricated (v1.xx) onSet/onGet format compatibility 35 | #define cbDefault nullptr 36 | 37 | struct TRegister; 38 | 39 | typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type 40 | 41 | struct TAddress { 42 | enum RegType {COIL, ISTS, IREG, HREG}; 43 | RegType type; 44 | uint16_t address; 45 | bool operator==(const TAddress &obj) const { // TAddress == TAddress 46 | return type == obj.type && address == obj.address; 47 | } 48 | bool operator!=(const TAddress &obj) const { // TAddress != TAddress 49 | return type != obj.type || address != obj.address; 50 | } 51 | TAddress& operator++() { // ++TAddress 52 | address++; 53 | return *this; 54 | } 55 | TAddress operator++(int) { // TAddress++ 56 | TAddress result(*this); 57 | ++(*this); 58 | return result; 59 | } 60 | TAddress& operator+=(const int& inc) { // TAddress += integer 61 | address += inc; 62 | return *this; 63 | } 64 | const TAddress operator+(const int& inc) const { // TAddress + integer 65 | TAddress result(*this); 66 | result.address += inc; 67 | return result; 68 | } 69 | bool isCoil() { 70 | return type == COIL; 71 | } 72 | bool isIsts() { 73 | return type == ISTS; 74 | } 75 | bool isIreg() { 76 | return type == IREG; 77 | } 78 | bool isHreg() { 79 | return type == HREG; 80 | } 81 | }; 82 | 83 | struct TCallback { 84 | enum CallbackType {ON_SET, ON_GET}; 85 | CallbackType type; 86 | TAddress address; 87 | cbModbus cb; 88 | }; 89 | 90 | struct TRegister { 91 | TAddress address; 92 | uint16_t value; 93 | bool operator ==(const TRegister &obj) const { 94 | return address == obj.address; 95 | } 96 | }; 97 | 98 | class Modbus { 99 | public: 100 | //Function Codes 101 | enum FunctionCode { 102 | FC_READ_COILS = 0x01, // Read Coils (Output) Status 103 | FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs) 104 | FC_READ_REGS = 0x03, // Read Holding Registers 105 | FC_READ_INPUT_REGS = 0x04, // Read Input Registers 106 | FC_WRITE_COIL = 0x05, // Write Single Coil (Output) 107 | FC_WRITE_REG = 0x06, // Preset Single Register 108 | FC_DIAGNOSTICS = 0x08, // Not implemented. Diagnostics (Serial Line only) 109 | FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs) 110 | FC_WRITE_REGS = 0x10, // Write block of contiguous registers 111 | FC_READ_FILE_REC = 0x14, // Not implemented. Read File Record 112 | FC_WRITE_FILE_REC = 0x15, // Not implemented. Write File Record 113 | FC_MASKWRITE_REG = 0x16, // Not implemented. Mask Write Register 114 | FC_READWRITE_REGS = 0x17 // Not implemented. Read/Write Multiple registers 115 | }; 116 | //Exception Codes 117 | //Custom result codes used internally and for callbacks but never used for Modbus responce 118 | enum ResultCode { 119 | EX_SUCCESS = 0x00, // Custom. No error 120 | EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported 121 | EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists 122 | EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range 123 | EX_SLAVE_FAILURE = 0x04, // Slave or Master Device Fails to process request 124 | EX_ACKNOWLEDGE = 0x05, // Not used 125 | EX_SLAVE_DEVICE_BUSY = 0x06, // Not used 126 | EX_MEMORY_PARITY_ERROR = 0x08, // Not used 127 | EX_PATH_UNAVAILABLE = 0x0A, // Not used 128 | EX_DEVICE_FAILED_TO_RESPOND = 0x0B, // Not used 129 | EX_GENERAL_FAILURE = 0xE1, // Custom. Unexpected master error 130 | EX_DATA_MISMACH = 0xE2, // Custom. Inpud data size mismach 131 | EX_UNEXPECTED_RESPONSE = 0xE3, // Custom. Returned result doesn't mach transaction 132 | EX_TIMEOUT = 0xE4, // Custom. Operation not finished within reasonable time 133 | EX_CONNECTION_LOST = 0xE5, // Custom. Connection with device lost 134 | EX_CANCEL = 0xE6 // Custom. Transaction/request canceled 135 | }; 136 | ~Modbus(); 137 | bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); 138 | bool Hreg(uint16_t offset, uint16_t value); 139 | uint16_t Hreg(uint16_t offset); 140 | uint16_t removeHreg(uint16_t offset, uint16_t numregs = 1); 141 | bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1); 142 | bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); 143 | bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); 144 | bool Coil(uint16_t offset, bool value); 145 | bool Ists(uint16_t offset, bool value); 146 | bool Ireg(uint16_t offset, uint16_t value); 147 | bool Coil(uint16_t offset); 148 | bool Ists(uint16_t offset); 149 | uint16_t Ireg(uint16_t offset); 150 | bool removeCoil(uint16_t offset, uint16_t numregs = 1); 151 | bool removeIsts(uint16_t offset, uint16_t numregs = 1); 152 | bool removeIreg(uint16_t offset, uint16_t numregs = 1); 153 | /* 154 | bool Hreg(uint16_t offset, uint16_t* value); 155 | bool Coil(uint16_t offset, bool* value); 156 | bool Ists(uint16_t offset, bool* value); 157 | bool Ireg(uint16_t offset, uint16_t* value); 158 | */ 159 | void cbEnable(bool state = true); 160 | void cbDisable(); 161 | 162 | bool onGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 163 | bool onSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 164 | bool onGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 165 | bool onSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 166 | bool onGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 167 | bool onSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 168 | bool onGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 169 | bool onSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 170 | 171 | bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 172 | bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 173 | bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 174 | bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 175 | bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 176 | bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 177 | bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 178 | bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 179 | 180 | private: 181 | void readBits(TAddress startreg, uint16_t numregs, FunctionCode fn); 182 | void readWords(TAddress startreg, uint16_t numregs, FunctionCode fn); 183 | 184 | void setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs); 185 | void setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs); 186 | 187 | void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs); 188 | void getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs); 189 | 190 | void bitsToBool(bool* dst, uint8_t* src, uint16_t numregs); 191 | void boolToBits(uint8_t* dst, bool* src, uint16_t numregs); 192 | 193 | protected: 194 | //Reply Types 195 | enum ReplyCode { 196 | REPLY_OFF = 0x01, 197 | REPLY_ECHO = 0x02, 198 | REPLY_NORMAL = 0x03, 199 | REPLY_ERROR = 0x04, 200 | REPLY_UNEXPECTED = 0x05 201 | }; 202 | #ifndef MB_GLOBAL_REGS 203 | std::vector _regs; 204 | std::vector _callbacks; 205 | #endif 206 | uint8_t* _frame = nullptr; 207 | uint16_t _len = 0; 208 | uint8_t _reply = 0; 209 | bool cbEnabled = true; 210 | uint16_t callback(TRegister* reg, uint16_t val, TCallback::CallbackType t); 211 | TRegister* searchRegister(TAddress addr); 212 | void exceptionResponse(FunctionCode fn, ResultCode excode); // Fills _frame with response 213 | void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); // Fills frame with response 214 | void slavePDU(uint8_t* frame); //For Slave 215 | void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output = nullptr); //For Master 216 | // frame - data received form slave 217 | // sourceFrame - data have sent fo slave 218 | // startreg - local register to start put data to 219 | // output - if not null put data to the buffer insted local registers. output assumed to by array of uint16_t or boolean 220 | 221 | bool readSlave(uint16_t address, uint16_t numregs, FunctionCode fn); 222 | bool writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data = nullptr); 223 | bool writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data = nullptr); 224 | // startreg - local register to get data from 225 | // to - slave register to write data to 226 | // numregs - number of registers 227 | // fn - Modbus function 228 | // data - if null use local registers. Otherwise use data from array to erite to slave 229 | 230 | bool addReg(TAddress address, uint16_t value = 0, uint16_t numregs = 1); 231 | bool Reg(TAddress address, uint16_t value); 232 | uint16_t Reg(TAddress address); 233 | bool removeReg(TAddress address, uint16_t numregs = 1); 234 | 235 | bool onGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); 236 | bool onSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); 237 | bool removeOnSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); 238 | bool removeOnGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1); 239 | 240 | virtual uint32_t eventSource() {return 0;} 241 | }; 242 | 243 | typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests 244 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Modbus Master-Slave Library for ESP8266/ESP32 2 | 3 | ## Common API 4 | 5 | ### Add [multiple] regs 6 | 7 | ```c 8 | bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1); 9 | bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1); 10 | bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1); 11 | bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t nemregs = 1); 12 | ``` 13 | 14 | ### Write local reg 15 | 16 | ```c 17 | bool Hreg(uint16_t offset, uint16_t value); 18 | bool Coil(uint16_t offset, bool value); 19 | bool Ists(uint16_t offset, bool value); 20 | bool Ireg(uint16_t offset, uint16_t value); 21 | ``` 22 | 23 | ### Read local reg 24 | 25 | ```c 26 | uint16_t Hreg(uint16_t offset); 27 | bool Coil(uint16_t offset); 28 | bool Ists(uint16_t offset); 29 | uint16_t Ireg(uint16_t offset); 30 | ``` 31 | 32 | ### Remove reg 33 | 34 | ```c 35 | bool removeHreg(uint16_t offset, uint16_t numregs = 1); 36 | bool removeCoil(uint16_t offset, uint16_t numregs = 1); 37 | bool removeIsts(uint16_t offset, uint16_t numregs = 1); 38 | bool removeIreg(uint16_t offset, uint16_t numregs = 1); 39 | ``` 40 | 41 | ```c 42 | void task(); 43 | ``` 44 | 45 | Processing routine. Should be periodically called form loop(). 46 | 47 | ### Modbus RTU Specific API 48 | 49 | ```c 50 | bool begin(SoftwareSerial* port, int16_t txPin=-1); // For ESP8266 only 51 | bool begin(HardwareSerial* port, int16_t txPin=-1); 52 | bool begin(Stream* port); 53 | ``` 54 | 55 | Assing Serial port. txPin controls transmit enable for MAX-485. 56 | 57 | ```c 58 | void setBaudrte(uint32 baud); 59 | ``` 60 | 61 | Set or override Serial baudrate. Must be called after .begin() for Non-ESP devices. 62 | 63 | ```c 64 | void master(); 65 | void slave(uint8_t slaveId); 66 | ``` 67 | 68 | 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. 69 | 70 | ```c 71 | uint8_t slave(); 72 | ``` 73 | 74 | Slave mode: Returns configured slave id. Master mode: Returns slave id for active request or 0 if no request in-progress. 75 | 76 | ### ModBus IP Slave specific API 77 | 78 | ```c 79 | void begin(); // Depricated. Use server() instead. 80 | void slave(uint16_t port = MODBUSIP_PORT); // For compatibility with ModbusRTU calls. Typically may be replaced with server() call. 81 | void server(uint16_t port = MODBUSIP_PORT); 82 | ``` 83 | 84 | ### ModBus IP Master specific 85 | 86 | ```c 87 | void master(); // For compatibility with ModbusRTU calls. Typically may be replaced with client() call. 88 | void client(); 89 | bool connect(IPAddress ip, uint16_t port = MODBUSIP_PORT); 90 | bool disconnect(IPAddress ip); 91 | bool isTransaction(uint16_t id); 92 | bool isConnected(IPAddress ip); 93 | void dropTransactions(); 94 | ``` 95 | 96 | ```c 97 | void autoConnect(bool enabled); 98 | ``` 99 | 100 | 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. 101 | 102 | ### Query [multiple] regs from remote slave 103 | 104 | ```c 105 | uint16_t pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 106 | uint16_t pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 107 | uint16_t pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 108 | uint16_t pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 109 | uint16_t pullHregToIreg(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 110 | uint16_t pullCoilToIsts(IPAddress ip, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 111 | 112 | uint16_t pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 113 | uint16_t pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 114 | uint16_t pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 115 | uint16_t pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr); 116 | uint16_t pullHregToIreg(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); 117 | uint16_t pullCoilToIsts(uint8_t slaveId, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr); 118 | ``` 119 | 120 | Result is saved to local registers. Method returns corresponding transaction id. [ip/from] or [ip/offset] - slave, [to] or [startreg] - local 121 | 122 | ### Send [multiple] regs to remote slave 123 | 124 | ```c 125 | uint16_t pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 126 | uint16_t pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 127 | uint16_t pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 128 | uint16_t pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 129 | 130 | uint16_t pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 131 | uint16_t pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 132 | uint16_t pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 133 | uint16_t pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr); 134 | ``` 135 | 136 | Write Register/Coil or Write Multiple Registers/Coils Modbus function selected automaticly depending on 'numregs' value. [ip/to] - slave, [from] - local 137 | 138 | ### Write [multiple] values to remote slave reg 139 | 140 | ```c 141 | uint16_t writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 142 | uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 143 | 144 | uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr); 145 | uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb = nullptr); 146 | ``` 147 | 148 | Write single value to remote Hreg/Coil. 149 | 150 | ```c 151 | uint16_t writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 152 | uint16_t writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 153 | 154 | uint16_t writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 155 | uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 156 | ``` 157 | 158 | Write multiple values from array to remote Coil/Hreg. 159 | 160 | ### Read values from multiple remote slave regs 161 | 162 | ```c 163 | uint16_t readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 164 | uint16_t readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 165 | uint16_t readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 166 | uint16_t readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t uint = MODBUSIP_UNIT); 167 | 168 | uint16_t readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 169 | uint16_t readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 170 | uint16_t readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 171 | uint16_t readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr); 172 | ``` 173 | 174 | Read values from remote Hreg/Coil/Ireg/Ists to array. 175 | 176 | ## Callbacks API 177 | 178 | ```c 179 | void cbEnable(bool state = true); 180 | void cbDisable(); 181 | ``` 182 | 183 | Callback generation control. Callback generation is enabled by default. *Has no effect on transactions callbacks.* 184 | 185 | ```c 186 | void onConnect(cbModbusConnect cb); 187 | void onDisonnect(cbModbusConnect cb); 188 | ``` 189 | 190 | *Modbus IP Slave* Assign callback function on new incoming connection event. 191 | 192 | ```c 193 | typedef bool (*cbModbusConnect)(IPAddress ip); 194 | ``` 195 | 196 | *Modbus IP Slave* Connect event callback function definition. For onConnect event client's IP address is passed as argument. onDisconnect callback function always gets INADDR_NONE as parameter. 197 | 198 | ```c 199 | typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); 200 | ``` 201 | 202 | Get/Set register callback function definition. Pointer to TRegister structure (see source for details) of the register and new value are passed as arguments. 203 | 204 | ```c 205 | typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); 206 | ``` 207 | 208 | Transaction end callback function definition. For ModbusIP *data* is currently reserved. For ModbusRTU *transactionId* is also reserved. 209 | 210 | ```c 211 | uint32_t eventSource(); 212 | ``` 213 | 214 | Should be called from onGet/onSet or transaction callback function. 215 | 216 | *Modbus IP Master/Slave* Returns IP address of remote requesting operation or INADDR_NONE for local. Use IPAddress(eventSource) to operate result as IPAddress type. 217 | 218 | *Note:* For transaction callback INADDR_NONE returned in case if transaction is timedout. 219 | 220 | *Modbus RTU Master/Slave* Returns slave id. 221 | 222 | ```c 223 | bool onSetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 224 | bool onSetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 225 | bool onSetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 226 | bool onSetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 227 | ``` 228 | 229 | Assign callback function on register modify event. Multiple sequental registers can be affected by specifing `numregs` parameter. 230 | 231 | 232 | ```c 233 | bool onGetCoil(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 234 | bool onGetHreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 235 | bool onGetIsts(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 236 | bool onGetIreg(uint16_t address, cbModbus cb = nullptr, uint16_t numregs = 1); 237 | ``` 238 | 239 | Assign callback function on register query event. Multiple sequental registers can be affected by specifing `numregs` parameter. 240 | 241 | ```c 242 | bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 243 | bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 244 | bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 245 | bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 246 | bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 247 | bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 248 | bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 249 | bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1); 250 | ``` 251 | 252 | Disconnect specific callback function or all callbacks of the type if cb=NULL. 253 | 254 | ### Macros 255 | 256 | ```c 257 | #define COIL_VAL(v) 258 | #define COIL_BOOL(v) 259 | #define ISTS_VAL(v) 260 | #define ISTS_BOOL(v) 261 | ``` 262 | 263 | ### Callback example 264 | 265 | ```arduino 266 | ModbusIP mb; 267 | bool coil = false; // Define external variable to get/set value 268 | uint16_t cbCoilSet(TRegister* reg, uint16_t val) { // 'reg' is pointer to reg structure to modify, 'val' is new register value 269 | Serial.print("Set query from "); 270 | Serial.println(mb.eventSource().toString()); 271 | coil = COIL_BOOL(val); // Assign value to external variable 272 | return val; // Returns value to be saved to TRegister structure 273 | } 274 | uint16_t cbCoilGet(TRegister* reg, uint16_t val) { 275 | Serial.print("Get query from "); 276 | Serial.println(mb.eventSource().toString()); 277 | return COIL_VAL(coil); // Override value to be returned to ModBus master as reply for current request 278 | } 279 | bool cbConn(IPAddress ip) { 280 | Serial.println(ip); 281 | return true; // Return 'true' to allow connection or 'false' to drop connection 282 | } 283 | ModbusIP mb; // ModbusIP object 284 | void setup() { 285 | ... 286 | mb.onConnect(cbConn); // Add callback on connection event 287 | mb.slave(); // Accept incoming Modbus connections 288 | mb.addCoil(COIL_NR); // Add Coil 289 | mb.onSetCoil(COIL_NR, cbCoilSet); // Add callback on Coil COIL_NR value set 290 | mb.onGetCoil(COIL_NR, cbCoilGet); // Add callback on Coil COIL_NR value get 291 | ... 292 | } 293 | void loop() { 294 | ... 295 | mb.task(); 296 | ... 297 | } 298 | ``` 299 | 300 | ## Contributions 301 | 302 | https://github.com/emelianov/modbus-esp8266 303 | 304 | a.m.emelianov@gmail.com 305 | 306 | Original version: 307 | 308 | https://github.com/andresarmento/modbus-esp8266 309 | https://github.com/andresarmento/modbus-arduino 310 | 311 | prof (at) andresarmento (dot) com 312 | 313 | ## License 314 | 315 | The code in this repo is licensed under the BSD New License. See LICENSE.txt for more info. 316 | -------------------------------------------------------------------------------- /src/ModbusRTU.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusRTU Library for ESP8266/ESP32 3 | Copyright (C) 2019-2020 Alexander Emelianov (a.m.emelianov@gmail.com) 4 | https://github.com/emelianov/modbus-esp8266 5 | This code is licensed under the BSD New License. See LICENSE.txt for more info. 6 | */ 7 | #pragma once 8 | #include "ModbusRTU.h" 9 | 10 | // Table of CRC values 11 | static const uint16_t _auchCRC[] PROGMEM = { 12 | 0x0000, 0xC1C0, 0x81C1, 0x4001, 0x01C3, 0xC003, 0x8002, 0x41C2, 0x01C6, 0xC006, 0x8007, 0x41C7, 0x0005, 0xC1C5, 0x81C4, 13 | 0x4004, 0x01CC, 0xC00C, 0x800D, 0x41CD, 0x000F, 0xC1CF, 0x81CE, 0x400E, 0x000A, 0xC1CA, 0x81CB, 0x400B, 0x01C9, 0xC009, 14 | 0x8008, 0x41C8, 0x01D8, 0xC018, 0x8019, 0x41D9, 0x001B, 0xC1DB, 0x81DA, 0x401A, 0x001E, 0xC1DE, 0x81DF, 0x401F, 0x01DD, 15 | 0xC01D, 0x801C, 0x41DC, 0x0014, 0xC1D4, 0x81D5, 0x4015, 0x01D7, 0xC017, 0x8016, 0x41D6, 0x01D2, 0xC012, 0x8013, 0x41D3, 16 | 0x0011, 0xC1D1, 0x81D0, 0x4010, 0x01F0, 0xC030, 0x8031, 0x41F1, 0x0033, 0xC1F3, 0x81F2, 0x4032, 0x0036, 0xC1F6, 0x81F7, 17 | 0x4037, 0x01F5, 0xC035, 0x8034, 0x41F4, 0x003C, 0xC1FC, 0x81FD, 0x403D, 0x01FF, 0xC03F, 0x803E, 0x41FE, 0x01FA, 0xC03A, 18 | 0x803B, 0x41FB, 0x0039, 0xC1F9, 0x81F8, 0x4038, 0x0028, 0xC1E8, 0x81E9, 0x4029, 0x01EB, 0xC02B, 0x802A, 0x41EA, 0x01EE, 19 | 0xC02E, 0x802F, 0x41EF, 0x002D, 0xC1ED, 0x81EC, 0x402C, 0x01E4, 0xC024, 0x8025, 0x41E5, 0x0027, 0xC1E7, 0x81E6, 0x4026, 20 | 0x0022, 0xC1E2, 0x81E3, 0x4023, 0x01E1, 0xC021, 0x8020, 0x41E0, 0x01A0, 0xC060, 0x8061, 0x41A1, 0x0063, 0xC1A3, 0x81A2, 21 | 0x4062, 0x0066, 0xC1A6, 0x81A7, 0x4067, 0x01A5, 0xC065, 0x8064, 0x41A4, 0x006C, 0xC1AC, 0x81AD, 0x406D, 0x01AF, 0xC06F, 22 | 0x806E, 0x41AE, 0x01AA, 0xC06A, 0x806B, 0x41AB, 0x0069, 0xC1A9, 0x81A8, 0x4068, 0x0078, 0xC1B8, 0x81B9, 0x4079, 0x01BB, 23 | 0xC07B, 0x807A, 0x41BA, 0x01BE, 0xC07E, 0x807F, 0x41BF, 0x007D, 0xC1BD, 0x81BC, 0x407C, 0x01B4, 0xC074, 0x8075, 0x41B5, 24 | 0x0077, 0xC1B7, 0x81B6, 0x4076, 0x0072, 0xC1B2, 0x81B3, 0x4073, 0x01B1, 0xC071, 0x8070, 0x41B0, 0x0050, 0xC190, 0x8191, 25 | 0x4051, 0x0193, 0xC053, 0x8052, 0x4192, 0x0196, 0xC056, 0x8057, 0x4197, 0x0055, 0xC195, 0x8194, 0x4054, 0x019C, 0xC05C, 26 | 0x805D, 0x419D, 0x005F, 0xC19F, 0x819E, 0x405E, 0x005A, 0xC19A, 0x819B, 0x405B, 0x0199, 0xC059, 0x8058, 0x4198, 0x0188, 27 | 0xC048, 0x8049, 0x4189, 0x004B, 0xC18B, 0x818A, 0x404A, 0x004E, 0xC18E, 0x818F, 0x404F, 0x018D, 0xC04D, 0x804C, 0x418C, 28 | 0x0044, 0xC184, 0x8185, 0x4045, 0x0187, 0xC047, 0x8046, 0x4186, 0x0182, 0xC042, 0x8043, 0x4183, 0x0041, 0xC181, 0x8180, 29 | 0x4040, 0x0000 30 | }; 31 | 32 | uint16_t ModbusRTU::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) { 33 | uint8_t i = 0xFF ^ address; 34 | uint16_t val = pgm_read_word(_auchCRC + i); 35 | uint8_t CRCHi = 0xFF ^ highByte(val); // Hi 36 | uint8_t CRCLo = lowByte(val); //Low 37 | while (pduLen--) { 38 | i = CRCHi ^ *frame++; 39 | val = pgm_read_word(_auchCRC + i); 40 | CRCHi = CRCLo ^ highByte(val); // Hi 41 | CRCLo = lowByte(val); //Low 42 | } 43 | return (CRCHi << 8) | CRCLo; 44 | } 45 | 46 | void ModbusRTU::setBaudrate(uint32_t baud) { 47 | if (baud > 19200) { 48 | _t = 2; 49 | } else { 50 | _t = (35000/baud) + 1; 51 | } 52 | } 53 | 54 | bool ModbusRTU::begin(Stream* port) { 55 | _port = port; 56 | _t = 2; 57 | return true; 58 | } 59 | 60 | bool ModbusRTU::begin(HardwareSerial* port, int16_t txPin) { 61 | uint32_t baud = 0; 62 | #if defined(ESP32) || defined(ESP8266) 63 | // baudRate() only available with ESP32+ESP8266 64 | baud = port->baudRate(); 65 | #else 66 | baud = 9600; 67 | #endif 68 | setBaudrate(baud); 69 | #if defined(ESP8266) 70 | maxRegs = port->setRxBufferSize(MODBUS_MAX_FRAME) / 2 - 3; 71 | #endif 72 | _port = port; 73 | _txPin = txPin; 74 | if (_txPin >= 0) { 75 | pinMode(_txPin, OUTPUT); 76 | digitalWrite(_txPin, LOW); 77 | } 78 | return true; 79 | } 80 | 81 | #if defined(ESP8266) 82 | bool ModbusRTU::begin(SoftwareSerial* port, int16_t txPin) { 83 | uint32_t baud = port->baudRate(); 84 | _port = port; 85 | if (txPin >= 0) 86 | port->setTransmitEnablePin(txPin); 87 | setBaudrate(baud); 88 | return true; 89 | } 90 | #endif 91 | 92 | bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) { 93 | uint16_t newCrc = crc16(slaveId, frame, len); 94 | if (_txPin >= 0) { 95 | digitalWrite(_txPin, HIGH); 96 | delay(1); 97 | } 98 | #ifdef ESP32 99 | portENTER_CRITICAL(&mux); 100 | #endif 101 | _port->write(slaveId); //Send slaveId 102 | _port->write(frame, len); // Send PDU 103 | _port->write(newCrc >> 8); //Send CRC 104 | _port->write(newCrc & 0xFF);//Send CRC 105 | #ifdef ESP32 106 | portEXIT_CRITICAL(&mux); 107 | #endif 108 | _port->flush(); 109 | if (_txPin >= 0) 110 | digitalWrite(_txPin, LOW); 111 | //delay(_t); 112 | return true; 113 | } 114 | 115 | bool ModbusRTU::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, void* data, bool waitResponse) { 116 | if (_slaveId) return false; // Break if waiting for previous request result 117 | rawSend(slaveId, _frame, _len); 118 | if (waitResponse) { 119 | _slaveId = slaveId; 120 | _timestamp = millis(); 121 | _cb = cb; 122 | _data = data; 123 | _sentFrame = _frame; 124 | _sentReg = startreg; 125 | _frame = nullptr; 126 | _len = 0; 127 | } 128 | return true; 129 | } 130 | 131 | void ModbusRTU::task() { 132 | #ifdef ESP32 133 | if (_len == 0) 134 | portENTER_CRITICAL(&mux); 135 | #endif 136 | if (_port->available() > _len) { 137 | _len = _port->available(); 138 | t = millis(); 139 | return; 140 | } 141 | if (_len != 0 && millis() - t < _t) // Wait data whitespace if there is data 142 | return; 143 | #ifdef ESP32 144 | portEXIT_CRITICAL(&mux); 145 | #endif 146 | if (isMaster) cleanup(); 147 | if (_len == 0) 148 | return; 149 | 150 | uint8_t address = _port->read(); //first byte of frame = address 151 | _len--; // Decrease by slaveId byte 152 | if (isMaster && _slaveId == 0) { // Check if slaveId is set 153 | for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if is not expected 154 | _len = 0; 155 | return; 156 | } 157 | if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check 158 | for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach 159 | _len = 0; 160 | return; 161 | } 162 | 163 | free(_frame); //Just in case 164 | _frame = (uint8_t*) malloc(_len); 165 | if (!_frame) { // Fail to allocate buffer 166 | for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if can't allocate buffer 167 | _len = 0; 168 | return; 169 | } 170 | for (uint8_t i=0 ; i < _len ; i++) { 171 | _frame[i] = _port->read(); // read data + crc 172 | #if defined(MODBUSRTU_DEBUG) 173 | Serial.printf("%02X ", _frame[i]); 174 | #endif 175 | } 176 | #if defined(MODBUSRTU_DEBUG) 177 | Serial.println(); 178 | #endif 179 | //_port->readBytes(_frame, _len); 180 | u_int frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc 181 | _len = _len - 2; // Decrease by CRC 2 bytes 182 | if (frameCrc != crc16(address, _frame, _len)) { // CRC Check 183 | free(_frame); 184 | _frame = nullptr; 185 | _len = 0; // Cleanup if wrong crc 186 | return; 187 | } 188 | if (isMaster) { 189 | _reply = EX_SUCCESS; 190 | if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested 191 | // Procass incoming frame as master 192 | masterPDU(_frame, _sentFrame, _sentReg, _data); 193 | if (_cb) { 194 | _cb((ResultCode)_reply, 0, nullptr); 195 | } 196 | free(_sentFrame); 197 | _sentFrame = nullptr; 198 | _data = nullptr; 199 | _slaveId = 0; 200 | } 201 | _reply = Modbus::REPLY_OFF; // No reply if master 202 | } else { 203 | slavePDU(_frame); 204 | if (address == MODBUSRTU_BROADCAST) 205 | _reply = Modbus::REPLY_OFF; // No reply for Broadcasts 206 | } 207 | if (_reply != Modbus::REPLY_OFF) 208 | rawSend(_slaveId, _frame, _len); 209 | // Cleanup 210 | free(_frame); 211 | _frame = nullptr; 212 | _len = 0; 213 | } 214 | 215 | 216 | bool ModbusRTU::cleanup() { 217 | // Remove timeouted request and forced event 218 | if (_slaveId && (millis() - _timestamp > MODBUSRTU_TIMEOUT)) { 219 | if (_cb) 220 | _cb(Modbus::EX_TIMEOUT, 0, nullptr); 221 | free(_sentFrame); 222 | _sentFrame = nullptr; 223 | _data = nullptr; 224 | _slaveId = 0; 225 | return true; 226 | } 227 | return false; 228 | } 229 | 230 | 231 | uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb) { 232 | readSlave(offset, value, FC_WRITE_REG); 233 | return send(slaveId, HREG(offset), cb, nullptr, cb); 234 | } 235 | 236 | uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool value, cbTransaction cb) { 237 | readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); 238 | return send(slaveId, COIL(offset), cb, nullptr, cb); 239 | } 240 | 241 | uint16_t ModbusRTU::readCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { 242 | if (numregs < 0x0001 || numregs > maxRegs << 4) return false; 243 | readSlave(offset, numregs, FC_READ_COILS); 244 | return send(slaveId, COIL(offset), cb, value); 245 | } 246 | 247 | 248 | uint16_t ModbusRTU::writeCoil(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { 249 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 250 | writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); 251 | return send(slaveId, COIL(offset), cb, nullptr, cb); 252 | } 253 | 254 | 255 | uint16_t ModbusRTU::writeHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { 256 | if (numregs < 0x0001 || numregs > 0x007D) return false; 257 | writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); 258 | return send(slaveId, HREG(offset), cb, nullptr, cb); 259 | } 260 | 261 | 262 | uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { 263 | if (numregs < 0x0001 || numregs > maxRegs) return false; 264 | readSlave(offset, numregs, FC_READ_REGS); 265 | return send(slaveId, HREG(offset), cb, value); 266 | } 267 | 268 | 269 | uint16_t ModbusRTU::readIsts(uint8_t slaveId, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb) { 270 | if (numregs < 0x0001 || numregs > maxRegs << 4) return false; 271 | readSlave(offset, numregs, FC_READ_INPUT_STAT); 272 | return send(slaveId, ISTS(offset), cb, value); 273 | } 274 | 275 | 276 | uint16_t ModbusRTU::readIreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) { 277 | if (numregs < 0x0001 || numregs > maxRegs) return false; 278 | readSlave(offset, numregs, FC_READ_INPUT_REGS); 279 | return send(slaveId, IREG(offset), cb, value); 280 | } 281 | 282 | 283 | uint16_t ModbusRTU::pushCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { 284 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 285 | if (!searchRegister(COIL(from))) return false; 286 | if (numregs == 1) { 287 | readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); 288 | } else { 289 | writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); 290 | } 291 | return send(slaveId, COIL(from), cb); 292 | } 293 | 294 | 295 | uint16_t ModbusRTU::pullCoil(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { 296 | if (numregs < 0x0001 || numregs > maxRegs << 4) return false; 297 | #ifdef MODBUSRTU_ADD_REG 298 | addCoil(to, numregs); 299 | #endif 300 | readSlave(from, numregs, FC_READ_COILS); 301 | return send(slaveId, COIL(to), cb); 302 | } 303 | 304 | 305 | uint16_t ModbusRTU::pullIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { 306 | if (numregs < 0x0001 || numregs > maxRegs << 4) return false; 307 | #ifdef MODBUSRTU_ADD_REG 308 | addIsts(to, numregs); 309 | #endif 310 | readSlave(from, numregs, FC_READ_INPUT_STAT); 311 | return send(slaveId, ISTS(to), cb); 312 | } 313 | 314 | 315 | uint16_t ModbusRTU::pushHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { 316 | if (numregs < 0x0001 || numregs > 0x007D) return false; 317 | if (!searchRegister(HREG(from))) return false; 318 | if (numregs == 1) { 319 | readSlave(to, Hreg(from), FC_WRITE_REG); 320 | } else { 321 | writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); 322 | } 323 | return send(slaveId, HREG(from), cb); 324 | } 325 | 326 | 327 | uint16_t ModbusRTU::pullHreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { 328 | if (numregs < 0x0001 || numregs > maxRegs) return false; 329 | #ifdef MODBUSRTU_ADD_REG 330 | addHreg(to, numregs); 331 | #endif 332 | readSlave(from, numregs, FC_READ_REGS); 333 | return send(slaveId, HREG(to), cb); 334 | } 335 | 336 | 337 | uint16_t ModbusRTU::pullIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { 338 | if (numregs < 0x0001 || numregs > maxRegs) return false; 339 | #ifdef MODBUSRTU_ADD_REG 340 | addIreg(to, numregs); 341 | #endif 342 | readSlave(from, numregs, FC_READ_INPUT_REGS); 343 | return send(slaveId, IREG(to), cb); 344 | } 345 | 346 | 347 | uint16_t ModbusRTU::pushIregToHreg(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { 348 | if (numregs < 0x0001 || numregs > 0x007D) return false; 349 | if (!searchRegister(IREG(from))) return false; 350 | if (numregs == 1) { 351 | readSlave(to, Ireg(from), FC_WRITE_REG); 352 | } else { 353 | writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); 354 | } 355 | return send(slaveId, IREG(from), cb); 356 | } 357 | 358 | 359 | uint16_t ModbusRTU::pushIstsToCoil(uint8_t slaveId, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb) { 360 | if (numregs < 0x0001 || numregs > maxRegs << 4) return false; 361 | if (!searchRegister(ISTS(from))) return false; 362 | if (numregs == 1) { 363 | readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); 364 | } else { 365 | writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); 366 | } 367 | return send(slaveId, ISTS(from), cb); 368 | } 369 | 370 | 371 | uint16_t ModbusRTU::pullHregToIreg(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { 372 | if (numregs < 0x0001 || numregs > maxRegs) return false; 373 | #ifdef MODBUSRTU_ADD_REG 374 | addIreg(to, numregs); 375 | #endif 376 | readSlave(from, numregs, FC_READ_REGS); 377 | return send(slaveId, IREG(to), cb); 378 | } 379 | 380 | 381 | uint16_t ModbusRTU::pullCoilToIsts(uint8_t slaveId, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb) { 382 | if (numregs < 0x0001 || numregs > maxRegs << 4) return false; 383 | #ifdef MODBUSRTU_ADD_REG 384 | addIsts(to, numregs); 385 | #endif 386 | readSlave(from, numregs, FC_READ_COILS); 387 | return send(slaveId, ISTS(to), cb); 388 | } -------------------------------------------------------------------------------- /src/ModbusIP_ESP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusIP_ESP.cpp - ModbusIP Library Implementation 3 | Copyright (C) 2014 Andr� Sarmento Barbosa 4 | 2017-2019 Alexander Emelianov (a.m.emelianov@gmail.com) 5 | */ 6 | #if defined(ESP32) || defined(ESP8266) 7 | 8 | #include "ModbusIP_ESP.h" 9 | 10 | ModbusIP::ModbusIP() { 11 | //_trans.reserve(MODBUSIP_MAX_TRANSACIONS); 12 | for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) 13 | tcpclient[i] = nullptr; 14 | } 15 | 16 | void ModbusIP::client() { 17 | 18 | } 19 | 20 | void ModbusIP::server(uint16_t port) { 21 | serverPort = port; 22 | tcpserver = new WiFiServer(serverPort); 23 | tcpserver->begin(); 24 | } 25 | 26 | bool ModbusIP::connect(IPAddress ip, uint16_t port) { 27 | //cleanup(); 28 | if(getSlave(ip) != -1) 29 | return true; 30 | int8_t p = getFreeClient(); 31 | if (p == -1) 32 | return false; 33 | tcpclient[p] = new WiFiClient(); 34 | return tcpclient[p]->connect(ip, port); 35 | } 36 | 37 | uint32_t ModbusIP::eventSource() { // Returns IP of current processing client query 38 | if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && tcpclient[n]) 39 | return (uint32_t)tcpclient[n]->remoteIP(); 40 | return (uint32_t)INADDR_NONE; 41 | } 42 | 43 | TTransaction* ModbusIP::searchTransaction(uint16_t id) { 44 | std::vector::iterator it = std::find_if(_trans.begin(), _trans.end(), [id](TTransaction& trans){return trans.transactionId == id;}); 45 | if (it != _trans.end()) return &*it; 46 | return nullptr; 47 | } 48 | 49 | void ModbusIP::task() { 50 | MBAP_t _MBAP; 51 | cleanup(); 52 | if (tcpserver) { 53 | while (tcpserver->hasClient()) { 54 | WiFiClient* currentClient = new WiFiClient(tcpserver->available()); 55 | if (!currentClient || !currentClient->connected()) 56 | continue; 57 | if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) { 58 | #ifdef MODBUSIP_UNIQUE_CLIENTS 59 | // Disconnect previous connection from same IP if present 60 | n = getMaster(currentClient->remoteIP()); 61 | if (n != -1) { 62 | tcpclient[n]->flush(); 63 | delete tcpclient[n]; 64 | tcpclient[n] = nullptr; 65 | } 66 | #endif 67 | n = getFreeClient(); 68 | if (n > -1) { 69 | tcpclient[n] = currentClient; 70 | continue; // while 71 | } 72 | } 73 | // Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached 74 | delete currentClient; 75 | } 76 | } 77 | for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) { 78 | if (!tcpclient[n]) continue; 79 | if (!tcpclient[n]->connected()) continue; 80 | uint32_t readStart = millis(); 81 | while (millis() - readStart < MODBUSIP_MAX_READMS && tcpclient[n]->available() > sizeof(_MBAP)) { 82 | tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP 83 | 84 | if (__bswap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __bswap is usless there. 85 | while (tcpclient[n]->available()) // Drop all incoming if wrong packet 86 | tcpclient[n]->read(); 87 | continue; 88 | } 89 | _len = __bswap_16(_MBAP.length); 90 | _len--; // Do not count with last byte from MBAP 91 | if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME 92 | exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); 93 | _len--; // Subtract for read byte 94 | for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of packet 95 | tcpclient[n]->read(); 96 | } else { 97 | free(_frame); 98 | _frame = (uint8_t*) malloc(_len); 99 | if (!_frame) { 100 | exceptionResponse((FunctionCode)tcpclient[n]->read(), EX_SLAVE_FAILURE); 101 | for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop packet 102 | tcpclient[n]->read(); 103 | } else { 104 | if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame 105 | exceptionResponse((FunctionCode)_frame[0], EX_ILLEGAL_VALUE); 106 | //while (tcpclient[n]->available()) // Drop all incoming (if any) 107 | // tcpclient[n]->read(); 108 | } else { 109 | if (tcpclient[n]->localPort() == serverPort) { 110 | // Process incoming frame as slave 111 | slavePDU(_frame); 112 | } else { 113 | // Process reply to master request 114 | _reply = EX_SUCCESS; 115 | TTransaction* trans = searchTransaction(__bswap_16(_MBAP.transactionId)); 116 | if (trans) { // if valid transaction id 117 | if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested 118 | // Procass incoming frame as master 119 | masterPDU(_frame, trans->_frame, trans->startreg, trans->data); 120 | } else { 121 | _reply = EX_UNEXPECTED_RESPONSE; 122 | } 123 | if (trans->cb) { 124 | trans->cb((ResultCode)_reply, trans->transactionId, nullptr); 125 | } 126 | free(trans->_frame); 127 | //_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() ); 128 | std::vector::iterator it = std::find(_trans.begin(), _trans.end(), *trans); 129 | if (it != _trans.end()) 130 | _trans.erase(it); 131 | } 132 | } 133 | } 134 | } 135 | } 136 | if (tcpclient[n]->localPort() != serverPort) _reply = REPLY_OFF; // No replay if it was responce to master 137 | if (_reply != REPLY_OFF) { 138 | _MBAP.length = __bswap_16(_len+1); // _len+1 for last byte from MBAP 139 | size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw); 140 | uint8_t sbuf[send_len]; 141 | memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); 142 | memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); 143 | tcpclient[n]->write(sbuf, send_len); 144 | tcpclient[n]->flush(); 145 | } 146 | if (_frame) { 147 | free(_frame); 148 | _frame = nullptr; 149 | } 150 | _len = 0; 151 | } 152 | } 153 | n = -1; 154 | } 155 | 156 | uint16_t ModbusIP::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, void* data, bool waitResponse) { 157 | MBAP_t _MBAP; 158 | #ifdef MODBUSIP_MAX_TRANSACIONS 159 | if (_trans.size() >= MODBUSIP_MAX_TRANSACIONS) return false; 160 | #endif 161 | int8_t p = getSlave(ip); 162 | if (p == -1 || !tcpclient[p]->connected()) 163 | return autoConnectMode?connect(ip):false; 164 | transactionId++; 165 | if (!transactionId) transactionId = 1; 166 | _MBAP.transactionId = __bswap_16(transactionId); 167 | _MBAP.protocolId = __bswap_16(0); 168 | _MBAP.length = __bswap_16(_len+1); //_len+1 for last byte from MBAP 169 | _MBAP.unitId = unit; 170 | size_t send_len = _len + sizeof(_MBAP.raw); 171 | uint8_t sbuf[send_len]; 172 | memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw)); 173 | memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len); 174 | if (tcpclient[p]->write(sbuf, send_len) != send_len) 175 | return false; 176 | tcpclient[p]->flush(); 177 | if (waitResponse) { 178 | TTransaction tmp; 179 | tmp.transactionId = transactionId; 180 | tmp.timestamp = millis(); 181 | tmp.cb = cb; 182 | tmp.data = data; // BUG: Should data be saved? It may lead to memory leak or double free. 183 | tmp._frame = _frame; 184 | tmp.startreg = startreg; 185 | _trans.push_back(tmp); 186 | _frame = nullptr; 187 | _len = 0; 188 | } 189 | return transactionId; 190 | } 191 | 192 | 193 | void ModbusIP::onConnect(cbModbusConnect cb) { 194 | cbConnect = cb; 195 | } 196 | 197 | void ModbusIP::onDisconnect(cbModbusConnect cb) { 198 | cbDisconnect = cb; 199 | } 200 | 201 | void ModbusIP::cleanup() { 202 | // Free clients if not connected 203 | for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { 204 | if (tcpclient[i] && !tcpclient[i]->connected()) { 205 | //IPAddress ip = tcpclient[i]->remoteIP(); 206 | //tcpclient[i]->stop(); 207 | delete tcpclient[i]; 208 | tcpclient[i] = nullptr; 209 | if (cbDisconnect && cbEnabled) 210 | cbDisconnect(IPADDR_NONE); 211 | } 212 | } 213 | // Remove timedout transactions and forced event 214 | for (auto it = _trans.begin(); it != _trans.end();) { 215 | if (millis() - it->timestamp > MODBUSIP_TIMEOUT || it->forcedEvent != Modbus::EX_SUCCESS) { 216 | Modbus::ResultCode res = (it->forcedEvent != Modbus::EX_SUCCESS)?it->forcedEvent:Modbus::EX_TIMEOUT; 217 | if (it->cb) 218 | it->cb(res, it->transactionId, nullptr); 219 | free(it->_frame); 220 | it = _trans.erase(it); 221 | } else 222 | it++; 223 | } 224 | } 225 | 226 | int8_t ModbusIP::getFreeClient() { 227 | for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) 228 | if (!tcpclient[i]) 229 | return i; 230 | return -1; 231 | } 232 | 233 | int8_t ModbusIP::getSlave(IPAddress ip) { 234 | for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) 235 | if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() != serverPort) 236 | return i; 237 | return -1; 238 | } 239 | 240 | int8_t ModbusIP::getMaster(IPAddress ip) { 241 | for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) 242 | if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && tcpclient[i]->localPort() == serverPort) 243 | return i; 244 | return -1; 245 | } 246 | 247 | uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool value, cbTransaction cb, uint8_t unit) { 248 | readSlave(offset, COIL_VAL(value), FC_WRITE_COIL); 249 | return send(ip, COIL(offset), cb, unit, nullptr, cb); 250 | } 251 | 252 | uint16_t ModbusIP::writeCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { 253 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 254 | writeSlaveBits(COIL(offset), offset, numregs, FC_WRITE_COILS, value); 255 | return send(ip, COIL(offset), cb, unit, nullptr, cb); 256 | } 257 | 258 | uint16_t ModbusIP::readCoil(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { 259 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 260 | readSlave(offset, numregs, FC_READ_COILS); 261 | return send(ip, COIL(offset), cb, unit, value); 262 | } 263 | 264 | uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t value, cbTransaction cb, uint8_t unit) { 265 | readSlave(offset, value, FC_WRITE_REG); 266 | return send(ip, HREG(offset), cb, unit, nullptr, cb); 267 | } 268 | 269 | uint16_t ModbusIP::writeHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { 270 | if (numregs < 0x0001 || numregs > 0x007D) return false; 271 | writeSlaveWords(HREG(offset), offset, numregs, FC_WRITE_REGS, value); 272 | return send(ip, HREG(offset), cb, unit, nullptr, cb); 273 | } 274 | 275 | uint16_t ModbusIP::readHreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { 276 | if (numregs < 0x0001 || numregs > 0x007D) return false; 277 | readSlave(offset, numregs, FC_READ_REGS); 278 | return send(ip, HREG(offset), cb, unit, value); 279 | } 280 | 281 | uint16_t ModbusIP::readIsts(IPAddress ip, uint16_t offset, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { 282 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 283 | readSlave(offset, numregs, FC_READ_INPUT_STAT); 284 | return send(ip, ISTS(offset), cb, unit, value); 285 | } 286 | 287 | uint16_t ModbusIP::readIreg(IPAddress ip, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { 288 | if (numregs < 0x0001 || numregs > 0x007D) return false; 289 | readSlave(offset, numregs, FC_READ_INPUT_REGS); 290 | return send(ip, IREG(offset), cb, unit, value); 291 | } 292 | 293 | uint16_t ModbusIP::pushCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { 294 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 295 | if (!searchRegister(COIL(from))) return false; 296 | if (numregs == 1) { 297 | readSlave(to, COIL_VAL(Coil(from)), FC_WRITE_COIL); 298 | } else { 299 | writeSlaveBits(COIL(from), to, numregs, FC_WRITE_COILS); 300 | } 301 | return send(ip, COIL(from), cb, unit); 302 | } 303 | 304 | uint16_t ModbusIP::pullCoil(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { 305 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 306 | #ifdef MODBUSIP_ADD_REG 307 | addCoil(to, numregs); 308 | #endif 309 | readSlave(from, numregs, FC_READ_COILS); 310 | return send(ip, COIL(to), cb, unit); 311 | } 312 | 313 | uint16_t ModbusIP::pullIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { 314 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 315 | #ifdef MODBUSIP_ADD_REG 316 | addIsts(to, numregs); 317 | #endif 318 | readSlave(from, numregs, FC_READ_INPUT_STAT); 319 | return send(ip, ISTS(to), cb, unit); 320 | } 321 | 322 | uint16_t ModbusIP::pushHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { 323 | if (numregs < 0x0001 || numregs > 0x007D) return false; 324 | if (!searchRegister(HREG(from))) return false; 325 | if (numregs == 1) { 326 | readSlave(to, Hreg(from), FC_WRITE_REG); 327 | } else { 328 | writeSlaveWords(HREG(from), to, numregs, FC_WRITE_REGS); 329 | } 330 | return send(ip, HREG(from), cb, unit); 331 | } 332 | 333 | uint16_t ModbusIP::pullHreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { 334 | if (numregs < 0x0001 || numregs > 0x007D) return false; 335 | #ifdef MODBUSIP_ADD_REG 336 | addHreg(to, numregs); 337 | #endif 338 | readSlave(from, numregs, FC_READ_REGS); 339 | return send(ip, HREG(to), cb, unit); 340 | } 341 | 342 | uint16_t ModbusIP::pullIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { 343 | if (numregs < 0x0001 || numregs > 0x007D) return false; 344 | #ifdef MODBUSIP_ADD_REG 345 | addIreg(to, numregs); 346 | #endif 347 | readSlave(from, numregs, FC_READ_INPUT_REGS); 348 | return send(ip, IREG(to), cb, unit); 349 | } 350 | 351 | uint16_t ModbusIP::pushIregToHreg(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { 352 | if (numregs < 0x0001 || numregs > 0x007D) return false; 353 | if (!searchRegister(IREG(from))) return false; 354 | if (numregs == 1) { 355 | readSlave(to, Ireg(from), FC_WRITE_REG); 356 | } else { 357 | writeSlaveWords(IREG(from), to, numregs, FC_WRITE_REGS); 358 | } 359 | return send(ip, IREG(from), cb, unit); 360 | } 361 | 362 | uint16_t ModbusIP::pushIstsToCoil(IPAddress ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { 363 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 364 | if (!searchRegister(ISTS(from))) return false; 365 | if (numregs == 1) { 366 | readSlave(to, ISTS_VAL(Ists(from)), FC_WRITE_COIL); 367 | } else { 368 | writeSlaveBits(ISTS(from), to, numregs, FC_WRITE_COILS); 369 | } 370 | return send(ip, ISTS(from), cb, unit); 371 | } 372 | 373 | uint16_t ModbusIP::pullHregToIreg(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { 374 | if (numregs < 0x0001 || numregs > 0x007D) return false; 375 | #ifdef MODBUSIP_ADD_REG 376 | addIreg(to, numregs); 377 | #endif 378 | readSlave(from, numregs, FC_READ_REGS); 379 | return send(ip, IREG(to), cb, unit); 380 | } 381 | 382 | uint16_t ModbusIP::pullCoilToIsts(IPAddress ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { 383 | if (numregs < 0x0001 || numregs > 0x07D0) return false; 384 | #ifdef MODBUSIP_ADD_REG 385 | addIsts(to, numregs); 386 | #endif 387 | readSlave(from, numregs, FC_READ_COILS); 388 | return send(ip, ISTS(to), cb, unit); 389 | } 390 | 391 | bool ModbusIP::isTransaction(uint16_t id) { 392 | return searchTransaction(id) != nullptr; 393 | } 394 | bool ModbusIP::isConnected(IPAddress ip) { 395 | int8_t p = getSlave(ip); 396 | return p != -1 && tcpclient[p]->connected(); 397 | } 398 | 399 | void ModbusIP::autoConnect(bool enabled) { 400 | autoConnectMode = enabled; 401 | } 402 | 403 | bool ModbusIP::disconnect(IPAddress ip) { 404 | int8_t p = getSlave(ip); 405 | if (p != -1) { 406 | delete tcpclient[p]; 407 | tcpclient[p] = nullptr; 408 | } 409 | return true; 410 | } 411 | 412 | void ModbusIP::dropTransactions() { 413 | for (auto &t : _trans) t.forcedEvent = EX_CANCEL; 414 | } 415 | 416 | ModbusIP::~ModbusIP() { 417 | free(_frame); 418 | dropTransactions(); 419 | cleanup(); 420 | for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) { 421 | delete tcpclient[i]; 422 | tcpclient[i] = nullptr; 423 | } 424 | } 425 | 426 | #endif 427 | -------------------------------------------------------------------------------- /src/Modbus.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Modbus.cpp - Modbus Core Library Implementation 3 | Copyright (C) 2014 Andr� Sarmento Barbosa 4 | 2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com) 5 | */ 6 | #include "Modbus.h" 7 | 8 | #ifdef MB_GLOBAL_REGS 9 | std::vector _regs; 10 | std::vector _callbacks; 11 | #endif 12 | 13 | uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType t) { 14 | uint16_t newVal = val; 15 | std::vector::iterator it = _callbacks.begin(); 16 | do { 17 | it = std::find_if(it, _callbacks.end(), [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}); 18 | if (it != _callbacks.end()) { 19 | newVal = it->cb(reg, newVal); 20 | it++; 21 | } 22 | } while (it != _callbacks.end()); 23 | return newVal; 24 | } 25 | 26 | TRegister* Modbus::searchRegister(TAddress address) { 27 | std::vector::iterator it = std::find_if(_regs.begin(), _regs.end(), [address](TRegister& addr){return addr.address == address;}); 28 | if (it != _regs.end()) return &*it; 29 | return nullptr; 30 | } 31 | 32 | bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) { 33 | #ifdef MB_MAX_REGS 34 | if (_regs.size() + numregs > MB_MAX_REGS) return false; 35 | #endif 36 | for (uint16_t i = 0; i < numregs; i++) { 37 | if (!searchRegister(address + i)) 38 | _regs.push_back({address + i, value}); 39 | } 40 | //std::sort(_regs.begin(), _regs.end()); 41 | return true; 42 | } 43 | 44 | bool Modbus::Reg(TAddress address, uint16_t value) { 45 | TRegister* reg; 46 | reg = searchRegister(address); //search for the register address 47 | if (reg) { //if found then assign the register value to the new value. 48 | if (cbEnabled) { 49 | reg->value = callback(reg, value, TCallback::ON_SET); 50 | } else { 51 | reg->value = value; 52 | } 53 | return true; 54 | } else 55 | return false; 56 | } 57 | 58 | uint16_t Modbus::Reg(TAddress address) { 59 | TRegister* reg; 60 | reg = searchRegister(address); 61 | if(reg) 62 | if (cbEnabled) { 63 | return callback(reg, reg->value, TCallback::ON_GET); 64 | } else { 65 | return reg->value; 66 | } 67 | else 68 | return 0; 69 | } 70 | 71 | bool Modbus::removeReg(TAddress address, uint16_t numregs) { 72 | TRegister* reg; 73 | bool atLeastOne = false; 74 | for (uint16_t i = 0; i < numregs; i++) { 75 | reg = searchRegister(address + i); 76 | if (reg) { 77 | atLeastOne = true; 78 | removeOnSet(address + i); 79 | removeOnGet(address + i); 80 | _regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() ); 81 | } 82 | } 83 | return atLeastOne; 84 | } 85 | 86 | void Modbus::slavePDU(uint8_t* frame) { 87 | FunctionCode fcode = (FunctionCode)frame[0]; 88 | uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2]; 89 | uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4]; 90 | uint16_t bytecount_calc; 91 | uint16_t k; 92 | switch (fcode) { 93 | case FC_WRITE_REG: 94 | //field1 = reg, field2 = value 95 | if (!Hreg(field1, field2)) { //Check Address and execute (reg exists?) 96 | exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); 97 | break; 98 | } 99 | if (Hreg(field1) != field2) { //Check for failure 100 | exceptionResponse(fcode, EX_SLAVE_FAILURE); 101 | break; 102 | } 103 | _reply = REPLY_ECHO; 104 | break; 105 | 106 | case FC_READ_REGS: 107 | //field1 = startreg, field2 = numregs, header len = 3 108 | readWords(HREG(field1), field2, fcode); 109 | break; 110 | 111 | case FC_WRITE_REGS: 112 | //field1 = startreg, field2 = numregs, frame[5] = data lenght, header len = 6 113 | if (field2 < 0x0001 || field2 > 0x007B || frame[5] != 2 * field2) { //Check value 114 | exceptionResponse(fcode, EX_ILLEGAL_VALUE); 115 | break; 116 | } 117 | for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) 118 | if (!searchRegister(HREG(field1) + k)) { 119 | exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); 120 | break; 121 | } 122 | } 123 | if (k >= field2) { 124 | setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2); 125 | successResponce(HREG(field1), field2, fcode); 126 | _reply = REPLY_NORMAL; 127 | } 128 | break; 129 | 130 | case FC_READ_COILS: 131 | //field1 = startreg, field2 = numregs 132 | readBits(COIL(field1), field2, fcode); 133 | break; 134 | 135 | case FC_READ_INPUT_STAT: 136 | //field1 = startreg, field2 = numregs 137 | readBits(ISTS(field1), field2, fcode); 138 | break; 139 | 140 | case FC_READ_INPUT_REGS: 141 | //field1 = startreg, field2 = numregs 142 | readWords(IREG(field1), field2, fcode); 143 | break; 144 | 145 | case FC_WRITE_COIL: 146 | //field1 = reg, field2 = status, header len = 3 147 | if (field2 != 0xFF00 && field2 != 0x0000) { //Check value (status) 148 | exceptionResponse(fcode, EX_ILLEGAL_VALUE); 149 | break; 150 | } 151 | if (!Coil(field1, COIL_BOOL(field2))) { //Check Address and execute (reg exists?) 152 | exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); 153 | break; 154 | } 155 | if (Coil(field1) != COIL_BOOL(field2)) { //Check for failure 156 | exceptionResponse(fcode, EX_SLAVE_FAILURE); 157 | break; 158 | } 159 | _reply = REPLY_ECHO; 160 | break; 161 | 162 | case FC_WRITE_COILS: 163 | //field1 = startreg, field2 = numregs, frame[5] = bytecount, header len = 6 164 | bytecount_calc = field2 / 8; 165 | if (field2%8) bytecount_calc++; 166 | if (field2 < 0x0001 || field2 > 0x07B0 || frame[5] != bytecount_calc) { //Check registers range and data size maches 167 | exceptionResponse(fcode, EX_ILLEGAL_VALUE); 168 | break; 169 | } 170 | for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs) 171 | if (!searchRegister(COIL(field1) + k)) { 172 | exceptionResponse(fcode, EX_ILLEGAL_ADDRESS); 173 | break; 174 | } 175 | } 176 | if (k >= field2) { 177 | setMultipleBits(frame + 6, COIL(field1), field2); 178 | successResponce(COIL(field1), field2, fcode); 179 | _reply = REPLY_NORMAL; 180 | } 181 | break; 182 | 183 | default: 184 | exceptionResponse(fcode, EX_ILLEGAL_FUNCTION); 185 | } 186 | } 187 | 188 | void Modbus::successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn) { 189 | free(_frame); 190 | _len = 5; 191 | _frame = (uint8_t*) malloc(_len); 192 | _frame[0] = fn; 193 | _frame[1] = startreg.address >> 8; 194 | _frame[2] = startreg.address & 0x00FF; 195 | _frame[3] = numoutputs >> 8; 196 | _frame[4] = numoutputs & 0x00FF; 197 | } 198 | 199 | void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) { 200 | free(_frame); 201 | _len = 2; 202 | _frame = (uint8_t*) malloc(_len); 203 | _frame[0] = fn + 0x80; 204 | _frame[1] = excode; 205 | _reply = REPLY_NORMAL; 206 | } 207 | 208 | void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs) { 209 | uint8_t bitn = 0; 210 | uint16_t i = 0; 211 | while (numregs--) { 212 | if (BIT_BOOL(Reg(startreg))) 213 | bitSet(frame[i], bitn); 214 | else 215 | bitClear(frame[i], bitn); 216 | bitn++; //increment the bit index 217 | if (bitn == 8) { 218 | i++; 219 | bitn = 0; 220 | } 221 | startreg++; //increment the register 222 | } 223 | } 224 | 225 | void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { 226 | for (uint8_t i = 0; i < numregs; i++) { 227 | frame[i] = __bswap_16(Reg(startreg + i)); 228 | } 229 | } 230 | 231 | void Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) { 232 | if (numregs < 0x0001 || numregs > 0x07D0) { //Check value (numregs) 233 | exceptionResponse(fn, EX_ILLEGAL_VALUE); 234 | return; 235 | } 236 | //Check Address 237 | //Check only startreg. Is this correct? 238 | //When I check all registers in range I got errors in ScadaBR 239 | //I think that ScadaBR request more than one in the single request 240 | //when you have more then one datapoint configured from same type. 241 | if (!searchRegister(startreg)) { 242 | exceptionResponse(fn, EX_ILLEGAL_ADDRESS); 243 | return; 244 | } 245 | free(_frame); 246 | //Determine the message length = function type, byte count and 247 | //for each group of 8 registers the message length increases by 1 248 | _len = 2 + numregs/8; 249 | if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. 250 | _frame = (uint8_t*) malloc(_len); 251 | if (!_frame) { 252 | exceptionResponse(fn, EX_SLAVE_FAILURE); 253 | return; 254 | } 255 | _frame[0] = fn; 256 | _frame[1] = _len - 2; //byte count (_len - function code and byte count) 257 | _frame[_len - 1] = 0; //Clean last probably partial byte 258 | getMultipleBits(_frame+2, startreg, numregs); 259 | _reply = REPLY_NORMAL; 260 | } 261 | 262 | void Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) { 263 | //Check value (numregs) 264 | if (numregs < 0x0001 || numregs > 0x007D) { 265 | exceptionResponse(fn, EX_ILLEGAL_VALUE); 266 | return; 267 | } 268 | if (!searchRegister(startreg)) { //Check Address 269 | exceptionResponse(fn, EX_ILLEGAL_ADDRESS); 270 | return; 271 | } 272 | free(_frame); 273 | _len = 2 + numregs * 2; //calculate the query reply message length. 2 bytes per register + 2 bytes for header 274 | _frame = (uint8_t*) malloc(_len); 275 | if (!_frame) { 276 | exceptionResponse(fn, EX_SLAVE_FAILURE); 277 | return; 278 | } 279 | _frame[0] = fn; 280 | _frame[1] = _len - 2; //byte count 281 | getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs); 282 | _reply = REPLY_NORMAL; 283 | } 284 | 285 | void Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) { 286 | uint8_t bitn = 0; 287 | uint16_t i = 0; 288 | while (numoutputs--) { 289 | Reg(startreg, BIT_VAL(bitRead(frame[i], bitn))); 290 | bitn++; //increment the bit index 291 | if (bitn == 8) { 292 | i++; 293 | bitn = 0; 294 | } 295 | startreg++; //increment the register 296 | } 297 | } 298 | 299 | void Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) { 300 | for (uint8_t i = 0; i < numregs; i++) { 301 | Reg(startreg + i, __bswap_16(frame[i])); 302 | } 303 | } 304 | 305 | bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) { 306 | TRegister* reg; 307 | bool atLeastOne = false; 308 | if (!cb) { 309 | return removeOnGet(address); 310 | } 311 | while (numregs > 0) { 312 | reg = searchRegister(address); 313 | if (reg) { 314 | _callbacks.push_back({TCallback::ON_GET, address, cb}); 315 | atLeastOne = true; 316 | } 317 | address++; 318 | numregs--; 319 | } 320 | return atLeastOne; 321 | } 322 | bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) { 323 | TRegister* reg; 324 | bool atLeastOne = false; 325 | if (!cb) { 326 | return removeOnGet(address); 327 | } 328 | while (numregs > 0) { 329 | reg = searchRegister(address); 330 | if (reg) { 331 | _callbacks.push_back({TCallback::ON_SET, address, cb}); 332 | atLeastOne = true; 333 | } 334 | address++; 335 | numregs--; 336 | } 337 | return atLeastOne; 338 | } 339 | 340 | bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) { 341 | while(numregs--) { 342 | _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), [address, cb](TCallback entry){ 343 | return entry.type == TCallback::ON_SET && entry.address == address && (!cb || entry.cb == cb);} ), _callbacks.end() ); 344 | address++; 345 | } 346 | return false; 347 | } 348 | bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) { 349 | while(numregs--) { 350 | _callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), [address, cb](TCallback entry){ 351 | return entry.type == TCallback::ON_GET && entry.address == address && (!cb || entry.cb == cb);} ), _callbacks.end() ); 352 | address++; 353 | } 354 | return false; 355 | } 356 | 357 | bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) { 358 | free(_frame); 359 | _len = 5; 360 | _frame = (uint8_t*) malloc(_len); 361 | _frame[0] = fn; 362 | _frame[1] = address >> 8; 363 | _frame[2] = address & 0x00FF; 364 | _frame[3] = numregs >> 8; 365 | _frame[4] = numregs & 0x00FF; 366 | return true; 367 | } 368 | 369 | bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data) { 370 | free(_frame); 371 | _len = 6 + numregs/8; 372 | if (numregs % 8) _len++; //Add 1 to the message length for the partial byte. 373 | _frame = (uint8_t*) malloc(_len); 374 | if (_frame) { 375 | _frame[0] = fn; 376 | _frame[1] = to >> 8; 377 | _frame[2] = to & 0x00FF; 378 | _frame[3] = numregs >> 8; 379 | _frame[4] = numregs & 0x00FF; 380 | _frame[5] = _len - 6; 381 | _frame[_len - 1] = 0; //Clean last probably partial byte 382 | if (data) { 383 | boolToBits(_frame + 6, data, numregs); 384 | } else { 385 | getMultipleBits(_frame + 6, startreg, numregs); 386 | } 387 | _reply = REPLY_NORMAL; 388 | return true; 389 | } 390 | _reply = REPLY_OFF; 391 | return false; 392 | } 393 | 394 | bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data) { 395 | free(_frame); 396 | _len = 6 + 2 * numregs; 397 | _frame = (uint8_t*) malloc(_len); 398 | if (_frame) { 399 | _frame[0] = fn; 400 | _frame[1] = to >> 8; 401 | _frame[2] = to & 0x00FF; 402 | _frame[3] = numregs >> 8; 403 | _frame[4] = numregs & 0x00FF; 404 | _frame[5] = _len - 6; 405 | if (data) { 406 | uint16_t* frame = (uint16_t*)(_frame + 6); 407 | for (uint8_t i = 0; i < numregs; i++) { 408 | frame[i] = __bswap_16(data[i]); 409 | } 410 | } else { 411 | getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs); 412 | } 413 | return true; 414 | } 415 | _reply = REPLY_OFF; 416 | return false; 417 | } 418 | 419 | void Modbus::boolToBits(uint8_t* dst, bool* src, uint16_t numregs) { 420 | uint8_t bitn = 0; 421 | uint16_t i = 0; 422 | uint16_t j = 0; 423 | while (numregs--) { 424 | if (src[j]) 425 | bitSet(dst[i], bitn); 426 | else 427 | bitClear(dst[i], bitn); 428 | bitn++; //increment the bit index 429 | if (bitn == 8) { 430 | i++; 431 | bitn = 0; 432 | } 433 | j++; //increment the register 434 | } 435 | } 436 | 437 | void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) { 438 | uint8_t bitn = 0; 439 | uint16_t i = 0; 440 | uint16_t j = 0; 441 | while (numregs--) { 442 | dst[j] = bitRead(src[i], bitn); 443 | bitn++; //increment the bit index 444 | if (bitn == 8) { 445 | i++; 446 | bitn = 0; 447 | } 448 | j++; //increment the register 449 | } 450 | } 451 | 452 | void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, void* output) { 453 | uint8_t fcode = frame[0]; 454 | _reply = EX_SUCCESS; 455 | if ((fcode & 0x80) != 0) { 456 | _reply = frame[1]; 457 | return; 458 | } 459 | uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4]; 460 | uint8_t bytecount_calc; 461 | switch (fcode) { 462 | case FC_READ_REGS: 463 | case FC_READ_INPUT_REGS: 464 | //field2 = numregs, frame[1] = data lenght, header len = 2 465 | if (frame[1] != 2 * field2) { //Check if data size matches 466 | _reply = EX_DATA_MISMACH; 467 | break; 468 | } 469 | if (output) { 470 | frame += 2; 471 | while(field2) { 472 | *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame)); 473 | frame += 2; 474 | output += 2; 475 | field2--; 476 | } 477 | } else { 478 | setMultipleWords((uint16_t*)(frame + 2), startreg, field2); 479 | } 480 | break; 481 | case FC_READ_COILS: 482 | case FC_READ_INPUT_STAT: 483 | //field2 = numregs, frame[1] = data length, header len = 2 484 | bytecount_calc = field2 / 8; 485 | if (field2 % 8) bytecount_calc++; 486 | if (frame[1] != bytecount_calc) { // check if data size matches 487 | _reply = EX_DATA_MISMACH; 488 | break; 489 | } 490 | if (output) { 491 | bitsToBool((bool*)output, frame + 2, field2); 492 | } else { 493 | setMultipleBits(frame + 2, startreg, field2); 494 | } 495 | break; 496 | case FC_WRITE_REG: 497 | case FC_WRITE_REGS: 498 | case FC_WRITE_COIL: 499 | case FC_WRITE_COILS: 500 | break; 501 | default: 502 | _reply = EX_GENERAL_FAILURE; 503 | } 504 | } 505 | 506 | void Modbus::cbEnable(bool state) { 507 | cbEnabled = state; 508 | } 509 | void Modbus::cbDisable() { 510 | cbEnable(false); 511 | } 512 | 513 | bool Modbus::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) { 514 | return addReg(HREG(offset), value, numregs); 515 | } 516 | bool Modbus::Hreg(uint16_t offset, uint16_t value) { 517 | return Reg(HREG(offset), value); 518 | } 519 | uint16_t Modbus::Hreg(uint16_t offset) { 520 | return Reg(HREG(offset)); 521 | } 522 | uint16_t Modbus::removeHreg(uint16_t offset, uint16_t numregs) { 523 | return removeReg(HREG(offset), numregs); 524 | } 525 | bool Modbus::addCoil(uint16_t offset, bool value, uint16_t numregs) { 526 | return addReg(COIL(offset), COIL_VAL(value), numregs); 527 | } 528 | bool Modbus::addIsts(uint16_t offset, bool value, uint16_t numregs) { 529 | return addReg(ISTS(offset), ISTS_VAL(value), numregs); 530 | } 531 | bool Modbus::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) { 532 | return addReg(IREG(offset), value, numregs); 533 | } 534 | bool Modbus::Coil(uint16_t offset, bool value) { 535 | return Reg(COIL(offset), COIL_VAL(value)); 536 | } 537 | bool Modbus::Ists(uint16_t offset, bool value) { 538 | return Reg(ISTS(offset), ISTS_VAL(value)); 539 | } 540 | bool Modbus::Ireg(uint16_t offset, uint16_t value) { 541 | return Reg(IREG(offset), value); 542 | } 543 | bool Modbus::Coil(uint16_t offset) { 544 | return COIL_BOOL(Reg(COIL(offset))); 545 | } 546 | bool Modbus::Ists(uint16_t offset) { 547 | return ISTS_BOOL(Reg(ISTS(offset))); 548 | } 549 | uint16_t Modbus::Ireg(uint16_t offset) { 550 | return Reg(IREG(offset)); 551 | } 552 | bool Modbus::removeCoil(uint16_t offset, uint16_t numregs) { 553 | return removeReg(COIL(offset), numregs); 554 | } 555 | bool Modbus::removeIsts(uint16_t offset, uint16_t numregs) { 556 | return removeReg(ISTS(offset), numregs); 557 | } 558 | bool Modbus::removeIreg(uint16_t offset, uint16_t numregs) { 559 | return removeReg(IREG(offset), numregs); 560 | } 561 | bool Modbus::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { 562 | return onGet(COIL(offset), cb, numregs); 563 | } 564 | bool Modbus::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { 565 | return onSet(COIL(offset), cb, numregs); 566 | } 567 | bool Modbus::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 568 | return onGet(HREG(offset), cb, numregs); 569 | } 570 | bool Modbus::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 571 | return onSet(HREG(offset), cb, numregs); 572 | } 573 | bool Modbus::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { 574 | return onGet(ISTS(offset), cb, numregs); 575 | } 576 | bool Modbus::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { 577 | return onSet(ISTS(offset), cb, numregs); 578 | } 579 | bool Modbus::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 580 | return onGet(IREG(offset), cb, numregs); 581 | } 582 | bool Modbus::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 583 | return onSet(IREG(offset), cb, numregs); 584 | } 585 | 586 | bool Modbus::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { 587 | return removeOnGet(COIL(offset), cb, numregs); 588 | } 589 | bool Modbus::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) { 590 | return removeOnSet(COIL(offset), cb, numregs); 591 | } 592 | bool Modbus::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 593 | return removeOnGet(HREG(offset), cb, numregs); 594 | } 595 | bool Modbus::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 596 | return removeOnSet(HREG(offset), cb, numregs); 597 | } 598 | bool Modbus::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { 599 | return removeOnGet(ISTS(offset), cb, numregs); 600 | } 601 | bool Modbus::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) { 602 | return removeOnSet(ISTS(offset), cb, numregs); 603 | } 604 | bool Modbus::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 605 | return removeOnGet(IREG(offset), cb, numregs); 606 | } 607 | bool Modbus::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) { 608 | return removeOnSet(IREG(offset), cb, numregs); 609 | } 610 | 611 | Modbus::~Modbus() { 612 | free(_frame); 613 | } --------------------------------------------------------------------------------