tasmota-webserial-esptool 9.2.19 → 9.2.21

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.
@@ -1,7 +1,7 @@
1
1
  /// <reference types="@types/w3c-web-serial" />
2
- import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, 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, CHIP_FAMILY_ESP8266, MAX_TIMEOUT, DEFAULT_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, ESP_CHANGE_BAUDRATE, ESP_CHECKSUM_MAGIC, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_DATA, ESP_MEM_END, ESP_READ_REG, ESP_WRITE_REG, ESP_SPI_ATTACH, ESP_SYNC, ESP_GET_SECURITY_INFO, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE, STUB_FLASH_WRITE_SIZE, MEM_END_ROM_TIMEOUT, ROM_INVALID_RECV_MSG, SYNC_PACKET, SYNC_TIMEOUT, USB_RAM_BLOCK, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, CHIP_ERASE_TIMEOUT, FLASH_READ_TIMEOUT, timeoutPerMb, ESP_ROM_BAUD, USB_JTAG_SERIAL_PID, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, getSpiFlashAddresses, DETECTED_FLASH_SIZES, CHIP_DETECT_MAGIC_REG_ADDR, CHIP_DETECT_MAGIC_VALUES, CHIP_ID_TO_INFO, ESP32P4_EFUSE_BLOCK1_ADDR, SlipReadError, ESP32S2_RTC_CNTL_WDTWPROTECT_REG, ESP32S2_RTC_CNTL_WDTCONFIG0_REG, ESP32S2_RTC_CNTL_WDTCONFIG1_REG, ESP32S2_RTC_CNTL_WDT_WKEY, ESP32S2_RTC_CNTL_OPTION1_REG, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32S3_RTC_CNTL_WDTWPROTECT_REG, ESP32S3_RTC_CNTL_WDTCONFIG0_REG, ESP32S3_RTC_CNTL_WDTCONFIG1_REG, ESP32S3_RTC_CNTL_WDT_WKEY, ESP32S3_RTC_CNTL_OPTION1_REG, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG, ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG, ESP32C3_RTC_CNTL_WDTWPROTECT_REG, ESP32C3_RTC_CNTL_WDTCONFIG0_REG, ESP32C3_RTC_CNTL_WDTCONFIG1_REG, ESP32C3_RTC_CNTL_WDT_WKEY, ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG, ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG, ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG, ESP32C5_C6_RTC_CNTL_WDT_WKEY, ESP32P4_RTC_CNTL_WDTWPROTECT_REG, ESP32P4_RTC_CNTL_WDTCONFIG0_REG, ESP32P4_RTC_CNTL_WDTCONFIG1_REG, ESP32P4_RTC_CNTL_WDT_WKEY, ESP32P4_RTC_CNTL_OPTION1_REG, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32P4_LP_SYSTEM_REG_ANA_XPD_PAD_GROUP_REG, ESP32P4_PMU_EXT_LDO_P0_0P1A_ANA_REG, ESP32P4_PMU_ANA_0P1A_EN_CUR_LIM_0, ESP32P4_PMU_EXT_LDO_P0_0P1A_REG, ESP32P4_PMU_0P1A_TARGET0_0, ESP32P4_PMU_0P1A_FORCE_TIEH_SEL_0, ESP32P4_PMU_DATE_REG, ESP32C5_PCR_SYSCLK_CONF_REG, ESP32C5_PCR_SYSCLK_XTAL_FREQ_V, ESP32C5_PCR_SYSCLK_XTAL_FREQ_S, ESP32C5_UART_CLKDIV_REG, ESP32S2_UARTDEV_BUF_NO, ESP32S2_UARTDEV_BUF_NO_USB_OTG, ESP32S3_UARTDEV_BUF_NO, ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32S3_UARTDEV_BUF_NO_USB_OTG, ESP32C3_BUF_UART_NO_OFFSET, ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C5_UARTDEV_BUF_NO, ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C6_UARTDEV_BUF_NO, ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C61_UARTDEV_BUF_NO_REV_LE2, ESP32C61_UARTDEV_BUF_NO_REV_GT2, ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_LE2, ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_GT2, ESP32H2_UARTDEV_BUF_NO, ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32H4_UARTDEV_BUF_NO, ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32P4_UARTDEV_BUF_NO_REV0, ESP32P4_UARTDEV_BUF_NO_REV300, ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32P4_UARTDEV_BUF_NO_USB_OTG, } from "./const";
2
+ import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, 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, CHIP_FAMILY_ESP8266, MAX_TIMEOUT, DEFAULT_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, ESP_CHANGE_BAUDRATE, ESP_CHECKSUM_MAGIC, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_DATA, ESP_MEM_END, ESP_READ_REG, ESP_WRITE_REG, ESP_SPI_ATTACH, ESP_SYNC, ESP_GET_SECURITY_INFO, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE, STUB_FLASH_WRITE_SIZE, MEM_END_ROM_TIMEOUT, ROM_INVALID_RECV_MSG, SYNC_PACKET, SYNC_TIMEOUT, USB_RAM_BLOCK, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, CHIP_ERASE_TIMEOUT, FLASH_READ_TIMEOUT, timeoutPerMb, ESP_ROM_BAUD, USB_JTAG_SERIAL_PID, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, getSpiFlashAddresses, DETECTED_FLASH_SIZES, CHIP_DETECT_MAGIC_REG_ADDR, CHIP_DETECT_MAGIC_VALUES, CHIP_ID_TO_INFO, ESP32_BASEFUSEADDR, ESP32_APB_CTL_DATE_ADDR, ESP32S2_EFUSE_BLOCK1_ADDR, ESP32S3_EFUSE_BLOCK1_ADDR, ESP32C2_EFUSE_BLOCK2_ADDR, ESP32C5_EFUSE_BLOCK1_ADDR, ESP32C6_EFUSE_BLOCK1_ADDR, ESP32C61_EFUSE_BLOCK1_ADDR, ESP32H2_EFUSE_BLOCK1_ADDR, ESP32P4_EFUSE_BLOCK1_ADDR, ESP32S31_EFUSE_BLOCK1_ADDR, SlipReadError, ESP32S2_RTC_CNTL_WDTWPROTECT_REG, ESP32S2_RTC_CNTL_WDTCONFIG0_REG, ESP32S2_RTC_CNTL_WDTCONFIG1_REG, ESP32S2_RTC_CNTL_WDT_WKEY, ESP32S2_RTC_CNTL_OPTION1_REG, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32S3_RTC_CNTL_WDTWPROTECT_REG, ESP32S3_RTC_CNTL_WDTCONFIG0_REG, ESP32S3_RTC_CNTL_WDTCONFIG1_REG, ESP32S3_RTC_CNTL_WDT_WKEY, ESP32S3_RTC_CNTL_OPTION1_REG, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG, ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG, ESP32C5_UART_CLKDIV_REG, ESP32C5_PCR_SYSCLK_CONF_REG, ESP32C5_PCR_SYSCLK_XTAL_FREQ_V, ESP32C5_PCR_SYSCLK_XTAL_FREQ_S, ESP32P4_RTC_CNTL_WDTWPROTECT_REG, ESP32P4_RTC_CNTL_WDTCONFIG0_REG, ESP32P4_RTC_CNTL_WDTCONFIG1_REG, ESP32P4_RTC_CNTL_WDT_WKEY, ESP32P4_RTC_CNTL_OPTION1_REG, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32P4_LP_SYSTEM_REG_ANA_XPD_PAD_GROUP_REG, ESP32P4_PMU_EXT_LDO_P0_0P1A_ANA_REG, ESP32P4_PMU_ANA_0P1A_EN_CUR_LIM_0, ESP32P4_PMU_EXT_LDO_P0_0P1A_REG, ESP32P4_PMU_0P1A_TARGET0_0, ESP32P4_PMU_0P1A_FORCE_TIEH_SEL_0, ESP32P4_PMU_DATE_REG, ESP32S2_UARTDEV_BUF_NO, ESP32S2_UARTDEV_BUF_NO_USB_OTG, ESP32S3_UARTDEV_BUF_NO, ESP32S3_UARTDEV_BUF_NO_USB_OTG, ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C3_BUF_UART_NO_OFFSET, ESP32C5_UARTDEV_BUF_NO, ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C6_UARTDEV_BUF_NO, ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C61_UARTDEV_BUF_NO_REV_LE2, ESP32C61_UARTDEV_BUF_NO_REV_GT2, ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_LE2, ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_GT2, ESP32H2_UARTDEV_BUF_NO, ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32H4_UARTDEV_BUF_NO, ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32P4_UARTDEV_BUF_NO_REV0, ESP32P4_UARTDEV_BUF_NO_REV300, ESP32P4_UARTDEV_BUF_NO_USB_OTG, ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL, } from "./const";
3
3
  import { getStubCode } from "./stubs";
4
- import { hexFormatter, sleep, slipEncode, toHex } from "./util";
4
+ import { hexFormatter, padTo, sleep, slipEncode, toHex } from "./util";
5
5
  import { deflate } from "pako";
6
6
  import { pack, unpack } from "./struct";
7
7
  export class ESPLoader extends EventTarget {
@@ -357,7 +357,13 @@ export class ESPLoader extends EventTarget {
357
357
  }
358
358
  catch (err) {
359
359
  this.logger.debug(`Could not detect USB connection type: ${err}`);
360
- // Leave as undefined if detection fails
360
+ }
361
+ try {
362
+ const usbMode = await this.getUsbMode();
363
+ this.logger.debug(`USB mode (register): ${usbMode.mode} (uartNo=${usbMode.uartNo})`);
364
+ }
365
+ catch (err) {
366
+ this.logger.debug(`Could not detect USB mode: ${err}`);
361
367
  }
362
368
  // Read the OTP data for this chip and store into this.efuses array
363
369
  const FlAddr = getSpiFlashAddresses(this.getChipFamily());
@@ -365,7 +371,10 @@ export class ESPLoader extends EventTarget {
365
371
  for (let i = 0; i < 4; i++) {
366
372
  this._efuses[i] = await this.readRegister(AddrMAC + 4 * i);
367
373
  }
368
- this.logger.log(`Chip type ${this.chipName}`);
374
+ const revisionInfo = this.chipRevision !== null && this.chipRevision !== undefined
375
+ ? ` (revision ${this.chipRevision})`
376
+ : "";
377
+ this.logger.log(`Connected to ${this.chipName}${revisionInfo}`);
369
378
  this.logger.debug(`Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`);
370
379
  // Mark initialization as successful
371
380
  this._initializationSucceeded = true;
@@ -382,22 +391,14 @@ export class ESPLoader extends EventTarget {
382
391
  if (chipInfo) {
383
392
  this.chipName = chipInfo.name;
384
393
  this.chipFamily = chipInfo.family;
385
- // Get chip revision for ESP32-P4 and ESP32-C3
386
- if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
387
- this.chipRevision = await this.getChipRevision();
388
- this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
389
- // Set chip variant based on revision
390
- if (this.chipRevision >= 300) {
391
- this.chipVariant = "rev300";
392
- }
393
- else {
394
- this.chipVariant = "rev0";
395
- }
396
- this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
394
+ this.chipRevision = await this.getChipRevision();
395
+ this.logger.debug(`${this.chipName} revision: ${this.chipRevision}`);
396
+ if (this.chipFamily === CHIP_FAMILY_ESP32P4 &&
397
+ this.chipRevision >= 300) {
398
+ this.chipVariant = "rev300";
397
399
  }
398
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
399
- this.chipRevision = await this.getChipRevision();
400
- this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
400
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
401
+ this.chipVariant = "rev0";
401
402
  }
402
403
  this.logger.debug(`Detected chip via IMAGE_CHIP_ID: ${chipId} (${this.chipName})`);
403
404
  return;
@@ -429,40 +430,109 @@ export class ESPLoader extends EventTarget {
429
430
  }
430
431
  this.chipName = chip.name;
431
432
  this.chipFamily = chip.family;
433
+ this.chipRevision = await this.getChipRevision();
434
+ this.logger.debug(`${this.chipName} revision: ${this.chipRevision}`);
432
435
  if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
433
- this.chipRevision = await this.getChipRevision();
434
- if (this.chipRevision >= 300) {
435
- this.chipVariant = "rev300";
436
- }
437
- else {
438
- this.chipVariant = "rev0";
439
- }
436
+ this.chipVariant = this.chipRevision >= 300 ? "rev300" : "rev0";
440
437
  this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
441
438
  }
442
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
443
- this.chipRevision = await this.getChipRevision();
444
- }
445
439
  this.logger.debug(`Detected chip via magic value: ${toHex(chipMagicValue >>> 0, 8)} (${this.chipName})`);
446
440
  }
447
- /**
448
- * Get chip revision for ESP32-P4
449
- */
450
441
  async getChipRevision() {
451
- if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
452
- // Read from EFUSE_BLOCK1 to get chip revision
453
- // Word 2 contains revision info for ESP32-P4
454
- const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
455
- // Minor revision: bits [3:0]
456
- const minorRev = word2 & 0x0f;
457
- // Major revision: bits [23] << 2 | bits [5:4]
458
- const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
459
- // Revision is major * 100 + minor
460
- return majorRev * 100 + minorRev;
461
- }
462
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
463
- return await this.getChipRevisionC3();
442
+ var _a;
443
+ let minor = 0;
444
+ let major = 0;
445
+ switch (this.chipFamily) {
446
+ case CHIP_FAMILY_ESP32: {
447
+ const efuse3 = await this.readRegister(ESP32_BASEFUSEADDR + 4 * 3);
448
+ const efuse5 = await this.readRegister(ESP32_BASEFUSEADDR + 4 * 5);
449
+ minor = (efuse5 >> 24) & 0x3;
450
+ const revBit0 = (efuse3 >> 15) & 0x1;
451
+ const revBit1 = (efuse5 >> 20) & 0x1;
452
+ const apb = await this.readRegister(ESP32_APB_CTL_DATE_ADDR);
453
+ const revBit2 = (apb >> 31) & 0x1;
454
+ const combined = (revBit2 << 2) | (revBit1 << 1) | revBit0;
455
+ major =
456
+ (_a = { 0: 0, 1: 1, 3: 2, 7: 3 }[combined]) !== null && _a !== void 0 ? _a : 0;
457
+ break;
458
+ }
459
+ case CHIP_FAMILY_ESP32S2: {
460
+ const w3 = await this.readRegister(ESP32S2_EFUSE_BLOCK1_ADDR + 4 * 3);
461
+ const w4 = await this.readRegister(ESP32S2_EFUSE_BLOCK1_ADDR + 4 * 4);
462
+ const hi = (w3 >> 20) & 0x01;
463
+ const lo = (w4 >> 4) & 0x07;
464
+ minor = (hi << 3) + lo;
465
+ major = (w3 >> 18) & 0x03;
466
+ break;
467
+ }
468
+ case CHIP_FAMILY_ESP32S3: {
469
+ const w3 = await this.readRegister(ESP32S3_EFUSE_BLOCK1_ADDR + 4 * 3);
470
+ const w5 = await this.readRegister(ESP32S3_EFUSE_BLOCK1_ADDR + 4 * 5);
471
+ const hi = (w5 >> 23) & 0x01;
472
+ const lo = (w3 >> 18) & 0x07;
473
+ minor = (hi << 3) + lo;
474
+ major = (w5 >> 24) & 0x03;
475
+ break;
476
+ }
477
+ case CHIP_FAMILY_ESP32C2: {
478
+ const w1 = await this.readRegister(ESP32C2_EFUSE_BLOCK2_ADDR + 4 * 1);
479
+ minor = (w1 >> 16) & 0x0f;
480
+ major = (w1 >> 20) & 0x03;
481
+ break;
482
+ }
483
+ case CHIP_FAMILY_ESP32C3: {
484
+ const w3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
485
+ const w5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
486
+ const hi = (w5 >> 23) & 0x01;
487
+ const lo = (w3 >> 18) & 0x07;
488
+ minor = (hi << 3) + lo;
489
+ major = (w5 >> 24) & 0x03;
490
+ break;
491
+ }
492
+ case CHIP_FAMILY_ESP32C5: {
493
+ const w2 = await this.readRegister(ESP32C5_EFUSE_BLOCK1_ADDR + 4 * 2);
494
+ minor = w2 & 0x0f;
495
+ major = (w2 >> 4) & 0x03;
496
+ break;
497
+ }
498
+ case CHIP_FAMILY_ESP32C6: {
499
+ const w3 = await this.readRegister(ESP32C6_EFUSE_BLOCK1_ADDR + 4 * 3);
500
+ minor = (w3 >> 18) & 0x0f;
501
+ major = (w3 >> 22) & 0x03;
502
+ break;
503
+ }
504
+ case CHIP_FAMILY_ESP32C61: {
505
+ const w2 = await this.readRegister(ESP32C61_EFUSE_BLOCK1_ADDR + 4 * 2);
506
+ minor = w2 & 0x0f;
507
+ major = (w2 >> 4) & 0x03;
508
+ break;
509
+ }
510
+ case CHIP_FAMILY_ESP32H2: {
511
+ const w3 = await this.readRegister(ESP32H2_EFUSE_BLOCK1_ADDR + 4 * 3);
512
+ minor = (w3 >> 18) & 0x07;
513
+ major = (w3 >> 21) & 0x03;
514
+ break;
515
+ }
516
+ case CHIP_FAMILY_ESP32H4: {
517
+ break;
518
+ }
519
+ case CHIP_FAMILY_ESP32H21: {
520
+ break;
521
+ }
522
+ case CHIP_FAMILY_ESP32P4: {
523
+ const w2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 4 * 2);
524
+ minor = w2 & 0x0f;
525
+ major = (((w2 >> 23) & 1) << 2) | ((w2 >> 4) & 0x03);
526
+ break;
527
+ }
528
+ case CHIP_FAMILY_ESP32S31: {
529
+ const w2 = await this.readRegister(ESP32S31_EFUSE_BLOCK1_ADDR + 4 * 2);
530
+ minor = w2 & 0x0f;
531
+ major = (w2 >> 4) & 0x03;
532
+ break;
533
+ }
464
534
  }
465
- return 0;
535
+ return major * 100 + minor;
466
536
  }
467
537
  /**
468
538
  * Power on the flash chip for ESP32-P4 Rev 301 (ECO6)
@@ -607,9 +677,6 @@ export class ESPLoader extends EventTarget {
607
677
  this._suppressDisconnect = false;
608
678
  this.logger.debug("Finished read loop");
609
679
  }
610
- sleep(ms = 100) {
611
- return new Promise((resolve) => setTimeout(resolve, ms));
612
- }
613
680
  // ============================================================================
614
681
  // Web Serial (Desktop) - DTR/RTS Signal Handling & Reset Strategies
615
682
  // ============================================================================
@@ -633,10 +700,6 @@ export class ESPLoader extends EventTarget {
633
700
  requestToSend: rts,
634
701
  });
635
702
  }
636
- /**
637
- * Helper function to run a sequence of signal changes
638
- * Automatically detects WebUSB vs Web Serial and calls appropriate methods
639
- */
640
703
  async runSignalSequence(steps) {
641
704
  const webusb = this.port.isWebUSB === true;
642
705
  for (const step of steps) {
@@ -650,14 +713,20 @@ export class ESPLoader extends EventTarget {
650
713
  }
651
714
  else {
652
715
  if (step.dtr !== undefined) {
653
- webusb
654
- ? await this.setDTRWebUSB(step.dtr)
655
- : await this.setDTR(step.dtr);
716
+ if (webusb) {
717
+ await this.setDTRWebUSB(step.dtr);
718
+ }
719
+ else {
720
+ await this.setDTR(step.dtr);
721
+ }
656
722
  }
657
723
  if (step.rts !== undefined) {
658
- webusb
659
- ? await this.setRTSWebUSB(step.rts)
660
- : await this.setRTS(step.rts);
724
+ if (webusb) {
725
+ await this.setRTSWebUSB(step.rts);
726
+ }
727
+ else {
728
+ await this.setRTS(step.rts);
729
+ }
661
730
  }
662
731
  }
663
732
  if (step.delayMs)
@@ -1002,8 +1071,12 @@ export class ESPLoader extends EventTarget {
1002
1071
  });
1003
1072
  }
1004
1073
  }
1005
- // Add general fallback strategies only for non-CP2102 and non-ESP32-S2 Native USB chips
1006
- if (!isCP2102 && !isESP32S2NativeUSB) {
1074
+ // Add general fallback strategies only for Native USB chips (not USB-Serial)
1075
+ // and only for chips not already handled by specific blocks above
1076
+ if (!isUSBSerialChip &&
1077
+ !isCP2102 &&
1078
+ !isESP32S2NativeUSB &&
1079
+ !isUSBJTAGSerial) {
1007
1080
  // Classic reset (for chips not handled above)
1008
1081
  if (portInfo.usbVendorId !== 0x1a86) {
1009
1082
  resetStrategies.push({
@@ -1035,7 +1108,7 @@ export class ESPLoader extends EventTarget {
1035
1108
  },
1036
1109
  });
1037
1110
  // WebUSB Strategy: USB-JTAG/Serial fallback
1038
- if (!isUSBJTAGSerial && !isEspressifUSB) {
1111
+ if (!isEspressifUSB) {
1039
1112
  resetStrategies.push({
1040
1113
  name: "USB-JTAG/Serial fallback (WebUSB)",
1041
1114
  fn: async function () {
@@ -1110,10 +1183,10 @@ export class ESPLoader extends EventTarget {
1110
1183
  try {
1111
1184
  await Promise.race([syncPromise, timeoutPromise]);
1112
1185
  // Sync succeeded
1113
- this.logger.log(`Connected CDC/JTAG successfully with ${strategy.name} reset.`);
1186
+ this.logger.debug(`Connected CDC/JTAG successfully with ${strategy.name} reset.`);
1114
1187
  return;
1115
1188
  }
1116
- catch (error) {
1189
+ catch {
1117
1190
  throw new Error("Sync timeout or abandoned");
1118
1191
  }
1119
1192
  }
@@ -1144,35 +1217,19 @@ export class ESPLoader extends EventTarget {
1144
1217
  }
1145
1218
  /**
1146
1219
  * @name watchdogReset
1147
- * Watchdog reset for ESP32-S2/S3/C3 with USB-OTG or USB-JTAG/Serial
1220
+ * Watchdog reset for ESP32-S2/S3/P4 with USB-OTG or USB-JTAG/Serial
1148
1221
  * Uses RTC watchdog timer to reset the chip - works when DTR/RTS signals are not available
1149
1222
  * This is an alias for rtcWdtResetChipSpecific() for backwards compatibility
1223
+ * Note: ESP32-C3, ESP32-C5, ESP32-C6 do NOT boot correctly after WDT reset
1150
1224
  */
1151
1225
  async watchdogReset() {
1152
1226
  await this.rtcWdtResetChipSpecific();
1153
1227
  }
1154
1228
  /**
1155
- * Get chip revision for ESP32-C3
1156
- * Reads from EFUSE registers and calculates revision
1157
- */
1158
- async getChipRevisionC3() {
1159
- if (this.chipFamily !== CHIP_FAMILY_ESP32C3) {
1160
- return 0;
1161
- }
1162
- // Read EFUSE_RD_MAC_SPI_SYS_3_REG (bits [20:18] = lower 3 bits of revision)
1163
- const word3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
1164
- const low = (word3 >> 18) & 0x07;
1165
- // Read EFUSE_RD_MAC_SPI_SYS_5_REG (bits [25:23] = upper 3 bits of revision)
1166
- const word5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
1167
- const hi = (word5 >> 23) & 0x07;
1168
- // Combine: upper 3 bits from word5, lower 3 bits from word3
1169
- const revision = (hi << 3) | low;
1170
- return revision;
1171
- }
1172
- /**
1173
- * RTC watchdog timer reset for ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, and ESP32-P4
1229
+ * RTC watchdog timer reset for ESP32-S2, ESP32-S3, and ESP32-P4
1174
1230
  * Uses specific registers for each chip family
1175
- * Note: ESP32-H2 does NOT support WDT reset
1231
+ * Note: ESP32-C3 does NOT boot correctly after WDT reset
1232
+ * Note: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2 do NOT support WDT reset (no usable RTC WDT path)
1176
1233
  */
1177
1234
  async rtcWdtResetChipSpecific() {
1178
1235
  this.logger.debug("Hard resetting with watchdog timer...");
@@ -1192,20 +1249,6 @@ export class ESPLoader extends EventTarget {
1192
1249
  WDTCONFIG1_REG = ESP32S3_RTC_CNTL_WDTCONFIG1_REG;
1193
1250
  WDT_WKEY = ESP32S3_RTC_CNTL_WDT_WKEY;
1194
1251
  }
1195
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
1196
- WDTWPROTECT_REG = ESP32C3_RTC_CNTL_WDTWPROTECT_REG;
1197
- WDTCONFIG0_REG = ESP32C3_RTC_CNTL_WDTCONFIG0_REG;
1198
- WDTCONFIG1_REG = ESP32C3_RTC_CNTL_WDTCONFIG1_REG;
1199
- WDT_WKEY = ESP32C3_RTC_CNTL_WDT_WKEY;
1200
- }
1201
- else if (this.chipFamily === CHIP_FAMILY_ESP32C5 ||
1202
- this.chipFamily === CHIP_FAMILY_ESP32C6) {
1203
- // C5 and C6 use LP_WDT (Low Power Watchdog Timer)
1204
- WDTWPROTECT_REG = ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG;
1205
- WDTCONFIG0_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG;
1206
- WDTCONFIG1_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG;
1207
- WDT_WKEY = ESP32C5_C6_RTC_CNTL_WDT_WKEY;
1208
- }
1209
1252
  else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1210
1253
  // P4 uses LP_WDT (Low Power Watchdog Timer)
1211
1254
  WDTWPROTECT_REG = ESP32P4_RTC_CNTL_WDTWPROTECT_REG;
@@ -1226,26 +1269,113 @@ export class ESPLoader extends EventTarget {
1226
1269
  // Lock watchdog registers
1227
1270
  await this.writeRegister(WDTWPROTECT_REG, 0, undefined, 0);
1228
1271
  // Wait for reset to take effect
1229
- await this.sleep(500);
1272
+ await sleep(500);
1230
1273
  }
1231
1274
  /**
1232
- * Helper: USB-based WDT reset
1233
- * Returns true if WDT reset was performed, false otherwise
1275
+ * Reset device from bootloader mode to firmware mode
1276
+ * Automatically selects the correct reset strategy based on USB connection type
1277
+ * @param clearForceDownloadFlag - If true, clears the force download boot flag (USB-OTG only)
1278
+ * @returns true if port will change (USB-OTG), false otherwise
1234
1279
  */
1235
- async tryUsbWdtReset(chipName) {
1236
- const isUsingUsbOtg = await this.detectUsbConnectionType();
1237
- if (isUsingUsbOtg) {
1238
- // Use WDT reset for USB-OTG devices
1239
- await this.rtcWdtResetChipSpecific();
1240
- this.logger.debug(`${chipName}: RTC WDT reset (USB-JTAG/Serial or USB-OTG detected)`);
1241
- return true;
1280
+ async resetToFirmwareMode(clearForceDownloadFlag = true) {
1281
+ this.logger.debug("Resetting from bootloader to firmware mode...");
1282
+ try {
1283
+ // Detect USB connection type
1284
+ const isUsbJtagOrOtg = await this.detectUsbConnectionType();
1285
+ if (isUsbJtagOrOtg) {
1286
+ // USB-JTAG/OTG devices need special handling
1287
+ this.logger.debug("USB-JTAG/OTG detected - checking WDT reset support");
1288
+ // Get detailed USB mode information
1289
+ let usbMode;
1290
+ try {
1291
+ usbMode = await this.getUsbMode();
1292
+ this.logger.debug(`USB mode: ${usbMode.mode} (uartNo=${usbMode.uartNo})`);
1293
+ }
1294
+ catch (err) {
1295
+ this.logger.debug(`Could not get USB mode: ${err}`);
1296
+ // Fall back to generic USB-JTAG/OTG handling
1297
+ usbMode = { mode: "usb-jtag-serial", uartNo: 0 };
1298
+ }
1299
+ // WDT reset is not needed for ESP32-C3
1300
+ // WDT reset is supported by: ESP32-S2, ESP32-S3, ESP32-P4
1301
+ // WDT reset is NOT supported by: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2
1302
+ const supportsWdtReset = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
1303
+ this.chipFamily === CHIP_FAMILY_ESP32S3 ||
1304
+ this.chipFamily === CHIP_FAMILY_ESP32P4;
1305
+ if (!supportsWdtReset) {
1306
+ this.logger.debug(`${this.chipName} does not support WDT reset - using classic reset instead`);
1307
+ // Use classic reset for chips without WDT support
1308
+ await this.hardResetToFirmware();
1309
+ this.logger.debug("Classic reset to firmware complete");
1310
+ return false; // Port stays open
1311
+ }
1312
+ // WDT reset is supported - proceed with WDT reset logic
1313
+ this.logger.debug(`${this.chipName} supports WDT reset - using WDT reset strategy`);
1314
+ // CRITICAL: WDT register writes require ROM (not stub) and baudrate 115200
1315
+ // If on stub, need to return to ROM first
1316
+ if (this.IS_STUB) {
1317
+ this.logger.debug("On stub - returning to ROM before WDT reset");
1318
+ // Change baudrate back to ROM baudrate if needed
1319
+ if (this.currentBaudRate !== ESP_ROM_BAUD) {
1320
+ this.logger.debug(`Changing baudrate from ${this.currentBaudRate} to ${ESP_ROM_BAUD}`);
1321
+ await this.reconfigurePort(ESP_ROM_BAUD);
1322
+ this.currentBaudRate = ESP_ROM_BAUD;
1323
+ this.logger.debug("Baudrate changed to 115200");
1324
+ }
1325
+ // CRITICAL: Temporarily clear console mode flag so hardReset(true) works
1326
+ const wasInConsoleMode = this._consoleMode;
1327
+ this._consoleMode = false;
1328
+ // Reset to bootloader (ROM)
1329
+ await this.hardReset(true);
1330
+ await sleep(200);
1331
+ // Restore console mode flag
1332
+ this._consoleMode = wasInConsoleMode;
1333
+ // Sync with ROM
1334
+ await this.sync();
1335
+ this.IS_STUB = false;
1336
+ this.logger.debug("Now on ROM");
1337
+ }
1338
+ else {
1339
+ // Even if not on stub, ensure baudrate is 115200 for WDT register writes
1340
+ if (this.currentBaudRate !== ESP_ROM_BAUD) {
1341
+ this.logger.debug(`Not on stub, but baudrate is ${this.currentBaudRate} - changing to ${ESP_ROM_BAUD} for WDT reset`);
1342
+ await this.reconfigurePort(ESP_ROM_BAUD);
1343
+ this.currentBaudRate = ESP_ROM_BAUD;
1344
+ this.logger.debug("Baudrate changed to 115200");
1345
+ }
1346
+ }
1347
+ // Clear force download boot flag if requested (USB-OTG only)
1348
+ if (clearForceDownloadFlag && usbMode.mode === "usb-otg") {
1349
+ const flagCleared = await this._clearForceDownloadBootIfNeeded();
1350
+ if (flagCleared) {
1351
+ this.logger.debug("Force download boot flag cleared");
1352
+ }
1353
+ }
1354
+ // Perform WDT reset to boot into firmware
1355
+ await this.rtcWdtResetChipSpecific();
1356
+ this.logger.debug("WDT reset performed - device will boot to firmware");
1357
+ // Check if port will change after WDT reset
1358
+ // USB-OTG (ESP32-S2/P4): Port always changes
1359
+ // USB-JTAG/Serial (ESP32-S3/C3/C5/C6/C61/H2/P4): Port may change depending on platform
1360
+ const portWillChange = usbMode.mode === "usb-otg" || usbMode.mode === "usb-jtag-serial";
1361
+ if (portWillChange) {
1362
+ this.logger.debug(`Port will change after WDT reset (${usbMode.mode}) - port reselection needed`);
1363
+ return true;
1364
+ }
1365
+ return false;
1366
+ }
1367
+ else {
1368
+ // External serial chip - use classic reset to firmware
1369
+ this.logger.debug("External serial chip detected - using classic reset");
1370
+ await this.hardResetToFirmware();
1371
+ this.logger.debug("Classic reset to firmware complete");
1372
+ return false;
1373
+ }
1242
1374
  }
1243
- else {
1244
- // Use classic reset for non-USB devices
1245
- await this.hardResetClassic();
1246
- this.logger.debug("Classic reset.");
1375
+ catch (err) {
1376
+ this.logger.error(`Failed to reset to firmware mode: ${err}`);
1377
+ throw err;
1247
1378
  }
1248
- return false;
1249
1379
  }
1250
1380
  async hardReset(bootloader = false) {
1251
1381
  // In console mode, only allow simple hardware reset (no bootloader entry)
@@ -1256,66 +1386,87 @@ export class ESPLoader extends EventTarget {
1256
1386
  }
1257
1387
  // Simple hardware reset to restart firmware (IO0=HIGH)
1258
1388
  this.logger.debug("Performing hardware reset (console mode)...");
1259
- await this.hardResetToFirmware();
1389
+ await this.resetInConsoleMode();
1260
1390
  this.logger.debug("Hardware reset complete");
1261
1391
  return;
1262
1392
  }
1263
1393
  if (bootloader) {
1264
- // enter flash mode
1394
+ // Enter bootloader/flash mode
1265
1395
  if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
1266
1396
  await this.hardResetUSBJTAGSerial();
1267
- this.logger.debug("USB-JTAG/Serial reset.");
1397
+ this.logger.debug("USB-JTAG/Serial reset to bootloader.");
1268
1398
  }
1269
1399
  else {
1270
1400
  await this.hardResetClassic();
1271
- this.logger.debug("Classic reset.");
1401
+ this.logger.debug("Classic reset to bootloader.");
1272
1402
  }
1273
1403
  }
1274
1404
  else {
1275
- // just reset (no bootloader mode)
1276
- // For ESP32-S2/S3/P4 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
1277
- this.logger.debug("*** Performing WDT reset strategy ***");
1278
- if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
1279
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S2");
1280
- if (wdtResetUsed)
1281
- return;
1282
- // } else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1283
- // const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S3");
1284
- // if (wdtResetUsed) return;
1285
- }
1286
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1287
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-P4");
1288
- if (wdtResetUsed)
1289
- return;
1290
- // } else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
1291
- // const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C3");
1292
- // if (wdtResetUsed) return;
1293
- }
1294
- else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
1295
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C5");
1296
- if (wdtResetUsed)
1405
+ // Reset to firmware mode (exit bootloader)
1406
+ // Use intelligent reset strategy based on USB connection type
1407
+ this.logger.debug("Resetting to firmware mode...");
1408
+ // Detect USB connection type to choose correct reset method
1409
+ const isUsbJtagOrOtg = await this.detectUsbConnectionType();
1410
+ if (isUsbJtagOrOtg) {
1411
+ // USB-JTAG/OTG devices: Check if chip supports WDT reset
1412
+ // Only S2, S3, P4 support WDT reset correctly
1413
+ // C3, C5, C6, C61, H2 do NOT boot correctly after WDT reset
1414
+ const supportsWdtReset = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
1415
+ this.chipFamily === CHIP_FAMILY_ESP32S3 ||
1416
+ this.chipFamily === CHIP_FAMILY_ESP32P4;
1417
+ if (supportsWdtReset) {
1418
+ this.logger.debug("USB-JTAG/OTG detected - using WDT reset");
1419
+ // Get USB mode details
1420
+ let usbMode;
1421
+ try {
1422
+ usbMode = await this.getUsbMode();
1423
+ this.logger.debug(`USB mode: ${usbMode.mode} (uartNo=${usbMode.uartNo})`);
1424
+ }
1425
+ catch (err) {
1426
+ this.logger.debug(`Could not get USB mode: ${err}`);
1427
+ usbMode = { mode: "usb-jtag-serial", uartNo: 0 };
1428
+ }
1429
+ // Clear force download flag for USB-OTG devices
1430
+ if (usbMode.mode === "usb-otg") {
1431
+ try {
1432
+ const flagCleared = await this._clearForceDownloadBootIfNeeded();
1433
+ if (flagCleared) {
1434
+ this.logger.debug("Force download boot flag cleared");
1435
+ }
1436
+ }
1437
+ catch (err) {
1438
+ this.logger.debug(`Could not clear force download flag: ${err}`);
1439
+ }
1440
+ }
1441
+ // Perform WDT reset
1442
+ await this.rtcWdtResetChipSpecific();
1443
+ this.logger.debug(`${this.chipName}: WDT reset to firmware complete`);
1297
1444
  return;
1445
+ }
1446
+ else {
1447
+ // C3, C5, C6, etc. - use classic reset (like external serial chips)
1448
+ this.logger.debug(`${this.chipName} does not support WDT reset - using classic reset instead`);
1449
+ }
1298
1450
  }
1299
- else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
1300
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C6");
1301
- if (wdtResetUsed)
1302
- return;
1451
+ else {
1452
+ // External serial chip: Use classic reset
1453
+ this.logger.debug("External serial chip detected - using classic reset");
1303
1454
  }
1304
- // Standard reset for all other cases
1455
+ // Classic reset: used for external serial chips and USB-JTAG chips that do not support WDT reset
1305
1456
  if (this.isWebUSB()) {
1306
1457
  // WebUSB: Use longer delays for better compatibility
1307
1458
  await this.setRTSWebUSB(true); // EN->LOW
1308
- await this.sleep(200);
1459
+ await sleep(200);
1309
1460
  await this.setRTSWebUSB(false);
1310
- await this.sleep(200);
1311
- this.logger.debug("Hard reset (WebUSB).");
1461
+ await sleep(200);
1462
+ this.logger.debug("Hard reset to firmware (WebUSB).");
1312
1463
  }
1313
1464
  else {
1314
1465
  // Web Serial: Standard reset
1315
1466
  await this.setRTS(true); // EN->LOW
1316
- await this.sleep(100);
1467
+ await sleep(100);
1317
1468
  await this.setRTS(false);
1318
- this.logger.debug("Hard reset.");
1469
+ this.logger.debug("Hard reset to firmware.");
1319
1470
  }
1320
1471
  }
1321
1472
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1715,28 +1866,6 @@ export class ESPLoader extends EventTarget {
1715
1866
  return 40;
1716
1867
  return 26;
1717
1868
  }
1718
- async setBaudrateC5Rom(baud) {
1719
- const crystalFreqRomExpect = await this.getC5CrystalFreqRomExpect();
1720
- const crystalFreqDetect = await this.getC5CrystalFreqDetected();
1721
- this.logger.log(`ROM expects crystal freq: ${crystalFreqRomExpect} MHz, detected ${crystalFreqDetect} MHz.`);
1722
- let baudRate = baud;
1723
- if (crystalFreqDetect === 48 && crystalFreqRomExpect === 40) {
1724
- baudRate = Math.trunc((baud * 40) / 48);
1725
- }
1726
- else if (crystalFreqDetect === 40 && crystalFreqRomExpect === 48) {
1727
- baudRate = Math.trunc((baud * 48) / 40);
1728
- }
1729
- this.logger.log(`Changing baud rate to ${baudRate}...`);
1730
- try {
1731
- const buffer = pack("<II", baudRate, 0);
1732
- await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
1733
- }
1734
- catch (e) {
1735
- this.logger.error(`Baudrate change error: ${e}`);
1736
- throw new Error(`Unable to change the baud rate to ${baudRate}: No response from set baud rate command.`);
1737
- }
1738
- this.logger.log("Changed.");
1739
- }
1740
1869
  async setBaudrate(baud) {
1741
1870
  const chipFamily = this._parent ? this._parent.chipFamily : this.chipFamily;
1742
1871
  if (!this.IS_STUB && chipFamily === CHIP_FAMILY_ESP32C5) {
@@ -1744,7 +1873,6 @@ export class ESPLoader extends EventTarget {
1744
1873
  }
1745
1874
  else {
1746
1875
  try {
1747
- // Send ESP_ROM_BAUD(115200) as the old one if running STUB otherwise 0
1748
1876
  const buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
1749
1877
  await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
1750
1878
  }
@@ -1776,7 +1904,29 @@ export class ESPLoader extends EventTarget {
1776
1904
  this.logger.log(`⚠️ WARNING: Baudrate ${baud} exceeds USB-Serial chip limit (${maxBaud})!`);
1777
1905
  this.logger.log(`⚠️ This may cause data corruption or connection failures!`);
1778
1906
  }
1779
- this.logger.log(`Changed baud rate to ${baud}`);
1907
+ this.logger.debug(`Changed baud rate to ${baud}`);
1908
+ }
1909
+ async setBaudrateC5Rom(baud) {
1910
+ const crystalFreqRomExpect = await this.getC5CrystalFreqRomExpect();
1911
+ const crystalFreqDetect = await this.getC5CrystalFreqDetected();
1912
+ this.logger.log(`ROM expects crystal freq: ${crystalFreqRomExpect} MHz, detected ${crystalFreqDetect} MHz.`);
1913
+ let baudRate = baud;
1914
+ if (crystalFreqDetect === 48 && crystalFreqRomExpect === 40) {
1915
+ baudRate = Math.trunc((baud * 40) / 48);
1916
+ }
1917
+ else if (crystalFreqDetect === 40 && crystalFreqRomExpect === 48) {
1918
+ baudRate = Math.trunc((baud * 48) / 40);
1919
+ }
1920
+ this.logger.log(`Changing baud rate to ${baudRate}...`);
1921
+ try {
1922
+ const buffer = pack("<II", baudRate, 0);
1923
+ await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
1924
+ }
1925
+ catch (e) {
1926
+ this.logger.error(`Baudrate change error: ${e}`);
1927
+ throw new Error(`Unable to change the baud rate to ${baudRate}: No response from set baud rate command.`);
1928
+ }
1929
+ this.logger.log("Changed.");
1780
1930
  }
1781
1931
  async reconfigurePort(baud) {
1782
1932
  var _a;
@@ -1837,9 +1987,9 @@ export class ESPLoader extends EventTarget {
1837
1987
  // Restart Readloop
1838
1988
  this.readLoop();
1839
1989
  }
1840
- catch (e) {
1841
- // this.logger.error(`Reconfigure port error: ${e}`);
1842
- // throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
1990
+ catch {
1991
+ // this.logger.error(`Reconfigure port error`);
1992
+ // throw new Error(`Unable to change the baud rate to ${baud}`);
1843
1993
  }
1844
1994
  finally {
1845
1995
  // Always reset flag, even on error or early return
@@ -1946,6 +2096,8 @@ export class ESPLoader extends EventTarget {
1946
2096
  const headerFlashSizeFreq = header[3];
1947
2097
  this.logger.log(`Image header, Magic=${toHex(headerMagic)}, FlashMode=${toHex(headerFlashMode)}, FlashSizeFreq=${toHex(headerFlashSizeFreq)}`);
1948
2098
  }
2099
+ const paddedData = padTo(new Uint8Array(binaryData), 4);
2100
+ binaryData = paddedData.buffer;
1949
2101
  const uncompressedFilesize = binaryData.byteLength;
1950
2102
  let compressedFilesize = 0;
1951
2103
  let dataToFlash;
@@ -2242,13 +2394,9 @@ export class ESPLoader extends EventTarget {
2242
2394
  return status;
2243
2395
  }
2244
2396
  async detectFlashSize() {
2245
- this.logger.log("Detecting Flash Size");
2397
+ this.logger.debug("Detecting Flash Size");
2246
2398
  const flashId = await this.flashId();
2247
- const manufacturer = flashId & 0xff;
2248
2399
  const flashIdLowbyte = (flashId >> 16) & 0xff;
2249
- this.logger.log(`FlashId: ${toHex(flashId)}`);
2250
- this.logger.log(`Flash Manufacturer: ${manufacturer.toString(16)}`);
2251
- this.logger.log(`Flash Device: ${((flashId >> 8) & 0xff).toString(16)}${flashIdLowbyte.toString(16)}`);
2252
2400
  this.flashSize = DETECTED_FLASH_SIZES[flashIdLowbyte];
2253
2401
  this.logger.log(`Auto-detected Flash size: ${this.flashSize}`);
2254
2402
  }
@@ -2310,7 +2458,7 @@ export class ESPLoader extends EventTarget {
2310
2458
  // We're transferring over USB, right?
2311
2459
  const ramBlock = USB_RAM_BLOCK;
2312
2460
  // Upload
2313
- this.logger.log("Uploading stub...");
2461
+ this.logger.debug("Uploading stub...");
2314
2462
  for (const field of ["text", "data"]) {
2315
2463
  const fieldData = stub[field];
2316
2464
  const offset = stub[`${field}_start`];
@@ -2332,7 +2480,7 @@ export class ESPLoader extends EventTarget {
2332
2480
  if (pChar != "OHAI") {
2333
2481
  throw new Error("Failed to start stub. Unexpected response: " + pChar);
2334
2482
  }
2335
- this.logger.log("Stub is now running...");
2483
+ this.logger.debug("Stub is now running...");
2336
2484
  const espStubLoader = new EspStubLoader(this.port, this.logger, this);
2337
2485
  // Try to autodetect the flash size.
2338
2486
  if (!skipFlashDetection) {
@@ -2436,81 +2584,6 @@ export class ESPLoader extends EventTarget {
2436
2584
  // Always await the write chain to ensure errors are caught
2437
2585
  await this._writeChain;
2438
2586
  }
2439
- async getUsbMode() {
2440
- var _a, _b;
2441
- const family = this._parent ? this._parent.chipFamily : this.chipFamily;
2442
- const revision = this._parent
2443
- ? ((_a = this._parent.chipRevision) !== null && _a !== void 0 ? _a : 0)
2444
- : ((_b = this.chipRevision) !== null && _b !== void 0 ? _b : 0);
2445
- let bufNoAddr = null;
2446
- let jtagSerialVal = null;
2447
- let otgVal = null;
2448
- switch (family) {
2449
- case CHIP_FAMILY_ESP32S2:
2450
- bufNoAddr = ESP32S2_UARTDEV_BUF_NO;
2451
- otgVal = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
2452
- break;
2453
- case CHIP_FAMILY_ESP32S3:
2454
- bufNoAddr = ESP32S3_UARTDEV_BUF_NO;
2455
- jtagSerialVal = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2456
- otgVal = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
2457
- break;
2458
- case CHIP_FAMILY_ESP32C3: {
2459
- const bssAddr = revision < 101 ? 0x3fcdf064 : 0x3fcdf060;
2460
- bufNoAddr = bssAddr + ESP32C3_BUF_UART_NO_OFFSET;
2461
- jtagSerialVal = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2462
- break;
2463
- }
2464
- case CHIP_FAMILY_ESP32C5:
2465
- bufNoAddr = ESP32C5_UARTDEV_BUF_NO;
2466
- jtagSerialVal = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2467
- break;
2468
- case CHIP_FAMILY_ESP32C6:
2469
- bufNoAddr = ESP32C6_UARTDEV_BUF_NO;
2470
- jtagSerialVal = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2471
- break;
2472
- case CHIP_FAMILY_ESP32C61:
2473
- bufNoAddr =
2474
- revision <= 200
2475
- ? ESP32C61_UARTDEV_BUF_NO_REV_LE2
2476
- : ESP32C61_UARTDEV_BUF_NO_REV_GT2;
2477
- jtagSerialVal =
2478
- revision <= 200
2479
- ? ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_LE2
2480
- : ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_GT2;
2481
- break;
2482
- case CHIP_FAMILY_ESP32H2:
2483
- bufNoAddr = ESP32H2_UARTDEV_BUF_NO;
2484
- jtagSerialVal = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2485
- break;
2486
- case CHIP_FAMILY_ESP32H4:
2487
- bufNoAddr = ESP32H4_UARTDEV_BUF_NO;
2488
- jtagSerialVal = ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2489
- break;
2490
- case CHIP_FAMILY_ESP32P4:
2491
- bufNoAddr =
2492
- revision < 300
2493
- ? ESP32P4_UARTDEV_BUF_NO_REV0
2494
- : ESP32P4_UARTDEV_BUF_NO_REV300;
2495
- jtagSerialVal = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2496
- otgVal = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
2497
- break;
2498
- }
2499
- if (bufNoAddr === null) {
2500
- return { mode: "uart", uartNo: 0 };
2501
- }
2502
- const uartNo = (await this.readRegister(bufNoAddr)) & 0xff;
2503
- if (otgVal !== null && uartNo === otgVal) {
2504
- this.logger.debug(`USB mode: USB-OTG (uartNo=${uartNo})`);
2505
- return { mode: "usb-otg", uartNo };
2506
- }
2507
- if (jtagSerialVal !== null && uartNo === jtagSerialVal) {
2508
- this.logger.debug(`USB mode: USB-JTAG/Serial (uartNo=${uartNo})`);
2509
- return { mode: "usb-jtag-serial", uartNo };
2510
- }
2511
- this.logger.debug(`USB mode: UART (uartNo=${uartNo})`);
2512
- return { mode: "uart", uartNo };
2513
- }
2514
2587
  async disconnect() {
2515
2588
  if (this._parent) {
2516
2589
  await this._parent.disconnect();
@@ -2524,8 +2597,8 @@ export class ESPLoader extends EventTarget {
2524
2597
  try {
2525
2598
  await this._writeChain;
2526
2599
  }
2527
- catch (err) {
2528
- // this.logger.debug(`Pending write error during disconnect: ${err}`);
2600
+ catch {
2601
+ // this.logger.debug("Pending write error during disconnect");
2529
2602
  }
2530
2603
  // Release persistent writer before closing
2531
2604
  if (this._writer) {
@@ -2533,8 +2606,8 @@ export class ESPLoader extends EventTarget {
2533
2606
  await this._writer.close();
2534
2607
  this._writer.releaseLock();
2535
2608
  }
2536
- catch (err) {
2537
- // this.logger.debug(`Writer close/release error: ${err}`);
2609
+ catch {
2610
+ // this.logger.debug("Writer close/release error");
2538
2611
  }
2539
2612
  this._writer = undefined;
2540
2613
  }
@@ -2546,8 +2619,8 @@ export class ESPLoader extends EventTarget {
2546
2619
  await writer.close();
2547
2620
  writer.releaseLock();
2548
2621
  }
2549
- catch (err) {
2550
- // this.logger.debug(`Direct writer close error: ${err}`);
2622
+ catch {
2623
+ // this.logger.debug("Direct writer close error");
2551
2624
  }
2552
2625
  }
2553
2626
  await new Promise((resolve) => {
@@ -2568,7 +2641,7 @@ export class ESPLoader extends EventTarget {
2568
2641
  try {
2569
2642
  this._reader.cancel();
2570
2643
  }
2571
- catch (err) {
2644
+ catch {
2572
2645
  // Reader already released, resolve immediately
2573
2646
  clearTimeout(timeout);
2574
2647
  resolve(undefined);
@@ -2598,8 +2671,8 @@ export class ESPLoader extends EventTarget {
2598
2671
  try {
2599
2672
  await this._writeChain;
2600
2673
  }
2601
- catch (err) {
2602
- // this.logger.debug(`Pending write error during release: ${err}`);
2674
+ catch {
2675
+ // this.logger.debug("Pending write error during release");
2603
2676
  }
2604
2677
  // Release writer
2605
2678
  if (this._writer) {
@@ -2612,29 +2685,25 @@ export class ESPLoader extends EventTarget {
2612
2685
  }
2613
2686
  this._writer = undefined;
2614
2687
  }
2615
- // Cancel and release reader
2688
+ // Cancel reader - let readLoop's finally block handle releaseLock()
2616
2689
  if (this._reader) {
2617
- const reader = this._reader;
2618
2690
  try {
2619
2691
  // Suppress disconnect event during console mode switching
2620
2692
  this._suppressDisconnect = true;
2621
- await reader.cancel();
2622
- this.logger.debug("Reader cancelled");
2693
+ // Cancel will cause readLoop to exit and call releaseLock() in its finally block
2694
+ await this._reader.cancel();
2695
+ this.logger.debug("Reader cancelled - waiting for readLoop to finish");
2696
+ // CRITICAL: Wait a bit for readLoop's finally block to complete
2697
+ // The finally block needs time to call releaseLock() and set _reader = undefined
2698
+ // This is much faster than waiting for browser to unlock (just waiting for JS execution)
2699
+ await sleep(50);
2700
+ this.logger.debug("ReadLoop cleanup should be complete");
2623
2701
  }
2624
2702
  catch (err) {
2625
2703
  this.logger.debug(`Reader cancel error: ${err}`);
2626
2704
  }
2627
- finally {
2628
- try {
2629
- reader.releaseLock();
2630
- }
2631
- catch (err) {
2632
- this.logger.debug(`Reader release error: ${err}`);
2633
- }
2634
- }
2635
- if (this._reader === reader) {
2636
- this._reader = undefined;
2637
- }
2705
+ // Don't call releaseLock() or set _reader to undefined here
2706
+ // Let readLoop's finally block handle it to avoid race conditions
2638
2707
  }
2639
2708
  }
2640
2709
  /**
@@ -2673,6 +2742,141 @@ export class ESPLoader extends EventTarget {
2673
2742
  this.logger.debug(`USB-JTAG/OTG detection: ${isUsbJtag ? "YES" : "NO"} (PID=0x${pid === null || pid === void 0 ? void 0 : pid.toString(16)})`);
2674
2743
  return isUsbJtag;
2675
2744
  }
2745
+ async getUsbMode() {
2746
+ var _a, _b;
2747
+ const family = this._parent ? this._parent.chipFamily : this.chipFamily;
2748
+ const revision = this._parent
2749
+ ? ((_a = this._parent.chipRevision) !== null && _a !== void 0 ? _a : 0)
2750
+ : ((_b = this.chipRevision) !== null && _b !== void 0 ? _b : 0);
2751
+ let bufNoAddr = null;
2752
+ let jtagSerialVal = null;
2753
+ let otgVal = null;
2754
+ switch (family) {
2755
+ case CHIP_FAMILY_ESP32S2:
2756
+ bufNoAddr = ESP32S2_UARTDEV_BUF_NO;
2757
+ otgVal = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
2758
+ break;
2759
+ case CHIP_FAMILY_ESP32S3:
2760
+ bufNoAddr = ESP32S3_UARTDEV_BUF_NO;
2761
+ jtagSerialVal = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2762
+ otgVal = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
2763
+ break;
2764
+ case CHIP_FAMILY_ESP32C3: {
2765
+ const bssAddr = revision < 101 ? 0x3fcdf064 : 0x3fcdf060;
2766
+ bufNoAddr = bssAddr + ESP32C3_BUF_UART_NO_OFFSET;
2767
+ jtagSerialVal = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2768
+ break;
2769
+ }
2770
+ case CHIP_FAMILY_ESP32C5:
2771
+ bufNoAddr = ESP32C5_UARTDEV_BUF_NO;
2772
+ jtagSerialVal = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2773
+ break;
2774
+ case CHIP_FAMILY_ESP32C6:
2775
+ bufNoAddr = ESP32C6_UARTDEV_BUF_NO;
2776
+ jtagSerialVal = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2777
+ break;
2778
+ case CHIP_FAMILY_ESP32C61:
2779
+ bufNoAddr =
2780
+ revision <= 200
2781
+ ? ESP32C61_UARTDEV_BUF_NO_REV_LE2
2782
+ : ESP32C61_UARTDEV_BUF_NO_REV_GT2;
2783
+ jtagSerialVal =
2784
+ revision <= 200
2785
+ ? ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_LE2
2786
+ : ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_GT2;
2787
+ break;
2788
+ case CHIP_FAMILY_ESP32H2:
2789
+ bufNoAddr = ESP32H2_UARTDEV_BUF_NO;
2790
+ jtagSerialVal = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2791
+ break;
2792
+ case CHIP_FAMILY_ESP32H4:
2793
+ bufNoAddr = ESP32H4_UARTDEV_BUF_NO;
2794
+ jtagSerialVal = ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2795
+ break;
2796
+ case CHIP_FAMILY_ESP32P4:
2797
+ bufNoAddr =
2798
+ revision < 300
2799
+ ? ESP32P4_UARTDEV_BUF_NO_REV0
2800
+ : ESP32P4_UARTDEV_BUF_NO_REV300;
2801
+ jtagSerialVal = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2802
+ otgVal = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
2803
+ break;
2804
+ }
2805
+ if (bufNoAddr === null) {
2806
+ return { mode: "uart", uartNo: 0 };
2807
+ }
2808
+ const uartNo = (await this.readRegister(bufNoAddr)) & 0xff;
2809
+ if (otgVal !== null && uartNo === otgVal) {
2810
+ this.logger.debug(`USB mode: USB-OTG (uartNo=${uartNo})`);
2811
+ return { mode: "usb-otg", uartNo };
2812
+ }
2813
+ if (jtagSerialVal !== null && uartNo === jtagSerialVal) {
2814
+ this.logger.debug(`USB mode: USB-JTAG/Serial (uartNo=${uartNo})`);
2815
+ return { mode: "usb-jtag-serial", uartNo };
2816
+ }
2817
+ this.logger.debug(`USB mode: UART (uartNo=${uartNo})`);
2818
+ return { mode: "uart", uartNo };
2819
+ }
2820
+ /**
2821
+ * Check if the current chip supports USB-JTAG or USB-OTG
2822
+ * @returns true if chip has native USB support (JTAG or OTG)
2823
+ */
2824
+ supportsNativeUsb() {
2825
+ const family = this._parent ? this._parent.chipFamily : this.chipFamily;
2826
+ // Chips with USB-JTAG/Serial or USB-OTG support
2827
+ const usbChips = [
2828
+ CHIP_FAMILY_ESP32S2, // USB-OTG
2829
+ CHIP_FAMILY_ESP32S3, // USB-OTG + USB-JTAG/Serial
2830
+ CHIP_FAMILY_ESP32C3, // USB-JTAG/Serial
2831
+ CHIP_FAMILY_ESP32C5, // USB-JTAG/Serial
2832
+ CHIP_FAMILY_ESP32C6, // USB-JTAG/Serial
2833
+ CHIP_FAMILY_ESP32C61, // USB-JTAG/Serial
2834
+ CHIP_FAMILY_ESP32H2, // USB-JTAG/Serial
2835
+ CHIP_FAMILY_ESP32H4, // USB-JTAG/Serial
2836
+ CHIP_FAMILY_ESP32P4, // USB-OTG + USB-JTAG/Serial
2837
+ ];
2838
+ return usbChips.includes(family);
2839
+ }
2840
+ /**
2841
+ * @name _ensureStreamsReady
2842
+ * After a hardware reset, ensure port streams are available.
2843
+ * On WebUSB, recreates streams since they break after reset.
2844
+ * On Web Serial, waits for streams to become available.
2845
+ */
2846
+ async _ensureStreamsReady() {
2847
+ if (this.isWebUSB()) {
2848
+ try {
2849
+ await this.port.recreateStreams();
2850
+ this.logger.debug("WebUSB streams recreated");
2851
+ let retries = 30;
2852
+ while (retries > 0 && !this.port.readable) {
2853
+ await sleep(100);
2854
+ retries--;
2855
+ }
2856
+ if (!this.port.readable) {
2857
+ throw new Error("Readable stream not available after recreating streams");
2858
+ }
2859
+ this.logger.debug("WebUSB streams are ready");
2860
+ }
2861
+ catch (err) {
2862
+ this.logger.error(`Failed to recreate WebUSB streams: ${err}`);
2863
+ this._consoleMode = false;
2864
+ throw err;
2865
+ }
2866
+ }
2867
+ else {
2868
+ let retries = 20;
2869
+ while (retries > 0 && !this.port.readable) {
2870
+ await sleep(100);
2871
+ retries--;
2872
+ }
2873
+ if (!this.port.readable) {
2874
+ this._consoleMode = false;
2875
+ throw new Error("Readable stream not available after reset");
2876
+ }
2877
+ this.logger.debug("Port streams are ready");
2878
+ }
2879
+ }
2676
2880
  /**
2677
2881
  * @name enterConsoleMode
2678
2882
  * Prepare device for console mode by resetting to firmware
@@ -2700,60 +2904,41 @@ export class ESPLoader extends EventTarget {
2700
2904
  if (this.isUsbJtagOrOtg === undefined) {
2701
2905
  throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
2702
2906
  }
2703
- // Set console mode flag
2704
- this._consoleMode = false;
2705
2907
  this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
2706
2908
  isUsbJtag = this.isUsbJtagOrOtg;
2707
2909
  }
2708
- // Release reader/writer so console can create new ones
2709
- // This is needed for Desktop (Web Serial) to unlock streams
2910
+ // Set console mode flag BEFORE any operations
2911
+ this._consoleMode = true;
2710
2912
  if (isUsbJtag) {
2711
- // USB-JTAG/OTG devices: Use watchdog reset which closes port
2913
+ // USB-JTAG/OTG devices: Use reset which may close port
2712
2914
  const wasReset = await this._resetToFirmwareIfNeeded();
2713
- return wasReset; // true = port closed, caller must reopen
2915
+ if (wasReset) {
2916
+ return true; // port closed, caller must reopen
2917
+ }
2918
+ // Port stayed open (e.g. C3/C5/C6/H2 classic reset)
2919
+ await this._ensureStreamsReady();
2920
+ return false;
2714
2921
  }
2715
2922
  else {
2716
2923
  // External serial chip devices: Release locks and do simple reset
2717
2924
  try {
2718
2925
  await this.releaseReaderWriter();
2719
- await this.sleep(100);
2926
+ await sleep(100);
2720
2927
  }
2721
2928
  catch (err) {
2722
2929
  this.logger.debug(`Failed to release locks: ${err}`);
2723
2930
  }
2724
- // Hardware reset to firmware mode (IO0=HIGH)
2725
2931
  try {
2726
- await this.hardReset(false);
2727
- this.logger.log("Device reset to firmware mode");
2932
+ await this.hardResetToFirmware();
2933
+ this.logger.debug("Device reset to firmware mode");
2728
2934
  }
2729
2935
  catch (err) {
2730
2936
  this.logger.debug(`Could not reset device: ${err}`);
2731
2937
  }
2732
- // For WebUSB (Android), recreate streams after hardware reset
2733
- if (this.isWebUSB()) {
2734
- try {
2735
- // Use the public recreateStreams() method to safely recreate streams
2736
- // without closing the port (important after hardware reset)
2737
- await this.port.recreateStreams();
2738
- this.logger.debug("WebUSB streams recreated for console mode");
2739
- }
2740
- catch (err) {
2741
- // Set console mode flag
2742
- this._consoleMode = false;
2743
- this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
2744
- }
2745
- }
2746
- // Set console mode flag
2747
- this._consoleMode = true;
2748
- return false; // Port stays open
2938
+ await this._ensureStreamsReady();
2939
+ return false;
2749
2940
  }
2750
2941
  }
2751
- /**
2752
- * @name _resetToFirmwareIfNeeded
2753
- * Reset device from bootloader to firmware when switching to console mode
2754
- * Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
2755
- * @returns true if reconnect was performed, false otherwise
2756
- */
2757
2942
  /**
2758
2943
  * @name _clearForceDownloadBootIfNeeded
2759
2944
  * Read and clear the force download boot flag if it is set
@@ -2808,99 +2993,66 @@ export class ESPLoader extends EventTarget {
2808
2993
  return false;
2809
2994
  }
2810
2995
  }
2996
+ /**
2997
+ * @name _resetToFirmwareIfNeeded
2998
+ * Reset device from bootloader to firmware when switching to console mode
2999
+ * Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
3000
+ * @returns true if reconnect was performed, false otherwise
3001
+ */
2811
3002
  async _resetToFirmwareIfNeeded() {
3003
+ // Detect if we need WDT reset (USB-JTAG/OTG) or classic reset
3004
+ const isUsbJtagOrOtg = await this.detectUsbConnectionType();
2812
3005
  try {
2813
3006
  // Check if port is open - if not, assume device is already in firmware mode
2814
3007
  if (!this.port.writable || !this.port.readable) {
2815
3008
  this.logger.debug("Port is not open - assuming device is already in firmware mode");
2816
3009
  return false;
2817
3010
  }
2818
- const isUsingUsbOtg = await this.detectUsbConnectionType();
2819
- if (isUsingUsbOtg) {
2820
- // For USB-OTG devices, we need to check if force download flag is set
2821
- // Only if it's set, we need WDT reset (which causes port change)
2822
- // If it's clear, we can use normal reset (no port change)
2823
- if (this.IS_STUB) {
2824
- this.logger.debug("On stub - need to get back to ROM to check flag");
2825
- // If we're running at higher baudrate, we need to change back to ROM baudrate
2826
- if (this.currentBaudRate !== ESP_ROM_BAUD) {
2827
- this.logger.debug(`Changing baudrate from ${this.currentBaudRate} to ${ESP_ROM_BAUD} for ROM`);
2828
- try {
2829
- await this.reconfigurePort(ESP_ROM_BAUD);
2830
- this.currentBaudRate = ESP_ROM_BAUD;
2831
- }
2832
- catch (err) {
2833
- this.logger.debug(`Baudrate change failed: ${err}`);
2834
- // Continue anyway
2835
- }
2836
- }
2837
- this.logger.debug("Resetting to bootloader (ROM)...");
2838
- // Reset to bootloader - this will clear the stub from RAM
2839
- try {
2840
- await this.hardReset(true);
2841
- // Wait for reset to complete
2842
- await sleep(200);
2843
- // Sync with ROM
2844
- await this.sync();
2845
- this.logger.debug("Now on ROM after reset");
2846
- // Mark that we're no longer on stub
2847
- this.IS_STUB = false;
2848
- }
2849
- catch (resetErr) {
2850
- this.logger.debug(`Reset to ROM failed: ${resetErr}`);
2851
- // If reset fails, we might already be in firmware mode
2852
- // In this case, we don't need to do anything - just use normal reset
2853
- this.logger.debug("Assuming device is already in firmware mode");
2854
- // Release reader/writer before returning
2855
- await this.releaseReaderWriter();
2856
- return false; // No port change needed
2857
- }
2858
- }
2859
- else {
2860
- this.logger.debug("Already on ROM - checking force download flag");
2861
- }
2862
- // Now check if force download flag is set and clear it if needed
2863
- const flagWasCleared = await this._clearForceDownloadBootIfNeeded();
2864
- if (flagWasCleared) {
2865
- this.logger.debug("Force download flag was cleared - device will boot to firmware after reset");
2866
- }
2867
- else {
2868
- this.logger.debug("Force download flag already clear - device will boot to firmware after reset");
2869
- }
2870
- // Perform WDT reset BEFORE releasing reader/writer (needs communication)
2871
- // After WDT reset, the device will reboot into firmware mode
2872
- await this.hardReset(false);
2873
- // For USB-OTG devices (ESP32-S2, ESP32-P4), the port will change after WDT reset
2874
- const portWillChange = (this.chipFamily === CHIP_FAMILY_ESP32S2 && isUsingUsbOtg) ||
2875
- (this.chipFamily === CHIP_FAMILY_ESP32P4 && isUsingUsbOtg);
2876
- if (portWillChange) {
2877
- // Port will change - release reader/writer and let the port become invalid
2878
- await this.releaseReaderWriter();
2879
- this.logger.log(`${this.chipName} USB-OTG: Port will change after WDT reset`);
2880
- this.logger.log("Please select the new port for console mode");
2881
- // Dispatch event to signal port change
2882
- this.dispatchEvent(new CustomEvent("usb-otg-port-change", {
2883
- detail: {
2884
- chipName: this.chipName,
2885
- message: `${this.chipName} USB port changed after reset. Please select the new port.`,
2886
- reason: "wdt-reset-to-firmware",
2887
- },
2888
- }));
2889
- // Return true to indicate port selection is needed
2890
- return true;
2891
- }
2892
- else {
2893
- // Port stays the same - release reader/writer so console can use the stream
3011
+ if (isUsbJtagOrOtg) {
3012
+ // USB-JTAG/OTG: DON'T release reader/writer before WDT reset
3013
+ // The WDT reset needs active communication to send register write commands
3014
+ // The port will close automatically after the WDT reset anyway
3015
+ this.logger.debug("USB-JTAG/OTG: Keeping reader/writer active for WDT reset");
3016
+ }
3017
+ else {
3018
+ // External serial chip: Release reader/writer before classic reset
3019
+ await this.releaseReaderWriter();
3020
+ this.logger.debug("External serial: Reader/writer released before reset");
3021
+ }
3022
+ // Use the new resetToFirmwareMode method which handles all the logic
3023
+ const portWillChange = await this.resetToFirmwareMode(true);
3024
+ if (portWillChange) {
3025
+ this.logger.debug(`${this.chipName}: Port will change after WDT reset - user must reselect port`);
3026
+ // Dispatch event to signal port change
3027
+ this.dispatchEvent(new CustomEvent("usb-otg-port-change", {
3028
+ detail: {
3029
+ chipName: this.chipName,
3030
+ message: `${this.chipName} USB port changed after reset. Please select the new port.`,
3031
+ reason: "wdt-reset-to-firmware",
3032
+ },
3033
+ }));
3034
+ return true;
3035
+ }
3036
+ else {
3037
+ // Port stays the same - release reader/writer now if not already done
3038
+ if (isUsbJtagOrOtg) {
2894
3039
  await this.releaseReaderWriter();
2895
- return false;
3040
+ this.logger.debug("Reader/writer released after reset");
2896
3041
  }
3042
+ return false;
2897
3043
  }
2898
3044
  }
2899
3045
  catch (err) {
2900
- this.logger.debug(`Could not reset device to firmware mode: ${err}`);
2901
- // Continue anyway - console mode might still work
3046
+ this.logger.error(`Reset to firmware mode failed: ${err}`);
3047
+ // For USB-JTAG/OTG, the port is likely dead after a failed reset
3048
+ // For external serial, the port is usually still fine
3049
+ if (isUsbJtagOrOtg) {
3050
+ this.logger.debug("Forcing port reselection due to USB-JTAG/OTG reset failure");
3051
+ return true;
3052
+ }
3053
+ this.logger.debug("External serial reset failed, but port should still be usable");
3054
+ return false;
2902
3055
  }
2903
- return false;
2904
3056
  }
2905
3057
  /**
2906
3058
  * @name reconnectAndResume
@@ -3114,7 +3266,7 @@ export class ESPLoader extends EventTarget {
3114
3266
  await this.connectWithResetStrategies();
3115
3267
  // Detect chip type
3116
3268
  await this.detectChip();
3117
- this.logger.log(`Reconnected to bootloader: ${this.chipName}`);
3269
+ this.logger.debug(`Reconnected to bootloader: ${this.chipName}`);
3118
3270
  }
3119
3271
  catch (err) {
3120
3272
  // Ensure flag is reset on error
@@ -3151,7 +3303,7 @@ export class ESPLoader extends EventTarget {
3151
3303
  }
3152
3304
  if (isUsbOtgChip && isUsbJtagOrOtg) {
3153
3305
  // USB-OTG devices: Need to reset to bootloader, which will cause port change
3154
- this.logger.log(`${this.chipName} USB: Resetting to bootloader mode`);
3306
+ this.logger.debug(`${this.chipName} USB: Resetting to bootloader mode`);
3155
3307
  // Perform hardware reset to bootloader (GPIO0=LOW)
3156
3308
  // This will cause the port to change from CDC (firmware) to JTAG (bootloader)
3157
3309
  try {
@@ -3163,7 +3315,7 @@ export class ESPLoader extends EventTarget {
3163
3315
  }
3164
3316
  // Wait for reset to complete and port to change
3165
3317
  await sleep(500);
3166
- this.logger.log(`${this.chipName}: Port changed. Please select the bootloader port.`);
3318
+ this.logger.debug(`${this.chipName}: Port changed. Please select the bootloader port.`);
3167
3319
  // Dispatch event to signal port change
3168
3320
  this.dispatchEvent(new CustomEvent("usb-otg-port-change", {
3169
3321
  detail: {
@@ -3208,8 +3360,10 @@ export class ESPLoader extends EventTarget {
3208
3360
  return await this._parent.resetInConsoleMode();
3209
3361
  }
3210
3362
  if (!this.isConsoleResetSupported()) {
3211
- this.logger.debug("Console reset not supported for ESP32-S2 USB-JTAG/CDC");
3212
- return; // Do nothing
3363
+ this.logger.debug("Simple Console reset not supported for ESP32-S2 USB-JTAG/CDC - using exitConsoleMode to enter bootloader");
3364
+ await this.exitConsoleMode();
3365
+ this.logger.debug("S2 now in bootloader mode - caller must do syncAndWdtReset on new port, then reconnect console");
3366
+ return;
3213
3367
  }
3214
3368
  // For other devices: Use standard firmware reset
3215
3369
  try {
@@ -3222,6 +3376,41 @@ export class ESPLoader extends EventTarget {
3222
3376
  throw err;
3223
3377
  }
3224
3378
  }
3379
+ /**
3380
+ * @name syncAndWdtReset
3381
+ * Open a new bootloader port, sync with ROM (no stub, no reset strategies), and fire WDT reset.
3382
+ * This is used for ESP32-S2 USB-OTG devices which require WDT reset to switch modes.
3383
+ * After WDT reset the port will re-enumerate again.
3384
+ * The user must select the new port after this method is called.
3385
+ * @param newPort - The bootloader port selected by the user
3386
+ */
3387
+ async syncAndWdtReset(newPort) {
3388
+ if (this._parent) {
3389
+ await this._parent.syncAndWdtReset(newPort);
3390
+ return;
3391
+ }
3392
+ this.port = newPort;
3393
+ this.connected = false;
3394
+ this.IS_STUB = false;
3395
+ this.__inputBuffer = [];
3396
+ this.__inputBufferReadIndex = 0;
3397
+ this.__totalBytesRead = 0;
3398
+ this.logger.debug("Opening bootloader port at 115200...");
3399
+ await this.port.open({ baudRate: ESP_ROM_BAUD });
3400
+ this.connected = true;
3401
+ this.currentBaudRate = ESP_ROM_BAUD;
3402
+ // Start read loop
3403
+ this.readLoop();
3404
+ await sleep(100);
3405
+ // Sync with ROM only - no reset strategies, device is already in bootloader
3406
+ this.logger.debug("Syncing with bootloader ROM...");
3407
+ await this.sync();
3408
+ this.logger.debug("Bootloader sync OK, no stub");
3409
+ // Fire WDT reset → device boots into firmware
3410
+ this.logger.debug("Firing WDT reset...");
3411
+ await this.rtcWdtResetChipSpecific();
3412
+ this.logger.debug("WDT reset fired - device will boot to firmware");
3413
+ }
3225
3414
  /**
3226
3415
  * @name drainInputBuffer
3227
3416
  * Actively drain the input buffer by reading data for a specified time.
@@ -3395,7 +3584,7 @@ export class ESPLoader extends EventTarget {
3395
3584
  }
3396
3585
  catch (err) {
3397
3586
  if (err instanceof SlipReadError) {
3398
- this.logger.debug(`SLIP read error at ${resp.length} bytes: ${err.message}`);
3587
+ this.logger.debug(`${err.message} at byte 0x${resp.length.toString(16)}`);
3399
3588
  // Send empty SLIP frame to abort the stub's read operation
3400
3589
  // The stub expects 4 bytes (ACK), if we send less it will break out
3401
3590
  try {
@@ -3505,18 +3694,8 @@ export class ESPLoader extends EventTarget {
3505
3694
  // Check if it's a timeout error or SLIP error
3506
3695
  if (err instanceof SlipReadError) {
3507
3696
  if (retryCount <= MAX_RETRIES) {
3508
- this.logger.debug(`${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
3509
- try {
3510
- await this.drainInputBuffer(200);
3511
- // Clear application buffer
3512
- await this.flushSerialBuffers();
3513
- // Wait before retry to let hardware settle
3514
- await sleep(SYNC_TIMEOUT);
3515
- // Continue to retry the same chunk (will send NEW read command)
3516
- }
3517
- catch (drainErr) {
3518
- this.logger.debug(`Buffer drain error: ${drainErr}`);
3519
- }
3697
+ this.logger.debug(`Cleared buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
3698
+ // Continue to retry the same chunk (will send NEW read command)
3520
3699
  }
3521
3700
  else {
3522
3701
  // All retries exhausted - attempt recovery by reloading stub