Skip to content

Commit

Permalink
Fix: DPL: proactively set known limit on inverters
Browse files Browse the repository at this point in the history
when starting up, the current limit set at the inverter is not known for
a couple of minutes (see upstream FAQ). to speed things up and to make
sure that the current limit is eventually known (which it is not always,
see #1427), the DPL now sends the lower power limit proactively.
  • Loading branch information
schlimmchen committed Nov 30, 2024
1 parent 4e4d46c commit be9e2aa
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 20 deletions.
10 changes: 9 additions & 1 deletion include/PowerLimiterInverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,17 @@ class PowerLimiterInverter {
protected:
PowerLimiterInverter(bool verboseLogging, PowerLimiterInverterConfig const& config);

enum class Eligibility : unsigned {
Unreachable,
SendingCommandsDisabled,
MaxOutputUnknown,
CurrentLimitUnknown,
Eligible
};

// returns false if the inverter cannot participate
// in achieving the requested change in power output
bool isEligible() const;
Eligibility isEligible() const;

uint16_t getCurrentLimitWatts() const;

Expand Down
8 changes: 4 additions & 4 deletions src/PowerLimiterBatteryInverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PowerLimiterBatteryInverter::PowerLimiterBatteryInverter(bool verboseLogging, Po

uint16_t PowerLimiterBatteryInverter::getMaxReductionWatts(bool allowStandby) const
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

if (!isProducing()) { return 0; }

Expand All @@ -18,7 +18,7 @@ uint16_t PowerLimiterBatteryInverter::getMaxReductionWatts(bool allowStandby) co

uint16_t PowerLimiterBatteryInverter::getMaxIncreaseWatts() const
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

if (!isProducing()) {
return getConfiguredMaxPowerWatts();
Expand All @@ -38,7 +38,7 @@ uint16_t PowerLimiterBatteryInverter::getMaxIncreaseWatts() const

uint16_t PowerLimiterBatteryInverter::applyReduction(uint16_t reduction, bool allowStandby)
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

if (reduction == 0) { return 0; }

Expand Down Expand Up @@ -67,7 +67,7 @@ uint16_t PowerLimiterBatteryInverter::applyReduction(uint16_t reduction, bool al

uint16_t PowerLimiterBatteryInverter::applyIncrease(uint16_t increase)
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

if (increase == 0) { return 0; }

Expand Down
63 changes: 52 additions & 11 deletions src/PowerLimiterInverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,24 @@ PowerLimiterInverter::PowerLimiterInverter(bool verboseLogging, PowerLimiterInve
snprintf(_logPrefix, sizeof(_logPrefix), "[DPL inverter %s]:", _serialStr);
}

bool PowerLimiterInverter::isEligible() const
PowerLimiterInverter::Eligibility PowerLimiterInverter::isEligible() const
{
if (!isReachable() || !isSendingCommandsEnabled()) { return false; }
if (!isReachable()) { return Eligibility::Unreachable; }

// after startup, the limit effective at the inverter is not known. the
// respective message to request this info is only sent after a significant
// backoff (4 minutes). this is to avoid error messages to appear in the
// inverter's event log. we will wait until the current limit is known.
if (getCurrentLimitWatts() == 0) { return false; }
if (!isSendingCommandsEnabled()) { return Eligibility::SendingCommandsDisabled; }

// the model-dependent maximum AC power output is only known after the
// first DevInfoSimpleCommand succeeded. we desperately need this info, so
// the inverter is not eligible until this value is known.
if (getInverterMaxPowerWatts() == 0) { return false; }
if (getInverterMaxPowerWatts() == 0) { return Eligibility::MaxOutputUnknown; }

// after startup, the limit effective at the inverter is not known. the
// respective message to request this info is only sent after a significant
// backoff (~5 minutes, see upstream FAQ). this is to avoid error messages
// to appear in the inverter's event log.
if (getCurrentLimitWatts() == 0) { return Eligibility::CurrentLimitUnknown; }

return true;
return Eligibility::Eligible;
}

bool PowerLimiterInverter::update()
Expand All @@ -62,6 +64,27 @@ bool PowerLimiterInverter::update()
return false;
};

switch (isEligible()) {
case Eligibility::Eligible:
break;

case Eligibility::CurrentLimitUnknown:
// we actually can and must do something about this: set the configured
// lower power limit. the inverter becomes eligible shortly and
// inverters whose current limit is not fetched for some reason (see
// #1427) are "woken up".
if (!_oTargetPowerLimitWatts.has_value()) {
MessageOutput.printf("%s bootstrapping by setting "
"lower power limit\r\n", _logPrefix);
_oTargetPowerLimitWatts = _config.LowerPowerLimit;
}
break;

default:
return reset();
break;
}

// do not reset _updateTimeouts below if no state change requested
if (!_oTargetPowerState.has_value() && !_oTargetPowerLimitWatts.has_value()) {
return reset();
Expand Down Expand Up @@ -286,6 +309,25 @@ void PowerLimiterInverter::debug() const
{
if (!_verboseLogging) { return; }

String eligibility("disqualified");
switch (isEligible()) {
case Eligibility::Unreachable:
eligibility += " (unreachable)";
break;
case Eligibility::SendingCommandsDisabled:
eligibility += " (sending commands disabled)";
break;
case Eligibility::MaxOutputUnknown:
eligibility += " (max output unknown)";
break;
case Eligibility::CurrentLimitUnknown:
eligibility += " (current limit unknown)";
break;
case Eligibility::Eligible:
eligibility = "eligible";
break;
}

MessageOutput.printf(
"%s\r\n"
" %s-powered, %s %d W\r\n"
Expand All @@ -299,8 +341,7 @@ void PowerLimiterInverter::debug() const
_config.LowerPowerLimit, getCurrentLimitWatts(), _config.UpperPowerLimit,
getInverterMaxPowerWatts(),
(isSendingCommandsEnabled()?"enabled":"disabled"),
(isReachable()?"reachable":"offline"),
(isEligible()?"eligible":"disqualified"),
(isReachable()?"reachable":"offline"), eligibility.c_str(),
getMaxReductionWatts(false), getMaxReductionWatts(true), getMaxIncreaseWatts(),
(_oTargetPowerLimitWatts.has_value()?*_oTargetPowerLimitWatts:-1),
(_oTargetPowerLimitWatts.has_value()?"update":"unchanged"),
Expand Down
8 changes: 4 additions & 4 deletions src/PowerLimiterSolarInverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ PowerLimiterSolarInverter::PowerLimiterSolarInverter(bool verboseLogging, PowerL

uint16_t PowerLimiterSolarInverter::getMaxReductionWatts(bool) const
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

auto low = std::min(getCurrentLimitWatts(), getCurrentOutputAcWatts());
if (low <= _config.LowerPowerLimit) { return 0; }
Expand All @@ -16,7 +16,7 @@ uint16_t PowerLimiterSolarInverter::getMaxReductionWatts(bool) const

uint16_t PowerLimiterSolarInverter::getMaxIncreaseWatts() const
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

// the maximum increase possible for this inverter
int16_t maxTotalIncrease = getConfiguredMaxPowerWatts() - getCurrentOutputAcWatts();
Expand Down Expand Up @@ -75,7 +75,7 @@ uint16_t PowerLimiterSolarInverter::getMaxIncreaseWatts() const

uint16_t PowerLimiterSolarInverter::applyReduction(uint16_t reduction, bool)
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

if (reduction == 0) { return 0; }

Expand All @@ -90,7 +90,7 @@ uint16_t PowerLimiterSolarInverter::applyReduction(uint16_t reduction, bool)

uint16_t PowerLimiterSolarInverter::applyIncrease(uint16_t increase)
{
if (!isEligible()) { return 0; }
if (isEligible() != Eligibility::Eligible) { return 0; }

if (increase == 0) { return 0; }

Expand Down

0 comments on commit be9e2aa

Please sign in to comment.