From 8c0ecbc1bd8b4ca6db10c5f50d68702750e1b8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Bo=CC=88hm?= Date: Thu, 5 Dec 2024 15:47:37 +0100 Subject: [PATCH] introduce 'SolarChargerStats' --- include/SolarCharger.h | 28 +------ include/SolarChargerProvider.h | 27 +------ include/SolarChargerStats.h | 107 +++++++++++++++++++++++++ include/VictronMppt.h | 26 ++++--- src/MqttHandleVedirect.cpp | 11 ++- src/MqttHandleVedirectHass.cpp | 11 ++- src/PowerLimiter.cpp | 12 +-- src/SolarCharger.cpp | 116 ++-------------------------- src/VictronMppt.cpp | 29 +++++++ src/WebApi_ws_live.cpp | 8 +- src/WebApi_ws_solarcharger_live.cpp | 4 +- 11 files changed, 193 insertions(+), 186 deletions(-) create mode 100644 include/SolarChargerStats.h diff --git a/include/SolarCharger.h b/include/SolarCharger.h index 9f91d3f82..e931eb772 100644 --- a/include/SolarCharger.h +++ b/include/SolarCharger.h @@ -4,38 +4,16 @@ #include #include #include + #include "SolarChargerProvider.h" -#include "VeDirectMpptController.h" +#include "SolarChargerStats.h" class SolarChargerClass { public: void init(Scheduler&); void updateSettings(); - // TODO(andreasboehm): below methods are taken from VictronMppt to start abstracting - // solar chargers without breaking everything. - size_t controllerAmount(); - uint32_t getDataAgeMillis(); - uint32_t getDataAgeMillis(size_t idx); - - // total output of all MPPT charge controllers in Watts - int32_t getOutputPowerWatts(); - - // total panel input power of all MPPT charge controllers in Watts - int32_t getPanelPowerWatts(); - - // sum of total yield of all MPPT charge controllers in kWh - float getYieldTotal(); - - // sum of today's yield of all MPPT charge controllers in kWh - float getYieldDay(); - - // minimum of all MPPT charge controllers' output voltages in V - float getOutputVoltage(); - - std::optional getData(size_t idx = 0); - - bool isDataValid(); + std::shared_ptr getStats() const; SolarChargerProvider* getProvider() const { return _upProvider.get(); } diff --git a/include/SolarChargerProvider.h b/include/SolarChargerProvider.h index d75846321..ac47a4ee4 100644 --- a/include/SolarChargerProvider.h +++ b/include/SolarChargerProvider.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include "VeDirectMpptController.h" +#include "SolarChargerStats.h" class SolarChargerProvider { public: @@ -13,28 +13,5 @@ class SolarChargerProvider { virtual bool init(bool verboseLogging) = 0; virtual void deinit() = 0; virtual void loop() = 0; - - // TODO(andreasboehm): below methods are taken from VictronMppt to start abstracting - // solar chargers without breaking everything. - virtual size_t controllerAmount() const = 0; - virtual uint32_t getDataAgeMillis() const = 0; - virtual uint32_t getDataAgeMillis(size_t idx) const = 0; - // total output of all MPPT charge controllers in Watts - virtual int32_t getOutputPowerWatts() const = 0; - - // total panel input power of all MPPT charge controllers in Watts - virtual int32_t getPanelPowerWatts() const = 0; - - // sum of total yield of all MPPT charge controllers in kWh - virtual float getYieldTotal() const = 0; - - // sum of today's yield of all MPPT charge controllers in kWh - virtual float getYieldDay() const = 0; - - // minimum of all MPPT charge controllers' output voltages in V - virtual float getOutputVoltage() const = 0; - - virtual std::optional getData(size_t idx = 0) const = 0; - - virtual bool isDataValid() const = 0; + virtual std::shared_ptr getStats() = 0; }; diff --git a/include/SolarChargerStats.h b/include/SolarChargerStats.h new file mode 100644 index 000000000..5236b914e --- /dev/null +++ b/include/SolarChargerStats.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +class SolarChargerStats { + + public: + class SolarChargerControllerStats { + public: + virtual int32_t getDataAgeMillis() const = 0; + virtual int32_t getOutputPowerWatts() const = 0; + virtual int32_t getPanelPowerWatts() const = 0; + virtual float getYieldTotal() const = 0; + virtual float getYieldDay() const = 0; + virtual float getOutputVoltage() const = 0; + }; + + virtual uint32_t getDataAgeMillis() const = 0; + + // minimum of all MPPT charge controllers' output voltages in V + virtual float getOutputVoltage() const = 0; + virtual bool isOuputVoltageValid() const = 0; + + // total output of all MPPT charge controllers in Watts + virtual int32_t getOutputPowerWatts() const = 0; + virtual bool isOutputPowerWattsValid() const = 0; + + // total panel input power of all MPPT charge controllers in Watts + virtual int32_t getPanelPowerWatts() const = 0; + virtual bool isPanelPowerWattsValid() const = 0; + + // sum of total yield of all MPPT charge controllers in kWh + virtual float getYieldTotal() const = 0; + + // sum of today's yield of all MPPT charge controllers in kWh + virtual float getYieldDay() const = 0; + + virtual size_t controllerAmount() const = 0; + + virtual std::vector> getControllerStats(size_t idx = 0) const = 0; +}; + +class SolarChargerStatsDummy : public SolarChargerStats { + public: + uint32_t getDataAgeMillis() const final { return INT32_MAX; } + float getOutputVoltage() const final { return 0; } + bool isOuputVoltageValid() const final { return false; } + int32_t getOutputPowerWatts() const final { return 0; } + bool isOutputPowerWattsValid() const final { return false; } + int32_t getPanelPowerWatts() const final { return 0; } + bool isPanelPowerWattsValid() const final { return false; } + float getYieldTotal() const final { return 0; } + float getYieldDay() const final { return 0; } + size_t controllerAmount() const final { return 0; }; +}; + +class VictronMpptStats : public SolarChargerStats { + friend class VictronMppt; + + class VictronMpptControllerStats : public SolarChargerControllerStats { + friend class VictronMppt; + + public: + int32_t getDataAgeMillis() const final { return _dataAgeMillis; }; + int32_t getOutputPowerWatts() const final { return _outputPowerWatts; }; + int32_t getPanelPowerWatts() const final { return _panelPowerWatts; }; + float getYieldTotal() const final { return _yieldTotal; }; + float getYieldDay() const final { return _yieldDay; }; + float getOutputVoltage() const final { return _outputVoltage; }; + + protected: + uint32_t _dataAgeMillis; + float _outputVoltage; + int32_t _outputPowerWatts; + int32_t _panelPowerWatts; + float _yieldTotal; + float _yieldDay; + }; + + public: + uint32_t getDataAgeMillis() const final { return _dataAgeMillis; }; + float getOutputVoltage() const final { return _outputVoltage; }; + bool isOuputVoltageValid() const final { return _isOuputVoltageValid; }; + int32_t getOutputPowerWatts() const final { return _outputPowerWatts; }; + bool isOutputPowerWattsValid() const final { return _isOutputPowerWattsValid; }; + int32_t getPanelPowerWatts() const final { return _panelPowerWatts; }; + bool isPanelPowerWattsValid() const final { return _isPanelPowerWattsValid; }; + float getYieldTotal() const final { return _yieldTotal; }; + float getYieldDay() const final { return _yieldDay; }; + + size_t controllerAmount() const final { return _controllerStats.size(); }; + + protected: + uint32_t _dataAgeMillis; + float _outputVoltage; + bool _isOuputVoltageValid; + int32_t _outputPowerWatts; + bool _isOutputPowerWattsValid; + int32_t _panelPowerWatts; + bool _isPanelPowerWattsValid; + float _yieldTotal; + float _yieldDay; + + std::vector> _controllerStats; +}; diff --git a/include/VictronMppt.h b/include/VictronMppt.h index 11f4840c1..b4b2db66a 100644 --- a/include/VictronMppt.h +++ b/include/VictronMppt.h @@ -6,6 +6,7 @@ #include #include "SolarChargerProvider.h" +#include "SolarChargerStats.h" #include "VeDirectMpptController.h" #include "Configuration.h" @@ -18,30 +19,32 @@ class VictronMppt : public SolarChargerProvider { void deinit() final; void loop() final; - bool isDataValid() const final; + std::shared_ptr getStats() final; + + bool isDataValid() const; // returns the data age of all controllers, // i.e, the youngest data's age is returned. - uint32_t getDataAgeMillis() const final; - uint32_t getDataAgeMillis(size_t idx) const final; + uint32_t getDataAgeMillis() const; + uint32_t getDataAgeMillis(size_t idx) const; - size_t controllerAmount() const final { return _controllers.size(); } - std::optional getData(size_t idx = 0) const final; + size_t controllerAmount() const { return _controllers.size(); } + std::optional getData(size_t idx = 0) const; // total output of all MPPT charge controllers in Watts - int32_t getOutputPowerWatts() const final; + int32_t getOutputPowerWatts() const; // total panel input power of all MPPT charge controllers in Watts - int32_t getPanelPowerWatts() const final; + int32_t getPanelPowerWatts() const; // sum of total yield of all MPPT charge controllers in kWh - float getYieldTotal() const final; + float getYieldTotal() const; // sum of today's yield of all MPPT charge controllers in kWh - float getYieldDay() const final; + float getYieldDay() const; // minimum of all MPPT charge controllers' output voltages in V - float getOutputVoltage() const final; + float getOutputVoltage() const; // returns the state of operation from the first available controller std::optional getStateOfOperation() const; @@ -67,4 +70,7 @@ class VictronMppt : public SolarChargerProvider { std::vector _serialPortOwners; bool initController(int8_t rx, int8_t tx, bool logging, uint8_t instance); + + std::shared_ptr _stats = + std::make_shared(); }; diff --git a/src/MqttHandleVedirect.cpp b/src/MqttHandleVedirect.cpp index 6dd52ae7b..6eb5ea146 100644 --- a/src/MqttHandleVedirect.cpp +++ b/src/MqttHandleVedirect.cpp @@ -7,6 +7,7 @@ #include "MessageOutput.h" #include "SolarChargerProvider.h" #include "SolarCharger.h" +#include "VictronMppt.h" MqttHandleVedirectClass MqttHandleVedirect; @@ -40,6 +41,12 @@ void MqttHandleVedirectClass::loop() return; } + auto victronMppt = static_cast(SolarCharger.getProvider()); + + if (!victronMppt) { + return; + } + if ((millis() >= _nextPublishFull) || (millis() >= _nextPublishUpdatesOnly)) { // determine if this cycle should publish full values or updates only if (_nextPublishFull <= _nextPublishUpdatesOnly) { @@ -57,8 +64,8 @@ void MqttHandleVedirectClass::loop() } #endif - for (int idx = 0; idx < SolarCharger.controllerAmount(); ++idx) { - std::optional optMpptData = SolarCharger.getData(idx); + for (int idx = 0; idx < victronMppt->controllerAmount(); ++idx) { + std::optional optMpptData = victronMppt->getData(idx); if (!optMpptData.has_value()) { continue; } auto const& kvFrame = _kvFrames[optMpptData->serialNr_SER]; diff --git a/src/MqttHandleVedirectHass.cpp b/src/MqttHandleVedirectHass.cpp index e8d6944a8..6b14cc9fa 100644 --- a/src/MqttHandleVedirectHass.cpp +++ b/src/MqttHandleVedirectHass.cpp @@ -12,6 +12,7 @@ #include "Utils.h" #include "__compiled_constants.h" #include "SolarCharger.h" +#include "VictronMppt.h" MqttHandleVedirectHassClass MqttHandleVedirectHass; @@ -57,9 +58,15 @@ void MqttHandleVedirectHassClass::publishConfig() return; } + auto victronMppt = static_cast(SolarCharger.getProvider()); + + if (!victronMppt) { + return; + } + // device info - for (int idx = 0; idx < SolarCharger.controllerAmount(); ++idx) { - auto optMpptData = SolarCharger.getData(idx); + for (int idx = 0; idx < victronMppt->controllerAmount(); ++idx) { + auto optMpptData = victronMppt->getData(idx); if (!optMpptData.has_value()) { continue; } publishSensor("MPPT serial number", "mdi:counter", "SER", nullptr, nullptr, nullptr, *optMpptData); diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 81707593c..e42f9c1b2 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -370,8 +370,8 @@ float PowerLimiterClass::getBatteryVoltage(bool log) { if (inverter.first > 0) { res = inverter.first; } float chargeControllerVoltage = -1; - if (SolarCharger.isDataValid()) { - res = chargeControllerVoltage = static_cast(SolarCharger.getOutputVoltage()); + if (SolarCharger.getStats()->isOuputVoltageValid()) { + res = chargeControllerVoltage = static_cast(SolarCharger.getStats()->getOutputVoltage()); } float bmsVoltage = -1; @@ -425,8 +425,8 @@ void PowerLimiterClass::fullSolarPassthrough(PowerLimiterClass::Status reason) uint16_t targetOutput = 0; - if (SolarCharger.isDataValid()) { - targetOutput = static_cast(std::max(0, SolarCharger.getOutputPowerWatts())); + if (SolarCharger.getStats()->isOutputPowerWattsValid()) { + targetOutput = static_cast(std::max(0, SolarCharger.getStats()->getOutputPowerWatts())); targetOutput = dcPowerBusToInverterAc(targetOutput); } @@ -678,11 +678,11 @@ uint16_t PowerLimiterClass::getSolarPassthroughPower() if (!config.PowerLimiter.SolarPassThroughEnabled || isBelowStopThreshold() - || !SolarCharger.isDataValid()) { + || !SolarCharger.getStats()->isOutputPowerWattsValid()) { return 0; } - return SolarCharger.getOutputPowerWatts(); + return SolarCharger.getStats()->getOutputPowerWatts(); } float PowerLimiterClass::getBatteryInvertersOutputAcWatts() diff --git a/src/SolarCharger.cpp b/src/SolarCharger.cpp index 43255d97a..b757a3c37 100644 --- a/src/SolarCharger.cpp +++ b/src/SolarCharger.cpp @@ -42,127 +42,23 @@ void SolarChargerClass::updateSettings() if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; } } -void SolarChargerClass::loop() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - _upProvider->loop(); - } -} - -size_t SolarChargerClass::controllerAmount() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->controllerAmount(); - } - - return 0; -} - -bool SolarChargerClass::isDataValid() +std::shared_ptr SolarChargerClass::getStats() const { std::lock_guard lock(_mutex); if (_upProvider) { - return _upProvider->isDataValid(); + return _upProvider->getStats(); } - return false; + static auto sspDummyStats = std::make_shared(); + return sspDummyStats; } -uint32_t SolarChargerClass::getDataAgeMillis() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->getDataAgeMillis(); - } - - return 0; -} - -uint32_t SolarChargerClass::getDataAgeMillis(size_t idx) -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->getDataAgeMillis(idx); - } - - return 0; -} - - -// total output of all MPPT charge controllers in Watts -int32_t SolarChargerClass::getOutputPowerWatts() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->getOutputPowerWatts(); - } - - return 0; -} - -// total panel input power of all MPPT charge controllers in Watts -int32_t SolarChargerClass::getPanelPowerWatts() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->getPanelPowerWatts(); - } - - return 0; -} - -// sum of total yield of all MPPT charge controllers in kWh -float SolarChargerClass::getYieldTotal() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->getYieldTotal(); - } - - return 0; -} - -// sum of today's yield of all MPPT charge controllers in kWh -float SolarChargerClass::getYieldDay() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->getYieldDay(); - } - - return 0; -} - -// minimum of all MPPT charge controllers' output voltages in V -float SolarChargerClass::getOutputVoltage() -{ - std::lock_guard lock(_mutex); - - if (_upProvider) { - return _upProvider->getOutputVoltage(); - } - - return 0; -} - -std::optional SolarChargerClass::getData(size_t idx) +void SolarChargerClass::loop() { std::lock_guard lock(_mutex); if (_upProvider) { - return _upProvider->getData(idx); + _upProvider->loop(); } - - return std::nullopt; } diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index a6bc4f7a0..0d581d5c9 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -69,6 +69,35 @@ void VictronMppt::loop() } } +std::shared_ptr VictronMppt::getStats() +{ + _stats->_isOuputVoltageValid = _stats->_isOutputPowerWattsValid = _stats->_isPanelPowerWattsValid = isDataValid(); + _stats->_dataAgeMillis = getDataAgeMillis(); + _stats->_outputVoltage = getOutputVoltage(); + _stats->_outputPowerWatts = getOutputPowerWatts(); + _stats->_panelPowerWatts = getPanelPowerWatts(); + _stats->_yieldTotal = getYieldTotal(); + _stats->_yieldDay = getYieldDay(); + + _stats->_controllerStats.clear(); + + for (const auto& upController : _controllers) { + if (!upController->isDataValid()) { continue; } + + auto controllerStats = std::make_unique(); + controllerStats->_dataAgeMillis = millis() - upController->getLastUpdate(); + controllerStats->_outputPowerWatts = upController->getData().batteryOutputPower_W; + controllerStats->_panelPowerWatts = upController->getData().panelPower_PPV_W; + controllerStats->_yieldTotal = upController->getData().yieldTotal_H19_Wh / 1000.0; + controllerStats->_yieldDay = upController->getData().yieldToday_H20_Wh / 1000.0; + controllerStats->_outputVoltage = upController->getData().batteryVoltage_V_mV / 1000.0; + + _stats->_controllerStats.push_back(controllerStats); + } + + return _stats; +} + /* * isDataValid() * return: true = if at least one of the MPPT controllers delivers valid data diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index e731081b4..78df1022b 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -72,16 +72,16 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al auto const& config = Configuration.get(); auto constexpr halfOfAllMillis = std::numeric_limits::max() / 2; - auto solarChargerAge = SolarCharger.getDataAgeMillis(); + auto solarChargerAge = SolarCharger.getStats()->getDataAgeMillis(); if (all || (solarChargerAge > 0 && (millis() - _lastPublishSolarCharger) > solarChargerAge)) { auto solarchargerObj = root["solarcharger"].to(); solarchargerObj["enabled"] = config.SolarCharger.Enabled; if (config.SolarCharger.Enabled) { auto totalVeObj = solarchargerObj["total"].to(); - addTotalField(totalVeObj, "Power", SolarCharger.getPanelPowerWatts(), "W", 1); - addTotalField(totalVeObj, "YieldDay", SolarCharger.getYieldDay() * 1000, "Wh", 0); - addTotalField(totalVeObj, "YieldTotal", SolarCharger.getYieldTotal(), "kWh", 2); + addTotalField(totalVeObj, "Power", SolarCharger.getStats()->getPanelPowerWatts(), "W", 1); + addTotalField(totalVeObj, "YieldDay", SolarCharger.getStats()->getYieldDay() * 1000, "Wh", 0); + addTotalField(totalVeObj, "YieldTotal", SolarCharger.getStats()->getYieldTotal(), "kWh", 2); } if (!all) { _lastPublishSolarCharger = millis(); } diff --git a/src/WebApi_ws_solarcharger_live.cpp b/src/WebApi_ws_solarcharger_live.cpp index ed5595314..ffa67373d 100644 --- a/src/WebApi_ws_solarcharger_live.cpp +++ b/src/WebApi_ws_solarcharger_live.cpp @@ -83,7 +83,7 @@ bool WebApiWsSolarChargerLiveClass::hasUpdate(size_t idx) uint16_t WebApiWsSolarChargerLiveClass::responseSize() const { // estimated with ArduinoJson assistant - return SolarCharger.controllerAmount() * (1024 + 512) + 128/*DPL status and structure*/; + return SolarCharger.getStats()->controllerAmount() * (1024 + 512) + 128/*DPL status and structure*/; } void WebApiWsSolarChargerLiveClass::sendDataTaskCb() @@ -95,7 +95,7 @@ void WebApiWsSolarChargerLiveClass::sendDataTaskCb() bool fullUpdate = (millis() - _lastFullPublish > (10 * 1000)); bool updateAvailable = false; if (!fullUpdate) { - for (size_t idx = 0; idx < SolarCharger.controllerAmount(); ++idx) { + for (size_t idx = 0; idx < SolarCharger.getStats()->controllerAmount(); ++idx) { if (hasUpdate(idx)) { updateAvailable = true; break;