Skip to content

Commit

Permalink
DPL: work on internal copy of pointer to inverter
Browse files Browse the repository at this point in the history
the DPL already took care to shut down the inverter if anything fishy
was going on, mainly to make sure that the battery is not drained.
however, some cases were missed:

* if the configuration changed such that another inverter is now
  targeted, the one the DPL controlled previously was not shut down.
* if the configuration changed such that another inverter (different
  serial number) was configured at the same index, the previous one
  was not shut down.

this change corrects these problems by making the DPL keep a copy of the
shared_ptr to the inverter. the shared_ptr is only released once the DPL
shut the respective inverter down.
  • Loading branch information
schlimmchen committed Jun 30, 2023
1 parent 2970e84 commit 71079fa
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 32 deletions.
2 changes: 2 additions & 0 deletions include/PowerLimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class PowerLimiterClass {
PowerMeterTimeout,
PowerMeterPending,
InverterInvalid,
InverterChanged,
InverterOffline,
InverterCommandsDisabled,
InverterLimitPending,
Expand Down Expand Up @@ -62,6 +63,7 @@ class PowerLimiterClass {
Status _lastStatus = Status::Initializing;
uint32_t _lastStatusPrinted = 0;
uint8_t _mode = PL_MODE_ENABLE_NORMAL_OP;
std::shared_ptr<InverterAbstract> _inverter = nullptr;
bool _batteryDischargeEnabled = false;
uint32_t _nextInverterRestart = 0; // Values: 0->not calculated / 1->no restart configured / >1->time of next inverter restart in millis()
uint32_t _nextCalculateCheck = 5000; // time in millis for next NTP check to calulate restart
Expand Down
76 changes: 44 additions & 32 deletions src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ std::string const& PowerLimiterClass::getStatusText(PowerLimiterClass::Status st
{ Status::PowerMeterTimeout, "power meter readings are outdated" },
{ Status::PowerMeterPending, "waiting for sufficiently recent power meter reading" },
{ Status::InverterInvalid, "invalid inverter selection/configuration" },
{ Status::InverterChanged, "target inverter changed" },
{ Status::InverterOffline, "inverter is offline (polling enabled? radio okay?)" },
{ Status::InverterCommandsDisabled, "inverter configuration prohibits sending commands" },
{ Status::InverterLimitPending, "waiting for a power limit command to complete" },
Expand Down Expand Up @@ -70,21 +71,20 @@ void PowerLimiterClass::shutdown(PowerLimiterClass::Status status)

_plState = plStates::SHUTDOWN;

CONFIG_T& config = Configuration.get();
std::shared_ptr<InverterAbstract> inverter = Hoymiles.getInverterByPos(config.PowerLimiter_InverterId);
if (inverter == nullptr || !inverter->isProducing() || !inverter->isReachable()) {
if (_inverter == nullptr || !_inverter->isProducing() || !_inverter->isReachable()) {
_inverter = nullptr;
_plState = plStates::OFF;
return;
}

auto lastLimitCommandState = inverter->SystemConfigPara()->getLastLimitCommandSuccess();
auto lastLimitCommandState = _inverter->SystemConfigPara()->getLastLimitCommandSuccess();
if (CMD_PENDING == lastLimitCommandState) { return; }

auto lastPowerCommandState = inverter->PowerCommand()->getLastPowerCommandSuccess();
auto lastPowerCommandState = _inverter->PowerCommand()->getLastPowerCommandSuccess();
if (CMD_PENDING == lastPowerCommandState) { return; }

commitPowerLimit(inverter, config.PowerLimiter_LowerPowerLimit, false);
CONFIG_T& config = Configuration.get();
commitPowerLimit(_inverter, config.PowerLimiter_LowerPowerLimit, false);
}

void PowerLimiterClass::loop()
Expand Down Expand Up @@ -115,13 +115,29 @@ void PowerLimiterClass::loop()
return shutdown(Status::PowerMeterTimeout);
}

std::shared_ptr<InverterAbstract> inverter =
std::shared_ptr<InverterAbstract> currentInverter =
Hoymiles.getInverterByPos(config.PowerLimiter_InverterId);

if (inverter == nullptr) { return announceStatus(Status::InverterInvalid); }
// in case of (newly) broken configuration, shut down
// the last inverter we worked with (if any)
if (currentInverter == nullptr) {
return shutdown(Status::InverterInvalid);
}

// if the DPL is supposed to manage another inverter now, we first
// shut down the previous one, if any. then we pick up the new one.
if (_inverter != nullptr && _inverter->serial() != currentInverter->serial()) {
return shutdown(Status::InverterChanged);
}

// update our pointer as the configuration might have changed
_inverter = currentInverter;

// controlling an inverter means the DPL will shut it down eventually
_plState = plStates::ACTIVE;

// data polling is disabled or the inverter is deemed offline
if (!inverter->isReachable()) {
if (!_inverter->isReachable()) {
return announceStatus(Status::InverterOffline);
}

Expand All @@ -131,29 +147,29 @@ void PowerLimiterClass::loop()
}

// concerns active power commands (power limits) only (also from web app or MQTT)
auto lastLimitCommandState = inverter->SystemConfigPara()->getLastLimitCommandSuccess();
auto lastLimitCommandState = _inverter->SystemConfigPara()->getLastLimitCommandSuccess();
if (CMD_PENDING == lastLimitCommandState) {
return announceStatus(Status::InverterLimitPending);
}

// concerns power commands (start, stop, restart) only (also from web app or MQTT)
auto lastPowerCommandState = inverter->PowerCommand()->getLastPowerCommandSuccess();
auto lastPowerCommandState = _inverter->PowerCommand()->getLastPowerCommandSuccess();
if (CMD_PENDING == lastPowerCommandState) {
return announceStatus(Status::InverterPowerCmdPending);
}

// concerns both power limits and start/stop/restart commands and is
// only updated if a respective response was received from the inverter
auto lastUpdateCmd = std::max(
inverter->SystemConfigPara()->getLastUpdateCommand(),
inverter->PowerCommand()->getLastUpdateCommand());
_inverter->SystemConfigPara()->getLastUpdateCommand(),
_inverter->PowerCommand()->getLastUpdateCommand());

// wait for power meter and inverter stat updates after a settling phase
auto settlingEnd = lastUpdateCmd + 3 * 1000;

if (millis() < settlingEnd) { return announceStatus(Status::Settling); }

if (inverter->Statistics()->getLastUpdate() <= settlingEnd) {
if (_inverter->Statistics()->getLastUpdate() <= settlingEnd) {
return announceStatus(Status::InverterStatsPending);
}

Expand All @@ -168,7 +184,7 @@ void PowerLimiterClass::loop()
// Check if next inverter restart time is reached
if ((_nextInverterRestart > 1) && (_nextInverterRestart <= millis())) {
MessageOutput.println("[PowerLimiterClass::loop] send inverter restart");
inverter->sendRestartControlRequest();
_inverter->sendRestartControlRequest();
calcNextInverterRestart();
}

Expand All @@ -188,26 +204,26 @@ void PowerLimiterClass::loop()

// Printout some stats
if (millis() - PowerMeter.getLastPowerMeterUpdate() < (30 * 1000)) {
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);
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());
}

// Battery charging cycle conditions
// First we always disable discharge if the battery is empty
if (isStopThresholdReached(inverter)) {
if (isStopThresholdReached(_inverter)) {
// Disable battery discharge when empty
_batteryDischargeEnabled = false;
} else {
// UI: Solar Passthrough Enabled -> false
// Battery discharge can be enabled when start threshold is reached
if (!config.PowerLimiter_SolarPassThroughEnabled && isStartThresholdReached(inverter)) {
if (!config.PowerLimiter_SolarPassThroughEnabled && isStartThresholdReached(_inverter)) {
_batteryDischargeEnabled = true;
}

// UI: Solar Passthrough Enabled -> true && EMPTY_AT_NIGHT
if (config.PowerLimiter_SolarPassThroughEnabled && config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGHT) {
if(isStartThresholdReached(inverter)) {
if(isStartThresholdReached(_inverter)) {
// In this case we should only discharge the battery as long it is above startThreshold
_batteryDischargeEnabled = true;
}
Expand All @@ -219,41 +235,38 @@ void PowerLimiterClass::loop()

// UI: Solar Passthrough Enabled -> true && EMPTY_WHEN_FULL
// Battery discharge can be enabled when start threshold is reached
if (config.PowerLimiter_SolarPassThroughEnabled && isStartThresholdReached(inverter) && config.PowerLimiter_BatteryDrainStategy == EMPTY_WHEN_FULL) {
if (config.PowerLimiter_SolarPassThroughEnabled && isStartThresholdReached(_inverter) && config.PowerLimiter_BatteryDrainStategy == EMPTY_WHEN_FULL) {
_batteryDischargeEnabled = true;
}
}
// Calculate and set Power Limit
int32_t newPowerLimit = calcPowerLimit(inverter, canUseDirectSolarPower(), _batteryDischargeEnabled);
setNewPowerLimit(inverter, newPowerLimit);
int32_t newPowerLimit = calcPowerLimit(_inverter, canUseDirectSolarPower(), _batteryDischargeEnabled);
setNewPowerLimit(_inverter, newPowerLimit);
#ifdef POWER_LIMITER_DEBUG
MessageOutput.printf("[PowerLimiterClass::loop] Status: SolarPT enabled %i, Drain Strategy: %i, canUseDirectSolarPower: %i, Batt discharge: %i\r\n",
config.PowerLimiter_SolarPassThroughEnabled, config.PowerLimiter_BatteryDrainStategy, canUseDirectSolarPower(), _batteryDischargeEnabled);
MessageOutput.printf("[PowerLimiterClass::loop] Status: StartTH %i, StopTH: %i, loadCorrectedV %f\r\n",
isStartThresholdReached(inverter), isStopThresholdReached(inverter), getLoadCorrectedVoltage(inverter));
isStartThresholdReached(_inverter), isStopThresholdReached(_inverter), getLoadCorrectedVoltage(_inverter));
MessageOutput.printf("[PowerLimiterClass::loop] Status Batt: Ena: %i, SOC: %i, StartTH: %i, StopTH: %i, LastUpdate: %li\r\n",
config.Battery_Enabled, Battery.stateOfCharge, config.PowerLimiter_BatterySocStartThreshold, config.PowerLimiter_BatterySocStopThreshold, millis() - Battery.stateOfChargeLastUpdate);
MessageOutput.printf("[PowerLimiterClass::loop] ******************* Leaving PL, PL set to: %i, SP: %i, Batt: %i, PM: %f\r\n", newPowerLimit, canUseDirectSolarPower(), _batteryDischargeEnabled, round(PowerMeter.getPowerTotal()));
#endif
}

uint8_t PowerLimiterClass::getPowerLimiterState() {
CONFIG_T& config = Configuration.get();

std::shared_ptr<InverterAbstract> inverter = Hoymiles.getInverterByPos(config.PowerLimiter_InverterId);
if (inverter == nullptr || !inverter->isReachable()) {
if (_inverter == nullptr || !_inverter->isReachable()) {
return PL_UI_STATE_INACTIVE;
}

if (inverter->isProducing() && _batteryDischargeEnabled) {
if (_inverter->isProducing() && _batteryDischargeEnabled) {
return PL_UI_STATE_USE_SOLAR_AND_BATTERY;
}

if (inverter->isProducing() && !_batteryDischargeEnabled) {
if (_inverter->isProducing() && !_batteryDischargeEnabled) {
return PL_UI_STATE_USE_SOLAR_ONLY;
}

if(!inverter->isProducing()) {
if(!_inverter->isProducing()) {
return PL_UI_STATE_CHARGING;
}

Expand Down Expand Up @@ -427,7 +440,6 @@ void PowerLimiterClass::setNewPowerLimit(std::shared_ptr<InverterAbstract> inver
MessageOutput.printf("[PowerLimiterClass::setNewPowerLimit] using new limit: %d W, requested power limit: %d\r\n",
effPowerLimit, newPowerLimit);

_plState = plStates::ACTIVE;
commitPowerLimit(inverter, effPowerLimit, true);
}

Expand Down

0 comments on commit 71079fa

Please sign in to comment.