tasmota-webserial-esptool 9.2.9 → 9.2.11

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 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;
@@ -16,9 +16,13 @@ export declare class ESPLoader extends EventTarget {
16
16
  __inputBuffer?: number[];
17
17
  __inputBufferReadIndex?: number;
18
18
  __totalBytesRead?: number;
19
- private __currentBaudRate;
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
  /**
@@ -401,9 +403,9 @@ export declare class ESPLoader extends EventTarget {
401
403
  /**
402
404
  * @name detectUsbConnectionType
403
405
  * 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
406
+ * Uses USB PID (Product ID) for reliable detection
405
407
  * @returns true if USB-JTAG or USB-OTG, false if external serial chip
406
- * @throws Error if detection fails and chipFamily is not set
408
+ * @throws Error if chipFamily is not set
407
409
  */
408
410
  private detectUsbConnectionType;
409
411
  /**
@@ -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.
@@ -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.__currentBaudRate = ESP_ROM_BAUD;
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
- // Don't log error if this is an expected disconnect during console mode transition
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(`${strategy.name} reset failed: ${error.message}`);
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 b = this._readByte();
1727
+ const byte = this._readByte();
1725
1728
  if (partialPacket === null) {
1726
1729
  // waiting for packet header
1727
- if (b == 0xc0) {
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(b));
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(b) + ")");
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 (b == 0xdc) {
1743
- partialPacket.push(0xc0);
1745
+ if (byte == this.SLIP_ESC_END) {
1746
+ partialPacket.push(this.SLIP_END);
1744
1747
  }
1745
- else if (b == 0xdd) {
1746
- partialPacket.push(0xdb);
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(b));
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(b) + ")");
1757
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1755
1758
  }
1756
1759
  }
1757
- else if (b == 0xdb) {
1760
+ else if (byte == this.SLIP_ESC) {
1758
1761
  // start of escape sequence
1759
1762
  inEscape = true;
1760
1763
  }
1761
- else if (b == 0xc0) {
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(b);
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 b of readBytes) {
1805
+ for (const byte of readBytes) {
1803
1806
  if (partialPacket === null) {
1804
1807
  // waiting for packet header
1805
- if (b == 0xc0) {
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(b));
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(b) + ")");
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 (b == 0xdc) {
1821
- partialPacket.push(0xc0);
1823
+ if (byte == this.SLIP_ESC_END) {
1824
+ partialPacket.push(this.SLIP_END);
1822
1825
  }
1823
- else if (b == 0xdd) {
1824
- partialPacket.push(0xdb);
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(b));
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(b) + ")");
1835
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1833
1836
  }
1834
1837
  }
1835
- else if (b == 0xdb) {
1838
+ else if (byte == this.SLIP_ESC) {
1836
1839
  // start of escape sequence
1837
1840
  inEscape = true;
1838
1841
  }
1839
- else if (b == 0xc0) {
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(b);
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._currentBaudRate = baud;
1917
+ this._parent.currentBaudRate = baud;
1915
1918
  }
1916
1919
  else {
1917
- this._currentBaudRate = baud;
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) {
@@ -2741,35 +2731,35 @@ export class ESPLoader extends EventTarget {
2741
2731
  /**
2742
2732
  * @name detectUsbConnectionType
2743
2733
  * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
2744
- * This helper extracts the detection logic from initialize() for reuse
2734
+ * Uses USB PID (Product ID) for reliable detection
2745
2735
  * @returns true if USB-JTAG or USB-OTG, false if external serial chip
2746
- * @throws Error if detection fails and chipFamily is not set
2736
+ * @throws Error if chipFamily is not set
2747
2737
  */
2748
2738
  async detectUsbConnectionType() {
2749
2739
  if (!this.chipFamily) {
2750
2740
  throw new Error("Cannot detect USB connection type: chipFamily not set");
2751
2741
  }
2752
- if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2753
- this.chipFamily === CHIP_FAMILY_ESP32S3) {
2754
- const isUsingUsbOtg = await this.usingUsbOtg();
2755
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2756
- return isUsingUsbOtg || isUsingUsbJtagSerial;
2757
- }
2758
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
2759
- this.chipFamily === CHIP_FAMILY_ESP32C5 ||
2760
- this.chipFamily === CHIP_FAMILY_ESP32C6) {
2761
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2762
- return isUsingUsbJtagSerial;
2763
- }
2764
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
2765
- const isUsingUsbOtg = await this.usingUsbOtg();
2766
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2767
- return isUsingUsbOtg || isUsingUsbJtagSerial;
2768
- }
2769
- else {
2770
- // Other chips don't have USB-JTAG/OTG
2742
+ // Use PID-based detection (most reliable method)
2743
+ const portInfo = this.port.getInfo();
2744
+ const pid = portInfo.usbProductId;
2745
+ const vid = portInfo.usbVendorId;
2746
+ this.logger.debug(`USB detection: VID=0x${vid === null || vid === void 0 ? void 0 : vid.toString(16)}, PID=0x${pid === null || pid === void 0 ? void 0 : pid.toString(16)}`);
2747
+ // Check if this is an Espressif device
2748
+ const isEspressif = vid === 0x303a;
2749
+ if (!isEspressif) {
2750
+ this.logger.debug("Not Espressif VID - external serial chip");
2771
2751
  return false;
2772
2752
  }
2753
+ // ESP32-S2/S3/C3/C6/H2 USB-JTAG/OTG PIDs
2754
+ // 0x1001 = ESP32-S3 USB-JTAG
2755
+ // 0x0002 = ESP32-S2 USB-JTAG
2756
+ // 0x1000 = ESP32-C3 USB-JTAG
2757
+ // 0x4008 = ESP32-C6 USB-JTAG
2758
+ // 0x4009 = ESP32-H2 USB-JTAG
2759
+ const usbJtagPids = [0x1001, 0x0002, 0x1000, 0x4008, 0x4009];
2760
+ const isUsbJtag = usbJtagPids.includes(pid || 0);
2761
+ this.logger.debug(`USB-JTAG/OTG detection: ${isUsbJtag ? "YES" : "NO"} (PID=0x${pid === null || pid === void 0 ? void 0 : pid.toString(16)})`);
2762
+ return isUsbJtag;
2773
2763
  }
2774
2764
  /**
2775
2765
  * @name enterConsoleMode
@@ -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.chipFamily} (${resetMethod}) to boot into firmware...`);
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._currentBaudRate;
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._currentBaudRate = ESP_ROM_BAUD;
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._currentBaudRate = ESP_ROM_BAUD;
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 = [0xc0, 0xc0]; // Empty SLIP frame
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
@@ -43,3 +43,5 @@ export const connectWithPort = async (port, logger) => {
43
43
  }
44
44
  return new ESPLoader(port, logger);
45
45
  };
46
+ // Export utility functions for use in UI code
47
+ export { toHex, sleep, hexFormatter, formatMacAddr } from "./util";
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));