fix(usb-deployer): preliminary get_sync robustness improvements

This commit is contained in:
a2nr 2026-07-17 10:23:39 +07:00
parent 24db01fd8d
commit 7dd8e5d522
1 changed files with 32 additions and 14 deletions

View File

@ -92,6 +92,7 @@ const ATMEGA328P_FLASH_SIZE = 32768;
const ATMEGA328P_PAGE_SIZE = 128; const ATMEGA328P_PAGE_SIZE = 128;
const STK_SYNC_RETRIES = 10; const STK_SYNC_RETRIES = 10;
const STK_SYNC_TIMEOUT_MS = 500; /* Longer timeout for initial sync (optiboot may be slow) */
const STK_CMD_TIMEOUT_MS = 200; const STK_CMD_TIMEOUT_MS = 200;
const STK_PAGE_TIMEOUT_MS = 500; const STK_PAGE_TIMEOUT_MS = 500;
const STK_LEAVE_TIMEOUT_MS = 100; const STK_LEAVE_TIMEOUT_MS = 100;
@ -215,7 +216,8 @@ export class USBHardwareDeployer implements HardwareDeployer {
dataTerminalReady: true, dataTerminalReady: true,
requestToSend: true requestToSend: true
}); });
await sleep(50); /* Wait for optiboot to initialize (~100ms is safe, gives us ~900ms for sync) */
await sleep(100);
console.log('[USB] resetArduino: DTR pulse done'); console.log('[USB] resetArduino: DTR pulse done');
} }
@ -483,20 +485,32 @@ export class USBHardwareDeployer implements HardwareDeployer {
/** /**
* Drain any stray bytes from the serial input (short timeout). * Drain any stray bytes from the serial input (short timeout).
* Drains repeatedly until no more bytes arrive or 50ms elapsed.
*/ */
private async drainStray(): Promise<void> { private async drainStray(): Promise<void> {
try { const deadline = Date.now() + 50;
const { value, done } = await Promise.race([ let totalDrained = 0;
this.reader!.read(),
new Promise<never>((_, reject) => while (Date.now() < deadline) {
setTimeout(() => reject(new Error('timeout')), 10) try {
) const { value, done } = await Promise.race([
]); this.reader!.read(),
if (!done && value && value.length > 0) { new Promise<never>((_, reject) =>
console.log(`[USB] drained ${value.length} stray bytes`); setTimeout(() => reject(new Error('timeout')), 10)
)
]);
if (done) break;
if (value && value.length > 0) {
totalDrained += value.length;
}
} catch {
/* timeout — no more bytes available */
break;
} }
} catch { }
/* nothing to drain — expected */
if (totalDrained > 0) {
console.log(`[USB] drained ${totalDrained} stray bytes`);
} }
} }
@ -507,14 +521,18 @@ export class USBHardwareDeployer implements HardwareDeployer {
for (let attempt = 0; attempt < STK_SYNC_RETRIES; attempt++) { for (let attempt = 0; attempt < STK_SYNC_RETRIES; attempt++) {
try { try {
await this.sendAndExpect(cmd, 0, STK_CMD_TIMEOUT_MS, true); /* Drain before sending to ensure clean channel */
await this.drainStray();
await this.sendAndExpect(cmd, 0, STK_SYNC_TIMEOUT_MS, true);
console.log(`[USB] get_sync OK (attempt ${attempt + 1})`); console.log(`[USB] get_sync OK (attempt ${attempt + 1})`);
return; return;
} catch (e) { } catch (e) {
console.log(`[USB] get_sync attempt ${attempt + 1} failed:`, e); console.log(`[USB] get_sync attempt ${attempt + 1} failed:`, e);
/* Drain stray bytes before retry */ /* Drain stray bytes before retry */
await this.drainStray(); await this.drainStray();
await sleep(20); /* Exponential backoff: 20ms, 40ms, 80ms, ... max 200ms */
const delay = Math.min(20 * Math.pow(2, attempt), 200);
await sleep(delay);
} }
} }
throw new Error( throw new Error(