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:
|
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.
|
* 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.
|
* 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
|
* 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.
|
* 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.
|
* 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();
|
bool canUseDirectSolarPower();
|
||||||
int32_t calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool solarPowerEnabled, bool batteryDischargeEnabled);
|
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);
|
void setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
|
||||||
int32_t getSolarChargePower();
|
int32_t getSolarChargePower();
|
||||||
float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter);
|
float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter);
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include <VeDirectFrameHandler.h>
|
#include <VeDirectFrameHandler.h>
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
PowerLimiterClass PowerLimiter;
|
PowerLimiterClass PowerLimiter;
|
||||||
|
|
||||||
@ -78,9 +79,7 @@ void PowerLimiterClass::loop()
|
|||||||
if (((!config.PowerLimiter_Enabled || _mode == PL_MODE_FULL_DISABLE) && _plState != SHUTDOWN)) {
|
if (((!config.PowerLimiter_Enabled || _mode == PL_MODE_FULL_DISABLE) && _plState != SHUTDOWN)) {
|
||||||
if (inverter->isProducing()) {
|
if (inverter->isProducing()) {
|
||||||
MessageOutput.printf("PL initiated inverter shutdown.\r\n");
|
MessageOutput.printf("PL initiated inverter shutdown.\r\n");
|
||||||
inverter->sendActivePowerControlRequest(static_cast<float>(config.PowerLimiter_LowerPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
commitPowerLimit(inverter, config.PowerLimiter_LowerPowerLimit, false);
|
||||||
_lastRequestedPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
|
||||||
inverter->sendPowerControlRequest(false);
|
|
||||||
} else {
|
} else {
|
||||||
_plState = SHUTDOWN;
|
_plState = SHUTDOWN;
|
||||||
}
|
}
|
||||||
@ -104,10 +103,8 @@ void PowerLimiterClass::loop()
|
|||||||
// If the power meter values are older than 30 seconds,
|
// If the power meter values are older than 30 seconds,
|
||||||
// or the Inverter Stats are older then 10x the poll interval
|
// or the Inverter Stats are older then 10x the poll interval
|
||||||
// set the limit to lower power limit for safety reasons.
|
// 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)");
|
MessageOutput.println("[PowerLimiterClass::loop] Power Meter/Inverter values too old, shutting down inverter");
|
||||||
inverter->sendActivePowerControlRequest(static_cast<float>(config.PowerLimiter_LowerPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
commitPowerLimit(inverter, config.PowerLimiter_LowerPowerLimit, false);
|
||||||
_lastRequestedPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
|
||||||
inverter->sendPowerControlRequest(false);
|
|
||||||
#ifdef POWER_LIMITER_DEBUG
|
#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());
|
MessageOutput.printf("[PowerLimiterClass::loop] ******************* PL safety shutdown, update times exceeded PM: %li, Inverter: %li \r\n", millis() - PowerMeter.getLastPowerMeterUpdate(), millis() - inverter->Statistics()->getLastUpdate());
|
||||||
#endif
|
#endif
|
||||||
@ -266,16 +263,6 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
|
|||||||
// Case 3
|
// Case 3
|
||||||
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
|
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.
|
// 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
|
// 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
|
// The next step is to determine if the Solar power as provided by the Victron charger
|
||||||
@ -304,44 +291,60 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
|
|||||||
newPowerLimit = adjustedVictronChargePower;
|
newPowerLimit = adjustedVictronChargePower;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respect power limit
|
|
||||||
if (newPowerLimit > config.PowerLimiter_UpperPowerLimit)
|
|
||||||
newPowerLimit = config.PowerLimiter_UpperPowerLimit;
|
|
||||||
|
|
||||||
MessageOutput.printf("[PowerLimiterClass::loop] newPowerLimit: %d\r\n", newPowerLimit);
|
MessageOutput.printf("[PowerLimiterClass::loop] newPowerLimit: %d\r\n", newPowerLimit);
|
||||||
return 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)
|
void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit)
|
||||||
{
|
{
|
||||||
CONFIG_T& config = Configuration.get();
|
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.
|
// Stop the inverter if limit is below threshold.
|
||||||
// We'll also set the power limit to the lower value in this case
|
// We'll also set the power limit to the lower value in this case
|
||||||
if (newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
|
if (newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
|
||||||
if (inverter->isProducing()) {
|
if (!inverter->isProducing()) { return; }
|
||||||
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
|
||||||
inverter->sendActivePowerControlRequest(static_cast<float>(config.PowerLimiter_LowerPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
MessageOutput.printf("[PowerLimiterClass::setNewPowerLimit] requested power limit %d is smaller than lower power limit %d\r\n",
|
||||||
_lastRequestedPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
newPowerLimit, config.PowerLimiter_LowerPowerLimit);
|
||||||
inverter->sendPowerControlRequest(false);
|
return commitPowerLimit(inverter, config.PowerLimiter_LowerPowerLimit, false);
|
||||||
}
|
|
||||||
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the actual limit. We'll only do this is if the limit is in the right range
|
// enforce configured upper power limit
|
||||||
// and differs from the last requested value
|
int32_t effPowerLimit = std::min(newPowerLimit, config.PowerLimiter_UpperPowerLimit);
|
||||||
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);
|
|
||||||
|
|
||||||
int32_t effPowerLimit = newPowerLimit;
|
|
||||||
|
// 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);
|
std::list<ChannelNum_t> dcChnls = inverter->Statistics()->getChannelsByType(TYPE_DC);
|
||||||
int dcProdChnls = 0, dcTotalChnls = dcChnls.size();
|
int dcProdChnls = 0, dcTotalChnls = dcChnls.size();
|
||||||
for (auto& c : dcChnls) {
|
for (auto& c : dcChnls) {
|
||||||
@ -350,18 +353,26 @@ void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dcProdChnls > 0) {
|
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);
|
effPowerLimit = round(newPowerLimit * static_cast<float>(dcTotalChnls) / dcProdChnls);
|
||||||
uint16_t inverterMaxPower = inverter->DevInfo()->getMaxPower();
|
if (effPowerLimit > inverter->DevInfo()->getMaxPower()) {
|
||||||
if (effPowerLimit > inverterMaxPower) {
|
effPowerLimit = inverter->DevInfo()->getMaxPower();
|
||||||
effPowerLimit = inverterMaxPower;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inverter->sendActivePowerControlRequest(static_cast<float>(effPowerLimit), PowerLimitControlType::AbsolutNonPersistent);
|
// Check if the new value is within the limits of the hysteresis
|
||||||
_lastRequestedPowerLimit = effPowerLimit;
|
auto diff = std::abs(effPowerLimit - _lastRequestedPowerLimit);
|
||||||
// wait for the next inverter update (+ 3 seconds to make sure the limit got applied)
|
if ( diff < config.PowerLimiter_TargetPowerConsumptionHysteresis) {
|
||||||
_lastLimitSetTime = millis();
|
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()
|
int32_t PowerLimiterClass::getSolarChargePower()
|
||||||
|
|||||||
@ -543,8 +543,8 @@
|
|||||||
"InverterChannelIdHint": "Wähle den Kanal an dem die Batterie hängt.",
|
"InverterChannelIdHint": "Wähle den Kanal an dem die Batterie hängt.",
|
||||||
"TargetPowerConsumption": "Angestrebter Netzbezung",
|
"TargetPowerConsumption": "Angestrebter Netzbezung",
|
||||||
"TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch aus dem Netz.",
|
"TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch aus dem Netz.",
|
||||||
"TargetPowerConsumptionHysteresis": "Hysterese für den angestrebten Netzbezug",
|
"TargetPowerConsumptionHysteresis": "Hysterese für das berechnete Limit",
|
||||||
"TargetPowerConsumptionHysteresisHint": "Wert um den der angestrebte Netzbezug schwanken darf, ohne dass nachgeregelt wird.",
|
"TargetPowerConsumptionHysteresisHint": "Neu berechnetes Limit nur dann an den Inverter senden, wenn es vom zuletzt gesendeten Limit um mindestens diesen Betrag abweicht.",
|
||||||
"LowerPowerLimit": "Unteres Leistungslimit",
|
"LowerPowerLimit": "Unteres Leistungslimit",
|
||||||
"UpperPowerLimit": "Oberes Leistungslimit",
|
"UpperPowerLimit": "Oberes Leistungslimit",
|
||||||
"PowerMeters": "Leistungsmesser",
|
"PowerMeters": "Leistungsmesser",
|
||||||
|
|||||||
@ -544,8 +544,8 @@
|
|||||||
"InverterChannelIdHint": "Select proper channel where battery is connected to.",
|
"InverterChannelIdHint": "Select proper channel where battery is connected to.",
|
||||||
"TargetPowerConsumption": "Target power consumption from grid",
|
"TargetPowerConsumption": "Target power consumption from grid",
|
||||||
"TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.",
|
"TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.",
|
||||||
"TargetPowerConsumptionHysteresis": "Hysteresis for power consumption from grid",
|
"TargetPowerConsumptionHysteresis": "Hysteresis for calculated power limit",
|
||||||
"TargetPowerConsumptionHysteresisHint": "Value around which the target grid power consumption fluctuates without readjustment.",
|
"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",
|
"LowerPowerLimit": "Lower power limit",
|
||||||
"UpperPowerLimit": "Upper power limit",
|
"UpperPowerLimit": "Upper power limit",
|
||||||
"PowerMeters": "Power meter",
|
"PowerMeters": "Power meter",
|
||||||
|
|||||||
@ -566,8 +566,8 @@
|
|||||||
"InverterChannelIdHint": "Select proper channel where battery is connected to.",
|
"InverterChannelIdHint": "Select proper channel where battery is connected to.",
|
||||||
"TargetPowerConsumption": "Target power consumption from grid",
|
"TargetPowerConsumption": "Target power consumption from grid",
|
||||||
"TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.",
|
"TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.",
|
||||||
"TargetPowerConsumptionHysteresis": "Hysteresis for power consumption from grid",
|
"TargetPowerConsumptionHysteresis": "Hysteresis for calculated power limit",
|
||||||
"TargetPowerConsumptionHysteresisHint": "Value around which the target grid power consumption fluctuates without readjustment.",
|
"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",
|
"LowerPowerLimit": "Lower power limit",
|
||||||
"UpperPowerLimit": "Upper power limit",
|
"UpperPowerLimit": "Upper power limit",
|
||||||
"PowerMeters": "Power meter",
|
"PowerMeters": "Power meter",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user