Files
ESP-Nodes/node_modules/esptool-js/lib/esploader.js
2025-07-26 14:04:40 -04:00

1360 lines
56 KiB
JavaScript

import { ESPError } from "./types/error.js";
import { deflate, Inflate } from "pako";
import { Transport } from "./webserial.js";
import { ClassicReset, CustomReset, HardReset, UsbJtagSerialReset } from "./reset.js";
import { getStubJsonByChipName } from "./stubFlasher.js";
import { padTo } from "./util.js";
/**
* Return the chip ROM based on the given magic number
* @param {number} magic - magic hex number to select ROM.
* @returns {ROM} The chip ROM class related to given magic hex number.
*/
async function magic2Chip(magic) {
switch (magic) {
case 0x00f01d83: {
const { ESP32ROM } = await import("./targets/esp32.js");
return new ESP32ROM();
}
case 0xc21e06f:
case 0x6f51306f:
case 0x7c41a06f: {
const { ESP32C2ROM } = await import("./targets/esp32c2.js");
return new ESP32C2ROM();
}
case 0x6921506f:
case 0x1b31506f:
case 0x4881606f:
case 0x4361606f: {
const { ESP32C3ROM } = await import("./targets/esp32c3.js");
return new ESP32C3ROM();
}
case 0x2ce0806f: {
const { ESP32C6ROM } = await import("./targets/esp32c6.js");
return new ESP32C6ROM();
}
case 0x2421606f:
case 0x33f0206f:
case 0x4f81606f: {
const { ESP32C61ROM } = await import("./targets/esp32c61.js");
return new ESP32C61ROM();
}
case 0x1101406f:
case 0x63e1406f:
case 0x5fd1406f: {
const { ESP32C5ROM } = await import("./targets/esp32c5.js");
return new ESP32C5ROM();
}
case 0xd7b73e80:
case 0x97e30068: {
const { ESP32H2ROM } = await import("./targets/esp32h2.js");
return new ESP32H2ROM();
}
case 0x09: {
const { ESP32S3ROM } = await import("./targets/esp32s3.js");
return new ESP32S3ROM();
}
case 0x000007c6: {
const { ESP32S2ROM } = await import("./targets/esp32s2.js");
return new ESP32S2ROM();
}
case 0xfff0c101: {
const { ESP8266ROM } = await import("./targets/esp8266.js");
return new ESP8266ROM();
}
case 0x0:
case 0x0addbad0:
case 0x7039ad9: {
const { ESP32P4ROM } = await import("./targets/esp32p4.js");
return new ESP32P4ROM();
}
default:
return null;
}
}
export class ESPLoader {
/**
* Create a new ESPLoader to perform serial communication
* such as read/write flash memory and registers using a LoaderOptions object.
* @param {LoaderOptions} options - LoaderOptions object argument for ESPLoader.
* ```
* const myLoader = new ESPLoader({ transport: Transport, baudrate: number, terminal?: IEspLoaderTerminal });
* ```
*/
constructor(options) {
var _a, _b, _c, _d, _e, _f, _g, _h;
this.ESP_RAM_BLOCK = 0x1800;
this.ESP_FLASH_BEGIN = 0x02;
this.ESP_FLASH_DATA = 0x03;
this.ESP_FLASH_END = 0x04;
this.ESP_MEM_BEGIN = 0x05;
this.ESP_MEM_END = 0x06;
this.ESP_MEM_DATA = 0x07;
this.ESP_WRITE_REG = 0x09;
this.ESP_READ_REG = 0x0a;
this.ESP_SPI_ATTACH = 0x0d;
this.ESP_CHANGE_BAUDRATE = 0x0f;
this.ESP_FLASH_DEFL_BEGIN = 0x10;
this.ESP_FLASH_DEFL_DATA = 0x11;
this.ESP_FLASH_DEFL_END = 0x12;
this.ESP_SPI_FLASH_MD5 = 0x13;
// Only Stub supported commands
this.ESP_ERASE_FLASH = 0xd0;
this.ESP_ERASE_REGION = 0xd1;
this.ESP_READ_FLASH = 0xd2;
this.ESP_RUN_USER_CODE = 0xd3;
this.ESP_IMAGE_MAGIC = 0xe9;
this.ESP_CHECKSUM_MAGIC = 0xef;
// Response code(s) sent by ROM
this.ROM_INVALID_RECV_MSG = 0x05; // response if an invalid message is received
this.DEFAULT_TIMEOUT = 3000;
this.ERASE_REGION_TIMEOUT_PER_MB = 30000;
this.ERASE_WRITE_TIMEOUT_PER_MB = 40000;
this.MD5_TIMEOUT_PER_MB = 8000;
this.CHIP_ERASE_TIMEOUT = 120000;
this.FLASH_READ_TIMEOUT = 100000;
this.MAX_TIMEOUT = this.CHIP_ERASE_TIMEOUT * 2;
this.CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000;
this.DETECTED_FLASH_SIZES = {
0x12: "256KB",
0x13: "512KB",
0x14: "1MB",
0x15: "2MB",
0x16: "4MB",
0x17: "8MB",
0x18: "16MB",
};
this.DETECTED_FLASH_SIZES_NUM = {
0x12: 256,
0x13: 512,
0x14: 1024,
0x15: 2048,
0x16: 4096,
0x17: 8192,
0x18: 16384,
};
this.USB_JTAG_SERIAL_PID = 0x1001;
this.romBaudrate = 115200;
this.debugLogging = false;
this.syncStubDetected = false;
/**
* Get flash size bytes from flash size string.
* @param {string} flashSize Flash Size string
* @returns {number} Flash size bytes
*/
this.flashSizeBytes = function (flashSize) {
let flashSizeB = -1;
if (flashSize.indexOf("KB") !== -1) {
flashSizeB = parseInt(flashSize.slice(0, flashSize.indexOf("KB"))) * 1024;
}
else if (flashSize.indexOf("MB") !== -1) {
flashSizeB = parseInt(flashSize.slice(0, flashSize.indexOf("MB"))) * 1024 * 1024;
}
return flashSizeB;
};
this.IS_STUB = false;
this.FLASH_WRITE_SIZE = 0x4000;
this.transport = options.transport;
this.baudrate = options.baudrate;
this.resetConstructors = {
classicReset: (transport, resetDelay) => new ClassicReset(transport, resetDelay),
customReset: (transport, sequenceString) => new CustomReset(transport, sequenceString),
hardReset: (transport, usingUsbOtg) => new HardReset(transport, usingUsbOtg),
usbJTAGSerialReset: (transport) => new UsbJtagSerialReset(transport),
};
if (options.serialOptions) {
this.serialOptions = options.serialOptions;
}
if (options.romBaudrate) {
this.romBaudrate = options.romBaudrate;
}
if (options.terminal) {
this.terminal = options.terminal;
this.terminal.clean();
}
if (typeof options.debugLogging !== "undefined") {
this.debugLogging = options.debugLogging;
}
if (options.port) {
this.transport = new Transport(options.port);
}
if (typeof options.enableTracing !== "undefined") {
this.transport.tracing = options.enableTracing;
}
if ((_a = options.resetConstructors) === null || _a === void 0 ? void 0 : _a.classicReset) {
this.resetConstructors.classicReset = (_b = options.resetConstructors) === null || _b === void 0 ? void 0 : _b.classicReset;
}
if ((_c = options.resetConstructors) === null || _c === void 0 ? void 0 : _c.customReset) {
this.resetConstructors.customReset = (_d = options.resetConstructors) === null || _d === void 0 ? void 0 : _d.customReset;
}
if ((_e = options.resetConstructors) === null || _e === void 0 ? void 0 : _e.hardReset) {
this.resetConstructors.hardReset = (_f = options.resetConstructors) === null || _f === void 0 ? void 0 : _f.hardReset;
}
if ((_g = options.resetConstructors) === null || _g === void 0 ? void 0 : _g.usbJTAGSerialReset) {
this.resetConstructors.usbJTAGSerialReset = (_h = options.resetConstructors) === null || _h === void 0 ? void 0 : _h.usbJTAGSerialReset;
}
this.info("esptool.js");
this.info("Serial port " + this.transport.getInfo());
}
_sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Write to ESP Loader constructor's terminal with or without new line.
* @param {string} str - String to write.
* @param {boolean} withNewline - Add new line at the end ?
*/
write(str, withNewline = true) {
if (this.terminal) {
if (withNewline) {
this.terminal.writeLine(str);
}
else {
this.terminal.write(str);
}
}
else {
// eslint-disable-next-line no-console
console.log(str);
}
}
/**
* Write error message to ESP Loader constructor's terminal with or without new line.
* @param {string} str - String to write.
* @param {boolean} withNewline - Add new line at the end ?
*/
error(str, withNewline = true) {
this.write(`Error: ${str}`, withNewline);
}
/**
* Write information message to ESP Loader constructor's terminal with or without new line.
* @param {string} str - String to write.
* @param {boolean} withNewline - Add new line at the end ?
*/
info(str, withNewline = true) {
this.write(str, withNewline);
}
/**
* Write debug message to ESP Loader constructor's terminal with or without new line.
* @param {string} str - String to write.
* @param {boolean} withNewline - Add new line at the end ?
*/
debug(str, withNewline = true) {
if (this.debugLogging) {
this.write(`Debug: ${str}`, withNewline);
}
}
/**
* Convert short integer to byte array
* @param {number} i - Number to convert.
* @returns {Uint8Array} Byte array.
*/
_shortToBytearray(i) {
return new Uint8Array([i & 0xff, (i >> 8) & 0xff]);
}
/**
* Convert an integer to byte array
* @param {number} i - Number to convert.
* @returns {ROM} The chip ROM class related to given magic hex number.
*/
_intToByteArray(i) {
return new Uint8Array([i & 0xff, (i >> 8) & 0xff, (i >> 16) & 0xff, (i >> 24) & 0xff]);
}
/**
* Convert a byte array to short integer.
* @param {number} i - Number to convert.
* @param {number} j - Number to convert.
* @returns {number} Return a short integer number.
*/
_byteArrayToShort(i, j) {
return i | (j >> 8);
}
/**
* Convert a byte array to integer.
* @param {number} i - Number to convert.
* @param {number} j - Number to convert.
* @param {number} k - Number to convert.
* @param {number} l - Number to convert.
* @returns {number} Return a integer number.
*/
_byteArrayToInt(i, j, k, l) {
return i | (j << 8) | (k << 16) | (l << 24);
}
/**
* Append a buffer array after another buffer array
* @param {ArrayBuffer} buffer1 - First array buffer.
* @param {ArrayBuffer} buffer2 - magic hex number to select ROM.
* @returns {ArrayBufferLike} Return an array buffer.
*/
_appendBuffer(buffer1, buffer2) {
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
/**
* Append a buffer array after another buffer array
* @param {Uint8Array} arr1 - First array buffer.
* @param {Uint8Array} arr2 - magic hex number to select ROM.
* @returns {Uint8Array} Return a 8 bit unsigned array.
*/
_appendArray(arr1, arr2) {
const c = new Uint8Array(arr1.length + arr2.length);
c.set(arr1, 0);
c.set(arr2, arr1.length);
return c;
}
/**
* Convert a unsigned 8 bit integer array to byte string.
* @param {Uint8Array} u8Array - magic hex number to select ROM.
* @returns {string} Return the equivalent string.
*/
ui8ToBstr(u8Array) {
let bStr = "";
for (let i = 0; i < u8Array.length; i++) {
bStr += String.fromCharCode(u8Array[i]);
}
return bStr;
}
/**
* Convert a byte string to unsigned 8 bit integer array.
* @param {string} bStr - binary string input
* @returns {Uint8Array} Return a 8 bit unsigned integer array.
*/
bstrToUi8(bStr) {
const u8Array = new Uint8Array(bStr.length);
for (let i = 0; i < bStr.length; i++) {
u8Array[i] = bStr.charCodeAt(i);
}
return u8Array;
}
/**
* Flush the serial input by raw read with 200 ms timeout.
*/
async flushInput() {
try {
await this.transport.flushInput();
}
catch (e) {
this.error(e.message);
}
}
/**
* Use the device serial port read function with given timeout to create a valid packet.
* @param {number} op Operation number
* @param {number} timeout timeout number in milliseconds
* @returns {[number, Uint8Array]} valid response packet.
*/
async readPacket(op = null, timeout = this.DEFAULT_TIMEOUT) {
// Check up-to next 100 packets for valid response packet
for (let i = 0; i < 100; i++) {
const { value: p } = await this.transport.read(timeout).next();
if (!p || p.length < 8) {
continue;
}
const resp = p[0];
if (resp !== 1) {
continue;
}
const opRet = p[1];
const val = this._byteArrayToInt(p[4], p[5], p[6], p[7]);
const data = p.slice(8);
if (resp == 1) {
if (op == null || opRet == op) {
return [val, data];
}
else if (data[0] != 0 && data[1] == this.ROM_INVALID_RECV_MSG) {
await this.flushInput();
throw new ESPError("unsupported command error");
}
}
}
throw new ESPError("invalid response");
}
/**
* Write a serial command to the chip
* @param {number} op - Operation number
* @param {Uint8Array} data - Unsigned 8 bit array
* @param {number} chk - channel number
* @param {boolean} waitResponse - wait for response ?
* @param {number} timeout - timeout number in milliseconds
* @returns {Promise<[number, Uint8Array]>} Return a number and a 8 bit unsigned integer array.
*/
async command(op = null, data = new Uint8Array(0), chk = 0, waitResponse = true, timeout = this.DEFAULT_TIMEOUT) {
if (op != null) {
if (this.transport.tracing) {
this.transport.trace(`command op:0x${op.toString(16).padStart(2, "0")} data len=${data.length} wait_response=${waitResponse ? 1 : 0} timeout=${(timeout / 1000).toFixed(3)} data=${this.transport.hexConvert(data)}`);
}
const pkt = new Uint8Array(8 + data.length);
pkt[0] = 0x00;
pkt[1] = op;
pkt[2] = this._shortToBytearray(data.length)[0];
pkt[3] = this._shortToBytearray(data.length)[1];
pkt[4] = this._intToByteArray(chk)[0];
pkt[5] = this._intToByteArray(chk)[1];
pkt[6] = this._intToByteArray(chk)[2];
pkt[7] = this._intToByteArray(chk)[3];
let i;
for (i = 0; i < data.length; i++) {
pkt[8 + i] = data[i];
}
await this.transport.write(pkt);
}
if (!waitResponse) {
return [0, new Uint8Array(0)];
}
return this.readPacket(op, timeout);
}
/**
* Read a register from chip.
* @param {number} addr - Register address number
* @param {number} timeout - Timeout in milliseconds (Default: 3000ms)
* @returns {number} - Command number value
*/
async readReg(addr, timeout = this.DEFAULT_TIMEOUT) {
const pkt = this._intToByteArray(addr);
const val = await this.command(this.ESP_READ_REG, pkt, undefined, undefined, timeout);
return val[0];
}
/**
* Write a number value to register address in chip.
* @param {number} addr - Register address number
* @param {number} value - Number value to write in register
* @param {number} mask - Hex number for mask
* @param {number} delayUs Delay number
* @param {number} delayAfterUs Delay after previous delay
*/
async writeReg(addr, value, mask = 0xffffffff, delayUs = 0, delayAfterUs = 0) {
let pkt = this._appendArray(this._intToByteArray(addr), this._intToByteArray(value));
pkt = this._appendArray(pkt, this._intToByteArray(mask));
pkt = this._appendArray(pkt, this._intToByteArray(delayUs));
if (delayAfterUs > 0) {
pkt = this._appendArray(pkt, this._intToByteArray(this.chip.UART_DATE_REG_ADDR));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, this._intToByteArray(delayAfterUs));
}
await this.checkCommand("write target memory", this.ESP_WRITE_REG, pkt);
}
/**
* Sync chip by sending sync command.
* @returns {[number, Uint8Array]} Command result
*/
async sync() {
this.debug("Sync");
const cmd = new Uint8Array(36);
let i;
cmd[0] = 0x07;
cmd[1] = 0x07;
cmd[2] = 0x12;
cmd[3] = 0x20;
for (i = 0; i < 32; i++) {
cmd[4 + i] = 0x55;
}
try {
let resp = await this.command(0x08, cmd, undefined, undefined, 100);
// ROM bootloaders send some non-zero "val" response. The flasher stub sends 0.
// If we receive 0 then it probably indicates that the chip wasn't or couldn't be
// reset properly and esptool is talking to the flasher stub.
this.syncStubDetected = resp[0] === 0;
for (let i = 0; i < 7; i++) {
resp = await this.command();
this.syncStubDetected = this.syncStubDetected && resp[0] === 0;
}
return resp;
}
catch (e) {
this.debug("Sync err " + e);
throw e;
}
}
/**
* Attempt to connect to the chip by sending a reset sequence and later a sync command.
* @param {string} mode - Reset mode to use
* @param {ResetStrategy} resetStrategy - Reset strategy class to use for connect
* @returns {string} - Returns 'success' or 'error' message.
*/
async _connectAttempt(mode = "default_reset", resetStrategy) {
this.debug("_connect_attempt " + mode);
if (resetStrategy) {
await resetStrategy.reset();
}
const waitingBytes = this.transport.inWaiting();
const readBytes = await this.transport.newRead(waitingBytes > 0 ? waitingBytes : 1, this.DEFAULT_TIMEOUT);
const binaryString = Array.from(readBytes, (byte) => String.fromCharCode(byte)).join("");
const regex = /boot:(0x[0-9a-fA-F]+)(.*waiting for download)?/;
const match = binaryString.match(regex);
let bootLogDetected = false, bootMode = "", downloadMode = false;
if (match) {
bootLogDetected = true;
bootMode = match[1];
downloadMode = !!match[2];
}
let lastError = "";
for (let i = 0; i < 5; i++) {
try {
this.debug(`Sync connect attempt ${i}`);
const resp = await this.sync();
this.debug(resp[0].toString());
return "success";
}
catch (error) {
this.debug(`Error at sync ${error}`);
if (error instanceof Error) {
lastError = error.message;
}
else if (typeof error === "string") {
lastError = error;
}
else {
lastError = JSON.stringify(error);
}
}
}
if (bootLogDetected) {
lastError = `Wrong boot mode detected (${bootMode}).
This chip needs to be in download mode.`;
if (downloadMode) {
lastError = `Download mode successfully detected, but getting no sync reply:
The serial TX path seems to be down.`;
}
}
return lastError;
}
/**
* 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
* @returns {ResetStrategy[]} - Array of reset strategies
*/
constructResetSequence(mode) {
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.resetConstructors.usbJTAGSerialReset) {
this.debug("using USB JTAG Serial Reset");
return [this.resetConstructors.usbJTAGSerialReset(this.transport)];
}
}
else {
const DEFAULT_RESET_DELAY = 50;
const EXTRA_DELAY = DEFAULT_RESET_DELAY + 500;
if (this.resetConstructors.classicReset) {
this.debug("using Classic Serial Reset");
return [
this.resetConstructors.classicReset(this.transport, DEFAULT_RESET_DELAY),
this.resetConstructors.classicReset(this.transport, EXTRA_DELAY),
];
}
}
}
return [];
}
/**
* 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 = true) {
let resp;
this.info("Connecting...", false);
await this.transport.connect(this.romBaudrate, this.serialOptions);
const resetSequences = this.constructResetSequence(mode);
for (let i = 0; i < attempts; i++) {
const resetSequence = resetSequences.length > 0 ? resetSequences[i % resetSequences.length] : null;
resp = await this._connectAttempt(mode, resetSequence);
if (resp === "success") {
break;
}
}
if (resp !== "success") {
throw new ESPError("Failed to connect with the device");
}
this.debug("Connect attempt successful.");
this.info("\n\r", false);
if (detecting) {
const chipMagicValue = (await this.readReg(this.CHIP_DETECT_MAGIC_REG_ADDR)) >>> 0;
this.debug("Chip Magic " + chipMagicValue.toString(16));
const chip = await magic2Chip(chipMagicValue);
if (this.chip === null) {
throw new ESPError(`Unexpected CHIP magic value ${chipMagicValue}. Failed to autodetect chip type.`);
}
else {
this.chip = chip;
}
}
}
/**
* Connect and detect the existing chip.
* @param {string} mode Reset mode to use for connection.
*/
async detectChip(mode = "default_reset") {
await this.connect(mode);
this.info("Detecting chip type... ", false);
if (this.chip != null) {
this.info(this.chip.CHIP_NAME);
}
else {
this.info("unknown!");
}
}
/**
* Execute the command and check the command response.
* @param {string} opDescription Command operation description.
* @param {number} op Command operation number
* @param {Uint8Array} data Command value
* @param {number} chk Checksum to use
* @param {number} timeout TImeout number in milliseconds (ms)
* @returns {number} Command result
*/
async checkCommand(opDescription = "", op = null, data = new Uint8Array(0), chk = 0, timeout = this.DEFAULT_TIMEOUT) {
this.debug("check_command " + opDescription);
const resp = await this.command(op, data, chk, undefined, timeout);
if (resp[1].length > 4) {
return resp[1];
}
else {
return resp[0];
}
}
/**
* Start downloading an application image to RAM
* @param {number} size Image size number
* @param {number} blocks Number of data blocks
* @param {number} blocksize Size of each data block
* @param {number} offset Image offset number
*/
async memBegin(size, blocks, blocksize, offset) {
/* XXX: Add check to ensure that STUB is not getting overwritten */
if (this.IS_STUB) {
const loadStart = offset;
const loadEnd = offset + size;
const stub = await getStubJsonByChipName(this.chip.CHIP_NAME);
if (stub) {
const areasToCheck = [
[stub.bss_start || stub.data_start, stub.data_start + stub.decodedData.length],
[stub.text_start, stub.text_start + stub.decodedText.length],
];
for (const [stubStart, stubEnd] of areasToCheck) {
if (loadStart < stubEnd && loadEnd > stubStart) {
throw new ESPError(`Software loader is resident at 0x${stubStart.toString(16).padStart(8, "0")}-0x${stubEnd
.toString(16)
.padStart(8, "0")}.
Can't load binary at overlapping address range 0x${loadStart.toString(16).padStart(8, "0")}-0x${loadEnd
.toString(16)
.padStart(8, "0")}.
Either change binary loading address, or use the no-stub option to disable the software loader.`);
}
}
}
}
this.debug("mem_begin " + size + " " + blocks + " " + blocksize + " " + offset.toString(16));
let pkt = this._appendArray(this._intToByteArray(size), this._intToByteArray(blocks));
pkt = this._appendArray(pkt, this._intToByteArray(blocksize));
pkt = this._appendArray(pkt, this._intToByteArray(offset));
await this.checkCommand("enter RAM download mode", this.ESP_MEM_BEGIN, pkt);
}
/**
* Get the checksum for given unsigned 8-bit array
* @param {Uint8Array} data Unsigned 8-bit integer array
* @param {number} state Initial checksum
* @returns {number} - Array checksum
*/
checksum(data, state = this.ESP_CHECKSUM_MAGIC) {
for (let i = 0; i < data.length; i++) {
state ^= data[i];
}
return state;
}
/**
* Send a block of image to RAM
* @param {Uint8Array} buffer Unsigned 8-bit array
* @param {number} seq Sequence number
*/
async memBlock(buffer, seq) {
let pkt = this._appendArray(this._intToByteArray(buffer.length), this._intToByteArray(seq));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, buffer);
const checksum = this.checksum(buffer);
await this.checkCommand("write to target RAM", this.ESP_MEM_DATA, pkt, checksum);
}
/**
* Leave RAM download mode and run application
* @param {number} entrypoint - Entrypoint number
*/
async memFinish(entrypoint) {
const isEntry = entrypoint === 0 ? 1 : 0;
const pkt = this._appendArray(this._intToByteArray(isEntry), this._intToByteArray(entrypoint));
await this.checkCommand("leave RAM download mode", this.ESP_MEM_END, pkt, undefined, 200); // XXX: handle non-stub with diff timeout
}
/**
* Configure SPI flash pins
* @param {number} hspiArg - Argument for SPI attachment
*/
async flashSpiAttach(hspiArg) {
const pkt = this._intToByteArray(hspiArg);
await this.checkCommand("configure SPI flash pins", this.ESP_SPI_ATTACH, pkt);
}
/**
* Scale timeouts which are size-specific.
* @param {number} secondsPerMb Seconds per megabytes as number
* @param {number} sizeBytes Size bytes number
* @returns {number} - Scaled timeout for specified size.
*/
timeoutPerMb(secondsPerMb, sizeBytes) {
const result = secondsPerMb * (sizeBytes / 1000000);
if (result < 3000) {
return 3000;
}
else {
return result;
}
}
/**
* Start downloading to Flash (performs an erase)
* @param {number} size Size to erase
* @param {number} offset Offset to erase
* @returns {number} Number of blocks (of size self.FLASH_WRITE_SIZE) to write.
*/
async flashBegin(size, offset) {
const numBlocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE);
const eraseSize = this.chip.getEraseSize(offset, size);
const d = new Date();
const t1 = d.getTime();
let timeout = 3000;
if (this.IS_STUB == false) {
timeout = this.timeoutPerMb(this.ERASE_REGION_TIMEOUT_PER_MB, size);
}
this.debug("flash begin " + eraseSize + " " + numBlocks + " " + this.FLASH_WRITE_SIZE + " " + offset + " " + size);
let pkt = this._appendArray(this._intToByteArray(eraseSize), this._intToByteArray(numBlocks));
pkt = this._appendArray(pkt, this._intToByteArray(this.FLASH_WRITE_SIZE));
pkt = this._appendArray(pkt, this._intToByteArray(offset));
if (this.IS_STUB == false) {
pkt = this._appendArray(pkt, this._intToByteArray(0)); // XXX: Support encrypted
}
await this.checkCommand("enter Flash download mode", this.ESP_FLASH_BEGIN, pkt, undefined, timeout);
const t2 = d.getTime();
if (size != 0 && this.IS_STUB == false) {
this.info("Took " + (t2 - t1) / 1000 + "." + ((t2 - t1) % 1000) + "s to erase flash block");
}
return numBlocks;
}
/**
* Start downloading compressed data to Flash (performs an erase)
* @param {number} size Write size
* @param {number} compsize Compressed size
* @param {number} offset Offset for write
* @returns {number} Returns number of blocks (size self.FLASH_WRITE_SIZE) to write.
*/
async flashDeflBegin(size, compsize, offset) {
const numBlocks = Math.floor((compsize + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE);
const eraseBlocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE);
const d = new Date();
const t1 = d.getTime();
let writeSize, timeout;
if (this.IS_STUB) {
writeSize = size;
timeout = this.DEFAULT_TIMEOUT;
}
else {
writeSize = eraseBlocks * this.FLASH_WRITE_SIZE;
timeout = this.timeoutPerMb(this.ERASE_REGION_TIMEOUT_PER_MB, writeSize);
}
this.info("Compressed " + size + " bytes to " + compsize + "...");
let pkt = this._appendArray(this._intToByteArray(writeSize), this._intToByteArray(numBlocks));
pkt = this._appendArray(pkt, this._intToByteArray(this.FLASH_WRITE_SIZE));
pkt = this._appendArray(pkt, this._intToByteArray(offset));
if ((this.chip.CHIP_NAME === "ESP32-S2" ||
this.chip.CHIP_NAME === "ESP32-S3" ||
this.chip.CHIP_NAME === "ESP32-C3" ||
this.chip.CHIP_NAME === "ESP32-C2") &&
this.IS_STUB === false) {
pkt = this._appendArray(pkt, this._intToByteArray(0));
}
await this.checkCommand("enter compressed flash mode", this.ESP_FLASH_DEFL_BEGIN, pkt, undefined, timeout);
const t2 = d.getTime();
if (size != 0 && this.IS_STUB === false) {
this.info("Took " + (t2 - t1) / 1000 + "." + ((t2 - t1) % 1000) + "s to erase flash block");
}
return numBlocks;
}
/**
* Write block to flash, retry if fail
* @param {Uint8Array} data Unsigned 8-bit array data.
* @param {number} seq Sequence number
* @param {number} timeout Timeout in milliseconds (ms)
*/
async flashBlock(data, seq, timeout) {
let pkt = this._appendArray(this._intToByteArray(data.length), this._intToByteArray(seq));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, data);
const checksum = this.checksum(data);
await this.checkCommand("write to target Flash after seq " + seq, this.ESP_FLASH_DATA, pkt, checksum, timeout);
}
/**
* Write block to flash, send compressed, retry if fail
* @param {Uint8Array} data Unsigned int 8-bit array data to write
* @param {number} seq Sequence number
* @param {number} timeout Timeout in milliseconds (ms)
*/
async flashDeflBlock(data, seq, timeout) {
let pkt = this._appendArray(this._intToByteArray(data.length), this._intToByteArray(seq));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, data);
const checksum = this.checksum(data);
this.debug("flash_defl_block " + data[0].toString(16) + " " + data[1].toString(16));
await this.checkCommand("write compressed data to flash after seq " + seq, this.ESP_FLASH_DEFL_DATA, pkt, checksum, timeout);
}
/**
* Leave flash mode and run/reboot
* @param {boolean} reboot Reboot after leaving flash mode ?
*/
async flashFinish(reboot = false) {
const val = reboot ? 0 : 1;
const pkt = this._intToByteArray(val);
await this.checkCommand("leave Flash mode", this.ESP_FLASH_END, pkt);
}
/**
* Leave compressed flash mode and run/reboot
* @param {boolean} reboot Reboot after leaving flash mode ?
*/
async flashDeflFinish(reboot = false) {
const val = reboot ? 0 : 1;
const pkt = this._intToByteArray(val);
await this.checkCommand("leave compressed flash mode", this.ESP_FLASH_DEFL_END, pkt);
}
/**
* Run an arbitrary SPI flash command.
*
* This function uses the "USR_COMMAND" functionality in the ESP
* SPI hardware, rather than the precanned commands supported by
* hardware. So the value of spiflashCommand is an actual command
* byte, sent over the wire.
*
* After writing command byte, writes 'data' to MOSI and then
* reads back 'readBits' of reply on MISO. Result is a number.
* @param {number} spiflashCommand Command to execute in SPI
* @param {Uint8Array} data Data to send
* @param {number} readBits Number of bits to read
* @returns {number} Register SPI_W0_REG value
*/
async runSpiflashCommand(spiflashCommand, data, readBits) {
// SPI_USR register flags
const SPI_USR_COMMAND = 1 << 31;
const SPI_USR_MISO = 1 << 28;
const SPI_USR_MOSI = 1 << 27;
// SPI registers, base address differs ESP32* vs 8266
const base = this.chip.SPI_REG_BASE;
const SPI_CMD_REG = base + 0x00;
const SPI_USR_REG = base + this.chip.SPI_USR_OFFS;
const SPI_USR1_REG = base + this.chip.SPI_USR1_OFFS;
const SPI_USR2_REG = base + this.chip.SPI_USR2_OFFS;
const SPI_W0_REG = base + this.chip.SPI_W0_OFFS;
let setDataLengths;
if (this.chip.SPI_MOSI_DLEN_OFFS != null) {
setDataLengths = async (mosiBits, misoBits) => {
const SPI_MOSI_DLEN_REG = base + this.chip.SPI_MOSI_DLEN_OFFS;
const SPI_MISO_DLEN_REG = base + this.chip.SPI_MISO_DLEN_OFFS;
if (mosiBits > 0) {
await this.writeReg(SPI_MOSI_DLEN_REG, mosiBits - 1);
}
if (misoBits > 0) {
await this.writeReg(SPI_MISO_DLEN_REG, misoBits - 1);
}
};
}
else {
setDataLengths = async (mosiBits, misoBits) => {
const SPI_DATA_LEN_REG = SPI_USR1_REG;
const SPI_MOSI_BITLEN_S = 17;
const SPI_MISO_BITLEN_S = 8;
const mosiMask = mosiBits === 0 ? 0 : mosiBits - 1;
const misoMask = misoBits === 0 ? 0 : misoBits - 1;
const val = (misoMask << SPI_MISO_BITLEN_S) | (mosiMask << SPI_MOSI_BITLEN_S);
await this.writeReg(SPI_DATA_LEN_REG, val);
};
}
const SPI_CMD_USR = 1 << 18;
const SPI_USR2_COMMAND_LEN_SHIFT = 28;
if (readBits > 32) {
throw new ESPError("Reading more than 32 bits back from a SPI flash operation is unsupported");
}
if (data.length > 64) {
throw new ESPError("Writing more than 64 bytes of data with one SPI command is unsupported");
}
const dataBits = data.length * 8;
const oldSpiUsr = await this.readReg(SPI_USR_REG);
const oldSpiUsr2 = await this.readReg(SPI_USR2_REG);
let flags = SPI_USR_COMMAND;
let i;
if (readBits > 0) {
flags |= SPI_USR_MISO;
}
if (dataBits > 0) {
flags |= SPI_USR_MOSI;
}
await setDataLengths(dataBits, readBits);
await this.writeReg(SPI_USR_REG, flags);
let val = (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflashCommand;
await this.writeReg(SPI_USR2_REG, val);
if (dataBits == 0) {
await this.writeReg(SPI_W0_REG, 0);
}
else {
if (data.length % 4 != 0) {
const padding = new Uint8Array(data.length % 4);
data = this._appendArray(data, padding);
}
let nextReg = SPI_W0_REG;
for (i = 0; i < data.length - 4; i += 4) {
val = this._byteArrayToInt(data[i], data[i + 1], data[i + 2], data[i + 3]);
await this.writeReg(nextReg, val);
nextReg += 4;
}
}
await this.writeReg(SPI_CMD_REG, SPI_CMD_USR);
for (i = 0; i < 10; i++) {
val = (await this.readReg(SPI_CMD_REG)) & SPI_CMD_USR;
if (val == 0) {
break;
}
}
if (i === 10) {
throw new ESPError("SPI command did not complete in time");
}
const stat = await this.readReg(SPI_W0_REG);
await this.writeReg(SPI_USR_REG, oldSpiUsr);
await this.writeReg(SPI_USR2_REG, oldSpiUsr2);
return stat;
}
/**
* Read flash id by executing the SPIFLASH_RDID flash command.
* @returns {Promise<number>} Register SPI_W0_REG value
*/
async readFlashId() {
const SPIFLASH_RDID = 0x9f;
const pkt = new Uint8Array(0);
return await this.runSpiflashCommand(SPIFLASH_RDID, pkt, 24);
}
/**
* Execute the erase flash command
* @returns {Promise<number | Uint8Array>} Erase flash command result
*/
async eraseFlash() {
this.info("Erasing flash (this may take a while)...");
let d = new Date();
const t1 = d.getTime();
const ret = await this.checkCommand("erase flash", this.ESP_ERASE_FLASH, undefined, undefined, this.CHIP_ERASE_TIMEOUT);
d = new Date();
const t2 = d.getTime();
this.info("Chip erase completed successfully in " + (t2 - t1) / 1000 + "s");
return ret;
}
/**
* Convert a number or unsigned 8-bit array to hex string
* @param {number | Uint8Array } buffer Data to convert to hex string.
* @returns {string} A hex string
*/
toHex(buffer) {
return Array.prototype.map.call(buffer, (x) => ("00" + x.toString(16)).slice(-2)).join("");
}
/**
* Calculate the MD5 Checksum command
* @param {number} addr Address number
* @param {number} size Package size
* @returns {string} MD5 Checksum string
*/
async flashMd5sum(addr, size) {
const timeout = this.timeoutPerMb(this.MD5_TIMEOUT_PER_MB, size);
let pkt = this._appendArray(this._intToByteArray(addr), this._intToByteArray(size));
pkt = this._appendArray(pkt, this._intToByteArray(0));
pkt = this._appendArray(pkt, this._intToByteArray(0));
let res = await this.checkCommand("calculate md5sum", this.ESP_SPI_FLASH_MD5, pkt, undefined, timeout);
if (res instanceof Uint8Array && res.length > 16) {
res = res.slice(0, 16);
}
const strmd5 = this.toHex(res);
return strmd5;
}
async readFlash(addr, size, onPacketReceived = null) {
let pkt = this._appendArray(this._intToByteArray(addr), this._intToByteArray(size));
pkt = this._appendArray(pkt, this._intToByteArray(0x1000));
pkt = this._appendArray(pkt, this._intToByteArray(1024));
const res = await this.checkCommand("read flash", this.ESP_READ_FLASH, pkt);
if (res != 0) {
throw new ESPError("Failed to read memory: " + res);
}
let resp = new Uint8Array(0);
while (resp.length < size) {
const { value: packet } = await this.transport.read(this.FLASH_READ_TIMEOUT).next();
if (packet instanceof Uint8Array) {
if (packet.length > 0) {
resp = this._appendArray(resp, packet);
await this.transport.write(this._intToByteArray(resp.length));
if (onPacketReceived) {
onPacketReceived(packet, resp.length, size);
}
}
}
else {
throw new ESPError("Failed to read memory: " + packet);
}
}
return resp;
}
/**
* Upload the flasher ROM bootloader (flasher stub) to the chip.
* @returns {ROM} The Chip ROM
*/
async runStub() {
if (this.syncStubDetected) {
this.info("Stub is already running. No upload is necessary.");
return this.chip;
}
this.info("Uploading stub...");
const stubFlasher = await getStubJsonByChipName(this.chip.CHIP_NAME);
if (stubFlasher === undefined) {
this.debug("Error loading Stub json");
throw new Error("Error loading Stub json");
}
const stub = [stubFlasher.decodedText, stubFlasher.decodedData];
for (let i = 0; i < stub.length; i++) {
if (stub[i]) {
const offs = i === 0 ? stubFlasher.text_start : stubFlasher.data_start;
const length = stub[i].length;
const blocks = Math.floor((length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK);
await this.memBegin(length, blocks, this.ESP_RAM_BLOCK, offs);
for (let seq = 0; seq < blocks; seq++) {
const fromOffs = seq * this.ESP_RAM_BLOCK;
const toOffs = fromOffs + this.ESP_RAM_BLOCK;
await this.memBlock(stub[i].slice(fromOffs, toOffs), seq);
}
}
}
this.info("Running stub...");
await this.memFinish(stubFlasher.entry);
const { value: packetResult } = await this.transport.read(this.DEFAULT_TIMEOUT).next();
const packetStr = String.fromCharCode(...packetResult);
if (packetStr !== "OHAI") {
throw new ESPError(`Failed to start stub. Unexpected response ${packetStr}`);
}
this.info("Stub running...");
this.IS_STUB = true;
return this.chip;
}
/**
* Change the chip baudrate.
*/
async changeBaud() {
this.info("Changing baudrate to " + this.baudrate);
const secondArg = this.IS_STUB ? this.romBaudrate : 0;
const pkt = this._appendArray(this._intToByteArray(this.baudrate), this._intToByteArray(secondArg));
await this.command(this.ESP_CHANGE_BAUDRATE, pkt);
this.info("Changed");
await this.transport.disconnect();
await this._sleep(50);
await this.transport.connect(this.baudrate, this.serialOptions);
}
/**
* Execute the main function of ESPLoader.
* @param {string} mode Reset mode to use
* @returns {string} chip ROM
*/
async main(mode = "default_reset") {
await this.detectChip(mode);
const chip = await this.chip.getChipDescription(this);
this.info("Chip is " + chip);
this.info("Features: " + (await this.chip.getChipFeatures(this)));
this.info("Crystal is " + (await this.chip.getCrystalFreq(this)) + "MHz");
this.info("MAC: " + (await this.chip.readMac(this)));
await this.chip.readMac(this);
if (typeof this.chip.postConnect != "undefined") {
await this.chip.postConnect(this);
}
await this.runStub();
if (this.romBaudrate !== this.baudrate) {
await this.changeBaud();
}
return chip;
}
/**
* Parse a given flash size string to a number
* @param {string} flsz Flash size to request
* @returns {number} Flash size number
*/
parseFlashSizeArg(flsz) {
if (typeof this.chip.FLASH_SIZES[flsz] === "undefined") {
throw new ESPError("Flash size " + flsz + " is not supported by this chip type. Supported sizes: " + this.chip.FLASH_SIZES);
}
return this.chip.FLASH_SIZES[flsz];
}
/**
* Update the image flash parameters with given arguments.
* @param {string} image binary image as string
* @param {number} address flash address number
* @param {string} flashSize Flash size string
* @param {string} flashMode Flash mode string
* @param {string} flashFreq Flash frequency string
* @returns {string} modified image string
*/
_updateImageFlashParams(image, address, flashSize, flashMode, flashFreq) {
this.debug("_update_image_flash_params " + flashSize + " " + flashMode + " " + flashFreq);
if (image.length < 8) {
return image;
}
if (address != this.chip.BOOTLOADER_FLASH_OFFSET) {
return image;
}
if (flashSize === "keep" && flashMode === "keep" && flashFreq === "keep") {
this.info("Not changing the image");
return image;
}
const magic = parseInt(image[0]);
let aFlashMode = parseInt(image[2]);
const flashSizeFreq = parseInt(image[3]);
if (magic !== this.ESP_IMAGE_MAGIC) {
this.info("Warning: Image file at 0x" +
address.toString(16) +
" doesn't look like an image file, so not changing any flash settings.");
return image;
}
/* XXX: Yet to implement actual image verification */
if (flashMode !== "keep") {
const flashModes = { qio: 0, qout: 1, dio: 2, dout: 3 };
aFlashMode = flashModes[flashMode];
}
let aFlashFreq = flashSizeFreq & 0x0f;
if (flashFreq !== "keep") {
const flashFreqs = { "40m": 0, "26m": 1, "20m": 2, "80m": 0xf };
aFlashFreq = flashFreqs[flashFreq];
}
let aFlashSize = flashSizeFreq & 0xf0;
if (flashSize !== "keep") {
aFlashSize = this.parseFlashSizeArg(flashSize);
}
const flashParams = (aFlashMode << 8) | (aFlashFreq + aFlashSize);
this.info("Flash params set to " + flashParams.toString(16));
if (parseInt(image[2]) !== aFlashMode << 8) {
image = image.substring(0, 2) + (aFlashMode << 8).toString() + image.substring(2 + 1);
}
if (parseInt(image[3]) !== aFlashFreq + aFlashSize) {
image = image.substring(0, 3) + (aFlashFreq + aFlashSize).toString() + image.substring(3 + 1);
}
return image;
}
/**
* Write set of file images into given address based on given FlashOptions object.
* @param {FlashOptions} options FlashOptions to configure how and what to write into flash.
*/
async writeFlash(options) {
this.debug("EspLoader program");
if (options.flashSize !== "keep") {
const flashEnd = this.flashSizeBytes(options.flashSize);
for (let i = 0; i < options.fileArray.length; i++) {
if (options.fileArray[i].data.length + options.fileArray[i].address > flashEnd) {
throw new ESPError(`File ${i + 1} doesn't fit in the available flash`);
}
}
}
if (this.IS_STUB === true && options.eraseAll === true) {
await this.eraseFlash();
}
let image, address;
for (let i = 0; i < options.fileArray.length; i++) {
this.debug("Data Length " + options.fileArray[i].data.length);
image = options.fileArray[i].data;
this.debug("Image Length " + image.length);
if (image.length === 0) {
this.debug("Warning: File is empty");
continue;
}
image = this.ui8ToBstr(padTo(this.bstrToUi8(image), 4));
address = options.fileArray[i].address;
image = this._updateImageFlashParams(image, address, options.flashSize, options.flashMode, options.flashFreq);
let calcmd5 = null;
if (options.calculateMD5Hash) {
calcmd5 = options.calculateMD5Hash(image);
this.debug("Image MD5 " + calcmd5);
}
const uncsize = image.length;
let blocks;
if (options.compress) {
const uncimage = this.bstrToUi8(image);
image = this.ui8ToBstr(deflate(uncimage, { level: 9 }));
blocks = await this.flashDeflBegin(uncsize, image.length, address);
}
else {
blocks = await this.flashBegin(uncsize, address);
}
let seq = 0;
let bytesSent = 0;
const totalBytes = image.length;
if (options.reportProgress)
options.reportProgress(i, 0, totalBytes);
let d = new Date();
const t1 = d.getTime();
let timeout = 5000;
// Create a decompressor to keep track of the size of uncompressed data
// to be written in each chunk.
const inflate = new Inflate({ chunkSize: 1 });
let totalLenUncompressed = 0;
inflate.onData = function (chunk) {
totalLenUncompressed += chunk.byteLength;
};
while (image.length > 0) {
this.debug("Write loop " + address + " " + seq + " " + blocks);
this.info("Writing at 0x" +
(address + totalLenUncompressed).toString(16) +
"... (" +
Math.floor((100 * (seq + 1)) / blocks) +
"%)");
const block = this.bstrToUi8(image.slice(0, this.FLASH_WRITE_SIZE));
if (options.compress) {
const lenUncompressedPrevious = totalLenUncompressed;
inflate.push(block, false);
const blockUncompressed = totalLenUncompressed - lenUncompressedPrevious;
let blockTimeout = 3000;
if (this.timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, blockUncompressed) > 3000) {
blockTimeout = this.timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, blockUncompressed);
}
if (this.IS_STUB === false) {
// ROM code writes block to flash before ACKing
timeout = blockTimeout;
}
await this.flashDeflBlock(block, seq, timeout);
if (this.IS_STUB) {
// Stub ACKs when block is received, then writes to flash while receiving the block after it
timeout = blockTimeout;
}
}
else {
throw new ESPError("Yet to handle Non Compressed writes");
}
bytesSent += block.length;
image = image.slice(this.FLASH_WRITE_SIZE, image.length);
seq++;
if (options.reportProgress)
options.reportProgress(i, bytesSent, totalBytes);
}
if (this.IS_STUB) {
await this.readReg(this.CHIP_DETECT_MAGIC_REG_ADDR, timeout);
}
d = new Date();
const t = d.getTime() - t1;
if (options.compress) {
this.info("Wrote " +
uncsize +
" bytes (" +
bytesSent +
" compressed) at 0x" +
address.toString(16) +
" in " +
t / 1000 +
" seconds.");
}
if (calcmd5) {
const res = await this.flashMd5sum(address, uncsize);
if (new String(res).valueOf() != new String(calcmd5).valueOf()) {
this.info("File md5: " + calcmd5);
this.info("Flash md5: " + res);
throw new ESPError("MD5 of file does not match data in flash!");
}
else {
this.info("Hash of data verified.");
}
}
}
this.info("Leaving...");
if (this.IS_STUB) {
await this.flashBegin(0, 0);
if (options.compress) {
await this.flashDeflFinish();
}
else {
await this.flashFinish();
}
}
}
/**
* Read SPI flash manufacturer and device id.
*/
async flashId() {
this.debug("flash_id");
const flashid = await this.readFlashId();
this.info("Manufacturer: " + (flashid & 0xff).toString(16));
const flidLowbyte = (flashid >> 16) & 0xff;
this.info("Device: " + ((flashid >> 8) & 0xff).toString(16) + flidLowbyte.toString(16));
this.info("Detected flash size: " + this.DETECTED_FLASH_SIZES[flidLowbyte]);
}
async getFlashSize() {
this.debug("flash_id");
const flashid = await this.readFlashId();
const flidLowbyte = (flashid >> 16) & 0xff;
return this.DETECTED_FLASH_SIZES_NUM[flidLowbyte];
}
/**
* 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(stayInBootloader) {
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 {
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. Default is 'hard_reset'.
* @param { boolean } usingUsbOtg For 'hard_reset' to specify if using USB-OTG
*/
async after(mode = "hard_reset", usingUsbOtg) {
switch (mode) {
case "hard_reset":
if (this.resetConstructors.hardReset) {
this.info("Hard resetting via RTS pin...");
const hardReset = this.resetConstructors.hardReset(this.transport, usingUsbOtg);
await hardReset.reset();
}
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;
}
}
}