esp32tool 1.3.1 → 1.3.3

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.
Binary file
package/css/style.css CHANGED
@@ -179,36 +179,7 @@ div.clear {
179
179
  display: none;
180
180
  }
181
181
 
182
- /* Console Container */
183
- .console-container {
184
- height: 500px;
185
- overflow: hidden;
186
- transition: height 0.3s ease-in-out, visibility 0.3s ease-in-out;
187
- margin: 0;
188
- padding: 0;
189
- visibility: visible;
190
- }
191
-
192
- .console-container.hidden {
193
- height: 0;
194
- visibility: hidden;
195
- max-height: 0;
196
- }
197
-
198
- /* Mobile console optimization */
199
- @media (max-width: 768px) {
200
- .console-container {
201
- height: 350px;
202
- font-size: 13px;
203
- }
204
- }
205
-
206
- @media (max-width: 480px) {
207
- .console-container {
208
- height: 300px;
209
- font-size: 12px;
210
- }
211
- }
182
+ /* Console Container - OLD DEFINITION REMOVED - See bottom of file for new fullscreen version */
212
183
 
213
184
  .notSupported {
214
185
  padding: 1em;
@@ -2189,11 +2160,7 @@ div.clear {
2189
2160
  padding-right: 10px;
2190
2161
  }
2191
2162
 
2192
- /* Console optimization for small screens */
2193
- .console-container {
2194
- height: 300px;
2195
- font-size: 12px;
2196
- }
2163
+ /* Console optimization for small screens - REMOVED - Using fullscreen console now */
2197
2164
 
2198
2165
  /* Filesystem manager for small screens */
2199
2166
  .littlefs-manager {
@@ -2257,3 +2224,48 @@ div.clear {
2257
2224
  -webkit-tap-highlight-color: transparent;
2258
2225
  }
2259
2226
  }
2227
+
2228
+ /* Console Container Styling */
2229
+ .console-container {
2230
+ position: fixed;
2231
+ top: 0;
2232
+ left: 0;
2233
+ right: 0;
2234
+ bottom: 0;
2235
+ z-index: 1001; /* Above header and main content */
2236
+ background-color: #1c1c1c;
2237
+ margin: 0;
2238
+ padding: 0;
2239
+ }
2240
+
2241
+ .console-container.hidden {
2242
+ display: none;
2243
+ }
2244
+
2245
+ /* Ensure console wrapper fills the container */
2246
+ .console-container .esp32tool-console-wrapper {
2247
+ position: absolute;
2248
+ top: 0;
2249
+ left: 0;
2250
+ right: 0;
2251
+ bottom: 0;
2252
+ }
2253
+
2254
+ /* Hide header and commands when console is active */
2255
+ body.console-active .header {
2256
+ display: none !important;
2257
+ }
2258
+
2259
+ body.console-active #commands {
2260
+ display: none !important;
2261
+ }
2262
+
2263
+ body.console-active #notSupported {
2264
+ display: none !important;
2265
+ }
2266
+
2267
+ /* Remove padding from main when console is active */
2268
+ body.console-active .main {
2269
+ padding-top: 0 !important;
2270
+ overflow: hidden !important;
2271
+ }
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 = 0x60008800 + 0x044;
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;
@@ -19,6 +19,10 @@ export declare class ESPLoader extends EventTarget {
19
19
  currentBaudRate: number;
20
20
  private _maxUSBSerialBaudrate?;
21
21
  private _reader?;
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;
@@ -394,6 +398,14 @@ export declare class ESPLoader extends EventTarget {
394
398
  * @returns true if reset was performed, false if not needed
395
399
  */
396
400
  resetToFirmware(): Promise<boolean>;
401
+ /**
402
+ * @name detectUsbConnectionType
403
+ * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
404
+ * This helper extracts the detection logic from initialize() for reuse
405
+ * @returns true if USB-JTAG or USB-OTG, false if external serial chip
406
+ * @throws Error if detection fails and chipFamily is not set
407
+ */
408
+ private detectUsbConnectionType;
397
409
  /**
398
410
  * @name enterConsoleMode
399
411
  * Prepare device for console mode by resetting to firmware
@@ -419,6 +431,29 @@ export declare class ESPLoader extends EventTarget {
419
431
  * This is needed after Improv or other operations that leave ESP in firmware mode
420
432
  */
421
433
  reconnectToBootloader(): Promise<void>;
434
+ /**
435
+ * @name exitConsoleMode
436
+ * Exit console mode and return to bootloader
437
+ * For ESP32-S2, uses reconnectToBootloader which will trigger port change
438
+ * @returns true if manual reconnection is needed (ESP32-S2), false otherwise
439
+ */
440
+ exitConsoleMode(): Promise<boolean>;
441
+ /**
442
+ * @name isConsoleResetSupported
443
+ * Check if console reset is supported for this device
444
+ * ESP32-S2 USB-JTAG/CDC does not support reset in console mode
445
+ * because any reset causes USB port to be lost (hardware limitation)
446
+ */
447
+ isConsoleResetSupported(): boolean;
448
+ /**
449
+ * @name resetInConsoleMode
450
+ * Reset device while in console mode (firmware mode)
451
+ *
452
+ * NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
453
+ * the USB port to be lost because the device switches USB modes during reset.
454
+ * This is a hardware limitation - use isConsoleResetSupported() to check first.
455
+ */
456
+ resetInConsoleMode(): Promise<void>;
422
457
  /**
423
458
  * @name drainInputBuffer
424
459
  * Actively drain the input buffer by reading data for a specified time.
@@ -27,6 +27,10 @@ export class ESPLoader extends EventTarget {
27
27
  this.connected = true;
28
28
  this.flashSize = null;
29
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, []]);
@@ -346,27 +350,7 @@ export class ESPLoader extends EventTarget {
346
350
  // Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
347
351
  // This is needed to determine the correct reset strategy for console mode
348
352
  try {
349
- if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
350
- this.chipFamily === CHIP_FAMILY_ESP32S3) {
351
- const isUsingUsbOtg = await this.usingUsbOtg();
352
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
353
- this._isUsbJtagOrOtg = isUsingUsbOtg || isUsingUsbJtagSerial;
354
- }
355
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
356
- this.chipFamily === CHIP_FAMILY_ESP32C5 ||
357
- this.chipFamily === CHIP_FAMILY_ESP32C6) {
358
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
359
- this._isUsbJtagOrOtg = isUsingUsbJtagSerial;
360
- }
361
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
362
- const isUsingUsbOtg = await this.usingUsbOtg();
363
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
364
- this._isUsbJtagOrOtg = isUsingUsbOtg || isUsingUsbJtagSerial;
365
- }
366
- else {
367
- // Other chips don't have USB-JTAG/OTG
368
- this._isUsbJtagOrOtg = false;
369
- }
353
+ this._isUsbJtagOrOtg = await this.detectUsbConnectionType();
370
354
  this.logger.debug(`USB connection type: ${this._isUsbJtagOrOtg ? "USB-JTAG/OTG" : "External Serial Chip"}`);
371
355
  }
372
356
  catch (err) {
@@ -445,7 +429,6 @@ export class ESPLoader extends EventTarget {
445
429
  this.chipFamily = chip.family;
446
430
  if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
447
431
  this.chipRevision = await this.getChipRevision();
448
- this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
449
432
  if (this.chipRevision >= 300) {
450
433
  this.chipVariant = "rev300";
451
434
  }
@@ -456,7 +439,6 @@ export class ESPLoader extends EventTarget {
456
439
  }
457
440
  else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
458
441
  this.chipRevision = await this.getChipRevision();
459
- this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
460
442
  }
461
443
  this.logger.debug(`Detected chip via magic value: ${toHex(chipMagicValue >>> 0, 8)} (${this.chipName})`);
462
444
  }
@@ -551,10 +533,7 @@ export class ESPLoader extends EventTarget {
551
533
  }
552
534
  }
553
535
  catch {
554
- // Don't log error if this is an expected disconnect during console mode transition
555
- if (!this._consoleMode) {
556
- this.logger.error("Read loop got disconnected");
557
- }
536
+ // this.logger.error("Read loop got disconnected");
558
537
  }
559
538
  finally {
560
539
  // Always reset reconfiguring flag when read loop ends
@@ -1164,7 +1143,9 @@ export class ESPLoader extends EventTarget {
1164
1143
  }
1165
1144
  catch (error) {
1166
1145
  lastError = error;
1167
- this.logger.debug(`${strategy.name} reset failed: ${error.message}`);
1146
+ // this.logger.debug(
1147
+ // `${strategy.name} reset failed: ${(error as Error).message}`,
1148
+ // );
1168
1149
  // Set abandon flag to stop any in-flight operations
1169
1150
  this._abandonCurrentOperation = true;
1170
1151
  // Wait a bit for in-flight operations to abort
@@ -1305,7 +1286,6 @@ export class ESPLoader extends EventTarget {
1305
1286
  const hi = (word5 >> 23) & 0x07;
1306
1287
  // Combine: upper 3 bits from word5, lower 3 bits from word3
1307
1288
  const revision = (hi << 3) | low;
1308
- this.logger.debug(`ESP32-C3 revision: ${revision}`);
1309
1289
  return revision;
1310
1290
  }
1311
1291
  /**
@@ -1744,44 +1724,44 @@ export class ESPLoader extends EventTarget {
1744
1724
  const waitingFor = partialPacket === null ? "header" : "content";
1745
1725
  throw new SlipReadError("Timed out waiting for packet " + waitingFor);
1746
1726
  }
1747
- const b = this._readByte();
1727
+ const byte = this._readByte();
1748
1728
  if (partialPacket === null) {
1749
1729
  // waiting for packet header
1750
- if (b == 0xc0) {
1730
+ if (byte == this.SLIP_END) {
1751
1731
  partialPacket = [];
1752
1732
  }
1753
1733
  else {
1754
1734
  if (this.debug) {
1755
- this.logger.debug("Read invalid data: " + toHex(b));
1735
+ this.logger.debug("Read invalid data: " + toHex(byte));
1756
1736
  this.logger.debug("Remaining data in serial buffer: " +
1757
1737
  hexFormatter(this._inputBuffer));
1758
1738
  }
1759
- throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
1739
+ throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
1760
1740
  }
1761
1741
  }
1762
1742
  else if (inEscape) {
1763
1743
  // part-way through escape sequence
1764
1744
  inEscape = false;
1765
- if (b == 0xdc) {
1766
- partialPacket.push(0xc0);
1745
+ if (byte == this.SLIP_ESC_END) {
1746
+ partialPacket.push(this.SLIP_END);
1767
1747
  }
1768
- else if (b == 0xdd) {
1769
- partialPacket.push(0xdb);
1748
+ else if (byte == this.SLIP_ESC_ESC) {
1749
+ partialPacket.push(this.SLIP_ESC);
1770
1750
  }
1771
1751
  else {
1772
1752
  if (this.debug) {
1773
- this.logger.debug("Read invalid data: " + toHex(b));
1753
+ this.logger.debug("Read invalid data: " + toHex(byte));
1774
1754
  this.logger.debug("Remaining data in serial buffer: " +
1775
1755
  hexFormatter(this._inputBuffer));
1776
1756
  }
1777
- throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
1757
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1778
1758
  }
1779
1759
  }
1780
- else if (b == 0xdb) {
1760
+ else if (byte == this.SLIP_ESC) {
1781
1761
  // start of escape sequence
1782
1762
  inEscape = true;
1783
1763
  }
1784
- else if (b == 0xc0) {
1764
+ else if (byte == this.SLIP_END) {
1785
1765
  // end of packet
1786
1766
  if (this.debug)
1787
1767
  this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
@@ -1791,7 +1771,7 @@ export class ESPLoader extends EventTarget {
1791
1771
  }
1792
1772
  else {
1793
1773
  // normal byte in packet
1794
- partialPacket.push(b);
1774
+ partialPacket.push(byte);
1795
1775
  }
1796
1776
  }
1797
1777
  }
@@ -1822,44 +1802,44 @@ export class ESPLoader extends EventTarget {
1822
1802
  }
1823
1803
  if (this.debug)
1824
1804
  this.logger.debug("Read " + readBytes.length + " bytes: " + hexFormatter(readBytes));
1825
- for (const b of readBytes) {
1805
+ for (const byte of readBytes) {
1826
1806
  if (partialPacket === null) {
1827
1807
  // waiting for packet header
1828
- if (b == 0xc0) {
1808
+ if (byte == this.SLIP_END) {
1829
1809
  partialPacket = [];
1830
1810
  }
1831
1811
  else {
1832
1812
  if (this.debug) {
1833
- this.logger.debug("Read invalid data: " + toHex(b));
1813
+ this.logger.debug("Read invalid data: " + toHex(byte));
1834
1814
  this.logger.debug("Remaining data in serial buffer: " +
1835
1815
  hexFormatter(this._inputBuffer));
1836
1816
  }
1837
- throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
1817
+ throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
1838
1818
  }
1839
1819
  }
1840
1820
  else if (inEscape) {
1841
1821
  // part-way through escape sequence
1842
1822
  inEscape = false;
1843
- if (b == 0xdc) {
1844
- partialPacket.push(0xc0);
1823
+ if (byte == this.SLIP_ESC_END) {
1824
+ partialPacket.push(this.SLIP_END);
1845
1825
  }
1846
- else if (b == 0xdd) {
1847
- partialPacket.push(0xdb);
1826
+ else if (byte == this.SLIP_ESC_ESC) {
1827
+ partialPacket.push(this.SLIP_ESC);
1848
1828
  }
1849
1829
  else {
1850
1830
  if (this.debug) {
1851
- this.logger.debug("Read invalid data: " + toHex(b));
1831
+ this.logger.debug("Read invalid data: " + toHex(byte));
1852
1832
  this.logger.debug("Remaining data in serial buffer: " +
1853
1833
  hexFormatter(this._inputBuffer));
1854
1834
  }
1855
- throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
1835
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1856
1836
  }
1857
1837
  }
1858
- else if (b == 0xdb) {
1838
+ else if (byte == this.SLIP_ESC) {
1859
1839
  // start of escape sequence
1860
1840
  inEscape = true;
1861
1841
  }
1862
- else if (b == 0xc0) {
1842
+ else if (byte == this.SLIP_END) {
1863
1843
  // end of packet
1864
1844
  if (this.debug)
1865
1845
  this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
@@ -1869,7 +1849,7 @@ export class ESPLoader extends EventTarget {
1869
1849
  }
1870
1850
  else {
1871
1851
  // normal byte in packet
1872
- partialPacket.push(b);
1852
+ partialPacket.push(byte);
1873
1853
  }
1874
1854
  }
1875
1855
  }
@@ -2009,8 +1989,8 @@ export class ESPLoader extends EventTarget {
2009
1989
  this.readLoop();
2010
1990
  }
2011
1991
  catch (e) {
2012
- this.logger.error(`Reconfigure port error: ${e}`);
2013
- 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}`);
2014
1994
  }
2015
1995
  finally {
2016
1996
  // Always reset flag, even on error or early return
@@ -2601,7 +2581,7 @@ export class ESPLoader extends EventTarget {
2601
2581
  return;
2602
2582
  }
2603
2583
  if (!this.port.writable) {
2604
- this.logger.debug("Port already closed, skipping disconnect");
2584
+ // this.logger.debug("Port already closed, skipping disconnect");
2605
2585
  return;
2606
2586
  }
2607
2587
  // Wait for pending writes to complete
@@ -2609,7 +2589,7 @@ export class ESPLoader extends EventTarget {
2609
2589
  await this._writeChain;
2610
2590
  }
2611
2591
  catch (err) {
2612
- this.logger.debug(`Pending write error during disconnect: ${err}`);
2592
+ // this.logger.debug(`Pending write error during disconnect: ${err}`);
2613
2593
  }
2614
2594
  // Release persistent writer before closing
2615
2595
  if (this._writer) {
@@ -2618,7 +2598,7 @@ export class ESPLoader extends EventTarget {
2618
2598
  this._writer.releaseLock();
2619
2599
  }
2620
2600
  catch (err) {
2621
- this.logger.debug(`Writer close/release error: ${err}`);
2601
+ // this.logger.debug(`Writer close/release error: ${err}`);
2622
2602
  }
2623
2603
  this._writer = undefined;
2624
2604
  }
@@ -2631,7 +2611,7 @@ export class ESPLoader extends EventTarget {
2631
2611
  writer.releaseLock();
2632
2612
  }
2633
2613
  catch (err) {
2634
- this.logger.debug(`Direct writer close error: ${err}`);
2614
+ // this.logger.debug(`Direct writer close error: ${err}`);
2635
2615
  }
2636
2616
  }
2637
2617
  await new Promise((resolve) => {
@@ -2653,7 +2633,7 @@ export class ESPLoader extends EventTarget {
2653
2633
  this._reader.cancel();
2654
2634
  }
2655
2635
  catch (err) {
2656
- this.logger.debug(`Reader cancel error: ${err}`);
2636
+ // this.logger.debug(`Reader cancel error: ${err}`);
2657
2637
  // Reader already released, resolve immediately
2658
2638
  clearTimeout(timeout);
2659
2639
  resolve(undefined);
@@ -2690,7 +2670,7 @@ export class ESPLoader extends EventTarget {
2690
2670
  await this._writeChain;
2691
2671
  }
2692
2672
  catch (err) {
2693
- this.logger.debug(`Pending write error during release: ${err}`);
2673
+ // this.logger.debug(`Pending write error during release: ${err}`);
2694
2674
  }
2695
2675
  // Release writer
2696
2676
  if (this._writer) {
@@ -2737,6 +2717,39 @@ export class ESPLoader extends EventTarget {
2737
2717
  async resetToFirmware() {
2738
2718
  return await this._resetToFirmwareIfNeeded();
2739
2719
  }
2720
+ /**
2721
+ * @name detectUsbConnectionType
2722
+ * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
2723
+ * This helper extracts the detection logic from initialize() for reuse
2724
+ * @returns true if USB-JTAG or USB-OTG, false if external serial chip
2725
+ * @throws Error if detection fails and chipFamily is not set
2726
+ */
2727
+ async detectUsbConnectionType() {
2728
+ if (!this.chipFamily) {
2729
+ throw new Error("Cannot detect USB connection type: chipFamily not set");
2730
+ }
2731
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2732
+ this.chipFamily === CHIP_FAMILY_ESP32S3) {
2733
+ const isUsingUsbOtg = await this.usingUsbOtg();
2734
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2735
+ return isUsingUsbOtg || isUsingUsbJtagSerial;
2736
+ }
2737
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
2738
+ this.chipFamily === CHIP_FAMILY_ESP32C5 ||
2739
+ this.chipFamily === CHIP_FAMILY_ESP32C6) {
2740
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2741
+ return isUsingUsbJtagSerial;
2742
+ }
2743
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
2744
+ const isUsingUsbOtg = await this.usingUsbOtg();
2745
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2746
+ return isUsingUsbOtg || isUsingUsbJtagSerial;
2747
+ }
2748
+ else {
2749
+ // Other chips don't have USB-JTAG/OTG
2750
+ return false;
2751
+ }
2752
+ }
2740
2753
  /**
2741
2754
  * @name enterConsoleMode
2742
2755
  * Prepare device for console mode by resetting to firmware
@@ -2746,8 +2759,23 @@ export class ESPLoader extends EventTarget {
2746
2759
  async enterConsoleMode() {
2747
2760
  // Set console mode flag
2748
2761
  this._consoleMode = true;
2749
- // Check device type
2750
- const isUsbJtag = this.isUsbJtagOrOtg === true;
2762
+ // Re-detect USB connection type to ensure we have a definitive value
2763
+ // This handles cases where isUsbJtagOrOtg might be undefined
2764
+ let isUsbJtag;
2765
+ try {
2766
+ isUsbJtag = await this.detectUsbConnectionType();
2767
+ this.logger.debug(`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`);
2768
+ }
2769
+ catch (err) {
2770
+ // If detection fails, fall back to cached value or fail-fast
2771
+ if (this.isUsbJtagOrOtg === undefined) {
2772
+ throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
2773
+ }
2774
+ this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
2775
+ isUsbJtag = this.isUsbJtagOrOtg;
2776
+ }
2777
+ // Release reader/writer so console can create new ones
2778
+ // This is needed for Desktop (Web Serial) to unlock streams
2751
2779
  if (isUsbJtag) {
2752
2780
  // USB-JTAG/OTG devices: Use watchdog reset which closes port
2753
2781
  const wasReset = await this._resetToFirmwareIfNeeded();
@@ -2770,6 +2798,19 @@ export class ESPLoader extends EventTarget {
2770
2798
  catch (err) {
2771
2799
  this.logger.debug(`Could not reset device: ${err}`);
2772
2800
  }
2801
+ // For WebUSB (Android), recreate streams after hardware reset
2802
+ const isWebUSB = this.port.isWebUSB === true;
2803
+ if (isWebUSB) {
2804
+ try {
2805
+ // Use the public recreateStreams() method to safely recreate streams
2806
+ // without closing the port (important after hardware reset)
2807
+ await this.port.recreateStreams();
2808
+ this.logger.debug("WebUSB streams recreated for console mode");
2809
+ }
2810
+ catch (err) {
2811
+ this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
2812
+ }
2813
+ }
2773
2814
  return false; // Port stays open
2774
2815
  }
2775
2816
  }
@@ -2790,7 +2831,7 @@ export class ESPLoader extends EventTarget {
2790
2831
  this.chipFamily === CHIP_FAMILY_ESP32S3
2791
2832
  ? "USB-JTAG/Serial or USB-OTG"
2792
2833
  : "USB-JTAG/Serial";
2793
- this.logger.log(`Resetting ${this.chipFamily} (${resetMethod}) to boot into firmware...`);
2834
+ this.logger.log(`Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`);
2794
2835
  // Set console mode flag before reset to prevent subsequent hardReset calls
2795
2836
  this._consoleMode = true;
2796
2837
  // For S2/S3: Clear force download boot mask before WDT reset
@@ -3053,6 +3094,98 @@ export class ESPLoader extends EventTarget {
3053
3094
  throw err;
3054
3095
  }
3055
3096
  }
3097
+ /**
3098
+ * @name exitConsoleMode
3099
+ * Exit console mode and return to bootloader
3100
+ * For ESP32-S2, uses reconnectToBootloader which will trigger port change
3101
+ * @returns true if manual reconnection is needed (ESP32-S2), false otherwise
3102
+ */
3103
+ async exitConsoleMode() {
3104
+ if (this._parent) {
3105
+ return await this._parent.exitConsoleMode();
3106
+ }
3107
+ // Clear console mode flag
3108
+ this._consoleMode = false;
3109
+ // Check if this is ESP32-S2 with USB-JTAG/OTG
3110
+ const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
3111
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
3112
+ // If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
3113
+ let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
3114
+ if (isESP32S2 && isUsbJtagOrOtg === undefined) {
3115
+ try {
3116
+ isUsbJtagOrOtg = await this.detectUsbConnectionType();
3117
+ }
3118
+ catch (err) {
3119
+ this.logger.debug(`USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`);
3120
+ isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
3121
+ }
3122
+ }
3123
+ if (isESP32S2 && isUsbJtagOrOtg) {
3124
+ // ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
3125
+ // This will close the port and the device will reboot to bootloader
3126
+ this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
3127
+ try {
3128
+ await this.reconnectToBootloader();
3129
+ }
3130
+ catch (err) {
3131
+ this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
3132
+ }
3133
+ // For ESP32-S2, port will change, so return true to indicate manual reconnection needed
3134
+ return true;
3135
+ }
3136
+ // For other devices, use standard reconnectToBootloader
3137
+ await this.reconnectToBootloader();
3138
+ return false; // No manual reconnection needed
3139
+ }
3140
+ /**
3141
+ * @name isConsoleResetSupported
3142
+ * Check if console reset is supported for this device
3143
+ * ESP32-S2 USB-JTAG/CDC does not support reset in console mode
3144
+ * because any reset causes USB port to be lost (hardware limitation)
3145
+ */
3146
+ isConsoleResetSupported() {
3147
+ if (this._parent) {
3148
+ return this._parent.isConsoleResetSupported();
3149
+ }
3150
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, assume USB-JTAG/OTG (conservative)
3151
+ // This means console reset is NOT supported (safer default)
3152
+ const isS2UsbJtag = this.chipFamily === CHIP_FAMILY_ESP32S2 &&
3153
+ (this._isUsbJtagOrOtg === true || this._isUsbJtagOrOtg === undefined);
3154
+ return !isS2UsbJtag; // Not supported for ESP32-S2 USB-JTAG/CDC
3155
+ }
3156
+ /**
3157
+ * @name resetInConsoleMode
3158
+ * Reset device while in console mode (firmware mode)
3159
+ *
3160
+ * NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
3161
+ * the USB port to be lost because the device switches USB modes during reset.
3162
+ * This is a hardware limitation - use isConsoleResetSupported() to check first.
3163
+ */
3164
+ async resetInConsoleMode() {
3165
+ if (this._parent) {
3166
+ return await this._parent.resetInConsoleMode();
3167
+ }
3168
+ if (!this.isConsoleResetSupported()) {
3169
+ this.logger.debug("Console reset not supported for ESP32-S2 USB-JTAG/CDC");
3170
+ return; // Do nothing
3171
+ }
3172
+ // For other devices: Use standard firmware reset
3173
+ const isWebUSB = this.port.isWebUSB === true;
3174
+ try {
3175
+ this.logger.debug("Resetting device in console mode");
3176
+ if (isWebUSB) {
3177
+ await this.hardResetToFirmwareWebUSB();
3178
+ }
3179
+ else {
3180
+ await this.hardResetToFirmware();
3181
+ }
3182
+ this.logger.debug("Device reset complete");
3183
+ }
3184
+ catch (err) {
3185
+ this.logger.error(`Reset failed: ${err}`);
3186
+ throw err;
3187
+ }
3188
+ }
3056
3189
  /**
3057
3190
  * @name drainInputBuffer
3058
3191
  * Actively drain the input buffer by reading data for a specified time.
@@ -3067,7 +3200,7 @@ export class ESPLoader extends EventTarget {
3067
3200
  // Wait for the buffer to fill
3068
3201
  await sleep(bufferingTime);
3069
3202
  // Unsupported command response is sent 8 times and has
3070
- // 14 bytes length including delimiter 0xC0 bytes.
3203
+ // 14 bytes length including delimiter SLIP_END (0xC0) bytes.
3071
3204
  // At least part of it is read as a command response,
3072
3205
  // but to be safe, read it all.
3073
3206
  const bytesToDrain = 14 * 8;
@@ -3231,7 +3364,7 @@ export class ESPLoader extends EventTarget {
3231
3364
  // The stub expects 4 bytes (ACK), if we send less it will break out
3232
3365
  try {
3233
3366
  // Send SLIP frame with no data (just delimiters)
3234
- const abortFrame = [0xc0, 0xc0]; // Empty SLIP frame
3367
+ const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
3235
3368
  await this.writeToStream(abortFrame);
3236
3369
  this.logger.debug(`Sent abort frame to stub`);
3237
3370
  // Give stub time to process abort
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare const connect: (logger: Logger) => Promise<ESPLoader>;
7
7
  export declare const connectWithPort: (port: SerialPort, logger: Logger) => Promise<ESPLoader>;
8
8
  export { parsePartitionTable, getPartitionTableOffset, formatSize, } from "./partition";
9
9
  export type { Partition } from "./partition";
10
+ export { toHex, sleep, hexFormatter, formatMacAddr } from "./util";
10
11
  export { FilesystemType, detectFilesystemType, detectFilesystemFromImage, getDefaultBlockSize, getBlockSizeCandidates, getESP8266FilesystemLayout, scanESP8266Filesystem, LITTLEFS_DEFAULT_BLOCK_SIZE, LITTLEFS_BLOCK_SIZE_CANDIDATES, FATFS_DEFAULT_BLOCK_SIZE, FATFS_BLOCK_SIZE_CANDIDATES, ESP8266_LITTLEFS_BLOCK_SIZE, ESP8266_LITTLEFS_BLOCK_SIZE_CANDIDATES, ESP8266_LITTLEFS_PAGE_SIZE, ESP8266_SPIFFS_PAGE_SIZE, ESP8266_SPIFFS_BLOCK_SIZE, } from "./wasm/filesystems";
11
12
  export type { ESP8266FilesystemLayout } from "./wasm/filesystems";
12
13
  export { SpiffsFS, SpiffsBuildConfig, SpiffsReader, DEFAULT_SPIFFS_CONFIG, type SpiffsFile, type SpiffsBuildConfigOptions, } from "./lib/spiffs/index";
package/dist/index.js CHANGED
@@ -44,5 +44,7 @@ export const connectWithPort = async (port, logger) => {
44
44
  return new ESPLoader(port, logger);
45
45
  };
46
46
  export { parsePartitionTable, getPartitionTableOffset, formatSize, } from "./partition";
47
+ // Export utility functions for use in UI code
48
+ export { toHex, sleep, hexFormatter, formatMacAddr } from "./util";
47
49
  export { FilesystemType, detectFilesystemType, detectFilesystemFromImage, getDefaultBlockSize, getBlockSizeCandidates, getESP8266FilesystemLayout, scanESP8266Filesystem, LITTLEFS_DEFAULT_BLOCK_SIZE, LITTLEFS_BLOCK_SIZE_CANDIDATES, FATFS_DEFAULT_BLOCK_SIZE, FATFS_BLOCK_SIZE_CANDIDATES, ESP8266_LITTLEFS_BLOCK_SIZE, ESP8266_LITTLEFS_BLOCK_SIZE_CANDIDATES, ESP8266_LITTLEFS_PAGE_SIZE, ESP8266_SPIFFS_PAGE_SIZE, ESP8266_SPIFFS_BLOCK_SIZE, } from "./wasm/filesystems";
48
50
  export { SpiffsFS, SpiffsBuildConfig, SpiffsReader, DEFAULT_SPIFFS_CONFIG, } from "./lib/spiffs/index";