mirror of
				https://github.com/alexandrebobkov/ESP-Nodes.git
				synced 2025-10-31 01:58:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1360 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
|         }
 | |
|     }
 | |
| }
 |