From d6e5a7f6818560aafcf061227006fc6b9771a8ae Mon Sep 17 00:00:00 2001 From: Mae PUGIN <48982737+mornepousse@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:41:21 +0200 Subject: [PATCH] fix: OTA protocol with proper handshake Previous OTA sent chunks blindly without waiting for firmware ACK. Now properly implements the protocol: 1. Send "OTA " 2. Wait for "OTA_READY" with 5s timeout 3. Send 4096-byte chunks, wait for "OTA_OK" after each 4. Stop on "OTA_DONE" or abort on "OTA_FAIL" 5. Proper port timeout management Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main.rs | 79 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2cd3bc8..acd87d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1376,27 +1376,21 @@ fn main() { let serial = serial.clone(); let tx = tx.clone(); std::thread::spawn(move || { + use std::io::{Read, Write, BufRead, BufReader}; + let mut ser = serial.lock().unwrap_or_else(|e| e.into_inner()); let total = firmware.len(); let chunk_size = 4096usize; - // Step 1: Send OTA command + // Step 1: Send OTA command let cmd = format!("OTA {}", total); if let Err(e) = ser.send_command(&cmd) { let _ = tx.send(BgMsg::OtaDone(Err(format!("Send OTA cmd failed: {}", e)))); return; } - // Step 2: Wait for OTA_READY + // Step 2: Wait for OTA_READY (read lines with timeout) let _ = tx.send(BgMsg::OtaProgress(0.0, "Waiting for OTA_READY...".into())); - let ready = ser.query_command("").unwrap_or_default(); - let got_ready = ready.iter().any(|l| l.contains("OTA_READY")); - if !got_ready { - // Try reading more - std::thread::sleep(std::time::Duration::from_secs(2)); - } - - // Step 3: Send chunks let port = match ser.port_mut() { Some(p) => p, None => { @@ -1405,28 +1399,77 @@ fn main() { } }; - use std::io::Write; + // Read until we get OTA_READY or timeout + let old_timeout = port.timeout(); + let _ = port.set_timeout(std::time::Duration::from_secs(5)); + let mut got_ready = false; + let port_clone = port.try_clone().unwrap(); + let mut reader = BufReader::new(port_clone); + for _ in 0..20 { + let mut line = String::new(); + if reader.read_line(&mut line).is_ok() { + if line.contains("OTA_READY") { + got_ready = true; + break; + } + } + } + drop(reader); + + if !got_ready { + let _ = port.set_timeout(old_timeout); + let _ = tx.send(BgMsg::OtaDone(Err("Firmware did not respond OTA_READY".into()))); + return; + } + + // Step 3: Send chunks and wait for ACK after each let num_chunks = (total + chunk_size - 1) / chunk_size; + let _ = port.set_timeout(std::time::Duration::from_secs(5)); + for (i, chunk) in firmware.chunks(chunk_size).enumerate() { + // Send chunk if let Err(e) = port.write_all(chunk) { + let _ = port.set_timeout(old_timeout); let _ = tx.send(BgMsg::OtaDone(Err(format!("Write chunk {} failed: {}", i, e)))); return; } let _ = port.flush(); let progress = (i + 1) as f32 / num_chunks as f32; - let msg = format!("Chunk {}/{} ({} KB / {} KB)", + let _ = tx.send(BgMsg::OtaProgress(progress * 0.95, format!( + "Chunk {}/{} ({} KB / {} KB)", i + 1, num_chunks, ((i + 1) * chunk_size).min(total) / 1024, - total / 1024); - let _ = tx.send(BgMsg::OtaProgress(progress * 0.95, msg)); + total / 1024 + ))); - // Wait for ACK (read with timeout) - std::thread::sleep(std::time::Duration::from_millis(50)); - let mut buf = [0u8; 256]; - let _ = port.read(&mut buf); + // Wait for ACK line + let mut ack_buf = [0u8; 256]; + let mut ack = String::new(); + let start = std::time::Instant::now(); + while start.elapsed() < std::time::Duration::from_secs(5) { + match port.read(&mut ack_buf) { + Ok(n) if n > 0 => { + ack.push_str(&String::from_utf8_lossy(&ack_buf[..n])); + if ack.contains("OTA_OK") || ack.contains("OTA_DONE") || ack.contains("OTA_FAIL") { + break; + } + } + _ => std::thread::sleep(std::time::Duration::from_millis(10)), + } + } + + if ack.contains("OTA_FAIL") { + let _ = port.set_timeout(old_timeout); + let _ = tx.send(BgMsg::OtaDone(Err(format!("Firmware error: {}", ack.trim())))); + return; + } + if ack.contains("OTA_DONE") { + break; // Firmware signals all received + } } + let _ = port.set_timeout(old_timeout); let _ = tx.send(BgMsg::OtaProgress(1.0, "OTA complete, rebooting...".into())); let _ = tx.send(BgMsg::OtaDone(Ok(()))); });