Skip to content

Commit

Permalink
DPL: update inverter state continuously
Browse files Browse the repository at this point in the history
we previously only called commitPowerLimit() if the desired limit
changed such that the change was bigger than the hysteresis. we found
that if the limit update was not received and the desired limit would
not change much, the limit of the inverter was wrong for a long time.

to mitigate this, we introduced re-sending the limit update every 60
seconds, regardless of what the limit reported by the inverter was at
that time.

if the powe-up command was not received, we also would repeat it only
once every 60 seconds.

this new approach calls commitPowerLimit unconditionally, and
commitPowerLimit itself compares the desired inverter state to the
reported state and sends an update (limit update or power on state)
every time the inverter state is not as expected.

this should make sure that the inverter is in the desired state even if
conditions change slowly and commands were not received as expected.
  • Loading branch information
schlimmchen committed Feb 27, 2024
1 parent 6aa9dfb commit 110526b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 38 deletions.
3 changes: 1 addition & 2 deletions include/PowerLimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class PowerLimiterClass {
Task _loopTask;

int32_t _lastRequestedPowerLimit = 0;
uint32_t _lastPowerLimitMillis = 0;
uint32_t _shutdownTimeout = 0;
Status _lastStatus = Status::Initializing;
uint32_t _lastStatusPrinted = 0;
Expand All @@ -93,7 +92,7 @@ class PowerLimiterClass {
void unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter);
bool canUseDirectSolarPower();
int32_t calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool solarPowerEnabled, bool batteryDischargeEnabled);
void commitPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t limit, bool enablePowerProduction);
bool commitPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newLimitAbs, bool enablePowerProduction);
bool setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
int32_t getSolarChargePower();
float getLoadCorrectedVoltage();
Expand Down
88 changes: 52 additions & 36 deletions src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,8 @@ bool PowerLimiterClass::shutdown(PowerLimiterClass::Status status)
auto lastPowerCommandState = _inverter->PowerCommand()->getLastPowerCommandSuccess();
if (CMD_PENDING == lastPowerCommandState) { return true; }

CONFIG_T& config = Configuration.get();
commitPowerLimit(_inverter, config.PowerLimiter.LowerPowerLimit, false);

return true;
auto const& config = Configuration.get();
return commitPowerLimit(_inverter, config.PowerLimiter.LowerPowerLimit, false);
}

void PowerLimiterClass::loop()
Expand Down Expand Up @@ -527,34 +525,74 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
return newPowerLimit;
}

void PowerLimiterClass::commitPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t limit, bool enablePowerProduction)
/**
* updates the inverter state (power production and limit). returns true if a
* change to its state was requested.
*
* TODO(schlimmchen): there is no guarantee that the limit update will complete
* successfully before the power control request enabled the inverter. this
* should be a two-step process, where the limit is updated und repeated until
* set as desired, and then the inverter is started.
*/
bool PowerLimiterClass::commitPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newLimitAbs, bool enablePowerProduction)
{
bool change = false;

// disable power production as soon as possible.
// setting the power limit is less important.
if (!enablePowerProduction && inverter->isProducing()) {
MessageOutput.println("[DPL::commitPowerLimit] Stopping inverter...");
inverter->sendPowerControlRequest(false);
change = true;
}

inverter->sendActivePowerControlRequest(static_cast<float>(limit),
PowerLimitControlType::AbsolutNonPersistent);
// early in the loop we make it a pre-requisite that this
// value is non-zero, so we can assume it to be valid.
auto maxPower = inverter->DevInfo()->getMaxPower();

float currentLimitPercent = inverter->SystemConfigPara()->getLimitPercent();
auto currentLimitAbs = static_cast<int32_t>(currentLimitPercent * maxPower / 100);
auto diff = std::abs(currentLimitAbs - newLimitAbs);

auto const& config = Configuration.get();
auto hysteresis = config.PowerLimiter.TargetPowerConsumptionHysteresis;

if (hysteresis < (maxPower / 100)) {
MessageOutput.printf("WARNING: Your DPL hysteresis setting of %d W is unreasonably small"
" (less than 1%% of the inverter's max power of %d W)\r\n",
hysteresis, maxPower);
}

if (diff > hysteresis) {
if (_verboseLogging) {
MessageOutput.printf("[DPL::commitPowerLimit] requested: %d W, reported: %d W,"
" last: %d W, diff: %d W, hysteresis: %d W\r\n",
newLimitAbs, currentLimitAbs, _lastRequestedPowerLimit, diff, hysteresis);
}

_lastRequestedPowerLimit = limit;
_lastPowerLimitMillis = millis();
inverter->sendActivePowerControlRequest(static_cast<float>(newLimitAbs),
PowerLimitControlType::AbsolutNonPersistent);

_lastRequestedPowerLimit = newLimitAbs;
change = true;
}

// enable power production only after setting the desired limit,
// such that an older, greater limit will not cause power spikes.
if (enablePowerProduction && !inverter->isProducing()) {
MessageOutput.println("[DPL::commitPowerLimit] Starting up inverter...");
inverter->sendPowerControlRequest(true);
change = true;
}

return change;
}

/**
* enforces limits and a hystersis on the requested power limit, after scaling
* the power limit to the ratio of total and producing inverter channels.
* commits the sanitized power limit. returns true if a limit update was
* committed, false otherwise.
* enforces limits on the requested power limit, after scaling the power limit
* to the ratio of total and producing inverter channels. commits the sanitized
* power limit. returns true if an inverter update was committed, false
* otherwise.
*/
bool PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit)
{
Expand Down Expand Up @@ -589,29 +627,7 @@ bool PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inver

effPowerLimit = std::min<int32_t>(effPowerLimit, inverter->DevInfo()->getMaxPower());

// Check if the new value is within the limits of the hysteresis
auto diff = std::abs(effPowerLimit - _lastRequestedPowerLimit);
auto hysteresis = config.PowerLimiter.TargetPowerConsumptionHysteresis;

// (re-)send power limit in case the last was sent a long time ago. avoids
// staleness in case a power limit update was not received by the inverter.
auto ageMillis = millis() - _lastPowerLimitMillis;

if (diff < hysteresis && ageMillis < 60 * 1000) {
if (_verboseLogging) {
MessageOutput.printf("[DPL::setNewPowerLimit] requested: %d W, last limit: %d W, diff: %d W, hysteresis: %d W, age: %ld ms\r\n",
newPowerLimit, _lastRequestedPowerLimit, diff, hysteresis, ageMillis);
}
return false;
}

if (_verboseLogging) {
MessageOutput.printf("[DPL::setNewPowerLimit] requested: %d W, (re-)sending limit: %d W\r\n",
newPowerLimit, effPowerLimit);
}

commitPowerLimit(inverter, effPowerLimit, true);
return true;
return commitPowerLimit(inverter, effPowerLimit, true);
}

int32_t PowerLimiterClass::getSolarChargePower()
Expand Down

0 comments on commit 110526b

Please sign in to comment.