Skip to content

Commit bdc425f

Browse files
author
Jesper Lindstrøm Nielsen
authored
Introduced background reader, again (#9)
1 parent a05a171 commit bdc425f

File tree

3 files changed

+298
-140
lines changed

3 files changed

+298
-140
lines changed

src/errors.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (C) 2021 Toitware ApS. All rights reserved.
2+
// Use of this source code is governed by an MIT-style license that can be
3+
// found in the LICENSE file.
4+
5+
export const UnknownChipFamilyError = "Unknown chip family";
6+
export const ClosedError = "closed";
7+
export const TimeoutError = "timeout";
8+
export const ConnectError = "connect error";
9+
export const ReadAlreadyInProgressError = "Read already in progress";
10+
export const AlreadyRunningError = "already running";
11+
export const NotRunningError = "not running";
12+
export const NotListeningError = "not listening";

src/index.ts

Lines changed: 65 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// found in the LICENSE file.
44

55
import { ESP32, Stub } from "./stubs";
6-
import { isTransientError, sleep, toByteArray, toHex, Uint8Buffer, Uint8BufferSlipEncode } from "./util";
6+
import { sleep, toByteArray, toHex, Uint8Buffer, Uint8BufferSlipEncode } from "./util";
7+
import { UnknownChipFamilyError, ConnectError } from "./errors";
8+
import { Reader } from "./reader";
79

810
export enum ChipFamily {
911
ESP32 = "esp32",
@@ -50,7 +52,7 @@ const ESP_RAM_BLOCK = 0x1800;
5052

5153
// Timeouts
5254
const DEFAULT_TIMEOUT = 3000; // timeout for most flash operations
53-
const CHIP_ERASE_TIMEOUT = 300000; // timeout for full chip erase
55+
const CHIP_ERASE_TIMEOUT = 120000; // timeout for full chip erase
5456
const MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2; // longest any command can run
5557
const SYNC_TIMEOUT = 100; // timeout for syncing with bootloader
5658
const ERASE_REGION_TIMEOUT_PER_MB = 30000; // timeout (per megabyte) for erasing a region
@@ -75,12 +77,6 @@ interface commandResult {
7577

7678
type progressCallback = (i: number, total: number) => void;
7779

78-
const UnknownChipFamilyError = "Unknown chip family";
79-
const ClosedError = "closed";
80-
const TimeoutError = "timeout";
81-
const ConnectError = "connect error";
82-
const ReadAlreadyInProgressError = "Read already in progress";
83-
8480
export class EspLoader {
8581
// caches
8682
private _chipfamily: ChipFamily | undefined;
@@ -92,8 +88,7 @@ export class EspLoader {
9288

9389
private baudRate = ESP_ROM_BAUD;
9490

95-
private serialReaderClosed = false;
96-
private serialReader: ReadableStreamDefaultReader<Uint8Array> | undefined = undefined;
91+
private reader: Reader;
9792

9893
constructor(serialPort: SerialPort, options?: Partial<EspLoaderOptions>) {
9994
this.options = Object.assign(
@@ -104,6 +99,7 @@ export class EspLoader {
10499
},
105100
options || {}
106101
);
102+
this.reader = new Reader();
107103
this.serialPort = serialPort;
108104
}
109105

@@ -125,8 +121,12 @@ export class EspLoader {
125121
* ESP ROM bootloader, we will retry a few times
126122
*/
127123
async connect(retries = 7): Promise<void> {
124+
this.reader.start(this.serialPort.readable);
128125
let connected = false;
129126
for (let i = 0; i < retries; i++) {
127+
if (i > 0) {
128+
this.options.logger.log("retrying...");
129+
}
130130
if (await this.try_connect()) {
131131
connected = true;
132132
break;
@@ -137,61 +137,52 @@ export class EspLoader {
137137
throw ConnectError;
138138
}
139139

140-
await this.flushInput();
140+
await this.reader.waitSilent(1, 200);
141141
await this.chipFamily();
142142
}
143143

144144
private async try_connect(): Promise<boolean> {
145-
await this.serialPort.setSignals({ dataTerminalReady: false });
146-
await this.serialPort.setSignals({ requestToSend: true });
145+
await this.serialPort.setSignals({ dataTerminalReady: false, requestToSend: true });
147146
await sleep(100);
148-
await this.serialPort.setSignals({ dataTerminalReady: true });
149-
await this.serialPort.setSignals({ requestToSend: false });
147+
await this.serialPort.setSignals({ dataTerminalReady: true, requestToSend: false });
150148
await sleep(50);
151-
await this.serialPort.setSignals({ dataTerminalReady: false });
149+
await this.serialPort.setSignals({ dataTerminalReady: false, requestToSend: false });
152150

153151
// Wait until device has stable output.
154-
for (let i = 0; i < 20; i++) {
155-
try {
156-
await this.read(false, 1000);
157-
} catch (e) {
158-
if (e === TimeoutError) {
159-
break;
160-
}
161-
}
162-
await sleep(50);
152+
const wasSilent = await this.reader.waitSilent(20, 1000);
153+
if (!wasSilent) {
154+
this.options.logger.log("failed to enter bootloader");
155+
return false;
163156
}
157+
this.options.logger.log("trying to sync with bootloader...");
164158

165159
// Try sync.
160+
this.options.logger.debug("sync started");
166161
for (let i = 0; i < 7; i++) {
167162
try {
168163
if (await this.sync()) {
164+
this.options.logger.log("synced with bootloader");
169165
return true;
170166
}
171167
} catch (e) {
172168
this.options.logger.debug("sync error", e);
173169
}
174170
await sleep(50);
175171
}
172+
this.options.logger.debug("sync stopped");
176173

174+
this.options.logger.log("failed to sync with bootloader");
177175
return false;
178176
}
179177

180178
/**
181179
* shutdown the read loop.
182180
*/
183181
async disconnect(): Promise<void> {
184-
const reader = this.serialReader;
185-
if (reader) {
186-
try {
187-
this.serialReaderClosed = true;
188-
await reader.cancel();
189-
await reader.closed;
190-
} catch (e) {
191-
//ignore cancel errors.
192-
}
182+
const err = await this.reader.stop();
183+
if (err !== undefined) {
184+
throw err;
193185
}
194-
return;
195186
}
196187

197188
async crystalFrequency(): Promise<number> {
@@ -348,20 +339,24 @@ export class EspLoader {
348339
timeout: number = DEFAULT_TIMEOUT
349340
): Promise<number[]> {
350341
timeout = Math.min(timeout, MAX_TIMEOUT);
351-
await this.sendCommand(opcode, buffer, checksum);
352-
const resp = await this.getResponse(opcode, timeout);
353-
const data = resp.data;
354-
const value = resp.value;
355-
if (data.length > 4) {
356-
return data;
357-
} else {
358-
return value;
342+
const unlisten = this.reader.listen();
343+
try {
344+
await this.sendCommand(opcode, buffer, checksum);
345+
const resp = await this.getResponse(opcode, timeout);
346+
const data = resp.data;
347+
const value = resp.value;
348+
if (data.length > 4) {
349+
return data;
350+
} else {
351+
return value;
352+
}
353+
} finally {
354+
unlisten();
359355
}
360356
}
361357

362358
private _sendCommandBuffer = new Uint8BufferSlipEncode();
363359
private async sendCommand(opcode: number, buffer: Uint8Array, checksum = 0) {
364-
this.readBuffer.reset();
365360
const packet = this._sendCommandBuffer;
366361
packet.reset();
367362
packet.push(0xc0, 0x00); // direction
@@ -382,7 +377,7 @@ export class EspLoader {
382377

383378
private async getResponse(opcode: number, timeout: number = DEFAULT_TIMEOUT): Promise<commandResult> {
384379
try {
385-
const reply = await this.read(true, timeout);
380+
const reply = await this.reader.packet(12, timeout);
386381
if (this.options.debug) {
387382
this.logger.debug("Reading", reply.length, "byte" + (reply.length == 1 ? "" : "s") + ":", reply);
388383
}
@@ -425,32 +420,34 @@ export class EspLoader {
425420

426421
// Reopen the port and read loop
427422
await this.serialPort.open({ baudRate: baud });
423+
this.reader.start(this.serialPort.readable);
428424
await sleep(50);
429-
await this.flushInput();
425+
const wasSilent = await this.reader.waitSilent(10, 200);
426+
if (!wasSilent) {
427+
this.logger.debug("after baud change reader was not silent");
428+
}
430429

431430
// Baud rate was changed
432431
this.logger.log("Changed baud rate to", baud);
433432
this.baudRate = baud;
434433
}
435434

436-
async flushInput(): Promise<void> {
437-
try {
438-
this.readBuffer.reset();
439-
await this.read(false, 200);
440-
} catch (e) {}
441-
}
442-
443435
/**
444436
* Put into ROM bootload mode & attempt to synchronize with the
445437
* ESP ROM bootloader, we will retry a few times
446438
*/
447439
private async sync(): Promise<boolean> {
448-
await this.sendCommand(ESP_SYNC, SYNC_PACKET);
449-
const { data } = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
450-
if (data.length > 1 && data[0] == 0 && data[1] == 0) {
451-
return true;
440+
const unlisten = this.reader.listen();
441+
try {
442+
await this.sendCommand(ESP_SYNC, SYNC_PACKET);
443+
const { data } = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
444+
if (data.length > 1 && data[0] == 0 && data[1] == 0) {
445+
return true;
446+
}
447+
return false;
448+
} finally {
449+
unlisten();
452450
}
453-
return false;
454451
}
455452

456453
private getFlashWriteSize(): number {
@@ -677,95 +674,23 @@ export class EspLoader {
677674
await writeMem(stub.text, stub.textStart);
678675
await writeMem(stub.data, stub.dataStart);
679676
this.logger.log("Running stub...");
680-
await this.memFinish(stub.entry);
681-
682-
const p = await this.read(true, 1000, 6);
683-
const str = String.fromCharCode(...p);
684-
if (str !== "OHAI") {
685-
throw "Failed to start stub. Unexpected response: " + str;
677+
const unlisten = this.reader.listen();
678+
try {
679+
await this.memFinish(stub.entry);
680+
const p = await this.reader.packet(6, 1000);
681+
const str = String.fromCharCode(...p);
682+
if (str !== "OHAI") {
683+
throw "Failed to start stub. Unexpected response: " + str;
684+
}
685+
} finally {
686+
unlisten();
686687
}
687688
this.logger.log("Stub is now running...");
688689
this.isStub = true;
689690
this._chipfamily = undefined;
690691
this._efuses = undefined;
691692
}
692693

693-
private readBuffer = new Uint8BufferSlipEncode();
694-
private async read(packetMode = true, timeoutMs = 1000, minRead = 12): Promise<Uint8Array> {
695-
if (this.serialReader !== undefined) {
696-
throw ReadAlreadyInProgressError;
697-
}
698-
let reader = this.serialPort.readable.getReader();
699-
this.serialReader = reader;
700-
this.serialReaderClosed = false;
701-
const chTimeout = setTimeout(async () => {
702-
try {
703-
this.serialReaderClosed = true;
704-
await reader.cancel();
705-
} catch (e) {
706-
// Ignore cancel errors.
707-
}
708-
}, timeoutMs);
709-
710-
try {
711-
while (true) {
712-
if (this.serialReaderClosed) {
713-
throw TimeoutError;
714-
}
715-
716-
try {
717-
return await this._read(reader, packetMode, minRead);
718-
} catch (e) {
719-
if (e === ClosedError) {
720-
reader.releaseLock();
721-
await sleep(1);
722-
reader = this.serialPort.readable.getReader();
723-
this.serialReader = reader;
724-
} else if (!isTransientError(e)) {
725-
throw e;
726-
}
727-
}
728-
}
729-
} finally {
730-
clearTimeout(chTimeout);
731-
try {
732-
await reader.cancel();
733-
} catch (e) {
734-
// ignore cancel errors.
735-
}
736-
reader.releaseLock();
737-
this.serialReader = undefined;
738-
this.serialReaderClosed = false;
739-
}
740-
}
741-
742-
private async _read(
743-
reader: ReadableStreamDefaultReader<Uint8Array>,
744-
packetMode = true,
745-
minRead = 12
746-
): Promise<Uint8Array> {
747-
while (true) {
748-
if (this.readBuffer.length >= minRead) {
749-
if (packetMode) {
750-
const res = this.readBuffer.packet(true);
751-
if (res !== undefined) {
752-
return res;
753-
}
754-
} else {
755-
return this.readBuffer.view();
756-
}
757-
}
758-
759-
const { value, done } = await reader.read();
760-
if (value) {
761-
this.readBuffer.copy(value);
762-
}
763-
if (done) {
764-
throw ClosedError;
765-
}
766-
}
767-
}
768-
769694
/**
770695
* erase the flash of the device
771696
*

0 commit comments

Comments
 (0)