Skip to content

Commit

Permalink
Feature: DPL: use best available voltage value
Browse files Browse the repository at this point in the history
the DPL is interested in the battery's voltage to make decisions about
draining the battery or letting it charge (if the user opts to use
voltage thresholds rather than SoC thresholds). using the DC input
voltage reported by the inverter under control has disadvantages:

* the data might be quite old due to the communication protocol
  implementation. more inverters being polled means even more lag. the
  connection being wireless makes this even worse, due to the need
  to retry the occasional lost packet, etc.
* the data is not very accurate, since the DC input of the inverter is
  actually some cabling and a couple of junctions away from the actual
  battery. this voltage drop can mostly only be estimated and is worse
  with higher load. the load correction factor is there to mitigate
  this, but it has its own problems and is cumbersome to calibrate.

instead, this change aims to use more accurate battery voltage readings,
if possible. the DPL now prefers the voltage as reported by the BMS,
since it is for sure the closest to the battery of all measuring points
and measures its voltage accurately regardless of the load (the voltage
reading will still drop with higher loads, but this will be only due to
the battery's internal resistance, not that of cabling or junctions). if
no BMS voltage reading is available, the DPL will instead use the charge
controller's voltage reading, as it is available with much higher
frequency and is assumed to be more accurate as it offers a resolution
of 10mV. only if none of these two sources can be used, the inverter DC
input voltage is assumed as the battery voltage.

closes #655.
  • Loading branch information
schlimmchen committed Feb 18, 2024
1 parent 6df3582 commit c930018
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 3 deletions.
1 change: 1 addition & 0 deletions include/BatteryStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions include/PowerLimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class PowerLimiterClass {
void announceStatus(Status status);
bool shutdown(Status status);
bool shutdown() { return shutdown(_lastStatus); }
float getBatteryVoltage(bool log = false);
int32_t inverterPowerDcToAc(std::shared_ptr<InverterAbstract> inverter, int32_t dcPower);
void unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter);
bool canUseDirectSolarPower();
Expand Down
3 changes: 3 additions & 0 deletions include/VictronMppt.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class VictronMpptClass {
// 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;
Expand Down
45 changes: 42 additions & 3 deletions src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<ChannelNum_t>(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<float>(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
Expand Down Expand Up @@ -593,9 +633,8 @@ float PowerLimiterClass::getLoadCorrectedVoltage()

CONFIG_T& config = Configuration.get();

auto channel = static_cast<ChannelNum_t>(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;
Expand Down
13 changes: 13 additions & 0 deletions src/VictronMppt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit c930018

Please sign in to comment.