Implement global data store to handle all invert total values

Use the new values in the LED, MQTT and Web interface.
This commit is contained in:
Thomas Basler 2023-05-21 22:37:33 +02:00
parent 56fe72d900
commit cd98941c5d
6 changed files with 188 additions and 82 deletions

61
include/Datastore.h Normal file
View File

@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <TimeoutHelper.h>
class DatastoreClass {
public:
DatastoreClass();
void init();
void loop();
// Sum of yield total of all enabled inverters, a inverter which is just disabled at night is also included
float totalAcYieldTotalEnabled = 0;
// Sum of yield day of all enabled inverters, a inverter which is just disabled at night is also included
float totalAcYieldDayEnabled = 0;
// Sum of total AC power of all enabled inverters
float totalAcPowerEnabled = 0;
// Sum of total DC power of all enabled inverters
float totalDcPowerEnabled = 0;
// Sum of total DC power of all enabled inverters with maxStringPower set
float totalDcPowerIrradiation = 0;
// Sum of total installed irradiation of all enabled inverters
float totalDcIrradiationInstalled = 0;
// Percentage (1-100) of total irradiation
float totalDcIrradiation = 0;
// Amount of relevant digits for yield total
unsigned int totalAcYieldTotalDigits = 0;
// Amount of relevant digits for yield total
unsigned int totalAcYieldDayDigits = 0;
// Amount of relevant digits for AC power
unsigned int totalAcPowerDigits = 0;
// Amount of relevant digits for DC power
unsigned int totalDcPowerDigits = 0;
// True, if at least one inverter is reachable
bool isAtLeastOneReachable = false;
// True if at least one inverter is producing
bool isAtLeastOneProducing = false;
// True if all enabled inverters are producing
bool isAllEnabledProducing = false;
// True if all enabled inverters are reachable
bool isAllEnabledReachable = false;
private:
TimeoutHelper _updateTimeout;
};
extern DatastoreClass Datastore;

106
src/Datastore.cpp Normal file
View File

@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
*/
#include "Datastore.h"
#include "Configuration.h"
#include <Hoymiles.h>
DatastoreClass Datastore;
DatastoreClass::DatastoreClass()
{
}
void DatastoreClass::init()
{
_updateTimeout.set(1000);
}
void DatastoreClass::loop()
{
if (Hoymiles.isAllRadioIdle() && _updateTimeout.occured()) {
uint8_t isProducing = 0;
uint8_t isReachable = 0;
totalAcYieldTotalEnabled = 0;
totalAcYieldTotalDigits = 0;
totalAcYieldDayEnabled = 0;
totalAcYieldDayDigits = 0;
totalAcPowerEnabled = 0;
totalAcPowerDigits = 0;
totalDcPowerEnabled = 0;
totalDcPowerDigits = 0;
totalDcPowerIrradiation = 0;
totalDcIrradiationInstalled = 0;
isAllEnabledProducing = true;
isAllEnabledReachable = true;
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i);
if (inv == nullptr) {
continue;
}
auto cfg = Configuration.getInverterConfig(inv->serial());
if (cfg == nullptr) {
continue;
}
if (inv->isProducing()) {
isProducing++;
} else {
if (inv->getEnablePolling()) {
isAllEnabledProducing = false;
}
}
if (inv->isReachable()) {
isReachable++;
} else {
if (inv->getEnablePolling()) {
isAllEnabledReachable = false;
}
}
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) {
if (cfg->Poll_Enable) {
totalAcYieldTotalEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
totalAcYieldDayEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
totalAcYieldTotalDigits = max<unsigned int>(totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT));
totalAcYieldDayDigits = max<unsigned int>(totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD));
}
if (inv->getEnablePolling()) {
totalAcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
totalAcPowerDigits = max<unsigned int>(totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC));
}
}
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_DC)) {
if (inv->getEnablePolling()) {
totalDcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
totalDcPowerDigits = max<unsigned int>(totalDcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_DC, c, FLD_PDC));
if (inv->Statistics()->getStringMaxPower(c) > 0) {
totalDcPowerIrradiation += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
totalDcIrradiationInstalled += inv->Statistics()->getStringMaxPower(c);
}
}
}
}
isAtLeastOneProducing = isProducing > 0;
isAtLeastOneReachable = isReachable > 0;
totalDcIrradiation = totalDcIrradiationInstalled > 0 ? totalDcPowerIrradiation / totalDcIrradiationInstalled * 100.0f : 0;
_updateTimeout.reset();
}
}

View File

@ -4,6 +4,7 @@
*/
#include "Led_Single.h"
#include "Configuration.h"
#include "Datastore.h"
#include "MqttSettings.h"
#include "NetworkSettings.h"
#include "PinMapping.h"
@ -57,27 +58,11 @@ void LedSingleClass::loop()
// Update inverter status
_ledState[1] = LedState_t::Off;
if (Hoymiles.getNumInverters()) {
bool allReachable = true;
bool allProducing = true;
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i);
if (inv == nullptr) {
continue;
}
if (inv->getEnablePolling()) {
if (!inv->isReachable()) {
allReachable = false;
}
if (!inv->isProducing()) {
allProducing = false;
}
}
}
// set LED status
if (allReachable && allProducing) {
if (Datastore.isAllEnabledReachable && Datastore.isAllEnabledProducing) {
_ledState[1] = LedState_t::On;
}
if (allReachable && !allProducing) {
if (Datastore.isAllEnabledReachable && !Datastore.isAllEnabledProducing) {
_ledState[1] = LedState_t::Blink;
}
}

View File

@ -4,6 +4,7 @@
*/
#include "MqttHandleInverterTotal.h"
#include "Configuration.h"
#include "Datastore.h"
#include "MqttSettings.h"
#include <Hoymiles.h>
@ -21,56 +22,13 @@ void MqttHandleInverterTotalClass::loop()
}
if (_lastPublish.occured()) {
float totalAcPower = 0;
float totalDcPower = 0;
float totalDcPowerIrr = 0;
float totalDcPowerIrrInst = 0;
float totalAcYieldDay = 0;
float totalAcYieldTotal = 0;
uint8_t totalAcPowerDigits = 0;
uint8_t totalDcPowerDigits = 0;
uint8_t totalAcYieldDayDigits = 0;
uint8_t totalAcYieldTotalDigits = 0;
bool totalReachable = true;
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i);
if (inv == nullptr || !inv->getEnablePolling()) {
continue;
}
if (!inv->isReachable()) {
totalReachable = false;
}
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) {
totalAcPower += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
totalAcPowerDigits = max<uint8_t>(totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC));
totalAcYieldDay += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
totalAcYieldDayDigits = max<uint8_t>(totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD));
totalAcYieldTotal += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
totalAcYieldTotalDigits = max<uint8_t>(totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT));
}
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_DC)) {
totalDcPower += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
totalDcPowerDigits = max<uint8_t>(totalDcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_DC, c, FLD_PDC));
if (inv->Statistics()->getStringMaxPower(c) > 0) {
totalDcPowerIrr += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
totalDcPowerIrrInst += inv->Statistics()->getStringMaxPower(c);
}
}
}
MqttSettings.publish("ac/power", String(totalAcPower, static_cast<unsigned int>(totalAcPowerDigits)));
MqttSettings.publish("ac/yieldtotal", String(totalAcYieldTotal, static_cast<unsigned int>(totalAcYieldTotalDigits)));
MqttSettings.publish("ac/yieldday", String(totalAcYieldDay, static_cast<unsigned int>(totalAcYieldDayDigits)));
MqttSettings.publish("ac/is_valid", String(totalReachable));
MqttSettings.publish("dc/power", String(totalDcPower, static_cast<unsigned int>(totalAcPowerDigits)));
MqttSettings.publish("dc/irradiation", String(totalDcPowerIrrInst > 0 ? totalDcPowerIrr / totalDcPowerIrrInst * 100.0f : 0, 3));
MqttSettings.publish("dc/is_valid", String(totalReachable));
MqttSettings.publish("ac/power", String(Datastore.totalAcPowerEnabled, Datastore.totalAcPowerDigits));
MqttSettings.publish("ac/yieldtotal", String(Datastore.totalAcYieldTotalEnabled, Datastore.totalAcYieldTotalDigits));
MqttSettings.publish("ac/yieldday", String(Datastore.totalAcYieldDayEnabled, Datastore.totalAcYieldDayDigits));
MqttSettings.publish("ac/is_valid", String(Datastore.isAllEnabledReachable));
MqttSettings.publish("dc/power", String(Datastore.totalDcPowerEnabled, Datastore.totalDcPowerDigits));
MqttSettings.publish("dc/irradiation", String(Datastore.totalDcIrradiation, 3));
MqttSettings.publish("dc/is_valid", String(Datastore.isAllEnabledReachable));
_lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000);
}

View File

@ -4,6 +4,7 @@
*/
#include "WebApi_ws_live.h"
#include "Configuration.h"
#include "Datastore.h"
#include "MessageOutput.h"
#include "WebApi.h"
#include "defaults.h"
@ -90,10 +91,6 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
{
JsonArray invArray = root.createNestedArray("inverters");
float totalPower = 0;
float totalYieldDay = 0;
float totalYieldTotal = 0;
// Loop all inverters
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i);
@ -158,19 +155,12 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
if (inv->Statistics()->getLastUpdate() > _newestInverterTimestamp) {
_newestInverterTimestamp = inv->Statistics()->getLastUpdate();
}
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) {
totalPower += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
totalYieldDay += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
totalYieldTotal += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
}
}
JsonObject totalObj = root.createNestedObject("total");
// todo: Fixed hard coded name, unit and digits
addTotalField(totalObj, "Power", totalPower, "W", 1);
addTotalField(totalObj, "YieldDay", totalYieldDay, "Wh", 0);
addTotalField(totalObj, "YieldTotal", totalYieldTotal, "kWh", 2);
addTotalField(totalObj, "Power", Datastore.totalAcPowerEnabled, "W", Datastore.totalAcPowerDigits);
addTotalField(totalObj, "YieldDay", Datastore.totalAcYieldDayEnabled, "Wh", Datastore.totalAcYieldDayDigits);
addTotalField(totalObj, "YieldTotal", Datastore.totalAcYieldTotalEnabled, "kWh", Datastore.totalAcYieldTotalDigits);
JsonObject hintObj = root.createNestedObject("hints");
struct tm timeinfo;

View File

@ -3,6 +3,7 @@
* Copyright (C) 2022 Thomas Basler and others
*/
#include "Configuration.h"
#include "Datastore.h"
#include "Display_Graphic.h"
#include "InverterSettings.h"
#include "Led_Single.h"
@ -141,6 +142,8 @@ void setup()
MessageOutput.println("done");
InverterSettings.init();
Datastore.init();
}
void loop()
@ -149,11 +152,14 @@ void loop()
yield();
InverterSettings.loop();
yield();
Datastore.loop();
yield();
MqttHandleDtu.loop();
yield();
MqttHandleInverter.loop();
yield();
MqttHandleInverterTotal.loop();
yield();
MqttHandleHass.loop();
yield();
WebApi.loop();