From 8003ed898b55e1d38ae310cb17978a0a939f6df7 Mon Sep 17 00:00:00 2001 From: Tobias Diedrich Date: Sun, 25 Aug 2024 10:29:32 +0200 Subject: [PATCH] DPL: Honor battery-provided discharge power limit 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 https://github.com/helgeerbe/OpenDTU-OnBattery/issues/657 --- include/BatteryStats.h | 3 +++ include/PowerLimiter.h | 3 ++- src/PowerLimiter.cpp | 46 ++++++++++++++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 94da35d78..49cb610de 100644 --- a/include/BatteryStats.h +++ b/include/BatteryStats.h @@ -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; @@ -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); } @@ -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); } diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index db69a303b..c55d1d4bb 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -90,10 +90,11 @@ class PowerLimiterClass { int32_t inverterPowerDcToAc(std::shared_ptr inverter, int32_t dcPower); void unconditionalSolarPassthrough(std::shared_ptr inverter); bool canUseDirectSolarPower(); - bool calcPowerLimit(std::shared_ptr inverter, int32_t solarPower, bool batteryPower); + bool calcPowerLimit(std::shared_ptr inverter, int32_t solarPower, int32_t batteryPowerLimit, bool batteryPower); bool updateInverter(); bool setNewPowerLimit(std::shared_ptr inverter, int32_t newPowerLimit); int32_t getSolarPower(); + int32_t getBatteryDischargeLimit(); float getLoadCorrectedVoltage(); bool testThreshold(float socThreshold, float voltThreshold, std::function compare); diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 93f027b07..eee485aaf 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -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 | 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 inverter, int32_t solarPowerDC, bool batteryPower) +bool PowerLimiterClass::calcPowerLimit(std::shared_ptr 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 inverte // old and unreliable. TODO(schlimmchen): is this comment outdated? auto inverterOutput = static_cast(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 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 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,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(powerLimit); +} + float PowerLimiterClass::getLoadCorrectedVoltage() { if (!_inverter) {