esp32tool 1.3.1 → 1.3.2

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
@@ -394,6 +394,14 @@ export declare class ESPLoader extends EventTarget {
394
394
  * @returns true if reset was performed, false if not needed
395
395
  */
396
396
  resetToFirmware(): Promise<boolean>;
397
+ /**
398
+ * @name detectUsbConnectionType
399
+ * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
400
+ * This helper extracts the detection logic from initialize() for reuse
401
+ * @returns true if USB-JTAG or USB-OTG, false if external serial chip
402
+ * @throws Error if detection fails and chipFamily is not set
403
+ */
404
+ private detectUsbConnectionType;
397
405
  /**
398
406
  * @name enterConsoleMode
399
407
  * Prepare device for console mode by resetting to firmware
@@ -419,6 +427,29 @@ export declare class ESPLoader extends EventTarget {
419
427
  * This is needed after Improv or other operations that leave ESP in firmware mode
420
428
  */
421
429
  reconnectToBootloader(): Promise<void>;
430
+ /**
431
+ * @name exitConsoleMode
432
+ * Exit console mode and return to bootloader
433
+ * For ESP32-S2, uses reconnectToBootloader which will trigger port change
434
+ * @returns true if manual reconnection is needed (ESP32-S2), false otherwise
435
+ */
436
+ exitConsoleMode(): Promise<boolean>;
437
+ /**
438
+ * @name isConsoleResetSupported
439
+ * Check if console reset is supported for this device
440
+ * ESP32-S2 USB-JTAG/CDC does not support reset in console mode
441
+ * because any reset causes USB port to be lost (hardware limitation)
442
+ */
443
+ isConsoleResetSupported(): boolean;
444
+ /**
445
+ * @name resetInConsoleMode
446
+ * Reset device while in console mode (firmware mode)
447
+ *
448
+ * NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
449
+ * the USB port to be lost because the device switches USB modes during reset.
450
+ * This is a hardware limitation - use isConsoleResetSupported() to check first.
451
+ */
452
+ resetInConsoleMode(): Promise<void>;
422
453
  /**
423
454
  * @name drainInputBuffer
424
455
  * Actively drain the input buffer by reading data for a specified time.
@@ -346,27 +346,7 @@ export class ESPLoader extends EventTarget {
346
346
  // Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
347
347
  // This is needed to determine the correct reset strategy for console mode
348
348
  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
- }
349
+ this._isUsbJtagOrOtg = await this.detectUsbConnectionType();
370
350
  this.logger.debug(`USB connection type: ${this._isUsbJtagOrOtg ? "USB-JTAG/OTG" : "External Serial Chip"}`);
371
351
  }
372
352
  catch (err) {
@@ -445,7 +425,6 @@ export class ESPLoader extends EventTarget {
445
425
  this.chipFamily = chip.family;
446
426
  if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
447
427
  this.chipRevision = await this.getChipRevision();
448
- this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
449
428
  if (this.chipRevision >= 300) {
450
429
  this.chipVariant = "rev300";
451
430
  }
@@ -456,7 +435,6 @@ export class ESPLoader extends EventTarget {
456
435
  }
457
436
  else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
458
437
  this.chipRevision = await this.getChipRevision();
459
- this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
460
438
  }
461
439
  this.logger.debug(`Detected chip via magic value: ${toHex(chipMagicValue >>> 0, 8)} (${this.chipName})`);
462
440
  }
@@ -551,10 +529,7 @@ export class ESPLoader extends EventTarget {
551
529
  }
552
530
  }
553
531
  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
- }
532
+ // this.logger.error("Read loop got disconnected");
558
533
  }
559
534
  finally {
560
535
  // Always reset reconfiguring flag when read loop ends
@@ -1164,7 +1139,9 @@ export class ESPLoader extends EventTarget {
1164
1139
  }
1165
1140
  catch (error) {
1166
1141
  lastError = error;
1167
- this.logger.debug(`${strategy.name} reset failed: ${error.message}`);
1142
+ // this.logger.debug(
1143
+ // `${strategy.name} reset failed: ${(error as Error).message}`,
1144
+ // );
1168
1145
  // Set abandon flag to stop any in-flight operations
1169
1146
  this._abandonCurrentOperation = true;
1170
1147
  // Wait a bit for in-flight operations to abort
@@ -1305,7 +1282,6 @@ export class ESPLoader extends EventTarget {
1305
1282
  const hi = (word5 >> 23) & 0x07;
1306
1283
  // Combine: upper 3 bits from word5, lower 3 bits from word3
1307
1284
  const revision = (hi << 3) | low;
1308
- this.logger.debug(`ESP32-C3 revision: ${revision}`);
1309
1285
  return revision;
1310
1286
  }
1311
1287
  /**
@@ -2009,8 +1985,8 @@ export class ESPLoader extends EventTarget {
2009
1985
  this.readLoop();
2010
1986
  }
2011
1987
  catch (e) {
2012
- this.logger.error(`Reconfigure port error: ${e}`);
2013
- throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
1988
+ // this.logger.error(`Reconfigure port error: ${e}`);
1989
+ // throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
2014
1990
  }
2015
1991
  finally {
2016
1992
  // Always reset flag, even on error or early return
@@ -2601,7 +2577,7 @@ export class ESPLoader extends EventTarget {
2601
2577
  return;
2602
2578
  }
2603
2579
  if (!this.port.writable) {
2604
- this.logger.debug("Port already closed, skipping disconnect");
2580
+ // this.logger.debug("Port already closed, skipping disconnect");
2605
2581
  return;
2606
2582
  }
2607
2583
  // Wait for pending writes to complete
@@ -2609,7 +2585,7 @@ export class ESPLoader extends EventTarget {
2609
2585
  await this._writeChain;
2610
2586
  }
2611
2587
  catch (err) {
2612
- this.logger.debug(`Pending write error during disconnect: ${err}`);
2588
+ // this.logger.debug(`Pending write error during disconnect: ${err}`);
2613
2589
  }
2614
2590
  // Release persistent writer before closing
2615
2591
  if (this._writer) {
@@ -2618,7 +2594,7 @@ export class ESPLoader extends EventTarget {
2618
2594
  this._writer.releaseLock();
2619
2595
  }
2620
2596
  catch (err) {
2621
- this.logger.debug(`Writer close/release error: ${err}`);
2597
+ // this.logger.debug(`Writer close/release error: ${err}`);
2622
2598
  }
2623
2599
  this._writer = undefined;
2624
2600
  }
@@ -2631,7 +2607,7 @@ export class ESPLoader extends EventTarget {
2631
2607
  writer.releaseLock();
2632
2608
  }
2633
2609
  catch (err) {
2634
- this.logger.debug(`Direct writer close error: ${err}`);
2610
+ // this.logger.debug(`Direct writer close error: ${err}`);
2635
2611
  }
2636
2612
  }
2637
2613
  await new Promise((resolve) => {
@@ -2653,7 +2629,7 @@ export class ESPLoader extends EventTarget {
2653
2629
  this._reader.cancel();
2654
2630
  }
2655
2631
  catch (err) {
2656
- this.logger.debug(`Reader cancel error: ${err}`);
2632
+ // this.logger.debug(`Reader cancel error: ${err}`);
2657
2633
  // Reader already released, resolve immediately
2658
2634
  clearTimeout(timeout);
2659
2635
  resolve(undefined);
@@ -2690,7 +2666,7 @@ export class ESPLoader extends EventTarget {
2690
2666
  await this._writeChain;
2691
2667
  }
2692
2668
  catch (err) {
2693
- this.logger.debug(`Pending write error during release: ${err}`);
2669
+ // this.logger.debug(`Pending write error during release: ${err}`);
2694
2670
  }
2695
2671
  // Release writer
2696
2672
  if (this._writer) {
@@ -2737,6 +2713,39 @@ export class ESPLoader extends EventTarget {
2737
2713
  async resetToFirmware() {
2738
2714
  return await this._resetToFirmwareIfNeeded();
2739
2715
  }
2716
+ /**
2717
+ * @name detectUsbConnectionType
2718
+ * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
2719
+ * This helper extracts the detection logic from initialize() for reuse
2720
+ * @returns true if USB-JTAG or USB-OTG, false if external serial chip
2721
+ * @throws Error if detection fails and chipFamily is not set
2722
+ */
2723
+ async detectUsbConnectionType() {
2724
+ if (!this.chipFamily) {
2725
+ throw new Error("Cannot detect USB connection type: chipFamily not set");
2726
+ }
2727
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2728
+ this.chipFamily === CHIP_FAMILY_ESP32S3) {
2729
+ const isUsingUsbOtg = await this.usingUsbOtg();
2730
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2731
+ return isUsingUsbOtg || isUsingUsbJtagSerial;
2732
+ }
2733
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
2734
+ this.chipFamily === CHIP_FAMILY_ESP32C5 ||
2735
+ this.chipFamily === CHIP_FAMILY_ESP32C6) {
2736
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2737
+ return isUsingUsbJtagSerial;
2738
+ }
2739
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
2740
+ const isUsingUsbOtg = await this.usingUsbOtg();
2741
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2742
+ return isUsingUsbOtg || isUsingUsbJtagSerial;
2743
+ }
2744
+ else {
2745
+ // Other chips don't have USB-JTAG/OTG
2746
+ return false;
2747
+ }
2748
+ }
2740
2749
  /**
2741
2750
  * @name enterConsoleMode
2742
2751
  * Prepare device for console mode by resetting to firmware
@@ -2746,8 +2755,21 @@ export class ESPLoader extends EventTarget {
2746
2755
  async enterConsoleMode() {
2747
2756
  // Set console mode flag
2748
2757
  this._consoleMode = true;
2749
- // Check device type
2750
- const isUsbJtag = this.isUsbJtagOrOtg === true;
2758
+ // Re-detect USB connection type to ensure we have a definitive value
2759
+ // This handles cases where isUsbJtagOrOtg might be undefined
2760
+ let isUsbJtag;
2761
+ try {
2762
+ isUsbJtag = await this.detectUsbConnectionType();
2763
+ this.logger.debug(`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`);
2764
+ }
2765
+ catch (err) {
2766
+ // If detection fails, fall back to cached value or fail-fast
2767
+ if (this.isUsbJtagOrOtg === undefined) {
2768
+ throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
2769
+ }
2770
+ this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
2771
+ isUsbJtag = this.isUsbJtagOrOtg;
2772
+ }
2751
2773
  if (isUsbJtag) {
2752
2774
  // USB-JTAG/OTG devices: Use watchdog reset which closes port
2753
2775
  const wasReset = await this._resetToFirmwareIfNeeded();
@@ -2790,7 +2812,7 @@ export class ESPLoader extends EventTarget {
2790
2812
  this.chipFamily === CHIP_FAMILY_ESP32S3
2791
2813
  ? "USB-JTAG/Serial or USB-OTG"
2792
2814
  : "USB-JTAG/Serial";
2793
- this.logger.log(`Resetting ${this.chipFamily} (${resetMethod}) to boot into firmware...`);
2815
+ this.logger.log(`Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`);
2794
2816
  // Set console mode flag before reset to prevent subsequent hardReset calls
2795
2817
  this._consoleMode = true;
2796
2818
  // For S2/S3: Clear force download boot mask before WDT reset
@@ -3053,6 +3075,98 @@ export class ESPLoader extends EventTarget {
3053
3075
  throw err;
3054
3076
  }
3055
3077
  }
3078
+ /**
3079
+ * @name exitConsoleMode
3080
+ * Exit console mode and return to bootloader
3081
+ * For ESP32-S2, uses reconnectToBootloader which will trigger port change
3082
+ * @returns true if manual reconnection is needed (ESP32-S2), false otherwise
3083
+ */
3084
+ async exitConsoleMode() {
3085
+ if (this._parent) {
3086
+ return await this._parent.exitConsoleMode();
3087
+ }
3088
+ // Clear console mode flag
3089
+ this._consoleMode = false;
3090
+ // Check if this is ESP32-S2 with USB-JTAG/OTG
3091
+ const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
3092
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
3093
+ // If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
3094
+ let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
3095
+ if (isESP32S2 && isUsbJtagOrOtg === undefined) {
3096
+ try {
3097
+ isUsbJtagOrOtg = await this.detectUsbConnectionType();
3098
+ }
3099
+ catch (err) {
3100
+ this.logger.debug(`USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`);
3101
+ isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
3102
+ }
3103
+ }
3104
+ if (isESP32S2 && isUsbJtagOrOtg) {
3105
+ // ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
3106
+ // This will close the port and the device will reboot to bootloader
3107
+ this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
3108
+ try {
3109
+ await this.reconnectToBootloader();
3110
+ }
3111
+ catch (err) {
3112
+ this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
3113
+ }
3114
+ // For ESP32-S2, port will change, so return true to indicate manual reconnection needed
3115
+ return true;
3116
+ }
3117
+ // For other devices, use standard reconnectToBootloader
3118
+ await this.reconnectToBootloader();
3119
+ return false; // No manual reconnection needed
3120
+ }
3121
+ /**
3122
+ * @name isConsoleResetSupported
3123
+ * Check if console reset is supported for this device
3124
+ * ESP32-S2 USB-JTAG/CDC does not support reset in console mode
3125
+ * because any reset causes USB port to be lost (hardware limitation)
3126
+ */
3127
+ isConsoleResetSupported() {
3128
+ if (this._parent) {
3129
+ return this._parent.isConsoleResetSupported();
3130
+ }
3131
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, assume USB-JTAG/OTG (conservative)
3132
+ // This means console reset is NOT supported (safer default)
3133
+ const isS2UsbJtag = this.chipFamily === CHIP_FAMILY_ESP32S2 &&
3134
+ (this._isUsbJtagOrOtg === true || this._isUsbJtagOrOtg === undefined);
3135
+ return !isS2UsbJtag; // Not supported for ESP32-S2 USB-JTAG/CDC
3136
+ }
3137
+ /**
3138
+ * @name resetInConsoleMode
3139
+ * Reset device while in console mode (firmware mode)
3140
+ *
3141
+ * NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
3142
+ * the USB port to be lost because the device switches USB modes during reset.
3143
+ * This is a hardware limitation - use isConsoleResetSupported() to check first.
3144
+ */
3145
+ async resetInConsoleMode() {
3146
+ if (this._parent) {
3147
+ return await this._parent.resetInConsoleMode();
3148
+ }
3149
+ if (!this.isConsoleResetSupported()) {
3150
+ this.logger.debug("Console reset not supported for ESP32-S2 USB-JTAG/CDC");
3151
+ return; // Do nothing
3152
+ }
3153
+ // For other devices: Use standard firmware reset
3154
+ const isWebUSB = this.port.isWebUSB === true;
3155
+ try {
3156
+ this.logger.debug("Resetting device in console mode");
3157
+ if (isWebUSB) {
3158
+ await this.hardResetToFirmwareWebUSB();
3159
+ }
3160
+ else {
3161
+ await this.hardResetToFirmware();
3162
+ }
3163
+ this.logger.debug("Device reset complete");
3164
+ }
3165
+ catch (err) {
3166
+ this.logger.error(`Reset failed: ${err}`);
3167
+ throw err;
3168
+ }
3169
+ }
3056
3170
  /**
3057
3171
  * @name drainInputBuffer
3058
3172
  * Actively drain the input buffer by reading data for a specified time.
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";
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));