Implement offsets for YieldTotal (#549)
This allows to enter a offset in kWh in the inverter properties which will be applied to the read Yield Total value of the inverter. Using this can set your total production to zero if you e.g. are using a used device.
This commit is contained in:
parent
9681edea19
commit
8cbae76797
@ -34,6 +34,7 @@
|
||||
struct CHANNEL_CONFIG_T {
|
||||
uint16_t MaxChannelPower;
|
||||
char Name[CHAN_MAX_NAME_STRLEN];
|
||||
float YieldTotalOffset;
|
||||
};
|
||||
|
||||
struct INVERTER_CONFIG_T {
|
||||
|
||||
@ -59,9 +59,21 @@ const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fieldSettings_t* StatisticsParser::getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
|
||||
{
|
||||
for (auto& i : _fieldSettings) {
|
||||
if (i.type == type && i.ch == channel && i.fieldId == fieldId) {
|
||||
return &i;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
|
||||
{
|
||||
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
|
||||
fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId);
|
||||
|
||||
if (pos == NULL) {
|
||||
return 0;
|
||||
}
|
||||
@ -88,6 +100,9 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch
|
||||
}
|
||||
|
||||
result /= static_cast<float>(div);
|
||||
if (setting != NULL) {
|
||||
result += setting->offset;
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Value has to be calculated
|
||||
@ -121,6 +136,25 @@ uint8_t StatisticsParser::getChannelFieldDigits(ChannelType_t type, ChannelNum_t
|
||||
return pos->digits;
|
||||
}
|
||||
|
||||
float StatisticsParser::getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
|
||||
{
|
||||
fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId);
|
||||
if (setting != NULL) {
|
||||
return setting->offset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StatisticsParser::setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset)
|
||||
{
|
||||
fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId);
|
||||
if (setting != NULL) {
|
||||
setting->offset = offset;
|
||||
} else {
|
||||
_fieldSettings.push_back({ type, channel, fieldId, offset });
|
||||
}
|
||||
}
|
||||
|
||||
std::list<ChannelType_t> StatisticsParser::getChannelTypes()
|
||||
{
|
||||
return {
|
||||
|
||||
@ -82,6 +82,13 @@ typedef struct {
|
||||
uint8_t digits; // number of valid digits after the decimal point
|
||||
} byteAssign_t;
|
||||
|
||||
typedef struct {
|
||||
ChannelType_t type;
|
||||
ChannelNum_t ch; // channel 0 - 4
|
||||
FieldId_t fieldId; // field id
|
||||
float offset; // offset (positive/negative) to be applied on the fetched value
|
||||
} fieldSettings_t;
|
||||
|
||||
class StatisticsParser : public Parser {
|
||||
public:
|
||||
void clearBuffer();
|
||||
@ -90,12 +97,17 @@ public:
|
||||
void setByteAssignment(const std::list<byteAssign_t>* byteAssignment);
|
||||
|
||||
const byteAssign_t* getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
fieldSettings_t* getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
|
||||
float getChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
bool hasChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
const char* getChannelFieldUnit(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
const char* getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
uint8_t getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
|
||||
float getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||
void setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset);
|
||||
|
||||
std::list<ChannelType_t> getChannelTypes();
|
||||
const char* getChannelTypeName(ChannelType_t type);
|
||||
std::list<ChannelNum_t> getChannelsByType(ChannelType_t type);
|
||||
@ -113,6 +125,7 @@ private:
|
||||
uint16_t _stringMaxPower[CH4];
|
||||
|
||||
const std::list<byteAssign_t>* _byteAssignment;
|
||||
std::list<fieldSettings_t> _fieldSettings;
|
||||
|
||||
uint32_t _rxFailureCount = 0;
|
||||
};
|
||||
@ -100,6 +100,7 @@ bool ConfigurationClass::write()
|
||||
JsonObject chanData = channel.createNestedObject();
|
||||
chanData["name"] = config.Inverter[i].channel[c].Name;
|
||||
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
|
||||
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,6 +229,7 @@ bool ConfigurationClass::read()
|
||||
JsonArray channel = inv["channel"];
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
config.Inverter[i].channel[c].MaxChannelPower = channel[c]["max_power"] | 0;
|
||||
config.Inverter[i].channel[c].YieldTotalOffset = channel[c]["yield_total_offset"] | 0.0f;
|
||||
strlcpy(config.Inverter[i].channel[c].Name, channel[c]["name"] | "", sizeof(config.Inverter[i].channel[c].Name));
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +67,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
||||
JsonObject chanData = channel.createNestedObject();
|
||||
chanData["name"] = config.Inverter[i].channel[c].Name;
|
||||
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
|
||||
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -267,6 +268,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
uint8_t arrayCount = 0;
|
||||
for (JsonVariant channel : channelArray) {
|
||||
inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as<uint16_t>();
|
||||
inverter.channel[arrayCount].YieldTotalOffset = channel[F("yield_total_offset")].as<float>();
|
||||
strncpy(inverter.channel[arrayCount].Name, channel[F("name")] | "", sizeof(inverter.channel[arrayCount].Name));
|
||||
arrayCount++;
|
||||
}
|
||||
@ -297,6 +299,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
||||
if (inv != nullptr) {
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower);
|
||||
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -150,6 +150,7 @@ void setup()
|
||||
if (inv != nullptr) {
|
||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||
inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
|
||||
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset);
|
||||
}
|
||||
}
|
||||
MessageOutput.println(F(" done"));
|
||||
|
||||
@ -404,6 +404,8 @@
|
||||
"StringNameHint": "Hier kann ein eigener Name für den entsprechenden Port des Wechselrichters angegeben werden.",
|
||||
"StringMaxPower": "Max. Leistung String {num}:",
|
||||
"StringMaxPowerHint": "Eingabe der maximalen Leistung der angeschlossenen Solarmodule.",
|
||||
"StringYtOffset": "Ertragsversatz String {num}:",
|
||||
"StringYtOffsetHint": "Dieser Offset wird beim Auslesen des Gesamtertragswertes des Wechselrichters angewendet. Damit kann der Gesamtertrag des Wechselrichters auf Null gesetzt werden, wenn ein gebrauchter Wechselrichter verwendet wird.",
|
||||
"InverterHint": "*) Geben Sie die W<sub>p</sub> des Ports ein, um die Einstrahlung zu errechnen.",
|
||||
"Cancel": "@:maintenancereboot.Cancel",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
|
||||
@ -404,6 +404,8 @@
|
||||
"StringNameHint": "Here you can specify a custom name for the respective port of your inverter.",
|
||||
"StringMaxPower": "Max power string {num}:",
|
||||
"StringMaxPowerHint": "Enter the max power of the connected solar panels.",
|
||||
"StringYtOffset": "Yield total offset string {num}:",
|
||||
"StringYtOffsetHint": "This offset is applied the read yield total value from the inverter. This can be used to set the yield total of the inverter to zero if a used inverter is used.",
|
||||
"InverterHint": "*) Enter the W<sub>p</sub> of the channel to calculate irradiation.",
|
||||
"Cancel": "@:maintenancereboot.Cancel",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
|
||||
@ -404,6 +404,8 @@
|
||||
"StringNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour le port respectif de votre onduleur.",
|
||||
"StringMaxPower": "Puissance maximale de la ligne {num}:",
|
||||
"StringMaxPowerHint": "Entrez la puissance maximale des panneaux solaires connectés.",
|
||||
"StringYtOffset": "Yield total offset string {num}:",
|
||||
"StringYtOffsetHint": "This offset is applied the read yield total value from the inverter. This can be used to set the yield total of the inverter to zero if a used inverter is used.",
|
||||
"InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.",
|
||||
"Cancel": "@:maintenancereboot.Cancel",
|
||||
"Save": "@:dtuadmin.Save",
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
</BasePage>
|
||||
|
||||
<div class="modal" id="inverterEdit" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ $t('inverteradmin.EditInverter') }}</h5>
|
||||
@ -90,7 +90,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<label :for="`inverter-max_${index}`" class="col-form-label">
|
||||
{{ $t('inverteradmin.StringMaxPower', { num: index + 1 }) }}
|
||||
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.StringMaxPowerHint')" />
|
||||
@ -105,6 +107,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label :for="`inverter-ytoffset_${index}`" class="col-form-label">
|
||||
{{ $t('inverteradmin.StringYtOffset', { num: index + 1 }) }}
|
||||
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.StringYtOffsetHint')" />
|
||||
</label>
|
||||
<div class="d-flex mb-2">
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" :id="`inverter-ytoffset_${index}`"
|
||||
min="0" v-model="selectedInverterData.channel[index].yield_total_offset"
|
||||
:aria-describedby="`inverter-ytoffsetDescription_${index} inverter-customizer`" />
|
||||
<span class="input-group-text"
|
||||
:id="`inverter-ytoffsetDescription_${index}`">kWh</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :id="`inverter-customizer`" class="form-text" v-html="$t('inverteradmin.InverterHint')">
|
||||
@ -163,6 +180,7 @@ import { defineComponent } from 'vue';
|
||||
declare interface Channel {
|
||||
name: string;
|
||||
max_power: number;
|
||||
yield_total_offset: number;
|
||||
}
|
||||
|
||||
declare interface Inverter {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user