powerLimiter with state machine

This commit is contained in:
helgeerbe 2023-03-07 21:04:19 +01:00
parent b70407d7fe
commit 716fc867a1
2 changed files with 137 additions and 100 deletions

View File

@ -7,6 +7,14 @@
#include <Hoymiles.h> #include <Hoymiles.h>
#include <memory> #include <memory>
enum PowerLimiterStates {
STATE_DISCOVER = 0,
STATE_OFF,
STATE_CONSUME_SOLAR_POWER_ONLY,
STATE_NORMAL_OPERATION
};
class PowerLimiterClass { class PowerLimiterClass {
public: public:
void init(); void init();
@ -18,13 +26,15 @@ private:
uint32_t _lastLoop; uint32_t _lastLoop;
uint32_t _lastPowerMeterUpdate; uint32_t _lastPowerMeterUpdate;
uint16_t _lastRequestedPowerLimit; uint16_t _lastRequestedPowerLimit;
bool _consumeSolarPowerOnly; u_int8_t _plState = STATE_DISCOVER;
float _powerMeter1Power; float _powerMeter1Power;
float _powerMeter2Power; float _powerMeter2Power;
float _powerMeter3Power; float _powerMeter3Power;
bool canUseDirectSolarPower(); 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(); uint16_t getDirectSolarPower();
float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter); float getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter);
bool isStartThresholdReached(std::shared_ptr<InverterAbstract> inverter); bool isStartThresholdReached(std::shared_ptr<InverterAbstract> inverter);

View File

@ -39,7 +39,6 @@ void PowerLimiterClass::init()
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter3, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter3, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
} }
_consumeSolarPowerOnly = true;
_lastCommandSent = 0; _lastCommandSent = 0;
_lastLoop = 0; _lastLoop = 0;
_lastPowerMeterUpdate = 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 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) { if ((millis() - inverter->Statistics()->getLastUpdate()) > 10000) {
return; 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)) { if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
MessageOutput.printf("[PowerLimiterClass::loop] dcVoltage: %.2f Voltage Start Threshold: %.2f Voltage Stop Threshold: %.2f inverter->isProducing(): %d\r\n", 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()); dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing());
} }
int32_t powerMeter = _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
if (inverter->isProducing()) { while(true) {
float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC); switch(_plState) {
float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor); 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)) uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit;
|| !canUseDirectSolarPower()) { inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent);
// The battery is full enough again, use the full battery power from now on. _lastRequestedPowerLimit = newPowerLimit;
_consumeSolarPowerOnly = false; _lastCommandSent = millis();
} else if (!_consumeSolarPowerOnly && !isStopThresholdReached(inverter) && canUseDirectSolarPower()) { }
// The battery voltage dropped too low
_consumeSolarPowerOnly = true;
}
if (isStopThresholdReached(inverter) // do nothing if battery is empty
|| (_consumeSolarPowerOnly && !canUseDirectSolarPower())) { if (isStopThresholdReached(inverter))
// DC voltage too low, stop the inverter return;
MessageOutput.printf("[PowerLimiterClass::loop] DC voltage: %.2f Corrected DC voltage: %.2f...\r\n", // check for possible state changes
dcVoltage, correctedDcVoltage); if (isStartThresholdReached(inverter) && calcPowerLimit(inverter, false) >= config.PowerLimiter_LowerPowerLimit) {
MessageOutput.println("[PowerLimiterClass::loop] Stopping inverter..."); _plState = STATE_NORMAL_OPERATION;
inverter->sendPowerControlRequest(Hoymiles.getRadio(), false); }
else if (canUseDirectSolarPower() && calcPowerLimit(inverter, true) >= config.PowerLimiter_LowerPowerLimit) {
_plState = STATE_CONSUME_SOLAR_POWER_ONLY;
}
uint16_t newPowerLimit = (uint16_t)config.PowerLimiter_LowerPowerLimit; // inverter on on state change
inverter->sendActivePowerControlRequest(Hoymiles.getRadio(), newPowerLimit, PowerLimitControlType::AbsolutNonPersistent); if (_plState != STATE_OFF) {
_lastRequestedPowerLimit = newPowerLimit; // DC voltage high enough, start the inverter
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter...");
_lastCommandSent = millis(); inverter->sendPowerControlRequest(Hoymiles.getRadio(), true);
_consumeSolarPowerOnly = false; _lastCommandSent = millis();
}
return; else
} return;
} else { break;
if ((isStartThresholdReached(inverter) || (canUseDirectSolarPower() && (!isStopThresholdReached(inverter)))) case STATE_CONSUME_SOLAR_POWER_ONLY: {
&& powerMeter >= config.PowerLimiter_LowerPowerLimit) { int32_t newPowerLimit = calcPowerLimit(inverter, true);
// DC voltage high enough, start the inverter if (!inverter->isProducing()
MessageOutput.println("[PowerLimiterClass::loop] Starting up inverter..."); || isStopThresholdReached(inverter)
_lastCommandSent = millis(); || newPowerLimit < config.PowerLimiter_LowerPowerLimit) {
inverter->sendPowerControlRequest(Hoymiles.getRadio(), true); _plState = STATE_OFF;
break;
// In this mode, the inverter should consume the current solar power only }
// and not drain additional power from the battery else if (!canUseDirectSolarPower() || isStartThresholdReached(inverter)) {
if (!isStartThresholdReached(inverter)) { _plState = STATE_NORMAL_OPERATION;
_consumeSolarPowerOnly = true; 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() bool PowerLimiterClass::canUseDirectSolarPower()
@ -222,6 +202,53 @@ bool PowerLimiterClass::canUseDirectSolarPower()
return true; 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() uint16_t PowerLimiterClass::getDirectSolarPower()
{ {
if (!this->canUseDirectSolarPower()) { if (!this->canUseDirectSolarPower()) {