From 5f01ec31ed2f997526f5e7b6a0b65faa6beef2d6 Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 22 Jul 2026 17:48:04 -0400 Subject: [PATCH] fix(mesh): probe retries across the listener's port-hold windows Linux double-opens ttys, so a probe racing the reconnect loop corrupted both handshakes into silence (framework-pt, real Reticulum stick). Three attempts 7s apart land in the listener's backoff gaps. Co-Authored-By: Claude Fable 5 --- core/archipelago/src/mesh/listener/session.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/archipelago/src/mesh/listener/session.rs b/core/archipelago/src/mesh/listener/session.rs index b27acaf8..9db19b43 100644 --- a/core/archipelago/src/mesh/listener/session.rs +++ b/core/archipelago/src/mesh/listener/session.rs @@ -366,6 +366,25 @@ pub struct DeviceProbe { /// loop can pick the device up afterwards. Reticulum uses the bare KISS /// DETECT probe — no daemon spawn just to identify a stick. pub(crate) async fn probe_device(path: &str) -> Result { + // The listener's reconnect loop may hold this port for ~10s of every + // backoff cycle, and Linux happily double-opens a tty — two concurrent + // handshakes corrupt each other into silence (observed on framework-pt: + // every firmware "failed" while the listener was mid-cycle). Retry across + // the listener's idle gaps instead of failing on first collision. + let mut last_err = None; + for attempt in 0..3u32 { + if attempt > 0 { + tokio::time::sleep(Duration::from_secs(7)).await; + } + match probe_device_once(path).await { + Ok(p) => return Ok(p), + Err(e) => last_err = Some(e), + } + } + Err(last_err.unwrap_or_else(|| anyhow::anyhow!("probe failed"))) +} + +async fn probe_device_once(path: &str) -> Result { if super::super::reticulum::probe_rnode(path).await.is_ok() { return Ok(DeviceProbe { path: path.to_string(),