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 } { 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) HERF_1CH::HERF_1CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -53,3 +57,13 @@ uint8_t HERF_1CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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) HERF_2CH::HERF_2CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -60,3 +65,13 @@ uint8_t HERF_2CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_1CH::HMS_1CH(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {}; : HMS_Abstract(radio, serial) {};
@ -52,3 +56,13 @@ uint8_t HMS_1CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_1CHv2::HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {}; : HMS_Abstract(radio, serial) {};
@ -52,3 +56,13 @@ uint8_t HMS_1CHv2::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_2CH::HMS_2CH(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {}; : HMS_Abstract(radio, serial) {};
@ -59,3 +64,13 @@ uint8_t HMS_2CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_4CH::HMS_4CH(HoymilesRadio* radio, const uint64_t serial)
: HMS_Abstract(radio, serial) {}; : HMS_Abstract(radio, serial) {};
@ -73,3 +80,13 @@ uint8_t HMS_4CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_4CH::HMT_4CH(HoymilesRadio* radio, const uint64_t serial)
: HMT_Abstract(radio, serial) {}; : HMT_Abstract(radio, serial) {};
@ -82,3 +89,13 @@ uint8_t HMT_4CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_6CH::HMT_6CH(HoymilesRadio* radio, const uint64_t serial)
: HMT_Abstract(radio, serial) {}; : HMT_Abstract(radio, serial) {};
@ -96,3 +105,13 @@ uint8_t HMT_6CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_1CH::HM_1CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -65,3 +69,13 @@ uint8_t HM_1CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_2CH::HM_2CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -73,3 +78,13 @@ uint8_t HM_2CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() 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 } { 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_4CH::HM_4CH(HoymilesRadio* radio, const uint64_t serial)
: HM_Abstract(radio, serial) {}; : HM_Abstract(radio, serial) {};
@ -86,3 +93,13 @@ uint8_t HM_4CH::getByteAssignmentSize() const
{ {
return sizeof(byteAssignment) / sizeof(byteAssignment[0]); 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; String typeName() const;
const byteAssign_t* getByteAssignment() const; const byteAssign_t* getByteAssignment() const;
uint8_t getByteAssignmentSize() const; uint8_t getByteAssignmentSize() const;
const channelMetaData_t* getChannelMetaData() const;
uint8_t getChannelMetaDataSize() const;
}; };

View File

@ -298,3 +298,35 @@ void InverterAbstract::resetRadioStats()
{ {
RadioStats = {}; 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 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 #define MAX_RF_FRAGMENT_COUNT 13
class CommandAbstract; class CommandAbstract;
@ -40,6 +54,9 @@ public:
virtual const byteAssign_t* getByteAssignment() const = 0; virtual const byteAssign_t* getByteAssignment() const = 0;
virtual uint8_t getByteAssignmentSize() const = 0; virtual uint8_t getByteAssignmentSize() const = 0;
virtual const channelMetaData_t* getChannelMetaData() const = 0;
virtual uint8_t getChannelMetaDataSize() const = 0;
bool isProducing(); bool isProducing();
bool isReachable(); bool isReachable();
@ -112,6 +129,10 @@ public:
StatisticsParser* Statistics(); StatisticsParser* Statistics();
SystemConfigParaParser* SystemConfigPara(); 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: protected:
HoymilesRadio* _radio; HoymilesRadio* _radio;

View File

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

View File

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