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

This commit is contained in:
helgeerbe 2023-02-27 07:42:46 +01:00
commit 3b62d5708a
17 changed files with 148 additions and 70 deletions

View File

@ -16,7 +16,7 @@
"mdc": 23,
"mdio": 18,
"type": 0,
"clk_mode": 3
"clk_mode": 0
}
}
]

View File

@ -35,6 +35,8 @@ public:
static bool checkCredentials(AsyncWebServerRequest* request);
static bool checkCredentialsReadonly(AsyncWebServerRequest* request);
static void sendTooManyRequests(AsyncWebServerRequest* request);
private:
AsyncWebServer _server;
AsyncEventSource _events;

View File

@ -3,6 +3,7 @@
#include <ESPAsyncWebServer.h>
#include <Hoymiles.h>
#include <map>
class WebApiPrometheusClass {
public:
@ -15,4 +16,28 @@ private:
void addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, const char* channelName = NULL);
AsyncWebServer* _server;
enum {
METRIC_TYPE_NONE = 0,
METRIC_TYPE_GAUGE,
METRIC_TYPE_COUNTER,
};
const char* _metricTypes[3] = { 0, "gauge", "counter" };
std::map<FieldId_t, uint8_t> _fieldMetricAssignment {
{ FLD_UDC, METRIC_TYPE_GAUGE },
{ FLD_IDC, METRIC_TYPE_GAUGE },
{ FLD_PDC, METRIC_TYPE_GAUGE },
{ FLD_YD, METRIC_TYPE_COUNTER },
{ FLD_YT, METRIC_TYPE_COUNTER },
{ FLD_UAC, METRIC_TYPE_GAUGE },
{ FLD_IAC, METRIC_TYPE_GAUGE },
{ FLD_PAC, METRIC_TYPE_GAUGE },
{ FLD_F, METRIC_TYPE_GAUGE },
{ FLD_T, METRIC_TYPE_GAUGE },
{ FLD_PF, METRIC_TYPE_GAUGE },
{ FLD_EFF, METRIC_TYPE_GAUGE },
{ FLD_IRR, METRIC_TYPE_GAUGE },
{ FLD_PRA, METRIC_TYPE_GAUGE }
};
};

View File

@ -34,7 +34,7 @@ String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id)
case 3:
reason_str = F("Software reset digital core");
break;
#ifndef CONFIG_IDF_TARGET_ESP32C3
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
case 4:
reason_str = F("Legacy watch dog reset digital core");
break;
@ -42,7 +42,7 @@ String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id)
case 5:
reason_str = F("Deep Sleep reset digital core");
break;
#ifndef CONFIG_IDF_TARGET_ESP32C3
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
case 6:
reason_str = F("Reset by SLC module, reset digital core");
break;
@ -68,9 +68,9 @@ String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id)
case 13:
reason_str = F("RTC Watch dog Reset CPU");
break;
#ifndef CONFIG_IDF_TARGET_ESP32C3
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
case 14:
reason_str = F("for APP CPU, reseted by PRO CPU");
reason_str = F("for APP CPU, reset by PRO CPU");
break;
#endif
case 15:
@ -100,7 +100,7 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id)
case 3:
reason_str = F("SW_RESET");
break;
#ifndef CONFIG_IDF_TARGET_ESP32C3
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
case 4:
reason_str = F("OWDT_RESET");
break;
@ -108,7 +108,7 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id)
case 5:
reason_str = F("DEEPSLEEP_RESET");
break;
#ifndef CONFIG_IDF_TARGET_ESP32C3
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
case 6:
reason_str = F("SDIO_RESET");
break;
@ -134,7 +134,7 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id)
case 13:
reason_str = F("RTCWDT_CPU_RESET");
break;
#ifndef CONFIG_IDF_TARGET_ESP32C3
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
case 14:
reason_str = F("EXT_CPU_RESET");
break;
@ -152,4 +152,4 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id)
return reason_str;
}
ResetReasonClass ResetReason;
ResetReasonClass ResetReason;

View File

@ -98,4 +98,11 @@ bool WebApiClass::checkCredentialsReadonly(AsyncWebServerRequest* request)
}
}
void WebApiClass::sendTooManyRequests(AsyncWebServerRequest* request)
{
auto response = request->beginResponse(429, "text/plain", "Too Many Requests");
response->addHeader("Retry-After", "60");
request->send(response);
}
WebApiClass WebApi;

View File

@ -5,7 +5,9 @@
*/
#include "WebApi_prometheus.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "NetworkSettings.h"
#include "WebApi.h"
#include <Hoymiles.h>
#include "MessageOutput.h"
@ -23,10 +25,10 @@ void WebApiPrometheusClass::loop()
}
void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* request)
{
{
try {
auto stream = request->beginResponseStream("text/plain; charset=utf-8", 40960);
stream->print(F("# HELP opendtu_build Build info\n"));
stream->print(F("# TYPE opendtu_build gauge\n"));
stream->printf("opendtu_build{name=\"%s\",id=\"%s\",version=\"%d.%d.%d\"} 1\n",
@ -64,39 +66,39 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%d\",name=\"%s\"} %d\n",
serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000);
// Loop all channels
for (auto& t : inv->Statistics()->getChannelTypes()) {
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
addField(stream, serial, i, inv, t, c, FLD_PAC);
addField(stream, serial, i, inv, t, c, FLD_UAC);
addField(stream, serial, i, inv, t, c, FLD_IAC);
if (t == TYPE_AC) {
addField(stream, serial, i, inv, t, c, FLD_PDC, "PowerDC");
} else {
addField(stream, serial, i, inv, t, c, FLD_PDC);
// Loop all channels if Statistics have been updated at least once since DTU boot
if (inv->Statistics()->getLastUpdate() > 0) {
for (auto& t : inv->Statistics()->getChannelTypes()) {
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
addField(stream, serial, i, inv, t, c, FLD_PAC);
addField(stream, serial, i, inv, t, c, FLD_UAC);
addField(stream, serial, i, inv, t, c, FLD_IAC);
if (t == TYPE_AC) {
addField(stream, serial, i, inv, t, c, FLD_PDC, "PowerDC");
} else {
addField(stream, serial, i, inv, t, c, FLD_PDC);
}
addField(stream, serial, i, inv, t, c, FLD_UDC);
addField(stream, serial, i, inv, t, c, FLD_IDC);
addField(stream, serial, i, inv, t, c, FLD_YD);
addField(stream, serial, i, inv, t, c, FLD_YT);
addField(stream, serial, i, inv, t, c, FLD_F);
addField(stream, serial, i, inv, t, c, FLD_T);
addField(stream, serial, i, inv, t, c, FLD_PF);
addField(stream, serial, i, inv, t, c, FLD_PRA);
addField(stream, serial, i, inv, t, c, FLD_EFF);
addField(stream, serial, i, inv, t, c, FLD_IRR);
}
addField(stream, serial, i, inv, t, c, FLD_UDC);
addField(stream, serial, i, inv, t, c, FLD_IDC);
addField(stream, serial, i, inv, t, c, FLD_YD);
addField(stream, serial, i, inv, t, c, FLD_YT);
addField(stream, serial, i, inv, t, c, FLD_F);
addField(stream, serial, i, inv, t, c, FLD_T);
addField(stream, serial, i, inv, t, c, FLD_PF);
addField(stream, serial, i, inv, t, c, FLD_PRA);
addField(stream, serial, i, inv, t, c, FLD_EFF);
addField(stream, serial, i, inv, t, c, FLD_IRR);
}
}
}
stream->addHeader(F("Cache-Control"), F("no-cache"));
request->send(stream);
}
catch (std::bad_alloc& bad_alloc) {
} catch (std::bad_alloc& bad_alloc) {
MessageOutput.printf("Call to /api/prometheus/metrics temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
auto response = request->beginResponse(429, "text/plain", "Too Many Requests");
response->addHeader("Retry-After", "60");
request->send(response);
WebApi.sendTooManyRequests(request);
}
}
@ -106,7 +108,7 @@ void WebApiPrometheusClass::addField(AsyncResponseStream* stream, String& serial
const char* chanName = (channelName == NULL) ? inv->Statistics()->getChannelFieldName(type, channel, fieldId) : channelName;
if (idx == 0 && type == TYPE_AC && channel == 0) {
stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(type, channel, fieldId));
stream->printf("# TYPE opendtu_%s gauge\n", chanName);
stream->printf("# TYPE opendtu_%s %s\n", chanName, _metricTypes[_fieldMetricAssignment[fieldId]]);
}
stream->printf("opendtu_%s{serial=\"%s\",unit=\"%d\",name=\"%s\",type=\"%s\",channel=\"%d\"} %f\n",
chanName,

View File

@ -59,6 +59,7 @@ void WebApiWsLiveClass::loop()
// Update on every inverter change or at least after 10 seconds
if (millis() - _lastWsPublish > (10 * 1000) || (maxTimeStamp != _newestInverterTimestamp)) {
try {
DynamicJsonDocument root(40960);
JsonVariant var = root;
@ -77,11 +78,11 @@ void WebApiWsLiveClass::loop()
_ws.textAll(buffer);
}
_lastWsPublish = millis();
}
catch (std::bad_alloc& bad_alloc) {
} catch (std::bad_alloc& bad_alloc) {
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
}
_lastWsPublish = millis();
}
}
@ -224,11 +225,18 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
return;
}
AsyncJsonResponse* response = new AsyncJsonResponse(false, 40960U);
JsonVariant root = response->getRoot();
try {
AsyncJsonResponse* response = new AsyncJsonResponse(false, 40960U);
JsonVariant root = response->getRoot();
generateJsonResponse(root);
generateJsonResponse(root);
response->setLength();
request->send(response);
response->setLength();
request->send(response);
} catch (std::bad_alloc& bad_alloc) {
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
WebApi.sendTooManyRequests(request);
}
}

View File

@ -55,6 +55,7 @@ void WebApiWsVedirectLiveClass::loop()
// Update on ve.direct change or at least after 10 seconds
if (millis() - _lastWsPublish > (10 * 1000) || (maxTimeStamp != _newestVedirectTimestamp)) {
try {
DynamicJsonDocument root(1024);
JsonVariant var = root;
@ -73,11 +74,11 @@ void WebApiWsVedirectLiveClass::loop()
_ws.textAll(buffer);
}
_lastWsPublish = millis();
}
catch (std::bad_alloc& bad_alloc) {
} catch (std::bad_alloc& bad_alloc) {
MessageOutput.printf("Call to /api/vedirectlivedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
}
_lastWsPublish = millis();
}
}

View File

@ -6,6 +6,18 @@
</BootstrapAlert>
<table v-if="devInfoList.valid_data" class="table table-hover">
<tbody>
<tr>
<td>{{ $t('devinfo.Serial') }}</td>
<td>{{ devInfoList.serial }}</td>
</tr>
<tr>
<td>{{ $t('devinfo.ProdYear') }}</td>
<td>{{ productionYear() }}</td>
</tr>
<tr>
<td>{{ $t('devinfo.ProdWeek') }}</td>
<td>{{ productionWeek() }}</td>
</tr>
<tr>
<td>{{ $t('devinfo.Model') }}</td>
<td v-if="devInfoList.hw_model_name != ''">{{ devInfoList.hw_model_name }}</td>
@ -61,6 +73,16 @@ export default defineComponent({
const version_patch = Math.floor((value - version_major * 10000 - version_minor * 100));
return version_major + "." + version_minor + "." + version_patch;
};
},
productionYear() {
return() => {
return ((parseInt(this.devInfoList.serial.toString(), 16) >> (7 * 4)) & 0xF) + 2014;
}
},
productionWeek() {
return() => {
return ((parseInt(this.devInfoList.serial.toString(), 16) >> (5 * 4)) & 0xFF).toString(16);
}
}
}
});

View File

@ -159,6 +159,9 @@
"NoInfo": "Keine Informationen verfügbar",
"NoInfoLong": "Bisher wurden noch keine gültigen Daten vom Wechselrichter empfangen. Versuche es weiter...",
"UnknownModel": "Unbekanntes Modell! Bitte melden Sie die \"Hardware Teilenummer\" und das Modell (z.B. HM-350) <a href=\"https://github.com/tbnobody/OpenDTU/issues\" target=\"_blank\">hier</a> als Problem.",
"Serial": "Seriennummer",
"ProdYear": "Produktionsjahr",
"ProdWeek": "Produktionswoche",
"Model": "Modell",
"DetectedMaxPower": "Ermittelte max. Leistung",
"BootloaderVersion": "Bootloader-Version",

View File

@ -159,6 +159,9 @@
"NoInfo": "No Information available",
"NoInfoLong": "Did not receive any valid data from the inverter till now. Still trying...",
"UnknownModel": "Unknown model! Please report the \"Hardware Part Number\" and model (e.g. HM-350) as an issue <a href=\"https://github.com/tbnobody/OpenDTU/issues\" target=\"_blank\">here</a>.",
"Serial": "Serial",
"ProdYear": "Production Year",
"ProdWeek": "Production Week",
"Model": "Model",
"DetectedMaxPower": "Detected max. Power",
"BootloaderVersion": "Bootloader Version",

View File

@ -2,14 +2,14 @@
"menu": {
"LiveView": "Direct",
"Settings": "Paramètres",
"NetworkSettings": "Paramètres réseau",
"NTPSettings": "Paramètres NTP",
"MQTTSettings": "Paramètres MQTT",
"InverterSettings": "Paramètres des onduleurs",
"SecuritySettings": "Paramètres de sécurité",
"DTUSettings": "Paramètres DTU",
"DeviceManager": "Gestionnaire de périphériques",
"VedirectSettings": "Paramètres Ve.direct",
"NetworkSettings": "Réseau",
"NTPSettings": "Heure locale",
"MQTTSettings": "MQTT",
"InverterSettings": "Onduleurs",
"SecuritySettings": "Sécurité",
"DTUSettings": "DTU",
"DeviceManager": "Périphériques",
"VedirectSettings": "Ve.direct",
"ConfigManagement": "Gestion de la configuration",
"FirmwareUpgrade": "Mise à jour du firmware",
"DeviceReboot": "Redémarrage de l'appareil",
@ -158,6 +158,9 @@
"NoInfo": "Aucune information disponible",
"NoInfoLong": "N'a pas reçu de données valides de l'onduleur jusqu'à présent. J'essaie toujours...",
"UnknownModel": "Modèle inconnu ! Veuillez signaler le \"Numéro d'article matériel\" et le modèle (par exemple, HM-350) comme un problème <a href=\"https://github.com/tbnobody/OpenDTU/issues\" target=\"_blank\">ici</a>.",
"Serial": "Serial",
"ProdYear": "Production Year",
"ProdWeek": "Production Week",
"Model": "Modèle",
"DetectedMaxPower": "Puissance maximale détectée",
"BootloaderVersion": "Version du bootloader",
@ -372,7 +375,7 @@
"TimeServerHint": "La valeur par défaut convient tant que OpenDTU a un accès direct à Internet.",
"Timezone": "Fuseau horaire",
"TimezoneConfig": "Configuration du fuseau horaire",
"LocationConfiguration": "Location Configuration",
"LocationConfiguration": "Géolocalisation",
"Longitude": "Longitude",
"Latitude": "Latitude",
"Save": "@:dtuadmin.Save",
@ -450,10 +453,10 @@
"Add": "Ajouter",
"AddHint": " <b>Astuce :</b> Vous pouvez définir des paramètres supplémentaires après avoir créé l'onduleur. Utilisez l'icône du stylo dans la liste des onduleurs.",
"InverterList": "Liste des onduleurs",
"Status": "Status",
"Send": "Send",
"Receive": "Receive",
"StatusHint": "<b>Astuce :</b> The inverter is power by it's DC input. If there is no sun, the inverter is off. Requests can still be sent.",
"Status": "État",
"Send": "Envoyer",
"Receive": "Recevoir",
"StatusHint": "<b>Astuce :</b> L'onduleur est alimenté par son entrée courant continu. S'il n'y a pas de soleil, l'onduleur est éteint, mais les requêtes peuvent toujours être envoyées.",
"Type": "Type",
"Action": "Action",
"DeleteInverter": "Supprimer l'onduleur",
@ -462,16 +465,16 @@
"InverterName": "Nom de l'onduleur :",
"InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.",
"InverterStatus": "Receive / Send",
"PollEnable": "Poll inverter data",
"PollEnableNight": "Poll inverter data at night",
"CommandEnable": "Send commands",
"CommandEnableNight": "Send commands at night",
"PollEnable": "Interroger les données de l'onduleur",
"PollEnableNight": "Interroger les données de l'onduleur la nuit",
"CommandEnable": "Envoyer des commandes",
"CommandEnableNight": "Envoyer des commandes la nuit",
"StringName": "Nom de la ligne {num}:",
"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.",
"StringYtOffset": "Décalage du rendement total de la ligne {num} :",
"StringYtOffsetHint": "Ce décalage est appliqué à la valeur de rendement total lue sur le variateur. Il peut être utilisé pour mettre le rendement total du variateur à zéro si un variateur usagé est utilisé.",
"InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.",
"Cancel": "@:maintenancereboot.Cancel",
"Save": "@:dtuadmin.Save",
@ -483,8 +486,8 @@
"BackupHeader": "Sauvegarder le fichier de configuration",
"BackupConfig": "Fichier de configuration",
"Backup": "Sauvegarder",
"Restore": "Restore",
"NoFileSelected": "No file selected",
"Restore": "Restaurer",
"NoFileSelected": "Aucun fichier sélectionné",
"RestoreHeader": "Restaurer le fichier de configuration",
"Back": "Retour",
"UploadSuccess": "Succès du téléversement",

View File

@ -1,4 +1,5 @@
export interface DevInfoStatus {
serial: number;
valid_data: boolean;
fw_bootloader_version: number;
fw_build_version: number;

View File

@ -543,6 +543,7 @@ export default defineComponent({
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.devInfoList = data[serial][0];
this.devInfoList.serial = serial;
this.devInfoLoading = false;
});

Binary file not shown.

Binary file not shown.

Binary file not shown.