From cd98941c5d3165d33401351d7b347e3733a83de2 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sun, 21 May 2023 22:37:33 +0200 Subject: [PATCH] Implement global data store to handle all invert total values Use the new values in the LED, MQTT and Web interface. --- include/Datastore.h | 61 ++++++++++++++++++ src/Datastore.cpp | 106 ++++++++++++++++++++++++++++++++ src/Led_Single.cpp | 21 +------ src/MqttHandleInverterTotal.cpp | 58 +++-------------- src/WebApi_ws_live.cpp | 18 ++---- src/main.cpp | 6 ++ 6 files changed, 188 insertions(+), 82 deletions(-) create mode 100644 include/Datastore.h create mode 100644 src/Datastore.cpp diff --git a/include/Datastore.h b/include/Datastore.h new file mode 100644 index 0000000..b36312a --- /dev/null +++ b/include/Datastore.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +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; \ No newline at end of file diff --git a/src/Datastore.cpp b/src/Datastore.cpp new file mode 100644 index 0000000..fa5b8c6 --- /dev/null +++ b/src/Datastore.cpp @@ -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 + +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(totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT)); + totalAcYieldDayDigits = max(totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD)); + } + if (inv->getEnablePolling()) { + totalAcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC); + totalAcPowerDigits = max(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(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(); + } +} \ No newline at end of file diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp index 5b6f569..f4f1cf0 100644 --- a/src/Led_Single.cpp +++ b/src/Led_Single.cpp @@ -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; } } diff --git a/src/MqttHandleInverterTotal.cpp b/src/MqttHandleInverterTotal.cpp index a0c3a38..f89436c 100644 --- a/src/MqttHandleInverterTotal.cpp +++ b/src/MqttHandleInverterTotal.cpp @@ -4,6 +4,7 @@ */ #include "MqttHandleInverterTotal.h" #include "Configuration.h" +#include "Datastore.h" #include "MqttSettings.h" #include @@ -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(totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC)); - - totalAcYieldDay += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD); - totalAcYieldDayDigits = max(totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD)); - - totalAcYieldTotal += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT); - totalAcYieldTotalDigits = max(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(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(totalAcPowerDigits))); - MqttSettings.publish("ac/yieldtotal", String(totalAcYieldTotal, static_cast(totalAcYieldTotalDigits))); - MqttSettings.publish("ac/yieldday", String(totalAcYieldDay, static_cast(totalAcYieldDayDigits))); - MqttSettings.publish("ac/is_valid", String(totalReachable)); - MqttSettings.publish("dc/power", String(totalDcPower, static_cast(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); } diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index b9919eb..d562d9f 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -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; diff --git a/src/main.cpp b/src/main.cpp index aec7d0f..f58efb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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();