arduino-sketches

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 9  →  ?path2? @ 10
/arduinoEpeverSolarController/arduinoEpeverSolarController.ino
@@ -0,0 +1,879 @@
/*************************************************************************/
/* Copyright (C) 2019 Don M/glitterkitty - License: GNU GPLv3 */
/* Copyright (C) 2023 Wizardry and Steamworks - License: GNU GPLv3 */
/*************************************************************************/
// v1.0 //
// Reworked from: https://github.com/glitterkitty/EpEverSolarMonitor //
// //
// Current documentation @ //
// https://grimore.org/hardware/epever/generic_controller_monitor //
// //
// Differences & Highlights: //
// * added OTA update capabilities, //
// * settings are made via MQTT instead of hardware (debug), //
// * used a JSON object instead of publishing to multiple MQTT topics, //
// * made deep sleep optional, //
// * ensured re-entrancy when resuming out of suspend, //
// * made time to process MQTT messages configurable, //
// * added some compatiblity with ESP32, //
// * made multiple parameters configurable, //
// * small retouches and aesthetics //
// //
// About: //
// This is an Arduino sketch for retrieving metrics off a solar panel //
// controller made by EpEver and should work for most MPPT models. //
// //
// The original documentation by glitterkitty mentioned that the EPEVER //
// outputs +7.5V on the orange-white RJ45 connector but at least for the //
// EPEVER 40A the voltage between the orange and the brown wire of the //
// RJ45 connector has been measured to be +5.11V which is great because //
// no stepdown is needed and the EPEVER can power both the MAX485 TTL //
// as well as the ESP. In any case, the voltage should be measured //
// again before implementing the circuit diagram just to make sure. //
// //
// Essential wiring diagram: //
// //
// +---------+ //
// | | //
// orange +<--------+--- 5V --+------------------------> + VCC 5V //
// | + DI <-------------------> + TX //
// green +<--------+ A + DE <-------------------> + D2 //
// EPEVER | | ESP //
// RJ45 | MAX485 | 8266 / 32 //
// blue +<--------+ B + RE <-------------------> + D1 //
// | + RO <-------------------> + RX //
// brown +<--------+-- GND --+------------------------> + GND //
// | | //
// +---------+ //
// //
// Optional wiring: //
// * connect the ESP D0 GPIO to RST via <= 1kOhm resistor and then set //
// the variable USE_DEEP_SLEEP in this sketch to true in order to //
// allow the ESP to go into deep sleep mode and consume less power. //
// //
// Software usage: //
// The sketch will generate a JSON payload and send it to the configured //
// MQTT server on a selected topic definied by MQTT_TOPIC_PUB. //
// //
// The the sketch will subscribe to the topic defined by MQTT_TOPIC_SUB //
// and listen to the following JSON grammar: //
// { //
// "action" : "settings" | "switch" //
// "sleep" (when "action" is "settings") : time in seconds //
// "switch" (when "action" is "switch") : "on" | "off" //
// } //
// //
// For example, to set the sleep time between metric updates to about //
// 5 minutes, send the following JSON payload on the MQTT_TOPIC_SUB //
// topic: //
// { //
// "action" : "settings" //
// "sleep" : 120 //
// "value" : //
// } //
// //
///////////////////////////////////////////////////////////////////////////
 
#include <ModbusMaster.h>
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <ESPmDNS.h>
#endif
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
 
///////////////////////////////////////////////////////////////////////////
// configuration //
///////////////////////////////////////////////////////////////////////////
// RS-485 module pin DE maps to D2 GPIO on ESP.
#define MAX485_DE D2
// RS-485 module pin RE maps to D1 GPIO on ESP.
#define MAX485_RE D1
 
const char* STA_SSID = "";
const char* STA_PASSWORD = "";
const char* MQTT_SERVER = "l";
const int MQTT_PORT = 1883;
const char* MQTT_CLIENT_ID = "EpEver Solar Monitor";
const char* MQTT_TOPIC_PUB = "epever-40a/talk";
const char* MQTT_TOPIC_SUB = "epever-40a/hear";
// seconds to wait for MQTT message to be delivered and processed
const int MQTT_SUBSCRIBE_WAIT = 10;
 
// The OTA hostname.
const char* OTA_HOSTNAME = "";
// The OTA port on which updates take place.
const unsigned int OTA_PORT = 8266;
// The authentication password to use for OTA updates.
// This should be set to the unsalted MD5 hash of the plaintext password.
const char* OTA_PASSWORD_HASH = "";
 
// Whether to use deep sleep or not (requires hardware modifications).
const bool USE_DEEP_SLEEP = true;
// the minimal amount that the ESP should sleep for.
const int MIN_SLEEP_SECONDS = 60;
 
///////////////////////////////////////////////////////////////////////////
// general variable declarations //
///////////////////////////////////////////////////////////////////////////
ModbusMaster node;
WiFiClient wifi_client;
PubSubClient mqtt_client(wifi_client);
char buf[256];
int switch_load = 0;
bool loadState = true;
// 1 minutes default
unsigned int sleepSeconds = 60;
char mqttMessageBuffer[64];
const int EPEVER_METRICS_PAYLOAD_SIZE = 256;
const int CONTROLLER_STATUS_PAYLOAD_SIZE = 32;
const int EPEVER_CONTROL_PAYLOAD_SIZE = 256;
 
///////////////////////////////////////////////////////////////////////////
// modbus data definitions //
///////////////////////////////////////////////////////////////////////////
// ModBus Register Locations
// Start of live-data
#define LIVE_DATA 0x3100
// 16 registers
#define LIVE_DATA_CNT 16
// just for reference, not used in code
#define PANEL_VOLTS 0x00
#define PANEL_AMPS 0x01
#define PANEL_POWER_L 0x02
#define PANEL_POWER_H 0x03
#define BATT_VOLTS 0x04
#define BATT_AMPS 0x05
#define BATT_POWER_L 0x06
#define BATT_POWER_H 0x07
// dummy * 4
#define LOAD_VOLTS 0x0C
#define LOAD_AMPS 0x0D
#define LOAD_POWER_L 0x0E
#define LOAD_POWER_H 0x0F
// D7-0 Sec, D15-8 Min : D7-0 Hour, D15-8 Day : D7-0 Month, D15-8 Year
#define RTC_CLOCK 0x9013
// 3 regs
#define RTC_CLOCK_CNT 3
// State of Charge in percent, 1 reg
#define BATTERY_SOC 0x311A
// Battery current L
#define BATTERY_CURRENT_L 0x331B
// Battery current H
#define BATTERY_CURRENT_H 0x331C
// start of statistical data
#define STATISTICS 0x3300
// 22 registers
#define STATISTICS_CNT 22
// just for reference, not used in code
// Maximum input volt (PV) today
#define PV_MAX 0x00
// Minimum input volt (PV) today
#define PV_MIN 0x01
// Maximum battery volt today
#define BATT_MAX 0x02
// Minimum battery volt today
#define BATT_MIN 0x03
// Consumed energy today L
#define CONS_ENERGY_DAY_L 0x04
// Consumed energy today H
#define CONS_ENGERY_DAY_H 0x05
// Consumed energy this month L
#define CONS_ENGERY_MON_L 0x06
// Consumed energy this month H
#define CONS_ENGERY_MON_H 0x07
// Consumed energy this year L
#define CONS_ENGERY_YEAR_L 0x08
// Consumed energy this year H
#define CONS_ENGERY_YEAR_H 0x09
// Total consumed energy L
#define CONS_ENGERY_TOT_L 0x0A
// Total consumed energy H
#define CONS_ENGERY_TOT_H 0x0B
// Generated energy today L
#define GEN_ENERGY_DAY_L 0x0C
// Generated energy today H
#define GEN_ENERGY_DAY_H 0x0D
// Generated energy this month L
#define GEN_ENERGY_MON_L 0x0E
// Generated energy this month H
#define GEN_ENERGY_MON_H 0x0F
// Generated energy this year L
#define GEN_ENERGY_YEAR_L 0x10
// Generated energy this year H
#define GEN_ENERGY_YEAR_H 0x11
// Total generated energy L
#define GEN_ENERGY_TOT_L 0x12
// Total Generated energy H
#define GEN_ENERGY_TOT_H 0x13
// Carbon dioxide reduction L
#define CO2_REDUCTION_L 0x14
// Carbon dioxide reduction H
#define CO2_REDUCTION_H 0x15
// r/w load switch state
#define LOAD_STATE 0x02
#define STATUS_FLAGS 0x3200
// Battery status register
#define STATUS_BATTERY 0x00
// Charging equipment status register
#define STATUS_CHARGER 0x01
 
///////////////////////////////////////////////////////////////////////////
// datastructures, also for buffer to values conversion //
///////////////////////////////////////////////////////////////////////////
// clock
union {
struct {
uint8_t s;
uint8_t m;
uint8_t h;
uint8_t d;
uint8_t M;
uint8_t y;
} r;
uint16_t buf[3];
} rtc;
 
// live data
union {
struct {
 
int16_t pV;
int16_t pI;
int32_t pP;
 
int16_t bV;
int16_t bI;
int32_t bP;
 
 
uint16_t dummy[4];
 
int16_t lV;
int16_t lI;
int32_t lP;
 
} l;
uint16_t buf[16];
} live;
 
// statistics
union {
struct {
 
// 4*1 = 4
uint16_t pVmax;
uint16_t pVmin;
uint16_t bVmax;
uint16_t bVmin;
 
// 4*2 = 8
uint32_t consEnerDay;
uint32_t consEnerMon;
uint32_t consEnerYear;
uint32_t consEnerTotal;
 
// 4*2 = 8
uint32_t genEnerDay;
uint32_t genEnerMon;
uint32_t genEnerYear;
uint32_t genEnerTotal;
 
// 1*2 = 2
uint32_t c02Reduction;
 
} s;
uint16_t buf[22];
} stats;
 
// these are too far away for the union conversion trick
uint16_t batterySOC = 0;
int32_t batteryCurrent = 0;
 
// battery status
struct {
uint8_t volt; // d3-d0 Voltage: 00H Normal, 01H Overvolt, 02H UnderVolt, 03H Low Volt Disconnect, 04H Fault
uint8_t temp; // d7-d4 Temperature: 00H Normal, 01H Over warning settings, 02H Lower than the warning settings
uint8_t resistance; // d8 abnormal 1, normal 0
uint8_t rated_volt; // d15 1-Wrong identification for rated voltage
} status_batt;
 
char batt_volt_status[][20] = {
"Normal",
"Overvolt",
"Low Volt Disconnect",
"Fault"
};
 
char batt_temp_status[][16] = {
"Normal",
"Over WarnTemp",
"Below WarnTemp"
};
 
// charging equipment status (not fully impl. yet)
//uint8_t charger_operation = 0;
//uint8_t charger_state = 0;
//uint8_t charger_input = 0;
uint8_t charger_mode = 0;
 
//char charger_input_status[][20] = {
// "Normal",
// "No power connected",
// "Higher volt input",
// "Input volt error"
//};
 
char charger_charging_status[][12] = {
"Off",
"Float",
"Boost",
"Equlization"
};
 
///////////////////////////////////////////////////////////////////////////
// ModBus helper functions //
///////////////////////////////////////////////////////////////////////////
void preTransmission() {
digitalWrite(MAX485_RE, 1);
digitalWrite(MAX485_DE, 1);
 
digitalWrite(LED_BUILTIN, LOW);
}
 
void postTransmission() {
digitalWrite(MAX485_RE, 0);
digitalWrite(MAX485_DE, 0);
 
digitalWrite(LED_BUILTIN, HIGH);
}
 
///////////////////////////////////////////////////////////////////////////
// MQTT helper functions //
///////////////////////////////////////////////////////////////////////////
char* epever_serialize_s(const char* topic, char* msg) {
Serial.print(topic);
Serial.print(": ");
 
memset(mqttMessageBuffer, 0, 64 * sizeof(char));
snprintf(mqttMessageBuffer, 64, "%s", msg);
Serial.println(msg);
 
return msg;
}
 
char* epever_serialize_f(const char* topic, float value) {
Serial.print(topic);
Serial.print(": ");
 
memset(mqttMessageBuffer, 0, 64 * sizeof(char));
snprintf(mqttMessageBuffer, 64, "%7.3f", value);
Serial.println(mqttMessageBuffer);
 
return mqttMessageBuffer;
}
 
char* epever_serialize_i(const char* topic, int value) {
Serial.print(topic);
Serial.print(": ");
 
memset(mqttMessageBuffer, 0, 64 * sizeof(char));
snprintf(mqttMessageBuffer, 64, "%d", value);
Serial.println(mqttMessageBuffer);
 
return mqttMessageBuffer;
}
 
///////////////////////////////////////////////////////////////////////////
// MQTT event handling //
///////////////////////////////////////////////////////////////////////////
void mqtt_reconnect() {
// Loop until we're reconnected
Serial.println("Checking MQT connection...");
while (!mqtt_client.connected()) {
Serial.print("MQTT Reconnecting: ");
 
// Create a client ID
String clientId = MQTT_CLIENT_ID;
 
// Attempt to connect
if (mqtt_client.connect(clientId.c_str())) {
Serial.println("success");
 
Serial.print("Subscribing MQTT: ");
mqtt_client.subscribe(MQTT_TOPIC_SUB);
Serial.println(MQTT_TOPIC_SUB);
} else {
Serial.print("failure, rc=");
Serial.print(mqtt_client.state());
Serial.println(" try again in 1 second");
 
delay(1000);
}
}
}
 
// control load on / off here, setting sleep duration
//
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
 
payload[length] = '\0';
 
// ignore messages not sent on the subscribed topic
if (strncmp(topic, MQTT_TOPIC_SUB, strlen(MQTT_TOPIC_SUB)) != 0) {
return;
}
 
// Parse the payload sent to the MQTT topic as a JSON document.
Serial.print("Deserializing message: ");
StaticJsonDocument<EPEVER_CONTROL_PAYLOAD_SIZE> epeverControlPayload;
DeserializationError error = deserializeJson(epeverControlPayload, payload);
if (error) {
Serial.print("failed, error=");
Serial.println(error.c_str());
return;
} else {
Serial.println("success");
}
 
if (!epeverControlPayload.containsKey("action")) {
return;
}
 
if (epeverControlPayload["action"] == "switch") {
// Switch - but i can't seem to switch a coil directly here ?!?
if (epeverControlPayload["status"] == "on") {
loadState = true;
switch_load = 1;
}
 
if (epeverControlPayload["status"] == "off") {
loadState = false;
switch_load = 1;
}
 
return;
}
 
if (epeverControlPayload["action"] == "settings") {
if (epeverControlPayload.containsKey("sleep")) {
// input sanitization
Serial.print("Set sleep seconds to: ");
int seconds = (unsigned int)epeverControlPayload["sleep"];
if(seconds < MIN_SLEEP_SECONDS) {
sleepSeconds = MIN_SLEEP_SECONDS;
Serial.println(MIN_SLEEP_SECONDS);
}
sleepSeconds = seconds;
Serial.println(sleepSeconds);
}
 
return;
}
}
 
///////////////////////////////////////////////////////////////////////////
// Arduino functions //
///////////////////////////////////////////////////////////////////////////
void setup() {
// say hello
Serial.begin(115200);
while (!Serial) {
;
}
Serial.println();
Serial.println("Hello World! I'm an EpEver Solar Monitor!");
 
// Connect D0 to RST to wake up
pinMode(D0, WAKEUP_PULLUP);
// init modbus in receive mode
pinMode(MAX485_RE, OUTPUT);
pinMode(MAX485_DE, OUTPUT);
digitalWrite(MAX485_RE, 0);
digitalWrite(MAX485_DE, 0);
// EPEver Device ID 1
node.begin(1, Serial);
// modbus callbacks
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
 
// Initialize the LED_BUILTIN pin as an output, low active
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
}
 
void loop() {
uint8_t i, result;
 
// flash the led
for (i = 0; i < 3; i++) {
digitalWrite(LED_BUILTIN, LOW);
delay(200);
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
}
 
// clear old data
//
memset(rtc.buf, 0, sizeof(rtc.buf));
memset(live.buf, 0, sizeof(live.buf));
memset(stats.buf, 0, sizeof(stats.buf));
 
// Read registers for clock
//
node.clearResponseBuffer();
result = node.readHoldingRegisters(RTC_CLOCK, RTC_CLOCK_CNT);
if (result == node.ku8MBSuccess) {
rtc.buf[0] = node.getResponseBuffer(0);
rtc.buf[1] = node.getResponseBuffer(1);
rtc.buf[2] = node.getResponseBuffer(2);
 
} else {
Serial.print("Miss read rtc-data, ret val:");
Serial.println(result, HEX);
}
 
// read LIVE-Data
//
node.clearResponseBuffer();
result = node.readInputRegisters(LIVE_DATA, LIVE_DATA_CNT);
 
if (result == node.ku8MBSuccess) {
 
for (i = 0; i < LIVE_DATA_CNT; i++) live.buf[i] = node.getResponseBuffer(i);
 
} else {
Serial.print("Miss read liva-data, ret val:");
Serial.println(result, HEX);
}
 
// Statistical Data
//
node.clearResponseBuffer();
result = node.readInputRegisters(STATISTICS, STATISTICS_CNT);
if (result == node.ku8MBSuccess) {
 
for (i = 0; i < STATISTICS_CNT; i++) stats.buf[i] = node.getResponseBuffer(i);
 
} else {
Serial.print("Miss read statistics, ret val:");
Serial.println(result, HEX);
}
 
// Battery SOC
//
node.clearResponseBuffer();
result = node.readInputRegisters(BATTERY_SOC, 1);
if (result == node.ku8MBSuccess) {
 
batterySOC = node.getResponseBuffer(0);
 
} else {
Serial.print("Miss read batterySOC, ret val:");
Serial.println(result, HEX);
}
 
// Battery Net Current = Icharge - Iload
//
node.clearResponseBuffer();
result = node.readInputRegisters(BATTERY_CURRENT_L, 2);
if (result == node.ku8MBSuccess) {
 
batteryCurrent = node.getResponseBuffer(0);
batteryCurrent |= node.getResponseBuffer(1) << 16;
 
} else {
Serial.print("Miss read batteryCurrent, ret val:");
Serial.println(result, HEX);
}
 
// State of the Load Switch
//
node.clearResponseBuffer();
result = node.readCoils(LOAD_STATE, 1);
if (result == node.ku8MBSuccess) {
 
loadState = node.getResponseBuffer(0);
 
} else {
Serial.print("Miss read loadState, ret val:");
Serial.println(result, HEX);
}
 
// Read Status Flags
//
node.clearResponseBuffer();
result = node.readInputRegisters(0x3200, 2);
if (result == node.ku8MBSuccess) {
 
uint16_t temp = node.getResponseBuffer(0);
Serial.print("Batt Flags : ");
Serial.println(temp);
 
status_batt.volt = temp & 0b1111;
status_batt.temp = (temp >> 4) & 0b1111;
status_batt.resistance = (temp >> 8) & 0b1;
status_batt.rated_volt = (temp >> 15) & 0b1;
 
 
temp = node.getResponseBuffer(1);
Serial.print("Chrg Flags : ");
Serial.println(temp, HEX);
 
//for(i=0; i<16; i++) Serial.print( (temp >> (15-i) ) & 1 );
//Serial.println();
 
//charger_input = ( temp & 0b0000000000000000 ) >> 15 ;
charger_mode = (temp & 0b0000000000001100) >> 2;
//charger_input = ( temp & 0b0000000000000000 ) >> 12 ;
//charger_operation = ( temp & 0b0000000000000000 ) >> 0 ;
 
//Serial.print( "charger_input : "); Serial.println( charger_input );
Serial.print("charger_mode : ");
Serial.println(charger_mode);
//Serial.print( "charger_oper : "); Serial.println( charger_operation );
//Serial.print( "charger_state : "); Serial.println( charger_state );
 
 
} else {
Serial.print("Miss read ChargeState, ret val:");
Serial.println(result, HEX);
}
 
// Print out to serial
//
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);
Serial.print("\nLive-Data: Volt Amp Watt ");
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);
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);
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);
Serial.println();
Serial.printf("\n Battery Current: %7.3f A ", batteryCurrent / 100.f);
Serial.printf("\n Battery SOC: %7.0f %% ", batterySOC / 1.0f);
Serial.printf("\n Load Switch: %s ", (loadState == 1 ? " On" : "Off"));
Serial.print("\n\nStatistics: ");
Serial.printf("\n Panel: min: %7.3f max: %7.3f V", stats.s.pVmin / 100.f, stats.s.pVmax / 100.f);
Serial.printf("\n Battery: min: %7.3f max: %7.3f V", stats.s.bVmin / 100.f, stats.s.bVmax / 100.f);
Serial.println();
Serial.printf("\n Consumed: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh",
stats.s.consEnerDay / 100.f, stats.s.consEnerMon / 100.f, stats.s.consEnerYear / 100.f, stats.s.consEnerTotal / 100.f);
Serial.printf("\n Generated: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh",
stats.s.genEnerDay / 100.f, stats.s.genEnerMon / 100.f, stats.s.genEnerYear / 100.f, stats.s.genEnerTotal / 100.f);
Serial.printf("\n CO2-Reduction: %7.3f t ", stats.s.c02Reduction / 100.f);
Serial.println();
Serial.print("\nStatus:");
Serial.printf("\n batt.volt: %s ", batt_volt_status[status_batt.volt]);
Serial.printf("\n batt.temp: %s ", batt_temp_status[status_batt.temp]);
Serial.printf("\n charger.charging: %s ", charger_charging_status[charger_mode]);
Serial.println();
Serial.println();
 
// Go Online to publish via mqtt
//
// get wifi going
digitalWrite(LED_BUILTIN, LOW);
WiFi.mode(WIFI_STA);
WiFi.begin(STA_SSID, STA_PASSWORD);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
 
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
 
// Port defaults to 8266
ArduinoOTA.setPort(OTA_PORT);
 
// Hostname defaults to esp8266-[ChipID]
if (strlen(OTA_HOSTNAME) != 0) {
ArduinoOTA.setHostname(OTA_HOSTNAME);
}
 
// No authentication by default
if (strlen(OTA_PASSWORD_HASH) != 0) {
ArduinoOTA.setPasswordHash(OTA_PASSWORD_HASH);
}
 
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_FS
type = "filesystem";
}
 
// NOTE: if updating FS this would be the place to unmount FS using FS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
 
// establish/keep mqtt connection
//
mqtt_client.setServer(MQTT_SERVER, MQTT_PORT);
mqtt_client.setCallback(mqtt_callback);
mqtt_reconnect();
digitalWrite(LED_BUILTIN, HIGH);
 
// Once connected, publish an announcement.
StaticJsonDocument<CONTROLLER_STATUS_PAYLOAD_SIZE> controllerStatusPayload;
controllerStatusPayload["status"] = "online";
char tmpJsonStatusBuffer[CONTROLLER_STATUS_PAYLOAD_SIZE];
serializeJson(controllerStatusPayload, tmpJsonStatusBuffer, CONTROLLER_STATUS_PAYLOAD_SIZE);
mqtt_client.publish(MQTT_TOPIC_PUB, tmpJsonStatusBuffer, CONTROLLER_STATUS_PAYLOAD_SIZE);
 
// publish via mqtt
//
Serial.println("Publishing: ");
 
// create a new JSON document to hold the metrics
StaticJsonDocument<EPEVER_METRICS_PAYLOAD_SIZE> epeverMetricsPayload;
 
// time
//
sprintf(buf, "20%02d-%02d-%02d %02d:%02d:%02d",
rtc.r.y, rtc.r.M, rtc.r.d, rtc.r.h, rtc.r.m, rtc.r.s);
epeverMetricsPayload["solar"]["status"]["time"] = epever_serialize_s("solar/status/time", buf);
epeverMetricsPayload["solar"]["status"]["updates"] = epever_serialize_i("solar/status/updates", sleepSeconds);
 
// panel
//
epeverMetricsPayload["solar"]["panel"]["V"] = epever_serialize_f("solar/panel/V", live.l.pV / 100.f);
epeverMetricsPayload["solar"]["panel"]["I"] = epever_serialize_f("solar/panel/I", live.l.pI / 100.f);
epeverMetricsPayload["solar"]["panel"]["P"] = epever_serialize_f("solar/panel/P", live.l.pP / 100.f);
 
epeverMetricsPayload["solar"]["battery"]["V"] = epever_serialize_f("solar/battery/V", live.l.bV / 100.f);
epeverMetricsPayload["solar"]["battery"]["I"] = epever_serialize_f("solar/battery/I", live.l.bI / 100.f);
epeverMetricsPayload["solar"]["battery"]["P"] = epever_serialize_f("solar/battery/P", live.l.bP / 100.f);
 
epeverMetricsPayload["solar"]["load"]["V"] = epever_serialize_f("solar/load/V", live.l.lV / 100.f);
epeverMetricsPayload["solar"]["load"]["I"] = epever_serialize_f("solar/load/I", live.l.lI / 100.f);
epeverMetricsPayload["solar"]["load"]["P"] = epever_serialize_f("solar/load/P", live.l.lP / 100.f);
 
epeverMetricsPayload["solar"]["co2reduction"]["t"] = epever_serialize_f("solar/co2reduction/t", stats.s.c02Reduction / 100.f);
epeverMetricsPayload["solar"]["battery"]["SOC"] = epever_serialize_f("solar/battery/SOC", batterySOC / 1.0f);
epeverMetricsPayload["solar"]["battery"]["netI"] = epever_serialize_f("solar/battery/netI", batteryCurrent / 100.0f);
// pimatic state topic does not work with integers or floats ?!?
epeverMetricsPayload["solar"]["load"]["state"] = epever_serialize_s("solar/load/state", (char*)(loadState == 1 ? "on" : "off"));
 
epeverMetricsPayload["solar"]["battery"]["minV"] = epever_serialize_f("solar/battery/minV", stats.s.bVmin / 100.f);
epeverMetricsPayload["solar"]["battery"]["maxV"] = epever_serialize_f("solar/battery/maxV", stats.s.bVmax / 100.f);
 
epeverMetricsPayload["solar"]["panel"]["minV"] = epever_serialize_f("solar/panel/minV", stats.s.pVmin / 100.f);
epeverMetricsPayload["solar"]["panel"]["maxV"] = epever_serialize_f("solar/panel/maxV", stats.s.pVmax / 100.f);
 
epeverMetricsPayload["solar"]["energy"]["consumed_day"] = epever_serialize_f("solar/energy/consumed_day", stats.s.consEnerDay / 100.f);
epeverMetricsPayload["solar"]["energy"]["consumed_all"] = epever_serialize_f("solar/energy/consumed_all", stats.s.consEnerTotal / 100.f);
 
epeverMetricsPayload["solar"]["energy"]["generated_day"] = epever_serialize_f("solar/energy/generated_day", stats.s.genEnerDay / 100.f);
epeverMetricsPayload["solar"]["energy"]["generated_all"] = epever_serialize_f("solar/energy/generated_all", stats.s.genEnerTotal / 100.f);
 
epeverMetricsPayload["solar"]["status"]["batt_volt"] = epever_serialize_s("solar/status/batt_volt", batt_volt_status[status_batt.volt]);
epeverMetricsPayload["solar"]["status"]["batt_temp"] = epever_serialize_s("solar/status/batt_temp", batt_temp_status[status_batt.temp]);
 
//epever_serialize_s( "solar/status/charger_input", charger_input_status[ charger_input ] );
epeverMetricsPayload["solar"]["status"]["charger_mode"] = epever_serialize_s("solar/status/charger_mode", charger_charging_status[charger_mode]);
 
// publish the object
char tmpJsonPayloadBuffer[EPEVER_METRICS_PAYLOAD_SIZE];
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer, EPEVER_METRICS_PAYLOAD_SIZE);
mqtt_client.publish(MQTT_TOPIC_PUB, tmpJsonPayloadBuffer, EPEVER_METRICS_PAYLOAD_SIZE);
 
// Do the Switching of the Load here - doesn't work in callback ?!?
//
if (switch_load == 1) {
switch_load = 0;
Serial.print("Switching Load ");
Serial.println((loadState ? "On" : "Off"));
 
result = node.writeSingleCoil(0x0002, loadState);
if (result != node.ku8MBSuccess) {
Serial.print("Miss write loadState, ret val:");
Serial.println(result, HEX);
}
}
 
controllerStatusPayload["status"] = "waiting";
memset(tmpJsonStatusBuffer, 0, CONTROLLER_STATUS_PAYLOAD_SIZE);
serializeJson(controllerStatusPayload, tmpJsonStatusBuffer, CONTROLLER_STATUS_PAYLOAD_SIZE);
mqtt_client.publish(MQTT_TOPIC_PUB, tmpJsonStatusBuffer, CONTROLLER_STATUS_PAYLOAD_SIZE);
 
// wait for MQTT subscription processing
Serial.print("Waiting for MQTT and OTA events.");
unsigned int now = millis();
while (millis() - now < MQTT_SUBSCRIBE_WAIT * 1000) {
// Loop for MQTT.
if (!mqtt_client.loop() || WiFi.status() != WL_CONNECTED) {
break;
}
// Loop for OTA.
ArduinoOTA.handle();
delay(100);
}
Serial.println("Done waiting for MQTT and OTA events.");
 
controllerStatusPayload["status"] = "offline";
memset(tmpJsonStatusBuffer, 0, CONTROLLER_STATUS_PAYLOAD_SIZE);
serializeJson(controllerStatusPayload, tmpJsonStatusBuffer, CONTROLLER_STATUS_PAYLOAD_SIZE);
mqtt_client.publish(MQTT_TOPIC_PUB, tmpJsonStatusBuffer, CONTROLLER_STATUS_PAYLOAD_SIZE);
 
// ensure all messages are sent
mqtt_client.unsubscribe(MQTT_TOPIC_SUB);
mqtt_client.disconnect();
while (mqtt_client.state() != -1) {
delay(100);
}
 
// disconnect wifi
WiFi.disconnect(true);
 
// power down MAX485_DE
// low active
digitalWrite(MAX485_RE, 0);
digitalWrite(MAX485_DE, 0);
 
// DeepSleep n microseconds
//
Serial.print("\nDeepSleep for ");
Serial.print(sleepSeconds);
Serial.println(" Seconds");
if (USE_DEEP_SLEEP) {
ESP.deepSleep(sleepSeconds * 1000000);
} else {
delay(sleepSeconds * 1000);
}
}