├── LICENSE ├── ModbusConfig.h ├── README.md ├── examples ├── ModbusConfig_SeveralRS485 │ ├── ModbusConfig_SeveralRS485.ino │ └── data │ │ └── modbus.cfg ├── ModbusConfig_Simple │ ├── ModbusConfig_Simple.ino │ └── data │ │ └── modbus.cfg ├── ModbusConfig_SingleRS485 │ ├── ModbusConfig_SingleRS485.ino │ └── data │ │ └── modbus.cfg ├── ModbusConfig_Termosensor │ ├── ModbusConfig_Termosensor.ino │ └── data │ │ ├── modbus.cfg │ │ └── modbus_ext.cfg └── UniversalConfig_RS485_iWare_ADC │ ├── UniversalConfig_RS485_iWare_ADC.ino │ └── data │ └── modbus.cfg ├── keywords.txt ├── library.json ├── library.properties └── src ├── EspFS.cpp ├── EspFS.h ├── ModbusConfig.cpp ├── ModbusConfig.h └── data └── modbus.cfg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrey Fedorov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ModbusConfig.h: -------------------------------------------------------------------------------- 1 | // ModbusConfig - bizkit.ru 2 | // Copyright Andrey Fedorov 2019 3 | // MIT License 4 | 5 | #include "src/ModbusConfig.h" 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModbusConfig 2 | ModbusConfig library for ESP8266/ESP32/Arduino. 3 | 4 | The library process a sensors configuration file in JSON format and execute operations described in the file. 5 | 6 | ## Description 7 | 8 | Library supports not only Modbus equipment, but iWare and analog also. 9 | 10 | Each device described in a configuration file has set of the parameters to read data from equipment correspondng PollingInterval property. 11 | 12 | RS485/Modbus devices: 13 | 14 | Each device connected to Rx/Tx pin (RS485 interface board) can have several operations block with different polling interval specified in a configuration file. 15 | Any operation has several parameters: SlaveID, function, address, len, operation name etc to visually distinguish operations. 16 | When a device (slave) polling interval has reached, the libriary invoke the callback function to send telemetry data to a cloud server or poll a slave device. 17 | 18 | JSON configuration file should be uploaded to SPIFFS file system on ESP8266/ESP32 boards using corresponding tool: http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html. 19 | 20 | ## Config file example 21 | There are several sections in the configuration file: 22 | - Modbus devices 23 | - iWare sensors (DS18B20, DS18S20 and etc.) 24 | - Analog sensors (current 4..20 mA) 25 | - Relays output 26 | 27 | ### Modbus configuration 28 | ``` 29 | "Modbus": [ 30 | { 31 | "Connection": "tty0", 32 | "Type": "RTU", 33 | "PollingInterval": 60000, 34 | "RetryCount": 20, 35 | "RetryInterval": 100, 36 | "HwId": "RS485TermoSensor", 37 | "RxPin": 5, 38 | "TxPin": 13, 39 | "BaudRate": 9600, 40 | "Config": "SERIAL_8N1", 41 | "HardwareSerial": 2, 42 | "Ops": [ 43 | { 44 | "PollingInterval": 60000, 45 | "SlaveId": 1, 46 | "Function": 4, 47 | "Address": 1, 48 | "Len": 1, 49 | "Transform": "0.1", 50 | "Location": "Warehouse1_Shelf1", 51 | "DisplayName": "Temp" 52 | }, 53 | { 54 | "PollingInterval": 60000, 55 | "SlaveId": 1, 56 | "Function": 4, 57 | "Address": 2, 58 | "Len": 1, 59 | "Transform": "%V%*0.1", 60 | "Location": "Warehouse1_Shelf1", 61 | "DisplayName": "Humidity" 62 | } 63 | ] 64 | } 65 | ] 66 | ``` 67 | Main section describes configuration for the RS485 interface. In case of ESP32 it can has 3 UART (hardware), so, it's possible to use no more than 3 pcs. RS485 interface card. Of course, possible to use SoftwareSerial, but reliability is less, than hardware one. 68 | 69 | #### Modbus main section 70 | - "Connection": "tty0", 71 | - "Type" - RS485 type: "RTU" or "TCP". 72 | - "PollingInterval" - an interval of polling equipment in milliseconds. Usually used "PollingInterval" in Operations sections. 73 | - "RetryCount" - how many times poll a equipment in case of error, 74 | - "RetryInterval" - interval in milliseconds between polling a equipment in case of error, 75 | - "HwId" - some human readable name of the equipment. E.g. for inventarization (Assets management). 76 | - "RxPin" - GPIO for RX, 77 | - "TxPin" - GPIO for TX, 78 | - "BaudRate" - connection speed, 79 | - "Config" - port configuration ("SERIAL_8N1" by default), 80 | - "HardwareSerial" - which hardware serial channel is used. E.g. for ESP32 it could be 2, 81 | 82 | #### Modbus operations section 83 | There are several modbus operations are possible. Operations - it's some polling action to get information from modbus equipment. To get info from the modbus device you need to know: 84 | - Slave id - ID of the Modbus equipment 85 | - A function: 86 | - 0x01 - Read Coils 87 | - 0x02 - Read Discrete Inputs 88 | - 0x03 - Read Holding Registers 89 | - 0x04 - Read Input Registers 90 | - A register address 91 | - Len of the data to read from a register 92 | 93 | Sections in configuration file looks like: 94 | - "PollingInterval" - an interval of polling registers of an equipment in milliseconds. 95 | - "SlaveId" - RS 485 slave ID number, 96 | - "Function" - number of function, 97 | - "Address" - a register address, 98 | - "Len" - len of the data to read, 99 | - "Transform" - formula to transform readed data. E.g. if you specify "0.1", it means that value was readed from the register needs to be multiply by 0.1. You can specify the same formula another way: "%V%/10" - %V% will be replaced by value readed from a sensor and divided by 10. The formula can be simple: 100 | - "Location" - some location description. E.g. "Warehouse_shelf_10". 101 | - "DisplayName" - some human readable name to display. E.g. "Temp". 102 | 103 | ### iWare sensors 104 | 105 | The structure for the iWare sensors looks similar to modbus: main section to describe interface and some common properties and "Sensors" sections - a little bit equal to "Operations" in Mobus. 106 | 107 | ``` 108 | "iWare": [ 109 | { 110 | "GPIO": 32, 111 | "PollingInterval": 60000, 112 | "Connection": "iWare_GPIO32", 113 | "Transform": "", 114 | "Sensors": [ 115 | { 116 | "HwId": "28B11346920D02A7", 117 | "PollingInterval": 60000, 118 | "DisplayName": "Equipment2348", 119 | "Location": "Warehouse1_Shelf1", 120 | "Transform": "" 121 | }, 122 | { 123 | "HwId": "28FF6C7997090341", 124 | "PollingInterval": 60000, 125 | "DisplayName": "Equipment1234", 126 | "Location": "Warehouse1_Shelf2", 127 | "Transform": "" 128 | } 129 | ] 130 | }, 131 | { 132 | "GPIO": 33, 133 | "PollingInterval": 120000, 134 | "Connection": "iWare_GPIO33", 135 | "Transform": "", 136 | "Sensors": [ 137 | { 138 | "HwId": "28B11346920D02A7", 139 | "PollingInterval": 60000, 140 | "DisplayName": "Equipment2348", 141 | "Location": "Warehouse2_Shelf1", 142 | "Transform": "" 143 | }, 144 | { 145 | "HwId": "28FF6C7997090341", 146 | "PollingInterval": 60000, 147 | "DisplayName": "Equipment1234", 148 | "Location": "Warehouse2_Shelf2", 149 | "Transform": "" 150 | } 151 | ] 152 | } 153 | ] 154 | ``` 155 | #### iWare main section 156 | - "GPIO" - GPIO of the microcontroller used as iWare interface. E.g. 32 (without quotes), 157 | - "PollingInterval" - an interval of polling iWare sensor in milliseconds. In case of iWare devices it's important to have such parameter at the level of interface description since sometimes there is no reasons to specify sensors parameters. E.g. iWare devices on the bus can be scanned, so, if you just need to send sensors data to a cloud, there is no need to create sensor section for each scanned sensor. 158 | - "Connection" - some name of the interface "iWare_GPIO32", 159 | - "Transform" - global formula to transform readed value. 160 | 161 | #### iWare sensors section 162 | - "HwId" - each iWare device has their own unique ID. E.g. "28B11346920D02A7", 163 | - "PollingInterval"- an interval of polling iWare sensor in milliseconds, 164 | - "DisplayName"- some human readable name to display. E.g. "Temp". 165 | - "Location" - some location description. E.g. "Warehouse_shelf_10". 166 | - "Transform" - formula to transform readed data (see upper). 167 | 168 | ### Analog sensors 169 | 170 | Analog sensors configuration have similar structure as iWare. Usually, analog sensors connected to specific ADC channel (GPIO) on a microcontroller. It can be several ADC channels on a microcontroller. Ana log sensors connect directly to specific GPIO. 171 | ``` 172 | "Analog": [ 173 | { 174 | "PollingInterval": 120000, 175 | "Connection": "Analog_test", 176 | "Sensors": [ 177 | { 178 | "HwId": "1234", 179 | "Channel": 6, 180 | "GPIO": 33, 181 | "PollingInterval": 60000, 182 | "DisplayName": "CurrenToVoltage1", 183 | "Location": "ServerRoom10_Chassis1", 184 | "Transform": "%V%+0.7" 185 | }, 186 | { 187 | "HwId": "4321", 188 | "Channel": 7, 189 | "GPIO": 34, 190 | "PollingInterval": 60000, 191 | "DisplayName": "CurrenToVoltage2", 192 | "Location": "ServerRoom11_Chassis1", 193 | "Transform": "%V%+0.7" 194 | } 195 | ] 196 | } 197 | ] 198 | ``` 199 | So, only two parameters need to be described more precisely: 200 | - "Channel" - number of ADC channel. E.g. - 6. 201 | - "GPIO" - number of GPIO. E.g. - 33. 202 | 203 | ### Relay output 204 | 205 | To describe outputs of a relay connected directly to a microcontroller needs only one parameter - GPIO of the pin. 206 | ``` 207 | "RelayOutput": [ 208 | { 209 | "Connection": "Relay_test", 210 | "Relays": [ 211 | { 212 | "HwId": "GPIO_26", 213 | "GPIO": 25, 214 | "DisplayName": "Ventilation_1", 215 | "Location": "ServerRoom01_Chassis1" 216 | }, 217 | { 218 | "HwId": "GPIO_26", 219 | "GPIO": 26, 220 | "DisplayName": "Ventilation_2", 221 | "Location": "ServerRoom01_Chassis2" 222 | }, 223 | { 224 | "HwId": "GPIO_27", 225 | "GPIO": 27, 226 | "DisplayName": "Ventilation_3", 227 | "Location": "ServerRoom01_Chassis3" 228 | }, 229 | { 230 | "HwId": "GPIO_13", 231 | "GPIO": 13, 232 | "DisplayName": "Ventilation_4", 233 | "Location": "ServerRoom01_Chassis4" 234 | }, 235 | { 236 | "HwId": "GPIO_15", 237 | "GPIO": 15, 238 | "DisplayName": "Ventilation_5", 239 | "Location": "ServerRoom01_Chassis5" 240 | }, 241 | { 242 | "HwId": "GPIO_12", 243 | "GPIO": 12, 244 | "DisplayName": "Ventilation_6", 245 | "Location": "ServerRoom01_Chassis6" 246 | }, 247 | { 248 | "HwId": "GPIO_33", 249 | "GPIO": 14, 250 | "DisplayName": "Ventilation_7", 251 | "Location": "ServerRoom01_Chassis7" 252 | }, 253 | { 254 | "HwId": "GPIO_34", 255 | "GPIO": 34, 256 | "DisplayName": "Ventilation_7", 257 | "Location": "ServerRoom01_Chassis7" 258 | } 259 | ] 260 | } 261 | ] 262 | ``` 263 | -------------------------------------------------------------------------------- /examples/ModbusConfig_SeveralRS485/ModbusConfig_SeveralRS485.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * The example shows how to work with a single RS485 interface using Modbus RTU and configure by ModbusConfig. 3 | * ESP32 microcontroller 4 | */ 5 | 6 | #include 7 | //#include 8 | #include "WiFi.h" 9 | #include "ModbusConfig.h" 10 | #include "EspFS.h" 11 | #include "ModbusMaster.h" 12 | //#include 13 | #include 14 | 15 | #define Num_of_Connections 2 //Number of serial ports on the board 16 | #define Num_of_Ops 5 //Number of operations per serial port 17 | 18 | //30 - is the maximum number of possible operations https://arduinojson.org/v6/assistant/ 19 | const size_t capacity = 2*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + Num_of_Ops*JSON_OBJECT_SIZE(6) + Num_of_Connections*JSON_OBJECT_SIZE(11) + 830; 20 | StaticJsonDocument doc; 21 | 22 | char* filename = "/modbus.cfg"; //You should upload the file to SPIFFS using the tool: https://github.com/esp8266/arduino-esp8266fs-plugin 23 | 24 | 25 | class ModbusMasterEx : public ModbusMaster 26 | { 27 | public: 28 | int SlaveId = -1; 29 | ModbusMasterEx() : ModbusMaster(){} 30 | 31 | bool getResultMsg(ModbusMaster node, uint8_t result) 32 | { 33 | String tmpstr2; 34 | 35 | switch (result) 36 | { 37 | case node.ku8MBSuccess: 38 | return true; 39 | break; 40 | case node.ku8MBIllegalFunction: 41 | tmpstr2 = "Illegal Function"; 42 | break; 43 | case node.ku8MBIllegalDataAddress: 44 | tmpstr2 = "Illegal Data Address"; 45 | break; 46 | case node.ku8MBIllegalDataValue: 47 | tmpstr2 = "Illegal Data Value"; 48 | break; 49 | case node.ku8MBSlaveDeviceFailure: 50 | tmpstr2 = "Slave Device Failure"; 51 | break; 52 | case node.ku8MBInvalidSlaveID: 53 | tmpstr2 = "Invalid Slave ID"; 54 | break; 55 | case node.ku8MBInvalidFunction: 56 | tmpstr2 = "Invalid Function"; 57 | break; 58 | case node.ku8MBResponseTimedOut: 59 | tmpstr2 = "Response Timed Out"; 60 | break; 61 | case node.ku8MBInvalidCRC: 62 | tmpstr2 = "Invalid CRC"; 63 | break; 64 | default: 65 | tmpstr2 = "Unknown error: " + String(result); 66 | break; 67 | } 68 | Serial.println(tmpstr2); 69 | return false; 70 | } 71 | }; 72 | 73 | class SoftwareSerialEx : public SoftwareSerial 74 | { 75 | public: 76 | int RxPin = -1; 77 | int TxPin = -1; 78 | int BaudRate = -1; 79 | SoftwareSerialEx(int receivePin, int transmitPin, bool inverse_logic, unsigned int buffSize) : SoftwareSerial(receivePin, transmitPin, inverse_logic, buffSize){} 80 | }; 81 | 82 | typedef std::vector ModbusConnectors; 83 | 84 | class ModbusRTUConnector 85 | { 86 | public: 87 | SoftwareSerialEx *serial; 88 | ModbusConnectors* Connectors; 89 | String tag; //Field just to test 90 | ModbusRTUConnector() {} 91 | }; 92 | 93 | //typedef std::vector Connections; 94 | 95 | ModbusConfig modbusCfg; 96 | EspFS fileSystem; 97 | 98 | //Connections connectors; 99 | ModbusRTUConnector* Connector; 100 | 101 | //Callback function to process polling interval 102 | void pollingIntervalProcessor(Connection* connection, Operation* operation) 103 | { 104 | if (connection) 105 | { 106 | if (operation == NULL) 107 | { 108 | Serial.println("Publish telemetry data to the cloud. Slave connection is [" + String(connection->Connection) + "]. HwId: [" + String(connection->HwId) + "]."); 109 | } 110 | else 111 | { 112 | Serial.println("Operation with name [" + String(operation->DisplayName) + "] has executed. Function: [0x0" + String(operation->Function, HEX) + "]. Address: [0x0" + String(operation->Address, HEX) + "]."); 113 | getInputRegistersResult(*operation); 114 | } 115 | } 116 | } 117 | 118 | void readModbusConfig() 119 | { 120 | bool res = fileSystem.loadTextFile(filename); 121 | if (res) 122 | { 123 | processModbusConfig(fileSystem.text); 124 | } 125 | else 126 | { 127 | Serial.println("File reading error."); 128 | } 129 | } 130 | 131 | void processModbusConfig(String json) 132 | { 133 | if (modbusCfg.parseConfig(json)) 134 | { 135 | modbusCfg.printConfig(); 136 | 137 | Connector = new ModbusRTUConnector(); 138 | 139 | int i = 0; 140 | for (Connection& connection : modbusCfg.connections) //Only one Slave settings supported in the example 141 | { 142 | if (connection.Type == ModbusType::RTU) 143 | { 144 | Connector->serial = new SoftwareSerialEx(connection.RxPin, connection.TxPin, false, 256); 145 | Connector->serial->begin(connection.BaudRate); 146 | Connector->serial->BaudRate = connection.BaudRate; 147 | Connector->serial->RxPin = connection.RxPin; 148 | Connector->serial->TxPin = connection.TxPin; 149 | ModbusConnectors* connectors = new ModbusConnectors(); 150 | for (Operation& operation : connection.Operations) 151 | { 152 | ModbusMasterEx* modbus = new ModbusMasterEx(); 153 | modbus->begin(operation.SlaveId, *Connector->serial); 154 | modbus->SlaveId = operation.SlaveId; 155 | operation.Modbus = modbus; 156 | connectors->push_back(modbus); 157 | } 158 | Connector->Connectors = connectors; 159 | connection.Connector = Connector; 160 | } 161 | } 162 | } 163 | } 164 | 165 | void setup() { 166 | Serial.begin(9600, SERIAL_8N1); 167 | 168 | modbusCfg.doc = new DynamicJsonDocument(capacity); 169 | modbusCfg.pollingIntervalCallback = *pollingIntervalProcessor; 170 | 171 | Serial.println("Try to read Modbus config"); 172 | readModbusConfig(); 173 | } 174 | 175 | void loop() { 176 | modbusCfg.loopModbusConfig(); 177 | } 178 | 179 | void getInputRegistersResult(Operation operation) 180 | { 181 | if (operation.Modbus) 182 | { 183 | ModbusMasterEx* node = static_cast(operation.Modbus); 184 | 185 | Serial.println("Read Input Registers from Slave ID: " + String(node->SlaveId) + " with address: " + String(operation.Address) + " and len: " + String(operation.Len)); 186 | ESP.wdtDisable(); 187 | uint8_t result = node->readInputRegisters(operation.Address, operation.Len); 188 | ESP.wdtEnable(1); 189 | 190 | if (node->getResultMsg(*node, result)) 191 | { 192 | Serial.println(); 193 | 194 | String res = operation.DisplayName; 195 | int j = 0; 196 | //for (j = 0; j < operation.Len; j++) 197 | { 198 | double res_dbl = node->getResponseBuffer(j); 199 | res += " " + String(res_dbl) + "\r\n"; 200 | } 201 | Serial.println(res); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /examples/ModbusConfig_SeveralRS485/data/modbus.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "Modbus": [ 3 | { 4 | "Connection": "tty0", 5 | "Type": "RTU", 6 | "PollingInterval": 4000, 7 | "RetryCount": 20, 8 | "RetryInterval": 100, 9 | "HwId": "TermoSensor-0a:01:01:01:01:02", 10 | "RxPin": 5, 11 | "TxPin": 13, 12 | "BaudRate": 9600, 13 | "Config": "SERIAL_8N1", 14 | "HardwareSerial": 2, 15 | "Ops": [ 16 | { 17 | "PollingInterval": 2000, 18 | "SlaveId": 1, 19 | "Function": 4, 20 | "Address": 1, 21 | "Len": 1, 22 | "DisplayName": "Temp" 23 | }, 24 | { 25 | "PollingInterval": 2000, 26 | "SlaveId": 1, 27 | "Function": 4, 28 | "Address": 2, 29 | "Len": 1, 30 | "DisplayName": "Humidity" 31 | } 32 | ] 33 | }, 34 | { 35 | "Connection": "tty0", 36 | "Type": "RTU", 37 | "PollingInterval": 4000, 38 | "RetryCount": 20, 39 | "RetryInterval": 100, 40 | "HwId": "TermoSensor", 41 | "RxPin": 4, 42 | "TxPin": 0, 43 | "BaudRate": 9600, 44 | "Config": "SERIAL_8N1", 45 | "HardwareSerial": 1, 46 | "Ops": [ 47 | { 48 | "PollingInterval": 2000, 49 | "SlaveId": 2, 50 | "Function": 4, 51 | "Address": 1, 52 | "Len": 1, 53 | "DisplayName": "Temp" 54 | }, 55 | { 56 | "PollingInterval": 2000, 57 | "SlaveId": 2, 58 | "Function": 4, 59 | "Address": 2, 60 | "Len": 1, 61 | "DisplayName": "Humidity" 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /examples/ModbusConfig_Simple/ModbusConfig_Simple.ino: -------------------------------------------------------------------------------- 1 | #include 2 | /* 3 | * ModbusConfig test example for ESP8266 boards. For Arduino just remove #include 4 | */ 5 | 6 | #include 7 | #include "ModbusConfig.h"; 8 | #include "EspFS.h"; 9 | 10 | #define Num_of_Slaves 2 //Number of serial ports on the board 11 | #define Num_of_Ops 5 //Number of operations per serial port 12 | 13 | //30 - is the maximum number of possible operations https://arduinojson.org/v6/assistant/ 14 | const size_t capacity = 2*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + Num_of_Ops*JSON_OBJECT_SIZE(6) + Num_of_Slaves*JSON_OBJECT_SIZE(11) + 830; 15 | StaticJsonDocument doc; 16 | 17 | char* filename = "/modbus.cfg"; //You should upload the file to SPDIFF using the tool: https://github.com/esp8266/arduino-esp8266fs-plugin 18 | 19 | ModbusConfig modbusCfg; 20 | EspFS fileSystem; 21 | 22 | //Callback function to process polling interval 23 | void pollingIntervalProcessor(Connection* connection, Operation* operation) 24 | { 25 | if (connection) 26 | { 27 | if (operation == NULL) 28 | { 29 | Serial.println("Publish to the cloud. Slave connection is [" + String(connection->Connection) + "]. HwId: [" + String(connection->HwId) + "]."); 30 | } 31 | else 32 | { 33 | Serial.println("Operation with name [" + String(operation->DisplayName) + "] has executed. Function: [0x0" + String(operation->Function, HEX) + "]. Address: [0x0" + String(operation->Address, HEX) + "]."); 34 | } 35 | } 36 | } 37 | 38 | void readModbusConfig() 39 | { 40 | bool res = fileSystem.loadTextFile(filename); 41 | if (res) 42 | { 43 | processModbusConfig(fileSystem.text); 44 | } 45 | else 46 | { 47 | Serial.println("File reading error."); 48 | } 49 | } 50 | 51 | void processModbusConfig(String json) 52 | { 53 | jsonParsing(json); 54 | Serial.println(); 55 | Serial.println("---ModbusConfig processing------------"); 56 | if (modbusCfg.parseConfig(json)) 57 | { 58 | modbusCfg.printConfig(); 59 | } 60 | } 61 | 62 | void jsonParsing(String json) 63 | { 64 | DeserializationError error = deserializeJson(doc, json); 65 | if (error) { 66 | processJsonError(error.code()); 67 | return; 68 | } 69 | 70 | JsonObject obj = doc.as(); 71 | JsonArray arr = doc.as(); 72 | 73 | /*Serial.println("--------------------------------------"); 74 | JsonArray::iterator it; 75 | for (it=arr.begin(); it!=arr.end(); ++it) { 76 | const JsonObject& elem = *it; 77 | JsonObject slave = elem["Slave"]; 78 | printValue("Connection", slave["Connection"].as()); 79 | }*/ 80 | 81 | Serial.println("---JSON parsing--------------------"); 82 | for (const JsonObject& item : arr) 83 | { 84 | const JsonObject& connections = item["Connection"]; 85 | for (const JsonPair& connection : connections) { 86 | String key = connection.key().c_str(); 87 | if (connection.value().is()) { 88 | JsonArray operations = connection.value().as(); 89 | Serial.println("---Operations:----------------------------"); 90 | for (const JsonObject& operation : operations) { 91 | for (const JsonPair& op : operation) { 92 | key = op.key().c_str(); 93 | Serial.println("\t" + key + ": " + op.value().as()); 94 | } 95 | Serial.println("--------------------------------------"); 96 | } 97 | } 98 | else { 99 | Serial.println(key + ": " + connection.value().as()); 100 | } 101 | } 102 | } 103 | } 104 | 105 | void setup() 106 | { 107 | Serial.begin(9600, SERIAL_8N1); 108 | 109 | modbusCfg.doc = new DynamicJsonDocument(capacity); 110 | modbusCfg.pollingIntervalCallback = *pollingIntervalProcessor; 111 | 112 | fileSystem.showDir(); 113 | readModbusConfig(); 114 | } 115 | 116 | void loop() 117 | { 118 | modbusCfg.loopModbusConfig(); 119 | } 120 | -------------------------------------------------------------------------------- /examples/ModbusConfig_Simple/data/modbus.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "Modbus": [ 3 | { 4 | "Connection": "tty0", 5 | "Type": "RTU", 6 | "PollingInterval": 4000, 7 | "RetryCount": 20, 8 | "RetryInterval": 100, 9 | "HwId": "TermoSensor-0a:01:01:01:01:02", 10 | "RxPin": 15, 11 | "TxPin": 13, 12 | "BaudRate": 9600, 13 | "Config": "SERIAL_8N1", 14 | "HardwareSerial": 2, 15 | "Ops": [ 16 | { 17 | "PollingInterval": 2000, 18 | "SlaveId": 1, 19 | "Function": 4, 20 | "Address": 1, 21 | "Len": 1, 22 | "DisplayName": "Temp" 23 | }, 24 | { 25 | "PollingInterval": 2000, 26 | "SlaveId": 1, 27 | "Function": 4, 28 | "Address": 2, 29 | "Len": 1, 30 | "DisplayName": "Humidity" 31 | } 32 | ] 33 | }, 34 | { 35 | "Connection": "tty0", 36 | "Type": "RTU", 37 | "PollingInterval": 4000, 38 | "RetryCount": 20, 39 | "RetryInterval": 100, 40 | "HwId": "TermoSensor", 41 | "RxPin": 4, 42 | "TxPin": 0, 43 | "BaudRate": 9600, 44 | "Config": "SERIAL_8N1", 45 | "HardwareSerial": 1, 46 | "Ops": [ 47 | { 48 | "PollingInterval": 2000, 49 | "SlaveId": 2, 50 | "Function": 4, 51 | "Address": 1, 52 | "Len": 1, 53 | "DisplayName": "Temp" 54 | }, 55 | { 56 | "PollingInterval": 2000, 57 | "SlaveId": 2, 58 | "Function": 4, 59 | "Address": 2, 60 | "Len": 1, 61 | "DisplayName": "Humidity" 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /examples/ModbusConfig_SingleRS485/ModbusConfig_SingleRS485.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * The example shows how to work with a single RS485 interface using Modbus RTU and configure by ModbusConfig. 3 | ESP8266 microcontroller 4 | */ 5 | 6 | #include 7 | #include 8 | #include "ModbusConfig.h"; 9 | #include "EspFS.h"; 10 | #include "ModbusMaster.h" 11 | #include 12 | 13 | #define Num_of_Connections 2 //Number of serial ports on the board 14 | #define Num_of_Ops 5 //Number of operations per serial port 15 | 16 | //30 - is the maximum number of possible operations 17 | const size_t capacity = 2*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + Num_of_Ops*JSON_OBJECT_SIZE(6) + Num_of_Connections*JSON_OBJECT_SIZE(11) + 830; 18 | StaticJsonDocument doc; 19 | 20 | char* filename = "/modbus.cfg"; //You should upload the file to SPIFFS using the tool: https://github.com/esp8266/arduino-esp8266fs-plugin 21 | 22 | 23 | class ModbusMasterEx : public ModbusMaster 24 | { 25 | public: 26 | int SlaveId = -1; 27 | ModbusMasterEx() : ModbusMaster(){} 28 | 29 | bool getResultMsg(ModbusMaster node, uint8_t result) 30 | { 31 | String tmpstr2; 32 | 33 | switch (result) 34 | { 35 | case node.ku8MBSuccess: 36 | return true; 37 | break; 38 | case node.ku8MBIllegalFunction: 39 | tmpstr2 = "Illegal Function"; 40 | break; 41 | case node.ku8MBIllegalDataAddress: 42 | tmpstr2 = "Illegal Data Address"; 43 | break; 44 | case node.ku8MBIllegalDataValue: 45 | tmpstr2 = "Illegal Data Value"; 46 | break; 47 | case node.ku8MBSlaveDeviceFailure: 48 | tmpstr2 = "Slave Device Failure"; 49 | break; 50 | case node.ku8MBInvalidSlaveID: 51 | tmpstr2 = "Invalid Slave ID"; 52 | break; 53 | case node.ku8MBInvalidFunction: 54 | tmpstr2 = "Invalid Function"; 55 | break; 56 | case node.ku8MBResponseTimedOut: 57 | tmpstr2 = "Response Timed Out"; 58 | break; 59 | case node.ku8MBInvalidCRC: 60 | tmpstr2 = "Invalid CRC"; 61 | break; 62 | default: 63 | tmpstr2 = "Unknown error: " + String(result); 64 | break; 65 | } 66 | Serial.println(tmpstr2); 67 | return false; 68 | } 69 | }; 70 | 71 | class SoftwareSerialEx : public SoftwareSerial 72 | { 73 | public: 74 | int RxPin = -1; 75 | int TxPin = -1; 76 | int BaudRate = -1; 77 | SoftwareSerialEx(int receivePin, int transmitPin, bool inverse_logic, unsigned int buffSize) : SoftwareSerial(receivePin, transmitPin, inverse_logic, buffSize){} 78 | }; 79 | 80 | typedef std::vector ModbusConnectors; 81 | 82 | class ModbusRTUConnector 83 | { 84 | public: 85 | SoftwareSerialEx *serial; 86 | ModbusConnectors* Connectors; 87 | String tag; //Field just to test 88 | ModbusRTUConnector() {} 89 | }; 90 | 91 | //typedef std::vector Connections; 92 | 93 | ModbusConfig modbusCfg; 94 | EspFS fileSystem; 95 | 96 | //Connections connectors; 97 | ModbusRTUConnector* Connector; 98 | 99 | //Callback function to process polling interval 100 | void pollingIntervalProcessor(Connection* connection, Operation* operation) 101 | { 102 | if (connection) 103 | { 104 | if (operation == NULL) 105 | { 106 | Serial.println("Publish telemetry data to the cloud. Slave connection is [" + String(connection->Connection) + "]. HwId: [" + String(connection->HwId) + "]."); 107 | } 108 | else 109 | { 110 | Serial.println("Operation with name [" + String(operation->DisplayName) + "] has executed. Function: [0x0" + String(operation->Function, HEX) + "]. Address: [0x0" + String(operation->Address, HEX) + "]."); 111 | getInputRegistersResult(*operation); 112 | } 113 | } 114 | } 115 | 116 | void readModbusConfig() 117 | { 118 | bool res = fileSystem.loadTextFile(filename); 119 | if (res) 120 | { 121 | processModbusConfig(fileSystem.text); 122 | } 123 | else 124 | { 125 | Serial.println("File reading error."); 126 | } 127 | } 128 | 129 | void processModbusConfig(String json) 130 | { 131 | if (modbusCfg.parseConfig(json)) 132 | { 133 | modbusCfg.printConfig(); 134 | 135 | Connector = new ModbusRTUConnector(); 136 | 137 | int i = 0; 138 | for (Connection& connection : modbusCfg.connections) //Only one Slave settings supported in the example 139 | { 140 | if (connection.Type == ModbusType::RTU) 141 | { 142 | Connector->serial = new SoftwareSerialEx(connection.RxPin, connection.TxPin, false, 256); 143 | Connector->serial->begin(connection.BaudRate); 144 | Connector->serial->BaudRate = connection.BaudRate; 145 | Connector->serial->RxPin = connection.RxPin; 146 | Connector->serial->TxPin = connection.TxPin; 147 | ModbusConnectors* connectors = new ModbusConnectors(); 148 | for (Operation& operation : connection.Operations) 149 | { 150 | ModbusMasterEx* modbus = new ModbusMasterEx(); 151 | modbus->begin(operation.SlaveId, *Connector->serial); 152 | modbus->SlaveId = operation.SlaveId; 153 | operation.Modbus = modbus; 154 | connectors->push_back(modbus); 155 | } 156 | Connector->Connectors = connectors; 157 | connection.Connector = Connector; 158 | } 159 | } 160 | } 161 | } 162 | 163 | void setup() { 164 | Serial.begin(9600, SERIAL_8N1); 165 | 166 | modbusCfg.doc = new DynamicJsonDocument(capacity); 167 | modbusCfg.pollingIntervalCallback = *pollingIntervalProcessor; 168 | 169 | Serial.println("Try to read Modbus config"); 170 | readModbusConfig(); 171 | } 172 | 173 | void loop() { 174 | modbusCfg.loopModbusConfig(); 175 | } 176 | 177 | void getInputRegistersResult(Operation operation) 178 | { 179 | if (operation.Modbus) 180 | { 181 | ModbusMasterEx* node = static_cast(operation.Modbus); 182 | 183 | Serial.println("Read Input Registers from Slave ID: " + String(node->SlaveId) + " with address: " + String(operation.Address) + " and len: " + String(operation.Len)); 184 | ESP.wdtDisable(); 185 | uint8_t result = node->readInputRegisters(operation.Address, operation.Len); 186 | ESP.wdtEnable(1); 187 | 188 | if (node->getResultMsg(*node, result)) 189 | { 190 | Serial.println(); 191 | 192 | String res = operation.DisplayName; 193 | int j = 0; 194 | //for (j = 0; j < operation.Len; j++) 195 | { 196 | double res_dbl = node->getResponseBuffer(j); 197 | res += " " + String(res_dbl) + "\r\n"; 198 | } 199 | Serial.println(res); 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /examples/ModbusConfig_SingleRS485/data/modbus.cfg: -------------------------------------------------------------------------------- 1 | { 2 | Modbus: 3 | [ 4 | { 5 | "Connection": "tty0", 6 | "Type": "RTU", 7 | "PollingInterval": 4000, 8 | "RetryCount": 20, 9 | "RetryInterval": 100, 10 | "HwId": "TermoSensor-0a:01:01:01:01:02", 11 | "RxPin": 5, 12 | "TxPin": 13, 13 | "BaudRate": 9600, 14 | "Config": "SERIAL_8N1", 15 | "HardwareSerial": 2, 16 | "Ops": [ 17 | { 18 | "PollingInterval": 2000, 19 | "SlaveId": 1, 20 | "Function": 4, 21 | "Address": 1, 22 | "Len": 1, 23 | "DisplayName": "Temp" 24 | }, 25 | { 26 | "PollingInterval": 2000, 27 | "SlaveId": 1, 28 | "Function": 4, 29 | "Address": 2, 30 | "Len": 1, 31 | "DisplayName": "Humidity" 32 | } 33 | ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /examples/ModbusConfig_Termosensor/ModbusConfig_Termosensor.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ModbusConfig.h"; 4 | #include "EspFS.h"; 5 | #include "ModbusMaster.h" 6 | #include 7 | 8 | #define Num_of_Slaves 2 //Number of serial ports on the board 9 | #define Num_of_Ops 5 //Number of operations per serial port 10 | 11 | //30 - is the maximum number of possible operations 12 | const size_t capacity = 2*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + Num_of_Ops*JSON_OBJECT_SIZE(6) + Num_of_Slaves*JSON_OBJECT_SIZE(11) + 830; 13 | StaticJsonDocument doc; 14 | 15 | char* filename = "/modbus.cfg"; //You should upload the file to SPIFFS using the tool: https://github.com/esp8266/arduino-esp8266fs-plugin 16 | 17 | /*typedef struct{ 18 | ModbusMaster modbus; 19 | int SlaveId; 20 | } ModbusMasterInfo;*/ 21 | 22 | 23 | class ModbusMasterEx : public ModbusMaster 24 | { 25 | public: 26 | int SlaveId = -1; 27 | ModbusMasterEx() : ModbusMaster(){} 28 | }; 29 | 30 | class SoftwareSerialEx : public SoftwareSerial 31 | { 32 | public: 33 | int RxPin = -1; 34 | int TxPin = -1; 35 | int BaudRate = -1; 36 | SoftwareSerialEx(int receivePin, int transmitPin, bool inverse_logic, unsigned int buffSize) : SoftwareSerial(receivePin, transmitPin, inverse_logic, buffSize){} 37 | }; 38 | 39 | //typedef std::vector ModbusConnectors; 40 | typedef std::vector ModbusConnectors; 41 | 42 | class ModbusRTUConnector 43 | { 44 | public: 45 | SoftwareSerialEx *serial; //default 46 | ModbusConnectors Connectors; //default 47 | String tag; 48 | ModbusRTUConnector() {} 49 | }; 50 | 51 | /*typedef struct{ 52 | SoftwareSerialEx *serial; //default 53 | ModbusConnectors Connectors; //default 54 | String tag; 55 | } ModbusRTUConnector;*/ 56 | 57 | typedef std::vector Connections; 58 | 59 | //#define Slave_ID 1 60 | #define RX_PIN 5 //D1 15 //D8 61 | #define TX_PIN 13 //D7 62 | 63 | // instantiate ModbusMaster object 64 | //ModbusMaster node; 65 | 66 | ModbusConfig modbusCfg; 67 | EspFS fileSystem; 68 | 69 | Connections connectors; 70 | 71 | //Callback function to process polling interval 72 | void pollingIntervalProcessor(Slave* slave, Operation* operation) 73 | { 74 | if (slave) 75 | { 76 | if (operation == NULL) 77 | { 78 | Serial.println("Publish to the cloud. Slave connection is [" + String(slave->Connection) + "]. HwId: [" + String(slave->HwId) + "]."); 79 | } 80 | else 81 | { 82 | Serial.println("Operation with name [" + String(operation->DisplayName) + "] has executed. Function: [0x0" + String(operation->Function, HEX) + "]. Address: [0x0" + String(operation->Address, HEX) + "]."); 83 | getInputRegistersResult(slave->Connector, *operation); 84 | } 85 | //Serial.printf("Connector: %p\n", slave->Connector); 86 | //ModbusRTUConnector* p = static_cast(slave->Connector); 87 | //Serial.println("Slave str: [" + (p->tag) + "]."); 88 | //SoftwareSerialEx* d = static_cast(p->serial); 89 | //Serial.println("Serial speed: " + String(d->BaudRate) + ", Rx pin: " + String(d->RxPin) + ", Tx pin: " + String(d->TxPin)); 90 | } 91 | } 92 | 93 | void readModbusConfig() 94 | { 95 | bool res = fileSystem.loadTextFile(filename); 96 | if (res) 97 | { 98 | processModbusConfig(fileSystem.text); 99 | } 100 | else 101 | { 102 | Serial.println("File reading error."); 103 | } 104 | } 105 | 106 | void processModbusConfig(String json) 107 | { 108 | if (modbusCfg.parseConfig(json)) 109 | { 110 | modbusCfg.printConfig(); 111 | 112 | connectors.clear(); 113 | 114 | int i = 0; 115 | for (Slave& slave : modbusCfg.slaves) 116 | { 117 | if (slave.Type == ModbusType::RTU) 118 | { 119 | ModbusRTUConnector* modbusRTU = new ModbusRTUConnector(); 120 | modbusRTU->serial = new SoftwareSerialEx(slave.RxPin, slave.TxPin, false, 128); 121 | modbusRTU->serial->begin(slave.BaudRate); 122 | modbusRTU->serial->BaudRate = slave.BaudRate; 123 | modbusRTU->serial->RxPin = slave.RxPin; 124 | modbusRTU->serial->TxPin = slave.TxPin; 125 | modbusRTU->tag = "Slave_" + String(i); 126 | int j = 0; 127 | for (Operation& operation : slave.Operations) 128 | { 129 | ModbusMasterEx* modbus = isSlaveIdInQueue(*modbusRTU, operation.SlaveId); 130 | if (modbus->SlaveId == -1) 131 | { 132 | //modbus = new ModbusMasterEx(); 133 | modbus->SlaveId = operation.SlaveId; 134 | modbus->begin(operation.SlaveId, *(modbusRTU->serial)); 135 | //modbus.str = "Operation_" + String(j); 136 | } 137 | Serial.println("Operation with SlaveID: " + String(modbus->SlaveId) + " Function: " + String(operation.Function) + " Address: " + String(operation.Address)); 138 | Serial.printf("Pointer to modbus object: %x\r\n", &modbus); 139 | modbusRTU->Connectors.push_back(modbus); 140 | j++; 141 | } 142 | connectors.push_back(modbusRTU); 143 | 144 | ModbusRTUConnector* k = connectors.back(); 145 | Serial.println("Back " + k->tag); 146 | // slave.Connector = &connectors.front(); 147 | // void* p = &connectors.front(); 148 | // Serial.print("String: " + modbusRTU.str); 149 | // Serial.printf(" with back() = %p and front() = %p\n", slave.Connector, p); 150 | i++; 151 | } 152 | } 153 | 154 | int j = 0; //vector can reallocate elements during the filling, so, addresses can be assign only after finishing vector filling 155 | for (ModbusRTUConnector* connector : connectors) 156 | { 157 | Serial.print("String: " + connector->tag); 158 | Serial.printf(" with address %p\n", &connector); 159 | modbusCfg.slaves[j].Connector = connector; 160 | int k = 0; 161 | for (ModbusMasterEx* modbus : connector->Connectors) 162 | { 163 | Serial.print("Modbus with SlaveId: " + String(modbus->SlaveId)); 164 | Serial.printf(" with address %p\n", modbus); 165 | modbusCfg.slaves[j].Operations[k].Modbus = modbus; 166 | k++; 167 | } 168 | j++; 169 | } 170 | 171 | //connectors.shrink_to_fit(); 172 | 173 | /*for (Slave& slave : modbusCfg.slaves) 174 | { 175 | ModbusRTUConnector* p = static_cast(slave.Connector); 176 | Serial.print("Slave str: [" + (p->tag) + "]."); 177 | Serial.printf(" with address %p\n", slave.Connector); 178 | for (Operation& operation : slave.Operations) 179 | { 180 | //String res = operation.DisplayName + " C"; 181 | //Serial.println(res); 182 | } 183 | }*/ 184 | } 185 | } 186 | 187 | void setup() 188 | { 189 | Serial.begin(9600, SERIAL_8N1); 190 | 191 | //Serial.setRxBufferSize(128); //Change default 256 to 128 192 | 193 | //swSer.begin(9600); 194 | //modbus1.begin(1, swSer); 195 | 196 | 197 | // Modbus slave ID 1 198 | //node.begin(Slave_ID, Serial); 199 | //node.begin(Slave_ID, swSer); 200 | 201 | 202 | modbusCfg.doc = new DynamicJsonDocument(capacity); 203 | modbusCfg.pollingIntervalCallback = *pollingIntervalProcessor; 204 | 205 | //fileSystem.showDir(); 206 | readModbusConfig(); 207 | 208 | 209 | //modbusRTU.serial = &swSer; 210 | } 211 | 212 | void loop() 213 | { 214 | modbusCfg.loopModbusConfig(); 215 | //getModbus(); 216 | //delay(1000); 217 | } 218 | 219 | ModbusRTUConnector modbusRTU; 220 | void* modbusConnector; 221 | //ModbusMasterEx* node; 222 | 223 | bool isInitiated= false; 224 | void getModbus() 225 | { 226 | if (!isInitiated) 227 | { 228 | modbusRTU.serial = new SoftwareSerialEx(RX_PIN, TX_PIN, false, 128); 229 | modbusRTU.serial->begin(9600); 230 | modbusRTU.serial->BaudRate = 9600; 231 | modbusRTU.serial->RxPin = RX_PIN; 232 | modbusRTU.serial->TxPin = TX_PIN; 233 | modbusRTU.tag = "Slave_0"; 234 | ModbusMasterEx* m = new ModbusMasterEx(); 235 | m->SlaveId = 1; 236 | Serial.printf("Address1 %p, SlaveId %u\r\n", m, m->SlaveId); 237 | m->begin(1, *(modbusRTU.serial)); 238 | modbusRTU.Connectors.push_back(m); 239 | 240 | //ModbusMasterEx* arr[1] = {m}; 241 | 242 | ModbusMasterEx* modbus = isSlaveIdInQueue(modbusRTU, 1); 243 | //modbus = modbusRTU.Connectors.back(); //arr[0];//m; //& 244 | 245 | Serial.printf("Address2 %p, SlaveId %u\r\n", modbus, modbus->SlaveId); 246 | //modbus = new ModbusMasterEx(); 247 | //modbus->SlaveId = 1; 248 | //modbus->begin(1, *(modbusRTU.serial)); 249 | modbusConnector = modbus; 250 | isInitiated = true; 251 | } 252 | 253 | Serial.println("Operation with SlaveID: " + String(1) + " Address: 1"); 254 | ModbusMasterEx* node = static_cast(modbusConnector); 255 | 256 | 257 | if (node) 258 | { 259 | ESP.wdtDisable(); 260 | uint8_t result = node->readInputRegisters(1, 1); 261 | ESP.wdtEnable(1); 262 | 263 | if (getResultMsg(*node, result)) 264 | { 265 | Serial.println(); 266 | 267 | String res = "Temp: "; 268 | int j = 0; 269 | double res_dbl = node->getResponseBuffer(j); 270 | res += " " + String(res_dbl) + "\r\n"; 271 | Serial.println(res); 272 | } 273 | } 274 | } 275 | 276 | ModbusMasterEx* isSlaveIdInQueue(ModbusRTUConnector connector, int SlaveId) 277 | { 278 | for (ModbusMasterEx* modbus : connector.Connectors) 279 | { 280 | if (modbus->SlaveId == SlaveId) 281 | { 282 | Serial.println("The Slave ID is found. Set the pointer to the found one."); 283 | return modbus; 284 | } 285 | } 286 | return (new ModbusMasterEx()); 287 | } 288 | 289 | void getInputRegistersResult(void* modbusConnector, Operation operation) 290 | { 291 | if (modbusConnector) 292 | { 293 | ModbusMasterEx* node = static_cast(modbusConnector); 294 | 295 | Serial.println("Read Input Registers from Slave ID: " + String(node->SlaveId) + " with address: " + String(operation.Address) + " and len: " + String(operation.Len)); 296 | ESP.wdtDisable(); 297 | uint8_t result = node->readInputRegisters(operation.Address, operation.Len); 298 | ESP.wdtEnable(1); 299 | 300 | if (getResultMsg(*node, result)) 301 | { 302 | Serial.println(); 303 | 304 | String res = operation.DisplayName; 305 | int j = 0; 306 | //for (j = 0; j < operation.Len; j++) 307 | { 308 | double res_dbl = node->getResponseBuffer(j); 309 | res += " " + String(res_dbl) + "\r\n"; 310 | } 311 | Serial.println(res); 312 | } 313 | } 314 | } 315 | 316 | bool getResultMsg(ModbusMaster node, uint8_t result) 317 | { 318 | String tmpstr2; 319 | 320 | switch (result) { 321 | case node.ku8MBSuccess: 322 | return true; 323 | break; 324 | case node.ku8MBIllegalFunction: 325 | tmpstr2 = "Illegal Function"; 326 | break; 327 | case node.ku8MBIllegalDataAddress: 328 | tmpstr2 = "Illegal Data Address"; 329 | break; 330 | case node.ku8MBIllegalDataValue: 331 | tmpstr2 = "Illegal Data Value"; 332 | break; 333 | case node.ku8MBSlaveDeviceFailure: 334 | tmpstr2 = "Slave Device Failure"; 335 | break; 336 | case node.ku8MBInvalidSlaveID: 337 | tmpstr2 = "Invalid Slave ID"; 338 | break; 339 | case node.ku8MBInvalidFunction: 340 | tmpstr2 = "Invalid Function"; 341 | break; 342 | case node.ku8MBResponseTimedOut: 343 | tmpstr2 = "Response Timed Out"; 344 | break; 345 | case node.ku8MBInvalidCRC: 346 | tmpstr2 = "Invalid CRC"; 347 | break; 348 | default: 349 | tmpstr2 = "Unknown error: " + String(result); 350 | break; 351 | } 352 | Serial.println(tmpstr2); 353 | return false; 354 | } 355 | -------------------------------------------------------------------------------- /examples/ModbusConfig_Termosensor/data/modbus.cfg: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Slave": { 4 | "Connection": "tty0", 5 | "PollingInterval": 4000, 6 | "RetryCount": 20, 7 | "RetryInterval": 100, 8 | "HwId": "TermoSensor", 9 | "RxPin": 5, 10 | "TxPin": 13, 11 | "BaudRate": 9600, 12 | "Config": "SERIAL_8N1", 13 | "HardwareSerial": 2, 14 | "Ops": [ 15 | { 16 | "PollingInterval": 2000, 17 | "SlaveId": 1, 18 | "Function": 4, 19 | "Address": 1, 20 | "Len": 1, 21 | "DisplayName": "Temp" 22 | }, 23 | { 24 | "PollingInterval": 2000, 25 | "SlaveId": 1, 26 | "Function": 4, 27 | "Address": 2, 28 | "Len": 1, 29 | "DisplayName": "Humidity" 30 | } 31 | ] 32 | } 33 | } 34 | ] -------------------------------------------------------------------------------- /examples/ModbusConfig_Termosensor/data/modbus_ext.cfg: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Slave": { 4 | "Connection": "tty0", 5 | "PollingInterval": 4000, 6 | "RetryCount": 20, 7 | "RetryInterval": 100, 8 | "HwId": "TermoSensor", 9 | "RxPin": 15, 10 | "TxPin": 13, 11 | "BaudRate": 9600, 12 | "Config": "SERIAL_8N1", 13 | "HardwareSerial": 2, 14 | "Ops": [ 15 | { 16 | "PollingInterval": 2000, 17 | "SlaveId": 1, 18 | "Function": 4, 19 | "Address": 1, 20 | "Len": 1, 21 | "DisplayName": "Temp" 22 | }, 23 | { 24 | "PollingInterval": 2000, 25 | "SlaveId": 1, 26 | "Function": 4, 27 | "Address": 2, 28 | "Len": 1, 29 | "DisplayName": "Humidity" 30 | } 31 | ] 32 | } 33 | }, 34 | { 35 | "Slave": { 36 | "Connection": "tty1", 37 | "PollingInterval": 10000, 38 | "RetryCount": 20, 39 | "RetryInterval": 100, 40 | "HwId": "TermoSensor", 41 | "RxPin": 10, 42 | "TxPin": 9, 43 | "BaudRate": 9600, 44 | "Config": "SERIAL_8N1", 45 | "Ops": [ 46 | { 47 | "PollingInterval": 3000, 48 | "SlaveId": 1, 49 | "Function": 4, 50 | "Address": 1, 51 | "Len": 1, 52 | "DisplayName": "Temp" 53 | }, 54 | { 55 | "PollingInterval": 3000, 56 | "SlaveId": 1, 57 | "Function": 4, 58 | "Address": 2, 59 | "Len": 1, 60 | "DisplayName": "Humidity" 61 | } 62 | ] 63 | } 64 | } 65 | ] -------------------------------------------------------------------------------- /examples/UniversalConfig_RS485_iWare_ADC/UniversalConfig_RS485_iWare_ADC.ino: -------------------------------------------------------------------------------- 1 | #include 2 | /* 3 | * ModbusConfig test example for ESP8266 boards. For Arduino just remove #include 4 | */ 5 | 6 | //#include 7 | #include "ModbusConfig.h"; 8 | #include "EspFS.h"; 9 | 10 | #define Num_of_Slaves 2 //Number of serial ports on the board 11 | #define Num_of_Ops 5 //Number of operations per serial port 12 | 13 | //30 - is the maximum number of possible operations https://arduinojson.org/v6/assistant/ 14 | //const size_t capacity = 2*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + Num_of_Ops*JSON_OBJECT_SIZE(6) + Num_of_Slaves*JSON_OBJECT_SIZE(11) + 830; 15 | const size_t capacity = 2*JSON_ARRAY_SIZE(1) + 5*JSON_ARRAY_SIZE(2) + JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(3) + 4*JSON_OBJECT_SIZE(5) + 2*JSON_OBJECT_SIZE(7) + 2*JSON_OBJECT_SIZE(8) + JSON_OBJECT_SIZE(12) + 1240*3; 16 | 17 | //StaticJsonDocument doc; 18 | DynamicJsonDocument doc(capacity); 19 | 20 | char* filename = "/modbus.cfg"; //You should upload the file to SPDIFF using the tool: https://github.com/esp8266/arduino-esp8266fs-plugin 21 | 22 | ModbusConfig modbusCfg; 23 | EspFS fileSystem; 24 | 25 | //Callback function to process polling interval 26 | void pollingIntervalProcessor(BaseConnection* connection, BaseOperation* operation) 27 | { 28 | if (connection) 29 | { 30 | switch (connection->Sensor) 31 | { 32 | case SensorType :: Modbus: 33 | { 34 | ModbusConnection* conn = static_cast(connection); 35 | if (operation == NULL) 36 | { 37 | Serial.println("Publish to the cloud. Slave connection is [" + String(connection->Connection) + "]. HwId: [" + String(conn->HwId) + "]."); 38 | } 39 | else 40 | { 41 | ModbusOperation* op = static_cast(operation); 42 | Serial.println("Operation with name [" + String(operation->DisplayName) + "] has executed. Function: [0x0" + String(op->Function, HEX) + "]. Address: [0x0" + String(op->Address, HEX) + "]."); 43 | } 44 | }; 45 | break; 46 | case SensorType :: iWare: 47 | { 48 | iWareConnection* conn = static_cast(connection); 49 | if (operation == NULL) 50 | { 51 | Serial.println("Connection is [" + String(connection->Connection) + "]. GPIO: [" + String(conn->GPIO) + "]."); 52 | } 53 | else 54 | { 55 | iWareSensor* sensor = static_cast(operation); 56 | Serial.println("Sensor with name [" + String(sensor->DisplayName) + "] has executed. HwId: [" + String(sensor->HwId) + "]."); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | void readModbusConfig() 64 | { 65 | bool res = fileSystem.loadTextFile(filename); 66 | if (res) 67 | { 68 | processModbusConfig(fileSystem.text); 69 | } 70 | else 71 | { 72 | Serial.println("File reading error."); 73 | } 74 | } 75 | 76 | void processModbusConfig(String json) 77 | { 78 | jsonParsing(json); 79 | Serial.println(); 80 | Serial.println("---ModbusConfig processing------------"); 81 | if (modbusCfg.parseConfig(json)) 82 | { 83 | modbusCfg.printConfig(); 84 | } 85 | } 86 | 87 | void jsonParsing(String json) 88 | { 89 | DeserializationError error = deserializeJson(doc, json); 90 | if (error) { 91 | processJsonError(error.code()); 92 | return; 93 | } 94 | 95 | JsonObject obj = doc.as(); 96 | JsonArray arr = doc.as(); 97 | 98 | /*Serial.println("--------------------------------------"); 99 | JsonArray::iterator it; 100 | for (it=arr.begin(); it!=arr.end(); ++it) { 101 | const JsonObject& elem = *it; 102 | JsonObject slave = elem["Slave"]; 103 | printValue("Connection", slave["Connection"].as()); 104 | }*/ 105 | 106 | Serial.println("---JSON parsing--------------------"); 107 | for (const JsonObject& item : arr) 108 | { 109 | const JsonObject& connections = item["Connection"]; 110 | for (const JsonPair& connection : connections) { 111 | String key = connection.key().c_str(); 112 | if (connection.value().is()) { 113 | JsonArray operations = connection.value().as(); 114 | Serial.println("---Operations:----------------------------"); 115 | for (const JsonObject& operation : operations) { 116 | for (const JsonPair& op : operation) { 117 | key = op.key().c_str(); 118 | Serial.println("\t" + key + ": " + op.value().as()); 119 | } 120 | Serial.println("--------------------------------------"); 121 | } 122 | } 123 | else { 124 | Serial.println(key + ": " + connection.value().as()); 125 | } 126 | } 127 | } 128 | } 129 | 130 | void setup() 131 | { 132 | Serial.begin(9600); 133 | fileSystem.initFS(); 134 | 135 | modbusCfg.doc = &doc; //new DynamicJsonDocument(capacity); 136 | modbusCfg.pollingIntervalCallback = *pollingIntervalProcessor; 137 | 138 | #ifdef ESP8266 139 | fileSystem.showDir(); 140 | #endif 141 | 142 | //readModbusConfig(); 143 | 144 | String expression = "%V%*1.1+0.5"; 145 | float value = modbusCfg.Eval(expression, 10.5); 146 | Serial.println("Value of " + expression + ": " + String(value)); 147 | expression = "1.1+2.2*%V%+0.5"; 148 | value = modbusCfg.Eval(expression, 10.5); 149 | Serial.println("Value of " + expression + ": " + String(value)); 150 | expression = "1+0.5"; 151 | value = modbusCfg.Eval(expression, 10); 152 | Serial.println("Value of " + expression + ": " + String(value)); 153 | } 154 | 155 | void loop() 156 | { 157 | modbusCfg.loopModbusConfig(); 158 | } 159 | -------------------------------------------------------------------------------- /examples/UniversalConfig_RS485_iWare_ADC/data/modbus.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "Modbus": [ 3 | { 4 | "Connection": "tty0", 5 | "Type": "RTU", 6 | "PollingInterval": 4000, 7 | "RetryCount": 20, 8 | "RetryInterval": 100, 9 | "HwId": "TermoSensor-0a:01:01:01:01:02", 10 | "RxPin": 5, 11 | "TxPin": 13, 12 | "BaudRate": 9600, 13 | "Config": "SERIAL_8N1", 14 | "HardwareSerial": 2, 15 | "Ops": [ 16 | { 17 | "PollingInterval": 2000, 18 | "SlaveId": 1, 19 | "Function": 4, 20 | "Address": 1, 21 | "Len": 1, 22 | "Transform": "V*1.01", 23 | "Location": "Warehouse1_Shelf1", 24 | "DisplayName": "Temp" 25 | }, 26 | { 27 | "PollingInterval": 2000, 28 | "SlaveId": 1, 29 | "Function": 4, 30 | "Address": 2, 31 | "Len": 1, 32 | "Tranform": "V*1,05", 33 | "Location": "Warehouse1_Shelf1", 34 | "DisplayName": "Humidity" 35 | } 36 | ] 37 | } 38 | ], 39 | "iWare": [ 40 | { 41 | "GPIO": 32, 42 | "Connection": "iWare_GPIO32", 43 | "Sensors": [ 44 | { 45 | "HwId": "28B11346920D02A7", 46 | "PollingInterval": 3000, 47 | "DisplayName": "Equipment2348", 48 | "Location": "Warehouse1_Shelf1", 49 | "Transform": "V+0.7" 50 | }, 51 | { 52 | "HwId": "28FF6C7997090341", 53 | "PollingInterval": 5000, 54 | "DisplayName": "Equipment1234", 55 | "Location": "Warehouse1_Shelf2", 56 | "Transform": "V+0.5" 57 | } 58 | ] 59 | }, 60 | { 61 | "GPIO": 33, 62 | "Connection": "iWare_GPIO33", 63 | "Sensors": [ 64 | { 65 | "HwId": "28B11346920D02A7", 66 | "PollingInterval": 3000, 67 | "DisplayName": "Equipment2348", 68 | "Location": "Warehouse2_Shelf1", 69 | "Transform": "V+0.7" 70 | }, 71 | { 72 | "HwId": "28FF6C7997090341", 73 | "PollingInterval": 5000, 74 | "DisplayName": "Equipment1234", 75 | "Location": "Warehouse2_Shelf2", 76 | "Transform": "V+0.5" 77 | } 78 | ] 79 | } 80 | ], 81 | "Analog": [ 82 | { 83 | "Connection": "Analog_test", 84 | "Sensors": [ 85 | { 86 | "HwId": "1234", 87 | "Channel": 6, 88 | "GPIO": 33, 89 | "PollingInterval": 2000, 90 | "DisplayName": "CurrenToVoltage1", 91 | "Location": "ServerRoom10_Chassis1", 92 | "Transform": "V+0.7" 93 | }, 94 | { 95 | "HwId": "4321", 96 | "Channel": 7, 97 | "GPIO": 34, 98 | "PollingInterval": 2500, 99 | "DisplayName": "CurrenToVoltage2", 100 | "Location": "ServerRoom11_Chassis1", 101 | "Transform": "V+0.7" 102 | } 103 | ] 104 | } 105 | ], 106 | "RelayOutput": [ 107 | { 108 | "Connection": "Relay_test", 109 | "Relays": [ 110 | { 111 | "HwId": "GPIO_26", 112 | "GPIO": 25, 113 | "DisplayName": "Ventilation_1", 114 | "Location": "ServerRoom01_Chassis1" 115 | }, 116 | { 117 | "HwId": "GPIO_26", 118 | "GPIO": 26, 119 | "DisplayName": "Ventilation_2", 120 | "Location": "ServerRoom01_Chassis2" 121 | }, 122 | { 123 | "HwId": "GPIO_27", 124 | "GPIO": 27, 125 | "DisplayName": "Ventilation_3", 126 | "Location": "ServerRoom01_Chassis3" 127 | }, 128 | { 129 | "HwId": "GPIO_13", 130 | "GPIO": 13, 131 | "DisplayName": "Ventilation_4", 132 | "Location": "ServerRoom01_Chassis4" 133 | }, 134 | { 135 | "HwId": "GPIO_15", 136 | "GPIO": 15, 137 | "DisplayName": "Ventilation_5", 138 | "Location": "ServerRoom01_Chassis5" 139 | }, 140 | { 141 | "HwId": "GPIO_12", 142 | "GPIO": 12, 143 | "DisplayName": "Ventilation_6", 144 | "Location": "ServerRoom01_Chassis6" 145 | }, 146 | { 147 | "HwId": "GPIO_33", 148 | "GPIO": 14, 149 | "DisplayName": "Ventilation_7", 150 | "Location": "ServerRoom01_Chassis7" 151 | }, 152 | { 153 | "HwId": "GPIO_33", 154 | "GPIO": 33, 155 | "DisplayName": "Ventilation_7", 156 | "Location": "ServerRoom01_Chassis7" 157 | } 158 | ] 159 | } 160 | ] 161 | } -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For ModbusConfig 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | ModbusConfig KEYWORD1 10 | Slave KEYWORD1 11 | Operation KEYWORD1 12 | 13 | ####################################### 14 | # Methods and Functions (KEYWORD2) 15 | ####################################### 16 | 17 | parseConfig KEYWORD2 18 | printConfig KEYWORD2 19 | loopModbusConfig KEYWORD2 20 | 21 | ####################################### 22 | # Constants (LITERAL1) 23 | ####################################### 24 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ModbusConfig", 3 | "keywords": "modbus, configuration, RS485, config, JSON", 4 | "description": "Modbus Config library for Arduino or ESP", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/Warlib1975/ModbusConfig.git" 8 | }, 9 | "version": "1.2", 10 | "exclude": [ 11 | "*.png", 12 | "*.zip" 13 | ], 14 | "frameworks": "arduino", 15 | "platforms": "*" 16 | } 17 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ModbusConfig 2 | version=1.2 3 | author=Andrey Fedorov 4 | maintainer=Andrey Fedorov 5 | sentence=ModbusConfig library for ESP8266/ESP32/Arduino. The library process a modbus configuration file in JSON format. 6 | paragraph=Each device connected to Rx/Tx pin (RS485 interface board) can have several operations block with different polling interval specified in a configuration file. Any operation has several parameters: SlaveID, function, address, len and operation name to visually distinguish operations. When a device (slave) polling interval has reached, the libriary invoke the callback function to send telemetry data to a cloud server or poll a slave device. 7 | category=Communication 8 | url=https://github.com/Warlib1975/ModbusConfig.git 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/EspFS.cpp: -------------------------------------------------------------------------------- 1 | // Please read ModbusConfig.h for information about the liscence and authors 2 | #include "EspFS.h" 3 | #include "FS.h" //http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html 4 | 5 | 6 | #ifdef ESP32 7 | #include "SPIFFS.h" //https://raw.githubusercontent.com/espressif/arduino-esp32/master/libraries/SPIFFS/examples/SPIFFS_Test/SPIFFS_Test.ino 8 | #endif 9 | 10 | EspFS::EspFS() 11 | { 12 | this->isSPIFFSInitialized = false; 13 | //this->initFS(); //On ESP32 such initialization goes to permanent reboot. 14 | } 15 | 16 | bool EspFS::loadTextFile() 17 | { 18 | return this->loadTextFile(this->filename); 19 | } 20 | 21 | bool EspFS::loadTextFile(char* filename) 22 | { 23 | int i; 24 | this->text = ""; 25 | 26 | if (!isSPIFFSInitialized) 27 | { 28 | isSPIFFSInitialized = this->initFS(); 29 | } 30 | 31 | if (!isSPIFFSInitialized) 32 | { 33 | return false; 34 | } 35 | 36 | Serial.println("Try to read file [" + String(filename) + "]."); 37 | 38 | //Read File data 39 | File file = SPIFFS.open(filename, "r"); 40 | 41 | if (!file) 42 | { 43 | Serial.println("File [" + String(file.name()) + "] open failed."); 44 | return false; 45 | } 46 | else 47 | { 48 | Serial.println("Reading Data from File: [" + String(file.name()) + "] with size [" + file.size() + "]."); 49 | 50 | //Data from file 51 | for(i=0;itext += (char)file.read(); 54 | } 55 | Serial.println(this->text); 56 | file.close(); //Close file 57 | Serial.println(); 58 | Serial.println("File Closed."); 59 | } 60 | return true; 61 | } 62 | 63 | bool EspFS::initFS() 64 | { 65 | //Initialize File System 66 | if (SPIFFS.begin()) 67 | { 68 | Serial.println("SPIFFS Initialize....ok"); 69 | isSPIFFSInitialized = true; 70 | return true; 71 | } 72 | else 73 | { 74 | Serial.println("SPIFFS Initialization...failed"); 75 | return false; 76 | } 77 | } 78 | 79 | bool EspFS::formatFS() 80 | { 81 | //Format File System 82 | if(SPIFFS.format()) 83 | { 84 | Serial.println("File System Formated"); 85 | return true; 86 | } 87 | else 88 | { 89 | Serial.println("File System Formatting Error"); 90 | return false; 91 | } 92 | } 93 | 94 | #ifdef ESP8266 95 | bool EspFS::showDir() 96 | { 97 | if (!isSPIFFSInitialized) 98 | { 99 | isSPIFFSInitialized = this->initFS(); 100 | } 101 | 102 | if (!isSPIFFSInitialized) 103 | { 104 | return false; 105 | } 106 | 107 | String str = ""; 108 | Dir dir = SPIFFS.openDir("/"); 109 | while (dir.next()) 110 | { 111 | str += dir.fileName(); 112 | str += " / "; 113 | str += dir.fileSize(); 114 | str += "\r\n"; 115 | } 116 | Serial.print(str); 117 | return true; 118 | } 119 | #endif -------------------------------------------------------------------------------- /src/EspFS.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 Andrey Fedorov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * 25 | Main code by Andrey Fedorov (fedorov@bizkit.ru) 26 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 27 | 28 | #ifndef EspFS_h 29 | #define EspFS_h 30 | 31 | #include "Arduino.h" 32 | #include //http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html 33 | 34 | class EspFS 35 | { 36 | public: 37 | 38 | /*! 39 | @brief Create an instance of the ESPSPIFFS class. 40 | 41 | @code 42 | 43 | // Create an instance of the ESPSPIFFS class. 44 | ESPSPIFFS() fs; 45 | 46 | @endcode 47 | */ 48 | EspFS(); 49 | 50 | /*! 51 | @brief Attach to a pin and sets that pin's mode (INPUT, INPUT_PULLUP or OUTPUT). 52 | 53 | @param filename 54 | JSON filename to load. 55 | @return True if the JSON script parsed successfully, otherwise false. 56 | */ 57 | bool loadTextFile(char* filename); 58 | bool loadTextFile(); 59 | 60 | 61 | /* 62 | @brief Initialize filesystem. 63 | @return True if the filesystem initialization passed successfully, otherwise false. 64 | */ 65 | bool initFS(); 66 | 67 | /* 68 | @brief Format a filesystem. 69 | @return True if the filesystem format passed successfully, otherwise false. 70 | */ 71 | bool formatFS(); 72 | 73 | /* 74 | @brief Show file list of the directory. 75 | @return True if the filesystem initialization passed successfully, otherwise false. 76 | */ 77 | #ifdef ESP8266 78 | bool showDir(); 79 | #endif 80 | /* 81 | @brief Convert char* string to int. 82 | @param str 83 | String containing HEX or DEC digit to convert to int. 84 | 85 | @return int with result of conversion. 86 | */ 87 | char* filename; 88 | String text; 89 | 90 | protected: 91 | 92 | private: 93 | bool isSPIFFSInitialized; 94 | }; 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /src/ModbusConfig.cpp: -------------------------------------------------------------------------------- 1 | // Please read ModbusConfig.h for information about the liscence and authors 2 | 3 | #include "ModbusConfig.h" 4 | //#include "Evaluator.h" 5 | 6 | bool processJsonError(int error) 7 | { 8 | bool res = false; 9 | switch (error) { 10 | case DeserializationError::Ok: 11 | Serial.println("Deserialization succeeded"); 12 | res = true; 13 | break; 14 | case DeserializationError::InvalidInput: 15 | Serial.println("Invalid input!"); 16 | break; 17 | case DeserializationError::NoMemory: 18 | Serial.println("Not enough memory"); 19 | break; 20 | default: 21 | Serial.println("Deserialization failed"); 22 | break; 23 | } 24 | return res; 25 | } 26 | 27 | ModbusConfig::ModbusConfig() 28 | { 29 | } 30 | 31 | int ModbusConfig::StrToHex(const char* str) 32 | { 33 | if (strchr(str, 'x') != NULL ) 34 | { 35 | return (int) strtol(str, 0, 16); 36 | } 37 | return (int) strtol(str, 0, 10); 38 | } 39 | 40 | bool ModbusConfig::parseConfig() 41 | { 42 | return this->parseConfig(this->json); 43 | } 44 | 45 | bool ModbusConfig::parseConfig(String json) 46 | { 47 | //DynamicJsonDocument doc; 48 | DeserializationError error = deserializeJson(*doc, json); 49 | if (error) { 50 | if (!processJsonError(error.code())) 51 | { 52 | Serial.println("JSON size is [" + String(json.length()) + "]."); 53 | } 54 | return false; 55 | } 56 | 57 | JsonObject obj = doc->as(); 58 | JsonArray modbus = obj["Modbus"]; 59 | //JsonArray arr = doc->as(); 60 | 61 | //Serial.println("--------------------------------------"); 62 | for (int i = 0; i < modbus.size(); i++) { 63 | //JsonObject connectionJSON = arr[i]["Connection"]; 64 | ModbusConnection* connection = new ModbusConnection; 65 | JsonObject connectionJSON = modbus[i];//doc["Modbus"][i]; 66 | connection->Connection = connectionJSON["Connection"].as(); 67 | connection->Sensor = SensorType :: Modbus; 68 | String type = connectionJSON["Type"].as(); 69 | connection->HardwareSerial = connectionJSON["HardwareSerial"] | -1; //.as(); // 70 | connection->Type = (type == "TCP") ? ModbusType::TCP : ModbusType::RTU; 71 | connection->PollingInterval = connectionJSON["PollingInterval"] | 60000; //.as(); // 72 | connection->HwId = connectionJSON["HwId"].as(); 73 | connection->BaudRate = connectionJSON["BaudRate"] | 9600; //.as(); // 74 | connection->Config = connectionJSON["Config"].as(); // 75 | connection->Config = (connection->Config == "") ? "SERIAL_8N1" : connection->Config; 76 | connection->RxPin = connectionJSON["RxPin"] | -1; //.as(); // | -1; 77 | connection->TxPin = connectionJSON["TxPin"] | -1; //.as(); // | -1; 78 | connection->RetryCount = connectionJSON["RetryCount"] | 10; //.as(); // 79 | connection->RetryInterval = connectionJSON["RetryInterval"] | 1000; //.as(); // 80 | connection->TcpPort = connectionJSON["TcpPort"] | -1; //.as(); // 81 | connection->Transform = connectionJSON["Transform"] | ""; //.as() 82 | connection->lastPolling = 0; 83 | connection->lastInstantMillis = 0; 84 | 85 | //JsonArray ops = arr[i]["Connection"]["Ops"]; 86 | JsonArray ops = connectionJSON["Ops"]; 87 | for (int j = 0; j < ops.size(); j++) { 88 | JsonObject operationJSON = ops[j]; 89 | ModbusOperation* operation = new ModbusOperation; 90 | operation->HwId = "Empty"; 91 | operation->PollingInterval = operationJSON["PollingInterval"] | 60000; //.as(); // 92 | operation->SlaveId = operationJSON["SlaveId"].as(); //| 1; // 93 | operation->Function = operationJSON["Function"].as(); // StrToHex(function); 94 | operation->Address = operationJSON["Address"].as(); // StrToHex(address); 95 | operation->Len = operationJSON["Len"].as(); // 96 | operation->DisplayName = operationJSON["DisplayName"].as(); // 97 | operation->Transform = operationJSON["Transform"] | ""; //.as() 98 | operation->Location = operationJSON["Location"].as(); // 99 | operation->lastPolling = 0; 100 | operation->lastInstantMillis = 0; 101 | connection->Operations.push_back(operation); 102 | } 103 | this->connections.push_back(connection); 104 | } 105 | 106 | JsonArray iWare = obj["iWare"]; 107 | for (int i = 0; i < iWare.size(); i++) { 108 | JsonObject connectionJSON = iWare[i]; 109 | iWareConnection* connection = new iWareConnection; 110 | connection->Sensor = SensorType :: iWare; 111 | connection->Connection = connectionJSON["Connection"].as(); 112 | connection->Transform = connectionJSON["Transform"] | "0.1"; //.as() 113 | connection->GPIO = connectionJSON["GPIO"].as() | -1; //.as(); // 114 | connection->PollingInterval = connectionJSON["PollingInterval"] | 600000; //.as(); // 115 | connection->lastPolling = 0; 116 | connection->lastInstantMillis = 0; 117 | 118 | JsonArray Sensors = connectionJSON["Sensors"]; 119 | for (int j = 0; j < Sensors.size(); j++) { 120 | iWareSensor* sensor = new iWareSensor; 121 | JsonObject sensorJSON = Sensors[j]; 122 | sensor->HwId = sensorJSON["HwId"].as(); 123 | sensor->PollingInterval = sensorJSON["PollingInterval"] | 60000; //.as(); // 124 | sensor->DisplayName = sensorJSON["DisplayName"].as(); // 125 | sensor->Location = sensorJSON["Location"].as(); // 126 | sensor->Transform = sensorJSON["Transform"] | ""; //.as() 127 | sensor->lastPolling = 0; 128 | sensor->lastInstantMillis = 0; 129 | //connection->Sensors.push_back(sensor); 130 | connection->Operations.push_back(sensor); 131 | } 132 | this->connections.push_back(connection); 133 | } 134 | 135 | JsonArray Analog = obj["Analog"]; 136 | for (int i = 0; i < Analog.size(); i++) { 137 | JsonObject connectionJSON = Analog[i]; 138 | AnalogConnection* connection = new AnalogConnection; 139 | connection->Connection = connectionJSON["Connection"].as(); 140 | connection->PollingInterval = connectionJSON["PollingInterval"] | 600000; //.as(); // 141 | connection->Sensor = SensorType :: Analog; 142 | connection->Transform = connectionJSON["Transform"] | ""; //.as() 143 | connection->lastPolling = 0; 144 | connection->lastInstantMillis = 0; 145 | 146 | JsonArray Sensors = connectionJSON["Sensors"]; 147 | for (int j = 0; j < Sensors.size(); j++) { 148 | JsonObject sensorJSON = Sensors[j]; 149 | AnalogSensor* sensor = new AnalogSensor; 150 | sensor->HwId = sensorJSON["HwId"].as(); //.as(); // 151 | sensor->PollingInterval = sensorJSON["PollingInterval"] | 5000; //.as(); // 152 | sensor->DisplayName = sensorJSON["DisplayName"].as(); // 153 | sensor->Location = sensorJSON["Location"].as(); // 154 | sensor->Transform = sensorJSON["Transform"] | ""; //.as() 155 | sensor->Channel = sensorJSON["Channel"] | -1; // 156 | sensor->GPIO = sensorJSON["GPIO"] | -1; // 157 | sensor->lastPolling = 0; 158 | sensor->lastInstantMillis = 0; 159 | //connection->Sensors.push_back(sensor); 160 | connection->Operations.push_back(sensor); 161 | } 162 | this->connections.push_back(connection); 163 | } 164 | 165 | JsonArray relays = obj["RelayOutput"]; 166 | for (int i = 0; i < relays.size(); i++) 167 | { 168 | JsonObject connectionJSON = relays[i]; 169 | //RelayConnection* connection = new RelayConnection; 170 | relayOutput = new RelayConnection; 171 | relayOutput->Connection = connectionJSON["Connection"].as(); 172 | relayOutput->PollingInterval = connectionJSON["PollingInterval"] | 600000; //.as(); // 173 | relayOutput->Transform = connectionJSON["Transform"] | ""; //.as() 174 | relayOutput->Sensor = SensorType :: Relay; 175 | relayOutput->lastPolling = 0; 176 | relayOutput->lastInstantMillis = 0; 177 | 178 | JsonArray Relays = connectionJSON["Relays"]; 179 | for (int j = 0; j < Relays.size(); j++) 180 | { 181 | JsonObject sensorJSON = Relays[j]; 182 | RelayOut* sensor = new RelayOut; 183 | sensor->HwId = sensorJSON["HwId"].as(); //.as(); // 184 | sensor->PollingInterval = sensorJSON["PollingInterval"] | 600000; //.as(); // 185 | sensor->DisplayName = sensorJSON["DisplayName"].as(); // 186 | sensor->Location = sensorJSON["Location"].as(); // 187 | sensor->GPIO = sensorJSON["GPIO"] | -1; // 188 | sensor->lastPolling = 0; 189 | sensor->lastInstantMillis = 0; //connection->Sensors.push_back(sensor); 190 | relayOutput->Operations.push_back(sensor); 191 | } 192 | this->connections.push_back(relayOutput); 193 | } 194 | return true; 195 | } 196 | 197 | void ModbusConfig::loopModbusConfig() 198 | { 199 | unsigned long currentMillis = millis(); 200 | //if (currentMillis - lastPolling > minconnectionPollingInterval) 201 | //{ 202 | for (BaseConnection* connectionJSON : this->connections) 203 | { 204 | if (currentMillis - connectionJSON->lastInstantMillis >= 10) //Call the function to calculate an average value 205 | { 206 | if (this->instantCallback) 207 | { 208 | instantCallback(connectionJSON, NULL); 209 | } 210 | connectionJSON->lastInstantMillis = currentMillis; 211 | } 212 | 213 | if ((connectionJSON->PollingInterval > 0) && 214 | (currentMillis - connectionJSON->lastPolling >= connectionJSON->PollingInterval)) 215 | { 216 | if (this->pollingIntervalCallback) 217 | { 218 | pollingIntervalCallback(connectionJSON, NULL); //Execute callback function to process connectionJSON polling interval 219 | } 220 | connectionJSON->lastPolling = currentMillis; 221 | } 222 | 223 | for (BaseOperation* operation : connectionJSON->Operations) 224 | { 225 | if ((operation->PollingInterval) && 226 | (currentMillis - operation->lastPolling >= operation->PollingInterval)) 227 | { 228 | if (this->pollingIntervalCallback) 229 | { 230 | pollingIntervalCallback(connectionJSON, operation); //Execute callback function to process operation polling interval 231 | } 232 | operation->lastPolling = currentMillis; 233 | } 234 | 235 | if (currentMillis - operation->lastInstantMillis >= 10) //Call the function to calculate an average value 236 | { 237 | if (this->instantCallback) 238 | { 239 | instantCallback(connectionJSON, operation); 240 | } 241 | operation->lastInstantMillis = currentMillis; 242 | } 243 | } 244 | //this->connections.shrink_to_fit(); //free unused memory 245 | } 246 | } 247 | 248 | void ModbusConfig::printOperations(SensorType sensorType, OperationsType operations) 249 | { 250 | Serial.println("---Operations:-------------------------"); 251 | for (BaseOperation* op : operations) 252 | { 253 | printValue("\tHwId", op->HwId); 254 | printValue("\tPollingInterval", String(op->PollingInterval)); 255 | printValue("\tDisplayName", op->DisplayName); 256 | printValue("\tTransform", op->Transform); 257 | printValue("\tLocation", op->Location); 258 | switch (sensorType) 259 | { 260 | case SensorType::Modbus: 261 | { 262 | ModbusOperation* operation = static_cast(op); 263 | printValue("\tSlaveId" , String(operation->SlaveId)); 264 | printValue("\tFunction" , String(operation->Function, HEX), true); 265 | printValue("\tAddress" , String(operation->Address, HEX), true); 266 | printValue("\tLen" , String(operation->Len)); 267 | break; 268 | } 269 | case SensorType::iWare: 270 | break; 271 | case SensorType::Analog: 272 | { 273 | AnalogSensor* sensor = static_cast(op); 274 | printValue("\tChannel" , String(sensor->Channel)); 275 | printValue("\tGPIO" , String(sensor->GPIO)); 276 | break; 277 | } 278 | case SensorType::Relay: 279 | { 280 | RelayOut* sensor = static_cast(op); 281 | printValue("\tGPIO" , String(sensor->GPIO)); 282 | break; 283 | } 284 | default: 285 | printValue("\nUnknown sensor type", ""); 286 | } 287 | Serial.println("--------------------------------------"); 288 | } 289 | } 290 | 291 | void ModbusConfig::printConfig() 292 | { 293 | for (BaseConnection* conn : connections) 294 | { 295 | printValue("Connection" , conn->Connection); 296 | printValue("Sensor" , String(conn->Sensor)); 297 | printValue("PollingInterval", String(conn->PollingInterval)); 298 | switch (conn->Sensor) 299 | { 300 | case SensorType::Modbus: 301 | { 302 | ModbusConnection* connection = static_cast(conn); 303 | printValue("PollingInterval", String(connection->PollingInterval)); 304 | String type = (connection->Type == ModbusType::TCP) ? "TCP" : "RTU"; 305 | printValue("Type" , type); 306 | printValue("HwId" , connection->HwId); 307 | printValue("HardwareSerial" , String(connection->HardwareSerial)); 308 | printValue("RxPin" , String(connection->RxPin)); 309 | printValue("TxPin" , String(connection->TxPin)); 310 | printValue("BaudRate" , String(connection->BaudRate)); 311 | printValue("Config" , connection->Config); 312 | printValue("RetryCount" , String(connection->RetryCount)); 313 | printValue("RetryInterval" , String(connection->RetryInterval)); 314 | printValue("TcpPort" , String(connection->TcpPort)); 315 | printOperations(connection->Sensor, connection->Operations); 316 | break; 317 | } 318 | case SensorType::iWare: 319 | { 320 | iWareConnection* connection = static_cast(conn); 321 | printValue("GPIO", String(connection->GPIO)); 322 | printOperations(connection->Sensor, connection->Operations); 323 | break; 324 | } 325 | case SensorType::Analog: 326 | { 327 | AnalogConnection* connection = static_cast(conn); 328 | printOperations(connection->Sensor, connection->Operations); 329 | break; 330 | } 331 | case SensorType::Relay: 332 | { 333 | RelayConnection* connection = static_cast(conn); 334 | printOperations(connection->Sensor, connection->Operations); 335 | break; 336 | } 337 | } 338 | } 339 | } 340 | 341 | //isHex default value is false 342 | void ModbusConfig::printValue(String name, String value, bool isHex) 343 | { 344 | if ((value != "null") && (value != "") && (value != "-1")) //-1 - default value for int 345 | { 346 | String prefix = ""; 347 | if (isHex) 348 | { 349 | prefix = (value.length() == 1) ? "0x0" : "0x"; 350 | } 351 | Serial.println(name + ": " + prefix + value); 352 | } 353 | } 354 | 355 | void ModbusConfig::math_operation(math_op op, float &prev_val, String ¤t_val, float &value) 356 | { 357 | if (prev_val != -16101975) //Check if the default value has changed 358 | { 359 | if (current_val.length() > 0) 360 | { 361 | float second = current_val.toFloat(); 362 | switch (op) 363 | { 364 | case math_op::Mul: 365 | value = prev_val * second; 366 | break; 367 | case math_op::Add: 368 | value = prev_val + second; 369 | break; 370 | case math_op::Div: 371 | value = prev_val / second; 372 | break; 373 | case math_op::Sub: 374 | value = prev_val - second; 375 | break; 376 | case math_op::None: 377 | printf("There is no operation."); 378 | } 379 | prev_val = value; 380 | current_val = ""; 381 | } 382 | } 383 | else 384 | { 385 | if (current_val.length() > 0) 386 | { 387 | prev_val = current_val.toFloat(); 388 | current_val = ""; 389 | } 390 | } 391 | } 392 | 393 | float ModbusConfig::Eval(String expr, float value) 394 | { 395 | if (expr == "") 396 | { 397 | return value; 398 | } 399 | 400 | //String pattern = "%V%"; 401 | expr.toUpperCase(); 402 | int pos = expr.indexOf("%V%"); //pattern 403 | if (pos == -1) 404 | { 405 | expr = "%V%*" + expr; //default operation is mul 406 | } 407 | expr.replace("%V%", String(value)); 408 | String val = ""; 409 | math_op last_op = math_op::None; 410 | float prev_val = -16101975;//Set the some default value to check if the value has changed 411 | value = 0; 412 | for (int i = 0; i < expr.length(); i++) 413 | { 414 | switch (expr[i]) 415 | { 416 | case '*': 417 | math_operation(last_op, prev_val, val, value); 418 | last_op = math_op::Mul; 419 | break; 420 | case '+': 421 | math_operation(last_op, prev_val, val, value); 422 | last_op = math_op::Add; 423 | break; 424 | case '/': 425 | math_operation(last_op, prev_val, val, value); 426 | last_op = math_op::Div; 427 | break; 428 | case '-': 429 | math_operation(last_op, prev_val, val, value); 430 | last_op = math_op::Sub; 431 | break; 432 | default: 433 | if (isdigit(expr[i]) || expr[i] == '.' || expr[i] == ',') 434 | { 435 | val += (expr[i] == ',') ? '.' : expr[i]; 436 | } 437 | break; 438 | } 439 | } 440 | math_operation(last_op, prev_val, val, value); 441 | return value; 442 | } 443 | -------------------------------------------------------------------------------- /src/ModbusConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 Andrey Fedorov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * 25 | Main code by Andrey Fedorov (fedorov@bizkit.ru) 26 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 27 | 28 | #ifndef ModbusConfig_h 29 | #define ModbusConfig_h 30 | 31 | #include "Arduino.h" 32 | //#include "Evaluator.h" 33 | 34 | #include 35 | 36 | /** 37 | @example ArduinoModbusConfig.ino 38 | Simple example of the ModbusConfig library. 39 | */ 40 | 41 | bool processJsonError(int error); 42 | 43 | enum SensorType { Modbus, iWare, Analog, Relay, DryContact, NONE }; 44 | 45 | struct BaseOperation 46 | { 47 | String HwId; //Sensor HwId or inventory number 48 | int PollingInterval; //default 5000 49 | String Transform; //Formulas to transform input data 50 | String DisplayName; //Name to display 51 | String Location; //Location of the sensor 52 | unsigned long lastPolling; //Last polling in millis 53 | unsigned long lastInstantMillis; 54 | void* Tag; //Any data to save information for calculation average value or filtering 55 | }; 56 | 57 | struct AnalogSensor : BaseOperation 58 | { 59 | int Channel; //ADC channel number 60 | int GPIO; //GPIO number 61 | }; 62 | 63 | struct iWareSensor : BaseOperation 64 | { 65 | 66 | }; 67 | 68 | struct RelayOut : BaseOperation 69 | { 70 | int GPIO; //GPIO number 71 | }; 72 | 73 | //typedef std::vector iWareSensors; 74 | 75 | typedef std::vector OperationsType; 76 | 77 | struct BaseConnection 78 | { 79 | String Connection; 80 | SensorType Sensor; 81 | unsigned long lastPolling; // 82 | int PollingInterval; //default 5000 83 | String Transform; //Formulas to transform input data 84 | OperationsType Operations; 85 | unsigned long lastInstantMillis; 86 | }; 87 | 88 | struct iWareConnection : BaseConnection 89 | { 90 | int GPIO; 91 | //iWareSensors Sensors; 92 | }; 93 | 94 | 95 | //typedef std::vector AnalogSensors; //Array of the analog sensors 96 | 97 | struct AnalogConnection : BaseConnection 98 | { 99 | 100 | }; 101 | 102 | struct RelayConnection : BaseConnection 103 | { 104 | 105 | }; 106 | 107 | //typedef std::vector iWareConnections; 108 | 109 | 110 | struct ModbusOperation : BaseOperation 111 | { 112 | int SlaveId; //default 1 113 | void* Modbus; 114 | int Function; 115 | int Address; 116 | int Len; 117 | }; 118 | 119 | //typedef std::vector OperationsType; 120 | 121 | enum ModbusType { RTU, TCP }; 122 | 123 | enum math_op { Mul, Add, Div, Sub, None }; 124 | 125 | struct ModbusConnection : BaseConnection 126 | { 127 | void* Connector; 128 | ModbusType Type = ModbusType :: RTU; 129 | int RxPin; //default -1 130 | int TxPin; //default -1 131 | int HardwareSerial = -1; //Hardware port number or -1 for SoftwareSerial 132 | int RetryCount; //default 10 133 | int RetryInterval; //default 1000 134 | String HwId; 135 | int BaudRate; //default 9600 136 | String Config; //SERIAL_8N1 137 | int TcpPort; //default -1 138 | // OperationsType Operations; 139 | }; 140 | 141 | typedef std::vector Connections; 142 | 143 | /* 144 | @brief Callback function to process an operation polling interval. 145 | */ 146 | //typedef void (*PHandler)(Connection* connection, Operation* operation); 147 | typedef void (*OnSensorPollingHandler)(BaseConnection* connection, BaseOperation* operation); 148 | 149 | 150 | /** 151 | The ModbusConfig class. 152 | */ 153 | class ModbusConfig 154 | { 155 | public: 156 | 157 | /*! 158 | @brief Create an instance of the ModbusConfig class. 159 | 160 | @code 161 | // Create an instance of the ModbusConfig class. 162 | ModbusConfig() modbus; 163 | 164 | @endcode 165 | */ 166 | ModbusConfig(); 167 | 168 | 169 | /*! 170 | @brief Parse Modbus Config JSON file. 171 | 172 | @param json 173 | JSON file with modbus configuration. 174 | @return True if the JSON script parsed successfully, otherwise false. 175 | */ 176 | bool parseConfig(String json); 177 | bool parseConfig(); 178 | 179 | /*! 180 | @brief Print loaded and parsed config to Serial. 181 | */ 182 | void printConfig(); 183 | 184 | /*! 185 | @brief Polling interval periodical checker and callback function executer if some operation should be processed. 186 | */ 187 | void loopModbusConfig(); 188 | 189 | float Eval(String expr, float value); 190 | 191 | 192 | /* 193 | @brief Convert char* string to int. 194 | @param str 195 | String containing HEX or DEC digit to convert to int. 196 | 197 | @return int with result of conversion. 198 | */ 199 | int StrToHex(const char* str); 200 | 201 | OnSensorPollingHandler pollingIntervalCallback; //Callback the method corresponding interval specified in config for the sensor 202 | OnSensorPollingHandler instantCallback; //Callback the method to calculate average value. 203 | DynamicJsonDocument *doc; 204 | char* filename; 205 | String json; 206 | Connections connections; 207 | RelayConnection* relayOutput; 208 | 209 | protected: 210 | 211 | private: 212 | //unsigned int minSlavePollingInterval = 0; 213 | //unsigned int lastPolling = 0; 214 | void printValue(String name, String value, bool isHex = false); 215 | void printOperations(SensorType sensorType, OperationsType operations); 216 | void math_operation(math_op op, float &prev_val, String ¤t_val, float &value); 217 | }; 218 | 219 | #endif 220 | -------------------------------------------------------------------------------- /src/data/modbus.cfg: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Connection": { 4 | "Connection": "ttyS0", 5 | "Type": "RTU", 6 | "PollingInterval": 4000, 7 | "RetryCount": 10, 8 | "RetryInterval": 100, 9 | "HwId": "TermoSensor-0a:01:01:01:01:02", 10 | "RxPin": 15, 11 | "TxPin": 13, 12 | "BaudRate": 9600, 13 | "Config": "SERIAL_8N1", 14 | "HardwareSerial": 2, 15 | "Ops": [ 16 | { 17 | "PollingInterval": 2000, 18 | "SlaveId": 1, 19 | "Function": 4, 20 | "Address": 1, 21 | "Len": 1, 22 | "DisplayName": "Temp" 23 | }, 24 | { 25 | "PollingInterval": 2000, 26 | "SlaveId": 1, 27 | "Function": 4, 28 | "Address": 2, 29 | "Len": 1, 30 | "DisplayName": "Humidity" 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "Connection": { 37 | "Connection": "192.168.1.2", 38 | "Type": "TCP", 39 | "PollingInterval": 4000, 40 | "TcpPort": 502, 41 | "HwId": "TermoSensor-0a:01:01:01:01:02", 42 | "Ops": [ 43 | { 44 | "PollingInterval": 2000, 45 | "SlaveId": 1, 46 | "Function": 4, 47 | "Address": 1, 48 | "Len": 1, 49 | "DisplayName": "Temp" 50 | } 51 | ] 52 | } 53 | } 54 | ] --------------------------------------------------------------------------------