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:
parent
c96762c765
commit
6318ab4a8b
@ -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; }
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user