arduino-sketches – Rev 14
?pathlinks?
/*************************************************************************/
/* 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 objects intead of 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 JSON payloads and send them to the following //
// MQTT topics: //
// //
// MQTT_TOPIC_PUB / panel | battery | load | energy | extra | monitor //
// //
// where panel, battery, load, energy, extra and monitor are various //
// subtopics of the configurable MQTT_TOPIC_PUB topic. For example to //
// just listen for battery notifications, subscribe to the following //
// MQTT topic: MQTT_TOPIC_PUB/battery //
// //
// 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 //
// } //
// //
// Note that for the switch to work, the Epever device has to be set to //
// "manual mode". From the manual, that seems to be setting the load //
// setting for the first timer to 117 and the second should display 2, n //
// and then the load can be toggled using this sketch. //
///////////////////////////////////////////////////////////////////////////
#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>
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP_EEPROM.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <EEPROM.h>
#endif
///////////////////////////////////////////////////////////////////////////
// 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 = "";
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);
bool loadState = true;
int sleepSeconds;
const int JSON_DOCUMENT_SIZE = 512;
StaticJsonDocument<JSON_DOCUMENT_SIZE> controllerStatusPayload;
StaticJsonDocument<JSON_DOCUMENT_SIZE> epeverMetricsPayload;
StaticJsonDocument<JSON_DOCUMENT_SIZE> epeverControlPayload;
char tmpJsonPayloadBuffer[JSON_DOCUMENT_SIZE];
///////////////////////////////////////////////////////////////////////////
// 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 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) {
uint8_t i, result;
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (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: ");
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")) {
epeverControlPayload.clear();
return;
}
if (epeverControlPayload["action"] == "switch") {
// Switch - but i can't seem to switch a coil directly here ?!?
if (epeverControlPayload["status"] == "on") {
loadState = true;
}
if (epeverControlPayload["status"] == "off") {
loadState = false;
}
Serial.print("Setting load state:");
node.clearResponseBuffer();
node.writeSingleCoil(0x0001, 1);
result = node.writeSingleCoil(0x0002, loadState);
if (result == node.ku8MBSuccess) {
Serial.println("success");
} else {
Serial.println("failure");
Serial.print("Miss write loadState, ret val: ");
Serial.println(result, HEX);
}
}
if (epeverControlPayload["action"] == "settings") {
if (epeverControlPayload.containsKey("sleep")) {
// input sanitization
int seconds = (unsigned int)epeverControlPayload["sleep"];
if (seconds == sleepSeconds) {
Serial.println("no change");
return;
}
if (seconds < MIN_SLEEP_SECONDS) {
sleepSeconds = MIN_SLEEP_SECONDS;
} else {
sleepSeconds = seconds;
}
EEPROM.put(0, sleepSeconds);
if (!EEPROM.commit()) {
Serial.println("Failure setting sleep seconds.");
return;
}
Serial.print("Set sleep seconds to: ");
Serial.println(sleepSeconds);
}
epeverControlPayload.clear();
return;
}
}
//////////////////////////
/////////////////////////////////////////////////
// Arduino functions //
///////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(115200); // DO NOT CHANGE!
while (!Serial)
;
Serial.println();
Serial.println("Hello World! I'm an EpEver Solar Monitor!");
// init modbus in receive mode
pinMode(MAX485_RE, OUTPUT);
pinMode(MAX485_DE, OUTPUT);
digitalWrite(MAX485_RE, 0);
digitalWrite(MAX485_DE, 0);
// modbus callbacks
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
// EPEver Device ID 1
node.begin(1, Serial);
// Connect D0 to RST to wake up
if (USE_DEEP_SLEEP) {
pinMode(D0, WAKEUP_PULLUP);
}
// variable handling
EEPROM.begin(16);
EEPROM.get(0, sleepSeconds);
if (sleepSeconds < MIN_SLEEP_SECONDS) {
sleepSeconds = 60;
EEPROM.put(0, sleepSeconds);
if (!EEPROM.commit()) {
Serial.println("Unable to set default sleep.");
}
}
// 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.
controllerStatusPayload["solar"]["monitor"]["status"] = "online";
serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer);
controllerStatusPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer);
controllerStatusPayload["solar"]["monitor"]["status"] = "waiting";
serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer);
controllerStatusPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer);
// wait for MQTT subscription processing
Serial.println("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.");
// publish via mqtt
//
Serial.print("Publishing to MQTT: ");
// panel
//
epeverMetricsPayload["solar"]["panel"]["V"] = String(live.l.pV / 100.f, 2);
epeverMetricsPayload["solar"]["panel"]["I"] = String(live.l.pI / 100.f, 2);
epeverMetricsPayload["solar"]["panel"]["P"] = String(live.l.pP / 100.f, 2);
epeverMetricsPayload["solar"]["panel"]["minV"] = String(stats.s.pVmin / 100.f, 3);
epeverMetricsPayload["solar"]["panel"]["maxV"] = String(stats.s.pVmax / 100.f, 3);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "panel").c_str(), tmpJsonPayloadBuffer);
// battery
//
epeverMetricsPayload["solar"]["battery"]["V"] = String(live.l.bV / 100.f, 2);
epeverMetricsPayload["solar"]["battery"]["I"] = String(live.l.bI / 100.f, 2);
epeverMetricsPayload["solar"]["battery"]["P"] = String(live.l.bP / 100.f, 2);
epeverMetricsPayload["solar"]["battery"]["minV"] = String(stats.s.bVmin / 100.f, 2);
epeverMetricsPayload["solar"]["battery"]["maxV"] = String(stats.s.bVmax / 100.f, 2);
epeverMetricsPayload["solar"]["battery"]["SOC"] = String(batterySOC / 1.0f, 2);
epeverMetricsPayload["solar"]["battery"]["netI"] = String(batteryCurrent / 100.0f, 2);
epeverMetricsPayload["solar"]["battery"]["status"]["voltage"].set(batt_volt_status[status_batt.volt]);
epeverMetricsPayload["solar"]["battery"]["status"]["temperature"].set(batt_temp_status[status_batt.temp]);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "battery").c_str(), tmpJsonPayloadBuffer);
// load
//
epeverMetricsPayload["solar"]["load"]["V"] = String(live.l.lV / 100.f, 2);
epeverMetricsPayload["solar"]["load"]["I"] = String(live.l.lI / 100.f, 2);
epeverMetricsPayload["solar"]["load"]["P"] = String(live.l.lP / 100.f, 2);
// pimatic state topic does not work with integers or floats ?!?
switch (loadState) {
case 1:
epeverMetricsPayload["solar"]["load"]["state"].set("on");
break;
default:
epeverMetricsPayload["solar"]["load"]["state"].set("off");
break;
}
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "load").c_str(), tmpJsonPayloadBuffer);
// energy
//
epeverMetricsPayload["solar"]["energy"]["consumed_day"] = String(stats.s.consEnerDay / 100.f, 3);
epeverMetricsPayload["solar"]["energy"]["consumed_all"] = String(stats.s.consEnerTotal / 100.f, 3);
epeverMetricsPayload["solar"]["energy"]["generated_day"] = String(stats.s.genEnerDay / 100.f, 3);
epeverMetricsPayload["solar"]["energy"]["generated_all"] = String(stats.s.genEnerTotal / 100.f, 3);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "energy").c_str(), tmpJsonPayloadBuffer);
// extra
//
epeverMetricsPayload["solar"]["extra"]["CO2"]["t"] = String(stats.s.c02Reduction / 100.f, 2);
//epever_serialize_s( "solar/status/charger_input", charger_input_status[ charger_input ]
epeverMetricsPayload["solar"]["extra"]["charger_mode"] = charger_charging_status[charger_mode];
char buf[21];
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"]["extra"]["time"] = buf;
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "extra").c_str(), tmpJsonPayloadBuffer);
// settings
//
epeverMetricsPayload["solar"]["monitor"]["settings"]["sleep"].set(sleepSeconds);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "settings").c_str(), tmpJsonPayloadBuffer);
Serial.println("done");
controllerStatusPayload["solar"]["monitor"]["status"] = "offline";
serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer);
controllerStatusPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer);
// 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);
}
}
Generated by GNU Enscript 1.6.5.90.