tasmota-webserial-esptool 9.2.8 → 9.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/const.d.ts +89 -6
- package/dist/const.js +116 -15
- package/dist/esp_loader.d.ts +136 -5
- package/dist/esp_loader.js +780 -148
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/util.d.ts +4 -0
- package/dist/util.js +8 -0
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/js/script.js +207 -180
- package/js/webusb-serial.js +21 -1
- package/package.json +3 -3
- package/src/const.ts +152 -19
- package/src/esp_loader.ts +930 -172
- package/src/index.ts +3 -0
- package/src/util.ts +9 -0
package/dist/esp_loader.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/// <reference types="@types/w3c-web-serial" />
|
|
2
|
-
import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31, CHIP_FAMILY_ESP8266, MAX_TIMEOUT, DEFAULT_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, ESP_CHANGE_BAUDRATE, ESP_CHECKSUM_MAGIC, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_DATA, ESP_MEM_END, ESP_READ_REG, ESP_WRITE_REG, ESP_SPI_ATTACH, ESP_SYNC, ESP_GET_SECURITY_INFO, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE, STUB_FLASH_WRITE_SIZE, MEM_END_ROM_TIMEOUT, ROM_INVALID_RECV_MSG, SYNC_PACKET, SYNC_TIMEOUT, USB_RAM_BLOCK, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, CHIP_ERASE_TIMEOUT, FLASH_READ_TIMEOUT, timeoutPerMb, ESP_ROM_BAUD, USB_JTAG_SERIAL_PID, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, getSpiFlashAddresses, DETECTED_FLASH_SIZES, CHIP_DETECT_MAGIC_REG_ADDR, CHIP_DETECT_MAGIC_VALUES, CHIP_ID_TO_INFO, ESP32P4_EFUSE_BLOCK1_ADDR, SlipReadError, ESP32S2_RTC_CNTL_WDTWPROTECT_REG, ESP32S2_RTC_CNTL_WDTCONFIG0_REG, ESP32S2_RTC_CNTL_WDTCONFIG1_REG, ESP32S2_RTC_CNTL_WDT_WKEY, ESP32S2_GPIO_STRAP_REG, ESP32S2_GPIO_STRAP_SPI_BOOT_MASK, ESP32S2_RTC_CNTL_OPTION1_REG, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32S3_RTC_CNTL_WDTWPROTECT_REG, ESP32S3_RTC_CNTL_WDTCONFIG0_REG, ESP32S3_RTC_CNTL_WDTCONFIG1_REG, ESP32S3_RTC_CNTL_WDT_WKEY, ESP32S3_GPIO_STRAP_REG, ESP32S3_GPIO_STRAP_SPI_BOOT_MASK, ESP32S3_RTC_CNTL_OPTION1_REG, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, } from "./const";
|
|
2
|
+
import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31, CHIP_FAMILY_ESP8266, MAX_TIMEOUT, DEFAULT_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, ESP_CHANGE_BAUDRATE, ESP_CHECKSUM_MAGIC, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_DATA, ESP_MEM_END, ESP_READ_REG, ESP_WRITE_REG, ESP_SPI_ATTACH, ESP_SYNC, ESP_GET_SECURITY_INFO, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE, STUB_FLASH_WRITE_SIZE, MEM_END_ROM_TIMEOUT, ROM_INVALID_RECV_MSG, SYNC_PACKET, SYNC_TIMEOUT, USB_RAM_BLOCK, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, CHIP_ERASE_TIMEOUT, FLASH_READ_TIMEOUT, timeoutPerMb, ESP_ROM_BAUD, USB_JTAG_SERIAL_PID, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, getSpiFlashAddresses, DETECTED_FLASH_SIZES, CHIP_DETECT_MAGIC_REG_ADDR, CHIP_DETECT_MAGIC_VALUES, CHIP_ID_TO_INFO, ESP32P4_EFUSE_BLOCK1_ADDR, SlipReadError, ESP32S2_RTC_CNTL_WDTWPROTECT_REG, ESP32S2_RTC_CNTL_WDTCONFIG0_REG, ESP32S2_RTC_CNTL_WDTCONFIG1_REG, ESP32S2_RTC_CNTL_WDT_WKEY, ESP32S2_GPIO_STRAP_REG, ESP32S2_GPIO_STRAP_SPI_BOOT_MASK, ESP32S2_RTC_CNTL_OPTION1_REG, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32S3_RTC_CNTL_WDTWPROTECT_REG, ESP32S3_RTC_CNTL_WDTCONFIG0_REG, ESP32S3_RTC_CNTL_WDTCONFIG1_REG, ESP32S3_RTC_CNTL_WDT_WKEY, ESP32S3_GPIO_STRAP_REG, ESP32S3_GPIO_STRAP_SPI_BOOT_MASK, ESP32S3_RTC_CNTL_OPTION1_REG, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32S2_UARTDEV_BUF_NO, ESP32S2_UARTDEV_BUF_NO_USB_OTG, ESP32S3_UARTDEV_BUF_NO, ESP32S3_UARTDEV_BUF_NO_USB_OTG, ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C3_BUF_UART_NO_OFFSET, ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG, ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG, ESP32C3_RTC_CNTL_WDTWPROTECT_REG, ESP32C3_RTC_CNTL_WDTCONFIG0_REG, ESP32C3_RTC_CNTL_WDTCONFIG1_REG, ESP32C3_RTC_CNTL_WDT_WKEY, ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG, ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG, ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG, ESP32C5_C6_RTC_CNTL_WDT_WKEY, ESP32C5_UARTDEV_BUF_NO, ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C6_UARTDEV_BUF_NO, ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32P4_RTC_CNTL_WDTWPROTECT_REG, ESP32P4_RTC_CNTL_WDTCONFIG0_REG, ESP32P4_RTC_CNTL_WDTCONFIG1_REG, ESP32P4_RTC_CNTL_WDT_WKEY, ESP32P4_UARTDEV_BUF_NO_REV0, ESP32P4_UARTDEV_BUF_NO_REV300, ESP32P4_UARTDEV_BUF_NO_USB_OTG, ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32P4_RTC_CNTL_OPTION1_REG, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32H2_UARTDEV_BUF_NO, ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL, } from "./const";
|
|
3
3
|
import { getStubCode } from "./stubs";
|
|
4
4
|
import { hexFormatter, sleep, slipEncode, toHex } from "./util";
|
|
5
5
|
import { deflate } from "pako";
|
|
6
6
|
import { pack, unpack } from "./struct";
|
|
7
7
|
export class ESPLoader extends EventTarget {
|
|
8
|
+
/**
|
|
9
|
+
* Check if device is using USB-JTAG or USB-OTG (not external serial chip)
|
|
10
|
+
* Returns undefined if not yet determined
|
|
11
|
+
*/
|
|
12
|
+
get isUsbJtagOrOtg() {
|
|
13
|
+
return this._parent ? this._parent._isUsbJtagOrOtg : this._isUsbJtagOrOtg;
|
|
14
|
+
}
|
|
8
15
|
constructor(port, logger, _parent) {
|
|
9
16
|
super();
|
|
10
17
|
this.port = port;
|
|
@@ -19,12 +26,19 @@ export class ESPLoader extends EventTarget {
|
|
|
19
26
|
this.IS_STUB = false;
|
|
20
27
|
this.connected = true;
|
|
21
28
|
this.flashSize = null;
|
|
22
|
-
this.
|
|
29
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
30
|
+
this.SLIP_END = 0xc0;
|
|
31
|
+
this.SLIP_ESC = 0xdb;
|
|
32
|
+
this.SLIP_ESC_END = 0xdc;
|
|
33
|
+
this.SLIP_ESC_ESC = 0xdd;
|
|
23
34
|
this._isESP32S2NativeUSB = false;
|
|
24
35
|
this._initializationSucceeded = false;
|
|
25
36
|
this.__commandLock = Promise.resolve([0, []]);
|
|
26
37
|
this.__isReconfiguring = false;
|
|
27
38
|
this.__abandonCurrentOperation = false;
|
|
39
|
+
this._suppressDisconnect = false;
|
|
40
|
+
this.__consoleMode = false;
|
|
41
|
+
this._isUsbJtagOrOtg = undefined;
|
|
28
42
|
// Adaptive speed adjustment for flash read operations
|
|
29
43
|
this.__adaptiveBlockMultiplier = 1;
|
|
30
44
|
this.__adaptiveMaxInFlightMultiplier = 1;
|
|
@@ -81,6 +95,22 @@ export class ESPLoader extends EventTarget {
|
|
|
81
95
|
this.__chipVariant = value;
|
|
82
96
|
}
|
|
83
97
|
}
|
|
98
|
+
// Console mode with parent delegation
|
|
99
|
+
get _consoleMode() {
|
|
100
|
+
return this._parent ? this._parent._consoleMode : this.__consoleMode;
|
|
101
|
+
}
|
|
102
|
+
set _consoleMode(value) {
|
|
103
|
+
if (this._parent) {
|
|
104
|
+
this._parent._consoleMode = value;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.__consoleMode = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Public setter for console mode (used by script.js)
|
|
111
|
+
setConsoleMode(value) {
|
|
112
|
+
this._consoleMode = value;
|
|
113
|
+
}
|
|
84
114
|
get _inputBuffer() {
|
|
85
115
|
if (this._parent) {
|
|
86
116
|
return this._parent._inputBuffer;
|
|
@@ -317,6 +347,16 @@ export class ESPLoader extends EventTarget {
|
|
|
317
347
|
await this.connectWithResetStrategies();
|
|
318
348
|
// Detect chip type
|
|
319
349
|
await this.detectChip();
|
|
350
|
+
// Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
351
|
+
// This is needed to determine the correct reset strategy for console mode
|
|
352
|
+
try {
|
|
353
|
+
this._isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
354
|
+
this.logger.debug(`USB connection type: ${this._isUsbJtagOrOtg ? "USB-JTAG/OTG" : "External Serial Chip"}`);
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
this.logger.debug(`Could not detect USB connection type: ${err}`);
|
|
358
|
+
// Leave as undefined if detection fails
|
|
359
|
+
}
|
|
320
360
|
// Read the OTP data for this chip and store into this.efuses array
|
|
321
361
|
const FlAddr = getSpiFlashAddresses(this.getChipFamily());
|
|
322
362
|
const AddrMAC = FlAddr.macFuse;
|
|
@@ -340,7 +380,7 @@ export class ESPLoader extends EventTarget {
|
|
|
340
380
|
if (chipInfo) {
|
|
341
381
|
this.chipName = chipInfo.name;
|
|
342
382
|
this.chipFamily = chipInfo.family;
|
|
343
|
-
// Get chip revision for ESP32-P4
|
|
383
|
+
// Get chip revision for ESP32-P4 and ESP32-C3
|
|
344
384
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
345
385
|
this.chipRevision = await this.getChipRevision();
|
|
346
386
|
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
@@ -353,6 +393,10 @@ export class ESPLoader extends EventTarget {
|
|
|
353
393
|
}
|
|
354
394
|
this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
|
|
355
395
|
}
|
|
396
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
397
|
+
this.chipRevision = await this.getChipRevision();
|
|
398
|
+
this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
|
|
399
|
+
}
|
|
356
400
|
this.logger.debug(`Detected chip via IMAGE_CHIP_ID: ${chipId} (${this.chipName})`);
|
|
357
401
|
return;
|
|
358
402
|
}
|
|
@@ -385,7 +429,6 @@ export class ESPLoader extends EventTarget {
|
|
|
385
429
|
this.chipFamily = chip.family;
|
|
386
430
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
387
431
|
this.chipRevision = await this.getChipRevision();
|
|
388
|
-
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
389
432
|
if (this.chipRevision >= 300) {
|
|
390
433
|
this.chipVariant = "rev300";
|
|
391
434
|
}
|
|
@@ -394,24 +437,30 @@ export class ESPLoader extends EventTarget {
|
|
|
394
437
|
}
|
|
395
438
|
this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
|
|
396
439
|
}
|
|
440
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
441
|
+
this.chipRevision = await this.getChipRevision();
|
|
442
|
+
}
|
|
397
443
|
this.logger.debug(`Detected chip via magic value: ${toHex(chipMagicValue >>> 0, 8)} (${this.chipName})`);
|
|
398
444
|
}
|
|
399
445
|
/**
|
|
400
446
|
* Get chip revision for ESP32-P4
|
|
401
447
|
*/
|
|
402
448
|
async getChipRevision() {
|
|
403
|
-
if (this.chipFamily
|
|
404
|
-
|
|
449
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
450
|
+
// Read from EFUSE_BLOCK1 to get chip revision
|
|
451
|
+
// Word 2 contains revision info for ESP32-P4
|
|
452
|
+
const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
|
|
453
|
+
// Minor revision: bits [3:0]
|
|
454
|
+
const minorRev = word2 & 0x0f;
|
|
455
|
+
// Major revision: bits [23] << 2 | bits [5:4]
|
|
456
|
+
const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
|
|
457
|
+
// Revision is major * 100 + minor
|
|
458
|
+
return majorRev * 100 + minorRev;
|
|
459
|
+
}
|
|
460
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
461
|
+
return await this.getChipRevisionC3();
|
|
405
462
|
}
|
|
406
|
-
|
|
407
|
-
// Word 2 contains revision info for ESP32-P4
|
|
408
|
-
const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
|
|
409
|
-
// Minor revision: bits [3:0]
|
|
410
|
-
const minorRev = word2 & 0x0f;
|
|
411
|
-
// Major revision: bits [23] << 2 | bits [5:4]
|
|
412
|
-
const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
|
|
413
|
-
// Revision is major * 100 + minor
|
|
414
|
-
return majorRev * 100 + minorRev;
|
|
463
|
+
return 0;
|
|
415
464
|
}
|
|
416
465
|
/**
|
|
417
466
|
* Get security info including chip ID (ESP32-C3 and later)
|
|
@@ -484,13 +533,24 @@ export class ESPLoader extends EventTarget {
|
|
|
484
533
|
}
|
|
485
534
|
}
|
|
486
535
|
catch {
|
|
487
|
-
this.logger.error("Read loop got disconnected");
|
|
536
|
+
// this.logger.error("Read loop got disconnected");
|
|
488
537
|
}
|
|
489
538
|
finally {
|
|
490
539
|
// Always reset reconfiguring flag when read loop ends
|
|
491
540
|
// This prevents "Cannot write during port reconfiguration" errors
|
|
492
541
|
// when the read loop dies unexpectedly
|
|
493
542
|
this._isReconfiguring = false;
|
|
543
|
+
// Release reader if still locked
|
|
544
|
+
if (this._reader) {
|
|
545
|
+
try {
|
|
546
|
+
this._reader.releaseLock();
|
|
547
|
+
this.logger.debug("Reader released in readLoop cleanup");
|
|
548
|
+
}
|
|
549
|
+
catch (err) {
|
|
550
|
+
this.logger.debug(`Reader release error in readLoop: ${err}`);
|
|
551
|
+
}
|
|
552
|
+
this._reader = undefined;
|
|
553
|
+
}
|
|
494
554
|
}
|
|
495
555
|
// Disconnected!
|
|
496
556
|
this.connected = false;
|
|
@@ -502,7 +562,11 @@ export class ESPLoader extends EventTarget {
|
|
|
502
562
|
detail: { message: "ESP32-S2 Native USB requires port reselection" },
|
|
503
563
|
}));
|
|
504
564
|
}
|
|
505
|
-
|
|
565
|
+
// Only dispatch disconnect event if not suppressed
|
|
566
|
+
if (!this._suppressDisconnect) {
|
|
567
|
+
this.dispatchEvent(new Event("disconnect"));
|
|
568
|
+
}
|
|
569
|
+
this._suppressDisconnect = false;
|
|
506
570
|
this.logger.debug("Finished read loop");
|
|
507
571
|
}
|
|
508
572
|
sleep(ms = 100) {
|
|
@@ -564,6 +628,30 @@ export class ESPLoader extends EventTarget {
|
|
|
564
628
|
await this.setDTR(false); // IO0=HIGH, done
|
|
565
629
|
await this.sleep(200);
|
|
566
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Reset to firmware mode (not bootloader) for Web Serial
|
|
633
|
+
* Keeps IO0=HIGH during reset so chip boots into firmware
|
|
634
|
+
*/
|
|
635
|
+
async hardResetToFirmware() {
|
|
636
|
+
await this.setDTR(false); // IO0=HIGH
|
|
637
|
+
await this.setRTS(true); // EN=LOW, chip in reset
|
|
638
|
+
await this.sleep(100);
|
|
639
|
+
await this.setRTS(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
|
|
640
|
+
await this.sleep(50);
|
|
641
|
+
await this.sleep(200);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Reset to firmware mode (not bootloader) for WebUSB
|
|
645
|
+
* Keeps IO0=HIGH during reset so chip boots into firmware
|
|
646
|
+
*/
|
|
647
|
+
async hardResetToFirmwareWebUSB() {
|
|
648
|
+
await this.setDTRWebUSB(false); // IO0=HIGH
|
|
649
|
+
await this.setRTSWebUSB(true); // EN=LOW, chip in reset
|
|
650
|
+
await this.sleep(100);
|
|
651
|
+
await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
|
|
652
|
+
await this.sleep(50);
|
|
653
|
+
await this.sleep(200);
|
|
654
|
+
}
|
|
567
655
|
/**
|
|
568
656
|
* @name hardResetUnixTight
|
|
569
657
|
* Unix Tight reset for Web Serial (Desktop) - sets DTR and RTS simultaneously
|
|
@@ -1013,7 +1101,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1013
1101
|
try {
|
|
1014
1102
|
// Check if port is still open, if not, skip this strategy
|
|
1015
1103
|
if (!this.connected || !this.port.writable) {
|
|
1016
|
-
this.logger.
|
|
1104
|
+
this.logger.debug(`Port disconnected, skipping ${strategy.name} reset`);
|
|
1017
1105
|
continue;
|
|
1018
1106
|
}
|
|
1019
1107
|
// Clear abandon flag before starting new strategy
|
|
@@ -1055,7 +1143,9 @@ export class ESPLoader extends EventTarget {
|
|
|
1055
1143
|
}
|
|
1056
1144
|
catch (error) {
|
|
1057
1145
|
lastError = error;
|
|
1058
|
-
this.logger.
|
|
1146
|
+
// this.logger.debug(
|
|
1147
|
+
// `${strategy.name} reset failed: ${(error as Error).message}`,
|
|
1148
|
+
// );
|
|
1059
1149
|
// Set abandon flag to stop any in-flight operations
|
|
1060
1150
|
this._abandonCurrentOperation = true;
|
|
1061
1151
|
// Wait a bit for in-flight operations to abort
|
|
@@ -1077,12 +1167,134 @@ export class ESPLoader extends EventTarget {
|
|
|
1077
1167
|
}
|
|
1078
1168
|
/**
|
|
1079
1169
|
* @name watchdogReset
|
|
1080
|
-
* Watchdog reset for ESP32-S2/S3 with USB-OTG
|
|
1170
|
+
* Watchdog reset for ESP32-S2/S3/C3 with USB-OTG or USB-JTAG/Serial
|
|
1081
1171
|
* Uses RTC watchdog timer to reset the chip - works when DTR/RTS signals are not available
|
|
1172
|
+
* This is an alias for rtcWdtResetChipSpecific() for backwards compatibility
|
|
1082
1173
|
*/
|
|
1083
1174
|
async watchdogReset() {
|
|
1084
|
-
this.
|
|
1085
|
-
|
|
1175
|
+
await this.rtcWdtResetChipSpecific();
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Check if current chip is using USB-OTG
|
|
1179
|
+
* Supports ESP32-S2 and ESP32-S3
|
|
1180
|
+
*/
|
|
1181
|
+
async usingUsbOtg() {
|
|
1182
|
+
let uartDevBufNo;
|
|
1183
|
+
let usbOtgValue;
|
|
1184
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1185
|
+
uartDevBufNo = ESP32S2_UARTDEV_BUF_NO;
|
|
1186
|
+
usbOtgValue = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
|
|
1187
|
+
}
|
|
1188
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1189
|
+
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1190
|
+
usbOtgValue = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
|
|
1191
|
+
}
|
|
1192
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1193
|
+
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1194
|
+
if (this.chipRevision === null) {
|
|
1195
|
+
this.chipRevision = await this.getChipRevision();
|
|
1196
|
+
}
|
|
1197
|
+
if (this.chipRevision < 300) {
|
|
1198
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1202
|
+
}
|
|
1203
|
+
usbOtgValue = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1209
|
+
return uartNo === usbOtgValue;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Check if current chip is using USB-JTAG/Serial
|
|
1213
|
+
* Supports ESP32-S3 and ESP32-C3
|
|
1214
|
+
*/
|
|
1215
|
+
async usingUsbJtagSerial() {
|
|
1216
|
+
let uartDevBufNo;
|
|
1217
|
+
let usbJtagSerialValue;
|
|
1218
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1219
|
+
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1220
|
+
usbJtagSerialValue = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1221
|
+
}
|
|
1222
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1223
|
+
// ESP32-C3: BSS_UART_DEV_ADDR depends on chip revision
|
|
1224
|
+
// Revision < 101: 0x3FCDF064
|
|
1225
|
+
// Revision >= 101: 0x3FCDF060
|
|
1226
|
+
let bssUartDevAddr;
|
|
1227
|
+
// Get chip revision if not already set
|
|
1228
|
+
if (this.chipRevision === null) {
|
|
1229
|
+
this.chipRevision = await this.getChipRevisionC3();
|
|
1230
|
+
}
|
|
1231
|
+
if (this.chipRevision < 101) {
|
|
1232
|
+
bssUartDevAddr = 0x3fcdf064;
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1235
|
+
bssUartDevAddr = 0x3fcdf060;
|
|
1236
|
+
}
|
|
1237
|
+
uartDevBufNo = bssUartDevAddr + ESP32C3_BUF_UART_NO_OFFSET;
|
|
1238
|
+
usbJtagSerialValue = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1239
|
+
}
|
|
1240
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
|
|
1241
|
+
uartDevBufNo = ESP32C5_UARTDEV_BUF_NO;
|
|
1242
|
+
usbJtagSerialValue = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1243
|
+
}
|
|
1244
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
1245
|
+
uartDevBufNo = ESP32C6_UARTDEV_BUF_NO;
|
|
1246
|
+
usbJtagSerialValue = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1247
|
+
}
|
|
1248
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1249
|
+
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1250
|
+
// Revision < 300: 0x4FF3FEC8
|
|
1251
|
+
// Revision >= 300: 0x4FFBFEC8
|
|
1252
|
+
if (this.chipRevision === null) {
|
|
1253
|
+
this.chipRevision = await this.getChipRevision();
|
|
1254
|
+
}
|
|
1255
|
+
if (this.chipRevision < 300) {
|
|
1256
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1260
|
+
}
|
|
1261
|
+
usbJtagSerialValue = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1262
|
+
}
|
|
1263
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32H2) {
|
|
1264
|
+
uartDevBufNo = ESP32H2_UARTDEV_BUF_NO;
|
|
1265
|
+
usbJtagSerialValue = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1271
|
+
return uartNo === usbJtagSerialValue;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Get chip revision for ESP32-C3
|
|
1275
|
+
* Reads from EFUSE registers and calculates revision
|
|
1276
|
+
*/
|
|
1277
|
+
async getChipRevisionC3() {
|
|
1278
|
+
if (this.chipFamily !== CHIP_FAMILY_ESP32C3) {
|
|
1279
|
+
return 0;
|
|
1280
|
+
}
|
|
1281
|
+
// Read EFUSE_RD_MAC_SPI_SYS_3_REG (bits [20:18] = lower 3 bits of revision)
|
|
1282
|
+
const word3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
|
|
1283
|
+
const low = (word3 >> 18) & 0x07;
|
|
1284
|
+
// Read EFUSE_RD_MAC_SPI_SYS_5_REG (bits [25:23] = upper 3 bits of revision)
|
|
1285
|
+
const word5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
|
|
1286
|
+
const hi = (word5 >> 23) & 0x07;
|
|
1287
|
+
// Combine: upper 3 bits from word5, lower 3 bits from word3
|
|
1288
|
+
const revision = (hi << 3) | low;
|
|
1289
|
+
return revision;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* RTC watchdog timer reset for ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, and ESP32-P4
|
|
1293
|
+
* Uses specific registers for each chip family
|
|
1294
|
+
* Note: ESP32-H2 does NOT support WDT reset
|
|
1295
|
+
*/
|
|
1296
|
+
async rtcWdtResetChipSpecific() {
|
|
1297
|
+
this.logger.debug("Hard resetting with watchdog timer...");
|
|
1086
1298
|
let WDTWPROTECT_REG;
|
|
1087
1299
|
let WDTCONFIG0_REG;
|
|
1088
1300
|
let WDTCONFIG1_REG;
|
|
@@ -1099,12 +1311,62 @@ export class ESPLoader extends EventTarget {
|
|
|
1099
1311
|
WDTCONFIG1_REG = ESP32S3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1100
1312
|
WDT_WKEY = ESP32S3_RTC_CNTL_WDT_WKEY;
|
|
1101
1313
|
}
|
|
1314
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1315
|
+
WDTWPROTECT_REG = ESP32C3_RTC_CNTL_WDTWPROTECT_REG;
|
|
1316
|
+
WDTCONFIG0_REG = ESP32C3_RTC_CNTL_WDTCONFIG0_REG;
|
|
1317
|
+
WDTCONFIG1_REG = ESP32C3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1318
|
+
WDT_WKEY = ESP32C3_RTC_CNTL_WDT_WKEY;
|
|
1319
|
+
}
|
|
1320
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
1321
|
+
this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
1322
|
+
// C5 and C6 use LP_WDT (Low Power Watchdog Timer)
|
|
1323
|
+
WDTWPROTECT_REG = ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG;
|
|
1324
|
+
WDTCONFIG0_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG;
|
|
1325
|
+
WDTCONFIG1_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG;
|
|
1326
|
+
WDT_WKEY = ESP32C5_C6_RTC_CNTL_WDT_WKEY;
|
|
1327
|
+
}
|
|
1328
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1329
|
+
// P4 uses LP_WDT (Low Power Watchdog Timer)
|
|
1330
|
+
WDTWPROTECT_REG = ESP32P4_RTC_CNTL_WDTWPROTECT_REG;
|
|
1331
|
+
WDTCONFIG0_REG = ESP32P4_RTC_CNTL_WDTCONFIG0_REG;
|
|
1332
|
+
WDTCONFIG1_REG = ESP32P4_RTC_CNTL_WDTCONFIG1_REG;
|
|
1333
|
+
WDT_WKEY = ESP32P4_RTC_CNTL_WDT_WKEY;
|
|
1334
|
+
}
|
|
1102
1335
|
else {
|
|
1103
|
-
throw new Error(`
|
|
1336
|
+
throw new Error(`rtcWdtResetChipSpecific() is not supported for ${this.chipFamily}`);
|
|
1104
1337
|
}
|
|
1105
1338
|
// Unlock watchdog registers
|
|
1106
1339
|
await this.writeRegister(WDTWPROTECT_REG, WDT_WKEY, undefined, 0);
|
|
1107
|
-
//
|
|
1340
|
+
// Clear force download boot register (if applicable) BEFORE triggering WDT reset
|
|
1341
|
+
// This ensures the chip boots into firmware mode after reset
|
|
1342
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1343
|
+
try {
|
|
1344
|
+
await this.writeRegister(ESP32S2_RTC_CNTL_OPTION1_REG, 0, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
1345
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1346
|
+
}
|
|
1347
|
+
catch (err) {
|
|
1348
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1352
|
+
try {
|
|
1353
|
+
await this.writeRegister(ESP32S3_RTC_CNTL_OPTION1_REG, 0, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
1354
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1355
|
+
}
|
|
1356
|
+
catch (err) {
|
|
1357
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1361
|
+
try {
|
|
1362
|
+
await this.writeRegister(ESP32P4_RTC_CNTL_OPTION1_REG, 0, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
1363
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1364
|
+
}
|
|
1365
|
+
catch (err) {
|
|
1366
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
// Set WDT timeout to 2000ms (matches Python esptool)
|
|
1108
1370
|
await this.writeRegister(WDTCONFIG1_REG, 2000, undefined, 0);
|
|
1109
1371
|
// Enable WDT: bit 31 = enable, bits 28-30 = stage, bit 8 = sys reset, bits 0-2 = prescaler
|
|
1110
1372
|
const wdtConfig = (1 << 31) | (5 << 28) | (1 << 8) | 2;
|
|
@@ -1114,102 +1376,139 @@ export class ESPLoader extends EventTarget {
|
|
|
1114
1376
|
// Wait for reset to take effect
|
|
1115
1377
|
await this.sleep(500);
|
|
1116
1378
|
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Helper: Check if USB-based WDT reset should be used for S2/S3
|
|
1381
|
+
* Returns true if WDT reset was performed, false otherwise
|
|
1382
|
+
*/
|
|
1383
|
+
async tryUsbWdtReset(chipName, GPIO_STRAP_REG, GPIO_STRAP_SPI_BOOT_MASK, RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) {
|
|
1384
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1385
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1386
|
+
if (isUsingUsbOtg || isUsingUsbJtagSerial) {
|
|
1387
|
+
const strapReg = await this.readRegister(GPIO_STRAP_REG);
|
|
1388
|
+
const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
|
|
1389
|
+
// Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
|
|
1390
|
+
if ((strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
|
|
1391
|
+
(forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0) {
|
|
1392
|
+
await this.rtcWdtResetChipSpecific();
|
|
1393
|
+
this.logger.debug(`${chipName}: RTC WDT reset (USB detected, GPIO0 low)`);
|
|
1394
|
+
return true;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Chip-specific hard reset for ESP32-S2
|
|
1401
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1402
|
+
*/
|
|
1403
|
+
async hardResetS2() {
|
|
1404
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1405
|
+
if (isUsingUsbOtg) {
|
|
1406
|
+
await this.rtcWdtResetChipSpecific();
|
|
1407
|
+
this.logger.debug("ESP32-S2: RTC WDT reset (USB-OTG detected)");
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
// Use standard hardware reset
|
|
1411
|
+
await this.hardResetClassic();
|
|
1412
|
+
this.logger.debug("ESP32-S2: Classic reset");
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Chip-specific hard reset for ESP32-S3
|
|
1417
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1418
|
+
*/
|
|
1419
|
+
async hardResetS3() {
|
|
1420
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1421
|
+
if (isUsingUsbJtagSerial) {
|
|
1422
|
+
await this.rtcWdtResetChipSpecific();
|
|
1423
|
+
this.logger.debug("ESP32-S3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1424
|
+
}
|
|
1425
|
+
else {
|
|
1426
|
+
// Use standard hardware reset
|
|
1427
|
+
await this.hardResetClassic();
|
|
1428
|
+
this.logger.debug("ESP32-S3: Classic reset");
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Chip-specific hard reset for ESP32-C3
|
|
1433
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1434
|
+
*/
|
|
1435
|
+
async hardResetC3() {
|
|
1436
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1437
|
+
if (isUsingUsbJtagSerial) {
|
|
1438
|
+
await this.rtcWdtResetChipSpecific();
|
|
1439
|
+
this.logger.debug("ESP32-C3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
// Use standard hardware reset
|
|
1443
|
+
await this.hardResetClassic();
|
|
1444
|
+
this.logger.debug("ESP32-C3: Classic reset");
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1117
1447
|
async hardReset(bootloader = false) {
|
|
1448
|
+
// In console mode, only allow simple hardware reset (no bootloader entry)
|
|
1449
|
+
if (this._consoleMode) {
|
|
1450
|
+
if (bootloader) {
|
|
1451
|
+
this.logger.debug("Skipping bootloader reset - device is in console mode");
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
// Simple hardware reset to restart firmware (IO0=HIGH)
|
|
1455
|
+
this.logger.debug("Performing hardware reset (console mode)...");
|
|
1456
|
+
if (this.isWebUSB()) {
|
|
1457
|
+
await this.hardResetToFirmwareWebUSB();
|
|
1458
|
+
}
|
|
1459
|
+
else {
|
|
1460
|
+
await this.hardResetToFirmware();
|
|
1461
|
+
}
|
|
1462
|
+
this.logger.debug("Hardware reset complete");
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1118
1465
|
if (bootloader) {
|
|
1119
1466
|
// enter flash mode
|
|
1120
1467
|
if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
|
|
1121
1468
|
await this.hardResetUSBJTAGSerial();
|
|
1122
|
-
this.logger.
|
|
1469
|
+
this.logger.debug("USB-JTAG/Serial reset.");
|
|
1123
1470
|
}
|
|
1124
1471
|
else {
|
|
1125
1472
|
// Use different reset strategy for WebUSB (Android) vs Web Serial (Desktop)
|
|
1126
1473
|
if (this.isWebUSB()) {
|
|
1127
1474
|
await this.hardResetClassicWebUSB();
|
|
1128
|
-
this.logger.
|
|
1475
|
+
this.logger.debug("Classic reset (WebUSB/Android).");
|
|
1129
1476
|
}
|
|
1130
1477
|
else {
|
|
1131
1478
|
await this.hardResetClassic();
|
|
1132
|
-
this.logger.
|
|
1479
|
+
this.logger.debug("Classic reset.");
|
|
1133
1480
|
}
|
|
1134
1481
|
}
|
|
1135
1482
|
}
|
|
1136
1483
|
else {
|
|
1137
1484
|
// just reset (no bootloader mode)
|
|
1138
|
-
// For ESP32-S2/S3 with USB-OTG, check if watchdog reset is needed
|
|
1139
|
-
if (this.
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
: ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
1150
|
-
const RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1151
|
-
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
1152
|
-
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
1153
|
-
await this.writeRegister(RTC_CNTL_OPTION1_REG, 0, RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
1154
|
-
}
|
|
1155
|
-
catch (e) {
|
|
1156
|
-
// Skip invalid response and continue reset (can happen when monitoring during reset)
|
|
1157
|
-
this.logger.log("Warning: Could not clear force download boot mode:", e);
|
|
1158
|
-
}
|
|
1159
|
-
// Check the strapping register to see if we can perform a watchdog reset
|
|
1160
|
-
// Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
|
|
1161
|
-
let useWatchdogReset = false;
|
|
1162
|
-
try {
|
|
1163
|
-
const GPIO_STRAP_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1164
|
-
? ESP32S2_GPIO_STRAP_REG
|
|
1165
|
-
: ESP32S3_GPIO_STRAP_REG;
|
|
1166
|
-
const GPIO_STRAP_SPI_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1167
|
-
? ESP32S2_GPIO_STRAP_SPI_BOOT_MASK
|
|
1168
|
-
: ESP32S3_GPIO_STRAP_SPI_BOOT_MASK;
|
|
1169
|
-
const RTC_CNTL_OPTION1_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1170
|
-
? ESP32S2_RTC_CNTL_OPTION1_REG
|
|
1171
|
-
: ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
1172
|
-
const RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1173
|
-
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
1174
|
-
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
1175
|
-
const strapReg = await this.readRegister(GPIO_STRAP_REG);
|
|
1176
|
-
const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
|
|
1177
|
-
// GPIO0 low (download mode) AND force download boot not set
|
|
1178
|
-
if ((strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
|
|
1179
|
-
(forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0) {
|
|
1180
|
-
useWatchdogReset = true;
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
catch (e) {
|
|
1184
|
-
// If we can't read the registers, use watchdog reset as fallback
|
|
1185
|
-
this.logger.log("Warning: Could not read strap/option registers, using watchdog reset:", e);
|
|
1186
|
-
useWatchdogReset = true;
|
|
1187
|
-
}
|
|
1188
|
-
if (useWatchdogReset) {
|
|
1189
|
-
await this.watchdogReset();
|
|
1190
|
-
this.logger.log("Watchdog reset (USB-OTG).");
|
|
1191
|
-
}
|
|
1192
|
-
else {
|
|
1193
|
-
// Not in download mode, can use DTR/RTS reset
|
|
1194
|
-
// But USB-OTG doesn't have DTR/RTS, so fall back to watchdog
|
|
1195
|
-
await this.watchdogReset();
|
|
1196
|
-
this.logger.log("Watchdog reset (USB-OTG, normal boot).");
|
|
1197
|
-
}
|
|
1485
|
+
// For ESP32-S2/S3 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
|
|
1486
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2 && !this._consoleMode) {
|
|
1487
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S2", ESP32S2_GPIO_STRAP_REG, ESP32S2_GPIO_STRAP_SPI_BOOT_MASK, ESP32S2_RTC_CNTL_OPTION1_REG, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK);
|
|
1488
|
+
if (wdtResetUsed)
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32S3 &&
|
|
1492
|
+
!this._consoleMode) {
|
|
1493
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S3", ESP32S3_GPIO_STRAP_REG, ESP32S3_GPIO_STRAP_SPI_BOOT_MASK, ESP32S3_RTC_CNTL_OPTION1_REG, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK);
|
|
1494
|
+
if (wdtResetUsed)
|
|
1495
|
+
return;
|
|
1198
1496
|
}
|
|
1199
|
-
|
|
1497
|
+
// Standard reset for all other cases
|
|
1498
|
+
if (this.isWebUSB()) {
|
|
1200
1499
|
// WebUSB: Use longer delays for better compatibility
|
|
1201
1500
|
await this.setRTSWebUSB(true); // EN->LOW
|
|
1202
1501
|
await this.sleep(200);
|
|
1203
1502
|
await this.setRTSWebUSB(false);
|
|
1204
1503
|
await this.sleep(200);
|
|
1205
|
-
this.logger.
|
|
1504
|
+
this.logger.debug("Hard reset (WebUSB).");
|
|
1206
1505
|
}
|
|
1207
1506
|
else {
|
|
1208
1507
|
// Web Serial: Standard reset
|
|
1209
1508
|
await this.setRTS(true); // EN->LOW
|
|
1210
1509
|
await this.sleep(100);
|
|
1211
1510
|
await this.setRTS(false);
|
|
1212
|
-
this.logger.
|
|
1511
|
+
this.logger.debug("Hard reset.");
|
|
1213
1512
|
}
|
|
1214
1513
|
}
|
|
1215
1514
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1425,44 +1724,44 @@ export class ESPLoader extends EventTarget {
|
|
|
1425
1724
|
const waitingFor = partialPacket === null ? "header" : "content";
|
|
1426
1725
|
throw new SlipReadError("Timed out waiting for packet " + waitingFor);
|
|
1427
1726
|
}
|
|
1428
|
-
const
|
|
1727
|
+
const byte = this._readByte();
|
|
1429
1728
|
if (partialPacket === null) {
|
|
1430
1729
|
// waiting for packet header
|
|
1431
|
-
if (
|
|
1730
|
+
if (byte == this.SLIP_END) {
|
|
1432
1731
|
partialPacket = [];
|
|
1433
1732
|
}
|
|
1434
1733
|
else {
|
|
1435
1734
|
if (this.debug) {
|
|
1436
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1735
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1437
1736
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1438
1737
|
hexFormatter(this._inputBuffer));
|
|
1439
1738
|
}
|
|
1440
|
-
throw new SlipReadError("Invalid head of packet (" + toHex(
|
|
1739
|
+
throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
|
|
1441
1740
|
}
|
|
1442
1741
|
}
|
|
1443
1742
|
else if (inEscape) {
|
|
1444
1743
|
// part-way through escape sequence
|
|
1445
1744
|
inEscape = false;
|
|
1446
|
-
if (
|
|
1447
|
-
partialPacket.push(
|
|
1745
|
+
if (byte == this.SLIP_ESC_END) {
|
|
1746
|
+
partialPacket.push(this.SLIP_END);
|
|
1448
1747
|
}
|
|
1449
|
-
else if (
|
|
1450
|
-
partialPacket.push(
|
|
1748
|
+
else if (byte == this.SLIP_ESC_ESC) {
|
|
1749
|
+
partialPacket.push(this.SLIP_ESC);
|
|
1451
1750
|
}
|
|
1452
1751
|
else {
|
|
1453
1752
|
if (this.debug) {
|
|
1454
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1753
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1455
1754
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1456
1755
|
hexFormatter(this._inputBuffer));
|
|
1457
1756
|
}
|
|
1458
|
-
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(
|
|
1757
|
+
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
|
|
1459
1758
|
}
|
|
1460
1759
|
}
|
|
1461
|
-
else if (
|
|
1760
|
+
else if (byte == this.SLIP_ESC) {
|
|
1462
1761
|
// start of escape sequence
|
|
1463
1762
|
inEscape = true;
|
|
1464
1763
|
}
|
|
1465
|
-
else if (
|
|
1764
|
+
else if (byte == this.SLIP_END) {
|
|
1466
1765
|
// end of packet
|
|
1467
1766
|
if (this.debug)
|
|
1468
1767
|
this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
|
|
@@ -1472,7 +1771,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1472
1771
|
}
|
|
1473
1772
|
else {
|
|
1474
1773
|
// normal byte in packet
|
|
1475
|
-
partialPacket.push(
|
|
1774
|
+
partialPacket.push(byte);
|
|
1476
1775
|
}
|
|
1477
1776
|
}
|
|
1478
1777
|
}
|
|
@@ -1503,44 +1802,44 @@ export class ESPLoader extends EventTarget {
|
|
|
1503
1802
|
}
|
|
1504
1803
|
if (this.debug)
|
|
1505
1804
|
this.logger.debug("Read " + readBytes.length + " bytes: " + hexFormatter(readBytes));
|
|
1506
|
-
for (const
|
|
1805
|
+
for (const byte of readBytes) {
|
|
1507
1806
|
if (partialPacket === null) {
|
|
1508
1807
|
// waiting for packet header
|
|
1509
|
-
if (
|
|
1808
|
+
if (byte == this.SLIP_END) {
|
|
1510
1809
|
partialPacket = [];
|
|
1511
1810
|
}
|
|
1512
1811
|
else {
|
|
1513
1812
|
if (this.debug) {
|
|
1514
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1813
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1515
1814
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1516
1815
|
hexFormatter(this._inputBuffer));
|
|
1517
1816
|
}
|
|
1518
|
-
throw new SlipReadError("Invalid head of packet (" + toHex(
|
|
1817
|
+
throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
|
|
1519
1818
|
}
|
|
1520
1819
|
}
|
|
1521
1820
|
else if (inEscape) {
|
|
1522
1821
|
// part-way through escape sequence
|
|
1523
1822
|
inEscape = false;
|
|
1524
|
-
if (
|
|
1525
|
-
partialPacket.push(
|
|
1823
|
+
if (byte == this.SLIP_ESC_END) {
|
|
1824
|
+
partialPacket.push(this.SLIP_END);
|
|
1526
1825
|
}
|
|
1527
|
-
else if (
|
|
1528
|
-
partialPacket.push(
|
|
1826
|
+
else if (byte == this.SLIP_ESC_ESC) {
|
|
1827
|
+
partialPacket.push(this.SLIP_ESC);
|
|
1529
1828
|
}
|
|
1530
1829
|
else {
|
|
1531
1830
|
if (this.debug) {
|
|
1532
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1831
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1533
1832
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1534
1833
|
hexFormatter(this._inputBuffer));
|
|
1535
1834
|
}
|
|
1536
|
-
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(
|
|
1835
|
+
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
|
|
1537
1836
|
}
|
|
1538
1837
|
}
|
|
1539
|
-
else if (
|
|
1838
|
+
else if (byte == this.SLIP_ESC) {
|
|
1540
1839
|
// start of escape sequence
|
|
1541
1840
|
inEscape = true;
|
|
1542
1841
|
}
|
|
1543
|
-
else if (
|
|
1842
|
+
else if (byte == this.SLIP_END) {
|
|
1544
1843
|
// end of packet
|
|
1545
1844
|
if (this.debug)
|
|
1546
1845
|
this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
|
|
@@ -1550,7 +1849,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1550
1849
|
}
|
|
1551
1850
|
else {
|
|
1552
1851
|
// normal byte in packet
|
|
1553
|
-
partialPacket.push(
|
|
1852
|
+
partialPacket.push(byte);
|
|
1554
1853
|
}
|
|
1555
1854
|
}
|
|
1556
1855
|
}
|
|
@@ -1615,10 +1914,10 @@ export class ESPLoader extends EventTarget {
|
|
|
1615
1914
|
await sleep(SYNC_TIMEOUT);
|
|
1616
1915
|
// Track current baudrate for reconnect
|
|
1617
1916
|
if (this._parent) {
|
|
1618
|
-
this._parent.
|
|
1917
|
+
this._parent.currentBaudRate = baud;
|
|
1619
1918
|
}
|
|
1620
1919
|
else {
|
|
1621
|
-
this.
|
|
1920
|
+
this.currentBaudRate = baud;
|
|
1622
1921
|
}
|
|
1623
1922
|
// Warn if baudrate exceeds USB-Serial chip capability
|
|
1624
1923
|
const maxBaud = this._parent
|
|
@@ -1690,8 +1989,8 @@ export class ESPLoader extends EventTarget {
|
|
|
1690
1989
|
this.readLoop();
|
|
1691
1990
|
}
|
|
1692
1991
|
catch (e) {
|
|
1693
|
-
this.logger.error(`Reconfigure port error: ${e}`);
|
|
1694
|
-
throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
1992
|
+
// this.logger.error(`Reconfigure port error: ${e}`);
|
|
1993
|
+
// throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
1695
1994
|
}
|
|
1696
1995
|
finally {
|
|
1697
1996
|
// Always reset flag, even on error or early return
|
|
@@ -2224,19 +2523,6 @@ export class ESPLoader extends EventTarget {
|
|
|
2224
2523
|
this.__writeChain = value;
|
|
2225
2524
|
}
|
|
2226
2525
|
}
|
|
2227
|
-
get _currentBaudRate() {
|
|
2228
|
-
return this._parent
|
|
2229
|
-
? this._parent._currentBaudRate
|
|
2230
|
-
: this.__currentBaudRate;
|
|
2231
|
-
}
|
|
2232
|
-
set _currentBaudRate(value) {
|
|
2233
|
-
if (this._parent) {
|
|
2234
|
-
this._parent._currentBaudRate = value;
|
|
2235
|
-
}
|
|
2236
|
-
else {
|
|
2237
|
-
this.__currentBaudRate = value;
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
2526
|
async writeToStream(data) {
|
|
2241
2527
|
if (!this.port.writable) {
|
|
2242
2528
|
this.logger.debug("Port writable stream not available, skipping write");
|
|
@@ -2306,7 +2592,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2306
2592
|
return;
|
|
2307
2593
|
}
|
|
2308
2594
|
if (!this.port.writable) {
|
|
2309
|
-
this.logger.debug("Port already closed, skipping disconnect");
|
|
2595
|
+
// this.logger.debug("Port already closed, skipping disconnect");
|
|
2310
2596
|
return;
|
|
2311
2597
|
}
|
|
2312
2598
|
// Wait for pending writes to complete
|
|
@@ -2314,7 +2600,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2314
2600
|
await this._writeChain;
|
|
2315
2601
|
}
|
|
2316
2602
|
catch (err) {
|
|
2317
|
-
this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
2603
|
+
// this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
2318
2604
|
}
|
|
2319
2605
|
// Release persistent writer before closing
|
|
2320
2606
|
if (this._writer) {
|
|
@@ -2323,7 +2609,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2323
2609
|
this._writer.releaseLock();
|
|
2324
2610
|
}
|
|
2325
2611
|
catch (err) {
|
|
2326
|
-
this.logger.debug(`Writer close/release error: ${err}`);
|
|
2612
|
+
// this.logger.debug(`Writer close/release error: ${err}`);
|
|
2327
2613
|
}
|
|
2328
2614
|
this._writer = undefined;
|
|
2329
2615
|
}
|
|
@@ -2336,7 +2622,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2336
2622
|
writer.releaseLock();
|
|
2337
2623
|
}
|
|
2338
2624
|
catch (err) {
|
|
2339
|
-
this.logger.debug(`Direct writer close error: ${err}`);
|
|
2625
|
+
// this.logger.debug(`Direct writer close error: ${err}`);
|
|
2340
2626
|
}
|
|
2341
2627
|
}
|
|
2342
2628
|
await new Promise((resolve) => {
|
|
@@ -2358,7 +2644,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2358
2644
|
this._reader.cancel();
|
|
2359
2645
|
}
|
|
2360
2646
|
catch (err) {
|
|
2361
|
-
this.logger.debug(`Reader cancel error: ${err}`);
|
|
2647
|
+
// this.logger.debug(`Reader cancel error: ${err}`);
|
|
2362
2648
|
// Reader already released, resolve immediately
|
|
2363
2649
|
clearTimeout(timeout);
|
|
2364
2650
|
resolve(undefined);
|
|
@@ -2374,6 +2660,235 @@ export class ESPLoader extends EventTarget {
|
|
|
2374
2660
|
this.logger.debug(`Port close error: ${err}`);
|
|
2375
2661
|
}
|
|
2376
2662
|
}
|
|
2663
|
+
/**
|
|
2664
|
+
* @name releaseReaderWriter
|
|
2665
|
+
* Release reader and writer locks without closing the port
|
|
2666
|
+
* Used when switching to console mode
|
|
2667
|
+
*/
|
|
2668
|
+
async releaseReaderWriter() {
|
|
2669
|
+
if (this._parent) {
|
|
2670
|
+
await this._parent.releaseReaderWriter();
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
// Check if device is in JTAG mode and needs reset to boot into firmware
|
|
2674
|
+
const didReconnect = await this._resetToFirmwareIfNeeded();
|
|
2675
|
+
// If we reconnected for console, the reader/writer are already released and restarted
|
|
2676
|
+
if (didReconnect) {
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
// Wait for pending writes to complete
|
|
2680
|
+
try {
|
|
2681
|
+
await this._writeChain;
|
|
2682
|
+
}
|
|
2683
|
+
catch (err) {
|
|
2684
|
+
// this.logger.debug(`Pending write error during release: ${err}`);
|
|
2685
|
+
}
|
|
2686
|
+
// Release writer
|
|
2687
|
+
if (this._writer) {
|
|
2688
|
+
try {
|
|
2689
|
+
this._writer.releaseLock();
|
|
2690
|
+
this.logger.debug("Writer released");
|
|
2691
|
+
}
|
|
2692
|
+
catch (err) {
|
|
2693
|
+
this.logger.debug(`Writer release error: ${err}`);
|
|
2694
|
+
}
|
|
2695
|
+
this._writer = undefined;
|
|
2696
|
+
}
|
|
2697
|
+
// Cancel and release reader
|
|
2698
|
+
if (this._reader) {
|
|
2699
|
+
const reader = this._reader;
|
|
2700
|
+
try {
|
|
2701
|
+
// Suppress disconnect event during console mode switching
|
|
2702
|
+
this._suppressDisconnect = true;
|
|
2703
|
+
await reader.cancel();
|
|
2704
|
+
this.logger.debug("Reader cancelled");
|
|
2705
|
+
}
|
|
2706
|
+
catch (err) {
|
|
2707
|
+
this.logger.debug(`Reader cancel error: ${err}`);
|
|
2708
|
+
}
|
|
2709
|
+
finally {
|
|
2710
|
+
try {
|
|
2711
|
+
reader.releaseLock();
|
|
2712
|
+
}
|
|
2713
|
+
catch (err) {
|
|
2714
|
+
this.logger.debug(`Reader release error: ${err}`);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
if (this._reader === reader) {
|
|
2718
|
+
this._reader = undefined;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* @name resetToFirmware
|
|
2724
|
+
* Public method to reset device from bootloader to firmware for console mode
|
|
2725
|
+
* Automatically detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
2726
|
+
* @returns true if reset was performed, false if not needed
|
|
2727
|
+
*/
|
|
2728
|
+
async resetToFirmware() {
|
|
2729
|
+
return await this._resetToFirmwareIfNeeded();
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* @name detectUsbConnectionType
|
|
2733
|
+
* Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
2734
|
+
* This helper extracts the detection logic from initialize() for reuse
|
|
2735
|
+
* @returns true if USB-JTAG or USB-OTG, false if external serial chip
|
|
2736
|
+
* @throws Error if detection fails and chipFamily is not set
|
|
2737
|
+
*/
|
|
2738
|
+
async detectUsbConnectionType() {
|
|
2739
|
+
if (!this.chipFamily) {
|
|
2740
|
+
throw new Error("Cannot detect USB connection type: chipFamily not set");
|
|
2741
|
+
}
|
|
2742
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
2743
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
2744
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
2745
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
2746
|
+
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
2747
|
+
}
|
|
2748
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
|
|
2749
|
+
this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
2750
|
+
this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
2751
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
2752
|
+
return isUsingUsbJtagSerial;
|
|
2753
|
+
}
|
|
2754
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
2755
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
2756
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
2757
|
+
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
2758
|
+
}
|
|
2759
|
+
else {
|
|
2760
|
+
// Other chips don't have USB-JTAG/OTG
|
|
2761
|
+
return false;
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* @name enterConsoleMode
|
|
2766
|
+
* Prepare device for console mode by resetting to firmware
|
|
2767
|
+
* Handles both USB-JTAG/OTG devices (closes port) and external serial chips (keeps port open)
|
|
2768
|
+
* @returns true if port was closed (USB-JTAG), false if port stays open (serial chip)
|
|
2769
|
+
*/
|
|
2770
|
+
async enterConsoleMode() {
|
|
2771
|
+
// Set console mode flag
|
|
2772
|
+
this._consoleMode = true;
|
|
2773
|
+
// Re-detect USB connection type to ensure we have a definitive value
|
|
2774
|
+
// This handles cases where isUsbJtagOrOtg might be undefined
|
|
2775
|
+
let isUsbJtag;
|
|
2776
|
+
try {
|
|
2777
|
+
isUsbJtag = await this.detectUsbConnectionType();
|
|
2778
|
+
this.logger.debug(`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`);
|
|
2779
|
+
}
|
|
2780
|
+
catch (err) {
|
|
2781
|
+
// If detection fails, fall back to cached value or fail-fast
|
|
2782
|
+
if (this.isUsbJtagOrOtg === undefined) {
|
|
2783
|
+
throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
|
|
2784
|
+
}
|
|
2785
|
+
this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
|
|
2786
|
+
isUsbJtag = this.isUsbJtagOrOtg;
|
|
2787
|
+
}
|
|
2788
|
+
// Release reader/writer so console can create new ones
|
|
2789
|
+
// This is needed for Desktop (Web Serial) to unlock streams
|
|
2790
|
+
if (isUsbJtag) {
|
|
2791
|
+
// USB-JTAG/OTG devices: Use watchdog reset which closes port
|
|
2792
|
+
const wasReset = await this._resetToFirmwareIfNeeded();
|
|
2793
|
+
return wasReset; // true = port closed, caller must reopen
|
|
2794
|
+
}
|
|
2795
|
+
else {
|
|
2796
|
+
// External serial chip devices: Release locks and do simple reset
|
|
2797
|
+
try {
|
|
2798
|
+
await this.releaseReaderWriter();
|
|
2799
|
+
await this.sleep(100);
|
|
2800
|
+
}
|
|
2801
|
+
catch (err) {
|
|
2802
|
+
this.logger.debug(`Failed to release locks: ${err}`);
|
|
2803
|
+
}
|
|
2804
|
+
// Hardware reset to firmware mode (IO0=HIGH)
|
|
2805
|
+
try {
|
|
2806
|
+
await this.hardReset(false);
|
|
2807
|
+
this.logger.log("Device reset to firmware mode");
|
|
2808
|
+
}
|
|
2809
|
+
catch (err) {
|
|
2810
|
+
this.logger.debug(`Could not reset device: ${err}`);
|
|
2811
|
+
}
|
|
2812
|
+
// For WebUSB (Android), recreate streams after hardware reset
|
|
2813
|
+
if (this.isWebUSB()) {
|
|
2814
|
+
try {
|
|
2815
|
+
// Use the public recreateStreams() method to safely recreate streams
|
|
2816
|
+
// without closing the port (important after hardware reset)
|
|
2817
|
+
await this.port.recreateStreams();
|
|
2818
|
+
this.logger.debug("WebUSB streams recreated for console mode");
|
|
2819
|
+
}
|
|
2820
|
+
catch (err) {
|
|
2821
|
+
this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
return false; // Port stays open
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* @name _resetToFirmwareIfNeeded
|
|
2829
|
+
* Reset device from bootloader to firmware when switching to console mode
|
|
2830
|
+
* Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
2831
|
+
* @returns true if reconnect was performed, false otherwise
|
|
2832
|
+
*/
|
|
2833
|
+
async _resetToFirmwareIfNeeded() {
|
|
2834
|
+
try {
|
|
2835
|
+
// Check if device is using USB-JTAG/Serial or USB-OTG
|
|
2836
|
+
// Value should already be set during main() connection
|
|
2837
|
+
// Use getter to access parent's value if this is a stub
|
|
2838
|
+
const needsReset = this.isUsbJtagOrOtg === true;
|
|
2839
|
+
if (needsReset) {
|
|
2840
|
+
const resetMethod = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
2841
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3
|
|
2842
|
+
? "USB-JTAG/Serial or USB-OTG"
|
|
2843
|
+
: "USB-JTAG/Serial";
|
|
2844
|
+
this.logger.log(`Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`);
|
|
2845
|
+
// Set console mode flag before reset to prevent subsequent hardReset calls
|
|
2846
|
+
this._consoleMode = true;
|
|
2847
|
+
// For S2/S3: Clear force download boot mask before WDT reset
|
|
2848
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
2849
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
2850
|
+
const OPTION1_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
2851
|
+
? ESP32S2_RTC_CNTL_OPTION1_REG
|
|
2852
|
+
: ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
2853
|
+
const FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
2854
|
+
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
2855
|
+
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
2856
|
+
try {
|
|
2857
|
+
// Clear force download boot mode to avoid chip being stuck in download mode
|
|
2858
|
+
await this.writeRegister(OPTION1_REG, 0, FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
2859
|
+
this.logger.debug("Cleared force download boot mask");
|
|
2860
|
+
}
|
|
2861
|
+
catch (err) {
|
|
2862
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
// Perform watchdog reset to reboot into firmware
|
|
2866
|
+
try {
|
|
2867
|
+
await this.rtcWdtResetChipSpecific();
|
|
2868
|
+
this.logger.debug("Watchdog reset triggered successfully");
|
|
2869
|
+
}
|
|
2870
|
+
catch (err) {
|
|
2871
|
+
// Error is expected - device resets before responding
|
|
2872
|
+
this.logger.debug(`Watchdog reset initiated (connection lost as expected: ${err})`);
|
|
2873
|
+
}
|
|
2874
|
+
// Wait for device to fully boot into firmware
|
|
2875
|
+
this.logger.log("Waiting for device to boot into firmware...");
|
|
2876
|
+
await this.sleep(1000);
|
|
2877
|
+
// After WDT reset, streams are dead/locked - don't try to manipulate them
|
|
2878
|
+
// Just mark everything as disconnected and let browser clean up
|
|
2879
|
+
this.connected = false;
|
|
2880
|
+
this._writer = undefined;
|
|
2881
|
+
this._reader = undefined;
|
|
2882
|
+
this.logger.debug("Device reset to firmware mode (port closed)");
|
|
2883
|
+
return true;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
catch (err) {
|
|
2887
|
+
this.logger.debug(`Could not reset device to firmware mode: ${err}`);
|
|
2888
|
+
// Continue anyway - console mode might still work
|
|
2889
|
+
}
|
|
2890
|
+
return false;
|
|
2891
|
+
}
|
|
2377
2892
|
/**
|
|
2378
2893
|
* @name reconnectAndResume
|
|
2379
2894
|
* Reconnect the serial port to flush browser buffers and reload stub
|
|
@@ -2385,6 +2900,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2385
2900
|
}
|
|
2386
2901
|
try {
|
|
2387
2902
|
this.logger.log("Reconnecting serial port...");
|
|
2903
|
+
const savedBaudRate = this.currentBaudRate;
|
|
2388
2904
|
this.connected = false;
|
|
2389
2905
|
this.__inputBuffer = [];
|
|
2390
2906
|
this.__inputBufferReadIndex = 0;
|
|
@@ -2420,7 +2936,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2420
2936
|
// Close port
|
|
2421
2937
|
try {
|
|
2422
2938
|
await this.port.close();
|
|
2423
|
-
this.logger.
|
|
2939
|
+
this.logger.debug("Port closed");
|
|
2424
2940
|
}
|
|
2425
2941
|
catch (err) {
|
|
2426
2942
|
this.logger.debug(`Port close error: ${err}`);
|
|
@@ -2430,6 +2946,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2430
2946
|
try {
|
|
2431
2947
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
2432
2948
|
this.connected = true;
|
|
2949
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
2433
2950
|
}
|
|
2434
2951
|
catch (err) {
|
|
2435
2952
|
throw new Error(`Failed to open port: ${err}`);
|
|
@@ -2471,8 +2988,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2471
2988
|
const stubLoader = await this.runStub(true);
|
|
2472
2989
|
this.logger.debug("Stub loaded");
|
|
2473
2990
|
// Restore baudrate if it was changed
|
|
2474
|
-
if (
|
|
2475
|
-
await stubLoader.setBaudrate(
|
|
2991
|
+
if (savedBaudRate !== ESP_ROM_BAUD) {
|
|
2992
|
+
await stubLoader.setBaudrate(savedBaudRate);
|
|
2476
2993
|
// Verify port is still ready after baudrate change
|
|
2477
2994
|
if (!this.port.writable || !this.port.readable) {
|
|
2478
2995
|
throw new Error(`Port not ready after baudrate change (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`);
|
|
@@ -2502,6 +3019,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2502
3019
|
}
|
|
2503
3020
|
try {
|
|
2504
3021
|
this.logger.log("Reconnecting to bootloader mode...");
|
|
3022
|
+
// Clear console mode flag when reconnecting to bootloader
|
|
3023
|
+
this._consoleMode = false;
|
|
2505
3024
|
this.connected = false;
|
|
2506
3025
|
this.__inputBuffer = [];
|
|
2507
3026
|
this.__inputBufferReadIndex = 0;
|
|
@@ -2537,7 +3056,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2537
3056
|
// Close port
|
|
2538
3057
|
try {
|
|
2539
3058
|
await this.port.close();
|
|
2540
|
-
this.logger.
|
|
3059
|
+
this.logger.debug("Port closed");
|
|
2541
3060
|
}
|
|
2542
3061
|
catch (err) {
|
|
2543
3062
|
this.logger.debug(`Port close error: ${err}`);
|
|
@@ -2547,6 +3066,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2547
3066
|
try {
|
|
2548
3067
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
2549
3068
|
this.connected = true;
|
|
3069
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
2550
3070
|
}
|
|
2551
3071
|
catch (err) {
|
|
2552
3072
|
throw new Error(`Failed to open port: ${err}`);
|
|
@@ -2560,6 +3080,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2560
3080
|
// Reset chip info and stub state
|
|
2561
3081
|
this.__chipFamily = undefined;
|
|
2562
3082
|
this.chipName = "Unknown Chip";
|
|
3083
|
+
this.chipRevision = null;
|
|
3084
|
+
this.chipVariant = null;
|
|
2563
3085
|
this.IS_STUB = false;
|
|
2564
3086
|
// Start read loop
|
|
2565
3087
|
if (!this._parent) {
|
|
@@ -2582,6 +3104,98 @@ export class ESPLoader extends EventTarget {
|
|
|
2582
3104
|
throw err;
|
|
2583
3105
|
}
|
|
2584
3106
|
}
|
|
3107
|
+
/**
|
|
3108
|
+
* @name exitConsoleMode
|
|
3109
|
+
* Exit console mode and return to bootloader
|
|
3110
|
+
* For ESP32-S2, uses reconnectToBootloader which will trigger port change
|
|
3111
|
+
* @returns true if manual reconnection is needed (ESP32-S2), false otherwise
|
|
3112
|
+
*/
|
|
3113
|
+
async exitConsoleMode() {
|
|
3114
|
+
if (this._parent) {
|
|
3115
|
+
return await this._parent.exitConsoleMode();
|
|
3116
|
+
}
|
|
3117
|
+
// Clear console mode flag
|
|
3118
|
+
this._consoleMode = false;
|
|
3119
|
+
// Check if this is ESP32-S2 with USB-JTAG/OTG
|
|
3120
|
+
const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
|
|
3121
|
+
// For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
|
|
3122
|
+
// If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
|
|
3123
|
+
let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
|
|
3124
|
+
if (isESP32S2 && isUsbJtagOrOtg === undefined) {
|
|
3125
|
+
try {
|
|
3126
|
+
isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
3127
|
+
}
|
|
3128
|
+
catch (err) {
|
|
3129
|
+
this.logger.debug(`USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`);
|
|
3130
|
+
isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
if (isESP32S2 && isUsbJtagOrOtg) {
|
|
3134
|
+
// ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
|
|
3135
|
+
// This will close the port and the device will reboot to bootloader
|
|
3136
|
+
this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
|
|
3137
|
+
try {
|
|
3138
|
+
await this.reconnectToBootloader();
|
|
3139
|
+
}
|
|
3140
|
+
catch (err) {
|
|
3141
|
+
this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
|
|
3142
|
+
}
|
|
3143
|
+
// For ESP32-S2, port will change, so return true to indicate manual reconnection needed
|
|
3144
|
+
return true;
|
|
3145
|
+
}
|
|
3146
|
+
// For other devices, use standard reconnectToBootloader
|
|
3147
|
+
await this.reconnectToBootloader();
|
|
3148
|
+
return false; // No manual reconnection needed
|
|
3149
|
+
}
|
|
3150
|
+
/**
|
|
3151
|
+
* @name isConsoleResetSupported
|
|
3152
|
+
* Check if console reset is supported for this device
|
|
3153
|
+
* ESP32-S2 USB-JTAG/CDC does not support reset in console mode
|
|
3154
|
+
* because any reset causes USB port to be lost (hardware limitation)
|
|
3155
|
+
*/
|
|
3156
|
+
isConsoleResetSupported() {
|
|
3157
|
+
if (this._parent) {
|
|
3158
|
+
return this._parent.isConsoleResetSupported();
|
|
3159
|
+
}
|
|
3160
|
+
// For ESP32-S2: if _isUsbJtagOrOtg is undefined, assume USB-JTAG/OTG (conservative)
|
|
3161
|
+
// This means console reset is NOT supported (safer default)
|
|
3162
|
+
const isS2UsbJtag = this.chipFamily === CHIP_FAMILY_ESP32S2 &&
|
|
3163
|
+
(this._isUsbJtagOrOtg === true || this._isUsbJtagOrOtg === undefined);
|
|
3164
|
+
return !isS2UsbJtag; // Not supported for ESP32-S2 USB-JTAG/CDC
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* @name resetInConsoleMode
|
|
3168
|
+
* Reset device while in console mode (firmware mode)
|
|
3169
|
+
*
|
|
3170
|
+
* NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
|
|
3171
|
+
* the USB port to be lost because the device switches USB modes during reset.
|
|
3172
|
+
* This is a hardware limitation - use isConsoleResetSupported() to check first.
|
|
3173
|
+
*/
|
|
3174
|
+
async resetInConsoleMode() {
|
|
3175
|
+
if (this._parent) {
|
|
3176
|
+
return await this._parent.resetInConsoleMode();
|
|
3177
|
+
}
|
|
3178
|
+
if (!this.isConsoleResetSupported()) {
|
|
3179
|
+
this.logger.debug("Console reset not supported for ESP32-S2 USB-JTAG/CDC");
|
|
3180
|
+
return; // Do nothing
|
|
3181
|
+
}
|
|
3182
|
+
// For other devices: Use standard firmware reset
|
|
3183
|
+
const isWebUSB = this.port.isWebUSB === true;
|
|
3184
|
+
try {
|
|
3185
|
+
this.logger.debug("Resetting device in console mode");
|
|
3186
|
+
if (isWebUSB) {
|
|
3187
|
+
await this.hardResetToFirmwareWebUSB();
|
|
3188
|
+
}
|
|
3189
|
+
else {
|
|
3190
|
+
await this.hardResetToFirmware();
|
|
3191
|
+
}
|
|
3192
|
+
this.logger.debug("Device reset complete");
|
|
3193
|
+
}
|
|
3194
|
+
catch (err) {
|
|
3195
|
+
this.logger.error(`Reset failed: ${err}`);
|
|
3196
|
+
throw err;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
2585
3199
|
/**
|
|
2586
3200
|
* @name drainInputBuffer
|
|
2587
3201
|
* Actively drain the input buffer by reading data for a specified time.
|
|
@@ -2596,7 +3210,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2596
3210
|
// Wait for the buffer to fill
|
|
2597
3211
|
await sleep(bufferingTime);
|
|
2598
3212
|
// Unsupported command response is sent 8 times and has
|
|
2599
|
-
// 14 bytes length including delimiter 0xC0 bytes.
|
|
3213
|
+
// 14 bytes length including delimiter SLIP_END (0xC0) bytes.
|
|
2600
3214
|
// At least part of it is read as a command response,
|
|
2601
3215
|
// but to be safe, read it all.
|
|
2602
3216
|
const bytesToDrain = 14 * 8;
|
|
@@ -2651,9 +3265,13 @@ export class ESPLoader extends EventTarget {
|
|
|
2651
3265
|
* @param addr - Address to read from
|
|
2652
3266
|
* @param size - Number of bytes to read
|
|
2653
3267
|
* @param onPacketReceived - Optional callback function called when packet is received
|
|
3268
|
+
* @param options - Optional parameters for advanced control
|
|
3269
|
+
* - chunkSize: Amount of data to request from ESP in one command (bytes)
|
|
3270
|
+
* - blockSize: Size of each data block sent by ESP (bytes)
|
|
3271
|
+
* - maxInFlight: Maximum unacknowledged bytes (bytes)
|
|
2654
3272
|
* @returns Uint8Array containing the flash data
|
|
2655
3273
|
*/
|
|
2656
|
-
async readFlash(addr, size, onPacketReceived) {
|
|
3274
|
+
async readFlash(addr, size, onPacketReceived, options) {
|
|
2657
3275
|
if (!this.IS_STUB) {
|
|
2658
3276
|
throw new Error("Reading flash is only supported in stub mode. Please run runStub() first.");
|
|
2659
3277
|
}
|
|
@@ -2681,7 +3299,12 @@ export class ESPLoader extends EventTarget {
|
|
|
2681
3299
|
// For WebUSB (Android), use smaller chunks to avoid timeouts and buffer issues
|
|
2682
3300
|
// For Web Serial (Desktop), use larger chunks for better performance
|
|
2683
3301
|
let CHUNK_SIZE;
|
|
2684
|
-
if (
|
|
3302
|
+
if ((options === null || options === void 0 ? void 0 : options.chunkSize) !== undefined) {
|
|
3303
|
+
// Use user-provided chunkSize if in advanced mode
|
|
3304
|
+
CHUNK_SIZE = options.chunkSize;
|
|
3305
|
+
this.logger.log(`Using custom chunk size: 0x${CHUNK_SIZE.toString(16)} bytes`);
|
|
3306
|
+
}
|
|
3307
|
+
else if (this.isWebUSB()) {
|
|
2685
3308
|
// WebUSB: Use smaller chunks to avoid SLIP timeout issues
|
|
2686
3309
|
CHUNK_SIZE = 0x4 * 0x1000; // 4KB = 16384 bytes
|
|
2687
3310
|
}
|
|
@@ -2709,7 +3332,16 @@ export class ESPLoader extends EventTarget {
|
|
|
2709
3332
|
}
|
|
2710
3333
|
let blockSize;
|
|
2711
3334
|
let maxInFlight;
|
|
2712
|
-
if (
|
|
3335
|
+
if ((options === null || options === void 0 ? void 0 : options.blockSize) !== undefined &&
|
|
3336
|
+
(options === null || options === void 0 ? void 0 : options.maxInFlight) !== undefined) {
|
|
3337
|
+
// Use user-provided values if in advanced mode
|
|
3338
|
+
blockSize = options.blockSize;
|
|
3339
|
+
maxInFlight = options.maxInFlight;
|
|
3340
|
+
if (retryCount === 0) {
|
|
3341
|
+
this.logger.debug(`Using custom parameters: blockSize=${blockSize}, maxInFlight=${maxInFlight}`);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
else if (this.isWebUSB()) {
|
|
2713
3345
|
// WebUSB (Android): All devices use adaptive speed
|
|
2714
3346
|
// All have maxTransferSize=64, baseBlockSize=31
|
|
2715
3347
|
const maxTransferSize = this.port.maxTransferSize || 64;
|
|
@@ -2742,7 +3374,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2742
3374
|
// The stub expects 4 bytes (ACK), if we send less it will break out
|
|
2743
3375
|
try {
|
|
2744
3376
|
// Send SLIP frame with no data (just delimiters)
|
|
2745
|
-
const abortFrame = [
|
|
3377
|
+
const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
|
|
2746
3378
|
await this.writeToStream(abortFrame);
|
|
2747
3379
|
this.logger.debug(`Sent abort frame to stub`);
|
|
2748
3380
|
// Give stub time to process abort
|
|
@@ -2847,7 +3479,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2847
3479
|
// Check if it's a timeout error or SLIP error
|
|
2848
3480
|
if (err instanceof SlipReadError) {
|
|
2849
3481
|
if (retryCount <= MAX_RETRIES) {
|
|
2850
|
-
this.logger.
|
|
3482
|
+
this.logger.debug(`${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
|
|
2851
3483
|
try {
|
|
2852
3484
|
await this.drainInputBuffer(200);
|
|
2853
3485
|
// Clear application buffer
|