Skip to content

Commit 6c57b8c

Browse files
brianignacio5tyeth
authored andcommitted
add onDeviceLostCallback to recover lost device
1 parent 9466053 commit 6c57b8c

File tree

2 files changed

+100
-7
lines changed

2 files changed

+100
-7
lines changed

examples/typescript/src/index.ts

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const term = new Terminal({ cols: 120, rows: 40 });
4949
term.open(terminal);
5050

5151
let device = null;
52+
let deviceInfo = null;
5253
let transport: Transport;
5354
let chip: string = null;
5455
let esploader: ESPLoader;
@@ -105,6 +106,7 @@ connectButton.onclick = async () => {
105106
try {
106107
if (device === null) {
107108
device = await serialLib.requestPort({});
109+
deviceInfo = device.getInfo();
108110
transport = new Transport(device, true);
109111
}
110112
const flashOptions = {
@@ -228,6 +230,7 @@ function removeRow(row: HTMLTableRowElement) {
228230
*/
229231
function cleanUp() {
230232
device = null;
233+
deviceInfo = null;
231234
transport = null;
232235
chip = null;
233236
initializePartitionTable();
@@ -461,11 +464,50 @@ disconnectButton.onclick = async () => {
461464
};
462465

463466
let isConsoleClosed = false;
467+
let isReconnecting = false;
468+
469+
const sleep = async (ms: number) => {
470+
return new Promise((resolve) => setTimeout(resolve, ms));
471+
};
472+
464473
consoleStartButton.onclick = async () => {
465474
if (device === null) {
466475
device = await serialLib.requestPort({});
467476
transport = new Transport(device, true);
477+
deviceInfo = device.getInfo();
478+
479+
// Set up device lost callback
480+
transport.setDeviceLostCallback(async () => {
481+
if (!isConsoleClosed && !isReconnecting) {
482+
term.writeln("\n[DEVICE LOST] Device disconnected. Click 'Reconnect' to restore connection...");
483+
await sleep(1000);
484+
isReconnecting = true;
485+
term.writeln("\n[RECONNECT] Attempting to reconnect...");
486+
if (serialLib && serialLib.getPorts) {
487+
const ports = await serialLib.getPorts();
488+
if (ports.length > 0) {
489+
const newDevice = ports.find(
490+
(port) =>
491+
port.getInfo().usbVendorId === deviceInfo.usbVendorId &&
492+
port.getInfo().usbProductId === deviceInfo.usbProductId,
493+
);
494+
device = newDevice;
495+
transport.updateDevice(device);
496+
term.writeln("[RECONNECT] Found previously authorized device, connecting...");
497+
await transport.connect(parseInt(consoleBaudrates.value));
498+
term.writeln("[RECONNECT] Successfully reconnected!");
499+
consoleStopButton.style.display = "initial";
500+
resetButton.style.display = "initial";
501+
isReconnecting = false;
502+
503+
startConsoleReading();
504+
return;
505+
}
506+
}
507+
}
508+
});
468509
}
510+
469511
lblConsoleFor.style.display = "block";
470512
lblConsoleBaudrate.style.display = "none";
471513
consoleBaudrates.style.display = "none";
@@ -476,21 +518,45 @@ consoleStartButton.onclick = async () => {
476518

477519
await transport.connect(parseInt(consoleBaudrates.value));
478520
isConsoleClosed = false;
521+
isReconnecting = false;
522+
523+
startConsoleReading();
524+
};
525+
526+
/**
527+
* Start the console reading loop
528+
*/
529+
async function startConsoleReading() {
530+
if (isConsoleClosed || !transport) return;
479531

480-
while (true && !isConsoleClosed) {
532+
try {
481533
const readLoop = transport.rawRead();
482-
const { value, done } = await readLoop.next();
483534

484-
if (done || !value) {
485-
break;
535+
while (true && !isConsoleClosed) {
536+
const { value, done } = await readLoop.next();
537+
538+
if (done || !value) {
539+
break;
540+
}
541+
542+
if (value) {
543+
term.write(value);
544+
}
545+
}
546+
} catch (error) {
547+
if (!isConsoleClosed) {
548+
term.writeln(`\n[CONSOLE ERROR] ${error instanceof Error ? error.message : String(error)}`);
486549
}
487-
term.write(value);
488550
}
489-
console.log("quitting console");
490-
};
551+
552+
if (!isConsoleClosed) {
553+
term.writeln("\n[CONSOLE] Connection lost, waiting for reconnection...");
554+
}
555+
}
491556

492557
consoleStopButton.onclick = async () => {
493558
isConsoleClosed = true;
559+
isReconnecting = false;
494560
if (transport) {
495561
await transport.disconnect();
496562
await transport.waitForUnlock(1500);

src/webserial.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,30 @@ class Transport {
6363
private lastTraceTime = Date.now();
6464
private reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
6565
private buffer: Uint8Array = new Uint8Array(0);
66+
private onDeviceLostCallback: (() => void) | null = null;
6667

6768
constructor(public device: SerialPort, public tracing = false, enableSlipReader = true) {
6869
this.slipReaderEnabled = enableSlipReader;
6970
}
7071

72+
/**
73+
* Set callback for when device is lost
74+
* @param callback Function to call when device is lost
75+
*/
76+
setDeviceLostCallback(callback: (() => void) | null) {
77+
this.onDeviceLostCallback = callback;
78+
}
79+
80+
81+
/**
82+
* Update the device reference (used when re-selecting device after reset)
83+
* @param newDevice New SerialPort device
84+
*/
85+
updateDevice(newDevice: SerialPort) {
86+
this.device = newDevice;
87+
this.trace("Device reference updated");
88+
}
89+
7190
/**
7291
* Request the serial device vendor ID and Product ID as string.
7392
* @returns {string} Return the device VendorID and ProductID from SerialPortInfo as formatted string.
@@ -388,6 +407,14 @@ class Transport {
388407
}
389408
} catch (error) {
390409
console.error("Error reading from serial port:", error);
410+
411+
// Check if it's a NetworkError indicating device loss
412+
if (error instanceof Error && error.name === 'NetworkError' && error.message.includes('device has been lost')) {
413+
this.trace("Device lost detected (NetworkError)");
414+
if (this.onDeviceLostCallback) {
415+
this.onDeviceLostCallback();
416+
}
417+
}
391418
} finally {
392419
this.buffer = new Uint8Array(0);
393420
}

0 commit comments

Comments
 (0)