tasmota-webserial-esptool 9.2.9 → 9.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/const.js +1 -1
- package/dist/esp_loader.d.ts +28 -3
- package/dist/esp_loader.js +157 -61
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/util.d.ts +4 -0
- package/dist/util.js +8 -0
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/js/script.js +207 -180
- package/js/webusb-serial.js +21 -1
- package/package.json +3 -3
- package/src/const.ts +1 -1
- package/src/esp_loader.ts +175 -65
- package/src/index.ts +3 -0
- package/src/util.ts +9 -0
package/dist/const.js
CHANGED
|
@@ -128,7 +128,7 @@ export const ESP32S3_UARTDEV_BUF_NO_USB_OTG = 3; // The above var when USB-OTG i
|
|
|
128
128
|
export const ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL = 4; // The above var when USB-JTAG/Serial is used
|
|
129
129
|
export const ESP32C2_SPI_REG_BASE = 0x60002000;
|
|
130
130
|
export const ESP32C2_BASEFUSEADDR = 0x60008800;
|
|
131
|
-
export const ESP32C2_MACFUSEADDR =
|
|
131
|
+
export const ESP32C2_MACFUSEADDR = ESP32C2_BASEFUSEADDR + 0x040;
|
|
132
132
|
export const ESP32C2_SPI_USR_OFFS = 0x18;
|
|
133
133
|
export const ESP32C2_SPI_USR1_OFFS = 0x1c;
|
|
134
134
|
export const ESP32C2_SPI_USR2_OFFS = 0x20;
|
package/dist/esp_loader.d.ts
CHANGED
|
@@ -16,9 +16,13 @@ export declare class ESPLoader extends EventTarget {
|
|
|
16
16
|
__inputBuffer?: number[];
|
|
17
17
|
__inputBufferReadIndex?: number;
|
|
18
18
|
__totalBytesRead?: number;
|
|
19
|
-
|
|
19
|
+
currentBaudRate: number;
|
|
20
20
|
private _maxUSBSerialBaudrate?;
|
|
21
21
|
__reader?: ReadableStreamDefaultReader<Uint8Array>;
|
|
22
|
+
private SLIP_END;
|
|
23
|
+
private SLIP_ESC;
|
|
24
|
+
private SLIP_ESC_END;
|
|
25
|
+
private SLIP_ESC_ESC;
|
|
22
26
|
private _isESP32S2NativeUSB;
|
|
23
27
|
private _initializationSucceeded;
|
|
24
28
|
private __commandLock;
|
|
@@ -381,8 +385,6 @@ export declare class ESPLoader extends EventTarget {
|
|
|
381
385
|
private set _writer(value);
|
|
382
386
|
private get _writeChain();
|
|
383
387
|
private set _writeChain(value);
|
|
384
|
-
private get _currentBaudRate();
|
|
385
|
-
private set _currentBaudRate(value);
|
|
386
388
|
writeToStream(data: number[]): Promise<void>;
|
|
387
389
|
disconnect(): Promise<void>;
|
|
388
390
|
/**
|
|
@@ -431,6 +433,29 @@ export declare class ESPLoader extends EventTarget {
|
|
|
431
433
|
* This is needed after Improv or other operations that leave ESP in firmware mode
|
|
432
434
|
*/
|
|
433
435
|
reconnectToBootloader(): Promise<void>;
|
|
436
|
+
/**
|
|
437
|
+
* @name exitConsoleMode
|
|
438
|
+
* Exit console mode and return to bootloader
|
|
439
|
+
* For ESP32-S2, uses reconnectToBootloader which will trigger port change
|
|
440
|
+
* @returns true if manual reconnection is needed (ESP32-S2), false otherwise
|
|
441
|
+
*/
|
|
442
|
+
exitConsoleMode(): Promise<boolean>;
|
|
443
|
+
/**
|
|
444
|
+
* @name isConsoleResetSupported
|
|
445
|
+
* Check if console reset is supported for this device
|
|
446
|
+
* ESP32-S2 USB-JTAG/CDC does not support reset in console mode
|
|
447
|
+
* because any reset causes USB port to be lost (hardware limitation)
|
|
448
|
+
*/
|
|
449
|
+
isConsoleResetSupported(): boolean;
|
|
450
|
+
/**
|
|
451
|
+
* @name resetInConsoleMode
|
|
452
|
+
* Reset device while in console mode (firmware mode)
|
|
453
|
+
*
|
|
454
|
+
* NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
|
|
455
|
+
* the USB port to be lost because the device switches USB modes during reset.
|
|
456
|
+
* This is a hardware limitation - use isConsoleResetSupported() to check first.
|
|
457
|
+
*/
|
|
458
|
+
resetInConsoleMode(): Promise<void>;
|
|
434
459
|
/**
|
|
435
460
|
* @name drainInputBuffer
|
|
436
461
|
* Actively drain the input buffer by reading data for a specified time.
|
package/dist/esp_loader.js
CHANGED
|
@@ -26,7 +26,11 @@ export class ESPLoader extends EventTarget {
|
|
|
26
26
|
this.IS_STUB = false;
|
|
27
27
|
this.connected = true;
|
|
28
28
|
this.flashSize = null;
|
|
29
|
-
this.
|
|
29
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
30
|
+
this.SLIP_END = 0xc0;
|
|
31
|
+
this.SLIP_ESC = 0xdb;
|
|
32
|
+
this.SLIP_ESC_END = 0xdc;
|
|
33
|
+
this.SLIP_ESC_ESC = 0xdd;
|
|
30
34
|
this._isESP32S2NativeUSB = false;
|
|
31
35
|
this._initializationSucceeded = false;
|
|
32
36
|
this.__commandLock = Promise.resolve([0, []]);
|
|
@@ -529,10 +533,7 @@ export class ESPLoader extends EventTarget {
|
|
|
529
533
|
}
|
|
530
534
|
}
|
|
531
535
|
catch {
|
|
532
|
-
//
|
|
533
|
-
if (!this._consoleMode) {
|
|
534
|
-
this.logger.error("Read loop got disconnected");
|
|
535
|
-
}
|
|
536
|
+
// this.logger.error("Read loop got disconnected");
|
|
536
537
|
}
|
|
537
538
|
finally {
|
|
538
539
|
// Always reset reconfiguring flag when read loop ends
|
|
@@ -1142,7 +1143,9 @@ export class ESPLoader extends EventTarget {
|
|
|
1142
1143
|
}
|
|
1143
1144
|
catch (error) {
|
|
1144
1145
|
lastError = error;
|
|
1145
|
-
this.logger.debug(
|
|
1146
|
+
// this.logger.debug(
|
|
1147
|
+
// `${strategy.name} reset failed: ${(error as Error).message}`,
|
|
1148
|
+
// );
|
|
1146
1149
|
// Set abandon flag to stop any in-flight operations
|
|
1147
1150
|
this._abandonCurrentOperation = true;
|
|
1148
1151
|
// Wait a bit for in-flight operations to abort
|
|
@@ -1721,44 +1724,44 @@ export class ESPLoader extends EventTarget {
|
|
|
1721
1724
|
const waitingFor = partialPacket === null ? "header" : "content";
|
|
1722
1725
|
throw new SlipReadError("Timed out waiting for packet " + waitingFor);
|
|
1723
1726
|
}
|
|
1724
|
-
const
|
|
1727
|
+
const byte = this._readByte();
|
|
1725
1728
|
if (partialPacket === null) {
|
|
1726
1729
|
// waiting for packet header
|
|
1727
|
-
if (
|
|
1730
|
+
if (byte == this.SLIP_END) {
|
|
1728
1731
|
partialPacket = [];
|
|
1729
1732
|
}
|
|
1730
1733
|
else {
|
|
1731
1734
|
if (this.debug) {
|
|
1732
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1735
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1733
1736
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1734
1737
|
hexFormatter(this._inputBuffer));
|
|
1735
1738
|
}
|
|
1736
|
-
throw new SlipReadError("Invalid head of packet (" + toHex(
|
|
1739
|
+
throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
|
|
1737
1740
|
}
|
|
1738
1741
|
}
|
|
1739
1742
|
else if (inEscape) {
|
|
1740
1743
|
// part-way through escape sequence
|
|
1741
1744
|
inEscape = false;
|
|
1742
|
-
if (
|
|
1743
|
-
partialPacket.push(
|
|
1745
|
+
if (byte == this.SLIP_ESC_END) {
|
|
1746
|
+
partialPacket.push(this.SLIP_END);
|
|
1744
1747
|
}
|
|
1745
|
-
else if (
|
|
1746
|
-
partialPacket.push(
|
|
1748
|
+
else if (byte == this.SLIP_ESC_ESC) {
|
|
1749
|
+
partialPacket.push(this.SLIP_ESC);
|
|
1747
1750
|
}
|
|
1748
1751
|
else {
|
|
1749
1752
|
if (this.debug) {
|
|
1750
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1753
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1751
1754
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1752
1755
|
hexFormatter(this._inputBuffer));
|
|
1753
1756
|
}
|
|
1754
|
-
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(
|
|
1757
|
+
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
|
|
1755
1758
|
}
|
|
1756
1759
|
}
|
|
1757
|
-
else if (
|
|
1760
|
+
else if (byte == this.SLIP_ESC) {
|
|
1758
1761
|
// start of escape sequence
|
|
1759
1762
|
inEscape = true;
|
|
1760
1763
|
}
|
|
1761
|
-
else if (
|
|
1764
|
+
else if (byte == this.SLIP_END) {
|
|
1762
1765
|
// end of packet
|
|
1763
1766
|
if (this.debug)
|
|
1764
1767
|
this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
|
|
@@ -1768,7 +1771,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1768
1771
|
}
|
|
1769
1772
|
else {
|
|
1770
1773
|
// normal byte in packet
|
|
1771
|
-
partialPacket.push(
|
|
1774
|
+
partialPacket.push(byte);
|
|
1772
1775
|
}
|
|
1773
1776
|
}
|
|
1774
1777
|
}
|
|
@@ -1799,44 +1802,44 @@ export class ESPLoader extends EventTarget {
|
|
|
1799
1802
|
}
|
|
1800
1803
|
if (this.debug)
|
|
1801
1804
|
this.logger.debug("Read " + readBytes.length + " bytes: " + hexFormatter(readBytes));
|
|
1802
|
-
for (const
|
|
1805
|
+
for (const byte of readBytes) {
|
|
1803
1806
|
if (partialPacket === null) {
|
|
1804
1807
|
// waiting for packet header
|
|
1805
|
-
if (
|
|
1808
|
+
if (byte == this.SLIP_END) {
|
|
1806
1809
|
partialPacket = [];
|
|
1807
1810
|
}
|
|
1808
1811
|
else {
|
|
1809
1812
|
if (this.debug) {
|
|
1810
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1813
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1811
1814
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1812
1815
|
hexFormatter(this._inputBuffer));
|
|
1813
1816
|
}
|
|
1814
|
-
throw new SlipReadError("Invalid head of packet (" + toHex(
|
|
1817
|
+
throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
|
|
1815
1818
|
}
|
|
1816
1819
|
}
|
|
1817
1820
|
else if (inEscape) {
|
|
1818
1821
|
// part-way through escape sequence
|
|
1819
1822
|
inEscape = false;
|
|
1820
|
-
if (
|
|
1821
|
-
partialPacket.push(
|
|
1823
|
+
if (byte == this.SLIP_ESC_END) {
|
|
1824
|
+
partialPacket.push(this.SLIP_END);
|
|
1822
1825
|
}
|
|
1823
|
-
else if (
|
|
1824
|
-
partialPacket.push(
|
|
1826
|
+
else if (byte == this.SLIP_ESC_ESC) {
|
|
1827
|
+
partialPacket.push(this.SLIP_ESC);
|
|
1825
1828
|
}
|
|
1826
1829
|
else {
|
|
1827
1830
|
if (this.debug) {
|
|
1828
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
1831
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
1829
1832
|
this.logger.debug("Remaining data in serial buffer: " +
|
|
1830
1833
|
hexFormatter(this._inputBuffer));
|
|
1831
1834
|
}
|
|
1832
|
-
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(
|
|
1835
|
+
throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
|
|
1833
1836
|
}
|
|
1834
1837
|
}
|
|
1835
|
-
else if (
|
|
1838
|
+
else if (byte == this.SLIP_ESC) {
|
|
1836
1839
|
// start of escape sequence
|
|
1837
1840
|
inEscape = true;
|
|
1838
1841
|
}
|
|
1839
|
-
else if (
|
|
1842
|
+
else if (byte == this.SLIP_END) {
|
|
1840
1843
|
// end of packet
|
|
1841
1844
|
if (this.debug)
|
|
1842
1845
|
this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
|
|
@@ -1846,7 +1849,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1846
1849
|
}
|
|
1847
1850
|
else {
|
|
1848
1851
|
// normal byte in packet
|
|
1849
|
-
partialPacket.push(
|
|
1852
|
+
partialPacket.push(byte);
|
|
1850
1853
|
}
|
|
1851
1854
|
}
|
|
1852
1855
|
}
|
|
@@ -1911,10 +1914,10 @@ export class ESPLoader extends EventTarget {
|
|
|
1911
1914
|
await sleep(SYNC_TIMEOUT);
|
|
1912
1915
|
// Track current baudrate for reconnect
|
|
1913
1916
|
if (this._parent) {
|
|
1914
|
-
this._parent.
|
|
1917
|
+
this._parent.currentBaudRate = baud;
|
|
1915
1918
|
}
|
|
1916
1919
|
else {
|
|
1917
|
-
this.
|
|
1920
|
+
this.currentBaudRate = baud;
|
|
1918
1921
|
}
|
|
1919
1922
|
// Warn if baudrate exceeds USB-Serial chip capability
|
|
1920
1923
|
const maxBaud = this._parent
|
|
@@ -1986,8 +1989,8 @@ export class ESPLoader extends EventTarget {
|
|
|
1986
1989
|
this.readLoop();
|
|
1987
1990
|
}
|
|
1988
1991
|
catch (e) {
|
|
1989
|
-
this.logger.error(`Reconfigure port error: ${e}`);
|
|
1990
|
-
throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
1992
|
+
// this.logger.error(`Reconfigure port error: ${e}`);
|
|
1993
|
+
// throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
1991
1994
|
}
|
|
1992
1995
|
finally {
|
|
1993
1996
|
// Always reset flag, even on error or early return
|
|
@@ -2520,19 +2523,6 @@ export class ESPLoader extends EventTarget {
|
|
|
2520
2523
|
this.__writeChain = value;
|
|
2521
2524
|
}
|
|
2522
2525
|
}
|
|
2523
|
-
get _currentBaudRate() {
|
|
2524
|
-
return this._parent
|
|
2525
|
-
? this._parent._currentBaudRate
|
|
2526
|
-
: this.__currentBaudRate;
|
|
2527
|
-
}
|
|
2528
|
-
set _currentBaudRate(value) {
|
|
2529
|
-
if (this._parent) {
|
|
2530
|
-
this._parent._currentBaudRate = value;
|
|
2531
|
-
}
|
|
2532
|
-
else {
|
|
2533
|
-
this.__currentBaudRate = value;
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
2526
|
async writeToStream(data) {
|
|
2537
2527
|
if (!this.port.writable) {
|
|
2538
2528
|
this.logger.debug("Port writable stream not available, skipping write");
|
|
@@ -2602,7 +2592,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2602
2592
|
return;
|
|
2603
2593
|
}
|
|
2604
2594
|
if (!this.port.writable) {
|
|
2605
|
-
this.logger.debug("Port already closed, skipping disconnect");
|
|
2595
|
+
// this.logger.debug("Port already closed, skipping disconnect");
|
|
2606
2596
|
return;
|
|
2607
2597
|
}
|
|
2608
2598
|
// Wait for pending writes to complete
|
|
@@ -2610,7 +2600,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2610
2600
|
await this._writeChain;
|
|
2611
2601
|
}
|
|
2612
2602
|
catch (err) {
|
|
2613
|
-
this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
2603
|
+
// this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
2614
2604
|
}
|
|
2615
2605
|
// Release persistent writer before closing
|
|
2616
2606
|
if (this._writer) {
|
|
@@ -2619,7 +2609,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2619
2609
|
this._writer.releaseLock();
|
|
2620
2610
|
}
|
|
2621
2611
|
catch (err) {
|
|
2622
|
-
this.logger.debug(`Writer close/release error: ${err}`);
|
|
2612
|
+
// this.logger.debug(`Writer close/release error: ${err}`);
|
|
2623
2613
|
}
|
|
2624
2614
|
this._writer = undefined;
|
|
2625
2615
|
}
|
|
@@ -2632,7 +2622,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2632
2622
|
writer.releaseLock();
|
|
2633
2623
|
}
|
|
2634
2624
|
catch (err) {
|
|
2635
|
-
this.logger.debug(`Direct writer close error: ${err}`);
|
|
2625
|
+
// this.logger.debug(`Direct writer close error: ${err}`);
|
|
2636
2626
|
}
|
|
2637
2627
|
}
|
|
2638
2628
|
await new Promise((resolve) => {
|
|
@@ -2654,7 +2644,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2654
2644
|
this._reader.cancel();
|
|
2655
2645
|
}
|
|
2656
2646
|
catch (err) {
|
|
2657
|
-
this.logger.debug(`Reader cancel error: ${err}`);
|
|
2647
|
+
// this.logger.debug(`Reader cancel error: ${err}`);
|
|
2658
2648
|
// Reader already released, resolve immediately
|
|
2659
2649
|
clearTimeout(timeout);
|
|
2660
2650
|
resolve(undefined);
|
|
@@ -2691,7 +2681,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2691
2681
|
await this._writeChain;
|
|
2692
2682
|
}
|
|
2693
2683
|
catch (err) {
|
|
2694
|
-
this.logger.debug(`Pending write error during release: ${err}`);
|
|
2684
|
+
// this.logger.debug(`Pending write error during release: ${err}`);
|
|
2695
2685
|
}
|
|
2696
2686
|
// Release writer
|
|
2697
2687
|
if (this._writer) {
|
|
@@ -2795,6 +2785,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2795
2785
|
this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
|
|
2796
2786
|
isUsbJtag = this.isUsbJtagOrOtg;
|
|
2797
2787
|
}
|
|
2788
|
+
// Release reader/writer so console can create new ones
|
|
2789
|
+
// This is needed for Desktop (Web Serial) to unlock streams
|
|
2798
2790
|
if (isUsbJtag) {
|
|
2799
2791
|
// USB-JTAG/OTG devices: Use watchdog reset which closes port
|
|
2800
2792
|
const wasReset = await this._resetToFirmwareIfNeeded();
|
|
@@ -2817,6 +2809,18 @@ export class ESPLoader extends EventTarget {
|
|
|
2817
2809
|
catch (err) {
|
|
2818
2810
|
this.logger.debug(`Could not reset device: ${err}`);
|
|
2819
2811
|
}
|
|
2812
|
+
// For WebUSB (Android), recreate streams after hardware reset
|
|
2813
|
+
if (this.isWebUSB()) {
|
|
2814
|
+
try {
|
|
2815
|
+
// Use the public recreateStreams() method to safely recreate streams
|
|
2816
|
+
// without closing the port (important after hardware reset)
|
|
2817
|
+
await this.port.recreateStreams();
|
|
2818
|
+
this.logger.debug("WebUSB streams recreated for console mode");
|
|
2819
|
+
}
|
|
2820
|
+
catch (err) {
|
|
2821
|
+
this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2820
2824
|
return false; // Port stays open
|
|
2821
2825
|
}
|
|
2822
2826
|
}
|
|
@@ -2837,7 +2841,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2837
2841
|
this.chipFamily === CHIP_FAMILY_ESP32S3
|
|
2838
2842
|
? "USB-JTAG/Serial or USB-OTG"
|
|
2839
2843
|
: "USB-JTAG/Serial";
|
|
2840
|
-
this.logger.log(`Resetting ${this.
|
|
2844
|
+
this.logger.log(`Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`);
|
|
2841
2845
|
// Set console mode flag before reset to prevent subsequent hardReset calls
|
|
2842
2846
|
this._consoleMode = true;
|
|
2843
2847
|
// For S2/S3: Clear force download boot mask before WDT reset
|
|
@@ -2896,7 +2900,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2896
2900
|
}
|
|
2897
2901
|
try {
|
|
2898
2902
|
this.logger.log("Reconnecting serial port...");
|
|
2899
|
-
const savedBaudRate = this.
|
|
2903
|
+
const savedBaudRate = this.currentBaudRate;
|
|
2900
2904
|
this.connected = false;
|
|
2901
2905
|
this.__inputBuffer = [];
|
|
2902
2906
|
this.__inputBufferReadIndex = 0;
|
|
@@ -2942,7 +2946,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2942
2946
|
try {
|
|
2943
2947
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
2944
2948
|
this.connected = true;
|
|
2945
|
-
this.
|
|
2949
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
2946
2950
|
}
|
|
2947
2951
|
catch (err) {
|
|
2948
2952
|
throw new Error(`Failed to open port: ${err}`);
|
|
@@ -3062,7 +3066,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3062
3066
|
try {
|
|
3063
3067
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
3064
3068
|
this.connected = true;
|
|
3065
|
-
this.
|
|
3069
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
3066
3070
|
}
|
|
3067
3071
|
catch (err) {
|
|
3068
3072
|
throw new Error(`Failed to open port: ${err}`);
|
|
@@ -3100,6 +3104,98 @@ export class ESPLoader extends EventTarget {
|
|
|
3100
3104
|
throw err;
|
|
3101
3105
|
}
|
|
3102
3106
|
}
|
|
3107
|
+
/**
|
|
3108
|
+
* @name exitConsoleMode
|
|
3109
|
+
* Exit console mode and return to bootloader
|
|
3110
|
+
* For ESP32-S2, uses reconnectToBootloader which will trigger port change
|
|
3111
|
+
* @returns true if manual reconnection is needed (ESP32-S2), false otherwise
|
|
3112
|
+
*/
|
|
3113
|
+
async exitConsoleMode() {
|
|
3114
|
+
if (this._parent) {
|
|
3115
|
+
return await this._parent.exitConsoleMode();
|
|
3116
|
+
}
|
|
3117
|
+
// Clear console mode flag
|
|
3118
|
+
this._consoleMode = false;
|
|
3119
|
+
// Check if this is ESP32-S2 with USB-JTAG/OTG
|
|
3120
|
+
const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
|
|
3121
|
+
// For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
|
|
3122
|
+
// If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
|
|
3123
|
+
let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
|
|
3124
|
+
if (isESP32S2 && isUsbJtagOrOtg === undefined) {
|
|
3125
|
+
try {
|
|
3126
|
+
isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
3127
|
+
}
|
|
3128
|
+
catch (err) {
|
|
3129
|
+
this.logger.debug(`USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`);
|
|
3130
|
+
isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
if (isESP32S2 && isUsbJtagOrOtg) {
|
|
3134
|
+
// ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
|
|
3135
|
+
// This will close the port and the device will reboot to bootloader
|
|
3136
|
+
this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
|
|
3137
|
+
try {
|
|
3138
|
+
await this.reconnectToBootloader();
|
|
3139
|
+
}
|
|
3140
|
+
catch (err) {
|
|
3141
|
+
this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
|
|
3142
|
+
}
|
|
3143
|
+
// For ESP32-S2, port will change, so return true to indicate manual reconnection needed
|
|
3144
|
+
return true;
|
|
3145
|
+
}
|
|
3146
|
+
// For other devices, use standard reconnectToBootloader
|
|
3147
|
+
await this.reconnectToBootloader();
|
|
3148
|
+
return false; // No manual reconnection needed
|
|
3149
|
+
}
|
|
3150
|
+
/**
|
|
3151
|
+
* @name isConsoleResetSupported
|
|
3152
|
+
* Check if console reset is supported for this device
|
|
3153
|
+
* ESP32-S2 USB-JTAG/CDC does not support reset in console mode
|
|
3154
|
+
* because any reset causes USB port to be lost (hardware limitation)
|
|
3155
|
+
*/
|
|
3156
|
+
isConsoleResetSupported() {
|
|
3157
|
+
if (this._parent) {
|
|
3158
|
+
return this._parent.isConsoleResetSupported();
|
|
3159
|
+
}
|
|
3160
|
+
// For ESP32-S2: if _isUsbJtagOrOtg is undefined, assume USB-JTAG/OTG (conservative)
|
|
3161
|
+
// This means console reset is NOT supported (safer default)
|
|
3162
|
+
const isS2UsbJtag = this.chipFamily === CHIP_FAMILY_ESP32S2 &&
|
|
3163
|
+
(this._isUsbJtagOrOtg === true || this._isUsbJtagOrOtg === undefined);
|
|
3164
|
+
return !isS2UsbJtag; // Not supported for ESP32-S2 USB-JTAG/CDC
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* @name resetInConsoleMode
|
|
3168
|
+
* Reset device while in console mode (firmware mode)
|
|
3169
|
+
*
|
|
3170
|
+
* NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
|
|
3171
|
+
* the USB port to be lost because the device switches USB modes during reset.
|
|
3172
|
+
* This is a hardware limitation - use isConsoleResetSupported() to check first.
|
|
3173
|
+
*/
|
|
3174
|
+
async resetInConsoleMode() {
|
|
3175
|
+
if (this._parent) {
|
|
3176
|
+
return await this._parent.resetInConsoleMode();
|
|
3177
|
+
}
|
|
3178
|
+
if (!this.isConsoleResetSupported()) {
|
|
3179
|
+
this.logger.debug("Console reset not supported for ESP32-S2 USB-JTAG/CDC");
|
|
3180
|
+
return; // Do nothing
|
|
3181
|
+
}
|
|
3182
|
+
// For other devices: Use standard firmware reset
|
|
3183
|
+
const isWebUSB = this.port.isWebUSB === true;
|
|
3184
|
+
try {
|
|
3185
|
+
this.logger.debug("Resetting device in console mode");
|
|
3186
|
+
if (isWebUSB) {
|
|
3187
|
+
await this.hardResetToFirmwareWebUSB();
|
|
3188
|
+
}
|
|
3189
|
+
else {
|
|
3190
|
+
await this.hardResetToFirmware();
|
|
3191
|
+
}
|
|
3192
|
+
this.logger.debug("Device reset complete");
|
|
3193
|
+
}
|
|
3194
|
+
catch (err) {
|
|
3195
|
+
this.logger.error(`Reset failed: ${err}`);
|
|
3196
|
+
throw err;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3103
3199
|
/**
|
|
3104
3200
|
* @name drainInputBuffer
|
|
3105
3201
|
* Actively drain the input buffer by reading data for a specified time.
|
|
@@ -3114,7 +3210,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3114
3210
|
// Wait for the buffer to fill
|
|
3115
3211
|
await sleep(bufferingTime);
|
|
3116
3212
|
// Unsupported command response is sent 8 times and has
|
|
3117
|
-
// 14 bytes length including delimiter 0xC0 bytes.
|
|
3213
|
+
// 14 bytes length including delimiter SLIP_END (0xC0) bytes.
|
|
3118
3214
|
// At least part of it is read as a command response,
|
|
3119
3215
|
// but to be safe, read it all.
|
|
3120
3216
|
const bytesToDrain = 14 * 8;
|
|
@@ -3278,7 +3374,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3278
3374
|
// The stub expects 4 bytes (ACK), if we send less it will break out
|
|
3279
3375
|
try {
|
|
3280
3376
|
// Send SLIP frame with no data (just delimiters)
|
|
3281
|
-
const abortFrame = [
|
|
3377
|
+
const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
|
|
3282
3378
|
await this.writeToStream(abortFrame);
|
|
3283
3379
|
this.logger.debug(`Sent abort frame to stub`);
|
|
3284
3380
|
// Give stub time to process abort
|
package/dist/index.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export { ESPLoader } from "./esp_loader";
|
|
|
5
5
|
export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_END, ESP_MEM_DATA, ESP_SYNC, ESP_WRITE_REG, ESP_READ_REG, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, ESP_SPI_SET_PARAMS, ESP_SPI_ATTACH, ESP_CHANGE_BAUDRATE, ESP_SPI_FLASH_MD5, ESP_GET_SECURITY_INFO, ESP_CHECKSUM_MAGIC, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, ROM_INVALID_RECV_MSG, USB_RAM_BLOCK, ESP_RAM_BLOCK, DEFAULT_TIMEOUT, CHIP_ERASE_TIMEOUT, MAX_TIMEOUT, SYNC_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, MEM_END_ROM_TIMEOUT, FLASH_READ_TIMEOUT, } from "./const";
|
|
6
6
|
export declare const connect: (logger: Logger) => Promise<ESPLoader>;
|
|
7
7
|
export declare const connectWithPort: (port: SerialPort, logger: Logger) => Promise<ESPLoader>;
|
|
8
|
+
export { toHex, sleep, hexFormatter, formatMacAddr } from "./util";
|
package/dist/index.js
CHANGED
package/dist/util.d.ts
CHANGED
|
@@ -11,4 +11,8 @@ export declare const slipEncode: (buffer: number[]) => number[];
|
|
|
11
11
|
export declare const toByteArray: (str: string) => number[];
|
|
12
12
|
export declare const hexFormatter: (bytes: number[]) => string;
|
|
13
13
|
export declare const toHex: (value: number, size?: number) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Format MAC address array to string (e.g., [0xAA, 0xBB, 0xCC] -> "AA:BB:CC:DD:EE:FF")
|
|
16
|
+
*/
|
|
17
|
+
export declare const formatMacAddr: (macAddr: number[]) => string;
|
|
14
18
|
export declare const sleep: (ms: number) => Promise<unknown>;
|
package/dist/util.js
CHANGED
|
@@ -43,4 +43,12 @@ export const toHex = (value, size = 2) => {
|
|
|
43
43
|
return "0x" + hex.padStart(size, "0");
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Format MAC address array to string (e.g., [0xAA, 0xBB, 0xCC] -> "AA:BB:CC:DD:EE:FF")
|
|
48
|
+
*/
|
|
49
|
+
export const formatMacAddr = (macAddr) => {
|
|
50
|
+
return macAddr
|
|
51
|
+
.map((value) => value.toString(16).toUpperCase().padStart(2, "0"))
|
|
52
|
+
.join(":");
|
|
53
|
+
};
|
|
46
54
|
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|