/arduinoEpeverSolarController/arduinoEpeverSolarController.ino |
@@ -84,57 +84,59 @@ |
// 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 // |
/////////////////////////////////////////////////////////////////////////// |
// Whether to send data over the serial port. |
#define SERIAL_DATA 1 |
// 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; |
#define STA_SSID "" |
#define STA_PASSWORD "" |
#define MQTT_SERVER "" |
#define MQTT_PORT 1883 |
#define MQTT_CLIENT_ID "EpEver Solar Monitor" |
#define MQTT_TOPIC_PUB "epever-40a/talk" |
#define MQTT_TOPIC_SUB "epever-40a/hear" |
// Seconds to wait for MQTT message to be delivered and processed. |
#define MQTT_SUBSCRIBE_WAIT 10 |
|
// The OTA hostname. |
const char* OTA_HOSTNAME = ""; |
//#define OTA_HOSTNAME |
// The OTA port on which updates take place. |
const unsigned int OTA_PORT = 8266; |
//#define 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 = ""; |
//#define OTA_PASSWORD_HASH |
|
// Whether to use deep sleep or not (requires hardware modifications). |
const bool USE_DEEP_SLEEP = true; |
//#define USE_DEEP_SLEEP 1 |
// the minimal amount that the ESP should sleep for. |
const int MIN_SLEEP_SECONDS = 60; |
#define MIN_SLEEP_SECONDS 60 |
|
/////////////////////////////////////////////////////////////////////////// |
// general variable declarations // |
// general variable declarations and libraries // |
/////////////////////////////////////////////////////////////////////////// |
#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 |
|
ModbusMaster node; |
WiFiClient wifi_client; |
PubSubClient mqtt_client(wifi_client); |
@@ -375,11 +377,8 @@ |
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())) { |
if (mqtt_client.connect(MQTT_CLIENT_ID)) { |
Serial.println("success"); |
|
Serial.print("Subscribing MQTT: "); |
@@ -484,8 +483,7 @@ |
} |
} |
|
////////////////////////// |
///////////////////////////////////////////////// |
/////////////////////////////////////////////////////////////////////////// |
// Arduino functions // |
/////////////////////////////////////////////////////////////////////////// |
void setup() { |
@@ -508,9 +506,9 @@ |
node.begin(1, Serial); |
|
// Connect D0 to RST to wake up |
if (USE_DEEP_SLEEP) { |
pinMode(D0, WAKEUP_PULLUP); |
} |
#ifdef USE_DEEP_SLEEP |
pinMode(D0, WAKEUP_PULLUP); |
#endif |
|
// variable handling |
EEPROM.begin(16); |
@@ -531,22 +529,15 @@ |
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); |
} |
// Turn on LED |
digitalWrite(LED_BUILTIN, HIGH); |
|
// clear old data |
// |
// 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) { |
@@ -559,8 +550,7 @@ |
Serial.println(result, HEX); |
} |
|
// read LIVE-Data |
// |
// Read LIVE-Data |
node.clearResponseBuffer(); |
result = node.readInputRegisters(LIVE_DATA, LIVE_DATA_CNT); |
|
@@ -574,13 +564,12 @@ |
} |
|
// 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); |
|
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); |
@@ -587,13 +576,10 @@ |
} |
|
// 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); |
@@ -600,14 +586,11 @@ |
} |
|
// 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); |
@@ -614,13 +597,10 @@ |
} |
|
// 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); |
@@ -627,11 +607,9 @@ |
} |
|
// 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); |
@@ -641,7 +619,6 @@ |
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); |
@@ -659,8 +636,6 @@ |
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); |
@@ -667,7 +642,7 @@ |
} |
|
// Print out to serial |
// |
#ifdef SERIAL_DATA |
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); |
@@ -693,10 +668,9 @@ |
Serial.printf("\n charger.charging: %s ", charger_charging_status[charger_mode]); |
Serial.println(); |
Serial.println(); |
#endif |
|
// Go Online to publish via mqtt |
// |
// get wifi going |
// Start WiFi connection. |
digitalWrite(LED_BUILTIN, LOW); |
WiFi.mode(WIFI_STA); |
WiFi.begin(STA_SSID, STA_PASSWORD); |
@@ -712,17 +686,23 @@ |
Serial.println(WiFi.localIP()); |
|
// Port defaults to 8266 |
#ifdef OTA_PORT |
ArduinoOTA.setPort(OTA_PORT); |
#endif |
|
// Hostname defaults to esp8266-[ChipID] |
#ifdef OTA_HOSTNAME |
if (strlen(OTA_HOSTNAME) != 0) { |
ArduinoOTA.setHostname(OTA_HOSTNAME); |
} |
#endif |
|
// No authentication by default |
#ifdef OTA_PASSWORD_HASH |
if (strlen(OTA_PASSWORD_HASH) != 0) { |
ArduinoOTA.setPasswordHash(OTA_PASSWORD_HASH); |
} |
#endif |
|
ArduinoOTA.onStart([]() { |
String type; |
@@ -757,8 +737,7 @@ |
}); |
ArduinoOTA.begin(); |
|
// establish/keep mqtt connection |
// |
// Establish/keep mqtt connection |
mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); |
mqtt_client.setCallback(mqtt_callback); |
mqtt_reconnect(); |
@@ -775,7 +754,7 @@ |
controllerStatusPayload.clear(); |
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer); |
|
// wait for MQTT subscription processing |
// Wait for MQTT subscription processing |
Serial.println("Waiting for MQTT and OTA events."); |
unsigned int now = millis(); |
while (millis() - now < MQTT_SUBSCRIBE_WAIT * 1000) { |
@@ -789,12 +768,10 @@ |
} |
Serial.println("Done waiting for MQTT and OTA events."); |
|
// publish via mqtt |
// |
// Publish to MQTT |
Serial.print("Publishing to MQTT: "); |
|
// panel |
// |
// 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); |
@@ -805,8 +782,7 @@ |
epeverMetricsPayload.clear(); |
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "panel").c_str(), tmpJsonPayloadBuffer); |
|
// battery |
// |
// 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); |
@@ -821,8 +797,7 @@ |
epeverMetricsPayload.clear(); |
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "battery").c_str(), tmpJsonPayloadBuffer); |
|
// load |
// |
// 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); |
@@ -840,8 +815,7 @@ |
epeverMetricsPayload.clear(); |
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "load").c_str(), tmpJsonPayloadBuffer); |
|
// energy |
// |
// 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); |
@@ -851,8 +825,7 @@ |
epeverMetricsPayload.clear(); |
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "energy").c_str(), tmpJsonPayloadBuffer); |
|
// extra |
// |
// 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]; |
@@ -865,8 +838,7 @@ |
epeverMetricsPayload.clear(); |
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "extra").c_str(), tmpJsonPayloadBuffer); |
|
// settings |
// |
// Settings |
epeverMetricsPayload["solar"]["monitor"]["settings"]["sleep"].set(sleepSeconds); |
|
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer); |
@@ -880,7 +852,7 @@ |
controllerStatusPayload.clear(); |
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer); |
|
// ensure all messages are sent |
// Ensure all messages are sent |
mqtt_client.unsubscribe(MQTT_TOPIC_SUB); |
mqtt_client.disconnect(); |
while (mqtt_client.state() != -1) { |
@@ -895,14 +867,18 @@ |
digitalWrite(MAX485_RE, 0); |
digitalWrite(MAX485_DE, 0); |
|
// DeepSleep n microseconds |
// |
Serial.print("\nDeepSleep for "); |
// Sleep |
Serial.print("\nSleep for "); |
Serial.print(sleepSeconds); |
Serial.println(" Seconds"); |
digitalWrite(LED_BUILTIN, LOW); |
#ifdef USE_DEEP_SLEEP |
if (USE_DEEP_SLEEP) { |
ESP.deepSleep(sleepSeconds * 1000000); |
} else { |
delay(sleepSeconds * 1000); |
} |
#else |
delay(sleepSeconds * 1000); |
#endif |
} |