diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 916bf311..e4bf4144 100644 --- a/include/BatteryStats.h +++ b/include/BatteryStats.h @@ -33,6 +33,7 @@ class BatteryStats { virtual uint32_t getMqttFullPublishIntervalMs() const; bool isSoCValid() const { return _lastUpdateSoC > 0; } + bool isVoltageValid() const { return _lastUpdateVoltage > 0; } protected: virtual void mqttPublish() const; diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index af6ed28f..32260150 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -88,6 +88,7 @@ private: void announceStatus(Status status); bool shutdown(Status status); bool shutdown() { return shutdown(_lastStatus); } + float getBatteryVoltage(bool log = false); int32_t inverterPowerDcToAc(std::shared_ptr inverter, int32_t dcPower); void unconditionalSolarPassthrough(std::shared_ptr inverter); bool canUseDirectSolarPower(); diff --git a/include/VictronMppt.h b/include/VictronMppt.h index 091ddb00..12d6bdf7 100644 --- a/include/VictronMppt.h +++ b/include/VictronMppt.h @@ -35,6 +35,9 @@ public: // sum of today's yield of all MPPT charge controllers in kWh double getYieldDay() const; + // minimum of all MPPT charge controllers' output voltages in V + double getOutputVoltage() const; + private: void loop(); VictronMpptClass(VictronMpptClass const& other) = delete; diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 68564e37..b4c2229a 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -298,7 +298,7 @@ void PowerLimiterClass::loop() Battery.getStats()->getSoCAgeSeconds(), (config.PowerLimiter.IgnoreSoc?"yes":"no")); - float dcVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t)config.PowerLimiter.InverterChannelId, FLD_UDC); + auto dcVoltage = getBatteryVoltage(true/*log voltages only once per DPL loop*/); MessageOutput.printf("[DPL::loop] dcVoltage: %.2f V, loadCorrectedVoltage: %.2f V, StartTH: %.2f V, StopTH: %.2f V\r\n", dcVoltage, getLoadCorrectedVoltage(), config.PowerLimiter.VoltageStartThreshold, @@ -340,6 +340,46 @@ void PowerLimiterClass::loop() _calculationBackoffMs = _calculationBackoffMsDefault; } +/** + * determines the battery's voltage, trying multiple data providers. the most + * accurate data is expected to be delivered by a BMS, if it's available. more + * accurate and more recent than the inverter's voltage reading is the volage + * at the charge controller's output, if it's available. only as a fallback + * the voltage reported by the inverter is used. + */ +float PowerLimiterClass::getBatteryVoltage(bool log) { + if (!_inverter) { + // there should be no need to call this method if no target inverter is known + MessageOutput.println("DPL getBatteryVoltage: no inverter (programmer error)"); + return 0.0; + } + + auto const& config = Configuration.get(); + auto channel = static_cast(config.PowerLimiter.InverterChannelId); + float inverterVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, channel, FLD_UDC); + float res = inverterVoltage; + + float chargeControllerVoltage = -1; + if (VictronMppt.isDataValid()) { + res = chargeControllerVoltage = static_cast(VictronMppt.getOutputVoltage()); + } + + float bmsVoltage = -1; + auto stats = Battery.getStats(); + if (config.Battery.Enabled + && stats->isVoltageValid() + && stats->getVoltageAgeSeconds() < 60) { + res = bmsVoltage = stats->getVoltage(); + } + + if (log) { + MessageOutput.printf("[DPL::getBatteryVoltage] BMS: %.2f V, MPPT: %.2f V, inverter: %.2f V, returning: %.2fV\r\n", + bmsVoltage, chargeControllerVoltage, inverterVoltage, res); + } + + return res; +} + /** * calculate the AC output power (limit) to set, such that the inverter uses * the given power on its DC side, i.e., adjust the power for the inverter's @@ -593,9 +633,8 @@ float PowerLimiterClass::getLoadCorrectedVoltage() CONFIG_T& config = Configuration.get(); - auto channel = static_cast(config.PowerLimiter.InverterChannelId); float acPower = _inverter->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC); - float dcVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, channel, FLD_UDC); + float dcVoltage = getBatteryVoltage(); if (dcVoltage <= 0.0) { return 0.0; diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index fd1073a7..c4dd0bd5 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -137,3 +137,16 @@ double VictronMpptClass::getYieldDay() const return sum; } + +double VictronMpptClass::getOutputVoltage() const +{ + double min = -1; + + for (const auto& upController : _controllers) { + double volts = upController->getData()->V; + if (min == -1) { min = volts; } + min = std::min(min, volts); + } + + return min; +}