├── .gitignore ├── README.md ├── Tracer-RS485-Modbus-Blynk-V2.ino ├── doc ├── 1733_modbus_protocol.pdf ├── blynk-app-qr-code.png ├── max485_module.jpg ├── mppt-triton.png ├── mppt-xtra.png ├── nodemcu_pins.png ├── schematic.png ├── screenshot-blynk.png ├── tracer-a.png └── tracer-b.png ├── esp_credentials.h ├── settings.h └── test └── test-mppt-tracer-modbus.py /.gitignore: -------------------------------------------------------------------------------- 1 | /esp_credentials.h 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tracer-RS485-Modbus-Blynk V2.0 - RELOADED 2 | 3 | ### An Arduino project to connect the `EPSolar`/`EPEver` `Tracer A/B`, `Xtra`, `Triton` Series MPPT Solar Controllers (RS-485 Modbus) to an `ESP8266` and monitor it using the `Blynk` mobile app, the reloaded version! 4 | 5 | This is almost complete rewrite of the original [project](https://github.com/jaminNZx/Tracer-RS485-Modbus-Blynk), with ton of improvements, refactored code, brand new Blynk project, and wider compatibility of RS485 convertrs. 6 | 7 | Feel free to make pull requests if you wish to help improving. 8 | There is also a support forum on the Blynk community forums: http://community.blynk.cc/t/epsolar-tracer-2210a-charge-controller-blynk-epic-solar-monitor/10596 9 | 10 | 11 | ## Hardware 12 | 13 | ![Triton](doc/mppt-triton.png) 14 | ![Xtra](doc/mppt-xtra.png) 15 | ![Tracer-AN](doc/tracer-a.png) 16 | ![Tracer-BN](doc/tracer-b.png) 17 | 18 | * [EPSolar/EPEver Tracer A/B-Series](https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20170114172728&SearchText=tracer+mppt+rs485) 19 | 20 | * [RS485 UART Module](https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20170114172807&SearchText=uart+rs485) (~~not the MAX485 chip!~~ - `@tekk:` I'm using [MAX485 cheapo module](doc/max485_module.jpg) and it works fine!) 21 | 22 | * [ESP8266 Dev Board](https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20170114172938&SearchText=esp8266+mini) 23 | 24 | * An old ethernet cable with RJ45 connector you are happy to cut open 25 | 26 | 27 | ## Sample screenshot 28 | 29 | ![Live data on Blynk on Android screenshot](doc/screenshot-blynk.png) 30 | 31 | 32 | ## Software 33 | 34 | * [Blynk](http://www.blynk.cc/) Mobile App ([iOS](https://itunes.apple.com/us/app/blynk-iot-for-arduino-rpi/id808760481?mt=8) & [Android](https://play.google.com/store/apps/details?id=cc.blynk&hl=en)) 35 | * Arduino IDE 1.6.9+ 36 | * The project sketch 37 | 38 | 39 | ## Wiring 40 | 41 | Cut open your ethernet cable and split out pin 3, 5, 7 (B, A, GND). Refer to [Tracer Modbus PDF](doc/1733_modbus_protocol.pdf) for additional info. 42 | 43 | Follow the wiring guide below: ~~(note that the 2-pol switch is only needed during flashing)~~ **No longer needed!** 44 | 45 | ![Tracer Wiring Diagram](doc/schematic.png) 46 | > Note: as @Don Vukovic noted, there is probably reversed RX and TX on this schematic. Correct is: DI to TX, and RO to RX 47 | 48 | ## Setup 49 | 50 | ### Libraries 51 | 52 | Follow links to get them. 53 | 54 | * [Blynk Library](https://github.com/blynkkk/blynk-library) 55 | * [ArduinoOTA](https://github.com/esp8266/Arduino/tree/master/libraries/ArduinoOTA) 56 | * [SimpleTimer](https://github.com/schinken/SimpleTimer) 57 | * [ModbusMaster](https://github.com/4-20ma/ModbusMaster) 58 | 59 | ## Tutorial 60 | 61 | ### Edit `esp_credentials.h` library 62 | 63 | Firstly, enter WiFi credentials in `esp_credentials.h`. 64 | 65 | You will be able to use this file by including it in any sketch by entering ```#include ```. (This include is already present in `settings.h`, there's no need to add it.) 66 | 67 | Example: 68 | 69 | ```cpp 70 | /************************************************************** 71 | * Settings - Wifi Credentials 72 | **************************************************************/ 73 | #define WIFI_SSID "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 74 | #define WIFI_PASS "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 75 | 76 | ``` 77 | 78 | ## Updated Blynk App V2.0 79 | 80 | * Open the Blynk mobile app and create a new project by scanning the following QR code 81 | 82 | ![Project QR Code](doc/blynk-app-qr-code.png) 83 | 84 | * Send yourself the generated auth code 85 | * Paste your auth code into `esp_credentials.h`: 86 | 87 | ```cpp 88 | #define AUTH "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 89 | ``` 90 | 91 | * ~~Disconnect TX/RX cables (or open the switch if you have one)~~ 92 | * You don't have to do this anymore! But it's always best to have it disconnected. 93 | * Upload the sketch to your ESP8266 94 | * ~~Once uploaded, reconnect the TX/RX cables and plug the cable in to the Tracer COM port~~ 95 | * Just plug the ethernet cable to the Solar controller. Anyhow, it is always a good idea to repower the MAX485 module, if you're using one, between sketch uploads 96 | * Load the Blynk project and hit PLAY button to start receiving data 97 | 98 | ## Reference 99 | 100 | * [Tracer A/B Series MPPT Solar Controller - Modbus Protocol](doc/1733_modbus_protocol.pdf) 101 | 102 | * MAX485 module: 103 | 104 | ![This one worked for me](doc/max485_module.jpg) 105 | 106 | I'm using this cheapo module and it works quite fine. 107 | It's powered from `+5V` from ESP8266, and wired as following: 108 | 109 | - MAX485 module <-> ESP8266 110 | - `DI` -> `D10` / `GPIO1` / `TX` 111 | - `RO` -> `D9` / `GPIO3` / `RX` 112 | - `DE` and `RE` are interconnected with a jumper and then connected do eighter pin `D1` or `D2` 113 | - `VCC` to `+5V` / `VIN` on ESP8266 114 | 115 | 116 | - Tracer A/B MPPT Controller Ethernet cable <-> MAX485 117 | - Ethernet green, pin `5` -> `A` 118 | - Ethernet blue, pin `3` -> `B` 119 | - Ethernet brown, pin `7` -> `GND` on module **and** ESP8266 `GND` pin 120 | - -> to prevent ground loops - **important!** 121 | 122 | 123 | ![ESP8266 NodeMCU v0.9](doc/nodemcu_pins.png) 124 | 125 | ## Developing further 126 | 127 | > I plan to add more features and pull more data from the controller once I have my own solar system running. 128 | > If you'd like to pick this up and have a go at adding features, I'll be happy to accept pull requests. 129 | 130 | You are welcome for suggestions, bugreports, and of course any further improvements of this code. 131 | 132 | 133 | ## `@tekk`'s V2 Changelog 134 | - Rewrote whole sketch 135 | - Tried to utilize HardwareSerial `UART2` - no avail :( 136 | - `ModbusMaster` library is incopatible with `SoftwareSerial`, (don't even try)... Would need to rewrite whole `ModbusMaster`, so Hardware UART is the only option for smooth & seamless communication because of the interrupt driven data transmission, more precise timing, and HW buffer, and stuff 137 | - Optimized for very cheap MAX485 module, you can buy it from usual sources... 138 | - **Feature:** Added option to switch the output of the Tracer MPPT Controller ON/OFF from the Blynk app 139 | - **Improvement:** You no longer need to disconnect and reconnect Modbus RS485 Serial port from the ESP8266 while uploading 140 | - Code rewrote to use as little magic constants as possible 141 | - Added `preTransmission` abd `postTransmission` Modbus handling / signalling, just to be sure... 142 | - Added calls to `ESP.wdtDisable()` and `ESP.wdtEnable(1)`, temporary System Watchdog shutdown and later found it to be not necessarry 143 | - Avoids unwanted rebooting of ESP8366 while receiving data from the Modbus 144 | - Added more debug outputs and results to USB Serial 145 | 146 | 147 | ## Credits 148 | 149 | - `@jaminNZx:` 150 | - Thanks to subtafuge on [Reddit](https://www.reddit.com/r/esp8266/comments/59dt00/using_esp8266_to_connect_rs485_modbus_protocol/) for lending me his working Tracer RS485 code! 151 | 152 | - `@tekk:` 153 | - Feel free to contact me about my code changes in this version 154 | - Thanks to [@jaminNZx](https://github.com/jaminNZx) for the original code. Big up! 155 | -------------------------------------------------------------------------------- /Tracer-RS485-Modbus-Blynk-V2.ino: -------------------------------------------------------------------------------- 1 | // CONNECT THE RS485 MODULE. 2 | // MAX485 module <-> ESP8266 3 | // - DI -> D10 / GPIO1 / TX 4 | // - RO -> D9 / GPIO3 / RX 5 | // - DE and RE are interconnected with a jumper and then connected do eighter pin D1 or D2 6 | // - VCC to +5V / VIN on ESP8266 7 | // - GNDs wired together 8 | // ------------------------------------- 9 | // You do not need to disconnect the RS485 while uploading code. 10 | // After first upload you should be able to upload over WiFi 11 | // Tested on NodeMCU + MAX485 module 12 | // RJ 45 cable: Green -> A, Blue -> B, Brown -> GND module + GND ESP8266 13 | // MAX485: DE + RE interconnected with a jumper and connected to D1 or D2 14 | // 15 | // Developed by @jaminNZx 16 | // With modifications by @tekk 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "settings.h" 27 | 28 | #define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) 29 | 30 | const int defaultBaudRate = 115200; 31 | int timerTask1, timerTask2, timerTask3; 32 | float battChargeCurrent, battDischargeCurrent, battOverallCurrent, battChargePower; 33 | float bvoltage, ctemp, btemp, bremaining, lpower, lcurrent, pvvoltage, pvcurrent, pvpower; 34 | float stats_today_pv_volt_min, stats_today_pv_volt_max; 35 | uint8_t result; 36 | bool rs485DataReceived = true; 37 | bool loadPoweredOn = true; 38 | 39 | #define MAX485_DE D1 40 | #define MAX485_RE_NEG D2 41 | 42 | ModbusMaster node; 43 | SimpleTimer timer; 44 | 45 | void preTransmission() { 46 | digitalWrite(MAX485_RE_NEG, 1); 47 | digitalWrite(MAX485_DE, 1); 48 | } 49 | 50 | void postTransmission() { 51 | digitalWrite(MAX485_RE_NEG, 0); 52 | digitalWrite(MAX485_DE, 0); 53 | } 54 | 55 | // A list of the regisities to query in order 56 | typedef void (*RegistryList[])(); 57 | 58 | RegistryList Registries = { 59 | AddressRegistry_3100, 60 | AddressRegistry_3106, 61 | AddressRegistry_310D, 62 | AddressRegistry_311A, 63 | AddressRegistry_331B, 64 | }; 65 | 66 | // keep log of where we are 67 | uint8_t currentRegistryNumber = 0; 68 | 69 | // function to switch to next registry 70 | void nextRegistryNumber() { 71 | // better not use modulo, because after overlow it will start reading in incorrect order 72 | currentRegistryNumber++; 73 | if (currentRegistryNumber >= ARRAY_SIZE(Registries)) { 74 | currentRegistryNumber = 0; 75 | } 76 | } 77 | 78 | // **************************************************************************** 79 | 80 | void setup() 81 | { 82 | pinMode(MAX485_RE_NEG, OUTPUT); 83 | pinMode(MAX485_DE, OUTPUT); 84 | 85 | digitalWrite(MAX485_RE_NEG, 0); 86 | digitalWrite(MAX485_DE, 0); 87 | 88 | Serial.begin(defaultBaudRate); 89 | 90 | // Modbus slave ID 1 91 | node.begin(1, Serial); 92 | 93 | // callbacks to toggle DE + RE on MAX485 94 | node.preTransmission(preTransmission); 95 | node.postTransmission(postTransmission); 96 | 97 | Serial.println("Connecting to Wifi..."); 98 | 99 | WiFi.mode(WIFI_STA); 100 | 101 | #if defined(USE_LOCAL_SERVER) 102 | Blynk.begin(AUTH, WIFI_SSID, WIFI_PASS, SERVER); 103 | #else 104 | Blynk.begin(AUTH, WIFI_SSID, WIFI_PASS); 105 | #endif 106 | 107 | while (WiFi.waitForConnectResult() != WL_CONNECTED) { 108 | Serial.println("Connection Failed! Rebooting..."); 109 | delay(5000); 110 | ESP.restart(); 111 | } 112 | 113 | Serial.println("Connected."); 114 | Serial.print("Connecting to Blynk..."); 115 | 116 | while (!Blynk.connect()) { 117 | Serial.print("."); 118 | delay(100); 119 | } 120 | 121 | Serial.println(); 122 | Serial.println("Connected to Blynk."); 123 | Serial.println("Starting ArduinoOTA..."); 124 | 125 | ArduinoOTA.setHostname(OTA_HOSTNAME); 126 | ArduinoOTA.setPassword((const char *)OTA_PASS); 127 | 128 | ArduinoOTA.onStart([]() { 129 | String type; 130 | if (ArduinoOTA.getCommand() == U_FLASH) { 131 | type = "sketch"; 132 | } else { // U_SPIFFS 133 | type = "filesystem"; 134 | } 135 | 136 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 137 | Serial.println("Start updating " + type); 138 | }); 139 | 140 | ArduinoOTA.onEnd([]() { 141 | Serial.println("\nEnd of update"); 142 | }); 143 | 144 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 145 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 146 | }); 147 | 148 | ArduinoOTA.onError([](ota_error_t error) { 149 | Serial.printf("Error[%u]: ", error); 150 | if (error == OTA_AUTH_ERROR) { 151 | Serial.println("Auth Failed"); 152 | } else if (error == OTA_BEGIN_ERROR) { 153 | Serial.println("Begin Failed"); 154 | } else if (error == OTA_CONNECT_ERROR) { 155 | Serial.println("Connect Failed"); 156 | } else if (error == OTA_RECEIVE_ERROR) { 157 | Serial.println("Receive Failed"); 158 | } else if (error == OTA_END_ERROR) { 159 | Serial.println("End Failed"); 160 | } 161 | }); 162 | 163 | ArduinoOTA.begin(); 164 | 165 | Serial.print("ArduinoOTA running. "); 166 | Serial.print("IP address: "); 167 | Serial.println(WiFi.localIP()); 168 | Serial.println("Starting timed actions..."); 169 | 170 | timerTask1 = timer.setInterval(1000L, executeCurrentRegistryFunction); 171 | timerTask2 = timer.setInterval(1000L, nextRegistryNumber); 172 | timerTask3 = timer.setInterval(1000L, uploadToBlynk); 173 | 174 | Serial.println("Setup OK!"); 175 | Serial.println("----------------------------"); 176 | Serial.println(); 177 | } 178 | 179 | // -------------------------------------------------------------------------------- 180 | 181 | // upload values 182 | void uploadToBlynk() { 183 | Blynk.virtualWrite(vPIN_PV_POWER, pvpower); 184 | Blynk.virtualWrite(vPIN_PV_CURRENT, pvcurrent); 185 | Blynk.virtualWrite(vPIN_PV_VOLTAGE, pvvoltage); 186 | Blynk.virtualWrite(vPIN_LOAD_CURRENT, lcurrent); 187 | Blynk.virtualWrite(vPIN_LOAD_POWER, lpower); 188 | Blynk.virtualWrite(vPIN_BATT_TEMP, btemp); 189 | Blynk.virtualWrite(vPIN_BATT_VOLTAGE, bvoltage); 190 | Blynk.virtualWrite(vPIN_BATT_REMAIN, bremaining); 191 | Blynk.virtualWrite(vPIN_CONTROLLER_TEMP, ctemp); 192 | Blynk.virtualWrite(vPIN_BATTERY_CHARGE_CURRENT, battChargeCurrent); 193 | Blynk.virtualWrite(vPIN_BATTERY_CHARGE_POWER, battChargePower); 194 | Blynk.virtualWrite(vPIN_BATTERY_OVERALL_CURRENT, battOverallCurrent); 195 | Blynk.virtualWrite(vPIN_LOAD_ENABLED, loadPoweredOn); 196 | } 197 | 198 | // exec a function of registry read (cycles between different addresses) 199 | void executeCurrentRegistryFunction() { 200 | Registries[currentRegistryNumber](); 201 | } 202 | 203 | uint8_t setOutputLoadPower(uint8_t state) { 204 | Serial.print("Writing coil 0x0006 value to: "); 205 | Serial.println(state); 206 | 207 | delay(10); 208 | // Set coil at address 0x0006 (Force the load on/off) 209 | result = node.writeSingleCoil(0x0006, state); 210 | 211 | if (result == node.ku8MBSuccess) { 212 | node.getResponseBuffer(0x00); 213 | Serial.println("Success."); 214 | } 215 | 216 | return result; 217 | } 218 | 219 | // callback to on/off button state changes from the Blynk app 220 | BLYNK_WRITE(vPIN_LOAD_ENABLED) { 221 | uint8_t newState = (uint8_t)param.asInt(); 222 | 223 | Serial.print("Setting load state output coil to value: "); 224 | Serial.println(newState); 225 | 226 | result = setOutputLoadPower(newState); 227 | //readOutputLoadState(); 228 | result &= checkLoadCoilState(); 229 | 230 | if (result == node.ku8MBSuccess) { 231 | Serial.println("Write & Read suceeded."); 232 | } else { 233 | Serial.println("Write & Read failed."); 234 | } 235 | 236 | Serial.print("Output Load state value: "); 237 | Serial.println(loadPoweredOn); 238 | Serial.println(); 239 | Serial.println("Uploading results to Blynk."); 240 | 241 | uploadToBlynk(); 242 | } 243 | 244 | uint8_t readOutputLoadState() { 245 | delay(10); 246 | result = node.readHoldingRegisters(0x903D, 1); 247 | 248 | if (result == node.ku8MBSuccess) { 249 | loadPoweredOn = (node.getResponseBuffer(0x00) & 0x02) > 0; 250 | 251 | Serial.print("Set success. Load: "); 252 | Serial.println(loadPoweredOn); 253 | } else { 254 | // update of status failed 255 | Serial.println("readHoldingRegisters(0x903D, 1) failed!"); 256 | } 257 | return result; 258 | } 259 | 260 | // reads Load Enable Override coil 261 | uint8_t checkLoadCoilState() { 262 | Serial.print("Reading coil 0x0006... "); 263 | 264 | delay(10); 265 | result = node.readCoils(0x0006, 1); 266 | 267 | Serial.print("Result: "); 268 | Serial.println(result); 269 | 270 | if (result == node.ku8MBSuccess) { 271 | loadPoweredOn = (node.getResponseBuffer(0x00) > 0); 272 | 273 | Serial.print(" Value: "); 274 | Serial.println(loadPoweredOn); 275 | } else { 276 | Serial.println("Failed to read coil 0x0006!"); 277 | } 278 | 279 | return result; 280 | } 281 | 282 | // ----------------------------------------------------------------- 283 | 284 | void AddressRegistry_3100() { 285 | result = node.readInputRegisters(0x3100, 6); 286 | 287 | if (result == node.ku8MBSuccess) { 288 | 289 | pvvoltage = node.getResponseBuffer(0x00) / 100.0f; 290 | Serial.print("PV Voltage: "); 291 | Serial.println(pvvoltage); 292 | 293 | pvcurrent = node.getResponseBuffer(0x01) / 100.0f; 294 | Serial.print("PV Current: "); 295 | Serial.println(pvcurrent); 296 | 297 | pvpower = (node.getResponseBuffer(0x02) | node.getResponseBuffer(0x03) << 16) / 100.0f; 298 | Serial.print("PV Power: "); 299 | Serial.println(pvpower); 300 | 301 | bvoltage = node.getResponseBuffer(0x04) / 100.0f; 302 | Serial.print("Battery Voltage: "); 303 | Serial.println(bvoltage); 304 | 305 | battChargeCurrent = node.getResponseBuffer(0x05) / 100.0f; 306 | Serial.print("Battery Charge Current: "); 307 | Serial.println(battChargeCurrent); 308 | } 309 | } 310 | 311 | void AddressRegistry_3106() 312 | { 313 | result = node.readInputRegisters(0x3106, 2); 314 | 315 | if (result == node.ku8MBSuccess) { 316 | battChargePower = (node.getResponseBuffer(0x00) | node.getResponseBuffer(0x01) << 16) / 100.0f; 317 | Serial.print("Battery Charge Power: "); 318 | Serial.println(battChargePower); 319 | } 320 | } 321 | 322 | void AddressRegistry_310D() 323 | { 324 | result = node.readInputRegisters(0x310D, 3); 325 | 326 | if (result == node.ku8MBSuccess) { 327 | lcurrent = node.getResponseBuffer(0x00) / 100.0f; 328 | Serial.print("Load Current: "); 329 | Serial.println(lcurrent); 330 | 331 | lpower = (node.getResponseBuffer(0x01) | node.getResponseBuffer(0x02) << 16) / 100.0f; 332 | Serial.print("Load Power: "); 333 | Serial.println(lpower); 334 | } else { 335 | rs485DataReceived = false; 336 | Serial.println("Read register 0x310D failed!"); 337 | } 338 | } 339 | 340 | void AddressRegistry_311A() { 341 | result = node.readInputRegisters(0x311A, 2); 342 | 343 | if (result == node.ku8MBSuccess) { 344 | bremaining = node.getResponseBuffer(0x00) / 1.0f; 345 | Serial.print("Battery Remaining %: "); 346 | Serial.println(bremaining); 347 | 348 | btemp = node.getResponseBuffer(0x01) / 100.0f; 349 | Serial.print("Battery Temperature: "); 350 | Serial.println(btemp); 351 | } else { 352 | rs485DataReceived = false; 353 | Serial.println("Read register 0x311A failed!"); 354 | } 355 | } 356 | 357 | void AddressRegistry_331B() { 358 | result = node.readInputRegisters(0x331B, 2); 359 | 360 | if (result == node.ku8MBSuccess) { 361 | battOverallCurrent = (node.getResponseBuffer(0x00) | node.getResponseBuffer(0x01) << 16) / 100.0f; 362 | Serial.print("Battery Discharge Current: "); 363 | Serial.println(battOverallCurrent); 364 | } else { 365 | rs485DataReceived = false; 366 | Serial.println("Read register 0x331B failed!"); 367 | } 368 | } 369 | 370 | 371 | void loop() 372 | { 373 | Blynk.run(); 374 | ArduinoOTA.handle(); 375 | timer.run(); 376 | } 377 | -------------------------------------------------------------------------------- /doc/1733_modbus_protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/1733_modbus_protocol.pdf -------------------------------------------------------------------------------- /doc/blynk-app-qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/blynk-app-qr-code.png -------------------------------------------------------------------------------- /doc/max485_module.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/max485_module.jpg -------------------------------------------------------------------------------- /doc/mppt-triton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/mppt-triton.png -------------------------------------------------------------------------------- /doc/mppt-xtra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/mppt-xtra.png -------------------------------------------------------------------------------- /doc/nodemcu_pins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/nodemcu_pins.png -------------------------------------------------------------------------------- /doc/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/schematic.png -------------------------------------------------------------------------------- /doc/screenshot-blynk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/screenshot-blynk.png -------------------------------------------------------------------------------- /doc/tracer-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/tracer-a.png -------------------------------------------------------------------------------- /doc/tracer-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tekk/Tracer-RS485-Modbus-Blynk-V2/ce738f1b9073d08cdb5c6ee268f62f48ef46893c/doc/tracer-b.png -------------------------------------------------------------------------------- /esp_credentials.h: -------------------------------------------------------------------------------- 1 | 2 | // Insert your credentials here, or move this folder into your Arduino ;ibraries folder 3 | 4 | #define WIFI_SSID "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 5 | #define WIFI_PASS "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 6 | 7 | // Blynk API key 8 | #define AUTH "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 9 | 10 | // OTA Update Password 11 | #define OTA_PASS "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 12 | -------------------------------------------------------------------------------- /settings.h: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | * 3 | * Settings - Tracer 4 | * 5 | **************************************************************/ 6 | /* 7 | Auth Codes & Wifi info go in the following file. 8 | Create a new folder in your library dir called 'esp_credentials' 9 | and create a new file called 'esp_credentials.h' or move the 10 | example dir in the repo. You can use this for all your wifi projects. 11 | 12 | Example esp_credentials.h is in ./esp-credentials/esp-credentials.h 13 | */ 14 | 15 | // include WIFI credentials and Blynk auth token credentials 16 | #include "esp_credentials.h" 17 | 18 | /* 19 | Local Server Settings 20 | Comment out to use Cloud Server 21 | */ 22 | //#define USE_LOCAL_SERVER 23 | //#define SERVER IPAddress(192, 168, 1, 78) 24 | 25 | /* 26 | Over The Air Hostname 27 | */ 28 | #define OTA_HOSTNAME "SOLAR-MODBUS" 29 | 30 | /* 31 | Virtual Pins - Base. (For Blynk) 32 | */ 33 | #define vPIN_PV_POWER V1 34 | #define vPIN_PV_CURRENT V2 35 | #define vPIN_PV_VOLTAGE V3 36 | #define vPIN_LOAD_CURRENT V4 37 | #define vPIN_LOAD_POWER V5 38 | #define vPIN_BATT_TEMP V6 39 | #define vPIN_BATT_VOLTAGE V7 40 | #define vPIN_BATT_REMAIN V8 41 | #define vPIN_CONTROLLER_TEMP V9 42 | #define vPIN_BATTERY_CHARGE_CURRENT V10 43 | #define vPIN_BATTERY_CHARGE_POWER V11 44 | #define vPIN_BATTERY_OVERALL_CURRENT V12 45 | #define vPIN_LOAD_ENABLED V14 46 | -------------------------------------------------------------------------------- /test/test-mppt-tracer-modbus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # You need to run: 4 | # sudo pip install pymodbus 5 | 6 | from pymodbus.client.sync import ModbusSerialClient as ModbusClient 7 | 8 | modbus_timeout = .1 # NOTE! This is default 3 seconds. It waits for the timeout before returning I think. Making this .1 works well. The pyepsolar project uses a 1 second timeout. 9 | 10 | client = ModbusClient(method = 'rtu', port = 'COM1', baudrate = 115200, timeout=1) 11 | client.connect() 12 | 13 | result = client.read_input_registers(0x3100,6,unit=1) 14 | solarVoltage = float(result.registers[0] / 100.0) 15 | solarCurrent = float(result.registers[1] / 100.0) 16 | batteryVoltage = float(result.registers[4] / 100.0) 17 | chargeCurrent = float(result.registers[5] / 100.0) 18 | 19 | print solarVoltage 20 | print solarCurrent 21 | print batteryVoltage 22 | print chargeCurrent 23 | 24 | client.close() 25 | --------------------------------------------------------------------------------