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 hoylabs#657
  • Loading branch information
ranma authored and AndreasBoehm committed Sep 10, 2024
1 parent 9067acd commit cfd7b5e
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 cfd7b5e

Please sign in to comment.