diff --git a/include/RestartHelper.h b/include/RestartHelper.h new file mode 100644 index 00000000..80f5f675 --- /dev/null +++ b/include/RestartHelper.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +class RestartHelperClass { +public: + RestartHelperClass(); + void init(Scheduler& scheduler); + void triggerRestart(); + +private: + void loop(); + + Task _rebootTask; +}; + +extern RestartHelperClass RestartHelper; diff --git a/include/Utils.h b/include/Utils.h index a6bc3b15..150294fb 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -10,7 +10,6 @@ public: static uint32_t getChipId(); static uint64_t generateDtuSerial(); static int getTimezoneOffset(); - static void restartDtu(); static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line); static void removeAllFiles(); diff --git a/include/WebApi_firmware.h b/include/WebApi_firmware.h index 692d148c..d4822afa 100644 --- a/include/WebApi_firmware.h +++ b/include/WebApi_firmware.h @@ -6,7 +6,6 @@ class WebApiFirmwareClass { public: - WebApiFirmwareClass(); void init(AsyncWebServer& server, Scheduler& scheduler); private: @@ -15,7 +14,4 @@ private: void onFirmwareUpdateFinish(AsyncWebServerRequest* request); void onFirmwareUpdateUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); void onFirmwareStatus(AsyncWebServerRequest* request); - - Task _rebootTask; - void rebootTaskCb(); }; diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index 3ce1d1fa..e12aad91 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 7534dcbe..7b3d4f32 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 68d61183..ab169696 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 2a51079b..72ad7a8e 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/MqttHandleDtu.cpp b/src/MqttHandleDtu.cpp index 6c8c38c8..df025f12 100644 --- a/src/MqttHandleDtu.cpp +++ b/src/MqttHandleDtu.cpp @@ -35,7 +35,6 @@ void MqttHandleDtuClass::loop() MqttSettings.publish("dtu/uptime", String(millis() / 1000)); MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); - MqttSettings.publish("dtu/temperature", String(CpuTemperature.read())); MqttSettings.publish("dtu/heap/size", String(ESP.getHeapSize())); MqttSettings.publish("dtu/heap/free", String(ESP.getFreeHeap())); MqttSettings.publish("dtu/heap/minfree", String(ESP.getMinFreeHeap())); @@ -44,4 +43,9 @@ void MqttHandleDtuClass::loop() MqttSettings.publish("dtu/rssi", String(WiFi.RSSI())); MqttSettings.publish("dtu/bssid", WiFi.BSSIDstr()); } + + float temperature = CpuTemperature.read(); + if (!std::isnan(temperature)) { + MqttSettings.publish("dtu/temperature", String(temperature)); + } } diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index d099b444..b90abdb6 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/PowerLimiter.cpp b/src/PowerLimiter.cpp index 6589d7fe..c96a5a26 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2022 Thomas Basler and others */ -#include "Utils.h" +#include "RestartHelper.h" #include "Battery.h" #include "PowerMeter.h" #include "PowerLimiter.h" @@ -588,7 +588,7 @@ bool PowerLimiterClass::updateInverter() if (_inverterUpdateTimeouts >= 20) { MessageOutput.println("[DPL::loop] restarting system since inverter is unresponsive"); - Utils::restartDtu(); + RestartHelper.triggerRestart(); } return reset(); diff --git a/src/RestartHelper.cpp b/src/RestartHelper.cpp new file mode 100644 index 00000000..ab385ef6 --- /dev/null +++ b/src/RestartHelper.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Thomas Basler and others + */ +#include "RestartHelper.h" +#include "Display_Graphic.h" +#include "Led_Single.h" +#include + +RestartHelperClass RestartHelper; + +RestartHelperClass::RestartHelperClass() + : _rebootTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&RestartHelperClass::loop, this)) +{ +} + +void RestartHelperClass::init(Scheduler& scheduler) +{ + scheduler.addTask(_rebootTask); +} + +void RestartHelperClass::triggerRestart() +{ + _rebootTask.enable(); + _rebootTask.restart(); +} + +void RestartHelperClass::loop() +{ + if (_rebootTask.isFirstIteration()) { + LedSingle.turnAllOff(); + Display.setStatus(false); + } else { + ESP.restart(); + } +} diff --git a/src/Utils.cpp b/src/Utils.cpp index c2e40885..70938952 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -4,10 +4,8 @@ */ #include "Utils.h" -#include "Display_Graphic.h" -#include "Led_Single.h" #include "MessageOutput.h" -#include +#include "PinMapping.h" #include uint32_t Utils::getChipId() @@ -59,16 +57,6 @@ int Utils::getTimezoneOffset() return static_cast(difftime(rawtime, gmt)); } -void Utils::restartDtu() -{ - LedSingle.turnAllOff(); - Display.setStatus(false); - yield(); - delay(1000); - yield(); - ESP.restart(); -} - bool Utils::checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line) { if (doc.overflowed()) { diff --git a/src/WebApi_config.cpp b/src/WebApi_config.cpp index 8d0e7a08..51a7aab1 100644 --- a/src/WebApi_config.cpp +++ b/src/WebApi_config.cpp @@ -4,6 +4,7 @@ */ #include "WebApi_config.h" #include "Configuration.h" +#include "RestartHelper.h" #include "Utils.h" #include "WebApi.h" #include "WebApi_errors.h" @@ -82,7 +83,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); Utils::removeAllFiles(); - Utils::restartDtu(); + RestartHelper.triggerRestart(); } void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request) @@ -124,7 +125,7 @@ void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request) response->addHeader("Connection", "close"); response->addHeader("Access-Control-Allow-Origin", "*"); request->send(response); - Utils::restartDtu(); + RestartHelper.triggerRestart(); } void WebApiConfigClass::onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index d65020ef..405c1dea 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -6,7 +6,7 @@ #include "Configuration.h" #include "Display_Graphic.h" #include "PinMapping.h" -#include "Utils.h" +#include "RestartHelper.h" #include "WebApi.h" #include "WebApi_errors.h" #include "helper.h" @@ -186,6 +186,6 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); if (performRestart) { - Utils::restartDtu(); + RestartHelper.triggerRestart(); } } diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 0cae76cf..7fd12271 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -95,7 +95,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) && root["pollinterval"].is() && root["verbose_logging"].is() && root["nrf_palevel"].is() - && root["cmt_palevel"].is() + && root["cmt_palevel"].is() && root["cmt_frequency"].is() && root["cmt_country"].is())) { retMsg["message"] = "Values are missing!"; diff --git a/src/WebApi_firmware.cpp b/src/WebApi_firmware.cpp index 7a525ebe..1511ae59 100644 --- a/src/WebApi_firmware.cpp +++ b/src/WebApi_firmware.cpp @@ -4,6 +4,7 @@ */ #include "WebApi_firmware.h" #include "Configuration.h" +#include "RestartHelper.h" #include "Update.h" #include "Utils.h" #include "WebApi.h" @@ -11,11 +12,6 @@ #include #include "esp_partition.h" -WebApiFirmwareClass::WebApiFirmwareClass() - : _rebootTask(TASK_IMMEDIATE, TASK_ONCE, std::bind(&WebApiFirmwareClass::rebootTaskCb, this)) -{ -} - void WebApiFirmwareClass::init(AsyncWebServer& server, Scheduler& scheduler) { using std::placeholders::_1; @@ -30,8 +26,6 @@ void WebApiFirmwareClass::init(AsyncWebServer& server, Scheduler& scheduler) std::bind(&WebApiFirmwareClass::onFirmwareUpdateUpload, this, _1, _2, _3, _4, _5, _6)); server.on("/api/firmware/status", HTTP_GET, std::bind(&WebApiFirmwareClass::onFirmwareStatus, this, _1)); - - scheduler.addTask(_rebootTask); } bool WebApiFirmwareClass::otaSupported() const @@ -54,8 +48,7 @@ void WebApiFirmwareClass::onFirmwareUpdateFinish(AsyncWebServerRequest* request) response->addHeader("Connection", "close"); response->addHeader("Access-Control-Allow-Origin", "*"); request->send(response); - _rebootTask.enable(); - _rebootTask.restart(); + RestartHelper.triggerRestart(); } void WebApiFirmwareClass::onFirmwareUpdateUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) @@ -101,11 +94,6 @@ void WebApiFirmwareClass::onFirmwareUpdateUpload(AsyncWebServerRequest* request, } } -void WebApiFirmwareClass::rebootTaskCb() -{ - Utils::restartDtu(); -} - void WebApiFirmwareClass::onFirmwareStatus(AsyncWebServerRequest* request) { if (!WebApi.checkCredentialsReadonly(request)) { diff --git a/src/WebApi_maintenance.cpp b/src/WebApi_maintenance.cpp index c7d254b8..1835138f 100644 --- a/src/WebApi_maintenance.cpp +++ b/src/WebApi_maintenance.cpp @@ -4,7 +4,7 @@ */ #include "WebApi_maintenance.h" -#include "Utils.h" +#include "RestartHelper.h" #include "WebApi.h" #include "WebApi_errors.h" #include @@ -43,7 +43,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::MaintenanceRebootTriggered; WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); - Utils::restartDtu(); + RestartHelper.triggerRestart(); } else { retMsg["message"] = "Reboot cancled!"; retMsg["code"] = WebApiError::MaintenanceRebootCancled; diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index cda20e72..aacb9659 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -229,6 +229,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/src/main.cpp b/src/main.cpp index 5743f428..b9f451af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ #include "NetworkSettings.h" #include "NtpSettings.h" #include "PinMapping.h" +#include "RestartHelper.h" #include "Scheduler.h" #include "SunPosition.h" #include "Utils.h" @@ -173,11 +174,11 @@ void setup() Configuration.write(); } MessageOutput.println("done"); - MessageOutput.println("done"); InverterSettings.init(scheduler); Datastore.init(scheduler); + RestartHelper.init(scheduler); VictronMppt.init(scheduler); diff --git a/webapp/package.json b/webapp/package.json index 04e7d848..5d7c5908 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -19,7 +19,7 @@ "mitt": "^3.0.1", "sortablejs": "^1.15.3", "spark-md5": "^3.0.2", - "vue": "^3.5.7", + "vue": "^3.5.8", "vue-i18n": "9.13.1", "vue-router": "^4.4.5" }, diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 91f4fa9f..7b68b28c 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -150,7 +150,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" }, "vedirecthome": { "SerialNumber": "Seriennummer", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 51f12dab..2602bba6 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -150,7 +150,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" }, "vedirecthome": { "SerialNumber": "Serial Number", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index fa0b2bb0..4a0fcac4 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -150,7 +150,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" }, "vedirecthome": { "SerialNumber": "Numéro de série", diff --git a/webapp/src/types/LiveDataStatus.ts b/webapp/src/types/LiveDataStatus.ts index cba51c62..23cf6b95 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/DeviceAdminView.vue b/webapp/src/views/DeviceAdminView.vue index 31eadcf1..eafcf902 100644 --- a/webapp/src/views/DeviceAdminView.vue +++ b/webapp/src/views/DeviceAdminView.vue @@ -202,7 +202,7 @@ min="0" max="100" id="inputDisplayContrast" - v-model="deviceConfigList.display.contrast" + v-model.number="deviceConfigList.display.contrast" /> @@ -239,7 +239,7 @@ min="0" max="100" :id="getLedIdFromNumber(index)" - v-model="ledSetting.brightness" + v-model.number="ledSetting.brightness" @change="syncSliders" /> diff --git a/webapp/src/views/DtuAdminView.vue b/webapp/src/views/DtuAdminView.vue index 9d6f4d37..668c3ede 100644 --- a/webapp/src/views/DtuAdminView.vue +++ b/webapp/src/views/DtuAdminView.vue @@ -54,7 +54,7 @@ +
@@ -215,6 +216,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) }}
+
+
+
+
@@ -402,6 +490,7 @@ import { authHeader, authUrl, handleResponse, isLoggedIn } from '@/utils/authent import * as bootstrap from 'bootstrap'; import { BIconArrowCounterclockwise, + BIconBroadcast, BIconCheckCircleFill, BIconCpu, BIconExclamationCircleFill, @@ -427,6 +516,7 @@ export default defineComponent({ InverterTotalInfo, ModalDialog, BIconArrowCounterclockwise, + BIconBroadcast, BIconCheckCircleFill, BIconCpu, BIconExclamationCircleFill, @@ -819,6 +909,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'); + }, }, }); diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 67e2ce98..9d7d8648 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -652,13 +652,13 @@ estree-walker "^2.0.2" source-map-js "^1.0.2" -"@vue/compiler-core@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.7.tgz#04300bdc9fb52f89e6f250bbac16e03f0e0ed914" - integrity sha512-A0gay3lK71MddsSnGlBxRPOugIVdACze9L/rCo5X5srCyjQfZOfYtSFMJc3aOZCM+xN55EQpb4R97rYn/iEbSw== +"@vue/compiler-core@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.8.tgz#03ee4a2fa022c9bc3e59f789a1e14593b1e95b10" + integrity sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA== dependencies: "@babel/parser" "^7.25.3" - "@vue/shared" "3.5.7" + "@vue/shared" "3.5.8" entities "^4.5.0" estree-walker "^2.0.2" source-map-js "^1.2.0" @@ -671,13 +671,13 @@ "@vue/compiler-core" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-dom@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.7.tgz#604ced082189b66cb811068332a45dcc11ae0af3" - integrity sha512-GYWl3+gO8/g0ZdYaJ18fYHdI/WVic2VuuUd1NsPp60DWXKy+XjdhFsDW7FbUto8siYYZcosBGn9yVBkjhq1M8Q== +"@vue/compiler-dom@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.8.tgz#03e4a6bef00a1979613a1db2ab39e9b2dced3373" + integrity sha512-GUNHWvoDSbSa5ZSHT9SnV5WkStWfzJwwTd6NMGzilOE/HM5j+9EB9zGXdtu/fCNEmctBqMs6C9SvVPpVPuk1Eg== dependencies: - "@vue/compiler-core" "3.5.7" - "@vue/shared" "3.5.7" + "@vue/compiler-core" "3.5.8" + "@vue/shared" "3.5.8" "@vue/compiler-dom@^3.4.0": version "3.4.21" @@ -687,16 +687,16 @@ "@vue/compiler-core" "3.4.21" "@vue/shared" "3.4.21" -"@vue/compiler-sfc@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.7.tgz#1150c49c0e3b39d40b2cf0f7de9edfcba98fa3e9" - integrity sha512-EjOJtCWJrC7HqoCEzOwpIYHm+JH7YmkxC1hG6VkqIukYRqj8KFUlTLK6hcT4nGgtVov2+ZfrdrRlcaqS78HnBA== +"@vue/compiler-sfc@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.8.tgz#b2091ec01c63ab02a1cd6783224322f245c6a308" + integrity sha512-taYpngQtSysrvO9GULaOSwcG5q821zCoIQBtQQSx7Uf7DxpR6CIHR90toPr9QfDD2mqHQPCSgoWBvJu0yV9zjg== dependencies: "@babel/parser" "^7.25.3" - "@vue/compiler-core" "3.5.7" - "@vue/compiler-dom" "3.5.7" - "@vue/compiler-ssr" "3.5.7" - "@vue/shared" "3.5.7" + "@vue/compiler-core" "3.5.8" + "@vue/compiler-dom" "3.5.8" + "@vue/compiler-ssr" "3.5.8" + "@vue/shared" "3.5.8" estree-walker "^2.0.2" magic-string "^0.30.11" postcss "^8.4.47" @@ -726,13 +726,13 @@ "@vue/compiler-dom" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-ssr@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.7.tgz#042144dfd574a1f64b685e87730b0196dc1846d2" - integrity sha512-oZx+jXP2k5arV/8Ly3TpQbfFyimMw2ANrRqvHJoKjPqtEzazxQGZjCLOfq8TnZ3wy2TOXdqfmVp4q7FyYeHV4g== +"@vue/compiler-ssr@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.8.tgz#fbad34f8bbed15aa6e7b9d78324d93af93403145" + integrity sha512-W96PtryNsNG9u0ZnN5Q5j27Z/feGrFV6zy9q5tzJVyJaLiwYxvC0ek4IXClZygyhjm+XKM7WD9pdKi/wIRVC/Q== dependencies: - "@vue/compiler-dom" "3.5.7" - "@vue/shared" "3.5.7" + "@vue/compiler-dom" "3.5.8" + "@vue/shared" "3.5.8" "@vue/compiler-vue2@^2.7.16": version "2.7.16" @@ -786,38 +786,38 @@ estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.7.tgz#a52237fce841d92fc861220a8f26b51f5c3245e2" - integrity sha512-yF0EpokpOHRNXyn/h6abXc9JFIzfdAf0MJHIi92xxCWS0mqrXH6+2aZ+A6EbSrspGzX5MHTd5N8iBA28HnXu9g== +"@vue/reactivity@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.8.tgz#23e1bceceb9b94b136fa91f11b308e3f712dea6d" + integrity sha512-mlgUyFHLCUZcAYkqvzYnlBRCh0t5ZQfLYit7nukn1GR96gc48Bp4B7OIcSfVSvlG1k3BPfD+p22gi1t2n9tsXg== dependencies: - "@vue/shared" "3.5.7" + "@vue/shared" "3.5.8" -"@vue/runtime-core@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.7.tgz#4181b0a921d331f2efd5eda9aa35549ac97e6530" - integrity sha512-OzLpBpKbZEaZVSNfd+hQbfBrDKux+b7Yl5hYhhWWWhHD7fEpF+CdI3Brm5k5GsufHEfvMcjruPxwQZuBN6nFYQ== +"@vue/runtime-core@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.8.tgz#235251fa40dae61db7becacf6bda5bc6561cbbc5" + integrity sha512-fJuPelh64agZ8vKkZgp5iCkPaEqFJsYzxLk9vSC0X3G8ppknclNDr61gDc45yBGTaN5Xqc1qZWU3/NoaBMHcjQ== dependencies: - "@vue/reactivity" "3.5.7" - "@vue/shared" "3.5.7" + "@vue/reactivity" "3.5.8" + "@vue/shared" "3.5.8" -"@vue/runtime-dom@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.7.tgz#646e006d403f2e6337f566fdf461fbe400e8487d" - integrity sha512-fL7cETfE27U2jyTgqzE382IGFY6a6uyznErn27KbbEzNctzxxUWYDbaN3B55l9nXh0xW2LRWPuWKOvjtO2UewQ== +"@vue/runtime-dom@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.8.tgz#9d3a4f4a9a9a0002b085a5e18a2ca16c009cb3ad" + integrity sha512-DpAUz+PKjTZPUOB6zJgkxVI3GuYc2iWZiNeeHQUw53kdrparSTG6HeXUrYDjaam8dVsCdvQxDz6ZWxnyjccUjQ== dependencies: - "@vue/reactivity" "3.5.7" - "@vue/runtime-core" "3.5.7" - "@vue/shared" "3.5.7" + "@vue/reactivity" "3.5.8" + "@vue/runtime-core" "3.5.8" + "@vue/shared" "3.5.8" csstype "^3.1.3" -"@vue/server-renderer@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.7.tgz#65ba8b60c0ee9e791619c0f8b2b6209a258484e5" - integrity sha512-peRypij815eIDjpPpPXvYQGYqPH6QXwLJGWraJYPPn8JqWGl29A8QXnS7/Mh3TkMiOcdsJNhbFCoW2Agc2NgAQ== +"@vue/server-renderer@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.8.tgz#d6c292409e880db4151223c27fa0d1cd879cc239" + integrity sha512-7AmC9/mEeV9mmXNVyUIm1a1AjUhyeeGNbkLh39J00E7iPeGks8OGRB5blJiMmvqSh8SkaS7jkLWSpXtxUCeagA== dependencies: - "@vue/compiler-ssr" "3.5.7" - "@vue/shared" "3.5.7" + "@vue/compiler-ssr" "3.5.8" + "@vue/shared" "3.5.8" "@vue/shared@3.2.47": version "3.2.47" @@ -829,10 +829,10 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1" integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g== -"@vue/shared@3.5.7": - version "3.5.7" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.7.tgz#1eedd1ffbf804c488fe806a17ff26c22e0ddb72f" - integrity sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g== +"@vue/shared@3.5.8": + version "3.5.8" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.8.tgz#6ef14933872dcc4f7b79fee3aaecf648ff807fed" + integrity sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A== "@vue/tsconfig@^0.5.1": version "0.5.1" @@ -2673,16 +2673,16 @@ vue-tsc@^2.1.6: "@vue/language-core" "2.1.6" semver "^7.5.4" -vue@^3.5.7: - version "3.5.7" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.7.tgz#511df1fab33a4c20cfe6b59659d6f601f0c26625" - integrity sha512-JcFm0f5j8DQO9E07pZRxqZ/ZsNopMVzHYXpKvnfqXFcA4JTi+4YcrikRn9wkzWsdj0YsLzlLIsR0zzGxA2P6Wg== +vue@^3.5.8: + version "3.5.8" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.8.tgz#7d2fa98ea85228dcb90f897ef5df74df1d5825a1" + integrity sha512-hvuvuCy51nP/1fSRvrrIqTLSvrSyz2Pq+KQ8S8SXCxTWVE0nMaOnSDnSOxV1eYmGfvK7mqiwvd1C59CEEz7dAQ== dependencies: - "@vue/compiler-dom" "3.5.7" - "@vue/compiler-sfc" "3.5.7" - "@vue/runtime-dom" "3.5.7" - "@vue/server-renderer" "3.5.7" - "@vue/shared" "3.5.7" + "@vue/compiler-dom" "3.5.8" + "@vue/compiler-sfc" "3.5.8" + "@vue/runtime-dom" "3.5.8" + "@vue/server-renderer" "3.5.8" + "@vue/shared" "3.5.8" webpack-sources@^3.2.3: version "3.2.3"