Feature: DPL: support overscaling on all inverters (#1286)

this change allows to support overscaling for all inverters, as the configuration of
inputs (which one is part of a particular MPPT) is now provided from withing the
code. this information is used to implement overscaling for any of the inverters
(which are generally compatible with OpenDTU(-OnBattery)).
This commit is contained in:
vaterlangen 2024-10-22 20:00:44 +02:00 committed by Bernhard Kirchen
parent 4524c0405d
commit cf4a59c740
26 changed files with 298 additions and 45 deletions

View File

@ -29,6 +29,10 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A }
};
HERF_1CH::HERF_1CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {};
@ -53,3 +57,13 @@ uint8_t HERF_1CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HERF_1CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HERF_1CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -10,4 +10,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -36,6 +36,11 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A },
{ CH1, MPPT_B }
};
HERF_2CH::HERF_2CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {};
@ -60,3 +65,13 @@ uint8_t HERF_2CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HERF_2CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HERF_2CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -10,4 +10,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -28,6 +28,10 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A }
};
HMS_1CH::HMS_1CH(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {};
@ -52,3 +56,13 @@ uint8_t HMS_1CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HMS_1CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HMS_1CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -11,4 +11,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -28,6 +28,10 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A }
};
HMS_1CHv2::HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {};
@ -52,3 +56,13 @@ uint8_t HMS_1CHv2::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HMS_1CHv2::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HMS_1CHv2::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -11,4 +11,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -35,6 +35,11 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A },
{ CH1, MPPT_B }
};
HMS_2CH::HMS_2CH(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {};
@ -59,3 +64,13 @@ uint8_t HMS_2CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HMS_2CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HMS_2CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -11,4 +11,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -49,6 +49,13 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A },
{ CH1, MPPT_B },
{ CH2, MPPT_C },
{ CH3, MPPT_D }
};
HMS_4CH::HMS_4CH(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {};
@ -73,3 +80,13 @@ uint8_t HMS_4CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HMS_4CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HMS_4CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -10,4 +10,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -58,6 +58,13 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A },
{ CH1, MPPT_A },
{ CH2, MPPT_B },
{ CH3, MPPT_B }
};
HMT_4CH::HMT_4CH(HoymilesRadio* radio, const uint64_t serial)
: HMT_Abstract(radio, serial) {};
@ -82,3 +89,13 @@ uint8_t HMT_4CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HMT_4CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HMT_4CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -10,4 +10,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -72,6 +72,15 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A },
{ CH1, MPPT_A },
{ CH2, MPPT_B },
{ CH3, MPPT_B },
{ CH4, MPPT_C },
{ CH5, MPPT_C }
};
HMT_6CH::HMT_6CH(HoymilesRadio* radio, const uint64_t serial)
: HMT_Abstract(radio, serial) {};
@ -96,3 +105,13 @@ uint8_t HMT_6CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HMT_6CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HMT_6CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -10,4 +10,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -28,6 +28,10 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A }
};
HM_1CH::HM_1CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {};
@ -65,3 +69,13 @@ uint8_t HM_1CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HM_1CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HM_1CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -11,4 +11,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -36,6 +36,11 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A },
{ CH1, MPPT_B }
};
HM_2CH::HM_2CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {};
@ -73,3 +78,13 @@ uint8_t HM_2CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HM_2CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HM_2CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -10,4 +10,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -49,6 +49,13 @@ static const byteAssign_t byteAssignment[] = {
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
};
static const channelMetaData_t channelMetaData[] = {
{ CH0, MPPT_A },
{ CH1, MPPT_A },
{ CH2, MPPT_B },
{ CH3, MPPT_B }
};
HM_4CH::HM_4CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {};
@ -86,3 +93,13 @@ uint8_t HM_4CH::getByteAssignmentSize() const
{
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
}
const channelMetaData_t* HM_4CH::getChannelMetaData() const
{
return channelMetaData;
}
uint8_t HM_4CH::getChannelMetaDataSize() const
{
return sizeof(channelMetaData) / sizeof(channelMetaData[0]);
}

View File

@ -10,4 +10,6 @@ public:
String typeName() const;
const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const;
};
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
};

View File

@ -298,3 +298,35 @@ void InverterAbstract::resetRadioStats()
{
RadioStats = {};
}
std::vector<ChannelNum_t> InverterAbstract::getChannelsDC() const
{
std::vector<ChannelNum_t> l;
for (uint8_t i = 0; i < getChannelMetaDataSize(); i++) {
l.push_back(getChannelMetaData()[i].ch);
}
return l;
}
std::vector<MpptNum_t> InverterAbstract::getMppts() const
{
std::vector<MpptNum_t> l;
for (uint8_t i = 0; i < getChannelMetaDataSize(); i++) {
auto m = getChannelMetaData()[i].mppt;
if (l.end() == std::find(l.begin(), l.end(), m)){
l.push_back(m);
}
}
return l;
}
std::vector<ChannelNum_t> InverterAbstract::getChannelsDCByMppt(const MpptNum_t mppt) const
{
std::vector<ChannelNum_t> l;
for (uint8_t i = 0; i < getChannelMetaDataSize(); i++) {
if (getChannelMetaData()[i].mppt == mppt) {
l.push_back(getChannelMetaData()[i].ch);
}
}
return l;
}

View File

@ -24,6 +24,20 @@ enum {
FRAGMENT_OK = 0
};
enum MpptNum_t {
MPPT_A = 0,
MPPT_B,
MPPT_C,
MPPT_D,
MPPT_CNT
};
// additional meta data per input channel
typedef struct {
ChannelNum_t ch; // channel 0 - 5
MpptNum_t mppt; // mppt a - d (0 - 3)
} channelMetaData_t;
#define MAX_RF_FRAGMENT_COUNT 13
class CommandAbstract;
@ -40,6 +54,9 @@ public:
virtual const byteAssign_t* getByteAssignment() const = 0;
virtual uint8_t getByteAssignmentSize() const = 0;
virtual const channelMetaData_t* getChannelMetaData() const = 0;
virtual uint8_t getChannelMetaDataSize() const = 0;
bool isProducing();
bool isReachable();
@ -112,6 +129,10 @@ public:
StatisticsParser* Statistics();
SystemConfigParaParser* SystemConfigPara();
std::vector<MpptNum_t> getMppts() const;
std::vector<ChannelNum_t> getChannelsDC() const;
std::vector<ChannelNum_t> getChannelsDCByMppt(const MpptNum_t mppt) const;
protected:
HoymilesRadio* _radio;

View File

@ -3,7 +3,6 @@
#include "PowerLimiterInverter.h"
#include "PowerLimiterBatteryInverter.h"
#include "PowerLimiterSolarInverter.h"
#include "inverters/HMS_4CH.h"
std::unique_ptr<PowerLimiterInverter> PowerLimiterInverter::create(
bool verboseLogging, PowerLimiterInverterConfig const& config)

View File

@ -1,6 +1,5 @@
#include "MessageOutput.h"
#include "PowerLimiterSolarInverter.h"
#include "inverters/HMS_4CH.h"
PowerLimiterSolarInverter::PowerLimiterSolarInverter(bool verboseLogging, PowerLimiterInverterConfig const& config)
: PowerLimiterInverter(verboseLogging, config) { }
@ -73,16 +72,13 @@ uint16_t PowerLimiterSolarInverter::scaleLimit(uint16_t expectedOutputWatts)
if (!isProducing()) { return expectedOutputWatts; }
auto pStats = _spInverter->Statistics();
std::list<ChannelNum_t> dcChnls = pStats->getChannelsByType(TYPE_DC);
std::vector<ChannelNum_t> dcChnls = _spInverter->getChannelsDC();
std::vector<MpptNum_t> dcMppts = _spInverter->getMppts();
size_t dcTotalChnls = dcChnls.size();
size_t dcTotalMppts = dcMppts.size();
// according to the upstream projects README (table with supported devs),
// every 2 channel inverter has 2 MPPTs. then there are the HM*S* 4 channel
// models which have 4 MPPTs. all others have a different number of MPPTs
// than inputs. those are not supported by the current scaling mechanism.
bool supported = dcTotalChnls == 2;
supported |= dcTotalChnls == 4 && HMS_4CH::isValidSerial(getSerial());
if (!supported) { return expectedOutputWatts; }
// if there is only one MPPT available, there is nothing we can do
if (dcTotalMppts <= 1) { return expectedOutputWatts; }
// test for a reasonable power limit that allows us to assume that an input
// channel with little energy is actually not producing, rather than
@ -101,37 +97,42 @@ uint16_t PowerLimiterSolarInverter::scaleLimit(uint16_t expectedOutputWatts)
inverterEfficiencyFactor = (inverterEfficiencyFactor > 0) ? inverterEfficiencyFactor/100 : 0.967;
// 98% of the expected power is good enough
auto expectedAcPowerPerChannel = (getCurrentLimitWatts() / dcTotalChnls) * 0.98;
auto expectedAcPowerPerMppt = (getCurrentLimitWatts() / dcTotalMppts) * 0.98;
if (_verboseLogging) {
MessageOutput.printf("%s expected AC power per channel %f W\r\n",
_logPrefix, expectedAcPowerPerChannel);
MessageOutput.printf("%s expected AC power per mppt %f W\r\n",
_logPrefix, expectedAcPowerPerMppt);
}
size_t dcShadedChnls = 0;
size_t dcShadedMppts = 0;
auto shadedChannelACPowerSum = 0.0;
for (auto& c : dcChnls) {
auto channelPowerAC = pStats->getChannelFieldValue(TYPE_DC, c, FLD_PDC) * inverterEfficiencyFactor;
for (auto& m : dcMppts) {
float mpptPowerAC = 0.0;
std::vector<ChannelNum_t> mpptChnls = _spInverter->getChannelsDCByMppt(m);
if (channelPowerAC < expectedAcPowerPerChannel) {
dcShadedChnls++;
shadedChannelACPowerSum += channelPowerAC;
for (auto& c : mpptChnls) {
mpptPowerAC += pStats->getChannelFieldValue(TYPE_DC, c, FLD_PDC) * inverterEfficiencyFactor;
}
if (mpptPowerAC < expectedAcPowerPerMppt) {
dcShadedMppts++;
shadedChannelACPowerSum += mpptPowerAC;
}
if (_verboseLogging) {
MessageOutput.printf("%s ch %d AC power %f W\r\n",
_logPrefix, c, channelPowerAC);
MessageOutput.printf("%s mppt-%c AC power %f W\r\n",
_logPrefix, m + 'a', mpptPowerAC);
}
}
// no shading or the shaded channels provide more power than what
// we currently need.
if (dcShadedChnls == 0 || shadedChannelACPowerSum >= expectedOutputWatts) {
if (dcShadedMppts == 0 || shadedChannelACPowerSum >= expectedOutputWatts) {
return expectedOutputWatts;
}
if (dcShadedChnls == dcTotalChnls) {
if (dcShadedMppts == dcTotalMppts) {
// keep the currentLimit when:
// - all channels are shaded
// - currentLimit >= expectedOutputWatts
@ -139,7 +140,7 @@ uint16_t PowerLimiterSolarInverter::scaleLimit(uint16_t expectedOutputWatts)
if (getCurrentLimitWatts() >= expectedOutputWatts &&
inverterOutputAC <= expectedOutputWatts) {
if (_verboseLogging) {
MessageOutput.printf("%s all channels are shaded, "
MessageOutput.printf("%s all mppts are shaded, "
"keeping the current limit of %d W\r\n",
_logPrefix, getCurrentLimitWatts());
}
@ -151,31 +152,38 @@ uint16_t PowerLimiterSolarInverter::scaleLimit(uint16_t expectedOutputWatts)
}
}
size_t dcNonShadedChnls = dcTotalChnls - dcShadedChnls;
uint16_t overScaledLimit = (expectedOutputWatts - shadedChannelACPowerSum) / dcNonShadedChnls * dcTotalChnls;
size_t dcNonShadedMppts = dcTotalMppts - dcShadedMppts;
uint16_t overScaledLimit = (expectedOutputWatts - shadedChannelACPowerSum) / dcNonShadedMppts * dcTotalMppts;
if (overScaledLimit <= expectedOutputWatts) { return expectedOutputWatts; }
if (_verboseLogging) {
MessageOutput.printf("%s %d/%d channels are shaded, scaling %d W\r\n",
_logPrefix, dcShadedChnls, dcTotalChnls, overScaledLimit);
MessageOutput.printf("%s %d/%d mppts are shaded, scaling %d W\r\n",
_logPrefix, dcShadedMppts, dcTotalMppts, overScaledLimit);
}
return overScaledLimit;
}
size_t dcProdChnls = 0;
for (auto& c : dcChnls) {
if (pStats->getChannelFieldValue(TYPE_DC, c, FLD_PDC) > 2.0) {
dcProdChnls++;
size_t dcProdMppts = 0;
for (auto& m : dcMppts) {
float dcPowerMppt = 0.0;
std::vector<ChannelNum_t> mpptChnls = _spInverter->getChannelsDCByMppt(m);
for (auto& c : mpptChnls) {
dcPowerMppt += pStats->getChannelFieldValue(TYPE_DC, c, FLD_PDC);
}
if (dcPowerMppt > 2.0 * mpptChnls.size()) {
dcProdMppts++;
}
}
if (dcProdChnls == 0 || dcProdChnls == dcTotalChnls) { return expectedOutputWatts; }
if (dcProdMppts == 0 || dcProdMppts == dcTotalMppts) { return expectedOutputWatts; }
uint16_t scaled = expectedOutputWatts / dcProdChnls * dcTotalChnls;
MessageOutput.printf("%s %d/%d channels are producing, scaling from %d to "
"%d W\r\n", _logPrefix, dcProdChnls, dcTotalChnls,
uint16_t scaled = expectedOutputWatts / dcProdMppts * dcTotalMppts;
MessageOutput.printf("%s %d/%d mppts are producing, scaling from %d to "
"%d W\r\n", _logPrefix, dcProdMppts, dcTotalMppts,
expectedOutputWatts, scaled);
return scaled;