powerLimiter with state machine
This commit is contained in:
parent
b70407d7fe
commit
716fc867a1
@ -7,6 +7,14 @@
|
||||
#include <Hoymiles.h>
|
||||
#include <memory>
|
||||
|
||||
enum PowerLimiterStates {
|
||||
STATE_DISCOVER = 0,
|
||||
STATE_OFF,
|
||||
STATE_CONSUME_SOLAR_POWER_ONLY,
|
||||
STATE_NORMAL_OPERATION
|
||||
};
|
||||
|
||||
|
||||
class PowerLimiterClass {
|
||||
public:
|
||||
void init();
|
||||
@ -18,13 +26,15 @@ private:
|
||||
uint32_t _lastLoop;
|
||||
uint32_t _lastPowerMeterUpdate;
|
||||
uint16_t _lastRequestedPowerLimit;
|
||||
bool _consumeSolarPowerOnly;
|
||||
u_int8_t _plState = STATE_DISCOVER;
|
||||
|
||||
float _powerMeter1Power;
|
||||
float _powerMeter2Power;
|
||||
float _powerMeter3Power;
|
||||
|
||||
bool canUseDirectSolarPower();
|
||||
int32_t calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool consumeSolarPowerOnly);
|
||||
void setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, uint32_t newPowerLimit);
|
||||
uint16_t getDirectSolarPower();
|
||||
float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter);
|
||||
bool isStartThresholdReached(std::shared_ptr<InverterAbstract> inverter);
|
||||
|
||||
@ -39,7 +39,6 @@ void PowerLimiterClass::init()
|
||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter3, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
_consumeSolarPowerOnly = true;
|
||||
_lastCommandSent = 0;
|
||||
_lastLoop = 0;
|
||||
_lastPowerMeterUpdate = 0;
|
||||
@ -88,121 +87,102 @@ void PowerLimiterClass::loop()
|
||||
}
|
||||
|
||||
float dcVoltage = inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_UDC);
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor);
|
||||
|
||||
if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000) {
|
||||
return;
|
||||
}
|
||||
|
||||
float efficency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF);
|
||||
uint32_t victronChargePower = this->getDirectSolarPower();
|
||||
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] victronChargePower: %d, efficiency: %.2f, consumeSolarPowerOnly: %s \r\n", victronChargePower, efficency, _consumeSolarPowerOnly ? "true" : "false");
|
||||
|
||||
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] dcVoltage: %.2f Voltage Start Threshold: %.2f Voltage Stop Threshold: %.2f inverter->isProducing(): %d\r\n",
|
||||
dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing());
|
||||
}
|
||||
|
||||
int32_t powerMeter = _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
||||
|
||||
if (inverter->isProducing()) {
|
||||
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC);
|
||||
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor);
|
||||
while(true) {
|
||||
switch(_plState) {
|
||||
case STATE_DISCOVER:
|
||||
if (!inverter->isProducing() || isStopThresholdReached(inverter)) {
|
||||
_plState = STATE_OFF;
|
||||
}
|
||||
else if (canUseDirectSolarPower()) {
|
||||
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
|
||||
}
|
||||
else {
|
||||
_plState = STATE_NORMAL_OPERATION;
|
||||
}
|
||||
break;
|
||||
case STATE_OFF:
|
||||
// if on turn off
|
||||
if (inverter->isProducing()) {
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] DC voltage: %.2f Corrected DC voltage: %.2f...\r\n",
|
||||
dcVoltage, correctedDcVoltage);
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false);
|
||||
|
||||
if ((_consumeSolarPowerOnly && isStartThresholdReached(inverter))
|
||||
|| !canUseDirectSolarPower()) {
|
||||
// The battery is full enough again, use the full battery power from now on.
|
||||
_consumeSolarPowerOnly = false;
|
||||
} else if (!_consumeSolarPowerOnly && !isStopThresholdReached(inverter) && canUseDirectSolarPower()) {
|
||||
// The battery voltage dropped too low
|
||||
_consumeSolarPowerOnly = true;
|
||||
}
|
||||
uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = newPowerLimit;
|
||||
_lastCommandSent = millis();
|
||||
}
|
||||
|
||||
if (isStopThresholdReached(inverter)
|
||||
|| (_consumeSolarPowerOnly && !canUseDirectSolarPower())) {
|
||||
// DC voltage too low, stop the inverter
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] DC voltage: %.2f Corrected DC voltage: %.2f...\r\n",
|
||||
dcVoltage, correctedDcVoltage);
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter...");
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false);
|
||||
// do nothing if battery is empty
|
||||
if (isStopThresholdReached(inverter))
|
||||
return;
|
||||
// check for possible state changes
|
||||
if (isStartThresholdReached(inverter) && calcPowerLimit(inverter, false) >= config.PowerLimiter_LowerPowerLimit) {
|
||||
_plState = STATE_NORMAL_OPERATION;
|
||||
}
|
||||
else if (canUseDirectSolarPower() && calcPowerLimit(inverter, true) >= config.PowerLimiter_LowerPowerLimit) {
|
||||
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
|
||||
}
|
||||
|
||||
uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = newPowerLimit;
|
||||
|
||||
_lastCommandSent = millis();
|
||||
_consumeSolarPowerOnly = false;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if ((isStartThresholdReached(inverter) || (canUseDirectSolarPower() && (!isStopThresholdReached(inverter))))
|
||||
&& powerMeter >= config.PowerLimiter_LowerPowerLimit) {
|
||||
// DC voltage high enough, start the inverter
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
|
||||
_lastCommandSent = millis();
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), true);
|
||||
|
||||
// In this mode, the inverter should consume the current solar power only
|
||||
// and not drain additional power from the battery
|
||||
if (!isStartThresholdReached(inverter)) {
|
||||
_consumeSolarPowerOnly = true;
|
||||
// inverter on on state change
|
||||
if (_plState != STATE_OFF) {
|
||||
// DC voltage high enough, start the inverter
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), true);
|
||||
_lastCommandSent = millis();
|
||||
}
|
||||
else
|
||||
return;
|
||||
break;
|
||||
case STATE_CONSUME_SOLAR_POWER_ONLY: {
|
||||
int32_t newPowerLimit = calcPowerLimit(inverter, true);
|
||||
if (!inverter->isProducing()
|
||||
|| isStopThresholdReached(inverter)
|
||||
|| newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
|
||||
_plState = STATE_OFF;
|
||||
break;
|
||||
}
|
||||
else if (!canUseDirectSolarPower() || isStartThresholdReached(inverter)) {
|
||||
_plState = STATE_NORMAL_OPERATION;
|
||||
break;
|
||||
}
|
||||
setNewPowerLimit(inverter, newPowerLimit);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case STATE_NORMAL_OPERATION: {
|
||||
int32_t newPowerLimit = calcPowerLimit(inverter, false);
|
||||
if (!inverter->isProducing()
|
||||
|| isStopThresholdReached(inverter)
|
||||
|| newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
|
||||
_plState = STATE_OFF;
|
||||
break;
|
||||
}
|
||||
// check if grid power consumption is within the upper an lower threshold of the target consumption
|
||||
else if (newPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||
newPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis)) {
|
||||
return;
|
||||
}
|
||||
setNewPowerLimit(inverter, newPowerLimit);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t newPowerLimit = 0;
|
||||
|
||||
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
||||
newPowerLimit = powerMeter;
|
||||
// check if grid power consumption is within the upper an lower threshold of the target consumption
|
||||
if (!_consumeSolarPowerOnly &&
|
||||
newPowerLimit >= (config.PowerLimiter_TargetPowerConsumption - config.PowerLimiter_TargetPowerConsumptionHysteresis) &&
|
||||
newPowerLimit <= (config.PowerLimiter_TargetPowerConsumption + config.PowerLimiter_TargetPowerConsumptionHysteresis))
|
||||
return;
|
||||
else {
|
||||
if (config.PowerLimiter_IsInverterBehindPowerMeter) {
|
||||
// If the inverter the behind the power meter (part of measurement),
|
||||
// the produced power of this inverter has also to be taken into account.
|
||||
// We don't use FLD_PAC from the statistics, because that
|
||||
// data might be too old and unrelieable.
|
||||
newPowerLimit += _lastRequestedPowerLimit;
|
||||
}
|
||||
|
||||
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
|
||||
|
||||
uint16_t upperPowerLimit = config.PowerLimiter_UpperPowerLimit;
|
||||
if (_consumeSolarPowerOnly && (upperPowerLimit > victronChargePower)) {
|
||||
// Battery voltage too low, use Victron solar power (corrected by efficency factor) only
|
||||
upperPowerLimit = victronChargePower * (efficency / 100.0);
|
||||
}
|
||||
|
||||
if (newPowerLimit > upperPowerLimit)
|
||||
newPowerLimit = upperPowerLimit;
|
||||
else if (newPowerLimit < (uint16_t)config.PowerLimiter_LowerPowerLimit) {
|
||||
newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|
||||
// stop the inverter
|
||||
MessageOutput.println("[PowerLimiterClass::loop] Power limit below lower power limit. Stopping inverter...");
|
||||
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false);
|
||||
|
||||
}
|
||||
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] powerMeter: %d W lastRequestedPowerLimit: %d\r\n",
|
||||
powerMeter, _lastRequestedPowerLimit);
|
||||
}
|
||||
} else {
|
||||
// If the power meter values are older than 30 seconds,
|
||||
// set the limit to config.PowerLimiter_LowerPowerLimit for safety reasons.
|
||||
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
||||
}
|
||||
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
|
||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = newPowerLimit;
|
||||
|
||||
_lastCommandSent = millis();
|
||||
}
|
||||
|
||||
bool PowerLimiterClass::canUseDirectSolarPower()
|
||||
@ -222,6 +202,53 @@ bool PowerLimiterClass::canUseDirectSolarPower()
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool consumeSolarPowerOnly)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
int32_t newPowerLimit = _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
||||
|
||||
float efficency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF);
|
||||
uint32_t victronChargePower = this->getDirectSolarPower();
|
||||
uint32_t adjustedVictronChargePower = victronChargePower * (efficency > 0.0 ? (efficency / 100.0) : 1.0); // if inverter is off, use 1.0
|
||||
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] victronChargePower: %d, efficiency: %.2f, consumeSolarPowerOnly: %s \r\n", victronChargePower, efficency, consumeSolarPowerOnly ? "true" : "false");
|
||||
|
||||
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
||||
if (config.PowerLimiter_IsInverterBehindPowerMeter) {
|
||||
// If the inverter the behind the power meter (part of measurement),
|
||||
// the produced power of this inverter has also to be taken into account.
|
||||
// We don't use FLD_PAC from the statistics, because that
|
||||
// data might be too old and unrelieable.
|
||||
newPowerLimit += _lastRequestedPowerLimit;
|
||||
}
|
||||
|
||||
newPowerLimit -= config.PowerLimiter_TargetPowerConsumption;
|
||||
|
||||
uint16_t upperPowerLimit = config.PowerLimiter_UpperPowerLimit;
|
||||
if (consumeSolarPowerOnly && (upperPowerLimit > adjustedVictronChargePower)) {
|
||||
// Battery voltage too low, use Victron solar power (corrected by efficency factor) only
|
||||
upperPowerLimit = adjustedVictronChargePower;
|
||||
}
|
||||
|
||||
if (newPowerLimit > upperPowerLimit)
|
||||
newPowerLimit = upperPowerLimit;
|
||||
} else {
|
||||
// If the power meter values are older than 30 seconds,
|
||||
// set the limit to config.PowerLimiter_LowerPowerLimit for safety reasons.
|
||||
newPowerLimit = config.PowerLimiter_LowerPowerLimit;
|
||||
}
|
||||
return newPowerLimit;
|
||||
}
|
||||
|
||||
void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, uint32_t newPowerLimit)
|
||||
{
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] Limit Non-Persistent: %d W\r\n", newPowerLimit);
|
||||
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
|
||||
_lastRequestedPowerLimit = newPowerLimit;
|
||||
_lastCommandSent = millis();
|
||||
}
|
||||
|
||||
uint16_t PowerLimiterClass::getDirectSolarPower()
|
||||
{
|
||||
if (!this->canUseDirectSolarPower()) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user