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 ( + + ); + })} )}