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:
parent
56fe72d900
commit
cd98941c5d
61
include/Datastore.h
Normal file
61
include/Datastore.h
Normal 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
106
src/Datastore.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "Led_Single.h"
|
#include "Led_Single.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "Datastore.h"
|
||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
@ -57,27 +58,11 @@ void LedSingleClass::loop()
|
|||||||
// Update inverter status
|
// Update inverter status
|
||||||
_ledState[1] = LedState_t::Off;
|
_ledState[1] = LedState_t::Off;
|
||||||
if (Hoymiles.getNumInverters()) {
|
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
|
// set LED status
|
||||||
if (allReachable && allProducing) {
|
if (Datastore.isAllEnabledReachable && Datastore.isAllEnabledProducing) {
|
||||||
_ledState[1] = LedState_t::On;
|
_ledState[1] = LedState_t::On;
|
||||||
}
|
}
|
||||||
if (allReachable && !allProducing) {
|
if (Datastore.isAllEnabledReachable && !Datastore.isAllEnabledProducing) {
|
||||||
_ledState[1] = LedState_t::Blink;
|
_ledState[1] = LedState_t::Blink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "MqttHandleInverterTotal.h"
|
#include "MqttHandleInverterTotal.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "Datastore.h"
|
||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
|
|
||||||
@ -21,56 +22,13 @@ void MqttHandleInverterTotalClass::loop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_lastPublish.occured()) {
|
if (_lastPublish.occured()) {
|
||||||
float totalAcPower = 0;
|
MqttSettings.publish("ac/power", String(Datastore.totalAcPowerEnabled, Datastore.totalAcPowerDigits));
|
||||||
float totalDcPower = 0;
|
MqttSettings.publish("ac/yieldtotal", String(Datastore.totalAcYieldTotalEnabled, Datastore.totalAcYieldTotalDigits));
|
||||||
float totalDcPowerIrr = 0;
|
MqttSettings.publish("ac/yieldday", String(Datastore.totalAcYieldDayEnabled, Datastore.totalAcYieldDayDigits));
|
||||||
float totalDcPowerIrrInst = 0;
|
MqttSettings.publish("ac/is_valid", String(Datastore.isAllEnabledReachable));
|
||||||
float totalAcYieldDay = 0;
|
MqttSettings.publish("dc/power", String(Datastore.totalDcPowerEnabled, Datastore.totalDcPowerDigits));
|
||||||
float totalAcYieldTotal = 0;
|
MqttSettings.publish("dc/irradiation", String(Datastore.totalDcIrradiation, 3));
|
||||||
uint8_t totalAcPowerDigits = 0;
|
MqttSettings.publish("dc/is_valid", String(Datastore.isAllEnabledReachable));
|
||||||
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));
|
|
||||||
|
|
||||||
_lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000);
|
_lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "WebApi_ws_live.h"
|
#include "WebApi_ws_live.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "Datastore.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
@ -90,10 +91,6 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
{
|
{
|
||||||
JsonArray invArray = root.createNestedArray("inverters");
|
JsonArray invArray = root.createNestedArray("inverters");
|
||||||
|
|
||||||
float totalPower = 0;
|
|
||||||
float totalYieldDay = 0;
|
|
||||||
float totalYieldTotal = 0;
|
|
||||||
|
|
||||||
// Loop all inverters
|
// Loop all inverters
|
||||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||||
auto inv = Hoymiles.getInverterByPos(i);
|
auto inv = Hoymiles.getInverterByPos(i);
|
||||||
@ -158,19 +155,12 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
if (inv->Statistics()->getLastUpdate() > _newestInverterTimestamp) {
|
if (inv->Statistics()->getLastUpdate() > _newestInverterTimestamp) {
|
||||||
_newestInverterTimestamp = inv->Statistics()->getLastUpdate();
|
_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");
|
JsonObject totalObj = root.createNestedObject("total");
|
||||||
// todo: Fixed hard coded name, unit and digits
|
addTotalField(totalObj, "Power", Datastore.totalAcPowerEnabled, "W", Datastore.totalAcPowerDigits);
|
||||||
addTotalField(totalObj, "Power", totalPower, "W", 1);
|
addTotalField(totalObj, "YieldDay", Datastore.totalAcYieldDayEnabled, "Wh", Datastore.totalAcYieldDayDigits);
|
||||||
addTotalField(totalObj, "YieldDay", totalYieldDay, "Wh", 0);
|
addTotalField(totalObj, "YieldTotal", Datastore.totalAcYieldTotalEnabled, "kWh", Datastore.totalAcYieldTotalDigits);
|
||||||
addTotalField(totalObj, "YieldTotal", totalYieldTotal, "kWh", 2);
|
|
||||||
|
|
||||||
JsonObject hintObj = root.createNestedObject("hints");
|
JsonObject hintObj = root.createNestedObject("hints");
|
||||||
struct tm timeinfo;
|
struct tm timeinfo;
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* Copyright (C) 2022 Thomas Basler and others
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "Datastore.h"
|
||||||
#include "Display_Graphic.h"
|
#include "Display_Graphic.h"
|
||||||
#include "InverterSettings.h"
|
#include "InverterSettings.h"
|
||||||
#include "Led_Single.h"
|
#include "Led_Single.h"
|
||||||
@ -141,6 +142,8 @@ void setup()
|
|||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
InverterSettings.init();
|
InverterSettings.init();
|
||||||
|
|
||||||
|
Datastore.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
@ -149,11 +152,14 @@ void loop()
|
|||||||
yield();
|
yield();
|
||||||
InverterSettings.loop();
|
InverterSettings.loop();
|
||||||
yield();
|
yield();
|
||||||
|
Datastore.loop();
|
||||||
|
yield();
|
||||||
MqttHandleDtu.loop();
|
MqttHandleDtu.loop();
|
||||||
yield();
|
yield();
|
||||||
MqttHandleInverter.loop();
|
MqttHandleInverter.loop();
|
||||||
yield();
|
yield();
|
||||||
MqttHandleInverterTotal.loop();
|
MqttHandleInverterTotal.loop();
|
||||||
|
yield();
|
||||||
MqttHandleHass.loop();
|
MqttHandleHass.loop();
|
||||||
yield();
|
yield();
|
||||||
WebApi.loop();
|
WebApi.loop();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user