From 8cbae767970cc356026be3ac87279f3852d9b036 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 13 Feb 2023 20:25:00 +0100 Subject: [PATCH] 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. --- include/Configuration.h | 1 + lib/Hoymiles/src/parser/StatisticsParser.cpp | 34 ++++++++++++++++++++ lib/Hoymiles/src/parser/StatisticsParser.h | 13 ++++++++ src/Configuration.cpp | 2 ++ src/WebApi_inverter.cpp | 3 ++ src/main.cpp | 1 + webapp/src/locales/de.json | 2 ++ webapp/src/locales/en.json | 2 ++ webapp/src/locales/fr.json | 2 ++ webapp/src/views/InverterAdminView.vue | 22 +++++++++++-- 10 files changed, 80 insertions(+), 2 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 7f3d7db4..15c037ae 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -34,6 +34,7 @@ struct CHANNEL_CONFIG_T { uint16_t MaxChannelPower; char Name[CHAN_MAX_NAME_STRLEN]; + float YieldTotalOffset; }; struct INVERTER_CONFIG_T { diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 79ff16da..0af50eda 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -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(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 StatisticsParser::getChannelTypes() { return { diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index 36116790..c52514ad 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -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* 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 getChannelTypes(); const char* getChannelTypeName(ChannelType_t type); std::list getChannelsByType(ChannelType_t type); @@ -113,6 +125,7 @@ private: uint16_t _stringMaxPower[CH4]; const std::list* _byteAssignment; + std::list _fieldSettings; uint32_t _rxFailureCount = 0; }; \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 47142e73..8ec7a9e8 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -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)); } } diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index 4305c87d..77a9c887 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -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(); + inverter.channel[arrayCount].YieldTotalOffset = channel[F("yield_total_offset")].as(); 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(c), FLD_YT, inverter.channel[c].YieldTotalOffset); } } diff --git a/src/main.cpp b/src/main.cpp index 56aa65b0..273a7bb6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset); } } MessageOutput.println(F(" done")); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index c63fa352..711ffedd 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -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 Wp des Ports ein, um die Einstrahlung zu errechnen.", "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 655a0560..cd8bb553 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -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 Wp of the channel to calculate irradiation.", "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index a5ecbc82..f9764a57 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -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 Wp du canal pour calculer l'irradiation.", "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", diff --git a/webapp/src/views/InverterAdminView.vue b/webapp/src/views/InverterAdminView.vue index e5c441e1..b30f0834 100644 --- a/webapp/src/views/InverterAdminView.vue +++ b/webapp/src/views/InverterAdminView.vue @@ -55,7 +55,7 @@
@@ -163,6 +180,7 @@ import { defineComponent } from 'vue'; declare interface Channel { name: string; max_power: number; + yield_total_offset: number; } declare interface Inverter {