arduino-sketches – Blame information for rev 21

Subversion Repositories:
Rev:
Rev Author Line No. Line
10 office 1 /*************************************************************************/
2 /* Copyright (C) 2019 Don M/glitterkitty - License: GNU GPLv3 */
3 /* Copyright (C) 2023 Wizardry and Steamworks - License: GNU GPLv3 */
4 /*************************************************************************/
5 // v1.0 //
6 // Reworked from: https://github.com/glitterkitty/EpEverSolarMonitor //
7 // //
8 // Current documentation @ //
9 // https://grimore.org/hardware/epever/generic_controller_monitor //
10 // //
11 // Differences & Highlights: //
12 // * added OTA update capabilities, //
13 // * settings are made via MQTT instead of hardware (debug), //
11 office 14 // * used a JSON objects intead of MQTT topics, //
10 office 15 // * made deep sleep optional, //
16 // * ensured re-entrancy when resuming out of suspend, //
17 // * made time to process MQTT messages configurable, //
18 // * added some compatiblity with ESP32, //
19 // * made multiple parameters configurable, //
20 // * small retouches and aesthetics //
21 // //
22 // About: //
23 // This is an Arduino sketch for retrieving metrics off a solar panel //
24 // controller made by EpEver and should work for most MPPT models. //
25 // //
26 // The original documentation by glitterkitty mentioned that the EPEVER //
27 // outputs +7.5V on the orange-white RJ45 connector but at least for the //
28 // EPEVER 40A the voltage between the orange and the brown wire of the //
29 // RJ45 connector has been measured to be +5.11V which is great because //
30 // no stepdown is needed and the EPEVER can power both the MAX485 TTL //
31 // as well as the ESP. In any case, the voltage should be measured //
32 // again before implementing the circuit diagram just to make sure. //
33 // //
34 // Essential wiring diagram: //
35 // //
36 // +---------+ //
37 // | | //
38 // orange +<--------+--- 5V --+------------------------> + VCC 5V //
39 // | + DI <-------------------> + TX //
40 // green +<--------+ A + DE <-------------------> + D2 //
41 // EPEVER | | ESP //
42 // RJ45 | MAX485 | 8266 / 32 //
43 // blue +<--------+ B + RE <-------------------> + D1 //
44 // | + RO <-------------------> + RX //
45 // brown +<--------+-- GND --+------------------------> + GND //
46 // | | //
47 // +---------+ //
48 // //
49 // Optional wiring: //
50 // * connect the ESP D0 GPIO to RST via <= 1kOhm resistor and then set //
51 // the variable USE_DEEP_SLEEP in this sketch to true in order to //
52 // allow the ESP to go into deep sleep mode and consume less power. //
53 // //
54 // Software usage: //
11 office 55 // The sketch will generate JSON payloads and send them to the following //
56 // MQTT topics: //
10 office 57 // //
11 office 58 // MQTT_TOPIC_PUB / panel | battery | load | energy | extra | monitor //
59 // //
60 // where panel, battery, load, energy, extra and monitor are various //
61 // subtopics of the configurable MQTT_TOPIC_PUB topic. For example to //
62 // just listen for battery notifications, subscribe to the following //
63 // MQTT topic: MQTT_TOPIC_PUB/battery //
64 // //
10 office 65 // The the sketch will subscribe to the topic defined by MQTT_TOPIC_SUB //
66 // and listen to the following JSON grammar: //
67 // { //
68 // "action" : "settings" | "switch" //
69 // "sleep" (when "action" is "settings") : time in seconds //
70 // "switch" (when "action" is "switch") : "on" | "off" //
71 // } //
72 // //
73 // For example, to set the sleep time between metric updates to about //
74 // 5 minutes, send the following JSON payload on the MQTT_TOPIC_SUB //
75 // topic: //
76 // { //
77 // "action" : "settings" //
78 // "sleep" : 120 //
79 // } //
80 // //
12 office 81 // Note that for the switch to work, the Epever device has to be set to //
82 // "manual mode". From the manual, that seems to be setting the load //
83 // setting for the first timer to 117 and the second should display 2, n //
84 // and then the load can be toggled using this sketch. //
10 office 85 ///////////////////////////////////////////////////////////////////////////
86  
87 ///////////////////////////////////////////////////////////////////////////
88 // configuration //
89 ///////////////////////////////////////////////////////////////////////////
21 office 90 // Whether to send data over the serial port.
91 #define SERIAL_DATA 1
10 office 92 // RS-485 module pin DE maps to D2 GPIO on ESP.
93 #define MAX485_DE D2
94 // RS-485 module pin RE maps to D1 GPIO on ESP.
95 #define MAX485_RE D1
96  
21 office 97 #define STA_SSID ""
98 #define STA_PASSWORD ""
99 #define MQTT_SERVER ""
100 #define MQTT_PORT 1883
101 #define MQTT_CLIENT_ID "EpEver Solar Monitor"
102 #define MQTT_TOPIC_PUB "epever-40a/talk"
103 #define MQTT_TOPIC_SUB "epever-40a/hear"
104 // Seconds to wait for MQTT message to be delivered and processed.
105 #define MQTT_SUBSCRIBE_WAIT 10
10 office 106  
107 // The OTA hostname.
21 office 108 //#define OTA_HOSTNAME
10 office 109 // The OTA port on which updates take place.
21 office 110 //#define OTA_PORT 8266
10 office 111 // The authentication password to use for OTA updates.
112 // This should be set to the unsalted MD5 hash of the plaintext password.
21 office 113 //#define OTA_PASSWORD_HASH
10 office 114  
115 // Whether to use deep sleep or not (requires hardware modifications).
21 office 116 //#define USE_DEEP_SLEEP 1
10 office 117 // the minimal amount that the ESP should sleep for.
21 office 118 #define MIN_SLEEP_SECONDS 60
10 office 119  
120 ///////////////////////////////////////////////////////////////////////////
21 office 121 // general variable declarations and libraries //
10 office 122 ///////////////////////////////////////////////////////////////////////////
21 office 123 #include <ModbusMaster.h>
124 #if defined(ARDUINO_ARCH_ESP8266)
125 #include <ESP8266WiFi.h>
126 #include <ESP8266mDNS.h>
127 #elif defined(ARDUINO_ARCH_ESP32)
128 #include <WiFi.h>
129 #include <ESPmDNS.h>
130 #endif
131 #include <PubSubClient.h>
132 #include <ArduinoJson.h>
133 #include <ArduinoOTA.h>
134 #if defined(ARDUINO_ARCH_ESP8266)
135 #include <ESP_EEPROM.h>
136 #elif defined(ARDUINO_ARCH_ESP32)
137 #include <EEPROM.h>
138 #endif
139  
10 office 140 ModbusMaster node;
141 WiFiClient wifi_client;
142 PubSubClient mqtt_client(wifi_client);
143 bool loadState = true;
13 office 144 int sleepSeconds;
12 office 145 const int JSON_DOCUMENT_SIZE = 512;
10 office 146  
12 office 147 StaticJsonDocument<JSON_DOCUMENT_SIZE> controllerStatusPayload;
148 StaticJsonDocument<JSON_DOCUMENT_SIZE> epeverMetricsPayload;
149 StaticJsonDocument<JSON_DOCUMENT_SIZE> epeverControlPayload;
150 char tmpJsonPayloadBuffer[JSON_DOCUMENT_SIZE];
11 office 151  
10 office 152 ///////////////////////////////////////////////////////////////////////////
153 // modbus data definitions //
154 ///////////////////////////////////////////////////////////////////////////
155 // ModBus Register Locations
156 // Start of live-data
157 #define LIVE_DATA 0x3100
158 // 16 registers
159 #define LIVE_DATA_CNT 16
160 // just for reference, not used in code
161 #define PANEL_VOLTS 0x00
162 #define PANEL_AMPS 0x01
163 #define PANEL_POWER_L 0x02
164 #define PANEL_POWER_H 0x03
165 #define BATT_VOLTS 0x04
166 #define BATT_AMPS 0x05
167 #define BATT_POWER_L 0x06
168 #define BATT_POWER_H 0x07
169 // dummy * 4
170 #define LOAD_VOLTS 0x0C
171 #define LOAD_AMPS 0x0D
172 #define LOAD_POWER_L 0x0E
173 #define LOAD_POWER_H 0x0F
174 // D7-0 Sec, D15-8 Min : D7-0 Hour, D15-8 Day : D7-0 Month, D15-8 Year
175 #define RTC_CLOCK 0x9013
176 // 3 regs
177 #define RTC_CLOCK_CNT 3
178 // State of Charge in percent, 1 reg
179 #define BATTERY_SOC 0x311A
180 // Battery current L
181 #define BATTERY_CURRENT_L 0x331B
182 // Battery current H
183 #define BATTERY_CURRENT_H 0x331C
184 // start of statistical data
185 #define STATISTICS 0x3300
186 // 22 registers
187 #define STATISTICS_CNT 22
188 // just for reference, not used in code
189 // Maximum input volt (PV) today
190 #define PV_MAX 0x00
191 // Minimum input volt (PV) today
192 #define PV_MIN 0x01
193 // Maximum battery volt today
194 #define BATT_MAX 0x02
195 // Minimum battery volt today
196 #define BATT_MIN 0x03
197 // Consumed energy today L
198 #define CONS_ENERGY_DAY_L 0x04
199 // Consumed energy today H
200 #define CONS_ENGERY_DAY_H 0x05
201 // Consumed energy this month L
202 #define CONS_ENGERY_MON_L 0x06
203 // Consumed energy this month H
204 #define CONS_ENGERY_MON_H 0x07
205 // Consumed energy this year L
206 #define CONS_ENGERY_YEAR_L 0x08
207 // Consumed energy this year H
208 #define CONS_ENGERY_YEAR_H 0x09
209 // Total consumed energy L
210 #define CONS_ENGERY_TOT_L 0x0A
211 // Total consumed energy H
212 #define CONS_ENGERY_TOT_H 0x0B
213 // Generated energy today L
214 #define GEN_ENERGY_DAY_L 0x0C
215 // Generated energy today H
216 #define GEN_ENERGY_DAY_H 0x0D
217 // Generated energy this month L
218 #define GEN_ENERGY_MON_L 0x0E
219 // Generated energy this month H
220 #define GEN_ENERGY_MON_H 0x0F
221 // Generated energy this year L
222 #define GEN_ENERGY_YEAR_L 0x10
223 // Generated energy this year H
224 #define GEN_ENERGY_YEAR_H 0x11
225 // Total generated energy L
226 #define GEN_ENERGY_TOT_L 0x12
227 // Total Generated energy H
228 #define GEN_ENERGY_TOT_H 0x13
229 // Carbon dioxide reduction L
230 #define CO2_REDUCTION_L 0x14
231 // Carbon dioxide reduction H
232 #define CO2_REDUCTION_H 0x15
233 // r/w load switch state
234 #define LOAD_STATE 0x02
235 #define STATUS_FLAGS 0x3200
236 // Battery status register
237 #define STATUS_BATTERY 0x00
238 // Charging equipment status register
239 #define STATUS_CHARGER 0x01
240  
241 ///////////////////////////////////////////////////////////////////////////
242 // datastructures, also for buffer to values conversion //
243 ///////////////////////////////////////////////////////////////////////////
244 // clock
245 union {
246 struct {
247 uint8_t s;
248 uint8_t m;
249 uint8_t h;
250 uint8_t d;
251 uint8_t M;
252 uint8_t y;
253 } r;
254 uint16_t buf[3];
255 } rtc;
256  
257 // live data
258 union {
259 struct {
260  
261 int16_t pV;
262 int16_t pI;
263 int32_t pP;
264  
265 int16_t bV;
266 int16_t bI;
267 int32_t bP;
268  
269  
270 uint16_t dummy[4];
271  
272 int16_t lV;
273 int16_t lI;
274 int32_t lP;
275  
276 } l;
277 uint16_t buf[16];
278 } live;
279  
280 // statistics
281 union {
282 struct {
283  
284 // 4*1 = 4
285 uint16_t pVmax;
286 uint16_t pVmin;
287 uint16_t bVmax;
288 uint16_t bVmin;
289  
290 // 4*2 = 8
291 uint32_t consEnerDay;
292 uint32_t consEnerMon;
293 uint32_t consEnerYear;
294 uint32_t consEnerTotal;
295  
296 // 4*2 = 8
297 uint32_t genEnerDay;
298 uint32_t genEnerMon;
299 uint32_t genEnerYear;
300 uint32_t genEnerTotal;
301  
302 // 1*2 = 2
303 uint32_t c02Reduction;
304  
305 } s;
306 uint16_t buf[22];
307 } stats;
308  
309 // these are too far away for the union conversion trick
310 uint16_t batterySOC = 0;
311 int32_t batteryCurrent = 0;
312  
313 // battery status
314 struct {
315 uint8_t volt; // d3-d0 Voltage: 00H Normal, 01H Overvolt, 02H UnderVolt, 03H Low Volt Disconnect, 04H Fault
316 uint8_t temp; // d7-d4 Temperature: 00H Normal, 01H Over warning settings, 02H Lower than the warning settings
317 uint8_t resistance; // d8 abnormal 1, normal 0
318 uint8_t rated_volt; // d15 1-Wrong identification for rated voltage
319 } status_batt;
320  
321 char batt_volt_status[][20] = {
322 "Normal",
323 "Overvolt",
324 "Low Volt Disconnect",
325 "Fault"
326 };
327  
328 char batt_temp_status[][16] = {
329 "Normal",
330 "Over WarnTemp",
331 "Below WarnTemp"
332 };
333  
334 // charging equipment status (not fully impl. yet)
335 //uint8_t charger_operation = 0;
336 //uint8_t charger_state = 0;
337 //uint8_t charger_input = 0;
338 uint8_t charger_mode = 0;
339  
340 //char charger_input_status[][20] = {
341 // "Normal",
342 // "No power connected",
343 // "Higher volt input",
344 // "Input volt error"
345 //};
346  
347 char charger_charging_status[][12] = {
348 "Off",
349 "Float",
350 "Boost",
351 "Equlization"
352 };
353  
354 ///////////////////////////////////////////////////////////////////////////
355 // ModBus helper functions //
356 ///////////////////////////////////////////////////////////////////////////
357 void preTransmission() {
358 digitalWrite(MAX485_RE, 1);
359 digitalWrite(MAX485_DE, 1);
360  
361 digitalWrite(LED_BUILTIN, LOW);
362 }
363  
364 void postTransmission() {
365 digitalWrite(MAX485_RE, 0);
366 digitalWrite(MAX485_DE, 0);
367  
368 digitalWrite(LED_BUILTIN, HIGH);
369 }
370  
371 ///////////////////////////////////////////////////////////////////////////
372 // MQTT event handling //
373 ///////////////////////////////////////////////////////////////////////////
374 void mqtt_reconnect() {
375 // Loop until we're reconnected
376 Serial.println("Checking MQT connection...");
377 while (!mqtt_client.connected()) {
378 Serial.print("MQTT Reconnecting: ");
379  
380 // Attempt to connect
21 office 381 if (mqtt_client.connect(MQTT_CLIENT_ID)) {
10 office 382 Serial.println("success");
383  
384 Serial.print("Subscribing MQTT: ");
385 mqtt_client.subscribe(MQTT_TOPIC_SUB);
386 Serial.println(MQTT_TOPIC_SUB);
387 } else {
388 Serial.print("failure, rc=");
389 Serial.print(mqtt_client.state());
390 Serial.println(" try again in 1 second");
391  
392 delay(1000);
393 }
394 }
395 }
396  
397 // control load on / off here, setting sleep duration
398 //
399 void mqtt_callback(char* topic, byte* payload, unsigned int length) {
12 office 400 uint8_t i, result;
401  
10 office 402 Serial.print("Message arrived [");
403 Serial.print(topic);
404 Serial.print("] ");
12 office 405 for (i = 0; i < length; i++) {
10 office 406 Serial.print((char)payload[i]);
407 }
408 Serial.println();
409  
410 payload[length] = '\0';
411  
412 // ignore messages not sent on the subscribed topic
413 if (strncmp(topic, MQTT_TOPIC_SUB, strlen(MQTT_TOPIC_SUB)) != 0) {
414 return;
415 }
416  
417 // Parse the payload sent to the MQTT topic as a JSON document.
418 Serial.print("Deserializing message: ");
419 DeserializationError error = deserializeJson(epeverControlPayload, payload);
420 if (error) {
421 Serial.print("failed, error=");
422 Serial.println(error.c_str());
423 return;
424 } else {
425 Serial.println("success");
426 }
427  
428 if (!epeverControlPayload.containsKey("action")) {
12 office 429 epeverControlPayload.clear();
10 office 430 return;
431 }
432  
433 if (epeverControlPayload["action"] == "switch") {
434 // Switch - but i can't seem to switch a coil directly here ?!?
435 if (epeverControlPayload["status"] == "on") {
436 loadState = true;
437 }
438  
439 if (epeverControlPayload["status"] == "off") {
440 loadState = false;
441 }
442  
12 office 443 Serial.print("Setting load state:");
444 node.clearResponseBuffer();
445 node.writeSingleCoil(0x0001, 1);
446 result = node.writeSingleCoil(0x0002, loadState);
13 office 447 if (result == node.ku8MBSuccess) {
12 office 448 Serial.println("success");
13 office 449 } else {
12 office 450 Serial.println("failure");
451 Serial.print("Miss write loadState, ret val: ");
452 Serial.println(result, HEX);
453 }
10 office 454 }
455  
456 if (epeverControlPayload["action"] == "settings") {
457 if (epeverControlPayload.containsKey("sleep")) {
458 // input sanitization
459 int seconds = (unsigned int)epeverControlPayload["sleep"];
13 office 460 if (seconds == sleepSeconds) {
461 Serial.println("no change");
462 return;
463 }
464  
11 office 465 if (seconds < MIN_SLEEP_SECONDS) {
10 office 466 sleepSeconds = MIN_SLEEP_SECONDS;
13 office 467 } else {
468 sleepSeconds = seconds;
10 office 469 }
13 office 470  
471 EEPROM.put(0, sleepSeconds);
472 if (!EEPROM.commit()) {
473 Serial.println("Failure setting sleep seconds.");
474 return;
475 }
476  
477 Serial.print("Set sleep seconds to: ");
10 office 478 Serial.println(sleepSeconds);
479 }
480  
12 office 481 epeverControlPayload.clear();
10 office 482 return;
483 }
484 }
485  
21 office 486 ///////////////////////////////////////////////////////////////////////////
10 office 487 // Arduino functions //
488 ///////////////////////////////////////////////////////////////////////////
489 void setup() {
13 office 490 Serial.begin(115200); // DO NOT CHANGE!
491 while (!Serial)
10 office 492 ;
493 Serial.println();
494 Serial.println("Hello World! I'm an EpEver Solar Monitor!");
495  
496 // init modbus in receive mode
497 pinMode(MAX485_RE, OUTPUT);
498 pinMode(MAX485_DE, OUTPUT);
499 digitalWrite(MAX485_RE, 0);
500 digitalWrite(MAX485_DE, 0);
12 office 501  
10 office 502 // modbus callbacks
503 node.preTransmission(preTransmission);
504 node.postTransmission(postTransmission);
13 office 505 // EPEver Device ID 1
506 node.begin(1, Serial);
10 office 507  
12 office 508 // Connect D0 to RST to wake up
21 office 509 #ifdef USE_DEEP_SLEEP
510 pinMode(D0, WAKEUP_PULLUP);
511 #endif
12 office 512  
13 office 513 // variable handling
514 EEPROM.begin(16);
515 EEPROM.get(0, sleepSeconds);
516 if (sleepSeconds < MIN_SLEEP_SECONDS) {
517 sleepSeconds = 60;
518 EEPROM.put(0, sleepSeconds);
519 if (!EEPROM.commit()) {
520 Serial.println("Unable to set default sleep.");
521 }
522 }
523  
10 office 524 // Initialize the LED_BUILTIN pin as an output, low active
525 pinMode(LED_BUILTIN, OUTPUT);
526 digitalWrite(LED_BUILTIN, HIGH);
527 }
528  
529 void loop() {
530 uint8_t i, result;
531  
21 office 532 // Turn on LED
533 digitalWrite(LED_BUILTIN, HIGH);
10 office 534  
21 office 535 // Clear old data
10 office 536 memset(rtc.buf, 0, sizeof(rtc.buf));
537 memset(live.buf, 0, sizeof(live.buf));
538 memset(stats.buf, 0, sizeof(stats.buf));
539  
540 // Read registers for clock
541 node.clearResponseBuffer();
542 result = node.readHoldingRegisters(RTC_CLOCK, RTC_CLOCK_CNT);
543 if (result == node.ku8MBSuccess) {
544 rtc.buf[0] = node.getResponseBuffer(0);
545 rtc.buf[1] = node.getResponseBuffer(1);
546 rtc.buf[2] = node.getResponseBuffer(2);
547  
548 } else {
549 Serial.print("Miss read rtc-data, ret val:");
550 Serial.println(result, HEX);
551 }
552  
21 office 553 // Read LIVE-Data
10 office 554 node.clearResponseBuffer();
555 result = node.readInputRegisters(LIVE_DATA, LIVE_DATA_CNT);
556  
557 if (result == node.ku8MBSuccess) {
558  
559 for (i = 0; i < LIVE_DATA_CNT; i++) live.buf[i] = node.getResponseBuffer(i);
560  
561 } else {
562 Serial.print("Miss read liva-data, ret val:");
563 Serial.println(result, HEX);
564 }
565  
566 // Statistical Data
567 node.clearResponseBuffer();
568 result = node.readInputRegisters(STATISTICS, STATISTICS_CNT);
569 if (result == node.ku8MBSuccess) {
21 office 570 for (i = 0; i < STATISTICS_CNT; i++) {
571 stats.buf[i] = node.getResponseBuffer(i);
572 }
10 office 573 } else {
574 Serial.print("Miss read statistics, ret val:");
575 Serial.println(result, HEX);
576 }
577  
578 // Battery SOC
579 node.clearResponseBuffer();
580 result = node.readInputRegisters(BATTERY_SOC, 1);
581 if (result == node.ku8MBSuccess) {
582 batterySOC = node.getResponseBuffer(0);
583 } else {
584 Serial.print("Miss read batterySOC, ret val:");
585 Serial.println(result, HEX);
586 }
587  
588 // Battery Net Current = Icharge - Iload
589 node.clearResponseBuffer();
590 result = node.readInputRegisters(BATTERY_CURRENT_L, 2);
591 if (result == node.ku8MBSuccess) {
592 batteryCurrent = node.getResponseBuffer(0);
593 batteryCurrent |= node.getResponseBuffer(1) << 16;
594 } else {
595 Serial.print("Miss read batteryCurrent, ret val:");
596 Serial.println(result, HEX);
597 }
598  
599 // State of the Load Switch
600 node.clearResponseBuffer();
601 result = node.readCoils(LOAD_STATE, 1);
602 if (result == node.ku8MBSuccess) {
603 loadState = node.getResponseBuffer(0);
604 } else {
605 Serial.print("Miss read loadState, ret val:");
606 Serial.println(result, HEX);
607 }
608  
609 // Read Status Flags
610 node.clearResponseBuffer();
611 result = node.readInputRegisters(0x3200, 2);
612 if (result == node.ku8MBSuccess) {
613 uint16_t temp = node.getResponseBuffer(0);
614 Serial.print("Batt Flags : ");
615 Serial.println(temp);
616  
617 status_batt.volt = temp & 0b1111;
618 status_batt.temp = (temp >> 4) & 0b1111;
619 status_batt.resistance = (temp >> 8) & 0b1;
620 status_batt.rated_volt = (temp >> 15) & 0b1;
621  
622 temp = node.getResponseBuffer(1);
623 Serial.print("Chrg Flags : ");
624 Serial.println(temp, HEX);
625  
626 //for(i=0; i<16; i++) Serial.print( (temp >> (15-i) ) & 1 );
627 //Serial.println();
628  
629 //charger_input = ( temp & 0b0000000000000000 ) >> 15 ;
630 charger_mode = (temp & 0b0000000000001100) >> 2;
631 //charger_input = ( temp & 0b0000000000000000 ) >> 12 ;
632 //charger_operation = ( temp & 0b0000000000000000 ) >> 0 ;
633  
634 //Serial.print( "charger_input : "); Serial.println( charger_input );
635 Serial.print("charger_mode : ");
636 Serial.println(charger_mode);
637 //Serial.print( "charger_oper : "); Serial.println( charger_operation );
638 //Serial.print( "charger_state : "); Serial.println( charger_state );
639 } else {
640 Serial.print("Miss read ChargeState, ret val:");
641 Serial.println(result, HEX);
642 }
643  
644 // Print out to serial
21 office 645 #ifdef SERIAL_DATA
10 office 646 Serial.printf("\n\nTime: 20%02d-%02d-%02d %02d:%02d:%02d \n", rtc.r.y, rtc.r.M, rtc.r.d, rtc.r.h, rtc.r.m, rtc.r.s);
647 Serial.print("\nLive-Data: Volt Amp Watt ");
648 Serial.printf("\n Panel: %7.3f %7.3f %7.3f ", live.l.pV / 100.f, live.l.pI / 100.f, live.l.pP / 100.0f);
649 Serial.printf("\n Batt: %7.3f %7.3f %7.3f ", live.l.bV / 100.f, live.l.bI / 100.f, live.l.bP / 100.0f);
650 Serial.printf("\n Load: %7.3f %7.3f %7.3f ", live.l.lV / 100.f, live.l.lI / 100.f, live.l.lP / 100.0f);
651 Serial.println();
652 Serial.printf("\n Battery Current: %7.3f A ", batteryCurrent / 100.f);
653 Serial.printf("\n Battery SOC: %7.0f %% ", batterySOC / 1.0f);
654 Serial.printf("\n Load Switch: %s ", (loadState == 1 ? " On" : "Off"));
655 Serial.print("\n\nStatistics: ");
656 Serial.printf("\n Panel: min: %7.3f max: %7.3f V", stats.s.pVmin / 100.f, stats.s.pVmax / 100.f);
657 Serial.printf("\n Battery: min: %7.3f max: %7.3f V", stats.s.bVmin / 100.f, stats.s.bVmax / 100.f);
658 Serial.println();
659 Serial.printf("\n Consumed: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh",
660 stats.s.consEnerDay / 100.f, stats.s.consEnerMon / 100.f, stats.s.consEnerYear / 100.f, stats.s.consEnerTotal / 100.f);
661 Serial.printf("\n Generated: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh",
662 stats.s.genEnerDay / 100.f, stats.s.genEnerMon / 100.f, stats.s.genEnerYear / 100.f, stats.s.genEnerTotal / 100.f);
663 Serial.printf("\n CO2-Reduction: %7.3f t ", stats.s.c02Reduction / 100.f);
664 Serial.println();
665 Serial.print("\nStatus:");
666 Serial.printf("\n batt.volt: %s ", batt_volt_status[status_batt.volt]);
667 Serial.printf("\n batt.temp: %s ", batt_temp_status[status_batt.temp]);
668 Serial.printf("\n charger.charging: %s ", charger_charging_status[charger_mode]);
669 Serial.println();
670 Serial.println();
21 office 671 #endif
10 office 672  
21 office 673 // Start WiFi connection.
10 office 674 digitalWrite(LED_BUILTIN, LOW);
675 WiFi.mode(WIFI_STA);
676 WiFi.begin(STA_SSID, STA_PASSWORD);
677 if (WiFi.waitForConnectResult() != WL_CONNECTED) {
678 Serial.println("Connection Failed! Rebooting...");
679 delay(5000);
680 ESP.restart();
681 }
682  
683 Serial.println("");
684 Serial.println("WiFi connected");
685 Serial.println("IP address: ");
686 Serial.println(WiFi.localIP());
687  
688 // Port defaults to 8266
21 office 689 #ifdef OTA_PORT
10 office 690 ArduinoOTA.setPort(OTA_PORT);
21 office 691 #endif
10 office 692  
693 // Hostname defaults to esp8266-[ChipID]
21 office 694 #ifdef OTA_HOSTNAME
10 office 695 if (strlen(OTA_HOSTNAME) != 0) {
696 ArduinoOTA.setHostname(OTA_HOSTNAME);
697 }
21 office 698 #endif
10 office 699  
700 // No authentication by default
21 office 701 #ifdef OTA_PASSWORD_HASH
10 office 702 if (strlen(OTA_PASSWORD_HASH) != 0) {
703 ArduinoOTA.setPasswordHash(OTA_PASSWORD_HASH);
704 }
21 office 705 #endif
10 office 706  
707 ArduinoOTA.onStart([]() {
708 String type;
709 if (ArduinoOTA.getCommand() == U_FLASH) {
710 type = "sketch";
711 } else { // U_FS
712 type = "filesystem";
713 }
714  
715 // NOTE: if updating FS this would be the place to unmount FS using FS.end()
716 Serial.println("Start updating " + type);
717 });
718 ArduinoOTA.onEnd([]() {
719 Serial.println("\nEnd");
720 });
721 ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
722 Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
723 });
724 ArduinoOTA.onError([](ota_error_t error) {
725 Serial.printf("Error[%u]: ", error);
726 if (error == OTA_AUTH_ERROR) {
727 Serial.println("Auth Failed");
728 } else if (error == OTA_BEGIN_ERROR) {
729 Serial.println("Begin Failed");
730 } else if (error == OTA_CONNECT_ERROR) {
731 Serial.println("Connect Failed");
732 } else if (error == OTA_RECEIVE_ERROR) {
733 Serial.println("Receive Failed");
734 } else if (error == OTA_END_ERROR) {
735 Serial.println("End Failed");
736 }
737 });
738 ArduinoOTA.begin();
739  
21 office 740 // Establish/keep mqtt connection
10 office 741 mqtt_client.setServer(MQTT_SERVER, MQTT_PORT);
742 mqtt_client.setCallback(mqtt_callback);
743 mqtt_reconnect();
744 digitalWrite(LED_BUILTIN, HIGH);
745  
746 // Once connected, publish an announcement.
12 office 747 controllerStatusPayload["solar"]["monitor"]["status"] = "online";
748 serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer);
749 controllerStatusPayload.clear();
750 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer);
10 office 751  
13 office 752 controllerStatusPayload["solar"]["monitor"]["status"] = "waiting";
753 serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer);
754 controllerStatusPayload.clear();
755 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer);
756  
21 office 757 // Wait for MQTT subscription processing
13 office 758 Serial.println("Waiting for MQTT and OTA events.");
759 unsigned int now = millis();
760 while (millis() - now < MQTT_SUBSCRIBE_WAIT * 1000) {
761 // Loop for MQTT.
762 if (!mqtt_client.loop() || WiFi.status() != WL_CONNECTED) {
763 break;
764 }
765 // Loop for OTA.
766 ArduinoOTA.handle();
767 delay(100);
768 }
769 Serial.println("Done waiting for MQTT and OTA events.");
770  
21 office 771 // Publish to MQTT
11 office 772 Serial.print("Publishing to MQTT: ");
10 office 773  
21 office 774 // Panel
11 office 775 epeverMetricsPayload["solar"]["panel"]["V"] = String(live.l.pV / 100.f, 2);
776 epeverMetricsPayload["solar"]["panel"]["I"] = String(live.l.pI / 100.f, 2);
777 epeverMetricsPayload["solar"]["panel"]["P"] = String(live.l.pP / 100.f, 2);
778 epeverMetricsPayload["solar"]["panel"]["minV"] = String(stats.s.pVmin / 100.f, 3);
779 epeverMetricsPayload["solar"]["panel"]["maxV"] = String(stats.s.pVmax / 100.f, 3);
10 office 780  
11 office 781 serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
782 epeverMetricsPayload.clear();
783 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "panel").c_str(), tmpJsonPayloadBuffer);
784  
21 office 785 // Battery
11 office 786 epeverMetricsPayload["solar"]["battery"]["V"] = String(live.l.bV / 100.f, 2);
787 epeverMetricsPayload["solar"]["battery"]["I"] = String(live.l.bI / 100.f, 2);
788 epeverMetricsPayload["solar"]["battery"]["P"] = String(live.l.bP / 100.f, 2);
789 epeverMetricsPayload["solar"]["battery"]["minV"] = String(stats.s.bVmin / 100.f, 2);
790 epeverMetricsPayload["solar"]["battery"]["maxV"] = String(stats.s.bVmax / 100.f, 2);
791 epeverMetricsPayload["solar"]["battery"]["SOC"] = String(batterySOC / 1.0f, 2);
792 epeverMetricsPayload["solar"]["battery"]["netI"] = String(batteryCurrent / 100.0f, 2);
793 epeverMetricsPayload["solar"]["battery"]["status"]["voltage"].set(batt_volt_status[status_batt.volt]);
794 epeverMetricsPayload["solar"]["battery"]["status"]["temperature"].set(batt_temp_status[status_batt.temp]);
10 office 795  
11 office 796 serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
797 epeverMetricsPayload.clear();
798 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "battery").c_str(), tmpJsonPayloadBuffer);
10 office 799  
21 office 800 // Load
11 office 801 epeverMetricsPayload["solar"]["load"]["V"] = String(live.l.lV / 100.f, 2);
802 epeverMetricsPayload["solar"]["load"]["I"] = String(live.l.lI / 100.f, 2);
803 epeverMetricsPayload["solar"]["load"]["P"] = String(live.l.lP / 100.f, 2);
10 office 804 // pimatic state topic does not work with integers or floats ?!?
11 office 805 switch (loadState) {
806 case 1:
807 epeverMetricsPayload["solar"]["load"]["state"].set("on");
808 break;
809 default:
810 epeverMetricsPayload["solar"]["load"]["state"].set("off");
811 break;
812 }
10 office 813  
11 office 814 serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
815 epeverMetricsPayload.clear();
816 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "load").c_str(), tmpJsonPayloadBuffer);
10 office 817  
21 office 818 // Energy
11 office 819 epeverMetricsPayload["solar"]["energy"]["consumed_day"] = String(stats.s.consEnerDay / 100.f, 3);
820 epeverMetricsPayload["solar"]["energy"]["consumed_all"] = String(stats.s.consEnerTotal / 100.f, 3);
821 epeverMetricsPayload["solar"]["energy"]["generated_day"] = String(stats.s.genEnerDay / 100.f, 3);
822 epeverMetricsPayload["solar"]["energy"]["generated_all"] = String(stats.s.genEnerTotal / 100.f, 3);
10 office 823  
11 office 824 serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
825 epeverMetricsPayload.clear();
826 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "energy").c_str(), tmpJsonPayloadBuffer);
10 office 827  
21 office 828 // Extra
11 office 829 epeverMetricsPayload["solar"]["extra"]["CO2"]["t"] = String(stats.s.c02Reduction / 100.f, 2);
830 //epever_serialize_s( "solar/status/charger_input", charger_input_status[ charger_input ]
831 epeverMetricsPayload["solar"]["extra"]["charger_mode"] = charger_charging_status[charger_mode];
832 char buf[21];
833 sprintf(buf, "20%02d-%02d-%02d %02d:%02d:%02d",
834 rtc.r.y, rtc.r.M, rtc.r.d, rtc.r.h, rtc.r.m, rtc.r.s);
835 epeverMetricsPayload["solar"]["extra"]["time"] = buf;
10 office 836  
11 office 837 serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
838 epeverMetricsPayload.clear();
839 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "extra").c_str(), tmpJsonPayloadBuffer);
10 office 840  
21 office 841 // Settings
11 office 842 epeverMetricsPayload["solar"]["monitor"]["settings"]["sleep"].set(sleepSeconds);
10 office 843  
11 office 844 serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
845 epeverMetricsPayload.clear();
846 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "settings").c_str(), tmpJsonPayloadBuffer);
10 office 847  
11 office 848 Serial.println("done");
849  
12 office 850 controllerStatusPayload["solar"]["monitor"]["status"] = "offline";
851 serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer);
852 controllerStatusPayload.clear();
853 mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer);
10 office 854  
21 office 855 // Ensure all messages are sent
10 office 856 mqtt_client.unsubscribe(MQTT_TOPIC_SUB);
857 mqtt_client.disconnect();
858 while (mqtt_client.state() != -1) {
859 delay(100);
860 }
861  
862 // disconnect wifi
863 WiFi.disconnect(true);
864  
865 // power down MAX485_DE
866 // low active
867 digitalWrite(MAX485_RE, 0);
868 digitalWrite(MAX485_DE, 0);
869  
21 office 870 // Sleep
871 Serial.print("\nSleep for ");
10 office 872 Serial.print(sleepSeconds);
873 Serial.println(" Seconds");
21 office 874 digitalWrite(LED_BUILTIN, LOW);
875 #ifdef USE_DEEP_SLEEP
10 office 876 if (USE_DEEP_SLEEP) {
877 ESP.deepSleep(sleepSeconds * 1000000);
878 } else {
879 delay(sleepSeconds * 1000);
880 }
21 office 881 #else
882 delay(sleepSeconds * 1000);
883 #endif
10 office 884 }