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 bool getImmediateChargingRequest() const { return false; };
virtual float getChargeCurrentLimitation() const { return FLT_MAX; }; virtual float getChargeCurrentLimitation() const { return FLT_MAX; };
virtual float getDischargeCurrentLimitation() const { return FLT_MAX; };
protected: protected:
virtual void mqttPublish() const; virtual void mqttPublish() const;
@ -98,6 +99,7 @@ class PylontechBatteryStats : public BatteryStats {
void mqttPublish() const final; void mqttPublish() const final;
bool getImmediateChargingRequest() const { return _chargeImmediately; } ; bool getImmediateChargingRequest() const { return _chargeImmediately; } ;
float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ; float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ;
float getDischargeCurrentLimitation() const { return _dischargeCurrentLimitation; } ;
private: private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }
@ -136,6 +138,7 @@ class PytesBatteryStats : public BatteryStats {
void getLiveViewData(JsonVariant& root) const final; void getLiveViewData(JsonVariant& root) const final;
void mqttPublish() const final; void mqttPublish() const final;
float getChargeCurrentLimitation() const { return _chargeCurrentLimit; } ; float getChargeCurrentLimitation() const { return _chargeCurrentLimit; } ;
float getDischargeCurrentLimitation() const { return _dischargeCurrentLimit; } ;
private: private:
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } 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); 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();
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 updateInverter();
bool setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit); bool setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
int32_t getSolarPower(); int32_t getSolarPower();
int32_t getBatteryDischargeLimit();
float getLoadCorrectedVoltage(); float getLoadCorrectedVoltage();
bool testThreshold(float socThreshold, float voltThreshold, bool testThreshold(float socThreshold, float voltThreshold,
std::function<bool(float, float)> compare); 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!) // 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(); _lastCalculation = millis();
@ -423,17 +423,17 @@ uint8_t PowerLimiterClass::getPowerLimiterState() {
} }
// Logic table ("PowerMeter value" can be "base load setting" as a fallback) // Logic table ("PowerMeter value" can be "base load setting" as a fallback)
// | Case # | batteryPower | solarPower | useFullSolarPassthrough | Resulting inverter limit | // | Case # | batteryPower | solarPower | batteryLimit | useFullSolarPassthrough | Resulting inverter limit |
// | 1 | false | < 20 W | doesn't matter | 0 (inverter off) | // | 1 | false | < 20 W | doesn't matter | doesn't matter | 0 (inverter off) |
// | 2 | false | >= 20 W | doesn't matter | min(PowerMeter value, solarPower) | // | 2 | false | >= 20 W | doesn't matter | doesn't matter | min(PowerMeter value, solarPower) |
// | 3 | true | doesn't matter | false | PowerMeter value (Battery can supply unlimited energy) | // | 3 | true | fully passed | applied | false | min(PowerMeter value batteryLimit+solarPower) |
// | 4 | true | fully passed | true | max(PowerMeter value, 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) { if (_verboseLogging) {
MessageOutput.printf("[DPL::calcPowerLimit] battery use %s, solar power (DC): %d W\r\n", MessageOutput.printf("[DPL::calcPowerLimit] battery use %s, solar power (DC): %d W, battery limit (DC): %d W\r\n",
(batteryPower?"allowed":"prevented"), solarPowerDC); (batteryPower?"allowed":"prevented"), solarPowerDC, batteryPowerLimitDC);
} }
// Case 1: // Case 1:
@ -458,6 +458,7 @@ bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverte
// old and unreliable. TODO(schlimmchen): is this comment outdated? // old and unreliable. TODO(schlimmchen): is this comment outdated?
auto inverterOutput = static_cast<int32_t>(inverter->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC)); 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 solarPowerAC = inverterPowerDcToAc(inverter, solarPowerDC);
auto const& config = Configuration.get(); auto const& config = Configuration.get();
@ -473,11 +474,12 @@ bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverte
(meterIncludesInv?"":"NOT ")); (meterIncludesInv?"":"NOT "));
MessageOutput.printf("[DPL::calcPowerLimit] power meter value: %d W, " 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, meterValue,
(meterValid?"yes":"no"), (meterValid?"yes":"no"),
inverterOutput, inverterOutput,
solarPowerAC); solarPowerAC,
batteryPowerLimitAC);
} }
auto newPowerLimit = baseLoad; auto newPowerLimit = baseLoad;
@ -507,6 +509,15 @@ bool PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverte
} }
return setNewPowerLimit(inverter, newPowerLimit); 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: // Case 4:
@ -888,6 +899,26 @@ int32_t PowerLimiterClass::getSolarPower()
return solarPower; 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() float PowerLimiterClass::getLoadCorrectedVoltage()
{ {
if (!_inverter) { if (!_inverter) {