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 7f3d7db..15c037a 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 79ff16d..0af50ed 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 3611679..c52514a 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 47142e7..8ec7a9e 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 4305c87..77a9c88 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 56aa65b..273a7bb 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 c63fa35..711ffed 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 655a056..cd8bb55 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 a5ecbc8..f9764a5 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 e5c441e..b30f083 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 {