From 9bbbb046a814ef7ab3e9f894719a038b59477ee8 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Sat, 4 Jul 2026 01:28:23 +0000 Subject: [PATCH] fix(mesh): deassert DTR/RTS before serial probes to avoid ESP32-S3 native-USB resets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Heltec V3/V4-class boards use the ESP32-S3's native USB-Serial-JTAG peripheral (no discrete USB-UART bridge chip), which resets the chip on a DTR/RTS transition — the same mechanism esptool uses to force bootloader entry. The Meshcore/Meshtastic/RNode serial probes all open the port with library defaults (DTR/RTS asserted), so each probe attempt was likely rebooting a real, correctly RNode-flashed Heltec board mid-handshake, surfacing as an endless "No supported mesh radio found" retry loop. Deassert both lines immediately after open and let the board settle before writing, in all three probe paths. Co-Authored-By: Claude Sonnet 5 --- core/archipelago/src/mesh/meshtastic.rs | 6 ++++++ core/archipelago/src/mesh/reticulum.rs | 8 ++++++++ core/archipelago/src/mesh/serial.rs | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/core/archipelago/src/mesh/meshtastic.rs b/core/archipelago/src/mesh/meshtastic.rs index 2692e483..1245e5fe 100644 --- a/core/archipelago/src/mesh/meshtastic.rs +++ b/core/archipelago/src/mesh/meshtastic.rs @@ -180,6 +180,12 @@ impl MeshtasticDevice { "Failed to open serial port {} (permission denied? device busy?)", path ))?; + // See probe_rnode() in reticulum.rs for why: ESP32-S3 native-USB + // boards reset on a DTR/RTS transition, so deassert both and settle + // before the handshake below. + let _ = port.set_dtr(false); + let _ = port.set_rts(false); + tokio::time::sleep(Duration::from_millis(300)).await; info!(path = %path, baud = BAUD_RATE, "Opened Meshtastic serial port"); Ok(Self { diff --git a/core/archipelago/src/mesh/reticulum.rs b/core/archipelago/src/mesh/reticulum.rs index adf02218..d2b89085 100644 --- a/core/archipelago/src/mesh/reticulum.rs +++ b/core/archipelago/src/mesh/reticulum.rs @@ -1005,6 +1005,14 @@ fn parse_hash16(hex_str: &str) -> Result<[u8; 16]> { async fn probe_rnode(path: &str) -> Result<()> { let port = serial2_tokio::SerialPort::open(path, PROBE_BAUD) .with_context(|| format!("Failed to open {} for Reticulum probe", path))?; + // ESP32-S3 native-USB boards (Heltec V3/V4 etc. — no separate USB-UART + // bridge chip) treat a DTR/RTS transition on open as a reset signal, the + // same mechanism esptool uses to force bootloader entry. Deassert both + // and let the board settle before writing the probe, or the reboot eats + // the DETECT_RESP window below. + let _ = port.set_dtr(false); + let _ = port.set_rts(false); + tokio::time::sleep(Duration::from_millis(300)).await; let probe: [u8; 13] = [ KISS_FEND, KISS_CMD_DETECT, diff --git a/core/archipelago/src/mesh/serial.rs b/core/archipelago/src/mesh/serial.rs index 5eaac130..5358ff0f 100644 --- a/core/archipelago/src/mesh/serial.rs +++ b/core/archipelago/src/mesh/serial.rs @@ -57,6 +57,12 @@ impl MeshcoreDevice { "Failed to open serial port {} (permission denied? device busy?)", path ))?; + // See probe_rnode() in reticulum.rs for why: ESP32-S3 native-USB + // boards reset on a DTR/RTS transition, so deassert both and settle + // before the handshake below. + let _ = port.set_dtr(false); + let _ = port.set_rts(false); + tokio::time::sleep(Duration::from_millis(300)).await; info!(path = %path, baud = BAUD_RATE, "Opened serial port");