arduino-sketches

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 10  →  ?path2? @ 11
/arduinoEpeverSolarController/arduinoEpeverSolarController.ino
@@ -11,7 +11,7 @@
// 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, //
// * 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, //
@@ -52,9 +52,16 @@
// 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 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: //
// { //
@@ -96,7 +103,7 @@
 
const char* STA_SSID = "";
const char* STA_PASSWORD = "";
const char* MQTT_SERVER = "l";
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";
@@ -123,16 +130,18 @@
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 EPEVER_METRICS_PAYLOAD_SIZE = 512;
const int CONTROLLER_STATUS_PAYLOAD_SIZE = 32;
const int EPEVER_CONTROL_PAYLOAD_SIZE = 256;
 
StaticJsonDocument<EPEVER_METRICS_PAYLOAD_SIZE> epeverMetricsPayload;
char tmpJsonPayloadBuffer[EPEVER_METRICS_PAYLOAD_SIZE];
JsonObject jsonObject;
 
///////////////////////////////////////////////////////////////////////////
// modbus data definitions //
///////////////////////////////////////////////////////////////////////////
@@ -353,42 +362,6 @@
}
 
///////////////////////////////////////////////////////////////////////////
// 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() {
@@ -471,7 +444,7 @@
// input sanitization
Serial.print("Set sleep seconds to: ");
int seconds = (unsigned int)epeverControlPayload["sleep"];
if(seconds < MIN_SLEEP_SECONDS) {
if (seconds < MIN_SLEEP_SECONDS) {
sleepSeconds = MIN_SLEEP_SECONDS;
Serial.println(MIN_SLEEP_SECONDS);
}
@@ -496,7 +469,9 @@
Serial.println("Hello World! I'm an EpEver Solar Monitor!");
 
// Connect D0 to RST to wake up
pinMode(D0, WAKEUP_PULLUP);
if (USE_DEEP_SLEEP) {
pinMode(D0, WAKEUP_PULLUP);
}
// init modbus in receive mode
pinMode(MAX485_RE, OUTPUT);
pinMode(MAX485_DE, OUTPUT);
@@ -758,61 +733,90 @@
 
// publish via mqtt
//
Serial.println("Publishing: ");
Serial.print("Publishing to MQTT: ");
 
// create a new JSON document to hold the metrics
StaticJsonDocument<EPEVER_METRICS_PAYLOAD_SIZE> epeverMetricsPayload;
 
// time
// panel
//
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);
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);
 
// panel
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "panel").c_str(), tmpJsonPayloadBuffer);
 
// battery
//
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"] = 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]);
 
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);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "battery").c_str(), tmpJsonPayloadBuffer);
 
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);
// 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 ?!?
epeverMetricsPayload["solar"]["load"]["state"] = epever_serialize_s("solar/load/state", (char*)(loadState == 1 ? "on" : "off"));
switch (loadState) {
case 1:
epeverMetricsPayload["solar"]["load"]["state"].set("on");
break;
default:
epeverMetricsPayload["solar"]["load"]["state"].set("off");
break;
}
 
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);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "load").c_str(), tmpJsonPayloadBuffer);
 
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);
// 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);
 
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);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "energy").c_str(), tmpJsonPayloadBuffer);
 
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);
// 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;
 
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]);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "extra").c_str(), tmpJsonPayloadBuffer);
 
//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]);
// settings
//
epeverMetricsPayload["solar"]["monitor"]["settings"]["sleep"].set(sleepSeconds);
 
// 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);
serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer);
epeverMetricsPayload.clear();
mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "settings").c_str(), tmpJsonPayloadBuffer);
 
Serial.println("done");
 
// Do the Switching of the Load here - doesn't work in callback ?!?
//
if (switch_load == 1) {
@@ -828,7 +832,6 @@
}
 
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);
 
@@ -847,7 +850,6 @@
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);