Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reset modes #146

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 98 additions & 148 deletions src/esploader.ts
Original file line number Diff line number Diff line change
@@ -1,114 +1,13 @@
import { ESPError } from "./error.js";
import { ESPError } from "./types/error.js";
import { Data, deflate, Inflate } from "pako";
import { Transport, SerialOptions } from "./webserial.js";
import { ROM } from "./targets/rom.js";
import { customReset, usbJTAGSerialReset } from "./reset.js";
import { classicReset, customReset, hardReset, ResetFunctions, usbJTAGSerialReset } from "./reset.js";
import atob from "atob-lite";

/* global SerialPort */

/**
* Options for flashing a device with firmware.
* @interface FlashOptions
*/
export interface FlashOptions {
/**
* An array of file objects representing the data to be flashed.
* @type {Array<{ data: string; address: number }>}
*/
fileArray: { data: string; address: number }[];

/**
* The size of the flash memory to be used.
* @type {string}
*/
flashSize: string;

/**
* The flash mode to be used (e.g., QIO, QOUT, DIO, DOUT).
* @type {string}
*/
flashMode: string;

/**
* The flash frequency to be used (e.g., 40MHz, 80MHz).
* @type {string}
*/
flashFreq: string;

/**
* Flag indicating whether to erase all existing data in the flash memory before flashing.
* @type {boolean}
*/
eraseAll: boolean;

/**
* Flag indicating whether to compress the data before flashing.
* @type {boolean}
*/
compress: boolean;

/**
* A function to report the progress of the flashing operation (optional).
* @type {(fileIndex: number, written: number, total: number) => void}
*/
reportProgress?: (fileIndex: number, written: number, total: number) => void;

/**
* A function to calculate the MD5 hash of the firmware image (optional).
* @type {(image: string) => string}
*/
calculateMD5Hash?: (image: string) => string;
}

/**
* Options to configure ESPLoader.
* @interface LoaderOptions
*/
export interface LoaderOptions {
/**
* The transport mechanism to communicate with the device.
* @type {Transport}
*/
transport: Transport;

/**
* The port to initialize the transport class.
* @type {SerialPort}
*/
port?: SerialPort;

/**
* Set of options for SerialPort class.
* @type {Transport}
*/
serialOptions?: SerialOptions;

/**
* The baud rate to be used for communication with the device.
* @type {number}
*/
baudrate: number;

/**
* An optional terminal interface to interact with the loader during the process.
* @type {IEspLoaderTerminal}
*/
terminal?: IEspLoaderTerminal;

/**
* The baud rate to be used during the initial ROM communication with the device.
* @type {number}
*/
romBaudrate: number;

/**
* Flag indicating whether to enable debug logging for the loader (optional).
* @type {boolean}
*/
debugLogging?: boolean;
enableTracing?: boolean;
}
import { IEspLoaderTerminal } from "./types/loaderTerminal.js";
import { LoaderOptions } from "./types/loaderOptions.js";
import { FlashOptions } from "./types/flashOptions.js";
import { After, Before } from "./types/resetModes.js";

type FlashReadCallback = ((packet: Uint8Array, progress: number, totalSize: number) => void) | null;

Expand Down Expand Up @@ -160,29 +59,6 @@ async function magic2Chip(magic: number): Promise<ROM | null> {
}
}

/**
* A wrapper around your implementation of a terminal by
* implementing the clean, write and writeLine methods
* which are called by the ESPLoader class.
* @interface IEspLoaderTerminal
*/
export interface IEspLoaderTerminal {
/**
* Execute a terminal clean command.
*/
clean: () => void;
/**
* Write a string of data that include a line terminator.
* @param {string} data - The string to write with line terminator.
*/
writeLine: (data: string) => void;
/**
* Write a string of data.
* @param {string} data - The string to write.
*/
write: (data: string) => void;
}

export class ESPLoader {
ESP_RAM_BLOCK = 0x1800;
ESP_FLASH_BEGIN = 0x02;
Expand Down Expand Up @@ -255,6 +131,7 @@ export class ESPLoader {
private romBaudrate = 115200;
private debugLogging = false;
private syncStubDetected = false;
private resetFunctions: ResetFunctions;

/**
* Create a new ESPLoader to perform serial communication
Expand All @@ -270,6 +147,12 @@ export class ESPLoader {

this.transport = options.transport;
this.baudrate = options.baudrate;
this.resetFunctions = {
classicReset,
customReset,
hardReset,
usbJTAGSerialReset,
};
if (options.serialOptions) {
this.serialOptions = options.serialOptions;
}
Expand All @@ -291,6 +174,19 @@ export class ESPLoader {
this.transport.tracing = options.enableTracing;
}

if (options.resetFunctions?.classicReset) {
this.resetFunctions.classicReset = options.resetFunctions?.classicReset;
}
if (options.resetFunctions?.customReset) {
this.resetFunctions.customReset = options.resetFunctions?.customReset;
}
if (options.resetFunctions?.hardReset) {
this.resetFunctions.hardReset = options.resetFunctions?.hardReset;
}
if (options.resetFunctions?.usbJTAGSerialReset) {
this.resetFunctions.usbJTAGSerialReset = options.resetFunctions?.usbJTAGSerialReset;
}

this.info("esptool.js");
this.info("Serial port " + this.transport.getInfo());
}
Expand Down Expand Up @@ -594,18 +490,9 @@ export class ESPLoader {
* @param {boolean} esp32r0Delay - Enable delay for ESP32 R0
* @returns {string} - Returns 'success' or 'error' message.
*/
async _connectAttempt(mode = "default_reset", esp32r0Delay = false) {
async _connectAttempt(mode: Before = "default_reset", esp32r0Delay = false) {
this.debug("_connect_attempt " + mode + " " + esp32r0Delay);
if (mode !== "no_reset") {
if (this.transport.getPid() === this.USB_JTAG_SERIAL_PID) {
// Custom reset sequence, which is required when the device
// is connecting via its USB-JTAG-Serial peripheral
await usbJTAGSerialReset(this.transport);
} else {
const strSequence = esp32r0Delay ? "D0|R1|W100|W2000|D1|R0|W50|D0" : "D0|R1|W100|D1|R0|W50|D0";
await customReset(this.transport, strSequence);
}
}
await this.constructResetSequence(mode, esp32r0Delay);
let i = 0;
let keepReading = true;
while (keepReading) {
Expand Down Expand Up @@ -643,13 +530,37 @@ export class ESPLoader {
return "error";
}

/**
* Constructs a sequence of reset strategies based on the OS,
* used ESP chip, external settings, and environment variables.
* Returns a tuple of one or more reset strategies to be tried sequentially.
* @param {string} mode - Reset mode to use
* @param {boolean} esp32r0Delay - Enable delay for ESP32 R0
*/
async constructResetSequence(mode: Before, esp32r0Delay: boolean) {
if (mode !== "no_reset") {
if (mode === "usb_reset" || this.transport.getPid() === this.USB_JTAG_SERIAL_PID) {
// Custom reset sequence, which is required when the device
// is connecting via its USB-JTAG-Serial peripheral
if (this.resetFunctions.usbJTAGSerialReset) {
await this.resetFunctions.usbJTAGSerialReset(this.transport);
}
} else {
const strSequence = esp32r0Delay ? "D0|R1|W100|W2000|D1|R0|W50|D0" : "D0|R1|W100|D1|R0|W50|D0";
if (this.resetFunctions.customReset) {
await this.resetFunctions.customReset(this.transport, strSequence);
}
}
}
}

/**
* Perform a connection to chip.
* @param {string} mode - Reset mode to use. Example: 'default_reset' | 'no_reset'
* @param {number} attempts - Number of connection attempts
* @param {boolean} detecting - Detect the connected chip
*/
async connect(mode = "default_reset", attempts = 7, detecting = false) {
async connect(mode: Before = "default_reset", attempts = 7, detecting = false) {
let i;
let resp;
this.info("Connecting...", false);
Expand Down Expand Up @@ -685,7 +596,7 @@ export class ESPLoader {
* Connect and detect the existing chip.
* @param {string} mode Reset mode to use for connection.
*/
async detectChip(mode = "default_reset") {
async detectChip(mode: Before = "default_reset") {
await this.connect(mode);
this.info("Detecting chip type... ", false);
if (this.chip != null) {
Expand Down Expand Up @@ -1234,7 +1145,7 @@ export class ESPLoader {
* @param {string} mode Reset mode to use
* @returns {string} chip ROM
*/
async main(mode = "default_reset") {
async main(mode: Before = "default_reset") {
await this.detectChip(mode);

const chip = await this.chip.getChipDescription(this);
Expand Down Expand Up @@ -1514,18 +1425,57 @@ export class ESPLoader {

/**
* Soft reset the device chip. Soft reset with run user code is the closest.
* @param {boolean} stayInBootloader Flag to indicate if to stay in bootloader
*/
async softReset() {
async softReset(stayInBootloader: boolean) {
if (!this.IS_STUB) {
if (stayInBootloader) {
return; // ROM bootloader is already in bootloader!
}
// "run user code" is as close to a soft reset as we can do
await this.flashBegin(0, 0);
await this.flashFinish(false);
} else if (this.chip.CHIP_NAME != "ESP8266") {
throw new ESPError("Soft resetting is currently only supported on ESP8266");
} else {
// running user code from stub loader requires some hacks
// in the stub loader
await this.command(this.ESP_RUN_USER_CODE, undefined, undefined, false);
if (stayInBootloader) {
// soft resetting from the stub loader
// will re-load the ROM bootloader
await this.flashBegin(0, 0);
await this.flashFinish(true);
} else {
// running user code from stub loader requires some hacks
// in the stub loader
await this.command(this.ESP_RUN_USER_CODE, undefined, undefined, false);
}
}
}

/**
* Execute this function to execute after operation reset functions.
* @param {After} mode After operation mode
* @param { boolean } usingUsbOtg For 'hard_reset' to specify if using USB-OTG
*/
async after(mode: After, usingUsbOtg?: boolean) {
switch (mode) {
case "hard_reset":
if (this.resetFunctions.hardReset) {
await this.resetFunctions.hardReset(this.transport, usingUsbOtg);
}
break;
case "soft_reset":
this.info("Soft resetting...");
await this.softReset(false);
break;
case "no_reset_stub":
this.info("Staying in flasher stub.");
break;
default:
this.info("Staying in bootloader.");
if (this.IS_STUB) {
this.softReset(true);
}
break;
}
}
}
7 changes: 6 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
export { IEspLoaderTerminal, ESPLoader, FlashOptions, LoaderOptions } from "./esploader.js";
export { ESPLoader } from "./esploader.js";
export {
classicReset,
customReset,
hardReset,
usbJTAGSerialReset,
validateCustomResetStringSequence,
ResetFunctions,
} from "./reset.js";
export { ROM } from "./targets/rom.js";
export { Transport, SerialOptions } from "./webserial.js";
export { LoaderOptions } from "./types/loaderOptions.js";
export { FlashOptions } from "./types/flashOptions.js";
export { IEspLoaderTerminal } from "./types/loaderTerminal.js";
export { Before, After } from "./types/resetModes.js";
33 changes: 33 additions & 0 deletions src/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,39 @@ import { Transport } from "./webserial.js";

const DEFAULT_RESET_DELAY = 50;

/**
* Set of reset functions for ESP Loader connection.
* @interface ResetFunctions
*/
export interface ResetFunctions {
/**
* Execute a classic set of commands that will reset the chip.
* @param transport Transport class to perform serial communication.
* @param resetDelay Delay in milliseconds for reset.
*/
classicReset?: (transport: Transport, resetDelay?: number) => Promise<void>;

/**
* Execute a set of commands for USB JTAG serial reset.
* @param transport Transport class to perform serial communication.
*/
usbJTAGSerialReset?: (transport: Transport) => Promise<void>;

/**
* Execute a classic set of commands that will reset the chip.
* @param transport Transport class to perform serial communication.
* @param {boolean} usingUsbOtg is it using USB-OTG ?
*/
hardReset?: (transport: Transport, usingUsbOtg?: boolean) => Promise<void>;

/**
* Execute a custom set of commands that will reset the chip.
* @param transport Transport class to perform serial communication.
* @param {string} sequenceString Custom string sequence for reset strategy
*/
customReset?: (transport: Transport, sequenceString: string) => Promise<void>;
}

/**
* Sleep for ms milliseconds
* @param {number} ms Milliseconds to wait
Expand Down
File renamed without changes.
Loading
Loading