Skip to content

Commit

Permalink
DPL: Honor battery-provided discharge power limit
Browse files Browse the repository at this point in the history
When the BMC provides a discharge current limit, apply the
this limit in the DPL to the inverter power target when running
from battery.

In my test this worked well, when the limit dropped from 0.5C (25A)
to 0.25C (12.5A) at 20% SoC, the actual measured current draw
dropped to 12.3A.

See #657
  • Loading branch information
ranma committed Aug 25, 2024
1 parent dcc8313 commit 8003ed8
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 12 deletions.
3 changes: 3 additions & 0 deletions include/BatteryStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -96,6 +97,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 setManufacturer(String&& m) { _manufacturer = std::move(m); }
Expand Down Expand Up @@ -135,6 +137,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 setManufacturer(String&& m) { _manufacturer = std::move(m); }
Expand Down
3 changes: 2 additions & 1 deletion include/PowerLimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,11 @@ class PowerLimiterClass {
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);
Expand Down
46 changes: 35 additions & 11 deletions src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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 | contributes | applied | false | max(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:
Expand All @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -888,6 +899,19 @@ int32_t PowerLimiterClass::getSolarPower()
return solarPower;
}

int32_t PowerLimiterClass::getBatteryDischargeLimit()
{
auto voltage = Battery.getStats()->getVoltage();
auto currentLimit = Battery.getStats()->getDischargeCurrentLimitation();
auto powerLimit = voltage * currentLimit;

if (currentLimit == FLT_MAX) {
return INT_MAX;
}

return static_cast<int32_t>(powerLimit);
}

float PowerLimiterClass::getLoadCorrectedVoltage()
{
if (!_inverter) {
Expand Down

0 comments on commit 8003ed8

Please sign in to comment.