tasmota-webserial-esptool 9.2.8 → 9.2.9
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 +115 -14
- package/dist/esp_loader.d.ts +108 -2
- package/dist/esp_loader.js +633 -97
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/package.json +1 -1
- package/src/const.ts +151 -18
- package/src/esp_loader.ts +765 -117
package/src/esp_loader.ts
CHANGED
|
@@ -76,6 +76,39 @@ import {
|
|
|
76
76
|
ESP32S3_GPIO_STRAP_SPI_BOOT_MASK,
|
|
77
77
|
ESP32S3_RTC_CNTL_OPTION1_REG,
|
|
78
78
|
ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
79
|
+
ESP32S2_UARTDEV_BUF_NO,
|
|
80
|
+
ESP32S2_UARTDEV_BUF_NO_USB_OTG,
|
|
81
|
+
ESP32S3_UARTDEV_BUF_NO,
|
|
82
|
+
ESP32S3_UARTDEV_BUF_NO_USB_OTG,
|
|
83
|
+
ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
84
|
+
ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
85
|
+
ESP32C3_BUF_UART_NO_OFFSET,
|
|
86
|
+
ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG,
|
|
87
|
+
ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG,
|
|
88
|
+
ESP32C3_RTC_CNTL_WDTWPROTECT_REG,
|
|
89
|
+
ESP32C3_RTC_CNTL_WDTCONFIG0_REG,
|
|
90
|
+
ESP32C3_RTC_CNTL_WDTCONFIG1_REG,
|
|
91
|
+
ESP32C3_RTC_CNTL_WDT_WKEY,
|
|
92
|
+
ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG,
|
|
93
|
+
ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG,
|
|
94
|
+
ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG,
|
|
95
|
+
ESP32C5_C6_RTC_CNTL_WDT_WKEY,
|
|
96
|
+
ESP32C5_UARTDEV_BUF_NO,
|
|
97
|
+
ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
98
|
+
ESP32C6_UARTDEV_BUF_NO,
|
|
99
|
+
ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
100
|
+
ESP32P4_RTC_CNTL_WDTWPROTECT_REG,
|
|
101
|
+
ESP32P4_RTC_CNTL_WDTCONFIG0_REG,
|
|
102
|
+
ESP32P4_RTC_CNTL_WDTCONFIG1_REG,
|
|
103
|
+
ESP32P4_RTC_CNTL_WDT_WKEY,
|
|
104
|
+
ESP32P4_UARTDEV_BUF_NO_REV0,
|
|
105
|
+
ESP32P4_UARTDEV_BUF_NO_REV300,
|
|
106
|
+
ESP32P4_UARTDEV_BUF_NO_USB_OTG,
|
|
107
|
+
ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
108
|
+
ESP32P4_RTC_CNTL_OPTION1_REG,
|
|
109
|
+
ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
110
|
+
ESP32H2_UARTDEV_BUF_NO,
|
|
111
|
+
ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
79
112
|
} from "./const";
|
|
80
113
|
import { getStubCode } from "./stubs";
|
|
81
114
|
import { hexFormatter, sleep, slipEncode, toHex } from "./util";
|
|
@@ -116,6 +149,17 @@ export class ESPLoader extends EventTarget {
|
|
|
116
149
|
private __commandLock: Promise<[number, number[]]> = Promise.resolve([0, []]);
|
|
117
150
|
private __isReconfiguring: boolean = false;
|
|
118
151
|
private __abandonCurrentOperation: boolean = false;
|
|
152
|
+
private _suppressDisconnect: boolean = false;
|
|
153
|
+
private __consoleMode: boolean = false;
|
|
154
|
+
public _isUsbJtagOrOtg: boolean | undefined = undefined;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if device is using USB-JTAG or USB-OTG (not external serial chip)
|
|
158
|
+
* Returns undefined if not yet determined
|
|
159
|
+
*/
|
|
160
|
+
public get isUsbJtagOrOtg(): boolean | undefined {
|
|
161
|
+
return this._parent ? this._parent._isUsbJtagOrOtg : this._isUsbJtagOrOtg;
|
|
162
|
+
}
|
|
119
163
|
|
|
120
164
|
// Adaptive speed adjustment for flash read operations
|
|
121
165
|
private __adaptiveBlockMultiplier: number = 1;
|
|
@@ -182,6 +226,24 @@ export class ESPLoader extends EventTarget {
|
|
|
182
226
|
}
|
|
183
227
|
}
|
|
184
228
|
|
|
229
|
+
// Console mode with parent delegation
|
|
230
|
+
private get _consoleMode(): boolean {
|
|
231
|
+
return this._parent ? this._parent._consoleMode : this.__consoleMode;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private set _consoleMode(value: boolean) {
|
|
235
|
+
if (this._parent) {
|
|
236
|
+
this._parent._consoleMode = value;
|
|
237
|
+
} else {
|
|
238
|
+
this.__consoleMode = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Public setter for console mode (used by script.js)
|
|
243
|
+
public setConsoleMode(value: boolean): void {
|
|
244
|
+
this._consoleMode = value;
|
|
245
|
+
}
|
|
246
|
+
|
|
185
247
|
private get _inputBuffer(): number[] {
|
|
186
248
|
if (this._parent) {
|
|
187
249
|
return this._parent._inputBuffer;
|
|
@@ -457,6 +519,18 @@ export class ESPLoader extends EventTarget {
|
|
|
457
519
|
// Detect chip type
|
|
458
520
|
await this.detectChip();
|
|
459
521
|
|
|
522
|
+
// Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
523
|
+
// This is needed to determine the correct reset strategy for console mode
|
|
524
|
+
try {
|
|
525
|
+
this._isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
526
|
+
this.logger.debug(
|
|
527
|
+
`USB connection type: ${this._isUsbJtagOrOtg ? "USB-JTAG/OTG" : "External Serial Chip"}`,
|
|
528
|
+
);
|
|
529
|
+
} catch (err) {
|
|
530
|
+
this.logger.debug(`Could not detect USB connection type: ${err}`);
|
|
531
|
+
// Leave as undefined if detection fails
|
|
532
|
+
}
|
|
533
|
+
|
|
460
534
|
// Read the OTP data for this chip and store into this.efuses array
|
|
461
535
|
const FlAddr = getSpiFlashAddresses(this.getChipFamily());
|
|
462
536
|
const AddrMAC = FlAddr.macFuse;
|
|
@@ -486,7 +560,7 @@ export class ESPLoader extends EventTarget {
|
|
|
486
560
|
this.chipName = chipInfo.name;
|
|
487
561
|
this.chipFamily = chipInfo.family;
|
|
488
562
|
|
|
489
|
-
// Get chip revision for ESP32-P4
|
|
563
|
+
// Get chip revision for ESP32-P4 and ESP32-C3
|
|
490
564
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
491
565
|
this.chipRevision = await this.getChipRevision();
|
|
492
566
|
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
@@ -498,6 +572,9 @@ export class ESPLoader extends EventTarget {
|
|
|
498
572
|
this.chipVariant = "rev0";
|
|
499
573
|
}
|
|
500
574
|
this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
|
|
575
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
576
|
+
this.chipRevision = await this.getChipRevision();
|
|
577
|
+
this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
|
|
501
578
|
}
|
|
502
579
|
|
|
503
580
|
this.logger.debug(
|
|
@@ -549,7 +626,6 @@ export class ESPLoader extends EventTarget {
|
|
|
549
626
|
|
|
550
627
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
551
628
|
this.chipRevision = await this.getChipRevision();
|
|
552
|
-
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
553
629
|
|
|
554
630
|
if (this.chipRevision >= 300) {
|
|
555
631
|
this.chipVariant = "rev300";
|
|
@@ -557,6 +633,8 @@ export class ESPLoader extends EventTarget {
|
|
|
557
633
|
this.chipVariant = "rev0";
|
|
558
634
|
}
|
|
559
635
|
this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
|
|
636
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
637
|
+
this.chipRevision = await this.getChipRevision();
|
|
560
638
|
}
|
|
561
639
|
|
|
562
640
|
this.logger.debug(
|
|
@@ -568,22 +646,24 @@ export class ESPLoader extends EventTarget {
|
|
|
568
646
|
* Get chip revision for ESP32-P4
|
|
569
647
|
*/
|
|
570
648
|
async getChipRevision(): Promise<number> {
|
|
571
|
-
if (this.chipFamily
|
|
572
|
-
|
|
573
|
-
|
|
649
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
650
|
+
// Read from EFUSE_BLOCK1 to get chip revision
|
|
651
|
+
// Word 2 contains revision info for ESP32-P4
|
|
652
|
+
const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
|
|
574
653
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
|
|
654
|
+
// Minor revision: bits [3:0]
|
|
655
|
+
const minorRev = word2 & 0x0f;
|
|
578
656
|
|
|
579
|
-
|
|
580
|
-
|
|
657
|
+
// Major revision: bits [23] << 2 | bits [5:4]
|
|
658
|
+
const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
|
|
581
659
|
|
|
582
|
-
|
|
583
|
-
|
|
660
|
+
// Revision is major * 100 + minor
|
|
661
|
+
return majorRev * 100 + minorRev;
|
|
662
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
663
|
+
return await this.getChipRevisionC3();
|
|
664
|
+
}
|
|
584
665
|
|
|
585
|
-
|
|
586
|
-
return majorRev * 100 + minorRev;
|
|
666
|
+
return 0;
|
|
587
667
|
}
|
|
588
668
|
|
|
589
669
|
/**
|
|
@@ -684,12 +764,26 @@ export class ESPLoader extends EventTarget {
|
|
|
684
764
|
this._totalBytesRead += value.length;
|
|
685
765
|
}
|
|
686
766
|
} catch {
|
|
687
|
-
|
|
767
|
+
// Don't log error if this is an expected disconnect during console mode transition
|
|
768
|
+
if (!this._consoleMode) {
|
|
769
|
+
this.logger.error("Read loop got disconnected");
|
|
770
|
+
}
|
|
688
771
|
} finally {
|
|
689
772
|
// Always reset reconfiguring flag when read loop ends
|
|
690
773
|
// This prevents "Cannot write during port reconfiguration" errors
|
|
691
774
|
// when the read loop dies unexpectedly
|
|
692
775
|
this._isReconfiguring = false;
|
|
776
|
+
|
|
777
|
+
// Release reader if still locked
|
|
778
|
+
if (this._reader) {
|
|
779
|
+
try {
|
|
780
|
+
this._reader.releaseLock();
|
|
781
|
+
this.logger.debug("Reader released in readLoop cleanup");
|
|
782
|
+
} catch (err) {
|
|
783
|
+
this.logger.debug(`Reader release error in readLoop: ${err}`);
|
|
784
|
+
}
|
|
785
|
+
this._reader = undefined;
|
|
786
|
+
}
|
|
693
787
|
}
|
|
694
788
|
|
|
695
789
|
// Disconnected!
|
|
@@ -708,7 +802,11 @@ export class ESPLoader extends EventTarget {
|
|
|
708
802
|
);
|
|
709
803
|
}
|
|
710
804
|
|
|
711
|
-
|
|
805
|
+
// Only dispatch disconnect event if not suppressed
|
|
806
|
+
if (!this._suppressDisconnect) {
|
|
807
|
+
this.dispatchEvent(new Event("disconnect"));
|
|
808
|
+
}
|
|
809
|
+
this._suppressDisconnect = false;
|
|
712
810
|
this.logger.debug("Finished read loop");
|
|
713
811
|
}
|
|
714
812
|
|
|
@@ -786,6 +884,34 @@ export class ESPLoader extends EventTarget {
|
|
|
786
884
|
await this.sleep(200);
|
|
787
885
|
}
|
|
788
886
|
|
|
887
|
+
/**
|
|
888
|
+
* Reset to firmware mode (not bootloader) for Web Serial
|
|
889
|
+
* Keeps IO0=HIGH during reset so chip boots into firmware
|
|
890
|
+
*/
|
|
891
|
+
async hardResetToFirmware() {
|
|
892
|
+
await this.setDTR(false); // IO0=HIGH
|
|
893
|
+
await this.setRTS(true); // EN=LOW, chip in reset
|
|
894
|
+
await this.sleep(100);
|
|
895
|
+
await this.setRTS(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
|
|
896
|
+
await this.sleep(50);
|
|
897
|
+
|
|
898
|
+
await this.sleep(200);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Reset to firmware mode (not bootloader) for WebUSB
|
|
903
|
+
* Keeps IO0=HIGH during reset so chip boots into firmware
|
|
904
|
+
*/
|
|
905
|
+
async hardResetToFirmwareWebUSB() {
|
|
906
|
+
await this.setDTRWebUSB(false); // IO0=HIGH
|
|
907
|
+
await this.setRTSWebUSB(true); // EN=LOW, chip in reset
|
|
908
|
+
await this.sleep(100);
|
|
909
|
+
await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
|
|
910
|
+
await this.sleep(50);
|
|
911
|
+
|
|
912
|
+
await this.sleep(200);
|
|
913
|
+
}
|
|
914
|
+
|
|
789
915
|
/**
|
|
790
916
|
* @name hardResetUnixTight
|
|
791
917
|
* Unix Tight reset for Web Serial (Desktop) - sets DTR and RTS simultaneously
|
|
@@ -1284,7 +1410,9 @@ export class ESPLoader extends EventTarget {
|
|
|
1284
1410
|
try {
|
|
1285
1411
|
// Check if port is still open, if not, skip this strategy
|
|
1286
1412
|
if (!this.connected || !this.port.writable) {
|
|
1287
|
-
this.logger.
|
|
1413
|
+
this.logger.debug(
|
|
1414
|
+
`Port disconnected, skipping ${strategy.name} reset`,
|
|
1415
|
+
);
|
|
1288
1416
|
continue;
|
|
1289
1417
|
}
|
|
1290
1418
|
|
|
@@ -1334,7 +1462,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1334
1462
|
}
|
|
1335
1463
|
} catch (error) {
|
|
1336
1464
|
lastError = error as Error;
|
|
1337
|
-
this.logger.
|
|
1465
|
+
this.logger.debug(
|
|
1338
1466
|
`${strategy.name} reset failed: ${(error as Error).message}`,
|
|
1339
1467
|
);
|
|
1340
1468
|
|
|
@@ -1367,13 +1495,140 @@ export class ESPLoader extends EventTarget {
|
|
|
1367
1495
|
|
|
1368
1496
|
/**
|
|
1369
1497
|
* @name watchdogReset
|
|
1370
|
-
* Watchdog reset for ESP32-S2/S3 with USB-OTG
|
|
1498
|
+
* Watchdog reset for ESP32-S2/S3/C3 with USB-OTG or USB-JTAG/Serial
|
|
1371
1499
|
* Uses RTC watchdog timer to reset the chip - works when DTR/RTS signals are not available
|
|
1500
|
+
* This is an alias for rtcWdtResetChipSpecific() for backwards compatibility
|
|
1372
1501
|
*/
|
|
1373
1502
|
async watchdogReset() {
|
|
1374
|
-
this.
|
|
1503
|
+
await this.rtcWdtResetChipSpecific();
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* Check if current chip is using USB-OTG
|
|
1508
|
+
* Supports ESP32-S2 and ESP32-S3
|
|
1509
|
+
*/
|
|
1510
|
+
public async usingUsbOtg(): Promise<boolean> {
|
|
1511
|
+
let uartDevBufNo: number;
|
|
1512
|
+
let usbOtgValue: number;
|
|
1513
|
+
|
|
1514
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1515
|
+
uartDevBufNo = ESP32S2_UARTDEV_BUF_NO;
|
|
1516
|
+
usbOtgValue = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
|
|
1517
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1518
|
+
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1519
|
+
usbOtgValue = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
|
|
1520
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1521
|
+
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1522
|
+
if (this.chipRevision === null) {
|
|
1523
|
+
this.chipRevision = await this.getChipRevision();
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (this.chipRevision < 300) {
|
|
1527
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1528
|
+
} else {
|
|
1529
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1530
|
+
}
|
|
1531
|
+
usbOtgValue = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
|
|
1532
|
+
} else {
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1537
|
+
return uartNo === usbOtgValue;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/**
|
|
1541
|
+
* Check if current chip is using USB-JTAG/Serial
|
|
1542
|
+
* Supports ESP32-S3 and ESP32-C3
|
|
1543
|
+
*/
|
|
1544
|
+
public async usingUsbJtagSerial(): Promise<boolean> {
|
|
1545
|
+
let uartDevBufNo: number;
|
|
1546
|
+
let usbJtagSerialValue: number;
|
|
1547
|
+
|
|
1548
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1549
|
+
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1550
|
+
usbJtagSerialValue = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1551
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1552
|
+
// ESP32-C3: BSS_UART_DEV_ADDR depends on chip revision
|
|
1553
|
+
// Revision < 101: 0x3FCDF064
|
|
1554
|
+
// Revision >= 101: 0x3FCDF060
|
|
1555
|
+
let bssUartDevAddr: number;
|
|
1556
|
+
|
|
1557
|
+
// Get chip revision if not already set
|
|
1558
|
+
if (this.chipRevision === null) {
|
|
1559
|
+
this.chipRevision = await this.getChipRevisionC3();
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (this.chipRevision < 101) {
|
|
1563
|
+
bssUartDevAddr = 0x3fcdf064;
|
|
1564
|
+
} else {
|
|
1565
|
+
bssUartDevAddr = 0x3fcdf060;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
uartDevBufNo = bssUartDevAddr + ESP32C3_BUF_UART_NO_OFFSET;
|
|
1569
|
+
usbJtagSerialValue = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1570
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
|
|
1571
|
+
uartDevBufNo = ESP32C5_UARTDEV_BUF_NO;
|
|
1572
|
+
usbJtagSerialValue = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1573
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
1574
|
+
uartDevBufNo = ESP32C6_UARTDEV_BUF_NO;
|
|
1575
|
+
usbJtagSerialValue = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1576
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1577
|
+
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1578
|
+
// Revision < 300: 0x4FF3FEC8
|
|
1579
|
+
// Revision >= 300: 0x4FFBFEC8
|
|
1580
|
+
if (this.chipRevision === null) {
|
|
1581
|
+
this.chipRevision = await this.getChipRevision();
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
if (this.chipRevision < 300) {
|
|
1585
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1586
|
+
} else {
|
|
1587
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1588
|
+
}
|
|
1589
|
+
usbJtagSerialValue = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1590
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32H2) {
|
|
1591
|
+
uartDevBufNo = ESP32H2_UARTDEV_BUF_NO;
|
|
1592
|
+
usbJtagSerialValue = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1593
|
+
} else {
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1598
|
+
return uartNo === usbJtagSerialValue;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* Get chip revision for ESP32-C3
|
|
1603
|
+
* Reads from EFUSE registers and calculates revision
|
|
1604
|
+
*/
|
|
1605
|
+
async getChipRevisionC3(): Promise<number> {
|
|
1606
|
+
if (this.chipFamily !== CHIP_FAMILY_ESP32C3) {
|
|
1607
|
+
return 0;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Read EFUSE_RD_MAC_SPI_SYS_3_REG (bits [20:18] = lower 3 bits of revision)
|
|
1611
|
+
const word3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
|
|
1612
|
+
const low = (word3 >> 18) & 0x07;
|
|
1613
|
+
|
|
1614
|
+
// Read EFUSE_RD_MAC_SPI_SYS_5_REG (bits [25:23] = upper 3 bits of revision)
|
|
1615
|
+
const word5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
|
|
1616
|
+
const hi = (word5 >> 23) & 0x07;
|
|
1617
|
+
|
|
1618
|
+
// Combine: upper 3 bits from word5, lower 3 bits from word3
|
|
1619
|
+
const revision = (hi << 3) | low;
|
|
1620
|
+
|
|
1621
|
+
return revision;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
/**
|
|
1625
|
+
* RTC watchdog timer reset for ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, and ESP32-P4
|
|
1626
|
+
* Uses specific registers for each chip family
|
|
1627
|
+
* Note: ESP32-H2 does NOT support WDT reset
|
|
1628
|
+
*/
|
|
1629
|
+
public async rtcWdtResetChipSpecific(): Promise<void> {
|
|
1630
|
+
this.logger.debug("Hard resetting with watchdog timer...");
|
|
1375
1631
|
|
|
1376
|
-
// Select correct register addresses based on chip family
|
|
1377
1632
|
let WDTWPROTECT_REG: number;
|
|
1378
1633
|
let WDTCONFIG0_REG: number;
|
|
1379
1634
|
let WDTCONFIG1_REG: number;
|
|
@@ -1389,16 +1644,82 @@ export class ESPLoader extends EventTarget {
|
|
|
1389
1644
|
WDTCONFIG0_REG = ESP32S3_RTC_CNTL_WDTCONFIG0_REG;
|
|
1390
1645
|
WDTCONFIG1_REG = ESP32S3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1391
1646
|
WDT_WKEY = ESP32S3_RTC_CNTL_WDT_WKEY;
|
|
1647
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1648
|
+
WDTWPROTECT_REG = ESP32C3_RTC_CNTL_WDTWPROTECT_REG;
|
|
1649
|
+
WDTCONFIG0_REG = ESP32C3_RTC_CNTL_WDTCONFIG0_REG;
|
|
1650
|
+
WDTCONFIG1_REG = ESP32C3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1651
|
+
WDT_WKEY = ESP32C3_RTC_CNTL_WDT_WKEY;
|
|
1652
|
+
} else if (
|
|
1653
|
+
this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
1654
|
+
this.chipFamily === CHIP_FAMILY_ESP32C6
|
|
1655
|
+
) {
|
|
1656
|
+
// C5 and C6 use LP_WDT (Low Power Watchdog Timer)
|
|
1657
|
+
WDTWPROTECT_REG = ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG;
|
|
1658
|
+
WDTCONFIG0_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG;
|
|
1659
|
+
WDTCONFIG1_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG;
|
|
1660
|
+
WDT_WKEY = ESP32C5_C6_RTC_CNTL_WDT_WKEY;
|
|
1661
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1662
|
+
// P4 uses LP_WDT (Low Power Watchdog Timer)
|
|
1663
|
+
WDTWPROTECT_REG = ESP32P4_RTC_CNTL_WDTWPROTECT_REG;
|
|
1664
|
+
WDTCONFIG0_REG = ESP32P4_RTC_CNTL_WDTCONFIG0_REG;
|
|
1665
|
+
WDTCONFIG1_REG = ESP32P4_RTC_CNTL_WDTCONFIG1_REG;
|
|
1666
|
+
WDT_WKEY = ESP32P4_RTC_CNTL_WDT_WKEY;
|
|
1392
1667
|
} else {
|
|
1393
1668
|
throw new Error(
|
|
1394
|
-
`
|
|
1669
|
+
`rtcWdtResetChipSpecific() is not supported for ${this.chipFamily}`,
|
|
1395
1670
|
);
|
|
1396
1671
|
}
|
|
1397
1672
|
|
|
1398
1673
|
// Unlock watchdog registers
|
|
1399
1674
|
await this.writeRegister(WDTWPROTECT_REG, WDT_WKEY, undefined, 0);
|
|
1400
1675
|
|
|
1401
|
-
//
|
|
1676
|
+
// Clear force download boot register (if applicable) BEFORE triggering WDT reset
|
|
1677
|
+
// This ensures the chip boots into firmware mode after reset
|
|
1678
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1679
|
+
try {
|
|
1680
|
+
await this.writeRegister(
|
|
1681
|
+
ESP32S2_RTC_CNTL_OPTION1_REG,
|
|
1682
|
+
0,
|
|
1683
|
+
ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1684
|
+
0,
|
|
1685
|
+
);
|
|
1686
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1687
|
+
} catch (err) {
|
|
1688
|
+
this.logger.debug(
|
|
1689
|
+
`Expected error clearing force download boot mask: ${err}`,
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1693
|
+
try {
|
|
1694
|
+
await this.writeRegister(
|
|
1695
|
+
ESP32S3_RTC_CNTL_OPTION1_REG,
|
|
1696
|
+
0,
|
|
1697
|
+
ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1698
|
+
0,
|
|
1699
|
+
);
|
|
1700
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
this.logger.debug(
|
|
1703
|
+
`Expected error clearing force download boot mask: ${err}`,
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1707
|
+
try {
|
|
1708
|
+
await this.writeRegister(
|
|
1709
|
+
ESP32P4_RTC_CNTL_OPTION1_REG,
|
|
1710
|
+
0,
|
|
1711
|
+
ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1712
|
+
0,
|
|
1713
|
+
);
|
|
1714
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1715
|
+
} catch (err) {
|
|
1716
|
+
this.logger.debug(
|
|
1717
|
+
`Expected error clearing force download boot mask: ${err}`,
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// Set WDT timeout to 2000ms (matches Python esptool)
|
|
1402
1723
|
await this.writeRegister(WDTCONFIG1_REG, 2000, undefined, 0);
|
|
1403
1724
|
|
|
1404
1725
|
// Enable WDT: bit 31 = enable, bits 28-30 = stage, bit 8 = sys reset, bits 0-2 = prescaler
|
|
@@ -1412,120 +1733,162 @@ export class ESPLoader extends EventTarget {
|
|
|
1412
1733
|
await this.sleep(500);
|
|
1413
1734
|
}
|
|
1414
1735
|
|
|
1736
|
+
/**
|
|
1737
|
+
* Helper: Check if USB-based WDT reset should be used for S2/S3
|
|
1738
|
+
* Returns true if WDT reset was performed, false otherwise
|
|
1739
|
+
*/
|
|
1740
|
+
private async tryUsbWdtReset(
|
|
1741
|
+
chipName: string,
|
|
1742
|
+
GPIO_STRAP_REG: number,
|
|
1743
|
+
GPIO_STRAP_SPI_BOOT_MASK: number,
|
|
1744
|
+
RTC_CNTL_OPTION1_REG: number,
|
|
1745
|
+
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK: number,
|
|
1746
|
+
): Promise<boolean> {
|
|
1747
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1748
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1749
|
+
|
|
1750
|
+
if (isUsingUsbOtg || isUsingUsbJtagSerial) {
|
|
1751
|
+
const strapReg = await this.readRegister(GPIO_STRAP_REG);
|
|
1752
|
+
const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
|
|
1753
|
+
|
|
1754
|
+
// Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
|
|
1755
|
+
if (
|
|
1756
|
+
(strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
|
|
1757
|
+
(forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0
|
|
1758
|
+
) {
|
|
1759
|
+
await this.rtcWdtResetChipSpecific();
|
|
1760
|
+
this.logger.debug(
|
|
1761
|
+
`${chipName}: RTC WDT reset (USB detected, GPIO0 low)`,
|
|
1762
|
+
);
|
|
1763
|
+
return true;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
return false;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Chip-specific hard reset for ESP32-S2
|
|
1771
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1772
|
+
*/
|
|
1773
|
+
public async hardResetS2(): Promise<void> {
|
|
1774
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1775
|
+
if (isUsingUsbOtg) {
|
|
1776
|
+
await this.rtcWdtResetChipSpecific();
|
|
1777
|
+
this.logger.debug("ESP32-S2: RTC WDT reset (USB-OTG detected)");
|
|
1778
|
+
} else {
|
|
1779
|
+
// Use standard hardware reset
|
|
1780
|
+
await this.hardResetClassic();
|
|
1781
|
+
this.logger.debug("ESP32-S2: Classic reset");
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* Chip-specific hard reset for ESP32-S3
|
|
1787
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1788
|
+
*/
|
|
1789
|
+
public async hardResetS3(): Promise<void> {
|
|
1790
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1791
|
+
if (isUsingUsbJtagSerial) {
|
|
1792
|
+
await this.rtcWdtResetChipSpecific();
|
|
1793
|
+
this.logger.debug("ESP32-S3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1794
|
+
} else {
|
|
1795
|
+
// Use standard hardware reset
|
|
1796
|
+
await this.hardResetClassic();
|
|
1797
|
+
this.logger.debug("ESP32-S3: Classic reset");
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
/**
|
|
1802
|
+
* Chip-specific hard reset for ESP32-C3
|
|
1803
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1804
|
+
*/
|
|
1805
|
+
public async hardResetC3(): Promise<void> {
|
|
1806
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1807
|
+
if (isUsingUsbJtagSerial) {
|
|
1808
|
+
await this.rtcWdtResetChipSpecific();
|
|
1809
|
+
this.logger.debug("ESP32-C3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1810
|
+
} else {
|
|
1811
|
+
// Use standard hardware reset
|
|
1812
|
+
await this.hardResetClassic();
|
|
1813
|
+
this.logger.debug("ESP32-C3: Classic reset");
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1415
1817
|
async hardReset(bootloader = false) {
|
|
1818
|
+
// In console mode, only allow simple hardware reset (no bootloader entry)
|
|
1819
|
+
if (this._consoleMode) {
|
|
1820
|
+
if (bootloader) {
|
|
1821
|
+
this.logger.debug(
|
|
1822
|
+
"Skipping bootloader reset - device is in console mode",
|
|
1823
|
+
);
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
// Simple hardware reset to restart firmware (IO0=HIGH)
|
|
1827
|
+
this.logger.debug("Performing hardware reset (console mode)...");
|
|
1828
|
+
if (this.isWebUSB()) {
|
|
1829
|
+
await this.hardResetToFirmwareWebUSB();
|
|
1830
|
+
} else {
|
|
1831
|
+
await this.hardResetToFirmware();
|
|
1832
|
+
}
|
|
1833
|
+
this.logger.debug("Hardware reset complete");
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1416
1837
|
if (bootloader) {
|
|
1417
1838
|
// enter flash mode
|
|
1418
1839
|
if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
|
|
1419
1840
|
await this.hardResetUSBJTAGSerial();
|
|
1420
|
-
this.logger.
|
|
1841
|
+
this.logger.debug("USB-JTAG/Serial reset.");
|
|
1421
1842
|
} else {
|
|
1422
1843
|
// Use different reset strategy for WebUSB (Android) vs Web Serial (Desktop)
|
|
1423
1844
|
if (this.isWebUSB()) {
|
|
1424
1845
|
await this.hardResetClassicWebUSB();
|
|
1425
|
-
this.logger.
|
|
1846
|
+
this.logger.debug("Classic reset (WebUSB/Android).");
|
|
1426
1847
|
} else {
|
|
1427
1848
|
await this.hardResetClassic();
|
|
1428
|
-
this.logger.
|
|
1849
|
+
this.logger.debug("Classic reset.");
|
|
1429
1850
|
}
|
|
1430
1851
|
}
|
|
1431
1852
|
} else {
|
|
1432
1853
|
// just reset (no bootloader mode)
|
|
1433
|
-
// For ESP32-S2/S3 with USB-OTG, check if watchdog reset is needed
|
|
1434
|
-
if (
|
|
1435
|
-
this.
|
|
1436
|
-
|
|
1437
|
-
|
|
1854
|
+
// For ESP32-S2/S3 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
|
|
1855
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2 && !this._consoleMode) {
|
|
1856
|
+
const wdtResetUsed = await this.tryUsbWdtReset(
|
|
1857
|
+
"ESP32-S2",
|
|
1858
|
+
ESP32S2_GPIO_STRAP_REG,
|
|
1859
|
+
ESP32S2_GPIO_STRAP_SPI_BOOT_MASK,
|
|
1860
|
+
ESP32S2_RTC_CNTL_OPTION1_REG,
|
|
1861
|
+
ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1862
|
+
);
|
|
1863
|
+
if (wdtResetUsed) return;
|
|
1864
|
+
} else if (
|
|
1865
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3 &&
|
|
1866
|
+
!this._consoleMode
|
|
1438
1867
|
) {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK =
|
|
1449
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1450
|
-
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
1451
|
-
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
1452
|
-
|
|
1453
|
-
await this.writeRegister(
|
|
1454
|
-
RTC_CNTL_OPTION1_REG,
|
|
1455
|
-
0,
|
|
1456
|
-
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1457
|
-
0,
|
|
1458
|
-
);
|
|
1459
|
-
} catch (e) {
|
|
1460
|
-
// Skip invalid response and continue reset (can happen when monitoring during reset)
|
|
1461
|
-
this.logger.log(
|
|
1462
|
-
"Warning: Could not clear force download boot mode:",
|
|
1463
|
-
e,
|
|
1464
|
-
);
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// Check the strapping register to see if we can perform a watchdog reset
|
|
1468
|
-
// Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
|
|
1469
|
-
let useWatchdogReset = false;
|
|
1470
|
-
try {
|
|
1471
|
-
const GPIO_STRAP_REG =
|
|
1472
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1473
|
-
? ESP32S2_GPIO_STRAP_REG
|
|
1474
|
-
: ESP32S3_GPIO_STRAP_REG;
|
|
1475
|
-
const GPIO_STRAP_SPI_BOOT_MASK =
|
|
1476
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1477
|
-
? ESP32S2_GPIO_STRAP_SPI_BOOT_MASK
|
|
1478
|
-
: ESP32S3_GPIO_STRAP_SPI_BOOT_MASK;
|
|
1479
|
-
const RTC_CNTL_OPTION1_REG =
|
|
1480
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1481
|
-
? ESP32S2_RTC_CNTL_OPTION1_REG
|
|
1482
|
-
: ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
1483
|
-
const RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK =
|
|
1484
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
1485
|
-
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
1486
|
-
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
1487
|
-
|
|
1488
|
-
const strapReg = await this.readRegister(GPIO_STRAP_REG);
|
|
1489
|
-
const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
|
|
1490
|
-
|
|
1491
|
-
// GPIO0 low (download mode) AND force download boot not set
|
|
1492
|
-
if (
|
|
1493
|
-
(strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
|
|
1494
|
-
(forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0
|
|
1495
|
-
) {
|
|
1496
|
-
useWatchdogReset = true;
|
|
1497
|
-
}
|
|
1498
|
-
} catch (e) {
|
|
1499
|
-
// If we can't read the registers, use watchdog reset as fallback
|
|
1500
|
-
this.logger.log(
|
|
1501
|
-
"Warning: Could not read strap/option registers, using watchdog reset:",
|
|
1502
|
-
e,
|
|
1503
|
-
);
|
|
1504
|
-
useWatchdogReset = true;
|
|
1505
|
-
}
|
|
1868
|
+
const wdtResetUsed = await this.tryUsbWdtReset(
|
|
1869
|
+
"ESP32-S3",
|
|
1870
|
+
ESP32S3_GPIO_STRAP_REG,
|
|
1871
|
+
ESP32S3_GPIO_STRAP_SPI_BOOT_MASK,
|
|
1872
|
+
ESP32S3_RTC_CNTL_OPTION1_REG,
|
|
1873
|
+
ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1874
|
+
);
|
|
1875
|
+
if (wdtResetUsed) return;
|
|
1876
|
+
}
|
|
1506
1877
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
this.logger.log("Watchdog reset (USB-OTG).");
|
|
1510
|
-
} else {
|
|
1511
|
-
// Not in download mode, can use DTR/RTS reset
|
|
1512
|
-
// But USB-OTG doesn't have DTR/RTS, so fall back to watchdog
|
|
1513
|
-
await this.watchdogReset();
|
|
1514
|
-
this.logger.log("Watchdog reset (USB-OTG, normal boot).");
|
|
1515
|
-
}
|
|
1516
|
-
} else if (this.isWebUSB()) {
|
|
1878
|
+
// Standard reset for all other cases
|
|
1879
|
+
if (this.isWebUSB()) {
|
|
1517
1880
|
// WebUSB: Use longer delays for better compatibility
|
|
1518
1881
|
await this.setRTSWebUSB(true); // EN->LOW
|
|
1519
1882
|
await this.sleep(200);
|
|
1520
1883
|
await this.setRTSWebUSB(false);
|
|
1521
1884
|
await this.sleep(200);
|
|
1522
|
-
this.logger.
|
|
1885
|
+
this.logger.debug("Hard reset (WebUSB).");
|
|
1523
1886
|
} else {
|
|
1524
1887
|
// Web Serial: Standard reset
|
|
1525
1888
|
await this.setRTS(true); // EN->LOW
|
|
1526
1889
|
await this.sleep(100);
|
|
1527
1890
|
await this.setRTS(false);
|
|
1528
|
-
this.logger.
|
|
1891
|
+
this.logger.debug("Hard reset.");
|
|
1529
1892
|
}
|
|
1530
1893
|
}
|
|
1531
1894
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2942,6 +3305,256 @@ export class ESPLoader extends EventTarget {
|
|
|
2942
3305
|
}
|
|
2943
3306
|
}
|
|
2944
3307
|
|
|
3308
|
+
/**
|
|
3309
|
+
* @name releaseReaderWriter
|
|
3310
|
+
* Release reader and writer locks without closing the port
|
|
3311
|
+
* Used when switching to console mode
|
|
3312
|
+
*/
|
|
3313
|
+
async releaseReaderWriter() {
|
|
3314
|
+
if (this._parent) {
|
|
3315
|
+
await this._parent.releaseReaderWriter();
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
// Check if device is in JTAG mode and needs reset to boot into firmware
|
|
3320
|
+
const didReconnect = await this._resetToFirmwareIfNeeded();
|
|
3321
|
+
|
|
3322
|
+
// If we reconnected for console, the reader/writer are already released and restarted
|
|
3323
|
+
if (didReconnect) {
|
|
3324
|
+
return;
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// Wait for pending writes to complete
|
|
3328
|
+
try {
|
|
3329
|
+
await this._writeChain;
|
|
3330
|
+
} catch (err) {
|
|
3331
|
+
this.logger.debug(`Pending write error during release: ${err}`);
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
// Release writer
|
|
3335
|
+
if (this._writer) {
|
|
3336
|
+
try {
|
|
3337
|
+
this._writer.releaseLock();
|
|
3338
|
+
this.logger.debug("Writer released");
|
|
3339
|
+
} catch (err) {
|
|
3340
|
+
this.logger.debug(`Writer release error: ${err}`);
|
|
3341
|
+
}
|
|
3342
|
+
this._writer = undefined;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// Cancel and release reader
|
|
3346
|
+
if (this._reader) {
|
|
3347
|
+
const reader = this._reader;
|
|
3348
|
+
try {
|
|
3349
|
+
// Suppress disconnect event during console mode switching
|
|
3350
|
+
this._suppressDisconnect = true;
|
|
3351
|
+
await reader.cancel();
|
|
3352
|
+
this.logger.debug("Reader cancelled");
|
|
3353
|
+
} catch (err) {
|
|
3354
|
+
this.logger.debug(`Reader cancel error: ${err}`);
|
|
3355
|
+
} finally {
|
|
3356
|
+
try {
|
|
3357
|
+
reader.releaseLock();
|
|
3358
|
+
} catch (err) {
|
|
3359
|
+
this.logger.debug(`Reader release error: ${err}`);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
if (this._reader === reader) {
|
|
3363
|
+
this._reader = undefined;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
/**
|
|
3369
|
+
* @name resetToFirmware
|
|
3370
|
+
* Public method to reset device from bootloader to firmware for console mode
|
|
3371
|
+
* Automatically detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
3372
|
+
* @returns true if reset was performed, false if not needed
|
|
3373
|
+
*/
|
|
3374
|
+
public async resetToFirmware(): Promise<boolean> {
|
|
3375
|
+
return await this._resetToFirmwareIfNeeded();
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
/**
|
|
3379
|
+
* @name detectUsbConnectionType
|
|
3380
|
+
* Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
3381
|
+
* This helper extracts the detection logic from initialize() for reuse
|
|
3382
|
+
* @returns true if USB-JTAG or USB-OTG, false if external serial chip
|
|
3383
|
+
* @throws Error if detection fails and chipFamily is not set
|
|
3384
|
+
*/
|
|
3385
|
+
private async detectUsbConnectionType(): Promise<boolean> {
|
|
3386
|
+
if (!this.chipFamily) {
|
|
3387
|
+
throw new Error("Cannot detect USB connection type: chipFamily not set");
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
if (
|
|
3391
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
3392
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3
|
|
3393
|
+
) {
|
|
3394
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
3395
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
3396
|
+
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
3397
|
+
} else if (
|
|
3398
|
+
this.chipFamily === CHIP_FAMILY_ESP32C3 ||
|
|
3399
|
+
this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
3400
|
+
this.chipFamily === CHIP_FAMILY_ESP32C6
|
|
3401
|
+
) {
|
|
3402
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
3403
|
+
return isUsingUsbJtagSerial;
|
|
3404
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
3405
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
3406
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
3407
|
+
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
3408
|
+
} else {
|
|
3409
|
+
// Other chips don't have USB-JTAG/OTG
|
|
3410
|
+
return false;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
/**
|
|
3415
|
+
* @name enterConsoleMode
|
|
3416
|
+
* Prepare device for console mode by resetting to firmware
|
|
3417
|
+
* Handles both USB-JTAG/OTG devices (closes port) and external serial chips (keeps port open)
|
|
3418
|
+
* @returns true if port was closed (USB-JTAG), false if port stays open (serial chip)
|
|
3419
|
+
*/
|
|
3420
|
+
public async enterConsoleMode(): Promise<boolean> {
|
|
3421
|
+
// Set console mode flag
|
|
3422
|
+
this._consoleMode = true;
|
|
3423
|
+
|
|
3424
|
+
// Re-detect USB connection type to ensure we have a definitive value
|
|
3425
|
+
// This handles cases where isUsbJtagOrOtg might be undefined
|
|
3426
|
+
let isUsbJtag: boolean;
|
|
3427
|
+
try {
|
|
3428
|
+
isUsbJtag = await this.detectUsbConnectionType();
|
|
3429
|
+
this.logger.debug(
|
|
3430
|
+
`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`,
|
|
3431
|
+
);
|
|
3432
|
+
} catch (err) {
|
|
3433
|
+
// If detection fails, fall back to cached value or fail-fast
|
|
3434
|
+
if (this.isUsbJtagOrOtg === undefined) {
|
|
3435
|
+
throw new Error(
|
|
3436
|
+
`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`,
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
this.logger.debug(
|
|
3440
|
+
`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`,
|
|
3441
|
+
);
|
|
3442
|
+
isUsbJtag = this.isUsbJtagOrOtg;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
if (isUsbJtag) {
|
|
3446
|
+
// USB-JTAG/OTG devices: Use watchdog reset which closes port
|
|
3447
|
+
const wasReset = await this._resetToFirmwareIfNeeded();
|
|
3448
|
+
return wasReset; // true = port closed, caller must reopen
|
|
3449
|
+
} else {
|
|
3450
|
+
// External serial chip devices: Release locks and do simple reset
|
|
3451
|
+
try {
|
|
3452
|
+
await this.releaseReaderWriter();
|
|
3453
|
+
await this.sleep(100);
|
|
3454
|
+
} catch (err) {
|
|
3455
|
+
this.logger.debug(`Failed to release locks: ${err}`);
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
// Hardware reset to firmware mode (IO0=HIGH)
|
|
3459
|
+
try {
|
|
3460
|
+
await this.hardReset(false);
|
|
3461
|
+
this.logger.log("Device reset to firmware mode");
|
|
3462
|
+
} catch (err) {
|
|
3463
|
+
this.logger.debug(`Could not reset device: ${err}`);
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
return false; // Port stays open
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
/**
|
|
3471
|
+
* @name _resetToFirmwareIfNeeded
|
|
3472
|
+
* Reset device from bootloader to firmware when switching to console mode
|
|
3473
|
+
* Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
3474
|
+
* @returns true if reconnect was performed, false otherwise
|
|
3475
|
+
*/
|
|
3476
|
+
private async _resetToFirmwareIfNeeded(): Promise<boolean> {
|
|
3477
|
+
try {
|
|
3478
|
+
// Check if device is using USB-JTAG/Serial or USB-OTG
|
|
3479
|
+
// Value should already be set during main() connection
|
|
3480
|
+
// Use getter to access parent's value if this is a stub
|
|
3481
|
+
const needsReset = this.isUsbJtagOrOtg === true;
|
|
3482
|
+
|
|
3483
|
+
if (needsReset) {
|
|
3484
|
+
const resetMethod =
|
|
3485
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
3486
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3
|
|
3487
|
+
? "USB-JTAG/Serial or USB-OTG"
|
|
3488
|
+
: "USB-JTAG/Serial";
|
|
3489
|
+
|
|
3490
|
+
this.logger.log(
|
|
3491
|
+
`Resetting ${this.chipFamily} (${resetMethod}) to boot into firmware...`,
|
|
3492
|
+
);
|
|
3493
|
+
|
|
3494
|
+
// Set console mode flag before reset to prevent subsequent hardReset calls
|
|
3495
|
+
this._consoleMode = true;
|
|
3496
|
+
|
|
3497
|
+
// For S2/S3: Clear force download boot mask before WDT reset
|
|
3498
|
+
if (
|
|
3499
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
3500
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3
|
|
3501
|
+
) {
|
|
3502
|
+
const OPTION1_REG =
|
|
3503
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
3504
|
+
? ESP32S2_RTC_CNTL_OPTION1_REG
|
|
3505
|
+
: ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
3506
|
+
const FORCE_DOWNLOAD_BOOT_MASK =
|
|
3507
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
3508
|
+
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
3509
|
+
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
3510
|
+
|
|
3511
|
+
try {
|
|
3512
|
+
// Clear force download boot mode to avoid chip being stuck in download mode
|
|
3513
|
+
await this.writeRegister(
|
|
3514
|
+
OPTION1_REG,
|
|
3515
|
+
0,
|
|
3516
|
+
FORCE_DOWNLOAD_BOOT_MASK,
|
|
3517
|
+
0,
|
|
3518
|
+
);
|
|
3519
|
+
this.logger.debug("Cleared force download boot mask");
|
|
3520
|
+
} catch (err) {
|
|
3521
|
+
this.logger.debug(
|
|
3522
|
+
`Expected error clearing force download boot mask: ${err}`,
|
|
3523
|
+
);
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
// Perform watchdog reset to reboot into firmware
|
|
3528
|
+
try {
|
|
3529
|
+
await this.rtcWdtResetChipSpecific();
|
|
3530
|
+
this.logger.debug("Watchdog reset triggered successfully");
|
|
3531
|
+
} catch (err) {
|
|
3532
|
+
// Error is expected - device resets before responding
|
|
3533
|
+
this.logger.debug(
|
|
3534
|
+
`Watchdog reset initiated (connection lost as expected: ${err})`,
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
// Wait for device to fully boot into firmware
|
|
3539
|
+
this.logger.log("Waiting for device to boot into firmware...");
|
|
3540
|
+
await this.sleep(1000);
|
|
3541
|
+
|
|
3542
|
+
// After WDT reset, streams are dead/locked - don't try to manipulate them
|
|
3543
|
+
// Just mark everything as disconnected and let browser clean up
|
|
3544
|
+
this.connected = false;
|
|
3545
|
+
this._writer = undefined;
|
|
3546
|
+
this._reader = undefined;
|
|
3547
|
+
|
|
3548
|
+
this.logger.debug("Device reset to firmware mode (port closed)");
|
|
3549
|
+
return true;
|
|
3550
|
+
}
|
|
3551
|
+
} catch (err) {
|
|
3552
|
+
this.logger.debug(`Could not reset device to firmware mode: ${err}`);
|
|
3553
|
+
// Continue anyway - console mode might still work
|
|
3554
|
+
}
|
|
3555
|
+
return false;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
2945
3558
|
/**
|
|
2946
3559
|
* @name reconnectAndResume
|
|
2947
3560
|
* Reconnect the serial port to flush browser buffers and reload stub
|
|
@@ -2954,6 +3567,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2954
3567
|
|
|
2955
3568
|
try {
|
|
2956
3569
|
this.logger.log("Reconnecting serial port...");
|
|
3570
|
+
const savedBaudRate = this._currentBaudRate;
|
|
2957
3571
|
|
|
2958
3572
|
this.connected = false;
|
|
2959
3573
|
this.__inputBuffer = [];
|
|
@@ -2992,7 +3606,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2992
3606
|
// Close port
|
|
2993
3607
|
try {
|
|
2994
3608
|
await this.port.close();
|
|
2995
|
-
this.logger.
|
|
3609
|
+
this.logger.debug("Port closed");
|
|
2996
3610
|
} catch (err) {
|
|
2997
3611
|
this.logger.debug(`Port close error: ${err}`);
|
|
2998
3612
|
}
|
|
@@ -3002,6 +3616,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3002
3616
|
try {
|
|
3003
3617
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
3004
3618
|
this.connected = true;
|
|
3619
|
+
this._currentBaudRate = ESP_ROM_BAUD;
|
|
3005
3620
|
} catch (err) {
|
|
3006
3621
|
throw new Error(`Failed to open port: ${err}`);
|
|
3007
3622
|
}
|
|
@@ -3055,8 +3670,8 @@ export class ESPLoader extends EventTarget {
|
|
|
3055
3670
|
this.logger.debug("Stub loaded");
|
|
3056
3671
|
|
|
3057
3672
|
// Restore baudrate if it was changed
|
|
3058
|
-
if (
|
|
3059
|
-
await stubLoader.setBaudrate(
|
|
3673
|
+
if (savedBaudRate !== ESP_ROM_BAUD) {
|
|
3674
|
+
await stubLoader.setBaudrate(savedBaudRate);
|
|
3060
3675
|
|
|
3061
3676
|
// Verify port is still ready after baudrate change
|
|
3062
3677
|
if (!this.port.writable || !this.port.readable) {
|
|
@@ -3093,6 +3708,9 @@ export class ESPLoader extends EventTarget {
|
|
|
3093
3708
|
try {
|
|
3094
3709
|
this.logger.log("Reconnecting to bootloader mode...");
|
|
3095
3710
|
|
|
3711
|
+
// Clear console mode flag when reconnecting to bootloader
|
|
3712
|
+
this._consoleMode = false;
|
|
3713
|
+
|
|
3096
3714
|
this.connected = false;
|
|
3097
3715
|
this.__inputBuffer = [];
|
|
3098
3716
|
this.__inputBufferReadIndex = 0;
|
|
@@ -3130,7 +3748,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3130
3748
|
// Close port
|
|
3131
3749
|
try {
|
|
3132
3750
|
await this.port.close();
|
|
3133
|
-
this.logger.
|
|
3751
|
+
this.logger.debug("Port closed");
|
|
3134
3752
|
} catch (err) {
|
|
3135
3753
|
this.logger.debug(`Port close error: ${err}`);
|
|
3136
3754
|
}
|
|
@@ -3140,6 +3758,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3140
3758
|
try {
|
|
3141
3759
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
3142
3760
|
this.connected = true;
|
|
3761
|
+
this._currentBaudRate = ESP_ROM_BAUD;
|
|
3143
3762
|
} catch (err) {
|
|
3144
3763
|
throw new Error(`Failed to open port: ${err}`);
|
|
3145
3764
|
}
|
|
@@ -3157,6 +3776,8 @@ export class ESPLoader extends EventTarget {
|
|
|
3157
3776
|
// Reset chip info and stub state
|
|
3158
3777
|
this.__chipFamily = undefined;
|
|
3159
3778
|
this.chipName = "Unknown Chip";
|
|
3779
|
+
this.chipRevision = null;
|
|
3780
|
+
this.chipVariant = null;
|
|
3160
3781
|
this.IS_STUB = false;
|
|
3161
3782
|
|
|
3162
3783
|
// Start read loop
|
|
@@ -3262,6 +3883,10 @@ export class ESPLoader extends EventTarget {
|
|
|
3262
3883
|
* @param addr - Address to read from
|
|
3263
3884
|
* @param size - Number of bytes to read
|
|
3264
3885
|
* @param onPacketReceived - Optional callback function called when packet is received
|
|
3886
|
+
* @param options - Optional parameters for advanced control
|
|
3887
|
+
* - chunkSize: Amount of data to request from ESP in one command (bytes)
|
|
3888
|
+
* - blockSize: Size of each data block sent by ESP (bytes)
|
|
3889
|
+
* - maxInFlight: Maximum unacknowledged bytes (bytes)
|
|
3265
3890
|
* @returns Uint8Array containing the flash data
|
|
3266
3891
|
*/
|
|
3267
3892
|
async readFlash(
|
|
@@ -3272,6 +3897,11 @@ export class ESPLoader extends EventTarget {
|
|
|
3272
3897
|
progress: number,
|
|
3273
3898
|
totalSize: number,
|
|
3274
3899
|
) => void,
|
|
3900
|
+
options?: {
|
|
3901
|
+
chunkSize?: number;
|
|
3902
|
+
blockSize?: number;
|
|
3903
|
+
maxInFlight?: number;
|
|
3904
|
+
},
|
|
3275
3905
|
): Promise<Uint8Array> {
|
|
3276
3906
|
if (!this.IS_STUB) {
|
|
3277
3907
|
throw new Error(
|
|
@@ -3311,7 +3941,13 @@ export class ESPLoader extends EventTarget {
|
|
|
3311
3941
|
// For WebUSB (Android), use smaller chunks to avoid timeouts and buffer issues
|
|
3312
3942
|
// For Web Serial (Desktop), use larger chunks for better performance
|
|
3313
3943
|
let CHUNK_SIZE: number;
|
|
3314
|
-
if (
|
|
3944
|
+
if (options?.chunkSize !== undefined) {
|
|
3945
|
+
// Use user-provided chunkSize if in advanced mode
|
|
3946
|
+
CHUNK_SIZE = options.chunkSize;
|
|
3947
|
+
this.logger.log(
|
|
3948
|
+
`Using custom chunk size: 0x${CHUNK_SIZE.toString(16)} bytes`,
|
|
3949
|
+
);
|
|
3950
|
+
} else if (this.isWebUSB()) {
|
|
3315
3951
|
// WebUSB: Use smaller chunks to avoid SLIP timeout issues
|
|
3316
3952
|
CHUNK_SIZE = 0x4 * 0x1000; // 4KB = 16384 bytes
|
|
3317
3953
|
} else {
|
|
@@ -3346,7 +3982,19 @@ export class ESPLoader extends EventTarget {
|
|
|
3346
3982
|
let blockSize: number;
|
|
3347
3983
|
let maxInFlight: number;
|
|
3348
3984
|
|
|
3349
|
-
if (
|
|
3985
|
+
if (
|
|
3986
|
+
options?.blockSize !== undefined &&
|
|
3987
|
+
options?.maxInFlight !== undefined
|
|
3988
|
+
) {
|
|
3989
|
+
// Use user-provided values if in advanced mode
|
|
3990
|
+
blockSize = options.blockSize;
|
|
3991
|
+
maxInFlight = options.maxInFlight;
|
|
3992
|
+
if (retryCount === 0) {
|
|
3993
|
+
this.logger.debug(
|
|
3994
|
+
`Using custom parameters: blockSize=${blockSize}, maxInFlight=${maxInFlight}`,
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
} else if (this.isWebUSB()) {
|
|
3350
3998
|
// WebUSB (Android): All devices use adaptive speed
|
|
3351
3999
|
// All have maxTransferSize=64, baseBlockSize=31
|
|
3352
4000
|
const maxTransferSize =
|
|
@@ -3538,7 +4186,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3538
4186
|
// Check if it's a timeout error or SLIP error
|
|
3539
4187
|
if (err instanceof SlipReadError) {
|
|
3540
4188
|
if (retryCount <= MAX_RETRIES) {
|
|
3541
|
-
this.logger.
|
|
4189
|
+
this.logger.debug(
|
|
3542
4190
|
`${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`,
|
|
3543
4191
|
);
|
|
3544
4192
|
|