From a54b19bf5bb5b3a4771f29e961d87cd2119f4e74 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sun, 22 Sep 2024 12:44:48 +0200 Subject: [PATCH] Feature: Inverter radio statistics (rx/tx statistics) The statistics are shown in the WebApp and published via MQTT. Statistics are reset at midnight. --- lib/Hoymiles/src/Hoymiles.cpp | 1 + lib/Hoymiles/src/HoymilesRadio.cpp | 19 ++++ .../src/inverters/InverterAbstract.cpp | 5 + lib/Hoymiles/src/inverters/InverterAbstract.h | 22 +++++ src/MqttHandleInverter.cpp | 8 ++ src/WebApi_ws_live.cpp | 6 ++ webapp/src/locales/de.json | 9 +- webapp/src/locales/en.json | 9 +- webapp/src/locales/fr.json | 9 +- webapp/src/types/LiveDataStatus.ts | 10 ++ webapp/src/views/HomeView.vue | 94 +++++++++++++++++++ 11 files changed, 189 insertions(+), 3 deletions(-) diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index 0e60301..ccc1a70 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -145,6 +145,7 @@ void HoymilesClass::loop() if (inv->getClearEventlogOnMidnight()) { inv->EventLog()->clearBuffer(); } + inv->resetRadioStats(); } lastWeekDay = currentWeekDay; diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index 9d28855..a3c3a02 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -66,16 +66,25 @@ void HoymilesRadio::handleReceivedPackage() } else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) { Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded"); + // Statistics: Count RX Fail No Answer + inv->RadioStats.RxFailNoAnswer++; + _commandQueue.pop(); _busyFlag = false; } else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) { Hoymiles.getMessageOutput()->println("Retransmit timeout"); + // Statistics: Count RX Fail Partial Answer + inv->RadioStats.RxFailPartialAnswer++; + _commandQueue.pop(); _busyFlag = false; } else if (verifyResult == FRAGMENT_HANDLE_ERROR) { Hoymiles.getMessageOutput()->println("Packet handling error"); + // Statistics: Count RX Fail Corrupt Data + inv->RadioStats.RxFailCorruptData++; + _commandQueue.pop(); _busyFlag = false; @@ -83,17 +92,24 @@ void HoymilesRadio::handleReceivedPackage() // Perform Retransmit Hoymiles.getMessageOutput()->print("Request retransmit: "); Hoymiles.getMessageOutput()->println(verifyResult); + // Statistics: Count TX Re-Request Fragment + inv->RadioStats.TxReRequestFragment++; + sendRetransmitPacket(verifyResult); } else { // Successful received all packages Hoymiles.getMessageOutput()->println("Success"); + // Statistics: Count RX Success + inv->RadioStats.RxSuccess++; + _commandQueue.pop(); _busyFlag = false; } } else { // If inverter was not found, assume the command is invalid Hoymiles.getMessageOutput()->println("RX: Invalid inverter found"); + // Statistics: Count RX Fail Unknown Data _commandQueue.pop(); _busyFlag = false; } @@ -105,6 +121,9 @@ void HoymilesRadio::handleReceivedPackage() auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress()); if (nullptr != inv) { inv->clearRxFragmentBuffer(); + // Statistics: TX Requests + inv->RadioStats.TxRequestData++; + sendEsbPacket(*cmd); } else { Hoymiles.getMessageOutput()->println("TX: Invalid inverter found"); diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 68d6118..ab16969 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -272,3 +272,8 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract& cmd) return FRAGMENT_OK; } + +void InverterAbstract::resetRadioStats() +{ + RadioStats = {}; +} diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index 2a51079..72ad7a8 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -65,6 +65,28 @@ public: void addRxFragment(const uint8_t fragment[], const uint8_t len); uint8_t verifyAllFragments(CommandAbstract& cmd); + void resetRadioStats(); + + struct { + // TX Request Data + uint32_t TxRequestData; + + // TX Re-Request Fragment + uint32_t TxReRequestFragment; + + // RX Success + uint32_t RxSuccess; + + // RX Fail Partial Answer + uint32_t RxFailPartialAnswer; + + // RX Fail No Answer + uint32_t RxFailNoAnswer; + + // RX Fail Corrupt Data + uint32_t RxFailCorruptData; + } RadioStats = {}; + virtual bool sendStatsRequest() = 0; virtual bool sendAlarmLogRequest(const bool force = false) = 0; virtual bool sendDevInfoRequest() = 0; diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index d099b44..b90abdb 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -50,6 +50,14 @@ void MqttHandleInverterClass::loop() // Name MqttSettings.publish(subtopic + "/name", inv->name()); + // Radio Statistics + MqttSettings.publish(subtopic + "/radio/tx_request", String(inv->RadioStats.TxRequestData)); + MqttSettings.publish(subtopic + "/radio/tx_re_request", String(inv->RadioStats.TxReRequestFragment)); + MqttSettings.publish(subtopic + "/radio/rx_success", String(inv->RadioStats.RxSuccess)); + MqttSettings.publish(subtopic + "/radio/rx_fail_nothing", String(inv->RadioStats.RxFailNoAnswer)); + MqttSettings.publish(subtopic + "/radio/rx_fail_partial", String(inv->RadioStats.RxFailPartialAnswer)); + MqttSettings.publish(subtopic + "/radio/rx_fail_corrupt", String(inv->RadioStats.RxFailCorruptData)); + if (inv->DevInfo()->getLastUpdate() > 0) { // Bootloader Version MqttSettings.publish(subtopic + "/device/bootloaderversion", String(inv->DevInfo()->getFwBootloaderVersion())); diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index eb1a83b..4fa7983 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -134,6 +134,12 @@ void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std } else { root["limit_absolute"] = -1; } + root["radio_stats"]["tx_request"] = inv->RadioStats.TxRequestData; + root["radio_stats"]["tx_re_request"] = inv->RadioStats.TxReRequestFragment; + root["radio_stats"]["rx_success"] = inv->RadioStats.RxSuccess; + root["radio_stats"]["rx_fail_nothing"] = inv->RadioStats.RxFailNoAnswer; + root["radio_stats"]["rx_fail_partial"] = inv->RadioStats.RxFailPartialAnswer; + root["radio_stats"]["rx_fail_corrupt"] = inv->RadioStats.RxFailCorruptData; } void WebApiWsLiveClass::generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr inv) diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 834849d..9361089 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -141,7 +141,14 @@ "Unknown": "Unbekannt", "ShowGridProfile": "Zeige Grid Profil", "GridProfile": "Grid Profil", - "LoadingInverter": "Warte auf Daten... (kann bis zu 10 Sekunden dauern)" + "LoadingInverter": "Warte auf Daten... (kann bis zu 10 Sekunden dauern)", + "RadioStats": "Funkstatistik", + "TxRequest": "Gesendete Anfragen", + "RxSuccess": "Empfang Erfolgreich", + "RxFailNothing": "Empfang Fehler: Nichts empfangen", + "RxFailPartial": "Empfang Fehler: Teilweise empfangen", + "RxFailCorrupt": "Empfang Fehler: Beschädigt empfangen", + "TxReRequest": "Gesendete Fragment Wiederanforderungen" }, "eventlog": { "Start": "Beginn", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index af2a1b4..34be62f 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -141,7 +141,14 @@ "Unknown": "Unknown", "ShowGridProfile": "Show Grid Profile", "GridProfile": "Grid Profile", - "LoadingInverter": "Waiting for data... (can take up to 10 seconds)" + "LoadingInverter": "Waiting for data... (can take up to 10 seconds)", + "RadioStats": "Radio Statistics", + "TxRequest": "TX Request Count", + "RxSuccess": "RX Success", + "RxFailNothing": "RX Fail: Receive Nothing", + "RxFailPartial": "RX Fail: Receive Partial", + "RxFailCorrupt": "RX Fail: Receive Corrupt", + "TxReRequest": "TX Re-Request Fragment" }, "eventlog": { "Start": "Start", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 96f259d..d9d148d 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -141,7 +141,14 @@ "Unknown": "Inconnu", "ShowGridProfile": "Show Grid Profile", "GridProfile": "Grid Profile", - "LoadingInverter": "Waiting for data... (can take up to 10 seconds)" + "LoadingInverter": "Waiting for data... (can take up to 10 seconds)", + "RadioStats": "Radio Statistics", + "TxRequest": "TX Request Count", + "RxSuccess": "RX Success", + "RxFailNothing": "RX Fail: Receive Nothing", + "RxFailPartial": "RX Fail: Receive Partial", + "RxFailCorrupt": "RX Fail: Receive Corrupt", + "TxReRequest": "TX Re-Request Fragment" }, "eventlog": { "Start": "Départ", diff --git a/webapp/src/types/LiveDataStatus.ts b/webapp/src/types/LiveDataStatus.ts index 156a03f..db95897 100644 --- a/webapp/src/types/LiveDataStatus.ts +++ b/webapp/src/types/LiveDataStatus.ts @@ -21,6 +21,15 @@ export interface InverterStatistics { Irradiation?: ValueObject; } +export interface RadioStatistics { + tx_request: number; + tx_re_request: number; + rx_success: number; + rx_fail_nothing: number; + rx_fail_partial: number; + rx_fail_corrupt: number; +} + export interface Inverter { serial: string; name: string; @@ -35,6 +44,7 @@ export interface Inverter { AC: InverterStatistics[]; DC: InverterStatistics[]; INV: InverterStatistics[]; + radio_stats: RadioStatistics; } export interface Total { diff --git a/webapp/src/views/HomeView.vue b/webapp/src/views/HomeView.vue index db5a889..89b05ff 100644 --- a/webapp/src/views/HomeView.vue +++ b/webapp/src/views/HomeView.vue @@ -201,6 +201,7 @@ +
@@ -209,6 +210,93 @@ {{ $t('home.LoadingInverter') }}
+ +
+
+

+ +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ $t('home.TxRequest') }}{{ $n(inverter.radio_stats.tx_request) }}
{{ $t('home.RxSuccess') }}{{ $n(inverter.radio_stats.rx_success) }} + {{ + ratio( + inverter.radio_stats.rx_success, + inverter.radio_stats.tx_request + ) + }} +
{{ $t('home.RxFailNothing') }}{{ $n(inverter.radio_stats.rx_fail_nothing) }} + {{ + ratio( + inverter.radio_stats.rx_fail_nothing, + inverter.radio_stats.tx_request + ) + }} +
{{ $t('home.RxFailPartial') }}{{ $n(inverter.radio_stats.rx_fail_partial) }} + {{ + ratio( + inverter.radio_stats.rx_fail_partial, + inverter.radio_stats.tx_request + ) + }} +
{{ $t('home.RxFailCorrupt') }}{{ $n(inverter.radio_stats.rx_fail_corrupt) }} + {{ + ratio( + inverter.radio_stats.rx_fail_corrupt, + inverter.radio_stats.tx_request + ) + }} +
{{ $t('home.TxReRequest') }}{{ $n(inverter.radio_stats.tx_re_request) }}
+
+
+
+
@@ -786,6 +874,12 @@ export default defineComponent({ }); return total; }, + ratio(val_small: number, val_large: number): string { + if (val_large == 0) { + return '-'; + } + return this.$n(val_small / val_large, 'percent'); + }, }, });