From 7c847349e2296be229f6477950955958d6a43cfb Mon Sep 17 00:00:00 2001 From: mrfrase3 Date: Sat, 23 Jul 2022 10:54:52 +0800 Subject: [PATCH 1/6] initial esp loader re-write --- package.json | 2 + src/esp/ESPLoader.ts | 1016 ++++++++++++++++++++++++++++++++++ src/esp/StubLoader.ts | 43 ++ src/esp/roms/esp32.ts | 207 +++++++ src/esp/roms/esp32c3.ts | 113 ++++ src/esp/roms/esp32s2.ts | 117 ++++ src/esp/roms/esp32s3beta2.ts | 31 ++ src/esp/roms/esp8266.ts | 156 ++++++ src/esp/stubs/esp32.json | 1 + src/esp/stubs/esp32c3.json | 1 + src/esp/stubs/esp32h2.json | 1 + src/esp/stubs/esp32s2.json | 1 + src/esp/stubs/esp32s3.json | 1 + src/esp/stubs/esp8266.json | 1 + yarn.lock | 10 + 15 files changed, 1701 insertions(+) create mode 100644 src/esp/ESPLoader.ts create mode 100644 src/esp/StubLoader.ts create mode 100644 src/esp/roms/esp32.ts create mode 100644 src/esp/roms/esp32c3.ts create mode 100644 src/esp/roms/esp32s2.ts create mode 100644 src/esp/roms/esp32s3beta2.ts create mode 100644 src/esp/roms/esp8266.ts create mode 100644 src/esp/stubs/esp32.json create mode 100644 src/esp/stubs/esp32c3.json create mode 100644 src/esp/stubs/esp32h2.json create mode 100644 src/esp/stubs/esp32s2.json create mode 100644 src/esp/stubs/esp32s3.json create mode 100644 src/esp/stubs/esp8266.json diff --git a/package.json b/package.json index 117dc7c..a25f0ed 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,13 @@ "@typescript-eslint/parser": "^5.23.0", "axios": "^0.27.2", "chai": "^4.3.6", + "crypto-js": "^4.1.1", "eslint": "^8.15.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-plugin-import": "^2.26.0", "mocha": "^10.0.0", + "pako": "^2.0.4", "serialport": "^10.4.0", "ts-node": "^10.8.0", "typescript": "^4.6.4", diff --git a/src/esp/ESPLoader.ts b/src/esp/ESPLoader.ts new file mode 100644 index 0000000..18f5bcc --- /dev/null +++ b/src/esp/ESPLoader.ts @@ -0,0 +1,1016 @@ +import { SerialPort } from 'serialport/dist/index.d'; +import pako from 'pako'; +import CryptoJS from 'crypto-js'; +import StubLoader from './StubLoader'; + +interface ESPOptions { + quiet?: boolean; + stubUrl?: string; + stdout?: any; +} + +export default class ESPLoader { + ESP_RAM_BLOCK = 0x1800; + + ESP_FLASH_BEGIN = 0x02; + + ESP_FLASH_DATA = 0x03; + + ESP_FLASH_END = 0x04; + + ESP_MEMBEGIN = 0x05; + + ESP_MEM_END = 0x06; + + ESP_MEM_DATA = 0x07; + + ESP_WRITE_REG = 0x09; + + ESP_FLASH_DEFL_BEGIN = 0x10; + + ESP_FLASH_DEFL_DATA = 0x11; + + ESP_FLASH_DEFL_END = 0x12; + + ESP_SPI_FLASH_MD5 = 0x13; + + ESP_READ_REG = 0x0A; + + ESP_SPI_ATTACH = 0x0D; + + // Only Stub supported commands + ESP_ERASE_FLASH = 0xD0; + + ESP_ERASE_REGION = 0xD1; + + ESP_IMAGE_MAGIC = 0xe9; + + ESP_CHECKSUM_MAGIC = 0xef; + + ERASE_REGION_TIMEOUT_PER_MB = 30000; + + ERASE_WRITE_TIMEOUT_PER_MB = 40000; + + MD5_TIMEOUT_PER_MB = 8000; + + CHIP_ERASE_TIMEOUT = 120000; + + MAX_TIMEOUT = this.CHIP_ERASE_TIMEOUT * 2; + + CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000; + + DETECTED_FLASH_SIZES = { + 0x12: '256KB', 0x13: '512KB', 0x14: '1MB', 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB', + }; + + opts: ESPOptions; + quiet: boolean; + serial: SerialPort; + IS_STUB: boolean; + chip: ROMDef | null; + stdout: any; + stubLoader: StubLoader; + syncStubDetected: boolean; + + constructor(serial: SerialPort, opts: ESPOptions) { + this.opts = opts || {}; + this.quiet = this.opts.quiet || false; + this.serial = serial; + this.IS_STUB = false; + this.chip = null; + this.stdout = opts.stdout || process?.stdout || { + write: (str: string) => console.log(str), + }; + this.stubLoader = new StubLoader(this.opts.stubUrl); + this.syncStubDetected = false; + } + + #sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + log (...args: any[]) { + if (this.quiet) return; + this.stdout.write(`${args.map(arg => `${arg}`).join(' ')}\n`); + } + + logChar(str: string) { + if (this.stdout) { + this.stdout.write(str); + } else { + // eslint-disable-next-line no-console + console.log(str); + } + } + + #shortToByteArray(i: number) { + return new Uint8Array([i & 0xff, (i >> 8) & 0xff]); + } + + #intToByteArray(i: number) { + return new Uint8Array([i & 0xff, (i >> 8) & 0xff, (i >> 16) & 0xff, (i >> 24) & 0xff]); + } + + #byteArrayToShort(arr: [number, number]) { + const [i, j] = arr; + return (new Uint16Array([(i | (j >> 8))]))[0]; + } + + #byteArrayToInt(arr: [number, number, number, number]) { + const [i, j, k, l] = arr; + return (new Uint32Array([(i | (j << 8) | (k << 16) | (l << 24))]))[0]; + } + + #appendArray(arr1: Uint8Array, arr2: Uint8Array) { + const c = new Uint8Array(arr1.length + arr2.length); + c.set(arr1, 0); + c.set(arr2, arr1.length); + return c; + } + + #ui8ToBstr(u8Array: Uint8Array) { + let i; + const len = u8Array.length; + let b_str = ''; + for (i = 0; i < len; i++) { + b_str += String.fromCharCode(u8Array[i]); + } + return b_str; + } + + #bstrToUi8(bStr: string) { + const len = bStr.length; + const u8_array = new Uint8Array(len); + for (let i = 0; i < len; i++) { + u8_array[i] = bStr.charCodeAt(i); + } + return u8_array; + } + + #flushInput = async () => { + try { + await this.serial.flush(); + } catch (e) {} + } + + // convert data before sending to https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol + async write(data: Buffer) { + const slipped_arr = []; + for (let i = 0; i < data.length; i++) { + if (i === 0xC0) slipped_arr.push(0xDB, 0xDC); // escape the end char + else if (i === 0xDB) slipped_arr.push(0xDB, 0xDD); // escape the escape char + else slipped_arr.push(data[i]); + } + const pkt = Buffer.from([ + 0xC0, + ...slipped_arr, + 0xC0, + ]); + return this.serial.write(pkt); + } + + read(timeout = 0, flush = false): Promise { + return new Promise((resolve, reject) => { + let buffer = Buffer.alloc(0); + let started = false; + let timeoutId = null as NodeJS.Timeout | null; + let handleChunk = (data: Buffer) => {}; + const finished = (err?: Error) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + this.serial.removeListener('data', handleChunk); + if (err) { + if (flush && buffer.length) { + resolve(buffer); + } else { + reject(err); + } + } else { + resolve(buffer); + } + }; + handleChunk = (data: Buffer) => { + if (flush) { + Buffer.concat([buffer, data]); + return; + } + const pkt = [] as number[]; + let inEscape = false; + for (let i = 0; i < data.length; i++) { + const byte = data[i]; + if (started) { + if (byte === 0xC0) { + started = false; + break; + } else if (byte === 0xDC && inEscape) { + pkt.push(0xC0); + inEscape = false; + } else if (byte === 0xDD && inEscape) { + pkt.push(0xDB); + inEscape = false; + } else if (byte === 0xDB) { + inEscape = true; + } else { + pkt.push(byte); + inEscape = false; + } + } else if (byte === 0xC0) { + started = true; + } + } + if (pkt.length) buffer = Buffer.concat([buffer, new Uint8Array(pkt)]); + if (buffer.length && !started) { + finished(); + } + }; + if (timeout && timeout > 0) { + timeoutId = setTimeout(() => { + timeoutId = null; + finished(new Error(`receiveData timeout after ${timeout}ms`)); + }, timeout); + } + this.serial.on('data', handleChunk); + }); + } + + command = async ({ + op = null as number | null, + data = [] as number[] | Uint8Array | Buffer, + chk = 0, + waitResponse = true, + timeout = 3000, + // min_data = 12, + } = {}): Promise<[number, Buffer]> => { + // console.log("command "+ op + " " + wait_response + " " + timeout); + if (op) { + const pkt = Buffer.from([ + 0x00, + op, + ...this.#shortToByteArray(data.length), // 2-3 + ...this.#intToByteArray(chk), // 4-7 + ...data, // 8+ + ]); + // console.log("Command " + pkt); + await this.serial.write(pkt); + } + + if (waitResponse) { + const p = await this.read(timeout); + // console.log(this.transport.slip_reader_enabled, p); + // const resp = p[0]; + const op_ret = p[1]; + // const len_ret = this._bytearray_to_short(p[2], p[3]); + const val = this.#byteArrayToInt([p[4], p[5], p[6], p[7]]); + // eslint-disable-next-line no-console + // console.log(`Resp ${resp} ${op_ret} ${op} ${len_ret} ${val} ${p}`); + const datum = p.slice(8); + if (!op || op_ret === op) { + return [val, datum]; + } + throw new Error(`Invalid response. Expected ${op.toString(16)}`); + } + return [0, Buffer.from([])]; + } + + readReg = async (addr: number, timeout = 3000) => { + // console.log(`read reg ${addr} ${timeout}`); + const pkt = this.#intToByteArray(addr); + const val = await this.command({ op: this.ESP_READ_REG, data: pkt, timeout }); + // console.log('Read reg resp', val); + return val[0]; + } + + writeReg = async (addr: number, value: number, mask = 0xFFFFFFFF, delay_us = 0, delay_after_us = 0) => { + let pkt = this.#appendArray(this.#intToByteArray(addr), this.#intToByteArray(value)); + pkt = this.#appendArray(pkt, this.#intToByteArray(mask)); + pkt = this.#appendArray(pkt, this.#intToByteArray(delay_us)); + + if (delay_after_us > 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(delay_after_us)); + } + + await this.checkCommand({ op_description: 'write target memory', op: this.ESP_WRITE_REG, data: pkt }); + } + + sync = async () => { + // console.log('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; + } + + const resp = await this.command({ op: 0x08, data: cmd, timeout: 100 }); + this.syncStubDetected = resp[0] === 0; + return resp; + } + + #connectAttempt = async ({ mode = 'default_reset', esp32r0_delay = false } = {}) => { + // console.log(`_connect_attempt ${esp32r0_delay}`); + if (mode !== 'no_reset') { + await this.serial.set({ dtr: false, rts: true }); + await this.#sleep(100); + if (esp32r0_delay) { + // await this._sleep(1200); + await this.#sleep(2000); + } + await this.serial.set({ dtr: true, rts: false }); + if (esp32r0_delay) { + // await this._sleep(400); + } + await this.#sleep(50); + await this.serial.set({ dtr: false }); + } + let i = 0; + // eslint-disable-next-line no-constant-condition + while (1) { + try { + const res = await this.read(1000, true); + i += res.length; + // console.log("Len = " + res.length); + // var str = new TextDecoder().decode(res); + // this.log(str); + } catch (err) { + if (err instanceof Error && err.message.includes('timeout')) { + break; + } + } + await this.#sleep(50); + } + // this.transport.slip_reader_enabled = true; + i = 7; + while (i--) { + try { + await this.sync(); + return 'success'; + } catch (err) { + if (err instanceof Error && err.message.includes('timeout')) { + this.logChar(esp32r0_delay ? '_' : '.'); + } + } + await this.#sleep(50); + } + return 'error'; + } + + // eslint-disable-next-line no-unused-vars + async connect({ mode = 'default_reset', attempts = 7, detecting = false } = {}) { + let resp = ''; + this.logChar('Connecting...'); + // await this.transport.connect(); + let success = false; + await (new Array(attempts)).fill(0).reduce(async (promise) => { + await promise; + if (resp === 'success') return; + resp = await this.#connectAttempt({ esp32r0_delay: false }); + if (resp === 'success') return; + resp = await this.#connectAttempt({ esp32r0_delay: true }); + if (resp === 'success') return; + }, Promise.resolve()); + if (resp !== 'success') { + this.log('Failed to connect with the device'); + return 'error'; + } + this.logChar('\n'); + this.logChar('\r'); + await this.#sleep(100); + await this.#flushInput(); + + if (!detecting) { + const chip_magic_value = await this.readReg(0x40001000); + // eslint-disable-next-line no-console + // console.log(`Chip Magic ${chip_magic_value}`); + const chips = [ESP8266ROM, ESP32ROM, ESP32S2ROM, ESP32S3BETA2ROM, ESP32C3ROM]; + this.chip = chips.find((cls) => chip_magic_value === cls.CHIP_DETECT_MAGIC_VALUE); + // console.log('chip', this.chip); + } + return null; + } + + async detectChip() { + await this.connect(); + this.logChar('Detecting chip type... '); + if (this.chip != null) { + this.log(this.chip.CHIP_NAME); + } + } + + async checkCommand({ + // eslint-disable-next-line no-unused-vars + op_description = '', + op = null as number | null, + data = [] as number[] | Uint8Array | Buffer, + chk = 0, + timeout = 3000, + /* min_data, */ + } = {}) { + // console.log(`checkCommand ${op}`); + const resp = await this.command({ + op, data, chk, timeout, /* min_data, */ + }); + if (resp[1].length > 4) { + return resp[1]; + } + return resp[0]; + } + + async memBegin(size: number, blocks: number, blocksize: number, offset: number) { + /* XXX: Add check to ensure that STUB is not getting overwritten */ + // console.log(`memBegin ${size} ${blocks} ${blocksize} ${offset}`); + 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({ op_description: 'write to target RAM', op: this.ESP_MEMBEGIN, data: pkt }); + } + + checksum(data: number[] | Uint8Array | Buffer) { + let i; + let chk = 0xEF; + + for (i = 0; i < data.length; i++) { + chk ^= data[i]; + } + return chk; + } + + async memBlock(buffer: Uint8Array, seq: number) { + 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({ + op_description: 'write to target RAM', op: this.ESP_MEM_DATA, data: pkt, chk: checksum, + }); + } + + mem_finish = async (entrypoint: number) => { + const is_entry = (entrypoint === 0) ? 1 : 0; + const pkt = this.#appendArray(this.#intToByteArray(is_entry), this.#intToByteArray(entrypoint)); + return this.checkCommand({ + op_description: 'leave RAM download mode', op: this.ESP_MEM_END, data: pkt, timeout: 500, min_data: 12, + }); // XXX: handle non-stub with diff timeout + } + + flash_spi_attach = async (hspi_arg) => { + const pkt = this.#intToByteArray(hspi_arg); + await this.checkCommand({ op_description: 'configure SPI flash pins', op: this.ESP_SPI_ATTACH, data: pkt }); + } + + timeout_per_mb = (seconds_per_mb, size_bytes) => { + const result = seconds_per_mb * (size_bytes / 1000000); + if (result < 3000) { + return 3000; + } + return result; + } + + flash_begin = async (size, offset) => { + const num_blocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); + const erase_size = this.chip.get_erase_size(offset, size); + + const d = new Date(); + const t1 = d.getTime(); + + let timeout = 3000; + if (this.IS_STUB === false) { + timeout = this.timeout_per_mb(this.ERASE_REGION_TIMEOUT_PER_MB, size); + } + + // eslint-disable-next-line no-console + // console.log(`flash begin ${erase_size} ${num_blocks} ${this.FLASH_WRITE_SIZE} ${offset} ${size}`); + let pkt = this.#appendArray(this.#intToByteArray(erase_size), this.#intToByteArray(num_blocks)); + 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({ + op_description: 'enter Flash download mode', op: this.ESP_FLASH_BEGIN, data: pkt, timeout, + }); + + const t2 = d.getTime(); + if (size !== 0 && this.IS_STUB === false) { + this.log(`Took ${(t2 - t1) / 1000}.${(t2 - t1) % 1000}s to erase flash block`); + } + return num_blocks; + } + + flash_defl_begin = async (size, compsize, offset) => { + const num_blocks = Math.floor((compsize + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); + const erase_blocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); + + const d = new Date(); + const t1 = d.getTime(); + + let write_size; let + timeout; + if (this.IS_STUB) { + write_size = size; + timeout = 3000; + } else { + write_size = erase_blocks * this.FLASH_WRITE_SIZE; + timeout = this.timeout_per_mb(this.ERASE_REGION_TIMEOUT_PER_MB, write_size); + } + this.log(`Compressed ${size} bytes to ${compsize}...`); + + let pkt = this.#appendArray(this.#intToByteArray(write_size), this.#intToByteArray(num_blocks)); + 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.IS_STUB === false) + ) { + pkt = this.#appendArray(pkt, this.#intToByteArray(0)); + } + if (this.chip.CHIP_NAME === 'ESP8266') { + await this.flush_input(); + } + await this.checkCommand({ + op_description: 'enter compressed flash mode', op: this.ESP_FLASH_DEFL_BEGIN, data: pkt, timeout, + }); + const t2 = d.getTime(); + if (size !== 0 && this.IS_STUB === false) { + this.log(`Took ${(t2 - t1) / 1000}.${(t2 - t1) % 1000}s to erase flash block`); + } + return num_blocks; + } + + flash_block = async (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({ + op_description: `write to target Flash after seq ${seq}`, op: this.ESP_FLASH_DATA, data: pkt, chk: checksum, timeout, + }); + } + + flash_defl_block = async (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); + // console.log(`flash_defl_block ${data[0].toString(16)}`, +' ' + data[1].toString(16)); + + await this.checkCommand({ + op_description: `write compressed data to flash after seq ${seq}`, + op: this.ESP_FLASH_DEFL_DATA, + data: pkt, + chk: checksum, + timeout, + }); + } + + flash_finish = async ({ reboot = false } = {}) => { + const val = reboot ? 0 : 1; + const pkt = this.#intToByteArray(val); + + await this.checkCommand({ op_description: 'leave Flash mode', op: this.ESP_FLASH_END, data: pkt }); + } + + flash_defl_finish = async ({ reboot = false } = {}) => { + const val = reboot ? 0 : 1; + const pkt = this.#intToByteArray(val); + + await this.checkCommand({ op_description: 'leave compressed flash mode', op: this.ESP_FLASH_DEFL_END, data: pkt }); + } + + run_spiflash_command = async (spiflash_command, data, read_bits) => { + // 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 set_data_lengths; + if (this.chip.SPI_MOSI_DLEN_OFFS != null) { + set_data_lengths = async (mosi_bits, miso_bits) => { + 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 (mosi_bits > 0) { + await this.write_reg({ addr: SPI_MOSI_DLEN_REG, value: (mosi_bits - 1) }); + } + if (miso_bits > 0) { + await this.write_reg({ addr: SPI_MISO_DLEN_REG, value: (miso_bits - 1) }); + } + }; + } else { + set_data_lengths = async (mosi_bits, miso_bits) => { + const SPI_DATA_LEN_REG = SPI_USR1_REG; + const SPI_MOSI_BITLEN_S = 17; + const SPI_MISO_BITLEN_S = 8; + const mosi_mask = (mosi_bits === 0) ? 0 : (mosi_bits - 1); + const miso_mask = (miso_bits === 0) ? 0 : (miso_bits - 1); + const val = (miso_mask << SPI_MISO_BITLEN_S) | (mosi_mask << SPI_MOSI_BITLEN_S); + await this.write_reg({ addr: SPI_DATA_LEN_REG, value: val }); + }; + } + + const SPI_CMD_USR = (1 << 18); + const SPI_USR2_COMMAND_LEN_SHIFT = 28; + if (read_bits > 32) { + throw 'Reading more than 32 bits back from a SPI flash operation is unsupported'; + } + if (data.length > 64) { + throw 'Writing more than 64 bytes of data with one SPI command is unsupported'; + } + + const data_bits = data.length * 8; + const old_spi_usr = await this.read_reg({ addr: SPI_USR_REG }); + const old_spi_usr2 = await this.read_reg({ addr: SPI_USR2_REG }); + let flags = SPI_USR_COMMAND; + let i; + if (read_bits > 0) { + flags |= SPI_USR_MISO; + } + if (data_bits > 0) { + flags |= SPI_USR_MOSI; + } + await set_data_lengths(data_bits, read_bits); + await this.write_reg({ addr: SPI_USR_REG, value: flags }); + let val = (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command; + await this.write_reg({ addr: SPI_USR2_REG, value: val }); + if (data_bits === 0) { + await this.write_reg({ addr: SPI_W0_REG, value: 0 }); + } else { + if (data.length % 4 !== 0) { + const padding = new Uint8Array(data.length % 4); + // eslint-disable-next-line no-param-reassign + data = this.#appendArray(data, padding); + } + let next_reg = 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.write_reg({ addr: next_reg, value: val }); + next_reg += 4; + } + } + await this.write_reg({ addr: SPI_CMD_REG, value: SPI_CMD_USR }); + for (i = 0; i < 10; i++) { + val = await this.read_reg({ addr: SPI_CMD_REG }) & SPI_CMD_USR; + if (val === 0) { + break; + } + } + if (i === 10) { + throw 'SPI command did not complete in time'; + } + const stat = await this.read_reg({ addr: SPI_W0_REG }); + await this.write_reg({ addr: SPI_USR_REG, value: old_spi_usr }); + await this.write_reg({ addr: SPI_USR2_REG, value: old_spi_usr2 }); + return stat; + } + + read_flash_id = async () => { + const SPIFLASH_RDID = 0x9F; + const pkt = new Uint8Array(0); + return this.run_spiflash_command(SPIFLASH_RDID, pkt, 24); + } + + erase_flash = async () => { + this.log('Erasing flash (this may take a while)...'); + let d = new Date(); + const t1 = d.getTime(); + const ret = await this.checkCommand({ + op_description: 'erase flash', + op: this.ESP_ERASE_FLASH, + timeout: this.CHIP_ERASE_TIMEOUT, + }); + d = new Date(); + const t2 = d.getTime(); + this.log(`Chip erase completed successfully in ${(t2 - t1) / 1000}s`); + return ret; + } + + toHex(buffer) { + return Array.prototype.map.call(buffer, (x) => (`00${x.toString(16)}`).slice(-2)).join(''); + } + + flash_md5sum = async (addr, size) => { + const timeout = this.timeout_per_mb(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({ + op_description: 'calculate md5sum', op: this.ESP_SPI_FLASH_MD5, data: pkt, timeout, min_data: 26, + }); + if (res.length > 16) { + res = res.slice(0, 16); + } + const strmd5 = this.toHex(res); + return strmd5; + } + + run_stub = async () => { + this.log('Fetching stub...'); + + const stub = await this._loadStub(); + // console.log(stub); + const { + data, text, data_start, text_start, entry, + } = stub; + + this.log('Uploading stub...'); + + let blocks = Math.floor((text.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); + let i; + + await this.memBegin(text.length, blocks, this.ESP_RAM_BLOCK, text_start); + for (i = 0; i < blocks; i++) { + const from_offs = i * this.ESP_RAM_BLOCK; + let to_offs = from_offs + this.ESP_RAM_BLOCK; + if (to_offs > text.length) to_offs = text.length; + await this.memBlock(text.slice(from_offs, to_offs), i); + } + + blocks = Math.floor((data.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); + await this.memBegin(data.length, blocks, this.ESP_RAM_BLOCK, data_start); + for (i = 0; i < blocks; i++) { + const from_offs = i * this.ESP_RAM_BLOCK; + let to_offs = from_offs + this.ESP_RAM_BLOCK; + if (to_offs > data.length) to_offs = data.length; + await this.memBlock(data.slice(from_offs, to_offs), i); + } + + this.log('Running stub...'); + let valid = false; + await this.mem_finish(entry); + + if (this.chip.CHIP_NAME === 'ESP8266') { + const [reply] = await this.sync(); + if (reply === 0) valid = true; + } else { + const res = await this.transport.read({ timeout: 1000, min_data: 6 }); + if (res[0] === 79 && res[1] === 72 && res[2] === 65 && res[3] === 73) { + valid = true; + } + } + + if (valid) { + this.log('Stub running...'); + this.IS_STUB = true; + this.FLASH_WRITE_SIZE = 0x4000; + return this.chip; + } + this.log('Failed to start stub. Unexpected response'); + return null; + } + + main_fn = async () => { + await this.detect_chip(); + if (this.chip == null) { + this.log('Error in connecting to board'); + return; + } + + const chip = await this.chip.get_chip_description(this); + this.log(`Chip is ${chip}`); + this.log(`Features: ${await this.chip.get_chip_features(this)}`); + this.log(`Crystal is ${await this.chip.get_crystal_freq(this)}MHz`); + this.log(`MAC: ${await this.chip.read_mac(this)}`); + await this.chip.read_mac(this); + + if (this.chip.IS_STUB) await this.run_stub(); + else this.FLASH_WRITE_SIZE = this.chip.FLASH_WRITE_SIZE || 0x4000; + } + + flash_size_bytes = (flash_size) => { + let flash_size_b = -1; + if (flash_size.indexOf('KB') !== -1) { + flash_size_b = parseInt(flash_size.slice(0, flash_size.indexOf('KB')), 10) * 1024; + } else if (flash_size.indexOf('MB') !== -1) { + flash_size_b = parseInt(flash_size.slice(0, flash_size.indexOf('MB')), 10) * 1024 * 1024; + } + return flash_size_b; + } + + pad_array = (arr, len, fillValue) => Object.assign(new Array(len).fill(fillValue), arr) + + parse_flash_size_arg = (flsz) => { + if (typeof this.chip.FLASH_SIZES[flsz] === 'undefined') { + this.log(`Flash size ${flsz} is not supported by this chip type. Supported sizes: ${this.chip.FLASH_SIZES}`); + throw 'Invalid flash size'; + } + return this.chip.FLASH_SIZES[flsz]; + } + + _update_image_flash_params = (image, address, flash_size, flash_mode, flash_freq) => { + // console.log(`_update_image_flash_params ${flash_size} ${flash_mode} ${flash_freq}`); + if (image.length < 8) { + return image; + } + if (address !== this.chip.BOOTLOADER_FLASH_OFFSET) { + return image; + } + if (flash_size === 'keep' && flash_mode === 'keep' && flash_freq === 'keep') { + // console.log('Not changing the image'); + return image; + } + + const magic = image[0]; + let a_flash_mode = image[2]; + const flash_size_freq = image[3]; + if (magic !== this.ESP_IMAGE_MAGIC) { + this.log(`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 (flash_mode !== 'keep') { + const flash_modes = { + qio: 0, qout: 1, dio: 2, dout: 3, + }; + a_flash_mode = flash_modes[flash_mode]; + } + let a_flash_freq = flash_size_freq & 0x0F; + if (flash_freq !== 'keep') { + const flash_freqs = { + '40m': 0, '26m': 1, '20m': 2, '80m': 0xf, + }; + a_flash_freq = flash_freqs[flash_freq]; + } + let a_flash_size = flash_size_freq & 0xF0; + if (flash_size !== 'keep') { + a_flash_size = this.parse_flash_size_arg(flash_size); + } + + const flash_params = (a_flash_mode << 8) | (a_flash_freq + a_flash_size); + this.log(`Flash params set to ${flash_params.toString(16)}`); + if (image[2] !== (a_flash_mode << 8)) { + // eslint-disable-next-line no-param-reassign + image[2] = (a_flash_mode << 8); + } + if (image[3] !== (a_flash_freq + a_flash_size)) { + // eslint-disable-next-line no-param-reassign + image[3] = (a_flash_freq + a_flash_size); + } + return image; + } + + write_flash = async ({ + fileArray = [], flash_size = 'keep', flash_mode = 'keep', flash_freq = 'keep', erase_all = false, compress = true, + } = {}) => { + // console.log('EspLoader program'); + if (flash_size !== 'keep') { + const flash_end = this.flash_size_bytes(flash_size); + for (let i = 0; i < fileArray.length; i++) { + if ((fileArray[i].data.length + fileArray[i].address) > flash_end) { + this.log("Specified file doesn't fit in the available flash"); + return; + } + } + } + + if (this.IS_STUB === true && erase_all === true) { + this.erase_flash(); + } + let image; + let address; + for (let i = 0; i < fileArray.length; i++) { + // console.log(`Data Length ${fileArray[i].data.length}`); + // image = this.pad_array(fileArray[i].data, Math.floor((fileArray[i].data.length + 3)/4) * 4, 0xff); + // XXX : handle padding + image = fileArray[i].data; + address = fileArray[i].address; + // console.log(`Image Length ${image.length}`); + if (image.length === 0) { + this.log('Warning: File is empty'); + // eslint-disable-next-line no-continue + continue; + } + image = this._update_image_flash_params(image, address, flash_size, flash_mode, flash_freq); + const calcmd5 = CryptoJS.MD5(CryptoJS.enc.Base64.parse(image.toString('base64'))); + // console.log(`Image MD5 ${calcmd5}`); + const uncsize = image.length; + let blocks; + // console.log(image); + if (compress) { + // const uncimage = this.bstrToUi8(image); + image = pako.deflate(image, { level: 9 }); + // console.log('Compressed image '); + // console.log(image); + blocks = await this.flash_defl_begin(uncsize, image.length, address); + } else { + blocks = await this.flash_begin(uncsize, address); + } + let seq = 0; + let bytes_sent = 0; + // const bytes_written = 0; + + let d = new Date(); + const t1 = d.getTime(); + + let timeout = 5000; + while (image.length > 0) { + // console.log(`Write loop ${address} ${seq} ${blocks}`); + this.write_char(`\rWriting at 0x${ + (address + (seq * this.FLASH_WRITE_SIZE)).toString(16) + }... (${ + Math.floor(100 * ((seq + 1) / blocks)) + }%)`); + let block = image.slice(0, this.FLASH_WRITE_SIZE); + if (compress) { + /* + let block_uncompressed = pako.inflate(block).length; + //let len_uncompressed = block_uncompressed.length; + bytes_written += block_uncompressed; + if (this.timeout_per_mb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed) > 3000) { + block_timeout = this.timeout_per_mb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed); + } else { + block_timeout = 3000; + } */ // XXX: Partial block inflate seems to be unsupported in Pako. Hardcoding timeout + const block_timeout = 5000; + if (this.IS_STUB === false) { + timeout = block_timeout; + } + await this.flash_defl_block(block, seq, timeout); + if (this.IS_STUB) { + timeout = block_timeout; + } + } else { + // this.log('Yet to handle Non Compressed writes'); + // block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) + if (block.length < this.FLASH_WRITE_SIZE) { + const existingBlock = block.toString('base64'); + block = Buffer.alloc(this.FLASH_WRITE_SIZE, 0xff); + block.write(existingBlock, 'base64'); + } + // if encrypted: + // esp.flash_encrypt_block(block, seq) + // else: + // esp.flash_block(block, seq) + // bytes_written += len(block) + await this.flash_block(block, seq, timeout); + } + bytes_sent += block.length; + image = image.slice(this.FLASH_WRITE_SIZE, image.length); + seq++; + } + if (this.IS_STUB) { + await this.read_reg({ addr: this.CHIP_DETECT_MAGIC_REG_ADDR, timeout }); + } + d = new Date(); + const t = d.getTime() - t1; + this.log(''); + this.log(`Wrote ${uncsize} bytes${ + compress ? ` (${bytes_sent} compressed)` : '' + } at 0x${address.toString(16)} in ${t / 1000} seconds.`); + this._sleep(100); + if (this.IS_STUB || this.chip.CHIP_NAME !== 'ESP8266') { + const res = await this.flash_md5sum(address, uncsize); + if (`${res}` !== `${calcmd5}`) { + this.log(`File md5: ${calcmd5}`); + this.log(`Flash md5: ${res}`); + } else { + this.log('Hash of data verified.'); + } + } + } + this.log('Leaving...'); + + if (this.IS_STUB) { + await this.flash_begin(0, 0); + if (compress) { + await this.flash_defl_finish(); + } else { + await this.flash_finish(); + } + } + } + + flash_id = async () => { + // console.log('flash_id'); + const flashid = await this.read_flash_id(); + this.log(`Manufacturer: ${(flashid & 0xff).toString(16)}`); + const flid_lowbyte = (flashid >> 16) & 0xff; + this.log(`Device: ${((flashid >> 8) & 0xff).toString(16)}${flid_lowbyte.toString(16)}`); + this.log(`Detected flash size: ${this.DETECTED_FLASH_SIZES[flid_lowbyte] || 'Unknown'}`); + } +} diff --git a/src/esp/StubLoader.ts b/src/esp/StubLoader.ts new file mode 100644 index 0000000..404415b --- /dev/null +++ b/src/esp/StubLoader.ts @@ -0,0 +1,43 @@ +import axios from 'axios'; + +interface StubDef { + data: Buffer; + text: Buffer; + entry: number; + text_start: number; + data_start: number; +} + +interface StubCache { + [stubName: string]: StubDef; +} + +const cache = {} as StubCache; + +export default class StubLoader { + stubsUrl: string + + constructor(stubsUrl?: string) { + this.stubsUrl = stubsUrl || 'https://raw.githubusercontent.com/duinoapp/duinoapp-client/master/public/stubs'; + this.stubsUrl = this.stubsUrl.replace(/\/$/, ''); + } + + async loadStub(chipName: string) { + const stubName = chipName.replace(/-/g, '').toLowerCase(); + if (cache[stubName]) { + return cache[stubName]; + } + const { data: res } = await axios.get(`${this.stubsUrl}/${stubName}.json`); + + const stub = { + data: Buffer.from(res.data, 'base64'), + text: Buffer.from(res.text, 'base64'), + entry: res.entry, + text_start: res.text_start, + data_start: res.data_start, + } as StubDef; + + cache[stubName] = stub; + return stub; + } +}; \ No newline at end of file diff --git a/src/esp/roms/esp32.ts b/src/esp/roms/esp32.ts new file mode 100644 index 0000000..f3cb4b3 --- /dev/null +++ b/src/esp/roms/esp32.ts @@ -0,0 +1,207 @@ +export default class ESP32ROM { + static CHIP_NAME = 'ESP32'; + + static IS_STUB = true; + + static IMAGE_CHIP_ID = 0; + + static CHIP_DETECT_MAGIC_VALUE = 0x00f01d83; + + static EFUSE_RD_REG_BASE = 0x3ff5a000; + + static DR_REG_SYSCON_BASE = 0x3ff66000; + + static UART_CLKDIV_REG = 0x3ff40014; + + static UART_CLKDIV_MASK = 0xFFFFF; + + static UART_DATE_REG_ADDR = 0x60000078; + + static XTAL_CLK_DIVIDER= 1; + + static FLASH_WRITE_SIZE = 0x400; + + static BOOTLOADER_FLASH_OFFSET = 0x1000; + + static FLASH_SIZES = { + '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, + }; + + static SPI_REG_BASE = 0x3ff42000; + + static SPI_USR_OFFS = 0x1c; + + static SPI_USR1_OFFS = 0x20; + + static SPI_USR2_OFFS = 0x24; + + static SPI_W0_OFFS = 0x80; + + static SPI_MOSI_DLEN_OFFS = 0x28; + + static SPI_MISO_DLEN_OFFS = 0x2c; + + static read_efuse = async (loader, offset) => { + const addr = this.EFUSE_RD_REG_BASE + (4 * offset); + // console.log(`Read efuse ${addr}`); + return loader.read_reg({ addr }); + } + + static get_pkg_version = async (loader) => { + const word3 = await this.read_efuse(loader, 3); + let pkg_version = (word3 >> 9) & 0x07; + pkg_version += ((word3 >> 2) & 0x1) << 3; + return pkg_version; + } + + static get_chip_revision = async (loader) => { + const word3 = await this.read_efuse(loader, 3); + const word5 = await this.read_efuse(loader, 5); + const apb_ctl_date = await loader.read_reg({ addr: this.DR_REG_SYSCON_BASE + 0x7C }); + + const rev_bit0 = (word3 >> 15) & 0x1; + const rev_bit1 = (word5 >> 20) & 0x1; + const rev_bit2 = (apb_ctl_date >> 31) & 0x1; + if (rev_bit0 !== 0) { + if (rev_bit1 !== 0) { + if (rev_bit2 !== 0) { + return 3; + } + return 2; + } + return 1; + } + return 0; + } + + static get_chip_description = async (loader) => { + const chip_desc = ['ESP32-D0WDQ6', 'ESP32-D0WD', 'ESP32-D2WD', '', 'ESP32-U4WDH', 'ESP32-PICO-D4', 'ESP32-PICO-V3-02']; + let chip_name = ''; + const pkg_version = await this.get_pkg_version(loader); + const chip_revision = await this.get_chip_revision(loader); + const rev3 = (chip_revision === 3); + const single_core = await this.read_efuse(loader, 3) & (1 << 0); + + if (single_core !== 0) { + chip_desc[0] = 'ESP32-S0WDQ6'; + chip_desc[1] = 'ESP32-S0WD'; + } + if (rev3) { + chip_desc[5] = 'ESP32-PICO-V3'; + } + if (pkg_version >= 0 && pkg_version <= 6) { + chip_name = chip_desc[pkg_version]; + } else { + chip_name = 'Unknown ESP32'; + } + + if (rev3 && (pkg_version === 0 || pkg_version === 1)) { + chip_name += '-V3'; + } + return `${chip_name} (revision ${chip_revision})`; + } + + static get_chip_features = async (loader) => { + const features = ['Wi-Fi']; + const word3 = await this.read_efuse(loader, 3); + + const chip_ver_dis_bt = word3 & (1 << 1); + if (chip_ver_dis_bt === 0) { + features.push(' BT'); + } + + const chip_ver_dis_app_cpu = word3 & (1 << 0); + if (chip_ver_dis_app_cpu !== 0) { + features.push(' Single Core'); + } else { + features.push(' Dual Core'); + } + + const chip_cpu_freq_rated = word3 & (1 << 13); + if (chip_cpu_freq_rated !== 0) { + const chip_cpu_freq_low = word3 & (1 << 12); + if (chip_cpu_freq_low !== 0) { + features.push(' 160MHz'); + } else { + features.push(' 240MHz'); + } + } + + const pkg_version = await this.get_pkg_version(loader); + if ([2, 4, 5, 6].includes(pkg_version)) { + features.push(' Embedded Flash'); + } + + if (pkg_version === 6) { + features.push(' Embedded PSRAM'); + } + + const word4 = await this.read_efuse(loader, 4); + const adc_vref = (word4 >> 8) & 0x1F; + if (adc_vref !== 0) { + features.push(' VRef calibration in efuse'); + } + + const blk3_part_res = (word3 >> 14) & 0x1; + if (blk3_part_res !== 0) { + features.push(' BLK3 partially reserved'); + } + + const word6 = await this.read_efuse(loader, 6); + const coding_scheme = word6 & 0x3; + const coding_scheme_arr = ['None', '3/4', 'Repeat (UNSUPPORTED)', 'Invalid']; + features.push(` Coding Scheme ${coding_scheme_arr[coding_scheme]}`); + + return features; + } + + static get_crystal_freq = async (loader) => { + const uart_div = await loader.read_reg({ addr: this.UART_CLKDIV_REG }) & this.UART_CLKDIV_MASK; + const ets_xtal = (loader.transport.baudrate * uart_div) / 1000000 / this.XTAL_CLK_DIVIDER; + let norm_xtal; + if (ets_xtal > 33) { + norm_xtal = 40; + } else { + norm_xtal = 26; + } + if (Math.abs(norm_xtal - ets_xtal) > 1) { + loader.log('WARNING: Unsupported crystal in use'); + } + return norm_xtal; + } + + static _d2h(d) { + const h = (+d).toString(16); + return h.length === 1 ? `0${h}` : h; + } + + static read_mac = async (loader) => { + let mac0 = await this.read_efuse(loader, 1); + mac0 >>>= 0; + let mac1 = await this.read_efuse(loader, 2); + mac1 >>>= 0; + const mac = new Uint8Array(6); + mac[0] = (mac1 >> 8) & 0xff; + mac[1] = mac1 & 0xff; + mac[2] = (mac0 >> 24) & 0xff; + mac[3] = (mac0 >> 16) & 0xff; + mac[4] = (mac0 >> 8) & 0xff; + mac[5] = mac0 & 0xff; + + return (`${ + this._d2h(mac[0]) + }:${ + this._d2h(mac[1]) + }:${ + this._d2h(mac[2]) + }:${ + this._d2h(mac[3]) + }:${ + this._d2h(mac[4]) + }:${ + this._d2h(mac[5]) + }`); + } + + static get_erase_size = (offset, size) => size +} \ No newline at end of file diff --git a/src/esp/roms/esp32c3.ts b/src/esp/roms/esp32c3.ts new file mode 100644 index 0000000..a3d3f54 --- /dev/null +++ b/src/esp/roms/esp32c3.ts @@ -0,0 +1,113 @@ +export default class ESP32C3ROM { + static CHIP_NAME = 'ESP32-C3'; + + static IS_STUB = true; + + static IMAGE_CHIP_ID = 5; + + static CHIP_DETECT_MAGIC_VALUE = 0x6921506f; + + static EFUSE_BASE = 0x60008800; + + static MAC_EFUSE_REG = this.EFUSE_BASE + 0x044; + + static UART_CLKDIV_REG = 0x3ff40014; + + static UART_CLKDIV_MASK = 0xFFFFF; + + static UART_DATE_REG_ADDR = 0x6000007C; + + static FLASH_WRITE_SIZE = 0x400; + + static BOOTLOADER_FLASH_OFFSET = 0x1000; + + static FLASH_SIZES = { + '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, + }; + + static SPI_REG_BASE = 0x60002000; + + static SPI_USR_OFFS = 0x18; + + static SPI_USR1_OFFS = 0x1C; + + static SPI_USR2_OFFS = 0x20; + + static SPI_MOSI_DLEN_OFFS = 0x24; + + static SPI_MISO_DLEN_OFFS = 0x28; + + static SPI_W0_OFFS = 0x58; + + static get_pkg_version = async (loader) => { + const num_word = 3; + const block1_addr = this.EFUSE_BASE + 0x044; + const addr = block1_addr + (4 * num_word); + const word3 = await loader.read_reg({ addr }); + const pkg_version = (word3 >> 21) & 0x0F; + return pkg_version; + } + + static get_chip_revision = async (loader) => { + const block1_addr = this.EFUSE_BASE + 0x044; + const num_word = 3; + const pos = 18; + const addr = block1_addr + (4 * num_word); + const ret = (await loader.read_reg({ addr }) & (0x7 << pos)) >> pos; + return ret; + } + + static get_chip_description = async (loader) => { + let desc; + const pkg_ver = await this.get_pkg_version(loader); + if (pkg_ver === 0) { + desc = 'ESP32-C3'; + } else { + desc = 'unknown ESP32-C3'; + } + const chip_rev = await this.get_chip_revision(loader); + desc += ` (revision ${chip_rev})`; + return desc; + } + + // eslint-disable-next-line no-unused-vars + static get_chip_features = async (loader) => ['Wi-Fi'] + + // eslint-disable-next-line no-unused-vars + static get_crystal_freq = async (loader) => 40 + + static _d2h(d) { + const h = (+d).toString(16); + return h.length === 1 ? `0${h}` : h; + } + + static read_mac = async (loader) => { + let mac0 = await loader.read_reg({ addr: this.MAC_EFUSE_REG }); + mac0 >>>= 0; + let mac1 = await loader.read_reg({ addr: this.MAC_EFUSE_REG + 4 }); + mac1 = (mac1 >>> 0) & 0x0000ffff; + const mac = new Uint8Array(6); + mac[0] = (mac1 >> 8) & 0xff; + mac[1] = mac1 & 0xff; + mac[2] = (mac0 >> 24) & 0xff; + mac[3] = (mac0 >> 16) & 0xff; + mac[4] = (mac0 >> 8) & 0xff; + mac[5] = mac0 & 0xff; + + return (`${ + this._d2h(mac[0]) + }:${ + this._d2h(mac[1]) + }:${ + this._d2h(mac[2]) + }:${ + this._d2h(mac[3]) + }:${ + this._d2h(mac[4]) + }:${ + this._d2h(mac[5]) + }`); + } + + static get_erase_size = (offset, size) => size +} \ No newline at end of file diff --git a/src/esp/roms/esp32s2.ts b/src/esp/roms/esp32s2.ts new file mode 100644 index 0000000..85a2290 --- /dev/null +++ b/src/esp/roms/esp32s2.ts @@ -0,0 +1,117 @@ +export default class ESP32S2ROM { + static CHIP_NAME = 'ESP32-S2'; + + static IS_STUB = true; + + static IMAGE_CHIP_ID = 2; + + static CHIP_DETECT_MAGIC_VALUE = 0x000007c6; + + static MAC_EFUSE_REG = 0x3f41A044; + + static EFUSE_BASE = 0x3f41A000; + + static UART_CLKDIV_REG = 0x3f400014; + + static UART_CLKDIV_MASK = 0xFFFFF; + + static UART_DATE_REG_ADDR = 0x60000078; + + static FLASH_WRITE_SIZE = 0x400; + + static BOOTLOADER_FLASH_OFFSET = 0x1000; + + static FLASH_SIZES = { + '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, + }; + + static SPI_REG_BASE = 0x3f402000; + + static SPI_USR_OFFS = 0x18; + + static SPI_USR1_OFFS = 0x1c; + + static SPI_USR2_OFFS = 0x20; + + static SPI_W0_OFFS = 0x58; + + static SPI_MOSI_DLEN_OFFS = 0x24; + + static SPI_MISO_DLEN_OFFS = 0x28; + + static get_pkg_version = async (loader) => { + const num_word = 3; + const block1_addr = this.EFUSE_BASE + 0x044; + const addr = block1_addr + (4 * num_word); + const word3 = await loader.read_reg({ addr }); + const pkg_version = (word3 >> 21) & 0x0F; + return pkg_version; + } + + static get_chip_description = async (loader) => { + const chip_desc = ['ESP32-S2', 'ESP32-S2FH16', 'ESP32-S2FH32']; + const pkg_ver = await this.get_pkg_version(loader); + if (pkg_ver >= 0 && pkg_ver <= 2) { + return chip_desc[pkg_ver]; + } + return 'unknown ESP32-S2'; + } + + static get_chip_features = async (loader) => { + const features = ['Wi-Fi']; + const pkg_ver = await this.get_pkg_version(loader); + if (pkg_ver === 1) { + features.push('Embedded 2MB Flash'); + } else if (pkg_ver === 2) { + features.push('Embedded 4MB Flash'); + } + const num_word = 4; + const block2_addr = this.EFUSE_BASE + 0x05C; + const addr = block2_addr + (4 * num_word); + const word4 = await loader.read_reg({ addr }); + const block2_ver = (word4 >> 4) & 0x07; + + if (block2_ver === 1) { + features.push('ADC and temperature sensor calibration in BLK2 of efuse'); + } + return features; + } + + // eslint-disable-next-line no-unused-vars + static get_crystal_freq = async (loader) => 40 + + static _d2h(d) { + const h = (+d).toString(16); + return h.length === 1 ? `0${h}` : h; + } + + static read_mac = async (loader) => { + let mac0 = await loader.read_reg({ addr: this.MAC_EFUSE_REG }); + mac0 >>>= 0; + let mac1 = await loader.read_reg({ addr: this.MAC_EFUSE_REG + 4 }); + mac1 = (mac1 >>> 0) & 0x0000ffff; + const mac = new Uint8Array(6); + mac[0] = (mac1 >> 8) & 0xff; + mac[1] = mac1 & 0xff; + mac[2] = (mac0 >> 24) & 0xff; + mac[3] = (mac0 >> 16) & 0xff; + mac[4] = (mac0 >> 8) & 0xff; + mac[5] = mac0 & 0xff; + + return (`${ + this._d2h(mac[0]) + }:${ + this._d2h(mac[1]) + }:${ + this._d2h(mac[2]) + }:${ + this._d2h(mac[3]) + }:${ + this._d2h(mac[4]) + }:${ + this._d2h(mac[5]) + }`); + } + + static get_erase_size = (offset, size) => size +} \ No newline at end of file diff --git a/src/esp/roms/esp32s3beta2.ts b/src/esp/roms/esp32s3beta2.ts new file mode 100644 index 0000000..486afc8 --- /dev/null +++ b/src/esp/roms/esp32s3beta2.ts @@ -0,0 +1,31 @@ +export default class ESP32S3BETA2ROM { + static CHIP_NAME = 'ESP32-S3'; + + static IMAGE_CHIP_ID = 4; + + static CHIP_DETECT_MAGIC_VALUE = 0xeb004136; + + // eslint-disable-next-line no-unused-vars + static get_pkg_version = async (loader) => { + } + + // eslint-disable-next-line no-unused-vars + static get_chip_revision = async (loader) => { + } + + // eslint-disable-next-line no-unused-vars + static get_chip_description = async (loader) => { + } + + // eslint-disable-next-line no-unused-vars + static get_chip_features = async (loader) => { + } + + // eslint-disable-next-line no-unused-vars + static get_crystal_freq = async (loader) => { + } + + // eslint-disable-next-line no-unused-vars + static read_mac = async (loader) => { + } +} diff --git a/src/esp/roms/esp8266.ts b/src/esp/roms/esp8266.ts new file mode 100644 index 0000000..da67fda --- /dev/null +++ b/src/esp/roms/esp8266.ts @@ -0,0 +1,156 @@ +export default class ESP8266ROM { + static CHIP_NAME = 'ESP8266'; + + static IS_STUB = true; + + static CHIP_DETECT_MAGIC_VALUE = 0xfff0c101; + + static FLASH_WRITE_SIZE = 0x400; + + // OTP ROM addresses + static ESP_OTP_MAC0 = 0x3ff00050 + + static ESP_OTP_MAC1 = 0x3ff00054 + + static ESP_OTP_MAC3 = 0x3ff0005c + + static SPI_REG_BASE = 0x60000200 + + static SPI_USR_OFFS = 0x1c + + static SPI_USR1_OFFS = 0x20 + + static SPI_USR2_OFFS = 0x24 + + static SPI_MOSI_DLEN_OFFS = null + + static SPI_MISO_DLEN_OFFS = null + + static SPI_W0_OFFS = 0x40 + + static UART_CLKDIV_REG = 0x60000014 + + static XTAL_CLK_DIVIDER = 2 + + static FLASH_SIZES = { + '512KB': 0x00, + '256KB': 0x10, + '1MB': 0x20, + '2MB': 0x30, + '4MB': 0x40, + '2MB-c1': 0x50, + '4MB-c1': 0x60, + '8MB': 0x80, + '16MB': 0x90, + } + + static BOOTLOADER_FLASH_OFFSET = 0 + + static MEMORY_MAP = [[0x3FF00000, 0x3FF00010, 'DPORT'], + [0x3FFE8000, 0x40000000, 'DRAM'], + [0x40100000, 0x40108000, 'IRAM'], + [0x40201010, 0x402E1010, 'IROM']] + + static get_efuses = async (loader) => { + // Return the 128 bits of ESP8266 efuse as a single integer + const result = (await loader.read_reg({ addr: 0x3ff0005c }) << 96) + | (await loader.read_reg({ addr: 0x3ff00058 }) << 64) + | (await loader.read_reg({ addr: 0x3ff00054 }) << 32) + | await loader.read_reg({ addr: 0x3ff00050 }); + return result; + } + + static _get_flash_size = (efuses) => { + // rX_Y = EFUSE_DATA_OUTX[Y] + const r0_4 = (efuses & (1 << 4)) !== 0; + const r3_25 = (efuses & (1 << 121)) !== 0; + const r3_26 = (efuses & (1 << 122)) !== 0; + const r3_27 = (efuses & (1 << 123)) !== 0; + + if (r0_4 && !r3_25) { + if (!r3_27 && !r3_26) { + return 1; + } if (!r3_27 && r3_26) { + return 2; + } + } + if (!r0_4 && r3_25) { + if (!r3_27 && !r3_26) { + return 2; + } if (!r3_27 && r3_26) { + return 4; + } + } + return -1; + } + + static get_chip_description = async (loader) => { + const efuses = await this.get_efuses(loader); + const is_8285 = (efuses & (((1 << 4) | 1) << 80)) !== 0; // One or the other efuse bit is set for ESP8285 + if (is_8285) { + const flash_size = this._get_flash_size(efuses); + const max_temp = (efuses & (1 << 5)) !== 0; // This efuse bit identifies the max flash temperature + const chip_name = { + 1: max_temp ? 'ESP8285H08' : 'ESP8285N08', + 2: max_temp ? 'ESP8285H16' : 'ESP8285N16', + }[flash_size] || 'ESP8285'; + return chip_name; + } + return 'ESP8266EX'; + } + + static get_chip_features = async (loader) => { + const features = ['WiFi']; + if (await this.get_chip_description(loader) === 'ESP8285') { + features.push('Embedded Flash'); + } + return features; + } + + static flash_spi_attach = async (loader, hspi_arg) => { + if (this.IS_STUB) { + await super.flash_spi_attach(loader, hspi_arg); + } else { + // ESP8266 ROM has no flash_spi_attach command in serial protocol, + // but flash_begin will do it + await loader.flash_begin(0, 0); + } + } + + static flash_set_parameters = async (loader, size) => { + // not implemented in ROM, but OK to silently skip for ROM + if (this.IS_STUB) { + await super.flash_set_parameters(loader, size); + } + } + + static chip_id = async (loader) => { + // Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function + const id0 = await loader.read_reg({ addr: this.ESP_OTP_MAC0 }); + const id1 = await loader.read_reg({ addr: this.ESP_OTP_MAC1 }); + return (id0 >> 24) | ((id1 & 0xffffff) << 8); + } + + static read_mac = async (loader) => { + // Read MAC from OTP ROM + const mac0 = await loader.read_reg({ addr: this.ESP_OTP_MAC0 }); + const mac1 = await loader.read_reg({ addr: this.ESP_OTP_MAC1 }); + const mac3 = await loader.read_reg({ addr: this.ESP_OTP_MAC3 }); + let oui; + 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 ('Unknown OUI'); + } + return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff); + } + + static get_erase_size = (offset, size) => size + + // eslint-disable-next-line no-unused-vars + static get_crystal_freq = async (loader) => 40 +} \ No newline at end of file diff --git a/src/esp/stubs/esp32.json b/src/esp/stubs/esp32.json new file mode 100644 index 0000000..4e48a85 --- /dev/null +++ b/src/esp/stubs/esp32.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1074520064, "entry": 1074521516, "data": "CMD8Pw==", "data_start": 1073605544} \ No newline at end of file diff --git a/src/esp/stubs/esp32c3.json b/src/esp/stubs/esp32c3.json new file mode 100644 index 0000000..80dad8c --- /dev/null +++ b/src/esp/stubs/esp32c3.json @@ -0,0 +1 @@ +{"text": "QREixCbCBsa3NwRgEUfYyzc0BGC3RMg/XECRi5HnskAiRJJEQQGCgAhAg6cEABN19Q+Cl9W3ARG3BwBgSsgDqYcAJspOxlLEBs4izLcEAGD9WTdKyD/ATBN09A8N4PJAYkQjqCQBsknSREJJIkoFYYKAiECDJwoAE3X1D4KXfRTjGTT/yb83JwBgfEudi/X/NzcAYHxLnYv1/4KAQREGxt03tycAYCOmBwI3BwAImMOYQ33/yFeyQBNF9f8FiUEBgoBBEQbG2T993TcHAEC3JwBgmMM3JwBgHEP9/7JAQQGCgEERIsQ3RMk/kwfECZxLBsYmwqHPXTcxyRMExAkYSL1HgURj1ucABES9iJO0FABNP5U/HEQ3BwABE5bHAGN/5gC3BoAAmeC3BgABNycAYFDDFMO3JgBgmEJ9/0FHkeAFRxRIupccxJmOFMiyQCJEkkRBAYKAEwcADJxBYxvlAIHnhUecwSGoI6AFAPlXPoWCgAVHY4fnAIlGY43XAP1X/beTFwUBEwewDcGH4xHl/olHyb+TB8ANYxb1AJjBkwcADPG3kwbQDf1X4xLV/JjBkwewDW2/t0XJP0ERk4VFCQbGUT9jSQUGt0fJP5OHxwCDpgcIA9dHCBN19Q9CB0GDEwYXAEIGQYIjkscINpcjAKcAA9dHCJFnk4cHBEIHQYNjHvcCN8fIPxMHxwChZ7qXA6YHCLcGyT+3R8k/k4fHAJOGxgRjH+YAI6bHCCOg1wgjkgcIIaD5V+MG9fyyQEEBgoAjptcII6DnCN23QREGxpcAyP/ngADmA0WFAbJAdRUTNRUAQQGCgEERBsbFNxHBDUWyQEEBFwPI/2cAo+BBEQbGlwDI/+eAYN7JNwHFskBBAdm/skBBAYKAQREGxhMHAAxjGuUAEwWwDdE/EwXADbJAQQHptxMHsA3jG+X+wTcTBdAN9bdBESLEJsIGxiqEswS1AGMXlACyQCJEkkRBAYKAA0UEAAUETT/ttxMFAAx5twERIsw3RMk/kwfECSbKxEcGzkrITsYTBMQJY/OVAK6EucADKUQAqokmmRNZyQAcSGNV8AAcRGNf+QKFO33dSEAmhs6FlwDI/+eAYN8TdfUPAcWTB0AMXMhcQKaXXMBcRLOEl0BExPJAYkTSREJJskkFYYKAtTtlvwERBs4izBk7NwTOP2wAEwVE/5cAyP/ngADehUcV5bJHk/cHID7GDTs3JwBgHEe3BkAAEwVE/9WPHMeyRZcAyP/ngKDbszegAPJAYkQ+hQVhgoBBEbdHyT8FRwbGI47nCJOHxwkT18UAmMcFZ30XzMPIx/mNOpWqlbGBjMsjqgcAQTcZwRMFUAyyQEEBgoB1cUrBfXMFaSLFJsPO3tLc1toGx310GpGTBwkHipcTBIT6PpSqiSKFroSXAMj/54AgH5MHCQcFaoqXs4pHQbngBWeTBwcHfXSTBYT6ipcTBIT5PpSTBwcHipe+lSKFlwDI/+eAYBwihcFFlTUBRQVjGpG6QCpEmkQKSfZZZlrWWklhgoAmiWNzmgAFaUqG1oVOhZcAyP/ngGDKE3X1DwHtSobWhSKFlwDI/+eAoBfKmbOEJEFptxMFMAZVvzFxfXNW01rRXs9izQbfIt0m20rZTtdS1WbLasluxwVnGpE2jBMHBwcUCDaX/Xe6lz7GI6oH+KqKLouyi7E7kwcAAhnBtwcCAD6FlwDI/+eAoBCFZ2PjdxWFZBgIfXSThwQHupcTBIT6M4mHAEqFlwDI/+eAIA99ehgIk4cEB7qXkww6+b6ck4cEBxMNivm6l4FJPp2FZ5OHBwcYCLqXM4RHAYMtRPlj9m0LY/G5A1WgYTOmhSKFsTtBMyaGooVKhZcAyP/ngEAKppqmmWP2aQOzh7lBY/KHA7MHO0HehGPzdwG+hCaGooVWhZcAyP/ngCC5E3X1D03dhWeThwcHGAi6lzOERwEjLAT4gUSNTaMJBPhmhZcAyP/ngICqffkDRTT56oW9PmNABQLj4p3+hWcYCJOHBwe6lzOHlwBSlyMKp/iFBOm3+VfjE/X8EUfjg+T0BWcUCJMHBwd9dLaXkwWE+hMEhPk+lJMHBwe2l76VIoWXAMj/54Bg/305wUUihUk5XTkRObcHAgAZ4ZMHAAI+hZcAyP/ngGD8BWMakfpQalTaVEpZulkqWppaClv6S2pM2kxKTbpNKWGCgLdXQUlZcZOH94QBRT7Ohtai1KbSytDOztLM1srayN7G4sTmwurAbt6XAMj/54BAordHyD83d8k/k4cHABMHh7pj6+cSJTmRRWgIMTEFObfHyD+Th8cAIWc+lyMg9wi3BzhAN0rIP5OHZxsjIPoAt0rJP602k4rKABMKCgBjAQUQtycMYEVHuNeFRUVFlwDI/+eAQO63BThAAUaThQUARUWXAMj/54BA7zc3BGAcSzcFAgCT50cAHMuXAMj/54BA7pcAyP/ngMD+t0cAYJxfEeUT9ccBYRUTNRUAgUWXAMj/54CAocFnN0vJP/0XEwcAEIVmQWa3BQABAUWTCcsJjWs3TMg/lwDI/+eAAJzOm5MMzACDp8oI9d+DpMoIhUcjpgoIIwTxAoPHFAAJRyMV4QKjBPECAtYpR2OG5wZNR2OA5wgtPqFFKBA5NgPHNACDxyQAkWYiB12Pk4cGAWP15wYTBbANbTQTBcANVTQTBeAOeTwpNnm/I6AHAJEH0bW3BThAAUaThWUDFUWXAMj/54Cg4DcHAGBcRxMFAAKT5xcQXMcZv4PHNAADxyQAogfZjxFH45jn+JxEnEM+1lm3yUcjFfECvb+DxxQANUZjiscqY272DhlGY4vHNGNi9ggNRmOKxxZjbPYECUZjhcckAUkTBPAPE3X0Dw08E3X5DzU0tTzjGATwg8cUAD1HY4jnQmNq9zQRR2OC51IZR2OA51QNR+OY5+6DxTQAg8ckABOFhAGiBd2NwRWpNOG9kUZjgdcMlUbjldf6wUcFRWMZ9w6cRNhIIyT6ACMi6gCdqqVGY4vXImPs9gKdRmOI1yahRuOf1/aTB0ACYxr3BgLWHUQBRXEyAUVVMtU6zTqhRSgQfRTRMnX0AUkBRKm/qUZjjNcirUbjldf04UdjGPcc3EyYTNRIkEjMRIhElwDI/+eAoIAqiTM1oAAqhC23TUZji8cUY2T2BEFGY4nHFmNs9gC9RuOW1/ChR+MH9/oBSRMEAAwJt8VGY4/XBElH45nn7oNHywljgAceg6fJAGOUByQjDgsIA6RJASWgkwYgDWOB1xBj4fYCkwYADWOK1waTBhAN457X6qFHYwz3BgVFKoQBSU29kwYwDWOH10KTBkAN45/X6INHywljhgcYnERBFwOkSQFjhOcAEwQADIFHkwbwDmPN5w4Dx1QAg8dEAAFJIgddj4PHZADCB12Pg8d0AOIH2Y/jgPbmEwQQDKG9BURF85fwx//ngOBwMzSgAEm/A62EAMBEs2eNABOXRwE9/y06Lf1BaSKdfRn9fTMFjUAZ6AFFrbcxgZfwx//ngABuMf1ulOW3s3clAfX3QWkzBY1AY26JAH15MwWNQHnYMYGX8Mf/54CAaxX5SpT1t0GBl/DH/+eAQGoV8TMEJEHBv8FH2bXBRwVE4xz38MxEiEShOqW/wUcFROMU9/CcSGPn9hDMSIhEGTKNtwVE4xr37pxIY+32DsBEzEiIRDOEhwL1MCOsCQAjpIuwgbczhvQAA0aGAYUHsY7tvQFJBUWptZFHBUXjHffqiESBRZfwx//ngIBmPb+Td/cA45kH5BNdRwAThIQAAUn9XeN2qd9IRJfwx//ngABTHERYQBRAfY9jh7cBkEKTx/f/8Y9dj5jCBQlBBNm/kUcBvYMlSgBBF5HlCc8BSRMEYAwps4MnigBj5ucGk3c3AOOaB94DKIoAAUaBR7OG9QAzBfhAY+nnAOMDBtgjItoAIySqAK27M4b0ABBOkQeQwgVG6b+hRwVF4xf34AMkigAZwBMEgAwjJAoAIyIKADM1gADVuwFJEwQgDE2xAUkTBIAMabkBSRMEkAxJuUlHY4rnHGNi9wRFR+OR57qDxzQAA8ckABOEhAGiB9mPk40H/wVJg6fJAGOFDQCZw2NEIBFjWAkYEwdwDCOq6QDjlwe2kweQDGGiEwcgDWOL5wwTB0AN45zntAPENACDxyQAIgRdjJfwx//ngOBNA6nJAEEUY3MkASKJ4woJsgOkSQBKlDGAg6cJAWNW8ACDp4kAY1D0Cu/wL8N13QOlSQBKhpOFhAGX8Mf/54BgSQnFkwdADCOq+QCDp0kAypcjovkAg6fJADOJJ0EjpikBl/DH/+eAoEfhvAllEwUFcQOpxACARJfwx//ngIA5twcAYNhLtwYAAcEWk1dHARIHdY+9i9mPs4cnAwFFs9WHApfwx//ngGA6EwWAPpfwx//ngCA2cbTUSJBIzESIRO/wT/u9vO/wz72Bv7d2yT8Dp4a6t8fIP5OHxwCZjz7Sg6eLsDd9yT9u0BMNzQmThIa6BUhj8/0ADUhCxjrE7/BPuiJHMkg3Rck/ooVcEJMGzAAQEBMFRQuX8Mf/54DAOYJXAyeNsIxAs439QB2PPpSSVyMk7bAqib6VjMCTB8wAnY1jVaAAoWfjmfXmZoXv8E/WI6CUAZW14x4J5uODB56TB4AMI6r5AOm6nETjmwec7/CPywllEwUFcZfwx//ngGApl/DH/+eA4CxlusBE4woEmu/wb8kTBYA+l/DH/+eAYCcClHm6tlAmVJZUBln2SWZK1kpGS7ZLJkyWTAZN8l1lYYKA", "text_start": 1077411840, "entry": 1077413488, "data": "DEDIPw==", "data_start": 1070164904} \ No newline at end of file diff --git a/src/esp/stubs/esp32h2.json b/src/esp/stubs/esp32h2.json new file mode 100644 index 0000000..67375e8 --- /dev/null +++ b/src/esp/stubs/esp32h2.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1077411840, "entry": 1077413328, "data": "DEDIPw==", "data_start": 1070164904} \ No newline at end of file diff --git a/src/esp/stubs/esp32s2.json b/src/esp/stubs/esp32s2.json new file mode 100644 index 0000000..74a8576 --- /dev/null +++ b/src/esp/stubs/esp32s2.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1073905664, "entry": 1073907540, "data": "CAD9Pw==", "data_start": 1073622004} \ No newline at end of file diff --git a/src/esp/stubs/esp32s3.json b/src/esp/stubs/esp32s3.json new file mode 100644 index 0000000..ba7d1ee --- /dev/null +++ b/src/esp/stubs/esp32s3.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1077379072, "entry": 1077381116, "data": "CADKPw==", "data_start": 1070279592} \ No newline at end of file diff --git a/src/esp/stubs/esp8266.json b/src/esp/stubs/esp8266.json new file mode 100644 index 0000000..6ae7a2e --- /dev/null +++ b/src/esp/stubs/esp8266.json @@ -0,0 +1 @@ +{"text": "qBAAQAH//0Z0AAAAkIH/PwgB/z+AgAAAhIAAAEBAAABIQf8/lIH/PzH5/xLB8CAgdAJhA8XvATKv/pZyA1H0/0H2/zH0/yAgdDA1gEpVwCAAaANCFQBAMPQbQ0BA9MAgAEJVADo2wCAAIkMAIhUAMev/ICD0N5I/Ieb/Meb/Qen/OjLAIABoA1Hm/yeWEoYAAAAAAMAgACkEwCAAWQNGAgDAIABZBMAgACkDMdv/OiIMA8AgADJSAAgxEsEQDfAAoA0AAJiB/z8Agf4/T0hBSais/z+krP8/KNAQQEzqEEAMAABg//8AAAAQAAAAAAEAAAAAAYyAAAAQQAAAAAD//wBAAAAAgf4/BIH+PxAnAAAUAABg//8PAKis/z8Igf4/uKz/PwCAAAA4KQAAkI//PwiD/z8Qg/8/rKz/P5yv/z8wnf8/iK//P5gbAAAACAAAYAkAAFAOAABQEgAAPCkAALCs/z+0rP8/1Kr/PzspAADwgf8/DK//P5Cu/z+ACwAAEK7/P5Ct/z8BAAAAAAAAALAVAADx/wAAmKz/P5iq/z+8DwBAiA8AQKgPAEBYPwBAREYAQCxMAEB4SABAAEoAQLRJAEDMLgBA2DkAQEjfAECQ4QBATCYAQIRJAEAhvP+SoRCQEcAiYSMioAACYUPCYULSYUHiYUDyYT8B6f/AAAAhsv8xs/8MBAYBAABJAksiNzL4xbUBIqCMDEMqIQWoAcW0ASF8/8F6/zGr/yoswCAAyQIhqP8MBDkCMaj/DFIB2f/AAAAxpv8ioQHAIABIAyAkIMAgACkDIqAgAdP/wAAAAdL/wAAAAdL/wAAAcZ3/UZ7/QZ7/MZ7/YqEADAIBzf/AAAAhnP8xYv8qI8AgADgCFnP/wCAA2AIMA8AgADkCDBIiQYQiDQEMJCJBhUJRQzJhIiaSCRwzNxIghggAAAAiDQMyDQKAIhEwIiBmQhEoLcAgACgCImEiBgEAHCIiUUPFqAEioIQMgxoiRZsBIg0DMg0CgCIRMDIgIX//N7ITIqDABZYBIqDuhZUBBaYBRtz/AAAiDQEMtEeSAgaZACc0Q2ZiAsbLAPZyIGYyAoZxAPZCCGYiAsZWAEbKAGZCAgaHAGZSAsarAIbGACaCefaCAoarAAyUR5ICho8AZpICBqMABsAAHCRHkgJGfAAnNCcM9EeSAoY+ACc0CwzUR5IChoMAxrcAAGayAkZLABwUR5ICRlgARrMAQqDRRxJoJzQRHDRHkgJGOABCoNBHEk/GrAAAQqDSR5IChi8AMqDTN5ICRpcFRqcALEIMDieTAgZqBUYrACKgAIWIASKgAEWIAcWYAYWYASKghDKgCBoiC8zFigFW3P0MDs0ORpsAAMwThl8FRpUAJoMCxpMABmAFAWn/wAAA+sycIsaPAAAAICxBAWb/wAAAVhIj8t/w8CzAzC+GaQUAIDD0VhP+4Sv/hgMAICD1AV7/wAAAVtIg4P/A8CzA9z7qhgMAICxBAVf/wAAAVlIf8t/w8CzAVq/+RloFJoOAxgEAAABmswJG3f8MDsKgwIZ4AAAAZrMCRkQFBnIAAMKgASazAgZwACItBDEX/+KgAMKgwiezAsZuADhdKC2FdgFGPAUAwqABJrMChmYAMi0EIQ7/4qAAwqDCN7ICRmUAKD0MHCDjgjhdKC3FcwEx9/4MBEljMtMr6SMgxIMGWgAAIfP+DA5CAgDCoMbnlALGWADIUigtMsPwMCLAQqDAIMSTIs0YTQJioO/GAQBSBAAbRFBmMCBUwDcl8TINBVINBCINBoAzEQAiEVBDIEAyICINBwwOgCIBMCIgICbAMqDBIMOThkMAAAAh2f4MDjICAMKgxueTAsY+ADgywqDI5xMCBjwA4kIAyFIGOgAcggwODBwnEwIGNwAGCQVmQwKGDwVGMAAwIDQMDsKgwOcSAoYwADD0QYvtzQJ888YMACg+MmExAQL/wAAASC4oHmIuACAkEDIhMSYEDsAgAFImAEBDMFBEEEAiIMAgACkGG8zizhD3PMjGgf9mQwJGgP8Gov9mswIG+QTGFgAAAGHA/gwOSAYMFTLD8C0OQCWDMF6DUCIQwqDG55JLcbn+7QKIB8KgyTc4PjBQFMKgwKLNGIzVBgwAWiooAktVKQRLRAwSUJjANzXtFmLaSQaZB8Zn/2aDAoblBAwcDA7GAQAAAOKgAMKg/8AgdAVfAeAgdMVeAUVvAVZMwCINAQzzNxIxJzMVZkICxq4EZmIChrMEJjICxvn+BhkAABwjN5ICxqgEMqDSNxJFHBM3EgJG8/5GGQAhlP7oPdItAgHA/sAAACGS/sAgADgCIZH+ICMQ4CKC0D0gRYsBPQItDAG5/sAAACKj6AG2/sAAAMbj/lhdSE04PSItAsVqAQbg/gAyDQMiDQKAMxEgMyAyw/AizRhFSQHG2f4AAABSzRhSYSQiDQMyDQKAIhEwIiAiwvAiYSoMH4Z0BCF3/nGW/rIiAGEy/oKgAyInApIhKoJhJ7DGwCc5BAwaomEnsmE2hTkBsiE2cW3+UiEkYiEqcEvAykRqVQuEUmElgmEshwQCxk0Ed7sCRkwEmO2iLRBSLRUobZJhKKJhJlJhKTxTyH3iLRT4/SezAkbuAzFc/jAioCgCoAIAMUL+DA4MEumT6YMp0ymj4mEm/Q7iYSjNDkYGAHIhJwwTcGEEfMRgQ5NtBDliXQtyISQG4AMAgiEkkiElITP+l7jZMggAG3g5goYGAKIhJwwjMGoQfMUMFGBFg20EOWJdC0bUA3IhJFIhJSEo/le321IHAPiCWZKALxEc81oiQmExUmE0smE2G9eFeQEME0IhMVIhNLIhNlYSASKgICBVEFaFAPAgNCLC+CA1g/D0QYv/DBJhLv4AH0AAUqFXNg8AD0BA8JEMBvBigzBmIJxGDB8GAQAAANIhJCEM/ixDOWJdCwabAF0Ltjwehg4AciEnfMNwYQQMEmAjg20CDDOGFQBdC9IhJEYAAP0GgiElh73bG90LLSICAAAcQAAioYvMIO4gtjzkbQ9x+P3gICQptyAhQSnH4ONBwsz9VuIfwCAkJzwoRhEAkiEnfMOQYQQMEmAjg20CDFMh7P05Yn0NxpQDAAAAXQvSISRGAAD9BqIhJae90RvdCy0iAgAAHEAAIqGLzCDuIMAgJCc84cAgJAACQODgkSKv+CDMEPKgABacBoYMAAAAciEnfMNwYQQMEmAjg20CDGMG5//SISRdC4IhJYe94BvdCy0iAgAAHEAAIqEg7iCLzLaM5CHM/cLM+PoyIeP9KiPiQgDg6EGGDAAAAJIhJwwTkGEEfMRgNINtAwxzxtT/0iEkXQuiISUhv/2nvd1B1v0yDQD6IkoiMkIAG90b//ZPAobc/yHt/Xz28hIcIhIdIGYwYGD0Z58Hxh0A0iEkXQssc8Y/ALaMIAYPAHIhJ3zDcGEEDBJgI4NtAjwzBrz/AABdC9IhJEYAAP0GgiElh73ZG90LLSICAAAcQAAioYvMIO4gtozkbQ/gkHSSYSjg6EHCzPj9BkYCADxDhtQC0iEkXQsha/0nte+iISgLb6JFABtVFoYHVrz4hhwADJPGywJdC9IhJEYAAP0GIWH9J7XqhgYAciEnfMNwYQQMEmAjg20CLGPGmf8AANIhJF0LgiElh73ekVb90GjAUCnAZ7IBbQJnvwFtD00G0D0gUCUgUmE0YmE1smE2Abz9wAAAYiE1UiE0siE2at1qVWBvwFZm+UbQAv0GJjIIxgQAANIhJF0LDKMhb/05Yn0NBhcDAAAMDyYSAkYgACKhICJnESwEIYL9QmcSMqAFUmE0YmE1cmEzsmE2Aab9wAAAciEzsiE2YiE1UiE0PQcioJBCoAhCQ1gLIhszVlL/IqBwDJMyR+gLIht3VlL/HJRyoViRVf0MeEYCAAB6IpoigkIALQMbMkeT8SFq/TFq/QyEBgEAQkIAGyI3kvdGYQEhZ/36IiICACc8HUYPAAAAoiEnfMOgYQQMEmAjg20CDLMGVP/SISRdCyFc/foiYiElZ73bG90LPTIDAAAcQAAzoTDuIDICAIvMNzzhIVT9QVT9+iIyAgAMEgATQAAioUBPoAsi4CIQMMzAAANA4OCRSAQxLf0qJDA/oCJjERv/9j8Cht7/IUf9QqEgDANSYTSyYTYBaP3AAAB9DQwPUiE0siE2RhUAAACCISd8w4BhBAwSYCODbQIM4wa0AnIhJF0LkiEll7fgG3cLJyICAAAcQAAioSDuIIvMtjzkITP9QRL9+iIiAgDgMCQqRCEw/cLM/SokMkIA4ONBG/8hC/0yIhM3P9McMzJiE90HbQ8GHQEATAQyoAAiwURSYTRiYTWyYTZyYTMBQ/3AAAByITOB/fwioWCAh4JBHv0qKPoiDAMiwhiCYTIBO/3AAACCITIhGf1CpIAqKPoiDAMiwhgBNf3AAACoz4IhMvAqoCIiEYr/omEtImEuTQ9SITRiITVyITOyITbGAwAiD1gb/xAioDIiERszMmIRMiEuQC/ANzLmDAIpESkBrQIME+BDEZLBREr5mA9KQSop8CIRGzMpFJqqZrPlMeb8OiKMEvYqKyHW/EKm0EBHgoLIWCqIIqC8KiSCYSsMCXzzQmE5ImEwxkMAAF0L0iEkRgAA/QYsM8aZAACiISuCCgCCYTcWiA4QKKB4Ahv3+QL9CAwC8CIRImE4QiE4cCAEImEvC/9AIiBwcUFWX/4Mp4c3O3B4EZB3IAB3EXBwMUIhMHJhLwwacbb8ABhAAKqhKoRwiJDw+hFyo/+GAgAAQiEvqiJCWAD6iCe38gYgAHIhOSCAlIqHoqCwQan8qohAiJBymAzMZzJYDH0DMsP+IClBoaP88qSwxgoAIIAEgIfAQiE5fPeAhzCKhPCIgKCIkHKYDMx3MlgMMHMgMsP+giE3C4iCYTdCITcMuCAhQYeUyCAgBCB3wHz6IiE5cHowenIipLAqdyGO/CB3kJJXDEIhKxuZG0RCYStyIS6XFwLGvf+CIS0mKALGmQBGggAM4seyAsYwAJIhJdApwKYiAoYlACGj/OAwlEF9/CojQCKQIhIMADIRMCAxlvIAMCkxFjIFJzwCRiQAhhIAAAyjx7NEkZj8fPgAA0DgYJFgYAQgKDAqJpoiQCKQIpIMG3PWggYrYz0HZ7zdhgYAoiEnfMOgYQQMEmAjg20CHAPGdv4AANIhJF0LYiElZ73eIg0AGz0AHEAAIqEg7iCLzAzi3QPHMgLG2v8GCAAiDQEyzAgAE0AAMqEiDQDSzQIAHEAAIqEgIyAg7iDCzBAhdfzgMJRhT/wqI2AikDISDAAzETAgMZaiADA5MSAghEYJAAAAgWz8DKR89xs0AARA4ECRQEAEICcwKiSKImAikCKSDE0DliL+AANA4OCRMMzAImEoDPMnIxUhOvxyISj6MiFe/Bv/KiNyQgAGNAAAgiEoZrga3H8cCZJhKAYBANIhJF0LHBMhL/x89jliBkH+MVP8KiMiwvAiAgAiYSYnPB0GDgCiISd8w6BhBAwSYCODbQIcI8Y1/gAA0iEkXQtiISVnvd4b3QstIgIAciEmABxAACKhi8wg7iB3POGCISYxQPySISgMFgAYQABmoZozC2Yyw/DgJhBiAwAACEDg4JEqZiE5/IDMwCovDANmuQwxDPz6QzE1/Do0MgMATQZSYTRiYTWyYTYBSfzAAABiITVSITRq/7IhNoYAAAAMD3EB/EInEWInEmpkZ78Chnj/95YHhgIA0iEkXQscU0bJ/wDxIfwhIvw9D1JhNGJhNbJhNnJhMwE1/MAAAHIhMyEL/DInEUInEjo/ATD8wAAAsiE2YiE1UiE0Mer7KMMLIinD8ej7eM/WN7iGPgFiISUM4tA2wKZDDkG2+1A0wKYjAkZNAMYyAseyAoYuAKYjAkYlAEHc++AglEAikCISvAAyETAgMZYSATApMRZSBSc8AsYkAAYTAAAAAAyjx7NEfPiSpLAAA0DgYJFgYAQgKDAqJpoiQCKQIpIMG3PWggYrYz0HZ7zdhgYAciEnfMNwYQQMEmAjg20CHHPG1P0AANIhJF0LgiElh73eIg0AGz0AHEAAIqEg7iCLzAzi3QPHMgKG2/8GCAAAACINAYs8ABNAADKhIg0AK90AHEAAIqEgIyAg7iDCzBBBr/vgIJRAIpAiErwAIhEg8DGWjwAgKTHw8ITGCAAMo3z3YqSwGyMAA0DgMJEwMATw9zD682r/QP+Q8p8MPQKWL/4AAkDg4JEgzMAioP/3ogLGQACGAgAAHIMG0wDSISRdCyFp+ye17/JFAG0PG1VG6wAM4scyGTINASINAIAzESAjIAAcQAAioSDuICvdwswQMYr74CCUqiIwIpAiEgwAIhEgMDEgKTHWEwIMpBskAARA4ECRQEAEMDkwOjRBf/uKM0AzkDKTDE0ClvP9/QMAAkDg4JEgzMB3g3xioA7HNhpCDQEiDQCARBEgJCAAHEAAIqEg7iDSzQLCzBBBcPvgIJSqIkAikEISDABEEUAgMUBJMdYSAgymG0YABkDgYJFgYAQgKTAqJmFl+4oiYCKQIpIMbQSW8v0yRQAABEDg4JFAzMB3AggbVf0CRgIAAAAiRQErVQZz//BghGb2AoazACKu/ypmIYH74GYRaiIoAiJhJiF/+3IhJmpi+AYWhwV3PBzGDQCCISd8w4BhBAwSYCODbQIck4Zb/QDSISRdC5IhJZe93xvdCy0iAgCiISYAHEAAIqGLzCDuIKc84WIhJgwSABZAACKhCyLgIhBgzMAABkDg4JEq/wzix7IChjAAciEl0CfApiICxiUAQTP74CCUQCKQItIPIhIMADIRMCAxlgIBMCkxFkIFJzwChiQAxhIAAAAMo8ezRJFW+3z4AANA4GCRYGAEICgwKiaaIkAikCKSDBtz1oIGK2M9B2e83YYGAIIhJ3zDgGEEDBJgI4NtAhyjxiv9AADSISRdC5IhJZe93iINABs9ABxAACKhIO4gi8wM4t0DxzICBtv/BggAAAAiDQGLPAATQAAyoSINACvdABxAACKhICMgIO4gwswQYQb74CCUYCKQItIPMhIMADMRMCAxloIAMDkxICCExggAgSv7DKR89xs0AARA4ECRQEAEICcwKiSKImAikCKSDE0DliL+AANA4OCRMMzAMSH74CIRKjM4AzJhJjEf+6IhJiojKAIiYSgWCganPB5GDgByISd8w3BhBAwSYCODbQIcs8b3/AAAANIhJF0LgiElh73dG90LLSICAJIhJgAcQAAioYvMIO4glzzhoiEmDBIAGkAAIqFiISgLIuAiECpmAApA4OCRoMzAYmEocen6giEocHXAkiEsMeb6gCfAkCIQOiJyYSk9BSe1AT0CQZ36+jNtDze0bQYSACHH+ixTOWLGbQA8UyHE+n0NOWIMJgZsAF0L0iEkRgAA/QYhkvonteGiISliIShyISxgKsAx0PpwIhAqIyICABuqIkUAomEpG1ULb1Yf/QYMAAAyAgBixv0yRQAyAgEyRQEyAgI7IjJFAjtV9jbjFgYBMgIAMkUAZiYFIgIBIkUBalX9BqKgsHz5gqSwcqEABr3+IaP6KLIH4gIGl/zAICQnPCBGDwCCISd8w4BhBAwSYCODbQIsAwas/AAAXQvSISRGAAD9BpIhJZe92RvdCy0iAgAAHEAAIqGLzCDuIMAgJCc84cAgJAACQODgkXyCIMwQfQ1GAQAAC3fCzPiiISR3ugL2jPEht/oxt/pNDFJhNHJhM7JhNoWVAAsisiE2ciEzUiE0IO4QDA8WLAaGDAAAAIIhJ3zDgGEEDBJgI4NtAiyTBg8AciEkXQuSISWXt+AbdwsnIgIAABxAACKhIO4gi8y2jOTgMHTCzPjg6EEGCgCiISd8w6BhBAwSYCODbQIsoyFm+jliRg8AciEkXQtiISVnt9syBwAbd0Fg+hv/KKSAIhEwIiAppPZPCEbe/wByISRdCyFa+iwjOWIMBoYBAHIhJF0LfPYmFhVLJsxyhgMAAAt3wsz4giEkd7gC9ozxgU/6IX/6MX/6yXhNDFJhNGJhNXJhM4JhMrJhNgWHAIIhMpIhKKIhJgsimeiSISng4hCiaBByITOiISRSITSyITZiITX5+OJoFJJoFaDXwLDFwP0GllYOMWz6+NgtDEV/APDg9E0C8PD1fQwMeGIhNbIhNkYlAAAAkgIAogIC6umSAgHqmZru+v7iAgOampr/mp7iAgSa/5qe4gIFmv+anuICBpr/mp7iAgea/5ru6v+LIjqSRznAQCNBsCKwsJBgRgIAADICABsiOu7q/yo5vQJHM+8xTvotDkJhMWJhNXJhM4JhMrJhNoV2ADFI+u0CLQ8FdgBCITFyITOyITZAd8CCITJBQfpiITX9AoyHLQuwOMDG5v8AAAD/ESEI+urv6dL9BtxW+KLw7sB87+D3g0YCAAAAAAwM3Qzyr/0xNPpSISooI2IhJNAiwNBVwNpm0RD6KSM4DQsvUmEqcQ76ylMgLyBiYSRZDSAvBXA1wMyiQtOAUqABQCWDFpIAwQX6LQwFKgDJDYIhKtHs+Yz4KD0WsgDwLzHwIsDWIgDGhPvWjwAioMcpXQY6AABWTw4oPcwSRlH6IqDIhgAAIqDJKV3GTfooLYwSBkz6Ie75ARv6wAAAAR76wAAAhkf6yD3MHMZF+iKj6AEV+sAAAMAMAAZC+gDiYSIMfEaU+gEV+sAAAAwcDAMGCAAAyC34PfAsICAgtMwSxpv6Ri77Mi0DIi0ChTMAMqAADBwgw4PGKft4fWhtWF1ITTg9KC0MDAH7+cAAAO0CDBLgwpOGJfsAAAH1+cAAAAwMBh/7ACHI+UhdOC1JAiHG+TkCBvr/QcT5DAI4BMKgyDDCgykEQcD5PQwMHCkEMMKDBhP7xzICxvP9xvr9KD0WIvLGF/oCIUOSoRDCIULSIUHiIUDyIT+aEQ3wAAAIAABgHAAAYAAAAGAQAABgIfz/EsHw6QHAIADoAgkxySHZESH4/8AgAMgCwMB0nOzRmvlGBAAAADH0/8AgACgDOA0gIHTAAwALzGYM6ob0/yHv/wgxwCAA6QLIIdgR6AESwRAN8AAAAPgCAGAQAgBgAAIAYAAAAAgh/P/AIAA4AjAwJFZD/yH5/0H6/8AgADkCMff/wCAASQPAIABIA1Z0/8AgACgCDBMgIAQwIjAN8AAAgAAAAABA////AAQCAGASwfDJIcFw+QkxKEzZERbiCEX6/xaCCChMDPMMDSejDCgsMCIQDBMg04PQ0HQQESBF+P8WYv8h3v8x7v/AIAA5AsAgADgCVnP/Mdf/wCAAKAMgICRWQv8oLDHn/0AiESezFhwDDBLQI5M4TCAzwDlMOCwqIykshgkAQd3/MV750DSTQd7/wCAAImQAIcn/wCAAMmIAwCAAOAJWc/+G8P8ACDHIIdgREsEQDfAATEoAQBLB4MlhwUT5+TH4POlBCXHZUe0C97MB/QMWHwTYHNrf0NxBBgEAAABF8v8oTKYSBCgsJ63yBe3/FpL/KBxNDz0OAe7/wAAAICB0jDIioMQpXCgcSDz6IvBEwCkcSTwIcchh2FHoQfgxEsEgDfAAAAD/DwAAUSn5EsHwCTEMFEJFADBMQUklQfr/ORUpNTAwtEoiKiMgLEEpRQwCImUFAVv5wAAACDEyoMUgI5MSwRAN8AAAADA7AEASwfAJMTKgwDeSESKg2wH7/8AAACKg3EYEAAAAADKg2zeSCAH2/8AAACKg3QH0/8AAAAgxEsEQDfAAAAASwfDJIdkRCTHNAjrSRgIAACIMAMLMAcX6/9ec8wIhA8IhAtgREsEQDfAAAFgQAABwEAAAGJgAQBxLAEA0mABAAJkAQJH7/xLB4Mlh6UH5MQlx2VGQEcDtAiLREM0DAfX/wAAA8fn4hgoA3QzHvwHdD00NPQEtDgHw/8AAACAgdPxCTQ09ASLREAHs/8AAANDugNDMwFYc/SHl/zLREBAigAHn/8AAACHh/xwDGiIF9f8tDAYBAAAAIqBjkd3/mhEIcchh2FHoQfgxEsEgDfAAEsHwIqDACTEBuv/AAAAIMRLBEA3wAAAAbBAAAGgQAAB0EAAAeBAAAHwQAACAEAAAkBAAAJgPAECMOwBAEsHgkfz/+TH9AiHG/8lh2VEJcelBkBHAGiI5AjHy/ywCGjNJA0Hw/9LREBpEwqAAUmQAwm0aAfD/wAAAYer/Ib/4GmZoBmeyAsZJAC0NAbb/wAAAIbP/MeX/KkEaM0kDRj4AAABhr/8x3/8aZmgGGjPoA8AmwOeyAiDiIGHd/z0BGmZZBk0O8C8gAaj/wAAAMdj/ICB0GjNYA4yyDARCbRbtBMYSAAAAAEHR/+r/GkRZBAXx/z0OLQGF4/9F8P9NDj0B0C0gAZr/wAAAYcn/6swaZlgGIZP/GiIoAie8vDHC/1AswBozOAM3sgJG3f9G6v9CoABCTWwhuf8QIoABv//AAABWAv9huf8iDWwQZoA4BkUHAPfiEfZODkGx/xpE6jQiQwAb7sbx/zKv/jeSwSZOKSF7/9A9IBAigAF+/8AAAAXo/yF2/xwDGiJF2v9F5/8sAgGq+MAAAIYFAGFx/1ItGhpmaAZntchXPAIG2f/G7/8AkaD/mhEIcchh2FHoQfgxEsEgDfBdAkKgwCgDR5UOzDIMEgYHAAwCKQN84g3wJhIHJiIUxgwAAABCoNstBUeVKwwiKQOGCAAAIqDcJ5UJDBIpAy0EDfAAAEKg3XzyR5ULDBIpAyKg2w3wAHzyDfAAALYjMG0CUPZAQPNAR7UpUETAABRAADOhDAI3NgQwZsAbIvAiETAxQQtEVsT+NzYBGyIN8ACMkw3wNzYMDBIN8AAAAAAARElWMAwCDfC2IyhQ8kBA80BHtRdQRMAAFEAAM6E3MgIwIsAwMUFCxP9WBP83MgIwIsAN8MxTAAAARElWMAwCDfAAAAAAFEDmxAkgM4EAIqEN8AAAADKhDAIN8AA=", "text_start": 1074843648, "entry": 1074843652, "data": "CIH+PwUFBAACAwcAAwMLALnXEEDv1xBAHdgQQLrYEEBo5xBAHtkQQHTZEEDA2RBAaOcQQILaEED/2hBAwNsQQGjnEEBo5xBAWNwQQGjnEEA33xBAAOAQQDvgEEBo5xBAaOcQQNfgEEBo5xBAv+EQQGXiEECj4xBAY+QQQDTlEEBo5xBAaOcQQGjnEEBo5xBAYuYQQGjnEEBX5xBAkN0QQI/YEECm5RBAq9oQQPzZEEBo5xBA7OYQQDHnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQCLaEEBf2hBAvuUQQAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAkAAAANAAAAEQAAABkAAAAhAAAAMQAAAEEAAABhAAAAgQAAAMEAAAABAQAAgQEAAAECAAABAwAAAQQAAAEGAAABCAAAAQwAAAEQAAABGAAAASAAAAEwAAABQAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAAAAAAAAAAAAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAANAAAADwAAABEAAAATAAAAFwAAABsAAAAfAAAAIwAAACsAAAAzAAAAOwAAAEMAAABTAAAAYwAAAHMAAACDAAAAowAAAMMAAADjAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADAAAAAwAAAAMAAAAEAAAABAAAAAQAAAAEAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAABAREgAIBwkGCgULBAwDDQIOAQ8AAQEAAAEAAAAEAAAA", "data_start": 1073720488} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a764dbf..2653c20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -548,6 +548,11 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + debug@4.3.4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1560,6 +1565,11 @@ p-try@^1.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= +pako@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" + integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" From 2e855a7a537ce854a73cd78d48620c3726403436 Mon Sep 17 00:00:00 2001 From: mrfrase3 Date: Sat, 23 Jul 2022 17:56:08 +0800 Subject: [PATCH 2/6] main overall TS/linting pass --- package.json | 4 +- src/esp/ESPLoader.ts | 542 ++++++++++++++++++----------------- src/esp/StubLoader.ts | 11 +- src/esp/roms/esp32.ts | 166 +++++------ src/esp/roms/esp32c3.ts | 68 +++-- src/esp/roms/esp32s2.ts | 78 ++--- src/esp/roms/esp32s3beta2.ts | 24 +- src/esp/roms/esp8266.ts | 109 +++---- src/esp/stubs/esp32.json | 2 +- src/esp/stubs/esp32c3.json | 2 +- src/esp/stubs/esp32h2.json | 2 +- src/esp/stubs/esp32s2.json | 2 +- src/esp/stubs/esp32s3.json | 2 +- src/esp/stubs/esp8266.json | 2 +- yarn.lock | 18 +- 15 files changed, 549 insertions(+), 483 deletions(-) diff --git a/package.json b/package.json index a25f0ed..f1e1fa9 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "license": "UNLICENSED", "devDependencies": { "@types/chai": "^4.3.1", + "@types/crypto-js": "^4.1.1", "@types/mocha": "^9.1.1", - "@types/node": "^17.0.33", + "@types/node": "^18.0.6", + "@types/pako": "^2.0.0", "@typescript-eslint/eslint-plugin": "^5.23.0", "@typescript-eslint/parser": "^5.23.0", "axios": "^0.27.2", diff --git a/src/esp/ESPLoader.ts b/src/esp/ESPLoader.ts index 18f5bcc..316adbe 100644 --- a/src/esp/ESPLoader.ts +++ b/src/esp/ESPLoader.ts @@ -3,12 +3,23 @@ import pako from 'pako'; import CryptoJS from 'crypto-js'; import StubLoader from './StubLoader'; +import ESP32ROM from './roms/esp32'; +import ESP32C3ROM from './roms/esp32c3'; +import ESP32S2ROM from './roms/esp32s2'; +// import ESP32S3BETA2ROM from './roms/esp32s3beta2'; +import ESP8266ROM from './roms/esp8266'; + interface ESPOptions { quiet?: boolean; stubUrl?: string; stdout?: any; } +export interface UploadFileDef { + data: Buffer; + address: number; +} + export default class ESPLoader { ESP_RAM_BLOCK = 0x1800; @@ -17,13 +28,13 @@ export default class ESPLoader { ESP_FLASH_DATA = 0x03; ESP_FLASH_END = 0x04; - - ESP_MEMBEGIN = 0x05; - + + ESP_MEM_BEGIN = 0x05; + ESP_MEM_END = 0x06; - + ESP_MEM_DATA = 0x07; - + ESP_WRITE_REG = 0x09; ESP_FLASH_DEFL_BEGIN = 0x10; @@ -61,16 +72,22 @@ export default class ESPLoader { DETECTED_FLASH_SIZES = { 0x12: '256KB', 0x13: '512KB', 0x14: '1MB', 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB', - }; + } as { [key: number]: string }; opts: ESPOptions; quiet: boolean; serial: SerialPort; IS_STUB: boolean; - chip: ROMDef | null; + chip: typeof ESP32ROM + | typeof ESP32C3ROM + | typeof ESP32S2ROM + // | typeof ESP32S3BETA2ROM + | typeof ESP8266ROM + | null; stdout: any; stubLoader: StubLoader; syncStubDetected: boolean; + FLASH_WRITE_SIZE: number; constructor(serial: SerialPort, opts: ESPOptions) { this.opts = opts || {}; @@ -83,6 +100,7 @@ export default class ESPLoader { }; this.stubLoader = new StubLoader(this.opts.stubUrl); this.syncStubDetected = false; + this.FLASH_WRITE_SIZE = 0x4000 } #sleep(ms: number) { @@ -128,25 +146,6 @@ export default class ESPLoader { return c; } - #ui8ToBstr(u8Array: Uint8Array) { - let i; - const len = u8Array.length; - let b_str = ''; - for (i = 0; i < len; i++) { - b_str += String.fromCharCode(u8Array[i]); - } - return b_str; - } - - #bstrToUi8(bStr: string) { - const len = bStr.length; - const u8_array = new Uint8Array(len); - for (let i = 0; i < len; i++) { - u8_array[i] = bStr.charCodeAt(i); - } - return u8_array; - } - #flushInput = async () => { try { await this.serial.flush(); @@ -234,14 +233,14 @@ export default class ESPLoader { }); } - command = async ({ + async command({ op = null as number | null, data = [] as number[] | Uint8Array | Buffer, chk = 0, waitResponse = true, timeout = 3000, // min_data = 12, - } = {}): Promise<[number, Buffer]> => { + } = {}): Promise<[number, Buffer]> { // console.log("command "+ op + " " + wait_response + " " + timeout); if (op) { const pkt = Buffer.from([ @@ -273,7 +272,7 @@ export default class ESPLoader { return [0, Buffer.from([])]; } - readReg = async (addr: number, timeout = 3000) => { + async readReg(addr: number, timeout = 3000) { // console.log(`read reg ${addr} ${timeout}`); const pkt = this.#intToByteArray(addr); const val = await this.command({ op: this.ESP_READ_REG, data: pkt, timeout }); @@ -281,22 +280,23 @@ export default class ESPLoader { return val[0]; } - writeReg = async (addr: number, value: number, mask = 0xFFFFFFFF, delay_us = 0, delay_after_us = 0) => { + async writeReg(addr: number, value: number, mask = 0xFFFFFFFF, delayUs = 0, delayAfterUs = 0) { + if (!this.chip) throw new Error('Chip not initialized'); let pkt = this.#appendArray(this.#intToByteArray(addr), this.#intToByteArray(value)); pkt = this.#appendArray(pkt, this.#intToByteArray(mask)); - pkt = this.#appendArray(pkt, this.#intToByteArray(delay_us)); + pkt = this.#appendArray(pkt, this.#intToByteArray(delayUs)); - if (delay_after_us > 0) { + 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(delay_after_us)); + pkt = this.#appendArray(pkt, this.#intToByteArray(delayAfterUs)); } - await this.checkCommand({ op_description: 'write target memory', op: this.ESP_WRITE_REG, data: pkt }); + await this.checkCommand({ opDescription: 'write target memory', op: this.ESP_WRITE_REG, data: pkt }); } - sync = async () => { + async sync() { // console.log('Sync'); const cmd = new Uint8Array(36); let i; @@ -313,17 +313,17 @@ export default class ESPLoader { return resp; } - #connectAttempt = async ({ mode = 'default_reset', esp32r0_delay = false } = {}) => { - // console.log(`_connect_attempt ${esp32r0_delay}`); + async #connectAttempt({ mode = 'default_reset', esp32r0Delay = false } = {}) { + // console.log(`_connect_attempt ${esp32r0Delay}`); if (mode !== 'no_reset') { await this.serial.set({ dtr: false, rts: true }); await this.#sleep(100); - if (esp32r0_delay) { + if (esp32r0Delay) { // await this._sleep(1200); await this.#sleep(2000); } await this.serial.set({ dtr: true, rts: false }); - if (esp32r0_delay) { + if (esp32r0Delay) { // await this._sleep(400); } await this.#sleep(50); @@ -353,7 +353,7 @@ export default class ESPLoader { return 'success'; } catch (err) { if (err instanceof Error && err.message.includes('timeout')) { - this.logChar(esp32r0_delay ? '_' : '.'); + this.logChar(esp32r0Delay ? '_' : '.'); } } await this.#sleep(50); @@ -366,13 +366,12 @@ export default class ESPLoader { let resp = ''; this.logChar('Connecting...'); // await this.transport.connect(); - let success = false; await (new Array(attempts)).fill(0).reduce(async (promise) => { await promise; if (resp === 'success') return; - resp = await this.#connectAttempt({ esp32r0_delay: false }); + resp = await this.#connectAttempt({ esp32r0Delay: false }); if (resp === 'success') return; - resp = await this.#connectAttempt({ esp32r0_delay: true }); + resp = await this.#connectAttempt({ esp32r0Delay: true }); if (resp === 'success') return; }, Promise.resolve()); if (resp !== 'success') { @@ -388,8 +387,14 @@ export default class ESPLoader { const chip_magic_value = await this.readReg(0x40001000); // eslint-disable-next-line no-console // console.log(`Chip Magic ${chip_magic_value}`); - const chips = [ESP8266ROM, ESP32ROM, ESP32S2ROM, ESP32S3BETA2ROM, ESP32C3ROM]; - this.chip = chips.find((cls) => chip_magic_value === cls.CHIP_DETECT_MAGIC_VALUE); + const chips = [ + ESP8266ROM, + ESP32ROM, + ESP32S2ROM, + // ESP32S3BETA2ROM, + ESP32C3ROM + ]; + this.chip = chips.find((cls) => chip_magic_value === cls.CHIP_DETECT_MAGIC_VALUE) ?? null; // console.log('chip', this.chip); } return null; @@ -398,14 +403,14 @@ export default class ESPLoader { async detectChip() { await this.connect(); this.logChar('Detecting chip type... '); - if (this.chip != null) { + if (this.chip !== null) { this.log(this.chip.CHIP_NAME); } } async checkCommand({ // eslint-disable-next-line no-unused-vars - op_description = '', + opDescription = '', op = null as number | null, data = [] as number[] | Uint8Array | Buffer, chk = 0, @@ -428,7 +433,7 @@ export default class ESPLoader { 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({ op_description: 'write to target RAM', op: this.ESP_MEMBEGIN, data: pkt }); + await this.checkCommand({ opDescription: 'write to target RAM', op: this.ESP_MEM_BEGIN, data: pkt }); } checksum(data: number[] | Uint8Array | Buffer) { @@ -448,46 +453,51 @@ export default class ESPLoader { pkt = this.#appendArray(pkt, buffer); const checksum = this.checksum(buffer); await this.checkCommand({ - op_description: 'write to target RAM', op: this.ESP_MEM_DATA, data: pkt, chk: checksum, + opDescription: 'write to target RAM', op: this.ESP_MEM_DATA, data: pkt, chk: checksum, }); } - mem_finish = async (entrypoint: number) => { + async memFinish(entrypoint: number) { const is_entry = (entrypoint === 0) ? 1 : 0; const pkt = this.#appendArray(this.#intToByteArray(is_entry), this.#intToByteArray(entrypoint)); return this.checkCommand({ - op_description: 'leave RAM download mode', op: this.ESP_MEM_END, data: pkt, timeout: 500, min_data: 12, + opDescription: 'leave RAM download mode', + op: this.ESP_MEM_END, + data: pkt, + timeout: 500, + // min_data: 12, }); // XXX: handle non-stub with diff timeout } - flash_spi_attach = async (hspi_arg) => { - const pkt = this.#intToByteArray(hspi_arg); - await this.checkCommand({ op_description: 'configure SPI flash pins', op: this.ESP_SPI_ATTACH, data: pkt }); + async flashSpiAttach(hspiArg: number) { + const pkt = this.#intToByteArray(hspiArg); + await this.checkCommand({ opDescription: 'configure SPI flash pins', op: this.ESP_SPI_ATTACH, data: pkt }); } - timeout_per_mb = (seconds_per_mb, size_bytes) => { - const result = seconds_per_mb * (size_bytes / 1000000); + timeoutPerMb(secondsPerMb: number, sizeBytes: number) { + const result = secondsPerMb * (sizeBytes / 1000000); if (result < 3000) { return 3000; } return result; } - flash_begin = async (size, offset) => { - const num_blocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); - const erase_size = this.chip.get_erase_size(offset, size); + async flashBegin(size: number, offset: number) { + if (!this.chip) throw new Error('chip not initialized'); + 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.timeout_per_mb(this.ERASE_REGION_TIMEOUT_PER_MB, size); + timeout = this.timeoutPerMb(this.ERASE_REGION_TIMEOUT_PER_MB, size); } // eslint-disable-next-line no-console - // console.log(`flash begin ${erase_size} ${num_blocks} ${this.FLASH_WRITE_SIZE} ${offset} ${size}`); - let pkt = this.#appendArray(this.#intToByteArray(erase_size), this.#intToByteArray(num_blocks)); + // console.log(`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) { @@ -495,35 +505,35 @@ export default class ESPLoader { } await this.checkCommand({ - op_description: 'enter Flash download mode', op: this.ESP_FLASH_BEGIN, data: pkt, timeout, + opDescription: 'enter Flash download mode', op: this.ESP_FLASH_BEGIN, data: pkt, timeout, }); const t2 = d.getTime(); if (size !== 0 && this.IS_STUB === false) { this.log(`Took ${(t2 - t1) / 1000}.${(t2 - t1) % 1000}s to erase flash block`); } - return num_blocks; + return numBlocks; } - flash_defl_begin = async (size, compsize, offset) => { - const num_blocks = Math.floor((compsize + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); - const erase_blocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); + async flashDeflBegin(size: number, compSize: number, offset: number) { + if (!this.chip) throw new Error('chip not initialized'); + 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(); + const t1 = Date.now(); - let write_size; let - timeout; + let writeSize; + let timeout; if (this.IS_STUB) { - write_size = size; + writeSize = size; timeout = 3000; } else { - write_size = erase_blocks * this.FLASH_WRITE_SIZE; - timeout = this.timeout_per_mb(this.ERASE_REGION_TIMEOUT_PER_MB, write_size); + writeSize = eraseBlocks * this.FLASH_WRITE_SIZE; + timeout = this.timeoutPerMb(this.ERASE_REGION_TIMEOUT_PER_MB, writeSize); } - this.log(`Compressed ${size} bytes to ${compsize}...`); + this.log(`Compressed ${size} bytes to ${compSize}...`); - let pkt = this.#appendArray(this.#intToByteArray(write_size), this.#intToByteArray(num_blocks)); + 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)); @@ -534,19 +544,19 @@ export default class ESPLoader { pkt = this.#appendArray(pkt, this.#intToByteArray(0)); } if (this.chip.CHIP_NAME === 'ESP8266') { - await this.flush_input(); + await this.#flushInput(); } await this.checkCommand({ - op_description: 'enter compressed flash mode', op: this.ESP_FLASH_DEFL_BEGIN, data: pkt, timeout, + opDescription: 'enter compressed flash mode', op: this.ESP_FLASH_DEFL_BEGIN, data: pkt, timeout, }); - const t2 = d.getTime(); + const t2 = Date.now(); if (size !== 0 && this.IS_STUB === false) { this.log(`Took ${(t2 - t1) / 1000}.${(t2 - t1) % 1000}s to erase flash block`); } - return num_blocks; + return numBlocks; } - flash_block = async (data, seq, timeout) => { + async flashBlock(data: Uint8Array, seq: number, timeout: number) { 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)); @@ -555,21 +565,21 @@ export default class ESPLoader { const checksum = this.checksum(data); await this.checkCommand({ - op_description: `write to target Flash after seq ${seq}`, op: this.ESP_FLASH_DATA, data: pkt, chk: checksum, timeout, + opDescription: `write to target Flash after seq ${seq}`, op: this.ESP_FLASH_DATA, data: pkt, chk: checksum, timeout, }); } - flash_defl_block = async (data, seq, timeout) => { + async flashDeflBlock(data: Uint8Array, seq: number, timeout: number) { 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); - // console.log(`flash_defl_block ${data[0].toString(16)}`, +' ' + data[1].toString(16)); + // console.log(`flashDeflBlock ${data[0].toString(16)}`, +' ' + data[1].toString(16)); await this.checkCommand({ - op_description: `write compressed data to flash after seq ${seq}`, + opDescription: `write compressed data to flash after seq ${seq}`, op: this.ESP_FLASH_DEFL_DATA, data: pkt, chk: checksum, @@ -577,21 +587,22 @@ export default class ESPLoader { }); } - flash_finish = async ({ reboot = false } = {}) => { + async flashFinish({ reboot = false } = {}) { const val = reboot ? 0 : 1; const pkt = this.#intToByteArray(val); - await this.checkCommand({ op_description: 'leave Flash mode', op: this.ESP_FLASH_END, data: pkt }); + await this.checkCommand({ opDescription: 'leave Flash mode', op: this.ESP_FLASH_END, data: pkt }); } - flash_defl_finish = async ({ reboot = false } = {}) => { + async flashDeflFinish({ reboot = false } = {}) { const val = reboot ? 0 : 1; const pkt = this.#intToByteArray(val); - await this.checkCommand({ op_description: 'leave compressed flash mode', op: this.ESP_FLASH_DEFL_END, data: pkt }); + await this.checkCommand({ opDescription: 'leave compressed flash mode', op: this.ESP_FLASH_DEFL_END, data: pkt }); } - run_spiflash_command = async (spiflash_command, data, read_bits) => { + async runSpiFlashCommand(spiFlashCommand: number, data: Uint8Array, readBits: number) { + if (!this.chip) throw new Error('chip not initialized'); // SPI_USR register flags const SPI_USR_COMMAND = (1 << 31); const SPI_USR_MISO = (1 << 28); @@ -605,72 +616,72 @@ export default class ESPLoader { const SPI_USR2_REG = base + this.chip.SPI_USR2_OFFS; const SPI_W0_REG = base + this.chip.SPI_W0_OFFS; - let set_data_lengths; + let setDataLengths; if (this.chip.SPI_MOSI_DLEN_OFFS != null) { - set_data_lengths = async (mosi_bits, miso_bits) => { - 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 (mosi_bits > 0) { - await this.write_reg({ addr: SPI_MOSI_DLEN_REG, value: (mosi_bits - 1) }); + setDataLengths = async (mosiBits: number, misoBits: number) => { + const SPI_MOSI_DLEN_REG = base + (this.chip?.SPI_MOSI_DLEN_OFFS || 0); + const SPI_MISO_DLEN_REG = base + (this.chip?.SPI_MISO_DLEN_OFFS || 0); + if (mosiBits > 0) { + await this.writeReg(SPI_MOSI_DLEN_REG, mosiBits - 1); } - if (miso_bits > 0) { - await this.write_reg({ addr: SPI_MISO_DLEN_REG, value: (miso_bits - 1) }); + if (misoBits > 0) { + await this.writeReg(SPI_MISO_DLEN_REG, misoBits - 1); } }; } else { - set_data_lengths = async (mosi_bits, miso_bits) => { + setDataLengths = async (mosiBits: number, misoBits: number) => { const SPI_DATA_LEN_REG = SPI_USR1_REG; - const SPI_MOSI_BITLEN_S = 17; - const SPI_MISO_BITLEN_S = 8; - const mosi_mask = (mosi_bits === 0) ? 0 : (mosi_bits - 1); - const miso_mask = (miso_bits === 0) ? 0 : (miso_bits - 1); - const val = (miso_mask << SPI_MISO_BITLEN_S) | (mosi_mask << SPI_MOSI_BITLEN_S); - await this.write_reg({ addr: SPI_DATA_LEN_REG, value: val }); + const SPI_MOSI_BIT_LEN_S = 17; + const SPI_MISO_BIT_LEN_S = 8; + const mosi_mask = (mosiBits === 0) ? 0 : (mosiBits - 1); + const miso_mask = (misoBits === 0) ? 0 : (misoBits - 1); + const val = (miso_mask << SPI_MISO_BIT_LEN_S) | (mosi_mask << SPI_MOSI_BIT_LEN_S); + await this.writeReg(SPI_DATA_LEN_REG, val); }; } const SPI_CMD_USR = (1 << 18); const SPI_USR2_COMMAND_LEN_SHIFT = 28; - if (read_bits > 32) { - throw 'Reading more than 32 bits back from a SPI flash operation is unsupported'; + if (readBits > 32) { + throw new Error('Reading more than 32 bits back from a SPI flash operation is unsupported'); } if (data.length > 64) { - throw 'Writing more than 64 bytes of data with one SPI command is unsupported'; + throw new Error('Writing more than 64 bytes of data with one SPI command is unsupported'); } - const data_bits = data.length * 8; - const old_spi_usr = await this.read_reg({ addr: SPI_USR_REG }); - const old_spi_usr2 = await this.read_reg({ addr: SPI_USR2_REG }); + 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 (read_bits > 0) { + if (readBits > 0) { flags |= SPI_USR_MISO; } - if (data_bits > 0) { + if (dataBits > 0) { flags |= SPI_USR_MOSI; } - await set_data_lengths(data_bits, read_bits); - await this.write_reg({ addr: SPI_USR_REG, value: flags }); - let val = (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command; - await this.write_reg({ addr: SPI_USR2_REG, value: val }); - if (data_bits === 0) { - await this.write_reg({ addr: SPI_W0_REG, value: 0 }); + 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); // eslint-disable-next-line no-param-reassign data = this.#appendArray(data, padding); } - let next_reg = SPI_W0_REG; + 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.write_reg({ addr: next_reg, value: val }); - next_reg += 4; + val = this.#byteArrayToInt([data[i], data[i + 1], data[i + 2], data[i + 3]]); + await this.writeReg(nextReg, val); + nextReg += 4; } } - await this.write_reg({ addr: SPI_CMD_REG, value: SPI_CMD_USR }); + await this.writeReg(SPI_CMD_REG, SPI_CMD_USR); for (i = 0; i < 10; i++) { - val = await this.read_reg({ addr: SPI_CMD_REG }) & SPI_CMD_USR; + val = await this.readReg(SPI_CMD_REG) & SPI_CMD_USR; if (val === 0) { break; } @@ -678,60 +689,60 @@ export default class ESPLoader { if (i === 10) { throw 'SPI command did not complete in time'; } - const stat = await this.read_reg({ addr: SPI_W0_REG }); - await this.write_reg({ addr: SPI_USR_REG, value: old_spi_usr }); - await this.write_reg({ addr: SPI_USR2_REG, value: old_spi_usr2 }); + 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 = async () => { - const SPIFLASH_RDID = 0x9F; + async readFlashId() { + const SPI_FLASH_RDID = 0x9F; const pkt = new Uint8Array(0); - return this.run_spiflash_command(SPIFLASH_RDID, pkt, 24); + return this.runSpiFlashCommand(SPI_FLASH_RDID, pkt, 24); } - erase_flash = async () => { + async eraseFlash() { this.log('Erasing flash (this may take a while)...'); - let d = new Date(); - const t1 = d.getTime(); + const t1 = Date.now(); const ret = await this.checkCommand({ - op_description: 'erase flash', + opDescription: 'erase flash', op: this.ESP_ERASE_FLASH, timeout: this.CHIP_ERASE_TIMEOUT, }); - d = new Date(); - const t2 = d.getTime(); + const t2 = Date.now(); this.log(`Chip erase completed successfully in ${(t2 - t1) / 1000}s`); return ret; } - toHex(buffer) { - return Array.prototype.map.call(buffer, (x) => (`00${x.toString(16)}`).slice(-2)).join(''); + toHex(buffer: Buffer) { + return buffer.toString('hex'); } - flash_md5sum = async (addr, size) => { - const timeout = this.timeout_per_mb(this.MD5_TIMEOUT_PER_MB, size); + async flashMd5sum(addr: number, size: number) { + 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({ - op_description: 'calculate md5sum', op: this.ESP_SPI_FLASH_MD5, data: pkt, timeout, min_data: 26, + opDescription: 'calculate md5sum', op: this.ESP_SPI_FLASH_MD5, data: pkt, timeout, // min_data: 26, }); + if (typeof res === 'number') throw new Error('Invalid response to md5sum command'); if (res.length > 16) { - res = res.slice(0, 16); + res = res.subarray(0, 16); } const strmd5 = this.toHex(res); return strmd5; } - run_stub = async () => { + async runStub() { + if (!this.chip) throw new Error('Chip not initialized'); this.log('Fetching stub...'); - const stub = await this._loadStub(); + const stub = await this.stubLoader.loadStub(this.chip.CHIP_NAME); // console.log(stub); const { - data, text, data_start, text_start, entry, + data, text, dataStart, textStart, entry, } = stub; this.log('Uploading stub...'); @@ -739,32 +750,32 @@ export default class ESPLoader { let blocks = Math.floor((text.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); let i; - await this.memBegin(text.length, blocks, this.ESP_RAM_BLOCK, text_start); + await this.memBegin(text.length, blocks, this.ESP_RAM_BLOCK, textStart); for (i = 0; i < blocks; i++) { - const from_offs = i * this.ESP_RAM_BLOCK; - let to_offs = from_offs + this.ESP_RAM_BLOCK; - if (to_offs > text.length) to_offs = text.length; - await this.memBlock(text.slice(from_offs, to_offs), i); + const fromOffs = i * this.ESP_RAM_BLOCK; + let toOffs = fromOffs + this.ESP_RAM_BLOCK; + if (toOffs > text.length) toOffs = text.length; + await this.memBlock(text.subarray(fromOffs, toOffs), i); } blocks = Math.floor((data.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); - await this.memBegin(data.length, blocks, this.ESP_RAM_BLOCK, data_start); + await this.memBegin(data.length, blocks, this.ESP_RAM_BLOCK, dataStart); for (i = 0; i < blocks; i++) { - const from_offs = i * this.ESP_RAM_BLOCK; - let to_offs = from_offs + this.ESP_RAM_BLOCK; - if (to_offs > data.length) to_offs = data.length; - await this.memBlock(data.slice(from_offs, to_offs), i); + const fromOffs = i * this.ESP_RAM_BLOCK; + let toOffs = fromOffs + this.ESP_RAM_BLOCK; + if (toOffs > data.length) toOffs = data.length; + await this.memBlock(data.subarray(fromOffs, toOffs), i); } this.log('Running stub...'); let valid = false; - await this.mem_finish(entry); + await this.memFinish(entry); if (this.chip.CHIP_NAME === 'ESP8266') { const [reply] = await this.sync(); if (reply === 0) valid = true; } else { - const res = await this.transport.read({ timeout: 1000, min_data: 6 }); + const res = await this.serial.read(6); // { timeout: 1000, min_data: 6 }); if (res[0] === 79 && res[1] === 72 && res[2] === 65 && res[3] === 73) { valid = true; } @@ -780,60 +791,64 @@ export default class ESPLoader { return null; } - main_fn = async () => { - await this.detect_chip(); + async mainFn() { + await this.detectChip(); if (this.chip == null) { this.log('Error in connecting to board'); return; } - const chip = await this.chip.get_chip_description(this); + const chip = await this.chip.getChipDescription(this); this.log(`Chip is ${chip}`); - this.log(`Features: ${await this.chip.get_chip_features(this)}`); - this.log(`Crystal is ${await this.chip.get_crystal_freq(this)}MHz`); - this.log(`MAC: ${await this.chip.read_mac(this)}`); - await this.chip.read_mac(this); + this.log(`Features: ${await this.chip.getChipFeatures(this)}`); + this.log(`Crystal is ${await this.chip.getCrystalFreq(this)}MHz`); + this.log(`MAC: ${await this.chip.readMac(this)}`); + await this.chip.readMac(this); - if (this.chip.IS_STUB) await this.run_stub(); + if (this.chip.IS_STUB) await this.runStub(); else this.FLASH_WRITE_SIZE = this.chip.FLASH_WRITE_SIZE || 0x4000; } - flash_size_bytes = (flash_size) => { - let flash_size_b = -1; - if (flash_size.indexOf('KB') !== -1) { - flash_size_b = parseInt(flash_size.slice(0, flash_size.indexOf('KB')), 10) * 1024; - } else if (flash_size.indexOf('MB') !== -1) { - flash_size_b = parseInt(flash_size.slice(0, flash_size.indexOf('MB')), 10) * 1024 * 1024; + flashSizeBytes(flashSize: string) { + let flashSizeB = -1; + if (flashSize.indexOf('KB') !== -1) { + flashSizeB = parseInt(flashSize.slice(0, flashSize.indexOf('KB')), 10) * 1024; + } else if (flashSize.indexOf('MB') !== -1) { + flashSizeB = parseInt(flashSize.slice(0, flashSize.indexOf('MB')), 10) * 1024 * 1024; } - return flash_size_b; + return flashSizeB; } - pad_array = (arr, len, fillValue) => Object.assign(new Array(len).fill(fillValue), arr) + padArray(arr: any[], len: number, fillValue: any) { + return Object.assign(new Array(len).fill(fillValue), arr); + } - parse_flash_size_arg = (flsz) => { - if (typeof this.chip.FLASH_SIZES[flsz] === 'undefined') { - this.log(`Flash size ${flsz} is not supported by this chip type. Supported sizes: ${this.chip.FLASH_SIZES}`); - throw 'Invalid flash size'; + parseFlashSizeArg(flashSize: string) { + if (!this.chip) throw new Error('Chip not initialized'); + if (!this.chip.FLASH_SIZES[flashSize]) { + this.log(`Flash size ${flashSize} is not supported by this chip type. Supported sizes: ${this.chip.FLASH_SIZES}`); + throw new Error('Invalid flash size'); } - return this.chip.FLASH_SIZES[flsz]; + return this.chip.FLASH_SIZES[flashSize]; } - _update_image_flash_params = (image, address, flash_size, flash_mode, flash_freq) => { - // console.log(`_update_image_flash_params ${flash_size} ${flash_mode} ${flash_freq}`); + #updateImageFlashParams = (image: Buffer, address: number, flashSize: string, flashMode: string, flashFreq: string) => { + if (!this.chip) throw new Error('Chip not initialized'); + // console.log(`_update_image_flashParams ${flashSize} ${flashMode} ${flashFreq}`); if (image.length < 8) { return image; } if (address !== this.chip.BOOTLOADER_FLASH_OFFSET) { return image; } - if (flash_size === 'keep' && flash_mode === 'keep' && flash_freq === 'keep') { + if (flashSize === 'keep' && flashMode === 'keep' && flashFreq === 'keep') { // console.log('Not changing the image'); return image; } const magic = image[0]; - let a_flash_mode = image[2]; - const flash_size_freq = image[3]; + let aFlashMode = image[2]; + const flashSizeFreq = image[3]; if (magic !== this.ESP_IMAGE_MAGIC) { this.log(`Warning: Image file at 0x${ address.toString(16) @@ -843,69 +858,72 @@ export default class ESPLoader { /* XXX: Yet to implement actual image verification */ - if (flash_mode !== 'keep') { - const flash_modes = { + if (flashMode !== 'keep') { + const flashModes = { qio: 0, qout: 1, dio: 2, dout: 3, - }; - a_flash_mode = flash_modes[flash_mode]; + } as { [key: string]: number }; + aFlashMode = flashModes[flashMode]; } - let a_flash_freq = flash_size_freq & 0x0F; - if (flash_freq !== 'keep') { - const flash_freqs = { + let aFlashFreq = flashSizeFreq & 0x0F; + if (flashFreq !== 'keep') { + const flashFreqs = { '40m': 0, '26m': 1, '20m': 2, '80m': 0xf, - }; - a_flash_freq = flash_freqs[flash_freq]; + } as { [key: string]: number }; + aFlashFreq = flashFreqs[flashFreq]; } - let a_flash_size = flash_size_freq & 0xF0; - if (flash_size !== 'keep') { - a_flash_size = this.parse_flash_size_arg(flash_size); + let aFlashSize = flashSizeFreq & 0xF0; + if (flashSize !== 'keep') { + aFlashSize = this.parseFlashSizeArg(flashSize); } - const flash_params = (a_flash_mode << 8) | (a_flash_freq + a_flash_size); - this.log(`Flash params set to ${flash_params.toString(16)}`); - if (image[2] !== (a_flash_mode << 8)) { + const flashParams = (aFlashMode << 8) | (aFlashFreq + aFlashSize); + this.log(`Flash params set to ${flashParams.toString(16)}`); + if (image[2] !== (aFlashMode << 8)) { // eslint-disable-next-line no-param-reassign - image[2] = (a_flash_mode << 8); + image[2] = (aFlashMode << 8); } - if (image[3] !== (a_flash_freq + a_flash_size)) { + if (image[3] !== (aFlashFreq + aFlashSize)) { // eslint-disable-next-line no-param-reassign - image[3] = (a_flash_freq + a_flash_size); + image[3] = (aFlashFreq + aFlashSize); } return image; } - write_flash = async ({ - fileArray = [], flash_size = 'keep', flash_mode = 'keep', flash_freq = 'keep', erase_all = false, compress = true, - } = {}) => { + async writeFlash({ + fileArray = [] as UploadFileDef[], + flashSize = 'keep', + flashMode = 'keep', + flashFreq = 'keep', + eraseAll = false, + compress = true, + } = {}) { + if (!this.chip) throw new Error('Chip not initialized, make sure you call connect() first'); // console.log('EspLoader program'); - if (flash_size !== 'keep') { - const flash_end = this.flash_size_bytes(flash_size); - for (let i = 0; i < fileArray.length; i++) { - if ((fileArray[i].data.length + fileArray[i].address) > flash_end) { - this.log("Specified file doesn't fit in the available flash"); - return; + if (flashSize !== 'keep') { + const flashEnd = this.flashSizeBytes(flashSize); + fileArray.forEach((file) => { + if ((file.data.length + file.address) > flashEnd) { + throw new Error('Specified file doesn\'t fit in the available flash'); } - } + }); } - if (this.IS_STUB === true && erase_all === true) { - this.erase_flash(); + if (this.IS_STUB === true && eraseAll === true) { + await this.eraseFlash(); } - let image; - let address; - for (let i = 0; i < fileArray.length; i++) { + await fileArray.reduce(async (prev, file) => { + await prev; + if (!this.chip) throw new Error('Chip not initialized'); + const { address } = file; // console.log(`Data Length ${fileArray[i].data.length}`); // image = this.pad_array(fileArray[i].data, Math.floor((fileArray[i].data.length + 3)/4) * 4, 0xff); // XXX : handle padding - image = fileArray[i].data; - address = fileArray[i].address; // console.log(`Image Length ${image.length}`); - if (image.length === 0) { + if (file.data.length === 0) { this.log('Warning: File is empty'); - // eslint-disable-next-line no-continue - continue; + return; } - image = this._update_image_flash_params(image, address, flash_size, flash_mode, flash_freq); + let image = this.#updateImageFlashParams(file.data, address, flashSize, flashMode, flashFreq); const calcmd5 = CryptoJS.MD5(CryptoJS.enc.Base64.parse(image.toString('base64'))); // console.log(`Image MD5 ${calcmd5}`); const uncsize = image.length; @@ -913,46 +931,45 @@ export default class ESPLoader { // console.log(image); if (compress) { // const uncimage = this.bstrToUi8(image); - image = pako.deflate(image, { level: 9 }); + image = Buffer.from(pako.deflate(image, { level: 9 })); // console.log('Compressed image '); // console.log(image); - blocks = await this.flash_defl_begin(uncsize, image.length, address); + blocks = await this.flashDeflBegin(uncsize, image.length, address); } else { - blocks = await this.flash_begin(uncsize, address); + blocks = await this.flashBegin(uncsize, address); } let seq = 0; - let bytes_sent = 0; + let bytesSent = 0; // const bytes_written = 0; - let d = new Date(); - const t1 = d.getTime(); + const t1 = Date.now(); let timeout = 5000; while (image.length > 0) { // console.log(`Write loop ${address} ${seq} ${blocks}`); - this.write_char(`\rWriting at 0x${ + this.logChar(`\rWriting at 0x${ (address + (seq * this.FLASH_WRITE_SIZE)).toString(16) }... (${ Math.floor(100 * ((seq + 1) / blocks)) }%)`); - let block = image.slice(0, this.FLASH_WRITE_SIZE); + let block = image.subarray(0, this.FLASH_WRITE_SIZE); if (compress) { /* - let block_uncompressed = pako.inflate(block).length; - //let len_uncompressed = block_uncompressed.length; - bytes_written += block_uncompressed; - if (this.timeout_per_mb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed) > 3000) { - block_timeout = this.timeout_per_mb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed); - } else { - block_timeout = 3000; - } */ // XXX: Partial block inflate seems to be unsupported in Pako. Hardcoding timeout - const block_timeout = 5000; + let block_uncompressed = pako.inflate(block).length; + //let len_uncompressed = block_uncompressed.length; + bytes_written += block_uncompressed; + if (this.timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed) > 3000) { + block_timeout = this.timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed); + } else { + block_timeout = 3000; + } */ // XXX: Partial block inflate seems to be unsupported in Pako. Hardcoding timeout + const blockTimeout = 5000; if (this.IS_STUB === false) { - timeout = block_timeout; + timeout = blockTimeout; } - await this.flash_defl_block(block, seq, timeout); + await this.flashDeflBlock(block, seq, timeout); if (this.IS_STUB) { - timeout = block_timeout; + timeout = blockTimeout; } } else { // this.log('Yet to handle Non Compressed writes'); @@ -965,26 +982,25 @@ export default class ESPLoader { // if encrypted: // esp.flash_encrypt_block(block, seq) // else: - // esp.flash_block(block, seq) + // esp.flashBlock(block, seq) // bytes_written += len(block) - await this.flash_block(block, seq, timeout); + await this.flashBlock(block, seq, timeout); } - bytes_sent += block.length; - image = image.slice(this.FLASH_WRITE_SIZE, image.length); + bytesSent += block.length; + image = image.subarray(this.FLASH_WRITE_SIZE, image.length); seq++; } if (this.IS_STUB) { - await this.read_reg({ addr: this.CHIP_DETECT_MAGIC_REG_ADDR, timeout }); + await this.readReg(this.CHIP_DETECT_MAGIC_REG_ADDR, timeout); } - d = new Date(); - const t = d.getTime() - t1; + const t = Date.now() - t1; this.log(''); this.log(`Wrote ${uncsize} bytes${ - compress ? ` (${bytes_sent} compressed)` : '' + compress ? ` (${bytesSent} compressed)` : '' } at 0x${address.toString(16)} in ${t / 1000} seconds.`); - this._sleep(100); + await this.#sleep(100); if (this.IS_STUB || this.chip.CHIP_NAME !== 'ESP8266') { - const res = await this.flash_md5sum(address, uncsize); + const res = await this.flashMd5sum(address, uncsize); if (`${res}` !== `${calcmd5}`) { this.log(`File md5: ${calcmd5}`); this.log(`Flash md5: ${res}`); @@ -992,25 +1008,25 @@ export default class ESPLoader { this.log('Hash of data verified.'); } } - } + }, Promise.resolve()); this.log('Leaving...'); if (this.IS_STUB) { - await this.flash_begin(0, 0); + await this.flashBegin(0, 0); if (compress) { - await this.flash_defl_finish(); + await this.flashDeflFinish(); } else { - await this.flash_finish(); + await this.flashFinish(); } } } - flash_id = async () => { + async flashId() { // console.log('flash_id'); - const flashid = await this.read_flash_id(); - this.log(`Manufacturer: ${(flashid & 0xff).toString(16)}`); - const flid_lowbyte = (flashid >> 16) & 0xff; - this.log(`Device: ${((flashid >> 8) & 0xff).toString(16)}${flid_lowbyte.toString(16)}`); - this.log(`Detected flash size: ${this.DETECTED_FLASH_SIZES[flid_lowbyte] || 'Unknown'}`); + const flashId = await this.readFlashId(); + this.log(`Manufacturer: ${(flashId & 0xff).toString(16)}`); + const idLowByte = (flashId >> 16) & 0xff; + this.log(`Device: ${((flashId >> 8) & 0xff).toString(16)}${idLowByte.toString(16)}`); + this.log(`Detected flash size: ${this.DETECTED_FLASH_SIZES[idLowByte] || 'Unknown'}`); } } diff --git a/src/esp/StubLoader.ts b/src/esp/StubLoader.ts index 404415b..7c40ed5 100644 --- a/src/esp/StubLoader.ts +++ b/src/esp/StubLoader.ts @@ -4,8 +4,8 @@ interface StubDef { data: Buffer; text: Buffer; entry: number; - text_start: number; - data_start: number; + textStart: number; + dataStart: number; } interface StubCache { @@ -18,7 +18,8 @@ export default class StubLoader { stubsUrl: string constructor(stubsUrl?: string) { - this.stubsUrl = stubsUrl || 'https://raw.githubusercontent.com/duinoapp/duinoapp-client/master/public/stubs'; + // TODO; Change branch from esp-support to main + this.stubsUrl = stubsUrl || 'https://raw.githubusercontent.com/duinoapp/upload-multitool/esp-support/src/esp/stubs/'; this.stubsUrl = this.stubsUrl.replace(/\/$/, ''); } @@ -33,8 +34,8 @@ export default class StubLoader { data: Buffer.from(res.data, 'base64'), text: Buffer.from(res.text, 'base64'), entry: res.entry, - text_start: res.text_start, - data_start: res.data_start, + textStart: res.textStart, + dataStart: res.dataStart, } as StubDef; cache[stubName] = stub; diff --git a/src/esp/roms/esp32.ts b/src/esp/roms/esp32.ts index f3cb4b3..117b1e6 100644 --- a/src/esp/roms/esp32.ts +++ b/src/esp/roms/esp32.ts @@ -1,3 +1,9 @@ +import ESPLoader from '../ESPLoader'; + +interface flashSizes { + [key: string]: number; +} + export default class ESP32ROM { static CHIP_NAME = 'ESP32'; @@ -25,7 +31,7 @@ export default class ESP32ROM { static FLASH_SIZES = { '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, - }; + } as flashSizes; static SPI_REG_BASE = 0x3ff42000; @@ -41,30 +47,30 @@ export default class ESP32ROM { static SPI_MISO_DLEN_OFFS = 0x2c; - static read_efuse = async (loader, offset) => { + static async readEfuse(loader: ESPLoader, offset: number) { const addr = this.EFUSE_RD_REG_BASE + (4 * offset); // console.log(`Read efuse ${addr}`); - return loader.read_reg({ addr }); + return loader.readReg(addr); } - static get_pkg_version = async (loader) => { - const word3 = await this.read_efuse(loader, 3); - let pkg_version = (word3 >> 9) & 0x07; - pkg_version += ((word3 >> 2) & 0x1) << 3; - return pkg_version; + static async getPkgVersion(loader: ESPLoader) { + const word3 = await this.readEfuse(loader, 3); + let pkgVersion = (word3 >> 9) & 0x07; + pkgVersion += ((word3 >> 2) & 0x1) << 3; + return pkgVersion; } - static get_chip_revision = async (loader) => { - const word3 = await this.read_efuse(loader, 3); - const word5 = await this.read_efuse(loader, 5); - const apb_ctl_date = await loader.read_reg({ addr: this.DR_REG_SYSCON_BASE + 0x7C }); - - const rev_bit0 = (word3 >> 15) & 0x1; - const rev_bit1 = (word5 >> 20) & 0x1; - const rev_bit2 = (apb_ctl_date >> 31) & 0x1; - if (rev_bit0 !== 0) { - if (rev_bit1 !== 0) { - if (rev_bit2 !== 0) { + static async getChipRevision(loader: ESPLoader) { + const word3 = await this.readEfuse(loader, 3); + const word5 = await this.readEfuse(loader, 5); + const apbCtlDate = await loader.readReg(this.DR_REG_SYSCON_BASE + 0x7C); + + const revBit0 = (word3 >> 15) & 0x1; + const revBit1 = (word5 >> 20) & 0x1; + const revBit2 = (apbCtlDate >> 31) & 0x1; + if (revBit0 !== 0) { + if (revBit1 !== 0) { + if (revBit2 !== 0) { return 3; } return 2; @@ -74,111 +80,111 @@ export default class ESP32ROM { return 0; } - static get_chip_description = async (loader) => { - const chip_desc = ['ESP32-D0WDQ6', 'ESP32-D0WD', 'ESP32-D2WD', '', 'ESP32-U4WDH', 'ESP32-PICO-D4', 'ESP32-PICO-V3-02']; - let chip_name = ''; - const pkg_version = await this.get_pkg_version(loader); - const chip_revision = await this.get_chip_revision(loader); - const rev3 = (chip_revision === 3); - const single_core = await this.read_efuse(loader, 3) & (1 << 0); - - if (single_core !== 0) { - chip_desc[0] = 'ESP32-S0WDQ6'; - chip_desc[1] = 'ESP32-S0WD'; + static async getChipDescription(loader: ESPLoader) { + const chipDesc = ['ESP32-D0WDQ6', 'ESP32-D0WD', 'ESP32-D2WD', '', 'ESP32-U4WDH', 'ESP32-PICO-D4', 'ESP32-PICO-V3-02']; + let chipName = ''; + const pkgVersion = await this.getPkgVersion(loader); + const chipRevision = await this.getChipRevision(loader); + const rev3 = (chipRevision === 3); + const singleCore = await this.readEfuse(loader, 3) & (1 << 0); + + if (singleCore !== 0) { + chipDesc[0] = 'ESP32-S0WDQ6'; + chipDesc[1] = 'ESP32-S0WD'; } if (rev3) { - chip_desc[5] = 'ESP32-PICO-V3'; + chipDesc[5] = 'ESP32-PICO-V3'; } - if (pkg_version >= 0 && pkg_version <= 6) { - chip_name = chip_desc[pkg_version]; + if (pkgVersion >= 0 && pkgVersion <= 6) { + chipName = chipDesc[pkgVersion]; } else { - chip_name = 'Unknown ESP32'; + chipName = 'Unknown ESP32'; } - if (rev3 && (pkg_version === 0 || pkg_version === 1)) { - chip_name += '-V3'; + if (rev3 && (pkgVersion === 0 || pkgVersion === 1)) { + chipName += '-V3'; } - return `${chip_name} (revision ${chip_revision})`; + return `${chipName} (revision ${chipRevision})`; } - static get_chip_features = async (loader) => { + static async getChipFeatures(loader: ESPLoader) { const features = ['Wi-Fi']; - const word3 = await this.read_efuse(loader, 3); + const word3 = await this.readEfuse(loader, 3); - const chip_ver_dis_bt = word3 & (1 << 1); - if (chip_ver_dis_bt === 0) { + const chipVerDisBt = word3 & (1 << 1); + if (chipVerDisBt === 0) { features.push(' BT'); } - const chip_ver_dis_app_cpu = word3 & (1 << 0); - if (chip_ver_dis_app_cpu !== 0) { + const chipVerDisAppCpu = word3 & (1 << 0); + if (chipVerDisAppCpu !== 0) { features.push(' Single Core'); } else { features.push(' Dual Core'); } - const chip_cpu_freq_rated = word3 & (1 << 13); - if (chip_cpu_freq_rated !== 0) { - const chip_cpu_freq_low = word3 & (1 << 12); - if (chip_cpu_freq_low !== 0) { + const chipCpuFreqRated = word3 & (1 << 13); + if (chipCpuFreqRated !== 0) { + const chipCpuFreqLow = word3 & (1 << 12); + if (chipCpuFreqLow !== 0) { features.push(' 160MHz'); } else { features.push(' 240MHz'); } } - const pkg_version = await this.get_pkg_version(loader); - if ([2, 4, 5, 6].includes(pkg_version)) { + const pkgVersion = await this.getPkgVersion(loader); + if ([2, 4, 5, 6].includes(pkgVersion)) { features.push(' Embedded Flash'); } - if (pkg_version === 6) { + if (pkgVersion === 6) { features.push(' Embedded PSRAM'); } - const word4 = await this.read_efuse(loader, 4); - const adc_vref = (word4 >> 8) & 0x1F; - if (adc_vref !== 0) { + const word4 = await this.readEfuse(loader, 4); + const adcVRef = (word4 >> 8) & 0x1F; + if (adcVRef !== 0) { features.push(' VRef calibration in efuse'); } - const blk3_part_res = (word3 >> 14) & 0x1; - if (blk3_part_res !== 0) { + const blk3PartRes = (word3 >> 14) & 0x1; + if (blk3PartRes !== 0) { features.push(' BLK3 partially reserved'); } - const word6 = await this.read_efuse(loader, 6); - const coding_scheme = word6 & 0x3; - const coding_scheme_arr = ['None', '3/4', 'Repeat (UNSUPPORTED)', 'Invalid']; - features.push(` Coding Scheme ${coding_scheme_arr[coding_scheme]}`); + const word6 = await this.readEfuse(loader, 6); + const codingScheme = word6 & 0x3; + const codingSchemeArr = ['None', '3/4', 'Repeat (UNSUPPORTED)', 'Invalid']; + features.push(` Coding Scheme ${codingSchemeArr[codingScheme]}`); return features; } - static get_crystal_freq = async (loader) => { - const uart_div = await loader.read_reg({ addr: this.UART_CLKDIV_REG }) & this.UART_CLKDIV_MASK; - const ets_xtal = (loader.transport.baudrate * uart_div) / 1000000 / this.XTAL_CLK_DIVIDER; - let norm_xtal; - if (ets_xtal > 33) { - norm_xtal = 40; + static async getCrystalFreq(loader: ESPLoader) { + const uartDiv = await loader.readReg(this.UART_CLKDIV_REG) & this.UART_CLKDIV_MASK; + const etsXtal = (loader.serial.baudRate * uartDiv) / 1000000 / this.XTAL_CLK_DIVIDER; + let normXtal; + if (etsXtal > 33) { + normXtal = 40; } else { - norm_xtal = 26; + normXtal = 26; } - if (Math.abs(norm_xtal - ets_xtal) > 1) { + if (Math.abs(normXtal - etsXtal) > 1) { loader.log('WARNING: Unsupported crystal in use'); } - return norm_xtal; + return normXtal; } - static _d2h(d) { - const h = (+d).toString(16); + static #d2h(d: number) { + const h = (Number(d)).toString(16); return h.length === 1 ? `0${h}` : h; } - static read_mac = async (loader) => { - let mac0 = await this.read_efuse(loader, 1); + static async readMac(loader: ESPLoader) { + let mac0 = await this.readEfuse(loader, 1); mac0 >>>= 0; - let mac1 = await this.read_efuse(loader, 2); + let mac1 = await this.readEfuse(loader, 2); mac1 >>>= 0; const mac = new Uint8Array(6); mac[0] = (mac1 >> 8) & 0xff; @@ -189,19 +195,19 @@ export default class ESP32ROM { mac[5] = mac0 & 0xff; return (`${ - this._d2h(mac[0]) + this.#d2h(mac[0]) }:${ - this._d2h(mac[1]) + this.#d2h(mac[1]) }:${ - this._d2h(mac[2]) + this.#d2h(mac[2]) }:${ - this._d2h(mac[3]) + this.#d2h(mac[3]) }:${ - this._d2h(mac[4]) + this.#d2h(mac[4]) }:${ - this._d2h(mac[5]) + this.#d2h(mac[5]) }`); } - static get_erase_size = (offset, size) => size + static getEraseSize(offset: number, size: number) { return size; } } \ No newline at end of file diff --git a/src/esp/roms/esp32c3.ts b/src/esp/roms/esp32c3.ts index a3d3f54..85872e7 100644 --- a/src/esp/roms/esp32c3.ts +++ b/src/esp/roms/esp32c3.ts @@ -1,3 +1,9 @@ +import ESPLoader from '../ESPLoader'; + +interface flashSizes { + [key: string]: number; +} + export default class ESP32C3ROM { static CHIP_NAME = 'ESP32-C3'; @@ -23,7 +29,7 @@ export default class ESP32C3ROM { static FLASH_SIZES = { '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, - }; + } as flashSizes; static SPI_REG_BASE = 0x60002000; @@ -39,52 +45,52 @@ export default class ESP32C3ROM { static SPI_W0_OFFS = 0x58; - static get_pkg_version = async (loader) => { - const num_word = 3; - const block1_addr = this.EFUSE_BASE + 0x044; - const addr = block1_addr + (4 * num_word); - const word3 = await loader.read_reg({ addr }); - const pkg_version = (word3 >> 21) & 0x0F; - return pkg_version; + static async getPkgVersion(loader: ESPLoader) { + const numWord = 3; + const block1Addr = this.EFUSE_BASE + 0x044; + const addr = block1Addr + (4 * numWord); + const word3 = await loader.readReg(addr); + const pkgVersion = (word3 >> 21) & 0x0F; + return pkgVersion; } - static get_chip_revision = async (loader) => { - const block1_addr = this.EFUSE_BASE + 0x044; - const num_word = 3; + static async getChipRevision(loader: ESPLoader) { + const block1Addr = this.EFUSE_BASE + 0x044; + const numWord = 3; const pos = 18; - const addr = block1_addr + (4 * num_word); - const ret = (await loader.read_reg({ addr }) & (0x7 << pos)) >> pos; + const addr = block1Addr + (4 * numWord); + const ret = (await loader.readReg(addr) & (0x7 << pos)) >> pos; return ret; } - static get_chip_description = async (loader) => { + static async getChipDescription(loader: ESPLoader) { let desc; - const pkg_ver = await this.get_pkg_version(loader); - if (pkg_ver === 0) { + const pkgVer = await this.getPkgVersion(loader); + if (pkgVer === 0) { desc = 'ESP32-C3'; } else { desc = 'unknown ESP32-C3'; } - const chip_rev = await this.get_chip_revision(loader); + const chip_rev = await this.getChipRevision(loader); desc += ` (revision ${chip_rev})`; return desc; } // eslint-disable-next-line no-unused-vars - static get_chip_features = async (loader) => ['Wi-Fi'] + static async getChipFeatures(loader: ESPLoader) { return ['Wi-Fi']; } // eslint-disable-next-line no-unused-vars - static get_crystal_freq = async (loader) => 40 + static async getCrystalFreq(loader: ESPLoader) { return 40; } - static _d2h(d) { - const h = (+d).toString(16); + static #d2h(d: number) { + const h = (Number(d)).toString(16); return h.length === 1 ? `0${h}` : h; } - static read_mac = async (loader) => { - let mac0 = await loader.read_reg({ addr: this.MAC_EFUSE_REG }); + static async readMac(loader: ESPLoader) { + let mac0 = await loader.readReg(this.MAC_EFUSE_REG); mac0 >>>= 0; - let mac1 = await loader.read_reg({ addr: this.MAC_EFUSE_REG + 4 }); + let mac1 = await loader.readReg(this.MAC_EFUSE_REG + 4); mac1 = (mac1 >>> 0) & 0x0000ffff; const mac = new Uint8Array(6); mac[0] = (mac1 >> 8) & 0xff; @@ -95,19 +101,19 @@ export default class ESP32C3ROM { mac[5] = mac0 & 0xff; return (`${ - this._d2h(mac[0]) + this.#d2h(mac[0]) }:${ - this._d2h(mac[1]) + this.#d2h(mac[1]) }:${ - this._d2h(mac[2]) + this.#d2h(mac[2]) }:${ - this._d2h(mac[3]) + this.#d2h(mac[3]) }:${ - this._d2h(mac[4]) + this.#d2h(mac[4]) }:${ - this._d2h(mac[5]) + this.#d2h(mac[5]) }`); } - static get_erase_size = (offset, size) => size + static getEraseSize(offset: number, size: number) { return size; }; } \ No newline at end of file diff --git a/src/esp/roms/esp32s2.ts b/src/esp/roms/esp32s2.ts index 85a2290..0bb3e2d 100644 --- a/src/esp/roms/esp32s2.ts +++ b/src/esp/roms/esp32s2.ts @@ -1,3 +1,9 @@ +import ESPLoader from '../ESPLoader'; + +interface flashSizes { + [key: string]: number; +} + export default class ESP32S2ROM { static CHIP_NAME = 'ESP32-S2'; @@ -23,7 +29,7 @@ export default class ESP32S2ROM { static FLASH_SIZES = { '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, - }; + } as flashSizes; static SPI_REG_BASE = 0x3f402000; @@ -39,56 +45,56 @@ export default class ESP32S2ROM { static SPI_MISO_DLEN_OFFS = 0x28; - static get_pkg_version = async (loader) => { - const num_word = 3; - const block1_addr = this.EFUSE_BASE + 0x044; - const addr = block1_addr + (4 * num_word); - const word3 = await loader.read_reg({ addr }); - const pkg_version = (word3 >> 21) & 0x0F; - return pkg_version; + static async getPkgVersion(loader: ESPLoader) { + const numWord = 3; + const block1Addr = this.EFUSE_BASE + 0x044; + const addr = block1Addr + (4 * numWord); + const word3 = await loader.readReg(addr); + const pkgVersion = (word3 >> 21) & 0x0F; + return pkgVersion; } - static get_chip_description = async (loader) => { - const chip_desc = ['ESP32-S2', 'ESP32-S2FH16', 'ESP32-S2FH32']; - const pkg_ver = await this.get_pkg_version(loader); - if (pkg_ver >= 0 && pkg_ver <= 2) { - return chip_desc[pkg_ver]; + static async getChipDescription(loader: ESPLoader) { + const chipDesc = ['ESP32-S2', 'ESP32-S2FH16', 'ESP32-S2FH32']; + const pkgVer = await this.getPkgVersion(loader); + if (pkgVer >= 0 && pkgVer <= 2) { + return chipDesc[pkgVer]; } return 'unknown ESP32-S2'; } - static get_chip_features = async (loader) => { + static async getChipFeatures(loader: ESPLoader) { const features = ['Wi-Fi']; - const pkg_ver = await this.get_pkg_version(loader); - if (pkg_ver === 1) { + const pkgVer = await this.getPkgVersion(loader); + if (pkgVer === 1) { features.push('Embedded 2MB Flash'); - } else if (pkg_ver === 2) { + } else if (pkgVer === 2) { features.push('Embedded 4MB Flash'); } - const num_word = 4; - const block2_addr = this.EFUSE_BASE + 0x05C; - const addr = block2_addr + (4 * num_word); - const word4 = await loader.read_reg({ addr }); - const block2_ver = (word4 >> 4) & 0x07; + const numWord = 4; + const block2Addr = this.EFUSE_BASE + 0x05C; + const addr = block2Addr + (4 * numWord); + const word4 = await loader.readReg(addr); + const block2Ver = (word4 >> 4) & 0x07; - if (block2_ver === 1) { + if (block2Ver === 1) { features.push('ADC and temperature sensor calibration in BLK2 of efuse'); } return features; } // eslint-disable-next-line no-unused-vars - static get_crystal_freq = async (loader) => 40 + static async getCrystalFreq(loader: ESPLoader) { return 40; } - static _d2h(d) { - const h = (+d).toString(16); + static #d2h(d: number) { + const h = (Number(d)).toString(16); return h.length === 1 ? `0${h}` : h; } - static read_mac = async (loader) => { - let mac0 = await loader.read_reg({ addr: this.MAC_EFUSE_REG }); + static readMac = async (loader: ESPLoader) => { + let mac0 = await loader.readReg(this.MAC_EFUSE_REG); mac0 >>>= 0; - let mac1 = await loader.read_reg({ addr: this.MAC_EFUSE_REG + 4 }); + let mac1 = await loader.readReg(this.MAC_EFUSE_REG + 4); mac1 = (mac1 >>> 0) & 0x0000ffff; const mac = new Uint8Array(6); mac[0] = (mac1 >> 8) & 0xff; @@ -99,19 +105,19 @@ export default class ESP32S2ROM { mac[5] = mac0 & 0xff; return (`${ - this._d2h(mac[0]) + this.#d2h(mac[0]) }:${ - this._d2h(mac[1]) + this.#d2h(mac[1]) }:${ - this._d2h(mac[2]) + this.#d2h(mac[2]) }:${ - this._d2h(mac[3]) + this.#d2h(mac[3]) }:${ - this._d2h(mac[4]) + this.#d2h(mac[4]) }:${ - this._d2h(mac[5]) + this.#d2h(mac[5]) }`); } - static get_erase_size = (offset, size) => size + static getEraseSize = (offset: number, size: number) => size } \ No newline at end of file diff --git a/src/esp/roms/esp32s3beta2.ts b/src/esp/roms/esp32s3beta2.ts index 486afc8..79931e1 100644 --- a/src/esp/roms/esp32s3beta2.ts +++ b/src/esp/roms/esp32s3beta2.ts @@ -1,3 +1,9 @@ +import ESPLoader from '../ESPLoader'; + +interface flashSizes { + [key: string]: number; +} + export default class ESP32S3BETA2ROM { static CHIP_NAME = 'ESP32-S3'; @@ -6,26 +12,32 @@ export default class ESP32S3BETA2ROM { static CHIP_DETECT_MAGIC_VALUE = 0xeb004136; // eslint-disable-next-line no-unused-vars - static get_pkg_version = async (loader) => { + static async get_pkg_version(loader: ESPLoader) { + throw new Error('Not implemented'); } // eslint-disable-next-line no-unused-vars - static get_chip_revision = async (loader) => { + static async get_chip_revision(loader: ESPLoader) { + throw new Error('Not implemented'); } // eslint-disable-next-line no-unused-vars - static get_chip_description = async (loader) => { + static async get_chip_description(loader: ESPLoader) { + throw new Error('Not implemented'); } // eslint-disable-next-line no-unused-vars - static get_chip_features = async (loader) => { + static async get_chip_features(loader: ESPLoader) { + throw new Error('Not implemented'); } // eslint-disable-next-line no-unused-vars - static get_crystal_freq = async (loader) => { + static async get_crystal_freq(loader: ESPLoader) { + throw new Error('Not implemented'); } // eslint-disable-next-line no-unused-vars - static read_mac = async (loader) => { + static async read_mac(loader: ESPLoader) { + throw new Error('Not implemented'); } } diff --git a/src/esp/roms/esp8266.ts b/src/esp/roms/esp8266.ts index da67fda..31dd717 100644 --- a/src/esp/roms/esp8266.ts +++ b/src/esp/roms/esp8266.ts @@ -1,3 +1,9 @@ +import ESPLoader from '../ESPLoader'; + +interface flashSizes { + [key: string]: number; +} + export default class ESP8266ROM { static CHIP_NAME = 'ESP8266'; @@ -42,25 +48,27 @@ export default class ESP8266ROM { '4MB-c1': 0x60, '8MB': 0x80, '16MB': 0x90, - } + } as flashSizes static BOOTLOADER_FLASH_OFFSET = 0 + static UART_DATE_REG_ADDR = 0 + static MEMORY_MAP = [[0x3FF00000, 0x3FF00010, 'DPORT'], [0x3FFE8000, 0x40000000, 'DRAM'], [0x40100000, 0x40108000, 'IRAM'], [0x40201010, 0x402E1010, 'IROM']] - static get_efuses = async (loader) => { + static async getEfuses(loader: ESPLoader) { // Return the 128 bits of ESP8266 efuse as a single integer - const result = (await loader.read_reg({ addr: 0x3ff0005c }) << 96) - | (await loader.read_reg({ addr: 0x3ff00058 }) << 64) - | (await loader.read_reg({ addr: 0x3ff00054 }) << 32) - | await loader.read_reg({ addr: 0x3ff00050 }); + const result = (await loader.readReg(0x3ff0005c) << 96) + | (await loader.readReg(0x3ff00058) << 64) + | (await loader.readReg(0x3ff00054) << 32) + | await loader.readReg(0x3ff00050); return result; } - static _get_flash_size = (efuses) => { + static #getFlashSize(efuses: number) { // rX_Y = EFUSE_DATA_OUTX[Y] const r0_4 = (efuses & (1 << 4)) !== 0; const r3_25 = (efuses & (1 << 121)) !== 0; @@ -84,73 +92,72 @@ export default class ESP8266ROM { return -1; } - static get_chip_description = async (loader) => { - const efuses = await this.get_efuses(loader); - const is_8285 = (efuses & (((1 << 4) | 1) << 80)) !== 0; // One or the other efuse bit is set for ESP8285 - if (is_8285) { - const flash_size = this._get_flash_size(efuses); - const max_temp = (efuses & (1 << 5)) !== 0; // This efuse bit identifies the max flash temperature - const chip_name = { - 1: max_temp ? 'ESP8285H08' : 'ESP8285N08', - 2: max_temp ? 'ESP8285H16' : 'ESP8285N16', - }[flash_size] || 'ESP8285'; - return chip_name; + static async getChipDescription(loader: ESPLoader) { + const efuses = await this.getEfuses(loader); + const is8285 = (efuses & (((1 << 4) | 1) << 80)) !== 0; // One or the other efuse bit is set for ESP8285 + if (is8285) { + const flashSize = this.#getFlashSize(efuses); + const maxTemp = (efuses & (1 << 5)) !== 0; // This efuse bit identifies the max flash temperature + let chipName = 'ESP8285'; + if (flashSize === 1) chipName = maxTemp ? 'ESP8285H08' : 'ESP8285N08'; + if (flashSize === 2) chipName = maxTemp ? 'ESP8285H16' : 'ESP8285N16'; + return chipName; } return 'ESP8266EX'; } - static get_chip_features = async (loader) => { + static async getChipFeatures(loader: ESPLoader) { const features = ['WiFi']; - if (await this.get_chip_description(loader) === 'ESP8285') { + if (await this.getChipDescription(loader) === 'ESP8285') { features.push('Embedded Flash'); } return features; } - static flash_spi_attach = async (loader, hspi_arg) => { - if (this.IS_STUB) { - await super.flash_spi_attach(loader, hspi_arg); - } else { - // ESP8266 ROM has no flash_spi_attach command in serial protocol, - // but flash_begin will do it - await loader.flash_begin(0, 0); - } - } - - static flash_set_parameters = async (loader, size) => { - // not implemented in ROM, but OK to silently skip for ROM - if (this.IS_STUB) { - await super.flash_set_parameters(loader, size); - } - } - - static chip_id = async (loader) => { + // static flash_spi_attach = async (loader, hspi_arg) => { + // if (this.IS_STUB) { + // await super.flash_spi_attach(loader, hspi_arg); + // } else { + // // ESP8266 ROM has no flash_spi_attach command in serial protocol, + // // but flash_begin will do it + // await loader.flash_begin(0, 0); + // } + // } + + // static flash_set_parameters = async (loader, size) => { + // // not implemented in ROM, but OK to silently skip for ROM + // if (this.IS_STUB) { + // await super.flash_set_parameters(loader, size); + // } + // } + + static async chipId(loader: ESPLoader) { // Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function - const id0 = await loader.read_reg({ addr: this.ESP_OTP_MAC0 }); - const id1 = await loader.read_reg({ addr: this.ESP_OTP_MAC1 }); + const id0 = await loader.readReg(this.ESP_OTP_MAC0); + const id1 = await loader.readReg(this.ESP_OTP_MAC1); return (id0 >> 24) | ((id1 & 0xffffff) << 8); } - static read_mac = async (loader) => { + static async readMac(loader: ESPLoader) { // Read MAC from OTP ROM - const mac0 = await loader.read_reg({ addr: this.ESP_OTP_MAC0 }); - const mac1 = await loader.read_reg({ addr: this.ESP_OTP_MAC1 }); - const mac3 = await loader.read_reg({ addr: this.ESP_OTP_MAC3 }); + const mac0 = await loader.readReg(this.ESP_OTP_MAC0); + const mac1 = await loader.readReg(this.ESP_OTP_MAC1); + const mac3 = await loader.readReg(this.ESP_OTP_MAC3); let oui; if (mac3 !== 0) { - oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff); + oui = [(mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff]; } else if (((mac1 >> 16) & 0xff) === 0) { - oui = (0x18, 0xfe, 0x34); + oui = [0x18, 0xfe, 0x34]; } else if (((mac1 >> 16) & 0xff) === 1) { - oui = (0xac, 0xd0, 0x74); + oui = [0xac, 0xd0, 0x74]; } else { - throw ('Unknown OUI'); + throw new Error('Unknown OUI'); } - return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff); + return [...oui, (mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff]; } - static get_erase_size = (offset, size) => size + static getEraseSize(offset: number, size: number) { return size; } // eslint-disable-next-line no-unused-vars - static get_crystal_freq = async (loader) => 40 + static async getCrystalFreq(loader: ESPLoader) { return 40; } } \ No newline at end of file diff --git a/src/esp/stubs/esp32.json b/src/esp/stubs/esp32.json index 4e48a85..9ff35dd 100644 --- a/src/esp/stubs/esp32.json +++ b/src/esp/stubs/esp32.json @@ -1 +1 @@ -{"text": "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", "text_start": 1074520064, "entry": 1074521516, "data": "CMD8Pw==", "data_start": 1073605544} \ No newline at end of file +{"text": "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", "textStart": 1074520064, "entry": 1074521516, "data": "CMD8Pw==", "dataStart": 1073605544} \ No newline at end of file diff --git a/src/esp/stubs/esp32c3.json b/src/esp/stubs/esp32c3.json index 80dad8c..c0b6a76 100644 --- a/src/esp/stubs/esp32c3.json +++ b/src/esp/stubs/esp32c3.json @@ -1 +1 @@ -{"text": "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", "text_start": 1077411840, "entry": 1077413488, "data": "DEDIPw==", "data_start": 1070164904} \ No newline at end of file +{"text": "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", "textStart": 1077411840, "entry": 1077413488, "data": "DEDIPw==", "dataStart": 1070164904} \ No newline at end of file diff --git a/src/esp/stubs/esp32h2.json b/src/esp/stubs/esp32h2.json index 67375e8..f778881 100644 --- a/src/esp/stubs/esp32h2.json +++ b/src/esp/stubs/esp32h2.json @@ -1 +1 @@ -{"text": "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", "text_start": 1077411840, "entry": 1077413328, "data": "DEDIPw==", "data_start": 1070164904} \ No newline at end of file +{"text": "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", "textStart": 1077411840, "entry": 1077413328, "data": "DEDIPw==", "dataStart": 1070164904} \ No newline at end of file diff --git a/src/esp/stubs/esp32s2.json b/src/esp/stubs/esp32s2.json index 74a8576..ffd6171 100644 --- a/src/esp/stubs/esp32s2.json +++ b/src/esp/stubs/esp32s2.json @@ -1 +1 @@ -{"text": "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", "text_start": 1073905664, "entry": 1073907540, "data": "CAD9Pw==", "data_start": 1073622004} \ No newline at end of file +{"text": "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", "textStart": 1073905664, "entry": 1073907540, "data": "CAD9Pw==", "dataStart": 1073622004} \ No newline at end of file diff --git a/src/esp/stubs/esp32s3.json b/src/esp/stubs/esp32s3.json index ba7d1ee..18b054b 100644 --- a/src/esp/stubs/esp32s3.json +++ b/src/esp/stubs/esp32s3.json @@ -1 +1 @@ -{"text": "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", "text_start": 1077379072, "entry": 1077381116, "data": "CADKPw==", "data_start": 1070279592} \ No newline at end of file +{"text": "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", "textStart": 1077379072, "entry": 1077381116, "data": "CADKPw==", "dataStart": 1070279592} \ No newline at end of file diff --git a/src/esp/stubs/esp8266.json b/src/esp/stubs/esp8266.json index 6ae7a2e..104fafc 100644 --- a/src/esp/stubs/esp8266.json +++ b/src/esp/stubs/esp8266.json @@ -1 +1 @@ -{"text": "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", "text_start": 1074843648, "entry": 1074843652, "data": "CIH+PwUFBAACAwcAAwMLALnXEEDv1xBAHdgQQLrYEEBo5xBAHtkQQHTZEEDA2RBAaOcQQILaEED/2hBAwNsQQGjnEEBo5xBAWNwQQGjnEEA33xBAAOAQQDvgEEBo5xBAaOcQQNfgEEBo5xBAv+EQQGXiEECj4xBAY+QQQDTlEEBo5xBAaOcQQGjnEEBo5xBAYuYQQGjnEEBX5xBAkN0QQI/YEECm5RBAq9oQQPzZEEBo5xBA7OYQQDHnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQCLaEEBf2hBAvuUQQAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAkAAAANAAAAEQAAABkAAAAhAAAAMQAAAEEAAABhAAAAgQAAAMEAAAABAQAAgQEAAAECAAABAwAAAQQAAAEGAAABCAAAAQwAAAEQAAABGAAAASAAAAEwAAABQAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAAAAAAAAAAAAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAANAAAADwAAABEAAAATAAAAFwAAABsAAAAfAAAAIwAAACsAAAAzAAAAOwAAAEMAAABTAAAAYwAAAHMAAACDAAAAowAAAMMAAADjAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADAAAAAwAAAAMAAAAEAAAABAAAAAQAAAAEAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAABAREgAIBwkGCgULBAwDDQIOAQ8AAQEAAAEAAAAEAAAA", "data_start": 1073720488} \ No newline at end of file +{"text": "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", "textStart": 1074843648, "entry": 1074843652, "data": "CIH+PwUFBAACAwcAAwMLALnXEEDv1xBAHdgQQLrYEEBo5xBAHtkQQHTZEEDA2RBAaOcQQILaEED/2hBAwNsQQGjnEEBo5xBAWNwQQGjnEEA33xBAAOAQQDvgEEBo5xBAaOcQQNfgEEBo5xBAv+EQQGXiEECj4xBAY+QQQDTlEEBo5xBAaOcQQGjnEEBo5xBAYuYQQGjnEEBX5xBAkN0QQI/YEECm5RBAq9oQQPzZEEBo5xBA7OYQQDHnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQCLaEEBf2hBAvuUQQAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAkAAAANAAAAEQAAABkAAAAhAAAAMQAAAEEAAABhAAAAgQAAAMEAAAABAQAAgQEAAAECAAABAwAAAQQAAAEGAAABCAAAAQwAAAEQAAABGAAAASAAAAEwAAABQAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAAAAAAAAAAAAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAANAAAADwAAABEAAAATAAAAFwAAABsAAAAfAAAAIwAAACsAAAAzAAAAOwAAAEMAAABTAAAAYwAAAHMAAACDAAAAowAAAMMAAADjAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADAAAAAwAAAAMAAAAEAAAABAAAAAQAAAAEAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAABAREgAIBwkGCgULBAwDDQIOAQ8AAQEAAAEAAAAEAAAA", "dataStart": 1073720488} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2653c20..2c46b20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,6 +191,11 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ== +"@types/crypto-js@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" + integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== + "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -206,10 +211,15 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== -"@types/node@^17.0.33": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.33.tgz#3c1879b276dc63e73030bb91165e62a4509cd506" - integrity sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ== +"@types/node@^18.0.6": + version "18.0.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.6.tgz#0ba49ac517ad69abe7a1508bc9b3a5483df9d5d7" + integrity sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw== + +"@types/pako@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.0.tgz#12ab4c19107528452e73ac99132c875ccd43bdfb" + integrity sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA== "@typescript-eslint/eslint-plugin@^5.23.0": version "5.23.0" From 1d886b8ed71f3d28cfc282539b2291152b608199 Mon Sep 17 00:00:00 2001 From: mrfrase3 Date: Sat, 30 Jul 2022 15:35:49 +0800 Subject: [PATCH 3/6] convert ROMs from classes to objects --- src/esp/ESPLoader.ts | 46 ++++------- src/esp/roms/esp32.ts | 144 +++++++++++++---------------------- src/esp/roms/esp32c3.ts | 129 +++++++++++-------------------- src/esp/roms/esp32s2.ts | 122 ++++++++++------------------- src/esp/roms/esp32s3beta2.ts | 43 ----------- src/esp/roms/esp8266.ts | 142 ++++++++++++++-------------------- src/esp/roms/index.ts | 12 +++ src/esp/roms/rom.d.ts | 42 ++++++++++ src/esp/roms/util.ts | 16 ++++ 9 files changed, 281 insertions(+), 415 deletions(-) delete mode 100644 src/esp/roms/esp32s3beta2.ts create mode 100644 src/esp/roms/index.ts create mode 100644 src/esp/roms/rom.d.ts create mode 100644 src/esp/roms/util.ts diff --git a/src/esp/ESPLoader.ts b/src/esp/ESPLoader.ts index 316adbe..8de58c6 100644 --- a/src/esp/ESPLoader.ts +++ b/src/esp/ESPLoader.ts @@ -2,12 +2,8 @@ import { SerialPort } from 'serialport/dist/index.d'; import pako from 'pako'; import CryptoJS from 'crypto-js'; import StubLoader from './StubLoader'; - -import ESP32ROM from './roms/esp32'; -import ESP32C3ROM from './roms/esp32c3'; -import ESP32S2ROM from './roms/esp32s2'; -// import ESP32S3BETA2ROM from './roms/esp32s3beta2'; -import ESP8266ROM from './roms/esp8266'; +import roms from './roms'; +import ROM from './roms/rom'; interface ESPOptions { quiet?: boolean; @@ -78,12 +74,7 @@ export default class ESPLoader { quiet: boolean; serial: SerialPort; IS_STUB: boolean; - chip: typeof ESP32ROM - | typeof ESP32C3ROM - | typeof ESP32S2ROM - // | typeof ESP32S3BETA2ROM - | typeof ESP8266ROM - | null; + chip: ROM | null; stdout: any; stubLoader: StubLoader; syncStubDetected: boolean; @@ -154,15 +145,15 @@ export default class ESPLoader { // convert data before sending to https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol async write(data: Buffer) { - const slipped_arr = []; + const slippedArr = []; for (let i = 0; i < data.length; i++) { - if (i === 0xC0) slipped_arr.push(0xDB, 0xDC); // escape the end char - else if (i === 0xDB) slipped_arr.push(0xDB, 0xDD); // escape the escape char - else slipped_arr.push(data[i]); + if (i === 0xC0) slippedArr.push(0xDB, 0xDC); // escape the end char + else if (i === 0xDB) slippedArr.push(0xDB, 0xDD); // escape the escape char + else slippedArr.push(data[i]); } const pkt = Buffer.from([ 0xC0, - ...slipped_arr, + ...slippedArr, 0xC0, ]); return this.serial.write(pkt); @@ -263,7 +254,7 @@ export default class ESPLoader { const val = this.#byteArrayToInt([p[4], p[5], p[6], p[7]]); // eslint-disable-next-line no-console // console.log(`Resp ${resp} ${op_ret} ${op} ${len_ret} ${val} ${p}`); - const datum = p.slice(8); + const datum = p.subarray(8); if (!op || op_ret === op) { return [val, datum]; } @@ -384,17 +375,10 @@ export default class ESPLoader { await this.#flushInput(); if (!detecting) { - const chip_magic_value = await this.readReg(0x40001000); + const chipMagicValue = await this.readReg(0x40001000); // eslint-disable-next-line no-console // console.log(`Chip Magic ${chip_magic_value}`); - const chips = [ - ESP8266ROM, - ESP32ROM, - ESP32S2ROM, - // ESP32S3BETA2ROM, - ESP32C3ROM - ]; - this.chip = chips.find((cls) => chip_magic_value === cls.CHIP_DETECT_MAGIC_VALUE) ?? null; + this.chip = roms.find((cls) => chipMagicValue === cls.CHIP_DETECT_MAGIC_VALUE) ?? null; // console.log('chip', this.chip); } return null; @@ -602,7 +586,7 @@ export default class ESPLoader { } async runSpiFlashCommand(spiFlashCommand: number, data: Uint8Array, readBits: number) { - if (!this.chip) throw new Error('chip not initialized'); + if (!this.chip?.SPI_REG_BASE) throw new Error('chip not initialized'); // SPI_USR register flags const SPI_USR_COMMAND = (1 << 31); const SPI_USR_MISO = (1 << 28); @@ -714,10 +698,6 @@ export default class ESPLoader { return ret; } - toHex(buffer: Buffer) { - return buffer.toString('hex'); - } - async flashMd5sum(addr: number, size: number) { const timeout = this.timeoutPerMb(this.MD5_TIMEOUT_PER_MB, size); let pkt = this.#appendArray(this.#intToByteArray(addr), this.#intToByteArray(size)); @@ -731,7 +711,7 @@ export default class ESPLoader { if (res.length > 16) { res = res.subarray(0, 16); } - const strmd5 = this.toHex(res); + const strmd5 = res.toString('hex'); return strmd5; } diff --git a/src/esp/roms/esp32.ts b/src/esp/roms/esp32.ts index 117b1e6..93c6a27 100644 --- a/src/esp/roms/esp32.ts +++ b/src/esp/roms/esp32.ts @@ -1,66 +1,49 @@ import ESPLoader from '../ESPLoader'; - -interface flashSizes { - [key: string]: number; -} - -export default class ESP32ROM { - static CHIP_NAME = 'ESP32'; - - static IS_STUB = true; - - static IMAGE_CHIP_ID = 0; - - static CHIP_DETECT_MAGIC_VALUE = 0x00f01d83; - - static EFUSE_RD_REG_BASE = 0x3ff5a000; - - static DR_REG_SYSCON_BASE = 0x3ff66000; - - static UART_CLKDIV_REG = 0x3ff40014; - - static UART_CLKDIV_MASK = 0xFFFFF; - - static UART_DATE_REG_ADDR = 0x60000078; - - static XTAL_CLK_DIVIDER= 1; - - static FLASH_WRITE_SIZE = 0x400; - - static BOOTLOADER_FLASH_OFFSET = 0x1000; - - static FLASH_SIZES = { +import ROM from './rom'; +import { toMac } from './util'; + +export default { + CHIP_NAME: 'ESP32', + IS_STUB: true, + IMAGE_CHIP_ID: 0, + CHIP_DETECT_MAGIC_VALUE: 0x00f01d83, + EFUSE_RD_REG_BASE: 0x3ff5a000, + DR_REG_SYSCON_BASE: 0x3ff66000, + UART_CLKDIV_REG: 0x3ff40014, + UART_CLKDIV_MASK: 0xFFFFF, + UART_DATE_REG_ADDR: 0x60000078, + XTAL_CLK_DIVIDER: 1, + FLASH_WRITE_SIZE: 0x400, + BOOTLOADER_FLASH_OFFSET: 0x1000, + SPI_REG_BASE: 0x3ff42000, + SPI_USR_OFFS: 0x1c, + SPI_USR1_OFFS: 0x20, + SPI_USR2_OFFS: 0x24, + SPI_W0_OFFS: 0x80, + SPI_MOSI_DLEN_OFFS: 0x28, + SPI_MISO_DLEN_OFFS: 0x2c, + FLASH_SIZES: { '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, - } as flashSizes; - - static SPI_REG_BASE = 0x3ff42000; - - static SPI_USR_OFFS = 0x1c; - - static SPI_USR1_OFFS = 0x20; - - static SPI_USR2_OFFS = 0x24; + }, - static SPI_W0_OFFS = 0x80; - - static SPI_MOSI_DLEN_OFFS = 0x28; - - static SPI_MISO_DLEN_OFFS = 0x2c; - - static async readEfuse(loader: ESPLoader, offset: number) { + async readEfuse(loader: ESPLoader, offset: number) { + if (!this.EFUSE_RD_REG_BASE) throw new Error('EFUSE_RD_REG_BASE not implemented'); const addr = this.EFUSE_RD_REG_BASE + (4 * offset); // console.log(`Read efuse ${addr}`); return loader.readReg(addr); - } + }, - static async getPkgVersion(loader: ESPLoader) { + async getPkgVersion(loader: ESPLoader) { + if (!this.readEfuse) throw new Error('readEfuse not implemented'); const word3 = await this.readEfuse(loader, 3); let pkgVersion = (word3 >> 9) & 0x07; pkgVersion += ((word3 >> 2) & 0x1) << 3; return pkgVersion; - } + }, - static async getChipRevision(loader: ESPLoader) { + async getChipRevision(loader: ESPLoader) { + if (!this.DR_REG_SYSCON_BASE) throw new Error('DR_REG_SYSCON_BASE not implemented'); + if (!this.readEfuse) throw new Error('readEfuse not implemented'); const word3 = await this.readEfuse(loader, 3); const word5 = await this.readEfuse(loader, 5); const apbCtlDate = await loader.readReg(this.DR_REG_SYSCON_BASE + 0x7C); @@ -78,9 +61,12 @@ export default class ESP32ROM { return 1; } return 0; - } + }, - static async getChipDescription(loader: ESPLoader) { + async getChipDescription(loader: ESPLoader) { + if (!this.getChipRevision) throw new Error('getChipRevision not implemented'); + if (!this.readEfuse) throw new Error('readEfuse not implemented'); + if (!this.getPkgVersion) throw new Error('getPkgVersion not implemented'); const chipDesc = ['ESP32-D0WDQ6', 'ESP32-D0WD', 'ESP32-D2WD', '', 'ESP32-U4WDH', 'ESP32-PICO-D4', 'ESP32-PICO-V3-02']; let chipName = ''; const pkgVersion = await this.getPkgVersion(loader); @@ -105,9 +91,11 @@ export default class ESP32ROM { chipName += '-V3'; } return `${chipName} (revision ${chipRevision})`; - } + }, - static async getChipFeatures(loader: ESPLoader) { + async getChipFeatures(loader: ESPLoader) { + if (!this.readEfuse) throw new Error('readEfuse not implemented'); + if (!this.getPkgVersion) throw new Error('getPkgVersion not implemented'); const features = ['Wi-Fi']; const word3 = await this.readEfuse(loader, 3); @@ -159,9 +147,11 @@ export default class ESP32ROM { features.push(` Coding Scheme ${codingSchemeArr[codingScheme]}`); return features; - } + }, - static async getCrystalFreq(loader: ESPLoader) { + async getCrystalFreq(loader: ESPLoader) { + if (!this.XTAL_CLK_DIVIDER) throw new Error('XTAL_CLK_DIVIDER not implemented'); + if (!this.UART_CLKDIV_MASK) throw new Error('UART_CLKDIV_MASK not implemented'); const uartDiv = await loader.readReg(this.UART_CLKDIV_REG) & this.UART_CLKDIV_MASK; const etsXtal = (loader.serial.baudRate * uartDiv) / 1000000 / this.XTAL_CLK_DIVIDER; let normXtal; @@ -174,40 +164,16 @@ export default class ESP32ROM { loader.log('WARNING: Unsupported crystal in use'); } return normXtal; - } + }, - static #d2h(d: number) { - const h = (Number(d)).toString(16); - return h.length === 1 ? `0${h}` : h; - } - - static async readMac(loader: ESPLoader) { + async readMac(loader: ESPLoader) { + if (!this.readEfuse) throw new Error('readEfuse not implemented'); let mac0 = await this.readEfuse(loader, 1); mac0 >>>= 0; let mac1 = await this.readEfuse(loader, 2); mac1 >>>= 0; - const mac = new Uint8Array(6); - mac[0] = (mac1 >> 8) & 0xff; - mac[1] = mac1 & 0xff; - mac[2] = (mac0 >> 24) & 0xff; - mac[3] = (mac0 >> 16) & 0xff; - mac[4] = (mac0 >> 8) & 0xff; - mac[5] = mac0 & 0xff; - - return (`${ - this.#d2h(mac[0]) - }:${ - this.#d2h(mac[1]) - }:${ - this.#d2h(mac[2]) - }:${ - this.#d2h(mac[3]) - }:${ - this.#d2h(mac[4]) - }:${ - this.#d2h(mac[5]) - }`); - } - - static getEraseSize(offset: number, size: number) { return size; } -} \ No newline at end of file + return toMac(mac0, mac1); + }, + + getEraseSize(offset: number, size: number) { return size; }, +} as ROM; \ No newline at end of file diff --git a/src/esp/roms/esp32c3.ts b/src/esp/roms/esp32c3.ts index 85872e7..c89a3b6 100644 --- a/src/esp/roms/esp32c3.ts +++ b/src/esp/roms/esp32c3.ts @@ -1,69 +1,54 @@ import ESPLoader from '../ESPLoader'; - -interface flashSizes { - [key: string]: number; -} - -export default class ESP32C3ROM { - static CHIP_NAME = 'ESP32-C3'; - - static IS_STUB = true; - - static IMAGE_CHIP_ID = 5; - - static CHIP_DETECT_MAGIC_VALUE = 0x6921506f; - - static EFUSE_BASE = 0x60008800; - - static MAC_EFUSE_REG = this.EFUSE_BASE + 0x044; - - static UART_CLKDIV_REG = 0x3ff40014; - - static UART_CLKDIV_MASK = 0xFFFFF; - - static UART_DATE_REG_ADDR = 0x6000007C; - - static FLASH_WRITE_SIZE = 0x400; - - static BOOTLOADER_FLASH_OFFSET = 0x1000; - - static FLASH_SIZES = { +import ROM from './rom'; +import { toMac } from './util'; + +const EFUSE_BASE = 0x60008800; + +export default { + CHIP_NAME: 'ESP32-C3', + IS_STUB: true, + IMAGE_CHIP_ID: 5, + CHIP_DETECT_MAGIC_VALUE: 0x6921506f, + EFUSE_BASE, + MAC_EFUSE_REG: EFUSE_BASE + 0x044, + UART_CLKDIV_REG: 0x3ff40014, + UART_CLKDIV_MASK: 0xFFFFF, + UART_DATE_REG_ADDR: 0x6000007C, + FLASH_WRITE_SIZE: 0x400, + BOOTLOADER_FLASH_OFFSET: 0x1000, + SPI_REG_BASE: 0x60002000, + SPI_USR_OFFS: 0x18, + SPI_USR1_OFFS: 0x1C, + SPI_USR2_OFFS: 0x20, + SPI_MOSI_DLEN_OFFS: 0x24, + SPI_MISO_DLEN_OFFS: 0x28, + SPI_W0_OFFS: 0x58, + + FLASH_SIZES: { '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, - } as flashSizes; - - static SPI_REG_BASE = 0x60002000; - - static SPI_USR_OFFS = 0x18; - - static SPI_USR1_OFFS = 0x1C; - - static SPI_USR2_OFFS = 0x20; + }, - static SPI_MOSI_DLEN_OFFS = 0x24; - - static SPI_MISO_DLEN_OFFS = 0x28; - - static SPI_W0_OFFS = 0x58; - - static async getPkgVersion(loader: ESPLoader) { + async getPkgVersion(loader: ESPLoader) { const numWord = 3; - const block1Addr = this.EFUSE_BASE + 0x044; + const block1Addr = EFUSE_BASE + 0x044; const addr = block1Addr + (4 * numWord); const word3 = await loader.readReg(addr); const pkgVersion = (word3 >> 21) & 0x0F; return pkgVersion; - } + }, - static async getChipRevision(loader: ESPLoader) { - const block1Addr = this.EFUSE_BASE + 0x044; + async getChipRevision(loader: ESPLoader) { + const block1Addr = EFUSE_BASE + 0x044; const numWord = 3; const pos = 18; const addr = block1Addr + (4 * numWord); const ret = (await loader.readReg(addr) & (0x7 << pos)) >> pos; return ret; - } + }, - static async getChipDescription(loader: ESPLoader) { + async getChipDescription(loader: ESPLoader) { + if (!this.getChipRevision) throw new Error('getChipRevision not implemented'); + if (!this.getPkgVersion) throw new Error('getPkgVersion not implemented'); let desc; const pkgVer = await this.getPkgVersion(loader); if (pkgVer === 0) { @@ -74,46 +59,22 @@ export default class ESP32C3ROM { const chip_rev = await this.getChipRevision(loader); desc += ` (revision ${chip_rev})`; return desc; - } + }, // eslint-disable-next-line no-unused-vars - static async getChipFeatures(loader: ESPLoader) { return ['Wi-Fi']; } + async getChipFeatures(loader: ESPLoader) { return ['Wi-Fi']; }, // eslint-disable-next-line no-unused-vars - static async getCrystalFreq(loader: ESPLoader) { return 40; } + async getCrystalFreq(loader: ESPLoader) { return 40; }, - static #d2h(d: number) { - const h = (Number(d)).toString(16); - return h.length === 1 ? `0${h}` : h; - } - - static async readMac(loader: ESPLoader) { + async readMac(loader: ESPLoader) { + if (!this.MAC_EFUSE_REG) return ''; let mac0 = await loader.readReg(this.MAC_EFUSE_REG); mac0 >>>= 0; let mac1 = await loader.readReg(this.MAC_EFUSE_REG + 4); mac1 = (mac1 >>> 0) & 0x0000ffff; - const mac = new Uint8Array(6); - mac[0] = (mac1 >> 8) & 0xff; - mac[1] = mac1 & 0xff; - mac[2] = (mac0 >> 24) & 0xff; - mac[3] = (mac0 >> 16) & 0xff; - mac[4] = (mac0 >> 8) & 0xff; - mac[5] = mac0 & 0xff; - - return (`${ - this.#d2h(mac[0]) - }:${ - this.#d2h(mac[1]) - }:${ - this.#d2h(mac[2]) - }:${ - this.#d2h(mac[3]) - }:${ - this.#d2h(mac[4]) - }:${ - this.#d2h(mac[5]) - }`); - } + return toMac(mac0, mac1); + }, - static getEraseSize(offset: number, size: number) { return size; }; -} \ No newline at end of file + getEraseSize(offset: number, size: number) { return size; }, +} as ROM; diff --git a/src/esp/roms/esp32s2.ts b/src/esp/roms/esp32s2.ts index 0bb3e2d..b3e6324 100644 --- a/src/esp/roms/esp32s2.ts +++ b/src/esp/roms/esp32s2.ts @@ -1,69 +1,53 @@ import ESPLoader from '../ESPLoader'; - -interface flashSizes { - [key: string]: number; -} - -export default class ESP32S2ROM { - static CHIP_NAME = 'ESP32-S2'; - - static IS_STUB = true; - - static IMAGE_CHIP_ID = 2; - - static CHIP_DETECT_MAGIC_VALUE = 0x000007c6; - - static MAC_EFUSE_REG = 0x3f41A044; - - static EFUSE_BASE = 0x3f41A000; - - static UART_CLKDIV_REG = 0x3f400014; - - static UART_CLKDIV_MASK = 0xFFFFF; - - static UART_DATE_REG_ADDR = 0x60000078; - - static FLASH_WRITE_SIZE = 0x400; - - static BOOTLOADER_FLASH_OFFSET = 0x1000; - - static FLASH_SIZES = { +import ROM from './rom'; +import { toMac } from './util'; + +export default { + CHIP_NAME: 'ESP32-S2', + IS_STUB: true, + IMAGE_CHIP_ID: 2, + CHIP_DETECT_MAGIC_VALUE: 0x000007c6, + MAC_EFUSE_REG: 0x3f41A044, + EFUSE_BASE: 0x3f41A000, + UART_CLKDIV_REG: 0x3f400014, + UART_CLKDIV_MASK: 0xFFFFF, + UART_DATE_REG_ADDR: 0x60000078, + FLASH_WRITE_SIZE: 0x400, + BOOTLOADER_FLASH_OFFSET: 0x1000, + SPI_REG_BASE: 0x3f402000, + SPI_USR_OFFS: 0x18, + SPI_USR1_OFFS: 0x1c, + SPI_USR2_OFFS: 0x20, + SPI_W0_OFFS: 0x58, + SPI_MOSI_DLEN_OFFS: 0x24, + SPI_MISO_DLEN_OFFS: 0x28, + FLASH_SIZES: { '1MB': 0x00, '2MB': 0x10, '4MB': 0x20, '8MB': 0x30, '16MB': 0x40, - } as flashSizes; - - static SPI_REG_BASE = 0x3f402000; - - static SPI_USR_OFFS = 0x18; - - static SPI_USR1_OFFS = 0x1c; - - static SPI_USR2_OFFS = 0x20; + }, - static SPI_W0_OFFS = 0x58; - - static SPI_MOSI_DLEN_OFFS = 0x24; - - static SPI_MISO_DLEN_OFFS = 0x28; - - static async getPkgVersion(loader: ESPLoader) { + async getPkgVersion(loader: ESPLoader) { + if (!this.EFUSE_BASE) throw new Error('EFUSE_BASE not implemented'); const numWord = 3; const block1Addr = this.EFUSE_BASE + 0x044; const addr = block1Addr + (4 * numWord); const word3 = await loader.readReg(addr); const pkgVersion = (word3 >> 21) & 0x0F; return pkgVersion; - } + }, - static async getChipDescription(loader: ESPLoader) { + async getChipDescription(loader: ESPLoader) { + if (!this.getPkgVersion) throw new Error('getPkgVersion not implemented'); const chipDesc = ['ESP32-S2', 'ESP32-S2FH16', 'ESP32-S2FH32']; const pkgVer = await this.getPkgVersion(loader); if (pkgVer >= 0 && pkgVer <= 2) { return chipDesc[pkgVer]; } return 'unknown ESP32-S2'; - } + }, - static async getChipFeatures(loader: ESPLoader) { + async getChipFeatures(loader: ESPLoader) { + if (!this.EFUSE_BASE) throw new Error('EFUSE_BASE not implemented'); + if (!this.getPkgVersion) throw new Error('getPkgVersion not implemented'); const features = ['Wi-Fi']; const pkgVer = await this.getPkgVersion(loader); if (pkgVer === 1) { @@ -81,43 +65,19 @@ export default class ESP32S2ROM { features.push('ADC and temperature sensor calibration in BLK2 of efuse'); } return features; - } + }, // eslint-disable-next-line no-unused-vars - static async getCrystalFreq(loader: ESPLoader) { return 40; } + async getCrystalFreq(loader: ESPLoader) { return 40; }, - static #d2h(d: number) { - const h = (Number(d)).toString(16); - return h.length === 1 ? `0${h}` : h; - } - - static readMac = async (loader: ESPLoader) => { + async readMac(loader: ESPLoader) { + if (!this.MAC_EFUSE_REG) throw new Error('MAC_EFUSE_REG not implemented'); let mac0 = await loader.readReg(this.MAC_EFUSE_REG); mac0 >>>= 0; let mac1 = await loader.readReg(this.MAC_EFUSE_REG + 4); mac1 = (mac1 >>> 0) & 0x0000ffff; - const mac = new Uint8Array(6); - mac[0] = (mac1 >> 8) & 0xff; - mac[1] = mac1 & 0xff; - mac[2] = (mac0 >> 24) & 0xff; - mac[3] = (mac0 >> 16) & 0xff; - mac[4] = (mac0 >> 8) & 0xff; - mac[5] = mac0 & 0xff; - - return (`${ - this.#d2h(mac[0]) - }:${ - this.#d2h(mac[1]) - }:${ - this.#d2h(mac[2]) - }:${ - this.#d2h(mac[3]) - }:${ - this.#d2h(mac[4]) - }:${ - this.#d2h(mac[5]) - }`); - } + return toMac(mac0, mac1); + }, - static getEraseSize = (offset: number, size: number) => size -} \ No newline at end of file + getEraseSize(offset: number, size: number) { return size; }, +} as ROM; diff --git a/src/esp/roms/esp32s3beta2.ts b/src/esp/roms/esp32s3beta2.ts deleted file mode 100644 index 79931e1..0000000 --- a/src/esp/roms/esp32s3beta2.ts +++ /dev/null @@ -1,43 +0,0 @@ -import ESPLoader from '../ESPLoader'; - -interface flashSizes { - [key: string]: number; -} - -export default class ESP32S3BETA2ROM { - static CHIP_NAME = 'ESP32-S3'; - - static IMAGE_CHIP_ID = 4; - - static CHIP_DETECT_MAGIC_VALUE = 0xeb004136; - - // eslint-disable-next-line no-unused-vars - static async get_pkg_version(loader: ESPLoader) { - throw new Error('Not implemented'); - } - - // eslint-disable-next-line no-unused-vars - static async get_chip_revision(loader: ESPLoader) { - throw new Error('Not implemented'); - } - - // eslint-disable-next-line no-unused-vars - static async get_chip_description(loader: ESPLoader) { - throw new Error('Not implemented'); - } - - // eslint-disable-next-line no-unused-vars - static async get_chip_features(loader: ESPLoader) { - throw new Error('Not implemented'); - } - - // eslint-disable-next-line no-unused-vars - static async get_crystal_freq(loader: ESPLoader) { - throw new Error('Not implemented'); - } - - // eslint-disable-next-line no-unused-vars - static async read_mac(loader: ESPLoader) { - throw new Error('Not implemented'); - } -} diff --git a/src/esp/roms/esp8266.ts b/src/esp/roms/esp8266.ts index 31dd717..2b8399c 100644 --- a/src/esp/roms/esp8266.ts +++ b/src/esp/roms/esp8266.ts @@ -1,44 +1,30 @@ import ESPLoader from '../ESPLoader'; - -interface flashSizes { - [key: string]: number; -} - -export default class ESP8266ROM { - static CHIP_NAME = 'ESP8266'; - - static IS_STUB = true; - - static CHIP_DETECT_MAGIC_VALUE = 0xfff0c101; - - static FLASH_WRITE_SIZE = 0x400; - +import ROM from './rom'; +import { toHex } from './util'; + +export default { + CHIP_NAME: 'ESP8266', + IS_STUB: true, + CHIP_DETECT_MAGIC_VALUE: 0xfff0c101, + FLASH_WRITE_SIZE: 0x400, // OTP ROM addresses - static ESP_OTP_MAC0 = 0x3ff00050 - - static ESP_OTP_MAC1 = 0x3ff00054 - - static ESP_OTP_MAC3 = 0x3ff0005c - - static SPI_REG_BASE = 0x60000200 - - static SPI_USR_OFFS = 0x1c - - static SPI_USR1_OFFS = 0x20 - - static SPI_USR2_OFFS = 0x24 - - static SPI_MOSI_DLEN_OFFS = null - - static SPI_MISO_DLEN_OFFS = null - - static SPI_W0_OFFS = 0x40 - - static UART_CLKDIV_REG = 0x60000014 - - static XTAL_CLK_DIVIDER = 2 - - static FLASH_SIZES = { + ESP_OTP_MAC: [ + 0x3ff00050, + 0x3ff00054, + 0x3ff0005c, + 0x60000200, + ], + SPI_USR_OFFS: 0x1c, + SPI_USR1_OFFS: 0x20, + SPI_USR2_OFFS: 0x24, + SPI_MOSI_DLEN_OFFS: null, + SPI_MISO_DLEN_OFFS: null, + SPI_W0_OFFS: 0x40, + UART_CLKDIV_REG: 0x60000014, + XTAL_CLK_DIVIDER: 2, + BOOTLOADER_FLASH_OFFSET: 0, + UART_DATE_REG_ADDR: 0, + FLASH_SIZES: { '512KB': 0x00, '256KB': 0x10, '1MB': 0x20, @@ -48,27 +34,25 @@ export default class ESP8266ROM { '4MB-c1': 0x60, '8MB': 0x80, '16MB': 0x90, - } as flashSizes - - static BOOTLOADER_FLASH_OFFSET = 0 - - static UART_DATE_REG_ADDR = 0 + }, - static MEMORY_MAP = [[0x3FF00000, 0x3FF00010, 'DPORT'], + MEMORY_MAP: [ + [0x3FF00000, 0x3FF00010, 'DPORT'], [0x3FFE8000, 0x40000000, 'DRAM'], [0x40100000, 0x40108000, 'IRAM'], - [0x40201010, 0x402E1010, 'IROM']] + [0x40201010, 0x402E1010, 'IROM'], + ], - static async getEfuses(loader: ESPLoader) { + async getEfuses(loader: ESPLoader) { // Return the 128 bits of ESP8266 efuse as a single integer const result = (await loader.readReg(0x3ff0005c) << 96) | (await loader.readReg(0x3ff00058) << 64) | (await loader.readReg(0x3ff00054) << 32) | await loader.readReg(0x3ff00050); return result; - } + }, - static #getFlashSize(efuses: number) { + getFlashSize(efuses: number) { // rX_Y = EFUSE_DATA_OUTX[Y] const r0_4 = (efuses & (1 << 4)) !== 0; const r3_25 = (efuses & (1 << 121)) !== 0; @@ -90,13 +74,15 @@ export default class ESP8266ROM { } } return -1; - } + }, - static async getChipDescription(loader: ESPLoader) { + async getChipDescription(loader: ESPLoader) { + if (!this.getEfuses) throw new Error('getEfuses not implemented'); + if (!this.getFlashSize) throw new Error('getFlashSize not implemented'); const efuses = await this.getEfuses(loader); const is8285 = (efuses & (((1 << 4) | 1) << 80)) !== 0; // One or the other efuse bit is set for ESP8285 if (is8285) { - const flashSize = this.#getFlashSize(efuses); + const flashSize = this.getFlashSize(efuses); const maxTemp = (efuses & (1 << 5)) !== 0; // This efuse bit identifies the max flash temperature let chipName = 'ESP8285'; if (flashSize === 1) chipName = maxTemp ? 'ESP8285H08' : 'ESP8285N08'; @@ -104,45 +90,30 @@ export default class ESP8266ROM { return chipName; } return 'ESP8266EX'; - } + }, - static async getChipFeatures(loader: ESPLoader) { + async getChipFeatures(loader: ESPLoader) { const features = ['WiFi']; if (await this.getChipDescription(loader) === 'ESP8285') { features.push('Embedded Flash'); } return features; - } - - // static flash_spi_attach = async (loader, hspi_arg) => { - // if (this.IS_STUB) { - // await super.flash_spi_attach(loader, hspi_arg); - // } else { - // // ESP8266 ROM has no flash_spi_attach command in serial protocol, - // // but flash_begin will do it - // await loader.flash_begin(0, 0); - // } - // } - - // static flash_set_parameters = async (loader, size) => { - // // not implemented in ROM, but OK to silently skip for ROM - // if (this.IS_STUB) { - // await super.flash_set_parameters(loader, size); - // } - // } + }, - static async chipId(loader: ESPLoader) { + async chipId(loader: ESPLoader) { + if (!this.ESP_OTP_MAC) throw new Error('ESP_OTP_MAC not implemented'); // Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function - const id0 = await loader.readReg(this.ESP_OTP_MAC0); - const id1 = await loader.readReg(this.ESP_OTP_MAC1); + const id0 = await loader.readReg(this.ESP_OTP_MAC[0]); + const id1 = await loader.readReg(this.ESP_OTP_MAC[1]); return (id0 >> 24) | ((id1 & 0xffffff) << 8); - } + }, - static async readMac(loader: ESPLoader) { + async readMac(loader: ESPLoader) { + if (!this.ESP_OTP_MAC) throw new Error('ESP_OTP_MAC not implemented'); // Read MAC from OTP ROM - const mac0 = await loader.readReg(this.ESP_OTP_MAC0); - const mac1 = await loader.readReg(this.ESP_OTP_MAC1); - const mac3 = await loader.readReg(this.ESP_OTP_MAC3); + const mac0 = await loader.readReg(this.ESP_OTP_MAC[0]); + const mac1 = await loader.readReg(this.ESP_OTP_MAC[1]); + const mac3 = await loader.readReg(this.ESP_OTP_MAC[3]); let oui; if (mac3 !== 0) { oui = [(mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff]; @@ -153,11 +124,12 @@ export default class ESP8266ROM { } else { throw new Error('Unknown OUI'); } - return [...oui, (mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff]; - } + return [...oui, (mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff] + .map(toHex).join(':'); + }, - static getEraseSize(offset: number, size: number) { return size; } + getEraseSize(offset: number, size: number) { return size; }, // eslint-disable-next-line no-unused-vars - static async getCrystalFreq(loader: ESPLoader) { return 40; } -} \ No newline at end of file + async getCrystalFreq(loader: ESPLoader) { return 40; }, +} as ROM; \ No newline at end of file diff --git a/src/esp/roms/index.ts b/src/esp/roms/index.ts new file mode 100644 index 0000000..661b0cd --- /dev/null +++ b/src/esp/roms/index.ts @@ -0,0 +1,12 @@ + +import ESP32ROM from './esp32'; +import ESP32C3ROM from './esp32c3'; +import ESP32S2ROM from './esp32s2'; +import ESP8266ROM from './esp8266'; + +export default [ + ESP32ROM, + ESP32C3ROM, + ESP32S2ROM, + ESP8266ROM, +]; \ No newline at end of file diff --git a/src/esp/roms/rom.d.ts b/src/esp/roms/rom.d.ts new file mode 100644 index 0000000..2c5506c --- /dev/null +++ b/src/esp/roms/rom.d.ts @@ -0,0 +1,42 @@ +import ESPLoader from '../ESPLoader'; + +interface flashSizes { + [key: string]: number; +} + +export default interface ROM { + CHIP_NAME: string; + IS_STUB: boolean; + FLASH_SIZES: flashSizes; + IMAGE_CHIP_ID?: number; + CHIP_DETECT_MAGIC_VALUE: number; + EFUSE_BASE?: number; + MAC_EFUSE_REG?: number; + EFUSE_RD_REG_BASE?: number; + DR_REG_SYSCON_BASE?: number; + UART_CLKDIV_REG: number; + UART_CLKDIV_MASK?: number; + UART_DATE_REG_ADDR: number; + XTAL_CLK_DIVIDER?: number; + FLASH_WRITE_SIZE: number; + BOOTLOADER_FLASH_OFFSET: number; + SPI_REG_BASE?: number; + SPI_USR_OFFS: number; + SPI_USR1_OFFS: number; + SPI_USR2_OFFS: number; + SPI_W0_OFFS: number; + SPI_MOSI_DLEN_OFFS: number | null; + SPI_MISO_DLEN_OFFS: number | null; + ESP_OTP_MAC?: [number, number, number, number]; + + readEfuse?: (loader: ESPLoader, offset: number) => Promise; + getPkgVersion?: (loader: ESPLoader) => Promise; + getChipRevision?: (loader: ESPLoader) => Promise; + getChipDescription: (loader: ESPLoader) => Promise; + getChipFeatures: (loader: ESPLoader) => Promise; + getCrystalFreq: (loader: ESPLoader) => Promise; + readMac: (loader: ESPLoader) => Promise; + getEraseSize: (offset: number, size: number) => number; + getEfuses?: (loader: ESPLoader) => Promise; + getFlashSize?: (efuses: number) => number; +} diff --git a/src/esp/roms/util.ts b/src/esp/roms/util.ts new file mode 100644 index 0000000..fa75f6e --- /dev/null +++ b/src/esp/roms/util.ts @@ -0,0 +1,16 @@ +export const toHex = (num: number): string => { + const hex = num.toString(16); + return hex.length % 2 === 1 ? `0${hex}` : hex; +} + +export const toMac = (mac0: number, mac1: number): string => { + const mac = new Array(6); + mac[0] = (mac1 >> 8) & 0xff; + mac[1] = mac1 & 0xff; + mac[2] = (mac0 >> 24) & 0xff; + mac[3] = (mac0 >> 16) & 0xff; + mac[4] = (mac0 >> 8) & 0xff; + mac[5] = mac0 & 0xff; + + return mac.map(toHex).join(':'); +} From bad613f904ff6d3b92a1d096c93a75c87aa1a130 Mon Sep 17 00:00:00 2001 From: mrfrase3 Date: Sun, 9 Oct 2022 13:55:39 +0800 Subject: [PATCH 4/6] add esp interface and initial testing --- src/esp/ESPLoader.ts | 2 +- src/esp/index.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 16 +++++----- test/boards.ts | 6 +++- test/test-config.yml | 52 +++++++++++++++++++------------- test/util.ts | 7 +++-- 6 files changed, 122 insertions(+), 32 deletions(-) create mode 100644 src/esp/index.ts diff --git a/src/esp/ESPLoader.ts b/src/esp/ESPLoader.ts index 8de58c6..09fa64f 100644 --- a/src/esp/ESPLoader.ts +++ b/src/esp/ESPLoader.ts @@ -5,7 +5,7 @@ import StubLoader from './StubLoader'; import roms from './roms'; import ROM from './roms/rom'; -interface ESPOptions { +export interface ESPOptions { quiet?: boolean; stubUrl?: string; stdout?: any; diff --git a/src/esp/index.ts b/src/esp/index.ts new file mode 100644 index 0000000..806fbf4 --- /dev/null +++ b/src/esp/index.ts @@ -0,0 +1,71 @@ +import { SerialPort } from 'serialport/dist/index.d'; +import { ProgramConfig } from '../index.d'; +import ESPLoader, { ESPOptions, UploadFileDef } from './ESPLoader'; +import asyncTimeout from '../util/asyncTimeout'; + +const isSupported = (cpu: string) => ['esp8266', 'esp32'].includes(cpu); + +export const upload = async (serial: SerialPort, config: ProgramConfig) => { + if (!config.files?.length) throw new Error('No files to upload'); + // const log = (...args) => config.debug(`${args.join(' ')}\r\n`); + const log = (...args: any[]) => console.log(...args); + // const term = { log, debug: log, write: config.debug }; + + // const { port } = serial; + // const transport = new Transport(port, term); + let espLoader; + + try { + log('> Connecting...'); + espLoader = new ESPLoader(serial, { + quiet: !config.verbose, + } as ESPOptions); + await espLoader.mainFn(); + // await espLoader.flash_id(); + log('> Connected'); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + // log('Failed to connect:', typeof err === 'string' ? err : err.message); + try { + await serial.close(); + } catch (err2) { + // eslint-disable-next-line no-console + console.error(err2); + } + return; + } + + try { + // if (board.config?.wipe && board.config.wipe !== 'none') { + // log('> Erasing device flash...'); + // await espLoader.erase_flash(); + // log('> Successfully erased device flash'); + // } + log('> Writing main data partition, this may take a while...'); + await espLoader.writeFlash({ + fileArray: config.files.map((file) => ({ ...file, data: Buffer.from(file.data, 'base64') })), + flashSize: 'keep', + // flash_freq, + // flash_mode, + // compress: board.props?.build?.mcu !== 'esp8266', + }); + await espLoader.flashDeflFinish({ reboot: true }); + await asyncTimeout(100); + log('> Successfully written data partition'); + log('> Flashing succeeded! Have a nice day! :)'); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + log('Failed to upload:', err instanceof Error ? err.message : err); + } + + // try { + // await serial.close(); + // } catch (err) { + // // eslint-disable-next-line no-console + // console.error(err); + // } +}; + +export default { upload, isSupported }; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 22b071b..b7e9300 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { SerialPort } from 'serialport/dist/index.d'; import { setBaud, waitForOpen } from './util/serial-helpers'; import avr from './avr'; -// import esp from './esp'; +import esp from './esp'; export const upload = async (serial: SerialPort, config: ProgramConfig) => { if (!config.hex && !config.files?.length) { @@ -27,10 +27,10 @@ export const upload = async (serial: SerialPort, config: ProgramConfig) => { case 'avrdude': await avr.upload(serial, config); break; - // case 'esptool': - // case 'esptool_py': - // await esp.upload(serial, config); - // break; + case 'esptool': + case 'esptool_py': + await esp.upload(serial, config); + break; default: throw new Error(`Tool ${config.tool} not supported`); } @@ -46,9 +46,9 @@ export const isSupported = (tool: string, cpu: string) => { case 'avr': case 'avrdude': return avr.isSupported(cpu); - // case 'esptool': - // case 'esptool_py': - // return esp.isSupported(cpu); + case 'esptool': + case 'esptool_py': + return esp.isSupported(cpu); default: return false; } diff --git a/test/boards.ts b/test/boards.ts index 0ca389a..9d991a0 100644 --- a/test/boards.ts +++ b/test/boards.ts @@ -5,11 +5,13 @@ import { SerialPort } from 'serialport'; import { upload } from '../src/index'; import { waitForData, config, getHex } from './util'; import { waitForOpen } from '../src/util/serial-helpers'; +import { ProgramFile } from '../src/index.d'; Object.keys(config.devices).forEach((deviceRef) => { const device = config.devices[deviceRef]; let key = ''; - let hex: Buffer; + let hex: Buffer | undefined; + let files: ProgramFile[] | undefined; let serial: SerialPort; describe(`upload to ${device.name}`, function () { @@ -19,6 +21,7 @@ Object.keys(config.devices).forEach((deviceRef) => { const res = await getHex(device.code, device.fqbn); key = res.key; hex = res.hex; + files = res.files; console.log('compiled hex'); }); @@ -44,6 +47,7 @@ Object.keys(config.devices).forEach((deviceRef) => { it(`should upload to ${device.name}`, async () => { await upload(serial, { hex, + files, speed: device.speed, tool: device.tool, cpu: device.cpu, diff --git a/test/test-config.yml b/test/test-config.yml index 29f294a..66823b8 100644 --- a/test/test-config.yml +++ b/test/test-config.yml @@ -1,26 +1,38 @@ devices: - uno: - name: Arduino Uno - # Find VID and PID in the Arduino IDE > Tools > Get Board Info + # uno: + # name: Arduino Uno + # # Find VID and PID in the Arduino IDE > Tools > Get Board Info + # vendorIds: + # - '2341' + # productIds: + # - '0001' + # code: blink + # fqbn: arduino:avr:uno + # cpu: atmega328p + # tool: avrdude + # speed: 115200 + # mega: + # name: Arduino Mega 2560 + # vendorIds: + # - '2341' + # productIds: + # - '0042' + # code: blink + # fqbn: arduino:avr:mega + # cpu: atmega2560 + # tool: avrdude + # speed: 115200 + nodemcuv2: + name: ESP 8266 - Node MCU V2 vendorIds: - - '2341' + - '1a86' productIds: - - '0001' + - '7523' code: blink - fqbn: arduino:avr:uno - cpu: atmega328p - tool: avrdude + fqbn: esp8266:esp8266:nodemcuv2 + cpu: esp8266 + tool: esptool speed: 115200 - mega: - name: Arduino Mega 2560 - vendorIds: - - '2341' - productIds: - - '0042' - code: blink - fqbn: arduino:avr:mega - cpu: atmega2560 - tool: avrdude - speed: 115200 -# verbose: true + +verbose: true compileServer: https://compile.duino.app \ No newline at end of file diff --git a/test/util.ts b/test/util.ts index 40aaf1f..c687ed9 100644 --- a/test/util.ts +++ b/test/util.ts @@ -3,6 +3,7 @@ import YAML from 'yaml'; import fs from 'fs'; import axios from 'axios'; import path from 'path'; +import { ProgramFile } from '../src/index.d'; export const waitForData = ( serial: SerialPort, @@ -33,7 +34,8 @@ export const waitForData = ( export const config = YAML.parse(fs.readFileSync(path.join(__dirname, 'test-config.yml'), 'utf8')); interface HexResult { - hex: Buffer; + hex?: Buffer; + files?: ProgramFile[]; key: string; code: string; } @@ -51,7 +53,8 @@ export const getHex = async (file: string, fqbn: string): Promise => }], }); return { - hex: Buffer.from(res.data.hex, 'base64'), + hex: res.data.hex ? Buffer.from(res.data.hex, 'base64') : undefined, + files: res.data.files as ProgramFile[], key, code, } as HexResult; From abd585b9fc4c06a3067baaa44af214f0fce7cb9d Mon Sep 17 00:00:00 2001 From: mrfrase3 Date: Tue, 25 Oct 2022 19:40:50 +0800 Subject: [PATCH 5/6] stable esp uploading --- src/esp/ESPLoader.ts | 1919 +++++++++++++++++++----------------- src/esp/StubLoader.ts | 23 +- src/esp/index.ts | 36 +- src/esp/roms/esp32c3.ts | 1 + src/esp/roms/esp32s2.ts | 1 + src/esp/roms/rom.d.ts | 1 + src/global.d.ts | 2 + src/index.d.ts | 3 + src/util/serial-helpers.ts | 2 +- test/boards.ts | 43 +- test/code/ping.ino | 42 + test/index.ts | 12 +- test/test-config.yml | 82 +- test/util.ts | 96 +- 14 files changed, 1313 insertions(+), 950 deletions(-) create mode 100644 test/code/ping.ino diff --git a/src/esp/ESPLoader.ts b/src/esp/ESPLoader.ts index 09fa64f..d8243ea 100644 --- a/src/esp/ESPLoader.ts +++ b/src/esp/ESPLoader.ts @@ -17,996 +17,1153 @@ export interface UploadFileDef { } export default class ESPLoader { - ESP_RAM_BLOCK = 0x1800; + ESP_RAM_BLOCK = 0x1800; - ESP_FLASH_BEGIN = 0x02; + ESP_FLASH_BEGIN = 0x02; - ESP_FLASH_DATA = 0x03; + ESP_FLASH_DATA = 0x03; - ESP_FLASH_END = 0x04; - - ESP_MEM_BEGIN = 0x05; - - ESP_MEM_END = 0x06; - - ESP_MEM_DATA = 0x07; - - ESP_WRITE_REG = 0x09; + ESP_FLASH_END = 0x04; + + ESP_MEM_BEGIN = 0x05; + + ESP_MEM_END = 0x06; + + ESP_MEM_DATA = 0x07; - ESP_FLASH_DEFL_BEGIN = 0x10; + ESP_SYNC = 0x08; + + ESP_WRITE_REG = 0x09; - ESP_FLASH_DEFL_DATA = 0x11; + ESP_FLASH_DEFL_BEGIN = 0x10; - ESP_FLASH_DEFL_END = 0x12; + ESP_FLASH_DEFL_DATA = 0x11; - ESP_SPI_FLASH_MD5 = 0x13; + ESP_FLASH_DEFL_END = 0x12; - ESP_READ_REG = 0x0A; + ESP_SPI_FLASH_MD5 = 0x13; - ESP_SPI_ATTACH = 0x0D; + ESP_READ_REG = 0x0A; - // Only Stub supported commands - ESP_ERASE_FLASH = 0xD0; + ESP_SPI_ATTACH = 0x0D; - ESP_ERASE_REGION = 0xD1; + // Only Stub supported commands + ESP_CHANGE_BAUDRATE = 0x0F; - ESP_IMAGE_MAGIC = 0xe9; + ESP_ERASE_FLASH = 0xD0; - ESP_CHECKSUM_MAGIC = 0xef; + ESP_ERASE_REGION = 0xD1; - ERASE_REGION_TIMEOUT_PER_MB = 30000; + ESP_IMAGE_MAGIC = 0xe9; - ERASE_WRITE_TIMEOUT_PER_MB = 40000; + ESP_CHECKSUM_MAGIC = 0xef; - MD5_TIMEOUT_PER_MB = 8000; + ERASE_REGION_TIMEOUT_PER_MB = 30000; - CHIP_ERASE_TIMEOUT = 120000; + ERASE_WRITE_TIMEOUT_PER_MB = 40000; - MAX_TIMEOUT = this.CHIP_ERASE_TIMEOUT * 2; + MD5_TIMEOUT_PER_MB = 8000; - CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000; + CHIP_ERASE_TIMEOUT = 120000; - DETECTED_FLASH_SIZES = { - 0x12: '256KB', 0x13: '512KB', 0x14: '1MB', 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB', - } as { [key: number]: string }; + MAX_TIMEOUT = this.CHIP_ERASE_TIMEOUT * 2; - opts: ESPOptions; - quiet: boolean; - serial: SerialPort; - IS_STUB: boolean; - chip: ROM | null; - stdout: any; - stubLoader: StubLoader; - syncStubDetected: boolean; - FLASH_WRITE_SIZE: number; - - constructor(serial: SerialPort, opts: ESPOptions) { - this.opts = opts || {}; - this.quiet = this.opts.quiet || false; - this.serial = serial; - this.IS_STUB = false; - this.chip = null; - this.stdout = opts.stdout || process?.stdout || { - write: (str: string) => console.log(str), - }; - this.stubLoader = new StubLoader(this.opts.stubUrl); - this.syncStubDetected = false; - this.FLASH_WRITE_SIZE = 0x4000 - } + CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000; - #sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } + DETECTED_FLASH_SIZES = { + 0x12: '256KB', 0x13: '512KB', 0x14: '1MB', 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB', + } as { [key: number]: string }; - log (...args: any[]) { - if (this.quiet) return; - this.stdout.write(`${args.map(arg => `${arg}`).join(' ')}\n`); - } + opts: ESPOptions; + quiet: boolean; + serial: SerialPort; + IS_STUB: boolean; + chip: ROM | null; + stdout: any; + stubLoader: StubLoader; + syncStubDetected: boolean; + FLASH_WRITE_SIZE: number; - logChar(str: string) { - if (this.stdout) { - this.stdout.write(str); - } else { - // eslint-disable-next-line no-console - console.log(str); - } - } + constructor(serial: SerialPort, opts = {} as ESPOptions) { + this.opts = opts || {}; + this.quiet = this.opts.quiet || false; + this.serial = serial; + this.IS_STUB = false; + this.chip = null; + this.stdout = opts.stdout || process?.stdout || { + write: (str: string) => console.log(str), + }; + this.stubLoader = new StubLoader(this.opts.stubUrl); + this.syncStubDetected = false; + this.FLASH_WRITE_SIZE = 0x4000 + } - #shortToByteArray(i: number) { - return new Uint8Array([i & 0xff, (i >> 8) & 0xff]); - } - - #intToByteArray(i: number) { - return new Uint8Array([i & 0xff, (i >> 8) & 0xff, (i >> 16) & 0xff, (i >> 24) & 0xff]); - } + // pause execution for x ms + #sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } - #byteArrayToShort(arr: [number, number]) { - const [i, j] = arr; - return (new Uint16Array([(i | (j >> 8))]))[0]; - } + // log out a line of text + log (...args: any[]) { + if (this.quiet) return; + this.stdout.write(`${args.map(arg => `${arg}`).join(' ')}\n`); + } - #byteArrayToInt(arr: [number, number, number, number]) { - const [i, j, k, l] = arr; - return (new Uint32Array([(i | (j << 8) | (k << 16) | (l << 24))]))[0]; + // log out a set of characters + logChar(str: string) { + if (this.quiet) return; + if (this.stdout) { + this.stdout.write(str); + } else { + // eslint-disable-next-line no-console + console.log(str); } - - #appendArray(arr1: Uint8Array, arr2: Uint8Array) { - const c = new Uint8Array(arr1.length + arr2.length); - c.set(arr1, 0); - c.set(arr2, arr1.length); - return c; + } + + // convert a number into a Uint8Array of 2 bytes, little endian + #shortToByteArray(i: number): Uint8Array { + const buff = Buffer.alloc(2); + buff.writeUInt16LE(i, 0); + return new Uint8Array(buff); + } + + // convert a number into a Uint8Array of 4 bytes, little endian + #intToByteArray(i: number) { + const buff = Buffer.alloc(4); + buff.writeUInt32LE(i, 0); + return new Uint8Array(buff); + } + + // convert an array of 2 bytes into a number, little endian + #byteArrayToShort(arr: [number, number] | Buffer) { + const buff = Buffer.alloc(2); + buff.set(arr, 0); + return buff.readUInt16LE(0); + } + + // convert an array of 4 bytes into a number, little endian + #byteArrayToInt(arr: [number, number, number, number] | Buffer) { + const buff = Buffer.alloc(4); + buff.set(arr, 0); + return buff.readUInt32LE(0); + } + + // join Uint8Arrays or Buffers together + #appendArray(...arrays: Buffer[] | Uint8Array[]) { + const arrayLengths = arrays.map((arr) => arr.length); + const totalLength = arrayLengths.reduce((a, b) => a + b, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + arrays.forEach((arr) => { + result.set(arr, offset); + offset += arr.length; + }); + return result; + } + + // flush the serial port + #flushInput = async () => { + try { + await this.serial.flush(); + } catch (e) {} + } + + // convert data before sending to the device https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol + async write(data: Buffer) { + const slippedArr = []; + for (let i = 0; i < data.length; i++) { + const byte = data[i]; + if (byte === 0xC0) slippedArr.push(0xDB, 0xDC); // escape the end char + else if (byte === 0xDB) slippedArr.push(0xDB, 0xDD); // escape the escape char + else slippedArr.push(byte); } - - #flushInput = async () => { - try { - await this.serial.flush(); - } catch (e) {} - } - - // convert data before sending to https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol - async write(data: Buffer) { - const slippedArr = []; - for (let i = 0; i < data.length; i++) { - if (i === 0xC0) slippedArr.push(0xDB, 0xDC); // escape the end char - else if (i === 0xDB) slippedArr.push(0xDB, 0xDD); // escape the escape char - else slippedArr.push(data[i]); - } - const pkt = Buffer.from([ - 0xC0, - ...slippedArr, - 0xC0, - ]); - return this.serial.write(pkt); - } - - read(timeout = 0, flush = false): Promise { - return new Promise((resolve, reject) => { - let buffer = Buffer.alloc(0); - let started = false; - let timeoutId = null as NodeJS.Timeout | null; - let handleChunk = (data: Buffer) => {}; - const finished = (err?: Error) => { - if (timeoutId) { - clearTimeout(timeoutId); - } - this.serial.removeListener('data', handleChunk); - if (err) { - if (flush && buffer.length) { - resolve(buffer); - } else { - reject(err); - } - } else { + const pkt = Buffer.from([0xC0, ...slippedArr, 0xC0]); + return this.serial.write(pkt); + } + + // read data from the device, un-slipping it + read(timeout = 0, flush = false): Promise { + return new Promise((resolve, reject) => { + // initialise some variables + let buffer = Buffer.alloc(0); + let started = false; + let timeoutId = null as NodeJS.Timeout | null; + let handleChunk = (data: Buffer) => {}; + // finish handler that cleans up the listeners and resolves the promise + const finished = (err?: Error) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + this.serial.removeListener('data', handleChunk); + if (err) { + if (flush && buffer.length) { resolve(buffer); + } else { + reject(err); } - }; - handleChunk = (data: Buffer) => { - if (flush) { - Buffer.concat([buffer, data]); - return; - } - const pkt = [] as number[]; - let inEscape = false; - for (let i = 0; i < data.length; i++) { - const byte = data[i]; - if (started) { - if (byte === 0xC0) { - started = false; - break; - } else if (byte === 0xDC && inEscape) { - pkt.push(0xC0); - inEscape = false; - } else if (byte === 0xDD && inEscape) { - pkt.push(0xDB); - inEscape = false; - } else if (byte === 0xDB) { - inEscape = true; - } else { - pkt.push(byte); - inEscape = false; - } - } else if (byte === 0xC0) { - started = true; + } else { + resolve(buffer); + } + }; + // handle a chunk of data + handleChunk = (data: Buffer) => { + if (flush) { // don't bother parsing the data, just return it + Buffer.concat([buffer, data]); + return; + } + // loop through each byte, looking for the start and end of the packet + // and un-escaping any escaped characters in the middle + const pkt = [] as number[]; + let inEscape = false; + for (let i = 0; i < data.length; i++) { + const byte = data[i]; + if (started) { + if (byte === 0xC0) { + started = false; + break; + } else if (byte === 0xDC && inEscape) { + pkt.push(0xC0); + inEscape = false; + } else if (byte === 0xDD && inEscape) { + pkt.push(0xDB); + inEscape = false; + } else if (byte === 0xDB) { + inEscape = true; + } else { + pkt.push(byte); + inEscape = false; } + } else if (byte === 0xC0) { + started = true; } - if (pkt.length) buffer = Buffer.concat([buffer, new Uint8Array(pkt)]); - if (buffer.length && !started) { - finished(); - } - }; - if (timeout && timeout > 0) { - timeoutId = setTimeout(() => { - timeoutId = null; - finished(new Error(`receiveData timeout after ${timeout}ms`)); - }, timeout); } - this.serial.on('data', handleChunk); - }); - } - - async command({ - op = null as number | null, - data = [] as number[] | Uint8Array | Buffer, - chk = 0, - waitResponse = true, - timeout = 3000, - // min_data = 12, - } = {}): Promise<[number, Buffer]> { - // console.log("command "+ op + " " + wait_response + " " + timeout); - if (op) { - const pkt = Buffer.from([ - 0x00, - op, - ...this.#shortToByteArray(data.length), // 2-3 - ...this.#intToByteArray(chk), // 4-7 - ...data, // 8+ - ]); - // console.log("Command " + pkt); - await this.serial.write(pkt); + if (pkt.length) buffer = Buffer.concat([buffer, new Uint8Array(pkt)]); + // if the packet is complete, call the finished handler + if (buffer.length && !started) { + finished(); + } + }; + // set up the timeout handler + if (timeout && timeout > 0) { + timeoutId = setTimeout(() => { + timeoutId = null; + finished(new Error(`receiveData timeout after ${timeout}ms`)); + }, timeout); } + // register the chunk handler + this.serial.on('data', handleChunk); + }); + } + + // send a command to the device and optionally wait for a response + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#command-packet + async command({ + op = null as number | null, + data = [] as number[] | Uint8Array | Buffer, + chk = 0, // checksum + waitResponse = true, + timeout = 3000, + // min_data = 12, + } = {}): Promise<[number, Buffer]> { + // console.log('command ', op, waitResponse, timeout); + if (op) { + const pkt = Buffer.from([ + 0x00, + op, + ...this.#shortToByteArray(data.length), // 2-3 + ...this.#intToByteArray(chk), // 4-7 + ...data, // 8+ + ]); + await this.write(pkt); + } - if (waitResponse) { - const p = await this.read(timeout); - // console.log(this.transport.slip_reader_enabled, p); - // const resp = p[0]; - const op_ret = p[1]; - // const len_ret = this._bytearray_to_short(p[2], p[3]); - const val = this.#byteArrayToInt([p[4], p[5], p[6], p[7]]); - // eslint-disable-next-line no-console - // console.log(`Resp ${resp} ${op_ret} ${op} ${len_ret} ${val} ${p}`); - const datum = p.subarray(8); - if (!op || op_ret === op) { - return [val, datum]; - } - throw new Error(`Invalid response. Expected ${op.toString(16)}`); + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#response-packet + if (waitResponse) { + const packet = await this.read(timeout, false); + // const direction = packet[0]; // always 0x01 + const command = packet[1]; + const resDataSize = this.#byteArrayToShort(packet.subarray(2, 4)); + // value is the data returned from readReg commands, otherwise it's 0x00 + const value = this.#byteArrayToInt(packet.subarray(4, 8)); + const resData = packet.subarray(8, 8 + resDataSize); + if (!op || command === op) { + return [value, resData]; } - return [0, Buffer.from([])]; + throw new Error(`Invalid response. Expected ${op.toString(16)}, got ${command.toString(16)}`); } - - async readReg(addr: number, timeout = 3000) { - // console.log(`read reg ${addr} ${timeout}`); - const pkt = this.#intToByteArray(addr); - const val = await this.command({ op: this.ESP_READ_REG, data: pkt, timeout }); - // console.log('Read reg resp', val); - return val[0]; + return [0x00, Buffer.from([])]; + } + + // read from the device's memory at a given address + async readReg(addr: number, timeout = 3000) { + // console.log(`read reg ${addr} ${timeout}`); + const pkt = this.#intToByteArray(addr); + const val = await this.command({ op: this.ESP_READ_REG, data: pkt, timeout }); + // console.log('Read reg resp', val); + return val[0]; + } + + // write to the device's memory at a given address + async writeReg(addr: number, value: number, mask = 0xFFFFFFFF, delayUs = 0, delayAfterUs = 0) { + if (!this.chip) throw new Error('Chip not initialized'); + let pkt = this.#appendArray( + this.#intToByteArray(addr), + this.#intToByteArray(value), + this.#intToByteArray(mask), + this.#intToByteArray(delayUs), + ); + + if (delayAfterUs > 0) { + pkt = this.#appendArray( + pkt, + this.#intToByteArray(this.chip.UART_DATE_REG_ADDR), + this.#intToByteArray(0), + this.#intToByteArray(0), + this.#intToByteArray(delayAfterUs), + ); } - async writeReg(addr: number, value: number, mask = 0xFFFFFFFF, delayUs = 0, delayAfterUs = 0) { - if (!this.chip) throw new Error('Chip not initialized'); - 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({ + opDescription: 'write target memory', + op: this.ESP_WRITE_REG, + data: pkt, + }); + } + + // try to initialise synchronisation with the device + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#initial-synchronisation + async sync() { + const cmd = Buffer.alloc(36, 0x55); + cmd.set([0x07, 0x07, 0x12, 0x20], 0); + + const resp = await this.command({ op: this.ESP_SYNC, data: cmd, timeout: 100 }); + this.syncStubDetected = resp[0] === 0; + return resp; + } + + // attempt to establish a synced connection with the device + async #connectAttempt({ mode = 'default_reset', esp32r0Delay = false } = {}) { + // console.log(`_connect_attempt ${esp32r0Delay}`); + if (mode !== 'no_reset') { + // reset the device before syncing + await this.serial.set({ dtr: false, rts: false }); + await this.#sleep(100); + await this.serial.set({ dtr: false, rts: true }); + await this.#sleep(100); + if (esp32r0Delay) { + // await this._sleep(1200); + await this.#sleep(2000); } - - await this.checkCommand({ opDescription: 'write target memory', op: this.ESP_WRITE_REG, data: pkt }); - } - - async sync() { - // console.log('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; + await this.serial.set({ dtr: true, rts: false }); + if (esp32r0Delay) { + // await this._sleep(400); } - - const resp = await this.command({ op: 0x08, data: cmd, timeout: 100 }); - this.syncStubDetected = resp[0] === 0; - return resp; + await this.#sleep(50); + await this.serial.set({ dtr: false, rts: false }); } - - async #connectAttempt({ mode = 'default_reset', esp32r0Delay = false } = {}) { - // console.log(`_connect_attempt ${esp32r0Delay}`); - if (mode !== 'no_reset') { - await this.serial.set({ dtr: false, rts: true }); - await this.#sleep(100); - if (esp32r0Delay) { - // await this._sleep(1200); - await this.#sleep(2000); - } - await this.serial.set({ dtr: true, rts: false }); - if (esp32r0Delay) { - // await this._sleep(400); - } - await this.#sleep(50); - await this.serial.set({ dtr: false }); - } - let i = 0; - // eslint-disable-next-line no-constant-condition - while (1) { - try { - const res = await this.read(1000, true); - i += res.length; - // console.log("Len = " + res.length); - // var str = new TextDecoder().decode(res); - // this.log(str); - } catch (err) { - if (err instanceof Error && err.message.includes('timeout')) { - break; - } + // wait until the device is finished booting (writing initial data to serial) + // eslint-disable-next-line no-constant-condition + while (1) { + try { + await this.read(1000, true); + } catch (err) { + // if nothing was read, the device is ready + if (err instanceof Error && err.message.includes('timeout')) { + break; } - await this.#sleep(50); } - // this.transport.slip_reader_enabled = true; - i = 7; - while (i--) { - try { - await this.sync(); - return 'success'; - } catch (err) { - if (err instanceof Error && err.message.includes('timeout')) { - this.logChar(esp32r0Delay ? '_' : '.'); - } + await this.#sleep(50); + } + // try to sync with the device 8 times + for (let i = 0; i < 8; i++) { + try { + await this.sync(); + return 'success'; + } catch (err) { + if (err instanceof Error && err.message.includes('timeout')) { + this.logChar(esp32r0Delay ? '_' : '.'); } - await this.#sleep(50); } + await this.#sleep(50); + } + return 'error'; + } + + // establish a synced connection with the device + // eslint-disable-next-line no-unused-vars + async connect({ mode = 'default_reset', attempts = 7, detecting = false } = {}) { + let resp = ''; + this.logChar('Connecting...'); + // try several times to connect, toggling between delay options + for (let i = 0; i < attempts * 2; i++) { + resp = await this.#connectAttempt({ mode, esp32r0Delay: i % 2 === 0 }); + if (resp === 'success') break; + } + this.logChar('\n'); + this.logChar('\r'); + if (resp !== 'success') { + this.log('Failed to connect with the device'); return 'error'; } + await this.#sleep(100); + await this.#flushInput(); - // eslint-disable-next-line no-unused-vars - async connect({ mode = 'default_reset', attempts = 7, detecting = false } = {}) { - let resp = ''; - this.logChar('Connecting...'); - // await this.transport.connect(); - await (new Array(attempts)).fill(0).reduce(async (promise) => { - await promise; - if (resp === 'success') return; - resp = await this.#connectAttempt({ esp32r0Delay: false }); - if (resp === 'success') return; - resp = await this.#connectAttempt({ esp32r0Delay: true }); - if (resp === 'success') return; - }, Promise.resolve()); - if (resp !== 'success') { - this.log('Failed to connect with the device'); - return 'error'; - } - this.logChar('\n'); - this.logChar('\r'); - await this.#sleep(100); - await this.#flushInput(); - - if (!detecting) { - const chipMagicValue = await this.readReg(0x40001000); - // eslint-disable-next-line no-console - // console.log(`Chip Magic ${chip_magic_value}`); - this.chip = roms.find((cls) => chipMagicValue === cls.CHIP_DETECT_MAGIC_VALUE) ?? null; - // console.log('chip', this.chip); - } - return null; + // try to detect the chip we're dealing with + if (!detecting) { + const chipMagicValue = await this.readReg(0x40001000); + // eslint-disable-next-line no-console + // console.log(`Chip Magic ${chip_magic_value}`); + this.chip = roms.find((cls) => chipMagicValue === cls.CHIP_DETECT_MAGIC_VALUE) ?? null; + // console.log('chip', this.chip); } - - async detectChip() { - await this.connect(); - this.logChar('Detecting chip type... '); - if (this.chip !== null) { - this.log(this.chip.CHIP_NAME); - } + return null; + } + + // connect to the device and detect the chip + async detectChip() { + await this.connect(); + this.logChar('Detecting chip type... '); + if (this.chip !== null) { + this.log(this.chip.CHIP_NAME); } + } - async checkCommand({ - // eslint-disable-next-line no-unused-vars - opDescription = '', - op = null as number | null, - data = [] as number[] | Uint8Array | Buffer, - chk = 0, - timeout = 3000, - /* min_data, */ - } = {}) { - // console.log(`checkCommand ${op}`); - const resp = await this.command({ - op, data, chk, timeout, /* min_data, */ - }); - if (resp[1].length > 4) { - return resp[1]; - } - return resp[0]; + // run a command and check the response, sort the readReg values from the data + async checkCommand({ + // eslint-disable-next-line no-unused-vars + opDescription = '', // useful for debugging + op = null as number | null, + data = [] as number[] | Uint8Array | Buffer, + chk = 0, + timeout = 3000, + /* min_data, */ + } = {}) { + // console.log(`checkCommand ${op}`); + const resp = await this.command({ + op, data, chk, timeout, /* min_data, */ + }); + if (resp[1].length > 4) { + return resp[1]; } - - async memBegin(size: number, blocks: number, blocksize: number, offset: number) { - /* XXX: Add check to ensure that STUB is not getting overwritten */ - // console.log(`memBegin ${size} ${blocks} ${blocksize} ${offset}`); - 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({ opDescription: 'write to target RAM', op: this.ESP_MEM_BEGIN, data: pkt }); + return resp[0]; + } + + // create a checksum number for a set of data by XORing all the bytes + #checksum(data: number[] | Uint8Array | Buffer) { + let chk = 0xEF; + for (let i = 0; i < data.length; i++) { + chk ^= data[i]; + } + return chk; + } + + // initialise a write to the device's memory with config data + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async memBegin(size: number, blocks: number, blocksize: number, offset: number) { + /* XXX: Add check to ensure that STUB is not getting overwritten */ + // console.log(`memBegin ${size} ${blocks} ${blocksize} ${offset}`); + const pkt = this.#appendArray( + this.#intToByteArray(size), + this.#intToByteArray(blocks), + this.#intToByteArray(blocksize), + this.#intToByteArray(offset), + ); + await this.checkCommand({ + opDescription: 'begin write to target RAM', + op: this.ESP_MEM_BEGIN, + data: pkt, + }); + } + + // write a block of data to the device's memory + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async memBlock(buffer: Uint8Array, seq: number) { + let pkt = this.#appendArray( + this.#intToByteArray(buffer.length), + this.#intToByteArray(seq), + this.#intToByteArray(0), + this.#intToByteArray(0), + buffer, + ); + const checksum = this.#checksum(buffer); + return this.checkCommand({ + opDescription: 'write to target RAM', + op: this.ESP_MEM_DATA, + data: pkt, + chk: checksum, + }); + } + + // finish writing to the device's memory, and run the code at the specified address + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async memFinish(entrypoint: number) { + const is_entry = (entrypoint === 0) ? 1 : 0; + const pkt = this.#appendArray(this.#intToByteArray(is_entry), this.#intToByteArray(entrypoint)); + return this.checkCommand({ + opDescription: 'leave RAM upload mode', + op: this.ESP_MEM_END, + data: pkt, + timeout: 500, + }); // XXX: handle non-stub with diff timeout + } + + // configure SPI flash pins + async flashSpiAttach(hspiArg: number) { + const pkt = this.#intToByteArray(hspiArg); + await this.checkCommand({ + opDescription: 'configure SPI flash pins', + op: this.ESP_SPI_ATTACH, + data: pkt, + }); + } + + // calculate the timeout required for a large data transfer + #timeoutPerMb(secondsPerMb: number, sizeBytes: number) { + const result = secondsPerMb * (sizeBytes / 1000000); + return Math.min(result, 3000); + } + + // initialise a write to the device's SPI Flash with config data + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async flashBegin(size: number, offset: number) { + if (!this.chip) throw new Error('chip not initialized'); + const numBlocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); + const eraseSize = this.chip.getEraseSize(offset, size); + const t1 = Date.now(); + + let timeout = 3000; + if (this.IS_STUB === false) { + timeout = this.#timeoutPerMb(this.ERASE_REGION_TIMEOUT_PER_MB, size); } - checksum(data: number[] | Uint8Array | Buffer) { - let i; - let chk = 0xEF; - - for (i = 0; i < data.length; i++) { - chk ^= data[i]; - } - return chk; + // eslint-disable-next-line no-console + // console.log(`flash begin ${eraseSize} ${numBlocks} ${this.FLASH_WRITE_SIZE} ${offset} ${size}`); + let pkt = this.#appendArray( + this.#intToByteArray(eraseSize), + this.#intToByteArray(numBlocks), + this.#intToByteArray(this.FLASH_WRITE_SIZE), + this.#intToByteArray(offset), + ); + if (this.chip.SUPPORTS_ENCRYPTION && !this.IS_STUB) { + // set encryption flag to false, ROM bootloader only, and on specific chips + // XXX: Support encrypted + pkt = this.#appendArray(pkt, this.#intToByteArray(0)); // XXX: Support encrypted } - async memBlock(buffer: Uint8Array, seq: number) { - let pkt = this.#appendArray(this.#intToByteArray(buffer.length), this.#intToByteArray(seq)); - pkt = this.#appendArray(pkt, this.#intToByteArray(0)); + await this.checkCommand({ + opDescription: 'enter Flash download mode', + op: this.ESP_FLASH_BEGIN, + data: pkt, + timeout, + }); + + const t2 = Date.now(); + if (size !== 0 && this.IS_STUB === false) { + // ROM bootloader will also erase the flash region + this.log(`Took ${(t2 - t1)}ms to erase flash block`); + } + return numBlocks; + } + + // initialise a write of compressed data to the device's SPI Flash with config data + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async flashDeflBegin(size: number, compSize: number, offset: number) { + if (!this.chip) throw new Error('chip not initialized'); + 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 t1 = Date.now(); + + let writeSize = size; + let timeout = 3000; + if (!this.IS_STUB) { + // ROM bootloader will also erase the flash region, rounded up to erase block size + writeSize = eraseBlocks * this.FLASH_WRITE_SIZE; + timeout = this.#timeoutPerMb(this.ERASE_REGION_TIMEOUT_PER_MB, writeSize); + } + this.log(`Compressed ${size} bytes to ${compSize}...`); + + let pkt = this.#appendArray( + this.#intToByteArray(writeSize), + this.#intToByteArray(numBlocks), + this.#intToByteArray(this.FLASH_WRITE_SIZE), + this.#intToByteArray(offset), + ); + + if (this.chip.SUPPORTS_ENCRYPTION && !this.IS_STUB) { + // set encryption flag to false, ROM bootloader only, and on specific chips + // XXX: Support encrypted pkt = this.#appendArray(pkt, this.#intToByteArray(0)); - pkt = this.#appendArray(pkt, buffer); - const checksum = this.checksum(buffer); - await this.checkCommand({ - opDescription: 'write to target RAM', op: this.ESP_MEM_DATA, data: pkt, chk: checksum, - }); } - - async memFinish(entrypoint: number) { - const is_entry = (entrypoint === 0) ? 1 : 0; - const pkt = this.#appendArray(this.#intToByteArray(is_entry), this.#intToByteArray(entrypoint)); - return this.checkCommand({ - opDescription: 'leave RAM download mode', - op: this.ESP_MEM_END, - data: pkt, - timeout: 500, - // min_data: 12, - }); // XXX: handle non-stub with diff timeout + if (this.chip.CHIP_NAME === 'ESP8266') { + await this.#flushInput(); } - - async flashSpiAttach(hspiArg: number) { - const pkt = this.#intToByteArray(hspiArg); - await this.checkCommand({ opDescription: 'configure SPI flash pins', op: this.ESP_SPI_ATTACH, data: pkt }); + await this.checkCommand({ + opDescription: 'enter compressed flash mode', + op: this.ESP_FLASH_DEFL_BEGIN, + data: pkt, + timeout, + }); + const t2 = Date.now(); + if (size !== 0 && !this.IS_STUB) { + // ROM bootloader will also erase the flash region + this.log(`Took ${(t2 - t1)}ms to erase flash block`); } - - timeoutPerMb(secondsPerMb: number, sizeBytes: number) { - const result = secondsPerMb * (sizeBytes / 1000000); - if (result < 3000) { - return 3000; - } - return result; + return numBlocks; + } + + // write a raw block of data to the device's SPI Flash + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async flashBlock(data: Uint8Array, seq: number, timeout: number) { + let pkt = this.#appendArray( + this.#intToByteArray(data.length), + this.#intToByteArray(seq), + this.#intToByteArray(0), + this.#intToByteArray(0), + data, + ); + const checksum = this.#checksum(data); + + await this.checkCommand({ + opDescription: `write to target Flash after seq ${seq}`, + op: this.ESP_FLASH_DATA, + data: pkt, + chk: checksum, + timeout, + }); + } + + // write a compressed block of data to the device's SPI Flash + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async flashDeflBlock(data: Uint8Array, seq: number, timeout: number) { + let pkt = this.#appendArray( + this.#intToByteArray(data.length), + this.#intToByteArray(seq), + this.#intToByteArray(0), + this.#intToByteArray(0), + data, + ); + const checksum = this.#checksum(data); + + await this.checkCommand({ + opDescription: `write compressed data to flash after seq ${seq}`, + op: this.ESP_FLASH_DEFL_DATA, + data: pkt, + chk: checksum, + timeout, + }); + } + + // finish writing to the device's SPI Flash, and optionally reboot the device + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async flashFinish({ reboot = false } = {}) { + const val = reboot ? 0 : 1; + const pkt = this.#intToByteArray(val); + + await this.checkCommand({ + opDescription: 'leave Flash mode', + op: this.ESP_FLASH_END, + data: pkt, + }); + } + + // finish writing compressed data to the device's SPI Flash, and optionally reboot the device + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async flashDeflFinish({ reboot = false } = {}) { + const val = reboot ? 0 : 1; + const pkt = this.#intToByteArray(val); + + await this.checkCommand({ + opDescription: 'leave compressed flash mode', + op: this.ESP_FLASH_DEFL_END, + data: 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. + async runSpiFlashCommand(spiFlashCommand: number, data: Uint8Array, readBits: number) { + if (!this.chip?.SPI_REG_BASE) throw new Error('chip not initialized'); + // 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; + // following two registers are ESP32 and later chips only + if (this.chip.SPI_MOSI_DLEN_OFFS !== null) { + // ESP32 and later chips have a more sophisticated way + // to set up "user" commands + setDataLengths = async (mosiBits: number, misoBits: number) => { + const SPI_MOSI_DLEN_REG = base + (this.chip?.SPI_MOSI_DLEN_OFFS || 0); + const SPI_MISO_DLEN_REG = base + (this.chip?.SPI_MISO_DLEN_OFFS || 0); + 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: number, misoBits: number) => { + const SPI_DATA_LEN_REG = SPI_USR1_REG; + const SPI_MOSI_BIT_LEN_S = 17; + const SPI_MISO_BIT_LEN_S = 8; + const mosi_mask = (mosiBits === 0) ? 0 : (mosiBits - 1); + const miso_mask = (misoBits === 0) ? 0 : (misoBits - 1); + const val = (miso_mask << SPI_MISO_BIT_LEN_S) | (mosi_mask << SPI_MOSI_BIT_LEN_S); + await this.writeReg(SPI_DATA_LEN_REG, val); + }; } - async flashBegin(size: number, offset: number) { - if (!this.chip) throw new Error('chip not initialized'); - 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); - } - - // eslint-disable-next-line no-console - // console.log(`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({ - opDescription: 'enter Flash download mode', op: this.ESP_FLASH_BEGIN, data: pkt, timeout, - }); - - const t2 = d.getTime(); - if (size !== 0 && this.IS_STUB === false) { - this.log(`Took ${(t2 - t1) / 1000}.${(t2 - t1) % 1000}s to erase flash block`); - } - return numBlocks; + // SPI peripheral "command" bitmasks for SPI_CMD_REG + const SPI_CMD_USR = (1 << 18); + // shift values + const SPI_USR2_COMMAND_LEN_SHIFT = 28; + if (readBits > 32) { + throw new Error('Reading more than 32 bits back from a SPI flash operation is unsupported'); + } + if (data.length > 64) { + throw new Error('Writing more than 64 bytes of data with one SPI command is unsupported'); } - async flashDeflBegin(size: number, compSize: number, offset: number) { - if (!this.chip) throw new Error('chip not initialized'); - 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 t1 = Date.now(); - - let writeSize; - let timeout; - if (this.IS_STUB) { - writeSize = size; - timeout = 3000; - } else { - writeSize = eraseBlocks * this.FLASH_WRITE_SIZE; - timeout = this.timeoutPerMb(this.ERASE_REGION_TIMEOUT_PER_MB, writeSize); - } - this.log(`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.IS_STUB === false) - ) { - pkt = this.#appendArray(pkt, this.#intToByteArray(0)); + 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); // clear data register before we read it + } else { + if (data.length % 4 !== 0) { + // pad to 32-bit multiple + const padding = new Uint8Array(data.length % 4); + // eslint-disable-next-line no-param-reassign + data = this.#appendArray(data, padding); } - if (this.chip.CHIP_NAME === 'ESP8266') { - await this.#flushInput(); + 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.checkCommand({ - opDescription: 'enter compressed flash mode', op: this.ESP_FLASH_DEFL_BEGIN, data: pkt, timeout, - }); - const t2 = Date.now(); - if (size !== 0 && this.IS_STUB === false) { - this.log(`Took ${(t2 - t1) / 1000}.${(t2 - t1) % 1000}s to erase flash block`); + } + 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; } - return numBlocks; } - - async flashBlock(data: Uint8Array, seq: number, timeout: number) { - 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({ - opDescription: `write to target Flash after seq ${seq}`, op: this.ESP_FLASH_DATA, data: pkt, chk: checksum, timeout, - }); + if (i === 10) { + throw 'SPI command did not complete in time'; } - - async flashDeflBlock(data: Uint8Array, seq: number, timeout: number) { - 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); - // console.log(`flashDeflBlock ${data[0].toString(16)}`, +' ' + data[1].toString(16)); - - await this.checkCommand({ - opDescription: `write compressed data to flash after seq ${seq}`, - op: this.ESP_FLASH_DEFL_DATA, - data: pkt, - chk: checksum, - timeout, - }); + const stat = await this.readReg(SPI_W0_REG); + // restore some SPI controller registers + await this.writeReg(SPI_USR_REG, oldSpiUsr); + await this.writeReg(SPI_USR2_REG, oldSpiUsr2); + return stat; + } + + // Read SPI flash manufacturer and device id + async readFlashId() { + const SPI_FLASH_RDID = 0x9F; + const pkt = new Uint8Array(0); + return this.runSpiFlashCommand(SPI_FLASH_RDID, pkt, 24); + } + + // Erase entire flash chip (Stub only) + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#supported-by-stub-loader-only + async eraseFlash() { + if (!this.IS_STUB) throw new Error('Erase flash is a stub only command'); + this.log('Erasing flash (this may take a while)...'); + const t1 = Date.now(); + const ret = await this.checkCommand({ + opDescription: 'erase flash', + op: this.ESP_ERASE_FLASH, + timeout: this.CHIP_ERASE_TIMEOUT, + }); + const t2 = Date.now(); + this.log(`Chip erase completed successfully in ${(t2 - t1) / 1000}s`); + return ret; + } + + // Calculate MD5 of flash region + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#commands + async flashMd5sum(addr: number, size: number) { + const timeout = this.#timeoutPerMb(this.MD5_TIMEOUT_PER_MB, size); + let pkt = this.#appendArray( + this.#intToByteArray(addr), + this.#intToByteArray(size), + this.#intToByteArray(0), + this.#intToByteArray(0), + ); + + let res = await this.checkCommand({ + opDescription: 'calculate md5sum', + op: this.ESP_SPI_FLASH_MD5, + data: pkt, + timeout, + }); + if (typeof res === 'number') throw new Error('Invalid response to md5sum command'); + if (this.IS_STUB) { + return res.subarray(0, 16).toString('hex'); } - - async flashFinish({ reboot = false } = {}) { - const val = reboot ? 0 : 1; - const pkt = this.#intToByteArray(val); - - await this.checkCommand({ opDescription: 'leave Flash mode', op: this.ESP_FLASH_END, data: pkt }); + return res.subarray(0, 32).toString('ascii'); + } + + // install a temporary stub loader to the device's memory and run it + // https://docs.espressif.com/projects/esptool/en/latest/esp32/esptool/flasher-stub.html + async runStub() { + if (!this.chip) throw new Error('Chip not initialized'); + this.log(`Fetching ${this.chip.CHIP_NAME} stub...`); + + const stub = await this.stubLoader.loadStub(this.chip.CHIP_NAME); + const { + data, text, dataStart, textStart, entry, + } = stub; + + this.log('Uploading stub...'); + + let blocks = Math.floor((text.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); + + await this.memBegin(text.length, blocks, this.ESP_RAM_BLOCK, textStart); + for (let i = 0; i < blocks; i++) { + const fromOffs = i * this.ESP_RAM_BLOCK; + let toOffs = fromOffs + this.ESP_RAM_BLOCK; + if (toOffs > text.length) toOffs = text.length; + await this.memBlock(text.subarray(fromOffs, toOffs), i); } - async flashDeflFinish({ reboot = false } = {}) { - const val = reboot ? 0 : 1; - const pkt = this.#intToByteArray(val); + blocks = Math.floor((data.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); - await this.checkCommand({ opDescription: 'leave compressed flash mode', op: this.ESP_FLASH_DEFL_END, data: pkt }); + await this.memBegin(data.length, blocks, this.ESP_RAM_BLOCK, dataStart); + for (let i = 0; i < blocks; i++) { + const fromOffs = i * this.ESP_RAM_BLOCK; + let toOffs = fromOffs + this.ESP_RAM_BLOCK; + if (toOffs > data.length) toOffs = data.length; + await this.memBlock(data.subarray(fromOffs, toOffs), i); } - async runSpiFlashCommand(spiFlashCommand: number, data: Uint8Array, readBits: number) { - if (!this.chip?.SPI_REG_BASE) throw new Error('chip not initialized'); - // 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; + this.log('Running stub...'); + let valid = false; + const validCheck = (data: Buffer) => { + if (data.toString('ascii').includes('OHAI')) valid = true; + }; + this.serial.on('data', validCheck); - let setDataLengths; - if (this.chip.SPI_MOSI_DLEN_OFFS != null) { - setDataLengths = async (mosiBits: number, misoBits: number) => { - const SPI_MOSI_DLEN_REG = base + (this.chip?.SPI_MOSI_DLEN_OFFS || 0); - const SPI_MISO_DLEN_REG = base + (this.chip?.SPI_MISO_DLEN_OFFS || 0); - 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: number, misoBits: number) => { - const SPI_DATA_LEN_REG = SPI_USR1_REG; - const SPI_MOSI_BIT_LEN_S = 17; - const SPI_MISO_BIT_LEN_S = 8; - const mosi_mask = (mosiBits === 0) ? 0 : (mosiBits - 1); - const miso_mask = (misoBits === 0) ? 0 : (misoBits - 1); - const val = (miso_mask << SPI_MISO_BIT_LEN_S) | (mosi_mask << SPI_MOSI_BIT_LEN_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 Error('Reading more than 32 bits back from a SPI flash operation is unsupported'); - } - if (data.length > 64) { - throw new Error('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); - // eslint-disable-next-line no-param-reassign - 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 '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; + await this.memFinish(entry); + for (let i = 0; i < 10 && !valid; i++) { + await this.#sleep(20); } + + this.serial.removeListener('data', validCheck); - async readFlashId() { - const SPI_FLASH_RDID = 0x9F; - const pkt = new Uint8Array(0); - return this.runSpiFlashCommand(SPI_FLASH_RDID, pkt, 24); + if (valid) { + this.log('Stub running...'); + this.IS_STUB = true; + this.FLASH_WRITE_SIZE = 0x4000; + return this.chip; } - - async eraseFlash() { - this.log('Erasing flash (this may take a while)...'); - const t1 = Date.now(); - const ret = await this.checkCommand({ - opDescription: 'erase flash', - op: this.ESP_ERASE_FLASH, - timeout: this.CHIP_ERASE_TIMEOUT, - }); - const t2 = Date.now(); - this.log(`Chip erase completed successfully in ${(t2 - t1) / 1000}s`); - return ret; + this.log('Failed to start stub. Unexpected response'); + return null; + } + + // initialise the device with the stub loader + async mainFn() { + await this.detectChip(); + if (this.chip == null) { + this.log('Error in connecting to board'); + return; } - async flashMd5sum(addr: number, size: number) { - 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({ - opDescription: 'calculate md5sum', op: this.ESP_SPI_FLASH_MD5, data: pkt, timeout, // min_data: 26, - }); - if (typeof res === 'number') throw new Error('Invalid response to md5sum command'); - if (res.length > 16) { - res = res.subarray(0, 16); - } - const strmd5 = res.toString('hex'); - return strmd5; + const chip = await this.chip.getChipDescription(this); + this.log(`Chip is ${chip}`); + this.log(`Features: ${await this.chip.getChipFeatures(this)}`); + this.log(`Crystal is ${await this.chip.getCrystalFreq(this)}MHz`); + this.log(`MAC: ${await this.chip.readMac(this)}`); + await this.chip.readMac(this); + + if (this.chip.IS_STUB) await this.runStub(); + else this.FLASH_WRITE_SIZE = this.chip.FLASH_WRITE_SIZE || 0x4000; + } + + // finish writing to the device's memory, and run the code at the specified address + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html#writing-data + async changeBaudrate(newBaud: number) { + this.log(`Changing baud rate to ${newBaud}...`); + const oldBaud = this.IS_STUB ? this.serial.baudRate : 0; + const pkt = this.#appendArray(this.#intToByteArray(newBaud), this.#intToByteArray(oldBaud)); + const res = await this.checkCommand({ + opDescription: 'change baudrate', + op: this.ESP_CHANGE_BAUDRATE, + data: pkt, + timeout: 500, + }); + await this.serial.update({ baudRate: newBaud }); + this.log('Changed.'); + return res; + } + + // read a byte size from text + #flashSizeBytes(flashSizeStr: string | number) { + if (typeof flashSizeStr === 'number') return flashSizeStr; + let flashSize = parseInt(flashSizeStr.replace(/\D/g, '') || '-1', 10); + if (flashSizeStr.toLowerCase().includes('kb')) { + return flashSize * 1024; } - - async runStub() { - if (!this.chip) throw new Error('Chip not initialized'); - this.log('Fetching stub...'); - - const stub = await this.stubLoader.loadStub(this.chip.CHIP_NAME); - // console.log(stub); - const { - data, text, dataStart, textStart, entry, - } = stub; - - this.log('Uploading stub...'); - - let blocks = Math.floor((text.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); - let i; - - await this.memBegin(text.length, blocks, this.ESP_RAM_BLOCK, textStart); - for (i = 0; i < blocks; i++) { - const fromOffs = i * this.ESP_RAM_BLOCK; - let toOffs = fromOffs + this.ESP_RAM_BLOCK; - if (toOffs > text.length) toOffs = text.length; - await this.memBlock(text.subarray(fromOffs, toOffs), i); - } - - blocks = Math.floor((data.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); - await this.memBegin(data.length, blocks, this.ESP_RAM_BLOCK, dataStart); - for (i = 0; i < blocks; i++) { - const fromOffs = i * this.ESP_RAM_BLOCK; - let toOffs = fromOffs + this.ESP_RAM_BLOCK; - if (toOffs > data.length) toOffs = data.length; - await this.memBlock(data.subarray(fromOffs, toOffs), i); - } - - this.log('Running stub...'); - let valid = false; - await this.memFinish(entry); - - if (this.chip.CHIP_NAME === 'ESP8266') { - const [reply] = await this.sync(); - if (reply === 0) valid = true; - } else { - const res = await this.serial.read(6); // { timeout: 1000, min_data: 6 }); - if (res[0] === 79 && res[1] === 72 && res[2] === 65 && res[3] === 73) { - valid = true; - } - } - - if (valid) { - this.log('Stub running...'); - this.IS_STUB = true; - this.FLASH_WRITE_SIZE = 0x4000; - return this.chip; - } - this.log('Failed to start stub. Unexpected response'); - return null; + if (flashSizeStr.toLowerCase().includes('mb')) { + return flashSize * 1024 * 1024; + } + if (flashSizeStr.toLowerCase().includes('gb')) { + return flashSize * 1024 * 1024 * 1024; + } + return flashSize; + } + + // resolve a byte size from text, using a list of possible sizes depending on the chip + #parseFlashSizeArg(flashSizeStr: string) { + if (!this.chip) throw new Error('Chip not initialized'); + const size = this.chip.FLASH_SIZES[flashSizeStr.toUpperCase()]; + if (typeof size !== 'number') { + this.log(`Flash size ${flashSizeStr} is not supported by this chip type.`); + this.log(`Supported sizes: ${Object.keys(this.chip.FLASH_SIZES).join(', ')}`); + throw new Error('Invalid flash size'); + } + return size; + } + + // set the flash modes for an image + // https://docs.espressif.com/projects/esptool/en/latest/esp32/esptool/flash-modes.html + // https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/spi-flash-modes.html + #updateImageFlashParams = (image: Buffer, address: number, flashSize: string, flashMode: string, flashFreq: string) => { + if (!this.chip) throw new Error('Chip not initialized'); + // console.log(`_update_image_flashParams ${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') { + // console.log('Not changing the image'); + return image; } - async mainFn() { - await this.detectChip(); - if (this.chip == null) { - this.log('Error in connecting to board'); - return; - } + const magic = image[0]; + let aFlashMode = image[2]; + const flashSizeFreq = image[3]; + if (magic !== this.ESP_IMAGE_MAGIC) { + this.log(`Warning: Image file at 0x${ + address.toString(16) + } doesn't look like an image file, so not changing any flash settings.`); + return image; + } - const chip = await this.chip.getChipDescription(this); - this.log(`Chip is ${chip}`); - this.log(`Features: ${await this.chip.getChipFeatures(this)}`); - this.log(`Crystal is ${await this.chip.getCrystalFreq(this)}MHz`); - this.log(`MAC: ${await this.chip.readMac(this)}`); - await this.chip.readMac(this); + /* XXX: Yet to implement actual image verification */ - if (this.chip.IS_STUB) await this.runStub(); - else this.FLASH_WRITE_SIZE = this.chip.FLASH_WRITE_SIZE || 0x4000; + if (flashMode !== 'keep') { + const flashModes = { + qio: 0, qout: 1, dio: 2, dout: 3, + } as { [key: string]: number }; + aFlashMode = flashModes[flashMode]; } - - flashSizeBytes(flashSize: string) { - let flashSizeB = -1; - if (flashSize.indexOf('KB') !== -1) { - flashSizeB = parseInt(flashSize.slice(0, flashSize.indexOf('KB')), 10) * 1024; - } else if (flashSize.indexOf('MB') !== -1) { - flashSizeB = parseInt(flashSize.slice(0, flashSize.indexOf('MB')), 10) * 1024 * 1024; - } - return flashSizeB; + + let aFlashFreq = flashSizeFreq & 0x0F; + if (flashFreq !== 'keep') { + const flashFreqs = { + '40m': 0, '26m': 1, '20m': 2, '80m': 0xf, + } as { [key: string]: number }; + aFlashFreq = flashFreqs[flashFreq]; } - padArray(arr: any[], len: number, fillValue: any) { - return Object.assign(new Array(len).fill(fillValue), arr); + let aFlashSize = flashSizeFreq & 0xF0; + if (flashSize !== 'keep') { + aFlashSize = this.#parseFlashSizeArg(flashSize); } - parseFlashSizeArg(flashSize: string) { - if (!this.chip) throw new Error('Chip not initialized'); - if (!this.chip.FLASH_SIZES[flashSize]) { - this.log(`Flash size ${flashSize} is not supported by this chip type. Supported sizes: ${this.chip.FLASH_SIZES}`); - throw new Error('Invalid flash size'); - } - return this.chip.FLASH_SIZES[flashSize]; + const flashParams = (aFlashMode << 8) | (aFlashFreq + aFlashSize); + if (aFlashMode !== image[2] || (aFlashFreq + aFlashSize) !== image[3]) { + this.log(`Flash params set to ${flashParams.toString(16).padStart(4, '0')}`); + image.set([(aFlashMode), (aFlashFreq + aFlashSize)], 2); + } + return image; + } + + // pad a buffer to a specific size + #padTo(data: Buffer, alignment: number, padCharacter = 0xFF) { + const padLength = alignment - (data.length % alignment); + if (padLength === alignment) return data; + const pad = Buffer.alloc(padLength, padCharacter); + return Buffer.concat([data, pad]); + } + + // write a list of image files to the device's flash + async writeFlash({ + fileArray = [] as UploadFileDef[], + flashSize = 'keep', + flashMode = 'keep', + flashFreq = 'keep', + eraseAll = false, + compress = true, + } = {}) { + if (!this.chip) throw new Error('Chip not initialized, make sure you call connect() first'); + + if (flashSize !== 'keep') { + const flashEnd = this.#flashSizeBytes(flashSize); + fileArray.forEach((file) => { + if ((file.data.length + file.address) > flashEnd) { + throw new Error('Specified file doesn\'t fit in the available flash'); + } + }); } - #updateImageFlashParams = (image: Buffer, address: number, flashSize: string, flashMode: string, flashFreq: string) => { + if (this.IS_STUB && eraseAll) { + await this.eraseFlash(); + } + for (let i = 0; i < fileArray.length; i += 1) { + const file = fileArray[i]; if (!this.chip) throw new Error('Chip not initialized'); - // console.log(`_update_image_flashParams ${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') { - // console.log('Not changing the image'); - return image; - } - - const magic = image[0]; - let aFlashMode = image[2]; - const flashSizeFreq = image[3]; - if (magic !== this.ESP_IMAGE_MAGIC) { - this.log(`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, - } as { [key: string]: number }; - aFlashMode = flashModes[flashMode]; - } - let aFlashFreq = flashSizeFreq & 0x0F; - if (flashFreq !== 'keep') { - const flashFreqs = { - '40m': 0, '26m': 1, '20m': 2, '80m': 0xf, - } as { [key: string]: number }; - aFlashFreq = flashFreqs[flashFreq]; - } - let aFlashSize = flashSizeFreq & 0xF0; - if (flashSize !== 'keep') { - aFlashSize = this.parseFlashSizeArg(flashSize); - } - - const flashParams = (aFlashMode << 8) | (aFlashFreq + aFlashSize); - this.log(`Flash params set to ${flashParams.toString(16)}`); - if (image[2] !== (aFlashMode << 8)) { - // eslint-disable-next-line no-param-reassign - image[2] = (aFlashMode << 8); + const { address } = file; + // console.log(`Data Length ${fileArray[i].data.length}`); + // image = this.pad_array(fileArray[i].data, Math.floor((fileArray[i].data.length + 3)/4) * 4, 0xff); + // XXX : handle padding + // console.log(`Image Length ${image.length}`); + if (file.data.length === 0) { + this.log('Warning: File is empty'); + break; } - if (image[3] !== (aFlashFreq + aFlashSize)) { - // eslint-disable-next-line no-param-reassign - image[3] = (aFlashFreq + aFlashSize); + let image = this.#padTo(file.data, 4); + image = this.#updateImageFlashParams(image, address, flashSize, flashMode, flashFreq); + const calcMd5 = CryptoJS.MD5(CryptoJS.enc.Base64.parse(image.toString('base64'))); + // console.log(`Image MD5 ${calcMd5}`); + const rawSize = image.length; + let blocks; + if (compress) { + image = Buffer.from(pako.deflate(image, { level: 9 })); + blocks = await this.flashDeflBegin(rawSize, image.length, address); + } else { + blocks = await this.flashBegin(rawSize, address); } - return image; - } + let seq = 0; + let bytesSent = 0; + // const bytes_written = 0; - async writeFlash({ - fileArray = [] as UploadFileDef[], - flashSize = 'keep', - flashMode = 'keep', - flashFreq = 'keep', - eraseAll = false, - compress = true, - } = {}) { - if (!this.chip) throw new Error('Chip not initialized, make sure you call connect() first'); - // console.log('EspLoader program'); - if (flashSize !== 'keep') { - const flashEnd = this.flashSizeBytes(flashSize); - fileArray.forEach((file) => { - if ((file.data.length + file.address) > flashEnd) { - throw new Error('Specified file doesn\'t fit in the available flash'); - } - }); - } + const t1 = Date.now(); - if (this.IS_STUB === true && eraseAll === true) { - await this.eraseFlash(); - } - await fileArray.reduce(async (prev, file) => { - await prev; - if (!this.chip) throw new Error('Chip not initialized'); - const { address } = file; - // console.log(`Data Length ${fileArray[i].data.length}`); - // image = this.pad_array(fileArray[i].data, Math.floor((fileArray[i].data.length + 3)/4) * 4, 0xff); - // XXX : handle padding - // console.log(`Image Length ${image.length}`); - if (file.data.length === 0) { - this.log('Warning: File is empty'); - return; - } - let image = this.#updateImageFlashParams(file.data, address, flashSize, flashMode, flashFreq); - const calcmd5 = CryptoJS.MD5(CryptoJS.enc.Base64.parse(image.toString('base64'))); - // console.log(`Image MD5 ${calcmd5}`); - const uncsize = image.length; - let blocks; - // console.log(image); + let timeout = 5000; + while (image.length > 0) { + // console.log(`Write loop ${address} ${seq} ${blocks}`); + this.logChar(`\rWriting at 0x${ + (address + (seq * this.FLASH_WRITE_SIZE)).toString(16) + }... (${ + Math.floor(100 * ((seq + 1) / blocks)) + }%)`); + let block = image.subarray(0, this.FLASH_WRITE_SIZE); if (compress) { - // const uncimage = this.bstrToUi8(image); - image = Buffer.from(pako.deflate(image, { level: 9 })); - // console.log('Compressed image '); - // console.log(image); - blocks = await this.flashDeflBegin(uncsize, image.length, address); - } else { - blocks = await this.flashBegin(uncsize, address); - } - let seq = 0; - let bytesSent = 0; - // const bytes_written = 0; - - const t1 = Date.now(); - - let timeout = 5000; - while (image.length > 0) { - // console.log(`Write loop ${address} ${seq} ${blocks}`); - this.logChar(`\rWriting at 0x${ - (address + (seq * this.FLASH_WRITE_SIZE)).toString(16) - }... (${ - Math.floor(100 * ((seq + 1) / blocks)) - }%)`); - let block = image.subarray(0, this.FLASH_WRITE_SIZE); - if (compress) { - /* - let block_uncompressed = pako.inflate(block).length; - //let len_uncompressed = block_uncompressed.length; - bytes_written += block_uncompressed; - if (this.timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed) > 3000) { - block_timeout = this.timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed); - } else { - block_timeout = 3000; - } */ // XXX: Partial block inflate seems to be unsupported in Pako. Hardcoding timeout - const blockTimeout = 5000; - if (this.IS_STUB === false) { - timeout = blockTimeout; - } - await this.flashDeflBlock(block, seq, timeout); - if (this.IS_STUB) { - timeout = blockTimeout; - } - } else { - // this.log('Yet to handle Non Compressed writes'); - // block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) - if (block.length < this.FLASH_WRITE_SIZE) { - const existingBlock = block.toString('base64'); - block = Buffer.alloc(this.FLASH_WRITE_SIZE, 0xff); - block.write(existingBlock, 'base64'); - } - // if encrypted: - // esp.flash_encrypt_block(block, seq) - // else: - // esp.flashBlock(block, seq) - // bytes_written += len(block) - await this.flashBlock(block, seq, timeout); + /* + let block_uncompressed = pako.inflate(block).length; + //let len_uncompressed = block_uncompressed.length; + bytes_written += block_uncompressed; + if (this.#timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed) > 3000) { + block_timeout = this.#timeoutPerMb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed); + } else { + block_timeout = 3000; + } */ // XXX: Partial block inflate seems to be unsupported in Pako. Hardcoding timeout + const blockTimeout = 5000; + if (!this.IS_STUB) { + timeout = blockTimeout; } - bytesSent += block.length; - image = image.subarray(this.FLASH_WRITE_SIZE, image.length); - seq++; - } - if (this.IS_STUB) { - await this.readReg(this.CHIP_DETECT_MAGIC_REG_ADDR, timeout); - } - const t = Date.now() - t1; - this.log(''); - this.log(`Wrote ${uncsize} bytes${ - compress ? ` (${bytesSent} compressed)` : '' - } at 0x${address.toString(16)} in ${t / 1000} seconds.`); - await this.#sleep(100); - if (this.IS_STUB || this.chip.CHIP_NAME !== 'ESP8266') { - const res = await this.flashMd5sum(address, uncsize); - if (`${res}` !== `${calcmd5}`) { - this.log(`File md5: ${calcmd5}`); - this.log(`Flash md5: ${res}`); - } else { - this.log('Hash of data verified.'); + await this.flashDeflBlock(block, seq, timeout); + if (this.IS_STUB) { + timeout = blockTimeout; + } + } else { + // this.log('Yet to handle Non Compressed writes'); + // block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) + if (block.length < this.FLASH_WRITE_SIZE) { + const existingBlock = block.toString('base64'); + block = Buffer.alloc(this.FLASH_WRITE_SIZE, 0xff); + block.write(existingBlock, 'base64'); } + // if encrypted: + // esp.flash_encrypt_block(block, seq) + // else: + // esp.flashBlock(block, seq) + // bytes_written += len(block) + await this.flashBlock(block, seq, timeout); } - }, Promise.resolve()); - this.log('Leaving...'); - + bytesSent += block.length; + image = image.subarray(this.FLASH_WRITE_SIZE, image.length); + seq++; + } if (this.IS_STUB) { - await this.flashBegin(0, 0); - if (compress) { - await this.flashDeflFinish(); + await this.readReg(this.CHIP_DETECT_MAGIC_REG_ADDR, timeout); + } + const t = Date.now() - t1; + this.log(''); + this.log(`Wrote ${rawSize} bytes${ + compress ? ` (${bytesSent} compressed)` : '' + } at 0x${address.toString(16)} in ${t / 1000} seconds.`); + + await this.#sleep(100); + if (this.IS_STUB || this.chip.CHIP_NAME !== 'ESP8266') { + const res = await this.flashMd5sum(address, rawSize); + if (`${res}` !== `${calcMd5}`) { + this.log(`File md5: ${calcMd5}`); + this.log(`Flash md5: ${res}`); } else { - await this.flashFinish(); + this.log('Hash of data verified.'); } } } + this.log('Leaving...'); - async flashId() { - // console.log('flash_id'); - const flashId = await this.readFlashId(); - this.log(`Manufacturer: ${(flashId & 0xff).toString(16)}`); - const idLowByte = (flashId >> 16) & 0xff; - this.log(`Device: ${((flashId >> 8) & 0xff).toString(16)}${idLowByte.toString(16)}`); - this.log(`Detected flash size: ${this.DETECTED_FLASH_SIZES[idLowByte] || 'Unknown'}`); + if (this.IS_STUB) { + await this.flashBegin(0, 0); + if (compress) { + await this.flashDeflFinish(); + } else { + await this.flashFinish(); + } } + } + + // read the device's manufacturer and device ID and log them + async flashId() { + // console.log('flash_id'); + const flashId = await this.readFlashId(); + this.log(`Manufacturer: ${(flashId & 0xff).toString(16)}`); + const idLowByte = (flashId >> 16) & 0xff; + this.log(`Device: ${((flashId >> 8) & 0xff).toString(16)}${idLowByte.toString(16)}`); + this.log(`Detected flash size: ${this.DETECTED_FLASH_SIZES[idLowByte] || 'Unknown'}`); + } + + // reboot the device + async reboot() { + await this.serial.set({ dtr: false, rts: true }); + await this.#sleep(100); + await this.serial.set({ dtr: false, rts: false }); + await this.#sleep(100); + } } diff --git a/src/esp/StubLoader.ts b/src/esp/StubLoader.ts index 7c40ed5..369e4c4 100644 --- a/src/esp/StubLoader.ts +++ b/src/esp/StubLoader.ts @@ -1,5 +1,17 @@ import axios from 'axios'; +/* + Stub loaders are uploaded and run in-memory on the target device. + They are usually more efficient than the default ROM loader and + more up-to-date with the latest features and bug fixes. + + https://docs.espressif.com/projects/esptool/en/latest/esp32/esptool/flasher-stub.html + + The data for stub loaders can be quite large, and there are different ones + for different esp chips, so we download it from the internet during run-time + rather than including it in the package bundle. +*/ + interface StubDef { data: Buffer; text: Buffer; @@ -18,24 +30,23 @@ export default class StubLoader { stubsUrl: string constructor(stubsUrl?: string) { - // TODO; Change branch from esp-support to main - this.stubsUrl = stubsUrl || 'https://raw.githubusercontent.com/duinoapp/upload-multitool/esp-support/src/esp/stubs/'; + this.stubsUrl = stubsUrl || 'https://raw.githubusercontent.com/espressif/esptool/master/esptool/targets/stub_flasher/'; this.stubsUrl = this.stubsUrl.replace(/\/$/, ''); } async loadStub(chipName: string) { - const stubName = chipName.replace(/-/g, '').toLowerCase(); + const stubName = chipName.replace(/-/g, '').toLowerCase().replace('esp', ''); if (cache[stubName]) { return cache[stubName]; } - const { data: res } = await axios.get(`${this.stubsUrl}/${stubName}.json`); + const { data: res } = await axios.get(`${this.stubsUrl}/stub_flasher_${stubName}.json`); const stub = { data: Buffer.from(res.data, 'base64'), text: Buffer.from(res.text, 'base64'), entry: res.entry, - textStart: res.textStart, - dataStart: res.dataStart, + textStart: res.text_start, + dataStart: res.data_start, } as StubDef; cache[stubName] = stub; diff --git a/src/esp/index.ts b/src/esp/index.ts index 806fbf4..125b685 100644 --- a/src/esp/index.ts +++ b/src/esp/index.ts @@ -11,22 +11,26 @@ export const upload = async (serial: SerialPort, config: ProgramConfig) => { const log = (...args: any[]) => console.log(...args); // const term = { log, debug: log, write: config.debug }; - // const { port } = serial; - // const transport = new Transport(port, term); - let espLoader; + // serial.on('data', (data: Buffer) => { + // console.log('read (utf8)', data.toString('utf-8')); + // console.log('read (hex)', data.toString('hex')); + // }); + let espLoader; try { - log('> Connecting...'); espLoader = new ESPLoader(serial, { quiet: !config.verbose, } as ESPOptions); await espLoader.mainFn(); // await espLoader.flash_id(); log('> Connected'); + + if (config.uploadSpeed) { + await espLoader.changeBaudrate(config.uploadSpeed); + } } catch (err) { // eslint-disable-next-line no-console console.error(err); - // log('Failed to connect:', typeof err === 'string' ? err : err.message); try { await serial.close(); } catch (err2) { @@ -37,21 +41,19 @@ export const upload = async (serial: SerialPort, config: ProgramConfig) => { } try { - // if (board.config?.wipe && board.config.wipe !== 'none') { - // log('> Erasing device flash...'); - // await espLoader.erase_flash(); - // log('> Successfully erased device flash'); - // } log('> Writing main data partition, this may take a while...'); await espLoader.writeFlash({ fileArray: config.files.map((file) => ({ ...file, data: Buffer.from(file.data, 'base64') })), - flashSize: 'keep', - // flash_freq, - // flash_mode, + flashSize: '4MB', + flashFreq: config.flashFreq || 'keep', + flashMode: config.flashMode || 'keep', // compress: board.props?.build?.mcu !== 'esp8266', }); - await espLoader.flashDeflFinish({ reboot: true }); + await espLoader.reboot(); await asyncTimeout(100); + if (config.uploadSpeed) { + await serial.update({ baudRate: config.speed || 115200 }); + } log('> Successfully written data partition'); log('> Flashing succeeded! Have a nice day! :)'); } catch (err) { @@ -60,12 +62,6 @@ export const upload = async (serial: SerialPort, config: ProgramConfig) => { log('Failed to upload:', err instanceof Error ? err.message : err); } - // try { - // await serial.close(); - // } catch (err) { - // // eslint-disable-next-line no-console - // console.error(err); - // } }; export default { upload, isSupported }; \ No newline at end of file diff --git a/src/esp/roms/esp32c3.ts b/src/esp/roms/esp32c3.ts index c89a3b6..13f99b5 100644 --- a/src/esp/roms/esp32c3.ts +++ b/src/esp/roms/esp32c3.ts @@ -7,6 +7,7 @@ const EFUSE_BASE = 0x60008800; export default { CHIP_NAME: 'ESP32-C3', IS_STUB: true, + SUPPORTS_ENCRYPTION: true, IMAGE_CHIP_ID: 5, CHIP_DETECT_MAGIC_VALUE: 0x6921506f, EFUSE_BASE, diff --git a/src/esp/roms/esp32s2.ts b/src/esp/roms/esp32s2.ts index b3e6324..698e39d 100644 --- a/src/esp/roms/esp32s2.ts +++ b/src/esp/roms/esp32s2.ts @@ -5,6 +5,7 @@ import { toMac } from './util'; export default { CHIP_NAME: 'ESP32-S2', IS_STUB: true, + SUPPORTS_ENCRYPTION: true, IMAGE_CHIP_ID: 2, CHIP_DETECT_MAGIC_VALUE: 0x000007c6, MAC_EFUSE_REG: 0x3f41A044, diff --git a/src/esp/roms/rom.d.ts b/src/esp/roms/rom.d.ts index 2c5506c..999d0ab 100644 --- a/src/esp/roms/rom.d.ts +++ b/src/esp/roms/rom.d.ts @@ -7,6 +7,7 @@ interface flashSizes { export default interface ROM { CHIP_NAME: string; IS_STUB: boolean; + SUPPORTS_ENCRYPTION?: boolean; FLASH_SIZES: flashSizes; IMAGE_CHIP_ID?: number; CHIP_DETECT_MAGIC_VALUE: number; diff --git a/src/global.d.ts b/src/global.d.ts index ad7280e..5e458ba 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1 +1,3 @@ declare module 'intel-hex'; +declare module 'pako'; +declare module 'crypto-js'; diff --git a/src/index.d.ts b/src/index.d.ts index bb4b74b..13d81ca 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -7,7 +7,10 @@ export interface ProgramConfig { hex?: Buffer; files?: ProgramFile[]; speed?: number; + uploadSpeed?: number; tool?: string; cpu?: string; verbose?: boolean; + flashMode?: string; + flashFreq?: string; } \ No newline at end of file diff --git a/src/util/serial-helpers.ts b/src/util/serial-helpers.ts index c38f2e7..11cfe42 100644 --- a/src/util/serial-helpers.ts +++ b/src/util/serial-helpers.ts @@ -8,7 +8,7 @@ export const waitForOpen = (serial: SerialPort, timeout: number = 1000): Promise let cleanup = () => {}; const timer = setTimeout(() => { cleanup(); - reject(new Error('Timeout')); + reject(new Error('Timeout opening port')); }, timeout); const handleOpen = () => { cleanup(); diff --git a/test/boards.ts b/test/boards.ts index 9d991a0..ca21359 100644 --- a/test/boards.ts +++ b/test/boards.ts @@ -3,40 +3,59 @@ import { expect } from 'chai'; import 'mocha'; import { SerialPort } from 'serialport'; import { upload } from '../src/index'; -import { waitForData, config, getHex } from './util'; +import { waitForData, config, getHex, espIdentify, ESPIdentifyResult } from './util'; import { waitForOpen } from '../src/util/serial-helpers'; import { ProgramFile } from '../src/index.d'; +const numEsps = Object.values(config.devices).filter((d) => d.espChip).length; +const listPromise = espIdentify(numEsps); + Object.keys(config.devices).forEach((deviceRef) => { const device = config.devices[deviceRef]; let key = ''; let hex: Buffer | undefined; let files: ProgramFile[] | undefined; let serial: SerialPort; + let flashMode: string | undefined; + let flashFreq: string | undefined; + let portList: ESPIdentifyResult[] = []; describe(`upload to ${device.name}`, function () { this.timeout(120 * 1000); before(async () => { - const res = await getHex(device.code, device.fqbn); + const res = await getHex(device.code, device.fqbn.trim()); key = res.key; hex = res.hex; files = res.files; + flashMode = res.flashMode; + flashFreq = res.flashFreq; console.log('compiled hex'); }); beforeEach(async () => { if (serial?.isOpen) await (new Promise(resolve => serial.close(resolve))); - // dynamically find the device path by using VID & PID - const list = await SerialPort.list(); - const port = list.find(p => device.vendorIds.includes(p.vendorId) && device.productIds.includes(p.productId)); + // dynamically find the device path by using VID & PID or esp props + portList = await listPromise; + const port = portList.find(p => { + if (device.vendorIds && device.productIds) { + return device.vendorIds.includes(p.vendorId || '') && device.productIds.includes(p.productId || ''); + } + if (device.espChip) { + return p.esp?.chip === device.espChip; + } + if (device.mac) { + return p.esp?.mac === device.mac; + } + return false; + }); if (!port) throw new Error(`could not locate ${device.name}`); // connect to the device serial = new SerialPort({ path: port.path, baudRate: device.speed }); await waitForOpen(serial); - console.log(`connected to ${device.name}`); + console.log(`connected to ${device.name} on ${port.path}`); }); this.afterEach(async () => { @@ -44,17 +63,25 @@ Object.keys(config.devices).forEach((deviceRef) => { if (serial?.isOpen) await (new Promise(resolve => serial.close(resolve))); }); - it(`should upload to ${device.name}`, async () => { + it(`should upload to ${device.name}`, async function() { + this.retries(config.retries || 1); await upload(serial, { hex, files, + flashMode, + flashFreq, speed: device.speed, + uploadSpeed: device.uploadSpeed, tool: device.tool, cpu: device.cpu, verbose: config.verbose, }); + console.log(`uploaded to ${device.name}, validating...`); - expect(await waitForData(serial, key, 3000)).to.be.true; + if (device.code === 'ping') { + await serial.write('ping\n'); + } + expect(await waitForData(serial, key, 5000)).to.be.true; }); }); }); diff --git a/test/code/ping.ino b/test/code/ping.ino new file mode 100644 index 0000000..456450a --- /dev/null +++ b/test/code/ping.ino @@ -0,0 +1,42 @@ +/* + Ping + + Turns an LED on for one second, then off for one second, repeatedly. + + Also prints pong to the serial monitor when ping is received. + + Some boards may interfere with the serial detectability when printing + so this sketch only prints when ping is received. + +*/ + +// the setup function runs once when you press reset or power the board +void setup() { + // initialize digital pin LED_BUILTIN as an output. + pinMode(LED_BUILTIN, OUTPUT); + Serial.begin(115200); + Serial.setTimeout(500); +} + +// read the serial port for ping, and respond with pong +void pingCheck() { + if (!Serial.available()) { + return; + } + String input = Serial.readStringUntil('\n'); + input.trim(); + if (input == "ping") { + delay(100); + Serial.println("pong {{key}}"); + } +} + +// the loop function runs over and over again forever +void loop() { + digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) + pingCheck(); + delay(500); // wait for half a second + digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW + pingCheck(); + delay(500); // wait for half a second +} \ No newline at end of file diff --git a/test/index.ts b/test/index.ts index aa068ff..fc78f27 100644 --- a/test/index.ts +++ b/test/index.ts @@ -9,10 +9,20 @@ describe('isSupported', () => { expect(result).to.be.true; }); - it('should be false for non-existant cpu', () => { + it('should be true for esp8266', () => { + const result = isSupported('esptool', 'esp8266'); + expect(result).to.be.true; + }); + + it('should be false for non-existant cpu (atmega420)', () => { const result = isSupported('avrdude', 'atmega420'); expect(result).to.be.false; }); + + it('should be false for non-existant cpu (esp69)', () => { + const result = isSupported('esptool', 'esp69'); + expect(result).to.be.false; + }); it('should be false for non-existant tool', () => { const result = isSupported('bob', 'atmega328p'); diff --git a/test/test-config.yml b/test/test-config.yml index 66823b8..f118cf9 100644 --- a/test/test-config.yml +++ b/test/test-config.yml @@ -1,38 +1,60 @@ +verbose: true +# compileServer: https://compile.duino.app +compileServer: http://localhost:3030 +retries: 2 devices: - # uno: - # name: Arduino Uno - # # Find VID and PID in the Arduino IDE > Tools > Get Board Info - # vendorIds: - # - '2341' - # productIds: - # - '0001' - # code: blink - # fqbn: arduino:avr:uno - # cpu: atmega328p - # tool: avrdude - # speed: 115200 - # mega: - # name: Arduino Mega 2560 - # vendorIds: - # - '2341' - # productIds: - # - '0042' - # code: blink - # fqbn: arduino:avr:mega - # cpu: atmega2560 - # tool: avrdude - # speed: 115200 - nodemcuv2: - name: ESP 8266 - Node MCU V2 + uno: + name: Arduino Uno + # Find VID and PID in the Arduino IDE > Tools > Get Board Info vendorIds: - - '1a86' + - '2341' productIds: - - '7523' + - '0001' code: blink - fqbn: esp8266:esp8266:nodemcuv2 + fqbn: arduino:avr:uno + cpu: atmega328p + tool: avrdude + speed: 115200 + + mega: + name: Arduino Mega 2560 + vendorIds: + - '2341' + productIds: + - '0042' + code: blink + fqbn: arduino:avr:mega + cpu: atmega2560 + tool: avrdude + speed: 115200 + + esp32: + # DOIT ESP32 DEVKIT V1 is the closest match + name: DOIT ESP32 DEVKIT V1 + espChip: ESP32-D0WD-V3 (revision 3) + code: ping + fqbn: esp32:esp32:esp32doit-devkit-v1 + cpu: esp32 + tool: esptool + speed: 115200 + uploadSpeed: 921600 + + nodemcu: + name: ESP 8266 - Node MCU 0.9 + espChip: ESP8266EX + code: blink + fqbn: esp8266:esp8266:nodemcu cpu: esp8266 tool: esptool speed: 115200 + uploadSpeed: 921600 -verbose: true -compileServer: https://compile.duino.app \ No newline at end of file + wemos: + name: ESP 8266 - LOLIN(WEMOS) D1 mini + espChip: ESP8285 + code: blink + fqbn: esp8266:esp8266:d1_mini + cpu: esp8266 + tool: esptool + speed: 115200 + uploadSpeed: 921600 diff --git a/test/util.ts b/test/util.ts index c687ed9..0617ac6 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,9 +1,13 @@ -import { SerialPort } from 'serialport/dist/index.d'; +import { PortInfo } from '@serialport/bindings-interface'; import YAML from 'yaml'; import fs from 'fs'; import axios from 'axios'; import path from 'path'; +import { SerialPort } from 'serialport'; import { ProgramFile } from '../src/index.d'; +import ESPLoader from '../src/esp/ESPLoader'; +import { waitForOpen } from '../src/util/serial-helpers'; +import asyncTimeout from '../src/util/asyncTimeout'; export const waitForData = ( serial: SerialPort, @@ -21,7 +25,7 @@ export const waitForData = ( }; const timer = setTimeout(() => { cleanup(); - reject(new Error('Timeout')); + reject(new Error('Timeout waiting for data')); }, timeout || 1000); serial.on('data', handleData); cleanup = () => { @@ -31,13 +35,38 @@ export const waitForData = ( }); }; -export const config = YAML.parse(fs.readFileSync(path.join(__dirname, 'test-config.yml'), 'utf8')); +interface Device { + name: string; + vendorIds?: string[]; + productIds?: string[]; + espChip?: string; + mac?: string; + code: string; + fqbn: string; + cpu: string; + tool: string; + speed: number; + uploadSpeed?: number; +} + +interface TestConfig { + devices: { + [key: string]: Device; + }; + compileServer: string; + verbose?: boolean; + retries?: number; +} + +export const config = YAML.parse(fs.readFileSync(path.join(__dirname, 'test-config.yml'), 'utf8')) as TestConfig; interface HexResult { hex?: Buffer; files?: ProgramFile[]; key: string; code: string; + flashMode?: string; + flashFreq?: string; } export const getHex = async (file: string, fqbn: string): Promise => { @@ -52,10 +81,71 @@ export const getHex = async (file: string, fqbn: string): Promise => name: `${file}/${file}.ino`, }], }); + // console.log({ ...res.data, files: null }); + // fs.writeFileSync(path.join(__dirname, `compiled-data.json`), JSON.stringify(res.data, null, 2)); return { hex: res.data.hex ? Buffer.from(res.data.hex, 'base64') : undefined, files: res.data.files as ProgramFile[], key, code, + flashMode: res.data.flash_mode, + flashFreq: res.data.flash_freq, } as HexResult; }; + +export interface ESPIdentifyResult extends PortInfo { + esp?: { + chip: string; + type: string; + mac: string; + } +} + +const pollDevices = async ( + espCount: number, + existingList = [] as PortInfo[], + count = 0 +): Promise => { + const list = await SerialPort.list(); + const newList = list.reduce((acc, p) => { + if (!acc.find(a => a.path === p.path)) acc.push(p); + return acc; + }, existingList); + const numEsps = newList.filter(p => p.vendorId === '1a86' && p.productId === '7523').length; + if (numEsps >= espCount) return newList; + if (count > 20) throw new Error('Could not detect enough ESPs'); + await asyncTimeout(250 + (Math.random() * 500)); + return pollDevices(espCount, newList, count + 1); +}; + +const espIdentifyDevice = async (port: PortInfo): Promise => { + const serial = new SerialPort({ path: port.path, baudRate: 115200 }); + await waitForOpen(serial); + const loader = new ESPLoader(serial, { quiet: true }); + await loader.detectChip(); + if (!loader.chip) throw new Error('Could not detect chip'); + const type = loader.chip.CHIP_NAME; + const chip = await loader.chip.getChipDescription(loader); + const mac = await loader.chip.readMac(loader); + await loader.reboot(); + await serial.close(); + console.log('Identified', port.path, type, '-', chip, '-', mac); + return { + ...port, + esp: { chip, type, mac }, + }; +} + +export const espIdentify = async (espCount: number): Promise => { + const list = await pollDevices(espCount); + const results = [] as ESPIdentifyResult[]; + await list.reduce(async (promise, port) => { + await promise; + if (port.vendorId === '1a86' && port.productId === '7523') { + results.push(await espIdentifyDevice(port)); + } else { + results.push({ ...port }); + } + }, Promise.resolve()); + return results; +} From 5def8a4e5e2b73977a06bc8990a4bbafb3924142 Mon Sep 17 00:00:00 2001 From: mrfrase3 Date: Tue, 25 Oct 2022 19:46:57 +0800 Subject: [PATCH 6/6] fix file names --- src/avr/stk500-v1/stk500-v1.ts | 2 +- src/avr/stk500-v2/stk500-v2.ts | 2 +- src/esp/index.ts | 4 ++-- src/esp/{ESPLoader.ts => loader.ts} | 2 +- src/esp/roms/esp32.ts | 2 +- src/esp/roms/esp32c3.ts | 2 +- src/esp/roms/esp32s2.ts | 2 +- src/esp/roms/esp8266.ts | 2 +- src/esp/roms/rom.d.ts | 2 +- src/esp/{StubLoader.ts => stub-loader.ts} | 0 src/esp/stubs/esp32.json | 1 - src/esp/stubs/esp32c3.json | 1 - src/esp/stubs/esp32h2.json | 1 - src/esp/stubs/esp32s2.json | 1 - src/esp/stubs/esp32s3.json | 1 - src/esp/stubs/esp8266.json | 1 - src/util/{asyncTimeout.ts => async-timeout.ts} | 0 test/util.ts | 4 ++-- 18 files changed, 12 insertions(+), 18 deletions(-) rename src/esp/{ESPLoader.ts => loader.ts} (99%) rename src/esp/{StubLoader.ts => stub-loader.ts} (100%) delete mode 100644 src/esp/stubs/esp32.json delete mode 100644 src/esp/stubs/esp32c3.json delete mode 100644 src/esp/stubs/esp32h2.json delete mode 100644 src/esp/stubs/esp32s2.json delete mode 100644 src/esp/stubs/esp32s3.json delete mode 100644 src/esp/stubs/esp8266.json rename src/util/{asyncTimeout.ts => async-timeout.ts} (100%) diff --git a/src/avr/stk500-v1/stk500-v1.ts b/src/avr/stk500-v1/stk500-v1.ts index 7ce7bb4..dd3f82e 100644 --- a/src/avr/stk500-v1/stk500-v1.ts +++ b/src/avr/stk500-v1/stk500-v1.ts @@ -3,7 +3,7 @@ import { SerialPort } from 'serialport/dist/index.d'; import { setDTRRTS } from '../../util/serial-helpers'; -import asyncTimeout from '../../util/asyncTimeout'; +import asyncTimeout from '../../util/async-timeout'; interface STK500v1Options { quiet?: boolean; diff --git a/src/avr/stk500-v2/stk500-v2.ts b/src/avr/stk500-v2/stk500-v2.ts index 7997643..851a2ea 100644 --- a/src/avr/stk500-v2/stk500-v2.ts +++ b/src/avr/stk500-v2/stk500-v2.ts @@ -2,7 +2,7 @@ import { SerialPort } from 'serialport/dist/index.d'; import statics from './constants'; import { setDTRRTS } from '../../util/serial-helpers'; -import asyncTimeout from '../../util/asyncTimeout'; +import asyncTimeout from '../../util/async-timeout'; interface STK500v2Options { quiet?: boolean; diff --git a/src/esp/index.ts b/src/esp/index.ts index 125b685..13ba6ce 100644 --- a/src/esp/index.ts +++ b/src/esp/index.ts @@ -1,7 +1,7 @@ import { SerialPort } from 'serialport/dist/index.d'; import { ProgramConfig } from '../index.d'; -import ESPLoader, { ESPOptions, UploadFileDef } from './ESPLoader'; -import asyncTimeout from '../util/asyncTimeout'; +import ESPLoader, { ESPOptions, UploadFileDef } from './loader'; +import asyncTimeout from '../util/async-timeout'; const isSupported = (cpu: string) => ['esp8266', 'esp32'].includes(cpu); diff --git a/src/esp/ESPLoader.ts b/src/esp/loader.ts similarity index 99% rename from src/esp/ESPLoader.ts rename to src/esp/loader.ts index d8243ea..b9f107b 100644 --- a/src/esp/ESPLoader.ts +++ b/src/esp/loader.ts @@ -1,7 +1,7 @@ import { SerialPort } from 'serialport/dist/index.d'; import pako from 'pako'; import CryptoJS from 'crypto-js'; -import StubLoader from './StubLoader'; +import StubLoader from './stub-loader'; import roms from './roms'; import ROM from './roms/rom'; diff --git a/src/esp/roms/esp32.ts b/src/esp/roms/esp32.ts index 93c6a27..56bfc11 100644 --- a/src/esp/roms/esp32.ts +++ b/src/esp/roms/esp32.ts @@ -1,4 +1,4 @@ -import ESPLoader from '../ESPLoader'; +import ESPLoader from '../loader'; import ROM from './rom'; import { toMac } from './util'; diff --git a/src/esp/roms/esp32c3.ts b/src/esp/roms/esp32c3.ts index 13f99b5..aaaa8d2 100644 --- a/src/esp/roms/esp32c3.ts +++ b/src/esp/roms/esp32c3.ts @@ -1,4 +1,4 @@ -import ESPLoader from '../ESPLoader'; +import ESPLoader from '../loader'; import ROM from './rom'; import { toMac } from './util'; diff --git a/src/esp/roms/esp32s2.ts b/src/esp/roms/esp32s2.ts index 698e39d..0b94513 100644 --- a/src/esp/roms/esp32s2.ts +++ b/src/esp/roms/esp32s2.ts @@ -1,4 +1,4 @@ -import ESPLoader from '../ESPLoader'; +import ESPLoader from '../loader'; import ROM from './rom'; import { toMac } from './util'; diff --git a/src/esp/roms/esp8266.ts b/src/esp/roms/esp8266.ts index 2b8399c..cc53135 100644 --- a/src/esp/roms/esp8266.ts +++ b/src/esp/roms/esp8266.ts @@ -1,4 +1,4 @@ -import ESPLoader from '../ESPLoader'; +import ESPLoader from '../loader'; import ROM from './rom'; import { toHex } from './util'; diff --git a/src/esp/roms/rom.d.ts b/src/esp/roms/rom.d.ts index 999d0ab..2bda6d2 100644 --- a/src/esp/roms/rom.d.ts +++ b/src/esp/roms/rom.d.ts @@ -1,4 +1,4 @@ -import ESPLoader from '../ESPLoader'; +import ESPLoader from '../loader'; interface flashSizes { [key: string]: number; diff --git a/src/esp/StubLoader.ts b/src/esp/stub-loader.ts similarity index 100% rename from src/esp/StubLoader.ts rename to src/esp/stub-loader.ts diff --git a/src/esp/stubs/esp32.json b/src/esp/stubs/esp32.json deleted file mode 100644 index 9ff35dd..0000000 --- a/src/esp/stubs/esp32.json +++ /dev/null @@ -1 +0,0 @@ -{"text": "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", "textStart": 1074520064, "entry": 1074521516, "data": "CMD8Pw==", "dataStart": 1073605544} \ No newline at end of file diff --git a/src/esp/stubs/esp32c3.json b/src/esp/stubs/esp32c3.json deleted file mode 100644 index c0b6a76..0000000 --- a/src/esp/stubs/esp32c3.json +++ /dev/null @@ -1 +0,0 @@ -{"text": "QREixCbCBsa3NwRgEUfYyzc0BGC3RMg/XECRi5HnskAiRJJEQQGCgAhAg6cEABN19Q+Cl9W3ARG3BwBgSsgDqYcAJspOxlLEBs4izLcEAGD9WTdKyD/ATBN09A8N4PJAYkQjqCQBsknSREJJIkoFYYKAiECDJwoAE3X1D4KXfRTjGTT/yb83JwBgfEudi/X/NzcAYHxLnYv1/4KAQREGxt03tycAYCOmBwI3BwAImMOYQ33/yFeyQBNF9f8FiUEBgoBBEQbG2T993TcHAEC3JwBgmMM3JwBgHEP9/7JAQQGCgEERIsQ3RMk/kwfECZxLBsYmwqHPXTcxyRMExAkYSL1HgURj1ucABES9iJO0FABNP5U/HEQ3BwABE5bHAGN/5gC3BoAAmeC3BgABNycAYFDDFMO3JgBgmEJ9/0FHkeAFRxRIupccxJmOFMiyQCJEkkRBAYKAEwcADJxBYxvlAIHnhUecwSGoI6AFAPlXPoWCgAVHY4fnAIlGY43XAP1X/beTFwUBEwewDcGH4xHl/olHyb+TB8ANYxb1AJjBkwcADPG3kwbQDf1X4xLV/JjBkwewDW2/t0XJP0ERk4VFCQbGUT9jSQUGt0fJP5OHxwCDpgcIA9dHCBN19Q9CB0GDEwYXAEIGQYIjkscINpcjAKcAA9dHCJFnk4cHBEIHQYNjHvcCN8fIPxMHxwChZ7qXA6YHCLcGyT+3R8k/k4fHAJOGxgRjH+YAI6bHCCOg1wgjkgcIIaD5V+MG9fyyQEEBgoAjptcII6DnCN23QREGxpcAyP/ngADmA0WFAbJAdRUTNRUAQQGCgEERBsbFNxHBDUWyQEEBFwPI/2cAo+BBEQbGlwDI/+eAYN7JNwHFskBBAdm/skBBAYKAQREGxhMHAAxjGuUAEwWwDdE/EwXADbJAQQHptxMHsA3jG+X+wTcTBdAN9bdBESLEJsIGxiqEswS1AGMXlACyQCJEkkRBAYKAA0UEAAUETT/ttxMFAAx5twERIsw3RMk/kwfECSbKxEcGzkrITsYTBMQJY/OVAK6EucADKUQAqokmmRNZyQAcSGNV8AAcRGNf+QKFO33dSEAmhs6FlwDI/+eAYN8TdfUPAcWTB0AMXMhcQKaXXMBcRLOEl0BExPJAYkTSREJJskkFYYKAtTtlvwERBs4izBk7NwTOP2wAEwVE/5cAyP/ngADehUcV5bJHk/cHID7GDTs3JwBgHEe3BkAAEwVE/9WPHMeyRZcAyP/ngKDbszegAPJAYkQ+hQVhgoBBEbdHyT8FRwbGI47nCJOHxwkT18UAmMcFZ30XzMPIx/mNOpWqlbGBjMsjqgcAQTcZwRMFUAyyQEEBgoB1cUrBfXMFaSLFJsPO3tLc1toGx310GpGTBwkHipcTBIT6PpSqiSKFroSXAMj/54AgH5MHCQcFaoqXs4pHQbngBWeTBwcHfXSTBYT6ipcTBIT5PpSTBwcHipe+lSKFlwDI/+eAYBwihcFFlTUBRQVjGpG6QCpEmkQKSfZZZlrWWklhgoAmiWNzmgAFaUqG1oVOhZcAyP/ngGDKE3X1DwHtSobWhSKFlwDI/+eAoBfKmbOEJEFptxMFMAZVvzFxfXNW01rRXs9izQbfIt0m20rZTtdS1WbLasluxwVnGpE2jBMHBwcUCDaX/Xe6lz7GI6oH+KqKLouyi7E7kwcAAhnBtwcCAD6FlwDI/+eAoBCFZ2PjdxWFZBgIfXSThwQHupcTBIT6M4mHAEqFlwDI/+eAIA99ehgIk4cEB7qXkww6+b6ck4cEBxMNivm6l4FJPp2FZ5OHBwcYCLqXM4RHAYMtRPlj9m0LY/G5A1WgYTOmhSKFsTtBMyaGooVKhZcAyP/ngEAKppqmmWP2aQOzh7lBY/KHA7MHO0HehGPzdwG+hCaGooVWhZcAyP/ngCC5E3X1D03dhWeThwcHGAi6lzOERwEjLAT4gUSNTaMJBPhmhZcAyP/ngICqffkDRTT56oW9PmNABQLj4p3+hWcYCJOHBwe6lzOHlwBSlyMKp/iFBOm3+VfjE/X8EUfjg+T0BWcUCJMHBwd9dLaXkwWE+hMEhPk+lJMHBwe2l76VIoWXAMj/54Bg/305wUUihUk5XTkRObcHAgAZ4ZMHAAI+hZcAyP/ngGD8BWMakfpQalTaVEpZulkqWppaClv6S2pM2kxKTbpNKWGCgLdXQUlZcZOH94QBRT7Ohtai1KbSytDOztLM1srayN7G4sTmwurAbt6XAMj/54BAordHyD83d8k/k4cHABMHh7pj6+cSJTmRRWgIMTEFObfHyD+Th8cAIWc+lyMg9wi3BzhAN0rIP5OHZxsjIPoAt0rJP602k4rKABMKCgBjAQUQtycMYEVHuNeFRUVFlwDI/+eAQO63BThAAUaThQUARUWXAMj/54BA7zc3BGAcSzcFAgCT50cAHMuXAMj/54BA7pcAyP/ngMD+t0cAYJxfEeUT9ccBYRUTNRUAgUWXAMj/54CAocFnN0vJP/0XEwcAEIVmQWa3BQABAUWTCcsJjWs3TMg/lwDI/+eAAJzOm5MMzACDp8oI9d+DpMoIhUcjpgoIIwTxAoPHFAAJRyMV4QKjBPECAtYpR2OG5wZNR2OA5wgtPqFFKBA5NgPHNACDxyQAkWYiB12Pk4cGAWP15wYTBbANbTQTBcANVTQTBeAOeTwpNnm/I6AHAJEH0bW3BThAAUaThWUDFUWXAMj/54Cg4DcHAGBcRxMFAAKT5xcQXMcZv4PHNAADxyQAogfZjxFH45jn+JxEnEM+1lm3yUcjFfECvb+DxxQANUZjiscqY272DhlGY4vHNGNi9ggNRmOKxxZjbPYECUZjhcckAUkTBPAPE3X0Dw08E3X5DzU0tTzjGATwg8cUAD1HY4jnQmNq9zQRR2OC51IZR2OA51QNR+OY5+6DxTQAg8ckABOFhAGiBd2NwRWpNOG9kUZjgdcMlUbjldf6wUcFRWMZ9w6cRNhIIyT6ACMi6gCdqqVGY4vXImPs9gKdRmOI1yahRuOf1/aTB0ACYxr3BgLWHUQBRXEyAUVVMtU6zTqhRSgQfRTRMnX0AUkBRKm/qUZjjNcirUbjldf04UdjGPcc3EyYTNRIkEjMRIhElwDI/+eAoIAqiTM1oAAqhC23TUZji8cUY2T2BEFGY4nHFmNs9gC9RuOW1/ChR+MH9/oBSRMEAAwJt8VGY4/XBElH45nn7oNHywljgAceg6fJAGOUByQjDgsIA6RJASWgkwYgDWOB1xBj4fYCkwYADWOK1waTBhAN457X6qFHYwz3BgVFKoQBSU29kwYwDWOH10KTBkAN45/X6INHywljhgcYnERBFwOkSQFjhOcAEwQADIFHkwbwDmPN5w4Dx1QAg8dEAAFJIgddj4PHZADCB12Pg8d0AOIH2Y/jgPbmEwQQDKG9BURF85fwx//ngOBwMzSgAEm/A62EAMBEs2eNABOXRwE9/y06Lf1BaSKdfRn9fTMFjUAZ6AFFrbcxgZfwx//ngABuMf1ulOW3s3clAfX3QWkzBY1AY26JAH15MwWNQHnYMYGX8Mf/54CAaxX5SpT1t0GBl/DH/+eAQGoV8TMEJEHBv8FH2bXBRwVE4xz38MxEiEShOqW/wUcFROMU9/CcSGPn9hDMSIhEGTKNtwVE4xr37pxIY+32DsBEzEiIRDOEhwL1MCOsCQAjpIuwgbczhvQAA0aGAYUHsY7tvQFJBUWptZFHBUXjHffqiESBRZfwx//ngIBmPb+Td/cA45kH5BNdRwAThIQAAUn9XeN2qd9IRJfwx//ngABTHERYQBRAfY9jh7cBkEKTx/f/8Y9dj5jCBQlBBNm/kUcBvYMlSgBBF5HlCc8BSRMEYAwps4MnigBj5ucGk3c3AOOaB94DKIoAAUaBR7OG9QAzBfhAY+nnAOMDBtgjItoAIySqAK27M4b0ABBOkQeQwgVG6b+hRwVF4xf34AMkigAZwBMEgAwjJAoAIyIKADM1gADVuwFJEwQgDE2xAUkTBIAMabkBSRMEkAxJuUlHY4rnHGNi9wRFR+OR57qDxzQAA8ckABOEhAGiB9mPk40H/wVJg6fJAGOFDQCZw2NEIBFjWAkYEwdwDCOq6QDjlwe2kweQDGGiEwcgDWOL5wwTB0AN45zntAPENACDxyQAIgRdjJfwx//ngOBNA6nJAEEUY3MkASKJ4woJsgOkSQBKlDGAg6cJAWNW8ACDp4kAY1D0Cu/wL8N13QOlSQBKhpOFhAGX8Mf/54BgSQnFkwdADCOq+QCDp0kAypcjovkAg6fJADOJJ0EjpikBl/DH/+eAoEfhvAllEwUFcQOpxACARJfwx//ngIA5twcAYNhLtwYAAcEWk1dHARIHdY+9i9mPs4cnAwFFs9WHApfwx//ngGA6EwWAPpfwx//ngCA2cbTUSJBIzESIRO/wT/u9vO/wz72Bv7d2yT8Dp4a6t8fIP5OHxwCZjz7Sg6eLsDd9yT9u0BMNzQmThIa6BUhj8/0ADUhCxjrE7/BPuiJHMkg3Rck/ooVcEJMGzAAQEBMFRQuX8Mf/54DAOYJXAyeNsIxAs439QB2PPpSSVyMk7bAqib6VjMCTB8wAnY1jVaAAoWfjmfXmZoXv8E/WI6CUAZW14x4J5uODB56TB4AMI6r5AOm6nETjmwec7/CPywllEwUFcZfwx//ngGApl/DH/+eA4CxlusBE4woEmu/wb8kTBYA+l/DH/+eAYCcClHm6tlAmVJZUBln2SWZK1kpGS7ZLJkyWTAZN8l1lYYKA", "textStart": 1077411840, "entry": 1077413488, "data": "DEDIPw==", "dataStart": 1070164904} \ No newline at end of file diff --git a/src/esp/stubs/esp32h2.json b/src/esp/stubs/esp32h2.json deleted file mode 100644 index f778881..0000000 --- a/src/esp/stubs/esp32h2.json +++ /dev/null @@ -1 +0,0 @@ -{"text": "ARG3BwBgSsgDqYcAJspOxlLEBs4izLcEAGD9WTdKyD/ATBN09A8N4PJAYkQjqCQBsknSREJJIkoFYYKAiECDJwoAE3X1D4KXfRTjGTT/yb83JwBgfEudi/X/NzcAYHxLnYv1/4KAQREGxt03tycAYCOmBwI3BwAImMOYQ33/yFeyQBNF9f8FiUEBgoBBEQbG2T993TcHAEC3JwBgmMM3JwBgHEP9/7JAQQGCgEERIsQ3RMk/kwfECZxLBsYmwqHPXTcxyRMExAkYSL1HgURj1ucABES9iJO0FABNP5U/HEQ3BwABE5bHAGN/5gC3BoAAmeC3BgABNycAYFDDFMO3JgBgmEJ9/0FHkeAFRxRIupccxJmOFMiyQCJEkkRBAYKAEwcADJxBYxvlAIHnhUecwSGoI6AFAPlXPoWCgAVHY4fnAIlGY43XAP1X/beTFwUBEwewDcGH4xHl/olHyb+TB8ANYxb1AJjBkwcADPG3kwbQDf1X4xLV/JjBkwewDW2/t0XJP0ERk4VFCQbGUT9jSQUGt0fJP5OHxwCDpgcIA9dHCBN19Q9CB0GDEwYXAEIGQYIjkscINpcjAKcAA9dHCJFnk4cHBEIHQYNjHvcCN8fIPxMHxwChZ7qXA6YHCLcGyT+3R8k/k4fHAJOGxgRjH+YAI6bHCCOg1wgjkgcIIaD5V+MG9fyyQEEBgoAjptcII6DnCN23AREizDdEyT+TB8QJJsrERwbOSshOxhMExAlj85UAroS5wAMpRACqiSaZE1nJABxIY1XwABxEY1/5Ahk9fd1IQCaGzoWXAMj/54Dg7BN19Q8BxZMHQAxcyFxAppdcwFxEs4SXQETE8kBiRNJEQkmySQVhgoANNWW/AREGziLMdTs3BM4/bAATBQT/lwDI/+eAgOuFRxXlskeT9wcgPsbhOzcnAGAcR7cGQAATBQT/1Y8cx7JFlwDI/+eAIOmzN6AA8kBiRD6FBWGCgEERt0fJPwVHBsYjjucIk4fHCRPXxQCYxwVnfRfMw8jH+Y06laqVsYGMyyOqBwBBNxnBEwVQDLJAQQGCgEERBsYTBwAMYxDlAhMFsA2XAMj/54AA0xMFwA2yQEEBFwPI/2cAA9ITB7AN4xjl/pcAyP/ngADREwXQDcW3QREixCbCBsYqhLMEtQBjF5QAskAiRJJEQQGCgANFBAAFBEU37bd1cUrBfXMFaSLFJsPO3tLc1toGx310GpGTBwkHipcTBIT6PpSqiSKFroSXAMj/54AgJ5MHCQcFaoqXs4pHQbngBWeTBwcHfXSTBYT6ipcTBIT5PpSTBwcHipe+lSKFlwDI/+eAYCQihcFFhT8BRQVjGpG6QCpEmkQKSfZZZlrWWklhgoAmiWNzmgAFaUqG1oVOhZcAyP/ngKDRE3X1DwHtSobWhSKFlwDI/+eAoB/KmbOEJEFptxMFMAZVvxMFAAwXA8j/ZwDDwXFxfXNWy1rJXsdixQbXItUm00rRTs9SzWbDasHu3qqKGpETBQACLouyizaMAsKXAMj/54CgGYVnY+d3E4VkfXSThwQHipcTBIT6PpQihZcAyP/ngGAYfXqThwQHipeTDDr5vpyThwQHEw2K+YqXAUk+nYVnk4cHB4qXs4RHAYOtRPlj9G0LY3G5A0WgpTfOhSaFQTWFN06GpoUihZcAyP/ngMATzppOmWN2aQOzB7lBY/KHA7MHK0HeiWPzdwG+iU6GpoVWhZcAyP/ngODBE3X1D03dhWeThwcHipezhEcBI6wE+IFJjU2jiQT4ZoWXAMj/54Dgsn35A8U0+eqF6T5jTwUA4+I9/4Vnk4cHB4qXM4c3AVKXIwqn+IUJ8bf5V+MU9fwRR+OG6fQFZ5MHBwd9dJMFhPqKlxMEhPk+lJMHBweKl76VIoWXAMj/54BACVU1IoXBRXU7cT0TBQAClwDI/+eA4AYFYxqRulAqVJpUCln6SWpK2kpKS7pLKkyaTApN9l1NYYKAt1dBSVlxk4f3hAFFPs6G1qLUptLK0M7O0szWytrI3sbixObC6sBu3pcAyP/ngACst0fIPzd3yT+ThwcAEweHumPn5xIlNZFFaAiBMwU1t8fIP5OHxwAhZz6XIyD3CLcFOEC3BzhAk4cHGAFGk4UFADdKyD8VRSMg+gCXAMj/54Ag/DcHAGBcRxMFAAK3Ssk/k+cXEFzHlwDI/+eA4PqXAMj/54BgC7dHAGCcX5OKygATCgoAEeUT9ccBYRUTNRUAgUWXAMj/54DgrMFnN0vJP/0XEwcAEIVmQWa3BQABAUWTCcsJjWs3TMg/lwDI/+eAYKfOm5MMzACDp8oI9d+DpMoIhUcjpgoIIwTxAoPHFAAJRyMV4QKjBPECAtYpR2OM5wRNR2OG5waRM6FFKBCxOQPHNACDxyQAkWYiB12Pk4cGAWP75wQTBbANlwDI/+eAIJQTBcANlwDI/+eAYJMTBeAOlwDI/+eAoJIJM3G3I6AHAJEH8bWDxzQAA8ckAKIH2Y8RR+OS5/qcRJxDPtZpv8lHIxXxAkm/g8cUADVGY4zHKmNg9hAZRmONxzRjYfYIDUZjjMcWY2v2BAlGY4fHJAFJEwTwDxN19A9JNhN1+Q+1Pmk5FfCDxxQAPUdji+dCY233NBFHY4XnUhlHY4bnVA1H45Pn8IPFNACDxyQAE4WEAaIF3Y3BFT08/bWRRmOE1wyVRuOW1/rBRwVFYxz3DpxE2EgjJPoAIyLqALWqpUZjjtciY+/2Ap1GY4vXJqFG45DX+JMHQAJjHfcGAtYdRAFFlwDI/+eAoIMBRcU8OTExMaFFKBB9FA02ffABSQFEmb+pRmOM1yKtRuOT1/ThR2MY9xzcTJhM1EiQSMxEiESXAMj/54AAjSqJMzWgACqEHbdNRmOLxxRjZPYEQUZjiccWY2z2AL1G45TX8KFH4wf3+gFJEwQADP29xUZjj9cESUfjl+fug0fLCWOABx6Dp8kAY5QHJCMOCwgDpEkBJaCTBiANY4HXEGPh9gKTBgANY4rXBpMGEA3jnNfqoUdjDPcGBUUqhAFJfbWTBjANY43XQpMGQA3jndfog0fLCWOGBxicREEXA6RJAWOE5wATBAAMgUeTBvAOY83nDgPHVACDx0QAAUkiB12Pg8dkAMIHXY+Dx3QA4gfZj+OO9uQTBBAMkb0FREXzl/DH/+eAQH0zNKAASb8DrYQAwESzZ40AE5dHAT3/JTIt/UFpIp19Gf19MwWNQBnoAUWttzGBl/DH/+eAYHox/W6U5bezdyUB9fdBaTMFjUBjbokAfXkzBY1AedgxgZfwx//ngOB3FflKlPW3QYGX8Mf/54CgdhXxMwQkQcG/wUfZtcFHBUTjHPfwzESIRG0ypb/BRwVE4xT38JxIY+f2EMxIiETVOI23BUTjGvfunEhj7fYOwETMSIhEM4SHAuk4I6wJACOki7CBtzOG9AADRoYBhQexju29AUkFRam1kUcFReMd9+qIRIFFl/DH/+eA4HI9v5N39wDjmQfkE11HABOEhAABSf1d43ap30hEl/DH/+eA4F4cRFhAFEB9j2OHtwGQQpPH9//xj12PmMIFCUEE2b+RRwG9gyVKAEEXkeUJzwFJEwRgDBmzgyeKAGPm5waTdzcA45oH3gMoigABRoFHs4b1ADMF+EBj6ecA4wMG2CMi2gAjJKoArbszhvQAEE6RB5DCBUbpv6FHBUXjF/fgAySKABnAEwSADCMkCgAjIgoAMzWAANW7AUkTBCAMebkBSRMEgAxZuQFJEwSQDHmxSUdjiuccY2L3BEVH45nnuoPHNAADxyQAE4SEAaIH2Y+TjQf/BUmDp8kAY4UNAJnDY0QgEWNYCRgTB3AMI6rpAOOfB7aTB5AMYaITByANY4vnDBMHQA3jlOe2A8Q0AIPHJAAiBF2Ml/DH/+eAQFoDqckAQRRjcyQBIonjAgm0A6RJAEqUMYCDpwkBY1bwAIOniQBjUPQK7/BvzHXdA6VJAEqGk4WEAZfwx//ngMBVCcWTB0AMI6r5AIOnSQDKlyOi+QCDp8kAM4knQSOmKQGX8Mf/54AAVOW0CWUTBQVxA6nEAIBEl/DH/+eAYEW3BwBg2Eu3BgABwRaTV0cBEgd1j72L2Y+zhycDAUWz1YcCl/DH/+eAQEYTBYA+l/DH/+eAAEJxvNRIkEjMRIhE7/A/gXm07/APx4G/t3bJPwOnhrq3x8g/k4fHAJmPPtKDp4uwN33JP27QEw3NCZOEhroFSGPz/QANSELGOsTv8I/DIkcySDdFyT+ihVwQkwbMABAQEwVFC5fwx//ngCBGglcDJ42wjECzjf1AHY8+lJJXIyTtsCqJvpWMwJMHzACdjWNVoAChZ+OZ9eZmhe/wL9UjoJQBlbXjHgnm44sHnpMHgAwjqvkA7bKcROOTB54BRZfwx//ngMA4CWUTBQVxl/DH/+eA4DSX8Mf/54BgOMmywETjDwSaAUWX8Mf/54BANhMFgD6X8Mf/54CAMgKUTbK2UCZUllQGWfZJZkrWSkZLtksmTJZMBk3yXWVhgoAAAA==", "textStart": 1077411840, "entry": 1077413328, "data": "DEDIPw==", "dataStart": 1070164904} \ No newline at end of file diff --git a/src/esp/stubs/esp32s2.json b/src/esp/stubs/esp32s2.json deleted file mode 100644 index ffd6171..0000000 --- a/src/esp/stubs/esp32s2.json +++ /dev/null @@ -1 +0,0 @@ -{"text": "CAAAYBwAAGAAAABgrCv+PxAAAGA2QQAh+v/AIAA4AkH5/8AgACgEICCUnOIGBQAAAEH1/4H2/8AgAKgEiAigoHTgCAALImYC54b0/yHx/8AgADkCHfAAAFQgQD9UMEA/NkEAkf3/wCAAiAmAgCRWSP+R+v/AIACICYCAJFZI/x3wAAAALCBAPwAgQD8AAAAINkEA5fz/Ifv/DAjAIACJApH7/4H5/8AgAJJoAMAgAJgIVnn/wCAAiAJ88oAiMCAgBB3wAAAAAEA2QQBl/P8Wmv+B7f+R/P/AIACZCMAgAJgIVnn/HfAAAAAAgAAAAAABmAD+P////wAEIEA/NkEAIfz/OEIWIwal+P8WygWIQgz5DAOHqQyIIpCIEAwZgDmDMDB0Zfr/pfP/iCKR8v9AiBGHOR+R7f/ME5Hs/6Hv/8AgAIkKgdH/wCAAmQjAIACYCFZ5/xwJDBgwiZM9CIhCMIjAiUKIIjo4OSId8JAA/j8IgP0/gIAAAISAAABAQAAASMD9P5QA/j82QQCx+P8goHTl4ACW6gWB9v+R9v+goHSQmIDAIACyKQCR8/+QiIDAIACSGACQkPQbycDA9MAgAMJYAJqbwCAAokkAwCAAkhgAger/kJD0gID0h5lGgeT/keX/oej/mpjAIADICbHk/4ecGUYCAHzohxrhRgkAAADAIACJCsAgALkJRgIAwCAAuQrAIACJCZHY/5qIDAnAIACSWAAd8AAA+Pz/P4QyAUDA8QBAtPEAQJAyAUA2QQAx+v+cIqgDgfn/4AgAoqIAgfj/4AgABgQAoqIAgfb/4AgAqAOB9f/gCAAd8ADwK/4/sCv+P4wxAUA2QQAh/P+B6v/IAqgIsfr/gfv/4AgADAiJAh3wFP3/P0ArAUA2QQCB/f+CCABmKAmB8f+ICIwYpfz/DAqB+f/gCAAd8CgrAUA2QQCtAiHz/yICAGYiMpHn/4gJGygpCZHm/wwCipmiSQCCyMEMGYApgyCAdMyIIq9AKqqgiYOM2OX3/wYCAAAAAIHu/+AIAB3wAAAANkEAgqDArQKHkg2ioNtl+v+ioNxGAwAAAIKg24eSBWX5/6Kg3eX4/x3wAAA2QQA6MgYCAACiAgAbImX8/zeS9B3wAAA2QQCioMCl9v8d8ACoK/4/pCv+PwAyAUDsMQFAMDMBQDZhAHzIrQKHky0xq//GBQAAqAMMHL0Bgff/4AgAgR//ogEAiAjgCACoA4Hz/+AIAOYa3cYKAAAAZgMmDAPNAQwrMmEAge7/4AgAmAGB6P83mQ2oCGYaCDHm/8AgAKJDAJkIHfDMcQFANkEAQUX/WDRQM2MW8wNYFFpTUFxBhgAApdD/iESmGASIJIel8iXJ/xaa/6gUzQO9AoHy/+AIAKCgdIw6IqDEKVQoFDoiKRQoNDAywDk0HfBw4vo/CCBAPwAAQACEYgFApGIBQDZhACXC/zH5/xCxIDCjIIH6/+AIAE0KDBLsuogBkqIAkIgQiQFlxv+R8v+h8v/AIACICaCIIMAgAIkJuAGtA4Hv/+AIAKAkgx3wAAD/DwAANkEAgRj/kqABkkgAMJxBkmgCkfr/MmgBKTgwMLSaIiozMDxBDAIpWDlIZfj/LQqMGiKgxR3wAAAAEAAAWBAAAGxSAECMcgFAjFIAQAxTAEA2ISGi0RCB+v/gCACGCgAAAFH1/70BUENjzQStAoH1/+AIAKCgdPwqzQS9AaLREIHy/+AIAEoiQDPAVjP9oev/stEQGqqB7f/gCACh6P8cCxqqpeD/LQMGAQAAACKgYx3wAAAAbBAAAGgQAABwEAAAdBAAAHgQAADwKwFANkEhYfv/gfv/EGaAQmYAQUv/EIiAYtEQDApyBABZCKJmGmYnBuXL/wYCAAAsCoEr/+AIAFHv/3HN/xpVWAVXtwLGPQCtBoHL/+AIAIHr/3Hm/xqIelEMBFkIRicAgeT/QHPAGoiICBCxIHB4Y80HIKIggcH/4AgAoKB0jNpx2/8MBVJmFnpxRg0AAACl1v9wtyCtAaXU/yXW/80HELEgYKYggbb/4AgAeiJ6RDe0zYHR/1B0wBqIiAiHN6BG7/8ADAqiRmyBzP8aiKIoAIHL/+AIAFbq/rGm/6IGbBq7pZYA9+oN9kUKWreiSwAbVYbz/wCyr/63msdmRQhSJho3tQJXtKehm/+9BhqqgZ3/4AgAZc7/oZf/HAsQqoAlzP+lzf8xBv8iAwBmIgcMGmW7/wYCAKKgIIHo/uAIAB3wAAAAAP0/T0hBSfQr/j+IgQJASDwBQHCDAkAIAAhgFIACQAwAAGA4QEA///8AAAAAAQAQJwAAKIFAPwAAAICMgAAAEEAAAABAAAAAAP0/BAD9PxQAAGDw//8A9Cv+PwgA/T+wAP4/XPIAQNDxAECk8QBA1DIBQFgyAUCg5ABABHABQAB1AUCI2ABAgEkBQOg1AUDsOwFAgAABQOxwAUBscQFADHEBQIQpAUB4dgFA4HcBQJR2AUAAMABAaAABQDbBACHR/wwKImEIQqAAgeb/4AgAIcz/Mc3/BgEAQmIASyI3Mvdlvf8MS6LBIGW7/6W8/zF6/iF6/kHF/yojwCAAOQIhHf5JAiG+/rICAGYrYiGg/sHt/qgCDBWB7/7gCAAMnDwLDAqB0f/gCACxuf/CoACioAmBzv/gCACiogCBl/7gCACxtP+oAoHK/+AIAKgCgZH+4AgAqAKBx//gCABBr//AIAAoBFAiIMAgACkEBgoAALGr/wwMDFqBvf/gCABBqP9SoQHAIAAoBCwKUCIgwCAAKQSBgf7gCACBuP/gCAAhof/AIAAoAsy6HMRAIhAiwvgMFCCkgwwLgbH/4AgA8Zr/0Rv/wZr/sSP+4qEADAqBrP/gCAAhmv9BIP4qM1LUK0YWAAAAAIG4/sAgAGIIAGBgdBZ2BKKiAMAgACJIAIFn/uAIAKGL/4Gf/+AIAIGf/+AIAHGI/3zowCAAaAehh/+AZhDAIABpB4GZ/+AIAIGY/+AIACCiIIGX/+AIAMAgACgDFgL6wCAAKAMMBwwWwCAAeQNiQRBiAgEMKGJBEYJRCXlRJpYHHDd3Fh3GBwBiAgNyAgKAZhFwZiBmRhBoIsAgAGgGaVEGAQAcJmJRCaWi/wyLosEQpaD/ggIDYgICgIgRYIggYWf/YGD0h7YSoqDAJZz/oqDu5Zv/5Z//Bt//AGICAQzXd5YChrQAZzdWZmYCRu0A9nYgZjYChoQA9kYIZiYCxmcABigAZkYCRpgAZlYCBskARiQADJd3lgKGrABnNxBmdgLGygBmhgKGIADGHQAAAGaWAka8AAy3d5YCRpQABhkAHDd3lgIGUABnNytmtgLGXgAcB2c3DAz3DA93lgKGRADGEAAcF3eWAsZnABwnd5YCBn4AhgsAAHKg0neWAoZAAGc3D3Kg0HcWV3Kg0XcWaIYEAAByoNN3lgKGYAFyoNR3lgJGWQAMD3Kg/0bGACxGDA9yoMBnGAIGwwBtD/lRDHetBuWM/60GZYz/pZD/ZZD/DIuiwRByx/8ljv9WF/6GJgAMF1boLYJhDoEy/+AIAIjhoHiDRrMAACaIBAwXBrEAYiICciIDcIYggIC0Vrj+5Zr/cGaAnBoG+P8AoKxBgSb/4AgAVjr9ctfwcKbAzCcGhgAAoID0Vhj+RgQAoKD1gR//4AgAVir7gf/+gHfAgf7+cKbAdzjkhgMAoKxBgRb/4AgAVkr5ctfwcKbAVqf+BnYAAHKgwCaIAoaSAAwPfQ/GkAAmuPXGaAAMFya4AsaMALgyqCJioABlnP+gdoPGiAByoAEmuAKGhgCB7f5iIgTyoAByoMJnuAKGggC4UqgiDBbllP8MB6B2k8Z9AJKgAWa4MGIiBIHi/vKgAHKgwme4AkZ4AHgyuFKoInB2gpnR5ZH/YWD9DAiY0YlmYtYreSagmIN9CcZuAAAAYVr9DA+SBgByoMb3mQKGagB4VmgigsjwgGbAkqDAYHmTYqDvhgIAAPqSkgkYG/+QZjCHL/KSAgWCAgSAmRGAmSCCAgYMDwCIEZCYIIICB4CIAZCIIIBmwIKgwWB4k4ZWAGFB/XKgxoIGAP0IFsgUiDYMD3KgyPcYAsZPAIJGAHhWRk0AHIYMDwwXZxgCxkoA+HLoYthSyEK4Mqgigb3+4AgA/QoMCvB6g8ZDAAAADBcmSALGQACoIgwLgbT+4AgABh8AgKA0DA9yoMD3GgKGOgCAZEGLko0KfPsGDgAAqDmJ4ZnRucGBq/7gCACY0YjheCmoGcgJoKcQuMEmBw3AIADYDHB7MNB3EHCqIMAgAKkMG4iSyRBnOMQGlf9mSAKGk/8MD3KgwEYkAAwXJrgCxiEAYYn+iFJ4IokGYYj+eQYMBwYdAMGE/gwP2AwMF4LI8G0PgGeT0H+TcGYQcqDG95ZZsX7+cqDJ6AuHPk6AkBRyoMD3mUUMH4YCAACaYmhmS5lpCm0PkH7Amq2HOe0W9t2pDHkLBnb/AAAMF2aIGmFv/ngGFicAcqDIDAqpBmFq/qkGDBZwppN9CgwPcKB08mEMJVz/8iEM8KB0pVv/pV//Vne3YgIBgqAPhxZDZzgUZkYChn0AZmYCRoMAJjYCRtb+RiMAHCd3lgLGdwBnNwscF3eWAsZAAAbQ/gByoNJ3Fl9yoNR3lgIGIABGy/4AAACBOv1iCABmJgKGx/6IMqFE/mgigmEOgVf+4AgAIUj+kUn+wCAAKAKI4SC0NcAiEZAiECArIIAigq0HYLLCgVX+4AgAoqPogUv+4AgAxrb+AADSIgXCIgSyIgOoIiV1/way/rICA2ICAoC7EWC7ILLL8KLCGKVb/was/gBiAgNyAgKAZhFwZiCBRP7gCABxrvxixvCIN4BmYxb2qIgXioaAjEGGAQCJ4aUq/4jhkicEphkEmCeXqO3lIv8Wmv+iJwFgxiCywhiBNf7gCAAWSgAioMQpVygXaiIpFyg3YGLAaTeBL/7gCAAGkP4AcgIDggICgHcRgHcgYsIYcsfwDBkGIQAAgRH+IbD84igAcmEH4CLAImEGKCUMGSe3AQw5ieGZ0enB5SL/mNEhCP7owaEI/r0GmQHywRjdAsLBHIEZ/uAIAJ0KuCWocYjhoLvAuSWgd8C4CKpmqGGquwupoKkguQigrwUgu8DMmsLbgAwdwK2DFhoBIKIggmEOkmENJUv/iOGY0SkIKDSMp5CPMZCIwNYoAFay9taJAGKgx2lUhgAAAIxJjLIGYP4AFsKXIqDIhgAAIqDJKVSGW/4oIlaSliUz/6HW/YHr/eAIAIH2/eAIAAZV/gAAACgyFsKUZTH/oqPogeP94AgA4AIAhk7+AAAAHfAAADZBAJ0CgqDAKAOHmQ/MMgwShgcADAIpA3zihg4AJhIHJiIWhgMAAACCoNuAKSOHmSYMIikDfPJGBwAioNwnmQgMEikDLQiGAwCCoN188oeZBgwSKQMioNsd8AAA", "textStart": 1073905664, "entry": 1073907540, "data": "CAD9Pw==", "dataStart": 1073622004} \ No newline at end of file diff --git a/src/esp/stubs/esp32s3.json b/src/esp/stubs/esp32s3.json deleted file mode 100644 index 18b054b..0000000 --- a/src/esp/stubs/esp32s3.json +++ /dev/null @@ -1 +0,0 @@ -{"text": "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", "textStart": 1077379072, "entry": 1077381116, "data": "CADKPw==", "dataStart": 1070279592} \ No newline at end of file diff --git a/src/esp/stubs/esp8266.json b/src/esp/stubs/esp8266.json deleted file mode 100644 index 104fafc..0000000 --- a/src/esp/stubs/esp8266.json +++ /dev/null @@ -1 +0,0 @@ -{"text": "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", "textStart": 1074843648, "entry": 1074843652, "data": "CIH+PwUFBAACAwcAAwMLALnXEEDv1xBAHdgQQLrYEEBo5xBAHtkQQHTZEEDA2RBAaOcQQILaEED/2hBAwNsQQGjnEEBo5xBAWNwQQGjnEEA33xBAAOAQQDvgEEBo5xBAaOcQQNfgEEBo5xBAv+EQQGXiEECj4xBAY+QQQDTlEEBo5xBAaOcQQGjnEEBo5xBAYuYQQGjnEEBX5xBAkN0QQI/YEECm5RBAq9oQQPzZEEBo5xBA7OYQQDHnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQCLaEEBf2hBAvuUQQAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAkAAAANAAAAEQAAABkAAAAhAAAAMQAAAEEAAABhAAAAgQAAAMEAAAABAQAAgQEAAAECAAABAwAAAQQAAAEGAAABCAAAAQwAAAEQAAABGAAAASAAAAEwAAABQAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAAAAAAAAAAAAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAANAAAADwAAABEAAAATAAAAFwAAABsAAAAfAAAAIwAAACsAAAAzAAAAOwAAAEMAAABTAAAAYwAAAHMAAACDAAAAowAAAMMAAADjAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADAAAAAwAAAAMAAAAEAAAABAAAAAQAAAAEAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAABAREgAIBwkGCgULBAwDDQIOAQ8AAQEAAAEAAAAEAAAA", "dataStart": 1073720488} \ No newline at end of file diff --git a/src/util/asyncTimeout.ts b/src/util/async-timeout.ts similarity index 100% rename from src/util/asyncTimeout.ts rename to src/util/async-timeout.ts diff --git a/test/util.ts b/test/util.ts index 0617ac6..ab8a9d9 100644 --- a/test/util.ts +++ b/test/util.ts @@ -5,9 +5,9 @@ import axios from 'axios'; import path from 'path'; import { SerialPort } from 'serialport'; import { ProgramFile } from '../src/index.d'; -import ESPLoader from '../src/esp/ESPLoader'; +import ESPLoader from '../src/esp/loader'; import { waitForOpen } from '../src/util/serial-helpers'; -import asyncTimeout from '../src/util/asyncTimeout'; +import asyncTimeout from '../src/util/async-timeout'; export const waitForData = ( serial: SerialPort,