From 3c7c04a662f8b8f363ae146e8e999ef1548ccea6 Mon Sep 17 00:00:00 2001 From: archipelago Date: Mon, 29 Jun 2026 05:04:09 -0400 Subject: [PATCH] =?UTF-8?q?fix(mesh):=20meshtastic=20receive=20=E2=80=94?= =?UTF-8?q?=20drain=20frame=20batch=20per=20poll=20+=20rx=20diagnostics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the open Meshtastic parity bug (project_meshtastic_parity): the running driver received nothing (`mesh.messages` stayed []) though the radio got the packets and sends worked. Root-cause candidate: `try_recv_frame` decoded ONE serial frame per poll and returned Ok(None) for every non-text FromRadio frame, so the session loop slept 50ms between frames. Under Meshtastic's frequent NodeInfo/telemetry stream a received text packet queued behind them, and read_from_radio's 64KB buffer cap could drain (drop) it before it was ever decoded — reception silently dead while sends kept working. - try_recv_frame now drains a bounded batch (64) per poll, processing each frame's side effects and returning the first inbound text frame, so a text packet is decoded the same poll it arrives and the buffer never grows enough to hit the lossy cap. Bounded so a continuous flood still yields to select!. - packet_to_inbound_frame logs every decoded packet (from/portnum/payload_len) and a "did not parse (dropped)" case, so one live radio pass is conclusive. The rest of the decode path was verified correct by inspection (FROM_RADIO_PACKET =2, wire-type-5 handled, parse_mesh_packet sound, 60s heartbeat present) — not a parse bug. cargo check green. NEEDS a live radio pass on a rig that isn't .228 (off-limits: bitcoin testing) to confirm. Co-Authored-By: Claude Opus 4.8 (1M context) --- core/archipelago/src/mesh/meshtastic.rs | 43 ++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/core/archipelago/src/mesh/meshtastic.rs b/core/archipelago/src/mesh/meshtastic.rs index 6a8d9c97..ec812f64 100644 --- a/core/archipelago/src/mesh/meshtastic.rs +++ b/core/archipelago/src/mesh/meshtastic.rs @@ -530,10 +530,25 @@ impl MeshtasticDevice { } pub async fn try_recv_frame(&mut self) -> Result> { - let Some(frame) = self.read_from_radio().await? else { - return Ok(None); - }; - Ok(self.handle_from_radio(&frame)) + // Drain a bounded batch of frames per poll, processing EACH for its side + // effects (my_info/config/channel/node_info) and returning the first that + // yields an inbound text frame. The old one-frame-per-poll behavior + // returned Ok(None) for every non-text frame, so the caller slept 50ms + // between frames; under Meshtastic's frequent NodeInfo/telemetry stream a + // received text packet queued behind them and the read buffer's 64KB cap + // could drain (drop) it before it was ever decoded — silently killing + // reception while sends kept working. Draining keeps the buffer short so + // the text frame is decoded the same poll it arrives. Bounded to 64 so a + // continuous flood still yields back to the session select! loop. + for _ in 0..64 { + let Some(frame) = self.read_from_radio().await? else { + return Ok(None); + }; + if let Some(inbound) = self.handle_from_radio(&frame) { + return Ok(Some(inbound)); + } + } + Ok(None) } /// Whether we've learned `node_num`'s real PKI (Curve25519) key — from a @@ -700,7 +715,25 @@ impl MeshtasticDevice { } fn packet_to_inbound_frame(&mut self, data: &[u8]) -> Option { - let packet = parse_mesh_packet(data)?; + let Some(packet) = parse_mesh_packet(data) else { + // Diagnostic for the open receive bug (project_meshtastic_parity): a + // forwarded FromRadio.packet that won't parse (e.g. encrypted-only, + // no `decoded` field) is dropped here. Watch for this on the live pass. + debug!( + len = data.len(), + head = %hex::encode(&data[..data.len().min(12)]), + "Meshtastic FromRadio.packet did not parse (dropped)" + ); + return None; + }; + // Trace EVERY decoded packet so the live pass shows what actually arrives + // and why a text frame is (or isn't) surfaced. + debug!( + from = format!("!{:08x}", packet.from.unwrap_or(0)), + portnum = packet.portnum, + payload_len = packet.payload.len(), + "Meshtastic FromRadio.packet decoded" + ); if packet.portnum != TEXT_MESSAGE_APP || packet.payload.is_empty() { return None; }