diff --git a/src/helpers/StatsHistory.cpp b/src/helpers/StatsHistory.cpp index 24e79e28..058988b9 100644 --- a/src/helpers/StatsHistory.cpp +++ b/src/helpers/StatsHistory.cpp @@ -233,6 +233,19 @@ bool buildPointValue(const HistorySample& sample, const HistorySample* previous, value = static_cast(curr_total >= prev_total ? (curr_total - prev_total) : 0); return true; } + if (strcmp(series, "error_rate") == 0) { + if (previous == nullptr) { + return false; + } + const uint32_t d_errors = sample.recv_errors - previous->recv_errors; + const uint32_t d_recv = sample.packets_recv - previous->packets_recv; + const uint32_t total = d_errors + d_recv; + if (total == 0) { + return false; + } + value = (int)((d_errors * 1000u) / total); + return true; + } if (strcmp(series, "voltage") == 0) { if ((sample.sensor_flags & HISTORY_SENSOR_SUPPLY_VOLTAGE) == 0) { return false; @@ -302,6 +315,9 @@ const char* seriesTitle(const char* series) { if (strcmp(series, "packets") == 0) { return "Packet Activity"; } + if (strcmp(series, "error_rate") == 0) { + return "Error Rate"; + } if (strcmp(series, "signal") == 0) { return "Signal"; } @@ -345,6 +361,9 @@ const char* seriesUnit(const char* series) { if (strcmp(series, "packets") == 0) { return "pkts"; } + if (strcmp(series, "error_rate") == 0) { + return "per_mille"; + } if (strcmp(series, "signal") == 0) { return "rssi_x4"; } @@ -1256,7 +1275,7 @@ bool StatsHistory::buildSeriesJson(const char* series, char* buffer, size_t buff bool have_current_value = false; getSampleFromOldest(_sample_count - 1, sample); - if (strcmp(series, "packets") == 0 && _sample_count >= 2) { + if ((strcmp(series, "packets") == 0 || strcmp(series, "error_rate") == 0) && _sample_count >= 2) { getSampleFromOldest(_sample_count - 2, previous); have_current_value = buildPointValue(sample, &previous, series, current_value); } else { diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index ff9d1360..68004a7d 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -1386,7 +1386,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
${escapeHtml(label)}
${escapeHtml(value)}
-
${escapeHtml(note)}
+ ${note ? `
${escapeHtml(note)}
` : ""}
`; @@ -1549,10 +1549,16 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( const directTx = packets.direct_tx || 0; const floodRx = packets.flood_rx || 0; const directRx = packets.direct_rx || 0; + const recvErrors = packets.recv_errors || 0; + const totalAttempts = recv + recvErrors; + const errorRatePct = pctRatio(recvErrors, totalAttempts); return `

Packets

- ${renderMeter("TX Flood Share", Math.round(pctRatio(floodTx, sent)) + "%", pctRatio(floodTx, sent), floodTx + " flood / " + directTx + " direct", false)} - ${renderMeter("RX Flood Share", Math.round(pctRatio(floodRx, recv)) + "%", pctRatio(floodRx, recv), floodRx + " flood / " + directRx + " direct", false)} +
+ ${renderMeter("TX Flood Share", Math.round(pctRatio(floodTx, sent)) + "%", pctRatio(floodTx, sent), "", false)} + ${renderMeter("RX Flood Share", Math.round(pctRatio(floodRx, recv)) + "%", pctRatio(floodRx, recv), "", false)} +
+ ${renderMeter("RX Error Rate", Math.round(errorRatePct) + "%", errorRatePct, recvErrors + " errors / " + totalAttempts + " attempts", false)}
${renderMetric("Sent", sent)} ${renderMetric("Recv", recv)} @@ -1771,6 +1777,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( if (key === "voltage") return (value / 100).toFixed(2) + " V"; if (key === "memory") return formatBytes(value); if (key === "packets") return Math.round(value) + " pkts"; + if (key === "error_rate") return (value / 10).toFixed(1) + " %"; if (key === "signal") return (value / 4).toFixed(1) + " dBm"; if (key === "noise_floor") return (value / 4).toFixed(1) + " dBm"; if (key === "sensor_temp" || key === "mcu_temp") return (value / 10).toFixed(1) + " C"; @@ -1808,6 +1815,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( if (recent >= 750) return "#d7a531"; // high return "#2f8f4e"; // normal } + if (key === "error_rate") return "#ef4444"; if (key === "gps_satellites") return "#2f8f4e"; if (key === "signal") return "#3b82f6"; if (key === "noise_floor") return "#94a3b8"; @@ -1830,6 +1838,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( const maxValue = Math.max(1, ...values); return { min:0, max:maxValue }; } + if (key === "error_rate") return { min: 0, max: 1000 }; if (key === "signal") { return { min:(-125 * 4), max:(-30 * 4) }; } @@ -1864,6 +1873,13 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( { from: 950, to: 1250, color: "rgba(191,75,75,0.14)" } // critical (> 95 °C) ]; } + if (key === "error_rate") { + return [ + { from: 0, to: 200, color: "rgba(47,143,78,0.12)" }, + { from: 200, to: 450, color: "rgba(215,165,49,0.14)" }, + { from: 450, to: 1000, color: "rgba(191,75,75,0.14)" } + ]; + } if (key === "signal") { return [ { from:(-125 * 4), to:(-110 * 4), color:"rgba(191,75,75,0.14)" }, @@ -2043,7 +2059,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( const sensors = summaryPayload && summaryPayload.sensors ? summaryPayload.sensors : null; const gpsEnabled = !!(sensors && sensors.gps_enabled === true); const mcuTempPresent = !!(sensors && Number.isFinite(sensors.mcu_temp_c)); - const order = ["battery", "memory", "signal", "noise_floor", "packets"]; + const order = ["battery", "memory", "packets", "error_rate", "signal", "noise_floor"]; if (mcuTempPresent) { order.splice(2, 0, "mcu_temp"); }