mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-08 09:43:03 +02:00
Add RSSI/SNR to received messages. Closes #148.
This commit is contained in:
@@ -300,6 +300,9 @@ export function MessageList({
|
||||
const [resendableIds, setResendableIds] = useState<Set<number>>(new Set());
|
||||
const resendTimersRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map());
|
||||
const packetCacheRef = useRef<Map<number, RawPacket>>(new Map());
|
||||
const packetSignalOverrideRef = useRef<{ rssi: number | null; snr: number | null } | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [packetInspectorSource, setPacketInspectorSource] = useState<
|
||||
| { kind: 'packet'; packet: RawPacket }
|
||||
| { kind: 'loading'; message: string }
|
||||
@@ -325,6 +328,13 @@ export function MessageList({
|
||||
const prevConvKeyRef = useRef<string | null>(null);
|
||||
|
||||
const handleAnalyzePacket = useCallback(async (message: Message) => {
|
||||
// Extract signal from the first path if available
|
||||
const firstPath = message.paths?.[0];
|
||||
packetSignalOverrideRef.current =
|
||||
firstPath && (firstPath.rssi != null || firstPath.snr != null)
|
||||
? { rssi: firstPath.rssi ?? null, snr: firstPath.snr ?? null }
|
||||
: undefined;
|
||||
|
||||
if (message.packet_id == null) {
|
||||
setPacketInspectorSource({
|
||||
kind: 'unavailable',
|
||||
@@ -1180,12 +1190,18 @@ export function MessageList({
|
||||
{packetInspectorSource && (
|
||||
<RawPacketInspectorDialog
|
||||
open={packetInspectorSource !== null}
|
||||
onOpenChange={(isOpen) => !isOpen && setPacketInspectorSource(null)}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (!isOpen) {
|
||||
setPacketInspectorSource(null);
|
||||
packetSignalOverrideRef.current = undefined;
|
||||
}
|
||||
}}
|
||||
channels={channels}
|
||||
source={packetInspectorSource}
|
||||
title="Analyze Packet"
|
||||
description="On-demand raw packet analysis for a message-backed archival packet."
|
||||
notice={ANALYZE_PACKET_NOTICE}
|
||||
signalOverride={packetSignalOverrideRef.current}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -103,14 +103,25 @@ export function PathModal({
|
||||
) : null}
|
||||
|
||||
{/* Raw path summary */}
|
||||
<div className="text-sm">
|
||||
<div className="text-sm space-y-1">
|
||||
{paths.map((p, index) => {
|
||||
const hops = parsePathHops(p.path, p.path_len);
|
||||
const rawPath = hops.length > 0 ? hops.join('->') : 'direct';
|
||||
const hasSignal = p.rssi != null || p.snr != null;
|
||||
return (
|
||||
<div key={index}>
|
||||
<span className="text-foreground/70 font-semibold">Path {index + 1}:</span>{' '}
|
||||
<span className="font-mono text-muted-foreground">{rawPath}</span>
|
||||
<div>
|
||||
<span className="text-foreground/70 font-semibold">Path {index + 1}:</span>{' '}
|
||||
<span className="font-mono text-muted-foreground">{rawPath}</span>
|
||||
</div>
|
||||
{hasSignal && (
|
||||
<div className="text-[0.6875rem] text-muted-foreground ml-4">
|
||||
Last hop (as heard by you):{' '}
|
||||
{p.rssi != null && <span>{p.rssi} dBm RSSI</span>}
|
||||
{p.rssi != null && p.snr != null && <span> · </span>}
|
||||
{p.snr != null && <span>{p.snr.toFixed(1)} dB SNR</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -35,6 +35,11 @@ type RawPacketInspectorDialogSource =
|
||||
message: string;
|
||||
};
|
||||
|
||||
interface SignalOverride {
|
||||
rssi: number | null;
|
||||
snr: number | null;
|
||||
}
|
||||
|
||||
interface RawPacketInspectorDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
@@ -43,10 +48,12 @@ interface RawPacketInspectorDialogProps {
|
||||
title: string;
|
||||
description: string;
|
||||
notice?: ReactNode;
|
||||
signalOverride?: SignalOverride;
|
||||
}
|
||||
|
||||
interface RawPacketInspectionPanelProps {
|
||||
packet: RawPacket;
|
||||
signalOverride?: SignalOverride;
|
||||
channels: Channel[];
|
||||
}
|
||||
|
||||
@@ -125,15 +132,21 @@ function formatTimestamp(timestamp: number): string {
|
||||
});
|
||||
}
|
||||
|
||||
function formatSignal(packet: RawPacket): string {
|
||||
const parts: string[] = [];
|
||||
if (packet.rssi !== null) {
|
||||
parts.push(`${packet.rssi} dBm RSSI`);
|
||||
}
|
||||
if (packet.snr !== null) {
|
||||
parts.push(`${packet.snr.toFixed(1)} dB SNR`);
|
||||
}
|
||||
return parts.length > 0 ? parts.join(' · ') : 'No signal sample';
|
||||
function formatSignal(
|
||||
packet: RawPacket,
|
||||
signalOverride?: SignalOverride
|
||||
): { lines: string[]; label: string } {
|
||||
const rssi = signalOverride?.rssi ?? packet.rssi;
|
||||
const snr = signalOverride?.snr ?? packet.snr;
|
||||
const lines: string[] = [];
|
||||
if (rssi !== null) lines.push(`${rssi} dBm RSSI`);
|
||||
if (snr !== null) lines.push(`${snr.toFixed(1)} dB SNR`);
|
||||
const isOverride =
|
||||
signalOverride != null && (signalOverride.rssi != null || signalOverride.snr != null);
|
||||
return {
|
||||
lines: lines.length > 0 ? lines : ['No signal sample'],
|
||||
label: isOverride ? 'Last Hop Signal' : 'Signal',
|
||||
};
|
||||
}
|
||||
|
||||
function formatByteRange(field: PacketByteField): string {
|
||||
@@ -569,7 +582,11 @@ function FieldSection({
|
||||
);
|
||||
}
|
||||
|
||||
export function RawPacketInspectionPanel({ packet, channels }: RawPacketInspectionPanelProps) {
|
||||
export function RawPacketInspectionPanel({
|
||||
packet,
|
||||
channels,
|
||||
signalOverride,
|
||||
}: RawPacketInspectionPanelProps) {
|
||||
const decoderOptions = useMemo(() => createDecoderOptions(channels), [channels]);
|
||||
const groupTextCandidates = useMemo(
|
||||
() => buildGroupTextResolutionCandidates(channels),
|
||||
@@ -641,11 +658,24 @@ export function RawPacketInspectionPanel({ packet, channels }: RawPacketInspecti
|
||||
primary={`${inspection.routeTypeName} · ${inspection.payloadTypeName}`}
|
||||
secondary={`${inspection.payloadVersionName} · ${formatPathMode(inspection.decoded?.pathHashSize, inspection.pathTokens.length)}`}
|
||||
/>
|
||||
<CompactMetaCard
|
||||
label="Signal"
|
||||
primary={formatSignal(packet)}
|
||||
secondary={packetContext ? null : undefined}
|
||||
/>
|
||||
{(() => {
|
||||
const sig = formatSignal(packet, signalOverride);
|
||||
return (
|
||||
<div className="rounded-lg border border-border/70 bg-card/70 p-2.5">
|
||||
<div className="text-[0.625rem] uppercase tracking-wider text-muted-foreground">
|
||||
{sig.label}
|
||||
</div>
|
||||
{sig.lines.map((line, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`${i === 0 ? 'mt-1' : 'mt-0.5'} text-sm font-medium leading-tight text-foreground`}
|
||||
>
|
||||
{line}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -715,6 +745,7 @@ export function RawPacketInspectorDialog({
|
||||
title,
|
||||
description,
|
||||
notice,
|
||||
signalOverride,
|
||||
}: RawPacketInspectorDialogProps) {
|
||||
const [packetInput, setPacketInput] = useState('');
|
||||
|
||||
@@ -739,7 +770,13 @@ export function RawPacketInspectorDialog({
|
||||
|
||||
let body: ReactNode;
|
||||
if (source.kind === 'packet') {
|
||||
body = <RawPacketInspectionPanel packet={source.packet} channels={channels} />;
|
||||
body = (
|
||||
<RawPacketInspectionPanel
|
||||
packet={source.packet}
|
||||
channels={channels}
|
||||
signalOverride={signalOverride}
|
||||
/>
|
||||
);
|
||||
} else if (source.kind === 'paste') {
|
||||
body = (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user