diff --git a/index.html b/index.html
index 9a35f52..6c5ba36 100644
--- a/index.html
+++ b/index.html
@@ -19,6 +19,7 @@
+
diff --git a/js/esptool.js b/js/esptool.js
new file mode 100644
index 0000000..69ed854
--- /dev/null
+++ b/js/esptool.js
@@ -0,0 +1,953 @@
+'use strict';
+
+let port;
+let reader;
+let inputStream;
+let outputStream;
+let inputBuffer = [];
+
+const ESP_ROM_BAUD = 115200;
+const FLASH_WRITE_SIZE = 0x200;
+const ESP32S2_FLASH_WRITE_SIZE = 0x400;
+const FLASH_SECTOR_SIZE = 0x1000; // Flash sector size, minimum unit of erase.
+
+const SYNC_PACKET = toByteArray("\x07\x07\x12 UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU");
+const CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000;
+const ESP8266 = 0x8266;
+const ESP32 = 0x32;
+const ESP32S2 = 0x3252;
+const ESP32_DATAREGVALUE = 0x15122500;
+const ESP8266_DATAREGVALUE = 0x00062000;
+const ESP32S2_DATAREGVALUE = 0x500;
+
+// Commands supported by ESP8266 ROM bootloader
+const ESP_FLASH_BEGIN = 0x02;
+const ESP_FLASH_DATA = 0x03;
+const ESP_FLASH_END = 0x04;
+const ESP_MEM_BEGIN = 0x05;
+const ESP_MEM_END = 0x06;
+const ESP_MEM_DATA = 0x07;
+const ESP_SYNC = 0x08;
+const ESP_WRITE_REG = 0x09;
+const ESP_READ_REG = 0x0A;
+
+const ESP_ERASE_FLASH = 0xD0;
+const ESP_ERASE_REGION = 0xD1;
+
+const ESP_SPI_SET_PARAMS = 0x0B;
+const ESP_SPI_ATTACH = 0x0D;
+const ESP_CHANGE_BAUDRATE = 0x0F;
+const ESP_SPI_FLASH_MD5 = 0x13;
+const ESP_CHECKSUM_MAGIC = 0xEF;
+
+const ROM_INVALID_RECV_MSG = 0x05;
+
+const USB_RAM_BLOCK = 0x800;
+const ESP_RAM_BLOCK = 0x1800;
+
+// Timeouts
+const DEFAULT_TIMEOUT = 3000;
+const CHIP_ERASE_TIMEOUT = 600000; // timeout for full chip erase in ms
+const MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2; // longest any command can run in ms
+const SYNC_TIMEOUT = 100; // timeout for syncing with bootloader in ms
+const ERASE_REGION_TIMEOUT_PER_MB = 30000; // timeout (per megabyte) for erasing a region in ms
+const MEM_END_ROM_TIMEOUT = 50;
+
+/**
+ * @name toByteArray
+ * Convert a string to a byte array
+ */
+function toByteArray(str) {
+ let byteArray = [];
+ for (let i = 0; i < str.length; i++) {
+ let charcode = str.charCodeAt(i);
+ if (charcode <= 0xFF) {
+ byteArray.push(charcode);
+ }
+ }
+ return byteArray;
+}
+
+class EspLoader {
+
+ constructor(params) {
+ this._chipfamily = null;
+ this._efuses = new Array(4).fill(0);
+ this._flashsize = 4 * 1024 * 1024;
+ if (this.isFunction(params.updateProgress)) {
+ this.updateProgress = params.updateProgress
+ } else {
+ this.updateProgress = null
+ }
+
+ if (this.isFunction(params.logMsg)) {
+ this.logMsg = params.logMsg
+ } else {
+ this.logMsg = console.log
+ }
+ this.debug = false;
+ if (this.isFunction(params.debugMsg)) {
+ if (params.debug !== false) {
+ this.debug = true;
+ }
+ console.log(params.debug)
+ console.log(this.debug)
+ this.debugMsg = params.debugMsg
+ } else {
+ this.debugMsg = this.logMsg()
+ }
+ this.IS_STUB = false;
+ }
+
+ isFunction(functionObj) {
+ return functionObj && {}.toString.call(functionObj) === '[object Function]';
+ }
+
+ toHex(value, size=2) {
+ return "0x" + value.toString(16).toUpperCase().padStart(size, "0");
+ }
+
+ getChromeVersion() {
+ let raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
+
+ return raw ? parseInt(raw[2], 10) : false;
+ }
+
+ /**
+ * @name slipEncode
+ * Take an array buffer and return back a new array where
+ * 0xdb is replaced with 0xdb 0xdd and 0xc0 is replaced with 0xdb 0xdc
+ */
+ slipEncode(buffer) {
+ let encoded = [];
+ for (let byte of buffer) {
+ if (byte == 0xDB) {
+ encoded = encoded.concat([0xDB, 0xDD]);
+ } else if (byte == 0xC0) {
+ encoded = encoded.concat([0xDB, 0xDC]);
+ } else {
+ encoded.push(byte);
+ }
+ }
+ return encoded;
+ };
+
+ /**
+ * @name macAddr
+ * The MAC address burned into the OTP memory of the ESP chip
+ */
+ macAddr() {
+ let macAddr = new Array(6).fill(0);
+ let mac0 = this._efuses[0];
+ let mac1 = this._efuses[1];
+ let mac2 = this._efuses[2];
+ let mac3 = this._efuses[3];
+ let oui;
+ if (this._chipfamily == ESP8266) {
+ if (mac3 != 0) {
+ oui = [(mac3 >> 16) & 0xFF, (mac3 >> 8) & 0xFF, mac3 & 0xFF];
+ } else if (((mac1 >> 16) & 0xFF) == 0) {
+ oui = [0x18, 0xFE, 0x34];
+ } else if (((mac1 >> 16) & 0xFF) == 1) {
+ oui = [0xAC, 0xD0, 0x74];
+ } else {
+ throw("Couldnt determine OUI");
+ }
+
+ macAddr[0] = oui[0];
+ macAddr[1] = oui[1];
+ macAddr[2] = oui[2];
+ macAddr[3] = (mac1 >> 8) & 0xFF;
+ macAddr[4] = mac1 & 0xFF;
+ macAddr[5] = (mac0 >> 24) & 0xFF;
+ } else if (this._chipfamily == ESP32) {
+ macAddr[0] = mac2 >> 8 & 0xFF;
+ macAddr[1] = mac2 & 0xFF;
+ macAddr[2] = mac1 >> 24 & 0xFF;
+ macAddr[3] = mac1 >> 16 & 0xFF;
+ macAddr[4] = mac1 >> 8 & 0xFF;
+ macAddr[5] = mac1 & 0xFF;
+ } else if (this._chipfamily == ESP32S2) {
+ macAddr[0] = mac2 >> 8 & 0xFF;
+ macAddr[1] = mac2 & 0xFF;
+ macAddr[2] = mac1 >> 24 & 0xFF;
+ macAddr[3] = mac1 >> 16 & 0xFF;
+ macAddr[4] = mac1 >> 8 & 0xFF;
+ macAddr[5] = mac1 & 0xFF;
+ } else {
+ throw("Unknown chip family")
+ }
+ return macAddr;
+ };
+
+ /**
+ * @name _readEfuses
+ * Read the OTP data for this chip and store into this.efuses array
+ */
+ async _readEfuses() {
+ let baseAddr
+ if (this._chipfamily == ESP8266) {
+ baseAddr = 0x3FF00050;
+ } else if (this._chipfamily == ESP32) {
+ baseAddr = 0x6001A000;
+ } else if (this._chipfamily == ESP32S2) {
+ baseAddr = 0x6001A000;
+ } else {
+ throw("Don't know what chip this is");
+ }
+ for (let i = 0; i < 4; i++) {
+ this._efuses[i] = await this.readRegister(baseAddr + 4 * i);
+ }
+ };
+
+ /**
+ * @name readRegister
+ * Read a register within the ESP chip RAM, returns a 4-element list
+ */
+ async readRegister(reg) {
+ if (this.debug) {
+ this.debugMsg("Reading Register", reg);
+ }
+ let packet = this.pack("I", reg);
+ let register = (await this.checkCommand(ESP_READ_REG, packet))[0];
+ return this.unpack("I", register)[0];
+ };
+
+ sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ /**
+ * @name chipType
+ * ESP32 or ESP8266 based on which chip type we're talking to
+ */
+ async chipType() {
+ if (this._chipfamily === null) {
+ let datareg = await this.readRegister(0x60000078);
+ if (datareg == ESP32_DATAREGVALUE) {
+ this._chipfamily = ESP32;
+ } else if (datareg == ESP8266_DATAREGVALUE) {
+ this._chipfamily = ESP8266;
+ } else if (datareg == ESP32S2_DATAREGVALUE) {
+ this._chipfamily = ESP32S2;
+ } else {
+ throw("Unknown Chip.");
+ }
+ }
+ return this._chipfamily;
+ };
+
+ /**
+ * @name chipType
+ * The specific name of the chip, e.g. ESP8266EX, to the best
+ * of our ability to determine without a stub bootloader.
+ */
+ async chipName() {
+ await this.chipType();
+ await this._readEfuses();
+
+ if (await this.chipType() == ESP32) {
+ return "ESP32";
+ }
+ if (await this.chipType() == ESP32S2) {
+ return "ESP32-S2";
+ }
+ if (await this.chipType() == ESP8266) {
+ if (this._efuses[0] & (1 << 4) || this._efuses[2] & (1 << 16)) {
+ return "ESP8285";
+ }
+ return "ESP8266EX";
+ }
+ return null;
+ };
+
+ /**
+ * @name checkCommand
+ * Send a command packet, check that the command succeeded and
+ * return a tuple with the value and data.
+ * See the ESP Serial Protocol for more details on what value/data are
+ */
+ async checkCommand(opcode, buffer, checksum=0, timeout=DEFAULT_TIMEOUT) {
+ timeout = Math.min(timeout, MAX_TIMEOUT);
+ await this.sendCommand(opcode, buffer, checksum);
+ let [value, data] = await this.getResponse(opcode, timeout);
+ let statusLen;
+ if (data !== null) {
+ if (this.IS_STUB) {
+ statusLen = 2;
+ } else if (this._chipfamily == ESP8266) {
+ statusLen = 2;
+ } else if ([ESP32, ESP32S2].includes(this._chipfamily)) {
+ statusLen = 4;
+ } else {
+ if ([2, 4].includes(data.length)) {
+ statusLen = data.length;
+ }
+ }
+ }
+ if (data === null || data.length < statusLen) {
+ throw("Didn't get enough status bytes");
+ }
+ let status = data.slice(-statusLen, data.length);
+ data = data.slice(0, -statusLen);
+ if (this.debug) {
+ this.debugMsg("status", status);
+ this.debugMsg("value", value);
+ this.debugMsg("data", data);
+ }
+ if (status[0] == 1) {
+ if (status[1] == ROM_INVALID_RECV_MSG) {
+ throw("Invalid (unsupported) command " + this.toHex(opcode));
+ } else {
+ throw("Command failure error code " + this.toHex(status[1]));
+ }
+ }
+ return [value, data];
+ };
+
+ /**
+ * @name timeoutPerMb
+ * Scales timeouts which are size-specific
+ */
+ timeoutPerMb(secondsPerMb, sizeBytes) {
+ let result = Math.floor(secondsPerMb * (sizeBytes / 0x1e6));
+ if (result < DEFAULT_TIMEOUT) {
+ return DEFAULT_TIMEOUT;
+ }
+ return result;
+ };
+
+ /**
+ * @name sendCommand
+ * Send a slip-encoded, checksummed command over the UART,
+ * does not check response
+ */
+ async sendCommand(opcode, buffer, checksum=0) {
+ //this.debugMsg("Running Send Command");
+ inputBuffer = []; // Reset input buffer
+ let packet = [0xC0, 0x00]; // direction
+ packet.push(opcode);
+ packet = packet.concat(this.pack("H", buffer.length));
+ packet = packet.concat(this.slipEncode(this.pack("I", checksum)));
+ packet = packet.concat(this.slipEncode(buffer));
+ packet.push(0xC0);
+ if (this.debug) {
+ this.debugMsg("Writing " + packet.length + " byte" + (packet.length == 1 ? "" : "s") + ":", packet);
+ }
+ await this.writeToStream(packet);
+ };
+
+ /**
+ * @name connect
+ * Opens a Web Serial connection to a micro:bit and sets up the input and
+ * output stream.
+ */
+ async connect() {
+ // - Request a port and open a connection.
+ port = await navigator.serial.requestPort();
+
+ // - Wait for the port to open.toggleUIConnected
+ if (this.getChromeVersion() < 86) {
+ await port.open({ baudrate: ESP_ROM_BAUD });
+ } else {
+ await port.open({ baudRate: ESP_ROM_BAUD });
+ }
+
+ const signals = await port.getSignals();
+
+ this.logMsg("Connected successfully.")
+
+ this.logMsg("Try to reset.")
+ await port.setSignals({ dataTerminalReady: false, requestToSend: true });
+ await port.setSignals({ dataTerminalReady: true, requestToSend: false });
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ outputStream = port.writable;
+ inputStream = port.readable;
+ }
+
+ connected() {
+ if (port) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @name disconnect
+ * Closes the Web Serial connection.
+ */
+ async disconnect() {
+ if (reader) {
+ await reader.cancel();
+ reader = null;
+ }
+
+ if (outputStream) {
+ await outputStream.getWriter().close();
+ outputStream = null;
+ }
+
+ await port.close();
+ port = null;
+ }
+
+ /**
+ * @name writeToStream
+ * Gets a writer from the output stream and send the raw data over WebSerial.
+ */
+ async writeToStream(data) {
+ const writer = outputStream.getWriter();
+ await writer.write(new Uint8Array(data));
+ writer.releaseLock();
+ }
+
+ /**
+ * @name getResponse
+ * Read response data and decodes the slip packet, then parses
+ * out the value/data and returns as a tuple of (value, data) where
+ * each is a list of bytes
+ */
+ async getResponse(opcode, timeout=DEFAULT_TIMEOUT) {
+ let reply = [];
+ let packetLength = 0;
+ let escapedByte = false;
+ let stamp = Date.now();
+ while (Date.now() - stamp < timeout) {
+ if (inputBuffer.length > 0) {
+ let c = inputBuffer.shift();
+ if (c == 0xDB) {
+ escapedByte = true;
+ } else if (escapedByte) {
+ if (c == 0xDD) {
+ reply.push(0xDC);
+ } else if (c == 0xDC) {
+ reply.push(0xC0);
+ } else {
+ reply = reply.concat([0xDB, c]);
+ }
+ escapedByte = false;
+ } else {
+ reply.push(c);
+ }
+ } else {
+ await this.sleep(10);
+ }
+ if (reply.length > 0 && reply[0] != 0xC0) {
+ // packets must start with 0xC0
+ reply.shift();
+ }
+ if (reply.length > 1 && reply[1] != 0x01) {
+ reply.shift();
+ }
+ if (reply.length > 2 && reply[2] != opcode) {
+ reply.shift();
+ }
+ if (reply.length > 4) {
+ // get the length
+ packetLength = reply[3] + (reply[4] << 8);
+ }
+ if (reply.length == packetLength + 10) {
+ break;
+ }
+ }
+
+ // Check to see if we have a complete packet. If not, we timed out.
+ if (reply.length != packetLength + 10) {
+ this.logMsg("Timed out after " + timeout + " milliseconds");
+ return [null, null];
+ }
+ if (this.debug) {
+ this.debugMsg("Reading " + reply.length + " byte" + (reply.length == 1 ? "" : "s") + ":", reply);
+ }
+ let value = reply.slice(5, 9);
+ let data = reply.slice(9, -1);
+ if (this.debug) {
+ this.debugMsg("value:", value, "data:", data);
+ }
+ return [value, data];
+ };
+
+/**
+ * @name read
+ * Read response data and decodes the slip packet.
+ * Keeps reading until we hit the timeout or get
+ * a packet closing byte
+ */
+ async readBuffer(timeout=DEFAULT_TIMEOUT) {
+ let reply = [];
+ let packetLength = 0;
+ let escapedByte = false;
+ let stamp = Date.now();
+ while (Date.now() - stamp < timeout) {
+ if (inputBuffer.length > 0) {
+ let c = inputBuffer.shift();
+ if (c == 0xDB) {
+ escapedByte = true;
+ } else if (escapedByte) {
+ if (c == 0xDD) {
+ reply.push(0xDC);
+ } else if (c == 0xDC) {
+ reply.push(0xC0);
+ } else {
+ reply = reply.concat([0xDB, c]);
+ }
+ escapedByte = false;
+ } else {
+ reply.push(c);
+ }
+ } else {
+ await this.sleep(10);
+ }
+ if (reply.length > 0 && reply[0] != 0xC0) {
+ // packets must start with 0xC0
+ reply.shift();
+ }
+ if (reply.length > 1 && reply[reply.length - 1] == 0xC0) {
+ break;
+ }
+ }
+
+ // Check to see if we have a complete packet. If not, we timed out.
+ if (reply.length < 2) {
+ this.logMsg("Timed out after " + timeout + " milliseconds");
+ return null;
+ }
+ if (this.debug) {
+ this.debugMsg("Reading " + reply.length + " byte" + (reply.length == 1 ? "" : "s") + ":", reply);
+ }
+ let data = reply.slice(1, -1);
+ if (this.debug) {
+ this.debugMsg("data:", data);
+ }
+ return data;
+ };
+
+
+ /**
+ * @name checksum
+ * Calculate checksum of a blob, as it is defined by the ROM
+ */
+ checksum(data, state=ESP_CHECKSUM_MAGIC) {
+ for (let b of data) {
+ state ^= b;
+ }
+ return state;
+ };
+
+ async setBaudrate(baud) {
+ if (this._chipfamily == ESP8266) {
+ this.logMsg("Baud rate can only change on ESP32 and ESP32-S2");
+ } else {
+ this.logMsg("Attempting to change baud rate to " + baud + "...");
+ try {
+ let buffer = this.pack("]/, '').length != data.length) {
+ throw("Pack format to Argument count mismatch");
+ return;
+ }
+ let bytes = [];
+ let littleEndian = true;
+ for (let i = 0; i < format.length; i++) {
+ if (format[i] == "<") {
+ littleEndian = true;
+ } else if (format[i] == ">") {
+ littleEndian = false;
+ } else if (format[i] == "B") {
+ pushBytes(data[pointer], 1);
+ pointer++;
+ } else if (format[i] == "H") {
+ pushBytes(data[pointer], 2);
+ pointer++;
+ } else if (format[i] == "I") {
+ pushBytes(data[pointer], 4);
+ pointer++;
+ } else {
+ throw("Unhandled character in pack format");
+ }
+ }
+
+ function pushBytes(value, byteCount) {
+ for (let i = 0; i < byteCount; i++) {
+ if (littleEndian) {
+ bytes.push((value >> (i * 8)) & 0xFF);
+ } else {
+ bytes.push((value >> ((byteCount - i) * 8)) & 0xFF);
+ }
+ }
+ }
+
+ return bytes;
+ };
+
+ unpack(format, bytes) {
+ let pointer = 0;
+ let data = [];
+ for (let c of format) {
+ if (c == "B") {
+ data.push((bytes[pointer] & 0xFF));
+ pointer += 1;
+ } else if (c == "H") {
+ data.push(
+ (bytes[pointer] & 0xFF) |
+ ((bytes[pointer + 1] & 0xFF) << 8)
+ );
+ pointer += 2;
+ } else if (c == "I") {
+ data.push(
+ (bytes[pointer] & 0xFF) |
+ ((bytes[pointer + 1] & 0xFF) << 8) |
+ ((bytes[pointer + 2] & 0xFF) << 16) |
+ ((bytes[pointer + 3] & 0xFF) << 24))
+ pointer += 4;
+ } else {
+ throw("Unhandled character in unpack format");
+ }
+ }
+ return data;
+ };
+
+ /**
+ * @name sync
+ * Put into ROM bootload mode & attempt to synchronize with the
+ * ESP ROM bootloader, we will retry a few times
+ */
+ async sync() {
+ for (let i = 0; i < 5; i++) {
+ let response = await this._sync();
+ if (response) {
+ await this.sleep(100);
+ return true;
+ }
+ await this.sleep(100);
+ }
+
+ throw("Couldn't sync to ESP. Try resetting.");
+ };
+
+ /**
+ * @name _sync
+ * Perform a soft-sync using AT sync packets, does not perform
+ * any hardware resetting
+ */
+ async _sync() {
+ await this.sendCommand(ESP_SYNC, SYNC_PACKET);
+ for (let i = 0; i < 8; i++) {
+ let [reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
+ if (data === null) {
+ continue;
+ }
+ if (data.length > 1 && data[0] == 0 && data[1] == 0) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ /**
+ * @name getFlashWriteSize
+ * Get the Flash write size based on the chip
+ */
+ getFlashWriteSize() {
+ if (this._chipfamily == ESP32S2) {
+ return ESP32S2_FLASH_WRITE_SIZE;
+ }
+ return FLASH_WRITE_SIZE;
+ };
+
+ /**
+ * @name flashData
+ * Program a full, uncompressed binary file into SPI Flash at
+ * a given offset. If an ESP32 and md5 string is passed in, will also
+ * verify memory. ESP8266 does not have checksum memory verification in
+ * ROM
+ */
+ async flashData(binaryData, offset=0, part=0) {
+ let filesize = binaryData.byteLength;
+ this.logMsg("\nWriting data with filesize:" + filesize);
+ let blocks = await this.flashBegin(filesize, offset);
+ let block = [];
+ let seq = 0;
+ let written = 0;
+ let address = offset;
+ let position = 0;
+ let stamp = Date.now();
+ let flashWriteSize = this.getFlashWriteSize();
+
+ while (filesize - position > 0) {
+ let percentage = Math.floor(100 * (seq + 1) / blocks);
+ /*this.logMsg(
+ "Writing at " + this.toHex(address + seq * flashWriteSize, 8) + "... (" + percentage + " %)"
+ );*/
+ if (this.updateProgress !== null) {
+ this.updateProgress(part, percentage);
+ }
+ if (filesize - position >= flashWriteSize) {
+ block = Array.from(new Uint8Array(binaryData, position, flashWriteSize));
+ } else {
+ // Pad the last block
+ block = Array.from(new Uint8Array(binaryData, position, filesize - position));
+ block = block.concat(new Array(flashWriteSize - block.length).fill(0xFF));
+ }
+ await this.flashBlock(block, seq, 2000);
+ seq += 1;
+ written += block.length;
+ position += flashWriteSize;
+ }
+ this.logMsg("Took " + (Date.now() - stamp) + "ms to write " + filesize + " bytes");
+ };
+
+ /**
+ * @name flashBlock
+ * Send one block of data to program into SPI Flash memory
+ */
+ async flashBlock(data, seq, timeout=100) {
+ await this.checkCommand(
+ ESP_FLASH_DATA,
+ this.pack(" start) {
+ throw("Software loader is resident at " + this.toHex(start, 8) + "-" + this.toHex(end, 8) + ". " +
+ "Can't load binary at overlapping address range " + this.toHex(load_start, 8) + "-" + this.toHex(load_end, 8) + ". " +
+ "Try changing the binary loading address.");
+ }
+ }
+ }
+
+ return this.checkCommand(ESP_MEM_BEGIN, this.pack(' length) {
+ toOffs = length;
+ }
+ await this.memBlock(stub[field].slice(fromOffs, toOffs), seq);
+ }
+ }
+ }
+ this.logMsg("Running stub...")
+ await this.memFinish(stub['entry']);
+
+ let p = await this.readBuffer(100);
+ p = String.fromCharCode(...p);
+
+ if (p != 'OHAI') {
+ throw "Failed to start stub. Unexpected response: " + p;
+ }
+ this.logMsg("Stub is now running...");
+ return new EspStubLoader({
+ updateProgress: this.updateProgress,
+ logMsg: this.logMsg,
+ debugMsg: this.debugMsg,
+ debug: this.debug,
+ });
+ }
+}
+
+class EspStubLoader extends EspLoader {
+ /*
+ The Stubloader has commands that run on the uploaded Stub Code in RAM
+ rather than built in commands.
+ */
+ constructor(params) {
+ super(params);
+ this.IS_STUB = true;
+ }
+ /**
+ * @name getEraseSize
+ * depending on flash chip model the erase may take this long (maybe longer!)
+ */
+ async eraseFlash() {
+ await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
+ };
+}
diff --git a/js/script.js b/js/script.js
index e4c4a44..5d5664d 100644
--- a/js/script.js
+++ b/js/script.js
@@ -1,9 +1,8 @@
+// Note: the code will still work without this line, but without it you
+// will see an error in the editor
+/* global EspLoader, ESP_ROM_BAUD, port, reader, inputBuffer */
'use strict';
-let port;
-let reader;
-let inputStream;
-let outputStream;
let espTool;
let isConnected = false;
let stubLoader = null;
@@ -21,52 +20,6 @@ const flashSizes = {
"16MB": 0x90,
};
-const FLASH_WRITE_SIZE = 0x200;
-const ESP32S2_FLASH_WRITE_SIZE = 0x400;
-const FLASH_SECTOR_SIZE = 0x1000; // Flash sector size, minimum unit of erase.
-const ESP_ROM_BAUD = 115200;
-
-const SYNC_PACKET = toByteArray("\x07\x07\x12 UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU");
-const CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000;
-const ESP8266 = 0x8266;
-const ESP32 = 0x32;
-const ESP32S2 = 0x3252;
-const ESP32_DATAREGVALUE = 0x15122500;
-const ESP8266_DATAREGVALUE = 0x00062000;
-const ESP32S2_DATAREGVALUE = 0x500;
-
-// Commands supported by ESP8266 ROM bootloader
-const ESP_FLASH_BEGIN = 0x02;
-const ESP_FLASH_DATA = 0x03;
-const ESP_FLASH_END = 0x04;
-const ESP_MEM_BEGIN = 0x05;
-const ESP_MEM_END = 0x06;
-const ESP_MEM_DATA = 0x07;
-const ESP_SYNC = 0x08;
-const ESP_WRITE_REG = 0x09;
-const ESP_READ_REG = 0x0A;
-
-const ESP_ERASE_FLASH = 0xD0;
-const ESP_ERASE_REGION = 0xD1;
-
-const ESP_SPI_SET_PARAMS = 0x0B;
-const ESP_SPI_ATTACH = 0x0D;
-const ESP_CHANGE_BAUDRATE = 0x0F;
-const ESP_SPI_FLASH_MD5 = 0x13;
-const ESP_CHECKSUM_MAGIC = 0xEF;
-
-const ROM_INVALID_RECV_MSG = 0x05;
-
-const USB_RAM_BLOCK = 0x800;
-const ESP_RAM_BLOCK = 0x1800;
-
-// Timeouts
-const DEFAULT_TIMEOUT = 3000;
-const CHIP_ERASE_TIMEOUT = 600000; // timeout for full chip erase in ms
-const MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2; // longest any command can run in ms
-const SYNC_TIMEOUT = 100; // timeout for syncing with bootloader in ms
-const ERASE_REGION_TIMEOUT_PER_MB = 30000; // timeout (per megabyte) for erasing a region in ms
-const MEM_END_ROM_TIMEOUT = 50;
const bufferSize = 512;
const colors = ['#00a7e9', '#f89521', '#be1e2d'];
@@ -93,10 +46,13 @@ let activePanels = [];
let bytesReceived = 0;
let currentBoard;
let buttonState = 0;
-let inputBuffer = [];
document.addEventListener('DOMContentLoaded', () => {
- espTool = new EspLoader()
+ espTool = new EspLoader({
+ updateProgress: updateProgress,
+ logMsg: logMsg,
+ debugMsg: debugMsg,
+ debug: false})
butConnect.addEventListener('click', () => {
clickConnect().catch(async (e) => {
errorMsg(e.message);
@@ -130,41 +86,14 @@ document.addEventListener('DOMContentLoaded', () => {
logMsg("Adafruit WebSerial ESPTool loaded.");
});
-function getChromeVersion() {
- let raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
-
- return raw ? parseInt(raw[2], 10) : false;
-}
-
/**
* @name connect
* Opens a Web Serial connection to a micro:bit and sets up the input and
* output stream.
*/
async function connect() {
- // - Request a port and open a connection.
- port = await navigator.serial.requestPort();
-
logMsg("Connecting...")
- // - Wait for the port to open.toggleUIConnected
- if (getChromeVersion() < 86) {
- await port.open({ baudrate: ESP_ROM_BAUD });
- } else {
- await port.open({ baudRate: ESP_ROM_BAUD });
- }
-
- const signals = await port.getSignals();
-
- logMsg("Connected successfully.")
-
- logMsg("Try to reset.")
- await port.setSignals({ dataTerminalReady: false, requestToSend: true });
- await port.setSignals({ dataTerminalReady: true, requestToSend: false });
- await new Promise(resolve => setTimeout(resolve, 1000));
-
- outputStream = port.writable;
- inputStream = port.readable;
-
+ await espTool.connect()
readLoop().catch((error) => {
toggleUIConnected(false);
});
@@ -179,19 +108,9 @@ function initBaudRate() {
}
}
-/**
- * @name toByteArray
- * Convert a string to a byte array
- */
-function toByteArray(str) {
- let byteArray = [];
- for (let i = 0; i < str.length; i++) {
- let charcode = str.charCodeAt(i);
- if (charcode <= 0xFF) {
- byteArray.push(charcode);
- }
- }
- return byteArray;
+function updateProgress(part, percentage) {
+ let progressBar = progress[part].querySelector("div");
+ progressBar.style.width = percentage + "%";
}
/**
@@ -200,18 +119,7 @@ function toByteArray(str) {
*/
async function disconnect() {
toggleUIToolbar(false);
- if (reader) {
- await reader.cancel();
- reader = null;
- }
-
- if (outputStream) {
- await outputStream.getWriter().close();
- outputStream = null;
- }
-
- await port.close();
- port = null;
+ await espTool.disconnect()
}
/**
@@ -219,6 +127,7 @@ async function disconnect() {
* Reads data from the input stream and places it in the inputBuffer
*/
async function readLoop() {
+ logMsg("Readloop started")
reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
@@ -247,7 +156,6 @@ function logMsg(text) {
function debugMsg(...args) {
function getStackTrace() {
let stack = new Error().stack;
- //console.log(stack);
stack = stack.split("\n").map(v => v.trim());
stack.shift();
stack.shift();
@@ -276,9 +184,9 @@ function debugMsg(...args) {
} else if (typeof arg == "boolean") {
logMsg(prefix + arg ? "true" : "false");
} else if (Array.isArray(arg)) {
- logMsg(prefix + "[" + arg.map(value => toHex(value)).join(", ") + "]");
+ logMsg(prefix + "[" + arg.map(value => espTool.toHex(value)).join(", ") + "]");
} else if (typeof arg == "object" && (arg instanceof Uint8Array)) {
- logMsg(prefix + "[" + Array.from(arg).map(value => toHex(value)).join(", ") + "]");
+ logMsg(prefix + "[" + Array.from(arg).map(value => espTool.toHex(value)).join(", ") + "]");
} else {
logMsg(prefix + "Unhandled type of argument:" + typeof arg);
console.log(arg);
@@ -296,20 +204,6 @@ function formatMacAddr(macAddr) {
return macAddr.map(value => value.toString(16).toUpperCase().padStart(2, "0")).join(":");
}
-function toHex(value, size=2) {
- return "0x" + value.toString(16).toUpperCase().padStart(size, "0");
-}
-
-/**
- * @name writeToStream
- * Gets a writer from the output stream and send the raw data over WebSerial.
- */
-async function writeToStream(data) {
- const writer = outputStream.getWriter();
- await writer.write(new Uint8Array(data));
- writer.releaseLock();
-}
-
/**
* @name updateTheme
* Sets the theme to Adafruit (dark) mode. Can be refactored later for more themes
@@ -349,7 +243,7 @@ async function reset() {
* Click handler for the connect/disconnect button.
*/
async function clickConnect() {
- if (port) {
+ if (espTool.connected()) {
await disconnect();
toggleUIConnected(false);
return;
@@ -593,785 +487,6 @@ function saveSetting(setting, value) {
window.localStorage.setItem(setting, JSON.stringify(value));
}
-function ucWords(text) {
- return text.replace('_', ' ').toLowerCase().replace(/(?<= )[^\s]|^./g, a=>a.toUpperCase())
-}
-
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
-
-class EspLoader {
- constructor() {
- this._chipfamily = null;
- this._efuses = new Array(4).fill(0);
- this._flashsize = 4 * 1024 * 1024;
- this.debug = false;
- this.IS_STUB = false;
- }
-
- /**
- * @name slipEncode
- * Take an array buffer and return back a new array where
- * 0xdb is replaced with 0xdb 0xdd and 0xc0 is replaced with 0xdb 0xdc
- */
- slipEncode(buffer) {
- let encoded = [];
- for (let byte of buffer) {
- if (byte == 0xDB) {
- encoded = encoded.concat([0xDB, 0xDD]);
- } else if (byte == 0xC0) {
- encoded = encoded.concat([0xDB, 0xDC]);
- } else {
- encoded.push(byte);
- }
- }
- return encoded;
- };
-
- /**
- * @name macAddr
- * The MAC address burned into the OTP memory of the ESP chip
- */
- macAddr() {
- let macAddr = new Array(6).fill(0);
- let mac0 = this._efuses[0];
- let mac1 = this._efuses[1];
- let mac2 = this._efuses[2];
- let mac3 = this._efuses[3];
- let oui;
- if (this._chipfamily == ESP8266) {
- if (mac3 != 0) {
- oui = [(mac3 >> 16) & 0xFF, (mac3 >> 8) & 0xFF, mac3 & 0xFF];
- } else if (((mac1 >> 16) & 0xFF) == 0) {
- oui = [0x18, 0xFE, 0x34];
- } else if (((mac1 >> 16) & 0xFF) == 1) {
- oui = [0xAC, 0xD0, 0x74];
- } else {
- throw("Couldnt determine OUI");
- }
-
- macAddr[0] = oui[0];
- macAddr[1] = oui[1];
- macAddr[2] = oui[2];
- macAddr[3] = (mac1 >> 8) & 0xFF;
- macAddr[4] = mac1 & 0xFF;
- macAddr[5] = (mac0 >> 24) & 0xFF;
- } else if (this._chipfamily == ESP32) {
- macAddr[0] = mac2 >> 8 & 0xFF;
- macAddr[1] = mac2 & 0xFF;
- macAddr[2] = mac1 >> 24 & 0xFF;
- macAddr[3] = mac1 >> 16 & 0xFF;
- macAddr[4] = mac1 >> 8 & 0xFF;
- macAddr[5] = mac1 & 0xFF;
- } else if (this._chipfamily == ESP32S2) {
- macAddr[0] = mac2 >> 8 & 0xFF;
- macAddr[1] = mac2 & 0xFF;
- macAddr[2] = mac1 >> 24 & 0xFF;
- macAddr[3] = mac1 >> 16 & 0xFF;
- macAddr[4] = mac1 >> 8 & 0xFF;
- macAddr[5] = mac1 & 0xFF;
- } else {
- throw("Unknown chip family")
- }
- return macAddr;
- };
-
- /**
- * @name _readEfuses
- * Read the OTP data for this chip and store into this.efuses array
- */
- async _readEfuses() {
- let baseAddr
- if (this._chipfamily == ESP8266) {
- baseAddr = 0x3FF00050;
- } else if (this._chipfamily == ESP32) {
- baseAddr = 0x6001A000;
- } else if (this._chipfamily == ESP32S2) {
- baseAddr = 0x6001A000;
- } else {
- throw("Don't know what chip this is");
- }
- for (let i = 0; i < 4; i++) {
- this._efuses[i] = await this.readRegister(baseAddr + 4 * i);
- }
- };
-
- /**
- * @name readRegister
- * Read a register within the ESP chip RAM, returns a 4-element list
- */
- async readRegister(reg) {
- if (this.debug) {
- debugMsg("Reading Register", reg);
- }
- let packet = this.pack("I", reg);
- let register = (await this.checkCommand(ESP_READ_REG, packet))[0];
- return this.unpack("I", register)[0];
- };
-
- /**
- * @name chipType
- * ESP32 or ESP8266 based on which chip type we're talking to
- */
- async chipType() {
- if (this._chipfamily === null) {
- let datareg = await this.readRegister(0x60000078);
- if (datareg == ESP32_DATAREGVALUE) {
- this._chipfamily = ESP32;
- } else if (datareg == ESP8266_DATAREGVALUE) {
- this._chipfamily = ESP8266;
- } else if (datareg == ESP32S2_DATAREGVALUE) {
- this._chipfamily = ESP32S2;
- } else {
- throw("Unknown Chip.");
- }
- }
- return this._chipfamily;
- };
-
- /**
- * @name chipType
- * The specific name of the chip, e.g. ESP8266EX, to the best
- * of our ability to determine without a stub bootloader.
- */
- async chipName() {
- await this.chipType();
- await this._readEfuses();
-
- if (await this.chipType() == ESP32) {
- return "ESP32";
- }
- if (await this.chipType() == ESP32S2) {
- return "ESP32-S2";
- }
- if (await this.chipType() == ESP8266) {
- if (this._efuses[0] & (1 << 4) || this._efuses[2] & (1 << 16)) {
- return "ESP8285";
- }
- return "ESP8266EX";
- }
- return null;
- };
-
- /**
- * @name checkCommand
- * Send a command packet, check that the command succeeded and
- * return a tuple with the value and data.
- * See the ESP Serial Protocol for more details on what value/data are
- */
- async checkCommand(opcode, buffer, checksum=0, timeout=DEFAULT_TIMEOUT) {
- timeout = Math.min(timeout, MAX_TIMEOUT);
- await this.sendCommand(opcode, buffer, checksum);
- let [value, data] = await this.getResponse(opcode, timeout);
- let statusLen;
- if (data !== null) {
- if (this.IS_STUB) {
- statusLen = 2;
- } else if (this._chipfamily == ESP8266) {
- statusLen = 2;
- } else if ([ESP32, ESP32S2].includes(this._chipfamily)) {
- statusLen = 4;
- } else {
- if ([2, 4].includes(data.length)) {
- statusLen = data.length;
- }
- }
- }
- if (data === null || data.length < statusLen) {
- throw("Didn't get enough status bytes");
- }
- let status = data.slice(-statusLen, data.length);
- data = data.slice(0, -statusLen);
- if (this.debug) {
- debugMsg("status", status);
- debugMsg("value", value);
- debugMsg("data", data);
- }
- if (status[0] == 1) {
- if (status[1] == ROM_INVALID_RECV_MSG) {
- throw("Invalid (unsupported) command " + toHex(opcode));
- } else {
- throw("Command failure error code " + toHex(status[1]));
- }
- }
- return [value, data];
- };
-
- /**
- * @name timeoutPerMb
- * Scales timeouts which are size-specific
- */
- timeoutPerMb(secondsPerMb, sizeBytes) {
- let result = Math.floor(secondsPerMb * (sizeBytes / 0x1e6));
- if (result < DEFAULT_TIMEOUT) {
- return DEFAULT_TIMEOUT;
- }
- return result;
- };
-
- /**
- * @name sendCommand
- * Send a slip-encoded, checksummed command over the UART,
- * does not check response
- */
- async sendCommand(opcode, buffer, checksum=0) {
- //debugMsg("Running Send Command");
- inputBuffer = []; // Reset input buffer
- let packet = [0xC0, 0x00]; // direction
- packet.push(opcode);
- packet = packet.concat(this.pack("H", buffer.length));
- packet = packet.concat(this.slipEncode(this.pack("I", checksum)));
- packet = packet.concat(this.slipEncode(buffer));
- packet.push(0xC0);
- if (this.debug) {
- debugMsg("Writing " + packet.length + " byte" + (packet.length == 1 ? "" : "s") + ":", packet);
- }
- await writeToStream(packet);
- };
-
- /**
- * @name getResponse
- * Read response data and decodes the slip packet, then parses
- * out the value/data and returns as a tuple of (value, data) where
- * each is a list of bytes
- */
- async getResponse(opcode, timeout=DEFAULT_TIMEOUT) {
- let reply = [];
- let packetLength = 0;
- let escapedByte = false;
- let stamp = Date.now();
- while (Date.now() - stamp < timeout) {
- if (inputBuffer.length > 0) {
- let c = inputBuffer.shift();
- if (c == 0xDB) {
- escapedByte = true;
- } else if (escapedByte) {
- if (c == 0xDD) {
- reply.push(0xDC);
- } else if (c == 0xDC) {
- reply.push(0xC0);
- } else {
- reply = reply.concat([0xDB, c]);
- }
- escapedByte = false;
- } else {
- reply.push(c);
- }
- } else {
- await sleep(10);
- }
- if (reply.length > 0 && reply[0] != 0xC0) {
- // packets must start with 0xC0
- reply.shift();
- }
- if (reply.length > 1 && reply[1] != 0x01) {
- reply.shift();
- }
- if (reply.length > 2 && reply[2] != opcode) {
- reply.shift();
- }
- if (reply.length > 4) {
- // get the length
- packetLength = reply[3] + (reply[4] << 8);
- }
- if (reply.length == packetLength + 10) {
- break;
- }
- }
-
- // Check to see if we have a complete packet. If not, we timed out.
- if (reply.length != packetLength + 10) {
- logMsg("Timed out after " + timeout + " milliseconds");
- return [null, null];
- }
- if (this.debug) {
- debugMsg("Reading " + reply.length + " byte" + (reply.length == 1 ? "" : "s") + ":", reply);
- }
- let value = reply.slice(5, 9);
- let data = reply.slice(9, -1);
- if (this.debug) {
- debugMsg("value:", value, "data:", data);
- }
- return [value, data];
- };
-
-/**
- * @name read
- * Read response data and decodes the slip packet.
- * Keeps reading until we hit the timeout or get
- * a packet closing byte
- */
- async readBuffer(timeout=DEFAULT_TIMEOUT) {
- let reply = [];
- let packetLength = 0;
- let escapedByte = false;
- let stamp = Date.now();
- while (Date.now() - stamp < timeout) {
- if (inputBuffer.length > 0) {
- let c = inputBuffer.shift();
- if (c == 0xDB) {
- escapedByte = true;
- } else if (escapedByte) {
- if (c == 0xDD) {
- reply.push(0xDC);
- } else if (c == 0xDC) {
- reply.push(0xC0);
- } else {
- reply = reply.concat([0xDB, c]);
- }
- escapedByte = false;
- } else {
- reply.push(c);
- }
- } else {
- await sleep(10);
- }
- if (reply.length > 0 && reply[0] != 0xC0) {
- // packets must start with 0xC0
- reply.shift();
- }
- if (reply.length > 1 && reply[reply.length - 1] == 0xC0) {
- break;
- }
- }
-
- // Check to see if we have a complete packet. If not, we timed out.
- if (reply.length < 2) {
- logMsg("Timed out after " + timeout + " milliseconds");
- return null;
- }
- if (this.debug) {
- debugMsg("Reading " + reply.length + " byte" + (reply.length == 1 ? "" : "s") + ":", reply);
- }
- let data = reply.slice(1, -1);
- if (this.debug) {
- debugMsg("data:", data);
- }
- return data;
- };
-
-
- /**
- * @name checksum
- * Calculate checksum of a blob, as it is defined by the ROM
- */
- checksum(data, state=ESP_CHECKSUM_MAGIC) {
- for (let b of data) {
- state ^= b;
- }
- return state;
- };
-
- async setBaudrate(baud) {
- if (this._chipfamily == ESP8266) {
- logMsg("Baud rate can only change on ESP32 and ESP32-S2");
- } else {
- logMsg("Attempting to change baud rate to " + baud + "...");
- try {
- let buffer = this.pack("]/, '').length != data.length) {
- errorMsg("Pack format to Argument count mismatch");
- return;
- }
- let bytes = [];
- let littleEndian = true;
- for (let i = 0; i < format.length; i++) {
- if (format[i] == "<") {
- littleEndian = true;
- } else if (format[i] == ">") {
- littleEndian = false;
- } else if (format[i] == "B") {
- pushBytes(data[pointer], 1);
- pointer++;
- } else if (format[i] == "H") {
- pushBytes(data[pointer], 2);
- pointer++;
- } else if (format[i] == "I") {
- pushBytes(data[pointer], 4);
- pointer++;
- } else {
- errorMsg("Unhandled character in pack format");
- }
- }
-
- function pushBytes(value, byteCount) {
- for (let i = 0; i < byteCount; i++) {
- if (littleEndian) {
- bytes.push((value >> (i * 8)) & 0xFF);
- } else {
- bytes.push((value >> ((byteCount - i) * 8)) & 0xFF);
- }
- }
- }
-
- return bytes;
- };
-
- unpack(format, bytes) {
- let pointer = 0;
- let data = [];
- for (let c of format) {
- if (c == "B") {
- data.push((bytes[pointer] & 0xFF));
- pointer += 1;
- } else if (c == "H") {
- data.push(
- (bytes[pointer] & 0xFF) |
- ((bytes[pointer + 1] & 0xFF) << 8)
- );
- pointer += 2;
- } else if (c == "I") {
- data.push(
- (bytes[pointer] & 0xFF) |
- ((bytes[pointer + 1] & 0xFF) << 8) |
- ((bytes[pointer + 2] & 0xFF) << 16) |
- ((bytes[pointer + 3] & 0xFF) << 24))
- pointer += 4;
- } else {
- errorMsg("Unhandled character in unpack format");
- }
- }
- return data;
- };
-
- /**
- * @name sync
- * Put into ROM bootload mode & attempt to synchronize with the
- * ESP ROM bootloader, we will retry a few times
- */
- async sync() {
- for (let i = 0; i < 5; i++) {
- let response = await this._sync();
- if (response) {
- await sleep(100);
- return true;
- }
- await sleep(100);
- }
-
- throw("Couldn't sync to ESP. Try resetting.");
- };
-
- /**
- * @name _sync
- * Perform a soft-sync using AT sync packets, does not perform
- * any hardware resetting
- */
- async _sync() {
- await this.sendCommand(ESP_SYNC, SYNC_PACKET);
- for (let i = 0; i < 8; i++) {
- let [reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
- if (data === null) {
- continue;
- }
- if (data.length > 1 && data[0] == 0 && data[1] == 0) {
- return true;
- }
- }
- return false;
- };
-
- /**
- * @name getFlashWriteSize
- * Get the Flash write size based on the chip
- */
- getFlashWriteSize() {
- if (this._chipfamily == ESP32S2) {
- return ESP32S2_FLASH_WRITE_SIZE;
- }
- return FLASH_WRITE_SIZE;
- };
-
- /**
- * @name flashData
- * Program a full, uncompressed binary file into SPI Flash at
- * a given offset. If an ESP32 and md5 string is passed in, will also
- * verify memory. ESP8266 does not have checksum memory verification in
- * ROM
- */
- async flashData(binaryData, offset=0, part=0) {
-
- let filesize = binaryData.byteLength;
- logMsg("\nWriting data with filesize:" + filesize);
- let blocks = await this.flashBegin(filesize, offset);
- let block = [];
- let seq = 0;
- let written = 0;
- let address = offset;
- let position = 0;
- let stamp = Date.now();
- let flashWriteSize = this.getFlashWriteSize();
- let progressBar = progress[part].querySelector("div");
-
- while (filesize - position > 0) {
- let percentage = Math.floor(100 * (seq + 1) / blocks);
- /*logMsg(
- "Writing at " + toHex(address + seq * flashWriteSize, 8) + "... (" + percentage + " %)"
- );*/
- progressBar.style.width = percentage + "%";
- if (filesize - position >= flashWriteSize) {
- block = Array.from(new Uint8Array(binaryData, position, flashWriteSize));
- } else {
- // Pad the last block
- block = Array.from(new Uint8Array(binaryData, position, filesize - position));
- block = block.concat(new Array(flashWriteSize - block.length).fill(0xFF));
- }
- await this.flashBlock(block, seq, 2000);
- seq += 1;
- written += block.length;
- position += flashWriteSize;
- }
- logMsg("Took " + (Date.now() - stamp) + "ms to write " + filesize + " bytes");
- };
-
- /**
- * @name flashBlock
- * Send one block of data to program into SPI Flash memory
- */
- async flashBlock(data, seq, timeout=100) {
- await this.checkCommand(
- ESP_FLASH_DATA,
- this.pack(" start) {
- throw("Software loader is resident at " + toHex(start, 8) + "-" + toHex(end, 8) + ". " +
- "Can't load binary at overlapping address range " + toHex(load_start, 8) + "-" + toHex(load_end, 8) + ". " +
- "Try changing the binary loading address.");
- }
- }
- }
-
- return this.checkCommand(ESP_MEM_BEGIN, this.pack(' length) {
- toOffs = length;
- }
- await this.memBlock(stub[field].slice(fromOffs, toOffs), seq);
- }
- }
- }
- logMsg("Running stub...")
- await this.memFinish(stub['entry']);
-
- let p = await this.readBuffer(100);
- p = String.fromCharCode(...p);
-
- if (p != 'OHAI') {
- throw "Failed to start stub. Unexpected response: " + p;
- }
- logMsg("Stub is now running...");
- return new EspStubLoader();
- }
-}
-
-class EspStubLoader extends EspLoader {
- /*
- The Stubloader has commands that run on the uploaded Stub Code in RAM
- rather than built in commands.
- */
- constructor() {
- super();
- this.IS_STUB = true;
- }
- /**
- * @name getEraseSize
- * depending on flash chip model the erase may take this long (maybe longer!)
- */
- async eraseFlash() {
- await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
- };
-}