Feature: DPL: Honor battery-provided discharge power limit (#1198)

When the BMS provides a discharge current limit, apply
this limit in the DPL to the inverter power target when running
from battery.
This commit is contained in:
ranma 2024-09-13 20:36:16 +02:00 committed by GitHub
parent c96762c765
commit 6318ab4a8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 47 additions and 12 deletions

View File

@ -46,6 +46,7 @@ class BatteryStats {
virtual bool getImmediateChargingRequest() const { return false; };
virtual float getChargeCurrentLimitation() const { return FLT_MAX; };
virtual float getDischargeCurrentLimitation() const { return FLT_MAX; };
protected:
virtual void mqttPublish() const;
@ -98,6 +99,7 @@ class PylontechBatteryStats : public BatteryStats {
void mqttPublish() const final;
bool getImmediateChargingRequest() const { return _chargeImmediately; } ;
float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ;
float getDischargeCurrentLimitation() const { return _dischargeCurrentLimitation; } ;
private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }
@ -136,6 +138,7 @@ class PytesBatteryStats : public BatteryStats {
void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final;
float getChargeCurrentLimitation() const { return _chargeCurrentLimit; } ;
float getDischargeCurrentLimitation() const { return _dischargeCurrentLimit; } ;
private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }

View File

@ -90,10 +90,11 @@ private:
int32_t inverterPowerDcToAc(std::shared_ptr<InverterAbstract> inverter, int32_t dcPower);
void unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter);
bool canUseDirectSolarPower();
bool calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t solarPower, bool batteryPower);
bool calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t solarPower, int32_t batteryPowerLimit, bool batteryPower);
bool updateInverter();
bool setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
int32_t getSolarPower();
int32_t getBatteryDischargeLimit();
float getLoadCorrectedVoltage();
bool testThreshold(float socThreshold, float voltThreshold,
std::function<bool(float, float)> compare);

View File

@ -288,7 +288,7 @@ void PowerLimiterClass::loop()
};
// Calculate and set Power Limit (NOTE: might reset _inverter to nullptr!)
bool limitUpdated = calcPowerLimit(_inverter, getSolarPower(), _batteryDischargeEnabled);
bool limitUpdated = calcPowerLimit(_inverter, getSolarPower(), getBatteryDischargeLimit(), _batteryDischargeEnabled);
_lastCalculation = millis();
@ -423,17 +423,17 @@ uint8_t PowerLimiterClass::getPowerLimiterState() {
}
// Logic table ("PowerMeter value" can be "base load setting" as a fallback)
// | Case # | batteryPower | solarPower | useFullSolarPassthrough | Resulting inverter limit |
// | 1 | false | < 20 W | doesn't matter | 0 (inverter off) |
// | 2 | false | >= 20 W | doesn't matter | min(PowerMeter value, solarPower) |
// | 3 | true | doesn't matter | false | PowerMeter value (Battery can supply unlimited energy) |
// | 4 | true | fully passed | true | max(PowerMeter value, solarPower) |
// | Case # | batteryPower | solarPower | batteryLimit | useFullSolarPassthrough | Resulting inverter limit |
// | 1 | false | < 20 W | doesn't matter | doesn't matter | 0 (inverter off) |
// | 2 | false | >= 20 W | doesn't matter | doesn't matter | min(PowerMeter value, solarPower) |
// | 3 | true | fully passed | applied | false | min(PowerMeter value batteryLimit+solarPower) |
// | 4 | true | fully passed | doesn't matter | true | max(PowerMeter value, solarPower) |
bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t solarPowerDC, bool batteryPower)
bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t solarPowerDC, int32_t batteryPowerLimitDC, bool batteryPower)
{
if (_verboseLogging) {
MessageOutput.printf("[DPL::calcPowerLimit] battery use %s, solar power (DC): %d W\r\n",
(batteryPower?"allowed":"prevented"), solarPowerDC);
MessageOutput.printf("[DPL::calcPowerLimit] battery use %s, solar power (DC): %d W, battery limit (DC): %d W\r\n",
(batteryPower?"allowed":"prevented"), solarPowerDC, batteryPowerLimitDC);
}
// Case 1:
@ -458,6 +458,7 @@ bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverte
// old and unreliable. TODO(schlimmchen): is this comment outdated?
auto inverterOutput = static_cast<int32_t>(inverter->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC));
auto batteryPowerLimitAC = inverterPowerDcToAc(inverter, batteryPowerLimitDC);
auto solarPowerAC = inverterPowerDcToAc(inverter, solarPowerDC);
auto const& config = Configuration.get();
@ -473,11 +474,12 @@ bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverte
(meterIncludesInv?"":"NOT "));
MessageOutput.printf("[DPL::calcPowerLimit] power meter value: %d W, "
"power meter valid: %s, inverter output: %d W, solar power (AC): %d W\r\n",
"power meter valid: %s, inverter output: %d W, solar power (AC): %d W, battery limit (AC): %d W\r\n",
meterValue,
(meterValid?"yes":"no"),
inverterOutput,
solarPowerAC);
solarPowerAC,
batteryPowerLimitAC);
}
auto newPowerLimit = baseLoad;
@ -507,6 +509,15 @@ bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverte
}
return setNewPowerLimit(inverter, newPowerLimit);
} else { // on batteryPower
// Apply battery-provided discharge power limit.
if (newPowerLimit > batteryPowerLimitAC + solarPowerAC) {
newPowerLimit = batteryPowerLimitAC + solarPowerAC;
if (_verboseLogging) {
MessageOutput.printf("[DPL::calcPowerLimit] limited by battery to: %d W\r\n",
newPowerLimit);
}
}
}
// Case 4:
@ -888,6 +899,26 @@ int32_t PowerLimiterClass::getSolarPower()
return solarPower;
}
int32_t PowerLimiterClass::getBatteryDischargeLimit()
{
auto currentLimit = Battery.getStats()->getDischargeCurrentLimitation();
if (currentLimit == FLT_MAX) {
// the returned value is arbitrary, as long as it's
// greater than the inverters max DC power consumption.
return 10 * 1000;
}
// This uses inverter voltage since there is a voltage drop between
// battery and inverter, so since we are regulating the inverter
// power we should use its voltage.
auto const& config = Configuration.get();
auto channel = static_cast<ChannelNum_t>(config.PowerLimiter.InverterChannelId);
float inverterVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, channel, FLD_UDC);
return static_cast<int32_t>(inverterVoltage * currentLimit);
}
float PowerLimiterClass::getLoadCorrectedVoltage()
{
if (!_inverter) {