esp32tool 1.3.2 → 1.3.4
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/apple-touch-icon.png +0 -0
- package/css/style.css +47 -35
- package/dist/const.js +1 -1
- package/dist/esp_loader.d.ts +15 -29
- package/dist/esp_loader.js +289 -314
- package/dist/web/index.js +1 -1
- package/icons/icon-128.png +0 -0
- package/icons/icon-144.png +0 -0
- package/icons/icon-152.png +0 -0
- package/icons/icon-192.png +0 -0
- package/icons/icon-384.png +0 -0
- package/icons/icon-512.png +0 -0
- package/icons/icon-72.png +0 -0
- package/icons/icon-96.png +0 -0
- package/js/console.js +12 -2
- package/js/modules/esptool.js +1 -1
- package/js/script.js +112 -160
- package/js/util/console-color.js +2 -1
- package/js/webusb-serial.js +42 -7
- package/package.json +3 -3
- package/screenshots/desktop.png +0 -0
- package/screenshots/mobile.png +0 -0
- package/src/const.ts +1 -1
- package/src/esp_loader.ts +321 -393
- package/sw.js +1 -1
package/src/esp_loader.ts
CHANGED
|
@@ -64,25 +64,14 @@ import {
|
|
|
64
64
|
ESP32S2_RTC_CNTL_WDTCONFIG0_REG,
|
|
65
65
|
ESP32S2_RTC_CNTL_WDTCONFIG1_REG,
|
|
66
66
|
ESP32S2_RTC_CNTL_WDT_WKEY,
|
|
67
|
-
ESP32S2_GPIO_STRAP_REG,
|
|
68
|
-
ESP32S2_GPIO_STRAP_SPI_BOOT_MASK,
|
|
69
67
|
ESP32S2_RTC_CNTL_OPTION1_REG,
|
|
70
68
|
ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
71
69
|
ESP32S3_RTC_CNTL_WDTWPROTECT_REG,
|
|
72
70
|
ESP32S3_RTC_CNTL_WDTCONFIG0_REG,
|
|
73
71
|
ESP32S3_RTC_CNTL_WDTCONFIG1_REG,
|
|
74
72
|
ESP32S3_RTC_CNTL_WDT_WKEY,
|
|
75
|
-
ESP32S3_GPIO_STRAP_REG,
|
|
76
|
-
ESP32S3_GPIO_STRAP_SPI_BOOT_MASK,
|
|
77
73
|
ESP32S3_RTC_CNTL_OPTION1_REG,
|
|
78
74
|
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
75
|
ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG,
|
|
87
76
|
ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG,
|
|
88
77
|
ESP32C3_RTC_CNTL_WDTWPROTECT_REG,
|
|
@@ -93,22 +82,12 @@ import {
|
|
|
93
82
|
ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG,
|
|
94
83
|
ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG,
|
|
95
84
|
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
85
|
ESP32P4_RTC_CNTL_WDTWPROTECT_REG,
|
|
101
86
|
ESP32P4_RTC_CNTL_WDTCONFIG0_REG,
|
|
102
87
|
ESP32P4_RTC_CNTL_WDTCONFIG1_REG,
|
|
103
88
|
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
89
|
ESP32P4_RTC_CNTL_OPTION1_REG,
|
|
109
90
|
ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
110
|
-
ESP32H2_UARTDEV_BUF_NO,
|
|
111
|
-
ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
112
91
|
} from "./const";
|
|
113
92
|
import { getStubCode } from "./stubs";
|
|
114
93
|
import { hexFormatter, sleep, slipEncode, toHex } from "./util";
|
|
@@ -144,6 +123,10 @@ export class ESPLoader extends EventTarget {
|
|
|
144
123
|
public currentBaudRate: number = ESP_ROM_BAUD;
|
|
145
124
|
private _maxUSBSerialBaudrate?: number;
|
|
146
125
|
private _reader?: ReadableStreamDefaultReader<Uint8Array>;
|
|
126
|
+
private SLIP_END = 0xc0;
|
|
127
|
+
private SLIP_ESC = 0xdb;
|
|
128
|
+
private SLIP_ESC_END = 0xdc;
|
|
129
|
+
private SLIP_ESC_ESC = 0xdd;
|
|
147
130
|
private _isESP32S2NativeUSB: boolean = false;
|
|
148
131
|
private _initializationSucceeded: boolean = false;
|
|
149
132
|
private __commandLock: Promise<[number, number[]]> = Promise.resolve([0, []]);
|
|
@@ -457,10 +440,8 @@ export class ESPLoader extends EventTarget {
|
|
|
457
440
|
0x303a: {
|
|
458
441
|
// Espressif (native USB)
|
|
459
442
|
0x2: { name: "ESP32-S2 Native USB", maxBaudrate: 2000000 },
|
|
443
|
+
0x12: { name: "ESP32-P4 Native USB", maxBaudrate: 2000000 },
|
|
460
444
|
0x1001: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
461
|
-
0x1002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
462
|
-
0x4002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
463
|
-
0x1000: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
464
445
|
},
|
|
465
446
|
};
|
|
466
447
|
|
|
@@ -757,7 +738,7 @@ export class ESPLoader extends EventTarget {
|
|
|
757
738
|
|
|
758
739
|
// Always read from browser's serial buffer immediately
|
|
759
740
|
// to prevent browser buffer overflow. Don't apply back-pressure here.
|
|
760
|
-
const chunk = Array.from(value);
|
|
741
|
+
const chunk = Array.from(value as Uint8Array);
|
|
761
742
|
Array.prototype.push.apply(this._inputBuffer, chunk);
|
|
762
743
|
|
|
763
744
|
// Track total bytes read from serial port
|
|
@@ -1500,101 +1481,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1500
1481
|
await this.rtcWdtResetChipSpecific();
|
|
1501
1482
|
}
|
|
1502
1483
|
|
|
1503
|
-
/**
|
|
1504
|
-
* Check if current chip is using USB-OTG
|
|
1505
|
-
* Supports ESP32-S2 and ESP32-S3
|
|
1506
|
-
*/
|
|
1507
|
-
public async usingUsbOtg(): Promise<boolean> {
|
|
1508
|
-
let uartDevBufNo: number;
|
|
1509
|
-
let usbOtgValue: number;
|
|
1510
|
-
|
|
1511
|
-
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1512
|
-
uartDevBufNo = ESP32S2_UARTDEV_BUF_NO;
|
|
1513
|
-
usbOtgValue = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
|
|
1514
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1515
|
-
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1516
|
-
usbOtgValue = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
|
|
1517
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1518
|
-
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1519
|
-
if (this.chipRevision === null) {
|
|
1520
|
-
this.chipRevision = await this.getChipRevision();
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
if (this.chipRevision < 300) {
|
|
1524
|
-
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1525
|
-
} else {
|
|
1526
|
-
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1527
|
-
}
|
|
1528
|
-
usbOtgValue = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
|
|
1529
|
-
} else {
|
|
1530
|
-
return false;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1534
|
-
return uartNo === usbOtgValue;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
/**
|
|
1538
|
-
* Check if current chip is using USB-JTAG/Serial
|
|
1539
|
-
* Supports ESP32-S3 and ESP32-C3
|
|
1540
|
-
*/
|
|
1541
|
-
public async usingUsbJtagSerial(): Promise<boolean> {
|
|
1542
|
-
let uartDevBufNo: number;
|
|
1543
|
-
let usbJtagSerialValue: number;
|
|
1544
|
-
|
|
1545
|
-
if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1546
|
-
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1547
|
-
usbJtagSerialValue = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1548
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1549
|
-
// ESP32-C3: BSS_UART_DEV_ADDR depends on chip revision
|
|
1550
|
-
// Revision < 101: 0x3FCDF064
|
|
1551
|
-
// Revision >= 101: 0x3FCDF060
|
|
1552
|
-
let bssUartDevAddr: number;
|
|
1553
|
-
|
|
1554
|
-
// Get chip revision if not already set
|
|
1555
|
-
if (this.chipRevision === null) {
|
|
1556
|
-
this.chipRevision = await this.getChipRevisionC3();
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
if (this.chipRevision < 101) {
|
|
1560
|
-
bssUartDevAddr = 0x3fcdf064;
|
|
1561
|
-
} else {
|
|
1562
|
-
bssUartDevAddr = 0x3fcdf060;
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
uartDevBufNo = bssUartDevAddr + ESP32C3_BUF_UART_NO_OFFSET;
|
|
1566
|
-
usbJtagSerialValue = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1567
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
|
|
1568
|
-
uartDevBufNo = ESP32C5_UARTDEV_BUF_NO;
|
|
1569
|
-
usbJtagSerialValue = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1570
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
1571
|
-
uartDevBufNo = ESP32C6_UARTDEV_BUF_NO;
|
|
1572
|
-
usbJtagSerialValue = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1573
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1574
|
-
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1575
|
-
// Revision < 300: 0x4FF3FEC8
|
|
1576
|
-
// Revision >= 300: 0x4FFBFEC8
|
|
1577
|
-
if (this.chipRevision === null) {
|
|
1578
|
-
this.chipRevision = await this.getChipRevision();
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
if (this.chipRevision < 300) {
|
|
1582
|
-
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1583
|
-
} else {
|
|
1584
|
-
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1585
|
-
}
|
|
1586
|
-
usbJtagSerialValue = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1587
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32H2) {
|
|
1588
|
-
uartDevBufNo = ESP32H2_UARTDEV_BUF_NO;
|
|
1589
|
-
usbJtagSerialValue = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1590
|
-
} else {
|
|
1591
|
-
return false;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1595
|
-
return uartNo === usbJtagSerialValue;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
1484
|
/**
|
|
1599
1485
|
* Get chip revision for ESP32-C3
|
|
1600
1486
|
* Reads from EFUSE registers and calculates revision
|
|
@@ -1670,52 +1556,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1670
1556
|
// Unlock watchdog registers
|
|
1671
1557
|
await this.writeRegister(WDTWPROTECT_REG, WDT_WKEY, undefined, 0);
|
|
1672
1558
|
|
|
1673
|
-
// Clear force download boot register (if applicable) BEFORE triggering WDT reset
|
|
1674
|
-
// This ensures the chip boots into firmware mode after reset
|
|
1675
|
-
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1676
|
-
try {
|
|
1677
|
-
await this.writeRegister(
|
|
1678
|
-
ESP32S2_RTC_CNTL_OPTION1_REG,
|
|
1679
|
-
0,
|
|
1680
|
-
ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1681
|
-
0,
|
|
1682
|
-
);
|
|
1683
|
-
this.logger.debug("Cleared force download boot mask");
|
|
1684
|
-
} catch (err) {
|
|
1685
|
-
this.logger.debug(
|
|
1686
|
-
`Expected error clearing force download boot mask: ${err}`,
|
|
1687
|
-
);
|
|
1688
|
-
}
|
|
1689
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1690
|
-
try {
|
|
1691
|
-
await this.writeRegister(
|
|
1692
|
-
ESP32S3_RTC_CNTL_OPTION1_REG,
|
|
1693
|
-
0,
|
|
1694
|
-
ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1695
|
-
0,
|
|
1696
|
-
);
|
|
1697
|
-
this.logger.debug("Cleared force download boot mask");
|
|
1698
|
-
} catch (err) {
|
|
1699
|
-
this.logger.debug(
|
|
1700
|
-
`Expected error clearing force download boot mask: ${err}`,
|
|
1701
|
-
);
|
|
1702
|
-
}
|
|
1703
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1704
|
-
try {
|
|
1705
|
-
await this.writeRegister(
|
|
1706
|
-
ESP32P4_RTC_CNTL_OPTION1_REG,
|
|
1707
|
-
0,
|
|
1708
|
-
ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1709
|
-
0,
|
|
1710
|
-
);
|
|
1711
|
-
this.logger.debug("Cleared force download boot mask");
|
|
1712
|
-
} catch (err) {
|
|
1713
|
-
this.logger.debug(
|
|
1714
|
-
`Expected error clearing force download boot mask: ${err}`,
|
|
1715
|
-
);
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
1559
|
// Set WDT timeout to 2000ms (matches Python esptool)
|
|
1720
1560
|
await this.writeRegister(WDTCONFIG1_REG, 2000, undefined, 0);
|
|
1721
1561
|
|
|
@@ -1731,84 +1571,30 @@ export class ESPLoader extends EventTarget {
|
|
|
1731
1571
|
}
|
|
1732
1572
|
|
|
1733
1573
|
/**
|
|
1734
|
-
* Helper:
|
|
1574
|
+
* Helper: USB-based WDT reset
|
|
1735
1575
|
* Returns true if WDT reset was performed, false otherwise
|
|
1736
1576
|
*/
|
|
1737
|
-
private async tryUsbWdtReset(
|
|
1738
|
-
|
|
1739
|
-
GPIO_STRAP_REG: number,
|
|
1740
|
-
GPIO_STRAP_SPI_BOOT_MASK: number,
|
|
1741
|
-
RTC_CNTL_OPTION1_REG: number,
|
|
1742
|
-
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK: number,
|
|
1743
|
-
): Promise<boolean> {
|
|
1744
|
-
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1745
|
-
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1746
|
-
|
|
1747
|
-
if (isUsingUsbOtg || isUsingUsbJtagSerial) {
|
|
1748
|
-
const strapReg = await this.readRegister(GPIO_STRAP_REG);
|
|
1749
|
-
const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
|
|
1750
|
-
|
|
1751
|
-
// Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
|
|
1752
|
-
if (
|
|
1753
|
-
(strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
|
|
1754
|
-
(forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0
|
|
1755
|
-
) {
|
|
1756
|
-
await this.rtcWdtResetChipSpecific();
|
|
1757
|
-
this.logger.debug(
|
|
1758
|
-
`${chipName}: RTC WDT reset (USB detected, GPIO0 low)`,
|
|
1759
|
-
);
|
|
1760
|
-
return true;
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
return false;
|
|
1764
|
-
}
|
|
1577
|
+
private async tryUsbWdtReset(chipName: string): Promise<boolean> {
|
|
1578
|
+
const isUsingUsbOtg = await this.detectUsbConnectionType();
|
|
1765
1579
|
|
|
1766
|
-
/**
|
|
1767
|
-
* Chip-specific hard reset for ESP32-S2
|
|
1768
|
-
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1769
|
-
*/
|
|
1770
|
-
public async hardResetS2(): Promise<void> {
|
|
1771
|
-
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1772
1580
|
if (isUsingUsbOtg) {
|
|
1581
|
+
// Use WDT reset for USB-OTG devices
|
|
1773
1582
|
await this.rtcWdtResetChipSpecific();
|
|
1774
|
-
this.logger.debug(
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
this.logger.debug("ESP32-S2: Classic reset");
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
/**
|
|
1783
|
-
* Chip-specific hard reset for ESP32-S3
|
|
1784
|
-
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1785
|
-
*/
|
|
1786
|
-
public async hardResetS3(): Promise<void> {
|
|
1787
|
-
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1788
|
-
if (isUsingUsbJtagSerial) {
|
|
1789
|
-
await this.rtcWdtResetChipSpecific();
|
|
1790
|
-
this.logger.debug("ESP32-S3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1791
|
-
} else {
|
|
1792
|
-
// Use standard hardware reset
|
|
1793
|
-
await this.hardResetClassic();
|
|
1794
|
-
this.logger.debug("ESP32-S3: Classic reset");
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
/**
|
|
1799
|
-
* Chip-specific hard reset for ESP32-C3
|
|
1800
|
-
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1801
|
-
*/
|
|
1802
|
-
public async hardResetC3(): Promise<void> {
|
|
1803
|
-
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1804
|
-
if (isUsingUsbJtagSerial) {
|
|
1805
|
-
await this.rtcWdtResetChipSpecific();
|
|
1806
|
-
this.logger.debug("ESP32-C3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1583
|
+
this.logger.debug(
|
|
1584
|
+
`${chipName}: RTC WDT reset (USB-JTAG/Serial or USB-OTG detected)`,
|
|
1585
|
+
);
|
|
1586
|
+
return true;
|
|
1807
1587
|
} else {
|
|
1808
|
-
// Use
|
|
1809
|
-
|
|
1810
|
-
|
|
1588
|
+
// Use classic reset for non-USB devices
|
|
1589
|
+
if (this.isWebUSB()) {
|
|
1590
|
+
await this.hardResetClassicWebUSB();
|
|
1591
|
+
this.logger.debug("Classic reset (WebUSB/Android).");
|
|
1592
|
+
} else {
|
|
1593
|
+
await this.hardResetClassic();
|
|
1594
|
+
this.logger.debug("Classic reset.");
|
|
1595
|
+
}
|
|
1811
1596
|
}
|
|
1597
|
+
return false;
|
|
1812
1598
|
}
|
|
1813
1599
|
|
|
1814
1600
|
async hardReset(bootloader = false) {
|
|
@@ -1848,27 +1634,25 @@ export class ESPLoader extends EventTarget {
|
|
|
1848
1634
|
}
|
|
1849
1635
|
} else {
|
|
1850
1636
|
// just reset (no bootloader mode)
|
|
1851
|
-
// For ESP32-S2/S3 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
ESP32S2_GPIO_STRAP_REG,
|
|
1856
|
-
ESP32S2_GPIO_STRAP_SPI_BOOT_MASK,
|
|
1857
|
-
ESP32S2_RTC_CNTL_OPTION1_REG,
|
|
1858
|
-
ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
1859
|
-
);
|
|
1637
|
+
// For ESP32-S2/S3/P4 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
|
|
1638
|
+
this.logger.debug("*** Performing WDT reset strategy ***");
|
|
1639
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1640
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S2");
|
|
1860
1641
|
if (wdtResetUsed) return;
|
|
1861
|
-
} else if (
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
) {
|
|
1865
|
-
const wdtResetUsed = await this.tryUsbWdtReset(
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
);
|
|
1642
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1643
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S3");
|
|
1644
|
+
if (wdtResetUsed) return;
|
|
1645
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1646
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-P4");
|
|
1647
|
+
if (wdtResetUsed) return;
|
|
1648
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1649
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C3");
|
|
1650
|
+
if (wdtResetUsed) return;
|
|
1651
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
|
|
1652
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C5");
|
|
1653
|
+
if (wdtResetUsed) return;
|
|
1654
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
1655
|
+
const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C6");
|
|
1872
1656
|
if (wdtResetUsed) return;
|
|
1873
1657
|
}
|
|
1874
1658
|
|
|
@@ -2126,47 +1910,47 @@ export class ESPLoader extends EventTarget {
|
|
|
2126
1910
|
"Timed out waiting for packet " + waitingFor,
|
|
2127
1911
|
);
|
|
2128
1912
|
}
|
|
2129
|
-
const
|
|
1913
|
+
const byte = this._readByte()!;
|
|
2130
1914
|
|
|
2131
1915
|
if (partialPacket === null) {
|
|
2132
1916
|
// waiting for packet header
|
|
2133
|
-
if (
|
|
1917
|
+
if (byte == this.SLIP_END) {
|
|
2134
1918
|
partialPacket = [];
|
|
2135
1919
|
} else {
|
|
2136
1920
|
if (this.debug) {
|
|
2137
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1921
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2138
1922
|
this.logger.debug(
|
|
2139
1923
|
"Remaining data in serial buffer: " +
|
|
2140
1924
|
hexFormatter(this._inputBuffer),
|
|
2141
1925
|
);
|
|
2142
1926
|
}
|
|
2143
1927
|
throw new SlipReadError(
|
|
2144
|
-
"Invalid head of packet (" + toHex(
|
|
1928
|
+
"Invalid head of packet (" + toHex(byte) + ")",
|
|
2145
1929
|
);
|
|
2146
1930
|
}
|
|
2147
1931
|
} else if (inEscape) {
|
|
2148
1932
|
// part-way through escape sequence
|
|
2149
1933
|
inEscape = false;
|
|
2150
|
-
if (
|
|
2151
|
-
partialPacket.push(
|
|
2152
|
-
} else if (
|
|
2153
|
-
partialPacket.push(
|
|
1934
|
+
if (byte == this.SLIP_ESC_END) {
|
|
1935
|
+
partialPacket.push(this.SLIP_END);
|
|
1936
|
+
} else if (byte == this.SLIP_ESC_ESC) {
|
|
1937
|
+
partialPacket.push(this.SLIP_ESC);
|
|
2154
1938
|
} else {
|
|
2155
1939
|
if (this.debug) {
|
|
2156
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1940
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2157
1941
|
this.logger.debug(
|
|
2158
1942
|
"Remaining data in serial buffer: " +
|
|
2159
1943
|
hexFormatter(this._inputBuffer),
|
|
2160
1944
|
);
|
|
2161
1945
|
}
|
|
2162
1946
|
throw new SlipReadError(
|
|
2163
|
-
"Invalid SLIP escape (0xdb, " + toHex(
|
|
1947
|
+
"Invalid SLIP escape (0xdb, " + toHex(byte) + ")",
|
|
2164
1948
|
);
|
|
2165
1949
|
}
|
|
2166
|
-
} else if (
|
|
1950
|
+
} else if (byte == this.SLIP_ESC) {
|
|
2167
1951
|
// start of escape sequence
|
|
2168
1952
|
inEscape = true;
|
|
2169
|
-
} else if (
|
|
1953
|
+
} else if (byte == this.SLIP_END) {
|
|
2170
1954
|
// end of packet
|
|
2171
1955
|
if (this.debug)
|
|
2172
1956
|
this.logger.debug(
|
|
@@ -2177,7 +1961,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2177
1961
|
return partialPacket;
|
|
2178
1962
|
} else {
|
|
2179
1963
|
// normal byte in packet
|
|
2180
|
-
partialPacket.push(
|
|
1964
|
+
partialPacket.push(byte);
|
|
2181
1965
|
}
|
|
2182
1966
|
}
|
|
2183
1967
|
}
|
|
@@ -2211,46 +1995,46 @@ export class ESPLoader extends EventTarget {
|
|
|
2211
1995
|
this.logger.debug(
|
|
2212
1996
|
"Read " + readBytes.length + " bytes: " + hexFormatter(readBytes),
|
|
2213
1997
|
);
|
|
2214
|
-
for (const
|
|
1998
|
+
for (const byte of readBytes) {
|
|
2215
1999
|
if (partialPacket === null) {
|
|
2216
2000
|
// waiting for packet header
|
|
2217
|
-
if (
|
|
2001
|
+
if (byte == this.SLIP_END) {
|
|
2218
2002
|
partialPacket = [];
|
|
2219
2003
|
} else {
|
|
2220
2004
|
if (this.debug) {
|
|
2221
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
2005
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2222
2006
|
this.logger.debug(
|
|
2223
2007
|
"Remaining data in serial buffer: " +
|
|
2224
2008
|
hexFormatter(this._inputBuffer),
|
|
2225
2009
|
);
|
|
2226
2010
|
}
|
|
2227
2011
|
throw new SlipReadError(
|
|
2228
|
-
"Invalid head of packet (" + toHex(
|
|
2012
|
+
"Invalid head of packet (" + toHex(byte) + ")",
|
|
2229
2013
|
);
|
|
2230
2014
|
}
|
|
2231
2015
|
} else if (inEscape) {
|
|
2232
2016
|
// part-way through escape sequence
|
|
2233
2017
|
inEscape = false;
|
|
2234
|
-
if (
|
|
2235
|
-
partialPacket.push(
|
|
2236
|
-
} else if (
|
|
2237
|
-
partialPacket.push(
|
|
2018
|
+
if (byte == this.SLIP_ESC_END) {
|
|
2019
|
+
partialPacket.push(this.SLIP_END);
|
|
2020
|
+
} else if (byte == this.SLIP_ESC_ESC) {
|
|
2021
|
+
partialPacket.push(this.SLIP_ESC);
|
|
2238
2022
|
} else {
|
|
2239
2023
|
if (this.debug) {
|
|
2240
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
2024
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2241
2025
|
this.logger.debug(
|
|
2242
2026
|
"Remaining data in serial buffer: " +
|
|
2243
2027
|
hexFormatter(this._inputBuffer),
|
|
2244
2028
|
);
|
|
2245
2029
|
}
|
|
2246
2030
|
throw new SlipReadError(
|
|
2247
|
-
"Invalid SLIP escape (0xdb, " + toHex(
|
|
2031
|
+
"Invalid SLIP escape (0xdb, " + toHex(byte) + ")",
|
|
2248
2032
|
);
|
|
2249
2033
|
}
|
|
2250
|
-
} else if (
|
|
2034
|
+
} else if (byte == this.SLIP_ESC) {
|
|
2251
2035
|
// start of escape sequence
|
|
2252
2036
|
inEscape = true;
|
|
2253
|
-
} else if (
|
|
2037
|
+
} else if (byte == this.SLIP_END) {
|
|
2254
2038
|
// end of packet
|
|
2255
2039
|
if (this.debug)
|
|
2256
2040
|
this.logger.debug(
|
|
@@ -2261,7 +2045,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2261
2045
|
return partialPacket;
|
|
2262
2046
|
} else {
|
|
2263
2047
|
// normal byte in packet
|
|
2264
|
-
partialPacket.push(
|
|
2048
|
+
partialPacket.push(byte);
|
|
2265
2049
|
}
|
|
2266
2050
|
}
|
|
2267
2051
|
}
|
|
@@ -3257,7 +3041,6 @@ export class ESPLoader extends EventTarget {
|
|
|
3257
3041
|
try {
|
|
3258
3042
|
this._reader.cancel();
|
|
3259
3043
|
} catch (err) {
|
|
3260
|
-
// this.logger.debug(`Reader cancel error: ${err}`);
|
|
3261
3044
|
// Reader already released, resolve immediately
|
|
3262
3045
|
clearTimeout(timeout);
|
|
3263
3046
|
resolve(undefined);
|
|
@@ -3285,14 +3068,6 @@ export class ESPLoader extends EventTarget {
|
|
|
3285
3068
|
return;
|
|
3286
3069
|
}
|
|
3287
3070
|
|
|
3288
|
-
// Check if device is in JTAG mode and needs reset to boot into firmware
|
|
3289
|
-
const didReconnect = await this._resetToFirmwareIfNeeded();
|
|
3290
|
-
|
|
3291
|
-
// If we reconnected for console, the reader/writer are already released and restarted
|
|
3292
|
-
if (didReconnect) {
|
|
3293
|
-
return;
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
3071
|
// Wait for pending writes to complete
|
|
3297
3072
|
try {
|
|
3298
3073
|
await this._writeChain;
|
|
@@ -3347,37 +3122,36 @@ export class ESPLoader extends EventTarget {
|
|
|
3347
3122
|
/**
|
|
3348
3123
|
* @name detectUsbConnectionType
|
|
3349
3124
|
* Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
3350
|
-
*
|
|
3125
|
+
* Uses USB PID (Product ID) for reliable detection - does NOT require chipFamily
|
|
3351
3126
|
* @returns true if USB-JTAG or USB-OTG, false if external serial chip
|
|
3352
|
-
* @throws Error if detection fails and chipFamily is not set
|
|
3353
3127
|
*/
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3128
|
+
public async detectUsbConnectionType(): Promise<boolean> {
|
|
3129
|
+
// Use PID-based detection
|
|
3130
|
+
const portInfo = this.port.getInfo();
|
|
3131
|
+
const pid = portInfo.usbProductId;
|
|
3132
|
+
const vid = portInfo.usbVendorId;
|
|
3358
3133
|
|
|
3359
|
-
if
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
) {
|
|
3363
|
-
|
|
3364
|
-
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
3365
|
-
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
3366
|
-
} else if (
|
|
3367
|
-
this.chipFamily === CHIP_FAMILY_ESP32C3 ||
|
|
3368
|
-
this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
3369
|
-
this.chipFamily === CHIP_FAMILY_ESP32C6
|
|
3370
|
-
) {
|
|
3371
|
-
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
3372
|
-
return isUsingUsbJtagSerial;
|
|
3373
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
3374
|
-
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
3375
|
-
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
3376
|
-
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
3377
|
-
} else {
|
|
3378
|
-
// Other chips don't have USB-JTAG/OTG
|
|
3134
|
+
// Check if this is an Espressif device
|
|
3135
|
+
const isEspressif = vid === 0x303a;
|
|
3136
|
+
|
|
3137
|
+
if (!isEspressif) {
|
|
3138
|
+
this.logger.debug("Not Espressif VID - external serial chip");
|
|
3379
3139
|
return false;
|
|
3380
3140
|
}
|
|
3141
|
+
|
|
3142
|
+
// ESP32-S2/S3/C3/C5/C6/C61/H2/P4 USB-JTAG/OTG PIDs
|
|
3143
|
+
// According to official Espressif documentation:
|
|
3144
|
+
// https://docs.espressif.com/projects/esp-iot-solution/en/latest/usb/usb_overview/usb_device_const_COM.html
|
|
3145
|
+
// 0x0002 = ESP32-S2 USB-OTG, 0x0012 = ESP32-P4 USB-Serial-JTAG
|
|
3146
|
+
// 0x1001 = ESP32-S3, C3, C5, C6, C61, H2 USB-Serial-JTAG
|
|
3147
|
+
const usbJtagPids = [0x0002, 0x0012, 0x1001];
|
|
3148
|
+
const isUsbJtag = usbJtagPids.includes(pid || 0);
|
|
3149
|
+
|
|
3150
|
+
this.logger.debug(
|
|
3151
|
+
`USB-JTAG/OTG detection: ${isUsbJtag ? "YES" : "NO"} (PID=0x${pid?.toString(16)})`,
|
|
3152
|
+
);
|
|
3153
|
+
|
|
3154
|
+
return isUsbJtag;
|
|
3381
3155
|
}
|
|
3382
3156
|
|
|
3383
3157
|
/**
|
|
@@ -3387,17 +3161,24 @@ export class ESPLoader extends EventTarget {
|
|
|
3387
3161
|
* @returns true if port was closed (USB-JTAG), false if port stays open (serial chip)
|
|
3388
3162
|
*/
|
|
3389
3163
|
public async enterConsoleMode(): Promise<boolean> {
|
|
3390
|
-
//
|
|
3391
|
-
this.
|
|
3164
|
+
// Check if port is open - if not, we need a new port selection
|
|
3165
|
+
if (!this.port.writable || !this.port.readable) {
|
|
3166
|
+
this.logger.debug("Port is not open - port selection needed");
|
|
3167
|
+
// Return true to signal that port selection is needed
|
|
3168
|
+
// The caller should handle port selection and try again
|
|
3169
|
+
return true;
|
|
3170
|
+
}
|
|
3392
3171
|
|
|
3393
3172
|
// Re-detect USB connection type to ensure we have a definitive value
|
|
3394
|
-
// This handles cases where isUsbJtagOrOtg might be undefined
|
|
3395
3173
|
let isUsbJtag: boolean;
|
|
3396
3174
|
try {
|
|
3397
3175
|
isUsbJtag = await this.detectUsbConnectionType();
|
|
3398
3176
|
this.logger.debug(
|
|
3399
3177
|
`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`,
|
|
3400
3178
|
);
|
|
3179
|
+
|
|
3180
|
+
// CRITICAL: Set the cached value so _resetToFirmwareIfNeeded() can use it
|
|
3181
|
+
this._isUsbJtagOrOtg = isUsbJtag;
|
|
3401
3182
|
} catch (err) {
|
|
3402
3183
|
// If detection fails, fall back to cached value or fail-fast
|
|
3403
3184
|
if (this.isUsbJtagOrOtg === undefined) {
|
|
@@ -3405,12 +3186,17 @@ export class ESPLoader extends EventTarget {
|
|
|
3405
3186
|
`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`,
|
|
3406
3187
|
);
|
|
3407
3188
|
}
|
|
3189
|
+
// Set console mode flag
|
|
3190
|
+
this._consoleMode = false;
|
|
3191
|
+
|
|
3408
3192
|
this.logger.debug(
|
|
3409
3193
|
`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`,
|
|
3410
3194
|
);
|
|
3411
3195
|
isUsbJtag = this.isUsbJtagOrOtg;
|
|
3412
3196
|
}
|
|
3413
3197
|
|
|
3198
|
+
// Release reader/writer so console can create new ones
|
|
3199
|
+
// This is needed for Desktop (Web Serial) to unlock streams
|
|
3414
3200
|
if (isUsbJtag) {
|
|
3415
3201
|
// USB-JTAG/OTG devices: Use watchdog reset which closes port
|
|
3416
3202
|
const wasReset = await this._resetToFirmwareIfNeeded();
|
|
@@ -3432,6 +3218,23 @@ export class ESPLoader extends EventTarget {
|
|
|
3432
3218
|
this.logger.debug(`Could not reset device: ${err}`);
|
|
3433
3219
|
}
|
|
3434
3220
|
|
|
3221
|
+
// For WebUSB (Android), recreate streams after hardware reset
|
|
3222
|
+
if (this.isWebUSB()) {
|
|
3223
|
+
try {
|
|
3224
|
+
// Use the public recreateStreams() method to safely recreate streams
|
|
3225
|
+
// without closing the port (important after hardware reset)
|
|
3226
|
+
await (this.port as any).recreateStreams();
|
|
3227
|
+
this.logger.debug("WebUSB streams recreated for console mode");
|
|
3228
|
+
} catch (err) {
|
|
3229
|
+
// Set console mode flag
|
|
3230
|
+
this._consoleMode = false;
|
|
3231
|
+
this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
// Set console mode flag
|
|
3236
|
+
this._consoleMode = true;
|
|
3237
|
+
|
|
3435
3238
|
return false; // Port stays open
|
|
3436
3239
|
}
|
|
3437
3240
|
}
|
|
@@ -3442,80 +3245,179 @@ export class ESPLoader extends EventTarget {
|
|
|
3442
3245
|
* Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
3443
3246
|
* @returns true if reconnect was performed, false otherwise
|
|
3444
3247
|
*/
|
|
3445
|
-
|
|
3248
|
+
/**
|
|
3249
|
+
* @name _clearForceDownloadBootIfNeeded
|
|
3250
|
+
* Read and clear the force download boot flag if it is set
|
|
3251
|
+
* This should ONLY be called when on ROM (not stub) and before WDT reset
|
|
3252
|
+
* Clearing it on every connect causes issues with flash operations
|
|
3253
|
+
* Returns true if the flag was cleared, false if it was already clear
|
|
3254
|
+
*/
|
|
3255
|
+
private async _clearForceDownloadBootIfNeeded(): Promise<boolean> {
|
|
3446
3256
|
try {
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
if (
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3257
|
+
let regAddr: number;
|
|
3258
|
+
let mask: number;
|
|
3259
|
+
let chipName: string;
|
|
3260
|
+
|
|
3261
|
+
// Get register address and mask for this chip
|
|
3262
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
3263
|
+
regAddr = ESP32S2_RTC_CNTL_OPTION1_REG;
|
|
3264
|
+
mask = ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
3265
|
+
chipName = "ESP32-S2";
|
|
3266
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
3267
|
+
regAddr = ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
3268
|
+
mask = ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
3269
|
+
chipName = "ESP32-S3";
|
|
3270
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
3271
|
+
regAddr = ESP32P4_RTC_CNTL_OPTION1_REG;
|
|
3272
|
+
mask = ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
3273
|
+
chipName = "ESP32-P4";
|
|
3274
|
+
} else {
|
|
3275
|
+
// Not a chip that needs this
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3458
3278
|
|
|
3459
|
-
|
|
3460
|
-
|
|
3279
|
+
// Read current register value
|
|
3280
|
+
const currentValue = await this.readRegister(regAddr);
|
|
3281
|
+
this.logger.debug(
|
|
3282
|
+
`${chipName} force download boot register: 0x${currentValue.toString(16)} (mask: 0x${mask.toString(16)})`,
|
|
3283
|
+
);
|
|
3284
|
+
|
|
3285
|
+
// Check if the flag is set
|
|
3286
|
+
const isFlagSet = (currentValue & mask) !== 0;
|
|
3287
|
+
|
|
3288
|
+
if (isFlagSet) {
|
|
3289
|
+
this.logger.debug(
|
|
3290
|
+
`${chipName} force download boot flag is SET - clearing it`,
|
|
3461
3291
|
);
|
|
3292
|
+
// Clear the flag by writing 0 to the masked bits
|
|
3293
|
+
await this.writeRegister(regAddr, 0, mask, 0);
|
|
3294
|
+
this.logger.debug(`${chipName} force download boot flag cleared`);
|
|
3295
|
+
return true;
|
|
3296
|
+
} else {
|
|
3297
|
+
this.logger.debug(
|
|
3298
|
+
`${chipName} force download boot flag is already CLEAR - no action needed`,
|
|
3299
|
+
);
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3302
|
+
} catch (err) {
|
|
3303
|
+
this.logger.debug(`Error checking/clearing force download flag: ${err}`);
|
|
3304
|
+
return false;
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3462
3307
|
|
|
3463
|
-
|
|
3464
|
-
|
|
3308
|
+
private async _resetToFirmwareIfNeeded(): Promise<boolean> {
|
|
3309
|
+
try {
|
|
3310
|
+
// Check if port is open - if not, assume device is already in firmware mode
|
|
3311
|
+
if (!this.port.writable || !this.port.readable) {
|
|
3312
|
+
this.logger.debug(
|
|
3313
|
+
"Port is not open - assuming device is already in firmware mode",
|
|
3314
|
+
);
|
|
3315
|
+
return false;
|
|
3316
|
+
}
|
|
3465
3317
|
|
|
3466
|
-
|
|
3467
|
-
if (
|
|
3468
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
3469
|
-
this.chipFamily === CHIP_FAMILY_ESP32S3
|
|
3470
|
-
) {
|
|
3471
|
-
const OPTION1_REG =
|
|
3472
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
3473
|
-
? ESP32S2_RTC_CNTL_OPTION1_REG
|
|
3474
|
-
: ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
3475
|
-
const FORCE_DOWNLOAD_BOOT_MASK =
|
|
3476
|
-
this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
3477
|
-
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
3478
|
-
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
3318
|
+
const isUsingUsbOtg = await this.detectUsbConnectionType();
|
|
3479
3319
|
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3320
|
+
if (isUsingUsbOtg) {
|
|
3321
|
+
// For USB-OTG devices, we need to check if force download flag is set
|
|
3322
|
+
// Only if it's set, we need WDT reset (which causes port change)
|
|
3323
|
+
// If it's clear, we can use normal reset (no port change)
|
|
3324
|
+
|
|
3325
|
+
if (this.IS_STUB) {
|
|
3326
|
+
this.logger.debug("On stub - need to get back to ROM to check flag");
|
|
3327
|
+
|
|
3328
|
+
// If we're running at higher baudrate, we need to change back to ROM baudrate
|
|
3329
|
+
if (this.currentBaudRate !== ESP_ROM_BAUD) {
|
|
3490
3330
|
this.logger.debug(
|
|
3491
|
-
`
|
|
3331
|
+
`Changing baudrate from ${this.currentBaudRate} to ${ESP_ROM_BAUD} for ROM`,
|
|
3492
3332
|
);
|
|
3333
|
+
try {
|
|
3334
|
+
await this.reconfigurePort(ESP_ROM_BAUD);
|
|
3335
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
3336
|
+
} catch (err) {
|
|
3337
|
+
this.logger.debug(`Baudrate change failed: ${err}`);
|
|
3338
|
+
// Continue anyway
|
|
3339
|
+
}
|
|
3493
3340
|
}
|
|
3341
|
+
|
|
3342
|
+
this.logger.debug("Resetting to bootloader (ROM)...");
|
|
3343
|
+
|
|
3344
|
+
// Reset to bootloader - this will clear the stub from RAM
|
|
3345
|
+
try {
|
|
3346
|
+
await this.hardReset(true);
|
|
3347
|
+
|
|
3348
|
+
// Wait for reset to complete
|
|
3349
|
+
await sleep(200);
|
|
3350
|
+
|
|
3351
|
+
// Sync with ROM
|
|
3352
|
+
await this.sync();
|
|
3353
|
+
|
|
3354
|
+
this.logger.debug("Now on ROM after reset");
|
|
3355
|
+
|
|
3356
|
+
// Mark that we're no longer on stub
|
|
3357
|
+
this.IS_STUB = false;
|
|
3358
|
+
} catch (resetErr) {
|
|
3359
|
+
this.logger.debug(`Reset to ROM failed: ${resetErr}`);
|
|
3360
|
+
// If reset fails, we might already be in firmware mode
|
|
3361
|
+
// In this case, we don't need to do anything - just use normal reset
|
|
3362
|
+
this.logger.debug("Assuming device is already in firmware mode");
|
|
3363
|
+
|
|
3364
|
+
// Release reader/writer before returning
|
|
3365
|
+
await this.releaseReaderWriter();
|
|
3366
|
+
return false; // No port change needed
|
|
3367
|
+
}
|
|
3368
|
+
} else {
|
|
3369
|
+
this.logger.debug("Already on ROM - checking force download flag");
|
|
3494
3370
|
}
|
|
3495
3371
|
|
|
3496
|
-
//
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3372
|
+
// Now check if force download flag is set and clear it if needed
|
|
3373
|
+
const flagWasCleared = await this._clearForceDownloadBootIfNeeded();
|
|
3374
|
+
|
|
3375
|
+
if (flagWasCleared) {
|
|
3376
|
+
this.logger.debug(
|
|
3377
|
+
"Force download flag was cleared - device will boot to firmware after reset",
|
|
3378
|
+
);
|
|
3379
|
+
} else {
|
|
3502
3380
|
this.logger.debug(
|
|
3503
|
-
|
|
3381
|
+
"Force download flag already clear - device will boot to firmware after reset",
|
|
3504
3382
|
);
|
|
3505
3383
|
}
|
|
3506
3384
|
|
|
3507
|
-
//
|
|
3508
|
-
|
|
3509
|
-
await this.
|
|
3385
|
+
// Perform WDT reset BEFORE releasing reader/writer (needs communication)
|
|
3386
|
+
// After WDT reset, the device will reboot into firmware mode
|
|
3387
|
+
await this.hardReset(false);
|
|
3510
3388
|
|
|
3511
|
-
//
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
this._reader = undefined;
|
|
3389
|
+
// For USB-OTG devices (ESP32-S2, ESP32-P4), the port will change after WDT reset
|
|
3390
|
+
const portWillChange =
|
|
3391
|
+
(this.chipFamily === CHIP_FAMILY_ESP32S2 && isUsingUsbOtg) ||
|
|
3392
|
+
(this.chipFamily === CHIP_FAMILY_ESP32P4 && isUsingUsbOtg);
|
|
3516
3393
|
|
|
3517
|
-
|
|
3518
|
-
|
|
3394
|
+
if (portWillChange) {
|
|
3395
|
+
// Port will change - release reader/writer and let the port become invalid
|
|
3396
|
+
await this.releaseReaderWriter();
|
|
3397
|
+
|
|
3398
|
+
this.logger.log(
|
|
3399
|
+
`${this.chipName} USB-OTG: Port will change after WDT reset`,
|
|
3400
|
+
);
|
|
3401
|
+
this.logger.log("Please select the new port for console mode");
|
|
3402
|
+
|
|
3403
|
+
// Dispatch event to signal port change
|
|
3404
|
+
this.dispatchEvent(
|
|
3405
|
+
new CustomEvent("usb-otg-port-change", {
|
|
3406
|
+
detail: {
|
|
3407
|
+
chipName: this.chipName,
|
|
3408
|
+
message: `${this.chipName} USB port changed after reset. Please select the new port.`,
|
|
3409
|
+
reason: "wdt-reset-to-firmware",
|
|
3410
|
+
},
|
|
3411
|
+
}),
|
|
3412
|
+
);
|
|
3413
|
+
|
|
3414
|
+
// Return true to indicate port selection is needed
|
|
3415
|
+
return true;
|
|
3416
|
+
} else {
|
|
3417
|
+
// Port stays the same - release reader/writer so console can use the stream
|
|
3418
|
+
await this.releaseReaderWriter();
|
|
3419
|
+
return false;
|
|
3420
|
+
}
|
|
3519
3421
|
}
|
|
3520
3422
|
} catch (err) {
|
|
3521
3423
|
this.logger.debug(`Could not reset device to firmware mode: ${err}`);
|
|
@@ -3788,35 +3690,61 @@ export class ESPLoader extends EventTarget {
|
|
|
3788
3690
|
// Clear console mode flag
|
|
3789
3691
|
this._consoleMode = false;
|
|
3790
3692
|
|
|
3791
|
-
// Check if this is ESP32-S2
|
|
3792
|
-
const
|
|
3693
|
+
// Check if this is a USB-OTG device (ESP32-S2 or ESP32-P4)
|
|
3694
|
+
const isUsbOtgChip =
|
|
3695
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
3696
|
+
this.chipFamily === CHIP_FAMILY_ESP32P4;
|
|
3793
3697
|
|
|
3794
|
-
// For
|
|
3698
|
+
// For USB-OTG chips: if _isUsbJtagOrOtg is undefined, try to detect it
|
|
3795
3699
|
// If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
|
|
3796
3700
|
let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
|
|
3797
|
-
if (
|
|
3701
|
+
if (isUsbOtgChip && isUsbJtagOrOtg === undefined) {
|
|
3798
3702
|
try {
|
|
3799
3703
|
isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
3800
3704
|
} catch (err) {
|
|
3801
3705
|
this.logger.debug(
|
|
3802
|
-
`USB detection failed, assuming USB-JTAG/OTG for
|
|
3706
|
+
`USB detection failed, assuming USB-JTAG/OTG for ${this.chipName}: ${err}`,
|
|
3803
3707
|
);
|
|
3804
|
-
isUsbJtagOrOtg = true; // Conservative fallback
|
|
3708
|
+
isUsbJtagOrOtg = true; // Conservative fallback
|
|
3805
3709
|
}
|
|
3806
3710
|
}
|
|
3807
3711
|
|
|
3808
|
-
if (
|
|
3809
|
-
//
|
|
3810
|
-
|
|
3811
|
-
this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
|
|
3712
|
+
if (isUsbOtgChip && isUsbJtagOrOtg) {
|
|
3713
|
+
// USB-OTG devices: Need to reset to bootloader, which will cause port change
|
|
3714
|
+
this.logger.log(`${this.chipName} USB: Resetting to bootloader mode`);
|
|
3812
3715
|
|
|
3716
|
+
// Perform hardware reset to bootloader (GPIO0=LOW)
|
|
3717
|
+
// This will cause the port to change from CDC (firmware) to JTAG (bootloader)
|
|
3813
3718
|
try {
|
|
3814
|
-
|
|
3719
|
+
if (this.isWebUSB()) {
|
|
3720
|
+
await this.hardResetClassicWebUSB();
|
|
3721
|
+
} else {
|
|
3722
|
+
await this.hardResetClassic();
|
|
3723
|
+
}
|
|
3724
|
+
this.logger.debug("Reset to bootloader initiated");
|
|
3815
3725
|
} catch (err) {
|
|
3816
|
-
this.logger.debug(`
|
|
3726
|
+
this.logger.debug(`Reset error: ${err}`);
|
|
3817
3727
|
}
|
|
3818
3728
|
|
|
3819
|
-
//
|
|
3729
|
+
// Wait for reset to complete and port to change
|
|
3730
|
+
await sleep(500);
|
|
3731
|
+
|
|
3732
|
+
this.logger.log(
|
|
3733
|
+
`${this.chipName}: Port changed. Please select the bootloader port.`,
|
|
3734
|
+
);
|
|
3735
|
+
|
|
3736
|
+
// Dispatch event to signal port change
|
|
3737
|
+
this.dispatchEvent(
|
|
3738
|
+
new CustomEvent("usb-otg-port-change", {
|
|
3739
|
+
detail: {
|
|
3740
|
+
chipName: this.chipName,
|
|
3741
|
+
message: `${this.chipName}: Port changed. Please select the bootloader port.`,
|
|
3742
|
+
reason: "exit-console-to-bootloader",
|
|
3743
|
+
},
|
|
3744
|
+
}),
|
|
3745
|
+
);
|
|
3746
|
+
|
|
3747
|
+
// Port will change, so return true to indicate manual reconnection needed
|
|
3820
3748
|
return true;
|
|
3821
3749
|
}
|
|
3822
3750
|
|
|
@@ -3898,7 +3826,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3898
3826
|
await sleep(bufferingTime);
|
|
3899
3827
|
|
|
3900
3828
|
// Unsupported command response is sent 8 times and has
|
|
3901
|
-
// 14 bytes length including delimiter 0xC0 bytes.
|
|
3829
|
+
// 14 bytes length including delimiter SLIP_END (0xC0) bytes.
|
|
3902
3830
|
// At least part of it is read as a command response,
|
|
3903
3831
|
// but to be safe, read it all.
|
|
3904
3832
|
const bytesToDrain = 14 * 8;
|
|
@@ -4118,7 +4046,7 @@ export class ESPLoader extends EventTarget {
|
|
|
4118
4046
|
// The stub expects 4 bytes (ACK), if we send less it will break out
|
|
4119
4047
|
try {
|
|
4120
4048
|
// Send SLIP frame with no data (just delimiters)
|
|
4121
|
-
const abortFrame = [
|
|
4049
|
+
const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
|
|
4122
4050
|
await this.writeToStream(abortFrame);
|
|
4123
4051
|
this.logger.debug(`Sent abort frame to stub`);
|
|
4124
4052
|
|