Skip to content

Commit

Permalink
Feature: DPL: Honor battery-provided discharge power limit (#1198)
Browse files Browse the repository at this point in the history
When the BMS provides a discharge current limit, apply
this limit in the DPL to the inverter power target when running
from battery.
  • Loading branch information
ranma authored Sep 13, 2024
1 parent c96762c commit 6318ab4
Show file tree
Hide file tree
Showing 3 changed files with 47 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 @@ -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; }
Expand Down Expand Up @@ -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; }
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
53 changes: 42 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 | 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:
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,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) {
Expand Down

0 comments on commit 6318ab4

Please sign in to comment.