Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Surplus-Power-Mode #1167

Draft
wants to merge 4 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
55 changes: 55 additions & 0 deletions include/Statistic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

/*
* Weighted average and statistics class (initialising value defines the weighted average 10 = 10%)
*/
template <typename T>
class WeightedAVG {
public:
explicit WeightedAVG(size_t factor)
: _countMax(factor)
, _count(0), _countNum(0), _avgV(0), _minV(0), _maxV(0), _lastV(0) {}

// Add a value to the statistics
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; }
if (_countNum < 10000) { _countNum++; }
}
_lastV = num;
}

// Reset the statistic data
void reset(void) { _count = 0; _avgV = 0; _minV = 0; _maxV = 0; _lastV = 0; _countNum = 0; }
// Reset the statistic data and initialize with first value
void reset(const T& num) { _count = 0; addNumber(num); }
// Returns the weighted average
T getAverage() const { return _avgV; }
// Returns the minimum value
T getMin() const { return _minV; }
// Returns the maximum value
T getMax() const { return _maxV; }
// Returns the last added value
T getLast() const { return _lastV; }
// Returns the amount of added values. Limited to 10000
size_t getCounts() const { return _countNum; }

private:
size_t _countMax; // weighting factor (10 => 1/10 => 10%)
size_t _count; // counter (0 - _countMax)
size_t _countNum; // counts the amount of added values (0 - 10000)
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-5*60*1000; // hint: this value avoids first calculation 5 min after start
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