From 8602bed1778691c7f9ffcddb791c36827344f8ff Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Mon, 25 Nov 2024 22:23:57 +0100 Subject: [PATCH] Fix: use battery when in full solar-passthrough re-using the unconditional full solar-passthrough implementation does not work if the battery is full (full solar-passthrough active) while the grid consumption is higher than the solar output. we then only produce as much power as is available from the charge controllers, where we actually should match the grid cosumption by also using the battery's power. notice that this makes "uncondictional full solar-passthrough" (the DPL mode of operation set through MQTT) something different than just "full solar-passthrough", as was before #1216 (support for multiple DPL inverters) was merged. --- include/PowerLimiter.h | 3 +-- src/PowerLimiter.cpp | 51 ++++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index e7cd30631..c79454517 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -32,7 +32,6 @@ class PowerLimiterClass { InverterCmdPending, ConfigReload, InverterStatsPending, - FullSolarPassthrough, UnconditionalSolarPassthrough, Stable, }; @@ -87,7 +86,7 @@ class PowerLimiterClass { int16_t calcHouseholdConsumption(); using inverter_filter_t = std::function; uint16_t updateInverterLimits(uint16_t powerRequested, inverter_filter_t filter, std::string const& filterExpression); - uint16_t calcBatteryAllowance(uint16_t powerRequested); + uint16_t calcPowerBusUsage(uint16_t powerRequested); bool updateInverters(); uint16_t getSolarPassthroughPower(); std::optional getBatteryDischargeLimit(); diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 15e75e088..68f9f2b6d 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -44,7 +44,7 @@ frozen::string const& PowerLimiterClass::getStatusText(PowerLimiterClass::Status { static const frozen::string missing = "programmer error: missing status text"; - static const frozen::map texts = { + static const frozen::map texts = { { Status::Initializing, "initializing (should not see me)" }, { Status::DisabledByConfig, "disabled by configuration" }, { Status::DisabledByMqtt, "disabled by MQTT" }, @@ -54,7 +54,6 @@ frozen::string const& PowerLimiterClass::getStatusText(PowerLimiterClass::Status { Status::InverterCmdPending, "waiting for a start/stop/restart/limit command to complete" }, { Status::ConfigReload, "reloading DPL configuration" }, { Status::InverterStatsPending, "waiting for sufficiently recent inverter data" }, - { Status::FullSolarPassthrough, "passing through all solar power (full solar passthrough)" }, { Status::UnconditionalSolarPassthrough, "unconditionally passing through all solar power (MQTT override)" }, { Status::Stable, "the system is stable, the last power limit is still valid" }, }; @@ -188,14 +187,13 @@ void PowerLimiterClass::loop() latestInverterStats = std::max(*oStatsMillis, latestInverterStats); } + // note that we can only perform unconditional full solar-passthrough or any + // calculation at all after surviving the loop above, which ensures that we + // have inverter stats more recent than their respective last update command if (Mode::UnconditionalFullSolarPassthrough == _mode) { return fullSolarPassthrough(Status::UnconditionalSolarPassthrough); } - if (isFullSolarPassthroughActive()) { - return fullSolarPassthrough(Status::FullSolarPassthrough); - } - // if the power meter is being used, i.e., if its data is valid, we want to // wait for a new reading after adjusting the inverter limit. otherwise, we // proceed as we will use a fallback limit independent of the power meter. @@ -295,16 +293,16 @@ void PowerLimiterClass::loop() auto coveredBySolar = updateInverterLimits(inverterTotalPower, sSolarPoweredFilter, sSolarPoweredExpression); auto remaining = (inverterTotalPower >= coveredBySolar) ? inverterTotalPower - coveredBySolar : 0; - auto batteryAllowance = calcBatteryAllowance(remaining); - auto coveredByBattery = updateInverterLimits(batteryAllowance, sBatteryPoweredFilter, sBatteryPoweredExpression); + auto powerBusUsage = calcPowerBusUsage(remaining); + auto coveredByBattery = updateInverterLimits(powerBusUsage, sBatteryPoweredFilter, sBatteryPoweredExpression); if (_verboseLogging) { MessageOutput.printf("[DPL::loop] consumption: %d W, " "target output: %u W (limited to %d W), " - "solar inverters output: %u W, battery allowance: " + "solar inverters output: %u W, DC power bus usage: " "%u W, battery inverters output: %u W\r\n", consumption, inverterTotalPower, totalAllowance, - coveredBySolar, batteryAllowance, coveredByBattery); + coveredBySolar, powerBusUsage, coveredByBattery); } _lastExpectedInverterOutput = coveredBySolar + coveredByBattery; @@ -598,10 +596,13 @@ uint16_t PowerLimiterClass::updateInverterLimits(uint16_t powerRequested, return covered; } -uint16_t PowerLimiterClass::calcBatteryAllowance(uint16_t powerRequested) +// calculates how much power the battery-powered inverters shall draw from the +// power bus, which we call the part of the circuitry that is supplied by the +// solar charge controller(s), possibly an AC charger, as well as the battery. +uint16_t PowerLimiterClass::calcPowerBusUsage(uint16_t powerRequested) { if (_verboseLogging) { - MessageOutput.printf("[DPL::calcBatteryAllowance] power requested: %d W\r\n", + MessageOutput.printf("[DPL::calcPowerBusUsage] power requested: %d W\r\n", powerRequested); } @@ -613,29 +614,35 @@ uint16_t PowerLimiterClass::calcBatteryAllowance(uint16_t powerRequested) // will shut down as a consequence. if (!isFullSolarPassthroughActive() && HuaweiCan.getAutoPowerStatus()) { if (_verboseLogging) { - MessageOutput.println("[DPL::calcBatteryAllowance] disabled " + MessageOutput.println("[DPL::calcPowerBusUsage] disabled " "by HuaweiCan auto power"); } return 0; } + auto solarPowerAC = solarDcToInverterAc(getSolarPassthroughPower()); + if (isFullSolarPassthroughActive() && solarPowerAC > powerRequested) { + if (_verboseLogging) { + MessageOutput.printf("[DPL::calcPowerBusUsage] using %d W " + "due to full solar-passthrough\r\n", solarPowerAC); + } + + return solarPowerAC; + } + auto oBatteryPowerDc = getBatteryDischargeLimit(); if (!oBatteryPowerDc.has_value()) { return powerRequested; } auto batteryPowerAC = solarDcToInverterAc(*oBatteryPowerDc); - auto solarPowerAC = solarDcToInverterAc(getSolarPassthroughPower()); - - if (powerRequested > batteryPowerAC + solarPowerAC) { - // respect battery-provided discharge power limit - auto res = batteryPowerAC + solarPowerAC; - + auto allowance = batteryPowerAC + solarPowerAC; + if (powerRequested > allowance) { if (_verboseLogging) { - MessageOutput.printf("[DPL::calcBatteryAllowance] limited by " + MessageOutput.printf("[DPL::calcPowerBusUsage] limited by " "battery (%d W) and/or solar power (%d W): %d W\r\n", - batteryPowerAC, solarPowerAC, res); + batteryPowerAC, solarPowerAC, allowance); } - return res; + return allowance; } return powerRequested;