Merge remote-tracking branch 'tbnobody/OpenDTU/master' into development

This commit is contained in:
helgeerbe 2023-02-16 12:35:18 +01:00
commit 27f20a76f0
15 changed files with 89 additions and 11 deletions

View File

@ -34,6 +34,7 @@
struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower;
char Name[CHAN_MAX_NAME_STRLEN];
float YieldTotalOffset;
};
struct INVERTER_CONFIG_T {

View File

@ -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 {

View File

@ -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;
};

View File

@ -15,7 +15,7 @@ extra_configs =
[env]
framework = arduino
platform = espressif32@>=6.0.0
platform = espressif32@>=6.0.1
build_flags =
-DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz
@ -23,7 +23,7 @@ build_flags =
lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer
bblanchon/ArduinoJson @ ^6.20.0
bblanchon/ArduinoJson @ ^6.20.1
https://github.com/bertmelis/espMqttClient.git#v1.3.3
nrf24/RF24 @ ^1.4.5
olikraus/U8g2 @ ^2.34.13

View File

@ -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;
}
}
@ -233,6 +234,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));
}
}

View File

@ -117,13 +117,13 @@ void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, Ch
if (type != TYPE_DC) {
name = String(inv->name()) + " " + fieldName;
} else {
name = String(inv->name()) + " CH" + String(channel) + " " + fieldName;
name = String(inv->name()) + " CH" + chanNum + " " + fieldName;
}
DynamicJsonDocument root(1024);
root[F("name")] = name;
root[F("stat_t")] = stateTopic;
root[F("uniq_id")] = serial + "_ch" + String(channel) + "_" + fieldName;
root[F("uniq_id")] = serial + "_ch" + chanNum + "_" + fieldName;
String unit_of_measure = inv->Statistics()->getChannelFieldUnit(type, channel, fieldType.fieldId);
if (unit_of_measure != "") {

View File

@ -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);
}
}

View File

@ -155,6 +155,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"));

View File

@ -31,7 +31,7 @@
"eslint": "^8.34.0",
"eslint-plugin-vue": "^9.9.0",
"npm-run-all": "^4.1.5",
"sass": "^1.58.0",
"sass": "^1.58.1",
"typescript": "^4.9.5",
"vite": "^4.1.1",
"vite-plugin-compression": "^0.5.1",

View File

@ -453,6 +453,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",

View File

@ -453,6 +453,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",

View File

@ -453,6 +453,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",

View File

@ -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 {

View File

@ -1888,10 +1888,10 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3"
is-regex "^1.1.4"
sass@^1.58.0:
version "1.58.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc"
integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==
sass@^1.58.1:
version "1.58.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.1.tgz#17ab0390076a50578ed0733f1cc45429e03405f6"
integrity sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"

Binary file not shown.