Feature: DPL: use best available voltage value
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.
This commit is contained in:
parent
6df358242c
commit
c930018764
@ -33,6 +33,7 @@ class BatteryStats {
|
|||||||
virtual uint32_t getMqttFullPublishIntervalMs() const;
|
virtual uint32_t getMqttFullPublishIntervalMs() const;
|
||||||
|
|
||||||
bool isSoCValid() const { return _lastUpdateSoC > 0; }
|
bool isSoCValid() const { return _lastUpdateSoC > 0; }
|
||||||
|
bool isVoltageValid() const { return _lastUpdateVoltage > 0; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void mqttPublish() const;
|
virtual void mqttPublish() const;
|
||||||
|
|||||||
@ -88,6 +88,7 @@ private:
|
|||||||
void announceStatus(Status status);
|
void announceStatus(Status status);
|
||||||
bool shutdown(Status status);
|
bool shutdown(Status status);
|
||||||
bool shutdown() { return shutdown(_lastStatus); }
|
bool shutdown() { return shutdown(_lastStatus); }
|
||||||
|
float getBatteryVoltage(bool log = false);
|
||||||
int32_t inverterPowerDcToAc(std::shared_ptr<InverterAbstract> inverter, int32_t dcPower);
|
int32_t inverterPowerDcToAc(std::shared_ptr<InverterAbstract> inverter, int32_t dcPower);
|
||||||
void unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter);
|
void unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter);
|
||||||
bool canUseDirectSolarPower();
|
bool canUseDirectSolarPower();
|
||||||
|
|||||||
@ -35,6 +35,9 @@ public:
|
|||||||
// sum of today's yield of all MPPT charge controllers in kWh
|
// sum of today's yield of all MPPT charge controllers in kWh
|
||||||
double getYieldDay() const;
|
double getYieldDay() const;
|
||||||
|
|
||||||
|
// minimum of all MPPT charge controllers' output voltages in V
|
||||||
|
double getOutputVoltage() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
VictronMpptClass(VictronMpptClass const& other) = delete;
|
VictronMpptClass(VictronMpptClass const& other) = delete;
|
||||||
|
|||||||
@ -298,7 +298,7 @@ void PowerLimiterClass::loop()
|
|||||||
Battery.getStats()->getSoCAgeSeconds(),
|
Battery.getStats()->getSoCAgeSeconds(),
|
||||||
(config.PowerLimiter.IgnoreSoc?"yes":"no"));
|
(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",
|
MessageOutput.printf("[DPL::loop] dcVoltage: %.2f V, loadCorrectedVoltage: %.2f V, StartTH: %.2f V, StopTH: %.2f V\r\n",
|
||||||
dcVoltage, getLoadCorrectedVoltage(),
|
dcVoltage, getLoadCorrectedVoltage(),
|
||||||
config.PowerLimiter.VoltageStartThreshold,
|
config.PowerLimiter.VoltageStartThreshold,
|
||||||
@ -340,6 +340,46 @@ void PowerLimiterClass::loop()
|
|||||||
_calculationBackoffMs = _calculationBackoffMsDefault;
|
_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
|
* 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
|
* 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();
|
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 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) {
|
if (dcVoltage <= 0.0) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
|||||||
@ -137,3 +137,16 @@ double VictronMpptClass::getYieldDay() const
|
|||||||
|
|
||||||
return sum;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user