Skip to content

Commit

Permalink
Feature: Surplus Stage-I and Stage-II
Browse files Browse the repository at this point in the history
  • Loading branch information
SW-Niko committed Dec 3, 2024
1 parent 4fa5c36 commit a322205
Show file tree
Hide file tree
Showing 6 changed files with 695 additions and 1 deletion.
2 changes: 2 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ struct POWERLIMITER_CONFIG_T {
int8_t RestartHour;
uint16_t TotalUpperPowerLimit;
PowerLimiterInverterConfig Inverters[INV_MAX_COUNT];
bool SurplusPowerStageIEnabled;
bool SurplusPowerStageIIEnabled;
};
using PowerLimiterConfig = struct POWERLIMITER_CONFIG_T;

Expand Down
45 changes: 45 additions & 0 deletions include/Statistic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once


template <typename T>
class WeightedAVG {
public:
WeightedAVG(int16_t factor)
: _countMax(factor)
, _count(0), _countNum(0), _avgV(0), _minV(0), _maxV(0), _lastV(0) {}

void addNumber(const T& num) {
if (_count == 0){
_count++;
_avgV = num;
_minV = num;
_maxV = num;
_countNum = 1;
} else {
if (_count < _countMax) { _count++; }
_avgV = (_avgV * (_count - 1) + num) / _count;
if (num < _minV) { _minV = num; }
if (num > _maxV) { _maxV = num; }
_countNum++;
}
_lastV = num;
}

void reset(void) { _count = 0; _avgV = 0; _minV = 0; _maxV = 0; _lastV = 0; _countNum = 0; }
void reset(const T& num) { _count = 0; addNumber(num); }
T getAverage() const { return _avgV; }
T getMin() const { return _minV; }
T getMax() const { return _maxV; }
T getLast() const { return _lastV; }
int32_t getCounts() const { return _countNum; }

private:
int16_t _countMax; // weighting factor (10 => 1/10 => 10%)
int16_t _count; // counter (0 - _countMax)
int32_t _countNum; // counts the amount of added numbers
T _avgV; // average value
T _minV; // minimum value
T _maxV; // maximum value
T _lastV; // last value
};

88 changes: 88 additions & 0 deletions include/SurplusPower.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#pragma once

#include <Arduino.h>
#include <frozen/string.h>
#include "Statistic.h"


class SurplusPowerClass {
public:
SurplusPowerClass() { updateSettings(); };
~SurplusPowerClass() = default;

bool isSurplusEnabled(void) const;
uint16_t calculateSurplusPower(uint16_t const requestedPower);
void updateSettings(void);

// can be used to temporary disable surplus-power
enum class Switch : uint8_t {
STAGE_I_ON = 0,
STAGE_I_OFF = 1,
STAGE_I_ASK = 2,
STAGE_II_ON = 3,
STAGE_II_OFF = 4,
STAGE_II_ASK = 5
};
bool switchSurplusOnOff(SurplusPowerClass::Switch const onoff);

private:
enum class State : uint8_t {
IDLE = 0,
TRY_MORE = 1,
REDUCE_POWER = 2,
IN_TARGET = 3,
MAXIMUM_POWER = 4,
KEEP_LAST_POWER = 5,
BULK_POWER = 6
};

enum class Text : uint8_t {
Q_NODATA = 0,
Q_EXCELLENT = 1,
Q_GOOD = 2,
Q_BAD = 3,
T_HEAD2 = 4,
T_HEAD1 = 5,
T_HEAD = 6
};

frozen::string const& getStatusText(SurplusPowerClass::State const state);
frozen::string const& getText(SurplusPowerClass::Text const tNr);
uint16_t calcBulkMode(uint16_t const requestedPower);
uint16_t calcAbsorptionFloatMode(uint16_t const requestedPower, uint8_t const modeMppt);
int16_t getTimeToSunset(void);

// to handle regulation in absorption- and float-mode
bool _stageIIEnabled = false; // surplus-stage-II enable / disable
bool _stageIITempOff = false; // used for temporary deactivation
State _surplusState = State::IDLE; // state machine
int32_t _surplusPower = 0; // actual surplus power [W]
int16_t _powerStepSize = 0; // approximation step size [W]
uint32_t _lastInTargetMillis = 0; // last millis we hit the target
uint32_t _lastCalcMillis = 0; // last millis we calculated the surplus power
int32_t _surplusUpperPowerLimit = 0; // inverter upper power limit [W]
WeightedAVG<float> _avgMPPTVoltage {5}; // the average helps to smooth the regulation [V]

// to handle the quality counter
int8_t _qualityCounter = 0; // quality counter
WeightedAVG<float> _qualityAVG {20}; // quality counter average
int16_t _lastAddPower = 0; // last power step
uint32_t _overruleCounter = 0; // counts how often the voltage regulation was overruled by battery current
uint32_t _errorCounter = 0; // counts all errors

// to handle bulk mode
bool _stageIEnabled = false; // surplus-stage-I enable / disable
bool _stageITempOff = false; // used for temporary deactivation
int32_t _batteryReserve = 0; // battery reserve power [W]
float _batterySafetyPercent = 0.0f; // battery reserve power safety factor [%] (20.0 = 20%)
int32_t _batteryCapacity = 0; // battery capacity [Wh]
int16_t _durationAbsorptionToSunset = 0; // time between absorption start and sunset [minutes]
int16_t _durationNowToAbsorption = 0; // time from now to start of absorption [minutes]
int32_t _solarPower = 0; // solar panel power [W]
float _startSoC = 0.0f; // start SoC [%] (85.0 = 85%)
uint32_t _lastReserveCalcMillis = 0; // last millis we calculated the battery reserve
WeightedAVG<float> _avgCellVoltage {20}; // in bulk mode we get better results with higher average [V]

};

extern SurplusPowerClass SurplusPower;
4 changes: 4 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ void ConfigurationClass::serializePowerLimiterConfig(PowerLimiterConfig const& s
target["inverter_channel_id_for_dc_voltage"] = source.InverterChannelIdForDcVoltage;
target["inverter_restart_hour"] = source.RestartHour;
target["total_upper_power_limit"] = source.TotalUpperPowerLimit;
target["surplus_stage_I_enabled"] = source.SurplusPowerStageIEnabled;
target["surplus_stage_II_enabled"] = source.SurplusPowerStageIIEnabled;

JsonArray inverters = target["inverters"].to<JsonArray>();
for (size_t i = 0; i < INV_MAX_COUNT; ++i) {
Expand Down Expand Up @@ -459,6 +461,8 @@ void ConfigurationClass::deserializePowerLimiterConfig(JsonObject const& source,
target.InverterChannelIdForDcVoltage = source["inverter_channel_id_for_dc_voltage"] | POWERLIMITER_INVERTER_CHANNEL_ID;
target.RestartHour = source["inverter_restart_hour"] | POWERLIMITER_RESTART_HOUR;
target.TotalUpperPowerLimit = source["total_upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT;
target.SurplusPowerStageIEnabled = source["surplus_stage_I_enabled"] | false;
target.SurplusPowerStageIIEnabled = source["surplus_stage_II_enabled"] | false;

JsonArray inverters = source["inverters"].as<JsonArray>();
for (size_t i = 0; i < INV_MAX_COUNT; ++i) {
Expand Down
8 changes: 7 additions & 1 deletion src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <limits>
#include <frozen/map.h>
#include "SunPosition.h"
#include "SurplusPower.h"

static auto sBatteryPoweredFilter = [](PowerLimiterInverter const& inv) {
return !inv.isSolarPowered();
Expand Down Expand Up @@ -312,7 +313,12 @@ void PowerLimiterClass::loop()
inverterTotalPower = std::min(inverterTotalPower, totalAllowance);

auto coveredBySolar = updateInverterLimits(inverterTotalPower, sSolarPoweredFilter, sSolarPoweredExpression);
auto remaining = (inverterTotalPower >= coveredBySolar) ? inverterTotalPower - coveredBySolar : 0;
auto remaining = (inverterTotalPower >= coveredBySolar) ? inverterTotalPower - coveredBySolar : 0u;

// todo: not 100% sure if this is the right place to call the surplus-power-mode
// because surplus can only handel battery powered inverter
if (SurplusPower.isSurplusEnabled()) { remaining = SurplusPower.calculateSurplusPower(remaining); }

auto powerBusUsage = calcPowerBusUsage(remaining);
auto coveredByBattery = updateInverterLimits(powerBusUsage, sBatteryPoweredFilter, sBatteryPoweredExpression);

Expand Down
Loading

0 comments on commit a322205

Please sign in to comment.