DPL hysteresis fix and refactor of setNewPowerLimit() (#264)
This commit is contained in:
parent
7d73ae3c20
commit
07bb0b03f7
@ -110,7 +110,8 @@ The dynamic power limiter is responsible for automatic inverter power adjustment
|
||||
Other settings are:
|
||||
* The inverter ID configures the inverter that is controlled by the power limiter. The power limiter can only control a single inverter at this point in time.
|
||||
* Channel ID is the inverter input channel ID that is used for battery voltage readings.
|
||||
* Target power consumption and hysteresis set the power range that can be consumed from the grid.
|
||||
* Target power consumption specifies the power to be either consumed from the grid (when set to a positive value) or fed back into the grid (when set to a negative value).
|
||||
* The hysteresis value helps optimize communication with the inverter by skipping unnecessary power limit updates. An update is only sent if the absolute difference between the newly computed power limit and the previously set limit matches or exceeds the hysteresis value. This approach can conserve both airtime and CPU resources.
|
||||
* Power limits control the min / max limits of the inverter
|
||||
* Inverter is behind power meter. Select this if your inverter power is measured by the power meter. This is typically the case.
|
||||
* Battery start and stop threshold can be configured using voltage and / or state of charge values. Stage of charge values requires a Pylontech battery at this point.
|
||||
|
||||
@ -55,6 +55,7 @@ private:
|
||||
|
||||
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);
|
||||
void setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
|
||||
int32_t getSolarChargePower();
|
||||
float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter);
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include <VeDirectFrameHandler.h>
|
||||
#include "MessageOutput.h"
|
||||
#include <ctime>
|
||||
#include <cmath>
|
||||
|
||||
PowerLimiterClass PowerLimiter;
|
||||
|
||||
@ -78,9 +79,7 @@ void PowerLimiterClass::loop()
|
||||
if (((!config.PowerLimiter_Enabled || _mode == PL_MODE_FULL_DISABLE) && _plState != SHUTDOWN)) {
|
||||
if (inverter->isProducing()) {
|
||||
MessageOutput.printf("PL initiated inverter shutdown.\r\n");
|
||||
inverter->sendActivePowerControlRequest(static_cast<float>(config.PowerLimiter_LowerPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
||||
inverter->sendPowerControlRequest(false);
|
||||
commitPowerLimit(inverter, config.PowerLimiter_LowerPowerLimit, false);
|
||||
} else {
|
||||
_plState = SHUTDOWN;
|
||||
}
|
||||
@ -104,10 +103,8 @@ void PowerLimiterClass::loop()
|
||||
// If the power meter values are older than 30 seconds,
|
||||
// or the Inverter Stats are older then 10x the poll interval
|
||||
// set the limit to lower power limit for safety reasons.
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Power Meter/Inverter values too old. Using 0W (i.e. disable inverter)");
|
||||
inverter->sendActivePowerControlRequest(static_cast<float>(config.PowerLimiter_LowerPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
||||
inverter->sendPowerControlRequest(false);
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Power Meter/Inverter values too old, shutting down inverter");
|
||||
commitPowerLimit(inverter, config.PowerLimiter_LowerPowerLimit, false);
|
||||
#ifdef POWER_LIMITER_DEBUG
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] ******************* PL safety shutdown, update times exceeded PM: %li, Inverter: %li \r\n", millis() - PowerMeter.getLastPowerMeterUpdate(), millis() - inverter->Statistics()->getLastUpdate());
|
||||
#endif
|
||||
@ -266,16 +263,6 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
|
||||
// Case 3
|
||||
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
|
||||
|
||||
// Check if the new value is within the limits of the hysteresis and
|
||||
// if we can discharge the battery
|
||||
// If things did not change much we just use the old setting
|
||||
if ((newPowerLimit - acPower) >= (-config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||
(newPowerLimit - acPower) <= (+config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||
batteryDischargeEnabled ) {
|
||||
MessageOutput.println("[PowerLimiterClass::loop] reusing old limit");
|
||||
return _lastRequestedPowerLimit;
|
||||
}
|
||||
|
||||
// At this point we've calculated the required energy to compensate for household consumption.
|
||||
// If the battery is enabled this can always be supplied since we assume that the battery can supply unlimited power
|
||||
// The next step is to determine if the Solar power as provided by the Victron charger
|
||||
@ -304,64 +291,88 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
|
||||
newPowerLimit = adjustedVictronChargePower;
|
||||
}
|
||||
|
||||
// Respect power limit
|
||||
if (newPowerLimit > config.PowerLimiter_UpperPowerLimit)
|
||||
newPowerLimit = config.PowerLimiter_UpperPowerLimit;
|
||||
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] newPowerLimit: %d\r\n", newPowerLimit);
|
||||
return newPowerLimit;
|
||||
}
|
||||
|
||||
void PowerLimiterClass::commitPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t limit, bool enablePowerProduction)
|
||||
{
|
||||
// disable power production as soon as possible.
|
||||
// setting the power limit is less important.
|
||||
if (!enablePowerProduction && inverter->isProducing()) {
|
||||
MessageOutput.println("[PowerLimiterClass::commitPowerLimit] Stopping inverter...");
|
||||
inverter->sendPowerControlRequest(false);
|
||||
}
|
||||
|
||||
inverter->sendActivePowerControlRequest(static_cast<float>(limit),
|
||||
PowerLimitControlType::AbsolutNonPersistent);
|
||||
|
||||
_lastRequestedPowerLimit = limit;
|
||||
_lastLimitSetTime = millis();
|
||||
|
||||
// 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("[PowerLimiterClass::commitPowerLimit] Starting up inverter...");
|
||||
inverter->sendPowerControlRequest(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
// Start the inverter in case it's inactive and if the requested power is high enough
|
||||
if (!inverter->isProducing() && newPowerLimit > config.PowerLimiter_LowerPowerLimit) {
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
|
||||
inverter->sendPowerControlRequest(true);
|
||||
}
|
||||
|
||||
// Stop the inverter if limit is below threshold.
|
||||
// We'll also set the power limit to the lower value in this case
|
||||
if (newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
|
||||
if (inverter->isProducing()) {
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
||||
inverter->sendActivePowerControlRequest(static_cast<float>(config.PowerLimiter_LowerPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
||||
inverter->sendPowerControlRequest(false);
|
||||
}
|
||||
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
||||
if (!inverter->isProducing()) { return; }
|
||||
|
||||
MessageOutput.printf("[PowerLimiterClass::setNewPowerLimit] requested power limit %d is smaller than lower power limit %d\r\n",
|
||||
newPowerLimit, config.PowerLimiter_LowerPowerLimit);
|
||||
return commitPowerLimit(inverter, config.PowerLimiter_LowerPowerLimit, false);
|
||||
}
|
||||
|
||||
// Set the actual limit. We'll only do this is if the limit is in the right range
|
||||
// and differs from the last requested value
|
||||
if( _lastRequestedPowerLimit != newPowerLimit &&
|
||||
/* newPowerLimit > config.PowerLimiter_LowerPowerLimit && --> This will always be true given the check above, kept for code readability */
|
||||
newPowerLimit <= config.PowerLimiter_UpperPowerLimit ) {
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
|
||||
// enforce configured upper power limit
|
||||
int32_t effPowerLimit = std::min(newPowerLimit, config.PowerLimiter_UpperPowerLimit);
|
||||
|
||||
int32_t effPowerLimit = newPowerLimit;
|
||||
std::list<ChannelNum_t> dcChnls = inverter->Statistics()->getChannelsByType(TYPE_DC);
|
||||
int dcProdChnls = 0, dcTotalChnls = dcChnls.size();
|
||||
for (auto& c : dcChnls) {
|
||||
if (inverter->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC) > 1.0) {
|
||||
dcProdChnls++;
|
||||
}
|
||||
}
|
||||
if (dcProdChnls > 0) {
|
||||
effPowerLimit = round(newPowerLimit * static_cast<float>(dcTotalChnls) / dcProdChnls);
|
||||
uint16_t inverterMaxPower = inverter->DevInfo()->getMaxPower();
|
||||
if (effPowerLimit > inverterMaxPower) {
|
||||
effPowerLimit = inverterMaxPower;
|
||||
}
|
||||
}
|
||||
|
||||
inverter->sendActivePowerControlRequest(static_cast<float>(effPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = effPowerLimit;
|
||||
// wait for the next inverter update (+ 3 seconds to make sure the limit got applied)
|
||||
_lastLimitSetTime = millis();
|
||||
// scale the power limit by the amount of all inverter channels devided by
|
||||
// the amount of producing inverter channels. the inverters limit each of
|
||||
// the n channels to 1/n of the total power limit. scaling the power limit
|
||||
// ensures the total inverter output is what we are asking for.
|
||||
std::list<ChannelNum_t> dcChnls = inverter->Statistics()->getChannelsByType(TYPE_DC);
|
||||
int dcProdChnls = 0, dcTotalChnls = dcChnls.size();
|
||||
for (auto& c : dcChnls) {
|
||||
if (inverter->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC) > 1.0) {
|
||||
dcProdChnls++;
|
||||
}
|
||||
}
|
||||
if (dcProdChnls > 0) {
|
||||
MessageOutput.printf("[PowerLimiterClass::setNewPowerLimit] %d channels total, %d producing channels, scaling power limit\r\n",
|
||||
dcTotalChnls, dcProdChnls);
|
||||
effPowerLimit = round(newPowerLimit * static_cast<float>(dcTotalChnls) / dcProdChnls);
|
||||
if (effPowerLimit > inverter->DevInfo()->getMaxPower()) {
|
||||
effPowerLimit = inverter->DevInfo()->getMaxPower();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the new value is within the limits of the hysteresis
|
||||
auto diff = std::abs(effPowerLimit - _lastRequestedPowerLimit);
|
||||
if ( diff < config.PowerLimiter_TargetPowerConsumptionHysteresis) {
|
||||
MessageOutput.printf("[PowerLimiterClass::setNewPowerLimit] reusing old limit: %d W, diff: %d, hysteresis: %d\r\n",
|
||||
_lastRequestedPowerLimit, diff, config.PowerLimiter_TargetPowerConsumptionHysteresis);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageOutput.printf("[PowerLimiterClass::setNewPowerLimit] using new limit: %d W, requested power limit: %d\r\n",
|
||||
effPowerLimit, newPowerLimit);
|
||||
|
||||
commitPowerLimit(inverter, effPowerLimit, true);
|
||||
}
|
||||
|
||||
int32_t PowerLimiterClass::getSolarChargePower()
|
||||
|
||||
@ -543,8 +543,8 @@
|
||||
"InverterChannelIdHint": "Wähle den Kanal an dem die Batterie hängt.",
|
||||
"TargetPowerConsumption": "Angestrebter Netzbezung",
|
||||
"TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch aus dem Netz.",
|
||||
"TargetPowerConsumptionHysteresis": "Hysterese für den angestrebten Netzbezug",
|
||||
"TargetPowerConsumptionHysteresisHint": "Wert um den der angestrebte Netzbezug schwanken darf, ohne dass nachgeregelt wird.",
|
||||
"TargetPowerConsumptionHysteresis": "Hysterese für das berechnete Limit",
|
||||
"TargetPowerConsumptionHysteresisHint": "Neu berechnetes Limit nur dann an den Inverter senden, wenn es vom zuletzt gesendeten Limit um mindestens diesen Betrag abweicht.",
|
||||
"LowerPowerLimit": "Unteres Leistungslimit",
|
||||
"UpperPowerLimit": "Oberes Leistungslimit",
|
||||
"PowerMeters": "Leistungsmesser",
|
||||
|
||||
@ -544,8 +544,8 @@
|
||||
"InverterChannelIdHint": "Select proper channel where battery is connected to.",
|
||||
"TargetPowerConsumption": "Target power consumption from grid",
|
||||
"TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.",
|
||||
"TargetPowerConsumptionHysteresis": "Hysteresis for power consumption from grid",
|
||||
"TargetPowerConsumptionHysteresisHint": "Value around which the target grid power consumption fluctuates without readjustment.",
|
||||
"TargetPowerConsumptionHysteresis": "Hysteresis for calculated power limit",
|
||||
"TargetPowerConsumptionHysteresisHint": "Only send a newly calculated power limit to the inverter if the absolute difference to the last sent power limit matches or exceeds this amount.",
|
||||
"LowerPowerLimit": "Lower power limit",
|
||||
"UpperPowerLimit": "Upper power limit",
|
||||
"PowerMeters": "Power meter",
|
||||
|
||||
@ -566,8 +566,8 @@
|
||||
"InverterChannelIdHint": "Select proper channel where battery is connected to.",
|
||||
"TargetPowerConsumption": "Target power consumption from grid",
|
||||
"TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.",
|
||||
"TargetPowerConsumptionHysteresis": "Hysteresis for power consumption from grid",
|
||||
"TargetPowerConsumptionHysteresisHint": "Value around which the target grid power consumption fluctuates without readjustment.",
|
||||
"TargetPowerConsumptionHysteresis": "Hysteresis for calculated power limit",
|
||||
"TargetPowerConsumptionHysteresisHint": "Only send a newly calculated power limit to the inverter if the absolute difference to the last sent power limit matches or exceeds this amount.",
|
||||
"LowerPowerLimit": "Lower power limit",
|
||||
"UpperPowerLimit": "Upper power limit",
|
||||
"PowerMeters": "Power meter",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user