diff --git a/frontend/src/components/repeater/RepeaterTelemetryHistoryPane.tsx b/frontend/src/components/repeater/RepeaterTelemetryHistoryPane.tsx
index 63f8acd..16876eb 100644
--- a/frontend/src/components/repeater/RepeaterTelemetryHistoryPane.tsx
+++ b/frontend/src/components/repeater/RepeaterTelemetryHistoryPane.tsx
@@ -154,13 +154,19 @@ export function TelemetryHistoryPane({
const chartData = useMemo(() => {
return entries.map((e) => {
const d = e.data;
+ const recvErrors = d.recv_errors ?? undefined;
+ const packetsReceived = d.packets_received;
const point: Record = {
timestamp: e.timestamp,
battery_volts: d.battery_volts,
noise_floor_dbm: d.noise_floor_dbm,
- packets_received: d.packets_received,
+ packets_received: packetsReceived,
packets_sent: d.packets_sent,
- recv_errors: d.recv_errors ?? undefined,
+ recv_errors: recvErrors,
+ recv_error_pct:
+ recvErrors != null && packetsReceived != null && packetsReceived + recvErrors > 0
+ ? +((recvErrors / (packetsReceived + recvErrors)) * 100).toFixed(2)
+ : undefined,
uptime_seconds: d.uptime_seconds,
};
// Flatten LPP sensors into the point, converting units as needed
@@ -174,7 +180,11 @@ export function TelemetryHistoryPane({
}, [entries, distanceUnit]);
const dataKeys =
- activeMetric === 'packets' ? ['packets_received', 'packets_sent'] : [activeMetric];
+ activeMetric === 'packets'
+ ? ['packets_received', 'packets_sent']
+ : activeMetric === 'recv_errors'
+ ? ['recv_errors', 'recv_error_pct']
+ : [activeMetric];
const yDomain = useMemo<[number, number] | undefined>(() => {
if (activeMetric !== 'battery_volts' || chartData.length === 0) return undefined;
@@ -185,6 +195,20 @@ export function TelemetryHistoryPane({
return [Math.min(3, Math.floor(lo) - 1), Math.max(5, Math.ceil(hi) + 1)];
}, [activeMetric, chartData]);
+ const yDomainPct = useMemo<[number, number]>(() => {
+ const MIN_SPAN = 5;
+ const values = chartData.map((d) => d.recv_error_pct).filter((v) => v != null) as number[];
+ if (values.length === 0) return [0, MIN_SPAN];
+ const lo = Math.min(...values);
+ const hi = Math.max(...values);
+ const span = hi - lo;
+ if (span >= MIN_SPAN)
+ return [Math.max(0, Math.floor(lo - span * 0.1)), Math.ceil(hi + span * 0.1)];
+ const pad = (MIN_SPAN - span) / 2;
+ const bottom = Math.max(0, Math.floor(lo - pad));
+ return [bottom, Math.ceil(bottom + MIN_SPAN)];
+ }, [chartData]);
+
const handleToggle = async () => {
setToggling(true);
try {
@@ -306,7 +330,15 @@ export function TelemetryHistoryPane({
) : (
-
+
+ {activeMetric === 'recv_errors' && (
+ `${v}%`}
+ />
+ )}
{
const numVal = typeof value === 'number' ? value : Number(value);
+ if (activeMetric === 'recv_errors') {
+ if (name === 'recv_error_pct') return [`${numVal}%`, 'Error Rate'];
+ return [`${value}`, 'RX Errors'];
+ }
const display =
activeMetric === 'uptime_seconds' ? formatUptime(numVal) : `${value}`;
const suffix =
@@ -354,51 +402,44 @@ export function TelemetryHistoryPane({
return [`${display}${suffix}`, label];
}}
/>
- {dataKeys.map((key, i) => (
- {
+ const color =
+ activeMetric === 'packets'
+ ? i === 0
+ ? '#0ea5e9'
+ : '#f43f5e'
+ : activeMetric === 'recv_errors'
? i === 0
- ? '#0ea5e9'
- : '#f43f5e'
- : activeConfig.color
- }
- fill={
- activeMetric === 'packets'
- ? i === 0
- ? '#0ea5e9'
- : '#f43f5e'
- : activeConfig.color
- }
- fillOpacity={0.15}
- strokeWidth={1.5}
- dot={{
- r: 4,
- fill:
- activeMetric === 'packets'
- ? i === 0
- ? '#0ea5e9'
- : '#f43f5e'
- : activeConfig.color,
- strokeWidth: 1.5,
- stroke: 'hsl(var(--popover))',
- }}
- activeDot={{
- r: 6,
- fill:
- activeMetric === 'packets'
- ? i === 0
- ? '#0ea5e9'
- : '#f43f5e'
- : activeConfig.color,
- strokeWidth: 2,
- stroke: 'hsl(var(--popover))',
- }}
- />
- ))}
+ ? '#ef4444'
+ : '#f59e0b'
+ : activeConfig.color;
+ return (
+
+ );
+ })}
)}